|
|
|
@ -11,6 +11,9 @@ struct MusicApp: App { |
|
|
|
@State private var playlistVM: PlaylistViewModel? |
|
|
|
@State private var playlistVM: PlaylistViewModel? |
|
|
|
@State private var showNewPlaylistAlert = false |
|
|
|
@State private var showNewPlaylistAlert = false |
|
|
|
@State private var initError: String? |
|
|
|
@State private var initError: String? |
|
|
|
|
|
|
|
@State private var hostServer: HostServer? |
|
|
|
|
|
|
|
@State private var remoteClient = RemoteClient() |
|
|
|
|
|
|
|
@State private var showConnectionSheet = false |
|
|
|
|
|
|
|
|
|
|
|
var body: some Scene { |
|
|
|
var body: some Scene { |
|
|
|
WindowGroup { |
|
|
|
WindowGroup { |
|
|
|
@ -27,7 +30,8 @@ struct MusicApp: App { |
|
|
|
playlist: playlist, |
|
|
|
playlist: playlist, |
|
|
|
shazam: shazamService, |
|
|
|
shazam: shazamService, |
|
|
|
db: db, |
|
|
|
db: db, |
|
|
|
showNewPlaylistAlert: $showNewPlaylistAlert |
|
|
|
showNewPlaylistAlert: $showNewPlaylistAlert, |
|
|
|
|
|
|
|
networkStatus: computeNetworkStatus() |
|
|
|
) |
|
|
|
) |
|
|
|
} else if let error = initError { |
|
|
|
} else if let error = initError { |
|
|
|
Text("Failed to initialize database: \(error)") |
|
|
|
Text("Failed to initialize database: \(error)") |
|
|
|
@ -38,6 +42,16 @@ struct MusicApp: App { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.frame(minWidth: 800, minHeight: 500) |
|
|
|
.frame(minWidth: 800, minHeight: 500) |
|
|
|
|
|
|
|
.onChange(of: remoteClient.connectionState) { _, newState in |
|
|
|
|
|
|
|
if case .connected = newState { |
|
|
|
|
|
|
|
enterRemoteMode() |
|
|
|
|
|
|
|
} else if newState == .disconnected { |
|
|
|
|
|
|
|
exitRemoteMode() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.sheet(isPresented: $showConnectionSheet) { |
|
|
|
|
|
|
|
ConnectionSheet(remoteClient: remoteClient, isPresented: $showConnectionSheet) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.commands { |
|
|
|
.commands { |
|
|
|
CommandGroup(after: .newItem) { |
|
|
|
CommandGroup(after: .newItem) { |
|
|
|
@ -45,11 +59,27 @@ struct MusicApp: App { |
|
|
|
pickFolder() |
|
|
|
pickFolder() |
|
|
|
} |
|
|
|
} |
|
|
|
.keyboardShortcut("o") |
|
|
|
.keyboardShortcut("o") |
|
|
|
|
|
|
|
.disabled(remoteClient.connectionState.isConnected) |
|
|
|
|
|
|
|
|
|
|
|
Button("New Playlist...") { |
|
|
|
Button("New Playlist...") { |
|
|
|
showNewPlaylistAlert = true |
|
|
|
showNewPlaylistAlert = true |
|
|
|
} |
|
|
|
} |
|
|
|
.keyboardShortcut("n") |
|
|
|
.keyboardShortcut("n") |
|
|
|
|
|
|
|
.disabled(remoteClient.connectionState.isConnected) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Divider() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Toggle("Enable Host Mode", isOn: Binding( |
|
|
|
|
|
|
|
get: { hostServer?.isHosting ?? false }, |
|
|
|
|
|
|
|
set: { $0 ? startHosting() : hostServer?.stop() } |
|
|
|
|
|
|
|
)) |
|
|
|
|
|
|
|
.disabled(remoteClient.connectionState.isConnected) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Button("Connect to Remote...") { |
|
|
|
|
|
|
|
showConnectionSheet = true |
|
|
|
|
|
|
|
remoteClient.startDiscovery() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.disabled(hostServer?.isHosting ?? false) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -144,4 +174,69 @@ struct MusicApp: App { |
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Remote / Host |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func startHosting() { |
|
|
|
|
|
|
|
guard let db = dbService, let player = playerVM else { return } |
|
|
|
|
|
|
|
let appSupport = FileManager.default.urls( |
|
|
|
|
|
|
|
for: .applicationSupportDirectory, in: .userDomainMask |
|
|
|
|
|
|
|
).first!.appendingPathComponent("Music", isDirectory: true) |
|
|
|
|
|
|
|
let dbPath = appSupport.appendingPathComponent("db.sqlite").path |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let server = HostServer(dbPath: dbPath) |
|
|
|
|
|
|
|
server.configure(player: player, db: db) |
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
try server.start() |
|
|
|
|
|
|
|
hostServer = server |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
print("Failed to start host: \(error)") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func enterRemoteMode() { |
|
|
|
|
|
|
|
guard let player = playerVM else { return } |
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
let remoteDb = try DatabaseService(path: RemoteClient.remoteDBPath) |
|
|
|
|
|
|
|
self.libraryVM = LibraryViewModel(db: remoteDb) |
|
|
|
|
|
|
|
self.playlistVM = PlaylistViewModel(db: remoteDb) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
player.enterRemoteMode(client: remoteClient) |
|
|
|
|
|
|
|
player.trackResolver = { trackId in |
|
|
|
|
|
|
|
self.libraryVM?.tracks.first(where: { $0.id == trackId }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
remoteClient.onPlaybackState = { [weak player] state in |
|
|
|
|
|
|
|
player?.applyRemoteState(state) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
print("Failed to load remote DB: \(error)") |
|
|
|
|
|
|
|
remoteClient.disconnect() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func exitRemoteMode() { |
|
|
|
|
|
|
|
playerVM?.exitRemoteMode() |
|
|
|
|
|
|
|
remoteClient.onPlaybackState = nil |
|
|
|
|
|
|
|
guard let db = dbService else { return } |
|
|
|
|
|
|
|
self.libraryVM = LibraryViewModel(db: db) |
|
|
|
|
|
|
|
self.playlistVM = PlaylistViewModel(db: db) |
|
|
|
|
|
|
|
try? FileManager.default.removeItem(atPath: RemoteClient.remoteDBPath) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func computeNetworkStatus() -> NetworkStatus? { |
|
|
|
|
|
|
|
if remoteClient.connectionState.isConnected { |
|
|
|
|
|
|
|
let hostName: String |
|
|
|
|
|
|
|
if case .connected(let name) = remoteClient.connectionState { hostName = name } else { hostName = "Unknown" } |
|
|
|
|
|
|
|
return NetworkStatus( |
|
|
|
|
|
|
|
mode: .remote(hostName: hostName), |
|
|
|
|
|
|
|
onDisconnect: { [remoteClient] in remoteClient.disconnect() }, |
|
|
|
|
|
|
|
onRefreshLibrary: { [remoteClient] in remoteClient.sendCommand(.refreshDB) } |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if let server = hostServer, server.isHosting { |
|
|
|
|
|
|
|
return NetworkStatus(mode: .hosting(connectedRemote: server.connectedRemoteName)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|