@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.
Files changed (151) hide show
  1. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +11 -71
  2. package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +125 -0
  3. package/ios/Browser/BrowserManager.swift +17 -2
  4. package/ios/HybridAudioBrowser.swift +7 -0
  5. package/ios/TrackPlayer.swift +150 -31
  6. package/lib/commonjs/features/browser.js.map +1 -1
  7. package/lib/commonjs/features/equalizer.js.map +1 -1
  8. package/lib/commonjs/features/errors.js.map +1 -1
  9. package/lib/commonjs/features/favorites.js.map +1 -1
  10. package/lib/commonjs/features/metadata.js.map +1 -1
  11. package/lib/commonjs/features/nowPlaying.js.map +1 -1
  12. package/lib/commonjs/features/output.js.map +1 -1
  13. package/lib/commonjs/features/playback/state.js.map +1 -1
  14. package/lib/commonjs/features/player/options.js.map +1 -1
  15. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  16. package/lib/commonjs/features/queue/queue.js +1 -1
  17. package/lib/commonjs/features/queue/queue.js.map +1 -1
  18. package/lib/commonjs/features/remoteControls.js.map +1 -1
  19. package/lib/commonjs/utils/useDebug.js.map +1 -1
  20. package/lib/commonjs/utils/validation.js +23 -0
  21. package/lib/commonjs/utils/validation.js.map +1 -0
  22. package/lib/commonjs/web/NativeAudioBrowser.js +53 -15
  23. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  24. package/lib/commonjs/web/SimpleRouter.js +5 -4
  25. package/lib/commonjs/web/SimpleRouter.js.map +1 -1
  26. package/lib/commonjs/web/TrackPlayer/Player.js +49 -24
  27. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  28. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +54 -45
  29. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +1 -1
  30. package/lib/commonjs/web/TrackPlayer/State.js.map +1 -1
  31. package/lib/commonjs/web/browser/BrowserManager.js +10 -17
  32. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  33. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  34. package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
  35. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  36. package/lib/commonjs/web/http/HttpClient.js +15 -2
  37. package/lib/commonjs/web/http/HttpClient.js.map +1 -1
  38. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  39. package/lib/commonjs/web/player/NowPlayingManager.js +9 -0
  40. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  41. package/lib/commonjs/web/player/OptionsManager.js +1 -1
  42. package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
  43. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  44. package/lib/module/features/browser.js.map +1 -1
  45. package/lib/module/features/equalizer.js.map +1 -1
  46. package/lib/module/features/errors.js.map +1 -1
  47. package/lib/module/features/favorites.js.map +1 -1
  48. package/lib/module/features/metadata.js.map +1 -1
  49. package/lib/module/features/nowPlaying.js +1 -0
  50. package/lib/module/features/nowPlaying.js.map +1 -1
  51. package/lib/module/features/output.js.map +1 -1
  52. package/lib/module/features/playback/state.js.map +1 -1
  53. package/lib/module/features/player/options.js.map +1 -1
  54. package/lib/module/features/queue/activeTrack.js.map +1 -1
  55. package/lib/module/features/queue/queue.js +1 -1
  56. package/lib/module/features/queue/queue.js.map +1 -1
  57. package/lib/module/features/remoteControls.js.map +1 -1
  58. package/lib/module/utils/useDebug.js.map +1 -1
  59. package/lib/module/utils/validation.js +18 -0
  60. package/lib/module/utils/validation.js.map +1 -0
  61. package/lib/module/web/NativeAudioBrowser.js +53 -15
  62. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  63. package/lib/module/web/SimpleRouter.js +5 -4
  64. package/lib/module/web/SimpleRouter.js.map +1 -1
  65. package/lib/module/web/TrackPlayer/Player.js +49 -24
  66. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  67. package/lib/module/web/TrackPlayer/PlaylistPlayer.js +54 -45
  68. package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +1 -1
  69. package/lib/module/web/TrackPlayer/State.js.map +1 -1
  70. package/lib/module/web/browser/BrowserManager.js +10 -17
  71. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  72. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  73. package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
  74. package/lib/module/web/browser/SearchManager.js.map +1 -1
  75. package/lib/module/web/http/HttpClient.js +15 -2
  76. package/lib/module/web/http/HttpClient.js.map +1 -1
  77. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  78. package/lib/module/web/player/NowPlayingManager.js +9 -0
  79. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  80. package/lib/module/web/player/OptionsManager.js +1 -1
  81. package/lib/module/web/player/OptionsManager.js.map +1 -1
  82. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  83. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  84. package/lib/typescript/src/features/equalizer.d.ts.map +1 -1
  85. package/lib/typescript/src/features/errors.d.ts.map +1 -1
  86. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  87. package/lib/typescript/src/features/metadata.d.ts +1 -1
  88. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  89. package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
  90. package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
  91. package/lib/typescript/src/features/output.d.ts.map +1 -1
  92. package/lib/typescript/src/features/playback/state.d.ts +1 -1
  93. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  94. package/lib/typescript/src/features/player/options.d.ts +1 -1
  95. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  96. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  97. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  98. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  99. package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/useDebug.d.ts +1 -1
  101. package/lib/typescript/src/utils/useDebug.d.ts.map +1 -1
  102. package/lib/typescript/src/utils/validation.d.ts +3 -0
  103. package/lib/typescript/src/utils/validation.d.ts.map +1 -0
  104. package/lib/typescript/src/web/NativeAudioBrowser.d.ts +6 -1
  105. package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +1 -1
  106. package/lib/typescript/src/web/SimpleRouter.d.ts.map +1 -1
  107. package/lib/typescript/src/web/TrackPlayer/Player.d.ts +7 -19
  108. package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +1 -1
  109. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +3 -3
  110. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +1 -1
  111. package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +1 -1
  112. package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +1 -1
  113. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +1 -1
  114. package/lib/typescript/src/web/browser/SearchManager.d.ts +1 -1
  115. package/lib/typescript/src/web/browser/SearchManager.d.ts.map +1 -1
  116. package/lib/typescript/src/web/http/HttpClient.d.ts.map +1 -1
  117. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +1 -1
  118. package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +1 -1
  119. package/lib/typescript/src/web/player/OptionsManager.d.ts.map +1 -1
  120. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +1 -1
  121. package/package.json +1 -1
  122. package/src/features/browser.ts +1 -1
  123. package/src/features/equalizer.ts +1 -1
  124. package/src/features/errors.ts +1 -1
  125. package/src/features/favorites.ts +1 -1
  126. package/src/features/metadata.ts +1 -1
  127. package/src/features/nowPlaying.ts +1 -1
  128. package/src/features/output.ts +1 -1
  129. package/src/features/playback/state.ts +1 -1
  130. package/src/features/player/options.ts +2 -2
  131. package/src/features/queue/activeTrack.ts +1 -1
  132. package/src/features/queue/queue.ts +2 -2
  133. package/src/features/remoteControls.ts +2 -2
  134. package/src/specs/audio-browser.nitro.ts +0 -1
  135. package/src/utils/useDebug.ts +6 -6
  136. package/src/utils/validation.ts +27 -0
  137. package/src/web/NativeAudioBrowser.ts +137 -58
  138. package/src/web/SimpleRouter.ts +24 -9
  139. package/src/web/TrackPlayer/Player.ts +58 -30
  140. package/src/web/TrackPlayer/PlaylistPlayer.ts +72 -63
  141. package/src/web/TrackPlayer/RepeatMode.ts +1 -1
  142. package/src/web/TrackPlayer/State.ts +9 -9
  143. package/src/web/browser/BrowserManager.ts +124 -67
  144. package/src/web/browser/FavoriteManager.ts +5 -3
  145. package/src/web/browser/NavigationErrorManager.ts +15 -8
  146. package/src/web/browser/SearchManager.ts +17 -11
  147. package/src/web/http/HttpClient.ts +25 -7
  148. package/src/web/http/RequestConfigBuilder.ts +29 -13
  149. package/src/web/player/NowPlayingManager.ts +13 -7
  150. package/src/web/player/OptionsManager.ts +7 -6
  151. package/src/web/util/BrowserPathHelper.ts +3 -2
