@yetric/audioplayer 0.0.0-stage → 1.0.0
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-7KCRIJPH.js +1259 -0
- package/dist/chunk-7KCRIJPH.js.map +1 -0
- package/dist/index.cjs +1287 -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
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react.tsx
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
AudioPlayerProvider: () => AudioPlayerProvider,
|
|
24
|
+
useAudioPlayer: () => useAudioPlayer,
|
|
25
|
+
useAudioPlayerState: () => useAudioPlayerState
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(react_exports);
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var DEFAULT_PREVIOUS_RESTART_THRESHOLD_SECONDS = 5;
|
|
32
|
+
var DEFAULT_PERSISTENCE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
33
|
+
var createInitialState = (options) => ({
|
|
34
|
+
status: "idle",
|
|
35
|
+
currentSource: null,
|
|
36
|
+
currentSourceId: null,
|
|
37
|
+
currentTime: 0,
|
|
38
|
+
duration: 0,
|
|
39
|
+
buffered: 0,
|
|
40
|
+
progress: {
|
|
41
|
+
currentTime: 0,
|
|
42
|
+
duration: 0,
|
|
43
|
+
buffered: 0,
|
|
44
|
+
playedFraction: 0,
|
|
45
|
+
bufferedFraction: 0
|
|
46
|
+
},
|
|
47
|
+
rate: options.initialRate ?? 1,
|
|
48
|
+
volume: options.initialVolume ?? 1,
|
|
49
|
+
muted: options.initialMuted ?? false,
|
|
50
|
+
queue: {
|
|
51
|
+
items: [],
|
|
52
|
+
itemIds: [],
|
|
53
|
+
position: {
|
|
54
|
+
currentIndex: -1,
|
|
55
|
+
currentSourceId: null,
|
|
56
|
+
length: 0,
|
|
57
|
+
repeatMode: options.initialRepeatMode ?? "off",
|
|
58
|
+
hasNext: false,
|
|
59
|
+
hasPrevious: false
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
error: null
|
|
63
|
+
});
|
|
64
|
+
var createPlayerError = (code, message, cause) => ({
|
|
65
|
+
code,
|
|
66
|
+
message,
|
|
67
|
+
cause
|
|
68
|
+
});
|
|
69
|
+
var isRuntimePlayerError = (value) => !!value && typeof value === "object" && "code" in value && "message" in value;
|
|
70
|
+
var normalizePlayerError = (fallbackCode, fallbackMessage, cause) => isRuntimePlayerError(cause) ? cause : createPlayerError(fallbackCode, fallbackMessage, cause);
|
|
71
|
+
var assertStableSource = (source) => {
|
|
72
|
+
if (!source.id || !source.src) {
|
|
73
|
+
throw createPlayerError(
|
|
74
|
+
"INVALID_QUEUE_ITEM",
|
|
75
|
+
"AudioSource requires stable id and src."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var isFiniteNumber = (value) => Number.isFinite(value) && !Number.isNaN(value);
|
|
80
|
+
var safeDuration = (audio, hint) => {
|
|
81
|
+
const duration = audio?.duration;
|
|
82
|
+
return duration !== void 0 && isFiniteNumber(duration) ? duration : hint ?? 0;
|
|
83
|
+
};
|
|
84
|
+
var safeBuffered = (audio) => {
|
|
85
|
+
if (!audio || audio.buffered.length === 0) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return audio.buffered.end(audio.buffered.length - 1);
|
|
90
|
+
} catch {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var cloneQueueState = (state) => ({
|
|
95
|
+
items: [...state.queue.items],
|
|
96
|
+
itemIds: [...state.queue.itemIds],
|
|
97
|
+
position: {
|
|
98
|
+
...state.queue.position
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
var updateQueueFlags = (state) => {
|
|
102
|
+
const { items, position } = state.queue;
|
|
103
|
+
const { currentIndex, repeatMode } = position;
|
|
104
|
+
const lastIndex = items.length - 1;
|
|
105
|
+
const hasItems = items.length > 0;
|
|
106
|
+
const hasNext = !hasItems || currentIndex < 0 ? false : repeatMode === "all" ? items.length > 0 : currentIndex < lastIndex;
|
|
107
|
+
const hasPrevious = !hasItems || currentIndex < 0 ? false : repeatMode === "all" ? items.length > 0 : currentIndex > 0;
|
|
108
|
+
return {
|
|
109
|
+
...state,
|
|
110
|
+
queue: {
|
|
111
|
+
...state.queue,
|
|
112
|
+
itemIds: items.map((item) => item.id),
|
|
113
|
+
position: {
|
|
114
|
+
...position,
|
|
115
|
+
currentSourceId: state.currentSource?.id ?? null,
|
|
116
|
+
length: items.length,
|
|
117
|
+
hasNext,
|
|
118
|
+
hasPrevious
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
var createAudioElement = () => {
|
|
124
|
+
if (typeof Audio === "undefined") {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return new Audio();
|
|
128
|
+
};
|
|
129
|
+
var createUnavailableAudioEngine = () => {
|
|
130
|
+
const unavailableError = () => createPlayerError(
|
|
131
|
+
"UNSUPPORTED_ENVIRONMENT",
|
|
132
|
+
"HTMLAudioElement is not available in this environment."
|
|
133
|
+
);
|
|
134
|
+
return {
|
|
135
|
+
available: false,
|
|
136
|
+
getSnapshot(durationHint) {
|
|
137
|
+
return {
|
|
138
|
+
currentTime: 0,
|
|
139
|
+
duration: durationHint ?? 0,
|
|
140
|
+
buffered: 0,
|
|
141
|
+
rate: 1,
|
|
142
|
+
volume: 1,
|
|
143
|
+
muted: false
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
getRuntimeError() {
|
|
147
|
+
return unavailableError();
|
|
148
|
+
},
|
|
149
|
+
on() {
|
|
150
|
+
return () => {
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
setSource() {
|
|
154
|
+
throw unavailableError();
|
|
155
|
+
},
|
|
156
|
+
clearSource() {
|
|
157
|
+
throw unavailableError();
|
|
158
|
+
},
|
|
159
|
+
load() {
|
|
160
|
+
throw unavailableError();
|
|
161
|
+
},
|
|
162
|
+
play() {
|
|
163
|
+
throw unavailableError();
|
|
164
|
+
},
|
|
165
|
+
pause() {
|
|
166
|
+
throw unavailableError();
|
|
167
|
+
},
|
|
168
|
+
setCurrentTime() {
|
|
169
|
+
throw unavailableError();
|
|
170
|
+
},
|
|
171
|
+
setPlaybackRate() {
|
|
172
|
+
throw unavailableError();
|
|
173
|
+
},
|
|
174
|
+
setVolume() {
|
|
175
|
+
throw unavailableError();
|
|
176
|
+
},
|
|
177
|
+
setMuted() {
|
|
178
|
+
throw unavailableError();
|
|
179
|
+
},
|
|
180
|
+
destroy() {
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
var createHtmlAudioEngine = () => {
|
|
185
|
+
const audio = createAudioElement();
|
|
186
|
+
if (!audio) {
|
|
187
|
+
return createUnavailableAudioEngine();
|
|
188
|
+
}
|
|
189
|
+
audio.preload = "metadata";
|
|
190
|
+
const clearSource = () => {
|
|
191
|
+
const element = audio;
|
|
192
|
+
if (typeof element.removeAttribute === "function") {
|
|
193
|
+
element.removeAttribute("src");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
audio.src = "";
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
available: true,
|
|
200
|
+
getSnapshot(durationHint) {
|
|
201
|
+
return {
|
|
202
|
+
currentTime: audio.currentTime,
|
|
203
|
+
duration: safeDuration(audio, durationHint),
|
|
204
|
+
buffered: safeBuffered(audio),
|
|
205
|
+
rate: audio.playbackRate,
|
|
206
|
+
volume: audio.volume,
|
|
207
|
+
muted: audio.muted
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
getRuntimeError() {
|
|
211
|
+
return audio.error;
|
|
212
|
+
},
|
|
213
|
+
on(eventName, listener) {
|
|
214
|
+
audio.addEventListener(eventName, listener);
|
|
215
|
+
return () => {
|
|
216
|
+
audio.removeEventListener(eventName, listener);
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
setSource(src) {
|
|
220
|
+
audio.src = src;
|
|
221
|
+
},
|
|
222
|
+
clearSource() {
|
|
223
|
+
clearSource();
|
|
224
|
+
},
|
|
225
|
+
load() {
|
|
226
|
+
audio.load();
|
|
227
|
+
},
|
|
228
|
+
play() {
|
|
229
|
+
return audio.play();
|
|
230
|
+
},
|
|
231
|
+
pause() {
|
|
232
|
+
audio.pause();
|
|
233
|
+
},
|
|
234
|
+
setCurrentTime(time) {
|
|
235
|
+
audio.currentTime = time;
|
|
236
|
+
},
|
|
237
|
+
setPlaybackRate(rate) {
|
|
238
|
+
audio.playbackRate = rate;
|
|
239
|
+
},
|
|
240
|
+
setVolume(volume) {
|
|
241
|
+
audio.volume = volume;
|
|
242
|
+
},
|
|
243
|
+
setMuted(muted) {
|
|
244
|
+
audio.muted = muted;
|
|
245
|
+
},
|
|
246
|
+
destroy() {
|
|
247
|
+
audio.pause();
|
|
248
|
+
clearSource();
|
|
249
|
+
audio.load();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
var createAudioPlayer = (options = {}) => {
|
|
254
|
+
let state = updateQueueFlags(createInitialState(options));
|
|
255
|
+
let destroyed = false;
|
|
256
|
+
let shouldAutoplayAfterLoad = false;
|
|
257
|
+
let suppressPauseEvent = false;
|
|
258
|
+
const engine = createHtmlAudioEngine();
|
|
259
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
260
|
+
const eventListeners = {
|
|
261
|
+
sourcechange: /* @__PURE__ */ new Set(),
|
|
262
|
+
queuechange: /* @__PURE__ */ new Set(),
|
|
263
|
+
play: /* @__PURE__ */ new Set(),
|
|
264
|
+
pause: /* @__PURE__ */ new Set(),
|
|
265
|
+
seeking: /* @__PURE__ */ new Set(),
|
|
266
|
+
seeked: /* @__PURE__ */ new Set(),
|
|
267
|
+
timeupdate: /* @__PURE__ */ new Set(),
|
|
268
|
+
ratechange: /* @__PURE__ */ new Set(),
|
|
269
|
+
volumechange: /* @__PURE__ */ new Set(),
|
|
270
|
+
ended: /* @__PURE__ */ new Set(),
|
|
271
|
+
error: /* @__PURE__ */ new Set(),
|
|
272
|
+
destroy: /* @__PURE__ */ new Set()
|
|
273
|
+
};
|
|
274
|
+
const getState = () => state;
|
|
275
|
+
const publishState = () => {
|
|
276
|
+
for (const subscriber of subscribers) {
|
|
277
|
+
subscriber(state);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const emit = (eventName, event) => {
|
|
281
|
+
for (const listener of eventListeners[eventName]) {
|
|
282
|
+
listener(event);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
const setState = (updater) => {
|
|
286
|
+
state = updateQueueFlags(updater(state));
|
|
287
|
+
publishState();
|
|
288
|
+
return state;
|
|
289
|
+
};
|
|
290
|
+
const setError = (error) => {
|
|
291
|
+
shouldAutoplayAfterLoad = false;
|
|
292
|
+
const nextState = setState((currentState) => ({
|
|
293
|
+
...currentState,
|
|
294
|
+
status: "error",
|
|
295
|
+
error: {
|
|
296
|
+
code: error.code,
|
|
297
|
+
message: error.message
|
|
298
|
+
}
|
|
299
|
+
}));
|
|
300
|
+
emit("error", { error, state: nextState });
|
|
301
|
+
};
|
|
302
|
+
const clearError = () => {
|
|
303
|
+
if (!state.error) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
setState((currentState) => ({
|
|
307
|
+
...currentState,
|
|
308
|
+
error: null
|
|
309
|
+
}));
|
|
310
|
+
};
|
|
311
|
+
const syncAudioSnapshot = (statusOverride) => {
|
|
312
|
+
const snapshot = engine.getSnapshot(state.currentSource?.durationHint);
|
|
313
|
+
const currentTime = snapshot.currentTime;
|
|
314
|
+
const duration = snapshot.duration;
|
|
315
|
+
const buffered = snapshot.buffered;
|
|
316
|
+
const playedFraction = duration > 0 ? Math.min(1, Math.max(0, currentTime / duration)) : 0;
|
|
317
|
+
const bufferedFraction = duration > 0 ? Math.min(1, Math.max(0, buffered / duration)) : 0;
|
|
318
|
+
const nextState = setState((currentState) => ({
|
|
319
|
+
...currentState,
|
|
320
|
+
currentSourceId: currentState.currentSource?.id ?? null,
|
|
321
|
+
currentTime,
|
|
322
|
+
duration,
|
|
323
|
+
buffered,
|
|
324
|
+
progress: {
|
|
325
|
+
currentTime,
|
|
326
|
+
duration,
|
|
327
|
+
buffered,
|
|
328
|
+
playedFraction,
|
|
329
|
+
bufferedFraction
|
|
330
|
+
},
|
|
331
|
+
rate: snapshot.rate,
|
|
332
|
+
volume: snapshot.volume,
|
|
333
|
+
muted: snapshot.muted,
|
|
334
|
+
status: statusOverride ?? currentState.status,
|
|
335
|
+
error: statusOverride === "error" ? currentState.error : null
|
|
336
|
+
}));
|
|
337
|
+
return nextState;
|
|
338
|
+
};
|
|
339
|
+
const findQueueIndexById = (id) => id ? state.queue.items.findIndex((item) => item.id === id) : -1;
|
|
340
|
+
const resolveNextQueueIndex = (mode) => {
|
|
341
|
+
const { items, position } = state.queue;
|
|
342
|
+
const { currentIndex, repeatMode } = position;
|
|
343
|
+
if (items.length === 0 || currentIndex < 0) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
if (repeatMode === "one") {
|
|
347
|
+
return currentIndex;
|
|
348
|
+
}
|
|
349
|
+
if (currentIndex < items.length - 1) {
|
|
350
|
+
return currentIndex + 1;
|
|
351
|
+
}
|
|
352
|
+
if (repeatMode === "all") {
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
return mode === "ended" ? -1 : null;
|
|
356
|
+
};
|
|
357
|
+
const resolvePreviousQueueIndex = () => {
|
|
358
|
+
const { items, position } = state.queue;
|
|
359
|
+
const { currentIndex, repeatMode } = position;
|
|
360
|
+
if (items.length === 0 || currentIndex < 0) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
if (currentIndex > 0) {
|
|
364
|
+
return currentIndex - 1;
|
|
365
|
+
}
|
|
366
|
+
if (repeatMode === "all") {
|
|
367
|
+
return items.length - 1;
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
};
|
|
371
|
+
const updateCurrentSource = (source, nextStatus) => {
|
|
372
|
+
const queueIndex = findQueueIndexById(source?.id);
|
|
373
|
+
const nextState = setState((currentState) => ({
|
|
374
|
+
...currentState,
|
|
375
|
+
currentSource: source,
|
|
376
|
+
currentSourceId: source?.id ?? null,
|
|
377
|
+
currentTime: 0,
|
|
378
|
+
duration: source?.durationHint ?? 0,
|
|
379
|
+
buffered: 0,
|
|
380
|
+
progress: {
|
|
381
|
+
currentTime: 0,
|
|
382
|
+
duration: source?.durationHint ?? 0,
|
|
383
|
+
buffered: 0,
|
|
384
|
+
playedFraction: 0,
|
|
385
|
+
bufferedFraction: 0
|
|
386
|
+
},
|
|
387
|
+
status: nextStatus,
|
|
388
|
+
error: null,
|
|
389
|
+
queue: {
|
|
390
|
+
...currentState.queue,
|
|
391
|
+
position: {
|
|
392
|
+
...currentState.queue.position,
|
|
393
|
+
currentIndex: queueIndex,
|
|
394
|
+
currentSourceId: source?.id ?? null
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}));
|
|
398
|
+
emit("sourcechange", { source, state: nextState });
|
|
399
|
+
return nextState;
|
|
400
|
+
};
|
|
401
|
+
const setQueueState = (items, currentIndex) => setState((currentState) => ({
|
|
402
|
+
...currentState,
|
|
403
|
+
queue: {
|
|
404
|
+
...currentState.queue,
|
|
405
|
+
items,
|
|
406
|
+
position: {
|
|
407
|
+
...currentState.queue.position,
|
|
408
|
+
currentIndex
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}));
|
|
412
|
+
const emitQueueChange = (nextState) => {
|
|
413
|
+
emit("queuechange", { queue: cloneQueueState(nextState), state: nextState });
|
|
414
|
+
};
|
|
415
|
+
const attachSourceToAudio = (source, autoplayAfterLoad) => {
|
|
416
|
+
shouldAutoplayAfterLoad = autoplayAfterLoad;
|
|
417
|
+
suppressPauseEvent = true;
|
|
418
|
+
try {
|
|
419
|
+
engine.pause();
|
|
420
|
+
} finally {
|
|
421
|
+
suppressPauseEvent = false;
|
|
422
|
+
}
|
|
423
|
+
engine.setSource(source.src);
|
|
424
|
+
engine.setCurrentTime(0);
|
|
425
|
+
engine.setPlaybackRate(state.rate);
|
|
426
|
+
engine.setVolume(state.volume);
|
|
427
|
+
engine.setMuted(state.muted);
|
|
428
|
+
engine.load();
|
|
429
|
+
};
|
|
430
|
+
const loadInternal = async (source, autoplayAfterLoad = false) => {
|
|
431
|
+
assertStableSource(source);
|
|
432
|
+
updateCurrentSource(source, "loading");
|
|
433
|
+
attachSourceToAudio(source, autoplayAfterLoad);
|
|
434
|
+
};
|
|
435
|
+
const selectQueueIndex = async (index, autoplayAfterLoad = false) => {
|
|
436
|
+
const item = state.queue.items[index] ?? null;
|
|
437
|
+
if (!item) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
await loadInternal(item, autoplayAfterLoad);
|
|
441
|
+
const nextState = setQueueState(state.queue.items, index);
|
|
442
|
+
emitQueueChange(nextState);
|
|
443
|
+
return item;
|
|
444
|
+
};
|
|
445
|
+
const playLoadedAudio = async () => {
|
|
446
|
+
try {
|
|
447
|
+
await engine.play();
|
|
448
|
+
} catch (cause) {
|
|
449
|
+
setError(createPlayerError("PLAY_ERROR", "Failed to start playback.", cause));
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const handlePlaybackEnded = async () => {
|
|
453
|
+
if (destroyed) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (state.queue.items.length > 0 && state.queue.position.currentIndex >= 0) {
|
|
457
|
+
const nextIndex = resolveNextQueueIndex("ended");
|
|
458
|
+
if (nextIndex === -1 || nextIndex === null) {
|
|
459
|
+
const endedState2 = syncAudioSnapshot("ended");
|
|
460
|
+
emit("ended", { source: endedState2.currentSource, state: endedState2 });
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
await selectQueueIndex(nextIndex, true);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const endedState = syncAudioSnapshot("ended");
|
|
467
|
+
emit("ended", { source: endedState.currentSource, state: endedState });
|
|
468
|
+
};
|
|
469
|
+
const detachEngineListeners = [
|
|
470
|
+
engine.on("loadedmetadata", () => {
|
|
471
|
+
if (destroyed || !state.currentSource) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
syncAudioSnapshot("ready");
|
|
475
|
+
if (shouldAutoplayAfterLoad) {
|
|
476
|
+
shouldAutoplayAfterLoad = false;
|
|
477
|
+
void playLoadedAudio();
|
|
478
|
+
}
|
|
479
|
+
}),
|
|
480
|
+
engine.on("timeupdate", () => {
|
|
481
|
+
if (destroyed) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const nextState = syncAudioSnapshot(
|
|
485
|
+
state.status === "ended" ? "playing" : void 0
|
|
486
|
+
);
|
|
487
|
+
emit("timeupdate", {
|
|
488
|
+
currentTime: nextState.currentTime,
|
|
489
|
+
duration: nextState.duration,
|
|
490
|
+
state: nextState
|
|
491
|
+
});
|
|
492
|
+
}),
|
|
493
|
+
engine.on("progress", () => {
|
|
494
|
+
if (!destroyed) {
|
|
495
|
+
syncAudioSnapshot();
|
|
496
|
+
}
|
|
497
|
+
}),
|
|
498
|
+
engine.on("play", () => {
|
|
499
|
+
if (destroyed) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const nextState = syncAudioSnapshot("playing");
|
|
503
|
+
emit("play", { source: nextState.currentSource, state: nextState });
|
|
504
|
+
}),
|
|
505
|
+
engine.on("pause", () => {
|
|
506
|
+
if (destroyed || suppressPauseEvent) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const nextStatus = state.status === "ended" ? "ended" : state.currentSource ? "paused" : "idle";
|
|
510
|
+
const nextState = syncAudioSnapshot(nextStatus);
|
|
511
|
+
emit("pause", { source: nextState.currentSource, state: nextState });
|
|
512
|
+
}),
|
|
513
|
+
engine.on("seeked", () => {
|
|
514
|
+
if (destroyed) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const nextState = syncAudioSnapshot();
|
|
518
|
+
emit("seeked", { currentTime: nextState.currentTime, state: nextState });
|
|
519
|
+
emit("timeupdate", {
|
|
520
|
+
currentTime: nextState.currentTime,
|
|
521
|
+
duration: nextState.duration,
|
|
522
|
+
state: nextState
|
|
523
|
+
});
|
|
524
|
+
}),
|
|
525
|
+
engine.on("ratechange", () => {
|
|
526
|
+
if (destroyed) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const nextState = syncAudioSnapshot();
|
|
530
|
+
emit("ratechange", { rate: nextState.rate, state: nextState });
|
|
531
|
+
}),
|
|
532
|
+
engine.on("volumechange", () => {
|
|
533
|
+
if (destroyed) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const nextState = syncAudioSnapshot();
|
|
537
|
+
emit("volumechange", {
|
|
538
|
+
volume: nextState.volume,
|
|
539
|
+
muted: nextState.muted,
|
|
540
|
+
state: nextState
|
|
541
|
+
});
|
|
542
|
+
}),
|
|
543
|
+
engine.on("ended", () => {
|
|
544
|
+
void handlePlaybackEnded();
|
|
545
|
+
}),
|
|
546
|
+
engine.on("error", () => {
|
|
547
|
+
if (destroyed) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
setError(
|
|
551
|
+
createPlayerError(
|
|
552
|
+
"LOAD_ERROR",
|
|
553
|
+
"Audio element reported a playback error.",
|
|
554
|
+
engine.getRuntimeError()
|
|
555
|
+
)
|
|
556
|
+
);
|
|
557
|
+
})
|
|
558
|
+
];
|
|
559
|
+
return {
|
|
560
|
+
getState,
|
|
561
|
+
subscribe(listener) {
|
|
562
|
+
subscribers.add(listener);
|
|
563
|
+
listener(state);
|
|
564
|
+
return () => {
|
|
565
|
+
subscribers.delete(listener);
|
|
566
|
+
};
|
|
567
|
+
},
|
|
568
|
+
on(eventName, listener) {
|
|
569
|
+
eventListeners[eventName].add(
|
|
570
|
+
listener
|
|
571
|
+
);
|
|
572
|
+
return () => {
|
|
573
|
+
eventListeners[eventName].delete(
|
|
574
|
+
listener
|
|
575
|
+
);
|
|
576
|
+
};
|
|
577
|
+
},
|
|
578
|
+
async load(source) {
|
|
579
|
+
try {
|
|
580
|
+
await loadInternal(source);
|
|
581
|
+
} catch (cause) {
|
|
582
|
+
setError(
|
|
583
|
+
normalizePlayerError("LOAD_ERROR", "Failed to load source.", cause)
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
async play(source) {
|
|
588
|
+
try {
|
|
589
|
+
if (source) {
|
|
590
|
+
await loadInternal(source, true);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (!state.currentSource) {
|
|
594
|
+
setError(
|
|
595
|
+
createPlayerError(
|
|
596
|
+
"NO_ACTIVE_SOURCE",
|
|
597
|
+
"Cannot play without an active source."
|
|
598
|
+
)
|
|
599
|
+
);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (state.status === "loading") {
|
|
603
|
+
shouldAutoplayAfterLoad = true;
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
await playLoadedAudio();
|
|
607
|
+
} catch (cause) {
|
|
608
|
+
setError(normalizePlayerError("PLAY_ERROR", "Failed to start playback.", cause));
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
pause() {
|
|
612
|
+
if (!engine.available) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
shouldAutoplayAfterLoad = false;
|
|
616
|
+
engine.pause();
|
|
617
|
+
},
|
|
618
|
+
async toggle() {
|
|
619
|
+
if (state.status === "playing") {
|
|
620
|
+
this.pause();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
await this.play();
|
|
624
|
+
},
|
|
625
|
+
unload() {
|
|
626
|
+
shouldAutoplayAfterLoad = false;
|
|
627
|
+
if (engine.available) {
|
|
628
|
+
suppressPauseEvent = true;
|
|
629
|
+
try {
|
|
630
|
+
engine.pause();
|
|
631
|
+
} finally {
|
|
632
|
+
suppressPauseEvent = false;
|
|
633
|
+
}
|
|
634
|
+
engine.clearSource();
|
|
635
|
+
engine.load();
|
|
636
|
+
}
|
|
637
|
+
const nextState = updateCurrentSource(null, "idle");
|
|
638
|
+
emitQueueChange(nextState);
|
|
639
|
+
},
|
|
640
|
+
seek(time) {
|
|
641
|
+
if (!state.currentSource) {
|
|
642
|
+
setError(
|
|
643
|
+
createPlayerError("SEEK_ERROR", "Cannot seek without an active source.")
|
|
644
|
+
);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const max = engine.getSnapshot(state.currentSource.durationHint).duration;
|
|
648
|
+
const normalized = Math.max(
|
|
649
|
+
0,
|
|
650
|
+
max > 0 ? Math.min(time, max) : isFiniteNumber(time) ? time : 0
|
|
651
|
+
);
|
|
652
|
+
emit("seeking", { from: state.currentTime, to: normalized, state });
|
|
653
|
+
try {
|
|
654
|
+
engine.setCurrentTime(normalized);
|
|
655
|
+
} catch (cause) {
|
|
656
|
+
setError(normalizePlayerError("SEEK_ERROR", "Failed to seek audio.", cause));
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
setRate(rate) {
|
|
660
|
+
if (!engine.available) {
|
|
661
|
+
setError(
|
|
662
|
+
createPlayerError("INVALID_RATE", "Audio element is unavailable.")
|
|
663
|
+
);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (!isFiniteNumber(rate) || rate <= 0) {
|
|
667
|
+
setError(
|
|
668
|
+
createPlayerError("INVALID_RATE", "Playback rate must be greater than 0.")
|
|
669
|
+
);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
engine.setPlaybackRate(rate);
|
|
673
|
+
clearError();
|
|
674
|
+
syncAudioSnapshot();
|
|
675
|
+
},
|
|
676
|
+
setVolume(volume) {
|
|
677
|
+
if (!engine.available) {
|
|
678
|
+
setError(
|
|
679
|
+
createPlayerError("INVALID_VOLUME", "Audio element is unavailable.")
|
|
680
|
+
);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!isFiniteNumber(volume) || volume < 0 || volume > 1) {
|
|
684
|
+
setError(
|
|
685
|
+
createPlayerError("INVALID_VOLUME", "Volume must be between 0 and 1.")
|
|
686
|
+
);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
engine.setVolume(volume);
|
|
690
|
+
clearError();
|
|
691
|
+
syncAudioSnapshot();
|
|
692
|
+
},
|
|
693
|
+
setMuted(muted) {
|
|
694
|
+
if (!engine.available) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
engine.setMuted(muted);
|
|
698
|
+
clearError();
|
|
699
|
+
syncAudioSnapshot();
|
|
700
|
+
},
|
|
701
|
+
async setQueue(items, queueOptions = {}) {
|
|
702
|
+
try {
|
|
703
|
+
for (const item of items) {
|
|
704
|
+
assertStableSource(item);
|
|
705
|
+
}
|
|
706
|
+
const startIndex = queueOptions.startAtId === void 0 ? items.length > 0 ? 0 : -1 : items.findIndex((item) => item.id === queueOptions.startAtId);
|
|
707
|
+
if (queueOptions.startAtId && startIndex < 0) {
|
|
708
|
+
throw createPlayerError(
|
|
709
|
+
"INVALID_QUEUE_ITEM",
|
|
710
|
+
`Queue start item ${queueOptions.startAtId} was not found.`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const nextState = setState((currentState) => ({
|
|
714
|
+
...currentState,
|
|
715
|
+
currentSource: startIndex >= 0 ? items[startIndex] ?? null : null,
|
|
716
|
+
currentSourceId: startIndex >= 0 ? items[startIndex]?.id ?? null : null,
|
|
717
|
+
currentTime: 0,
|
|
718
|
+
duration: startIndex >= 0 ? items[startIndex]?.durationHint ?? 0 : 0,
|
|
719
|
+
buffered: 0,
|
|
720
|
+
progress: {
|
|
721
|
+
currentTime: 0,
|
|
722
|
+
duration: startIndex >= 0 ? items[startIndex]?.durationHint ?? 0 : 0,
|
|
723
|
+
buffered: 0,
|
|
724
|
+
playedFraction: 0,
|
|
725
|
+
bufferedFraction: 0
|
|
726
|
+
},
|
|
727
|
+
status: startIndex >= 0 ? "ready" : "idle",
|
|
728
|
+
error: null,
|
|
729
|
+
queue: {
|
|
730
|
+
...currentState.queue,
|
|
731
|
+
items: [...items],
|
|
732
|
+
position: {
|
|
733
|
+
...currentState.queue.position,
|
|
734
|
+
currentIndex: startIndex,
|
|
735
|
+
currentSourceId: startIndex >= 0 ? items[startIndex]?.id ?? null : null
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}));
|
|
739
|
+
emitQueueChange(nextState);
|
|
740
|
+
emit("sourcechange", {
|
|
741
|
+
source: nextState.currentSource,
|
|
742
|
+
state: nextState
|
|
743
|
+
});
|
|
744
|
+
if (nextState.currentSource) {
|
|
745
|
+
await loadInternal(nextState.currentSource, queueOptions.autoplay === true);
|
|
746
|
+
const syncedState = setQueueState(items, startIndex);
|
|
747
|
+
emitQueueChange(syncedState);
|
|
748
|
+
} else if (engine.available) {
|
|
749
|
+
shouldAutoplayAfterLoad = false;
|
|
750
|
+
suppressPauseEvent = true;
|
|
751
|
+
engine.pause();
|
|
752
|
+
suppressPauseEvent = false;
|
|
753
|
+
engine.clearSource();
|
|
754
|
+
engine.load();
|
|
755
|
+
}
|
|
756
|
+
} catch (cause) {
|
|
757
|
+
setError(
|
|
758
|
+
normalizePlayerError(
|
|
759
|
+
"INVALID_QUEUE_ITEM",
|
|
760
|
+
"Failed to set queue.",
|
|
761
|
+
cause
|
|
762
|
+
)
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
appendToQueue(items) {
|
|
767
|
+
try {
|
|
768
|
+
for (const item of items) {
|
|
769
|
+
assertStableSource(item);
|
|
770
|
+
}
|
|
771
|
+
const nextState = setState((currentState) => ({
|
|
772
|
+
...currentState,
|
|
773
|
+
queue: {
|
|
774
|
+
...currentState.queue,
|
|
775
|
+
items: [...currentState.queue.items, ...items]
|
|
776
|
+
}
|
|
777
|
+
}));
|
|
778
|
+
emitQueueChange(nextState);
|
|
779
|
+
} catch (cause) {
|
|
780
|
+
setError(
|
|
781
|
+
normalizePlayerError(
|
|
782
|
+
"INVALID_QUEUE_ITEM",
|
|
783
|
+
"Failed to append queue items.",
|
|
784
|
+
cause
|
|
785
|
+
)
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
removeFromQueue(id) {
|
|
790
|
+
const nextItems = state.queue.items.filter((item) => item.id !== id);
|
|
791
|
+
const nextIndex = nextItems.findIndex(
|
|
792
|
+
(item) => item.id === state.currentSource?.id
|
|
793
|
+
);
|
|
794
|
+
const nextState = setState((currentState) => ({
|
|
795
|
+
...currentState,
|
|
796
|
+
queue: {
|
|
797
|
+
...currentState.queue,
|
|
798
|
+
items: nextItems,
|
|
799
|
+
position: {
|
|
800
|
+
...currentState.queue.position,
|
|
801
|
+
currentIndex: nextIndex,
|
|
802
|
+
currentSourceId: nextIndex >= 0 ? nextItems[nextIndex]?.id ?? null : null
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}));
|
|
806
|
+
emitQueueChange(nextState);
|
|
807
|
+
},
|
|
808
|
+
clearQueue() {
|
|
809
|
+
const nextState = setState((currentState) => ({
|
|
810
|
+
...currentState,
|
|
811
|
+
queue: {
|
|
812
|
+
...currentState.queue,
|
|
813
|
+
items: [],
|
|
814
|
+
position: {
|
|
815
|
+
...currentState.queue.position,
|
|
816
|
+
currentIndex: -1,
|
|
817
|
+
currentSourceId: null
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}));
|
|
821
|
+
emitQueueChange(nextState);
|
|
822
|
+
},
|
|
823
|
+
async next() {
|
|
824
|
+
const nextIndex = resolveNextQueueIndex("manual");
|
|
825
|
+
if (nextIndex === null) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
await selectQueueIndex(nextIndex, true);
|
|
829
|
+
},
|
|
830
|
+
async previous(previousOptions = {}) {
|
|
831
|
+
if (!state.currentSource) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const threshold = previousOptions.restartThresholdSeconds ?? DEFAULT_PREVIOUS_RESTART_THRESHOLD_SECONDS;
|
|
835
|
+
if (state.currentTime > threshold) {
|
|
836
|
+
this.seek(0);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const previousIndex = resolvePreviousQueueIndex();
|
|
840
|
+
if (previousIndex === null) {
|
|
841
|
+
this.seek(0);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
await selectQueueIndex(previousIndex, true);
|
|
845
|
+
},
|
|
846
|
+
setRepeatMode(mode) {
|
|
847
|
+
const nextState = setState((currentState) => ({
|
|
848
|
+
...currentState,
|
|
849
|
+
queue: {
|
|
850
|
+
...currentState.queue,
|
|
851
|
+
position: {
|
|
852
|
+
...currentState.queue.position,
|
|
853
|
+
repeatMode: mode
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}));
|
|
857
|
+
emitQueueChange(nextState);
|
|
858
|
+
},
|
|
859
|
+
destroy() {
|
|
860
|
+
if (destroyed) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
destroyed = true;
|
|
864
|
+
shouldAutoplayAfterLoad = false;
|
|
865
|
+
for (const detach of detachEngineListeners) {
|
|
866
|
+
detach();
|
|
867
|
+
}
|
|
868
|
+
suppressPauseEvent = true;
|
|
869
|
+
engine.destroy();
|
|
870
|
+
suppressPauseEvent = false;
|
|
871
|
+
const nextState = setState(() => createInitialState(options));
|
|
872
|
+
emit("destroy", { state: nextState });
|
|
873
|
+
subscribers.clear();
|
|
874
|
+
for (const eventName of Object.keys(eventListeners)) {
|
|
875
|
+
eventListeners[eventName].clear();
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
// src/react.tsx
|
|
882
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
883
|
+
var AudioPlayerContext = (0, import_react.createContext)(null);
|
|
884
|
+
var AudioPlayerProvider = ({
|
|
885
|
+
children,
|
|
886
|
+
options,
|
|
887
|
+
player
|
|
888
|
+
}) => {
|
|
889
|
+
const playerRef = (0, import_react.useRef)(player ?? null);
|
|
890
|
+
if (playerRef.current === null) {
|
|
891
|
+
playerRef.current = createAudioPlayer(options);
|
|
892
|
+
}
|
|
893
|
+
(0, import_react.useEffect)(() => {
|
|
894
|
+
if (!player) {
|
|
895
|
+
return () => {
|
|
896
|
+
playerRef.current?.destroy();
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
return void 0;
|
|
900
|
+
}, [player]);
|
|
901
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AudioPlayerContext.Provider, { value: playerRef.current, children });
|
|
902
|
+
};
|
|
903
|
+
var useAudioPlayer = () => {
|
|
904
|
+
const player = (0, import_react.useContext)(AudioPlayerContext);
|
|
905
|
+
if (!player) {
|
|
906
|
+
throw new Error("useAudioPlayer must be used inside AudioPlayerProvider.");
|
|
907
|
+
}
|
|
908
|
+
return player;
|
|
909
|
+
};
|
|
910
|
+
var useAudioPlayerState = () => {
|
|
911
|
+
const player = useAudioPlayer();
|
|
912
|
+
return (0, import_react.useSyncExternalStore)(
|
|
913
|
+
player.subscribe,
|
|
914
|
+
player.getState,
|
|
915
|
+
player.getState
|
|
916
|
+
);
|
|
917
|
+
};
|
|
918
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
919
|
+
0 && (module.exports = {
|
|
920
|
+
AudioPlayerProvider,
|
|
921
|
+
useAudioPlayer,
|
|
922
|
+
useAudioPlayerState
|
|
923
|
+
});
|
|
924
|
+
//# sourceMappingURL=react.cjs.map
|