add mobile androida and ios apps

This commit is contained in:
2026-05-18 22:30:45 +02:00
parent 0e40a23828
commit 6146bf18b0
32 changed files with 1594 additions and 0 deletions
@@ -0,0 +1,106 @@
import Combine
import SwiftUI
@MainActor
final class NowPlayingController: ObservableObject {
@Published var currentTrack: MopidyPlaybackState.Track?
@Published var isPlaying = false
@Published var volume = 70
@Published var coverArt: UIImage?
@Published var connectionStatus: String = "Connecting..."
private let settings = AppSettings.shared
private let streamPlayer = StreamPlayer.shared
private var cancellables = Set<AnyCancellable>()
private var refreshTimer: Timer?
init() {
bindStreamPlayer()
startPolling()
fetchVolume()
}
func connect() {
streamPlayer.playStream(host: settings.host, password: settings.password)
}
func reconnect() {
streamPlayer.reconnect(host: settings.host, password: settings.password)
}
func togglePlay() {
if isPlaying {
streamPlayer.pause()
Task { try? await MopidyClient.shared.pause() }
} else {
streamPlayer.resume()
Task { try? await MopidyClient.shared.play() }
}
}
func setVolume(_ newVolume: Int) {
volume = newVolume
Task { [weak self] in
try? await MopidyClient.shared.setVolume(newVolume)
}
}
// MARK: - Private
private func bindStreamPlayer() {
streamPlayer.$connectionStatus
.map { status in
switch status {
case .connected: return "Connected"
case .disconnected: return "Disconnected"
case .error(let msg): return "Error: \(msg)"
}
}
.assign(to: &$connectionStatus)
streamPlayer.$isPlaying
.assign(to: &$isPlaying)
}
private func startPolling() {
refreshTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in
Task { [weak self] in
guard let self else { return }
do {
let state = try await MopidyClient.shared.getPlaybackState()
if let track = state.tlTrack?.track {
self.currentTrack = track
self.fetchCoverArt(track.coverArtURI)
}
self.isPlaying = (state.state == .playing)
} catch {
self.connectionStatus = "Poll error"
}
}
}
}
private func fetchCoverArt(_ uris: [String]?) {
guard let firstURI = uris?.first, let url = URL(string: firstURI) else { return }
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let image = UIImage(data: data) {
self.coverArt = image
}
} catch {
self.coverArt = nil
}
}
}
private func fetchVolume() {
Task {
do {
self.volume = try await MopidyClient.shared.getVolume()
} catch {
print("Volume fetch error: \(error)")
}
}
}
}