@5stones/react-native-audio-browser 0.1.3 → 0.1.5
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/player/MediaFactory.kt +11 -71
- package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +125 -0
- package/ios/Browser/BrowserManager.swift +17 -2
- package/ios/HybridAudioBrowser.swift +7 -0
- package/ios/TrackPlayer.swift +150 -31
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/equalizer.js.map +1 -1
- package/lib/commonjs/features/errors.js.map +1 -1
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/nowPlaying.js.map +1 -1
- package/lib/commonjs/features/output.js.map +1 -1
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +1 -1
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/utils/useDebug.js.map +1 -1
- package/lib/commonjs/utils/validation.js +23 -0
- package/lib/commonjs/utils/validation.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +53 -15
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/SimpleRouter.js +5 -4
- package/lib/commonjs/web/SimpleRouter.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/Player.js +49 -24
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +54 -45
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/State.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +10 -17
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/http/HttpClient.js +15 -2
- package/lib/commonjs/web/http/HttpClient.js.map +1 -1
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/player/NowPlayingManager.js +9 -0
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/player/OptionsManager.js +1 -1
- package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/equalizer.js.map +1 -1
- package/lib/module/features/errors.js.map +1 -1
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/nowPlaying.js +1 -0
- package/lib/module/features/nowPlaying.js.map +1 -1
- package/lib/module/features/output.js.map +1 -1
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +1 -1
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/utils/useDebug.js.map +1 -1
- package/lib/module/utils/validation.js +18 -0
- package/lib/module/utils/validation.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +53 -15
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/SimpleRouter.js +5 -4
- package/lib/module/web/SimpleRouter.js.map +1 -1
- package/lib/module/web/TrackPlayer/Player.js +49 -24
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js +54 -45
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +1 -1
- package/lib/module/web/TrackPlayer/State.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +10 -17
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/http/HttpClient.js +15 -2
- package/lib/module/web/http/HttpClient.js.map +1 -1
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/player/NowPlayingManager.js +9 -0
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/player/OptionsManager.js +1 -1
- package/lib/module/web/player/OptionsManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/equalizer.d.ts.map +1 -1
- package/lib/typescript/src/features/errors.d.ts.map +1 -1
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +1 -1
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
- package/lib/typescript/src/features/output.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +1 -1
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +1 -1
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
- package/lib/typescript/src/utils/useDebug.d.ts +1 -1
- package/lib/typescript/src/utils/useDebug.d.ts.map +1 -1
- package/lib/typescript/src/utils/validation.d.ts +3 -0
- package/lib/typescript/src/utils/validation.d.ts.map +1 -0
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts +6 -1
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/web/SimpleRouter.d.ts.map +1 -1
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts +7 -19
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +1 -1
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +3 -3
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +1 -1
- package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +1 -1
- package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +1 -1
- package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +1 -1
- package/lib/typescript/src/web/browser/SearchManager.d.ts +1 -1
- package/lib/typescript/src/web/browser/SearchManager.d.ts.map +1 -1
- package/lib/typescript/src/web/http/HttpClient.d.ts.map +1 -1
- package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +1 -1
- package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +1 -1
- package/lib/typescript/src/web/player/OptionsManager.d.ts.map +1 -1
- package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/browser.ts +1 -1
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +1 -1
- package/src/features/favorites.ts +1 -1
- package/src/features/metadata.ts +1 -1
- package/src/features/nowPlaying.ts +1 -1
- package/src/features/output.ts +1 -1
- package/src/features/playback/state.ts +1 -1
- package/src/features/player/options.ts +2 -2
- package/src/features/queue/activeTrack.ts +1 -1
- package/src/features/queue/queue.ts +2 -2
- package/src/features/remoteControls.ts +2 -2
- package/src/specs/audio-browser.nitro.ts +0 -1
- package/src/utils/useDebug.ts +6 -6
- package/src/utils/validation.ts +27 -0
- package/src/web/NativeAudioBrowser.ts +137 -58
- package/src/web/SimpleRouter.ts +24 -9
- package/src/web/TrackPlayer/Player.ts +58 -30
- package/src/web/TrackPlayer/PlaylistPlayer.ts +72 -63
- package/src/web/TrackPlayer/RepeatMode.ts +1 -1
- package/src/web/TrackPlayer/State.ts +9 -9
- package/src/web/browser/BrowserManager.ts +124 -67
- package/src/web/browser/FavoriteManager.ts +5 -3
- package/src/web/browser/NavigationErrorManager.ts +15 -8
- package/src/web/browser/SearchManager.ts +17 -11
- package/src/web/http/HttpClient.ts +25 -7
- package/src/web/http/RequestConfigBuilder.ts +29 -13
- package/src/web/player/NowPlayingManager.ts +13 -7
- package/src/web/player/OptionsManager.ts +7 -6
- package/src/web/util/BrowserPathHelper.ts +3 -2
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import type { NavigationErrorType } from '../../features'
|
|
1
2
|
import type { Track, ResolvedTrack } from '../../types'
|
|
2
3
|
import type { NativeBrowserConfiguration } from '../../types/browser-native'
|
|
3
|
-
import type { NavigationErrorType } from '../../features'
|
|
4
|
-
import { SimpleRouter } from '../SimpleRouter'
|
|
5
4
|
import type { HttpClient } from '../http/HttpClient'
|
|
6
5
|
import type { FavoriteManager } from './FavoriteManager'
|
|
7
6
|
import type { NavigationErrorManager } from './NavigationErrorManager'
|
|
7
|
+
import { assertedNotNullish } from '../../utils/validation'
|
|
8
8
|
import { RequestConfigBuilder } from '../http/RequestConfigBuilder'
|
|
9
|
+
import { SimpleRouter } from '../SimpleRouter'
|
|
9
10
|
import { BrowserPathHelper } from '../util/BrowserPathHelper'
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -49,10 +50,23 @@ export class BrowserManager {
|
|
|
49
50
|
'message' in error &&
|
|
50
51
|
typeof error.message === 'string'
|
|
51
52
|
) {
|
|
52
|
-
const navError = error as {
|
|
53
|
-
|
|
53
|
+
const navError = error as {
|
|
54
|
+
code: NavigationErrorType
|
|
55
|
+
message: string
|
|
56
|
+
statusCode?: number
|
|
57
|
+
}
|
|
58
|
+
this.navigationErrorManager.setNavigationError(
|
|
59
|
+
navError.code,
|
|
60
|
+
navError.message,
|
|
61
|
+
path,
|
|
62
|
+
navError.statusCode
|
|
63
|
+
)
|
|
54
64
|
} else {
|
|
55
|
-
this.navigationErrorManager.setNavigationError(
|
|
65
|
+
this.navigationErrorManager.setNavigationError(
|
|
66
|
+
'network-error',
|
|
67
|
+
'Failed to load content',
|
|
68
|
+
path
|
|
69
|
+
)
|
|
56
70
|
}
|
|
57
71
|
}
|
|
58
72
|
|
|
@@ -69,7 +83,8 @@ export class BrowserManager {
|
|
|
69
83
|
set path(value: string | undefined) {
|
|
70
84
|
if (this.hasValidConfiguration()) {
|
|
71
85
|
this.navigationErrorManager.clearNavigationError()
|
|
72
|
-
const pathToNavigate =
|
|
86
|
+
const pathToNavigate =
|
|
87
|
+
value ?? this._configuration.path ?? this.getDefaultPath()
|
|
73
88
|
if (pathToNavigate) {
|
|
74
89
|
void this.navigate(pathToNavigate)
|
|
75
90
|
}
|
|
@@ -130,7 +145,7 @@ export class BrowserManager {
|
|
|
130
145
|
|
|
131
146
|
// If no path specified, try to get first tab URL
|
|
132
147
|
if (!initialPath) {
|
|
133
|
-
const tabsRoute = value.routes?.find(r => r.path === '__tabs__')
|
|
148
|
+
const tabsRoute = value.routes?.find((r) => r.path === '__tabs__')
|
|
134
149
|
if (tabsRoute?.browseStatic?.children?.[0]?.url) {
|
|
135
150
|
initialPath = tabsRoute.browseStatic.children[0].url
|
|
136
151
|
} else {
|
|
@@ -205,19 +220,14 @@ export class BrowserManager {
|
|
|
205
220
|
return
|
|
206
221
|
}
|
|
207
222
|
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
// reach this block because it returns directly above. This matches Android's
|
|
213
|
-
// architecture where search() bypasses the resolve() contextual URL logic.
|
|
214
|
-
// IMPORTANT: Create a shallow copy to avoid mutating the original config object
|
|
215
|
-
// (e.g., browseStatic from routes). Without this, static route children would
|
|
216
|
-
// accumulate contextual URLs, breaking search which reads from the same source.
|
|
223
|
+
// Add contextual URLs to non-search content (matches Android behavior where
|
|
224
|
+
// search() bypasses the resolve() contextual URL logic).
|
|
225
|
+
// Shallow copy to avoid mutating the original config object (e.g., browseStatic
|
|
226
|
+
// from routes), which would break search that reads from the same source.
|
|
217
227
|
if (content?.children && !isSearchPath) {
|
|
218
228
|
content = {
|
|
219
229
|
...content,
|
|
220
|
-
children: content.children.map(track => {
|
|
230
|
+
children: content.children.map((track) => {
|
|
221
231
|
// If track has src, always add/update contextual URL with current path context
|
|
222
232
|
// This matches Android's BrowserManager.kt:436-441
|
|
223
233
|
if (track.src) {
|
|
@@ -249,7 +259,9 @@ export class BrowserManager {
|
|
|
249
259
|
this.onContentChanged(content)
|
|
250
260
|
|
|
251
261
|
// Query and update tabs if configuration has __tabs__ route
|
|
252
|
-
const tabsRoute = this._configuration.routes?.find(
|
|
262
|
+
const tabsRoute = this._configuration.routes?.find(
|
|
263
|
+
(r) => r.path === '__tabs__'
|
|
264
|
+
)
|
|
253
265
|
if (tabsRoute) {
|
|
254
266
|
const tabs = await this.queryTabs()
|
|
255
267
|
|
|
@@ -271,9 +283,7 @@ export class BrowserManager {
|
|
|
271
283
|
this._content = undefined
|
|
272
284
|
this.onContentChanged(undefined)
|
|
273
285
|
|
|
274
|
-
|
|
275
|
-
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
276
|
-
this.navigationErrorManager.setNavigationError('unknown-error', message)
|
|
286
|
+
this.handleNavigationError(error, path)
|
|
277
287
|
}
|
|
278
288
|
}
|
|
279
289
|
|
|
@@ -285,7 +295,9 @@ export class BrowserManager {
|
|
|
285
295
|
* @param searchPath The search path (format: /__search?q=query)
|
|
286
296
|
* @returns ResolvedTrack containing search results as children
|
|
287
297
|
*/
|
|
288
|
-
private async resolveSearchContent(
|
|
298
|
+
private async resolveSearchContent(
|
|
299
|
+
searchPath: string
|
|
300
|
+
): Promise<ResolvedTrack | undefined> {
|
|
289
301
|
// Extract query from search path
|
|
290
302
|
const queryMatch = searchPath.match(/[?&]q=([^&]*)/)
|
|
291
303
|
if (!queryMatch) {
|
|
@@ -295,7 +307,9 @@ export class BrowserManager {
|
|
|
295
307
|
const query = decodeURIComponent(queryMatch[1] ?? '')
|
|
296
308
|
|
|
297
309
|
// Find __search__ route entry
|
|
298
|
-
const searchRoute = this._configuration.routes?.find(
|
|
310
|
+
const searchRoute = this._configuration.routes?.find(
|
|
311
|
+
(r) => r.path === '__search__'
|
|
312
|
+
)
|
|
299
313
|
if (!searchRoute) {
|
|
300
314
|
console.warn('No __search__ route configured')
|
|
301
315
|
return undefined
|
|
@@ -310,9 +324,12 @@ export class BrowserManager {
|
|
|
310
324
|
// Handle request config-based search
|
|
311
325
|
else if (searchRoute.searchConfig) {
|
|
312
326
|
const searchQueryParams: Record<string, string> = { q: query }
|
|
313
|
-
const requestConfig = this.httpClient.mergeRequestConfig(
|
|
314
|
-
|
|
315
|
-
|
|
327
|
+
const requestConfig = this.httpClient.mergeRequestConfig(
|
|
328
|
+
searchRoute.searchConfig,
|
|
329
|
+
{
|
|
330
|
+
query: searchQueryParams
|
|
331
|
+
}
|
|
332
|
+
)
|
|
316
333
|
|
|
317
334
|
try {
|
|
318
335
|
const response = await this.httpClient.executeRequest(requestConfig)
|
|
@@ -328,7 +345,7 @@ export class BrowserManager {
|
|
|
328
345
|
return {
|
|
329
346
|
url: searchPath,
|
|
330
347
|
title: `Search: ${query}`,
|
|
331
|
-
children: searchResults
|
|
348
|
+
children: searchResults
|
|
332
349
|
}
|
|
333
350
|
}
|
|
334
351
|
|
|
@@ -337,27 +354,31 @@ export class BrowserManager {
|
|
|
337
354
|
* Supports both static config and resolve/transform callbacks.
|
|
338
355
|
* Matches Android's artwork URL transformation with full Track access.
|
|
339
356
|
*/
|
|
340
|
-
private async transformArtworkForContent(
|
|
357
|
+
private async transformArtworkForContent(
|
|
358
|
+
content: ResolvedTrack
|
|
359
|
+
): Promise<ResolvedTrack> {
|
|
341
360
|
const artworkConfig = this._configuration.artwork
|
|
342
361
|
if (!artworkConfig) {
|
|
343
362
|
return content
|
|
344
363
|
}
|
|
345
364
|
|
|
346
365
|
// Transform parent artwork
|
|
347
|
-
const parentArtworkSource =
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
366
|
+
const parentArtworkSource =
|
|
367
|
+
await RequestConfigBuilder.resolveArtworkSourceAsync(
|
|
368
|
+
content,
|
|
369
|
+
artworkConfig
|
|
370
|
+
)
|
|
351
371
|
|
|
352
372
|
// Transform children artwork
|
|
353
373
|
let transformedChildren: Track[] | undefined
|
|
354
374
|
if (content.children) {
|
|
355
375
|
transformedChildren = await Promise.all(
|
|
356
|
-
content.children.map(async track => {
|
|
357
|
-
const artworkSource =
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
376
|
+
content.children.map(async (track) => {
|
|
377
|
+
const artworkSource =
|
|
378
|
+
await RequestConfigBuilder.resolveArtworkSourceAsync(
|
|
379
|
+
track,
|
|
380
|
+
artworkConfig
|
|
381
|
+
)
|
|
361
382
|
if (artworkSource && !track.artworkSource) {
|
|
362
383
|
return { ...track, artworkSource }
|
|
363
384
|
}
|
|
@@ -369,7 +390,7 @@ export class BrowserManager {
|
|
|
369
390
|
return {
|
|
370
391
|
...content,
|
|
371
392
|
artworkSource: parentArtworkSource ?? content.artworkSource,
|
|
372
|
-
children: transformedChildren ?? content.children
|
|
393
|
+
children: transformedChildren ?? content.children
|
|
373
394
|
}
|
|
374
395
|
}
|
|
375
396
|
|
|
@@ -385,12 +406,20 @@ export class BrowserManager {
|
|
|
385
406
|
*/
|
|
386
407
|
private async resolveRouteContent(
|
|
387
408
|
route: {
|
|
388
|
-
browseCallback?: NativeBrowserConfiguration['routes'] extends
|
|
389
|
-
|
|
409
|
+
browseCallback?: NativeBrowserConfiguration['routes'] extends
|
|
410
|
+
| (infer R)[]
|
|
411
|
+
| undefined
|
|
412
|
+
? R extends { browseCallback?: infer C }
|
|
413
|
+
? C
|
|
414
|
+
: never
|
|
390
415
|
: never
|
|
391
416
|
browseStatic?: ResolvedTrack
|
|
392
|
-
browseConfig?: NativeBrowserConfiguration['routes'] extends
|
|
393
|
-
|
|
417
|
+
browseConfig?: NativeBrowserConfiguration['routes'] extends
|
|
418
|
+
| (infer R)[]
|
|
419
|
+
| undefined
|
|
420
|
+
? R extends { browseConfig?: infer C }
|
|
421
|
+
? C
|
|
422
|
+
: never
|
|
394
423
|
: never
|
|
395
424
|
},
|
|
396
425
|
path: string,
|
|
@@ -419,7 +448,10 @@ export class BrowserManager {
|
|
|
419
448
|
|
|
420
449
|
// Handle request config-based route
|
|
421
450
|
if (route.browseConfig) {
|
|
422
|
-
const requestConfig = this.httpClient.mergeRequestConfig(
|
|
451
|
+
const requestConfig = this.httpClient.mergeRequestConfig(
|
|
452
|
+
route.browseConfig,
|
|
453
|
+
{ path }
|
|
454
|
+
)
|
|
423
455
|
try {
|
|
424
456
|
const response = await this.httpClient.executeRequest(requestConfig)
|
|
425
457
|
return response as ResolvedTrack
|
|
@@ -436,18 +468,23 @@ export class BrowserManager {
|
|
|
436
468
|
/**
|
|
437
469
|
* Resolves content for a specific path using configured routes.
|
|
438
470
|
*/
|
|
439
|
-
private async resolveContent(
|
|
471
|
+
private async resolveContent(
|
|
472
|
+
path: string
|
|
473
|
+
): Promise<ResolvedTrack | undefined> {
|
|
440
474
|
const routes = this._configuration.routes
|
|
441
475
|
if (!routes || routes.length === 0) {
|
|
442
476
|
return undefined
|
|
443
477
|
}
|
|
444
478
|
|
|
445
479
|
// Convert routes array to record for SimpleRouter
|
|
446
|
-
const routePatterns: Record<
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
480
|
+
const routePatterns: Record<
|
|
481
|
+
string,
|
|
482
|
+
{
|
|
483
|
+
browseCallback?: (typeof routes)[0]['browseCallback']
|
|
484
|
+
browseConfig?: (typeof routes)[0]['browseConfig']
|
|
485
|
+
browseStatic?: (typeof routes)[0]['browseStatic']
|
|
486
|
+
}
|
|
487
|
+
> = {}
|
|
451
488
|
|
|
452
489
|
for (const route of routes) {
|
|
453
490
|
// Skip special routes
|
|
@@ -456,7 +493,7 @@ export class BrowserManager {
|
|
|
456
493
|
routePatterns[route.path] = {
|
|
457
494
|
browseCallback: route.browseCallback,
|
|
458
495
|
browseConfig: route.browseConfig,
|
|
459
|
-
browseStatic: route.browseStatic
|
|
496
|
+
browseStatic: route.browseStatic
|
|
460
497
|
}
|
|
461
498
|
}
|
|
462
499
|
|
|
@@ -464,14 +501,19 @@ export class BrowserManager {
|
|
|
464
501
|
const match = this.router.findBestMatch(path, routePatterns)
|
|
465
502
|
if (match) {
|
|
466
503
|
const [matchedPattern, routeMatch] = match
|
|
467
|
-
const matchedRoute = routes.find(r => r.path === matchedPattern)
|
|
504
|
+
const matchedRoute = routes.find((r) => r.path === matchedPattern)
|
|
468
505
|
if (matchedRoute) {
|
|
469
|
-
return this.resolveRouteContent(
|
|
506
|
+
return this.resolveRouteContent(
|
|
507
|
+
matchedRoute,
|
|
508
|
+
path,
|
|
509
|
+
routeMatch.params,
|
|
510
|
+
'Route'
|
|
511
|
+
)
|
|
470
512
|
}
|
|
471
513
|
}
|
|
472
514
|
|
|
473
515
|
// Fall back to __default__ route
|
|
474
|
-
const defaultRoute = routes.find(r => r.path === '__default__')
|
|
516
|
+
const defaultRoute = routes.find((r) => r.path === '__default__')
|
|
475
517
|
if (defaultRoute) {
|
|
476
518
|
return this.resolveRouteContent(defaultRoute, path, {}, 'Default route')
|
|
477
519
|
}
|
|
@@ -483,7 +525,9 @@ export class BrowserManager {
|
|
|
483
525
|
* Queries tabs from the __tabs__ route.
|
|
484
526
|
*/
|
|
485
527
|
private async queryTabs(): Promise<Track[]> {
|
|
486
|
-
const tabsRoute = this._configuration.routes?.find(
|
|
528
|
+
const tabsRoute = this._configuration.routes?.find(
|
|
529
|
+
(r) => r.path === '__tabs__'
|
|
530
|
+
)
|
|
487
531
|
if (!tabsRoute) {
|
|
488
532
|
return []
|
|
489
533
|
}
|
|
@@ -492,14 +536,20 @@ export class BrowserManager {
|
|
|
492
536
|
const tabs = result?.children ?? []
|
|
493
537
|
|
|
494
538
|
// Transform artwork URLs on tabs
|
|
495
|
-
return RequestConfigBuilder.transformTracksArtwork(
|
|
539
|
+
return RequestConfigBuilder.transformTracksArtwork(
|
|
540
|
+
tabs,
|
|
541
|
+
this._configuration.artwork
|
|
542
|
+
)
|
|
496
543
|
}
|
|
497
544
|
|
|
498
545
|
/**
|
|
499
546
|
* Checks if the configuration has valid routes.
|
|
500
547
|
*/
|
|
501
548
|
private hasValidConfiguration(): boolean {
|
|
502
|
-
return
|
|
549
|
+
return (
|
|
550
|
+
this._configuration.routes !== undefined &&
|
|
551
|
+
this._configuration.routes.length > 0
|
|
552
|
+
)
|
|
503
553
|
}
|
|
504
554
|
|
|
505
555
|
/**
|
|
@@ -528,7 +578,7 @@ export class BrowserManager {
|
|
|
528
578
|
}
|
|
529
579
|
|
|
530
580
|
// Filter to only playable tracks (tracks with src)
|
|
531
|
-
const playableTracks = children.filter(track => track.src != null)
|
|
581
|
+
const playableTracks = children.filter((track) => track.src != null)
|
|
532
582
|
|
|
533
583
|
if (playableTracks.length === 0) {
|
|
534
584
|
console.warn('Parent has no playable tracks, cannot expand queue')
|
|
@@ -536,23 +586,30 @@ export class BrowserManager {
|
|
|
536
586
|
}
|
|
537
587
|
|
|
538
588
|
// Find the index of the selected track in the playable tracks array
|
|
539
|
-
const selectedIndex = playableTracks.findIndex(
|
|
589
|
+
const selectedIndex = playableTracks.findIndex(
|
|
590
|
+
(track) => track.src === trackId
|
|
591
|
+
)
|
|
540
592
|
|
|
541
593
|
if (selectedIndex < 0) {
|
|
542
|
-
console.warn(
|
|
594
|
+
console.warn(
|
|
595
|
+
`Track with src='${trackId}' not found in playable children`
|
|
596
|
+
)
|
|
543
597
|
return undefined
|
|
544
598
|
}
|
|
545
599
|
|
|
546
600
|
// Check singleTrack setting - if true, return only the selected track
|
|
547
601
|
if (this._configuration.singleTrack) {
|
|
548
602
|
return {
|
|
549
|
-
tracks: [playableTracks[selectedIndex]
|
|
550
|
-
selectedIndex: 0
|
|
603
|
+
tracks: [assertedNotNullish(playableTracks[selectedIndex])],
|
|
604
|
+
selectedIndex: 0
|
|
551
605
|
}
|
|
552
606
|
}
|
|
553
607
|
return { tracks: playableTracks, selectedIndex }
|
|
554
608
|
} catch (error) {
|
|
555
|
-
console.error(
|
|
609
|
+
console.error(
|
|
610
|
+
`Error expanding queue from contextual URL: ${contextualUrl}`,
|
|
611
|
+
error
|
|
612
|
+
)
|
|
556
613
|
return undefined
|
|
557
614
|
}
|
|
558
615
|
}
|
|
@@ -576,13 +633,13 @@ export class BrowserManager {
|
|
|
576
633
|
): Promise<{ tracks: Track[]; startIndex: number; startPositionMs: number }> {
|
|
577
634
|
// Single track: check for search context or contextual URL
|
|
578
635
|
if (tracks.length === 1) {
|
|
579
|
-
const track = tracks[0]
|
|
636
|
+
const track = assertedNotNullish(tracks[0])
|
|
580
637
|
const trackUrl = track.url
|
|
581
638
|
|
|
582
639
|
// If search query present, expand search results
|
|
583
640
|
if (searchQuery) {
|
|
584
641
|
// Execute search (will hit cache if already performed)
|
|
585
|
-
const searchResults = await this.
|
|
642
|
+
const searchResults = await this.resolveSearchContent(
|
|
586
643
|
BrowserPathHelper.createSearchPath(searchQuery)
|
|
587
644
|
)
|
|
588
645
|
const searchTracks = searchResults?.children
|
|
@@ -590,14 +647,14 @@ export class BrowserManager {
|
|
|
590
647
|
if (searchTracks && searchTracks.length > 0) {
|
|
591
648
|
// Find the selected track in search results
|
|
592
649
|
const selectedIdx = searchTracks.findIndex(
|
|
593
|
-
t => t.url === trackUrl || t.src === track.src
|
|
650
|
+
(t) => t.url === trackUrl || t.src === track.src
|
|
594
651
|
)
|
|
595
652
|
|
|
596
653
|
if (selectedIdx >= 0) {
|
|
597
654
|
return {
|
|
598
655
|
tracks: searchTracks,
|
|
599
656
|
startIndex: selectedIdx,
|
|
600
|
-
startPositionMs
|
|
657
|
+
startPositionMs
|
|
601
658
|
}
|
|
602
659
|
}
|
|
603
660
|
}
|
|
@@ -618,7 +675,7 @@ export class BrowserManager {
|
|
|
618
675
|
return {
|
|
619
676
|
tracks: expanded.tracks,
|
|
620
677
|
startIndex: expanded.selectedIndex,
|
|
621
|
-
startPositionMs
|
|
678
|
+
startPositionMs
|
|
622
679
|
}
|
|
623
680
|
}
|
|
624
681
|
}
|
|
@@ -53,7 +53,7 @@ export class FavoriteManager {
|
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
55
|
...track,
|
|
56
|
-
favorited: true
|
|
56
|
+
favorited: true
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -68,10 +68,12 @@ export class FavoriteManager {
|
|
|
68
68
|
const children = resolvedTrack.children
|
|
69
69
|
if (!children) return resolvedTrack
|
|
70
70
|
|
|
71
|
-
const hydratedChildren = children.map(track =>
|
|
71
|
+
const hydratedChildren = children.map((track) =>
|
|
72
|
+
this.hydrateFavorite(track)
|
|
73
|
+
)
|
|
72
74
|
return {
|
|
73
75
|
...resolvedTrack,
|
|
74
|
-
children: hydratedChildren
|
|
76
|
+
children: hydratedChildren
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
}
|
|
@@ -2,7 +2,7 @@ import type {
|
|
|
2
2
|
NavigationError,
|
|
3
3
|
NavigationErrorType,
|
|
4
4
|
FormattedNavigationError,
|
|
5
|
-
NavigationErrorEvent
|
|
5
|
+
NavigationErrorEvent
|
|
6
6
|
} from '../../features'
|
|
7
7
|
import type { NativeBrowserConfiguration } from '../../types/browser-native'
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ const ERROR_TITLES: Record<NavigationErrorType, string> = {
|
|
|
11
11
|
'http-error': 'Server Error',
|
|
12
12
|
'content-not-found': 'Content Not Found',
|
|
13
13
|
'callback-error': 'Error',
|
|
14
|
-
'unknown-error': 'Error'
|
|
14
|
+
'unknown-error': 'Error'
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -25,12 +25,16 @@ export class NavigationErrorManager {
|
|
|
25
25
|
|
|
26
26
|
// Event callbacks
|
|
27
27
|
onNavigationError: (data: NavigationErrorEvent) => void = () => {}
|
|
28
|
-
onFormattedNavigationError: (
|
|
28
|
+
onFormattedNavigationError: (
|
|
29
|
+
formattedError: FormattedNavigationError | undefined
|
|
30
|
+
) => void = () => {}
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Sets the custom error formatter callback from configuration.
|
|
32
34
|
*/
|
|
33
|
-
setFormatCallback(
|
|
35
|
+
setFormatCallback(
|
|
36
|
+
callback: NativeBrowserConfiguration['formatNavigationError']
|
|
37
|
+
): void {
|
|
34
38
|
this.formatCallback = callback
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -38,7 +42,10 @@ export class NavigationErrorManager {
|
|
|
38
42
|
* Clears the current navigation error and emits clear events.
|
|
39
43
|
*/
|
|
40
44
|
clearNavigationError(): void {
|
|
41
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
this._navigationError !== undefined ||
|
|
47
|
+
this._formattedNavigationError !== undefined
|
|
48
|
+
) {
|
|
42
49
|
this._navigationError = undefined
|
|
43
50
|
this._formattedNavigationError = undefined
|
|
44
51
|
this.onNavigationError({ error: undefined })
|
|
@@ -65,7 +72,7 @@ export class NavigationErrorManager {
|
|
|
65
72
|
code,
|
|
66
73
|
message,
|
|
67
74
|
statusCode,
|
|
68
|
-
statusCodeSuccess: undefined
|
|
75
|
+
statusCodeSuccess: undefined
|
|
69
76
|
}
|
|
70
77
|
this._navigationError = navError
|
|
71
78
|
this.onNavigationError({ error: navError })
|
|
@@ -73,7 +80,7 @@ export class NavigationErrorManager {
|
|
|
73
80
|
// Default formatted error
|
|
74
81
|
const defaultFormatted: FormattedNavigationError = {
|
|
75
82
|
title: ERROR_TITLES[code],
|
|
76
|
-
message
|
|
83
|
+
message
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
// Call custom formatter if available
|
|
@@ -82,7 +89,7 @@ export class NavigationErrorManager {
|
|
|
82
89
|
const customFormatted = this.formatCallback({
|
|
83
90
|
error: navError,
|
|
84
91
|
defaultFormatted,
|
|
85
|
-
path
|
|
92
|
+
path
|
|
86
93
|
})
|
|
87
94
|
this._formattedNavigationError = customFormatted || defaultFormatted
|
|
88
95
|
} catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Track, SearchParams } from '../../types'
|
|
2
|
-
import type { BrowserManager } from './BrowserManager'
|
|
3
2
|
import type { HttpClient } from '../http/HttpClient'
|
|
3
|
+
import type { BrowserManager } from './BrowserManager'
|
|
4
4
|
import { RequestConfigBuilder } from '../http/RequestConfigBuilder'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -23,7 +23,9 @@ export class SearchManager {
|
|
|
23
23
|
*/
|
|
24
24
|
async search(params: SearchParams): Promise<Track[]> {
|
|
25
25
|
// Find __search__ route entry
|
|
26
|
-
const searchRoute = this.browserManager.configuration.routes?.find(
|
|
26
|
+
const searchRoute = this.browserManager.configuration.routes?.find(
|
|
27
|
+
(r) => r.path === '__search__'
|
|
28
|
+
)
|
|
27
29
|
if (!searchRoute) {
|
|
28
30
|
return []
|
|
29
31
|
}
|
|
@@ -38,7 +40,7 @@ export class SearchManager {
|
|
|
38
40
|
else if (searchRoute.searchConfig) {
|
|
39
41
|
// Build query parameters from SearchParams (matches Android's executeSearchApiRequest)
|
|
40
42
|
const searchQueryParams: Record<string, string> = {
|
|
41
|
-
q: params.query
|
|
43
|
+
q: params.query
|
|
42
44
|
}
|
|
43
45
|
if (params.mode) searchQueryParams.mode = params.mode
|
|
44
46
|
if (params.genre) searchQueryParams.genre = params.genre
|
|
@@ -47,9 +49,12 @@ export class SearchManager {
|
|
|
47
49
|
if (params.title) searchQueryParams.title = params.title
|
|
48
50
|
if (params.playlist) searchQueryParams.playlist = params.playlist
|
|
49
51
|
|
|
50
|
-
const requestConfig = this.httpClient.mergeRequestConfig(
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const requestConfig = this.httpClient.mergeRequestConfig(
|
|
53
|
+
searchRoute.searchConfig,
|
|
54
|
+
{
|
|
55
|
+
query: searchQueryParams
|
|
56
|
+
}
|
|
57
|
+
)
|
|
53
58
|
|
|
54
59
|
try {
|
|
55
60
|
const response = await this.httpClient.executeRequest(requestConfig)
|
|
@@ -64,11 +69,12 @@ export class SearchManager {
|
|
|
64
69
|
const artworkConfig = this.browserManager.configuration.artwork
|
|
65
70
|
if (artworkConfig) {
|
|
66
71
|
results = await Promise.all(
|
|
67
|
-
results.map(async track => {
|
|
68
|
-
const artworkSource =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
results.map(async (track) => {
|
|
73
|
+
const artworkSource =
|
|
74
|
+
await RequestConfigBuilder.resolveArtworkSourceAsync(
|
|
75
|
+
track,
|
|
76
|
+
artworkConfig
|
|
77
|
+
)
|
|
72
78
|
if (artworkSource && !track.artworkSource) {
|
|
73
79
|
return { ...track, artworkSource }
|
|
74
80
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
RequestConfig,
|
|
3
|
-
TransformableRequestConfig,
|
|
4
|
-
} from '../../types'
|
|
5
1
|
import type { NavigationErrorType } from '../../features'
|
|
2
|
+
import type { RequestConfig, TransformableRequestConfig } from '../../types'
|
|
6
3
|
import { RequestConfigBuilder } from './RequestConfigBuilder'
|
|
7
4
|
|
|
8
5
|
type HttpError = Error & {
|
|
@@ -21,6 +18,8 @@ function isHttpError(error: unknown): error is HttpError {
|
|
|
21
18
|
)
|
|
22
19
|
}
|
|
23
20
|
|
|
21
|
+
const TIMEOUT_MS = 30_000
|
|
22
|
+
|
|
24
23
|
/**
|
|
25
24
|
* HTTP client for making browser API requests.
|
|
26
25
|
* Mirrors Android's HttpClient.kt
|
|
@@ -54,7 +53,10 @@ export class HttpClient {
|
|
|
54
53
|
base as RequestConfig
|
|
55
54
|
)
|
|
56
55
|
// Then merge with overrides
|
|
57
|
-
return RequestConfigBuilder.mergeConfig(
|
|
56
|
+
return RequestConfigBuilder.mergeConfig(
|
|
57
|
+
withBase,
|
|
58
|
+
overrides as RequestConfig
|
|
59
|
+
)
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
|
@@ -67,22 +69,36 @@ export class HttpClient {
|
|
|
67
69
|
const headers = config.headers ?? {}
|
|
68
70
|
const method = config.method ?? 'GET'
|
|
69
71
|
|
|
72
|
+
const controller = new AbortController()
|
|
73
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS)
|
|
74
|
+
|
|
70
75
|
try {
|
|
71
76
|
const response = await fetch(url, {
|
|
72
77
|
method,
|
|
73
78
|
headers,
|
|
79
|
+
signal: controller.signal
|
|
74
80
|
})
|
|
75
81
|
|
|
76
82
|
if (!response.ok) {
|
|
77
|
-
const error = new Error(
|
|
83
|
+
const error = new Error(
|
|
84
|
+
`HTTP ${response.status}: ${response.statusText}`
|
|
85
|
+
) as HttpError
|
|
78
86
|
error.isHttpError = true
|
|
79
87
|
error.status = response.status
|
|
80
88
|
error.statusText = response.statusText
|
|
81
89
|
throw error
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
|
|
92
|
+
// await so JSON parse errors are caught by the surrounding try/catch
|
|
93
|
+
return await response.json()
|
|
85
94
|
} catch (error: unknown) {
|
|
95
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
96
|
+
const navError = new Error(
|
|
97
|
+
`Request timed out after ${TIMEOUT_MS}ms`
|
|
98
|
+
) as Error & { code: NavigationErrorType }
|
|
99
|
+
navError.code = 'network-error'
|
|
100
|
+
throw navError
|
|
101
|
+
}
|
|
86
102
|
// Distinguish between network errors and HTTP errors
|
|
87
103
|
if (isHttpError(error)) {
|
|
88
104
|
const navError = new Error(error.message) as Error & {
|
|
@@ -102,6 +118,8 @@ export class HttpClient {
|
|
|
102
118
|
}
|
|
103
119
|
navError.code = 'network-error'
|
|
104
120
|
throw navError
|
|
121
|
+
} finally {
|
|
122
|
+
clearTimeout(timeoutId)
|
|
105
123
|
}
|
|
106
124
|
}
|
|
107
125
|
}
|