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.
Files changed (139) hide show
  1. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +8 -4
  2. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +3 -4
  3. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +13 -0
  4. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +2 -2
  5. package/ios/AVPlayerEngine.swift +334 -0
  6. package/ios/AVPlayerEngineCallbacks.swift +15 -0
  7. package/ios/Browser/BrowseGate.swift +58 -0
  8. package/ios/Browser/BrowserConfig.swift +31 -89
  9. package/ios/Browser/BrowserManager.swift +68 -487
  10. package/ios/Browser/NavigationErrorState.swift +41 -0
  11. package/ios/Browser/PageResolver.swift +227 -0
  12. package/ios/Browser/RequestLayer.swift +90 -0
  13. package/ios/Browser/{BrowserManager+URLResolution.swift → RequestPipeline.swift} +175 -107
  14. package/ios/CarPlay/CPInterfaceController+Safe.swift +11 -24
  15. package/ios/CarPlay/CarPlayArtworkResolver.swift +35 -83
  16. package/ios/CarPlay/CarPlayController.swift +42 -1442
  17. package/ios/CarPlay/CarPlayImageLoader.swift +42 -83
  18. package/ios/CarPlay/CarPlayListItemFactory.swift +9 -23
  19. package/ios/CarPlay/CarPlayNowPlayingManager.swift +119 -129
  20. package/ios/CarPlay/CarPlaySession+Navigator.swift +114 -0
  21. package/ios/CarPlay/CarPlaySession+PageLoader.swift +92 -0
  22. package/ios/CarPlay/CarPlaySession+Refresher.swift +43 -0
  23. package/ios/CarPlay/CarPlaySession+TabBar.swift +193 -0
  24. package/ios/CarPlay/CarPlaySession.swift +209 -0
  25. package/ios/CarPlay/CarPlayTemplates.swift +63 -0
  26. package/ios/CarPlay/HybridAudioBrowser+IntentFavorite.swift +20 -0
  27. package/ios/CarPlay/MediaIntentPlayback.swift +74 -0
  28. package/ios/CarPlay/RNABMediaAddHandler.swift +2 -18
  29. package/ios/CarPlay/RNABMediaAffinityHandler.swift +5 -21
  30. package/ios/CarPlay/RNABMediaIntentHandler.swift +2 -3
  31. package/ios/Extension/NavigationError+Conversion.swift +7 -4
  32. package/ios/Extension/UIImage+Adaptive.swift +12 -0
  33. package/ios/Extension/URLError+Connectivity.swift +15 -0
  34. package/ios/Http/HttpClient.swift +63 -145
  35. package/ios/HybridAudioBrowser+Browser.swift +153 -0
  36. package/ios/HybridAudioBrowser+Options.swift +144 -0
  37. package/ios/HybridAudioBrowser+Player.swift +397 -0
  38. package/ios/HybridAudioBrowser+PlayerCallbacks.swift +190 -0
  39. package/ios/HybridAudioBrowser+Stubs.swift +46 -0
  40. package/ios/HybridAudioBrowser.swift +74 -1687
  41. package/ios/Model/NitroTypeStubs.swift +18 -1
  42. package/ios/Model/PlayerUpdateOptions.swift +1 -1
  43. package/ios/NowPlayingInfo/NowPlayingInfoController.swift +18 -53
  44. package/ios/Observer/ItemObserver.swift +90 -0
  45. package/ios/Observer/PlayerObserver.swift +59 -0
  46. package/ios/Option/SessionCategories.swift +41 -92
  47. package/ios/Player/AudioSessionController.swift +177 -0
  48. package/ios/Player/DeferredSeek.swift +70 -0
  49. package/ios/Player/ErrorStatus.swift +27 -0
  50. package/ios/Player/MediaLoader.swift +24 -11
  51. package/ios/Player/NowPlayingUpdater.swift +157 -58
  52. package/ios/Player/PlaybackFailure.swift +125 -0
  53. package/ios/Player/PlaybackPersistence.swift +73 -0
  54. package/ios/Player/Player.swift +1083 -0
  55. package/ios/Player/{PlaybackCoordinatorCallbacks.swift → PlayerCallbacks.swift} +3 -4
  56. package/ios/Player/PlayerEngine.swift +89 -0
  57. package/ios/Player/PlayerStatusTypes.swift +2 -6
  58. package/ios/Player/QueueManager.swift +52 -59
  59. package/ios/Player/RetryLoop.swift +261 -0
  60. package/ios/Player/SleepTimerManager.swift +1 -1
  61. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +2 -3
  62. package/ios/RemoteCommand/RemoteCommandController.swift +85 -367
  63. package/ios/Tests/CarPlayArtworkResolverTests.swift +60 -356
  64. package/ios/Tests/DeferredSeekTests.swift +70 -0
  65. package/ios/Tests/EmitterTests.swift +29 -0
  66. package/ios/Tests/GenerationMemoTests.swift +121 -0
  67. package/ios/Tests/LRUCacheTests.swift +4 -5
  68. package/ios/Tests/MediaLoaderTests.swift +12 -33
  69. package/ios/Tests/ObserverTests.swift +88 -0
  70. package/ios/Tests/PlaybackErrorTests.swift +76 -0
  71. package/ios/Tests/PlaybackFailureTests.swift +189 -0
  72. package/ios/Tests/PlaybackPersistenceTests.swift +93 -0
  73. package/ios/Tests/PlayerPolicyTests.swift +405 -0
  74. package/ios/Tests/{PlaybackCoordinatorTests.swift → PlayerTests.swift} +298 -248
  75. package/ios/Tests/PlayingStateTests.swift +42 -0
  76. package/ios/Tests/QueueManagerTests.swift +76 -107
  77. package/ios/Tests/ReadyValueTests.swift +73 -0
  78. package/ios/Tests/RemoteCommandControllerTests.swift +10 -30
  79. package/ios/Tests/RemoteCommandTests.swift +2 -4
  80. package/ios/Tests/RequestLayerTests.swift +112 -0
  81. package/ios/Tests/RetryLoopTests.swift +270 -0
  82. package/ios/Tests/Support/TestTypes.swift +111 -112
  83. package/ios/Tests/TrackIdTemplateTests.swift +1 -1
  84. package/ios/Tests/URLErrorConnectivityTests.swift +20 -0
  85. package/ios/TrackSelector.swift +1 -1
  86. package/ios/Util/ArtworkImageFetcher.swift +42 -0
  87. package/ios/Util/Emitter.swift +42 -0
  88. package/ios/Util/GenerationMemo.swift +36 -0
  89. package/ios/Util/LRUCache.swift +23 -162
  90. package/ios/Util/NetworkMonitor.swift +1 -1
  91. package/ios/Util/ReadyValue.swift +56 -0
  92. package/ios/Util/SFSymbolRenderer.swift +35 -29
  93. package/ios/Util/SVGProcessor.swift +0 -10
  94. package/lib/commonjs/web/NativeAudioBrowser.js +1 -1
  95. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +1 -1
  96. package/lib/module/web/NativeAudioBrowser.js +1 -1
  97. package/lib/module/web/TrackPlayer/PlayingStateFactory.js +1 -1
  98. package/lib/typescript/src/types/browser.d.ts +3 -3
  99. package/package.json +1 -1
  100. package/src/types/browser.ts +3 -3
  101. package/src/web/NativeAudioBrowser.ts +1 -1
  102. package/src/web/TrackPlayer/PlayingStateFactory.test.ts +1 -1
  103. package/src/web/TrackPlayer/PlayingStateFactory.ts +1 -1
  104. package/ios/Bridge.h +0 -8
  105. package/ios/Browser/MediaResolveComposer.swift +0 -114
  106. package/ios/NowPlayingInfo/MediaItemProperty.swift +0 -78
  107. package/ios/NowPlayingInfo/NowPlayingInfoKeyValue.swift +0 -6
  108. package/ios/NowPlayingInfo/NowPlayingInfoProperty.swift +0 -220
  109. package/ios/Observer/PlayerItemNotificationObserver.swift +0 -119
  110. package/ios/Observer/PlayerItemPropertyObserver.swift +0 -133
  111. package/ios/Observer/PlayerStateObserver.swift +0 -50
  112. package/ios/Observer/PlayerTimeObserver.swift +0 -55
  113. package/ios/Option/PitchAlgorithms.swift +0 -17
  114. package/ios/Option/TimeEventFrequency.swift +0 -18
  115. package/ios/Player/LoadSeekCoordinator.swift +0 -99
  116. package/ios/Player/MediaLoaderDelegate.swift +0 -11
  117. package/ios/Player/PlaybackCoordinator.swift +0 -761
  118. package/ios/Player/PlaybackEffectHandler.swift +0 -54
  119. package/ios/Player/PlaybackErrorHandler.swift +0 -175
  120. package/ios/Player/PlaybackStateStore.swift +0 -27
  121. package/ios/Player/PlayingStateManager.swift +0 -33
  122. package/ios/Player/RetryManager.swift +0 -384
  123. package/ios/Player/SeekCompletionHandler.swift +0 -5
  124. package/ios/Player/SleepTimerHandling.swift +0 -28
  125. package/ios/Support/RNTrackPlayer-Bridging-Header.h +0 -7
  126. package/ios/Tests/LoadSeekCoordinatorTests.swift +0 -327
  127. package/ios/Tests/MediaResolveTests.swift +0 -126
  128. package/ios/Tests/OnceValueTests.swift +0 -79
  129. package/ios/Tests/PlaybackErrorHandlerTests.swift +0 -549
  130. package/ios/Tests/PlaybackStateStoreTests.swift +0 -49
  131. package/ios/Tests/PlayerTimeObserverTests.swift +0 -37
  132. package/ios/Tests/PlayingStateManagerTests.swift +0 -57
  133. package/ios/Tests/RetryManagerTests.swift +0 -178
  134. package/ios/TrackPlayer.swift +0 -1042
  135. package/ios/TrackPlayerCallbacks.swift +0 -53
  136. package/ios/Util/MetadataAdapter.swift +0 -131
  137. package/ios/Util/OnceValue.swift +0 -104
  138. /package/ios/{Player → CarPlay}/MediaIntentCriteria.swift +0 -0
  139. /package/ios/{Browser → CarPlay}/SectionPresentation.swift +0 -0
