@5stones/react-native-audio-browser 0.1.4 → 0.1.6
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/TrackPlayer.swift +145 -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,9 +1,3 @@
|
|
|
1
|
-
import type { AudioBrowser as AudioBrowserSpec, IosOutput } from '../specs/audio-browser.nitro'
|
|
2
|
-
import type {
|
|
3
|
-
ResolvedTrack,
|
|
4
|
-
Track,
|
|
5
|
-
} from '../types'
|
|
6
|
-
import type { NativeBrowserConfiguration } from '../types/browser-native'
|
|
7
1
|
import type {
|
|
8
2
|
PlaybackErrorEvent,
|
|
9
3
|
PlaybackError,
|
|
@@ -40,23 +34,32 @@ import type {
|
|
|
40
34
|
BatteryOptimizationStatus,
|
|
41
35
|
BatteryOptimizationStatusChangedEvent,
|
|
42
36
|
BatteryWarningPendingChangedEvent,
|
|
43
|
-
PartialSetupPlayerOptions
|
|
37
|
+
PartialSetupPlayerOptions
|
|
44
38
|
} from '../features'
|
|
45
|
-
import {
|
|
46
|
-
|
|
39
|
+
import type {
|
|
40
|
+
AudioBrowser as AudioBrowserSpec,
|
|
41
|
+
IosOutput
|
|
42
|
+
} from '../specs/audio-browser.nitro'
|
|
43
|
+
import type { ResolvedTrack, Track } from '../types'
|
|
44
|
+
import type { NativeBrowserConfiguration } from '../types/browser-native'
|
|
47
45
|
import { BrowserManager } from './browser/BrowserManager'
|
|
48
46
|
import { FavoriteManager } from './browser/FavoriteManager'
|
|
49
47
|
import { NavigationErrorManager } from './browser/NavigationErrorManager'
|
|
50
48
|
import { SearchManager } from './browser/SearchManager'
|
|
51
|
-
import {
|
|
52
|
-
import { NowPlayingManager } from './player/NowPlayingManager'
|
|
49
|
+
import { HttpClient } from './http/HttpClient'
|
|
53
50
|
import { RequestConfigBuilder } from './http/RequestConfigBuilder'
|
|
51
|
+
import { NowPlayingManager } from './player/NowPlayingManager'
|
|
52
|
+
import { OptionsManager } from './player/OptionsManager'
|
|
53
|
+
import { PlaylistPlayer, SleepTimerManager } from './TrackPlayer'
|
|
54
54
|
import { BrowserPathHelper } from './util/BrowserPathHelper'
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Web implementation of AudioBrowser (unified browser + player)
|
|
58
58
|
*/
|
|
59
|
-
export class NativeAudioBrowser
|
|
59
|
+
export class NativeAudioBrowser
|
|
60
|
+
extends PlaylistPlayer
|
|
61
|
+
implements AudioBrowserSpec
|
|
62
|
+
{
|
|
60
63
|
// HybridObject stuff
|
|
61
64
|
readonly name = 'WebAudioBrowser'
|
|
62
65
|
equals() {
|
|
@@ -83,8 +86,10 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
83
86
|
private nowPlayingManager: NowPlayingManager
|
|
84
87
|
|
|
85
88
|
// Player state
|
|
89
|
+
private currentLoadId = 0
|
|
86
90
|
private progressUpdateEventInterval: NodeJS.Timeout | undefined
|
|
87
|
-
private _online: boolean =
|
|
91
|
+
private _online: boolean =
|
|
92
|
+
typeof navigator !== 'undefined' ? navigator.onLine : true
|
|
88
93
|
private onlineHandler: (() => void) | undefined
|
|
89
94
|
private offlineHandler: (() => void) | undefined
|
|
90
95
|
private sleepTimer = new (class extends SleepTimerManager {
|
|
@@ -128,17 +133,24 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
128
133
|
onContentChanged: (content: ResolvedTrack | undefined) => void = () => {}
|
|
129
134
|
onTabsChanged: (tabs: Track[]) => void = () => {}
|
|
130
135
|
onNavigationError: (data: NavigationErrorEvent) => void = () => {}
|
|
131
|
-
onFormattedNavigationError: (
|
|
136
|
+
onFormattedNavigationError: (
|
|
137
|
+
formattedError: FormattedNavigationError | undefined
|
|
138
|
+
) => void = () => {}
|
|
132
139
|
|
|
133
140
|
// MARK: Player event callbacks
|
|
134
141
|
onChapterMetadata: (chapters: ChapterMetadata[]) => void = () => {}
|
|
135
142
|
onTrackMetadata: (metadata: TrackMetadata) => void = () => {}
|
|
136
143
|
onTimedMetadata: (metadata: TimedMetadata) => void = () => {}
|
|
137
|
-
onPlaybackActiveTrackChanged: (
|
|
144
|
+
onPlaybackActiveTrackChanged: (
|
|
145
|
+
data: PlaybackActiveTrackChangedEvent
|
|
146
|
+
) => void = () => {}
|
|
138
147
|
onPlaybackError: (data: PlaybackErrorEvent) => void = () => {}
|
|
139
|
-
onPlaybackPlayWhenReadyChanged: (
|
|
148
|
+
onPlaybackPlayWhenReadyChanged: (
|
|
149
|
+
data: PlaybackPlayWhenReadyChangedEvent
|
|
150
|
+
) => void = () => {}
|
|
140
151
|
onPlaybackPlayingState: (data: PlayingState) => void = () => {}
|
|
141
|
-
onPlaybackProgressUpdated: (data: PlaybackProgressUpdatedEvent) => void =
|
|
152
|
+
onPlaybackProgressUpdated: (data: PlaybackProgressUpdatedEvent) => void =
|
|
153
|
+
() => {}
|
|
142
154
|
onPlaybackQueueEnded: (data: PlaybackQueueEndedEvent) => void = () => {}
|
|
143
155
|
onPlaybackQueueChanged: (queue: Track[]) => void = () => {}
|
|
144
156
|
onPlaybackRepeatModeChanged: (data: RepeatModeChangedEvent) => void = () => {}
|
|
@@ -165,25 +177,36 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
165
177
|
onNowPlayingChanged: (metadata: NowPlayingMetadata) => void = () => {}
|
|
166
178
|
onOnlineChanged: (online: boolean) => void = () => {}
|
|
167
179
|
onEqualizerChanged: (settings: EqualizerSettings) => void = () => {}
|
|
168
|
-
onBatteryWarningPendingChanged: (
|
|
169
|
-
|
|
180
|
+
onBatteryWarningPendingChanged: (
|
|
181
|
+
event: BatteryWarningPendingChangedEvent
|
|
182
|
+
) => void = () => {}
|
|
183
|
+
onBatteryOptimizationStatusChanged: (
|
|
184
|
+
event: BatteryOptimizationStatusChangedEvent
|
|
185
|
+
) => void = () => {}
|
|
170
186
|
onSystemVolumeChanged: (volume: number) => void = () => {}
|
|
171
187
|
onIosOutputChanged: (output: IosOutput) => void = () => {}
|
|
172
188
|
|
|
173
189
|
// MARK: Remote handlers
|
|
174
190
|
handleRemoteBookmark: (() => void) | undefined = undefined
|
|
175
191
|
handleRemoteDislike: (() => void) | undefined = undefined
|
|
176
|
-
handleRemoteJumpBackward:
|
|
177
|
-
|
|
192
|
+
handleRemoteJumpBackward:
|
|
193
|
+
| ((event: RemoteJumpBackwardEvent) => void)
|
|
194
|
+
| undefined = undefined
|
|
195
|
+
handleRemoteJumpForward:
|
|
196
|
+
| ((event: RemoteJumpForwardEvent) => void)
|
|
197
|
+
| undefined = undefined
|
|
178
198
|
handleRemoteLike: (() => void) | undefined = undefined
|
|
179
199
|
handleRemoteNext: (() => void) | undefined = undefined
|
|
180
200
|
handleRemotePause: (() => void) | undefined = undefined
|
|
181
201
|
handleRemotePlay: (() => void) | undefined = undefined
|
|
182
|
-
handleRemotePlayId: ((event: RemotePlayIdEvent) => void) | undefined =
|
|
183
|
-
|
|
202
|
+
handleRemotePlayId: ((event: RemotePlayIdEvent) => void) | undefined =
|
|
203
|
+
undefined
|
|
204
|
+
handleRemotePlaySearch: ((event: RemotePlaySearchEvent) => void) | undefined =
|
|
205
|
+
undefined
|
|
184
206
|
handleRemotePrevious: (() => void) | undefined = undefined
|
|
185
207
|
handleRemoteSeek: ((event: RemoteSeekEvent) => void) | undefined = undefined
|
|
186
|
-
handleRemoteSetRating: ((event: RemoteSetRatingEvent) => void) | undefined =
|
|
208
|
+
handleRemoteSetRating: ((event: RemoteSetRatingEvent) => void) | undefined =
|
|
209
|
+
undefined
|
|
187
210
|
handleRemoteSkip: (() => void) | undefined = undefined
|
|
188
211
|
handleRemoteStop: (() => void) | undefined = undefined
|
|
189
212
|
|
|
@@ -204,19 +227,21 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
204
227
|
this.navigationErrorManager
|
|
205
228
|
)
|
|
206
229
|
|
|
207
|
-
this.searchManager = new SearchManager(
|
|
208
|
-
this.browserManager,
|
|
209
|
-
this.httpClient
|
|
210
|
-
)
|
|
230
|
+
this.searchManager = new SearchManager(this.browserManager, this.httpClient)
|
|
211
231
|
|
|
212
232
|
// Wire up event callbacks from managers to class callbacks
|
|
213
233
|
this.browserManager.onPathChanged = (path) => this.onPathChanged(path)
|
|
214
|
-
this.browserManager.onContentChanged = (content) =>
|
|
234
|
+
this.browserManager.onContentChanged = (content) =>
|
|
235
|
+
this.onContentChanged(content)
|
|
215
236
|
this.browserManager.onTabsChanged = (tabs) => this.onTabsChanged(tabs)
|
|
216
|
-
this.navigationErrorManager.onNavigationError = (data) =>
|
|
217
|
-
|
|
218
|
-
this.
|
|
219
|
-
|
|
237
|
+
this.navigationErrorManager.onNavigationError = (data) =>
|
|
238
|
+
this.onNavigationError(data)
|
|
239
|
+
this.navigationErrorManager.onFormattedNavigationError = (error) =>
|
|
240
|
+
this.onFormattedNavigationError(error)
|
|
241
|
+
this.optionsManager.onOptionsChanged = (options) =>
|
|
242
|
+
this.onOptionsChanged(options)
|
|
243
|
+
this.nowPlayingManager.onNowPlayingChanged = (metadata) =>
|
|
244
|
+
this.onNowPlayingChanged(metadata)
|
|
220
245
|
|
|
221
246
|
// Setup online/offline listeners
|
|
222
247
|
if (typeof window !== 'undefined') {
|
|
@@ -243,7 +268,8 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
243
268
|
const didStateChange = newState.state !== oldState.state
|
|
244
269
|
const didErrorChange =
|
|
245
270
|
newState.state === 'error' && oldState.state === 'error'
|
|
246
|
-
? newState.error !== oldState.error
|
|
271
|
+
? newState.error?.code !== oldState.error?.code ||
|
|
272
|
+
newState.error?.message !== oldState.error?.message
|
|
247
273
|
: false
|
|
248
274
|
|
|
249
275
|
super.state = newState
|
|
@@ -261,10 +287,18 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
261
287
|
}
|
|
262
288
|
}
|
|
263
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Derives PlayingState from playback state and playWhenReady.
|
|
292
|
+
* Matches Android's PlayingStateFactory.derive() logic.
|
|
293
|
+
*/
|
|
264
294
|
private getPlayingStateFromPlayback(playback: Playback): PlayingState {
|
|
295
|
+
const state = playback.state
|
|
296
|
+
const pwr = this._playWhenReady
|
|
265
297
|
return {
|
|
266
|
-
playing:
|
|
267
|
-
|
|
298
|
+
playing:
|
|
299
|
+
pwr && state !== 'error' && state !== 'ended' && state !== 'none',
|
|
300
|
+
buffering:
|
|
301
|
+
pwr && (state === 'loading' || state === 'buffering')
|
|
268
302
|
}
|
|
269
303
|
}
|
|
270
304
|
|
|
@@ -272,11 +306,17 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
272
306
|
this.clearUpdateEventInterval()
|
|
273
307
|
if (interval) {
|
|
274
308
|
this.progressUpdateEventInterval = setInterval(() => {
|
|
275
|
-
|
|
309
|
+
const state = this.state.state
|
|
310
|
+
// Match Android: emit progress during loading, buffering, and playing
|
|
311
|
+
if (
|
|
312
|
+
state === 'playing' ||
|
|
313
|
+
state === 'loading' ||
|
|
314
|
+
state === 'buffering'
|
|
315
|
+
) {
|
|
276
316
|
const progress = this.getProgress()
|
|
277
317
|
const event: PlaybackProgressUpdatedEvent = {
|
|
278
318
|
...progress,
|
|
279
|
-
track: this.currentIndex || 0
|
|
319
|
+
track: this.currentIndex || 0
|
|
280
320
|
}
|
|
281
321
|
this.onPlaybackProgressUpdated(event)
|
|
282
322
|
}
|
|
@@ -294,7 +334,7 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
294
334
|
super.onPlaylistEnded()
|
|
295
335
|
this.onPlaybackQueueEnded({
|
|
296
336
|
track: this.currentIndex ?? 0,
|
|
297
|
-
position: this.element?.currentTime ?? 0
|
|
337
|
+
position: this.element?.currentTime ?? 0
|
|
298
338
|
})
|
|
299
339
|
}
|
|
300
340
|
|
|
@@ -317,13 +357,16 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
317
357
|
* @param parentPath The parent path to check against queueSourcePath
|
|
318
358
|
* @returns true if successfully skipped to existing track, false otherwise
|
|
319
359
|
*/
|
|
320
|
-
private trySkipToExistingQueueTrack(
|
|
360
|
+
private trySkipToExistingQueueTrack(
|
|
361
|
+
trackId: string,
|
|
362
|
+
parentPath: string
|
|
363
|
+
): boolean {
|
|
321
364
|
if (parentPath !== this.browserManager.queueSourcePath) {
|
|
322
365
|
return false
|
|
323
366
|
}
|
|
324
367
|
|
|
325
368
|
const queue = this.getQueue()
|
|
326
|
-
const index = queue.findIndex(t => t.src === trackId)
|
|
369
|
+
const index = queue.findIndex((t) => t.src === trackId)
|
|
327
370
|
|
|
328
371
|
if (index < 0) {
|
|
329
372
|
return false
|
|
@@ -359,7 +402,10 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
359
402
|
* Async implementation of track navigation with queue expansion support.
|
|
360
403
|
* Matches Android's MediaSessionCallback behavior.
|
|
361
404
|
*/
|
|
362
|
-
private async navigateTrackAsync(
|
|
405
|
+
private async navigateTrackAsync(
|
|
406
|
+
track: Track,
|
|
407
|
+
url: string | undefined
|
|
408
|
+
): Promise<void> {
|
|
363
409
|
try {
|
|
364
410
|
// Handle contextual URL (playable track with queue context)
|
|
365
411
|
if (url && BrowserPathHelper.isContextual(url)) {
|
|
@@ -469,24 +515,49 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
469
515
|
// Clear now playing override when track changes (matches Android's PlayerListener.onMediaItemTransition)
|
|
470
516
|
this.nowPlayingManager.clearNowPlayingOverride()
|
|
471
517
|
|
|
518
|
+
// Match Android: load() modifies the queue.
|
|
519
|
+
// If queue is empty, add the track. If queue has items, replace at currentIndex.
|
|
520
|
+
if (this.playlist.length === 0) {
|
|
521
|
+
this.playlist = [track]
|
|
522
|
+
this._currentIndex = 0
|
|
523
|
+
this.onPlaybackQueueChanged(this.playlist)
|
|
524
|
+
} else if (
|
|
525
|
+
this.currentIndex !== undefined &&
|
|
526
|
+
this.playlist[this.currentIndex] !== track
|
|
527
|
+
) {
|
|
528
|
+
this.playlist[this.currentIndex] = track
|
|
529
|
+
this.onPlaybackQueueChanged(this.playlist)
|
|
530
|
+
}
|
|
531
|
+
|
|
472
532
|
const lastTrack = this.current
|
|
473
533
|
const lastPosition = element.currentTime
|
|
474
534
|
const lastIndex = this.lastIndex
|
|
475
535
|
const currentIndex = this.currentIndex
|
|
476
536
|
|
|
537
|
+
// Set loading flag early so seekTo() calls during async URL resolution
|
|
538
|
+
// are captured as pending seeks rather than silently dropped
|
|
539
|
+
this._loadInProgress = true
|
|
540
|
+
this._pendingSeek = undefined
|
|
541
|
+
|
|
477
542
|
// Resolve the media URL before loading (async but we don't await)
|
|
543
|
+
const loadId = ++this.currentLoadId
|
|
478
544
|
const doLoad = async () => {
|
|
479
545
|
const resolvedTrack: Track = track.src
|
|
480
546
|
? { ...track, src: await this.resolveMediaUrl(track.src) }
|
|
481
547
|
: track
|
|
482
548
|
|
|
549
|
+
// A newer load() was called while resolving — discard this stale result
|
|
550
|
+
if (loadId !== this.currentLoadId) {
|
|
551
|
+
return
|
|
552
|
+
}
|
|
553
|
+
|
|
483
554
|
super.load(resolvedTrack, (loadedTrack) => {
|
|
484
555
|
this.onPlaybackActiveTrackChanged({
|
|
485
556
|
lastTrack,
|
|
486
557
|
lastPosition,
|
|
487
558
|
lastIndex,
|
|
488
559
|
index: currentIndex,
|
|
489
|
-
track
|
|
560
|
+
track
|
|
490
561
|
})
|
|
491
562
|
|
|
492
563
|
// Update now playing metadata
|
|
@@ -504,14 +575,13 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
504
575
|
|
|
505
576
|
// Execute async load without blocking, with error handling
|
|
506
577
|
doLoad().catch((error: unknown) => {
|
|
578
|
+
this._loadInProgress = false
|
|
579
|
+
this._pendingSeek = undefined
|
|
507
580
|
console.error('Error loading track:', error)
|
|
508
|
-
const message =
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
message,
|
|
513
|
-
},
|
|
514
|
-
})
|
|
581
|
+
const message =
|
|
582
|
+
error instanceof Error ? error.message : 'Failed to load track'
|
|
583
|
+
const playbackError = { code: 'load-error', message }
|
|
584
|
+
this.state = { state: 'error', error: playbackError }
|
|
515
585
|
})
|
|
516
586
|
}
|
|
517
587
|
|
|
@@ -525,7 +595,7 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
525
595
|
|
|
526
596
|
if (didChange) {
|
|
527
597
|
this.onPlaybackPlayWhenReadyChanged({
|
|
528
|
-
playWhenReady: this._playWhenReady
|
|
598
|
+
playWhenReady: this._playWhenReady
|
|
529
599
|
})
|
|
530
600
|
}
|
|
531
601
|
}
|
|
@@ -553,7 +623,7 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
553
623
|
|
|
554
624
|
if (didChange) {
|
|
555
625
|
this.onPlaybackRepeatModeChanged({
|
|
556
|
-
repeatMode: mode
|
|
626
|
+
repeatMode: mode
|
|
557
627
|
})
|
|
558
628
|
}
|
|
559
629
|
}
|
|
@@ -619,14 +689,24 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
619
689
|
}
|
|
620
690
|
|
|
621
691
|
// MARK: Queue management
|
|
622
|
-
setQueue(
|
|
692
|
+
setQueue(
|
|
693
|
+
tracks: Track[],
|
|
694
|
+
startIndex?: number,
|
|
695
|
+
startPositionMs?: number
|
|
696
|
+
): void {
|
|
623
697
|
this.stop()
|
|
698
|
+
// Clear stale references from previous queue
|
|
699
|
+
this.current = undefined
|
|
700
|
+
this._currentIndex = undefined
|
|
624
701
|
// Hydrate favorites and transform artwork URLs on all tracks in the queue
|
|
625
702
|
const artworkConfig = this.browserManager.configuration.artwork
|
|
626
|
-
this.playlist = tracks.map(track => {
|
|
703
|
+
this.playlist = tracks.map((track) => {
|
|
627
704
|
try {
|
|
628
705
|
const hydratedTrack = this.favoriteManager.hydrateFavorite(track)
|
|
629
|
-
return RequestConfigBuilder.transformTrackArtwork(
|
|
706
|
+
return RequestConfigBuilder.transformTrackArtwork(
|
|
707
|
+
hydratedTrack,
|
|
708
|
+
artworkConfig
|
|
709
|
+
)
|
|
630
710
|
} catch (error) {
|
|
631
711
|
console.error('Failed to transform track:', error)
|
|
632
712
|
return track // Use original track as fallback
|
|
@@ -673,7 +753,7 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
673
753
|
// Create updated track with new favorited state
|
|
674
754
|
const updatedTrack: Track = {
|
|
675
755
|
...track,
|
|
676
|
-
favorited
|
|
756
|
+
favorited
|
|
677
757
|
}
|
|
678
758
|
|
|
679
759
|
// Replace the track in the playlist
|
|
@@ -688,7 +768,7 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
688
768
|
lastTrack: track,
|
|
689
769
|
lastPosition: this.element?.currentTime ?? 0,
|
|
690
770
|
index,
|
|
691
|
-
track: updatedTrack
|
|
771
|
+
track: updatedTrack
|
|
692
772
|
})
|
|
693
773
|
|
|
694
774
|
// Emit queue changed so useQueue() hook updates
|
|
@@ -777,5 +857,4 @@ export class NativeAudioBrowser extends PlaylistPlayer implements AudioBrowserSp
|
|
|
777
857
|
openIosOutputPicker(): void {
|
|
778
858
|
// No-op on web
|
|
779
859
|
}
|
|
780
|
-
|
|
781
860
|
}
|
package/src/web/SimpleRouter.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - Most specific route wins
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { assertedNotNullish } from '../utils/validation'
|
|
18
|
+
|
|
17
19
|
export interface RouteMatch {
|
|
18
20
|
params: Record<string, string>
|
|
19
21
|
specificity: number
|
|
@@ -81,13 +83,19 @@ export class SimpleRouter {
|
|
|
81
83
|
|
|
82
84
|
// Match all segments except the tail wildcard
|
|
83
85
|
for (let i = 0; i < patternSegments.length - 1; i++) {
|
|
84
|
-
if (
|
|
86
|
+
if (
|
|
87
|
+
!this.matchSingleSegment(
|
|
88
|
+
assertedNotNullish(patternSegments[i]),
|
|
89
|
+
assertedNotNullish(pathSegments[i]),
|
|
90
|
+
params
|
|
91
|
+
)
|
|
92
|
+
) {
|
|
85
93
|
return null
|
|
86
94
|
}
|
|
87
95
|
// Note: The Kotlin implementation doesn't update counts here (appears to be a bug)
|
|
88
96
|
// but we'll match it exactly for consistency
|
|
89
97
|
this.updateSegmentCounts(
|
|
90
|
-
patternSegments[i]
|
|
98
|
+
assertedNotNullish(patternSegments[i]),
|
|
91
99
|
constantSegments,
|
|
92
100
|
parameterSegments,
|
|
93
101
|
wildcardSegments
|
|
@@ -106,15 +114,22 @@ export class SimpleRouter {
|
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
for (let i = 0; i < patternSegments.length; i++) {
|
|
109
|
-
if (
|
|
117
|
+
if (
|
|
118
|
+
!this.matchSingleSegment(
|
|
119
|
+
assertedNotNullish(patternSegments[i]),
|
|
120
|
+
assertedNotNullish(pathSegments[i]),
|
|
121
|
+
params
|
|
122
|
+
)
|
|
123
|
+
) {
|
|
110
124
|
return null
|
|
111
125
|
}
|
|
112
|
-
const [constCount, paramCount, wildcardCount] =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
const [constCount, paramCount, wildcardCount] =
|
|
127
|
+
this.updateSegmentCounts(
|
|
128
|
+
assertedNotNullish(patternSegments[i]),
|
|
129
|
+
constantSegments,
|
|
130
|
+
parameterSegments,
|
|
131
|
+
wildcardSegments
|
|
132
|
+
)
|
|
118
133
|
constantSegments = constCount
|
|
119
134
|
parameterSegments = paramCount
|
|
120
135
|
wildcardSegments = wildcardCount
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { State as StateType } from './State'
|
|
1
|
+
import type shaka from 'shaka-player/dist/shaka-player.ui'
|
|
3
2
|
import type {
|
|
4
3
|
Progress,
|
|
5
4
|
PartialSetupPlayerOptions,
|
|
6
5
|
Playback,
|
|
7
|
-
PlaybackError
|
|
6
|
+
PlaybackError
|
|
8
7
|
} from '../../features'
|
|
9
8
|
import type { Track } from '../../types'
|
|
9
|
+
import type { State as StateType } from './State'
|
|
10
10
|
import { SetupNotCalledError } from './SetupNotCalledError'
|
|
11
|
-
import
|
|
11
|
+
import { State } from './State'
|
|
12
12
|
|
|
13
13
|
// Extend Window interface for debug purposes
|
|
14
14
|
declare global {
|
|
@@ -38,6 +38,9 @@ export class Player {
|
|
|
38
38
|
protected _current?: Track = undefined
|
|
39
39
|
protected _playWhenReady = false
|
|
40
40
|
protected _state: Playback = { state: State.None }
|
|
41
|
+
protected _isStopped = false
|
|
42
|
+
protected _loadInProgress = false
|
|
43
|
+
protected _pendingSeek: number | undefined
|
|
41
44
|
|
|
42
45
|
// current getter/setter
|
|
43
46
|
public get current(): Track | undefined {
|
|
@@ -86,7 +89,7 @@ export class Player {
|
|
|
86
89
|
if (this.hasInitialized === true) {
|
|
87
90
|
const error: PlaybackError = {
|
|
88
91
|
code: 'player_already_initialized',
|
|
89
|
-
message: 'The player has already been initialized via setupPlayer.'
|
|
92
|
+
message: 'The player has already been initialized via setupPlayer.'
|
|
90
93
|
}
|
|
91
94
|
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
92
95
|
throw error
|
|
@@ -102,8 +105,8 @@ export class Player {
|
|
|
102
105
|
state: State.Error,
|
|
103
106
|
error: {
|
|
104
107
|
code: 'not_supported',
|
|
105
|
-
message: 'Browser not supported...'
|
|
106
|
-
}
|
|
108
|
+
message: 'Browser not supported...'
|
|
109
|
+
}
|
|
107
110
|
}
|
|
108
111
|
throw new Error('Browser not supported.')
|
|
109
112
|
}
|
|
@@ -152,6 +155,8 @@ export class Player {
|
|
|
152
155
|
* event handlers
|
|
153
156
|
*/
|
|
154
157
|
protected onStateUpdate(state: Exclude<StateType, typeof State.Error>): void {
|
|
158
|
+
// Ignore Shaka/element events while stopped (e.g., from unload)
|
|
159
|
+
if (this._isStopped) return
|
|
155
160
|
this.state = { state }
|
|
156
161
|
}
|
|
157
162
|
|
|
@@ -178,34 +183,33 @@ export class Player {
|
|
|
178
183
|
|
|
179
184
|
const error: PlaybackError = {
|
|
180
185
|
code: shakaError.code.toString(),
|
|
181
|
-
message: shakaError.message
|
|
186
|
+
message: shakaError.message
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
this.state = {
|
|
185
190
|
state: State.Error,
|
|
186
|
-
error
|
|
191
|
+
error
|
|
187
192
|
}
|
|
188
193
|
|
|
189
194
|
// Log the error.
|
|
190
195
|
console.debug('Error code', shakaError.code, 'object', shakaError)
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
|
|
194
|
-
* NOTE: this method is sync despite the actual load being async. This
|
|
195
|
-
* behavior is intentional as it mirrors what happens in Android. State
|
|
196
|
-
* changes should be captured by event listeners.
|
|
197
|
-
*/
|
|
198
|
-
public load(track: Track, onComplete?: (track: Track) => void): void {
|
|
198
|
+
public load(track: Track, onLoaded?: (track: Track) => void): void {
|
|
199
199
|
const player = this.requirePlayer()
|
|
200
|
+
this._isStopped = false
|
|
201
|
+
this._loadInProgress = true
|
|
200
202
|
|
|
201
203
|
if (!track.src) {
|
|
204
|
+
this._loadInProgress = false
|
|
205
|
+
this._pendingSeek = undefined
|
|
202
206
|
const error: PlaybackError = {
|
|
203
207
|
code: 'invalid_track',
|
|
204
|
-
message: 'Track does not have a valid src URL'
|
|
208
|
+
message: 'Track does not have a valid src URL'
|
|
205
209
|
}
|
|
206
210
|
this.state = {
|
|
207
211
|
state: State.Error,
|
|
208
|
-
error
|
|
212
|
+
error
|
|
209
213
|
}
|
|
210
214
|
return
|
|
211
215
|
}
|
|
@@ -213,8 +217,15 @@ export class Player {
|
|
|
213
217
|
player
|
|
214
218
|
.load(track.src)
|
|
215
219
|
.then(() => {
|
|
220
|
+
this._loadInProgress = false
|
|
216
221
|
this.current = track
|
|
217
|
-
|
|
222
|
+
onLoaded?.(track)
|
|
223
|
+
|
|
224
|
+
// Execute any pending seek that arrived during loading
|
|
225
|
+
if (this._pendingSeek !== undefined) {
|
|
226
|
+
this.requireElement().currentTime = this._pendingSeek
|
|
227
|
+
this._pendingSeek = undefined
|
|
228
|
+
}
|
|
218
229
|
|
|
219
230
|
// Auto-play if playWhenReady is true
|
|
220
231
|
if (this.playWhenReady) {
|
|
@@ -222,19 +233,23 @@ export class Player {
|
|
|
222
233
|
}
|
|
223
234
|
})
|
|
224
235
|
.catch((err: unknown) => {
|
|
236
|
+
this._loadInProgress = false
|
|
237
|
+
this._pendingSeek = undefined
|
|
225
238
|
this.onError(this.toNormalizedError(err))
|
|
226
239
|
})
|
|
227
240
|
}
|
|
228
241
|
|
|
229
|
-
/**
|
|
230
|
-
* NOTE: this method is sync despite the actual load being async. This
|
|
231
|
-
* behavior is intentional as it mirrors what happens in Android. State
|
|
232
|
-
* changes should be captured by event listeners.
|
|
233
|
-
*/
|
|
234
242
|
public stop(onComplete?: () => void): void {
|
|
235
243
|
const player = this.requirePlayer()
|
|
236
244
|
|
|
237
|
-
|
|
245
|
+
// Match Android: stop sets playWhenReady=false and state=stopped,
|
|
246
|
+
// but keeps the current track so play() can resume.
|
|
247
|
+
this._isStopped = true
|
|
248
|
+
this._loadInProgress = false
|
|
249
|
+
this._pendingSeek = undefined
|
|
250
|
+
this.playWhenReady = false
|
|
251
|
+
this.state = { state: State.Stopped }
|
|
252
|
+
|
|
238
253
|
player
|
|
239
254
|
.unload()
|
|
240
255
|
.then(() => onComplete?.())
|
|
@@ -244,11 +259,6 @@ export class Player {
|
|
|
244
259
|
})
|
|
245
260
|
}
|
|
246
261
|
|
|
247
|
-
/**
|
|
248
|
-
* NOTE: this method is sync despite the actual load being async. This
|
|
249
|
-
* behavior is intentional as it mirrors what happens in Android. State
|
|
250
|
-
* changes should be captured by event listeners.
|
|
251
|
-
*/
|
|
252
262
|
public play(): void {
|
|
253
263
|
const element = this.requireElement()
|
|
254
264
|
this.playWhenReady = true
|
|
@@ -258,6 +268,12 @@ export class Player {
|
|
|
258
268
|
return
|
|
259
269
|
}
|
|
260
270
|
|
|
271
|
+
// Match Android: play() after stop() re-prepares the current track
|
|
272
|
+
if (this._isStopped && this.current) {
|
|
273
|
+
this.load(this.current)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
261
277
|
element.play().catch((err: unknown) => console.error(err))
|
|
262
278
|
}
|
|
263
279
|
|
|
@@ -293,11 +309,19 @@ export class Player {
|
|
|
293
309
|
}
|
|
294
310
|
|
|
295
311
|
public seekBy(offset: number): void {
|
|
312
|
+
if (this._loadInProgress) {
|
|
313
|
+
this._pendingSeek = (this._pendingSeek ?? 0) + offset
|
|
314
|
+
return
|
|
315
|
+
}
|
|
296
316
|
const element = this.requireElement()
|
|
297
317
|
element.currentTime += offset
|
|
298
318
|
}
|
|
299
319
|
|
|
300
320
|
public seekTo(seconds: number): void {
|
|
321
|
+
if (this._loadInProgress) {
|
|
322
|
+
this._pendingSeek = seconds
|
|
323
|
+
return
|
|
324
|
+
}
|
|
301
325
|
const element = this.requireElement()
|
|
302
326
|
element.currentTime = seconds
|
|
303
327
|
}
|
|
@@ -314,10 +338,14 @@ export class Player {
|
|
|
314
338
|
|
|
315
339
|
public getProgress(): Progress {
|
|
316
340
|
const element = this.requireElement()
|
|
341
|
+
let buffered = 0
|
|
342
|
+
if (element.buffered.length > 0) {
|
|
343
|
+
buffered = element.buffered.end(element.buffered.length - 1)
|
|
344
|
+
}
|
|
317
345
|
return {
|
|
318
346
|
position: element.currentTime,
|
|
319
347
|
duration: element.duration || 0,
|
|
320
|
-
buffered
|
|
348
|
+
buffered
|
|
321
349
|
}
|
|
322
350
|
}
|
|
323
351
|
}
|