@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,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
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Player } from './Player'
|
|
2
|
-
import { State } from './State'
|
|
3
|
-
import type { State as StateType } from './State'
|
|
4
|
-
|
|
5
|
-
import type { Track } from '../../types'
|
|
6
1
|
import type { RepeatMode as RepeatModeType } from '../../features'
|
|
7
|
-
import {
|
|
2
|
+
import type { Track } from '../../types'
|
|
3
|
+
import type { State as StateType } from './State'
|
|
4
|
+
import { assertedNotNullish } from '../../utils/validation'
|
|
8
5
|
import { fisherYatesShuffle } from '../util/shuffle'
|
|
6
|
+
import { Player } from './Player'
|
|
7
|
+
import { RepeatMode } from './RepeatMode'
|
|
8
|
+
import { State } from './State'
|
|
9
9
|
|
|
10
10
|
export class PlaylistPlayer extends Player {
|
|
11
11
|
// TODO: use immer to make the `playlist` immutable
|
|
@@ -19,6 +19,8 @@ export class PlaylistPlayer extends Player {
|
|
|
19
19
|
protected onStateUpdate(state: Exclude<StateType, typeof State.Error>) {
|
|
20
20
|
super.onStateUpdate(state)
|
|
21
21
|
|
|
22
|
+
if (this._isStopped) return
|
|
23
|
+
|
|
22
24
|
if (state === State.Ended) {
|
|
23
25
|
this.onTrackEnded()
|
|
24
26
|
}
|
|
@@ -63,19 +65,15 @@ export class PlaylistPlayer extends Player {
|
|
|
63
65
|
if (!track) return
|
|
64
66
|
|
|
65
67
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
66
|
-
const
|
|
68
|
+
const onLoaded = (_track: Track) => {
|
|
67
69
|
if (initialPosition !== undefined) {
|
|
68
70
|
this.seekTo(initialPosition)
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
if (this.playWhenReady) {
|
|
72
|
-
this.play()
|
|
73
|
-
}
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
if (this.currentIndex !== index) {
|
|
77
75
|
this.currentIndex = index
|
|
78
|
-
this.load(track,
|
|
76
|
+
this.load(track, onLoaded)
|
|
79
77
|
} else {
|
|
80
78
|
// Replay the same track - seek to start (or initialPosition if specified)
|
|
81
79
|
this.seekTo(initialPosition ?? 0)
|
|
@@ -124,7 +122,8 @@ export class PlaylistPlayer extends Player {
|
|
|
124
122
|
public skipToPrevious(initialPosition?: number): void {
|
|
125
123
|
if (this.currentIndex === undefined) return
|
|
126
124
|
|
|
127
|
-
const previousIndex =
|
|
125
|
+
const previousIndex =
|
|
126
|
+
this.getPreviousIndex() ?? this.getWrapAroundLastIndex()
|
|
128
127
|
if (previousIndex === undefined) return
|
|
129
128
|
|
|
130
129
|
this.goToIndex(previousIndex, initialPosition)
|
|
@@ -184,13 +183,17 @@ export class PlaylistPlayer extends Player {
|
|
|
184
183
|
|
|
185
184
|
protected getWrapAroundLastIndex(): number | undefined {
|
|
186
185
|
if (this.repeatMode !== RepeatMode.Playlist) return undefined
|
|
187
|
-
if (this.shuffleEnabled)
|
|
186
|
+
if (this.shuffleEnabled)
|
|
187
|
+
return this.shuffleOrder[this.shuffleOrder.length - 1]
|
|
188
188
|
return this.playlist.length - 1
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
protected generateShuffleOrder(): void {
|
|
192
192
|
// Create array of indices [0, 1, 2, ..., n-1]
|
|
193
|
-
this.shuffleOrder = Array.from(
|
|
193
|
+
this.shuffleOrder = Array.from(
|
|
194
|
+
{ length: this.playlist.length },
|
|
195
|
+
(_, i) => i
|
|
196
|
+
)
|
|
194
197
|
|
|
195
198
|
// Shuffle the indices
|
|
196
199
|
fisherYatesShuffle(this.shuffleOrder)
|
|
@@ -203,8 +206,8 @@ export class PlaylistPlayer extends Player {
|
|
|
203
206
|
// If there is a current track, move it to the beginning of the shuffle order
|
|
204
207
|
const currentPos = this.shuffleOrder.indexOf(this.currentIndex)
|
|
205
208
|
if (currentPos > 0) {
|
|
206
|
-
const temp = this.shuffleOrder[0]
|
|
207
|
-
this.shuffleOrder[0] = this.shuffleOrder[currentPos]
|
|
209
|
+
const temp = assertedNotNullish(this.shuffleOrder[0])
|
|
210
|
+
this.shuffleOrder[0] = assertedNotNullish(this.shuffleOrder[currentPos])
|
|
208
211
|
this.shuffleOrder[currentPos] = temp
|
|
209
212
|
}
|
|
210
213
|
}
|
|
@@ -236,16 +239,19 @@ export class PlaylistPlayer extends Player {
|
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
public remove(indexes: number[]): void {
|
|
239
|
-
const
|
|
240
|
-
acc[elem] = true
|
|
241
|
-
return acc
|
|
242
|
-
}, {})
|
|
242
|
+
const idxSet = new Set(indexes)
|
|
243
243
|
let isCurrentRemoved = false
|
|
244
|
+
let removedBeforeCurrent = 0
|
|
245
|
+
|
|
244
246
|
this.playlist = this.playlist.filter((_track, idx) => {
|
|
245
|
-
const keep = !
|
|
247
|
+
const keep = !idxSet.has(idx)
|
|
246
248
|
|
|
247
|
-
if (!keep
|
|
248
|
-
|
|
249
|
+
if (!keep) {
|
|
250
|
+
if (idx === this.currentIndex) {
|
|
251
|
+
isCurrentRemoved = true
|
|
252
|
+
} else if (this.currentIndex !== undefined && idx < this.currentIndex) {
|
|
253
|
+
removedBeforeCurrent++
|
|
254
|
+
}
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
return keep
|
|
@@ -255,11 +261,22 @@ export class PlaylistPlayer extends Player {
|
|
|
255
261
|
return
|
|
256
262
|
}
|
|
257
263
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
if (isCurrentRemoved) {
|
|
265
|
+
const hasItems = this.playlist.length > 0
|
|
266
|
+
if (hasItems) {
|
|
267
|
+
// Adjust for removed items before current, then clamp to valid range
|
|
268
|
+
const adjustedIndex = this.currentIndex - removedBeforeCurrent
|
|
269
|
+
// Reset so goToIndex always loads the new track at this position
|
|
270
|
+
this._currentIndex = undefined
|
|
271
|
+
this.goToIndex(Math.min(adjustedIndex, this.playlist.length - 1))
|
|
272
|
+
} else {
|
|
273
|
+
this.current = undefined
|
|
274
|
+
this._currentIndex = undefined
|
|
275
|
+
this.stop()
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// Adjust currentIndex to account for removed items before it
|
|
279
|
+
this._currentIndex = this.currentIndex - removedBeforeCurrent
|
|
263
280
|
}
|
|
264
281
|
|
|
265
282
|
// Regenerate shuffle order when tracks are removed
|
|
@@ -269,16 +286,17 @@ export class PlaylistPlayer extends Player {
|
|
|
269
286
|
}
|
|
270
287
|
|
|
271
288
|
public stop(onComplete?: () => void): void {
|
|
272
|
-
super.stop(
|
|
273
|
-
this.currentIndex = undefined
|
|
274
|
-
onComplete?.()
|
|
275
|
-
})
|
|
289
|
+
super.stop(onComplete)
|
|
276
290
|
}
|
|
277
291
|
|
|
278
292
|
public reset(): void {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
293
|
+
// Clear queue state synchronously so subsequent add()/load() calls
|
|
294
|
+
// see a clean slate. The async player.unload() in stop() can finish
|
|
295
|
+
// in the background — it only releases the Shaka source.
|
|
296
|
+
this.playlist = []
|
|
297
|
+
this.current = undefined
|
|
298
|
+
this._currentIndex = undefined
|
|
299
|
+
this.stop()
|
|
282
300
|
}
|
|
283
301
|
|
|
284
302
|
public removeUpcomingTracks(): void {
|
|
@@ -296,37 +314,28 @@ export class PlaylistPlayer extends Player {
|
|
|
296
314
|
throw new Error('index out of bounds')
|
|
297
315
|
}
|
|
298
316
|
|
|
299
|
-
|
|
300
|
-
throw new Error('you cannot move the currently playing track')
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (this.currentIndex === toIndex) {
|
|
304
|
-
throw new Error('you cannot replace the currently playing track')
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// calculate `currentIndex` after move
|
|
308
|
-
let shift: number | undefined
|
|
309
|
-
if (
|
|
310
|
-
this.currentIndex !== undefined &&
|
|
311
|
-
fromIndex < this.currentIndex &&
|
|
312
|
-
toIndex > this.currentIndex
|
|
313
|
-
) {
|
|
314
|
-
shift = -1
|
|
315
|
-
} else if (
|
|
316
|
-
this.currentIndex !== undefined &&
|
|
317
|
-
fromIndex > this.currentIndex &&
|
|
318
|
-
toIndex < this.currentIndex
|
|
319
|
-
) {
|
|
320
|
-
shift = 1
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// move the track
|
|
317
|
+
// Move the track in the playlist
|
|
324
318
|
const fromItem = this.playlist[fromIndex]
|
|
325
319
|
this.playlist.splice(fromIndex, 1)
|
|
326
320
|
this.playlist.splice(toIndex, 0, fromItem)
|
|
327
321
|
|
|
328
|
-
|
|
329
|
-
|
|
322
|
+
// Update currentIndex to track the currently playing item's new position.
|
|
323
|
+
// Matches Android's exoPlayer.moveMediaItem() which has no restrictions.
|
|
324
|
+
if (this.currentIndex !== undefined) {
|
|
325
|
+
if (fromIndex === this.currentIndex) {
|
|
326
|
+
// Moving the current track — follow it to its new position
|
|
327
|
+
this._currentIndex = toIndex
|
|
328
|
+
} else if (
|
|
329
|
+
fromIndex < this.currentIndex &&
|
|
330
|
+
toIndex >= this.currentIndex
|
|
331
|
+
) {
|
|
332
|
+
this._currentIndex = this.currentIndex - 1
|
|
333
|
+
} else if (
|
|
334
|
+
fromIndex > this.currentIndex &&
|
|
335
|
+
toIndex <= this.currentIndex
|
|
336
|
+
) {
|
|
337
|
+
this._currentIndex = this.currentIndex + 1
|
|
338
|
+
}
|
|
330
339
|
}
|
|
331
340
|
|
|
332
341
|
// Regenerate shuffle order when tracks are moved
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { PlaybackState } from '../../features'
|
|
2
2
|
|
|
3
3
|
export const State = {
|
|
4
|
-
None: 'none'
|
|
5
|
-
Ready: 'ready'
|
|
6
|
-
Playing: 'playing'
|
|
7
|
-
Paused: 'paused'
|
|
8
|
-
Stopped: 'stopped'
|
|
9
|
-
Loading: 'loading'
|
|
10
|
-
Buffering: 'buffering'
|
|
11
|
-
Error: 'error'
|
|
12
|
-
Ended: 'ended'
|
|
4
|
+
None: 'none',
|
|
5
|
+
Ready: 'ready',
|
|
6
|
+
Playing: 'playing',
|
|
7
|
+
Paused: 'paused',
|
|
8
|
+
Stopped: 'stopped',
|
|
9
|
+
Loading: 'loading',
|
|
10
|
+
Buffering: 'buffering',
|
|
11
|
+
Error: 'error',
|
|
12
|
+
Ended: 'ended'
|
|
13
13
|
} satisfies Record<string, PlaybackState>
|
|
14
14
|
|
|
15
15
|
export type State = (typeof State)[keyof typeof State]
|