@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,5 +1,5 @@
1
- import { nativeBrowser } from '../../native'
2
1
  import type { Track } from '../../types'
2
+ import { nativeBrowser } from '../../native'
3
3
  import { NativeUpdatedValue } from '../../utils/NativeUpdatedValue'
4
4
  import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue'
5
5
 
@@ -1,5 +1,5 @@
1
- import { nativeBrowser } from '../../native'
2
1
  import type { Track } from '../../types'
2
+ import { nativeBrowser } from '../../native'
3
3
  import { NativeUpdatedValue } from '../../utils/NativeUpdatedValue'
4
4
  import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue'
5
5
 
@@ -31,7 +31,7 @@ export function add(tracks: Track[], insertBeforeIndex?: number): void
31
31
  * By default the track will be added to the end of the queue.
32
32
  */
33
33
  export function add(track: Track, insertBeforeIndex?: number): void
34
- export function add(tracks: Track | Track[], insertBeforeIndex = -1): void {
34
+ export function add(tracks: Track | Track[], insertBeforeIndex?: number): void {
35
35
  const addTracks = Array.isArray(tracks) ? tracks : [tracks]
36
36
  if (addTracks.length > 0) {
37
37
  nativeBrowser.add(addTracks, insertBeforeIndex)
@@ -1,11 +1,11 @@
1
- import { nativeBrowser } from '../native'
2
- import { LazyNativeEmitter } from '../utils/LazyNativeEmitter'
3
1
  import type {
4
2
  HeartRating,
5
3
  PercentageRating,
6
4
  StarRating,
7
5
  ThumbsRating
8
6
  } from './rating'
7
+ import { nativeBrowser } from '../native'
8
+ import { LazyNativeEmitter } from '../utils/LazyNativeEmitter'
9
9
 
10
10
  // MARK: - Event Interfaces
11
11
 
@@ -1,5 +1,4 @@
1
1
  import { type HybridObject } from 'react-native-nitro-modules'
2
-
3
2
  import type {
4
3
  BatteryOptimizationStatus,
5
4
  BatteryOptimizationStatusChangedEvent,
@@ -1,4 +1,10 @@
1
1
  import { useEffect, useRef, useSyncExternalStore } from 'react'
2
+ import type { NowPlayingMetadata } from '../features/metadata'
3
+ import type { PlayingState } from '../features/playback/playing'
4
+ import type { PlaybackState } from '../features/playback/state'
5
+ import type { Options } from '../features/player/options'
6
+ import type { RepeatMode } from '../features/queue/repeatMode'
7
+ import type { Track } from '../types'
2
8
  import { useContent, usePath } from '../features/browser'
3
9
  import { useNavigationError, usePlaybackError } from '../features/errors'
4
10
  import {
@@ -8,21 +14,15 @@ import {
8
14
  } from '../features/metadata'
9
15
  import { useOnline } from '../features/network'
10
16
  import { useNowPlaying } from '../features/nowPlaying'
11
- import type { PlayingState } from '../features/playback/playing'
12
17
  import { usePlayingState } from '../features/playback/playing'
13
18
  import { usePlayWhenReady } from '../features/playback/playWhenReady'
14
19
  import { useProgress } from '../features/playback/progress'
15
- import type { PlaybackState } from '../features/playback/state'
16
20
  import { usePlayback } from '../features/playback/state'
17
- import type { Options } from '../features/player/options'
18
21
  import { useOptions } from '../features/player/options'
19
22
  import { useActiveTrack } from '../features/queue/activeTrack'
20
23
  import { useQueue } from '../features/queue/queue'
21
- import type { RepeatMode } from '../features/queue/repeatMode'
22
24
  import { useRepeatMode } from '../features/queue/repeatMode'
23
25
  import { useShuffle } from '../features/queue/shuffle'
24
- import type { NowPlayingMetadata } from '../features/metadata'
25
- import type { Track } from '../types'
26
26
 
27
27
  // MARK: - Types
28
28
 
@@ -0,0 +1,27 @@
1
+ function isNotNullish<T>(val: T | undefined | null | void): val is T {
2
+ return val !== undefined && val !== null
3
+ }
4
+
5
+ function assert(
6
+ condition: any,
7
+ message = 'assertion error'
8
+ ): asserts condition {
9
+ if (!condition) {
10
+ throw new Error(message)
11
+ }
12
+ }
13
+
14
+ export function assertNotNullish<T>(
15
+ val: T | undefined | null | void,
16
+ message = 'Expected value to not be nullish'
17
+ ): asserts val is T {
18
+ assert(isNotNullish(val), message)
19
+ }
20
+
21
+ export function assertedNotNullish<T>(
22
+ val: T | undefined | null | void,
23
+ message = 'Expected value to not be nullish'
24
+ ): T {
25
+ assertNotNullish(val, message)
26
+ return val
27
+ }
@@ -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 { PlaylistPlayer, SleepTimerManager } from './TrackPlayer'
46
- import { HttpClient } from './http/HttpClient'
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 { OptionsManager } from './player/OptionsManager'
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 extends PlaylistPlayer implements AudioBrowserSpec {
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 = typeof navigator !== 'undefined' ? navigator.onLine : true
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: (formattedError: FormattedNavigationError | undefined) => void = () => {}
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: (data: PlaybackActiveTrackChangedEvent) => void = () => {}
144
+ onPlaybackActiveTrackChanged: (
145
+ data: PlaybackActiveTrackChangedEvent
146
+ ) => void = () => {}
138
147
  onPlaybackError: (data: PlaybackErrorEvent) => void = () => {}
139
- onPlaybackPlayWhenReadyChanged: (data: PlaybackPlayWhenReadyChangedEvent) => void = () => {}
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: (event: BatteryWarningPendingChangedEvent) => void = () => {}
169
- onBatteryOptimizationStatusChanged: (event: BatteryOptimizationStatusChangedEvent) => void = () => {}
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: ((event: RemoteJumpBackwardEvent) => void) | undefined = undefined
177
- handleRemoteJumpForward: ((event: RemoteJumpForwardEvent) => void) | undefined = undefined
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 = undefined
183
- handleRemotePlaySearch: ((event: RemotePlaySearchEvent) => void) | undefined = undefined
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 = 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) => this.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) => this.onNavigationError(data)
217
- this.navigationErrorManager.onFormattedNavigationError = (error) => this.onFormattedNavigationError(error)
218
- this.optionsManager.onOptionsChanged = (options) => this.onOptionsChanged(options)
219
- this.nowPlayingManager.onNowPlayingChanged = (metadata) => this.onNowPlayingChanged(metadata)
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: playback.state === 'playing',
267
- buffering: playback.state === 'buffering',
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
- if (this.state.state === 'playing') {
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(trackId: string, parentPath: string): boolean {
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(track: Track, url: string | undefined): Promise<void> {
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 = error instanceof Error ? error.message : 'Failed to load track'
509
- this.onPlaybackError({
510
- error: {
511
- code: 'load-error',
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(tracks: Track[], startIndex?: number, startPositionMs?: number): void {
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(hydratedTrack, artworkConfig)
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
  }
@@ -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 (!this.matchSingleSegment(patternSegments[i]!, pathSegments[i]!, params)) {
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 (!this.matchSingleSegment(patternSegments[i]!, pathSegments[i]!, params)) {
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] = this.updateSegmentCounts(
113
- patternSegments[i]!,
114
- constantSegments,
115
- parameterSegments,
116
- wildcardSegments
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