react-native-queue-player 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/cpp/CMakeLists.txt +11 -0
- package/android/src/main/cpp/airplay_jni.cpp +5 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/BufferStateTranslator.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CarConnectionState.kt +46 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +1 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +2 -4
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +7 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/MilestoneTracker.kt +1 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +8 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/ProgressEmissionGate.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/RemoteCommandDeduper.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/SleepTimerCore.kt +29 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +51 -27
- package/android/src/main/java/com/margelo/nitro/queueplayer/TransportStateTranslator.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/AudioPipelineRenderersFactoryTest.kt +2 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/BrowseCallbackRegistryTest.kt +1 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineReplayGainTest.kt +3 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +2 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +0 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +3 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/HeadlessJsMediaServiceTest.kt +2 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +2 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/MilestoneTrackerTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchCorrectionTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +76 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/ProgressEmissionGateTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/SleepTimerCoreTest.kt +41 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +2 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPitchCorrectionTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPlaybackModeTest.kt +2 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReplayGainTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +2 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +1 -2
- package/ios/AVPlayerItemQueueItemId.swift +2 -2
- package/ios/AVQueueBuilder.swift +12 -14
- package/ios/AudioCategoryMapping.swift +1 -2
- package/ios/CarPlayCoordinator.swift +28 -0
- package/ios/CarPlaySceneDelegate.swift +2 -0
- package/ios/CrossfadeEngine.swift +4 -5
- package/ios/InputGuards.swift +3 -3
- package/ios/LookaheadCache.swift +4 -6
- package/ios/LookaheadCachePrefetcher.swift +1 -1
- package/ios/MilestoneTracker.swift +1 -2
- package/ios/PlaybackErrorMapping.swift +6 -7
- package/ios/PlayerStateDerivation.swift +1 -2
- package/ios/ProgressEmissionGate.swift +1 -1
- package/ios/QueueMutationArithmetic.swift +2 -2
- package/ios/QueueSkipArithmetic.swift +8 -12
- package/ios/Siri/PlayMediaIntentHandler.swift +1 -5
- package/ios/SleepTimerCore.swift +26 -6
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +5 -9
- package/ios/Tests/CarPlayBridgeTests.swift +2 -6
- package/ios/Tests/CarPlayBrowseBuilderTests.swift +1 -2
- package/ios/Tests/CarPlayCoordinatorTests.swift +35 -0
- package/ios/Tests/EQProcessorTests.swift +1 -4
- package/ios/Tests/EQTapTests.swift +1 -2
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +3 -4
- package/ios/Tests/EqualizerHybridTests.swift +1 -3
- package/ios/Tests/FFTProcessorTests.swift +9 -8
- package/ios/Tests/GaplessEngineLifecycleTests.swift +0 -2
- package/ios/Tests/LookaheadCacheEvictionTests.swift +1 -4
- package/ios/Tests/MetadataReaderTests.swift +0 -2
- package/ios/Tests/MilestoneTrackerTests.swift +1 -3
- package/ios/Tests/NowPlayingFormatExtractorTests.swift +3 -4
- package/ios/Tests/PlaybackErrorMappingTests.swift +0 -3
- package/ios/Tests/PlaybackModeStateMachineTests.swift +5 -9
- package/ios/Tests/PlayerStateDerivationTests.swift +1 -2
- package/ios/Tests/ProgressEmissionGateTests.swift +1 -3
- package/ios/Tests/QueueMutationArithmeticTests.swift +0 -3
- package/ios/Tests/RemoteCommandsTests.swift +2 -5
- package/ios/Tests/ReplayGainExtractorTests.swift +0 -5
- package/ios/Tests/SkipCapabilityTests.swift +4 -7
- package/ios/Tests/SleepTimerCoreTests.swift +39 -0
- package/ios/Tests/VoiceDonationTests.swift +4 -4
- package/ios/TrackPlayer.swift +47 -22
- package/lib/typescript/TrackPlayer.nitro.d.ts +17 -0
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +5 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +4 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +12 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +1 -0
- package/package.json +1 -1
- package/src/TrackPlayer.nitro.ts +18 -0
- package/ios/InterruptionEventMapping.swift +0 -51
- package/ios/Tests/InterruptionEventMappingTests.swift +0 -91
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// Throttles the JS `onProgress` fan-out to a configurable cadence, with an
|
|
2
2
|
/// optional reduced rate while the app is backgrounded. Pure + deterministic
|
|
3
|
-
/// (the caller supplies a monotonic timestamp)
|
|
3
|
+
/// (the caller supplies a monotonic timestamp).
|
|
4
4
|
///
|
|
5
5
|
/// This gates ONLY the JS emission — the native 2 Hz sampler keeps running, so
|
|
6
6
|
/// milestone detection and buffer-state recomputation stay full-rate. A tick
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
/// Pure-Swift index-shift arithmetic for queue mutations. Extracted
|
|
4
|
-
/// from `TrackPlayer.swift` so the shift semantics
|
|
4
|
+
/// from `TrackPlayer.swift` so the shift semantics stand alone,
|
|
5
5
|
/// without an AVQueuePlayer. Mirrors
|
|
6
6
|
/// `android/.../QueueMutationArithmetic.kt` 1:1 — same function
|
|
7
|
-
/// shapes, same input/output semantics
|
|
7
|
+
/// shapes, same input/output semantics.
|
|
8
8
|
///
|
|
9
9
|
/// The helper only computes the new `currentTrackIndex` and (for
|
|
10
10
|
/// `removeFromQueue`) which original indices apply after dedup /
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
/// Pure-Swift skip-index arithmetic. Extracted from `TrackPlayer.swift`
|
|
2
|
-
/// so the logic
|
|
3
|
-
///
|
|
4
|
-
///
|
|
5
|
-
/// `-cxx-interoperability-mode=default` settings
|
|
6
|
-
///
|
|
7
|
-
/// types. By taking primitive arguments, these helpers stay reachable
|
|
8
|
-
/// from the XCTest bundle via plain `import QueuePlayer`.
|
|
2
|
+
/// so the logic stays clear of the Swift ↔ C++ interop boundary —
|
|
3
|
+
/// Nitrogen-generated types (`TrackItem`, `RepeatMode`) don't export
|
|
4
|
+
/// their members to importing modules under Swift's current
|
|
5
|
+
/// `-cxx-interoperability-mode=default` settings. These helpers take
|
|
6
|
+
/// primitive arguments instead.
|
|
9
7
|
///
|
|
10
8
|
/// `TrackPlayer.computeNextIndex` / `.computePreviousIndex` instance
|
|
11
9
|
/// methods are thin adapters that map the Nitrogen `RepeatMode` enum
|
|
12
10
|
/// onto `QueueSkipRepeatMode` and call through here.
|
|
13
11
|
|
|
14
12
|
/// Repeat-mode enum expressed in plain Swift. Value order (0/1/2)
|
|
15
|
-
/// matches `RepeatModeKind` in the nitro.ts spec
|
|
16
|
-
/// asserts the raw integer is robust against accidental reordering
|
|
17
|
-
/// in the Nitrogen spec.
|
|
13
|
+
/// matches `RepeatModeKind` in the nitro.ts spec.
|
|
18
14
|
///
|
|
19
|
-
/// Gated behind `@_spi(QueuePlayerTests)` so the
|
|
20
|
-
///
|
|
15
|
+
/// Gated behind `@_spi(QueuePlayerTests)` so the helper surface stays
|
|
16
|
+
/// off the library's consumer-facing API.
|
|
21
17
|
@_spi(QueuePlayerTests)
|
|
22
18
|
public enum QueueSkipRepeatMode: Int {
|
|
23
19
|
case off = 0
|
|
@@ -189,11 +189,7 @@ internal final class PlayMediaIntentHandler: NSObject, INPlayMediaIntentHandling
|
|
|
189
189
|
/// Map `INPlayMediaIntent.mediaSearch` into the lib's
|
|
190
190
|
/// `MediaSearchRequest` shape so the consumer's JS resolver sees
|
|
191
191
|
/// the same wire format regardless of voice surface (SiriKit on
|
|
192
|
-
/// iOS, Google Assistant on Android).
|
|
193
|
-
/// Maestro integration layer (`e2e/manual/siri-cold-start.md`
|
|
194
|
-
/// S1–S5) — the return type's cxx-interop bridging blocks
|
|
195
|
-
/// `@testable import` from constructing or comparing
|
|
196
|
-
/// `MediaSearchRequest` values in XCTest.
|
|
192
|
+
/// iOS, Google Assistant on Android).
|
|
197
193
|
static func buildRequest(from intent: INPlayMediaIntent) -> MediaSearchRequest {
|
|
198
194
|
let mediaSearch = intent.mediaSearch
|
|
199
195
|
let mediaName = mediaSearch?.mediaName ?? ""
|
package/ios/SleepTimerCore.swift
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
/// Pure state machine for the sleep timer — no player, volume, or timer
|
|
4
|
-
/// dependencies, so it
|
|
4
|
+
/// dependencies, so it stands alone. The owning `TrackPlayer`
|
|
5
5
|
/// drives it from a repeating native timer and applies the returned decision
|
|
6
6
|
/// (volume fade, pause) to the engine.
|
|
7
7
|
///
|
|
@@ -36,6 +36,11 @@ struct SleepTimerCore {
|
|
|
36
36
|
// Guards the tail rule to a single track so a stalled/looping position can't
|
|
37
37
|
// defer the pause forever.
|
|
38
38
|
private var tailExtended = false
|
|
39
|
+
// True while an end-of-track timer is pending, and survives the conversion to
|
|
40
|
+
// a `.duration` fade so the real track-end signal (`fireAtTrackEnd`) knows to
|
|
41
|
+
// fire — the poll can't hit the boundary exactly and never fires at all when
|
|
42
|
+
// the duration is unknown (live/streaming).
|
|
43
|
+
private var originEndOfTrack = false
|
|
39
44
|
|
|
40
45
|
let fadeSeconds: Double
|
|
41
46
|
let tailGraceSeconds: Double
|
|
@@ -52,18 +57,31 @@ struct SleepTimerCore {
|
|
|
52
57
|
mode = .duration
|
|
53
58
|
deadlineEpochMs = nowMs + Int64(seconds * 1000)
|
|
54
59
|
tailExtended = false
|
|
60
|
+
originEndOfTrack = false
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
mutating func armEndOfTrack() {
|
|
58
64
|
mode = .endOfTrack
|
|
59
65
|
deadlineEpochMs = nil
|
|
60
66
|
tailExtended = false
|
|
67
|
+
originEndOfTrack = true
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
mutating func clear() {
|
|
64
71
|
mode = .inactive
|
|
65
72
|
deadlineEpochMs = nil
|
|
66
73
|
tailExtended = false
|
|
74
|
+
originEndOfTrack = false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Fire the pending end-of-track timer at the current track's real end. The
|
|
78
|
+
/// owning player calls this from its track-end signal so the pause lands
|
|
79
|
+
/// exactly at the boundary (and fires even when the track duration was never
|
|
80
|
+
/// known). No-op unless an end-of-track timer is pending.
|
|
81
|
+
mutating func fireAtTrackEnd() -> Tick {
|
|
82
|
+
guard originEndOfTrack else { return .idle }
|
|
83
|
+
clear()
|
|
84
|
+
return Tick(fadeFraction: 1.0, pauseNow: true, stateChanged: true)
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
/// Whole seconds until the fixed deadline, or `nil` when inactive / awaiting
|
|
@@ -83,9 +101,13 @@ struct SleepTimerCore {
|
|
|
83
101
|
// then let the duration branch ride it out + fire.
|
|
84
102
|
if let tail = trackRemaining(trackDurationSec, trackPositionSec), tail <= tailGraceSeconds {
|
|
85
103
|
mode = .duration
|
|
86
|
-
|
|
104
|
+
// Deadline lands AT the track boundary (not 1s past it) so the fade
|
|
105
|
+
// completes at the end; on local playback the real track-end signal
|
|
106
|
+
// (fireAtTrackEnd) owns the actual pause, and this deadline is the
|
|
107
|
+
// fallback the cast path rides out on the receiver's clock.
|
|
108
|
+
deadlineEpochMs = nowMs + Int64(tail * 1000)
|
|
87
109
|
tailExtended = true
|
|
88
|
-
return Tick(fadeFraction: fadeFraction(tail
|
|
110
|
+
return Tick(fadeFraction: fadeFraction(tail), pauseNow: false, stateChanged: true)
|
|
89
111
|
}
|
|
90
112
|
return .idle
|
|
91
113
|
|
|
@@ -106,9 +128,7 @@ struct SleepTimerCore {
|
|
|
106
128
|
return Tick(fadeFraction: fadeFraction(tail + 1), pauseNow: false, stateChanged: true)
|
|
107
129
|
}
|
|
108
130
|
// Fire.
|
|
109
|
-
|
|
110
|
-
deadlineEpochMs = nil
|
|
111
|
-
tailExtended = false
|
|
131
|
+
clear()
|
|
112
132
|
return Tick(fadeFraction: 1.0, pauseNow: true, stateChanged: true)
|
|
113
133
|
}
|
|
114
134
|
}
|
|
@@ -3,19 +3,15 @@ import XCTest
|
|
|
3
3
|
@testable import QueuePlayer
|
|
4
4
|
|
|
5
5
|
/// Unit tests for the ReplayGain mode + gain-math paths in
|
|
6
|
-
/// `AudioTapProvider`.
|
|
7
|
-
/// recompute walk are covered at the integration layer (auto-suite
|
|
8
|
-
/// `replaygain.ts` step). These tests drive the pure
|
|
6
|
+
/// `AudioTapProvider`. These tests drive the pure
|
|
9
7
|
/// `computeLinearGain(data:mode:)` static helper plus the mode
|
|
10
8
|
/// getter/setter round-trip.
|
|
11
9
|
final class AudioTapProviderReplayGainTests: XCTestCase {
|
|
12
10
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// it (NOTES.md §6). The pure gain-math matrix below is the unit
|
|
18
|
-
// surface; mode propagation lands in the integration tests.
|
|
11
|
+
// `getReplayGainMode`'s `ReplayGainMode` return type touches the
|
|
12
|
+
// Swift↔C++ interop graph, so direct equality from `@testable`
|
|
13
|
+
// XCTest can't observe it (NOTES.md §6). The pure gain-math matrix
|
|
14
|
+
// below is the unit surface.
|
|
19
15
|
|
|
20
16
|
// MARK: - computeLinearGain — strict mode + selection
|
|
21
17
|
|
|
@@ -2,9 +2,7 @@ import XCTest
|
|
|
2
2
|
@testable import QueuePlayer
|
|
3
3
|
|
|
4
4
|
/// `CarPlayBridge` tests cover only the registration/dispose/clear
|
|
5
|
-
/// state machine
|
|
6
|
-
/// production scene delegate + browse builder via integration paths;
|
|
7
|
-
/// the unit suite stays focused on the slot semantics. The
|
|
5
|
+
/// state machine — the slot semantics. The
|
|
8
6
|
/// Android-side `BrowseCallbackRegistry` exposes suspending
|
|
9
7
|
/// `await*Callback()` helpers because its dispatch sites coroutine-
|
|
10
8
|
/// poll for cold-start race recovery; iOS uses NotificationCenter
|
|
@@ -18,9 +16,7 @@ final class CarPlayBridgeTests: XCTestCase {
|
|
|
18
16
|
// The browse / playFromId slot reads return a Promise of a
|
|
19
17
|
// Nitrogen-bridged type, which Swift cxx-interop excludes from
|
|
20
18
|
// `@testable import`. Their slots use the identical `Slot<>`
|
|
21
|
-
// machinery asserted here for connect/disconnect
|
|
22
|
-
// dispatch is covered at the integration layer (scene delegate +
|
|
23
|
-
// browse builder).
|
|
19
|
+
// machinery asserted here for connect/disconnect.
|
|
24
20
|
XCTAssertNil(bridge.carConnectCallback())
|
|
25
21
|
XCTAssertNil(bridge.carDisconnectCallback())
|
|
26
22
|
}
|
|
@@ -5,8 +5,7 @@ import XCTest
|
|
|
5
5
|
/// Coverage for [CarPlayBrowseBuilder] — snapshot → template
|
|
6
6
|
/// conversion, item count + accessory mapping, pagination against
|
|
7
7
|
/// `CPListTemplate.maximumItemCount`. Direct method calls on the
|
|
8
|
-
/// builder; the scene delegate's full lifecycle is
|
|
9
|
-
/// CarPlay Sim manual playbook.
|
|
8
|
+
/// builder; the scene delegate's full lifecycle is out of scope here.
|
|
10
9
|
final class CarPlayBrowseBuilderTests: XCTestCase {
|
|
11
10
|
|
|
12
11
|
private func item(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import QueuePlayer
|
|
3
|
+
|
|
4
|
+
/// `CarPlayCoordinator` connection-flag tests. The flag is written by the
|
|
5
|
+
/// CarPlay scene delegate at scene attach / detach; this suite locks the
|
|
6
|
+
/// get/set contract the `isCarConnected()` getter reads back.
|
|
7
|
+
final class CarPlayCoordinatorTests: XCTestCase {
|
|
8
|
+
|
|
9
|
+
override func setUp() {
|
|
10
|
+
super.setUp()
|
|
11
|
+
// Shared process-wide singleton — start from a known-disconnected state
|
|
12
|
+
// regardless of test order.
|
|
13
|
+
CarPlayCoordinator.shared.setCarConnected(false)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override func tearDown() {
|
|
17
|
+
// Shared process-wide singleton — leave it disconnected so it can't
|
|
18
|
+
// leak a connected state into another suite.
|
|
19
|
+
CarPlayCoordinator.shared.setCarConnected(false)
|
|
20
|
+
super.tearDown()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func testCarConnectedFlagReflectsSetter() {
|
|
24
|
+
let coordinator = CarPlayCoordinator.shared
|
|
25
|
+
|
|
26
|
+
coordinator.setCarConnected(false)
|
|
27
|
+
XCTAssertFalse(coordinator.isCarConnected, "disconnected after setCarConnected(false)")
|
|
28
|
+
|
|
29
|
+
coordinator.setCarConnected(true)
|
|
30
|
+
XCTAssertTrue(coordinator.isCarConnected, "connected after setCarConnected(true)")
|
|
31
|
+
|
|
32
|
+
coordinator.setCarConnected(false)
|
|
33
|
+
XCTAssertFalse(coordinator.isCarConnected, "disconnected again after setCarConnected(false)")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import XCTest
|
|
2
2
|
@testable import QueuePlayer
|
|
3
3
|
|
|
4
|
-
/// Unit coverage for `EQProcessor` cascade.
|
|
5
|
-
/// (sine-sweep magnitude per band) is verified at the integration
|
|
6
|
-
/// layer via the demo Maestro EQ suite once the AVPlayerItem mix
|
|
7
|
-
/// attach path is wired.
|
|
4
|
+
/// Unit coverage for `EQProcessor` cascade.
|
|
8
5
|
final class EQProcessorTests: XCTestCase {
|
|
9
6
|
|
|
10
7
|
func testIsNotReadyBeforeChannelCountSet() {
|
|
@@ -4,8 +4,7 @@ import XCTest
|
|
|
4
4
|
|
|
5
5
|
/// Skeleton coverage for `EQTap` — tap creation, HLS detection,
|
|
6
6
|
/// pass-through process body. The actual tap firing is on the
|
|
7
|
-
/// audio render thread
|
|
8
|
-
/// the demo Maestro EQ suite once the DSP body lands.
|
|
7
|
+
/// audio render thread.
|
|
9
8
|
final class EQTapTests: XCTestCase {
|
|
10
9
|
|
|
11
10
|
func testPassThroughCopiesInputToOutput() {
|
|
@@ -2,10 +2,9 @@ import AVFoundation
|
|
|
2
2
|
import XCTest
|
|
3
3
|
@testable import QueuePlayer
|
|
4
4
|
|
|
5
|
-
/// Provider-layer coverage for the shared `AudioTapProvider`.
|
|
6
|
-
///
|
|
7
|
-
///
|
|
8
|
-
/// configuration + bypass + consumer-registration surface.
|
|
5
|
+
/// Provider-layer coverage for the shared `AudioTapProvider`. These
|
|
6
|
+
/// tests cover the configuration + bypass + consumer-registration
|
|
7
|
+
/// surface.
|
|
9
8
|
///
|
|
10
9
|
/// Filename note: kept at `EqualizerAudioMixProviderTests.swift`
|
|
11
10
|
/// even though the class is `AudioTapProviderTests`. Renaming the
|
|
@@ -3,9 +3,7 @@ import XCTest
|
|
|
3
3
|
@testable import QueuePlayer
|
|
4
4
|
|
|
5
5
|
/// Wiring coverage for the iOS `Equalizer` HybridObject.
|
|
6
|
-
/// Targets the synchronous internal core
|
|
7
|
-
/// returning surface is exercised at integration via the demo
|
|
8
|
-
/// Maestro EQ suite.
|
|
6
|
+
/// Targets the synchronous internal core.
|
|
9
7
|
final class EqualizerHybridTests: XCTestCase {
|
|
10
8
|
|
|
11
9
|
private var defaults: UserDefaults!
|
|
@@ -39,9 +39,15 @@ final class FFTProcessorTests: XCTestCase {
|
|
|
39
39
|
|
|
40
40
|
func testDoesNotEmitUntilFftSizeSamplesAccumulate() throws {
|
|
41
41
|
var calls = 0
|
|
42
|
+
// Fulfilled by the emit itself so the wait tracks the actual main-queue
|
|
43
|
+
// callback rather than a fixed delay that can lose the race under load.
|
|
44
|
+
let emitted = expectation(description: "emit landed")
|
|
42
45
|
let proc = try FFTProcessor(
|
|
43
46
|
fftSize: 512, intervalMs: 1, includeSamples: false,
|
|
44
|
-
) { _, _, _ in
|
|
47
|
+
) { _, _, _ in
|
|
48
|
+
calls += 1
|
|
49
|
+
emitted.fulfill()
|
|
50
|
+
}
|
|
45
51
|
let half = [Float](repeating: 0, count: 256)
|
|
46
52
|
half.withUnsafeBufferPointer { p in
|
|
47
53
|
proc.ingest(samples: p.baseAddress!, frameCount: 256, sampleRate: 44100)
|
|
@@ -51,10 +57,7 @@ final class FFTProcessorTests: XCTestCase {
|
|
|
51
57
|
rest.withUnsafeBufferPointer { p in
|
|
52
58
|
proc.ingest(samples: p.baseAddress!, frameCount: 256, sampleRate: 44100)
|
|
53
59
|
}
|
|
54
|
-
|
|
55
|
-
let exp = expectation(description: "emit landed")
|
|
56
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { exp.fulfill() }
|
|
57
|
-
wait(for: [exp], timeout: 1.0)
|
|
60
|
+
wait(for: [emitted], timeout: 1.0)
|
|
58
61
|
XCTAssertEqual(calls, 1)
|
|
59
62
|
}
|
|
60
63
|
|
|
@@ -240,9 +243,7 @@ final class FFTProcessorTests: XCTestCase {
|
|
|
240
243
|
// `ingest` resumes, `onFrame` fires again at the documented
|
|
241
244
|
// intervalMs cadence. Live audio chains express "pause" by
|
|
242
245
|
// ceasing tap callbacks; the unit-test analogue is a sleep
|
|
243
|
-
// window.
|
|
244
|
-
// `visualizer:*` integration suite's `pause-stops-resume-restarts`
|
|
245
|
-
// step.
|
|
246
|
+
// window.
|
|
246
247
|
let n = 256
|
|
247
248
|
let intervalMs = 1
|
|
248
249
|
let lock = NSLock()
|
|
@@ -4,8 +4,6 @@ import XCTest
|
|
|
4
4
|
|
|
5
5
|
/// Lifecycle smoke coverage for `GaplessEngine` — empty-queue
|
|
6
6
|
/// sentinels + idempotent / delegate-clearing release contract.
|
|
7
|
-
/// Behavioural coverage of transport + seek runs through the
|
|
8
|
-
/// existing TrackPlayer integration suites + Maestro flows.
|
|
9
7
|
final class GaplessEngineLifecycleTests: XCTestCase {
|
|
10
8
|
|
|
11
9
|
func testEmptyQueueReturnsDocumentedSentinelState() {
|
|
@@ -4,10 +4,7 @@ import XCTest
|
|
|
4
4
|
|
|
5
5
|
/// `LookaheadCacheEviction.computeEvictions` unit tests.
|
|
6
6
|
///
|
|
7
|
-
/// Pure-logic LRU policy.
|
|
8
|
-
/// URLSession. File + index mutation is tested through the
|
|
9
|
-
/// LookaheadCacheTests integration path that drives `download` past
|
|
10
|
-
/// the budget.
|
|
7
|
+
/// Pure-logic LRU policy. No FS access, no URLSession.
|
|
11
8
|
final class LookaheadCacheEvictionTests: XCTestCase {
|
|
12
9
|
|
|
13
10
|
func testNoEvictionWhenUnderBudget() {
|
|
@@ -4,8 +4,6 @@ import XCTest
|
|
|
4
4
|
/// Byte-level unit tests for `MetadataReader`'s container parsers.
|
|
5
5
|
/// Builds synthetic ID3v2 / FLAC / MP4 byte sequences in-memory and
|
|
6
6
|
/// verifies tag extraction surfaces the expected `[key: value]` map.
|
|
7
|
-
/// End-to-end "extract from a real media file" coverage lives in
|
|
8
|
-
/// the auto-suite `replaygain.ts`.
|
|
9
7
|
final class MetadataReaderTests: XCTestCase {
|
|
10
8
|
|
|
11
9
|
// MARK: - ID3v2 TXXX
|
|
@@ -2,9 +2,7 @@ import XCTest
|
|
|
2
2
|
@testable import QueuePlayer
|
|
3
3
|
|
|
4
4
|
/// Unit tests for `MilestoneTracker` — the pure 25/50/75/90% crossing
|
|
5
|
-
/// logic behind `onPlaybackMilestone`.
|
|
6
|
-
/// seek, track-change reset) is covered by the `playback-milestones`
|
|
7
|
-
/// demo suite.
|
|
5
|
+
/// logic behind `onPlaybackMilestone`.
|
|
8
6
|
final class MilestoneTrackerTests: XCTestCase {
|
|
9
7
|
|
|
10
8
|
/// 200s track, ticks every ~10s of media time.
|
|
@@ -3,10 +3,9 @@ import CoreMedia
|
|
|
3
3
|
import XCTest
|
|
4
4
|
@testable import QueuePlayer
|
|
5
5
|
|
|
6
|
-
/// Pure-mapping coverage for `NowPlayingFormatExtractor`.
|
|
7
|
-
///
|
|
8
|
-
///
|
|
9
|
-
/// future contributor can't drift the JS-visible enum without a test
|
|
6
|
+
/// Pure-mapping coverage for `NowPlayingFormatExtractor`. These
|
|
7
|
+
/// tests pin the codec / container / MIME tables so a future
|
|
8
|
+
/// contributor can't drift the JS-visible enum without a test
|
|
10
9
|
/// failure.
|
|
11
10
|
final class NowPlayingFormatExtractorTests: XCTestCase {
|
|
12
11
|
|
|
@@ -5,9 +5,6 @@ import AVFoundation
|
|
|
5
5
|
|
|
6
6
|
/// Unit tests for the iOS half of the cross-platform error-code
|
|
7
7
|
/// mapping in `PlaybackErrorMapping.swift`.
|
|
8
|
-
/// Mirrors the Android-side coverage in
|
|
9
|
-
/// `TrackPlayerEventsTest.kt#error mapping classifies Media3 codes`
|
|
10
|
-
/// + `error mapping strips query strings`.
|
|
11
8
|
///
|
|
12
9
|
/// **Why string-keyed assertions?** NOTES.md §6 — Swift's
|
|
13
10
|
/// `-cxx-interoperability-mode=default` hides members of functions
|
|
@@ -3,15 +3,11 @@ import XCTest
|
|
|
3
3
|
|
|
4
4
|
/// Coverage for the pure-Swift `CrossfadeDurationBounds` helper that
|
|
5
5
|
/// powers `PlaybackModeStateMachine.requireValid`. Full state-machine
|
|
6
|
-
/// validation
|
|
7
|
-
/// `setRequested`)
|
|
8
|
-
///
|
|
9
|
-
///
|
|
10
|
-
///
|
|
11
|
-
/// `crossfade-duration-out-of-range-rejected` and
|
|
12
|
-
/// `crossfade-duration-non-quantised-rejected` steps in
|
|
13
|
-
/// `RNQP-Demo/src/tests/suites/playback-mode.ts`). Mirrors the
|
|
14
|
-
/// cxx-interop carve-out already documented in AGENTS.md §2.
|
|
6
|
+
/// validation (constructing `PlaybackMode` instances + driving
|
|
7
|
+
/// `setRequested`) is out of scope here — `PlaybackMode` is bridged
|
|
8
|
+
/// via Nitro `@_spi` and can't be constructed from a Swift XCTest
|
|
9
|
+
/// target. Mirrors the cxx-interop carve-out already documented in
|
|
10
|
+
/// AGENTS.md §2.
|
|
15
11
|
final class PlaybackModeStateMachineTests: XCTestCase {
|
|
16
12
|
|
|
17
13
|
func testCrossfadeBoundsHelperAcceptsEveryValueOnTheStep() {
|
|
@@ -3,8 +3,7 @@ import XCTest
|
|
|
3
3
|
@testable import QueuePlayer
|
|
4
4
|
|
|
5
5
|
/// Unit tests for `PlayerStateDerivation` — the pure `PlayerState` mapping
|
|
6
|
-
/// shared by both engines.
|
|
7
|
-
/// end-of-queue latch) is covered by the queue-end / crossfade demo suites.
|
|
6
|
+
/// shared by both engines.
|
|
8
7
|
final class PlayerStateDerivationTests: XCTestCase {
|
|
9
8
|
|
|
10
9
|
private func derive(
|
|
@@ -2,9 +2,7 @@ import XCTest
|
|
|
2
2
|
@testable import QueuePlayer
|
|
3
3
|
|
|
4
4
|
/// Unit tests for `ProgressEmissionGate` — the pure throttle behind the
|
|
5
|
-
/// configurable / background-reduced `onProgress` cadence.
|
|
6
|
-
/// (the periodic tick, the cast push, and the UIApplication background /
|
|
7
|
-
/// foreground observers) is covered by the progress demo suites.
|
|
5
|
+
/// configurable / background-reduced `onProgress` cadence.
|
|
8
6
|
///
|
|
9
7
|
/// Most tests pass `samplerPeriodMs: 0` to exercise exact interval semantics;
|
|
10
8
|
/// the jitter + quantization tests pass the production `500` to verify the
|
|
@@ -3,9 +3,6 @@ import XCTest
|
|
|
3
3
|
@_spi(QueuePlayerTests) import QueuePlayer
|
|
4
4
|
|
|
5
5
|
/// Pure-Swift tests for the queue-mutation index-shift arithmetic.
|
|
6
|
-
/// 1:1 mirror of `android/.../QueueMutationArithmeticTest.kt` —
|
|
7
|
-
/// every Kotlin test name has a Swift twin so the cross-platform
|
|
8
|
-
/// matrix stays in lock-step.
|
|
9
6
|
final class QueueMutationArithmeticTests: XCTestCase {
|
|
10
7
|
|
|
11
8
|
// MARK: - clampInsertBefore
|
|
@@ -6,11 +6,8 @@ import XCTest
|
|
|
6
6
|
/// registrations + the per-command `isEnabled` state. Tests pin the
|
|
7
7
|
/// install / uninstall lifecycle and the `setSkipCapability` ->
|
|
8
8
|
/// `nextTrackCommand.isEnabled` / `previousTrackCommand.isEnabled`
|
|
9
|
-
/// mapping.
|
|
10
|
-
///
|
|
11
|
-
/// classification); attempting to fire `MPRemoteCommandCenter`
|
|
12
|
-
/// handlers from XCTest would require lib-side SPI we deliberately
|
|
13
|
-
/// do not ship.
|
|
9
|
+
/// mapping. Attempting to fire `MPRemoteCommandCenter` handlers from
|
|
10
|
+
/// XCTest would require lib-side SPI we deliberately do not ship.
|
|
14
11
|
///
|
|
15
12
|
/// The shared command center is process-global. `setUp` resets all
|
|
16
13
|
/// seven commands the lib touches, `tearDown` calls `uninstall()` to
|
|
@@ -4,11 +4,6 @@ import XCTest
|
|
|
4
4
|
/// Unit tests for `ReplayGainExtractor`'s pure string parsers
|
|
5
5
|
/// (`parseGainDb`, `parsePeak`) and the `selectGain(mode:)` matrix
|
|
6
6
|
/// on `ReplayGainData`.
|
|
7
|
-
///
|
|
8
|
-
/// Container-level extraction (ID3v2 TXXX, FLAC Vorbis, MP4
|
|
9
|
-
/// freeform) is covered by `MetadataReaderTests`. End-to-end
|
|
10
|
-
/// extraction-on-load is covered by the auto-suite `replaygain.ts`
|
|
11
|
-
/// step.
|
|
12
7
|
final class ReplayGainExtractorTests: XCTestCase {
|
|
13
8
|
|
|
14
9
|
// Helper: `XCTAssertEqual` with the `accuracy:` overload requires
|
|
@@ -2,15 +2,12 @@ import XCTest
|
|
|
2
2
|
@testable import QueuePlayer
|
|
3
3
|
@_spi(QueuePlayerTests) import QueuePlayer
|
|
4
4
|
|
|
5
|
-
///
|
|
6
|
-
/// listener emission.
|
|
7
|
-
/// `SkipIndexTests` (`canSkipNext` / `canSkipPrevious`).
|
|
5
|
+
/// Tests for the canSkip surface + `onSkipCapabilityChange`
|
|
6
|
+
/// listener emission.
|
|
8
7
|
///
|
|
9
|
-
///
|
|
8
|
+
/// Covers the cases where `configure(...)` has not yet
|
|
10
9
|
/// stood up an AVQueuePlayer — initial cached-getter state, listener
|
|
11
|
-
/// registration + auto-fire, dispose closure correctness.
|
|
12
|
-
/// coverage of every `recomputeCapabilities()` hook point lives in
|
|
13
|
-
/// the Robolectric sibling and the end-to-end demo suite.
|
|
10
|
+
/// registration + auto-fire, dispose closure correctness.
|
|
14
11
|
final class SkipCapabilityTests: XCTestCase {
|
|
15
12
|
|
|
16
13
|
func testGetCanSkipNextReturnsFalseInitially() throws {
|
|
@@ -85,6 +85,8 @@ final class SleepTimerCoreTests: XCTestCase {
|
|
|
85
85
|
XCTAssertTrue(converted.stateChanged)
|
|
86
86
|
XCTAssertFalse(t.isEndOfTrack)
|
|
87
87
|
XCTAssertTrue(t.isActive)
|
|
88
|
+
// Deadline lands AT the boundary (tail=50s), not 1s past it.
|
|
89
|
+
XCTAssertEqual(t.deadlineEpochMs, 2000 + 50_000)
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
func testClearResetsToInactive() {
|
|
@@ -95,4 +97,41 @@ final class SleepTimerCoreTests: XCTestCase {
|
|
|
95
97
|
XCTAssertNil(t.deadlineEpochMs)
|
|
96
98
|
XCTAssertNil(t.remainingSeconds(nowMs: 0))
|
|
97
99
|
}
|
|
100
|
+
|
|
101
|
+
func testEndOfTrackFiresPauseAtTrackEndSignal() {
|
|
102
|
+
var t = SleepTimerCore()
|
|
103
|
+
t.armEndOfTrack()
|
|
104
|
+
// Fires at the real track-end signal, before any tail conversion and even
|
|
105
|
+
// when the duration was never known (never ticked).
|
|
106
|
+
let fired = t.fireAtTrackEnd()
|
|
107
|
+
XCTAssertTrue(fired.pauseNow)
|
|
108
|
+
XCTAssertTrue(fired.stateChanged)
|
|
109
|
+
XCTAssertFalse(t.isActive)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func testEndOfTrackSignalFiresAfterTailConversion() {
|
|
113
|
+
var t = SleepTimerCore()
|
|
114
|
+
t.armEndOfTrack()
|
|
115
|
+
_ = t.tick(nowMs: 1000, trackDurationSec: 200, trackPositionSec: 150) // converts to fade
|
|
116
|
+
// The end-of-track origin survives the conversion, so the boundary signal
|
|
117
|
+
// still owns the pause.
|
|
118
|
+
let fired = t.fireAtTrackEnd()
|
|
119
|
+
XCTAssertTrue(fired.pauseNow)
|
|
120
|
+
XCTAssertFalse(t.isActive)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func testFireAtTrackEndIsIdempotent() {
|
|
124
|
+
var t = SleepTimerCore()
|
|
125
|
+
t.armEndOfTrack()
|
|
126
|
+
_ = t.fireAtTrackEnd()
|
|
127
|
+
XCTAssertFalse(t.fireAtTrackEnd().pauseNow)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func testFireAtTrackEndIgnoresDurationTimer() {
|
|
131
|
+
var t = SleepTimerCore()
|
|
132
|
+
t.armDuration(seconds: 60, nowMs: 0)
|
|
133
|
+
let fired = t.fireAtTrackEnd()
|
|
134
|
+
XCTAssertFalse(fired.pauseNow)
|
|
135
|
+
XCTAssertTrue(t.isActive)
|
|
136
|
+
}
|
|
98
137
|
}
|
|
@@ -4,10 +4,10 @@ import XCTest
|
|
|
4
4
|
@_spi(QueuePlayerTests) import QueuePlayer
|
|
5
5
|
|
|
6
6
|
/// Smoke coverage for `VoiceDonation.donateVoiceVocabulary`. Live
|
|
7
|
-
/// `INVocabulary` delivery
|
|
8
|
-
///
|
|
9
|
-
///
|
|
10
|
-
///
|
|
7
|
+
/// `INVocabulary` delivery isn't exercised here; this test pins the
|
|
8
|
+
/// in-process contract that the method early-returns when
|
|
9
|
+
/// `NSSiriUsageDescription` is absent from the host bundle (the XCTest
|
|
10
|
+
/// bundle's plist).
|
|
11
11
|
@available(iOS 13.0, *)
|
|
12
12
|
final class VoiceDonationTests: XCTestCase {
|
|
13
13
|
func testDonateVoiceVocabularyEarlyReturnsWithoutUsageDescription() {
|