gapless 4.0.12 → 4.1.1
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/README.md +64 -3
- package/dist/index.d.ts +38 -8
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/Queue.ts +56 -13
- package/src/Track.ts +38 -11
- package/src/machines/track.machine.ts +38 -9
- package/src/types.ts +24 -3
package/README.md
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Gapless.js
|
|
4
|
+
|
|
5
|
+
[](https://github.com/RelistenNet/gapless.js/actions/workflows/ci.yml)
|
|
6
|
+
[](https://bundlejs.com/?q=gapless&treeshake=[{+default+}])
|
|
2
7
|
|
|
3
8
|
Gapless audio player for the web. Takes an array of audio tracks and uses HTML5 audio with the Web Audio API to enable seamless, gapless transitions between tracks.
|
|
4
9
|
|
|
@@ -53,9 +58,11 @@ const player = new Queue({
|
|
|
53
58
|
onError: (error) => {}, // Called on audio errors
|
|
54
59
|
onPlayBlocked: () => {}, // Called when autoplay is blocked by the browser
|
|
55
60
|
onDebug: (msg) => {}, // Internal debug messages (development only)
|
|
56
|
-
|
|
61
|
+
playbackMethod: 'HYBRID', // 'HYBRID' | 'HTML5_ONLY' | 'WEBAUDIO_ONLY'
|
|
57
62
|
trackMetadata: [], // Per-track metadata (aligned by index)
|
|
58
63
|
volume: 1, // Initial volume, 0.0–1.0
|
|
64
|
+
preloadNumTracks: 2, // Number of tracks to preload ahead (0 to disable)
|
|
65
|
+
playbackRate: 1, // Initial playback rate, 0.25–4.0
|
|
59
66
|
});
|
|
60
67
|
```
|
|
61
68
|
|
|
@@ -71,6 +78,7 @@ const player = new Queue({
|
|
|
71
78
|
| `gotoTrack(index, playImmediately?)` | Jump to a track by index |
|
|
72
79
|
| `seek(time)` | Seek to a position in seconds |
|
|
73
80
|
| `setVolume(volume)` | Set volume (0.0–1.0) |
|
|
81
|
+
| `setPlaybackRate(rate)` | Set playback rate (0.25–4.0), reschedules gapless transitions |
|
|
74
82
|
| `addTrack(url, options?)` | Add a track to the end of the queue |
|
|
75
83
|
| `removeTrack(index)` | Remove a track by index |
|
|
76
84
|
| `resumeAudioContext()` | Resume the AudioContext (for browsers that require user gesture) |
|
|
@@ -86,6 +94,8 @@ const player = new Queue({
|
|
|
86
94
|
| `isPlaying` | `boolean` | Whether the queue is playing |
|
|
87
95
|
| `isPaused` | `boolean` | Whether the queue is paused |
|
|
88
96
|
| `volume` | `number` | Current volume |
|
|
97
|
+
| `playbackRate` | `number` | Current playback rate |
|
|
98
|
+
| `preloadNumTracks` | `number` | Number of tracks to preload ahead (read/write) |
|
|
89
99
|
|
|
90
100
|
### `TrackInfo`
|
|
91
101
|
|
|
@@ -103,6 +113,7 @@ interface TrackInfo {
|
|
|
103
113
|
playbackType: 'HTML5' | 'WEBAUDIO';
|
|
104
114
|
webAudioLoadingState: 'NONE' | 'LOADING' | 'LOADED' | 'ERROR';
|
|
105
115
|
metadata?: TrackMetadata;
|
|
116
|
+
playbackRate: number; // Current playback rate
|
|
106
117
|
machineState: string; // Internal state machine state
|
|
107
118
|
}
|
|
108
119
|
```
|
|
@@ -135,6 +146,56 @@ interface TrackMetadata {
|
|
|
135
146
|
}
|
|
136
147
|
```
|
|
137
148
|
|
|
149
|
+
## Playback Method
|
|
150
|
+
|
|
151
|
+
The `playbackMethod` option controls how audio is rendered:
|
|
152
|
+
|
|
153
|
+
| Value | Behavior | Use case |
|
|
154
|
+
|-------|----------|----------|
|
|
155
|
+
| `'HYBRID'` (default) | Starts with HTML5 audio, switches to Web Audio after decode | Remote files — instant playback + gapless transitions |
|
|
156
|
+
| `'HTML5_ONLY'` | HTML5 audio exclusively, no Web Audio | When Web Audio is unavailable or unwanted; gapless playback disabled |
|
|
157
|
+
| `'WEBAUDIO_ONLY'` | Web Audio API exclusively, no HTML5 fallback | Very small or local files where buffering is instant |
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// Web Audio only — waits for decode before playing
|
|
161
|
+
const player = new Queue({
|
|
162
|
+
tracks: ['track1.mp3', 'track2.mp3'],
|
|
163
|
+
playbackMethod: 'WEBAUDIO_ONLY',
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
player.play(); // Waits for decode, then plays via Web Audio
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Preload Count
|
|
170
|
+
|
|
171
|
+
Control how many tracks are preloaded ahead of the current track:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
const player = new Queue({
|
|
175
|
+
tracks: ['a.mp3', 'b.mp3', 'c.mp3', 'd.mp3'],
|
|
176
|
+
preloadNumTracks: 1, // Only preload 1 track ahead (default: 2)
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Can also be changed at runtime:
|
|
180
|
+
player.preloadNumTracks = 0; // Disable preloading
|
|
181
|
+
player.preloadNumTracks = 3; // Preload 3 ahead
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Playback Rate
|
|
185
|
+
|
|
186
|
+
Control the speed of playback (0.25x to 4x). Gapless scheduling automatically adjusts for the current rate:
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
const player = new Queue({
|
|
190
|
+
tracks: ['a.mp3', 'b.mp3'],
|
|
191
|
+
playbackRate: 1.5, // Start at 1.5x
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
player.play();
|
|
195
|
+
player.setPlaybackRate(2); // Change to 2x mid-playback
|
|
196
|
+
console.log(player.playbackRate); // 2
|
|
197
|
+
```
|
|
198
|
+
|
|
138
199
|
## Migration from v3
|
|
139
200
|
|
|
140
201
|
v4 is a complete rewrite. The public API has changed:
|
|
@@ -145,7 +206,7 @@ v4 is a complete rewrite. The public API has changed:
|
|
|
145
206
|
| `player.playNext()` | `player.next()` |
|
|
146
207
|
| `player.playPrevious()` | `player.previous()` |
|
|
147
208
|
| `player.resetCurrentTrack()` | `player.seek(0)` |
|
|
148
|
-
| `player.disableWebAudio()` | Pass `
|
|
209
|
+
| `player.disableWebAudio()` | Pass `playbackMethod: 'HTML5_ONLY'` in constructor |
|
|
149
210
|
| `player.nextTrack` | `player.tracks[player.currentTrackIndex + 1]` |
|
|
150
211
|
| `track.completeState` | Callbacks now receive `TrackInfo` objects |
|
|
151
212
|
| Callbacks receive Track instances | Callbacks receive plain `TrackInfo` data snapshots |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
type PlaybackType = 'HTML5' | 'WEBAUDIO';
|
|
2
|
+
/**
|
|
3
|
+
* Controls how audio is rendered.
|
|
4
|
+
* - `'HYBRID'` (default): Starts with HTML5 audio, then switches to Web Audio after decode. Best for remote files.
|
|
5
|
+
* - `'HTML5_ONLY'`: Uses HTML5 audio exclusively. Gapless playback is not available.
|
|
6
|
+
* - `'WEBAUDIO_ONLY'`: Uses Web Audio API exclusively. Audio must fully buffer before playing — only use for very small or local files.
|
|
7
|
+
*/
|
|
8
|
+
type PlaybackMethod = 'HYBRID' | 'HTML5_ONLY' | 'WEBAUDIO_ONLY';
|
|
2
9
|
type WebAudioLoadingState = 'NONE' | 'LOADING' | 'LOADED' | 'ERROR';
|
|
3
10
|
/** Metadata attached to a track (arbitrary user data). */
|
|
4
11
|
interface TrackMetadata {
|
|
@@ -29,14 +36,25 @@ interface GaplessOptions {
|
|
|
29
36
|
/** Called when autoplay is blocked by the browser. */
|
|
30
37
|
onPlayBlocked?: () => void;
|
|
31
38
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
39
|
+
* Controls how audio is rendered.
|
|
40
|
+
* - `'HYBRID'` (default): Starts with HTML5 audio, switches to Web Audio after decode. Best for remote files.
|
|
41
|
+
* - `'HTML5_ONLY'`: HTML5 audio only. Gapless playback is not available.
|
|
42
|
+
* - `'WEBAUDIO_ONLY'`: Web Audio API only. Audio must fully buffer before playing.
|
|
34
43
|
*/
|
|
35
|
-
|
|
44
|
+
playbackMethod?: PlaybackMethod;
|
|
36
45
|
/** Per-track metadata (aligned to the tracks array by index). */
|
|
37
46
|
trackMetadata?: TrackMetadata[];
|
|
38
47
|
/** Initial volume, 0.0–1.0. Defaults to 1. */
|
|
39
48
|
volume?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Number of tracks to preload ahead of the current track.
|
|
51
|
+
* Defaults to 2. Set to 0 to disable preloading.
|
|
52
|
+
*/
|
|
53
|
+
preloadNumTracks?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Initial playback rate, 0.25–4.0. Defaults to 1.
|
|
56
|
+
*/
|
|
57
|
+
playbackRate?: number;
|
|
40
58
|
}
|
|
41
59
|
/** Options for dynamically adding a track. */
|
|
42
60
|
interface AddTrackOptions {
|
|
@@ -73,6 +91,8 @@ interface TrackInfo {
|
|
|
73
91
|
webAudioLoadingState: WebAudioLoadingState;
|
|
74
92
|
/** Arbitrary metadata supplied when the track was added. */
|
|
75
93
|
metadata?: TrackMetadata;
|
|
94
|
+
/** Current playback rate. */
|
|
95
|
+
playbackRate: number;
|
|
76
96
|
/** Current xstate machine state for this track (e.g. 'idle', 'html5', 'webaudio'). */
|
|
77
97
|
machineState: string;
|
|
78
98
|
}
|
|
@@ -86,7 +106,8 @@ interface TrackQueueRef {
|
|
|
86
106
|
onPlayBlocked(): void;
|
|
87
107
|
onDebug(msg: string): void;
|
|
88
108
|
readonly volume: number;
|
|
89
|
-
readonly
|
|
109
|
+
readonly playbackMethod: PlaybackMethod;
|
|
110
|
+
readonly playbackRate: number;
|
|
90
111
|
readonly currentTrackIndex: number;
|
|
91
112
|
}
|
|
92
113
|
declare class Track {
|
|
@@ -98,13 +119,15 @@ declare class Track {
|
|
|
98
119
|
/** Temporary holder between fetch and decode steps (unserializable — stays on Track class). */
|
|
99
120
|
private _pendingArrayBuffer;
|
|
100
121
|
readonly audio: HTMLAudioElement;
|
|
101
|
-
private readonly
|
|
122
|
+
private readonly _playbackMethod;
|
|
102
123
|
private get ctx();
|
|
103
124
|
private gainNode;
|
|
104
125
|
private sourceNode;
|
|
105
126
|
audioBuffer: AudioBuffer | null;
|
|
106
|
-
/** AudioContext.currentTime
|
|
107
|
-
private
|
|
127
|
+
/** AudioContext.currentTime at the start of the current playback segment. */
|
|
128
|
+
private _waRefCtxTime;
|
|
129
|
+
/** Track position (seconds) at the start of the current playback segment. */
|
|
130
|
+
private _waRefTrackTime;
|
|
108
131
|
/** Track-time (seconds) frozen at the moment of the most recent pause. */
|
|
109
132
|
private pausedAtTrackTime;
|
|
110
133
|
private readonly _actor;
|
|
@@ -122,6 +145,7 @@ declare class Track {
|
|
|
122
145
|
pause(): void;
|
|
123
146
|
seek(time: number): void;
|
|
124
147
|
setVolume(v: number): void;
|
|
148
|
+
setPlaybackRate(rate: number): void;
|
|
125
149
|
preload(): void;
|
|
126
150
|
seekToEnd(secondsFromEnd?: number): void;
|
|
127
151
|
activate(): void;
|
|
@@ -163,8 +187,10 @@ declare class Queue implements TrackQueueRef {
|
|
|
163
187
|
private readonly _onError?;
|
|
164
188
|
private readonly _onPlayBlocked?;
|
|
165
189
|
private readonly _onDebug?;
|
|
166
|
-
readonly
|
|
190
|
+
readonly playbackMethod: PlaybackMethod;
|
|
167
191
|
private _volume;
|
|
192
|
+
private _preloadNumTracks;
|
|
193
|
+
private _playbackRate;
|
|
168
194
|
/** Index of the next track with a pre-scheduled gapless start, or null. */
|
|
169
195
|
private _scheduledNextIndex;
|
|
170
196
|
private _throttledUpdatePositionState;
|
|
@@ -177,6 +203,7 @@ declare class Queue implements TrackQueueRef {
|
|
|
177
203
|
gotoTrack(index: number, playImmediately?: boolean): void;
|
|
178
204
|
seek(time: number): void;
|
|
179
205
|
setVolume(volume: number): void;
|
|
206
|
+
setPlaybackRate(rate: number): void;
|
|
180
207
|
addTrack(url: string, options?: AddTrackOptions): void;
|
|
181
208
|
removeTrack(index: number): void;
|
|
182
209
|
resumeAudioContext(): Promise<void>;
|
|
@@ -187,6 +214,9 @@ declare class Queue implements TrackQueueRef {
|
|
|
187
214
|
get isPlaying(): boolean;
|
|
188
215
|
get isPaused(): boolean;
|
|
189
216
|
get volume(): number;
|
|
217
|
+
get preloadNumTracks(): number;
|
|
218
|
+
set preloadNumTracks(value: number);
|
|
219
|
+
get playbackRate(): number;
|
|
190
220
|
/** Snapshot of the queue state machine (state name + context). For debugging. */
|
|
191
221
|
get queueSnapshot(): {
|
|
192
222
|
state: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createActor as Q}from"xstate";var B=typeof window<"u",p;function F(){if(!B)return null;let o=window.AudioContext??window.webkitAudioContext;return o?new o:null}function k(){return p===void 0?null:p}function g(){p===void 0&&(p=F());let o=p;return o&&o.state==="suspended"?o.resume():Promise.resolve()}var A=typeof navigator<"u"&&"mediaSession"in navigator;function v(o){if(!A)return;let{mediaSession:e}=navigator;e.setActionHandler("play",o.onPlay),e.setActionHandler("pause",o.onPause),e.setActionHandler("nexttrack",o.onNext),e.setActionHandler("previoustrack",o.onPrevious),e.setActionHandler("seekto",t=>{t.seekTime!=null&&o.onSeek(t.seekTime)})}function P(o){if(A){if(!o){navigator.mediaSession.metadata=null;return}navigator.mediaSession.metadata=new MediaMetadata({title:o.title??"",artist:o.artist??"",album:o.album??"",artwork:o.artwork??[]})}}function b(o){A&&(navigator.mediaSession.playbackState=o?"playing":"paused")}function C(o,e,t=1){if(A)try{navigator.mediaSession.setPositionState({duration:o,position:e,playbackRate:t})}catch{}}import{setup as $,assign as h}from"xstate";function D(o){return $({types:{context:{},events:{}},guards:{hasNextTrack:({context:e})=>e.currentTrackIndex+1<e.trackCount,playImmediately:({event:e})=>!!e.playImmediately,willBeEmpty:({context:e})=>e.trackCount<=1,isRemovingCurrent:({context:e,event:t})=>t.index===e.currentTrackIndex},actions:{incrementTrackCount:h({trackCount:({context:e})=>e.trackCount+1}),decrementTrackCount:h({trackCount:({context:e})=>Math.max(0,e.trackCount-1),currentTrackIndex:({context:e,event:t})=>{let n=t,r=Math.max(0,e.trackCount-1);return n.index<e.currentTrackIndex?Math.max(0,e.currentTrackIndex-1):r>0?Math.min(e.currentTrackIndex,r-1):0}}),gotoTrackIndex:h({currentTrackIndex:({event:e})=>e.index}),advanceToNextTrack:h({currentTrackIndex:({context:e})=>{let t=e.currentTrackIndex+1;return t<e.trackCount?t:e.currentTrackIndex}}),goToPreviousTrack:h({currentTrackIndex:({context:e})=>Math.max(0,e.currentTrackIndex-1)}),advanceOnTrackEnd:h({currentTrackIndex:({context:e})=>Math.min(e.currentTrackIndex+1,e.trackCount-1)}),resetToFirstTrack:h({currentTrackIndex:()=>0}),deactivateCurrent:()=>{},deactivateEndedTrack:()=>{},activateAndPlayCurrent:()=>{},playOrContinueGapless:()=>{},cancelAllGapless:()=>{},notifyStartNewTrack:()=>{},notifyPlayNextTrack:()=>{},notifyPlayPreviousTrack:()=>{},notifyEnded:()=>{},updateMediaSessionMetadata:()=>{},preloadAhead:()=>{},playCurrent:()=>{},pauseCurrent:()=>{},seekCurrent:()=>{},seekCurrentToZero:()=>{},scheduleGapless:()=>{},cancelScheduledGapless:()=>{},cancelAndRescheduleGapless:()=>{}}}).createMachine({id:"queue",initial:"idle",context:o,on:{ADD_TRACK:{actions:"incrementTrackCount"},REMOVE_TRACK:{actions:"decrementTrackCount"}},states:{idle:{on:{PLAY:{target:"playing",actions:["playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},GOTO:[{guard:"playImmediately",target:"playing",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{target:"paused",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}],TRACK_LOADED:{actions:["preloadAhead"]}}},playing:{on:{REMOVE_TRACK:[{guard:"willBeEmpty",target:"idle",actions:["cancelAllGapless","decrementTrackCount"]},{guard:"isRemovingCurrent",actions:["cancelAllGapless","decrementTrackCount","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{actions:["decrementTrackCount","cancelAndRescheduleGapless","preloadAhead"]}],PAUSE:{target:"paused",actions:["cancelScheduledGapless","pauseCurrent"]},TOGGLE:{target:"paused",actions:["cancelScheduledGapless","pauseCurrent"]},NEXT:{actions:["deactivateCurrent","cancelAllGapless","advanceToNextTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayNextTrack","updateMediaSessionMetadata","preloadAhead"]},PREVIOUS:{actions:["deactivateCurrent","cancelAllGapless","goToPreviousTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayPreviousTrack","updateMediaSessionMetadata","preloadAhead"]},GOTO:[{guard:"playImmediately",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}],SEEK:{actions:["seekCurrent","cancelAndRescheduleGapless"]},TRACK_ENDED:[{guard:"hasNextTrack",target:"playing",actions:["deactivateEndedTrack","advanceOnTrackEnd","playOrContinueGapless","notifyStartNewTrack","notifyPlayNextTrack","updateMediaSessionMetadata","preloadAhead"]},{target:"ended",actions:["deactivateEndedTrack","notifyEnded"]}],TRACK_LOADED:{actions:["scheduleGapless","preloadAhead"]}}},paused:{on:{REMOVE_TRACK:[{guard:"willBeEmpty",target:"idle",actions:["cancelAllGapless","decrementTrackCount"]},{guard:"isRemovingCurrent",actions:["cancelAllGapless","decrementTrackCount","preloadAhead"]},{actions:["decrementTrackCount","preloadAhead"]}],PLAY:{target:"playing",actions:["playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},TOGGLE:{target:"playing",actions:["playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},NEXT:{actions:["deactivateCurrent","cancelAllGapless","advanceToNextTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayNextTrack","updateMediaSessionMetadata","preloadAhead"]},PREVIOUS:{actions:["deactivateCurrent","cancelAllGapless","goToPreviousTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayPreviousTrack","updateMediaSessionMetadata","preloadAhead"]},GOTO:[{guard:"playImmediately",target:"playing",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}],SEEK:{actions:["seekCurrent"]},TRACK_ENDED:[{guard:"hasNextTrack",target:"paused",actions:["deactivateEndedTrack","advanceOnTrackEnd","notifyStartNewTrack","updateMediaSessionMetadata"]},{target:"ended",actions:["deactivateEndedTrack","notifyEnded"]}],TRACK_LOADED:{actions:["preloadAhead"]}}},ended:{on:{ADD_TRACK:{target:"paused",actions:"incrementTrackCount"},PLAY:{target:"playing",actions:["resetToFirstTrack","playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},GOTO:[{guard:"playImmediately",target:"playing",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{target:"paused",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}]}}}})}import{createActor as K,fromPromise as _}from"xstate";import{setup as q,assign as d,spawnChild as V}from"xstate";import{setup as W,assign as E,sendParent as y,fromPromise as S}from"xstate";var f=W({types:{context:{},input:{}},actors:{resolveUrl:S(async()=>null),fetchAudio:S(async()=>{}),decodeAudio:S(async()=>{})},guards:{shouldSkipHEAD:({context:o})=>o.skipHEAD}}).createMachine({id:"fetchDecode",initial:"resolvingUrl",context:{trackUrl:"",resolvedUrl:"",skipHEAD:!1},entry:E(({event:o})=>{let{input:e}=o;return{trackUrl:e.trackUrl,resolvedUrl:e.resolvedUrl,skipHEAD:e.skipHEAD}}),states:{resolvingUrl:{always:{guard:"shouldSkipHEAD",target:"fetching"},invoke:{id:"resolveUrl",src:"resolveUrl",input:({context:o})=>({trackUrl:o.trackUrl}),onDone:{target:"fetching",actions:[E({resolvedUrl:({event:o,context:e})=>o.output??e.resolvedUrl,skipHEAD:()=>!0}),y(({event:o,context:e})=>({type:"URL_RESOLVED",url:o.output??e.resolvedUrl}))]},onError:{target:"fetching",actions:E({skipHEAD:()=>!0})}}},fetching:{invoke:{id:"fetchAudio",src:"fetchAudio",input:({context:o})=>({resolvedUrl:o.resolvedUrl}),onDone:"decoding",onError:{target:"error",actions:y({type:"BUFFER_ERROR"})}}},decoding:{invoke:{id:"decodeAudio",src:"decodeAudio",input:()=>{},onDone:{target:"done",actions:y({type:"BUFFER_READY"})},onError:{target:"error",actions:y({type:"BUFFER_ERROR"})}}},done:{type:"final"},error:{type:"final"}}});function N(o){return q({types:{context:{},events:{}},actors:{fetchDecode:f},guards:{canPlayWebAudio:()=>!1,canStartFetch:({context:e})=>e.webAudioLoadingState==="NONE"&&!e.fetchStarted},actions:{playHtml5:()=>{},startSourceNode:()=>{},startScheduledSourceNode:()=>{},startProgressLoop:()=>{},pauseHtml5:()=>{},freezePausedTime:()=>{},stopSourceNode:()=>{},disconnectGain:()=>{},stopProgressLoop:()=>{},reportProgress:()=>{},seekHtml5:()=>{},seekWebAudio:()=>{},resetHtml5Element:()=>{},resetTiming:()=>{},notifyTrackEnded:()=>{},setIsPlaying:d({isPlaying:()=>!0}),clearIsPlaying:d({isPlaying:()=>!1}),setLoadingState:d({webAudioLoadingState:()=>"LOADING"}),setLoadedState:d({webAudioLoadingState:()=>"LOADED"}),setErrorState:d({webAudioLoadingState:()=>"ERROR"}),clearScheduleAndLookahead:d({scheduledStartContextTime:()=>null,notifiedLookahead:()=>!1}),setPlayingWebAudio:d({isPlaying:()=>!0,webAudioLoadingState:()=>"LOADED",playbackType:()=>"WEBAUDIO"}),setScheduledGapless:d({isPlaying:()=>!0,webAudioLoadingState:()=>"LOADED",playbackType:()=>"WEBAUDIO",scheduledStartContextTime:({event:e})=>e.when}),clearPlayingAndSchedule:d({isPlaying:()=>!1,scheduledStartContextTime:()=>null,notifiedLookahead:()=>!1}),setNotifiedLookahead:d({notifiedLookahead:()=>!0}),setResolvedUrl:d({resolvedUrl:({event:e})=>e.url}),clearScheduledStart:d({scheduledStartContextTime:()=>null}),setPlayingWebAudioType:d({isPlaying:()=>!0,playbackType:()=>"WEBAUDIO"})}}).createMachine({id:"track",initial:"idle",context:o,on:{START_FETCH:{guard:"canStartFetch",actions:[d({webAudioLoadingState:()=>"LOADING",fetchStarted:()=>!0}),V("fetchDecode",{id:"fetchDecode",input:({context:e})=>({trackUrl:e.trackUrl,resolvedUrl:e.resolvedUrl,skipHEAD:e.skipHEAD})})]}},states:{idle:{on:{HTML5_ENDED:{actions:["notifyTrackEnded"]},DEACTIVATE:{actions:["resetHtml5Element","resetTiming","stopProgressLoop","clearScheduleAndLookahead"]},ACTIVATE:{actions:["resetTiming","resetHtml5Element","clearScheduleAndLookahead"]},PLAY:[{guard:"canPlayWebAudio",target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},{target:"html5",actions:["setIsPlaying","playHtml5","startProgressLoop"]}],PLAY_WEBAUDIO:{target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},SCHEDULE_GAPLESS:{target:"webaudio",actions:["setScheduledGapless","startScheduledSourceNode"]},PRELOAD:{target:"loading"},BUFFER_LOADING:{actions:"setLoadingState"},BUFFER_READY:{actions:"setLoadedState"},BUFFER_ERROR:{actions:"setErrorState"},URL_RESOLVED:{actions:"setResolvedUrl"}}},html5:{on:{PAUSE:{actions:["clearIsPlaying","pauseHtml5","stopProgressLoop","reportProgress"]},PLAY:{actions:["setIsPlaying","playHtml5","startProgressLoop"]},BUFFER_LOADING:{actions:"setLoadingState"},PLAY_WEBAUDIO:{target:"webaudio",actions:"setPlayingWebAudio"},BUFFER_READY:{actions:"setLoadedState"},BUFFER_ERROR:{actions:"setErrorState"},SEEK:{actions:["seekHtml5","reportProgress"]},LOOKAHEAD_REACHED:{actions:"setNotifiedLookahead"},HTML5_ENDED:{target:"idle",actions:["clearIsPlaying","stopProgressLoop","notifyTrackEnded"]},ACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","pauseHtml5","stopProgressLoop","resetTiming","resetHtml5Element"]},URL_RESOLVED:{actions:"setResolvedUrl"},DEACTIVATE:{target:"idle",actions:["clearIsPlaying","pauseHtml5","resetHtml5Element","resetTiming","stopProgressLoop"]}}},loading:{on:{BUFFER_LOADING:{actions:"setLoadingState"},BUFFER_READY:{target:"idle",actions:"setLoadedState"},BUFFER_ERROR:{target:"idle",actions:"setErrorState"},PLAY:[{guard:"canPlayWebAudio",target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},{target:"html5",actions:["setIsPlaying","playHtml5","startProgressLoop"]}],PLAY_WEBAUDIO:{target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},SCHEDULE_GAPLESS:{target:"webaudio",actions:["setScheduledGapless","startScheduledSourceNode"]},ACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","resetTiming","resetHtml5Element"]},DEACTIVATE:{target:"idle",actions:["clearIsPlaying","resetTiming"]},URL_RESOLVED:{actions:"setResolvedUrl"}}},webaudio:{on:{PAUSE:{actions:["clearIsPlaying","freezePausedTime","stopSourceNode","disconnectGain","stopProgressLoop","reportProgress"]},PLAY:[{guard:"canPlayWebAudio",actions:["setIsPlaying","startSourceNode","startProgressLoop"]},{actions:"setIsPlaying"}],PLAY_WEBAUDIO:{actions:"setPlayingWebAudioType"},SEEK:{actions:["clearScheduledStart","seekWebAudio","reportProgress"]},SET_VOLUME:{},CANCEL_GAPLESS:{target:"idle",actions:["clearPlayingAndSchedule","stopSourceNode","disconnectGain","stopProgressLoop","resetTiming"]},LOOKAHEAD_REACHED:{actions:"setNotifiedLookahead"},WEBAUDIO_ENDED:{target:"idle",actions:["clearIsPlaying","stopProgressLoop","notifyTrackEnded"]},ACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","stopSourceNode","disconnectGain","stopProgressLoop","resetTiming","resetHtml5Element"]},DEACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","stopSourceNode","disconnectGain","resetTiming","resetHtml5Element","stopProgressLoop"]}}}}})}var Y=5,L=15,T=class{index;metadata;_trackUrl;_resolvedUrl;skipHEAD;_pendingArrayBuffer=null;audio;_webAudioDisabled;get ctx(){if(this._webAudioDisabled)return null;let e=k();return e&&!this.gainNode&&(this.gainNode=e.createGain(),this.gainNode.gain.value=this.audio.volume),e}gainNode=null;sourceNode=null;audioBuffer=null;webAudioStartedAt=0;pausedAtTrackTime=0;_actor;queueRef;rafId=null;_notifiedPreloadThreshold=!1;constructor(e){this.index=e.index,this._trackUrl=e.trackUrl,this._resolvedUrl=e.trackUrl,this.skipHEAD=e.skipHEAD??!1,this.metadata=e.metadata??{},this.queueRef=e.queue,this.audio=new Audio,this.audio.preload="none",this.audio.src=this._trackUrl,this.audio.volume=e.queue.volume,this.audio.controls=!1,this.audio.onerror=()=>{var c,l;let r=(c=this.audio.error)==null?void 0:c.code;if(r===1)return;let s=((l=this.audio.error)==null?void 0:l.message)??"unknown";this.queueRef.onError(new Error(`HTML5 audio error on track ${this.index} (code ${r}): ${s}`))},this.audio.onended=()=>{this.queueRef.onDebug(`audio.onended track=${this.index} machineState=${this._actor.getSnapshot().value} queueIdx=${this.queueRef.currentTrackIndex}`),this._actor.send({type:"HTML5_ENDED"})},this._webAudioDisabled=e.queue.webAudioIsDisabled;let t={trackUrl:this._trackUrl,resolvedUrl:this._trackUrl,skipHEAD:this.skipHEAD,playbackType:"HTML5",webAudioLoadingState:"NONE",isPlaying:!1,scheduledStartContextTime:null,notifiedLookahead:!1,fetchStarted:!1},n=N(t).provide({guards:{canPlayWebAudio:()=>!!(this.ctx&&this.audioBuffer&&this.gainNode)},actors:{fetchDecode:f.provide({actors:{resolveUrl:_(async({signal:r})=>{let s=await fetch(this._trackUrl,{method:"HEAD",signal:r});return s.redirected&&s.url?(this._resolvedUrl=s.url,this.audio.src=s.url,s.url):null}),fetchAudio:_(async({input:r,signal:s})=>{let{resolvedUrl:c}=r,l=await fetch(c,{signal:s});if(!l.ok)throw new Error(`HTTP ${l.status} for ${c}`);this._pendingArrayBuffer=await l.arrayBuffer()}),decodeAudio:_(async()=>{let r=this._pendingArrayBuffer;if(this._pendingArrayBuffer=null,!r||!this.ctx)throw new Error("No ArrayBuffer or AudioContext");this.audioBuffer=await this.ctx.decodeAudioData(r),queueMicrotask(()=>this.queueRef.onTrackBufferReady(this))})}})},actions:{playHtml5:()=>this._playHtml5(),startSourceNode:()=>{this._startSourceNode(this.pausedAtTrackTime)},startScheduledSourceNode:({context:r})=>{let s=r.scheduledStartContextTime;s===null||!this.ctx||!this.audioBuffer||!this.gainNode||(this._stopSourceNode(),this.sourceNode=this.ctx.createBufferSource(),this.sourceNode.buffer=this.audioBuffer,this.sourceNode.connect(this.gainNode),this.gainNode.connect(this.ctx.destination),this.sourceNode.onended=this._handleWebAudioEnded,this.sourceNode.start(s,0),this.webAudioStartedAt=s,this.queueRef.onDebug(`startScheduledSourceNode track=${this.index} when=${s.toFixed(3)} ctxNow=${this.ctx.currentTime.toFixed(3)} delta=${(s-this.ctx.currentTime).toFixed(3)}s`))},startProgressLoop:()=>this.startProgressLoop(),pauseHtml5:()=>this.audio.pause(),freezePausedTime:()=>{let r=this.currentTime;this.pausedAtTrackTime=isFinite(r)?r:0},stopSourceNode:()=>this._stopSourceNode(),disconnectGain:()=>this._disconnectGain(),stopProgressLoop:()=>this._stopProgressLoop(),reportProgress:()=>{queueMicrotask(()=>this.queueRef.onProgress(this.toInfo()))},seekHtml5:()=>this._seekHtml5(),seekWebAudio:()=>this._seekWebAudio(),resetHtml5Element:()=>{this.audio.currentTime=0},resetTiming:()=>{this.webAudioStartedAt=0,this.pausedAtTrackTime=0},notifyTrackEnded:()=>{queueMicrotask(()=>this.queueRef.onTrackEnded(this))}}});this._actor=K(n),this._actor.start()}play(){this.queueRef.onDebug(`Track.play() track=${this.index} machineState=${this._actor.getSnapshot().value} hasBuffer=${!!this.audioBuffer} hasCtx=${!!this.ctx} audioPaused=${this.audio.paused}`),this._actor.send({type:"PLAY"})}pause(){this._actor.send({type:"PAUSE"})}seek(e){if(!isFinite(e))return;let t=Math.max(0,isNaN(this.duration)?e:Math.min(e,this.duration));this.pausedAtTrackTime=t,this._actor.send({type:"SEEK",time:t})}setVolume(e){let t=Math.min(1,Math.max(0,e));this.audio.volume=t,this.gainNode&&(this.gainNode.gain.value=t),this._actor.send({type:"SET_VOLUME",volume:t})}preload(){this.queueRef.onDebug(`preload() track=${this.index} state=${this._actor.getSnapshot().value} hasBuffer=${!!this.audioBuffer} hasCtx=${!!this.ctx}`),this._actor.getSnapshot().value==="idle"&&this._actor.send({type:"PRELOAD"}),!this.audioBuffer&&(g(),this.ctx&&this._actor.send({type:"START_FETCH"}))}seekToEnd(e=6){let t=this.duration;!isNaN(t)&&t>e&&this.seek(t-e)}activate(){this._actor.send({type:"ACTIVATE"})}deactivate(){this.queueRef.onDebug(`Track.deactivate() track=${this.index} machineState=${this._actor.getSnapshot().value} isPlaying=${this.isPlaying}`),this._notifiedPreloadThreshold=!1,this._actor.send({type:"DEACTIVATE"}),this.queueRef.onDebug(`Track.deactivate() done track=${this.index} machineState=${this._actor.getSnapshot().value}`)}destroy(){var e;this.deactivate(),this._pendingArrayBuffer=null,this.audioBuffer=null,(e=this.gainNode)==null||e.disconnect(),this.gainNode=null,this._actor.stop()}cancelGaplessStart(){this._actor.getSnapshot().context.scheduledStartContextTime!==null&&this._actor.send({type:"CANCEL_GAPLESS"})}scheduleGaplessStart(e){!this.ctx||!this.audioBuffer||!this.gainNode||this._actor.send({type:"SCHEDULE_GAPLESS",when:e})}get currentTime(){let e=this._actor.getSnapshot();return e.value==="webaudio"?e.context.isPlaying?this.ctx?Math.max(0,this.ctx.currentTime-this.webAudioStartedAt):0:this.pausedAtTrackTime:this.audio.currentTime}get duration(){return this.audioBuffer?this.audioBuffer.duration:this.audio.duration}get isPaused(){let e=this._actor.getSnapshot();return e.value==="webaudio"?!e.context.isPlaying:this.audio.paused}get isPlaying(){return this._actor.getSnapshot().context.isPlaying}get trackUrl(){return this._resolvedUrl}get playbackType(){return this._actor.getSnapshot().context.playbackType}get webAudioLoadingState(){return this._actor.getSnapshot().context.webAudioLoadingState}get hasSourceNode(){return this.sourceNode!==null}get machineState(){return this._actor.getSnapshot().value}get scheduledStartContextTime(){return this._actor.getSnapshot().context.scheduledStartContextTime}get isBufferLoaded(){return this.audioBuffer!==null}toInfo(){var e;return{index:this.index,currentTime:this.currentTime,duration:this.duration,isPlaying:this.isPlaying,isPaused:this.isPaused,volume:((e=this.gainNode)==null?void 0:e.gain.value)??this.audio.volume,trackUrl:this.trackUrl,playbackType:this.playbackType,webAudioLoadingState:this.webAudioLoadingState,metadata:this.metadata,machineState:this.machineState}}_playHtml5(){this.audio.preload!=="auto"&&(this.audio.preload="auto");let e=this.audio.play();e&&e.catch(t=>{t instanceof Error&&t.name==="NotAllowedError"?this.queueRef.onPlayBlocked():t instanceof Error&&t.name==="AbortError"||this.queueRef.onError(t instanceof Error?t:new Error(String(t)))})}_seekHtml5(){let e=this.pausedAtTrackTime;isFinite(e)&&(this.audio.preload!=="auto"&&(this.audio.preload="auto"),this.audio.readyState>=HTMLMediaElement.HAVE_METADATA?this.audio.currentTime=e:(this.audio.addEventListener("loadedmetadata",()=>{this.audio.currentTime=e},{once:!0}),this.audio.load()))}_startSourceNode(e){!this.ctx||!this.audioBuffer||!this.gainNode||(this._stopSourceNode(),this.ctx.state==="suspended"&&this.ctx.resume(),this.sourceNode=this.ctx.createBufferSource(),this.sourceNode.buffer=this.audioBuffer,this.sourceNode.connect(this.gainNode),this.gainNode.connect(this.ctx.destination),this.sourceNode.onended=this._handleWebAudioEnded,this.webAudioStartedAt=this.ctx.currentTime-e,this.sourceNode.start(0,e))}_stopSourceNode(){if(this.sourceNode){this.sourceNode.onended=null;try{this.sourceNode.stop()}catch{}try{this.sourceNode.disconnect()}catch{}this.sourceNode=null}}_disconnectGain(){if(!(!this.gainNode||!this.ctx))try{this.gainNode.disconnect(this.ctx.destination)}catch{}}_seekWebAudio(){let t=this._actor.getSnapshot().context.isPlaying,n=this.pausedAtTrackTime;this._stopSourceNode(),t&&this._startSourceNode(n)}_handleWebAudioEnded=()=>{this.queueRef.onDebug(`_handleWebAudioEnded track=${this.index} sourceNode=${!!this.sourceNode} queueIdx=${this.queueRef.currentTrackIndex}`),this.sourceNode&&this._actor.send({type:"WEBAUDIO_ENDED"})};startProgressLoop(){if(this.rafId!==null)return;let e=()=>{if(this.isPaused||!this.isPlaying){this.rafId=null;return}this.queueRef.onProgress(this.toInfo());let t=this.duration-this.currentTime;!this._actor.getSnapshot().context.notifiedLookahead&&!isNaN(t)&&t<=Y&&(this._actor.send({type:"LOOKAHEAD_REACHED"}),queueMicrotask(()=>this.queueRef.onTrackBufferReady(this)));let r=isNaN(this.duration)?L:Math.min(this.duration*.2,L);!this._notifiedPreloadThreshold&&this.currentTime>=r&&(this._notifiedPreloadThreshold=!0,queueMicrotask(()=>this.queueRef.onPreloadReady(this))),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}_stopProgressLoop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}};function I(o,e){let t=0;return(...n)=>{let r=performance.now();r-t<e||(t=r,o(...n))}}var Z=2,m=class{_tracks=[];_actor;_onProgress;_onEnded;_onPlayNextTrack;_onPlayPreviousTrack;_onStartNewTrack;_onError;_onPlayBlocked;_onDebug;webAudioIsDisabled;_volume;_scheduledNextIndex=null;_throttledUpdatePositionState=I((e,t)=>C(e,t),1e3);constructor(e={}){let{tracks:t=[],onProgress:n,onEnded:r,onPlayNextTrack:s,onPlayPreviousTrack:c,onStartNewTrack:l,onError:R,onPlayBlocked:M,onDebug:U,webAudioIsDisabled:w=!1,trackMetadata:O=[],volume:G=1}=e;this._volume=Math.min(1,Math.max(0,G)),this.webAudioIsDisabled=w,this._onProgress=n,this._onEnded=r,this._onPlayNextTrack=s,this._onPlayPreviousTrack=c,this._onStartNewTrack=l,this._onError=R,this._onPlayBlocked=M,this._onDebug=U,this._tracks=t.map((i,a)=>new T({trackUrl:i,index:a,queue:this,metadata:O[a]}));let H=D({currentTrackIndex:0,trackCount:this._tracks.length}).provide({actions:{deactivateCurrent:({context:i})=>{var a;(a=this._trackAt(i.currentTrackIndex))==null||a.deactivate()},deactivateEndedTrack:({context:i})=>{var a;(a=this._trackAt(i.currentTrackIndex))==null||a.deactivate()},activateAndPlayCurrent:({context:i})=>{let a=this._trackAt(i.currentTrackIndex);a&&(a.activate(),this._scheduledNextIndex!==a.index&&a.play())},playOrContinueGapless:({context:i})=>{let a=this._trackAt(i.currentTrackIndex);a&&(this._scheduledNextIndex!==a.index?a.play():(this._scheduledNextIndex=null,this.onDebug(`onTrackEnded: gapless track ${a.index} \u2014 sourceNode=${a.hasSourceNode} isPlaying=${a.isPlaying} machineState=${a.machineState}`),a.startProgressLoop()))},cancelAllGapless:()=>this._cancelScheduledGapless(),notifyStartNewTrack:({context:i})=>{var u;let a=this._trackAt(i.currentTrackIndex);a&&((u=this._onStartNewTrack)==null||u.call(this,a.toInfo()))},notifyPlayNextTrack:({context:i})=>{var u;let a=this._trackAt(i.currentTrackIndex);a&&((u=this._onPlayNextTrack)==null||u.call(this,a.toInfo()))},notifyPlayPreviousTrack:({context:i})=>{var u;let a=this._trackAt(i.currentTrackIndex);a&&((u=this._onPlayPreviousTrack)==null||u.call(this,a.toInfo()))},notifyEnded:()=>{var i;return(i=this._onEnded)==null?void 0:i.call(this)},updateMediaSessionMetadata:({context:i})=>{let a=this._trackAt(i.currentTrackIndex);a&&P(a.metadata)},preloadAhead:({context:i})=>{this._preloadAhead(i.currentTrackIndex)},playCurrent:({context:i})=>{var a;(a=this._trackAt(i.currentTrackIndex))==null||a.play()},pauseCurrent:({context:i})=>{var a;(a=this._trackAt(i.currentTrackIndex))==null||a.pause()},seekCurrent:({context:i,event:a})=>{var x;let u=a;(x=this._trackAt(i.currentTrackIndex))==null||x.seek(u.time)},seekCurrentToZero:({context:i})=>{var a;(a=this._trackAt(i.currentTrackIndex))==null||a.seek(0)},scheduleGapless:({context:i})=>{this._tryScheduleGapless(i.currentTrackIndex)},cancelScheduledGapless:()=>{this._cancelScheduledGapless()},cancelAndRescheduleGapless:({context:i})=>{this._cancelScheduledGapless(),this._tryScheduleGapless(i.currentTrackIndex)}}});this._actor=Q(H),this._actor.subscribe(i=>{b(i.value==="playing")}),this._actor.start(),v({onPlay:()=>this.play(),onPause:()=>{this._actor.getSnapshot().value==="playing"&&this.pause()},onNext:()=>this.next(),onPrevious:()=>this.previous(),onSeek:i=>this.seek(i)})}play(){this._currentTrack&&this._actor.send({type:"PLAY"})}pause(){this._actor.send({type:"PAUSE"})}togglePlayPause(){this._actor.getSnapshot().value==="playing"?this.pause():this.play()}next(){this._actor.getSnapshot().context.currentTrackIndex+1>=this._tracks.length||this._actor.send({type:"NEXT"})}previous(){let e=this._currentTrack;if(e&&e.currentTime>8){e.seek(0),e.play();return}this._actor.send({type:"PREVIOUS"})}gotoTrack(e,t=!1){e<0||e>=this._tracks.length||(this.onDebug(`gotoTrack(${e}, playImmediately=${t}) queueState=${this._actor.getSnapshot().value} curIdx=${this._actor.getSnapshot().context.currentTrackIndex}`),this._actor.send({type:"GOTO",index:e,playImmediately:t}))}seek(e){this._actor.send({type:"SEEK",time:e})}setVolume(e){let t=Math.min(1,Math.max(0,e));this._volume=t;for(let n of this._tracks)n.setVolume(t)}addTrack(e,t={}){let n=this._tracks.length,r=t.metadata??{};this._tracks.push(new T({trackUrl:e,index:n,queue:this,skipHEAD:t.skipHEAD,metadata:r})),this._actor.send({type:"ADD_TRACK"})}removeTrack(e){if(!(e<0||e>=this._tracks.length)){this._tracks[e].destroy(),this._tracks.splice(e,1);for(let t=e;t<this._tracks.length;t++)this._tracks[t].index=t;this._scheduledNextIndex===e?this._scheduledNextIndex=null:this._scheduledNextIndex!==null&&this._scheduledNextIndex>e&&this._scheduledNextIndex--,this._actor.send({type:"REMOVE_TRACK",index:e})}}resumeAudioContext(){return g()}destroy(){for(let e of this._tracks)e.destroy();this._tracks=[],this._actor.stop()}get currentTrack(){var e;return(e=this._currentTrack)==null?void 0:e.toInfo()}get currentTrackIndex(){return this._actor.getSnapshot().context.currentTrackIndex}get tracks(){return this._tracks.map(e=>e.toInfo())}get isPlaying(){return this._actor.getSnapshot().value==="playing"}get isPaused(){return this._actor.getSnapshot().value==="paused"}get volume(){return this._volume}get queueSnapshot(){let e=this._actor.getSnapshot();return{state:e.value,context:e.context}}onTrackEnded(e){let t=this._actor.getSnapshot();if(this.onDebug(`onTrackEnded track=${e.index} queueState=${t.value} curIdx=${t.context.currentTrackIndex}`),e!==this._trackAt(t.context.currentTrackIndex))return;this._actor.send({type:"TRACK_ENDED"});let n=this._actor.getSnapshot();this.onDebug(`onTrackEnded after TRACK_ENDED \u2192 queueState=${n.value} curIdx=${n.context.currentTrackIndex}`)}onTrackBufferReady(e){this._actor.send({type:"TRACK_LOADED",index:e.index})}onProgress(e){var t;e.index===this._actor.getSnapshot().context.currentTrackIndex&&(isNaN(e.duration)||this._throttledUpdatePositionState(e.duration,e.currentTime),(t=this._onProgress)==null||t.call(this,e))}onError(e){var t;(t=this._onError)==null||t.call(this,e)}onPlayBlocked(){var e;this._actor.send({type:"PAUSE"}),(e=this._onPlayBlocked)==null||e.call(this)}onPreloadReady(e){let t=this._actor.getSnapshot();e.index===t.context.currentTrackIndex&&this._preloadAhead(t.context.currentTrackIndex)}onDebug(e){var t;(t=this._onDebug)==null||t.call(this,e)}_trackAt(e){return this._tracks[e]}get _currentTrack(){return this._tracks[this._actor.getSnapshot().context.currentTrackIndex]}_preloadAhead(e){let t=this._trackAt(e);if(t&&t.playbackType==="HTML5"&&t.isPlaying){let r=isNaN(t.duration)?15:Math.min(t.duration*.2,15);if(t.currentTime<r){this.onDebug(`_preloadAhead: deferring \u2014 HTML5 track ${e} at ${t.currentTime.toFixed(1)}s (threshold=${r.toFixed(1)}s)`);return}}let n=e+Z+1;this.onDebug(`_preloadAhead(${e}) limit=${n} trackCount=${this._tracks.length}`);for(let r=e+1;r<this._tracks.length&&r<n;r++){let s=this._tracks[r];if(s.isBufferLoaded)this.onDebug(`_preloadAhead: track ${r} already loaded`);else{this.onDebug(`_preloadAhead: starting preload for track ${r}`),s.preload();break}}}_cancelScheduledGapless(){if(this._scheduledNextIndex===null)return;let e=this._trackAt(this._scheduledNextIndex);e&&(e.cancelGaplessStart(),this.onDebug(`_cancelScheduledGapless: cancelled track ${this._scheduledNextIndex}`)),this._scheduledNextIndex=null}_tryScheduleGapless(e){let t=k();if(!t||this.webAudioIsDisabled)return;let n=e+1;if(n>=this._tracks.length)return;let r=this._tracks[e],s=this._tracks[n];if(!r.isBufferLoaded||!s.isBufferLoaded||this._scheduledNextIndex===n||!r.isPlaying)return;let c=this._computeTrackEndTime(r);c!==null&&(c<t.currentTime+.01||(s.scheduleGaplessStart(c),this._scheduledNextIndex=n))}_computeTrackEndTime(e){let t=k();if(!t||!e.isBufferLoaded)return null;let n=e.duration;if(isNaN(n))return null;if(e.scheduledStartContextTime!==null)return e.scheduledStartContextTime+n;let r=n-e.currentTime;return r<=0?null:t.currentTime+r}};export{m as Queue,m as default};
|
|
1
|
+
import{createActor as z}from"xstate";var W=typeof window<"u",p;function q(){if(!W)return null;let o=window.AudioContext??window.webkitAudioContext;return o?new o:null}function k(){return p===void 0?null:p}function y(){p===void 0&&(p=q());let o=p;return o&&o.state==="suspended"?o.resume():Promise.resolve()}var g=typeof navigator<"u"&&"mediaSession"in navigator;function v(o){if(!g)return;let{mediaSession:e}=navigator;e.setActionHandler("play",o.onPlay),e.setActionHandler("pause",o.onPause),e.setActionHandler("nexttrack",o.onNext),e.setActionHandler("previoustrack",o.onPrevious),e.setActionHandler("seekto",t=>{t.seekTime!=null&&o.onSeek(t.seekTime)})}function P(o){if(g){if(!o){navigator.mediaSession.metadata=null;return}navigator.mediaSession.metadata=new MediaMetadata({title:o.title??"",artist:o.artist??"",album:o.album??"",artwork:o.artwork??[]})}}function b(o){g&&(navigator.mediaSession.playbackState=o?"playing":"paused")}function C(o,e,t=1){if(g)try{navigator.mediaSession.setPositionState({duration:o,position:e,playbackRate:t})}catch{}}import{setup as V,assign as h}from"xstate";function N(o){return V({types:{context:{},events:{}},guards:{hasNextTrack:({context:e})=>e.currentTrackIndex+1<e.trackCount,playImmediately:({event:e})=>!!e.playImmediately,willBeEmpty:({context:e})=>e.trackCount<=1,isRemovingCurrent:({context:e,event:t})=>t.index===e.currentTrackIndex},actions:{incrementTrackCount:h({trackCount:({context:e})=>e.trackCount+1}),decrementTrackCount:h({trackCount:({context:e})=>Math.max(0,e.trackCount-1),currentTrackIndex:({context:e,event:t})=>{let n=t,a=Math.max(0,e.trackCount-1);return n.index<e.currentTrackIndex?Math.max(0,e.currentTrackIndex-1):a>0?Math.min(e.currentTrackIndex,a-1):0}}),gotoTrackIndex:h({currentTrackIndex:({event:e})=>e.index}),advanceToNextTrack:h({currentTrackIndex:({context:e})=>{let t=e.currentTrackIndex+1;return t<e.trackCount?t:e.currentTrackIndex}}),goToPreviousTrack:h({currentTrackIndex:({context:e})=>Math.max(0,e.currentTrackIndex-1)}),advanceOnTrackEnd:h({currentTrackIndex:({context:e})=>Math.min(e.currentTrackIndex+1,e.trackCount-1)}),resetToFirstTrack:h({currentTrackIndex:()=>0}),deactivateCurrent:()=>{},deactivateEndedTrack:()=>{},activateAndPlayCurrent:()=>{},playOrContinueGapless:()=>{},cancelAllGapless:()=>{},notifyStartNewTrack:()=>{},notifyPlayNextTrack:()=>{},notifyPlayPreviousTrack:()=>{},notifyEnded:()=>{},updateMediaSessionMetadata:()=>{},preloadAhead:()=>{},playCurrent:()=>{},pauseCurrent:()=>{},seekCurrent:()=>{},seekCurrentToZero:()=>{},scheduleGapless:()=>{},cancelScheduledGapless:()=>{},cancelAndRescheduleGapless:()=>{}}}).createMachine({id:"queue",initial:"idle",context:o,on:{ADD_TRACK:{actions:"incrementTrackCount"},REMOVE_TRACK:{actions:"decrementTrackCount"}},states:{idle:{on:{PLAY:{target:"playing",actions:["playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},GOTO:[{guard:"playImmediately",target:"playing",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{target:"paused",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}],TRACK_LOADED:{actions:["preloadAhead"]}}},playing:{on:{REMOVE_TRACK:[{guard:"willBeEmpty",target:"idle",actions:["cancelAllGapless","decrementTrackCount"]},{guard:"isRemovingCurrent",actions:["cancelAllGapless","decrementTrackCount","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{actions:["decrementTrackCount","cancelAndRescheduleGapless","preloadAhead"]}],PAUSE:{target:"paused",actions:["cancelScheduledGapless","pauseCurrent"]},TOGGLE:{target:"paused",actions:["cancelScheduledGapless","pauseCurrent"]},NEXT:{actions:["deactivateCurrent","cancelAllGapless","advanceToNextTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayNextTrack","updateMediaSessionMetadata","preloadAhead"]},PREVIOUS:{actions:["deactivateCurrent","cancelAllGapless","goToPreviousTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayPreviousTrack","updateMediaSessionMetadata","preloadAhead"]},GOTO:[{guard:"playImmediately",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}],SEEK:{actions:["seekCurrent","cancelAndRescheduleGapless"]},TRACK_ENDED:[{guard:"hasNextTrack",target:"playing",actions:["deactivateEndedTrack","advanceOnTrackEnd","playOrContinueGapless","notifyStartNewTrack","notifyPlayNextTrack","updateMediaSessionMetadata","preloadAhead"]},{target:"ended",actions:["deactivateEndedTrack","notifyEnded"]}],TRACK_LOADED:{actions:["scheduleGapless","preloadAhead"]}}},paused:{on:{REMOVE_TRACK:[{guard:"willBeEmpty",target:"idle",actions:["cancelAllGapless","decrementTrackCount"]},{guard:"isRemovingCurrent",actions:["cancelAllGapless","decrementTrackCount","preloadAhead"]},{actions:["decrementTrackCount","preloadAhead"]}],PLAY:{target:"playing",actions:["playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},TOGGLE:{target:"playing",actions:["playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},NEXT:{actions:["deactivateCurrent","cancelAllGapless","advanceToNextTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayNextTrack","updateMediaSessionMetadata","preloadAhead"]},PREVIOUS:{actions:["deactivateCurrent","cancelAllGapless","goToPreviousTrack","activateAndPlayCurrent","notifyStartNewTrack","notifyPlayPreviousTrack","updateMediaSessionMetadata","preloadAhead"]},GOTO:[{guard:"playImmediately",target:"playing",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}],SEEK:{actions:["seekCurrent"]},TRACK_ENDED:[{guard:"hasNextTrack",target:"paused",actions:["deactivateEndedTrack","advanceOnTrackEnd","notifyStartNewTrack","updateMediaSessionMetadata"]},{target:"ended",actions:["deactivateEndedTrack","notifyEnded"]}],TRACK_LOADED:{actions:["preloadAhead"]}}},ended:{on:{ADD_TRACK:{target:"paused",actions:"incrementTrackCount"},PLAY:{target:"playing",actions:["resetToFirstTrack","playCurrent","updateMediaSessionMetadata","preloadAhead","scheduleGapless"]},GOTO:[{guard:"playImmediately",target:"playing",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","activateAndPlayCurrent","notifyStartNewTrack","updateMediaSessionMetadata","preloadAhead"]},{target:"paused",actions:["deactivateCurrent","cancelAllGapless","gotoTrackIndex","seekCurrentToZero","preloadAhead"]}]}}}})}import{createActor as Z,fromPromise as x}from"xstate";import{setup as Y,assign as d,spawnChild as Q}from"xstate";import{setup as K,assign as _,sendParent as f,fromPromise as E}from"xstate";var m=K({types:{context:{},input:{}},actors:{resolveUrl:E(async()=>null),fetchAudio:E(async()=>{}),decodeAudio:E(async()=>{})},guards:{shouldSkipHEAD:({context:o})=>o.skipHEAD}}).createMachine({id:"fetchDecode",initial:"resolvingUrl",context:{trackUrl:"",resolvedUrl:"",skipHEAD:!1},entry:_(({event:o})=>{let{input:e}=o;return{trackUrl:e.trackUrl,resolvedUrl:e.resolvedUrl,skipHEAD:e.skipHEAD}}),states:{resolvingUrl:{always:{guard:"shouldSkipHEAD",target:"fetching"},invoke:{id:"resolveUrl",src:"resolveUrl",input:({context:o})=>({trackUrl:o.trackUrl}),onDone:{target:"fetching",actions:[_({resolvedUrl:({event:o,context:e})=>o.output??e.resolvedUrl,skipHEAD:()=>!0}),f(({event:o,context:e})=>({type:"URL_RESOLVED",url:o.output??e.resolvedUrl}))]},onError:{target:"fetching",actions:_({skipHEAD:()=>!0})}}},fetching:{invoke:{id:"fetchAudio",src:"fetchAudio",input:({context:o})=>({resolvedUrl:o.resolvedUrl}),onDone:"decoding",onError:{target:"error",actions:f({type:"BUFFER_ERROR"})}}},decoding:{invoke:{id:"decodeAudio",src:"decodeAudio",input:()=>{},onDone:{target:"done",actions:f({type:"BUFFER_READY"})},onError:{target:"error",actions:f({type:"BUFFER_ERROR"})}}},done:{type:"final"},error:{type:"final"}}});function R(o){return Y({types:{context:{},events:{}},actors:{fetchDecode:m},guards:{canPlayWebAudio:()=>!1,isWebAudioOnly:()=>!1,canStartFetch:({context:e})=>e.webAudioLoadingState==="NONE"&&!e.fetchStarted},actions:{playHtml5:()=>{},startSourceNode:()=>{},startScheduledSourceNode:()=>{},startProgressLoop:()=>{},pauseHtml5:()=>{},freezePausedTime:()=>{},stopSourceNode:()=>{},disconnectGain:()=>{},stopProgressLoop:()=>{},reportProgress:()=>{},seekHtml5:()=>{},seekWebAudio:()=>{},resetHtml5Element:()=>{},resetTiming:()=>{},notifyTrackEnded:()=>{},triggerFetchForPendingPlay:()=>{},setPendingPlay:d({pendingPlay:()=>!0}),clearPendingPlay:d({pendingPlay:()=>!1}),setIsPlaying:d({isPlaying:()=>!0}),clearIsPlaying:d({isPlaying:()=>!1}),setLoadingState:d({webAudioLoadingState:()=>"LOADING"}),setLoadedState:d({webAudioLoadingState:()=>"LOADED"}),setErrorState:d({webAudioLoadingState:()=>"ERROR"}),clearScheduleAndLookahead:d({scheduledStartContextTime:()=>null,notifiedLookahead:()=>!1}),setPlayingWebAudio:d({isPlaying:()=>!0,webAudioLoadingState:()=>"LOADED",playbackType:()=>"WEBAUDIO"}),setScheduledGapless:d({isPlaying:()=>!0,webAudioLoadingState:()=>"LOADED",playbackType:()=>"WEBAUDIO",scheduledStartContextTime:({event:e})=>e.when}),clearPlayingAndSchedule:d({isPlaying:()=>!1,scheduledStartContextTime:()=>null,notifiedLookahead:()=>!1}),setNotifiedLookahead:d({notifiedLookahead:()=>!0}),setResolvedUrl:d({resolvedUrl:({event:e})=>e.url}),clearScheduledStart:d({scheduledStartContextTime:()=>null}),setPlayingWebAudioType:d({isPlaying:()=>!0,playbackType:()=>"WEBAUDIO"})}}).createMachine({id:"track",initial:"idle",context:o,on:{START_FETCH:{guard:"canStartFetch",actions:[d({webAudioLoadingState:()=>"LOADING",fetchStarted:()=>!0}),Q("fetchDecode",{id:"fetchDecode",input:({context:e})=>({trackUrl:e.trackUrl,resolvedUrl:e.resolvedUrl,skipHEAD:e.skipHEAD})})]}},states:{idle:{on:{HTML5_ENDED:{actions:["notifyTrackEnded"]},DEACTIVATE:{actions:["pauseHtml5","resetHtml5Element","resetTiming","stopProgressLoop","clearScheduleAndLookahead"]},ACTIVATE:{actions:["resetTiming","resetHtml5Element","clearScheduleAndLookahead"]},PLAY:[{guard:"canPlayWebAudio",target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},{guard:"isWebAudioOnly",actions:["setPendingPlay","triggerFetchForPendingPlay"]},{target:"html5",actions:["setIsPlaying","playHtml5","startProgressLoop"]}],PLAY_WEBAUDIO:{target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},SCHEDULE_GAPLESS:{target:"webaudio",actions:["setScheduledGapless","startScheduledSourceNode"]},PRELOAD:{target:"loading"},BUFFER_LOADING:{actions:"setLoadingState"},BUFFER_READY:[{guard:({context:e})=>e.pendingPlay,target:"webaudio",actions:["clearPendingPlay","setPlayingWebAudio","startSourceNode","startProgressLoop"]},{actions:"setLoadedState"}],BUFFER_ERROR:{actions:["setErrorState","clearPendingPlay"]},URL_RESOLVED:{actions:"setResolvedUrl"}}},html5:{on:{PAUSE:{actions:["clearIsPlaying","pauseHtml5","stopProgressLoop","reportProgress"]},PLAY:{actions:["setIsPlaying","playHtml5","startProgressLoop"]},BUFFER_LOADING:{actions:"setLoadingState"},PLAY_WEBAUDIO:{target:"webaudio",actions:"setPlayingWebAudio"},BUFFER_READY:{actions:"setLoadedState"},BUFFER_ERROR:{actions:"setErrorState"},SEEK:{actions:["seekHtml5","reportProgress"]},LOOKAHEAD_REACHED:{actions:"setNotifiedLookahead"},HTML5_ENDED:{target:"idle",actions:["clearIsPlaying","stopProgressLoop","notifyTrackEnded"]},ACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","pauseHtml5","stopProgressLoop","resetTiming","resetHtml5Element"]},URL_RESOLVED:{actions:"setResolvedUrl"},DEACTIVATE:{target:"idle",actions:["clearIsPlaying","pauseHtml5","resetHtml5Element","resetTiming","stopProgressLoop"]}}},loading:{on:{BUFFER_LOADING:{actions:"setLoadingState"},BUFFER_READY:[{guard:({context:e})=>e.pendingPlay,target:"webaudio",actions:["clearPendingPlay","setPlayingWebAudio","startSourceNode","startProgressLoop"]},{target:"idle",actions:"setLoadedState"}],BUFFER_ERROR:{target:"idle",actions:["setErrorState","clearPendingPlay"]},PLAY:[{guard:"canPlayWebAudio",target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},{guard:"isWebAudioOnly",actions:["setPendingPlay","triggerFetchForPendingPlay"]},{target:"html5",actions:["setIsPlaying","playHtml5","startProgressLoop"]}],PLAY_WEBAUDIO:{target:"webaudio",actions:["setPlayingWebAudio","startSourceNode","startProgressLoop"]},SCHEDULE_GAPLESS:{target:"webaudio",actions:["setScheduledGapless","startScheduledSourceNode"]},ACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","resetTiming","resetHtml5Element"]},DEACTIVATE:{target:"idle",actions:["clearIsPlaying","resetTiming"]},URL_RESOLVED:{actions:"setResolvedUrl"}}},webaudio:{on:{PAUSE:{actions:["clearIsPlaying","freezePausedTime","stopSourceNode","disconnectGain","stopProgressLoop","reportProgress"]},PLAY:[{guard:"canPlayWebAudio",actions:["setIsPlaying","startSourceNode","startProgressLoop"]},{actions:"setIsPlaying"}],PLAY_WEBAUDIO:{actions:"setPlayingWebAudioType"},SEEK:{actions:["clearScheduledStart","seekWebAudio","reportProgress"]},SET_VOLUME:{},CANCEL_GAPLESS:{target:"idle",actions:["clearPlayingAndSchedule","stopSourceNode","disconnectGain","stopProgressLoop","resetTiming"]},LOOKAHEAD_REACHED:{actions:"setNotifiedLookahead"},WEBAUDIO_ENDED:{target:"idle",actions:["clearIsPlaying","stopProgressLoop","notifyTrackEnded"]},ACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","stopSourceNode","disconnectGain","stopProgressLoop","resetTiming","resetHtml5Element"]},DEACTIVATE:{target:"idle",actions:["clearPlayingAndSchedule","stopSourceNode","disconnectGain","resetTiming","resetHtml5Element","stopProgressLoop"]}}}}})}var X=5,L=15,T=class{index;metadata;_trackUrl;_resolvedUrl;skipHEAD;_pendingArrayBuffer=null;audio;_playbackMethod;get ctx(){if(this._playbackMethod==="HTML5_ONLY")return null;let e=k();return e&&!this.gainNode&&(this.gainNode=e.createGain(),this.gainNode.gain.value=this.audio.volume),e}gainNode=null;sourceNode=null;audioBuffer=null;_waRefCtxTime=0;_waRefTrackTime=0;pausedAtTrackTime=0;_actor;queueRef;rafId=null;_notifiedPreloadThreshold=!1;constructor(e){this.index=e.index,this._trackUrl=e.trackUrl,this._resolvedUrl=e.trackUrl,this.skipHEAD=e.skipHEAD??!1,this.metadata=e.metadata??{},this.queueRef=e.queue,this.audio=new Audio,this.audio.preload="none",this.audio.src=this._trackUrl,this.audio.volume=e.queue.volume,this.audio.controls=!1,this.audio.onerror=()=>{var c,u;let a=(c=this.audio.error)==null?void 0:c.code;if(a===1)return;let s=((u=this.audio.error)==null?void 0:u.message)??"unknown";this.queueRef.onError(new Error(`HTML5 audio error on track ${this.index} (code ${a}): ${s}`))},this.audio.onended=()=>{this.queueRef.onDebug(`audio.onended track=${this.index} machineState=${this._actor.getSnapshot().value} queueIdx=${this.queueRef.currentTrackIndex}`),this._actor.send({type:"HTML5_ENDED"})},this._playbackMethod=e.queue.playbackMethod;let t={trackUrl:this._trackUrl,resolvedUrl:this._trackUrl,skipHEAD:this.skipHEAD,playbackType:"HTML5",webAudioLoadingState:"NONE",isPlaying:!1,scheduledStartContextTime:null,notifiedLookahead:!1,fetchStarted:!1,pendingPlay:!1},n=R(t).provide({guards:{canPlayWebAudio:()=>!!(this.ctx&&this.audioBuffer&&this.gainNode),isWebAudioOnly:()=>this._playbackMethod==="WEBAUDIO_ONLY"},actors:{fetchDecode:m.provide({actors:{resolveUrl:x(async({signal:a})=>{let s=await fetch(this._trackUrl,{method:"HEAD",signal:a});return s.redirected&&s.url?(this._resolvedUrl=s.url,this.audio.src=s.url,s.url):null}),fetchAudio:x(async({input:a,signal:s})=>{let{resolvedUrl:c}=a,u=await fetch(c,{signal:s});if(!u.ok)throw new Error(`HTTP ${u.status} for ${c}`);this._pendingArrayBuffer=await u.arrayBuffer()}),decodeAudio:x(async()=>{let a=this._pendingArrayBuffer;if(this._pendingArrayBuffer=null,!a||!this.ctx)throw new Error("No ArrayBuffer or AudioContext");this.audioBuffer=await this.ctx.decodeAudioData(a),queueMicrotask(()=>this.queueRef.onTrackBufferReady(this))})}})},actions:{triggerFetchForPendingPlay:()=>{this.preload(),y()},playHtml5:()=>this._playHtml5(),startSourceNode:()=>{this._startSourceNode(this.pausedAtTrackTime)},startScheduledSourceNode:({context:a})=>{let s=a.scheduledStartContextTime;s===null||!this.ctx||!this.audioBuffer||!this.gainNode||(this._stopSourceNode(),this.sourceNode=this.ctx.createBufferSource(),this.sourceNode.buffer=this.audioBuffer,this.sourceNode.playbackRate.value=this.queueRef.playbackRate,this.sourceNode.connect(this.gainNode),this.gainNode.connect(this.ctx.destination),this.sourceNode.onended=this._handleWebAudioEnded,this.sourceNode.start(s,0),this._waRefCtxTime=s,this._waRefTrackTime=0,this.queueRef.onDebug(`startScheduledSourceNode track=${this.index} when=${s.toFixed(3)} ctxNow=${this.ctx.currentTime.toFixed(3)} delta=${(s-this.ctx.currentTime).toFixed(3)}s`))},startProgressLoop:()=>this.startProgressLoop(),pauseHtml5:()=>this.audio.pause(),freezePausedTime:()=>{let a=this.currentTime;this.pausedAtTrackTime=isFinite(a)?a:0},stopSourceNode:()=>this._stopSourceNode(),disconnectGain:()=>this._disconnectGain(),stopProgressLoop:()=>this._stopProgressLoop(),reportProgress:()=>{queueMicrotask(()=>this.queueRef.onProgress(this.toInfo()))},seekHtml5:()=>this._seekHtml5(),seekWebAudio:()=>this._seekWebAudio(),resetHtml5Element:()=>{this.audio.currentTime=0},resetTiming:()=>{this._waRefCtxTime=0,this._waRefTrackTime=0,this.pausedAtTrackTime=0},notifyTrackEnded:()=>{queueMicrotask(()=>this.queueRef.onTrackEnded(this))}}});this._actor=Z(n),this._actor.start()}play(){this.queueRef.onDebug(`Track.play() track=${this.index} machineState=${this._actor.getSnapshot().value} hasBuffer=${!!this.audioBuffer} hasCtx=${!!this.ctx} audioPaused=${this.audio.paused}`),this._actor.send({type:"PLAY"})}pause(){this._actor.send({type:"PAUSE"})}seek(e){if(!isFinite(e))return;let t=Math.max(0,isNaN(this.duration)?e:Math.min(e,this.duration));this.pausedAtTrackTime=t,this._actor.send({type:"SEEK",time:t})}setVolume(e){let t=Math.min(1,Math.max(0,e));this.audio.volume=t,this.gainNode&&(this.gainNode.gain.value=t),this._actor.send({type:"SET_VOLUME",volume:t})}setPlaybackRate(e){if(this.ctx&&this.sourceNode&&this._actor.getSnapshot().context.isPlaying){let t=this.sourceNode.playbackRate.value;this._waRefTrackTime=this._waRefTrackTime+(this.ctx.currentTime-this._waRefCtxTime)*t,this._waRefCtxTime=this.ctx.currentTime}this.audio.playbackRate=e,this.sourceNode&&(this.sourceNode.playbackRate.value=e)}preload(){this.queueRef.onDebug(`preload() track=${this.index} state=${this._actor.getSnapshot().value} hasBuffer=${!!this.audioBuffer} hasCtx=${!!this.ctx}`),this._actor.getSnapshot().value==="idle"&&this._actor.send({type:"PRELOAD"}),!this.audioBuffer&&(y(),this.ctx&&this._actor.send({type:"START_FETCH"}))}seekToEnd(e=6){let t=this.duration;!isNaN(t)&&t>e&&this.seek(t-e)}activate(){this._actor.send({type:"ACTIVATE"})}deactivate(){this.queueRef.onDebug(`Track.deactivate() track=${this.index} machineState=${this._actor.getSnapshot().value} isPlaying=${this.isPlaying}`),this._notifiedPreloadThreshold=!1,this._actor.send({type:"DEACTIVATE"}),this.queueRef.onDebug(`Track.deactivate() done track=${this.index} machineState=${this._actor.getSnapshot().value}`)}destroy(){var e;this.deactivate(),this._pendingArrayBuffer=null,this.audioBuffer=null,(e=this.gainNode)==null||e.disconnect(),this.gainNode=null,this._actor.stop()}cancelGaplessStart(){this._actor.getSnapshot().context.scheduledStartContextTime!==null&&this._actor.send({type:"CANCEL_GAPLESS"})}scheduleGaplessStart(e){!this.ctx||!this.audioBuffer||!this.gainNode||this._actor.send({type:"SCHEDULE_GAPLESS",when:e})}get currentTime(){let e=this._actor.getSnapshot();return e.value==="webaudio"?e.context.isPlaying?this.ctx?Math.max(0,this._waRefTrackTime+(this.ctx.currentTime-this._waRefCtxTime)*this.queueRef.playbackRate):0:this.pausedAtTrackTime:this.audio.currentTime}get duration(){return this.audioBuffer?this.audioBuffer.duration:this.audio.duration}get isPaused(){let e=this._actor.getSnapshot();return e.value==="webaudio"?!e.context.isPlaying:this.audio.paused}get isPlaying(){return this._actor.getSnapshot().context.isPlaying}get trackUrl(){return this._resolvedUrl}get playbackType(){return this._actor.getSnapshot().context.playbackType}get webAudioLoadingState(){return this._actor.getSnapshot().context.webAudioLoadingState}get hasSourceNode(){return this.sourceNode!==null}get machineState(){return this._actor.getSnapshot().value}get scheduledStartContextTime(){return this._actor.getSnapshot().context.scheduledStartContextTime}get isBufferLoaded(){return this.audioBuffer!==null}toInfo(){var e;return{index:this.index,currentTime:this.currentTime,duration:this.duration,isPlaying:this.isPlaying,isPaused:this.isPaused,volume:((e=this.gainNode)==null?void 0:e.gain.value)??this.audio.volume,trackUrl:this.trackUrl,playbackType:this.playbackType,webAudioLoadingState:this.webAudioLoadingState,metadata:this.metadata,playbackRate:this.queueRef.playbackRate,machineState:this.machineState}}_playHtml5(){this.audio.preload!=="auto"&&(this.audio.preload="auto"),this.audio.playbackRate=this.queueRef.playbackRate;let e=this.audio.play();e&&e.catch(t=>{t instanceof Error&&t.name==="NotAllowedError"?this.queueRef.onPlayBlocked():t instanceof Error&&t.name==="AbortError"||this.queueRef.onError(t instanceof Error?t:new Error(String(t)))})}_seekHtml5(){let e=this.pausedAtTrackTime;isFinite(e)&&(this.audio.preload!=="auto"&&(this.audio.preload="auto"),this.audio.readyState>=HTMLMediaElement.HAVE_METADATA?this.audio.currentTime=e:(this.audio.addEventListener("loadedmetadata",()=>{this.audio.currentTime=e},{once:!0}),this.audio.load()))}_startSourceNode(e){!this.ctx||!this.audioBuffer||!this.gainNode||(this._stopSourceNode(),this.ctx.state==="suspended"&&this.ctx.resume(),this.sourceNode=this.ctx.createBufferSource(),this.sourceNode.buffer=this.audioBuffer,this.sourceNode.playbackRate.value=this.queueRef.playbackRate,this.sourceNode.connect(this.gainNode),this.gainNode.connect(this.ctx.destination),this.sourceNode.onended=this._handleWebAudioEnded,this._waRefCtxTime=this.ctx.currentTime,this._waRefTrackTime=e,this.sourceNode.start(0,e))}_stopSourceNode(){if(this.sourceNode){this.sourceNode.onended=null;try{this.sourceNode.stop()}catch{}try{this.sourceNode.disconnect()}catch{}this.sourceNode=null}}_disconnectGain(){if(!(!this.gainNode||!this.ctx))try{this.gainNode.disconnect(this.ctx.destination)}catch{}}_seekWebAudio(){let t=this._actor.getSnapshot().context.isPlaying,n=this.pausedAtTrackTime;this._stopSourceNode(),t&&this._startSourceNode(n)}_handleWebAudioEnded=()=>{this.queueRef.onDebug(`_handleWebAudioEnded track=${this.index} sourceNode=${!!this.sourceNode} queueIdx=${this.queueRef.currentTrackIndex}`),this.sourceNode&&this._actor.send({type:"WEBAUDIO_ENDED"})};startProgressLoop(){if(this.rafId!==null)return;let e=()=>{if(this.isPaused||!this.isPlaying){this.rafId=null;return}this.queueRef.onProgress(this.toInfo());let t=this.duration-this.currentTime;!this._actor.getSnapshot().context.notifiedLookahead&&!isNaN(t)&&t<=X&&(this._actor.send({type:"LOOKAHEAD_REACHED"}),queueMicrotask(()=>this.queueRef.onTrackBufferReady(this)));let a=isNaN(this.duration)?L:Math.min(this.duration*.2,L);!this._notifiedPreloadThreshold&&this.currentTime>=a&&(this._notifiedPreloadThreshold=!0,queueMicrotask(()=>this.queueRef.onPreloadReady(this))),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}_stopProgressLoop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}};function D(o,e){let t=0;return(...n)=>{let a=performance.now();a-t<e||(t=a,o(...n))}}var I=5,A=class{_tracks=[];_actor;_onProgress;_onEnded;_onPlayNextTrack;_onPlayPreviousTrack;_onStartNewTrack;_onError;_onPlayBlocked;_onDebug;playbackMethod;_volume;_preloadNumTracks;_playbackRate;_scheduledNextIndex=null;_throttledUpdatePositionState=D((e,t,n)=>C(e,t,n),1e3);constructor(e={}){let{tracks:t=[],onProgress:n,onEnded:a,onPlayNextTrack:s,onPlayPreviousTrack:c,onStartNewTrack:u,onError:M,onPlayBlocked:U,onDebug:O,playbackMethod:w="HYBRID",trackMetadata:G=[],volume:H=1,preloadNumTracks:B=2,playbackRate:F=1}=e;this._volume=Math.min(1,Math.max(0,H)),this._preloadNumTracks=Math.max(0,B),this._playbackRate=Math.min(4,Math.max(.25,F)),this.playbackMethod=w,this._onProgress=n,this._onEnded=a,this._onPlayNextTrack=s,this._onPlayPreviousTrack=c,this._onStartNewTrack=u,this._onError=M,this._onPlayBlocked=U,this._onDebug=O,this._tracks=t.map((i,r)=>new T({trackUrl:i,index:r,queue:this,metadata:G[r]}));let $=N({currentTrackIndex:0,trackCount:this._tracks.length}).provide({actions:{deactivateCurrent:({context:i})=>{var r;(r=this._trackAt(i.currentTrackIndex))==null||r.deactivate()},deactivateEndedTrack:({context:i})=>{var r;(r=this._trackAt(i.currentTrackIndex))==null||r.deactivate()},activateAndPlayCurrent:({context:i})=>{let r=this._trackAt(i.currentTrackIndex);r&&(r.activate(),this._scheduledNextIndex!==r.index&&r.play())},playOrContinueGapless:({context:i})=>{let r=this._trackAt(i.currentTrackIndex);r&&(this._scheduledNextIndex!==r.index?r.play():(this._scheduledNextIndex=null,this.onDebug(`onTrackEnded: gapless track ${r.index} \u2014 sourceNode=${r.hasSourceNode} isPlaying=${r.isPlaying} machineState=${r.machineState}`),r.startProgressLoop()))},cancelAllGapless:()=>this._cancelScheduledGapless(),notifyStartNewTrack:({context:i})=>{var l;let r=this._trackAt(i.currentTrackIndex);r&&((l=this._onStartNewTrack)==null||l.call(this,r.toInfo()))},notifyPlayNextTrack:({context:i})=>{var l;let r=this._trackAt(i.currentTrackIndex);r&&((l=this._onPlayNextTrack)==null||l.call(this,r.toInfo()))},notifyPlayPreviousTrack:({context:i})=>{var l;let r=this._trackAt(i.currentTrackIndex);r&&((l=this._onPlayPreviousTrack)==null||l.call(this,r.toInfo()))},notifyEnded:()=>{var i;return(i=this._onEnded)==null?void 0:i.call(this)},updateMediaSessionMetadata:({context:i})=>{let r=this._trackAt(i.currentTrackIndex);r&&P(r.metadata)},preloadAhead:({context:i})=>{this._preloadAhead(i.currentTrackIndex)},playCurrent:({context:i})=>{var r;(r=this._trackAt(i.currentTrackIndex))==null||r.play()},pauseCurrent:({context:i})=>{var r;(r=this._trackAt(i.currentTrackIndex))==null||r.pause()},seekCurrent:({context:i,event:r})=>{var S;let l=r;(S=this._trackAt(i.currentTrackIndex))==null||S.seek(l.time)},seekCurrentToZero:({context:i})=>{var r;(r=this._trackAt(i.currentTrackIndex))==null||r.seek(0)},scheduleGapless:({context:i})=>{this._tryScheduleGapless(i.currentTrackIndex)},cancelScheduledGapless:()=>{this._cancelScheduledGapless()},cancelAndRescheduleGapless:({context:i})=>{this._cancelScheduledGapless(),this._tryScheduleGapless(i.currentTrackIndex)}}});this._actor=z($),this._actor.subscribe(i=>{b(i.value==="playing")}),this._actor.start(),v({onPlay:()=>this.play(),onPause:()=>{this._actor.getSnapshot().value==="playing"&&this.pause()},onNext:()=>this.next(),onPrevious:()=>this.previous(),onSeek:i=>this.seek(i)})}play(){this._currentTrack&&this._actor.send({type:"PLAY"})}pause(){this._actor.send({type:"PAUSE"})}togglePlayPause(){this._actor.getSnapshot().value==="playing"?this.pause():this.play()}next(){this._actor.getSnapshot().context.currentTrackIndex+1>=this._tracks.length||this._actor.send({type:"NEXT"})}previous(){let e=this._currentTrack;if(e&&e.currentTime>8){e.seek(0),e.play();return}this._actor.send({type:"PREVIOUS"})}gotoTrack(e,t=!1){e<0||e>=this._tracks.length||(this.onDebug(`gotoTrack(${e}, playImmediately=${t}) queueState=${this._actor.getSnapshot().value} curIdx=${this._actor.getSnapshot().context.currentTrackIndex}`),this._actor.send({type:"GOTO",index:e,playImmediately:t}))}seek(e){this._actor.send({type:"SEEK",time:e})}setVolume(e){let t=Math.min(1,Math.max(0,e));this._volume=t;for(let n of this._tracks)n.setVolume(t)}setPlaybackRate(e){var a;let t=Math.min(4,Math.max(.25,e));this._playbackRate=t,(a=this._currentTrack)==null||a.setPlaybackRate(t),this._cancelScheduledGapless();let n=this._actor.getSnapshot();n.value==="playing"&&this._tryScheduleGapless(n.context.currentTrackIndex)}addTrack(e,t={}){let n=this._tracks.length,a=t.metadata??{};this._tracks.push(new T({trackUrl:e,index:n,queue:this,skipHEAD:t.skipHEAD,metadata:a})),this._actor.send({type:"ADD_TRACK"})}removeTrack(e){if(!(e<0||e>=this._tracks.length)){this._tracks[e].destroy(),this._tracks.splice(e,1);for(let t=e;t<this._tracks.length;t++)this._tracks[t].index=t;this._scheduledNextIndex===e?this._scheduledNextIndex=null:this._scheduledNextIndex!==null&&this._scheduledNextIndex>e&&this._scheduledNextIndex--,this._actor.send({type:"REMOVE_TRACK",index:e})}}resumeAudioContext(){return y()}destroy(){for(let e of this._tracks)e.destroy();this._tracks=[],this._actor.stop()}get currentTrack(){var e;return(e=this._currentTrack)==null?void 0:e.toInfo()}get currentTrackIndex(){return this._actor.getSnapshot().context.currentTrackIndex}get tracks(){return this._tracks.map(e=>e.toInfo())}get isPlaying(){return this._actor.getSnapshot().value==="playing"}get isPaused(){return this._actor.getSnapshot().value==="paused"}get volume(){return this._volume}get preloadNumTracks(){return this._preloadNumTracks}set preloadNumTracks(e){this._preloadNumTracks=Math.max(0,e);let t=this._actor.getSnapshot();t.value==="playing"&&this._preloadAhead(t.context.currentTrackIndex)}get playbackRate(){return this._playbackRate}get queueSnapshot(){let e=this._actor.getSnapshot();return{state:e.value,context:e.context}}onTrackEnded(e){let t=this._actor.getSnapshot();if(this.onDebug(`onTrackEnded track=${e.index} queueState=${t.value} curIdx=${t.context.currentTrackIndex}`),e!==this._trackAt(t.context.currentTrackIndex))return;this._actor.send({type:"TRACK_ENDED"});let n=this._actor.getSnapshot();this.onDebug(`onTrackEnded after TRACK_ENDED \u2192 queueState=${n.value} curIdx=${n.context.currentTrackIndex}`)}onTrackBufferReady(e){this._actor.send({type:"TRACK_LOADED",index:e.index})}onProgress(e){var t;e.index===this._actor.getSnapshot().context.currentTrackIndex&&(isNaN(e.duration)||this._throttledUpdatePositionState(e.duration,e.currentTime,this._playbackRate),(t=this._onProgress)==null||t.call(this,e))}onError(e){var t;(t=this._onError)==null||t.call(this,e)}onPlayBlocked(){var e;this._actor.send({type:"PAUSE"}),(e=this._onPlayBlocked)==null||e.call(this)}onPreloadReady(e){let t=this._actor.getSnapshot();e.index===t.context.currentTrackIndex&&this._preloadAhead(t.context.currentTrackIndex)}onDebug(e){var t;(t=this._onDebug)==null||t.call(this,e)}_trackAt(e){return this._tracks[e]}get _currentTrack(){return this._tracks[this._actor.getSnapshot().context.currentTrackIndex]}_preloadAhead(e){let t=this._trackAt(e);if(t&&t.playbackType==="HTML5"&&t.isPlaying){let a=isNaN(t.duration)?15:Math.min(t.duration*.2,15);if(t.currentTime<a){this.onDebug(`_preloadAhead: deferring \u2014 HTML5 track ${e} at ${t.currentTime.toFixed(1)}s (threshold=${a.toFixed(1)}s)`);return}}let n=e+this._preloadNumTracks+1;this.onDebug(`_preloadAhead(${e}) limit=${n} trackCount=${this._tracks.length}`);for(let a=e+1;a<this._tracks.length&&a<n;a++){let s=this._tracks[a];if(s.isBufferLoaded)this.onDebug(`_preloadAhead: track ${a} already loaded`);else{this.onDebug(`_preloadAhead: starting preload for track ${a}`),s.preload();break}}}_cancelScheduledGapless(){if(this._scheduledNextIndex===null)return;let e=this._trackAt(this._scheduledNextIndex);e&&(e.cancelGaplessStart(),this.onDebug(`_cancelScheduledGapless: cancelled track ${this._scheduledNextIndex}`)),this._scheduledNextIndex=null}_tryScheduleGapless(e){let t=k();if(!t||this.playbackMethod==="HTML5_ONLY")return;let n=e+1;if(n>=this._tracks.length)return;let a=this._tracks[e],s=this._tracks[n];if(!a.isBufferLoaded||!s.isBufferLoaded||this._scheduledNextIndex===n||!a.isPlaying)return;let c=this._computeTrackEndTime(a);if(c===null||c<t.currentTime+.01)return;let u=c-t.currentTime;if(a.playbackType==="HTML5"&&u>I){this.onDebug(`_tryScheduleGapless: deferring \u2014 HTML5 track ${e} has ${u.toFixed(1)}s remaining (max lookahead=${I}s)`);return}s.scheduleGaplessStart(c),this.onDebug(`_tryScheduleGapless: scheduled track ${n} at endTime=${c.toFixed(3)} (in ${(c-t.currentTime).toFixed(1)}s) curPlaybackType=${a.playbackType}`),this._scheduledNextIndex=n}_computeTrackEndTime(e){let t=k();if(!t||!e.isBufferLoaded)return null;let n=e.duration;if(isNaN(n))return null;if(e.scheduledStartContextTime!==null)return e.scheduledStartContextTime+n/this._playbackRate;let a=(n-e.currentTime)/this._playbackRate;return a<=0?null:t.currentTime+a}};export{A as Queue,A as default};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|