@yetric/audioplayer 0.0.0-stage → 1.0.0-beta.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/LICENSE +21 -0
- package/README.md +148 -2
- package/dist/chunk-BN5MQE43.js +1253 -0
- package/dist/chunk-BN5MQE43.js.map +1 -0
- package/dist/index.cjs +1281 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +208 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +924 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +14 -0
- package/dist/react.d.ts +14 -0
- package/dist/react.js +54 -0
- package/dist/react.js.map +1 -0
- package/package.json +80 -4
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_PREVIOUS_RESTART_THRESHOLD_SECONDS = 5;
|
|
3
|
+
var DEFAULT_PERSISTENCE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
4
|
+
var DEFAULT_PERSISTENCE_SAVE_INTERVAL_MS = 5e3;
|
|
5
|
+
var DEFAULT_PROGRESS_REPORT_INTERVAL_MS = 5e3;
|
|
6
|
+
var DEFAULT_MEDIA_SESSION_ARTWORK_SIZES = [96, 128, 192, 256, 384, 512];
|
|
7
|
+
var DEFAULT_MEDIA_SESSION_SEEK_OFFSET_SECONDS = 10;
|
|
8
|
+
var createInitialState = (options) => ({
|
|
9
|
+
status: "idle",
|
|
10
|
+
currentSource: null,
|
|
11
|
+
currentSourceId: null,
|
|
12
|
+
currentTime: 0,
|
|
13
|
+
duration: 0,
|
|
14
|
+
buffered: 0,
|
|
15
|
+
progress: {
|
|
16
|
+
currentTime: 0,
|
|
17
|
+
duration: 0,
|
|
18
|
+
buffered: 0,
|
|
19
|
+
playedFraction: 0,
|
|
20
|
+
bufferedFraction: 0
|
|
21
|
+
},
|
|
22
|
+
rate: options.initialRate ?? 1,
|
|
23
|
+
volume: options.initialVolume ?? 1,
|
|
24
|
+
muted: options.initialMuted ?? false,
|
|
25
|
+
queue: {
|
|
26
|
+
items: [],
|
|
27
|
+
itemIds: [],
|
|
28
|
+
position: {
|
|
29
|
+
currentIndex: -1,
|
|
30
|
+
currentSourceId: null,
|
|
31
|
+
length: 0,
|
|
32
|
+
repeatMode: options.initialRepeatMode ?? "off",
|
|
33
|
+
hasNext: false,
|
|
34
|
+
hasPrevious: false
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
error: null
|
|
38
|
+
});
|
|
39
|
+
var createPlayerError = (code, message, cause) => ({
|
|
40
|
+
code,
|
|
41
|
+
message,
|
|
42
|
+
cause
|
|
43
|
+
});
|
|
44
|
+
var isRuntimePlayerError = (value) => !!value && typeof value === "object" && "code" in value && "message" in value;
|
|
45
|
+
var normalizePlayerError = (fallbackCode, fallbackMessage, cause) => isRuntimePlayerError(cause) ? cause : createPlayerError(fallbackCode, fallbackMessage, cause);
|
|
46
|
+
var assertStableSource = (source) => {
|
|
47
|
+
if (!source.id || !source.src) {
|
|
48
|
+
throw createPlayerError(
|
|
49
|
+
"INVALID_QUEUE_ITEM",
|
|
50
|
+
"AudioSource requires stable id and src."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var isFiniteNumber = (value) => Number.isFinite(value) && !Number.isNaN(value);
|
|
55
|
+
var safeDuration = (audio, hint) => {
|
|
56
|
+
const duration = audio?.duration;
|
|
57
|
+
return duration !== void 0 && isFiniteNumber(duration) ? duration : hint ?? 0;
|
|
58
|
+
};
|
|
59
|
+
var safeBuffered = (audio) => {
|
|
60
|
+
if (!audio || audio.buffered.length === 0) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return audio.buffered.end(audio.buffered.length - 1);
|
|
65
|
+
} catch {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var cloneQueueState = (state) => ({
|
|
70
|
+
items: [...state.queue.items],
|
|
71
|
+
itemIds: [...state.queue.itemIds],
|
|
72
|
+
position: {
|
|
73
|
+
...state.queue.position
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
var updateQueueFlags = (state) => {
|
|
77
|
+
const { items, position } = state.queue;
|
|
78
|
+
const { currentIndex, repeatMode } = position;
|
|
79
|
+
const lastIndex = items.length - 1;
|
|
80
|
+
const hasItems = items.length > 0;
|
|
81
|
+
const hasNext = !hasItems || currentIndex < 0 ? false : repeatMode === "all" ? items.length > 0 : currentIndex < lastIndex;
|
|
82
|
+
const hasPrevious = !hasItems || currentIndex < 0 ? false : repeatMode === "all" ? items.length > 0 : currentIndex > 0;
|
|
83
|
+
return {
|
|
84
|
+
...state,
|
|
85
|
+
queue: {
|
|
86
|
+
...state.queue,
|
|
87
|
+
itemIds: items.map((item) => item.id),
|
|
88
|
+
position: {
|
|
89
|
+
...position,
|
|
90
|
+
currentSourceId: state.currentSource?.id ?? null,
|
|
91
|
+
length: items.length,
|
|
92
|
+
hasNext,
|
|
93
|
+
hasPrevious
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
var createAudioElement = () => {
|
|
99
|
+
if (typeof Audio === "undefined") {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return new Audio();
|
|
103
|
+
};
|
|
104
|
+
var createUnavailableAudioEngine = () => {
|
|
105
|
+
const unavailableError = () => createPlayerError(
|
|
106
|
+
"UNSUPPORTED_ENVIRONMENT",
|
|
107
|
+
"HTMLAudioElement is not available in this environment."
|
|
108
|
+
);
|
|
109
|
+
return {
|
|
110
|
+
available: false,
|
|
111
|
+
getSnapshot(durationHint) {
|
|
112
|
+
return {
|
|
113
|
+
currentTime: 0,
|
|
114
|
+
duration: durationHint ?? 0,
|
|
115
|
+
buffered: 0,
|
|
116
|
+
rate: 1,
|
|
117
|
+
volume: 1,
|
|
118
|
+
muted: false
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
getRuntimeError() {
|
|
122
|
+
return unavailableError();
|
|
123
|
+
},
|
|
124
|
+
on() {
|
|
125
|
+
return () => {
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
setSource() {
|
|
129
|
+
throw unavailableError();
|
|
130
|
+
},
|
|
131
|
+
clearSource() {
|
|
132
|
+
throw unavailableError();
|
|
133
|
+
},
|
|
134
|
+
load() {
|
|
135
|
+
throw unavailableError();
|
|
136
|
+
},
|
|
137
|
+
play() {
|
|
138
|
+
throw unavailableError();
|
|
139
|
+
},
|
|
140
|
+
pause() {
|
|
141
|
+
throw unavailableError();
|
|
142
|
+
},
|
|
143
|
+
setCurrentTime() {
|
|
144
|
+
throw unavailableError();
|
|
145
|
+
},
|
|
146
|
+
setPlaybackRate() {
|
|
147
|
+
throw unavailableError();
|
|
148
|
+
},
|
|
149
|
+
setVolume() {
|
|
150
|
+
throw unavailableError();
|
|
151
|
+
},
|
|
152
|
+
setMuted() {
|
|
153
|
+
throw unavailableError();
|
|
154
|
+
},
|
|
155
|
+
destroy() {
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
var createHtmlAudioEngine = () => {
|
|
160
|
+
const audio = createAudioElement();
|
|
161
|
+
if (!audio) {
|
|
162
|
+
return createUnavailableAudioEngine();
|
|
163
|
+
}
|
|
164
|
+
audio.preload = "metadata";
|
|
165
|
+
const clearSource = () => {
|
|
166
|
+
const element = audio;
|
|
167
|
+
if (typeof element.removeAttribute === "function") {
|
|
168
|
+
element.removeAttribute("src");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
audio.src = "";
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
available: true,
|
|
175
|
+
getSnapshot(durationHint) {
|
|
176
|
+
return {
|
|
177
|
+
currentTime: audio.currentTime,
|
|
178
|
+
duration: safeDuration(audio, durationHint),
|
|
179
|
+
buffered: safeBuffered(audio),
|
|
180
|
+
rate: audio.playbackRate,
|
|
181
|
+
volume: audio.volume,
|
|
182
|
+
muted: audio.muted
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
getRuntimeError() {
|
|
186
|
+
return audio.error;
|
|
187
|
+
},
|
|
188
|
+
on(eventName, listener) {
|
|
189
|
+
audio.addEventListener(eventName, listener);
|
|
190
|
+
return () => {
|
|
191
|
+
audio.removeEventListener(eventName, listener);
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
setSource(src) {
|
|
195
|
+
audio.src = src;
|
|
196
|
+
},
|
|
197
|
+
clearSource() {
|
|
198
|
+
clearSource();
|
|
199
|
+
},
|
|
200
|
+
load() {
|
|
201
|
+
audio.load();
|
|
202
|
+
},
|
|
203
|
+
play() {
|
|
204
|
+
return audio.play();
|
|
205
|
+
},
|
|
206
|
+
pause() {
|
|
207
|
+
audio.pause();
|
|
208
|
+
},
|
|
209
|
+
setCurrentTime(time) {
|
|
210
|
+
audio.currentTime = time;
|
|
211
|
+
},
|
|
212
|
+
setPlaybackRate(rate) {
|
|
213
|
+
audio.playbackRate = rate;
|
|
214
|
+
},
|
|
215
|
+
setVolume(volume) {
|
|
216
|
+
audio.volume = volume;
|
|
217
|
+
},
|
|
218
|
+
setMuted(muted) {
|
|
219
|
+
audio.muted = muted;
|
|
220
|
+
},
|
|
221
|
+
destroy() {
|
|
222
|
+
audio.pause();
|
|
223
|
+
clearSource();
|
|
224
|
+
audio.load();
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
var isBrowserStorageAvailable = () => typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
229
|
+
var isPersistedPlayerState = (value) => {
|
|
230
|
+
if (!value || typeof value !== "object") {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const candidate = value;
|
|
234
|
+
return candidate.version === 1 && typeof candidate.savedAt === "number" && "currentSource" in candidate && typeof candidate.currentTime === "number" && typeof candidate.rate === "number" && typeof candidate.volume === "number" && typeof candidate.muted === "boolean" && !!candidate.queue && Array.isArray(candidate.queue.items) && !!candidate.queue.position;
|
|
235
|
+
};
|
|
236
|
+
var toPersistedPlayerState = (state) => ({
|
|
237
|
+
version: 1,
|
|
238
|
+
savedAt: Date.now(),
|
|
239
|
+
currentSource: state.currentSource,
|
|
240
|
+
currentSourceId: state.currentSourceId,
|
|
241
|
+
currentTime: state.currentTime,
|
|
242
|
+
rate: state.rate,
|
|
243
|
+
volume: state.volume,
|
|
244
|
+
muted: state.muted,
|
|
245
|
+
queue: {
|
|
246
|
+
items: [...state.queue.items],
|
|
247
|
+
position: {
|
|
248
|
+
currentIndex: state.queue.position.currentIndex,
|
|
249
|
+
currentSourceId: state.queue.position.currentSourceId,
|
|
250
|
+
repeatMode: state.queue.position.repeatMode
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
var toProgressSnapshot = (state) => ({
|
|
255
|
+
source: state.currentSource,
|
|
256
|
+
sourceId: state.currentSourceId,
|
|
257
|
+
currentTime: state.currentTime,
|
|
258
|
+
duration: state.duration,
|
|
259
|
+
playedFraction: state.progress.playedFraction,
|
|
260
|
+
status: state.status,
|
|
261
|
+
queue: {
|
|
262
|
+
...state.queue.position
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
var hasMediaSession = () => typeof navigator !== "undefined" && "mediaSession" in navigator;
|
|
266
|
+
var createMediaSessionArtwork = (source, sizes) => {
|
|
267
|
+
if (!source?.artwork) {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
return sizes.map((size) => ({
|
|
271
|
+
src: source.artwork,
|
|
272
|
+
sizes: `${size}x${size}`
|
|
273
|
+
}));
|
|
274
|
+
};
|
|
275
|
+
var createLocalStoragePersistenceAdapter = (key) => ({
|
|
276
|
+
load() {
|
|
277
|
+
if (!isBrowserStorageAvailable()) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const raw = window.localStorage.getItem(key);
|
|
282
|
+
if (!raw) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const parsed = JSON.parse(raw);
|
|
286
|
+
return isPersistedPlayerState(parsed) ? parsed : null;
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
save(state) {
|
|
292
|
+
if (!isBrowserStorageAvailable()) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
window.localStorage.setItem(key, JSON.stringify(state));
|
|
296
|
+
},
|
|
297
|
+
clear() {
|
|
298
|
+
if (!isBrowserStorageAvailable()) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
window.localStorage.removeItem(key);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
var createAudioPlayer = (options = {}) => {
|
|
305
|
+
let state = updateQueueFlags(createInitialState(options));
|
|
306
|
+
let destroyed = false;
|
|
307
|
+
let shouldAutoplayAfterLoad = false;
|
|
308
|
+
let suppressPauseEvent = false;
|
|
309
|
+
const engine = createHtmlAudioEngine();
|
|
310
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
311
|
+
const eventListeners = {
|
|
312
|
+
sourcechange: /* @__PURE__ */ new Set(),
|
|
313
|
+
queuechange: /* @__PURE__ */ new Set(),
|
|
314
|
+
play: /* @__PURE__ */ new Set(),
|
|
315
|
+
pause: /* @__PURE__ */ new Set(),
|
|
316
|
+
seeking: /* @__PURE__ */ new Set(),
|
|
317
|
+
seeked: /* @__PURE__ */ new Set(),
|
|
318
|
+
timeupdate: /* @__PURE__ */ new Set(),
|
|
319
|
+
ratechange: /* @__PURE__ */ new Set(),
|
|
320
|
+
volumechange: /* @__PURE__ */ new Set(),
|
|
321
|
+
ended: /* @__PURE__ */ new Set(),
|
|
322
|
+
error: /* @__PURE__ */ new Set(),
|
|
323
|
+
destroy: /* @__PURE__ */ new Set()
|
|
324
|
+
};
|
|
325
|
+
const getState = () => state;
|
|
326
|
+
const publishState = () => {
|
|
327
|
+
for (const subscriber of subscribers) {
|
|
328
|
+
subscriber(state);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const emit = (eventName, event) => {
|
|
332
|
+
for (const listener of eventListeners[eventName]) {
|
|
333
|
+
listener(event);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const setState = (updater) => {
|
|
337
|
+
state = updateQueueFlags(updater(state));
|
|
338
|
+
publishState();
|
|
339
|
+
return state;
|
|
340
|
+
};
|
|
341
|
+
const setError = (error) => {
|
|
342
|
+
shouldAutoplayAfterLoad = false;
|
|
343
|
+
const nextState = setState((currentState) => ({
|
|
344
|
+
...currentState,
|
|
345
|
+
status: "error",
|
|
346
|
+
error: {
|
|
347
|
+
code: error.code,
|
|
348
|
+
message: error.message
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
351
|
+
emit("error", { error, state: nextState });
|
|
352
|
+
};
|
|
353
|
+
const clearError = () => {
|
|
354
|
+
if (!state.error) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
setState((currentState) => ({
|
|
358
|
+
...currentState,
|
|
359
|
+
error: null
|
|
360
|
+
}));
|
|
361
|
+
};
|
|
362
|
+
const syncAudioSnapshot = (statusOverride) => {
|
|
363
|
+
const snapshot = engine.getSnapshot(state.currentSource?.durationHint);
|
|
364
|
+
const currentTime = snapshot.currentTime;
|
|
365
|
+
const duration = snapshot.duration;
|
|
366
|
+
const buffered = snapshot.buffered;
|
|
367
|
+
const playedFraction = duration > 0 ? Math.min(1, Math.max(0, currentTime / duration)) : 0;
|
|
368
|
+
const bufferedFraction = duration > 0 ? Math.min(1, Math.max(0, buffered / duration)) : 0;
|
|
369
|
+
const nextState = setState((currentState) => ({
|
|
370
|
+
...currentState,
|
|
371
|
+
currentSourceId: currentState.currentSource?.id ?? null,
|
|
372
|
+
currentTime,
|
|
373
|
+
duration,
|
|
374
|
+
buffered,
|
|
375
|
+
progress: {
|
|
376
|
+
currentTime,
|
|
377
|
+
duration,
|
|
378
|
+
buffered,
|
|
379
|
+
playedFraction,
|
|
380
|
+
bufferedFraction
|
|
381
|
+
},
|
|
382
|
+
rate: snapshot.rate,
|
|
383
|
+
volume: snapshot.volume,
|
|
384
|
+
muted: snapshot.muted,
|
|
385
|
+
status: statusOverride ?? currentState.status,
|
|
386
|
+
error: statusOverride === "error" ? currentState.error : null
|
|
387
|
+
}));
|
|
388
|
+
return nextState;
|
|
389
|
+
};
|
|
390
|
+
const findQueueIndexById = (id) => id ? state.queue.items.findIndex((item) => item.id === id) : -1;
|
|
391
|
+
const resolveNextQueueIndex = (mode) => {
|
|
392
|
+
const { items, position } = state.queue;
|
|
393
|
+
const { currentIndex, repeatMode } = position;
|
|
394
|
+
if (items.length === 0 || currentIndex < 0) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
if (repeatMode === "one") {
|
|
398
|
+
return currentIndex;
|
|
399
|
+
}
|
|
400
|
+
if (currentIndex < items.length - 1) {
|
|
401
|
+
return currentIndex + 1;
|
|
402
|
+
}
|
|
403
|
+
if (repeatMode === "all") {
|
|
404
|
+
return 0;
|
|
405
|
+
}
|
|
406
|
+
return mode === "ended" ? -1 : null;
|
|
407
|
+
};
|
|
408
|
+
const resolvePreviousQueueIndex = () => {
|
|
409
|
+
const { items, position } = state.queue;
|
|
410
|
+
const { currentIndex, repeatMode } = position;
|
|
411
|
+
if (items.length === 0 || currentIndex < 0) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
if (currentIndex > 0) {
|
|
415
|
+
return currentIndex - 1;
|
|
416
|
+
}
|
|
417
|
+
if (repeatMode === "all") {
|
|
418
|
+
return items.length - 1;
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
};
|
|
422
|
+
const updateCurrentSource = (source, nextStatus) => {
|
|
423
|
+
const queueIndex = findQueueIndexById(source?.id);
|
|
424
|
+
const nextState = setState((currentState) => ({
|
|
425
|
+
...currentState,
|
|
426
|
+
currentSource: source,
|
|
427
|
+
currentSourceId: source?.id ?? null,
|
|
428
|
+
currentTime: 0,
|
|
429
|
+
duration: source?.durationHint ?? 0,
|
|
430
|
+
buffered: 0,
|
|
431
|
+
progress: {
|
|
432
|
+
currentTime: 0,
|
|
433
|
+
duration: source?.durationHint ?? 0,
|
|
434
|
+
buffered: 0,
|
|
435
|
+
playedFraction: 0,
|
|
436
|
+
bufferedFraction: 0
|
|
437
|
+
},
|
|
438
|
+
status: nextStatus,
|
|
439
|
+
error: null,
|
|
440
|
+
queue: {
|
|
441
|
+
...currentState.queue,
|
|
442
|
+
position: {
|
|
443
|
+
...currentState.queue.position,
|
|
444
|
+
currentIndex: queueIndex,
|
|
445
|
+
currentSourceId: source?.id ?? null
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}));
|
|
449
|
+
emit("sourcechange", { source, state: nextState });
|
|
450
|
+
return nextState;
|
|
451
|
+
};
|
|
452
|
+
const setQueueState = (items, currentIndex) => setState((currentState) => ({
|
|
453
|
+
...currentState,
|
|
454
|
+
queue: {
|
|
455
|
+
...currentState.queue,
|
|
456
|
+
items,
|
|
457
|
+
position: {
|
|
458
|
+
...currentState.queue.position,
|
|
459
|
+
currentIndex
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}));
|
|
463
|
+
const emitQueueChange = (nextState) => {
|
|
464
|
+
emit("queuechange", { queue: cloneQueueState(nextState), state: nextState });
|
|
465
|
+
};
|
|
466
|
+
const attachSourceToAudio = (source, autoplayAfterLoad) => {
|
|
467
|
+
shouldAutoplayAfterLoad = autoplayAfterLoad;
|
|
468
|
+
suppressPauseEvent = true;
|
|
469
|
+
try {
|
|
470
|
+
engine.pause();
|
|
471
|
+
} finally {
|
|
472
|
+
suppressPauseEvent = false;
|
|
473
|
+
}
|
|
474
|
+
engine.setSource(source.src);
|
|
475
|
+
engine.setCurrentTime(0);
|
|
476
|
+
engine.setPlaybackRate(state.rate);
|
|
477
|
+
engine.setVolume(state.volume);
|
|
478
|
+
engine.setMuted(state.muted);
|
|
479
|
+
engine.load();
|
|
480
|
+
};
|
|
481
|
+
const loadInternal = async (source, autoplayAfterLoad = false) => {
|
|
482
|
+
assertStableSource(source);
|
|
483
|
+
updateCurrentSource(source, "loading");
|
|
484
|
+
attachSourceToAudio(source, autoplayAfterLoad);
|
|
485
|
+
};
|
|
486
|
+
const selectQueueIndex = async (index, autoplayAfterLoad = false) => {
|
|
487
|
+
const item = state.queue.items[index] ?? null;
|
|
488
|
+
if (!item) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
await loadInternal(item, autoplayAfterLoad);
|
|
492
|
+
const nextState = setQueueState(state.queue.items, index);
|
|
493
|
+
emitQueueChange(nextState);
|
|
494
|
+
return item;
|
|
495
|
+
};
|
|
496
|
+
const playLoadedAudio = async () => {
|
|
497
|
+
try {
|
|
498
|
+
await engine.play();
|
|
499
|
+
} catch (cause) {
|
|
500
|
+
setError(createPlayerError("PLAY_ERROR", "Failed to start playback.", cause));
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
const handlePlaybackEnded = async () => {
|
|
504
|
+
if (destroyed) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (state.queue.items.length > 0 && state.queue.position.currentIndex >= 0) {
|
|
508
|
+
const nextIndex = resolveNextQueueIndex("ended");
|
|
509
|
+
if (nextIndex === -1 || nextIndex === null) {
|
|
510
|
+
const endedState2 = syncAudioSnapshot("ended");
|
|
511
|
+
emit("ended", { source: endedState2.currentSource, state: endedState2 });
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
await selectQueueIndex(nextIndex, true);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const endedState = syncAudioSnapshot("ended");
|
|
518
|
+
emit("ended", { source: endedState.currentSource, state: endedState });
|
|
519
|
+
};
|
|
520
|
+
const detachEngineListeners = [
|
|
521
|
+
engine.on("loadedmetadata", () => {
|
|
522
|
+
if (destroyed || !state.currentSource) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
syncAudioSnapshot("ready");
|
|
526
|
+
if (shouldAutoplayAfterLoad) {
|
|
527
|
+
shouldAutoplayAfterLoad = false;
|
|
528
|
+
void playLoadedAudio();
|
|
529
|
+
}
|
|
530
|
+
}),
|
|
531
|
+
engine.on("timeupdate", () => {
|
|
532
|
+
if (destroyed) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const nextState = syncAudioSnapshot(
|
|
536
|
+
state.status === "ended" ? "playing" : void 0
|
|
537
|
+
);
|
|
538
|
+
emit("timeupdate", {
|
|
539
|
+
currentTime: nextState.currentTime,
|
|
540
|
+
duration: nextState.duration,
|
|
541
|
+
state: nextState
|
|
542
|
+
});
|
|
543
|
+
}),
|
|
544
|
+
engine.on("progress", () => {
|
|
545
|
+
if (!destroyed) {
|
|
546
|
+
syncAudioSnapshot();
|
|
547
|
+
}
|
|
548
|
+
}),
|
|
549
|
+
engine.on("play", () => {
|
|
550
|
+
if (destroyed) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const nextState = syncAudioSnapshot("playing");
|
|
554
|
+
emit("play", { source: nextState.currentSource, state: nextState });
|
|
555
|
+
}),
|
|
556
|
+
engine.on("pause", () => {
|
|
557
|
+
if (destroyed || suppressPauseEvent) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const nextStatus = state.status === "ended" ? "ended" : state.currentSource ? "paused" : "idle";
|
|
561
|
+
const nextState = syncAudioSnapshot(nextStatus);
|
|
562
|
+
emit("pause", { source: nextState.currentSource, state: nextState });
|
|
563
|
+
}),
|
|
564
|
+
engine.on("seeked", () => {
|
|
565
|
+
if (destroyed) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const nextState = syncAudioSnapshot();
|
|
569
|
+
emit("seeked", { currentTime: nextState.currentTime, state: nextState });
|
|
570
|
+
emit("timeupdate", {
|
|
571
|
+
currentTime: nextState.currentTime,
|
|
572
|
+
duration: nextState.duration,
|
|
573
|
+
state: nextState
|
|
574
|
+
});
|
|
575
|
+
}),
|
|
576
|
+
engine.on("ratechange", () => {
|
|
577
|
+
if (destroyed) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const nextState = syncAudioSnapshot();
|
|
581
|
+
emit("ratechange", { rate: nextState.rate, state: nextState });
|
|
582
|
+
}),
|
|
583
|
+
engine.on("volumechange", () => {
|
|
584
|
+
if (destroyed) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const nextState = syncAudioSnapshot();
|
|
588
|
+
emit("volumechange", {
|
|
589
|
+
volume: nextState.volume,
|
|
590
|
+
muted: nextState.muted,
|
|
591
|
+
state: nextState
|
|
592
|
+
});
|
|
593
|
+
}),
|
|
594
|
+
engine.on("ended", () => {
|
|
595
|
+
void handlePlaybackEnded();
|
|
596
|
+
}),
|
|
597
|
+
engine.on("error", () => {
|
|
598
|
+
if (destroyed) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
setError(
|
|
602
|
+
createPlayerError(
|
|
603
|
+
"LOAD_ERROR",
|
|
604
|
+
"Audio element reported a playback error.",
|
|
605
|
+
engine.getRuntimeError()
|
|
606
|
+
)
|
|
607
|
+
);
|
|
608
|
+
})
|
|
609
|
+
];
|
|
610
|
+
return {
|
|
611
|
+
getState,
|
|
612
|
+
subscribe(listener) {
|
|
613
|
+
subscribers.add(listener);
|
|
614
|
+
listener(state);
|
|
615
|
+
return () => {
|
|
616
|
+
subscribers.delete(listener);
|
|
617
|
+
};
|
|
618
|
+
},
|
|
619
|
+
on(eventName, listener) {
|
|
620
|
+
eventListeners[eventName].add(
|
|
621
|
+
listener
|
|
622
|
+
);
|
|
623
|
+
return () => {
|
|
624
|
+
eventListeners[eventName].delete(
|
|
625
|
+
listener
|
|
626
|
+
);
|
|
627
|
+
};
|
|
628
|
+
},
|
|
629
|
+
async load(source) {
|
|
630
|
+
try {
|
|
631
|
+
await loadInternal(source);
|
|
632
|
+
} catch (cause) {
|
|
633
|
+
setError(
|
|
634
|
+
normalizePlayerError("LOAD_ERROR", "Failed to load source.", cause)
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
async play(source) {
|
|
639
|
+
try {
|
|
640
|
+
if (source) {
|
|
641
|
+
await loadInternal(source, true);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (!state.currentSource) {
|
|
645
|
+
setError(
|
|
646
|
+
createPlayerError(
|
|
647
|
+
"NO_ACTIVE_SOURCE",
|
|
648
|
+
"Cannot play without an active source."
|
|
649
|
+
)
|
|
650
|
+
);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (state.status === "loading") {
|
|
654
|
+
shouldAutoplayAfterLoad = true;
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
await playLoadedAudio();
|
|
658
|
+
} catch (cause) {
|
|
659
|
+
setError(normalizePlayerError("PLAY_ERROR", "Failed to start playback.", cause));
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
pause() {
|
|
663
|
+
if (!engine.available) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
shouldAutoplayAfterLoad = false;
|
|
667
|
+
engine.pause();
|
|
668
|
+
},
|
|
669
|
+
async toggle() {
|
|
670
|
+
if (state.status === "playing") {
|
|
671
|
+
this.pause();
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
await this.play();
|
|
675
|
+
},
|
|
676
|
+
unload() {
|
|
677
|
+
shouldAutoplayAfterLoad = false;
|
|
678
|
+
if (engine.available) {
|
|
679
|
+
suppressPauseEvent = true;
|
|
680
|
+
try {
|
|
681
|
+
engine.pause();
|
|
682
|
+
} finally {
|
|
683
|
+
suppressPauseEvent = false;
|
|
684
|
+
}
|
|
685
|
+
engine.clearSource();
|
|
686
|
+
engine.load();
|
|
687
|
+
}
|
|
688
|
+
const nextState = updateCurrentSource(null, "idle");
|
|
689
|
+
emitQueueChange(nextState);
|
|
690
|
+
},
|
|
691
|
+
seek(time) {
|
|
692
|
+
if (!state.currentSource) {
|
|
693
|
+
setError(
|
|
694
|
+
createPlayerError("SEEK_ERROR", "Cannot seek without an active source.")
|
|
695
|
+
);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const max = engine.getSnapshot(state.currentSource.durationHint).duration;
|
|
699
|
+
const normalized = Math.max(
|
|
700
|
+
0,
|
|
701
|
+
max > 0 ? Math.min(time, max) : isFiniteNumber(time) ? time : 0
|
|
702
|
+
);
|
|
703
|
+
emit("seeking", { from: state.currentTime, to: normalized, state });
|
|
704
|
+
try {
|
|
705
|
+
engine.setCurrentTime(normalized);
|
|
706
|
+
} catch (cause) {
|
|
707
|
+
setError(normalizePlayerError("SEEK_ERROR", "Failed to seek audio.", cause));
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
setRate(rate) {
|
|
711
|
+
if (!engine.available) {
|
|
712
|
+
setError(
|
|
713
|
+
createPlayerError("INVALID_RATE", "Audio element is unavailable.")
|
|
714
|
+
);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (!isFiniteNumber(rate) || rate <= 0) {
|
|
718
|
+
setError(
|
|
719
|
+
createPlayerError("INVALID_RATE", "Playback rate must be greater than 0.")
|
|
720
|
+
);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
engine.setPlaybackRate(rate);
|
|
724
|
+
clearError();
|
|
725
|
+
syncAudioSnapshot();
|
|
726
|
+
},
|
|
727
|
+
setVolume(volume) {
|
|
728
|
+
if (!engine.available) {
|
|
729
|
+
setError(
|
|
730
|
+
createPlayerError("INVALID_VOLUME", "Audio element is unavailable.")
|
|
731
|
+
);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (!isFiniteNumber(volume) || volume < 0 || volume > 1) {
|
|
735
|
+
setError(
|
|
736
|
+
createPlayerError("INVALID_VOLUME", "Volume must be between 0 and 1.")
|
|
737
|
+
);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
engine.setVolume(volume);
|
|
741
|
+
clearError();
|
|
742
|
+
syncAudioSnapshot();
|
|
743
|
+
},
|
|
744
|
+
setMuted(muted) {
|
|
745
|
+
if (!engine.available) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
engine.setMuted(muted);
|
|
749
|
+
clearError();
|
|
750
|
+
syncAudioSnapshot();
|
|
751
|
+
},
|
|
752
|
+
async setQueue(items, queueOptions = {}) {
|
|
753
|
+
try {
|
|
754
|
+
for (const item of items) {
|
|
755
|
+
assertStableSource(item);
|
|
756
|
+
}
|
|
757
|
+
const startIndex = queueOptions.startAtId === void 0 ? items.length > 0 ? 0 : -1 : items.findIndex((item) => item.id === queueOptions.startAtId);
|
|
758
|
+
if (queueOptions.startAtId && startIndex < 0) {
|
|
759
|
+
throw createPlayerError(
|
|
760
|
+
"INVALID_QUEUE_ITEM",
|
|
761
|
+
`Queue start item ${queueOptions.startAtId} was not found.`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const nextState = setState((currentState) => ({
|
|
765
|
+
...currentState,
|
|
766
|
+
currentSource: startIndex >= 0 ? items[startIndex] ?? null : null,
|
|
767
|
+
currentSourceId: startIndex >= 0 ? items[startIndex]?.id ?? null : null,
|
|
768
|
+
currentTime: 0,
|
|
769
|
+
duration: startIndex >= 0 ? items[startIndex]?.durationHint ?? 0 : 0,
|
|
770
|
+
buffered: 0,
|
|
771
|
+
progress: {
|
|
772
|
+
currentTime: 0,
|
|
773
|
+
duration: startIndex >= 0 ? items[startIndex]?.durationHint ?? 0 : 0,
|
|
774
|
+
buffered: 0,
|
|
775
|
+
playedFraction: 0,
|
|
776
|
+
bufferedFraction: 0
|
|
777
|
+
},
|
|
778
|
+
status: startIndex >= 0 ? "ready" : "idle",
|
|
779
|
+
error: null,
|
|
780
|
+
queue: {
|
|
781
|
+
...currentState.queue,
|
|
782
|
+
items: [...items],
|
|
783
|
+
position: {
|
|
784
|
+
...currentState.queue.position,
|
|
785
|
+
currentIndex: startIndex,
|
|
786
|
+
currentSourceId: startIndex >= 0 ? items[startIndex]?.id ?? null : null
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}));
|
|
790
|
+
emitQueueChange(nextState);
|
|
791
|
+
emit("sourcechange", {
|
|
792
|
+
source: nextState.currentSource,
|
|
793
|
+
state: nextState
|
|
794
|
+
});
|
|
795
|
+
if (nextState.currentSource) {
|
|
796
|
+
await loadInternal(nextState.currentSource, queueOptions.autoplay === true);
|
|
797
|
+
const syncedState = setQueueState(items, startIndex);
|
|
798
|
+
emitQueueChange(syncedState);
|
|
799
|
+
} else if (engine.available) {
|
|
800
|
+
shouldAutoplayAfterLoad = false;
|
|
801
|
+
suppressPauseEvent = true;
|
|
802
|
+
engine.pause();
|
|
803
|
+
suppressPauseEvent = false;
|
|
804
|
+
engine.clearSource();
|
|
805
|
+
engine.load();
|
|
806
|
+
}
|
|
807
|
+
} catch (cause) {
|
|
808
|
+
setError(
|
|
809
|
+
normalizePlayerError(
|
|
810
|
+
"INVALID_QUEUE_ITEM",
|
|
811
|
+
"Failed to set queue.",
|
|
812
|
+
cause
|
|
813
|
+
)
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
appendToQueue(items) {
|
|
818
|
+
try {
|
|
819
|
+
for (const item of items) {
|
|
820
|
+
assertStableSource(item);
|
|
821
|
+
}
|
|
822
|
+
const nextState = setState((currentState) => ({
|
|
823
|
+
...currentState,
|
|
824
|
+
queue: {
|
|
825
|
+
...currentState.queue,
|
|
826
|
+
items: [...currentState.queue.items, ...items]
|
|
827
|
+
}
|
|
828
|
+
}));
|
|
829
|
+
emitQueueChange(nextState);
|
|
830
|
+
} catch (cause) {
|
|
831
|
+
setError(
|
|
832
|
+
normalizePlayerError(
|
|
833
|
+
"INVALID_QUEUE_ITEM",
|
|
834
|
+
"Failed to append queue items.",
|
|
835
|
+
cause
|
|
836
|
+
)
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
removeFromQueue(id) {
|
|
841
|
+
const nextItems = state.queue.items.filter((item) => item.id !== id);
|
|
842
|
+
const nextIndex = nextItems.findIndex(
|
|
843
|
+
(item) => item.id === state.currentSource?.id
|
|
844
|
+
);
|
|
845
|
+
const nextState = setState((currentState) => ({
|
|
846
|
+
...currentState,
|
|
847
|
+
queue: {
|
|
848
|
+
...currentState.queue,
|
|
849
|
+
items: nextItems,
|
|
850
|
+
position: {
|
|
851
|
+
...currentState.queue.position,
|
|
852
|
+
currentIndex: nextIndex,
|
|
853
|
+
currentSourceId: nextIndex >= 0 ? nextItems[nextIndex]?.id ?? null : null
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}));
|
|
857
|
+
emitQueueChange(nextState);
|
|
858
|
+
},
|
|
859
|
+
clearQueue() {
|
|
860
|
+
const nextState = setState((currentState) => ({
|
|
861
|
+
...currentState,
|
|
862
|
+
queue: {
|
|
863
|
+
...currentState.queue,
|
|
864
|
+
items: [],
|
|
865
|
+
position: {
|
|
866
|
+
...currentState.queue.position,
|
|
867
|
+
currentIndex: -1,
|
|
868
|
+
currentSourceId: null
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}));
|
|
872
|
+
emitQueueChange(nextState);
|
|
873
|
+
},
|
|
874
|
+
async next() {
|
|
875
|
+
const nextIndex = resolveNextQueueIndex("manual");
|
|
876
|
+
if (nextIndex === null) {
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
await selectQueueIndex(nextIndex, true);
|
|
880
|
+
},
|
|
881
|
+
async previous(previousOptions = {}) {
|
|
882
|
+
if (!state.currentSource) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const threshold = previousOptions.restartThresholdSeconds ?? DEFAULT_PREVIOUS_RESTART_THRESHOLD_SECONDS;
|
|
886
|
+
if (state.currentTime > threshold) {
|
|
887
|
+
this.seek(0);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const previousIndex = resolvePreviousQueueIndex();
|
|
891
|
+
if (previousIndex === null) {
|
|
892
|
+
this.seek(0);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
await selectQueueIndex(previousIndex, true);
|
|
896
|
+
},
|
|
897
|
+
setRepeatMode(mode) {
|
|
898
|
+
const nextState = setState((currentState) => ({
|
|
899
|
+
...currentState,
|
|
900
|
+
queue: {
|
|
901
|
+
...currentState.queue,
|
|
902
|
+
position: {
|
|
903
|
+
...currentState.queue.position,
|
|
904
|
+
repeatMode: mode
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}));
|
|
908
|
+
emitQueueChange(nextState);
|
|
909
|
+
},
|
|
910
|
+
destroy() {
|
|
911
|
+
if (destroyed) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
destroyed = true;
|
|
915
|
+
shouldAutoplayAfterLoad = false;
|
|
916
|
+
for (const detach of detachEngineListeners) {
|
|
917
|
+
detach();
|
|
918
|
+
}
|
|
919
|
+
suppressPauseEvent = true;
|
|
920
|
+
engine.destroy();
|
|
921
|
+
suppressPauseEvent = false;
|
|
922
|
+
const nextState = setState(() => createInitialState(options));
|
|
923
|
+
emit("destroy", { state: nextState });
|
|
924
|
+
subscribers.clear();
|
|
925
|
+
for (const eventName of Object.keys(eventListeners)) {
|
|
926
|
+
eventListeners[eventName].clear();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
};
|
|
931
|
+
var attachAudioPlayerPersistence = (player, options) => {
|
|
932
|
+
const maxAgeMs = options.maxAgeMs ?? DEFAULT_PERSISTENCE_MAX_AGE_MS;
|
|
933
|
+
const saveIntervalMs = options.saveIntervalMs ?? DEFAULT_PERSISTENCE_SAVE_INTERVAL_MS;
|
|
934
|
+
let destroyed = false;
|
|
935
|
+
let playIntervalId = null;
|
|
936
|
+
const save = () => {
|
|
937
|
+
if (destroyed) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const state = player.getState();
|
|
941
|
+
if (!state.currentSource) {
|
|
942
|
+
options.adapter.clear();
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
options.adapter.save(toPersistedPlayerState(state));
|
|
946
|
+
};
|
|
947
|
+
const clear = () => {
|
|
948
|
+
options.adapter.clear();
|
|
949
|
+
};
|
|
950
|
+
const stopPlayInterval = () => {
|
|
951
|
+
if (playIntervalId !== null) {
|
|
952
|
+
clearInterval(playIntervalId);
|
|
953
|
+
playIntervalId = null;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
const startPlayInterval = () => {
|
|
957
|
+
if (playIntervalId !== null) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
playIntervalId = setInterval(() => {
|
|
961
|
+
save();
|
|
962
|
+
}, saveIntervalMs);
|
|
963
|
+
};
|
|
964
|
+
const waitForRestoredSource = (sourceId) => new Promise((resolve) => {
|
|
965
|
+
const unsubscribe = player.subscribe((state) => {
|
|
966
|
+
if (state.currentSourceId !== sourceId) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (state.status === "loading") {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
unsubscribe();
|
|
973
|
+
resolve();
|
|
974
|
+
});
|
|
975
|
+
});
|
|
976
|
+
const restore = async () => {
|
|
977
|
+
const persisted = options.adapter.load();
|
|
978
|
+
if (!persisted) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
if (Date.now() - persisted.savedAt > maxAgeMs) {
|
|
982
|
+
options.adapter.clear();
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
player.setRepeatMode(persisted.queue.position.repeatMode);
|
|
986
|
+
player.setVolume(persisted.volume);
|
|
987
|
+
player.setMuted(persisted.muted);
|
|
988
|
+
player.setRate(persisted.rate);
|
|
989
|
+
if (persisted.queue.items.length > 0) {
|
|
990
|
+
await player.setQueue(persisted.queue.items, {
|
|
991
|
+
startAtId: persisted.queue.position.currentSourceId ?? void 0
|
|
992
|
+
});
|
|
993
|
+
} else if (persisted.currentSource) {
|
|
994
|
+
await player.load(persisted.currentSource);
|
|
995
|
+
} else {
|
|
996
|
+
return persisted;
|
|
997
|
+
}
|
|
998
|
+
if (persisted.currentSourceId) {
|
|
999
|
+
await waitForRestoredSource(persisted.currentSourceId);
|
|
1000
|
+
}
|
|
1001
|
+
if (persisted.currentTime > 0) {
|
|
1002
|
+
player.seek(persisted.currentTime);
|
|
1003
|
+
}
|
|
1004
|
+
return persisted;
|
|
1005
|
+
};
|
|
1006
|
+
const stateUnsubscribe = player.subscribe((state) => {
|
|
1007
|
+
if (state.status === "playing") {
|
|
1008
|
+
startPlayInterval();
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
stopPlayInterval();
|
|
1012
|
+
});
|
|
1013
|
+
const pauseUnsubscribe = player.on("pause", () => {
|
|
1014
|
+
save();
|
|
1015
|
+
});
|
|
1016
|
+
const endedUnsubscribe = player.on("ended", () => {
|
|
1017
|
+
save();
|
|
1018
|
+
});
|
|
1019
|
+
const sourceUnsubscribe = player.on("sourcechange", () => {
|
|
1020
|
+
save();
|
|
1021
|
+
});
|
|
1022
|
+
const queueUnsubscribe = player.on("queuechange", () => {
|
|
1023
|
+
save();
|
|
1024
|
+
});
|
|
1025
|
+
const rateUnsubscribe = player.on("ratechange", () => {
|
|
1026
|
+
save();
|
|
1027
|
+
});
|
|
1028
|
+
const volumeUnsubscribe = player.on("volumechange", () => {
|
|
1029
|
+
save();
|
|
1030
|
+
});
|
|
1031
|
+
const beforeUnloadHandler = () => {
|
|
1032
|
+
save();
|
|
1033
|
+
};
|
|
1034
|
+
const visibilityHandler = () => {
|
|
1035
|
+
if (typeof document === "undefined") {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (document.visibilityState === "hidden") {
|
|
1039
|
+
save();
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
if (typeof window !== "undefined") {
|
|
1043
|
+
window.addEventListener("beforeunload", beforeUnloadHandler);
|
|
1044
|
+
}
|
|
1045
|
+
if (typeof document !== "undefined") {
|
|
1046
|
+
document.addEventListener("visibilitychange", visibilityHandler);
|
|
1047
|
+
}
|
|
1048
|
+
if (options.autoRestore !== false) {
|
|
1049
|
+
void restore();
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
restore,
|
|
1053
|
+
save,
|
|
1054
|
+
clear,
|
|
1055
|
+
destroy() {
|
|
1056
|
+
destroyed = true;
|
|
1057
|
+
stopPlayInterval();
|
|
1058
|
+
stateUnsubscribe();
|
|
1059
|
+
pauseUnsubscribe();
|
|
1060
|
+
endedUnsubscribe();
|
|
1061
|
+
sourceUnsubscribe();
|
|
1062
|
+
queueUnsubscribe();
|
|
1063
|
+
rateUnsubscribe();
|
|
1064
|
+
volumeUnsubscribe();
|
|
1065
|
+
if (typeof window !== "undefined") {
|
|
1066
|
+
window.removeEventListener("beforeunload", beforeUnloadHandler);
|
|
1067
|
+
}
|
|
1068
|
+
if (typeof document !== "undefined") {
|
|
1069
|
+
document.removeEventListener("visibilitychange", visibilityHandler);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
};
|
|
1074
|
+
var attachAudioPlayerProgressReporter = (player, options) => {
|
|
1075
|
+
const intervalMs = options.intervalMs ?? DEFAULT_PROGRESS_REPORT_INTERVAL_MS;
|
|
1076
|
+
let destroyed = false;
|
|
1077
|
+
let intervalId = null;
|
|
1078
|
+
const report = () => {
|
|
1079
|
+
if (destroyed) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const state = player.getState();
|
|
1083
|
+
if (!state.currentSource) {
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
options.onReport(toProgressSnapshot(state));
|
|
1087
|
+
};
|
|
1088
|
+
const stopInterval = () => {
|
|
1089
|
+
if (intervalId !== null) {
|
|
1090
|
+
clearInterval(intervalId);
|
|
1091
|
+
intervalId = null;
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
const startInterval = () => {
|
|
1095
|
+
if (intervalId !== null) {
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
intervalId = setInterval(() => {
|
|
1099
|
+
report();
|
|
1100
|
+
}, intervalMs);
|
|
1101
|
+
};
|
|
1102
|
+
const stateUnsubscribe = player.subscribe((state) => {
|
|
1103
|
+
if (state.status === "playing") {
|
|
1104
|
+
startInterval();
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
stopInterval();
|
|
1108
|
+
});
|
|
1109
|
+
const pauseUnsubscribe = player.on("pause", () => {
|
|
1110
|
+
report();
|
|
1111
|
+
});
|
|
1112
|
+
const endedUnsubscribe = player.on("ended", () => {
|
|
1113
|
+
report();
|
|
1114
|
+
});
|
|
1115
|
+
const seekedUnsubscribe = player.on("seeked", () => {
|
|
1116
|
+
report();
|
|
1117
|
+
});
|
|
1118
|
+
const sourceUnsubscribe = player.on("sourcechange", () => {
|
|
1119
|
+
report();
|
|
1120
|
+
});
|
|
1121
|
+
return {
|
|
1122
|
+
report,
|
|
1123
|
+
destroy() {
|
|
1124
|
+
destroyed = true;
|
|
1125
|
+
stopInterval();
|
|
1126
|
+
stateUnsubscribe();
|
|
1127
|
+
pauseUnsubscribe();
|
|
1128
|
+
endedUnsubscribe();
|
|
1129
|
+
seekedUnsubscribe();
|
|
1130
|
+
sourceUnsubscribe();
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
};
|
|
1134
|
+
var attachAudioPlayerMediaSession = (player, options = {}) => {
|
|
1135
|
+
if (!hasMediaSession()) {
|
|
1136
|
+
return {
|
|
1137
|
+
sync() {
|
|
1138
|
+
},
|
|
1139
|
+
destroy() {
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
const artworkSizes = options.artworkSizes ?? DEFAULT_MEDIA_SESSION_ARTWORK_SIZES;
|
|
1144
|
+
const seekOffsetSeconds = options.seekOffsetSeconds ?? DEFAULT_MEDIA_SESSION_SEEK_OFFSET_SECONDS;
|
|
1145
|
+
let destroyed = false;
|
|
1146
|
+
const sync = () => {
|
|
1147
|
+
if (destroyed || !hasMediaSession()) {
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const state = player.getState();
|
|
1151
|
+
const source = state.currentSource;
|
|
1152
|
+
navigator.mediaSession.metadata = source ? new MediaMetadata({
|
|
1153
|
+
title: source.title ?? "",
|
|
1154
|
+
artist: source.artist ?? "",
|
|
1155
|
+
album: source.album ?? "",
|
|
1156
|
+
artwork: createMediaSessionArtwork(source, artworkSizes)
|
|
1157
|
+
}) : null;
|
|
1158
|
+
navigator.mediaSession.playbackState = state.status === "playing" ? "playing" : state.currentSource ? "paused" : "none";
|
|
1159
|
+
if (state.currentSource && state.duration > 0) {
|
|
1160
|
+
try {
|
|
1161
|
+
navigator.mediaSession.setPositionState({
|
|
1162
|
+
duration: state.duration,
|
|
1163
|
+
playbackRate: state.rate,
|
|
1164
|
+
position: Math.min(state.currentTime, state.duration)
|
|
1165
|
+
});
|
|
1166
|
+
} catch {
|
|
1167
|
+
}
|
|
1168
|
+
} else {
|
|
1169
|
+
try {
|
|
1170
|
+
navigator.mediaSession.setPositionState();
|
|
1171
|
+
} catch {
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
const setActionHandler = (action, handler) => {
|
|
1176
|
+
try {
|
|
1177
|
+
navigator.mediaSession.setActionHandler(action, handler);
|
|
1178
|
+
} catch {
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
setActionHandler("play", () => {
|
|
1182
|
+
void player.play();
|
|
1183
|
+
});
|
|
1184
|
+
setActionHandler("pause", () => {
|
|
1185
|
+
player.pause();
|
|
1186
|
+
});
|
|
1187
|
+
setActionHandler("seekbackward", () => {
|
|
1188
|
+
const state = player.getState();
|
|
1189
|
+
player.seek(Math.max(0, state.currentTime - seekOffsetSeconds));
|
|
1190
|
+
});
|
|
1191
|
+
setActionHandler("seekforward", () => {
|
|
1192
|
+
const state = player.getState();
|
|
1193
|
+
const max = state.duration > 0 ? state.duration : state.currentTime + seekOffsetSeconds;
|
|
1194
|
+
player.seek(Math.min(max, state.currentTime + seekOffsetSeconds));
|
|
1195
|
+
});
|
|
1196
|
+
setActionHandler("previoustrack", () => {
|
|
1197
|
+
void player.previous();
|
|
1198
|
+
});
|
|
1199
|
+
setActionHandler("nexttrack", () => {
|
|
1200
|
+
void player.next();
|
|
1201
|
+
});
|
|
1202
|
+
setActionHandler("seekto", (details) => {
|
|
1203
|
+
if (details.seekTime === void 0) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
player.seek(details.seekTime);
|
|
1207
|
+
});
|
|
1208
|
+
setActionHandler("stop", () => {
|
|
1209
|
+
player.pause();
|
|
1210
|
+
});
|
|
1211
|
+
const stateUnsubscribe = player.subscribe(() => {
|
|
1212
|
+
sync();
|
|
1213
|
+
});
|
|
1214
|
+
sync();
|
|
1215
|
+
return {
|
|
1216
|
+
sync,
|
|
1217
|
+
destroy() {
|
|
1218
|
+
destroyed = true;
|
|
1219
|
+
stateUnsubscribe();
|
|
1220
|
+
if (!hasMediaSession()) {
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
const actions = [
|
|
1224
|
+
"play",
|
|
1225
|
+
"pause",
|
|
1226
|
+
"seekbackward",
|
|
1227
|
+
"seekforward",
|
|
1228
|
+
"previoustrack",
|
|
1229
|
+
"nexttrack",
|
|
1230
|
+
"seekto",
|
|
1231
|
+
"stop"
|
|
1232
|
+
];
|
|
1233
|
+
for (const action of actions) {
|
|
1234
|
+
setActionHandler(action, null);
|
|
1235
|
+
}
|
|
1236
|
+
navigator.mediaSession.metadata = null;
|
|
1237
|
+
navigator.mediaSession.playbackState = "none";
|
|
1238
|
+
try {
|
|
1239
|
+
navigator.mediaSession.setPositionState();
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
export {
|
|
1247
|
+
createLocalStoragePersistenceAdapter,
|
|
1248
|
+
createAudioPlayer,
|
|
1249
|
+
attachAudioPlayerPersistence,
|
|
1250
|
+
attachAudioPlayerProgressReporter,
|
|
1251
|
+
attachAudioPlayerMediaSession
|
|
1252
|
+
};
|
|
1253
|
+
//# sourceMappingURL=chunk-BN5MQE43.js.map
|