2026-04-11 15:37:11 +00:00
|
|
|
//
|
|
|
|
|
// MusicPlugin.swift
|
|
|
|
|
// MioIsland Music Plugin
|
|
|
|
|
//
|
2026-04-18 18:27:21 +00:00
|
|
|
// Principal class for the music-player.bundle plugin (v2.0.0).
|
|
|
|
|
//
|
|
|
|
|
// Wires together the data layer (NowPlayingState + sources/*) and the UI
|
|
|
|
|
// layer (ui/*). Loaded at runtime by the host's NativePluginManager via
|
|
|
|
|
// Info.plist -> NSPrincipalClass = "MusicPlugin.MusicPlugin".
|
|
|
|
|
//
|
|
|
|
|
// v2.0.0 is a complete rewrite of the v1.0.0 shell. The old files
|
|
|
|
|
// (NowPlayingBridge / MusicPlayerView / MusicHeaderButton) have been
|
|
|
|
|
// replaced by a layered design:
|
|
|
|
|
//
|
|
|
|
|
// NowPlayingState -> orchestrator + sticky source routing
|
|
|
|
|
// +-> sources/MediaRemoteSource (dlopen private framework)
|
|
|
|
|
// +-> sources/SpotifyAppleScript
|
|
|
|
|
// +-> sources/AppleMusicAppleScript
|
|
|
|
|
// +-> sources/ChromeWebSource (JS injection into video/audio)
|
|
|
|
|
// +-> support/ChineseAppDetector (QQ / NetEase / Kugou)
|
|
|
|
|
// +-> support/HostVersionCheck (host >= 2.1.7 gate)
|
|
|
|
|
//
|
|
|
|
|
// ui/ExpandedView -> main panel (makeView)
|
|
|
|
|
// ui/HeaderSlotView -> 20x20 header icon + pseudo-spectrum
|
|
|
|
|
// ui/AlbumArtColorExtractor + ui/SeekBar
|
|
|
|
|
// support/Localization (zh/en)
|
2026-04-11 15:37:11 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import AppKit
|
|
|
|
|
import SwiftUI
|
|
|
|
|
|
2026-04-18 18:27:21 +00:00
|
|
|
/// Principal class. Module is `MusicPlugin`, class is `MusicPlugin`, so
|
|
|
|
|
/// Info.plist NSPrincipalClass = "MusicPlugin.MusicPlugin".
|
2026-04-11 15:37:11 +00:00
|
|
|
final class MusicPlugin: NSObject, MioPlugin {
|
|
|
|
|
var id: String { "music-player" }
|
|
|
|
|
var name: String { "Music Player" }
|
|
|
|
|
var icon: String { "music.note" }
|
2026-04-19 03:09:29 +00:00
|
|
|
var version: String { "2.0.1" }
|
2026-04-11 15:37:11 +00:00
|
|
|
|
|
|
|
|
func activate() {
|
2026-04-18 18:27:21 +00:00
|
|
|
NSLog("[mio-plugin-music] activate")
|
2026-04-11 15:37:11 +00:00
|
|
|
Task { @MainActor in
|
2026-04-18 18:27:21 +00:00
|
|
|
NowPlayingState.shared.start()
|
2026-04-11 15:37:11 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func deactivate() {
|
2026-04-18 18:27:21 +00:00
|
|
|
NSLog("[mio-plugin-music] deactivate")
|
2026-04-11 15:37:11 +00:00
|
|
|
Task { @MainActor in
|
2026-04-18 18:27:21 +00:00
|
|
|
NowPlayingState.shared.stop()
|
2026-04-11 15:37:11 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func makeView() -> NSView {
|
2026-04-18 18:27:21 +00:00
|
|
|
let view = NSHostingView(rootView: ExpandedView())
|
|
|
|
|
view.autoresizingMask = [.width, .height]
|
|
|
|
|
return view
|
2026-04-11 15:37:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 18:27:21 +00:00
|
|
|
@objc func viewForSlot(_ slot: String, context: [String: Any]) -> NSView? {
|
2026-04-11 15:37:11 +00:00
|
|
|
switch slot {
|
|
|
|
|
case "header":
|
2026-04-18 18:27:21 +00:00
|
|
|
let view = NSHostingView(rootView: HeaderSlotView())
|
2026-04-11 15:37:11 +00:00
|
|
|
view.frame = NSRect(x: 0, y: 0, width: 20, height: 20)
|
|
|
|
|
view.setFrameSize(NSSize(width: 20, height: 20))
|
|
|
|
|
return view
|
|
|
|
|
default:
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|