react-native-audio-browser 0.2.0-next.28 → 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/browser/BrowserUrlResolution.kt +3 -4
- 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
|
@@ -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
|
-
}
|