94 lines
2.6 KiB
Swift
94 lines
2.6 KiB
Swift
import Domain
|
|
import Foundation
|
|
|
|
public struct QueueItem: Identifiable, Hashable, Codable, Sendable {
|
|
public let id: UUID
|
|
public let track: Track
|
|
|
|
public init(id: UUID = UUID(), track: Track) {
|
|
self.id = id
|
|
self.track = track
|
|
}
|
|
}
|
|
|
|
public enum RepeatMode: String, Codable, Sendable, CaseIterable {
|
|
case off, all, one
|
|
}
|
|
|
|
public enum PlaybackCommand: Codable, Sendable {
|
|
case play
|
|
case pause
|
|
case togglePlayPause
|
|
case seek(to: TimeInterval)
|
|
case next
|
|
case previous
|
|
case setQueue(tracks: [Track], startIndex: Int, shuffled: Bool)
|
|
case insertNext([Track])
|
|
case insertLast([Track])
|
|
case jump(to: QueueItem.ID)
|
|
case move(item: QueueItem.ID, after: QueueItem.ID?)
|
|
case remove(QueueItem.ID)
|
|
case clearUpcoming
|
|
case clearHistory
|
|
case setShuffle(Bool)
|
|
case setRepeat(RepeatMode)
|
|
case setVolume(Double)
|
|
}
|
|
|
|
public struct PlaybackAnchor: Hashable, Sendable {
|
|
public var position: TimeInterval
|
|
public var instant: ContinuousClock.Instant
|
|
public var rate: Double
|
|
|
|
public init(position: TimeInterval, instant: ContinuousClock.Instant, rate: Double) {
|
|
self.position = position
|
|
self.instant = instant
|
|
self.rate = rate
|
|
}
|
|
|
|
public func position(at now: ContinuousClock.Instant) -> TimeInterval {
|
|
let elapsed = now - instant
|
|
let seconds = Double(elapsed.components.seconds) + Double(elapsed.components.attoseconds) / 1e18
|
|
return position + seconds * rate
|
|
}
|
|
}
|
|
|
|
public enum PlaybackStatus: String, Sendable {
|
|
case stopped, paused, playing
|
|
}
|
|
|
|
public struct PlaybackState: Hashable, Sendable {
|
|
public var history: [QueueItem] = []
|
|
public var current: QueueItem?
|
|
public var upcoming: [QueueItem] = []
|
|
public var status: PlaybackStatus = .stopped
|
|
public var anchor = PlaybackAnchor(position: 0, instant: .now, rate: 0)
|
|
public var isShuffled = false
|
|
public var repeatMode: RepeatMode = .off
|
|
public var volume: Double = 1
|
|
|
|
public init() {}
|
|
}
|
|
|
|
public enum OutputEvent: Sendable {
|
|
case finished(QueueItem.ID)
|
|
case failed(QueueItem.ID)
|
|
case anchor(QueueItem.ID, position: TimeInterval, rate: Double)
|
|
}
|
|
|
|
public protocol OutputEngine: Sendable {
|
|
var events: AsyncStream<OutputEvent> { get }
|
|
func load(_ item: QueueItem, at position: TimeInterval) async
|
|
func prepare(_ item: QueueItem?) async
|
|
func play() async
|
|
func pause() async
|
|
func seek(to position: TimeInterval) async
|
|
func stop() async
|
|
func setVolume(_ volume: Double) async
|
|
}
|
|
|
|
public extension OutputEngine {
|
|
func prepare(_: QueueItem?) async {}
|
|
func setVolume(_: Double) async {}
|
|
}
|