@djangocfg/ui-tools 2.1.416 → 2.1.418
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/audio-player/index.cjs +2098 -0
- package/dist/audio-player/index.cjs.map +1 -0
- package/dist/audio-player/index.css +65 -0
- package/dist/audio-player/index.css.map +1 -0
- package/dist/audio-player/index.d.cts +166 -0
- package/dist/audio-player/index.d.ts +166 -0
- package/dist/audio-player/index.mjs +2075 -0
- package/dist/audio-player/index.mjs.map +1 -0
- package/dist/composer-registry/index.cjs +45 -0
- package/dist/composer-registry/index.cjs.map +1 -0
- package/dist/composer-registry/index.d.cts +73 -0
- package/dist/composer-registry/index.d.ts +73 -0
- package/dist/composer-registry/index.mjs +39 -0
- package/dist/composer-registry/index.mjs.map +1 -0
- package/dist/tree/index.cjs +82 -63
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +15 -1
- package/dist/tree/index.d.ts +15 -1
- package/dist/tree/index.mjs +83 -64
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +38 -17
- package/src/tools/chat/composer/Composer.tsx +8 -8
- package/src/tools/chat/context/ChatProvider.tsx +13 -78
- package/src/tools/chat/hooks/useAutoFocusOnStreamEnd.ts +12 -15
- package/src/tools/chat/hooks/useFocusOnEmptyClick.ts +4 -5
- package/src/tools/chat/launcher/header/ChatHeader.tsx +14 -19
- package/src/tools/chat/launcher/header/ChatHeaderActionButton.tsx +8 -12
- package/src/tools/data/Tree/TreeRoot.tsx +33 -109
- package/src/tools/data/Tree/context/TreeContext.tsx +22 -3
- package/src/tools/data/Tree/context/menu/index.ts +1 -0
- package/src/tools/data/Tree/context/menu/render.tsx +75 -0
- package/src/tools/data/Tree/context/menu/use-resolved-menu.ts +16 -2
- package/src/tools/data/Tree/index.tsx +1 -0
- package/src/tools/data/Tree/types/index.ts +1 -1
- package/src/tools/data/Tree/types/root-props.ts +16 -0
- package/src/tools/dev/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +6 -9
- package/src/tools/dev/OpenapiViewer/components/DocsLayout/index.tsx +2 -4
- package/src/tools/forms/CodeEditor/components/Editor.tsx +19 -0
- package/src/tools/forms/CodeEditor/types/index.ts +7 -0
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +40 -0
- package/src/tools/forms/MarkdownEditor/styles.css +174 -21
- package/src/tools/forms/NotionEditor/CustomKeymap.ts +48 -0
- package/src/tools/forms/NotionEditor/LinkDialog.tsx +133 -0
- package/src/tools/forms/NotionEditor/NotionEditor.tsx +304 -0
- package/src/tools/forms/NotionEditor/SlashExtension.ts +32 -0
- package/src/tools/forms/NotionEditor/SlashList.tsx +136 -0
- package/src/tools/forms/NotionEditor/TaskItemView.tsx +41 -0
- package/src/tools/forms/NotionEditor/createSlashSuggestion.ts +121 -0
- package/src/tools/forms/NotionEditor/extensions.ts +105 -0
- package/src/tools/forms/NotionEditor/index.ts +1 -0
- package/src/tools/forms/NotionEditor/lazy.tsx +44 -0
- package/src/tools/forms/NotionEditor/slashItems.ts +159 -0
- package/src/tools/forms/NotionEditor/styles.css +478 -0
- package/src/tools/forms/NotionEditor/types.ts +28 -0
- package/src/tools/input/SpeechRecognition/widgets/VoiceComposerSlot.tsx +11 -12
- package/src/tools/integration/ComposerRegistry/index.ts +105 -0
- package/src/tools/media/AudioPlayer/Player.tsx +2 -0
- package/src/tools/media/AudioPlayer/PlayerShell.tsx +29 -22
- package/src/tools/media/AudioPlayer/lazy.tsx +30 -42
- package/src/tools/media/AudioPlayer/parts/Controls/IconButton.tsx +10 -11
- package/src/tools/media/AudioPlayer/parts/Controls/VolumeControl.tsx +52 -115
- package/src/tools/media/AudioPlayer/types.ts +8 -0
- package/src/tools/media/ImageViewer/components/ImageViewer.tsx +8 -0
- package/src/tools/media/ImageViewer/types.ts +4 -0
- package/src/tools/media/VideoPlayer/VideoPlayer.tsx +20 -1
- package/src/tools/media/VideoPlayer/types.ts +4 -0
|
@@ -0,0 +1,2098 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkPK6SKIKE_cjs = require('../chunk-PK6SKIKE.cjs');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var hooks = require('@djangocfg/ui-core/hooks');
|
|
7
|
+
var lucideReact = require('lucide-react');
|
|
8
|
+
var components = require('@djangocfg/ui-core/components');
|
|
9
|
+
var palette = require('@djangocfg/ui-core/styles/palette');
|
|
10
|
+
|
|
11
|
+
// src/tools/media/AudioPlayer/audio/audioContext.ts
|
|
12
|
+
var _ctx = null;
|
|
13
|
+
function getAudioContext() {
|
|
14
|
+
if (_ctx) return _ctx;
|
|
15
|
+
const w = window;
|
|
16
|
+
const Ctor = w.AudioContext ?? w.webkitAudioContext;
|
|
17
|
+
if (!Ctor) {
|
|
18
|
+
throw new Error("Web Audio API is not supported in this environment");
|
|
19
|
+
}
|
|
20
|
+
_ctx = new Ctor({ latencyHint: "interactive" });
|
|
21
|
+
return _ctx;
|
|
22
|
+
}
|
|
23
|
+
chunkPK6SKIKE_cjs.__name(getAudioContext, "getAudioContext");
|
|
24
|
+
async function unlockAudioContext() {
|
|
25
|
+
const ctx = getAudioContext();
|
|
26
|
+
if (ctx.state === "suspended") {
|
|
27
|
+
try {
|
|
28
|
+
await ctx.resume();
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
chunkPK6SKIKE_cjs.__name(unlockAudioContext, "unlockAudioContext");
|
|
34
|
+
|
|
35
|
+
// src/tools/media/AudioPlayer/utils/bucketize.ts
|
|
36
|
+
function bucketize(channel2, buckets) {
|
|
37
|
+
const out = new Float32Array(buckets);
|
|
38
|
+
if (buckets <= 0 || channel2.length === 0) return out;
|
|
39
|
+
const samplesPerBucket = Math.max(1, Math.floor(channel2.length / buckets));
|
|
40
|
+
let peakMax = 0;
|
|
41
|
+
for (let b = 0; b < buckets; b++) {
|
|
42
|
+
const start = b * samplesPerBucket;
|
|
43
|
+
const end = Math.min(start + samplesPerBucket, channel2.length);
|
|
44
|
+
let max = 0;
|
|
45
|
+
for (let i = start; i < end; i++) {
|
|
46
|
+
const v = channel2[i];
|
|
47
|
+
const abs = v < 0 ? -v : v;
|
|
48
|
+
if (abs > max) max = abs;
|
|
49
|
+
}
|
|
50
|
+
out[b] = max;
|
|
51
|
+
if (max > peakMax) peakMax = max;
|
|
52
|
+
}
|
|
53
|
+
if (peakMax > 0 && peakMax < 1) {
|
|
54
|
+
const scale = 1 / peakMax;
|
|
55
|
+
for (let i = 0; i < out.length; i++) out[i] *= scale;
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
chunkPK6SKIKE_cjs.__name(bucketize, "bucketize");
|
|
60
|
+
|
|
61
|
+
// src/tools/media/AudioPlayer/audio/decodePeaks.ts
|
|
62
|
+
var SAMPLE_RATE = 22050;
|
|
63
|
+
var DEFAULT_BUCKETS = 1800;
|
|
64
|
+
function getOfflineCtor() {
|
|
65
|
+
const w = window;
|
|
66
|
+
const Ctor = w.OfflineAudioContext ?? w.webkitOfflineAudioContext;
|
|
67
|
+
if (!Ctor) throw new Error("OfflineAudioContext is not supported");
|
|
68
|
+
return Ctor;
|
|
69
|
+
}
|
|
70
|
+
chunkPK6SKIKE_cjs.__name(getOfflineCtor, "getOfflineCtor");
|
|
71
|
+
async function decodePeaks(src, buckets = DEFAULT_BUCKETS, signal) {
|
|
72
|
+
const response = await fetch(src, { signal, credentials: "same-origin" });
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Failed to fetch audio for peaks (${response.status})`);
|
|
75
|
+
}
|
|
76
|
+
const arr = await response.arrayBuffer();
|
|
77
|
+
const Ctor = getOfflineCtor();
|
|
78
|
+
const ctx = new Ctor(1, 1, SAMPLE_RATE);
|
|
79
|
+
const audio = await ctx.decodeAudioData(arr);
|
|
80
|
+
const channel2 = audio.getChannelData(0);
|
|
81
|
+
return bucketize(channel2, buckets);
|
|
82
|
+
}
|
|
83
|
+
chunkPK6SKIKE_cjs.__name(decodePeaks, "decodePeaks");
|
|
84
|
+
|
|
85
|
+
// src/tools/media/AudioPlayer/audio/peaksCache.ts
|
|
86
|
+
var cache = /* @__PURE__ */ new Map();
|
|
87
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
88
|
+
async function getPeaks(src, buckets) {
|
|
89
|
+
const hit = cache.get(src);
|
|
90
|
+
if (hit) return hit;
|
|
91
|
+
const flying = inflight.get(src);
|
|
92
|
+
if (flying) return flying;
|
|
93
|
+
const promise = decodePeaks(src, buckets).then((peaks) => {
|
|
94
|
+
cache.set(src, peaks);
|
|
95
|
+
return peaks;
|
|
96
|
+
});
|
|
97
|
+
inflight.set(src, promise);
|
|
98
|
+
try {
|
|
99
|
+
return await promise;
|
|
100
|
+
} finally {
|
|
101
|
+
inflight.delete(src);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
chunkPK6SKIKE_cjs.__name(getPeaks, "getPeaks");
|
|
105
|
+
function setPeaks(src, peaks) {
|
|
106
|
+
cache.set(src, peaks);
|
|
107
|
+
}
|
|
108
|
+
chunkPK6SKIKE_cjs.__name(setPeaks, "setPeaks");
|
|
109
|
+
function getPeaksFromCache(src) {
|
|
110
|
+
return cache.get(src);
|
|
111
|
+
}
|
|
112
|
+
chunkPK6SKIKE_cjs.__name(getPeaksFromCache, "getPeaksFromCache");
|
|
113
|
+
|
|
114
|
+
// src/tools/media/AudioPlayer/store/activePlayerBus.ts
|
|
115
|
+
var CHANNEL = "djangocfg-audioplayer:active";
|
|
116
|
+
var activeId = null;
|
|
117
|
+
var lastActiveId = null;
|
|
118
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
119
|
+
var pausers = /* @__PURE__ */ new Map();
|
|
120
|
+
var channel = null;
|
|
121
|
+
function getChannel() {
|
|
122
|
+
if (typeof BroadcastChannel === "undefined") return null;
|
|
123
|
+
if (channel) return channel;
|
|
124
|
+
channel = new BroadcastChannel(CHANNEL);
|
|
125
|
+
channel.addEventListener("message", (e) => {
|
|
126
|
+
const next = typeof e.data === "string" ? e.data : null;
|
|
127
|
+
if (next === activeId) return;
|
|
128
|
+
activeId = next;
|
|
129
|
+
if (next) lastActiveId = next;
|
|
130
|
+
for (const [id, pause] of pausers) {
|
|
131
|
+
if (id !== next) pause();
|
|
132
|
+
}
|
|
133
|
+
for (const l of listeners) l(next);
|
|
134
|
+
});
|
|
135
|
+
return channel;
|
|
136
|
+
}
|
|
137
|
+
chunkPK6SKIKE_cjs.__name(getChannel, "getChannel");
|
|
138
|
+
function registerPlayer(id, pause) {
|
|
139
|
+
pausers.set(id, pause);
|
|
140
|
+
return () => {
|
|
141
|
+
pausers.delete(id);
|
|
142
|
+
if (activeId === id) activeId = null;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
chunkPK6SKIKE_cjs.__name(registerPlayer, "registerPlayer");
|
|
146
|
+
function setActivePlayer(id) {
|
|
147
|
+
if (activeId === id) return;
|
|
148
|
+
activeId = id;
|
|
149
|
+
if (id) lastActiveId = id;
|
|
150
|
+
for (const [pid, pause] of pausers) {
|
|
151
|
+
if (pid !== id) pause();
|
|
152
|
+
}
|
|
153
|
+
for (const l of listeners) l(id);
|
|
154
|
+
getChannel()?.postMessage(id);
|
|
155
|
+
}
|
|
156
|
+
chunkPK6SKIKE_cjs.__name(setActivePlayer, "setActivePlayer");
|
|
157
|
+
function getActivePlayer() {
|
|
158
|
+
return activeId;
|
|
159
|
+
}
|
|
160
|
+
chunkPK6SKIKE_cjs.__name(getActivePlayer, "getActivePlayer");
|
|
161
|
+
function getLastActivePlayer() {
|
|
162
|
+
return lastActiveId;
|
|
163
|
+
}
|
|
164
|
+
chunkPK6SKIKE_cjs.__name(getLastActivePlayer, "getLastActivePlayer");
|
|
165
|
+
function subscribeActivePlayer(cb) {
|
|
166
|
+
listeners.add(cb);
|
|
167
|
+
getChannel();
|
|
168
|
+
return () => listeners.delete(cb);
|
|
169
|
+
}
|
|
170
|
+
chunkPK6SKIKE_cjs.__name(subscribeActivePlayer, "subscribeActivePlayer");
|
|
171
|
+
|
|
172
|
+
// src/tools/media/AudioPlayer/store/createLevelsStore.ts
|
|
173
|
+
function createLevelsStore(initial) {
|
|
174
|
+
let current = initial ?? new Float32Array(0);
|
|
175
|
+
let active = false;
|
|
176
|
+
const listeners3 = /* @__PURE__ */ new Set();
|
|
177
|
+
return {
|
|
178
|
+
subscribe(cb) {
|
|
179
|
+
listeners3.add(cb);
|
|
180
|
+
return () => listeners3.delete(cb);
|
|
181
|
+
},
|
|
182
|
+
getCurrent() {
|
|
183
|
+
return current;
|
|
184
|
+
},
|
|
185
|
+
set(next) {
|
|
186
|
+
current = next;
|
|
187
|
+
for (const cb of listeners3) cb();
|
|
188
|
+
},
|
|
189
|
+
setActive(value) {
|
|
190
|
+
active = value;
|
|
191
|
+
},
|
|
192
|
+
isActive() {
|
|
193
|
+
return active;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
chunkPK6SKIKE_cjs.__name(createLevelsStore, "createLevelsStore");
|
|
198
|
+
|
|
199
|
+
// src/tools/media/AudioPlayer/store/preferencesStore.ts
|
|
200
|
+
var STORAGE_KEY = "djangocfg-audioplayer:prefs";
|
|
201
|
+
var DEFAULT_VOLUME = 1;
|
|
202
|
+
var DEFAULT_MUTED = false;
|
|
203
|
+
var cached = null;
|
|
204
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
205
|
+
var storageBound = false;
|
|
206
|
+
function clamp01(v) {
|
|
207
|
+
if (!Number.isFinite(v)) return DEFAULT_VOLUME;
|
|
208
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
209
|
+
}
|
|
210
|
+
chunkPK6SKIKE_cjs.__name(clamp01, "clamp01");
|
|
211
|
+
function readFromStorage() {
|
|
212
|
+
if (typeof window === "undefined") {
|
|
213
|
+
return { volume: DEFAULT_VOLUME, muted: DEFAULT_MUTED };
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
217
|
+
if (!raw) return { volume: DEFAULT_VOLUME, muted: DEFAULT_MUTED };
|
|
218
|
+
const parsed = JSON.parse(raw);
|
|
219
|
+
return {
|
|
220
|
+
volume: typeof parsed?.volume === "number" ? clamp01(parsed.volume) : DEFAULT_VOLUME,
|
|
221
|
+
muted: typeof parsed?.muted === "boolean" ? parsed.muted : DEFAULT_MUTED
|
|
222
|
+
};
|
|
223
|
+
} catch {
|
|
224
|
+
return { volume: DEFAULT_VOLUME, muted: DEFAULT_MUTED };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
chunkPK6SKIKE_cjs.__name(readFromStorage, "readFromStorage");
|
|
228
|
+
function writeToStorage(prefs) {
|
|
229
|
+
if (typeof window === "undefined") return;
|
|
230
|
+
try {
|
|
231
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
chunkPK6SKIKE_cjs.__name(writeToStorage, "writeToStorage");
|
|
236
|
+
function bindStorage() {
|
|
237
|
+
if (storageBound || typeof window === "undefined") return;
|
|
238
|
+
storageBound = true;
|
|
239
|
+
window.addEventListener("storage", (e) => {
|
|
240
|
+
if (e.key !== STORAGE_KEY) return;
|
|
241
|
+
cached = readFromStorage();
|
|
242
|
+
for (const cb of listeners2) cb(cached);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
chunkPK6SKIKE_cjs.__name(bindStorage, "bindStorage");
|
|
246
|
+
function getPreferences() {
|
|
247
|
+
if (!cached) cached = readFromStorage();
|
|
248
|
+
bindStorage();
|
|
249
|
+
return cached;
|
|
250
|
+
}
|
|
251
|
+
chunkPK6SKIKE_cjs.__name(getPreferences, "getPreferences");
|
|
252
|
+
function update(next, persist = true) {
|
|
253
|
+
cached = next;
|
|
254
|
+
if (persist) writeToStorage(next);
|
|
255
|
+
for (const cb of listeners2) cb(next);
|
|
256
|
+
}
|
|
257
|
+
chunkPK6SKIKE_cjs.__name(update, "update");
|
|
258
|
+
function setStoredVolume(volume) {
|
|
259
|
+
const current = getPreferences();
|
|
260
|
+
const v = clamp01(volume);
|
|
261
|
+
if (v === current.volume) return;
|
|
262
|
+
update({ ...current, volume: v });
|
|
263
|
+
}
|
|
264
|
+
chunkPK6SKIKE_cjs.__name(setStoredVolume, "setStoredVolume");
|
|
265
|
+
function setStoredMuted(muted) {
|
|
266
|
+
const current = getPreferences();
|
|
267
|
+
if (muted === current.muted) return;
|
|
268
|
+
update({ ...current, muted });
|
|
269
|
+
}
|
|
270
|
+
chunkPK6SKIKE_cjs.__name(setStoredMuted, "setStoredMuted");
|
|
271
|
+
function subscribePreferences(cb) {
|
|
272
|
+
listeners2.add(cb);
|
|
273
|
+
bindStorage();
|
|
274
|
+
return () => listeners2.delete(cb);
|
|
275
|
+
}
|
|
276
|
+
chunkPK6SKIKE_cjs.__name(subscribePreferences, "subscribePreferences");
|
|
277
|
+
|
|
278
|
+
// src/tools/media/AudioPlayer/utils/clamp.ts
|
|
279
|
+
function clamp(value, min, max) {
|
|
280
|
+
if (value < min) return min;
|
|
281
|
+
if (value > max) return max;
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
chunkPK6SKIKE_cjs.__name(clamp, "clamp");
|
|
285
|
+
var AudioRefCtx = react.createContext(null);
|
|
286
|
+
AudioRefCtx.displayName = "AudioPlayerAudioRefCtx";
|
|
287
|
+
var ControlsCtx = react.createContext(null);
|
|
288
|
+
ControlsCtx.displayName = "AudioPlayerControlsCtx";
|
|
289
|
+
var LevelsCtx = react.createContext(null);
|
|
290
|
+
LevelsCtx.displayName = "AudioPlayerLevelsCtx";
|
|
291
|
+
var MetaCtx = react.createContext(null);
|
|
292
|
+
MetaCtx.displayName = "AudioPlayerMetaCtx";
|
|
293
|
+
var StateCtx = react.createContext(null);
|
|
294
|
+
StateCtx.displayName = "AudioPlayerStateCtx";
|
|
295
|
+
var TRACKED = [
|
|
296
|
+
"play",
|
|
297
|
+
"pause",
|
|
298
|
+
"ended",
|
|
299
|
+
"loadstart",
|
|
300
|
+
"loadedmetadata",
|
|
301
|
+
"durationchange",
|
|
302
|
+
"emptied",
|
|
303
|
+
"waiting",
|
|
304
|
+
"canplay",
|
|
305
|
+
"error"
|
|
306
|
+
];
|
|
307
|
+
var LOAD_WATCHDOG_MS = 12e3;
|
|
308
|
+
var SYNTHETIC_NETWORK_ERROR = 2;
|
|
309
|
+
function readSnapshot(audio, stalled) {
|
|
310
|
+
const nativeCode = audio.error?.code ?? null;
|
|
311
|
+
return {
|
|
312
|
+
paused: audio.paused,
|
|
313
|
+
ended: audio.ended,
|
|
314
|
+
duration: Number.isFinite(audio.duration) ? audio.duration : 0,
|
|
315
|
+
ready: audio.readyState >= 2,
|
|
316
|
+
errorCode: nativeCode ?? (stalled ? SYNTHETIC_NETWORK_ERROR : null)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
chunkPK6SKIKE_cjs.__name(readSnapshot, "readSnapshot");
|
|
320
|
+
function snapshotsEqual(a, b) {
|
|
321
|
+
return a.paused === b.paused && a.ended === b.ended && a.duration === b.duration && a.ready === b.ready && a.errorCode === b.errorCode;
|
|
322
|
+
}
|
|
323
|
+
chunkPK6SKIKE_cjs.__name(snapshotsEqual, "snapshotsEqual");
|
|
324
|
+
function createAudioSnapshotSource(audio) {
|
|
325
|
+
let stalled = false;
|
|
326
|
+
let cached2 = readSnapshot(audio, stalled);
|
|
327
|
+
return {
|
|
328
|
+
subscribe(cb) {
|
|
329
|
+
let watchdog = null;
|
|
330
|
+
const clearWatchdog = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
331
|
+
if (watchdog !== null) {
|
|
332
|
+
clearTimeout(watchdog);
|
|
333
|
+
watchdog = null;
|
|
334
|
+
}
|
|
335
|
+
}, "clearWatchdog");
|
|
336
|
+
const refresh = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
337
|
+
const next = readSnapshot(audio, stalled);
|
|
338
|
+
if (!snapshotsEqual(cached2, next)) {
|
|
339
|
+
cached2 = next;
|
|
340
|
+
cb();
|
|
341
|
+
}
|
|
342
|
+
}, "refresh");
|
|
343
|
+
const armWatchdog = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
344
|
+
clearWatchdog();
|
|
345
|
+
if (audio.readyState >= 2 || audio.error) return;
|
|
346
|
+
watchdog = setTimeout(() => {
|
|
347
|
+
watchdog = null;
|
|
348
|
+
if (audio.readyState < 2 && !audio.error) {
|
|
349
|
+
stalled = true;
|
|
350
|
+
refresh();
|
|
351
|
+
}
|
|
352
|
+
}, LOAD_WATCHDOG_MS);
|
|
353
|
+
}, "armWatchdog");
|
|
354
|
+
const onProgress = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((e) => {
|
|
355
|
+
if (e.type === "loadstart" || e.type === "emptied") {
|
|
356
|
+
stalled = false;
|
|
357
|
+
armWatchdog();
|
|
358
|
+
} else if (audio.readyState >= 2 || audio.error) {
|
|
359
|
+
clearWatchdog();
|
|
360
|
+
if (stalled) stalled = false;
|
|
361
|
+
}
|
|
362
|
+
refresh();
|
|
363
|
+
}, "onProgress");
|
|
364
|
+
for (const e of TRACKED) audio.addEventListener(e, onProgress);
|
|
365
|
+
armWatchdog();
|
|
366
|
+
refresh();
|
|
367
|
+
return () => {
|
|
368
|
+
clearWatchdog();
|
|
369
|
+
for (const e of TRACKED) audio.removeEventListener(e, onProgress);
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
getSnapshot() {
|
|
373
|
+
return cached2;
|
|
374
|
+
},
|
|
375
|
+
getServerSnapshot() {
|
|
376
|
+
return cached2;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
chunkPK6SKIKE_cjs.__name(createAudioSnapshotSource, "createAudioSnapshotSource");
|
|
381
|
+
function errorCodeToReason(code) {
|
|
382
|
+
if (code === 2) return "network";
|
|
383
|
+
if (code === 3) return "decode";
|
|
384
|
+
if (code === 4) return "unsupported";
|
|
385
|
+
return "unknown";
|
|
386
|
+
}
|
|
387
|
+
chunkPK6SKIKE_cjs.__name(errorCodeToReason, "errorCodeToReason");
|
|
388
|
+
function snapshotToState(snap, hasSrc, peaks) {
|
|
389
|
+
if (snap.errorCode !== null) {
|
|
390
|
+
return { kind: "error", reason: errorCodeToReason(snap.errorCode), duration: snap.duration };
|
|
391
|
+
}
|
|
392
|
+
if (!hasSrc) return { kind: "idle" };
|
|
393
|
+
if (!snap.ready) return { kind: "loading" };
|
|
394
|
+
const kind = snap.ended ? "ended" : snap.paused ? "paused" : "playing";
|
|
395
|
+
return { kind, duration: snap.duration, peaks };
|
|
396
|
+
}
|
|
397
|
+
chunkPK6SKIKE_cjs.__name(snapshotToState, "snapshotToState");
|
|
398
|
+
function useAudioElementState(source, hasSrc, peaks) {
|
|
399
|
+
const snap = react.useSyncExternalStore(source.subscribe, source.getSnapshot, source.getServerSnapshot);
|
|
400
|
+
return snapshotToState(snap, hasSrc, peaks);
|
|
401
|
+
}
|
|
402
|
+
chunkPK6SKIKE_cjs.__name(useAudioElementState, "useAudioElementState");
|
|
403
|
+
function createAudioElement() {
|
|
404
|
+
const a = new Audio();
|
|
405
|
+
a.crossOrigin = "anonymous";
|
|
406
|
+
a.preload = "metadata";
|
|
407
|
+
return a;
|
|
408
|
+
}
|
|
409
|
+
chunkPK6SKIKE_cjs.__name(createAudioElement, "createAudioElement");
|
|
410
|
+
function PlayerProvider(props) {
|
|
411
|
+
const {
|
|
412
|
+
src,
|
|
413
|
+
peaks: peaksProp,
|
|
414
|
+
title,
|
|
415
|
+
artist,
|
|
416
|
+
album,
|
|
417
|
+
cover,
|
|
418
|
+
autoplay = false,
|
|
419
|
+
loop = false,
|
|
420
|
+
initialVolume,
|
|
421
|
+
muted: mutedProp,
|
|
422
|
+
preload = "metadata",
|
|
423
|
+
exclusive = true,
|
|
424
|
+
onPrev,
|
|
425
|
+
onNext,
|
|
426
|
+
onPlay,
|
|
427
|
+
onPause,
|
|
428
|
+
onEnded,
|
|
429
|
+
onError,
|
|
430
|
+
children
|
|
431
|
+
} = props;
|
|
432
|
+
const volumeIsControlled = initialVolume !== void 0;
|
|
433
|
+
const mutedIsControlled = mutedProp !== void 0;
|
|
434
|
+
const playerId = react.useId();
|
|
435
|
+
const [audio] = react.useState(createAudioElement);
|
|
436
|
+
const [source] = react.useState(() => createAudioSnapshotSource(audio));
|
|
437
|
+
const [levelsStore] = react.useState(() => createLevelsStore());
|
|
438
|
+
const [activePeaks, setActivePeaks] = react.useState(
|
|
439
|
+
() => peaksProp ?? getPeaksFromCache(src)
|
|
440
|
+
);
|
|
441
|
+
react.useEffect(() => {
|
|
442
|
+
if (peaksProp && getPeaksFromCache(src) === void 0) {
|
|
443
|
+
setPeaks(src, peaksProp);
|
|
444
|
+
}
|
|
445
|
+
}, [src, peaksProp]);
|
|
446
|
+
react.useEffect(() => {
|
|
447
|
+
if (audio.src !== src) {
|
|
448
|
+
audio.src = src;
|
|
449
|
+
audio.load();
|
|
450
|
+
}
|
|
451
|
+
}, [audio, src]);
|
|
452
|
+
react.useEffect(() => {
|
|
453
|
+
audio.preload = preload;
|
|
454
|
+
}, [audio, preload]);
|
|
455
|
+
react.useEffect(() => {
|
|
456
|
+
audio.loop = loop;
|
|
457
|
+
}, [audio, loop]);
|
|
458
|
+
react.useEffect(() => {
|
|
459
|
+
const stored = getPreferences();
|
|
460
|
+
audio.volume = clamp(volumeIsControlled ? initialVolume : stored.volume, 0, 1);
|
|
461
|
+
audio.muted = mutedIsControlled ? Boolean(mutedProp) : stored.muted;
|
|
462
|
+
}, [audio]);
|
|
463
|
+
react.useEffect(() => {
|
|
464
|
+
if (!volumeIsControlled || initialVolume === void 0) return;
|
|
465
|
+
audio.volume = clamp(initialVolume, 0, 1);
|
|
466
|
+
}, [audio, volumeIsControlled, initialVolume]);
|
|
467
|
+
react.useEffect(() => {
|
|
468
|
+
if (!mutedIsControlled || mutedProp === void 0) return;
|
|
469
|
+
audio.muted = mutedProp;
|
|
470
|
+
}, [audio, mutedIsControlled, mutedProp]);
|
|
471
|
+
react.useEffect(() => {
|
|
472
|
+
if (volumeIsControlled && mutedIsControlled) return;
|
|
473
|
+
return subscribePreferences((prefs) => {
|
|
474
|
+
if (!volumeIsControlled && audio.volume !== prefs.volume) {
|
|
475
|
+
audio.volume = prefs.volume;
|
|
476
|
+
}
|
|
477
|
+
if (!mutedIsControlled && audio.muted !== prefs.muted) {
|
|
478
|
+
audio.muted = prefs.muted;
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}, [audio, volumeIsControlled, mutedIsControlled]);
|
|
482
|
+
react.useEffect(() => {
|
|
483
|
+
setActivePeaks(peaksProp ?? getPeaksFromCache(src));
|
|
484
|
+
}, [src, peaksProp]);
|
|
485
|
+
react.useEffect(() => {
|
|
486
|
+
const handlePlay = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => onPlay?.(), "handlePlay");
|
|
487
|
+
const handlePause = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => onPause?.(), "handlePause");
|
|
488
|
+
const handleEnded = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => onEnded?.(), "handleEnded");
|
|
489
|
+
const handleError = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((e) => {
|
|
490
|
+
const code = audio.error?.code ?? null;
|
|
491
|
+
const reason = code === 2 ? "network" : code === 3 ? "decode" : code === 4 ? "unsupported" : "unknown";
|
|
492
|
+
onError?.(reason, e);
|
|
493
|
+
}, "handleError");
|
|
494
|
+
audio.addEventListener("play", handlePlay);
|
|
495
|
+
audio.addEventListener("pause", handlePause);
|
|
496
|
+
audio.addEventListener("ended", handleEnded);
|
|
497
|
+
audio.addEventListener("error", handleError);
|
|
498
|
+
return () => {
|
|
499
|
+
audio.removeEventListener("play", handlePlay);
|
|
500
|
+
audio.removeEventListener("pause", handlePause);
|
|
501
|
+
audio.removeEventListener("ended", handleEnded);
|
|
502
|
+
audio.removeEventListener("error", handleError);
|
|
503
|
+
};
|
|
504
|
+
}, [audio, onEnded, onError, onPause, onPlay]);
|
|
505
|
+
const exclusiveRef = react.useRef(exclusive);
|
|
506
|
+
exclusiveRef.current = exclusive;
|
|
507
|
+
const playerIdRef = react.useRef(playerId);
|
|
508
|
+
playerIdRef.current = playerId;
|
|
509
|
+
react.useEffect(() => {
|
|
510
|
+
if (!exclusive) return;
|
|
511
|
+
const unregister = registerPlayer(playerId, () => {
|
|
512
|
+
audio.pause();
|
|
513
|
+
});
|
|
514
|
+
const onPlayHandler = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => setActivePlayer(playerId), "onPlayHandler");
|
|
515
|
+
audio.addEventListener("play", onPlayHandler);
|
|
516
|
+
return () => {
|
|
517
|
+
audio.removeEventListener("play", onPlayHandler);
|
|
518
|
+
unregister();
|
|
519
|
+
};
|
|
520
|
+
}, [audio, exclusive, playerId]);
|
|
521
|
+
react.useEffect(() => {
|
|
522
|
+
return () => {
|
|
523
|
+
try {
|
|
524
|
+
audio.pause();
|
|
525
|
+
audio.removeAttribute("src");
|
|
526
|
+
audio.load();
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
}, [audio]);
|
|
531
|
+
const controls = react.useMemo(() => {
|
|
532
|
+
const playFn = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(async () => {
|
|
533
|
+
if (exclusiveRef.current) setActivePlayer(playerIdRef.current);
|
|
534
|
+
try {
|
|
535
|
+
await unlockAudioContext();
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
await audio.play();
|
|
540
|
+
} catch (e) {
|
|
541
|
+
const isAbort = e instanceof DOMException && e.name === "AbortError";
|
|
542
|
+
if (!isAbort) onError?.("unknown", e);
|
|
543
|
+
}
|
|
544
|
+
}, "playFn");
|
|
545
|
+
const pauseFn = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
546
|
+
audio.pause();
|
|
547
|
+
}, "pauseFn");
|
|
548
|
+
return {
|
|
549
|
+
play: playFn,
|
|
550
|
+
pause: pauseFn,
|
|
551
|
+
toggle: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(async () => {
|
|
552
|
+
if (audio.paused || audio.ended) await playFn();
|
|
553
|
+
else pauseFn();
|
|
554
|
+
}, "toggle"),
|
|
555
|
+
seek: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((seconds) => {
|
|
556
|
+
if (!Number.isFinite(seconds)) return;
|
|
557
|
+
const dur = Number.isFinite(audio.duration) ? audio.duration : 0;
|
|
558
|
+
audio.currentTime = clamp(seconds, 0, dur || seconds);
|
|
559
|
+
}, "seek"),
|
|
560
|
+
seekTo: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((ratio) => {
|
|
561
|
+
const r = clamp(ratio, 0, 1);
|
|
562
|
+
const dur = Number.isFinite(audio.duration) ? audio.duration : 0;
|
|
563
|
+
if (dur > 0) audio.currentTime = r * dur;
|
|
564
|
+
}, "seekTo"),
|
|
565
|
+
setVolume: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((v) => {
|
|
566
|
+
const next = clamp(v, 0, 1);
|
|
567
|
+
audio.volume = next;
|
|
568
|
+
if (next > 0 && audio.muted) audio.muted = false;
|
|
569
|
+
if (!volumeIsControlled) setStoredVolume(next);
|
|
570
|
+
if (!mutedIsControlled && next > 0) setStoredMuted(false);
|
|
571
|
+
}, "setVolume"),
|
|
572
|
+
toggleMute: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
573
|
+
const next = !audio.muted;
|
|
574
|
+
audio.muted = next;
|
|
575
|
+
if (!mutedIsControlled) setStoredMuted(next);
|
|
576
|
+
}, "toggleMute"),
|
|
577
|
+
toggleLoop: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
578
|
+
audio.loop = !audio.loop;
|
|
579
|
+
}, "toggleLoop")
|
|
580
|
+
};
|
|
581
|
+
}, [audio, volumeIsControlled, mutedIsControlled]);
|
|
582
|
+
react.useEffect(() => {
|
|
583
|
+
if (!autoplay) return;
|
|
584
|
+
void controls.play();
|
|
585
|
+
}, [autoplay, controls]);
|
|
586
|
+
const state = useAudioElementState(source, Boolean(audio.src), activePeaks);
|
|
587
|
+
const meta = react.useMemo(
|
|
588
|
+
() => ({
|
|
589
|
+
src,
|
|
590
|
+
title,
|
|
591
|
+
artist,
|
|
592
|
+
album,
|
|
593
|
+
cover,
|
|
594
|
+
hasPrev: Boolean(onPrev),
|
|
595
|
+
hasNext: Boolean(onNext)
|
|
596
|
+
}),
|
|
597
|
+
[src, title, artist, album, cover, onPrev, onNext]
|
|
598
|
+
);
|
|
599
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AudioRefCtx.Provider, { value: audio, children: /* @__PURE__ */ jsxRuntime.jsx(MetaCtx.Provider, { value: meta, children: /* @__PURE__ */ jsxRuntime.jsx(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsxRuntime.jsx(ControlsCtx.Provider, { value: controls, children: /* @__PURE__ */ jsxRuntime.jsx(LevelsCtx.Provider, { value: levelsStore, children }) }) }) }) });
|
|
600
|
+
}
|
|
601
|
+
chunkPK6SKIKE_cjs.__name(PlayerProvider, "PlayerProvider");
|
|
602
|
+
function useCtxOrThrow(ctx, name) {
|
|
603
|
+
const value = react.useContext(ctx);
|
|
604
|
+
if (value === null) {
|
|
605
|
+
throw new Error(`${name} must be used inside <PlayerProvider>`);
|
|
606
|
+
}
|
|
607
|
+
return value;
|
|
608
|
+
}
|
|
609
|
+
chunkPK6SKIKE_cjs.__name(useCtxOrThrow, "useCtxOrThrow");
|
|
610
|
+
var usePlayerState = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => useCtxOrThrow(StateCtx, "usePlayerState"), "usePlayerState");
|
|
611
|
+
var usePlayerControls = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => useCtxOrThrow(ControlsCtx, "usePlayerControls"), "usePlayerControls");
|
|
612
|
+
var usePlayerLevels = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => useCtxOrThrow(LevelsCtx, "usePlayerLevels"), "usePlayerLevels");
|
|
613
|
+
var usePlayerMeta = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => useCtxOrThrow(MetaCtx, "usePlayerMeta"), "usePlayerMeta");
|
|
614
|
+
var usePlayerAudio = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => useCtxOrThrow(AudioRefCtx, "usePlayerAudio"), "usePlayerAudio");
|
|
615
|
+
var usePlayerPaused = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
616
|
+
const s = usePlayerState();
|
|
617
|
+
return s.kind !== "playing";
|
|
618
|
+
}, "usePlayerPaused");
|
|
619
|
+
var usePlayerDuration = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
620
|
+
const s = usePlayerState();
|
|
621
|
+
return "duration" in s ? s.duration : 0;
|
|
622
|
+
}, "usePlayerDuration");
|
|
623
|
+
function useElementWidth(el) {
|
|
624
|
+
const [w, setW] = react.useState(0);
|
|
625
|
+
react.useEffect(() => {
|
|
626
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
627
|
+
setW(el.clientWidth);
|
|
628
|
+
const obs = new ResizeObserver((entries) => {
|
|
629
|
+
for (const entry of entries) {
|
|
630
|
+
const next = entry.contentRect.width;
|
|
631
|
+
setW((prev) => Math.abs(prev - next) < 0.5 ? prev : next);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
obs.observe(el);
|
|
635
|
+
return () => obs.disconnect();
|
|
636
|
+
}, [el]);
|
|
637
|
+
return w;
|
|
638
|
+
}
|
|
639
|
+
chunkPK6SKIKE_cjs.__name(useElementWidth, "useElementWidth");
|
|
640
|
+
var OPTS = { preventDefault: true };
|
|
641
|
+
var BINDINGS = [
|
|
642
|
+
{ label: "Play / pause", hint: "Space" },
|
|
643
|
+
{ label: "Seek +5s", hint: "\u2192" },
|
|
644
|
+
{ label: "Seek \u22125s", hint: "\u2190" },
|
|
645
|
+
{ label: "Volume up", hint: "\u2191" },
|
|
646
|
+
{ label: "Volume down", hint: "\u2193" },
|
|
647
|
+
{ label: "Toggle mute", hint: "M" },
|
|
648
|
+
{ label: "Toggle loop", hint: "L" }
|
|
649
|
+
];
|
|
650
|
+
function useKeyboardShortcuts({
|
|
651
|
+
audio,
|
|
652
|
+
controls,
|
|
653
|
+
enabled = true
|
|
654
|
+
}) {
|
|
655
|
+
const refToggle = hooks.useHotkey(["space", "k"], () => void controls.toggle(), {
|
|
656
|
+
...OPTS,
|
|
657
|
+
enabled,
|
|
658
|
+
description: "Play / pause"
|
|
659
|
+
});
|
|
660
|
+
const refForward = hooks.useHotkey("right", () => controls.seek(audio.currentTime + 5), {
|
|
661
|
+
...OPTS,
|
|
662
|
+
enabled,
|
|
663
|
+
description: "Seek +5s"
|
|
664
|
+
});
|
|
665
|
+
const refBackward = hooks.useHotkey("left", () => controls.seek(audio.currentTime - 5), {
|
|
666
|
+
...OPTS,
|
|
667
|
+
enabled,
|
|
668
|
+
description: "Seek \u22125s"
|
|
669
|
+
});
|
|
670
|
+
const refVolUp = hooks.useHotkey("up", () => controls.setVolume(clamp(audio.volume + 0.05, 0, 1)), {
|
|
671
|
+
...OPTS,
|
|
672
|
+
enabled,
|
|
673
|
+
description: "Volume up"
|
|
674
|
+
});
|
|
675
|
+
const refVolDown = hooks.useHotkey("down", () => controls.setVolume(clamp(audio.volume - 0.05, 0, 1)), {
|
|
676
|
+
...OPTS,
|
|
677
|
+
enabled,
|
|
678
|
+
description: "Volume down"
|
|
679
|
+
});
|
|
680
|
+
const refMute = hooks.useHotkey("m", () => controls.toggleMute(), {
|
|
681
|
+
...OPTS,
|
|
682
|
+
enabled,
|
|
683
|
+
description: "Toggle mute"
|
|
684
|
+
});
|
|
685
|
+
const refLoop = hooks.useHotkey("l", () => controls.toggleLoop(), {
|
|
686
|
+
...OPTS,
|
|
687
|
+
enabled,
|
|
688
|
+
description: "Toggle loop"
|
|
689
|
+
});
|
|
690
|
+
const ref = react.useCallback(
|
|
691
|
+
(instance) => {
|
|
692
|
+
refToggle(instance);
|
|
693
|
+
refForward(instance);
|
|
694
|
+
refBackward(instance);
|
|
695
|
+
refVolUp(instance);
|
|
696
|
+
refVolDown(instance);
|
|
697
|
+
refMute(instance);
|
|
698
|
+
refLoop(instance);
|
|
699
|
+
},
|
|
700
|
+
[refToggle, refForward, refBackward, refVolUp, refVolDown, refMute, refLoop]
|
|
701
|
+
);
|
|
702
|
+
return { ref, bindings: BINDINGS };
|
|
703
|
+
}
|
|
704
|
+
chunkPK6SKIKE_cjs.__name(useKeyboardShortcuts, "useKeyboardShortcuts");
|
|
705
|
+
var ACTIONS = [
|
|
706
|
+
"play",
|
|
707
|
+
"pause",
|
|
708
|
+
"previoustrack",
|
|
709
|
+
"nexttrack",
|
|
710
|
+
"seekbackward",
|
|
711
|
+
"seekforward"
|
|
712
|
+
];
|
|
713
|
+
function useMediaSession(audio, meta, controls, onPrev, onNext) {
|
|
714
|
+
react.useEffect(() => {
|
|
715
|
+
if (typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
|
|
716
|
+
const ms = navigator.mediaSession;
|
|
717
|
+
try {
|
|
718
|
+
ms.metadata = new MediaMetadata({
|
|
719
|
+
title: meta.title ?? "",
|
|
720
|
+
artist: meta.artist ?? "",
|
|
721
|
+
album: meta.album ?? "",
|
|
722
|
+
artwork: meta.cover ? [{ src: meta.cover, sizes: "512x512", type: "image/jpeg" }] : []
|
|
723
|
+
});
|
|
724
|
+
} catch {
|
|
725
|
+
}
|
|
726
|
+
const handlers = {
|
|
727
|
+
play: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
728
|
+
void controls.play();
|
|
729
|
+
}, "play"),
|
|
730
|
+
pause: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => controls.pause(), "pause"),
|
|
731
|
+
previoustrack: onPrev ?? null,
|
|
732
|
+
nexttrack: onNext ?? null,
|
|
733
|
+
seekbackward: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((details) => {
|
|
734
|
+
const offset = details.seekOffset ?? 10;
|
|
735
|
+
controls.seek(audio.currentTime - offset);
|
|
736
|
+
}, "seekbackward"),
|
|
737
|
+
seekforward: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((details) => {
|
|
738
|
+
const offset = details.seekOffset ?? 10;
|
|
739
|
+
controls.seek(audio.currentTime + offset);
|
|
740
|
+
}, "seekforward")
|
|
741
|
+
};
|
|
742
|
+
for (const a of ACTIONS) {
|
|
743
|
+
try {
|
|
744
|
+
ms.setActionHandler(a, handlers[a] ?? null);
|
|
745
|
+
} catch {
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return () => {
|
|
749
|
+
for (const a of ACTIONS) {
|
|
750
|
+
try {
|
|
751
|
+
ms.setActionHandler(a, null);
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
}, [audio, controls, meta.title, meta.artist, meta.album, meta.cover, onPrev, onNext]);
|
|
757
|
+
}
|
|
758
|
+
chunkPK6SKIKE_cjs.__name(useMediaSession, "useMediaSession");
|
|
759
|
+
function PlayButton({ size = "default" }) {
|
|
760
|
+
const state = usePlayerState();
|
|
761
|
+
const { toggle, play } = usePlayerControls();
|
|
762
|
+
const dim = size === "compact" ? 28 : 36;
|
|
763
|
+
const icon = size === "compact" ? 14 : 16;
|
|
764
|
+
let label = "Play";
|
|
765
|
+
let Icon = lucideReact.Play;
|
|
766
|
+
let onClick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => void toggle(), "onClick");
|
|
767
|
+
let disabled = false;
|
|
768
|
+
switch (state.kind) {
|
|
769
|
+
case "idle":
|
|
770
|
+
Icon = lucideReact.Play;
|
|
771
|
+
disabled = true;
|
|
772
|
+
break;
|
|
773
|
+
case "loading":
|
|
774
|
+
case "decoding":
|
|
775
|
+
Icon = lucideReact.LoaderCircle;
|
|
776
|
+
label = "Loading";
|
|
777
|
+
disabled = true;
|
|
778
|
+
break;
|
|
779
|
+
case "playing":
|
|
780
|
+
Icon = lucideReact.Pause;
|
|
781
|
+
label = "Pause";
|
|
782
|
+
break;
|
|
783
|
+
case "paused":
|
|
784
|
+
Icon = lucideReact.Play;
|
|
785
|
+
label = "Play";
|
|
786
|
+
break;
|
|
787
|
+
case "ended":
|
|
788
|
+
Icon = lucideReact.RotateCcw;
|
|
789
|
+
label = "Replay";
|
|
790
|
+
onClick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => void play(), "onClick");
|
|
791
|
+
break;
|
|
792
|
+
case "error":
|
|
793
|
+
Icon = lucideReact.Play;
|
|
794
|
+
label = "Retry";
|
|
795
|
+
onClick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => void play(), "onClick");
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
798
|
+
const button = /* @__PURE__ */ jsxRuntime.jsx(
|
|
799
|
+
"button",
|
|
800
|
+
{
|
|
801
|
+
type: "button",
|
|
802
|
+
onClick,
|
|
803
|
+
disabled,
|
|
804
|
+
"aria-label": label,
|
|
805
|
+
className: "audioplayer-press grid place-items-center rounded-full bg-foreground text-background transition-colors hover:bg-foreground/90 disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60",
|
|
806
|
+
style: { width: dim, height: dim },
|
|
807
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
808
|
+
Icon,
|
|
809
|
+
{
|
|
810
|
+
size: icon,
|
|
811
|
+
strokeWidth: 2,
|
|
812
|
+
className: state.kind === "loading" || state.kind === "decoding" ? "animate-spin" : ""
|
|
813
|
+
}
|
|
814
|
+
)
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
818
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: button }),
|
|
819
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipContent, { side: "top", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
|
|
820
|
+
label,
|
|
821
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "rounded border border-border/40 bg-muted px-1 font-mono text-[10px] text-muted-foreground", children: "Space" })
|
|
822
|
+
] }) })
|
|
823
|
+
] });
|
|
824
|
+
}
|
|
825
|
+
chunkPK6SKIKE_cjs.__name(PlayButton, "PlayButton");
|
|
826
|
+
var IconButton = react.forwardRef(/* @__PURE__ */ chunkPK6SKIKE_cjs.__name(function IconButton2({ label, shortcut, active, children, noTooltip, className = "", ...rest }, ref) {
|
|
827
|
+
const stateClasses = active ? "bg-primary/10 text-primary hover:bg-primary/15" : "text-muted-foreground hover:bg-accent hover:text-foreground";
|
|
828
|
+
const button = /* @__PURE__ */ jsxRuntime.jsx(
|
|
829
|
+
"button",
|
|
830
|
+
{
|
|
831
|
+
ref,
|
|
832
|
+
type: "button",
|
|
833
|
+
"aria-label": label,
|
|
834
|
+
"aria-pressed": active,
|
|
835
|
+
className: `audioplayer-press grid h-8 w-8 place-items-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 disabled:opacity-50 disabled:pointer-events-none ${stateClasses} ${className}`,
|
|
836
|
+
...rest,
|
|
837
|
+
children
|
|
838
|
+
}
|
|
839
|
+
);
|
|
840
|
+
if (noTooltip) return button;
|
|
841
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(components.Tooltip, { children: [
|
|
842
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipTrigger, { asChild: true, children: button }),
|
|
843
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.TooltipContent, { side: "top", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
|
|
844
|
+
label,
|
|
845
|
+
shortcut && /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "rounded border border-border/40 bg-muted px-1 font-mono text-[10px] text-muted-foreground", children: shortcut })
|
|
846
|
+
] }) })
|
|
847
|
+
] });
|
|
848
|
+
}, "IconButton"));
|
|
849
|
+
function SkipButton({ direction, onClick }) {
|
|
850
|
+
if (!onClick) return null;
|
|
851
|
+
const Icon = direction === "prev" ? lucideReact.SkipBack : lucideReact.SkipForward;
|
|
852
|
+
const label = direction === "prev" ? "Previous track" : "Next track";
|
|
853
|
+
return /* @__PURE__ */ jsxRuntime.jsx(IconButton, { label, shortcut: direction === "prev" ? "\u2190" : "\u2192", onClick, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 16, strokeWidth: 1.75 }) });
|
|
854
|
+
}
|
|
855
|
+
chunkPK6SKIKE_cjs.__name(SkipButton, "SkipButton");
|
|
856
|
+
function LoopButton() {
|
|
857
|
+
const audio = usePlayerAudio();
|
|
858
|
+
const { toggleLoop } = usePlayerControls();
|
|
859
|
+
const [loop, setLoop] = react.useState(audio.loop);
|
|
860
|
+
react.useEffect(() => {
|
|
861
|
+
setLoop(audio.loop);
|
|
862
|
+
}, [audio]);
|
|
863
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
864
|
+
IconButton,
|
|
865
|
+
{
|
|
866
|
+
label: loop ? "Disable loop" : "Enable loop",
|
|
867
|
+
shortcut: "L",
|
|
868
|
+
active: loop,
|
|
869
|
+
onClick: () => {
|
|
870
|
+
toggleLoop();
|
|
871
|
+
setLoop(audio.loop);
|
|
872
|
+
},
|
|
873
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Repeat, { size: 16, strokeWidth: 1.75 })
|
|
874
|
+
}
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
chunkPK6SKIKE_cjs.__name(LoopButton, "LoopButton");
|
|
878
|
+
function isIosSafari() {
|
|
879
|
+
if (typeof navigator === "undefined") return false;
|
|
880
|
+
const ua = navigator.userAgent;
|
|
881
|
+
const iOS = /iPad|iPhone|iPod/.test(ua) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
|
|
882
|
+
return iOS;
|
|
883
|
+
}
|
|
884
|
+
chunkPK6SKIKE_cjs.__name(isIosSafari, "isIosSafari");
|
|
885
|
+
var HIDE_VOLUME = isIosSafari();
|
|
886
|
+
function VolumeControl() {
|
|
887
|
+
const audio = usePlayerAudio();
|
|
888
|
+
const { setVolume, toggleMute } = usePlayerControls();
|
|
889
|
+
const [volume, setVol] = react.useState(audio.volume);
|
|
890
|
+
const [muted, setMuted] = react.useState(audio.muted);
|
|
891
|
+
const [isOpen, setOpen] = react.useState(false);
|
|
892
|
+
react.useEffect(() => {
|
|
893
|
+
const sync = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
894
|
+
setVol(audio.volume);
|
|
895
|
+
setMuted(audio.muted);
|
|
896
|
+
}, "sync");
|
|
897
|
+
audio.addEventListener("volumechange", sync);
|
|
898
|
+
return () => audio.removeEventListener("volumechange", sync);
|
|
899
|
+
}, [audio]);
|
|
900
|
+
if (HIDE_VOLUME) {
|
|
901
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
902
|
+
IconButton,
|
|
903
|
+
{
|
|
904
|
+
label: muted ? "Unmute" : "Mute",
|
|
905
|
+
shortcut: "M",
|
|
906
|
+
active: muted,
|
|
907
|
+
onClick: () => {
|
|
908
|
+
toggleMute();
|
|
909
|
+
setMuted(audio.muted);
|
|
910
|
+
},
|
|
911
|
+
children: muted || volume === 0 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VolumeX, { size: 16, strokeWidth: 1.75 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Volume2, { size: 16, strokeWidth: 1.75 })
|
|
912
|
+
}
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
const Icon = muted || volume === 0 ? lucideReact.VolumeX : lucideReact.Volume2;
|
|
916
|
+
const handleChange = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((v) => {
|
|
917
|
+
setVolume(v);
|
|
918
|
+
setVol(v);
|
|
919
|
+
if (v > 0) setMuted(false);
|
|
920
|
+
}, "handleChange");
|
|
921
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(components.Popover, { open: isOpen, onOpenChange: setOpen, children: [
|
|
922
|
+
/* @__PURE__ */ jsxRuntime.jsx(components.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
923
|
+
IconButton,
|
|
924
|
+
{
|
|
925
|
+
label: isOpen ? "Close volume" : "Volume",
|
|
926
|
+
noTooltip: isOpen,
|
|
927
|
+
active: muted,
|
|
928
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 16, strokeWidth: 1.75 })
|
|
929
|
+
}
|
|
930
|
+
) }),
|
|
931
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
932
|
+
components.PopoverContent,
|
|
933
|
+
{
|
|
934
|
+
side: "top",
|
|
935
|
+
align: "center",
|
|
936
|
+
sideOffset: 6,
|
|
937
|
+
className: "flex w-14! flex-col items-center gap-2 p-3!",
|
|
938
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
939
|
+
children: [
|
|
940
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-full text-center tabular-nums text-[10px] text-muted-foreground", children: Math.round((muted ? 0 : volume) * 100) }),
|
|
941
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
942
|
+
components.Slider,
|
|
943
|
+
{
|
|
944
|
+
orientation: "vertical",
|
|
945
|
+
min: 0,
|
|
946
|
+
max: 1,
|
|
947
|
+
step: 0.01,
|
|
948
|
+
value: [muted ? 0 : volume],
|
|
949
|
+
onValueChange: ([v]) => handleChange(v),
|
|
950
|
+
"aria-label": "Volume",
|
|
951
|
+
className: "h-32"
|
|
952
|
+
}
|
|
953
|
+
)
|
|
954
|
+
]
|
|
955
|
+
}
|
|
956
|
+
)
|
|
957
|
+
] });
|
|
958
|
+
}
|
|
959
|
+
chunkPK6SKIKE_cjs.__name(VolumeControl, "VolumeControl");
|
|
960
|
+
|
|
961
|
+
// src/tools/media/AudioPlayer/utils/formatTime.ts
|
|
962
|
+
function formatTime(seconds) {
|
|
963
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
|
|
964
|
+
const total = Math.floor(seconds);
|
|
965
|
+
const s = total % 60;
|
|
966
|
+
const m = Math.floor(total / 60) % 60;
|
|
967
|
+
const h = Math.floor(total / 3600);
|
|
968
|
+
const ss = s.toString().padStart(2, "0");
|
|
969
|
+
if (h > 0) {
|
|
970
|
+
const mm = m.toString().padStart(2, "0");
|
|
971
|
+
return `${h}:${mm}:${ss}`;
|
|
972
|
+
}
|
|
973
|
+
return `${m}:${ss}`;
|
|
974
|
+
}
|
|
975
|
+
chunkPK6SKIKE_cjs.__name(formatTime, "formatTime");
|
|
976
|
+
var READ_INTERVAL_MS = 200;
|
|
977
|
+
function TimeDisplay() {
|
|
978
|
+
const audio = usePlayerAudio();
|
|
979
|
+
const duration = usePlayerDuration();
|
|
980
|
+
const currentRef = react.useRef(null);
|
|
981
|
+
react.useEffect(() => {
|
|
982
|
+
const el = currentRef.current;
|
|
983
|
+
if (!el) return;
|
|
984
|
+
let last = -1;
|
|
985
|
+
const write = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
986
|
+
const t = audio.currentTime;
|
|
987
|
+
if (Math.abs(t - last) < 0.5) return;
|
|
988
|
+
last = t;
|
|
989
|
+
el.textContent = formatTime(t);
|
|
990
|
+
}, "write");
|
|
991
|
+
write();
|
|
992
|
+
const onSeek = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => write(), "onSeek");
|
|
993
|
+
audio.addEventListener("seeked", onSeek);
|
|
994
|
+
audio.addEventListener("timeupdate", onSeek);
|
|
995
|
+
const timer = setInterval(() => {
|
|
996
|
+
if (!audio.paused) write();
|
|
997
|
+
}, READ_INTERVAL_MS);
|
|
998
|
+
return () => {
|
|
999
|
+
audio.removeEventListener("seeked", onSeek);
|
|
1000
|
+
audio.removeEventListener("timeupdate", onSeek);
|
|
1001
|
+
clearInterval(timer);
|
|
1002
|
+
};
|
|
1003
|
+
}, [audio]);
|
|
1004
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tabular-nums text-xs text-muted-foreground", children: [
|
|
1005
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { ref: currentRef, children: formatTime(audio.currentTime) }),
|
|
1006
|
+
" / ",
|
|
1007
|
+
formatTime(duration)
|
|
1008
|
+
] });
|
|
1009
|
+
}
|
|
1010
|
+
chunkPK6SKIKE_cjs.__name(TimeDisplay, "TimeDisplay");
|
|
1011
|
+
function ControlsRow({ onPrev, onNext, showTime = false }) {
|
|
1012
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1013
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1014
|
+
/* @__PURE__ */ jsxRuntime.jsx(SkipButton, { direction: "prev", onClick: onPrev }),
|
|
1015
|
+
/* @__PURE__ */ jsxRuntime.jsx(PlayButton, {}),
|
|
1016
|
+
/* @__PURE__ */ jsxRuntime.jsx(SkipButton, { direction: "next", onClick: onNext })
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1019
|
+
showTime && /* @__PURE__ */ jsxRuntime.jsx(TimeDisplay, {}),
|
|
1020
|
+
/* @__PURE__ */ jsxRuntime.jsx(VolumeControl, {}),
|
|
1021
|
+
/* @__PURE__ */ jsxRuntime.jsx(LoopButton, {})
|
|
1022
|
+
] })
|
|
1023
|
+
] });
|
|
1024
|
+
}
|
|
1025
|
+
chunkPK6SKIKE_cjs.__name(ControlsRow, "ControlsRow");
|
|
1026
|
+
function CoverPlaceholder({ size }) {
|
|
1027
|
+
const inset = Math.max(4, Math.round(size * 0.14));
|
|
1028
|
+
const iconSize = Math.round(size * 0.4);
|
|
1029
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1030
|
+
"div",
|
|
1031
|
+
{
|
|
1032
|
+
className: "relative grid place-items-center rounded-md bg-muted",
|
|
1033
|
+
style: { width: size, height: size },
|
|
1034
|
+
"aria-hidden": "true",
|
|
1035
|
+
children: [
|
|
1036
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1037
|
+
"span",
|
|
1038
|
+
{
|
|
1039
|
+
className: "absolute rounded-sm bg-muted-foreground/10",
|
|
1040
|
+
style: { inset }
|
|
1041
|
+
}
|
|
1042
|
+
),
|
|
1043
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1044
|
+
lucideReact.Music,
|
|
1045
|
+
{
|
|
1046
|
+
className: "relative text-muted-foreground",
|
|
1047
|
+
style: { width: iconSize, height: iconSize },
|
|
1048
|
+
strokeWidth: 1.5
|
|
1049
|
+
}
|
|
1050
|
+
)
|
|
1051
|
+
]
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
chunkPK6SKIKE_cjs.__name(CoverPlaceholder, "CoverPlaceholder");
|
|
1056
|
+
function Cover({ src, alt, size }) {
|
|
1057
|
+
const [errored, setErrored] = react.useState(false);
|
|
1058
|
+
if (!src || errored) return /* @__PURE__ */ jsxRuntime.jsx(CoverPlaceholder, { size });
|
|
1059
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1060
|
+
"img",
|
|
1061
|
+
{
|
|
1062
|
+
src,
|
|
1063
|
+
alt: alt ?? "",
|
|
1064
|
+
width: size,
|
|
1065
|
+
height: size,
|
|
1066
|
+
loading: "lazy",
|
|
1067
|
+
decoding: "async",
|
|
1068
|
+
onError: () => setErrored(true),
|
|
1069
|
+
className: "block rounded-md object-cover",
|
|
1070
|
+
style: { width: size, height: size }
|
|
1071
|
+
}
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
chunkPK6SKIKE_cjs.__name(Cover, "Cover");
|
|
1075
|
+
var VAR = "--audioplayer-pulse";
|
|
1076
|
+
var MAX_SCALE = 0.03;
|
|
1077
|
+
var SMOOTH = 0.18;
|
|
1078
|
+
function ReactivePulse({ enabled, children }) {
|
|
1079
|
+
const ref = react.useRef(null);
|
|
1080
|
+
const store = usePlayerLevels();
|
|
1081
|
+
react.useEffect(() => {
|
|
1082
|
+
if (!enabled) {
|
|
1083
|
+
ref.current?.style.setProperty(VAR, "1");
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const el = ref.current;
|
|
1087
|
+
if (!el) return;
|
|
1088
|
+
if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
|
1089
|
+
el.style.setProperty(VAR, "1");
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
let raf = 0;
|
|
1093
|
+
let env = 0;
|
|
1094
|
+
const tick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1095
|
+
const buf = store.getCurrent();
|
|
1096
|
+
let energy = 0;
|
|
1097
|
+
const usable = Math.min(buf.length, 32);
|
|
1098
|
+
if (usable > 0) {
|
|
1099
|
+
for (let i = 0; i < usable; i++) energy += buf[i];
|
|
1100
|
+
energy /= usable;
|
|
1101
|
+
}
|
|
1102
|
+
env = env + (energy - env) * SMOOTH;
|
|
1103
|
+
const scale = 1 + Math.min(MAX_SCALE, env * MAX_SCALE * 1.5);
|
|
1104
|
+
el.style.setProperty(VAR, scale.toFixed(4));
|
|
1105
|
+
raf = requestAnimationFrame(tick);
|
|
1106
|
+
}, "tick");
|
|
1107
|
+
raf = requestAnimationFrame(tick);
|
|
1108
|
+
return () => cancelAnimationFrame(raf);
|
|
1109
|
+
}, [enabled, store]);
|
|
1110
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1111
|
+
"div",
|
|
1112
|
+
{
|
|
1113
|
+
ref,
|
|
1114
|
+
className: "audioplayer-pulse",
|
|
1115
|
+
style: {
|
|
1116
|
+
transform: "scale(var(--audioplayer-pulse, 1))",
|
|
1117
|
+
transformOrigin: "center",
|
|
1118
|
+
willChange: enabled ? "transform" : void 0
|
|
1119
|
+
},
|
|
1120
|
+
children
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
chunkPK6SKIKE_cjs.__name(ReactivePulse, "ReactivePulse");
|
|
1125
|
+
var REASONS = {
|
|
1126
|
+
network: "Network error while loading audio.",
|
|
1127
|
+
decode: "We can't decode this audio.",
|
|
1128
|
+
unsupported: "This audio format is not supported.",
|
|
1129
|
+
unknown: "Audio playback failed."
|
|
1130
|
+
};
|
|
1131
|
+
function ErrorState() {
|
|
1132
|
+
const state = usePlayerState();
|
|
1133
|
+
const { play } = usePlayerControls();
|
|
1134
|
+
if (state.kind !== "error") return null;
|
|
1135
|
+
const message = REASONS[state.reason] ?? REASONS.unknown;
|
|
1136
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1137
|
+
"div",
|
|
1138
|
+
{
|
|
1139
|
+
role: "alert",
|
|
1140
|
+
className: "flex items-center gap-3 rounded-md bg-destructive/10 px-3 py-2 text-xs text-destructive",
|
|
1141
|
+
children: [
|
|
1142
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: 14, strokeWidth: 1.75 }),
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: message }),
|
|
1144
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1145
|
+
"button",
|
|
1146
|
+
{
|
|
1147
|
+
type: "button",
|
|
1148
|
+
onClick: () => void play(),
|
|
1149
|
+
className: "audioplayer-press inline-flex items-center gap-1 rounded-md px-2 py-1 text-destructive hover:bg-destructive/15 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive/40",
|
|
1150
|
+
children: [
|
|
1151
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 12, strokeWidth: 2 }),
|
|
1152
|
+
"Retry"
|
|
1153
|
+
]
|
|
1154
|
+
}
|
|
1155
|
+
)
|
|
1156
|
+
]
|
|
1157
|
+
}
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
chunkPK6SKIKE_cjs.__name(ErrorState, "ErrorState");
|
|
1161
|
+
function Title() {
|
|
1162
|
+
const { title } = usePlayerMeta();
|
|
1163
|
+
if (!title) return null;
|
|
1164
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", title, children: title });
|
|
1165
|
+
}
|
|
1166
|
+
chunkPK6SKIKE_cjs.__name(Title, "Title");
|
|
1167
|
+
function Artist() {
|
|
1168
|
+
const { artist, album } = usePlayerMeta();
|
|
1169
|
+
if (!artist && !album) return null;
|
|
1170
|
+
const text = [artist, album].filter(Boolean).join(" \xB7 ");
|
|
1171
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-muted-foreground", title: text, children: text });
|
|
1172
|
+
}
|
|
1173
|
+
chunkPK6SKIKE_cjs.__name(Artist, "Artist");
|
|
1174
|
+
function usePeaks(opts) {
|
|
1175
|
+
const { src, enabled = true, triggerRef, decodeOnMount = false } = opts;
|
|
1176
|
+
const cached2 = getPeaksFromCache(src) ?? null;
|
|
1177
|
+
const [peaks, setLocal] = react.useState(cached2);
|
|
1178
|
+
const [loading, setLoading] = react.useState(!cached2 && enabled);
|
|
1179
|
+
const [error, setError] = react.useState(null);
|
|
1180
|
+
react.useEffect(() => {
|
|
1181
|
+
if (!enabled) return;
|
|
1182
|
+
const hit = getPeaksFromCache(src);
|
|
1183
|
+
if (hit) {
|
|
1184
|
+
setLocal(hit);
|
|
1185
|
+
setLoading(false);
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
setLocal(null);
|
|
1189
|
+
setLoading(true);
|
|
1190
|
+
let cancelled = false;
|
|
1191
|
+
let started = false;
|
|
1192
|
+
const startDecode = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1193
|
+
if (started) return;
|
|
1194
|
+
started = true;
|
|
1195
|
+
getPeaks(src).then((p) => {
|
|
1196
|
+
if (cancelled) return;
|
|
1197
|
+
setLocal(p);
|
|
1198
|
+
setLoading(false);
|
|
1199
|
+
}).catch((e) => {
|
|
1200
|
+
if (cancelled) return;
|
|
1201
|
+
setError(e);
|
|
1202
|
+
setLoading(false);
|
|
1203
|
+
});
|
|
1204
|
+
}, "startDecode");
|
|
1205
|
+
if (decodeOnMount || !triggerRef?.current || typeof IntersectionObserver === "undefined") {
|
|
1206
|
+
startDecode();
|
|
1207
|
+
return () => {
|
|
1208
|
+
cancelled = true;
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
const obs = new IntersectionObserver(
|
|
1212
|
+
(entries) => {
|
|
1213
|
+
for (const entry of entries) {
|
|
1214
|
+
if (entry.isIntersecting) {
|
|
1215
|
+
startDecode();
|
|
1216
|
+
obs.disconnect();
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
},
|
|
1221
|
+
{ rootMargin: "200px" }
|
|
1222
|
+
);
|
|
1223
|
+
obs.observe(triggerRef.current);
|
|
1224
|
+
return () => {
|
|
1225
|
+
cancelled = true;
|
|
1226
|
+
obs.disconnect();
|
|
1227
|
+
};
|
|
1228
|
+
}, [src, enabled, decodeOnMount, triggerRef]);
|
|
1229
|
+
return { peaks, loading, error };
|
|
1230
|
+
}
|
|
1231
|
+
chunkPK6SKIKE_cjs.__name(usePeaks, "usePeaks");
|
|
1232
|
+
var BAR_COUNT = 28;
|
|
1233
|
+
function BarsWaveform({ height, barWidth, barGap, bars = BAR_COUNT }) {
|
|
1234
|
+
const paused = usePlayerPaused();
|
|
1235
|
+
const items = react.useMemo(() => {
|
|
1236
|
+
return Array.from({ length: bars }, (_, i) => ({
|
|
1237
|
+
delay: `${i % 7 * 90}ms`,
|
|
1238
|
+
duration: `${800 + i % 5 * 120}ms`
|
|
1239
|
+
}));
|
|
1240
|
+
}, [bars]);
|
|
1241
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1242
|
+
"div",
|
|
1243
|
+
{
|
|
1244
|
+
className: "audioplayer-bars flex w-full items-center justify-between",
|
|
1245
|
+
style: { height, gap: barGap },
|
|
1246
|
+
"data-mode": "bars",
|
|
1247
|
+
"aria-hidden": "true",
|
|
1248
|
+
children: items.map((it, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1249
|
+
"span",
|
|
1250
|
+
{
|
|
1251
|
+
className: "rounded-sm bg-primary",
|
|
1252
|
+
style: {
|
|
1253
|
+
width: barWidth,
|
|
1254
|
+
height: "40%",
|
|
1255
|
+
animation: paused ? void 0 : `audioplayer-bar ${it.duration} ${it.delay} ease-in-out infinite`,
|
|
1256
|
+
transformOrigin: "center"
|
|
1257
|
+
}
|
|
1258
|
+
},
|
|
1259
|
+
i
|
|
1260
|
+
))
|
|
1261
|
+
}
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
chunkPK6SKIKE_cjs.__name(BarsWaveform, "BarsWaveform");
|
|
1265
|
+
|
|
1266
|
+
// src/tools/media/AudioPlayer/audio/mediaElementSourceCache.ts
|
|
1267
|
+
var cache2 = /* @__PURE__ */ new WeakMap();
|
|
1268
|
+
function getMediaElementSource(el) {
|
|
1269
|
+
const hit = cache2.get(el);
|
|
1270
|
+
if (hit) return hit;
|
|
1271
|
+
const ctx = getAudioContext();
|
|
1272
|
+
const node = ctx.createMediaElementSource(el);
|
|
1273
|
+
node.connect(ctx.destination);
|
|
1274
|
+
cache2.set(el, node);
|
|
1275
|
+
return node;
|
|
1276
|
+
}
|
|
1277
|
+
chunkPK6SKIKE_cjs.__name(getMediaElementSource, "getMediaElementSource");
|
|
1278
|
+
|
|
1279
|
+
// src/tools/media/AudioPlayer/hooks/useAnalyser.ts
|
|
1280
|
+
var FFT_SIZE = 1024;
|
|
1281
|
+
var READ_INTERVAL_MS2 = 33;
|
|
1282
|
+
var SMOOTHING = 0.8;
|
|
1283
|
+
function useAnalyser(audio, store, enabled) {
|
|
1284
|
+
react.useEffect(() => {
|
|
1285
|
+
if (!enabled) return;
|
|
1286
|
+
let analyser = null;
|
|
1287
|
+
let interval = null;
|
|
1288
|
+
let buffer = new Uint8Array(0);
|
|
1289
|
+
let normalized = new Float32Array(0);
|
|
1290
|
+
let cancelled = false;
|
|
1291
|
+
try {
|
|
1292
|
+
const ctx = getAudioContext();
|
|
1293
|
+
const source = getMediaElementSource(audio);
|
|
1294
|
+
analyser = ctx.createAnalyser();
|
|
1295
|
+
analyser.fftSize = FFT_SIZE;
|
|
1296
|
+
analyser.smoothingTimeConstant = SMOOTHING;
|
|
1297
|
+
source.connect(analyser);
|
|
1298
|
+
buffer = new Uint8Array(analyser.frequencyBinCount);
|
|
1299
|
+
normalized = new Float32Array(analyser.frequencyBinCount);
|
|
1300
|
+
store.setActive(true);
|
|
1301
|
+
const tick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1302
|
+
if (cancelled || !analyser) return;
|
|
1303
|
+
if (typeof document !== "undefined" && document.hidden) return;
|
|
1304
|
+
analyser.getByteFrequencyData(buffer);
|
|
1305
|
+
for (let i = 0; i < buffer.length; i++) normalized[i] = buffer[i] / 255;
|
|
1306
|
+
store.set(normalized);
|
|
1307
|
+
}, "tick");
|
|
1308
|
+
interval = setInterval(tick, READ_INTERVAL_MS2);
|
|
1309
|
+
} catch {
|
|
1310
|
+
store.setActive(false);
|
|
1311
|
+
}
|
|
1312
|
+
return () => {
|
|
1313
|
+
cancelled = true;
|
|
1314
|
+
if (interval) clearInterval(interval);
|
|
1315
|
+
if (analyser) {
|
|
1316
|
+
try {
|
|
1317
|
+
analyser.disconnect();
|
|
1318
|
+
} catch {
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
store.setActive(false);
|
|
1322
|
+
};
|
|
1323
|
+
}, [audio, store, enabled]);
|
|
1324
|
+
}
|
|
1325
|
+
chunkPK6SKIKE_cjs.__name(useAnalyser, "useAnalyser");
|
|
1326
|
+
|
|
1327
|
+
// src/tools/media/AudioPlayer/parts/Waveform/waveformInteraction.ts
|
|
1328
|
+
var HOVER_X = "--hp";
|
|
1329
|
+
var HOVER_OPACITY = "--ho";
|
|
1330
|
+
var TOOLTIP_LABEL = "--ht";
|
|
1331
|
+
function attachSeek(container, audio, options = {}) {
|
|
1332
|
+
let dragging = false;
|
|
1333
|
+
let movedDuringDrag = false;
|
|
1334
|
+
const { startsPlayback = true, onPlayRequest } = options;
|
|
1335
|
+
const ratioFor = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((clientX) => {
|
|
1336
|
+
const rect = container.getBoundingClientRect();
|
|
1337
|
+
if (rect.width === 0) return 0;
|
|
1338
|
+
return Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
1339
|
+
}, "ratioFor");
|
|
1340
|
+
const seekTo = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((clientX) => {
|
|
1341
|
+
const dur = Number.isFinite(audio.duration) ? audio.duration : 0;
|
|
1342
|
+
if (dur > 0) audio.currentTime = ratioFor(clientX) * dur;
|
|
1343
|
+
}, "seekTo");
|
|
1344
|
+
const onPointerDown = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((e) => {
|
|
1345
|
+
if (e.button !== 0 && e.pointerType === "mouse") return;
|
|
1346
|
+
dragging = true;
|
|
1347
|
+
movedDuringDrag = false;
|
|
1348
|
+
container.setPointerCapture?.(e.pointerId);
|
|
1349
|
+
seekTo(e.clientX);
|
|
1350
|
+
}, "onPointerDown");
|
|
1351
|
+
const onPointerMove = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((e) => {
|
|
1352
|
+
if (!dragging) return;
|
|
1353
|
+
movedDuringDrag = true;
|
|
1354
|
+
seekTo(e.clientX);
|
|
1355
|
+
}, "onPointerMove");
|
|
1356
|
+
const onPointerEnd = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((e) => {
|
|
1357
|
+
if (!dragging) return;
|
|
1358
|
+
const wasDrag = movedDuringDrag;
|
|
1359
|
+
dragging = false;
|
|
1360
|
+
movedDuringDrag = false;
|
|
1361
|
+
try {
|
|
1362
|
+
container.releasePointerCapture?.(e.pointerId);
|
|
1363
|
+
} catch {
|
|
1364
|
+
}
|
|
1365
|
+
if (!wasDrag && startsPlayback && (audio.paused || audio.ended)) {
|
|
1366
|
+
onPlayRequest?.();
|
|
1367
|
+
}
|
|
1368
|
+
}, "onPointerEnd");
|
|
1369
|
+
container.addEventListener("pointerdown", onPointerDown);
|
|
1370
|
+
container.addEventListener("pointermove", onPointerMove);
|
|
1371
|
+
container.addEventListener("pointerup", onPointerEnd);
|
|
1372
|
+
container.addEventListener("pointercancel", onPointerEnd);
|
|
1373
|
+
return () => {
|
|
1374
|
+
container.removeEventListener("pointerdown", onPointerDown);
|
|
1375
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
1376
|
+
container.removeEventListener("pointerup", onPointerEnd);
|
|
1377
|
+
container.removeEventListener("pointercancel", onPointerEnd);
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
chunkPK6SKIKE_cjs.__name(attachSeek, "attachSeek");
|
|
1381
|
+
function attachHover(container, audio) {
|
|
1382
|
+
const tooltip = container.querySelector("[data-audioplayer-time-tip]");
|
|
1383
|
+
const onMove = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((e) => {
|
|
1384
|
+
const rect = container.getBoundingClientRect();
|
|
1385
|
+
if (rect.width === 0) return;
|
|
1386
|
+
const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
|
|
1387
|
+
container.style.setProperty(HOVER_X, `${x}px`);
|
|
1388
|
+
container.style.setProperty(HOVER_OPACITY, "1");
|
|
1389
|
+
if (tooltip) {
|
|
1390
|
+
const dur = Number.isFinite(audio.duration) ? audio.duration : 0;
|
|
1391
|
+
const t = x / rect.width * dur;
|
|
1392
|
+
tooltip.textContent = formatTime(t);
|
|
1393
|
+
tooltip.style.setProperty(TOOLTIP_LABEL, "1");
|
|
1394
|
+
}
|
|
1395
|
+
}, "onMove");
|
|
1396
|
+
const onLeave = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1397
|
+
container.style.setProperty(HOVER_OPACITY, "0");
|
|
1398
|
+
if (tooltip) tooltip.style.setProperty(TOOLTIP_LABEL, "0");
|
|
1399
|
+
}, "onLeave");
|
|
1400
|
+
container.addEventListener("pointermove", onMove);
|
|
1401
|
+
container.addEventListener("pointerleave", onLeave);
|
|
1402
|
+
return () => {
|
|
1403
|
+
container.removeEventListener("pointermove", onMove);
|
|
1404
|
+
container.removeEventListener("pointerleave", onLeave);
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
chunkPK6SKIKE_cjs.__name(attachHover, "attachHover");
|
|
1408
|
+
|
|
1409
|
+
// src/tools/media/AudioPlayer/utils/dpr.ts
|
|
1410
|
+
var MAX_DPR = 2;
|
|
1411
|
+
var MAX_BACKING_WIDTH = 2048;
|
|
1412
|
+
function getDpr() {
|
|
1413
|
+
if (typeof window === "undefined") return 1;
|
|
1414
|
+
const dpr = window.devicePixelRatio ?? 1;
|
|
1415
|
+
return Math.min(Math.max(dpr, 1), MAX_DPR);
|
|
1416
|
+
}
|
|
1417
|
+
chunkPK6SKIKE_cjs.__name(getDpr, "getDpr");
|
|
1418
|
+
function backingWidth(cssWidth, dpr = getDpr()) {
|
|
1419
|
+
return Math.min(Math.round(cssWidth * dpr), MAX_BACKING_WIDTH);
|
|
1420
|
+
}
|
|
1421
|
+
chunkPK6SKIKE_cjs.__name(backingWidth, "backingWidth");
|
|
1422
|
+
function backingHeight(cssHeight, dpr = getDpr()) {
|
|
1423
|
+
return Math.round(cssHeight * dpr);
|
|
1424
|
+
}
|
|
1425
|
+
chunkPK6SKIKE_cjs.__name(backingHeight, "backingHeight");
|
|
1426
|
+
|
|
1427
|
+
// src/tools/media/AudioPlayer/parts/Waveform/waveformRenderer.ts
|
|
1428
|
+
function resizeCanvas(canvas) {
|
|
1429
|
+
const cssW = canvas.clientWidth;
|
|
1430
|
+
const cssH = canvas.clientHeight;
|
|
1431
|
+
if (cssW === 0 || cssH === 0) return null;
|
|
1432
|
+
const dpr = getDpr();
|
|
1433
|
+
const w = backingWidth(cssW, dpr);
|
|
1434
|
+
const h = backingHeight(cssH, dpr);
|
|
1435
|
+
if (canvas.width !== w) canvas.width = w;
|
|
1436
|
+
if (canvas.height !== h) canvas.height = h;
|
|
1437
|
+
const ctx = canvas.getContext("2d", { alpha: true, desynchronized: true });
|
|
1438
|
+
if (!ctx) return null;
|
|
1439
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1440
|
+
return { ctx, cssW, cssH };
|
|
1441
|
+
}
|
|
1442
|
+
chunkPK6SKIKE_cjs.__name(resizeCanvas, "resizeCanvas");
|
|
1443
|
+
function paintPeaks(canvas, peaks, opts) {
|
|
1444
|
+
const sized = resizeCanvas(canvas);
|
|
1445
|
+
if (!sized) return;
|
|
1446
|
+
const { ctx, cssW, cssH } = sized;
|
|
1447
|
+
ctx.clearRect(0, 0, cssW, cssH);
|
|
1448
|
+
if (opts.background) {
|
|
1449
|
+
ctx.fillStyle = opts.background;
|
|
1450
|
+
ctx.fillRect(0, 0, cssW, cssH);
|
|
1451
|
+
}
|
|
1452
|
+
if (peaks.length === 0) return;
|
|
1453
|
+
const step = Math.max(1, opts.barWidth + opts.barGap);
|
|
1454
|
+
const numBars = Math.max(1, Math.floor(cssW / step));
|
|
1455
|
+
const mid = cssH / 2;
|
|
1456
|
+
const minH = opts.minBarHeight ?? 1;
|
|
1457
|
+
ctx.fillStyle = opts.color;
|
|
1458
|
+
for (let i = 0; i < numBars; i++) {
|
|
1459
|
+
const peakIdx = Math.min(peaks.length - 1, Math.floor(i / numBars * peaks.length));
|
|
1460
|
+
const amp = peaks[peakIdx];
|
|
1461
|
+
const h = Math.max(minH, amp * cssH);
|
|
1462
|
+
const x = i * step;
|
|
1463
|
+
ctx.fillRect(x, mid - h / 2, opts.barWidth, h);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
chunkPK6SKIKE_cjs.__name(paintPeaks, "paintPeaks");
|
|
1467
|
+
function paintLive(canvas, levels, opts) {
|
|
1468
|
+
const sized = resizeCanvas(canvas);
|
|
1469
|
+
if (!sized) return;
|
|
1470
|
+
const { ctx, cssW, cssH } = sized;
|
|
1471
|
+
ctx.clearRect(0, 0, cssW, cssH);
|
|
1472
|
+
if (levels.length === 0) return;
|
|
1473
|
+
const step = Math.max(1, opts.barWidth + opts.barGap);
|
|
1474
|
+
const numBars = Math.max(1, Math.floor(cssW / step));
|
|
1475
|
+
const mid = cssH / 2;
|
|
1476
|
+
const minH = opts.minBarHeight ?? 1;
|
|
1477
|
+
const usable = Math.floor(levels.length * 0.7);
|
|
1478
|
+
ctx.fillStyle = opts.color;
|
|
1479
|
+
for (let i = 0; i < numBars; i++) {
|
|
1480
|
+
const idx = Math.min(usable - 1, Math.floor(i / numBars * usable));
|
|
1481
|
+
const v = levels[idx] ?? 0;
|
|
1482
|
+
const h = Math.max(minH, v * cssH);
|
|
1483
|
+
ctx.fillRect(i * step, mid - h / 2, opts.barWidth, h);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
chunkPK6SKIKE_cjs.__name(paintLive, "paintLive");
|
|
1487
|
+
function LiveWaveform({ height, barWidth, barGap, seekStartsPlayback }) {
|
|
1488
|
+
const audio = usePlayerAudio();
|
|
1489
|
+
const controls = usePlayerControls();
|
|
1490
|
+
const store = usePlayerLevels();
|
|
1491
|
+
const [container, setContainer] = react.useState(null);
|
|
1492
|
+
const canvasRef = react.useRef(null);
|
|
1493
|
+
const fgHex = palette.useThemeColor("primary");
|
|
1494
|
+
const mutedHex = palette.useThemeColor("muted-foreground");
|
|
1495
|
+
const colorRef = react.useRef(fgHex);
|
|
1496
|
+
colorRef.current = fgHex;
|
|
1497
|
+
useElementWidth(container);
|
|
1498
|
+
useAnalyser(audio, store, true);
|
|
1499
|
+
react.useEffect(() => {
|
|
1500
|
+
const canvas = canvasRef.current;
|
|
1501
|
+
if (!canvas) return;
|
|
1502
|
+
let raf = 0;
|
|
1503
|
+
const tick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1504
|
+
if (typeof document !== "undefined" && document.hidden) {
|
|
1505
|
+
raf = requestAnimationFrame(tick);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
paintLive(canvas, store.getCurrent(), {
|
|
1509
|
+
color: colorRef.current,
|
|
1510
|
+
barWidth,
|
|
1511
|
+
barGap,
|
|
1512
|
+
minBarHeight: 1
|
|
1513
|
+
});
|
|
1514
|
+
raf = requestAnimationFrame(tick);
|
|
1515
|
+
}, "tick");
|
|
1516
|
+
raf = requestAnimationFrame(tick);
|
|
1517
|
+
return () => cancelAnimationFrame(raf);
|
|
1518
|
+
}, [store, barWidth, barGap]);
|
|
1519
|
+
react.useEffect(() => {
|
|
1520
|
+
if (!container) return;
|
|
1521
|
+
const detachSeek = attachSeek(container, audio, {
|
|
1522
|
+
startsPlayback: seekStartsPlayback,
|
|
1523
|
+
onPlayRequest: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => void controls.play(), "onPlayRequest")
|
|
1524
|
+
});
|
|
1525
|
+
const detachHover = attachHover(container, audio);
|
|
1526
|
+
return () => {
|
|
1527
|
+
detachSeek();
|
|
1528
|
+
detachHover();
|
|
1529
|
+
};
|
|
1530
|
+
}, [audio, container, controls, seekStartsPlayback]);
|
|
1531
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1532
|
+
"div",
|
|
1533
|
+
{
|
|
1534
|
+
ref: setContainer,
|
|
1535
|
+
className: "audioplayer-waveform relative w-full select-none cursor-pointer",
|
|
1536
|
+
style: { height },
|
|
1537
|
+
"data-mode": "live",
|
|
1538
|
+
children: [
|
|
1539
|
+
/* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, className: "absolute inset-0 h-full w-full", "aria-hidden": "true" }),
|
|
1540
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1541
|
+
"div",
|
|
1542
|
+
{
|
|
1543
|
+
className: "audioplayer-hover pointer-events-none absolute top-0 bottom-0 w-px transition-opacity",
|
|
1544
|
+
style: {
|
|
1545
|
+
left: "var(--hp, -10px)",
|
|
1546
|
+
opacity: "var(--ho, 0)",
|
|
1547
|
+
backgroundColor: palette.alpha(mutedHex, 0.5)
|
|
1548
|
+
},
|
|
1549
|
+
"aria-hidden": "true"
|
|
1550
|
+
}
|
|
1551
|
+
),
|
|
1552
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1553
|
+
"div",
|
|
1554
|
+
{
|
|
1555
|
+
"data-audioplayer-time-tip": true,
|
|
1556
|
+
className: "audioplayer-tip pointer-events-none absolute -top-7 -translate-x-1/2 rounded bg-foreground px-1.5 py-0.5 text-[10px] tabular-nums text-background transition-opacity",
|
|
1557
|
+
style: {
|
|
1558
|
+
left: "var(--hp, -100px)",
|
|
1559
|
+
opacity: "var(--ht, 0)"
|
|
1560
|
+
},
|
|
1561
|
+
"aria-hidden": "true"
|
|
1562
|
+
}
|
|
1563
|
+
)
|
|
1564
|
+
]
|
|
1565
|
+
}
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
chunkPK6SKIKE_cjs.__name(LiveWaveform, "LiveWaveform");
|
|
1569
|
+
var VAR2 = "--p";
|
|
1570
|
+
function usePlayheadLoop(audio, el, enabled = true) {
|
|
1571
|
+
react.useEffect(() => {
|
|
1572
|
+
if (!enabled || !el) return;
|
|
1573
|
+
let raf = 0;
|
|
1574
|
+
let lastPct = -1;
|
|
1575
|
+
const writePct = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1576
|
+
const dur = audio.duration;
|
|
1577
|
+
if (!Number.isFinite(dur) || dur <= 0) return;
|
|
1578
|
+
const pct = Math.max(0, Math.min(100, audio.currentTime / dur * 100));
|
|
1579
|
+
if (Math.abs(pct - lastPct) < 0.01) return;
|
|
1580
|
+
lastPct = pct;
|
|
1581
|
+
el.style.setProperty(VAR2, `${pct.toFixed(2)}%`);
|
|
1582
|
+
}, "writePct");
|
|
1583
|
+
const tick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1584
|
+
if (typeof document !== "undefined" && document.hidden) {
|
|
1585
|
+
raf = 0;
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
writePct();
|
|
1589
|
+
raf = requestAnimationFrame(tick);
|
|
1590
|
+
}, "tick");
|
|
1591
|
+
const start = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1592
|
+
if (raf) return;
|
|
1593
|
+
raf = requestAnimationFrame(tick);
|
|
1594
|
+
}, "start");
|
|
1595
|
+
const stop = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1596
|
+
if (!raf) return;
|
|
1597
|
+
cancelAnimationFrame(raf);
|
|
1598
|
+
raf = 0;
|
|
1599
|
+
writePct();
|
|
1600
|
+
}, "stop");
|
|
1601
|
+
if (!audio.paused) start();
|
|
1602
|
+
const onPlay = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => start(), "onPlay");
|
|
1603
|
+
const onPauseOrEnd = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => stop(), "onPauseOrEnd");
|
|
1604
|
+
const onSeek = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => writePct(), "onSeek");
|
|
1605
|
+
const onVisibility = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
|
|
1606
|
+
if (document.hidden) stop();
|
|
1607
|
+
else if (!audio.paused) start();
|
|
1608
|
+
}, "onVisibility");
|
|
1609
|
+
audio.addEventListener("play", onPlay);
|
|
1610
|
+
audio.addEventListener("pause", onPauseOrEnd);
|
|
1611
|
+
audio.addEventListener("ended", onPauseOrEnd);
|
|
1612
|
+
audio.addEventListener("seeked", onSeek);
|
|
1613
|
+
audio.addEventListener("timeupdate", writePct);
|
|
1614
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1615
|
+
return () => {
|
|
1616
|
+
stop();
|
|
1617
|
+
audio.removeEventListener("play", onPlay);
|
|
1618
|
+
audio.removeEventListener("pause", onPauseOrEnd);
|
|
1619
|
+
audio.removeEventListener("ended", onPauseOrEnd);
|
|
1620
|
+
audio.removeEventListener("seeked", onSeek);
|
|
1621
|
+
audio.removeEventListener("timeupdate", writePct);
|
|
1622
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1623
|
+
};
|
|
1624
|
+
}, [audio, el, enabled]);
|
|
1625
|
+
}
|
|
1626
|
+
chunkPK6SKIKE_cjs.__name(usePlayheadLoop, "usePlayheadLoop");
|
|
1627
|
+
function useThemeWatcher(cb) {
|
|
1628
|
+
react.useEffect(() => {
|
|
1629
|
+
const root = document.documentElement;
|
|
1630
|
+
const obs = new MutationObserver(cb);
|
|
1631
|
+
obs.observe(root, { attributes: true, attributeFilter: ["class", "data-theme"] });
|
|
1632
|
+
const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
1633
|
+
const onMq = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => cb(), "onMq");
|
|
1634
|
+
mq?.addEventListener?.("change", onMq);
|
|
1635
|
+
return () => {
|
|
1636
|
+
obs.disconnect();
|
|
1637
|
+
mq?.removeEventListener?.("change", onMq);
|
|
1638
|
+
};
|
|
1639
|
+
}, [cb]);
|
|
1640
|
+
}
|
|
1641
|
+
chunkPK6SKIKE_cjs.__name(useThemeWatcher, "useThemeWatcher");
|
|
1642
|
+
function PeaksWaveform({ peaks, height, barWidth, barGap, seekStartsPlayback }) {
|
|
1643
|
+
const audio = usePlayerAudio();
|
|
1644
|
+
const controls = usePlayerControls();
|
|
1645
|
+
const [container, setContainer] = react.useState(null);
|
|
1646
|
+
const bgCanvasRef = react.useRef(null);
|
|
1647
|
+
const fgCanvasRef = react.useRef(null);
|
|
1648
|
+
const width = useElementWidth(container);
|
|
1649
|
+
const fgHex = palette.useThemeColor("primary");
|
|
1650
|
+
const mutedHex = palette.useThemeColor("muted-foreground");
|
|
1651
|
+
const repaint = react.useCallback(() => {
|
|
1652
|
+
const bg = bgCanvasRef.current;
|
|
1653
|
+
const fg = fgCanvasRef.current;
|
|
1654
|
+
if (!bg || !fg) return;
|
|
1655
|
+
paintPeaks(bg, peaks, { color: palette.alpha(mutedHex, 0.4), barWidth, barGap, minBarHeight: 1 });
|
|
1656
|
+
paintPeaks(fg, peaks, { color: fgHex, barWidth, barGap, minBarHeight: 1 });
|
|
1657
|
+
}, [peaks, barWidth, barGap, fgHex, mutedHex]);
|
|
1658
|
+
react.useEffect(repaint, [repaint, width]);
|
|
1659
|
+
useThemeWatcher(repaint);
|
|
1660
|
+
usePlayheadLoop(audio, container, true);
|
|
1661
|
+
react.useEffect(() => {
|
|
1662
|
+
if (!container) return;
|
|
1663
|
+
const detachSeek = attachSeek(container, audio, {
|
|
1664
|
+
startsPlayback: seekStartsPlayback,
|
|
1665
|
+
onPlayRequest: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => void controls.play(), "onPlayRequest")
|
|
1666
|
+
});
|
|
1667
|
+
const detachHover = attachHover(container, audio);
|
|
1668
|
+
return () => {
|
|
1669
|
+
detachSeek();
|
|
1670
|
+
detachHover();
|
|
1671
|
+
};
|
|
1672
|
+
}, [audio, container, controls, seekStartsPlayback]);
|
|
1673
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1674
|
+
"div",
|
|
1675
|
+
{
|
|
1676
|
+
ref: setContainer,
|
|
1677
|
+
className: "audioplayer-waveform relative w-full select-none cursor-pointer",
|
|
1678
|
+
style: { height, ["--p"]: "0%" },
|
|
1679
|
+
"data-mode": "peaks",
|
|
1680
|
+
children: [
|
|
1681
|
+
/* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: bgCanvasRef, className: "absolute inset-0 h-full w-full", "aria-hidden": "true" }),
|
|
1682
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1683
|
+
"div",
|
|
1684
|
+
{
|
|
1685
|
+
className: "audioplayer-fg-clip absolute inset-0",
|
|
1686
|
+
style: {
|
|
1687
|
+
clipPath: "polygon(0 0, var(--p) 0, var(--p) 100%, 0 100%)",
|
|
1688
|
+
willChange: "clip-path"
|
|
1689
|
+
},
|
|
1690
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: fgCanvasRef, className: "absolute inset-0 h-full w-full", "aria-hidden": "true" })
|
|
1691
|
+
}
|
|
1692
|
+
),
|
|
1693
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1694
|
+
"div",
|
|
1695
|
+
{
|
|
1696
|
+
className: "audioplayer-cursor pointer-events-none absolute top-0 bottom-0 w-px",
|
|
1697
|
+
style: { left: "var(--p)", backgroundColor: palette.alpha(fgHex, 0.7) },
|
|
1698
|
+
"aria-hidden": "true"
|
|
1699
|
+
}
|
|
1700
|
+
),
|
|
1701
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1702
|
+
"div",
|
|
1703
|
+
{
|
|
1704
|
+
className: "audioplayer-hover pointer-events-none absolute top-0 bottom-0 w-px transition-opacity",
|
|
1705
|
+
style: {
|
|
1706
|
+
left: "var(--hp, -10px)",
|
|
1707
|
+
opacity: "var(--ho, 0)",
|
|
1708
|
+
backgroundColor: palette.alpha(mutedHex, 0.5)
|
|
1709
|
+
},
|
|
1710
|
+
"aria-hidden": "true"
|
|
1711
|
+
}
|
|
1712
|
+
),
|
|
1713
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1714
|
+
"div",
|
|
1715
|
+
{
|
|
1716
|
+
"data-audioplayer-time-tip": true,
|
|
1717
|
+
className: "audioplayer-tip pointer-events-none absolute -top-7 -translate-x-1/2 rounded bg-foreground px-1.5 py-0.5 text-[10px] tabular-nums text-background transition-opacity",
|
|
1718
|
+
style: {
|
|
1719
|
+
left: "var(--hp, -100px)",
|
|
1720
|
+
opacity: "var(--ht, 0)"
|
|
1721
|
+
},
|
|
1722
|
+
"aria-hidden": "true"
|
|
1723
|
+
}
|
|
1724
|
+
)
|
|
1725
|
+
]
|
|
1726
|
+
}
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
chunkPK6SKIKE_cjs.__name(PeaksWaveform, "PeaksWaveform");
|
|
1730
|
+
function ProgressBar({ height = 4, seekStartsPlayback }) {
|
|
1731
|
+
const audio = usePlayerAudio();
|
|
1732
|
+
const controls = usePlayerControls();
|
|
1733
|
+
const [container, setContainer] = react.useState(null);
|
|
1734
|
+
const fgHex = palette.useThemeColor("primary");
|
|
1735
|
+
const mutedHex = palette.useThemeColor("muted-foreground");
|
|
1736
|
+
usePlayheadLoop(audio, container, true);
|
|
1737
|
+
react.useEffect(() => {
|
|
1738
|
+
if (!container) return;
|
|
1739
|
+
const detachSeek = attachSeek(container, audio, {
|
|
1740
|
+
startsPlayback: seekStartsPlayback,
|
|
1741
|
+
onPlayRequest: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => void controls.play(), "onPlayRequest")
|
|
1742
|
+
});
|
|
1743
|
+
const detachHover = attachHover(container, audio);
|
|
1744
|
+
return () => {
|
|
1745
|
+
detachSeek();
|
|
1746
|
+
detachHover();
|
|
1747
|
+
};
|
|
1748
|
+
}, [audio, container, controls, seekStartsPlayback]);
|
|
1749
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1750
|
+
"div",
|
|
1751
|
+
{
|
|
1752
|
+
ref: setContainer,
|
|
1753
|
+
className: "audioplayer-waveform relative w-full select-none cursor-pointer",
|
|
1754
|
+
style: { ["--p"]: "0%" },
|
|
1755
|
+
"data-mode": "progress",
|
|
1756
|
+
children: [
|
|
1757
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1758
|
+
"div",
|
|
1759
|
+
{
|
|
1760
|
+
className: "rounded-full",
|
|
1761
|
+
style: { height, backgroundColor: palette.alpha(mutedHex, 0.25) }
|
|
1762
|
+
}
|
|
1763
|
+
),
|
|
1764
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1765
|
+
"div",
|
|
1766
|
+
{
|
|
1767
|
+
className: "absolute inset-y-0 left-0 rounded-full",
|
|
1768
|
+
style: {
|
|
1769
|
+
width: "var(--p)",
|
|
1770
|
+
backgroundColor: fgHex
|
|
1771
|
+
},
|
|
1772
|
+
"aria-hidden": "true"
|
|
1773
|
+
}
|
|
1774
|
+
),
|
|
1775
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1776
|
+
"div",
|
|
1777
|
+
{
|
|
1778
|
+
className: "audioplayer-hover pointer-events-none absolute -top-1 -bottom-1 w-px transition-opacity",
|
|
1779
|
+
style: {
|
|
1780
|
+
left: "var(--hp, -10px)",
|
|
1781
|
+
opacity: "var(--ho, 0)",
|
|
1782
|
+
backgroundColor: palette.alpha(mutedHex, 0.5)
|
|
1783
|
+
},
|
|
1784
|
+
"aria-hidden": "true"
|
|
1785
|
+
}
|
|
1786
|
+
),
|
|
1787
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1788
|
+
"div",
|
|
1789
|
+
{
|
|
1790
|
+
"data-audioplayer-time-tip": true,
|
|
1791
|
+
className: "audioplayer-tip pointer-events-none absolute -top-7 -translate-x-1/2 rounded bg-foreground px-1.5 py-0.5 text-[10px] tabular-nums text-background transition-opacity",
|
|
1792
|
+
style: {
|
|
1793
|
+
left: "var(--hp, -100px)",
|
|
1794
|
+
opacity: "var(--ht, 0)"
|
|
1795
|
+
},
|
|
1796
|
+
"aria-hidden": "true"
|
|
1797
|
+
}
|
|
1798
|
+
)
|
|
1799
|
+
]
|
|
1800
|
+
}
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
chunkPK6SKIKE_cjs.__name(ProgressBar, "ProgressBar");
|
|
1804
|
+
function WaveformSkeleton({ height }) {
|
|
1805
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1806
|
+
"div",
|
|
1807
|
+
{
|
|
1808
|
+
className: "audioplayer-skeleton relative w-full overflow-hidden",
|
|
1809
|
+
style: { height },
|
|
1810
|
+
"aria-hidden": "true",
|
|
1811
|
+
children: [
|
|
1812
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-muted-foreground/30" }),
|
|
1813
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 audioplayer-shimmer" })
|
|
1814
|
+
]
|
|
1815
|
+
}
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
chunkPK6SKIKE_cjs.__name(WaveformSkeleton, "WaveformSkeleton");
|
|
1819
|
+
function Waveform({ config, height, seekStartsPlayback }) {
|
|
1820
|
+
const meta = usePlayerMeta();
|
|
1821
|
+
const triggerRef = react.useRef(null);
|
|
1822
|
+
const mode = config?.mode ?? "peaks";
|
|
1823
|
+
const barWidth = config?.barWidth ?? 2;
|
|
1824
|
+
const barGap = config?.barGap ?? 1;
|
|
1825
|
+
const peaksEnabled = mode === "peaks";
|
|
1826
|
+
const { peaks, loading, error } = usePeaks({
|
|
1827
|
+
src: meta.src,
|
|
1828
|
+
enabled: peaksEnabled,
|
|
1829
|
+
triggerRef,
|
|
1830
|
+
decodeOnMount: config?.decodeOnMount
|
|
1831
|
+
});
|
|
1832
|
+
if (mode === "none") return null;
|
|
1833
|
+
if (mode === "progress")
|
|
1834
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ProgressBar, { height: Math.min(height, 6), seekStartsPlayback });
|
|
1835
|
+
if (mode === "bars") return /* @__PURE__ */ jsxRuntime.jsx(BarsWaveform, { height, barWidth, barGap });
|
|
1836
|
+
if (mode === "live")
|
|
1837
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1838
|
+
LiveWaveform,
|
|
1839
|
+
{
|
|
1840
|
+
height,
|
|
1841
|
+
barWidth,
|
|
1842
|
+
barGap,
|
|
1843
|
+
seekStartsPlayback
|
|
1844
|
+
}
|
|
1845
|
+
);
|
|
1846
|
+
if (loading || !peaks && !error) {
|
|
1847
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: triggerRef, children: /* @__PURE__ */ jsxRuntime.jsx(WaveformSkeleton, { height }) });
|
|
1848
|
+
}
|
|
1849
|
+
if (!peaks) {
|
|
1850
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ProgressBar, { height: Math.min(height, 6), seekStartsPlayback });
|
|
1851
|
+
}
|
|
1852
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: triggerRef, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1853
|
+
PeaksWaveform,
|
|
1854
|
+
{
|
|
1855
|
+
peaks,
|
|
1856
|
+
height,
|
|
1857
|
+
barWidth,
|
|
1858
|
+
barGap,
|
|
1859
|
+
seekStartsPlayback
|
|
1860
|
+
}
|
|
1861
|
+
) });
|
|
1862
|
+
}
|
|
1863
|
+
chunkPK6SKIKE_cjs.__name(Waveform, "Waveform");
|
|
1864
|
+
function DefaultLayout({ waveform, reactiveCover, onPrev, onNext, seekStartsPlayback }) {
|
|
1865
|
+
const meta = usePlayerMeta();
|
|
1866
|
+
const cover = /* @__PURE__ */ jsxRuntime.jsx(Cover, { src: meta.cover, alt: meta.title ? `${meta.title} cover` : "", size: 56 });
|
|
1867
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3.5 p-4", children: [
|
|
1868
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1869
|
+
reactiveCover === "subtle" ? /* @__PURE__ */ jsxRuntime.jsx(ReactivePulse, { enabled: true, children: cover }) : cover,
|
|
1870
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1871
|
+
/* @__PURE__ */ jsxRuntime.jsx(Title, {}),
|
|
1872
|
+
/* @__PURE__ */ jsxRuntime.jsx(Artist, {})
|
|
1873
|
+
] })
|
|
1874
|
+
] }),
|
|
1875
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1876
|
+
Waveform,
|
|
1877
|
+
{
|
|
1878
|
+
config: waveform,
|
|
1879
|
+
height: waveform?.height ?? 40,
|
|
1880
|
+
seekStartsPlayback
|
|
1881
|
+
}
|
|
1882
|
+
),
|
|
1883
|
+
/* @__PURE__ */ jsxRuntime.jsx(ErrorState, {}),
|
|
1884
|
+
/* @__PURE__ */ jsxRuntime.jsx(ControlsRow, { onPrev, onNext, showTime: true })
|
|
1885
|
+
] });
|
|
1886
|
+
}
|
|
1887
|
+
chunkPK6SKIKE_cjs.__name(DefaultLayout, "DefaultLayout");
|
|
1888
|
+
function CompactLayout({ waveform, seekStartsPlayback }) {
|
|
1889
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 p-2", children: [
|
|
1890
|
+
/* @__PURE__ */ jsxRuntime.jsx(PlayButton, { size: "compact" }),
|
|
1891
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1892
|
+
Waveform,
|
|
1893
|
+
{
|
|
1894
|
+
config: waveform,
|
|
1895
|
+
height: waveform?.height ?? 24,
|
|
1896
|
+
seekStartsPlayback
|
|
1897
|
+
}
|
|
1898
|
+
) }),
|
|
1899
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimeDisplay, {}),
|
|
1900
|
+
/* @__PURE__ */ jsxRuntime.jsx(VolumeControl, {})
|
|
1901
|
+
] });
|
|
1902
|
+
}
|
|
1903
|
+
chunkPK6SKIKE_cjs.__name(CompactLayout, "CompactLayout");
|
|
1904
|
+
var COMPACT_BREAKPOINT = 480;
|
|
1905
|
+
function PlayerShell({
|
|
1906
|
+
className = "",
|
|
1907
|
+
variant = "auto",
|
|
1908
|
+
waveform,
|
|
1909
|
+
reactiveCover = false,
|
|
1910
|
+
onPrev,
|
|
1911
|
+
onNext,
|
|
1912
|
+
enableKeyboardShortcuts = true,
|
|
1913
|
+
ariaLabel,
|
|
1914
|
+
seekStartsPlayback = true,
|
|
1915
|
+
autoFocus = false,
|
|
1916
|
+
handleRef
|
|
1917
|
+
}) {
|
|
1918
|
+
const [container, setContainer] = react.useState(null);
|
|
1919
|
+
const audio = usePlayerAudio();
|
|
1920
|
+
const controls = usePlayerControls();
|
|
1921
|
+
const meta = usePlayerMeta();
|
|
1922
|
+
const width = useElementWidth(container);
|
|
1923
|
+
const isPhone = hooks.useIsPhone();
|
|
1924
|
+
const resolvedVariant = variant === "auto" ? isPhone || width > 0 && width < COMPACT_BREAKPOINT ? "compact" : "default" : variant;
|
|
1925
|
+
useMediaSession(audio, meta, controls, onPrev, onNext);
|
|
1926
|
+
const hotkeys = useKeyboardShortcuts({
|
|
1927
|
+
audio,
|
|
1928
|
+
controls,
|
|
1929
|
+
enabled: enableKeyboardShortcuts
|
|
1930
|
+
});
|
|
1931
|
+
const setRootRef = react.useCallback(
|
|
1932
|
+
(node) => {
|
|
1933
|
+
setContainer(node);
|
|
1934
|
+
hotkeys.ref(node);
|
|
1935
|
+
},
|
|
1936
|
+
[hotkeys]
|
|
1937
|
+
);
|
|
1938
|
+
react.useImperativeHandle(
|
|
1939
|
+
handleRef,
|
|
1940
|
+
() => ({
|
|
1941
|
+
audio,
|
|
1942
|
+
play: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => controls.play(), "play"),
|
|
1943
|
+
pause: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => controls.pause(), "pause"),
|
|
1944
|
+
seek: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((s) => controls.seek(s), "seek"),
|
|
1945
|
+
getCurrentTime: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => audio.currentTime, "getCurrentTime"),
|
|
1946
|
+
getDuration: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => Number.isFinite(audio.duration) ? audio.duration : 0, "getDuration"),
|
|
1947
|
+
focus: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => container?.focus(), "focus")
|
|
1948
|
+
}),
|
|
1949
|
+
[audio, controls, container]
|
|
1950
|
+
);
|
|
1951
|
+
react.useEffect(() => {
|
|
1952
|
+
if (!container || container.hasAttribute("tabindex")) return;
|
|
1953
|
+
container.setAttribute("tabindex", "0");
|
|
1954
|
+
}, [container]);
|
|
1955
|
+
react.useEffect(() => {
|
|
1956
|
+
if (!autoFocus || !container) return;
|
|
1957
|
+
container.focus({ preventScroll: true });
|
|
1958
|
+
}, [autoFocus, container]);
|
|
1959
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1960
|
+
"div",
|
|
1961
|
+
{
|
|
1962
|
+
ref: setRootRef,
|
|
1963
|
+
role: "group",
|
|
1964
|
+
"aria-label": ariaLabel ?? "Audio player",
|
|
1965
|
+
className: `audioplayer @container/player rounded-lg border border-border/60 bg-card text-foreground shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 ${className}`,
|
|
1966
|
+
children: resolvedVariant === "compact" ? /* @__PURE__ */ jsxRuntime.jsx(CompactLayout, { waveform, seekStartsPlayback }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1967
|
+
DefaultLayout,
|
|
1968
|
+
{
|
|
1969
|
+
waveform,
|
|
1970
|
+
reactiveCover,
|
|
1971
|
+
onPrev,
|
|
1972
|
+
onNext,
|
|
1973
|
+
seekStartsPlayback
|
|
1974
|
+
}
|
|
1975
|
+
)
|
|
1976
|
+
}
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
chunkPK6SKIKE_cjs.__name(PlayerShell, "PlayerShell");
|
|
1980
|
+
var Player = react.forwardRef(/* @__PURE__ */ chunkPK6SKIKE_cjs.__name(function Player2(props, ref) {
|
|
1981
|
+
const {
|
|
1982
|
+
src,
|
|
1983
|
+
peaks,
|
|
1984
|
+
title,
|
|
1985
|
+
artist,
|
|
1986
|
+
album,
|
|
1987
|
+
cover,
|
|
1988
|
+
autoplay,
|
|
1989
|
+
loop,
|
|
1990
|
+
initialVolume,
|
|
1991
|
+
muted,
|
|
1992
|
+
preload,
|
|
1993
|
+
exclusive,
|
|
1994
|
+
onPrev,
|
|
1995
|
+
onNext,
|
|
1996
|
+
onPlay,
|
|
1997
|
+
onPause,
|
|
1998
|
+
onEnded,
|
|
1999
|
+
onError,
|
|
2000
|
+
onTimeUpdate,
|
|
2001
|
+
variant,
|
|
2002
|
+
waveform,
|
|
2003
|
+
reactiveCover,
|
|
2004
|
+
className,
|
|
2005
|
+
ariaLabel,
|
|
2006
|
+
enableKeyboardShortcuts,
|
|
2007
|
+
seekStartsPlayback,
|
|
2008
|
+
autoFocus
|
|
2009
|
+
} = props;
|
|
2010
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2011
|
+
PlayerProvider,
|
|
2012
|
+
{
|
|
2013
|
+
src,
|
|
2014
|
+
peaks,
|
|
2015
|
+
title,
|
|
2016
|
+
artist,
|
|
2017
|
+
album,
|
|
2018
|
+
cover,
|
|
2019
|
+
autoplay,
|
|
2020
|
+
loop,
|
|
2021
|
+
initialVolume,
|
|
2022
|
+
muted,
|
|
2023
|
+
preload,
|
|
2024
|
+
exclusive,
|
|
2025
|
+
onPrev,
|
|
2026
|
+
onNext,
|
|
2027
|
+
onPlay,
|
|
2028
|
+
onPause,
|
|
2029
|
+
onEnded,
|
|
2030
|
+
onError,
|
|
2031
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2032
|
+
PlayerShell,
|
|
2033
|
+
{
|
|
2034
|
+
className,
|
|
2035
|
+
variant,
|
|
2036
|
+
waveform,
|
|
2037
|
+
reactiveCover,
|
|
2038
|
+
onPrev,
|
|
2039
|
+
onNext,
|
|
2040
|
+
enableKeyboardShortcuts,
|
|
2041
|
+
ariaLabel,
|
|
2042
|
+
seekStartsPlayback,
|
|
2043
|
+
autoFocus,
|
|
2044
|
+
handleRef: ref
|
|
2045
|
+
}
|
|
2046
|
+
)
|
|
2047
|
+
}
|
|
2048
|
+
);
|
|
2049
|
+
}, "Player"));
|
|
2050
|
+
Player.displayName = "AudioPlayer";
|
|
2051
|
+
var getServerSnapshot = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => null, "getServerSnapshot");
|
|
2052
|
+
function useActivePlayer() {
|
|
2053
|
+
return react.useSyncExternalStore(subscribeActivePlayer, getActivePlayer, getServerSnapshot);
|
|
2054
|
+
}
|
|
2055
|
+
chunkPK6SKIKE_cjs.__name(useActivePlayer, "useActivePlayer");
|
|
2056
|
+
function useLastActivePlayer() {
|
|
2057
|
+
return react.useSyncExternalStore(subscribeActivePlayer, getLastActivePlayer, getServerSnapshot);
|
|
2058
|
+
}
|
|
2059
|
+
chunkPK6SKIKE_cjs.__name(useLastActivePlayer, "useLastActivePlayer");
|
|
2060
|
+
function useIsActivePlayer(id) {
|
|
2061
|
+
const active = useActivePlayer();
|
|
2062
|
+
return Boolean(id) && active === id;
|
|
2063
|
+
}
|
|
2064
|
+
chunkPK6SKIKE_cjs.__name(useIsActivePlayer, "useIsActivePlayer");
|
|
2065
|
+
var SSR = { volume: 1, muted: false };
|
|
2066
|
+
function getServerSnapshot2() {
|
|
2067
|
+
return SSR;
|
|
2068
|
+
}
|
|
2069
|
+
chunkPK6SKIKE_cjs.__name(getServerSnapshot2, "getServerSnapshot");
|
|
2070
|
+
function usePlayerPreferences() {
|
|
2071
|
+
return react.useSyncExternalStore(subscribePreferences, getPreferences, getServerSnapshot2);
|
|
2072
|
+
}
|
|
2073
|
+
chunkPK6SKIKE_cjs.__name(usePlayerPreferences, "usePlayerPreferences");
|
|
2074
|
+
|
|
2075
|
+
exports.LazyPlayer = Player;
|
|
2076
|
+
exports.Player = Player;
|
|
2077
|
+
exports.PlayerProvider = PlayerProvider;
|
|
2078
|
+
exports.getActivePlayer = getActivePlayer;
|
|
2079
|
+
exports.getLastActivePlayer = getLastActivePlayer;
|
|
2080
|
+
exports.getPreferences = getPreferences;
|
|
2081
|
+
exports.setActivePlayer = setActivePlayer;
|
|
2082
|
+
exports.setStoredMuted = setStoredMuted;
|
|
2083
|
+
exports.setStoredVolume = setStoredVolume;
|
|
2084
|
+
exports.subscribeActivePlayer = subscribeActivePlayer;
|
|
2085
|
+
exports.subscribePreferences = subscribePreferences;
|
|
2086
|
+
exports.useActivePlayer = useActivePlayer;
|
|
2087
|
+
exports.useIsActivePlayer = useIsActivePlayer;
|
|
2088
|
+
exports.useLastActivePlayer = useLastActivePlayer;
|
|
2089
|
+
exports.usePlayerAudio = usePlayerAudio;
|
|
2090
|
+
exports.usePlayerControls = usePlayerControls;
|
|
2091
|
+
exports.usePlayerDuration = usePlayerDuration;
|
|
2092
|
+
exports.usePlayerLevels = usePlayerLevels;
|
|
2093
|
+
exports.usePlayerMeta = usePlayerMeta;
|
|
2094
|
+
exports.usePlayerPaused = usePlayerPaused;
|
|
2095
|
+
exports.usePlayerPreferences = usePlayerPreferences;
|
|
2096
|
+
exports.usePlayerState = usePlayerState;
|
|
2097
|
+
//# sourceMappingURL=index.cjs.map
|
|
2098
|
+
//# sourceMappingURL=index.cjs.map
|