gapless.js 4.0.1 → 4.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gapless.js",
3
- "version": "4.0.1",
3
+ "version": "4.0.2",
4
4
  "description": "Gapless audio playback javascript plugin",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
package/src/Track.ts CHANGED
@@ -227,9 +227,10 @@ export class Track {
227
227
 
228
228
  activate(): void {
229
229
  const state = this._actor.getSnapshot().value;
230
- // If the track was in webaudio state (e.g. from gapless scheduling or
231
- // preload BUFFER_READY), reset it so play() can start from a clean state.
232
- if (state === 'webaudio') {
230
+ // If the track was in webaudio or loading state (e.g. from gapless scheduling,
231
+ // preload BUFFER_READY, or stuck in loading), reset it so play() can start
232
+ // from a clean state.
233
+ if (state === 'webaudio' || state === 'loading') {
233
234
  this._stopSourceNode();
234
235
  this._disconnectGain();
235
236
  this.scheduledStartContextTime = null;
@@ -7,6 +7,16 @@
7
7
  // loading Track is preloaded (not yet playing). Decode in progress.
8
8
  // webaudio AudioBufferSourceNode is the active output.
9
9
  //
10
+ // Design invariant — "Web Audio always wins eventually":
11
+ // When a track's buffer finishes decoding (BUFFER_READY), webAudioLoadingState
12
+ // is set to 'LOADED' regardless of what state the machine is in (html5, loading,
13
+ // or idle). We intentionally do NOT switch mid-stream — the track stays in html5
14
+ // until the next play(). But every state handles BUFFER_READY, so the flag is
15
+ // never lost, and the next play() will see the buffer and use Web Audio.
16
+ //
17
+ // All DEACTIVATE transitions land in idle (not loading), so a deactivated track
18
+ // with a loaded buffer is always in idle+LOADED — ready for Web Audio on re-play.
19
+ //
10
20
  // Bug fixes in this rewrite:
11
21
  // #2: BUFFER_READY in html5 stays in html5 (no longer auto-transitions to webaudio)
12
22
  // #3: DEACTIVATE from webaudio → idle (was staying in webaudio)
@@ -83,6 +93,12 @@ export function createTrackMachine(initialContext: TrackContext) {
83
93
  BUFFER_LOADING: {
84
94
  actions: assign({ webAudioLoadingState: () => 'LOADING' as WebAudioLoadingState }),
85
95
  },
96
+ BUFFER_READY: {
97
+ actions: assign({ webAudioLoadingState: () => 'LOADED' as WebAudioLoadingState }),
98
+ },
99
+ BUFFER_ERROR: {
100
+ actions: assign({ webAudioLoadingState: () => 'ERROR' as WebAudioLoadingState }),
101
+ },
86
102
  URL_RESOLVED: {
87
103
  actions: assign({
88
104
  resolvedUrl: ({ event }) => (event as { type: 'URL_RESOLVED'; url: string }).url,
@@ -140,7 +156,7 @@ export function createTrackMachine(initialContext: TrackContext) {
140
156
  }),
141
157
  },
142
158
  DEACTIVATE: {
143
- target: 'loading',
159
+ target: 'idle',
144
160
  actions: assign({ isPlaying: () => false }),
145
161
  },
146
162
  },
@@ -178,6 +194,10 @@ export function createTrackMachine(initialContext: TrackContext) {
178
194
  playbackType: () => 'WEBAUDIO' as PlaybackType,
179
195
  }),
180
196
  },
197
+ DEACTIVATE: {
198
+ target: 'idle',
199
+ actions: assign({ isPlaying: () => false }),
200
+ },
181
201
  URL_RESOLVED: {
182
202
  actions: assign({
183
203
  resolvedUrl: ({ event }) => (event as { type: 'URL_RESOLVED'; url: string }).url,