gapless 4.4.0 → 4.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Queue.d.ts +72 -0
- package/dist/Queue.d.ts.map +1 -0
- package/dist/Track.d.ts +150 -0
- package/dist/Track.d.ts.map +1 -0
- package/dist/index.d.ts +4 -318
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/machines/fetchDecode.machine.d.ts +42 -0
- package/dist/machines/fetchDecode.machine.d.ts.map +1 -0
- package/dist/machines/queue.machine.d.ts +165 -0
- package/dist/machines/queue.machine.d.ts.map +1 -0
- package/dist/machines/track.machine.d.ts +291 -0
- package/dist/machines/track.machine.d.ts.map +1 -0
- package/dist/types.d.ts +99 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/audioContext.d.ts +25 -0
- package/dist/utils/audioContext.d.ts.map +1 -0
- package/dist/utils/mediaSession.d.ts +17 -0
- package/dist/utils/mediaSession.d.ts.map +1 -0
- package/dist/utils/throttle.d.ts +3 -0
- package/dist/utils/throttle.d.ts.map +1 -0
- package/package.json +10 -10
- package/src/Queue.ts +6 -5
- package/src/Track.ts +5 -1
- package/src/machines/queue.machine.ts +10 -6
package/dist/Queue.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Track } from './Track';
|
|
2
|
+
import type { TrackQueueRef } from './Track';
|
|
3
|
+
import type { GaplessOptions, AddTrackOptions, TrackInfo, PlaybackMethod } from './types';
|
|
4
|
+
export declare class Queue implements TrackQueueRef {
|
|
5
|
+
private _tracks;
|
|
6
|
+
private readonly _actor;
|
|
7
|
+
private readonly _onProgress?;
|
|
8
|
+
private readonly _onEnded?;
|
|
9
|
+
private readonly _onPlayNextTrack?;
|
|
10
|
+
private readonly _onPlayPreviousTrack?;
|
|
11
|
+
private readonly _onStartNewTrack?;
|
|
12
|
+
private readonly _onError?;
|
|
13
|
+
private readonly _onPlayBlocked?;
|
|
14
|
+
private readonly _onDebug?;
|
|
15
|
+
readonly playbackMethod: PlaybackMethod;
|
|
16
|
+
private _volume;
|
|
17
|
+
private _preloadNumTracks;
|
|
18
|
+
private _playbackRate;
|
|
19
|
+
/** Silent looping element that keeps the browser's MediaSession anchor alive. */
|
|
20
|
+
private _mediaSessionAnchor;
|
|
21
|
+
/** Index of the next track with a pre-scheduled gapless start, or null. */
|
|
22
|
+
private _scheduledNextIndex;
|
|
23
|
+
private _throttledUpdatePositionState;
|
|
24
|
+
constructor(options?: GaplessOptions);
|
|
25
|
+
play(): void;
|
|
26
|
+
pause(): void;
|
|
27
|
+
togglePlayPause(): void;
|
|
28
|
+
next(): void;
|
|
29
|
+
previous(): void;
|
|
30
|
+
gotoTrack(index: number, playImmediately?: boolean, startTime?: number): void;
|
|
31
|
+
seek(time: number): void;
|
|
32
|
+
setVolume(volume: number): void;
|
|
33
|
+
setPlaybackRate(rate: number): void;
|
|
34
|
+
addTrack(url: string, options?: AddTrackOptions): void;
|
|
35
|
+
removeTrack(index: number): void;
|
|
36
|
+
resumeAudioContext(): Promise<void>;
|
|
37
|
+
destroy(): void;
|
|
38
|
+
get currentTrack(): TrackInfo | undefined;
|
|
39
|
+
get currentTrackIndex(): number;
|
|
40
|
+
get tracks(): readonly TrackInfo[];
|
|
41
|
+
get isPlaying(): boolean;
|
|
42
|
+
get isPaused(): boolean;
|
|
43
|
+
get volume(): number;
|
|
44
|
+
get preloadNumTracks(): number;
|
|
45
|
+
set preloadNumTracks(value: number);
|
|
46
|
+
get playbackRate(): number;
|
|
47
|
+
/** Snapshot of the queue state machine (state name + context). For debugging. */
|
|
48
|
+
get queueSnapshot(): {
|
|
49
|
+
state: string;
|
|
50
|
+
context: {
|
|
51
|
+
currentTrackIndex: number;
|
|
52
|
+
trackCount: number;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
onTrackEnded(track: Track): void;
|
|
56
|
+
onTrackBufferReady(track: Track): void;
|
|
57
|
+
onProgress(info: TrackInfo): void;
|
|
58
|
+
onError(error: Error): void;
|
|
59
|
+
onPlayBlocked(): void;
|
|
60
|
+
onPreloadReady(track: Track): void;
|
|
61
|
+
onDebug(msg: string): void;
|
|
62
|
+
/** Look up a track by index — safe for use inside machine actions. */
|
|
63
|
+
private _trackAt;
|
|
64
|
+
private get _currentTrack();
|
|
65
|
+
private _preloadAhead;
|
|
66
|
+
private _cancelScheduledGapless;
|
|
67
|
+
private _tryScheduleGapless;
|
|
68
|
+
private _computeTrackEndTime;
|
|
69
|
+
private _startMediaSessionAnchor;
|
|
70
|
+
private _stopMediaSessionAnchor;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=Queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Queue.d.ts","sourceRoot":"","sources":["../src/Queue.ts"],"names":[],"mappings":"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"}
|
package/dist/Track.d.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { TrackInfo, TrackMetadata, WebAudioLoadingState, PlaybackType, PlaybackMethod } from './types';
|
|
2
|
+
export interface TrackQueueRef {
|
|
3
|
+
onTrackEnded(track: Track): void;
|
|
4
|
+
onTrackBufferReady(track: Track): void;
|
|
5
|
+
onPreloadReady(track: Track): void;
|
|
6
|
+
onProgress(info: TrackInfo): void;
|
|
7
|
+
onError(error: Error): void;
|
|
8
|
+
onPlayBlocked(): void;
|
|
9
|
+
onDebug(msg: string): void;
|
|
10
|
+
readonly volume: number;
|
|
11
|
+
readonly playbackMethod: PlaybackMethod;
|
|
12
|
+
readonly playbackRate: number;
|
|
13
|
+
readonly currentTrackIndex: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class Track {
|
|
16
|
+
readonly index: number;
|
|
17
|
+
readonly metadata: TrackMetadata;
|
|
18
|
+
private _trackUrl;
|
|
19
|
+
private _resolvedUrl;
|
|
20
|
+
private readonly skipHEAD;
|
|
21
|
+
/** Temporary holder between fetch and decode steps (unserializable — stays on Track class). */
|
|
22
|
+
private _pendingArrayBuffer;
|
|
23
|
+
readonly audio: HTMLAudioElement;
|
|
24
|
+
private readonly _playbackMethod;
|
|
25
|
+
private get ctx();
|
|
26
|
+
private gainNode;
|
|
27
|
+
private _mediaElementSource;
|
|
28
|
+
private _html5GainNode;
|
|
29
|
+
private sourceNode;
|
|
30
|
+
audioBuffer: AudioBuffer | null;
|
|
31
|
+
/** AudioContext.currentTime at the start of the current playback segment. */
|
|
32
|
+
/**
|
|
33
|
+
* Offset (in seconds) between the decoded buffer's "offset 0" and the music's
|
|
34
|
+
* "offset 0". Some MP3 files include ID3v2 metadata, encoder priming samples,
|
|
35
|
+
* or container padding at the start; HTML5 audio elements skip past these
|
|
36
|
+
* natively (audio.currentTime=0 means music start), but `decodeAudioData` in
|
|
37
|
+
* some browsers includes them in the decoded buffer (buffer offset 0 = file
|
|
38
|
+
* start, music actually starts at offset _bufferStartPaddingSec).
|
|
39
|
+
*
|
|
40
|
+
* Without this shift, calling source.start(when, audio.currentTime) plays
|
|
41
|
+
* `audio.currentTime` seconds AHEAD of what HTML5 was just outputting,
|
|
42
|
+
* sounding like a backward skip at crossover. We compute this once both
|
|
43
|
+
* the buffer and audio.duration are known, and apply it as
|
|
44
|
+
* source.start(when, trackTime + _bufferStartPaddingSec)
|
|
45
|
+
* everywhere we read from the buffer. User-facing time (currentTime/duration
|
|
46
|
+
* getters) continues to be reported in music-time, not buffer-time.
|
|
47
|
+
*/
|
|
48
|
+
private _bufferStartPaddingSec;
|
|
49
|
+
private _bufferAlignmentMeasured;
|
|
50
|
+
private _waRefCtxTime;
|
|
51
|
+
/** Track position (seconds) at the start of the current playback segment. */
|
|
52
|
+
private _waRefTrackTime;
|
|
53
|
+
/** Track-time (seconds) frozen at the moment of the most recent pause. */
|
|
54
|
+
private pausedAtTrackTime;
|
|
55
|
+
private readonly _actor;
|
|
56
|
+
private readonly queueRef;
|
|
57
|
+
private rafId;
|
|
58
|
+
private _notifiedPreloadThreshold;
|
|
59
|
+
constructor(opts: {
|
|
60
|
+
trackUrl: string;
|
|
61
|
+
index: number;
|
|
62
|
+
queue: TrackQueueRef;
|
|
63
|
+
skipHEAD?: boolean;
|
|
64
|
+
metadata?: TrackMetadata;
|
|
65
|
+
});
|
|
66
|
+
play(): void;
|
|
67
|
+
pause(): void;
|
|
68
|
+
seek(time: number): void;
|
|
69
|
+
setVolume(v: number): void;
|
|
70
|
+
setPlaybackRate(rate: number): void;
|
|
71
|
+
preload(): void;
|
|
72
|
+
seekToEnd(secondsFromEnd?: number): void;
|
|
73
|
+
activate(): void;
|
|
74
|
+
deactivate(): void;
|
|
75
|
+
destroy(): void;
|
|
76
|
+
/** Schedule the HTML5 gain node to mute at `when` — safety valve so an
|
|
77
|
+
* HTML5 element that runs past the predicted end is silenced instead of
|
|
78
|
+
* overlapping the next track. No-op if the HTML5 gain path isn't active. */
|
|
79
|
+
scheduleHtml5Mute(when: number): void;
|
|
80
|
+
/** Cancel any scheduled HTML5 gain mute (called when gapless is cancelled). */
|
|
81
|
+
cancelHtml5Mute(): void;
|
|
82
|
+
cancelGaplessStart(): void;
|
|
83
|
+
scheduleGaplessStart(when: number): boolean;
|
|
84
|
+
get currentTime(): number;
|
|
85
|
+
get duration(): number;
|
|
86
|
+
get isPaused(): boolean;
|
|
87
|
+
get isPlaying(): boolean;
|
|
88
|
+
get trackUrl(): string;
|
|
89
|
+
get playbackType(): PlaybackType;
|
|
90
|
+
get webAudioLoadingState(): WebAudioLoadingState;
|
|
91
|
+
get hasSourceNode(): boolean;
|
|
92
|
+
get machineState(): string;
|
|
93
|
+
get scheduledStartContextTime(): number | null;
|
|
94
|
+
/**
|
|
95
|
+
* Context-clock time at which the currently playing Web Audio source will
|
|
96
|
+
* reach the end of its buffer, derived from the live playback anchor
|
|
97
|
+
* (_waRefCtxTime/_waRefTrackTime). The anchor is re-established every time
|
|
98
|
+
* a source node starts (scheduled gapless start, resume, seek, crossover),
|
|
99
|
+
* so this stays correct across interruptions — unlike
|
|
100
|
+
* scheduledStartContextTime + duration, which goes stale the moment a
|
|
101
|
+
* pause/resume re-anchors the source at a later context time. For an
|
|
102
|
+
* uninterrupted gapless chain it is exact (the anchor IS the scheduled
|
|
103
|
+
* start), so next-track scheduling stays sample-accurate.
|
|
104
|
+
*
|
|
105
|
+
* Null when no source node is active (idle/html5/loading, or paused).
|
|
106
|
+
*/
|
|
107
|
+
get playbackEndContextTime(): number | null;
|
|
108
|
+
get isBufferLoaded(): boolean;
|
|
109
|
+
toInfo(): TrackInfo;
|
|
110
|
+
private _playHtml5;
|
|
111
|
+
private _seekHtml5;
|
|
112
|
+
/**
|
|
113
|
+
* Mid-stream crossover: switch an actively-playing HTML5 track to Web Audio.
|
|
114
|
+
*
|
|
115
|
+
* Why this exists: we cannot reliably predict when an HTML5 <audio> element
|
|
116
|
+
* will fire 'ended' from within the AudioContext clock. Any prediction is
|
|
117
|
+
* at the mercy of the browser's audio pipeline (buffering stalls, codec
|
|
118
|
+
* padding differences, clock drift between audio.currentTime and
|
|
119
|
+
* ctx.currentTime over long sessions). Scheduling the next gapless track
|
|
120
|
+
* against that prediction is how overlap bugs happen.
|
|
121
|
+
*
|
|
122
|
+
* Instead, as soon as the buffer is decoded, we hand playback off to Web
|
|
123
|
+
* Audio while the track is still mid-song. From that point on, the track
|
|
124
|
+
* and all subsequent gapless transitions live on a single clock
|
|
125
|
+
* (AudioContext.currentTime), so scheduling is sample-accurate by
|
|
126
|
+
* construction — no prediction involved.
|
|
127
|
+
*
|
|
128
|
+
* Ordering: pause the HTML5 element FIRST, then start the source node at
|
|
129
|
+
* the captured offset. Pausing first ensures audio.currentTime is frozen
|
|
130
|
+
* before we read it as the Web Audio start offset, so there's no brief
|
|
131
|
+
* double-audio window at the crossover point.
|
|
132
|
+
*/
|
|
133
|
+
private _crossoverHtml5ToWebAudio;
|
|
134
|
+
/**
|
|
135
|
+
* Reverted: alignment-based fixes (duration-delta and buffer-silence
|
|
136
|
+
* scanning) reduced the perceived skip on archive.org files but did not
|
|
137
|
+
* eliminate it, suggesting the residual gap isn't a buffer/timeline
|
|
138
|
+
* alignment problem at all. Leaving _bufferStartPaddingSec at 0 (no shift)
|
|
139
|
+
* until we have a confirmed root cause; the field and call sites are kept
|
|
140
|
+
* so we can re-introduce a fix without churning the source-start code.
|
|
141
|
+
*/
|
|
142
|
+
private _maybeComputeBufferAlignment;
|
|
143
|
+
private _startSourceNode;
|
|
144
|
+
private _stopSourceNode;
|
|
145
|
+
private _seekWebAudio;
|
|
146
|
+
private _handleWebAudioEnded;
|
|
147
|
+
startProgressLoop(): void;
|
|
148
|
+
private _stopProgressLoop;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=Track.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Track.d.ts","sourceRoot":"","sources":["../src/Track.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,318 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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';
|
|
9
|
-
type WebAudioLoadingState = 'NONE' | 'LOADING' | 'LOADED' | 'ERROR';
|
|
10
|
-
/** Metadata attached to a track (arbitrary user data). */
|
|
11
|
-
interface TrackMetadata {
|
|
12
|
-
title?: string;
|
|
13
|
-
artist?: string;
|
|
14
|
-
album?: string;
|
|
15
|
-
artwork?: MediaImage[];
|
|
16
|
-
[key: string]: unknown;
|
|
17
|
-
}
|
|
18
|
-
/** Options accepted by the Queue constructor. */
|
|
19
|
-
interface GaplessOptions {
|
|
20
|
-
/** Initial list of track URLs. */
|
|
21
|
-
tracks?: string[];
|
|
22
|
-
/** Called at ~60fps while playing. */
|
|
23
|
-
onProgress?: (info: TrackInfo) => void;
|
|
24
|
-
/** Called when the last track in the queue ends. */
|
|
25
|
-
onEnded?: () => void;
|
|
26
|
-
/** Called when the queue advances to the next track. */
|
|
27
|
-
onPlayNextTrack?: (info: TrackInfo) => void;
|
|
28
|
-
/** Called when the queue goes back to the previous track. */
|
|
29
|
-
onPlayPreviousTrack?: (info: TrackInfo) => void;
|
|
30
|
-
/** Called whenever a new track becomes the current track. */
|
|
31
|
-
onStartNewTrack?: (info: TrackInfo) => void;
|
|
32
|
-
/** Called on HTML5 audio errors. */
|
|
33
|
-
onError?: (error: Error) => void;
|
|
34
|
-
/** Called with internal debug messages. Only use for development. */
|
|
35
|
-
onDebug?: (msg: string) => void;
|
|
36
|
-
/** Called when autoplay is blocked by the browser. */
|
|
37
|
-
onPlayBlocked?: () => void;
|
|
38
|
-
/**
|
|
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.
|
|
43
|
-
*/
|
|
44
|
-
playbackMethod?: PlaybackMethod;
|
|
45
|
-
/** Per-track metadata (aligned to the tracks array by index). */
|
|
46
|
-
trackMetadata?: TrackMetadata[];
|
|
47
|
-
/** Initial volume, 0.0–1.0. Defaults to 1. */
|
|
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;
|
|
58
|
-
}
|
|
59
|
-
/** Options for dynamically adding a track. */
|
|
60
|
-
interface AddTrackOptions {
|
|
61
|
-
/**
|
|
62
|
-
* Skip the HEAD request used to resolve redirects.
|
|
63
|
-
* Set true when the URL is already a direct, final URL.
|
|
64
|
-
*/
|
|
65
|
-
skipHEAD?: boolean;
|
|
66
|
-
metadata?: TrackMetadata;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* A plain-data snapshot of a track's current state.
|
|
70
|
-
* Returned by Queue getters and passed to callbacks.
|
|
71
|
-
* No methods — pure data.
|
|
72
|
-
*/
|
|
73
|
-
interface TrackInfo {
|
|
74
|
-
/** Zero-based position of this track in the queue. */
|
|
75
|
-
index: number;
|
|
76
|
-
/** Current playback position in seconds. */
|
|
77
|
-
currentTime: number;
|
|
78
|
-
/** Total duration in seconds (NaN until loaded). */
|
|
79
|
-
duration: number;
|
|
80
|
-
/** True if currently playing. */
|
|
81
|
-
isPlaying: boolean;
|
|
82
|
-
/** True if explicitly paused. */
|
|
83
|
-
isPaused: boolean;
|
|
84
|
-
/** Current volume, 0.0–1.0. */
|
|
85
|
-
volume: number;
|
|
86
|
-
/** The resolved URL of the audio file. */
|
|
87
|
-
trackUrl: string;
|
|
88
|
-
/** Which backend is currently producing sound. */
|
|
89
|
-
playbackType: PlaybackType;
|
|
90
|
-
/** Whether the Web Audio buffer has been decoded. */
|
|
91
|
-
webAudioLoadingState: WebAudioLoadingState;
|
|
92
|
-
/** Arbitrary metadata supplied when the track was added. */
|
|
93
|
-
metadata?: TrackMetadata;
|
|
94
|
-
/** Current playback rate. */
|
|
95
|
-
playbackRate: number;
|
|
96
|
-
/** Current xstate machine state for this track (e.g. 'idle', 'html5', 'webaudio'). */
|
|
97
|
-
machineState: string;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
interface TrackQueueRef {
|
|
101
|
-
onTrackEnded(track: Track): void;
|
|
102
|
-
onTrackBufferReady(track: Track): void;
|
|
103
|
-
onPreloadReady(track: Track): void;
|
|
104
|
-
onProgress(info: TrackInfo): void;
|
|
105
|
-
onError(error: Error): void;
|
|
106
|
-
onPlayBlocked(): void;
|
|
107
|
-
onDebug(msg: string): void;
|
|
108
|
-
readonly volume: number;
|
|
109
|
-
readonly playbackMethod: PlaybackMethod;
|
|
110
|
-
readonly playbackRate: number;
|
|
111
|
-
readonly currentTrackIndex: number;
|
|
112
|
-
}
|
|
113
|
-
declare class Track {
|
|
114
|
-
readonly index: number;
|
|
115
|
-
readonly metadata: TrackMetadata;
|
|
116
|
-
private _trackUrl;
|
|
117
|
-
private _resolvedUrl;
|
|
118
|
-
private readonly skipHEAD;
|
|
119
|
-
/** Temporary holder between fetch and decode steps (unserializable — stays on Track class). */
|
|
120
|
-
private _pendingArrayBuffer;
|
|
121
|
-
readonly audio: HTMLAudioElement;
|
|
122
|
-
private readonly _playbackMethod;
|
|
123
|
-
private get ctx();
|
|
124
|
-
private gainNode;
|
|
125
|
-
private _mediaElementSource;
|
|
126
|
-
private _html5GainNode;
|
|
127
|
-
private sourceNode;
|
|
128
|
-
audioBuffer: AudioBuffer | null;
|
|
129
|
-
/** AudioContext.currentTime at the start of the current playback segment. */
|
|
130
|
-
/**
|
|
131
|
-
* Offset (in seconds) between the decoded buffer's "offset 0" and the music's
|
|
132
|
-
* "offset 0". Some MP3 files include ID3v2 metadata, encoder priming samples,
|
|
133
|
-
* or container padding at the start; HTML5 audio elements skip past these
|
|
134
|
-
* natively (audio.currentTime=0 means music start), but `decodeAudioData` in
|
|
135
|
-
* some browsers includes them in the decoded buffer (buffer offset 0 = file
|
|
136
|
-
* start, music actually starts at offset _bufferStartPaddingSec).
|
|
137
|
-
*
|
|
138
|
-
* Without this shift, calling source.start(when, audio.currentTime) plays
|
|
139
|
-
* `audio.currentTime` seconds AHEAD of what HTML5 was just outputting,
|
|
140
|
-
* sounding like a backward skip at crossover. We compute this once both
|
|
141
|
-
* the buffer and audio.duration are known, and apply it as
|
|
142
|
-
* source.start(when, trackTime + _bufferStartPaddingSec)
|
|
143
|
-
* everywhere we read from the buffer. User-facing time (currentTime/duration
|
|
144
|
-
* getters) continues to be reported in music-time, not buffer-time.
|
|
145
|
-
*/
|
|
146
|
-
private _bufferStartPaddingSec;
|
|
147
|
-
private _bufferAlignmentMeasured;
|
|
148
|
-
private _waRefCtxTime;
|
|
149
|
-
/** Track position (seconds) at the start of the current playback segment. */
|
|
150
|
-
private _waRefTrackTime;
|
|
151
|
-
/** Track-time (seconds) frozen at the moment of the most recent pause. */
|
|
152
|
-
private pausedAtTrackTime;
|
|
153
|
-
private readonly _actor;
|
|
154
|
-
private readonly queueRef;
|
|
155
|
-
private rafId;
|
|
156
|
-
private _notifiedPreloadThreshold;
|
|
157
|
-
constructor(opts: {
|
|
158
|
-
trackUrl: string;
|
|
159
|
-
index: number;
|
|
160
|
-
queue: TrackQueueRef;
|
|
161
|
-
skipHEAD?: boolean;
|
|
162
|
-
metadata?: TrackMetadata;
|
|
163
|
-
});
|
|
164
|
-
play(): void;
|
|
165
|
-
pause(): void;
|
|
166
|
-
seek(time: number): void;
|
|
167
|
-
setVolume(v: number): void;
|
|
168
|
-
setPlaybackRate(rate: number): void;
|
|
169
|
-
preload(): void;
|
|
170
|
-
seekToEnd(secondsFromEnd?: number): void;
|
|
171
|
-
activate(): void;
|
|
172
|
-
deactivate(): void;
|
|
173
|
-
destroy(): void;
|
|
174
|
-
/** Schedule the HTML5 gain node to mute at `when` — safety valve so an
|
|
175
|
-
* HTML5 element that runs past the predicted end is silenced instead of
|
|
176
|
-
* overlapping the next track. No-op if the HTML5 gain path isn't active. */
|
|
177
|
-
scheduleHtml5Mute(when: number): void;
|
|
178
|
-
/** Cancel any scheduled HTML5 gain mute (called when gapless is cancelled). */
|
|
179
|
-
cancelHtml5Mute(): void;
|
|
180
|
-
cancelGaplessStart(): void;
|
|
181
|
-
scheduleGaplessStart(when: number): boolean;
|
|
182
|
-
get currentTime(): number;
|
|
183
|
-
get duration(): number;
|
|
184
|
-
get isPaused(): boolean;
|
|
185
|
-
get isPlaying(): boolean;
|
|
186
|
-
get trackUrl(): string;
|
|
187
|
-
get playbackType(): PlaybackType;
|
|
188
|
-
get webAudioLoadingState(): WebAudioLoadingState;
|
|
189
|
-
get hasSourceNode(): boolean;
|
|
190
|
-
get machineState(): string;
|
|
191
|
-
get scheduledStartContextTime(): number | null;
|
|
192
|
-
/**
|
|
193
|
-
* Context-clock time at which the currently playing Web Audio source will
|
|
194
|
-
* reach the end of its buffer, derived from the live playback anchor
|
|
195
|
-
* (_waRefCtxTime/_waRefTrackTime). The anchor is re-established every time
|
|
196
|
-
* a source node starts (scheduled gapless start, resume, seek, crossover),
|
|
197
|
-
* so this stays correct across interruptions — unlike
|
|
198
|
-
* scheduledStartContextTime + duration, which goes stale the moment a
|
|
199
|
-
* pause/resume re-anchors the source at a later context time. For an
|
|
200
|
-
* uninterrupted gapless chain it is exact (the anchor IS the scheduled
|
|
201
|
-
* start), so next-track scheduling stays sample-accurate.
|
|
202
|
-
*
|
|
203
|
-
* Null when no source node is active (idle/html5/loading, or paused).
|
|
204
|
-
*/
|
|
205
|
-
get playbackEndContextTime(): number | null;
|
|
206
|
-
get isBufferLoaded(): boolean;
|
|
207
|
-
toInfo(): TrackInfo;
|
|
208
|
-
private _playHtml5;
|
|
209
|
-
private _seekHtml5;
|
|
210
|
-
/**
|
|
211
|
-
* Mid-stream crossover: switch an actively-playing HTML5 track to Web Audio.
|
|
212
|
-
*
|
|
213
|
-
* Why this exists: we cannot reliably predict when an HTML5 <audio> element
|
|
214
|
-
* will fire 'ended' from within the AudioContext clock. Any prediction is
|
|
215
|
-
* at the mercy of the browser's audio pipeline (buffering stalls, codec
|
|
216
|
-
* padding differences, clock drift between audio.currentTime and
|
|
217
|
-
* ctx.currentTime over long sessions). Scheduling the next gapless track
|
|
218
|
-
* against that prediction is how overlap bugs happen.
|
|
219
|
-
*
|
|
220
|
-
* Instead, as soon as the buffer is decoded, we hand playback off to Web
|
|
221
|
-
* Audio while the track is still mid-song. From that point on, the track
|
|
222
|
-
* and all subsequent gapless transitions live on a single clock
|
|
223
|
-
* (AudioContext.currentTime), so scheduling is sample-accurate by
|
|
224
|
-
* construction — no prediction involved.
|
|
225
|
-
*
|
|
226
|
-
* Ordering: pause the HTML5 element FIRST, then start the source node at
|
|
227
|
-
* the captured offset. Pausing first ensures audio.currentTime is frozen
|
|
228
|
-
* before we read it as the Web Audio start offset, so there's no brief
|
|
229
|
-
* double-audio window at the crossover point.
|
|
230
|
-
*/
|
|
231
|
-
private _crossoverHtml5ToWebAudio;
|
|
232
|
-
/**
|
|
233
|
-
* Reverted: alignment-based fixes (duration-delta and buffer-silence
|
|
234
|
-
* scanning) reduced the perceived skip on archive.org files but did not
|
|
235
|
-
* eliminate it, suggesting the residual gap isn't a buffer/timeline
|
|
236
|
-
* alignment problem at all. Leaving _bufferStartPaddingSec at 0 (no shift)
|
|
237
|
-
* until we have a confirmed root cause; the field and call sites are kept
|
|
238
|
-
* so we can re-introduce a fix without churning the source-start code.
|
|
239
|
-
*/
|
|
240
|
-
private _maybeComputeBufferAlignment;
|
|
241
|
-
private _startSourceNode;
|
|
242
|
-
private _stopSourceNode;
|
|
243
|
-
private _seekWebAudio;
|
|
244
|
-
private _handleWebAudioEnded;
|
|
245
|
-
startProgressLoop(): void;
|
|
246
|
-
private _stopProgressLoop;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
declare class Queue implements TrackQueueRef {
|
|
250
|
-
private _tracks;
|
|
251
|
-
private readonly _actor;
|
|
252
|
-
private readonly _onProgress?;
|
|
253
|
-
private readonly _onEnded?;
|
|
254
|
-
private readonly _onPlayNextTrack?;
|
|
255
|
-
private readonly _onPlayPreviousTrack?;
|
|
256
|
-
private readonly _onStartNewTrack?;
|
|
257
|
-
private readonly _onError?;
|
|
258
|
-
private readonly _onPlayBlocked?;
|
|
259
|
-
private readonly _onDebug?;
|
|
260
|
-
readonly playbackMethod: PlaybackMethod;
|
|
261
|
-
private _volume;
|
|
262
|
-
private _preloadNumTracks;
|
|
263
|
-
private _playbackRate;
|
|
264
|
-
/** Silent looping element that keeps the browser's MediaSession anchor alive. */
|
|
265
|
-
private _mediaSessionAnchor;
|
|
266
|
-
/** Index of the next track with a pre-scheduled gapless start, or null. */
|
|
267
|
-
private _scheduledNextIndex;
|
|
268
|
-
private _throttledUpdatePositionState;
|
|
269
|
-
constructor(options?: GaplessOptions);
|
|
270
|
-
play(): void;
|
|
271
|
-
pause(): void;
|
|
272
|
-
togglePlayPause(): void;
|
|
273
|
-
next(): void;
|
|
274
|
-
previous(): void;
|
|
275
|
-
gotoTrack(index: number, playImmediately?: boolean): void;
|
|
276
|
-
seek(time: number): void;
|
|
277
|
-
setVolume(volume: number): void;
|
|
278
|
-
setPlaybackRate(rate: number): void;
|
|
279
|
-
addTrack(url: string, options?: AddTrackOptions): void;
|
|
280
|
-
removeTrack(index: number): void;
|
|
281
|
-
resumeAudioContext(): Promise<void>;
|
|
282
|
-
destroy(): void;
|
|
283
|
-
get currentTrack(): TrackInfo | undefined;
|
|
284
|
-
get currentTrackIndex(): number;
|
|
285
|
-
get tracks(): readonly TrackInfo[];
|
|
286
|
-
get isPlaying(): boolean;
|
|
287
|
-
get isPaused(): boolean;
|
|
288
|
-
get volume(): number;
|
|
289
|
-
get preloadNumTracks(): number;
|
|
290
|
-
set preloadNumTracks(value: number);
|
|
291
|
-
get playbackRate(): number;
|
|
292
|
-
/** Snapshot of the queue state machine (state name + context). For debugging. */
|
|
293
|
-
get queueSnapshot(): {
|
|
294
|
-
state: string;
|
|
295
|
-
context: {
|
|
296
|
-
currentTrackIndex: number;
|
|
297
|
-
trackCount: number;
|
|
298
|
-
};
|
|
299
|
-
};
|
|
300
|
-
onTrackEnded(track: Track): void;
|
|
301
|
-
onTrackBufferReady(track: Track): void;
|
|
302
|
-
onProgress(info: TrackInfo): void;
|
|
303
|
-
onError(error: Error): void;
|
|
304
|
-
onPlayBlocked(): void;
|
|
305
|
-
onPreloadReady(track: Track): void;
|
|
306
|
-
onDebug(msg: string): void;
|
|
307
|
-
/** Look up a track by index — safe for use inside machine actions. */
|
|
308
|
-
private _trackAt;
|
|
309
|
-
private get _currentTrack();
|
|
310
|
-
private _preloadAhead;
|
|
311
|
-
private _cancelScheduledGapless;
|
|
312
|
-
private _tryScheduleGapless;
|
|
313
|
-
private _computeTrackEndTime;
|
|
314
|
-
private _startMediaSessionAnchor;
|
|
315
|
-
private _stopMediaSessionAnchor;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export { type AddTrackOptions, type GaplessOptions, type PlaybackType, Queue, type TrackInfo, type TrackMetadata, type WebAudioLoadingState, Queue as default };
|
|
1
|
+
export { Queue } from './Queue';
|
|
2
|
+
export type { GaplessOptions, AddTrackOptions, TrackInfo, TrackMetadata, PlaybackType, WebAudioLoadingState, } from './types';
|
|
3
|
+
export { Queue as default } from './Queue';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EACV,cAAc,EACd,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,oBAAoB,GACrB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC"}
|