react-native-audio-browser 0.2.0-next.18 → 0.2.0-next.19
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/build.gradle +2 -1
- package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
- package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +163 -50
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/Player.js +232 -39
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +24 -4
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +12 -12
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
- package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +163 -50
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
- package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/module/web/TrackPlayer/Player.js +232 -39
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +24 -4
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +12 -12
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/http/RequestConfigBuilder.js +110 -27
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/http/followMediaRedirect.js +215 -0
- package/lib/module/web/http/followMediaRedirect.js.map +1 -0
- package/lib/typescript/src/types/browser.d.ts +13 -4
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/types/browser.ts +13 -4
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
- package/src/web/NativeAudioBrowser.ts +193 -60
- package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
- package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
- package/src/web/TrackPlayer/Player.load.test.ts +137 -1
- package/src/web/TrackPlayer/Player.ts +256 -37
- package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
- package/src/web/__tests__/lifecycle.test.ts +345 -0
- package/src/web/__tests__/lifecycleHarness.ts +351 -0
- package/src/web/__tests__/loadEdgeCases.test.ts +195 -0
- package/src/web/__tests__/loadSupersession.test.ts +175 -0
- package/src/web/__tests__/retry.test.ts +169 -0
- package/src/web/browser/BrowserManager.ts +26 -4
- package/src/web/browser/SearchManager.ts +16 -16
- package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
- package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
- package/src/web/http/RequestConfigBuilder.test.ts +241 -1
- package/src/web/http/RequestConfigBuilder.ts +140 -33
- package/src/web/http/followMediaRedirect.test.ts +289 -0
- package/src/web/http/followMediaRedirect.ts +253 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a media URL that is authenticated by a header.
|
|
5
|
+
*
|
|
6
|
+
* Shaka only routes manifest requests through its networking engine, where a
|
|
7
|
+
* request filter can attach headers. A progressive file is played by assigning
|
|
8
|
+
* `mediaElement.src`, and a media element cannot send custom headers — so such
|
|
9
|
+
* a URL is unplayable that way. Following the redirect here yields the signed,
|
|
10
|
+
* public URL these endpoints hand back, which needs no auth of its own.
|
|
11
|
+
*
|
|
12
|
+
* Best effort and bounded: anything that fails — including taking too long, see
|
|
13
|
+
* {@link PROBE_TIMEOUT_MS} — falls back to the original URL, so the load fails
|
|
14
|
+
* or succeeds on its own terms rather than ours. It must never be the reason a
|
|
15
|
+
* load hangs: `load()` awaits this before Shaka sees the URL at all.
|
|
16
|
+
*
|
|
17
|
+
* Gated, because the probe is not free: a HEAD at a third-party stream host
|
|
18
|
+
* costs a CORS preflight, and most will refuse it, so an app that never
|
|
19
|
+
* authenticates its media would pay a round trip on every play for a result it
|
|
20
|
+
* always discards. It runs only for headers that look like media credentials
|
|
21
|
+
* ({@link isMediaAuth}), on media a media element actually has to play
|
|
22
|
+
* ({@link isManifestUrl}), and never on a URL the browser resolves locally.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** URLs the browser resolves locally; there is no redirect to follow. */
|
|
26
|
+
const isLocalUrl = url => /^(file|blob|data|mediasource):/i.test(url);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extensions Shaka types as a manifest, from its own `EXTENSIONS_TO_MIME_TYPES_`
|
|
30
|
+
* map: HLS, DASH and Smooth Streaming.
|
|
31
|
+
*/
|
|
32
|
+
const MANIFEST_EXTENSIONS = new Set(['m3u8', 'mpd', 'ism']);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether Shaka will parse this as a manifest rather than hand it to the media
|
|
36
|
+
* element — in which case the probe has nothing to offer and something to cost.
|
|
37
|
+
*
|
|
38
|
+
* A manifest goes through Shaka's networking engine, where the request filter
|
|
39
|
+
* attaches the headers to the manifest, its segments and any key requests. The
|
|
40
|
+
* probe cannot improve on that, and it actively breaks it: on a cross-origin
|
|
41
|
+
* redirect the probe clears the headers, so the segments that Android still
|
|
42
|
+
* authenticates would go out bare and 401. So this is a correctness fix as much
|
|
43
|
+
* as it is one fewer round trip.
|
|
44
|
+
*
|
|
45
|
+
* Extension-based, and therefore partial — Shaka guesses the same way
|
|
46
|
+
* (`NetworkingUtils.getExtension`), but plenty of HLS endpoints have no
|
|
47
|
+
* extension at all. Those still get probed, which is the status quo rather than
|
|
48
|
+
* a regression. Nothing on `Track` carries a MIME type to do better with.
|
|
49
|
+
*/
|
|
50
|
+
const isManifestUrl = url => {
|
|
51
|
+
const path = url.split(/[?#]/)[0] ?? '';
|
|
52
|
+
const filename = path.slice(path.lastIndexOf('/') + 1);
|
|
53
|
+
const pieces = filename.split('.');
|
|
54
|
+
return pieces.length > 1 && MANIFEST_EXTENSIONS.has(pieces[pieces.length - 1].toLowerCase());
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* How long the probe may take before the original URL is played instead.
|
|
58
|
+
*
|
|
59
|
+
* The same 8s the Android media client allows a connect or a read
|
|
60
|
+
* (`MediaFactory.mediaHttpClient`), which is generous for a HEAD but errs the
|
|
61
|
+
* safe way: a slow-but-working auth endpoint should not be abandoned, since
|
|
62
|
+
* giving up means playing a URL that will 401.
|
|
63
|
+
*/
|
|
64
|
+
const PROBE_TIMEOUT_MS = 8_000;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* `AbortSignal.timeout` where it exists (Safari 16+, Chrome 103+, Node 17.3+),
|
|
68
|
+
* else a hand-rolled equivalent, else nothing — an environment with neither
|
|
69
|
+
* still gets the deadline below, just without cancelling the request.
|
|
70
|
+
*/
|
|
71
|
+
const timeoutSignal = ms => {
|
|
72
|
+
if (typeof AbortSignal === 'undefined') return undefined;
|
|
73
|
+
if (typeof AbortSignal.timeout === 'function') return AbortSignal.timeout(ms);
|
|
74
|
+
if (typeof AbortController === 'undefined') return undefined;
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
setTimeout(() => controller.abort(), ms);
|
|
77
|
+
return controller.signal;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The probe, with a deadline. `load()` awaits this before Shaka is asked to
|
|
82
|
+
* play anything, so an unreachable host would otherwise stall the load
|
|
83
|
+
* indefinitely — no audio, and no error to show for it.
|
|
84
|
+
*
|
|
85
|
+
* Two mechanisms, each with its own job: the signal cancels the request, so a
|
|
86
|
+
* dead host does not hold a connection open behind us, and the race bounds *us*
|
|
87
|
+
* even where the fetch implementation ignores the signal. Whichever fires, the
|
|
88
|
+
* caller treats it as any other probe failure and plays the original URL.
|
|
89
|
+
*/
|
|
90
|
+
const fetchWithDeadline = async (doFetch, url, headers, timeoutMs) => {
|
|
91
|
+
let timer;
|
|
92
|
+
try {
|
|
93
|
+
return await Promise.race([doFetch(url, {
|
|
94
|
+
method: 'HEAD',
|
|
95
|
+
headers,
|
|
96
|
+
redirect: 'follow',
|
|
97
|
+
signal: timeoutSignal(timeoutMs)
|
|
98
|
+
}), new Promise((_resolve, reject) => {
|
|
99
|
+
timer = setTimeout(() => reject(new Error(`Media redirect probe timed out (${timeoutMs}ms)`)), timeoutMs);
|
|
100
|
+
})]);
|
|
101
|
+
} finally {
|
|
102
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether these headers look like a media credential — the only thing the
|
|
108
|
+
* probe can fix, and the only thing worth a preflight.
|
|
109
|
+
*
|
|
110
|
+
* A header the `media` layer set is media-specific by construction. Otherwise
|
|
111
|
+
* only `Authorization` counts: an app authenticating one API for browse and
|
|
112
|
+
* media alike puts it on the shared `request` layer, and that is the common
|
|
113
|
+
* shape. Shared plumbing that also rides browse requests — an `X-Client-Version`
|
|
114
|
+
* — is not a credential and must not trigger the probe.
|
|
115
|
+
*
|
|
116
|
+
* The residual case is a shared layer carrying a custom auth header, say
|
|
117
|
+
* `X-Api-Key`, for media that needs it: move it to the `media` layer and the
|
|
118
|
+
* probe runs.
|
|
119
|
+
*/
|
|
120
|
+
const isMediaAuth = (headers, mediaLayerHeaders) => mediaLayerHeaders || Object.keys(headers).some(key => key.toLowerCase() === 'authorization');
|
|
121
|
+
/**
|
|
122
|
+
* Origin of an http(s) URL, or undefined when it cannot be determined.
|
|
123
|
+
*
|
|
124
|
+
* Relative URLs are resolved against the document, because that is what the
|
|
125
|
+
* probe's own `fetch` does — a `src` of `/api/authed/7` is a request to the
|
|
126
|
+
* page's origin, so the credential went there. Without this a relative `src`
|
|
127
|
+
* has no origin, never matches the absolute URL the response reports, and the
|
|
128
|
+
* headers are dropped from every load that uses one.
|
|
129
|
+
*
|
|
130
|
+
* Parsed rather than pattern-matched so the comparison normalizes the way
|
|
131
|
+
* Android's `CrossOriginHeaderStripper.sameOrigin` does: `URL.host` omits a
|
|
132
|
+
* default port, so `https://a.com` and `https://a.com:443` are one origin, and
|
|
133
|
+
* it excludes userinfo, so `https://user@a.com` is not a third.
|
|
134
|
+
*/
|
|
135
|
+
const originOf = url => {
|
|
136
|
+
try {
|
|
137
|
+
const base = typeof location === 'undefined' ? undefined : location.href;
|
|
138
|
+
const {
|
|
139
|
+
protocol,
|
|
140
|
+
host
|
|
141
|
+
} = new URL(url, base);
|
|
142
|
+
if (protocol !== 'http:' && protocol !== 'https:') return undefined;
|
|
143
|
+
return `${protocol}//${host}`;
|
|
144
|
+
} catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Whether the redirect left the origin the credential was addressed to.
|
|
151
|
+
*
|
|
152
|
+
* Only when both origins are known and differ. An origin we could not work out
|
|
153
|
+
* — a relative URL with no document base, outside a browser — is not evidence
|
|
154
|
+
* of a hop, and treating it as one is how the headers went missing before.
|
|
155
|
+
*/
|
|
156
|
+
const leftTheOrigin = (from, to) => {
|
|
157
|
+
const origin = originOf(from);
|
|
158
|
+
return origin !== undefined && originOf(to) !== origin;
|
|
159
|
+
};
|
|
160
|
+
export async function followMediaRedirect(url, headers, {
|
|
161
|
+
fetchImpl,
|
|
162
|
+
mediaLayerHeaders = false,
|
|
163
|
+
timeoutMs = PROBE_TIMEOUT_MS
|
|
164
|
+
} = {}) {
|
|
165
|
+
if (!headers || Object.keys(headers).length === 0) return {
|
|
166
|
+
src: url,
|
|
167
|
+
headers
|
|
168
|
+
};
|
|
169
|
+
if (!isMediaAuth(headers, mediaLayerHeaders)) return {
|
|
170
|
+
src: url,
|
|
171
|
+
headers
|
|
172
|
+
};
|
|
173
|
+
if (isManifestUrl(url)) return {
|
|
174
|
+
src: url,
|
|
175
|
+
headers
|
|
176
|
+
};
|
|
177
|
+
if (isLocalUrl(url)) return {
|
|
178
|
+
src: url,
|
|
179
|
+
headers
|
|
180
|
+
};
|
|
181
|
+
const doFetch = fetchImpl ?? globalThis.fetch;
|
|
182
|
+
if (typeof doFetch !== 'function') return {
|
|
183
|
+
src: url,
|
|
184
|
+
headers
|
|
185
|
+
};
|
|
186
|
+
try {
|
|
187
|
+
// HEAD walks the chain without tripping download counting, which keys off
|
|
188
|
+
// ranged GETs. Deadlined, because the load waits on it.
|
|
189
|
+
const response = await fetchWithDeadline(doFetch, url, headers, timeoutMs);
|
|
190
|
+
const src = response.url || url;
|
|
191
|
+
|
|
192
|
+
// The status belongs to the *last* hop, so it only speaks for the
|
|
193
|
+
// authenticating endpoint when there was no redirect. Once one has
|
|
194
|
+
// happened, hop one authenticated and handed us its target, and a refusal
|
|
195
|
+
// past that is routinely about the method rather than the credential:
|
|
196
|
+
// presigned S3 and GCS URLs sign it — SigV4's canonical request opens with
|
|
197
|
+
// HTTPMethod, GCS V4's with HTTP_VERB — so a URL signed for GET answers a
|
|
198
|
+
// HEAD with 403. Keep the signed URL, which is the whole point of the walk,
|
|
199
|
+
// and let a real error surface through the player.
|
|
200
|
+
if (!response.ok && src === url) return {
|
|
201
|
+
src: url,
|
|
202
|
+
headers
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
src,
|
|
206
|
+
headers: leftTheOrigin(url, src) ? undefined : headers
|
|
207
|
+
};
|
|
208
|
+
} catch {
|
|
209
|
+
return {
|
|
210
|
+
src: url,
|
|
211
|
+
headers
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=followMediaRedirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["isLocalUrl","url","test","MANIFEST_EXTENSIONS","Set","isManifestUrl","path","split","filename","slice","lastIndexOf","pieces","length","has","toLowerCase","PROBE_TIMEOUT_MS","timeoutSignal","ms","AbortSignal","undefined","timeout","AbortController","controller","setTimeout","abort","signal","fetchWithDeadline","doFetch","headers","timeoutMs","timer","Promise","race","method","redirect","_resolve","reject","Error","clearTimeout","isMediaAuth","mediaLayerHeaders","Object","keys","some","key","originOf","base","location","href","protocol","host","URL","leftTheOrigin","from","to","origin","followMediaRedirect","fetchImpl","src","globalThis","fetch","response","ok"],"sourceRoot":"../../../../src","sources":["web/http/followMediaRedirect.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -372,8 +372,16 @@ export interface ImageQueryParams {
|
|
|
372
372
|
* When a request is made, configs are merged in this order (later overrides earlier):
|
|
373
373
|
* 1. `request` (base config) - shared settings like user agent, common headers
|
|
374
374
|
* 2. `media`/`artwork` config - resource-specific settings
|
|
375
|
-
* 3.
|
|
376
|
-
*
|
|
375
|
+
* 3. the per-track callbacks, in an order that differs by kind:
|
|
376
|
+
* - `artwork`: `resolve(track)`, then `transform(request)` last
|
|
377
|
+
* - `media`: `transform(request)`, then **`resolve(track)` last** — the
|
|
378
|
+
* per-track resolver is the most specific layer and wins over the
|
|
379
|
+
* transform, on every platform
|
|
380
|
+
*
|
|
381
|
+
* The `media` order is deliberate: a resolver minting a per-track signed URL
|
|
382
|
+
* must not be overwritten by the static layer that shaped the unsigned one. It
|
|
383
|
+
* means a `media.transform` cannot sign what `resolve` produced — do that work
|
|
384
|
+
* inside `resolve`, or move the signing to `transform` and drop `resolve`.
|
|
377
385
|
*
|
|
378
386
|
* ### Usage Patterns
|
|
379
387
|
*
|
|
@@ -418,8 +426,9 @@ export interface MediaRequestConfig extends TransformableRequestConfig {
|
|
|
418
426
|
* Receives the full Track object, allowing URL generation based on
|
|
419
427
|
* track metadata (id, artist, album, src, etc.).
|
|
420
428
|
*
|
|
421
|
-
*
|
|
422
|
-
* `
|
|
429
|
+
* Runs **last** for media — after `transform`, and winning over it on every
|
|
430
|
+
* field including `path`. See the hierarchy note on this interface; artwork
|
|
431
|
+
* runs them the other way round.
|
|
423
432
|
*
|
|
424
433
|
* @param track - The track being requested
|
|
425
434
|
* @returns Request configuration for this specific track (sync or async)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../../src/types/browser.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../../src/types/browser.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/types/browser.ts
CHANGED
|
@@ -432,8 +432,16 @@ export interface ImageQueryParams {
|
|
|
432
432
|
* When a request is made, configs are merged in this order (later overrides earlier):
|
|
433
433
|
* 1. `request` (base config) - shared settings like user agent, common headers
|
|
434
434
|
* 2. `media`/`artwork` config - resource-specific settings
|
|
435
|
-
* 3.
|
|
436
|
-
*
|
|
435
|
+
* 3. the per-track callbacks, in an order that differs by kind:
|
|
436
|
+
* - `artwork`: `resolve(track)`, then `transform(request)` last
|
|
437
|
+
* - `media`: `transform(request)`, then **`resolve(track)` last** — the
|
|
438
|
+
* per-track resolver is the most specific layer and wins over the
|
|
439
|
+
* transform, on every platform
|
|
440
|
+
*
|
|
441
|
+
* The `media` order is deliberate: a resolver minting a per-track signed URL
|
|
442
|
+
* must not be overwritten by the static layer that shaped the unsigned one. It
|
|
443
|
+
* means a `media.transform` cannot sign what `resolve` produced — do that work
|
|
444
|
+
* inside `resolve`, or move the signing to `transform` and drop `resolve`.
|
|
437
445
|
*
|
|
438
446
|
* ### Usage Patterns
|
|
439
447
|
*
|
|
@@ -478,8 +486,9 @@ export interface MediaRequestConfig extends TransformableRequestConfig {
|
|
|
478
486
|
* Receives the full Track object, allowing URL generation based on
|
|
479
487
|
* track metadata (id, artist, album, src, etc.).
|
|
480
488
|
*
|
|
481
|
-
*
|
|
482
|
-
* `
|
|
489
|
+
* Runs **last** for media — after `transform`, and winning over it on every
|
|
490
|
+
* field including `path`. See the hierarchy note on this interface; artwork
|
|
491
|
+
* runs them the other way round.
|
|
483
492
|
*
|
|
484
493
|
* @param track - The track being requested
|
|
485
494
|
* @returns Request configuration for this specific track (sync or async)
|
|
@@ -156,9 +156,14 @@ describe('NativeAudioBrowser stop vs in-flight load', () => {
|
|
|
156
156
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
157
157
|
|
|
158
158
|
// The stale load must not revive the player: previously its continuation
|
|
159
|
-
// re-armed _isStopped = false and
|
|
160
|
-
expect(browser.current).toBeUndefined()
|
|
159
|
+
// re-armed _isStopped = false and re-entered super.load() after stop().
|
|
161
160
|
expect(browser.getPlayback().state).toBe('stopped')
|
|
161
|
+
// `current` is the track, not undefined: the transition is announced when
|
|
162
|
+
// load() is called, before the URL resolves, so by the time stop() runs the
|
|
163
|
+
// track is already active — and `Player.stop()` deliberately keeps it so
|
|
164
|
+
// play() can resume. Android behaves the same way; it was only undefined
|
|
165
|
+
// here because the async gap meant it had never been set.
|
|
166
|
+
expect(browser.current?.id).toBe(track.id)
|
|
162
167
|
})
|
|
163
168
|
})
|
|
164
169
|
|
|
@@ -299,3 +304,228 @@ describe('NativeAudioBrowser setQueue start position', () => {
|
|
|
299
304
|
expect(skips).toEqual([[0, 30]])
|
|
300
305
|
})
|
|
301
306
|
})
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Android emits the active-track transition from ExoPlayer's
|
|
310
|
+
* `onMediaItemTransition`, which fires when the media item is set — before the
|
|
311
|
+
* load has resolved, and regardless of whether it ever does. iOS drives it from
|
|
312
|
+
* the queue coordinator, which behaves the same. Web announcing it only on
|
|
313
|
+
* success meant a track that failed to load never became the active track for
|
|
314
|
+
* consumers, so a UI bound to `useActiveTrack()` had nothing to render and a
|
|
315
|
+
* playback error had nowhere to appear.
|
|
316
|
+
*/
|
|
317
|
+
describe('NativeAudioBrowser active track announced on the load attempt', () => {
|
|
318
|
+
class FailingLoadBrowser extends TestBrowser {
|
|
319
|
+
constructor(loadResult: () => Promise<void>) {
|
|
320
|
+
super()
|
|
321
|
+
this.player = {
|
|
322
|
+
load: loadResult,
|
|
323
|
+
unload: () => Promise.resolve()
|
|
324
|
+
} as unknown as typeof this.player
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** getNowPlaying() reads progress off the element, which needs buffered ranges. */
|
|
328
|
+
withProgressCapableElement(): this {
|
|
329
|
+
this.element = {
|
|
330
|
+
play: () => Promise.resolve(),
|
|
331
|
+
pause: () => {},
|
|
332
|
+
currentTime: 0,
|
|
333
|
+
duration: 100,
|
|
334
|
+
buffered: { length: 0, end: () => 0 }
|
|
335
|
+
} as unknown as HTMLMediaElement
|
|
336
|
+
return this
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const tick = () => new Promise((resolve) => setTimeout(resolve, 0))
|
|
341
|
+
|
|
342
|
+
it('announces the transition even when the load fails', async () => {
|
|
343
|
+
const browser = new FailingLoadBrowser(() =>
|
|
344
|
+
Promise.reject(new Error('401'))
|
|
345
|
+
)
|
|
346
|
+
const announced: (Track | undefined)[] = []
|
|
347
|
+
browser.onPlaybackActiveTrackChanged = (event) =>
|
|
348
|
+
announced.push(event.track)
|
|
349
|
+
|
|
350
|
+
browser.load(track)
|
|
351
|
+
await tick()
|
|
352
|
+
|
|
353
|
+
expect(announced.map((t) => t?.src)).toContain(track.src)
|
|
354
|
+
expect(browser.getActiveTrack()?.src).toBe(track.src)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('announces the transition exactly once on a successful load', async () => {
|
|
358
|
+
const browser = new FailingLoadBrowser(() => Promise.resolve())
|
|
359
|
+
const announced: (Track | undefined)[] = []
|
|
360
|
+
browser.onPlaybackActiveTrackChanged = (event) =>
|
|
361
|
+
announced.push(event.track)
|
|
362
|
+
|
|
363
|
+
browser.load(track)
|
|
364
|
+
await tick()
|
|
365
|
+
|
|
366
|
+
expect(announced).toHaveLength(1)
|
|
367
|
+
expect(announced[0]?.src).toBe(track.src)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('does not report a load failure when the announcement throws', async () => {
|
|
371
|
+
// The announcement runs after the load has committed, so a consumer's own
|
|
372
|
+
// throwing handler must not travel back up and error a load that worked.
|
|
373
|
+
const browser = new FailingLoadBrowser(() => Promise.resolve())
|
|
374
|
+
browser.onPlaybackActiveTrackChanged = () => {
|
|
375
|
+
throw new Error('consumer handler blew up')
|
|
376
|
+
}
|
|
377
|
+
const errors: (string | undefined)[] = []
|
|
378
|
+
browser.onPlaybackError = (event) => errors.push(event.error?.code)
|
|
379
|
+
|
|
380
|
+
browser.load(track)
|
|
381
|
+
await tick()
|
|
382
|
+
|
|
383
|
+
expect(errors).not.toContain('load-error')
|
|
384
|
+
expect(browser.getPlayback().state).not.toBe('error')
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('announces the transition before the error for a track with no src', async () => {
|
|
388
|
+
// super.load() sets the error state synchronously when there is nothing to
|
|
389
|
+
// play, so announcing after it put onPlaybackError first — and a consumer
|
|
390
|
+
// that clears its banner on a track change wiped the error. Native cannot
|
|
391
|
+
// invert these: Android fires onMediaItemTransition at prepare, onPlayerError
|
|
392
|
+
// only after.
|
|
393
|
+
const browser = new FailingLoadBrowser(() => Promise.resolve())
|
|
394
|
+
const order: string[] = []
|
|
395
|
+
browser.onPlaybackActiveTrackChanged = () => order.push('track-changed')
|
|
396
|
+
browser.onPlaybackError = (event) => {
|
|
397
|
+
if (event.error) order.push('error')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
browser.load({ id: 'no-src', title: 'No src' })
|
|
401
|
+
await tick()
|
|
402
|
+
|
|
403
|
+
expect(order).toEqual(['track-changed', 'error'])
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('does not report loading before the active track change', async () => {
|
|
407
|
+
// Native emits these in the opposite order — Android's active-track
|
|
408
|
+
// callback comes from onMediaItemTransition, its LOADING from onEvents,
|
|
409
|
+
// which ExoPlayer runs after the individual callbacks. Dispatching
|
|
410
|
+
// `trackLoading` when load() is called inverted that, handing a consumer
|
|
411
|
+
// that reads getActiveTrack() on the loading edge the outgoing track.
|
|
412
|
+
const browser = new FailingLoadBrowser(() => Promise.resolve())
|
|
413
|
+
const order: string[] = []
|
|
414
|
+
browser.onPlaybackChanged = (playback) => {
|
|
415
|
+
if (playback.state === 'loading') order.push('loading')
|
|
416
|
+
}
|
|
417
|
+
browser.onPlaybackActiveTrackChanged = () => order.push('active-track')
|
|
418
|
+
|
|
419
|
+
browser.load(track)
|
|
420
|
+
// both land synchronously, in native's order: the transition is announced
|
|
421
|
+
// when the caller asks for it, and `loading` covers the resolution that
|
|
422
|
+
// follows
|
|
423
|
+
expect(order).toEqual(['active-track', 'loading'])
|
|
424
|
+
|
|
425
|
+
await tick()
|
|
426
|
+
expect(order.indexOf('active-track')).toBeLessThan(order.indexOf('loading'))
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('still publishes now-playing when the announcement throws', async () => {
|
|
430
|
+
// The two are independent: a consumer's throwing handler must not leave the
|
|
431
|
+
// lock screen showing the previous track.
|
|
432
|
+
const browser = new FailingLoadBrowser(() =>
|
|
433
|
+
Promise.resolve()
|
|
434
|
+
).withProgressCapableElement()
|
|
435
|
+
browser.onPlaybackActiveTrackChanged = () => {
|
|
436
|
+
throw new Error('consumer handler blew up')
|
|
437
|
+
}
|
|
438
|
+
const published: (string | undefined)[] = []
|
|
439
|
+
browser.onNowPlayingChanged = (metadata) => published.push(metadata.title)
|
|
440
|
+
|
|
441
|
+
browser.load(track)
|
|
442
|
+
await tick()
|
|
443
|
+
|
|
444
|
+
expect(published).toContain(track.title)
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* The media path reads the *resolved* request layer, so unlike browse it can be
|
|
450
|
+
* handed a rejected resolver. A track whose `src` is already absolute needs no
|
|
451
|
+
* layer at all, and native degrades rather than failing — `getMediaRequestConfig`
|
|
452
|
+
* returns null and the original URL plays on.
|
|
453
|
+
*/
|
|
454
|
+
describe('NativeAudioBrowser media load vs a failing request resolver', () => {
|
|
455
|
+
const tick = () => new Promise((resolve) => setTimeout(resolve, 0))
|
|
456
|
+
|
|
457
|
+
it('still plays an absolute src when the request resolver rejects', async () => {
|
|
458
|
+
const browser = new TestBrowser()
|
|
459
|
+
browser.configuration = {
|
|
460
|
+
requestResolver: () => Promise.reject(new Error('token refresh failed'))
|
|
461
|
+
}
|
|
462
|
+
const errors: (string | undefined)[] = []
|
|
463
|
+
browser.onPlaybackError = (event) => errors.push(event.error?.code)
|
|
464
|
+
|
|
465
|
+
browser.load(track)
|
|
466
|
+
await tick()
|
|
467
|
+
|
|
468
|
+
expect(errors).not.toContain('load-error')
|
|
469
|
+
expect(browser.getActiveTrack()?.src).toBe(track.src)
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Both native platforms clear the playback error when the state leaves `error`,
|
|
475
|
+
* and say so: Android emits `onPlaybackError(null)` when leaving ERROR, iOS
|
|
476
|
+
* clears `playbackError` before emitting the state change. Web derived the
|
|
477
|
+
* cleared value correctly from state but never emitted it, so a consumer
|
|
478
|
+
* subscribed to the event kept showing a stale error after the next track
|
|
479
|
+
* loaded fine.
|
|
480
|
+
*/
|
|
481
|
+
describe('NativeAudioBrowser playback error lifecycle', () => {
|
|
482
|
+
class ErrorStateBrowser extends TestBrowser {
|
|
483
|
+
setErrorState(code: string): void {
|
|
484
|
+
this.state = {
|
|
485
|
+
state: 'error',
|
|
486
|
+
error: { kind: 'unknown', code, message: code }
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
setPlainState(state: PlaybackState): void {
|
|
491
|
+
this.state = { state }
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const collect = () => {
|
|
496
|
+
const browser = new ErrorStateBrowser()
|
|
497
|
+
const events: (string | undefined)[] = []
|
|
498
|
+
browser.onPlaybackError = (event) => events.push(event.error?.code)
|
|
499
|
+
return { browser, events }
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
it('emits the error, then emits the clear when leaving the error state', () => {
|
|
503
|
+
const { browser, events } = collect()
|
|
504
|
+
|
|
505
|
+
browser.setErrorState('shaka-1001')
|
|
506
|
+
expect(events).toEqual(['shaka-1001'])
|
|
507
|
+
expect(browser.getPlaybackError()?.code).toBe('shaka-1001')
|
|
508
|
+
|
|
509
|
+
browser.setPlainState('loading')
|
|
510
|
+
expect(events).toEqual(['shaka-1001', undefined])
|
|
511
|
+
expect(browser.getPlaybackError()).toBeUndefined()
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('does not emit a clear when no error preceded the transition', () => {
|
|
515
|
+
const { browser, events } = collect()
|
|
516
|
+
|
|
517
|
+
browser.setPlainState('loading')
|
|
518
|
+
browser.setPlainState('playing')
|
|
519
|
+
|
|
520
|
+
expect(events).toEqual([])
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
it('emits the new error when one error replaces another', () => {
|
|
524
|
+
const { browser, events } = collect()
|
|
525
|
+
|
|
526
|
+
browser.setErrorState('shaka-1001')
|
|
527
|
+
browser.setErrorState('shaka-3016')
|
|
528
|
+
|
|
529
|
+
expect(events).toEqual(['shaka-1001', 'shaka-3016'])
|
|
530
|
+
})
|
|
531
|
+
})
|