@@ -1,178 +0,0 @@
1
- import AVFoundation
2
- import Foundation
3
- import Testing
4
-
5
- @testable import AudioBrowserTestable
6
-
7
- private final class MockNetworkStatus: NetworkStatusProviding, @unchecked Sendable {
8
- var isOnline: Bool
9
- init(isOnline: Bool) { self.isOnline = isOnline }
10
- }
11
-
12
- /// The two duration budgets: a never-played load gives up fast while online; a
13
- /// load that produced audio — or a device that is offline — keeps the full
14
- /// recovery budget. Timing-based: budgets are configured in single-digit
15
- /// milliseconds so one executed backoff (~1s) exhausts them.
16
- @Suite("RetryManager budgets")
17
- @MainActor
18
- struct RetryManagerBudgetTests {
19
- /// Returns the mock too — `networkMonitor` is weak, the test must keep it alive.
20
- private func makeManager(
21
- firstConnectMs: Double,
22
- maxMs: Double = 120_000,
23
- online: Bool,
24
- ) -> (RetryManager, MockNetworkStatus) {
25
- let manager = RetryManager()
26
- manager.updatePolicy(from: .second(RetryConfig(
27
- maxRetries: nil,
28
- maxRetryDurationMs: maxMs,
29
- firstConnectMaxRetryDurationMs: firstConnectMs,
30
- )))
31
- let network = MockNetworkStatus(isOnline: online)
32
- manager.networkMonitor = network
33
- return (manager, network)
34
- }
35
-
36
- /// A dead-on-arrival stream: the first attempt runs, and the budget refuses
37
- /// a later one. Budgets are checked before AND after each backoff wait, so
38
- /// the test budget must exceed the first backoff (~1s) for attempt one to
39
- /// execute, and be exceeded by the cumulative ~2.5s before attempt two ends.
40
- /// 1800ms sits ~800ms clear of the first backoff (sleep overshoot on a loaded
41
- /// machine only widens the passing margins, never shrinks them into failure).
42
- @Test func neverPlayedLoad_givesUpAfterFirstConnectBudget() async {
43
- let (manager, network) = makeManager(firstConnectMs: 1800, online: true)
44
- _ = network
45
-
46
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == true)
47
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == false)
48
- }
49
-
50
- /// The same failure timeline on a load that has produced audio keeps
51
- /// retrying: only the recovery budget applies.
52
- @Test func playedLoad_ignoresTheFirstConnectBudget() async {
53
- let (manager, network) = makeManager(firstConnectMs: 500, online: true)
54
- _ = network
55
- manager.hasPlayed = true
56
-
57
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == true)
58
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == true)
59
- }
60
-
61
- /// Offline failures must not burn the first-connect budget (a station tapped
62
- /// in a tunnel parks for connectivity); the budget's clock starts at the
63
- /// first failure observed online, so a dead station still gets a fast
64
- /// verdict after restoration.
65
- @Test func offlineTime_doesNotBurnTheFirstConnectBudget() async {
66
- // 4s: well above the ~2.3s backoff of the first online attempt (the ladder
67
- // kept climbing while offline), well below the ~5.6s cumulative after it.
68
- let (manager, network) = makeManager(firstConnectMs: 4000, online: false)
69
-
70
- // Offline: the short budget never engages, attempts keep running.
71
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == true)
72
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == true)
73
-
74
- // Connectivity returns: the short clock starts now — one more attempt
75
- // runs, then the budget is spent.
76
- network.isOnline = true
77
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == true)
78
- #expect(await manager.attemptRetry(startFromCurrentTime: false) == false)
79
- }
80
-
81
- /// `reset()` (track change / budget refill) restarts the clocks but must NOT
82
- /// touch `hasPlayed` — the refill runs mid-play, and clearing the flag there
83
- /// would hand a playing stream the short budget on its next drop.
84
- @Test func reset_keepsHasPlayed() {
85
- let manager = RetryManager()
86
- manager.hasPlayed = true
87
- manager.reset()
88
- #expect(manager.hasPlayed == true)
89
- }
90
- }
91
-
92
- /// Which failures earn another attempt. The reference is Android's
93
- /// `RetryLoadErrorHandlingPolicy.classifyError`: same HTTP set, and the same
94
- /// default of treating an unrecognized failure as transient. Defaulting the
95
- /// other way made `retry: true` largely inert on iOS, since AVFoundation
96
- /// reports most stream failures as opaque CoreMedia errors, not `URLError`.
97
- @Suite("RetryManager error classification")
98
- @MainActor
99
- struct RetryManagerClassificationTests {
100
- private func makeManager() -> RetryManager {
101
- let manager = RetryManager()
102
- manager.updatePolicy(from: .first(true))
103
- return manager
104
- }
105
-
106
- /// `AVPlayerItem.error` never carries the status, so the caller reads it off
107
- /// the item's error log and passes it in. It outranks the opaque error.
108
- @Test(arguments: [408, 429, 500, 502, 503, 504])
109
- func retryableHTTPStatus_retries(status: Int) {
110
- let opaque = NSError(domain: "CoreMediaErrorDomain", code: -12660)
111
- #expect(makeManager().isRetryable(opaque, httpStatusCode: status) == true)
112
- }
113
-
114
- /// A retry cannot conjure content that isn't there, whatever the error says.
115
- @Test(arguments: [400, 401, 403, 404, 410, 451])
116
- func permanentHTTPStatus_doesNotRetry(status: Int) {
117
- let transient = URLError(.timedOut)
118
- #expect(makeManager().isRetryable(transient, httpStatusCode: status) == false)
119
- }
120
-
121
- @Test func transientURLError_retries() {
122
- #expect(makeManager().isRetryable(URLError(.networkConnectionLost), httpStatusCode: nil) == true)
123
- }
124
-
125
- @Test func fatalURLError_doesNotRetry() {
126
- #expect(makeManager().isRetryable(URLError(.badURL), httpStatusCode: nil) == false)
127
- }
128
-
129
- /// The regression this suite exists for: an opaque CoreMedia failure with no
130
- /// status to read used to be terminal, so a station that dropped mid-song
131
- /// never came back on iOS while Android reconnected.
132
- @Test func opaqueCoreMediaError_retries() {
133
- let error = NSError(domain: "CoreMediaErrorDomain", code: -12939)
134
- #expect(makeManager().isRetryable(error, httpStatusCode: nil) == true)
135
- }
136
-
137
- /// `AVErrorUnknown` is the wrapper AVFoundation puts around transport
138
- /// failures, so it must stay retryable even though it is an AVError.
139
- @Test func avErrorUnknown_retries() {
140
- let error = NSError(domain: AVFoundationErrorDomain, code: AVError.unknown.rawValue)
141
- #expect(makeManager().isRetryable(error, httpStatusCode: nil) == true)
142
- }
143
-
144
- @Test(arguments: [
145
- AVError.fileFormatNotRecognized.rawValue,
146
- AVError.failedToParse.rawValue,
147
- AVError.decodeFailed.rawValue,
148
- ])
149
- func unusableMedia_doesNotRetry(code: Int) {
150
- let error = NSError(domain: AVFoundationErrorDomain, code: code)
151
- #expect(makeManager().isRetryable(error, httpStatusCode: nil) == false)
152
- }
153
-
154
- /// AVFoundation wraps the real cause rather than surfacing it, which is why
155
- /// the classifier walks `NSUnderlyingError` instead of inspecting only the
156
- /// outermost error.
157
- @Test func underlyingURLError_isFound() {
158
- let manager = makeManager()
159
- let wrapped = NSError(
160
- domain: AVFoundationErrorDomain,
161
- code: AVError.unknown.rawValue,
162
- userInfo: [NSUnderlyingErrorKey: URLError(.badURL)],
163
- )
164
- #expect(manager.isRetryable(wrapped, httpStatusCode: nil) == false)
165
-
166
- let transient = NSError(
167
- domain: AVFoundationErrorDomain,
168
- code: AVError.unknown.rawValue,
169
- userInfo: [NSUnderlyingErrorKey: URLError(.dnsLookupFailed)],
170
- )
171
- #expect(manager.isRetryable(transient, httpStatusCode: nil) == true)
172
- }
173
-
174
- /// Nothing to classify: no error and no status stays terminal.
175
- @Test func nilError_doesNotRetry() {
176
- #expect(makeManager().isRetryable(nil, httpStatusCode: nil) == false)
177
- }
178
- }