feat(mac,ios,playback,store): initial macos implementation

This commit is contained in:
hh committed 2026-09-23 04:56:48 +02:00
1 parent e5bb559d7f
commit d2d7166cf0
72 files changed
+4452 -318

No files matched your search

@@ -6,8 +6,10 @@ import Testing
actor RecordingEngine: OutputEngine {
nonisolated let events: AsyncStream<OutputEvent>
nonisolated let continuation: AsyncStream<OutputEvent>.Continuation
private(set) var volumes: [Double] = []
private(set) var loaded: [QueueItem.ID] = []
private(set) var prepared: [QueueItem.ID?] = []
private(set) var isPlaying = false
init() {
(events, continuation) = AsyncStream<OutputEvent>.makeStream()
@@ -21,8 +23,18 @@ actor RecordingEngine: OutputEngine {
prepared.append(item?.id)
}
func play() {}
func pause() {}
func setVolume(_ volume: Double) {
volumes.append(volume)
}
func play() {
isPlaying = true
}
func pause() {
isPlaying = false
}
func seek(to _: TimeInterval) {}
func stop() {}
}
@@ -183,3 +195,37 @@ private func tracks(_ count: Int) -> [Track] {
#expect(state.status == .paused && state.anchor.rate == 0)
#expect(await engine.loaded.count == 2)
}
@Test func volumeReachesEngineAndSurvivesQueueChanges() async {
let engine = RecordingEngine()
let session = PlaybackSession(engine: engine)
await session.send(.setVolume(0.35))
await session.send(.setQueue(tracks: tracks(2), startIndex: 0, shuffled: false))
await session.send(.next)
#expect(await session.snapshot.volume == 0.35)
#expect(await engine.volumes == [0.35])
await session.send(.setVolume(-1))
await session.send(.setVolume(2))
await session.send(.setVolume(.nan))
#expect(await engine.volumes == [0.35, 0, 1])
#expect(await session.snapshot.volume == 1)
}
@Test func skippingPastTheEndStopsTheEngine() async {
let engine = RecordingEngine()
let session = PlaybackSession(engine: engine)
await session.send(.setQueue(tracks: tracks(1), startIndex: 0, shuffled: false))
#expect(await engine.isPlaying)
await session.send(.next)
#expect(await session.snapshot.status == .paused)
#expect(await !engine.isPlaying)
}
@Test func clearingHistoryKeepsCurrentAndUpcoming() async {
let session = PlaybackSession(engine: RecordingEngine())
await session.send(.setQueue(tracks: tracks(4), startIndex: 2, shuffled: false))
await session.send(.clearHistory)
let state = await session.snapshot
#expect(state.history.isEmpty)
#expect(state.current?.track.title == "Track 2" && state.upcoming.count == 1)
}
@@ -0,0 +1,34 @@
import Domain
import Foundation
@testable import Playback
import Testing
struct SampleRateMatcherTests {
private let device = OutputDeviceInfo(id: 1, name: "Fake", nominalRate: 48000, availableRates: [44100, 48000, 96000])
private let flac = AudioFormat(codec: .flac, sampleRate: 44100, bitDepth: 16)
@Test func settingOffLeavesDeviceAlone() {
#expect(SampleRateMatcher(isEnabled: false).targetRate(for: flac, on: device) == nil)
}
@Test func sameRateNeedsNoSwitch() {
let format = AudioFormat(codec: .flac, sampleRate: 48000)
#expect(SampleRateMatcher(isEnabled: true).targetRate(for: format, on: device) == nil)
}
@Test func unavailableRateIsIgnored() {
let format = AudioFormat(codec: .flac, sampleRate: 192_000)
#expect(SampleRateMatcher(isEnabled: true).targetRate(for: format, on: device) == nil)
}
@Test func unknownRateOrDeviceIsIgnored() {
let matcher = SampleRateMatcher(isEnabled: true)
#expect(matcher.targetRate(for: AudioFormat(codec: .mp3), on: device) == nil)
#expect(matcher.targetRate(for: nil, on: device) == nil)
#expect(matcher.targetRate(for: flac, on: nil) == nil)
}
@Test func availableRateIsChosenExactly() {
#expect(SampleRateMatcher(isEnabled: true).targetRate(for: flac, on: device) == 44100)
}
}