@@ -1,14 +1,14 @@
1
- import { State } from './State'
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 type shaka from 'shaka-player/dist/shaka-player.ui'
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
- onComplete?.(track)
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
- this.current = undefined
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: 0, // TODO: element.buffered.end,
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 { RepeatMode } from './RepeatMode'
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 onCompletedLoading = (_track: Track) => {
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, onCompletedLoading)
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 = this.getPreviousIndex() ?? this.getWrapAroundLastIndex()
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) return this.shuffleOrder[this.shuffleOrder.length - 1]
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({ length: this.playlist.length }, (_, i) => i)
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 idxMap = indexes.reduce<Record<number, boolean>>((acc, elem) => {
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 = !idxMap[idx]
247
+ const keep = !idxSet.has(idx)
246
248
 
247
- if (!keep && idx === this.currentIndex) {
248
- isCurrentRemoved = true
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
- const hasItems = this.playlist.length > 0
259
- if (isCurrentRemoved && hasItems) {
260
- this.goToIndex(this.currentIndex % this.playlist.length)
261
- } else if (isCurrentRemoved) {
262
- this.stop()
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
- this.stop(() => {
280
- this.playlist = []
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
- if (this.currentIndex === fromIndex) {
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
- if (this.currentIndex !== undefined && shift) {
329
- this.currentIndex = this.currentIndex + shift
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
@@ -3,5 +3,5 @@ import type { RepeatMode as RepeatModeType } from '../../features'
3
3
  export const RepeatMode = {
4
4
  Off: 'off' as const,
5
5
  Track: 'track' as const,
6
- Playlist: 'queue' as const,
6
+ Playlist: 'queue' as const
7
7
  } satisfies Record<string, RepeatModeType>
@@ -1,15 +1,15 @@
1
1
  import type { PlaybackState } from '../../features'
2
2
 
3
3
  export const State = {
4
- None: 'none' as const,
5
- Ready: 'ready' as const,
6
- Playing: 'playing' as const,
7
- Paused: 'paused' as const,
8
- Stopped: 'stopped' as const,
9
- Loading: 'loading' as const,
10
- Buffering: 'buffering' as const,
11
- Error: 'error' as const,
12
- Ended: 'ended' as const
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]