react-native-audio-browser 0.2.0-next.27 → 0.2.0-next.29
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/java/com/audiobrowser/ServiceBinding.kt +8 -4
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +3 -4
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +13 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +2 -2
- package/ios/AVPlayerEngine.swift +334 -0
- package/ios/AVPlayerEngineCallbacks.swift +15 -0
- package/ios/Browser/BrowseGate.swift +58 -0
- package/ios/Browser/BrowserConfig.swift +31 -89
- package/ios/Browser/BrowserManager.swift +68 -487
- package/ios/Browser/NavigationErrorState.swift +41 -0
- package/ios/Browser/PageResolver.swift +227 -0
- package/ios/Browser/RequestLayer.swift +90 -0
- package/ios/Browser/{BrowserManager+URLResolution.swift → RequestPipeline.swift} +175 -107
- package/ios/CarPlay/CPInterfaceController+Safe.swift +11 -24
- package/ios/CarPlay/CarPlayArtworkResolver.swift +35 -83
- package/ios/CarPlay/CarPlayController.swift +42 -1442
- package/ios/CarPlay/CarPlayImageLoader.swift +42 -83
- package/ios/CarPlay/CarPlayListItemFactory.swift +9 -23
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +119 -129
- package/ios/CarPlay/CarPlaySession+Navigator.swift +114 -0
- package/ios/CarPlay/CarPlaySession+PageLoader.swift +92 -0
- package/ios/CarPlay/CarPlaySession+Refresher.swift +43 -0
- package/ios/CarPlay/CarPlaySession+TabBar.swift +193 -0
- package/ios/CarPlay/CarPlaySession.swift +209 -0
- package/ios/CarPlay/CarPlayTemplates.swift +63 -0
- package/ios/CarPlay/HybridAudioBrowser+IntentFavorite.swift +20 -0
- package/ios/CarPlay/MediaIntentPlayback.swift +74 -0
- package/ios/CarPlay/RNABMediaAddHandler.swift +2 -18
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +5 -21
- package/ios/CarPlay/RNABMediaIntentHandler.swift +2 -3
- package/ios/Extension/NavigationError+Conversion.swift +7 -4
- package/ios/Extension/UIImage+Adaptive.swift +12 -0
- package/ios/Extension/URLError+Connectivity.swift +15 -0
- package/ios/Http/HttpClient.swift +63 -145
- package/ios/HybridAudioBrowser+Browser.swift +153 -0
- package/ios/HybridAudioBrowser+Options.swift +144 -0
- package/ios/HybridAudioBrowser+Player.swift +397 -0
- package/ios/HybridAudioBrowser+PlayerCallbacks.swift +190 -0
- package/ios/HybridAudioBrowser+Stubs.swift +46 -0
- package/ios/HybridAudioBrowser.swift +74 -1687
- package/ios/Model/NitroTypeStubs.swift +18 -1
- package/ios/Model/PlayerUpdateOptions.swift +1 -1
- package/ios/NowPlayingInfo/NowPlayingInfoController.swift +18 -53
- package/ios/Observer/ItemObserver.swift +90 -0
- package/ios/Observer/PlayerObserver.swift +59 -0
- package/ios/Option/SessionCategories.swift +41 -92
- package/ios/Player/AudioSessionController.swift +177 -0
- package/ios/Player/DeferredSeek.swift +70 -0
- package/ios/Player/ErrorStatus.swift +27 -0
- package/ios/Player/MediaLoader.swift +24 -11
- package/ios/Player/NowPlayingUpdater.swift +157 -58
- package/ios/Player/PlaybackFailure.swift +125 -0
- package/ios/Player/PlaybackPersistence.swift +73 -0
- package/ios/Player/Player.swift +1083 -0
- package/ios/Player/{PlaybackCoordinatorCallbacks.swift → PlayerCallbacks.swift} +3 -4
- package/ios/Player/PlayerEngine.swift +89 -0
- package/ios/Player/PlayerStatusTypes.swift +2 -6
- package/ios/Player/QueueManager.swift +52 -59
- package/ios/Player/RetryLoop.swift +261 -0
- package/ios/Player/SleepTimerManager.swift +1 -1
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +2 -3
- package/ios/RemoteCommand/RemoteCommandController.swift +85 -367
- package/ios/Tests/CarPlayArtworkResolverTests.swift +60 -356
- package/ios/Tests/DeferredSeekTests.swift +70 -0
- package/ios/Tests/EmitterTests.swift +29 -0
- package/ios/Tests/GenerationMemoTests.swift +121 -0
- package/ios/Tests/LRUCacheTests.swift +4 -5
- package/ios/Tests/MediaLoaderTests.swift +12 -33
- package/ios/Tests/ObserverTests.swift +88 -0
- package/ios/Tests/PlaybackErrorTests.swift +76 -0
- package/ios/Tests/PlaybackFailureTests.swift +189 -0
- package/ios/Tests/PlaybackPersistenceTests.swift +93 -0
- package/ios/Tests/PlayerPolicyTests.swift +405 -0
- package/ios/Tests/{PlaybackCoordinatorTests.swift → PlayerTests.swift} +298 -248
- package/ios/Tests/PlayingStateTests.swift +42 -0
- package/ios/Tests/QueueManagerTests.swift +76 -107
- package/ios/Tests/ReadyValueTests.swift +73 -0
- package/ios/Tests/RemoteCommandControllerTests.swift +10 -30
- package/ios/Tests/RemoteCommandTests.swift +2 -4
- package/ios/Tests/RequestLayerTests.swift +112 -0
- package/ios/Tests/RetryLoopTests.swift +270 -0
- package/ios/Tests/Support/TestTypes.swift +111 -112
- package/ios/Tests/TrackIdTemplateTests.swift +1 -1
- package/ios/Tests/URLErrorConnectivityTests.swift +20 -0
- package/ios/TrackSelector.swift +1 -1
- package/ios/Util/ArtworkImageFetcher.swift +42 -0
- package/ios/Util/Emitter.swift +42 -0
- package/ios/Util/GenerationMemo.swift +36 -0
- package/ios/Util/LRUCache.swift +23 -162
- package/ios/Util/NetworkMonitor.swift +1 -1
- package/ios/Util/ReadyValue.swift +56 -0
- package/ios/Util/SFSymbolRenderer.swift +35 -29
- package/ios/Util/SVGProcessor.swift +0 -10
- package/lib/commonjs/web/NativeAudioBrowser.js +1 -1
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +1 -1
- package/lib/module/web/NativeAudioBrowser.js +1 -1
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js +1 -1
- package/lib/typescript/src/types/browser.d.ts +3 -3
- package/package.json +1 -1
- package/src/types/browser.ts +3 -3
- package/src/web/NativeAudioBrowser.ts +1 -1
- package/src/web/TrackPlayer/PlayingStateFactory.test.ts +1 -1
- package/src/web/TrackPlayer/PlayingStateFactory.ts +1 -1
- package/ios/Bridge.h +0 -8
- package/ios/Browser/MediaResolveComposer.swift +0 -114
- package/ios/NowPlayingInfo/MediaItemProperty.swift +0 -78
- package/ios/NowPlayingInfo/NowPlayingInfoKeyValue.swift +0 -6
- package/ios/NowPlayingInfo/NowPlayingInfoProperty.swift +0 -220
- package/ios/Observer/PlayerItemNotificationObserver.swift +0 -119
- package/ios/Observer/PlayerItemPropertyObserver.swift +0 -133
- package/ios/Observer/PlayerStateObserver.swift +0 -50
- package/ios/Observer/PlayerTimeObserver.swift +0 -55
- package/ios/Option/PitchAlgorithms.swift +0 -17
- package/ios/Option/TimeEventFrequency.swift +0 -18
- package/ios/Player/LoadSeekCoordinator.swift +0 -99
- package/ios/Player/MediaLoaderDelegate.swift +0 -11
- package/ios/Player/PlaybackCoordinator.swift +0 -761
- package/ios/Player/PlaybackEffectHandler.swift +0 -54
- package/ios/Player/PlaybackErrorHandler.swift +0 -175
- package/ios/Player/PlaybackStateStore.swift +0 -27
- package/ios/Player/PlayingStateManager.swift +0 -33
- package/ios/Player/RetryManager.swift +0 -384
- package/ios/Player/SeekCompletionHandler.swift +0 -5
- package/ios/Player/SleepTimerHandling.swift +0 -28
- package/ios/Support/RNTrackPlayer-Bridging-Header.h +0 -7
- package/ios/Tests/LoadSeekCoordinatorTests.swift +0 -327
- package/ios/Tests/MediaResolveTests.swift +0 -126
- package/ios/Tests/OnceValueTests.swift +0 -79
- package/ios/Tests/PlaybackErrorHandlerTests.swift +0 -549
- package/ios/Tests/PlaybackStateStoreTests.swift +0 -49
- package/ios/Tests/PlayerTimeObserverTests.swift +0 -37
- package/ios/Tests/PlayingStateManagerTests.swift +0 -57
- package/ios/Tests/RetryManagerTests.swift +0 -178
- package/ios/TrackPlayer.swift +0 -1042
- package/ios/TrackPlayerCallbacks.swift +0 -53
- package/ios/Util/MetadataAdapter.swift +0 -131
- package/ios/Util/OnceValue.swift +0 -104
- /package/ios/{Player → CarPlay}/MediaIntentCriteria.swift +0 -0
- /package/ios/{Browser → CarPlay}/SectionPresentation.swift +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
|
|
3
|
+
@testable import AudioBrowserTestable
|
|
4
|
+
|
|
5
|
+
@Suite("Player.playingState")
|
|
6
|
+
@MainActor
|
|
7
|
+
struct PlayingStateTests {
|
|
8
|
+
private func state(_ playWhenReady: Bool, _ state: PlaybackState) -> PlayingState {
|
|
9
|
+
Player.playingState(playWhenReady: playWhenReady, state: state)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@Test func playingWithIntentInAnActiveState() {
|
|
13
|
+
#expect(state(true, .playing).playing)
|
|
14
|
+
#expect(!state(true, .playing).buffering)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@Test func bufferingWithIntentWhileLoading() {
|
|
18
|
+
#expect(state(true, .loading).playing)
|
|
19
|
+
#expect(state(true, .loading).buffering)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Test func notPlayingWithoutIntent() {
|
|
23
|
+
#expect(!state(false, .playing).playing)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// .stopped was once missing here, masked only by stop() clearing the intent right after.
|
|
27
|
+
@Test func notPlayingInTerminalStatesEvenWithIntent() {
|
|
28
|
+
for terminal in [PlaybackState.none, .stopped, .ended, .error] {
|
|
29
|
+
#expect(!state(true, terminal).playing, "Expected not playing in \(terminal) despite intent")
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Test func aChangeIsPublishedOnce() {
|
|
34
|
+
let callbacks = MockPlayerCallbacks()
|
|
35
|
+
let player = makePlayer()
|
|
36
|
+
player.callbacks = callbacks
|
|
37
|
+
player.transition(.avPlayerPlaying)
|
|
38
|
+
player.setPlayWhenReady(true)
|
|
39
|
+
player.setPlayWhenReady(true)
|
|
40
|
+
#expect(callbacks.playingStateChanges.count == 1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -9,16 +9,6 @@ private func tracks(_ ids: String...) -> [Track] {
|
|
|
9
9
|
ids.map { Track(id: $0) }
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/// A delegate that records every `queueDidChangeTracks` callback.
|
|
13
|
-
@MainActor
|
|
14
|
-
private final class DelegateSpy: QueueManagerDelegate {
|
|
15
|
-
var calls: [[Track]] = []
|
|
16
|
-
|
|
17
|
-
func queueDidChangeTracks(_ tracks: [Track]) {
|
|
18
|
-
calls.append(tracks)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
// MARK: - Navigation: next()
|
|
23
13
|
|
|
24
14
|
@Suite("next()")
|
|
@@ -338,9 +328,9 @@ struct SetQueueTests {
|
|
|
338
328
|
#expect(q.queueSourcePath == "/albums/123")
|
|
339
329
|
}
|
|
340
330
|
|
|
341
|
-
@Test func
|
|
331
|
+
@Test func changesTracksAndCurrent() {
|
|
342
332
|
let q = QueueManager()
|
|
343
|
-
#expect(q.setQueue(tracks("a")) ==
|
|
333
|
+
#expect(q.setQueue(tracks("a")) == .tracksAndCurrent)
|
|
344
334
|
}
|
|
345
335
|
|
|
346
336
|
@Test func emptyArray_setsCurrentIndexToZero() {
|
|
@@ -373,9 +363,9 @@ struct SetQueueTests {
|
|
|
373
363
|
@Suite("add()")
|
|
374
364
|
@MainActor
|
|
375
365
|
struct AddTests {
|
|
376
|
-
@Test func
|
|
366
|
+
@Test func toEmptyQueue_changesCurrent_setsCurrentIndex() {
|
|
377
367
|
let q = QueueManager()
|
|
378
|
-
let changed = q.add(tracks("a", "b"))
|
|
368
|
+
let changed = q.add(tracks("a", "b")).currentTrack
|
|
379
369
|
#expect(changed == true)
|
|
380
370
|
#expect(q.currentIndex == 0)
|
|
381
371
|
#expect(q.tracks.count == 2)
|
|
@@ -383,28 +373,28 @@ struct AddTests {
|
|
|
383
373
|
|
|
384
374
|
@Test func toEmptyQueue_respectsInitialIndex() {
|
|
385
375
|
let q = QueueManager()
|
|
386
|
-
let changed = q.add(tracks("a", "b", "c"), initialIndex: 2)
|
|
376
|
+
let changed = q.add(tracks("a", "b", "c"), initialIndex: 2).currentTrack
|
|
387
377
|
#expect(changed == true)
|
|
388
378
|
#expect(q.currentIndex == 2)
|
|
389
379
|
}
|
|
390
380
|
|
|
391
|
-
@Test func
|
|
381
|
+
@Test func toNonEmpty_keepsCurrent_currentIndexUnchanged() {
|
|
392
382
|
let q = QueueManager()
|
|
393
383
|
q.setQueue(tracks("a"))
|
|
394
|
-
let changed = q.add(tracks("b", "c"))
|
|
384
|
+
let changed = q.add(tracks("b", "c")).currentTrack
|
|
395
385
|
#expect(changed == false)
|
|
396
386
|
#expect(q.currentIndex == 0)
|
|
397
387
|
#expect(q.tracks.count == 3)
|
|
398
388
|
}
|
|
399
389
|
|
|
400
|
-
@Test func
|
|
390
|
+
@Test func emptyArray_changesNothing() {
|
|
401
391
|
let q = QueueManager()
|
|
402
|
-
#expect(q.add([]) ==
|
|
392
|
+
#expect(q.add([]) == .none)
|
|
403
393
|
}
|
|
404
394
|
|
|
405
395
|
@Test func toEmptyQueue_clampsInitialIndex() {
|
|
406
396
|
let q = QueueManager()
|
|
407
|
-
let changed = q.add(tracks("a", "b"), initialIndex: 99)
|
|
397
|
+
let changed = q.add(tracks("a", "b"), initialIndex: 99).currentTrack
|
|
408
398
|
#expect(changed == true)
|
|
409
399
|
#expect(q.currentIndex == 1)
|
|
410
400
|
}
|
|
@@ -446,7 +436,7 @@ struct AddAtTests {
|
|
|
446
436
|
@Test func beforeCurrent_shiftsCurrentIndex() throws {
|
|
447
437
|
let q = QueueManager()
|
|
448
438
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
|
|
449
|
-
let changed = try q.addAt(tracks("x", "y"), at: 0)
|
|
439
|
+
let changed = try q.addAt(tracks("x", "y"), at: 0).currentTrack
|
|
450
440
|
#expect(changed == false)
|
|
451
441
|
#expect(q.currentIndex == 3) // was 1, shifted by 2
|
|
452
442
|
#expect(q.tracks.count == 5)
|
|
@@ -455,15 +445,15 @@ struct AddAtTests {
|
|
|
455
445
|
@Test func afterCurrent_currentIndexUnchanged() throws {
|
|
456
446
|
let q = QueueManager()
|
|
457
447
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 0)
|
|
458
|
-
let changed = try q.addAt(tracks("x"), at: 2)
|
|
448
|
+
let changed = try q.addAt(tracks("x"), at: 2).currentTrack
|
|
459
449
|
#expect(changed == false)
|
|
460
450
|
#expect(q.currentIndex == 0)
|
|
461
451
|
#expect(q.tracks.count == 4)
|
|
462
452
|
}
|
|
463
453
|
|
|
464
|
-
@Test func
|
|
454
|
+
@Test func toEmptyQueue_changesCurrent() throws {
|
|
465
455
|
let q = QueueManager()
|
|
466
|
-
let changed = try q.addAt(tracks("a"), at: 0)
|
|
456
|
+
let changed = try q.addAt(tracks("a"), at: 0).currentTrack
|
|
467
457
|
#expect(changed == true)
|
|
468
458
|
#expect(q.currentIndex == 0)
|
|
469
459
|
}
|
|
@@ -496,16 +486,16 @@ struct AddAtTests {
|
|
|
496
486
|
}
|
|
497
487
|
}
|
|
498
488
|
|
|
499
|
-
@Test func
|
|
489
|
+
@Test func emptyArray_changesNothing() throws {
|
|
500
490
|
let q = QueueManager()
|
|
501
491
|
q.setQueue(tracks("a"))
|
|
502
|
-
#expect(try q.addAt([], at: 0) ==
|
|
492
|
+
#expect(try q.addAt([], at: 0) == .none)
|
|
503
493
|
}
|
|
504
494
|
|
|
505
495
|
@Test func singleExistingTrack_insertBeforeCurrent_shifts() throws {
|
|
506
496
|
let q = QueueManager()
|
|
507
497
|
q.setQueue(tracks("a"))
|
|
508
|
-
let changed = try q.addAt(tracks("x"), at: 0)
|
|
498
|
+
let changed = try q.addAt(tracks("x"), at: 0).currentTrack
|
|
509
499
|
#expect(changed == false)
|
|
510
500
|
// The pointer follows the playing track — the old `tracks.count > 1`
|
|
511
501
|
// guard silently swapped currentTrack to the inserted track while the
|
|
@@ -518,7 +508,7 @@ struct AddAtTests {
|
|
|
518
508
|
let q = QueueManager()
|
|
519
509
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
|
|
520
510
|
// currentIndex >= index (1 >= 1) triggers the shift
|
|
521
|
-
let changed = try q.addAt(tracks("x"), at: 1)
|
|
511
|
+
let changed = try q.addAt(tracks("x"), at: 1).currentTrack
|
|
522
512
|
#expect(changed == false)
|
|
523
513
|
#expect(q.currentIndex == 2)
|
|
524
514
|
// "b" should still be the current track
|
|
@@ -529,7 +519,7 @@ struct AddAtTests {
|
|
|
529
519
|
let q = QueueManager()
|
|
530
520
|
q.setQueue(tracks("a", "b"))
|
|
531
521
|
// index == tracks.count is the valid append boundary
|
|
532
|
-
let changed = try q.addAt(tracks("x"), at: 2)
|
|
522
|
+
let changed = try q.addAt(tracks("x"), at: 2).currentTrack
|
|
533
523
|
#expect(changed == false)
|
|
534
524
|
#expect(q.tracks.count == 3)
|
|
535
525
|
#expect(q.tracks[2].id == "x")
|
|
@@ -542,29 +532,29 @@ struct AddAtTests {
|
|
|
542
532
|
@Suite("remove()")
|
|
543
533
|
@MainActor
|
|
544
534
|
struct RemoveTests {
|
|
545
|
-
@Test func
|
|
535
|
+
@Test func currentTrack_changesCurrent_wrapsIndex() throws {
|
|
546
536
|
let q = QueueManager()
|
|
547
537
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 2)
|
|
548
|
-
let changed = try q.remove(2)
|
|
538
|
+
let changed = try q.remove(2).currentTrack
|
|
549
539
|
#expect(changed == true)
|
|
550
540
|
// After removing index 2, tracks has 2 items, currentIndex wraps: 2 % 2 = 0
|
|
551
541
|
#expect(q.currentIndex == 0)
|
|
552
542
|
#expect(q.tracks.count == 2)
|
|
553
543
|
}
|
|
554
544
|
|
|
555
|
-
@Test func
|
|
545
|
+
@Test func beforeCurrent_adjustsIndex_keepsCurrent() throws {
|
|
556
546
|
let q = QueueManager()
|
|
557
547
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 2)
|
|
558
|
-
let changed = try q.remove(0)
|
|
548
|
+
let changed = try q.remove(0).currentTrack
|
|
559
549
|
#expect(changed == false)
|
|
560
550
|
#expect(q.currentIndex == 1) // was 2, decremented
|
|
561
551
|
#expect(q.tracks.count == 2)
|
|
562
552
|
}
|
|
563
553
|
|
|
564
|
-
@Test func
|
|
554
|
+
@Test func afterCurrent_keepsCurrent() throws {
|
|
565
555
|
let q = QueueManager()
|
|
566
556
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 0)
|
|
567
|
-
let changed = try q.remove(2)
|
|
557
|
+
let changed = try q.remove(2).currentTrack
|
|
568
558
|
#expect(changed == false)
|
|
569
559
|
#expect(q.currentIndex == 0)
|
|
570
560
|
#expect(q.tracks.count == 2)
|
|
@@ -573,7 +563,7 @@ struct RemoveTests {
|
|
|
573
563
|
@Test func lastRemaining_currentIndexBecomesMinusOne() throws {
|
|
574
564
|
let q = QueueManager()
|
|
575
565
|
q.setQueue(tracks("a"))
|
|
576
|
-
let changed = try q.remove(0)
|
|
566
|
+
let changed = try q.remove(0).currentTrack
|
|
577
567
|
#expect(changed == true)
|
|
578
568
|
#expect(q.currentIndex == -1)
|
|
579
569
|
#expect(q.tracks.isEmpty)
|
|
@@ -605,7 +595,7 @@ struct RemoveTests {
|
|
|
605
595
|
@Test func currentAtMiddle_wrapsCorrectly() throws {
|
|
606
596
|
let q = QueueManager()
|
|
607
597
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
|
|
608
|
-
let changed = try q.remove(1)
|
|
598
|
+
let changed = try q.remove(1).currentTrack
|
|
609
599
|
#expect(changed == true)
|
|
610
600
|
// 1 % 2 = 1, pointing to what was "c"
|
|
611
601
|
#expect(q.currentIndex == 1)
|
|
@@ -615,7 +605,7 @@ struct RemoveTests {
|
|
|
615
605
|
@Test func currentAtZero_staysAtZero() throws {
|
|
616
606
|
let q = QueueManager()
|
|
617
607
|
q.setQueue(tracks("a", "b", "c"))
|
|
618
|
-
let changed = try q.remove(0)
|
|
608
|
+
let changed = try q.remove(0).currentTrack
|
|
619
609
|
#expect(changed == true)
|
|
620
610
|
// 0 % 2 = 0
|
|
621
611
|
#expect(q.currentIndex == 0)
|
|
@@ -643,16 +633,16 @@ struct MoveTests {
|
|
|
643
633
|
@Test func currentTrack_pointerFollows() throws {
|
|
644
634
|
let q = QueueManager()
|
|
645
635
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 0)
|
|
646
|
-
let changed = try q.move(fromIndex: 0, toIndex: 2)
|
|
636
|
+
let changed = try q.move(fromIndex: 0, toIndex: 2).currentTrack
|
|
647
637
|
#expect(changed == false)
|
|
648
638
|
#expect(q.currentIndex == 2)
|
|
649
639
|
#expect(q.currentTrack?.id == "a")
|
|
650
640
|
}
|
|
651
641
|
|
|
652
|
-
@Test func
|
|
642
|
+
@Test func nonCurrent_keepsCurrent() throws {
|
|
653
643
|
let q = QueueManager()
|
|
654
644
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 0)
|
|
655
|
-
let changed = try q.move(fromIndex: 1, toIndex: 2)
|
|
645
|
+
let changed = try q.move(fromIndex: 1, toIndex: 2).currentTrack
|
|
656
646
|
#expect(changed == false)
|
|
657
647
|
#expect(q.currentIndex == 0)
|
|
658
648
|
}
|
|
@@ -667,7 +657,7 @@ struct MoveTests {
|
|
|
667
657
|
@Test func nonCurrent_acrossCurrent_keepsPointerOnSameTrack() throws {
|
|
668
658
|
let q = QueueManager()
|
|
669
659
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
|
|
670
|
-
let changed = try q.move(fromIndex: 0, toIndex: 2)
|
|
660
|
+
let changed = try q.move(fromIndex: 0, toIndex: 2).currentTrack
|
|
671
661
|
#expect(changed == false)
|
|
672
662
|
// [a,b,c] → [b,c,a]; "b" is now index 0.
|
|
673
663
|
#expect(q.currentTrack?.id == "b")
|
|
@@ -677,7 +667,7 @@ struct MoveTests {
|
|
|
677
667
|
@Test func nonCurrent_acrossCurrentBackwards_keepsPointerOnSameTrack() throws {
|
|
678
668
|
let q = QueueManager()
|
|
679
669
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
|
|
680
|
-
let changed = try q.move(fromIndex: 2, toIndex: 0)
|
|
670
|
+
let changed = try q.move(fromIndex: 2, toIndex: 0).currentTrack
|
|
681
671
|
#expect(changed == false)
|
|
682
672
|
// [a,b,c] → [c,a,b]; "b" is now index 2.
|
|
683
673
|
#expect(q.currentTrack?.id == "b")
|
|
@@ -687,7 +677,7 @@ struct MoveTests {
|
|
|
687
677
|
@Test func toIndexBeyondCount_clampsPointerToo() throws {
|
|
688
678
|
let q = QueueManager()
|
|
689
679
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 0)
|
|
690
|
-
let changed = try q.move(fromIndex: 0, toIndex: 99)
|
|
680
|
+
let changed = try q.move(fromIndex: 0, toIndex: 99).currentTrack
|
|
691
681
|
#expect(changed == false)
|
|
692
682
|
// remove(0): [b, c], insert at min(2, 99)=2: [b, c, a]
|
|
693
683
|
#expect(q.tracks[2].id == "a")
|
|
@@ -719,19 +709,19 @@ struct MoveTests {
|
|
|
719
709
|
@Suite("clear()")
|
|
720
710
|
@MainActor
|
|
721
711
|
struct ClearTests {
|
|
722
|
-
@Test func
|
|
712
|
+
@Test func withTracks_changesCurrent_resetsToMinusOne() {
|
|
723
713
|
let q = QueueManager()
|
|
724
714
|
q.setQueue(tracks("a", "b"))
|
|
725
|
-
let changed = q.clear()
|
|
715
|
+
let changed = q.clear().currentTrack
|
|
726
716
|
#expect(changed == true)
|
|
727
717
|
#expect(q.currentIndex == -1)
|
|
728
718
|
#expect(q.tracks.isEmpty)
|
|
729
719
|
#expect(q.queueSourcePath == nil)
|
|
730
720
|
}
|
|
731
721
|
|
|
732
|
-
@Test func
|
|
722
|
+
@Test func alreadyEmpty_changesNothing() {
|
|
733
723
|
let q = QueueManager()
|
|
734
|
-
#expect(q.clear() ==
|
|
724
|
+
#expect(q.clear() == .none)
|
|
735
725
|
}
|
|
736
726
|
|
|
737
727
|
/// clear() and removeUpcomingTracks() were the only mutations not
|
|
@@ -941,95 +931,74 @@ struct ComputedPropertyTests {
|
|
|
941
931
|
}
|
|
942
932
|
}
|
|
943
933
|
|
|
944
|
-
// MARK: -
|
|
934
|
+
// MARK: - QueueChange
|
|
945
935
|
|
|
946
|
-
@Suite("
|
|
936
|
+
@Suite("QueueChange")
|
|
947
937
|
@MainActor
|
|
948
|
-
struct
|
|
949
|
-
@Test func
|
|
938
|
+
struct QueueChangeTests {
|
|
939
|
+
@Test func setQueue_changesTracksAndCurrent() {
|
|
940
|
+
#expect(QueueManager().setQueue(tracks("a", "b")) == .tracksAndCurrent)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
@Test func addToEmpty_changesTracksAndCurrent() {
|
|
944
|
+
#expect(QueueManager().add(tracks("a")) == .tracksAndCurrent)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
@Test func addToNonEmpty_changesTracksOnly() {
|
|
948
|
+
let q = QueueManager()
|
|
949
|
+
q.setQueue(tracks("a"))
|
|
950
|
+
#expect(q.add(tracks("b")) == .tracksOnly)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
@Test func addAt_changesTracksOnly() throws {
|
|
950
954
|
let q = QueueManager()
|
|
951
|
-
let spy = DelegateSpy()
|
|
952
|
-
q.delegate = spy
|
|
953
955
|
q.setQueue(tracks("a", "b"))
|
|
954
|
-
#expect(
|
|
955
|
-
#expect(
|
|
956
|
+
#expect(try q.addAt(tracks("x"), at: 1) == .tracksOnly)
|
|
957
|
+
#expect(q.tracks.count == 3)
|
|
956
958
|
}
|
|
957
959
|
|
|
958
|
-
@Test func
|
|
960
|
+
@Test func removeCurrent_changesTracksAndCurrent() throws {
|
|
959
961
|
let q = QueueManager()
|
|
960
|
-
|
|
961
|
-
q.
|
|
962
|
-
q.add(tracks("a"))
|
|
963
|
-
#expect(spy.calls.count == 1)
|
|
962
|
+
q.setQueue(tracks("a", "b"))
|
|
963
|
+
#expect(try q.remove(0) == .tracksAndCurrent)
|
|
964
964
|
}
|
|
965
965
|
|
|
966
|
-
@Test func
|
|
966
|
+
@Test func removeOther_changesTracksOnly() throws {
|
|
967
967
|
let q = QueueManager()
|
|
968
968
|
q.setQueue(tracks("a", "b"))
|
|
969
|
-
|
|
970
|
-
q.delegate = spy
|
|
971
|
-
try q.remove(1)
|
|
972
|
-
#expect(spy.calls.count == 1)
|
|
973
|
-
#expect(spy.calls[0].count == 1)
|
|
969
|
+
#expect(try q.remove(1) == .tracksOnly)
|
|
974
970
|
}
|
|
975
971
|
|
|
976
|
-
@Test func
|
|
972
|
+
@Test func clear_changesTracksAndCurrent() {
|
|
977
973
|
let q = QueueManager()
|
|
978
974
|
q.setQueue(tracks("a"))
|
|
979
|
-
|
|
980
|
-
q.
|
|
981
|
-
q.clear()
|
|
982
|
-
#expect(spy.calls.count == 1)
|
|
983
|
-
#expect(spy.calls[0].isEmpty)
|
|
975
|
+
#expect(q.clear() == .tracksAndCurrent)
|
|
976
|
+
#expect(q.tracks.isEmpty)
|
|
984
977
|
}
|
|
985
978
|
|
|
986
|
-
@Test func
|
|
979
|
+
@Test func moveCurrent_changesTracksOnly() throws {
|
|
987
980
|
let q = QueueManager()
|
|
988
981
|
q.setQueue(tracks("a", "b", "c"))
|
|
989
|
-
|
|
990
|
-
q.delegate = spy
|
|
991
|
-
try q.move(fromIndex: 0, toIndex: 2)
|
|
992
|
-
#expect(spy.calls.count == 1)
|
|
982
|
+
#expect(try q.move(fromIndex: 0, toIndex: 2) == .tracksOnly)
|
|
993
983
|
}
|
|
994
984
|
|
|
995
|
-
@Test func
|
|
985
|
+
@Test func replaceCurrent_changesTracksOnly() throws {
|
|
996
986
|
let q = QueueManager()
|
|
997
987
|
q.setQueue(tracks("a", "b"))
|
|
998
|
-
|
|
999
|
-
q.delegate = spy
|
|
1000
|
-
try q.replace(0, Track(id: "A"))
|
|
1001
|
-
#expect(spy.calls.count == 1)
|
|
988
|
+
#expect(try q.replace(0, Track(id: "A")) == .tracksOnly)
|
|
1002
989
|
}
|
|
1003
990
|
|
|
1004
|
-
@Test func
|
|
991
|
+
@Test func removeUpcomingTracks_changesTracksOnly() {
|
|
1005
992
|
let q = QueueManager()
|
|
1006
993
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 0)
|
|
1007
|
-
|
|
1008
|
-
q.
|
|
1009
|
-
q.removeUpcomingTracks()
|
|
1010
|
-
#expect(spy.calls.count == 1)
|
|
1011
|
-
#expect(spy.calls[0].count == 1)
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
@Test func queueDidChangeTracks_firesOnAddAt() throws {
|
|
1015
|
-
let q = QueueManager()
|
|
1016
|
-
q.setQueue(tracks("a", "b"))
|
|
1017
|
-
let spy = DelegateSpy()
|
|
1018
|
-
q.delegate = spy
|
|
1019
|
-
try q.addAt(tracks("x"), at: 1)
|
|
1020
|
-
#expect(spy.calls.count == 1)
|
|
1021
|
-
#expect(spy.calls[0].count == 3)
|
|
994
|
+
#expect(q.removeUpcomingTracks() == .tracksOnly)
|
|
995
|
+
#expect(q.tracks.count == 1)
|
|
1022
996
|
}
|
|
1023
997
|
|
|
1024
|
-
@Test func
|
|
998
|
+
@Test func removeUpcomingTracks_atEnd_changesNothing() {
|
|
1025
999
|
let q = QueueManager()
|
|
1026
|
-
q.setQueue(tracks("a", "b"
|
|
1027
|
-
|
|
1028
|
-
q.delegate = spy
|
|
1029
|
-
_ = q.next()
|
|
1030
|
-
_ = q.previous()
|
|
1031
|
-
try q.skipTo(2)
|
|
1032
|
-
#expect(spy.calls.isEmpty)
|
|
1000
|
+
q.setQueue(tracks("a", "b"), initialIndex: 1)
|
|
1001
|
+
#expect(q.removeUpcomingTracks() == .none)
|
|
1033
1002
|
}
|
|
1034
1003
|
}
|
|
1035
1004
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Testing
|
|
3
|
+
|
|
4
|
+
@testable import AudioBrowserTestable
|
|
5
|
+
|
|
6
|
+
@MainActor
|
|
7
|
+
@Suite("ReadyValue")
|
|
8
|
+
struct ReadyValueTests {
|
|
9
|
+
final class Source {
|
|
10
|
+
var value: Int?
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Test func returnsAtOnceWhenTheProviderHasAValue() async {
|
|
14
|
+
let ready = ReadyValue<Int> { 7 }
|
|
15
|
+
#expect(await ready.wait() == 7)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Test func resumesEveryWaiterOnCheck() async {
|
|
19
|
+
let source = Source()
|
|
20
|
+
let ready = ReadyValue { source.value }
|
|
21
|
+
let first = Task { await ready.wait() }
|
|
22
|
+
let second = Task { await ready.wait() }
|
|
23
|
+
while ready.waiterCount < 2 {
|
|
24
|
+
await Task.yield()
|
|
25
|
+
}
|
|
26
|
+
ready.check()
|
|
27
|
+
#expect(ready.waiterCount == 2)
|
|
28
|
+
source.value = 3
|
|
29
|
+
ready.check()
|
|
30
|
+
#expect(await first.value == 3)
|
|
31
|
+
#expect(await second.value == 3)
|
|
32
|
+
#expect(ready.waiterCount == 0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test func followsTheProviderWithoutAReset() async {
|
|
36
|
+
let source = Source()
|
|
37
|
+
source.value = 1
|
|
38
|
+
let ready = ReadyValue { source.value }
|
|
39
|
+
#expect(await ready.wait() == 1)
|
|
40
|
+
source.value = 2
|
|
41
|
+
#expect(await ready.wait() == 2)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Test func timesOutWithNilAndDropsTheWaiter() async {
|
|
45
|
+
let ready = ReadyValue<Int> { nil }
|
|
46
|
+
#expect(await ready.wait(timeout: .milliseconds(10)) == nil)
|
|
47
|
+
#expect(ready.waiterCount == 0)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test func aCancelledWaiterGetsNilAndIsDropped() async {
|
|
51
|
+
let ready = ReadyValue<Int> { nil }
|
|
52
|
+
let waiter = Task { await ready.wait() }
|
|
53
|
+
while ready.waiterCount < 1 {
|
|
54
|
+
await Task.yield()
|
|
55
|
+
}
|
|
56
|
+
waiter.cancel()
|
|
57
|
+
#expect(await waiter.value == nil)
|
|
58
|
+
while ready.waiterCount > 0 {
|
|
59
|
+
await Task.yield()
|
|
60
|
+
}
|
|
61
|
+
#expect(ready.waiterCount == 0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Test func aWaiterCancelledBeforeWaitingGetsNil() async {
|
|
65
|
+
let ready = ReadyValue<Int> { nil }
|
|
66
|
+
let waiter = Task {
|
|
67
|
+
withUnsafeCurrentTask { $0?.cancel() }
|
|
68
|
+
return await ready.wait()
|
|
69
|
+
}
|
|
70
|
+
#expect(await waiter.value == nil)
|
|
71
|
+
#expect(ready.waiterCount == 0)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -59,8 +59,6 @@ struct RemoteCommandControllerTests {
|
|
|
59
59
|
|
|
60
60
|
#expect(center.skipForwardCommand.isEnabled)
|
|
61
61
|
#expect(center.skipBackwardCommand.isEnabled)
|
|
62
|
-
#expect(controller.commandTargetPointers["skipForward"] != nil)
|
|
63
|
-
#expect(controller.commandTargetPointers["skipBackward"] != nil)
|
|
64
62
|
#expect(center.skipForwardCommand.preferredIntervals == [30])
|
|
65
63
|
#expect(center.skipBackwardCommand.preferredIntervals == [30])
|
|
66
64
|
}
|
|
@@ -73,7 +71,6 @@ struct RemoteCommandControllerTests {
|
|
|
73
71
|
controller.enable(commands: [.changePlaybackRate(supportedPlaybackRates: [1.0, 1.5, 2.0])])
|
|
74
72
|
|
|
75
73
|
#expect(center.changePlaybackRateCommand.isEnabled)
|
|
76
|
-
#expect(controller.commandTargetPointers["changePlaybackRate"] != nil)
|
|
77
74
|
#expect(center.changePlaybackRateCommand.supportedPlaybackRates == [1.0, 1.5, 2.0])
|
|
78
75
|
}
|
|
79
76
|
|
|
@@ -87,9 +84,7 @@ struct RemoteCommandControllerTests {
|
|
|
87
84
|
controller.enable(commands: [.play, .pause])
|
|
88
85
|
|
|
89
86
|
#expect(!center.nextTrackCommand.isEnabled)
|
|
90
|
-
#expect(controller.commandTargetPointers["nextTrack"] == nil)
|
|
91
87
|
#expect(center.playCommand.isEnabled)
|
|
92
|
-
#expect(controller.commandTargetPointers["play"] != nil)
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
@Test func disableAllClearsEveryTarget() {
|
|
@@ -99,34 +94,23 @@ struct RemoteCommandControllerTests {
|
|
|
99
94
|
controller.enable(commands: [.play, .pause, .skipForward(preferredIntervals: [15])])
|
|
100
95
|
controller.disableAll()
|
|
101
96
|
|
|
102
|
-
#expect(controller.commandTargetPointers.isEmpty)
|
|
103
97
|
#expect(!center.playCommand.isEnabled)
|
|
104
98
|
#expect(!center.pauseCommand.isEnabled)
|
|
105
99
|
#expect(!center.skipForwardCommand.isEnabled)
|
|
106
100
|
}
|
|
107
101
|
|
|
108
|
-
///
|
|
109
|
-
|
|
110
|
-
@Test func reEnablingKeepsOneTargetPerCommand() {
|
|
111
|
-
let (controller, _) = makeController()
|
|
112
|
-
|
|
113
|
-
controller.enable(commands: [.play, .pause])
|
|
114
|
-
let first = controller.commandTargetPointers["play"]
|
|
115
|
-
controller.enable(commands: [.play, .pause])
|
|
116
|
-
let second = controller.commandTargetPointers["play"]
|
|
117
|
-
|
|
118
|
-
#expect(controller.commandTargetPointers.count == 2)
|
|
119
|
-
#expect(first as AnyObject? !== second as AnyObject?)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/// `setSkipAvailability` greys out next/previous when the queue has nowhere to
|
|
123
|
-
/// go, and the state must survive a later `enable(commands:)`.
|
|
124
|
-
@Test func skipAvailabilitySurvivesReEnable() {
|
|
102
|
+
/// Next/previous follow `skipAvailability` when refreshed and whenever they are enabled.
|
|
103
|
+
@Test func skipAvailabilityAppliesOnRefreshAndEnable() {
|
|
125
104
|
let (controller, _) = makeController()
|
|
126
105
|
let center = MPRemoteCommandCenter.shared()
|
|
106
|
+
var availability = (canNext: true, canPrevious: true)
|
|
107
|
+
controller.skipAvailability = { availability }
|
|
127
108
|
|
|
128
109
|
controller.enable(commands: [.next, .previous])
|
|
129
|
-
|
|
110
|
+
#expect(center.nextTrackCommand.isEnabled)
|
|
111
|
+
|
|
112
|
+
availability = (false, true)
|
|
113
|
+
controller.refreshSkipAvailability()
|
|
130
114
|
#expect(!center.nextTrackCommand.isEnabled)
|
|
131
115
|
#expect(center.previousTrackCommand.isEnabled)
|
|
132
116
|
|
|
@@ -136,15 +120,12 @@ struct RemoteCommandControllerTests {
|
|
|
136
120
|
#expect(center.previousTrackCommand.isEnabled)
|
|
137
121
|
}
|
|
138
122
|
|
|
139
|
-
/// The
|
|
140
|
-
///
|
|
141
|
-
/// `{ [self] in self.method($0) }`, and storing that back on self is a permanent
|
|
142
|
-
/// cycle — one leaked controller per player instance, across every JS reload.
|
|
123
|
+
/// The command targets must not capture the controller strongly, or every player
|
|
124
|
+
/// instance leaks one across JS reloads.
|
|
143
125
|
@Test func doesNotRetainItselfThroughHandlers() {
|
|
144
126
|
weak var weakController: RemoteCommandController?
|
|
145
127
|
autoreleasepool {
|
|
146
128
|
let (controller, _) = makeController()
|
|
147
|
-
// Forces every lazy handler to initialize, which is what closes the cycle.
|
|
148
129
|
controller.enable(commands: RemoteCommand.all())
|
|
149
130
|
controller.disableAll()
|
|
150
131
|
weakController = controller
|
|
@@ -167,6 +148,5 @@ struct RemoteCommandControllerTests {
|
|
|
167
148
|
#expect(center.changeShuffleModeCommand.currentShuffleType == .items)
|
|
168
149
|
#expect(center.changeRepeatModeCommand.currentRepeatType == .all)
|
|
169
150
|
#expect(center.changeShuffleModeCommand.isEnabled)
|
|
170
|
-
#expect(controller.commandTargetPointers["changeShuffleMode"] != nil)
|
|
171
151
|
}
|
|
172
152
|
}
|
|
@@ -48,10 +48,8 @@ struct RemoteCommandTests {
|
|
|
48
48
|
#expect(toDisable.count == RemoteCommand.all().count)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/// `key` is the identity
|
|
52
|
-
///
|
|
53
|
-
/// sharing a key would silently share one slot: enabling one would remove the
|
|
54
|
-
/// other's handler.
|
|
51
|
+
/// `key` is the identity the diff above compares; two cases sharing a key would
|
|
52
|
+
/// disable each other.
|
|
55
53
|
@Test func keysAreUnique() {
|
|
56
54
|
let all = RemoteCommand.all()
|
|
57
55
|
#expect(Set(all.map(\.key)).count == all.count)
|