Files

81 lines
2.5 KiB
Swift

import AVFoundation
import Foundation
import MediaPlayer
/// An internet radio stream the user saved in Browse > Web Radio.
struct WebStation: Codable, Hashable, Identifiable {
var id = UUID().uuidString
var name: String
var url: String
}
/// Plays a web radio stream through AVPlayer, outside the stem engine. Starting a station pauses
/// the queue, and playing the queue again stops the station. Main-thread only, like PlaybackManager.
@Observable
final class WebRadio: @unchecked Sendable {
private let pm: PlaybackManager
@ObservationIgnored private let player = AVPlayer()
@ObservationIgnored weak var nowPlaying: NowPlaying?
private(set) var station: WebStation?
private(set) var playing = false
/// A station is loaded (playing or paused); the lock screen belongs to the radio.
var active: Bool { station != nil }
init(pm: PlaybackManager) {
self.pm = pm
player.automaticallyWaitsToMinimizeStalling = true
pm.addListener { [weak self] in
guard let self, self.active, pm.playWhenReady else { return }
self.stop()
}
}
func play(_ s: WebStation) {
guard let url = URL(string: s.url) else { return }
station = s
if pm.playWhenReady { pm.pause() }
let a = AVAudioSession.sharedInstance()
try? a.setCategory(.playback, mode: .default, policy: .longFormAudio)
try? a.setActive(true)
player.replaceCurrentItem(with: AVPlayerItem(url: url))
player.play()
playing = true
publish()
}
func resume() {
guard active else { return }
// Live streams go stale while paused; rejoin at the live edge.
if let s = station { play(s) }
}
func pause() {
player.pause()
playing = false
publish()
}
func toggle() { if playing { pause() } else { resume() } }
func stop() {
player.pause()
player.replaceCurrentItem(with: nil)
station = nil
playing = false
nowPlaying?.refresh()
}
private func publish() {
guard let s = station else { return }
let c = MPNowPlayingInfoCenter.default()
c.nowPlayingInfo = [
MPMediaItemPropertyTitle: s.name,
MPMediaItemPropertyArtist: "Web Radio",
MPNowPlayingInfoPropertyIsLiveStream: true,
MPNowPlayingInfoPropertyPlaybackRate: playing ? 1.0 : 0.0,
]
c.playbackState = playing ? .playing : .paused
}
}