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 CHANGED
@@ -1,4 +1,9 @@
1
- # gapless.js
1
+ ![gapless.js diagram](./artifacts/graph.svg)
2
+
3
+ # Gapless.js
4
+
5
+ [![CI](https://github.com/RelistenNet/gapless.js/actions/workflows/ci.yml/badge.svg)](https://github.com/RelistenNet/gapless.js/actions/workflows/ci.yml)
6
+ [![Bundle Size](https://deno.bundlejs.com/badge?q=gapless&treeshake=[{+default+}])](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
- webAudioIsDisabled: false, // Disable Web Audio API (disables gapless playback)
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 `webAudioIsDisabled: true` in constructor |
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
- * Set true to disable Web Audio API entirely and use HTML5 audio only.
33
- * Gapless playback will not be available in this mode.
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
- webAudioIsDisabled?: boolean;
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 webAudioIsDisabled: boolean;
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 _webAudioDisabled;
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 when the current source node was started. */
107
- private webAudioStartedAt;
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 webAudioIsDisabled: boolean;
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