@prose-reader/enhancer-audio 1.303.0 → 1.304.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,935 +1,537 @@
1
- import { ReactiveEntity, mapKeysTo, isDefined, DocumentRenderer, setStylePropertyIfChanged } from '@prose-reader/core';
2
- import { BehaviorSubject, Subscription, distinctUntilChanged, switchMap, of, concat, map, defaultIfEmpty, defer, from, EMPTY, animationFrames, fromEvent, share, merge, retry, take, shareReplay, combineLatest, Subject, withLatestFrom, filter, tap, catchError, takeUntil } from 'rxjs';
3
-
4
- const AUDIO_VISUALIZER_LEVEL_COUNT = 80;
5
- const AUDIO_VISUALIZER_NOISE_FLOOR = 0.035;
6
- const getIdleVisualizerLevels = () => Array.from({ length: AUDIO_VISUALIZER_LEVEL_COUNT }, () => 0);
7
- const getVisualizerLevels = (data) => {
8
- if (data.length === 0) return getIdleVisualizerLevels();
9
- const bucketSize = Math.max(
10
- 1,
11
- Math.floor(data.length / AUDIO_VISUALIZER_LEVEL_COUNT)
12
- );
13
- return Array.from({ length: AUDIO_VISUALIZER_LEVEL_COUNT }, (_, index) => {
14
- const start = index * bucketSize;
15
- const end = Math.min(data.length, start + bucketSize);
16
- if (start >= data.length || start === end) return 0;
17
- let total = 0;
18
- for (let cursor = start; cursor < end; cursor += 1) {
19
- total += data[cursor] ?? 0;
20
- }
21
- const average = total / (end - start);
22
- const normalizedAverage = average / 255;
23
- const gatedAverage = (normalizedAverage - AUDIO_VISUALIZER_NOISE_FLOOR) / (1 - AUDIO_VISUALIZER_NOISE_FLOOR);
24
- return Math.max(0, Math.min(1, gatedAverage));
25
- });
26
- };
27
-
28
- const AUDIO_VISUALIZER_FFT_SIZE = 256;
29
- class VisualizerAudioGraph {
30
- constructor(audioElement) {
31
- this.audioElement = audioElement;
32
- }
33
- audioElement;
34
- audioContext;
35
- audioSourceNode;
36
- analyserNode;
37
- frequencyData;
38
- async resumeIfNeeded() {
39
- if (!this.ensure()) {
40
- return false;
41
- }
42
- const audioContext = this.audioContext;
43
- if (!audioContext) {
44
- return false;
45
- }
46
- if (audioContext.state !== `suspended`) {
47
- return true;
48
- }
49
- try {
50
- await audioContext.resume();
51
- return true;
52
- } catch {
53
- return false;
54
- }
55
- }
56
- readLevels() {
57
- if (!this.analyserNode || !this.frequencyData) {
58
- return getIdleVisualizerLevels();
59
- }
60
- this.analyserNode.getByteFrequencyData(this.frequencyData);
61
- return getVisualizerLevels(this.frequencyData);
62
- }
63
- destroy() {
64
- this.audioSourceNode?.disconnect();
65
- this.analyserNode?.disconnect();
66
- this.audioContext?.close().catch(() => void 0);
67
- this.audioContext = void 0;
68
- this.audioSourceNode = void 0;
69
- this.analyserNode = void 0;
70
- this.frequencyData = void 0;
71
- }
72
- ensure() {
73
- if (this.audioContext && this.analyserNode && this.frequencyData) {
74
- return true;
75
- }
76
- if (typeof window === `undefined` || !window.AudioContext) {
77
- return false;
78
- }
79
- const audioContext = new window.AudioContext();
80
- const audioSourceNode = audioContext.createMediaElementSource(
81
- this.audioElement
82
- );
83
- const analyserNode = audioContext.createAnalyser();
84
- analyserNode.fftSize = AUDIO_VISUALIZER_FFT_SIZE;
85
- analyserNode.smoothingTimeConstant = 0.8;
86
- audioSourceNode.connect(analyserNode);
87
- analyserNode.connect(audioContext.destination);
88
- this.audioContext = audioContext;
89
- this.audioSourceNode = audioSourceNode;
90
- this.analyserNode = analyserNode;
91
- this.frequencyData = new Uint8Array(analyserNode.frequencyBinCount);
92
- return true;
93
- }
94
- }
95
-
96
- class AudioVisualizer extends ReactiveEntity {
97
- audioGraph;
98
- playbackState$ = new BehaviorSubject({
99
- trackId: void 0,
100
- isRunning: false,
101
- resetLevels: false
102
- });
103
- subscriptions = new Subscription();
104
- constructor(audioElement) {
105
- super({
106
- levels: getIdleVisualizerLevels(),
107
- isActive: false,
108
- trackId: void 0
109
- });
110
- this.audioGraph = new VisualizerAudioGraph(audioElement);
111
- this.subscriptions.add(
112
- this.playbackState$.pipe(
113
- distinctUntilChanged(
114
- (previous, next) => previous.trackId === next.trackId && previous.isRunning === next.isRunning && previous.resetLevels === next.resetLevels
115
- ),
116
- switchMap(({ trackId, isRunning, resetLevels }) => {
117
- if (!trackId || !isRunning) {
118
- return of({
119
- trackId,
120
- isActive: false,
121
- ...resetLevels ? { levels: getIdleVisualizerLevels() } : void 0
122
- });
123
- }
124
- const shouldResetLevels = this.value.trackId !== trackId;
125
- return concat(
126
- of({
127
- trackId,
128
- isActive: true,
129
- ...shouldResetLevels ? { levels: getIdleVisualizerLevels() } : void 0
130
- }),
131
- this.createLevels$().pipe(
132
- map((levels) => ({
133
- trackId,
134
- levels,
135
- isActive: true
136
- })),
137
- defaultIfEmpty({
138
- trackId,
139
- isActive: false,
140
- levels: getIdleVisualizerLevels()
141
- })
142
- )
143
- );
144
- })
145
- ).subscribe((value) => {
146
- this.mergeCompare(value);
147
- })
148
- );
149
- }
150
- start(currentTrack) {
151
- if (!currentTrack) return;
152
- this.playbackState$.next({
153
- trackId: currentTrack.id,
154
- isRunning: true,
155
- resetLevels: false
156
- });
157
- }
158
- setTrack(trackId) {
159
- this.playbackState$.next({
160
- trackId,
161
- isRunning: false,
162
- resetLevels: false
163
- });
164
- }
165
- reset(trackId) {
166
- this.playbackState$.next({
167
- trackId,
168
- isRunning: false,
169
- resetLevels: true
170
- });
171
- }
172
- stop({ resetLevels = false } = {}) {
173
- this.playbackState$.next({
174
- trackId: this.playbackState$.value.trackId,
175
- isRunning: false,
176
- resetLevels
177
- });
178
- }
179
- destroy() {
180
- this.stop({
181
- resetLevels: true
182
- });
183
- this.subscriptions.unsubscribe();
184
- this.playbackState$.complete();
185
- this.audioGraph.destroy();
186
- super.destroy();
187
- }
188
- createLevels$() {
189
- return defer(() => {
190
- return from(this.audioGraph.resumeIfNeeded()).pipe(
191
- switchMap(
192
- (isReady) => isReady ? animationFrames().pipe(map(() => this.audioGraph.readLevels())) : EMPTY
193
- )
194
- );
195
- });
196
- }
197
- }
198
-
199
- class AudioElementAdapter {
200
- element;
201
- canPlay$;
202
- ended$;
203
- isPlaying$;
204
- metrics$;
205
- constructor() {
206
- this.element = document.createElement(`audio`);
207
- this.element.preload = `metadata`;
208
- this.canPlay$ = fromEvent(this.element, `canplay`).pipe(share());
209
- this.ended$ = fromEvent(this.element, `ended`).pipe(share());
210
- this.isPlaying$ = merge(
211
- fromEvent(this.element, `play`).pipe(map(() => true)),
212
- fromEvent(this.element, `pause`).pipe(map(() => false))
213
- ).pipe(share());
214
- this.metrics$ = merge(
215
- fromEvent(this.element, `timeupdate`),
216
- fromEvent(this.element, `seeking`),
217
- fromEvent(this.element, `seeked`),
218
- fromEvent(this.element, `loadedmetadata`),
219
- fromEvent(this.element, `durationchange`),
220
- this.canPlay$
221
- ).pipe(
222
- map(() => ({
223
- currentTime: this.element.currentTime,
224
- duration: Number.isFinite(this.element.duration) ? this.element.duration : void 0
225
- })),
226
- share()
227
- );
228
- }
229
- get paused() {
230
- return this.element.paused;
231
- }
232
- get hasSource() {
233
- return this.element.hasAttribute(`src`);
234
- }
235
- play$() {
236
- return defer(() => from(this.element.play())).pipe(
237
- retry({
238
- count: 1,
239
- delay: () => this.canPlay$.pipe(take(1))
240
- })
241
- );
242
- }
243
- pause() {
244
- this.element.pause();
245
- }
246
- loadSource(src) {
247
- this.element.src = src;
248
- this.element.load();
249
- }
250
- unloadSource() {
251
- this.element.removeAttribute(`src`);
252
- this.element.load();
253
- }
254
- setCurrentTime(value) {
255
- this.element.currentTime = value;
256
- }
257
- }
258
-
259
- class ResourcesResolver {
260
- cachedSourceByTrackId = /* @__PURE__ */ new Map();
261
- hasCachedSource(trackId) {
262
- return this.cachedSourceByTrackId.has(trackId);
263
- }
264
- getTrackResourceUrl$ = (track, resourcesHandler) => {
265
- const cachedSource = this.cachedSourceByTrackId.get(track.id);
266
- if (cachedSource) {
267
- return of(cachedSource.url);
268
- }
269
- const resource$ = from(resourcesHandler.getResource());
270
- return resource$.pipe(
271
- switchMap((resource) => {
272
- if (resource instanceof URL) {
273
- this.cachedSourceByTrackId.set(track.id, {
274
- url: resource.href
275
- });
276
- return of(resource.href);
277
- }
278
- if (resource instanceof Response) {
279
- return from(resource.blob()).pipe(
280
- map((blob) => {
281
- const objectUrl = URL.createObjectURL(blob);
282
- this.cachedSourceByTrackId.set(track.id, {
283
- url: objectUrl,
284
- release: () => {
285
- URL.revokeObjectURL(objectUrl);
286
- }
287
- });
288
- return objectUrl;
289
- })
290
- );
291
- }
292
- this.cachedSourceByTrackId.set(track.id, {
293
- url: track.href
294
- });
295
- return of(track.href);
296
- })
297
- );
298
- };
299
- releaseTrackSource(trackId) {
300
- const cachedSource = this.cachedSourceByTrackId.get(trackId);
301
- if (!cachedSource) return;
302
- this.cachedSourceByTrackId.delete(trackId);
303
- cachedSource.release?.();
304
- }
305
- releaseAll() {
306
- for (const trackId of this.cachedSourceByTrackId.keys()) {
307
- this.releaseTrackSource(trackId);
308
- }
309
- }
310
- destroy() {
311
- this.releaseAll();
312
- }
313
- }
314
-
315
- const y = (e, t) => e === t ? true : e.length !== t.length ? false : e.every((n, o) => n === t[o]);
316
- const F = (e, t, n) => {
317
- if (Object.is(e, t))
318
- return true;
319
- if (typeof e != "object" || e === null || typeof t != "object" || t === null)
320
- return false;
321
- const o = Object.keys(e), r = Object.keys(t);
322
- if (o.length !== r.length)
323
- return false;
324
- const l = n?.customEqual ?? Object.is, c = e, s = t;
325
- for (const a of o)
326
- if (!Object.hasOwn(s, a) || !l(c[a], s[a]))
327
- return false;
328
- return true;
329
- };
330
- const m = () => {
331
- if (!(typeof window > "u"))
332
- return window;
1
+ import { DocumentRenderer as e, ReactiveEntity as t, isDefined as n, mapKeysTo as r, setStylePropertyIfChanged as i } from "@prose-reader/core";
2
+ import { BehaviorSubject as a, EMPTY as o, Subject as s, Subscription as c, animationFrames as l, catchError as u, combineLatest as d, concat as f, defaultIfEmpty as p, defer as m, distinctUntilChanged as h, filter as g, from as _, fromEvent as v, map as y, merge as b, of as x, retry as S, share as C, shareReplay as w, switchMap as T, take as E, takeUntil as D, tap as O, withLatestFrom as k } from "rxjs";
3
+ //#region src/visualizer/levels.ts
4
+ var A = 80, j = .035, M = () => Array.from({ length: A }, () => 0), N = (e) => {
5
+ if (e.length === 0) return M();
6
+ let t = Math.max(1, Math.floor(e.length / A));
7
+ return Array.from({ length: A }, (n, r) => {
8
+ let i = r * t, a = Math.min(e.length, i + t);
9
+ if (i >= e.length || i === a) return 0;
10
+ let o = 0;
11
+ for (let t = i; t < a; t += 1) o += e[t] ?? 0;
12
+ let s = (o / (a - i) / 255 - j) / (1 - j);
13
+ return Math.max(0, Math.min(1, s));
14
+ });
15
+ }, P = 256, ee = class {
16
+ audioElement;
17
+ audioContext;
18
+ audioSourceNode;
19
+ analyserNode;
20
+ frequencyData;
21
+ constructor(e) {
22
+ this.audioElement = e;
23
+ }
24
+ async resumeIfNeeded() {
25
+ if (!this.ensure()) return !1;
26
+ let e = this.audioContext;
27
+ if (!e) return !1;
28
+ if (e.state !== "suspended") return !0;
29
+ try {
30
+ return await e.resume(), !0;
31
+ } catch {
32
+ return !1;
33
+ }
34
+ }
35
+ readLevels() {
36
+ return !this.analyserNode || !this.frequencyData ? M() : (this.analyserNode.getByteFrequencyData(this.frequencyData), N(this.frequencyData));
37
+ }
38
+ destroy() {
39
+ this.audioSourceNode?.disconnect(), this.analyserNode?.disconnect(), this.audioContext?.close().catch(() => void 0), this.audioContext = void 0, this.audioSourceNode = void 0, this.analyserNode = void 0, this.frequencyData = void 0;
40
+ }
41
+ ensure() {
42
+ if (this.audioContext && this.analyserNode && this.frequencyData) return !0;
43
+ if (typeof window > "u" || !window.AudioContext) return !1;
44
+ let e = new window.AudioContext(), t = e.createMediaElementSource(this.audioElement), n = e.createAnalyser();
45
+ return n.fftSize = P, n.smoothingTimeConstant = .8, t.connect(n), n.connect(e.destination), this.audioContext = e, this.audioSourceNode = t, this.analyserNode = n, this.frequencyData = new Uint8Array(n.frequencyBinCount), !0;
46
+ }
47
+ }, F = class extends t {
48
+ audioGraph;
49
+ playbackState$ = new a({
50
+ trackId: void 0,
51
+ isRunning: !1,
52
+ resetLevels: !1
53
+ });
54
+ subscriptions = new c();
55
+ constructor(e) {
56
+ super({
57
+ levels: M(),
58
+ isActive: !1,
59
+ trackId: void 0
60
+ }), this.audioGraph = new ee(e), this.subscriptions.add(this.playbackState$.pipe(h((e, t) => e.trackId === t.trackId && e.isRunning === t.isRunning && e.resetLevels === t.resetLevels), T(({ trackId: e, isRunning: t, resetLevels: n }) => !e || !t ? x({
61
+ trackId: e,
62
+ isActive: !1,
63
+ ...n ? { levels: M() } : void 0
64
+ }) : f(x({
65
+ trackId: e,
66
+ isActive: !0,
67
+ ...this.value.trackId === e ? void 0 : { levels: M() }
68
+ }), this.createLevels$().pipe(y((t) => ({
69
+ trackId: e,
70
+ levels: t,
71
+ isActive: !0
72
+ })), p({
73
+ trackId: e,
74
+ isActive: !1,
75
+ levels: M()
76
+ }))))).subscribe((e) => {
77
+ this.mergeCompare(e);
78
+ }));
79
+ }
80
+ start(e) {
81
+ e && this.playbackState$.next({
82
+ trackId: e.id,
83
+ isRunning: !0,
84
+ resetLevels: !1
85
+ });
86
+ }
87
+ setTrack(e) {
88
+ this.playbackState$.next({
89
+ trackId: e,
90
+ isRunning: !1,
91
+ resetLevels: !1
92
+ });
93
+ }
94
+ reset(e) {
95
+ this.playbackState$.next({
96
+ trackId: e,
97
+ isRunning: !1,
98
+ resetLevels: !0
99
+ });
100
+ }
101
+ stop({ resetLevels: e = !1 } = {}) {
102
+ this.playbackState$.next({
103
+ trackId: this.playbackState$.value.trackId,
104
+ isRunning: !1,
105
+ resetLevels: e
106
+ });
107
+ }
108
+ destroy() {
109
+ this.stop({ resetLevels: !0 }), this.subscriptions.unsubscribe(), this.playbackState$.complete(), this.audioGraph.destroy(), super.destroy();
110
+ }
111
+ createLevels$() {
112
+ return m(() => _(this.audioGraph.resumeIfNeeded()).pipe(T((e) => e ? l().pipe(y(() => this.audioGraph.readLevels())) : o)));
113
+ }
114
+ }, I = class {
115
+ element;
116
+ canPlay$;
117
+ ended$;
118
+ isPlaying$;
119
+ metrics$;
120
+ constructor() {
121
+ this.element = document.createElement("audio"), this.element.preload = "metadata", this.canPlay$ = v(this.element, "canplay").pipe(C()), this.ended$ = v(this.element, "ended").pipe(C()), this.isPlaying$ = b(v(this.element, "play").pipe(y(() => !0)), v(this.element, "pause").pipe(y(() => !1))).pipe(C()), this.metrics$ = b(v(this.element, "timeupdate"), v(this.element, "seeking"), v(this.element, "seeked"), v(this.element, "loadedmetadata"), v(this.element, "durationchange"), this.canPlay$).pipe(y(() => ({
122
+ currentTime: this.element.currentTime,
123
+ duration: Number.isFinite(this.element.duration) ? this.element.duration : void 0
124
+ })), C());
125
+ }
126
+ get paused() {
127
+ return this.element.paused;
128
+ }
129
+ get hasSource() {
130
+ return this.element.hasAttribute("src");
131
+ }
132
+ play$() {
133
+ return m(() => _(this.element.play())).pipe(S({
134
+ count: 1,
135
+ delay: () => this.canPlay$.pipe(E(1))
136
+ }));
137
+ }
138
+ pause() {
139
+ this.element.pause();
140
+ }
141
+ loadSource(e) {
142
+ this.element.src = e, this.element.load();
143
+ }
144
+ unloadSource() {
145
+ this.element.removeAttribute("src"), this.element.load();
146
+ }
147
+ setCurrentTime(e) {
148
+ this.element.currentTime = e;
149
+ }
150
+ }, L = class {
151
+ cachedSourceByTrackId = /* @__PURE__ */ new Map();
152
+ hasCachedSource(e) {
153
+ return this.cachedSourceByTrackId.has(e);
154
+ }
155
+ getTrackResourceUrl$ = (e, t) => {
156
+ let n = this.cachedSourceByTrackId.get(e.id);
157
+ return n ? x(n.url) : _(t.getResource()).pipe(T((t) => t instanceof URL ? (this.cachedSourceByTrackId.set(e.id, { url: t.href }), x(t.href)) : t instanceof Response ? _(t.blob()).pipe(y((t) => {
158
+ let n = URL.createObjectURL(t);
159
+ return this.cachedSourceByTrackId.set(e.id, {
160
+ url: n,
161
+ release: () => {
162
+ URL.revokeObjectURL(n);
163
+ }
164
+ }), n;
165
+ })) : (this.cachedSourceByTrackId.set(e.id, { url: e.href }), x(e.href))));
166
+ };
167
+ releaseTrackSource(e) {
168
+ let t = this.cachedSourceByTrackId.get(e);
169
+ t && (this.cachedSourceByTrackId.delete(e), t.release?.());
170
+ }
171
+ releaseAll() {
172
+ for (let e of this.cachedSourceByTrackId.keys()) this.releaseTrackSource(e);
173
+ }
174
+ destroy() {
175
+ this.releaseAll();
176
+ }
177
+ }, R = (e, t) => e === t ? !0 : e.length === t.length ? e.every((e, n) => e === t[n]) : !1, z = (e, t, n) => {
178
+ if (Object.is(e, t)) return !0;
179
+ if (typeof e != "object" || !e || typeof t != "object" || !t) return !1;
180
+ let r = Object.keys(e), i = Object.keys(t);
181
+ if (r.length !== i.length) return !1;
182
+ let a = n?.customEqual ?? Object.is, o = e, s = t;
183
+ for (let e of r) if (!Object.hasOwn(s, e) || !a(o[e], s[e])) return !1;
184
+ return !0;
185
+ }, B = () => {
186
+ if (!(typeof window > "u")) return window;
333
187
  };
334
- function b() {
335
- const e = m()?.__PROSE_READER_DEBUG;
336
- return e === true || e === "true";
188
+ function V() {
189
+ let e = B()?.__PROSE_READER_DEBUG;
190
+ return e === !0 || e === "true";
337
191
  }
338
- const E = (e, t) => typeof e == "boolean" ? {
339
- enabled: e,
340
- options: t
192
+ var H = (e, t) => typeof e == "boolean" ? {
193
+ enabled: e,
194
+ options: t
341
195
  } : {
342
- enabled: e?.enabled,
343
- options: e ?? t
344
- }, w = (e) => e.at(-1)?.color, u = (e) => {
345
- if (!e.length)
346
- return;
347
- if (!e.some((o) => o.color))
348
- return [e.map((o) => o.label).join(" ")];
349
- let t = "";
350
- const n = [];
351
- for (const o of e)
352
- t += `%c${t ? ` ${o.label}` : o.label}`, n.push(
353
- o.color ? `color: ${o.color}` : ""
354
- );
355
- return [t, ...n];
356
- }, p = (e = [], t = b()) => {
357
- let n = t;
358
- const o = {
359
- enable: (c) => {
360
- l(c);
361
- },
362
- namespace: (c, s, a) => {
363
- const i = E(
364
- s,
365
- a
366
- ), g = i.options?.color ?? w(e);
367
- return p(
368
- [
369
- ...e,
370
- {
371
- label: `[${c}]`,
372
- color: g
373
- }
374
- ],
375
- i.enabled ?? n
376
- );
377
- },
378
- isEnabled: () => n,
379
- debug: () => {
380
- },
381
- info: () => {
382
- },
383
- log: () => {
384
- },
385
- groupCollapsed: () => {
386
- },
387
- groupEnd: () => {
388
- },
389
- getGroupArgs: (c) => {
390
- const s = u(e);
391
- if (!s)
392
- return [c];
393
- const [a, ...i] = s;
394
- return [`${a} ${c}`, ...i];
395
- },
396
- warn: () => {
397
- },
398
- error: () => {
399
- }
400
- }, r = (c) => {
401
- if (!c) {
402
- o.debug = () => {
403
- }, o.info = () => {
404
- }, o.log = () => {
405
- }, o.groupCollapsed = () => {
406
- }, o.groupEnd = () => {
407
- }, o.warn = () => {
408
- }, o.error = () => {
409
- };
410
- return;
411
- }
412
- const s = u(e) ?? [];
413
- o.debug = Function.prototype.bind.call(
414
- console.debug,
415
- console,
416
- ...s
417
- ), o.info = Function.prototype.bind.call(
418
- console.info,
419
- console,
420
- ...s
421
- ), o.log = Function.prototype.bind.call(
422
- console.log,
423
- console,
424
- ...s
425
- ), o.groupCollapsed = Function.prototype.bind.call(
426
- console.groupCollapsed,
427
- console
428
- ), o.groupEnd = Function.prototype.bind.call(console.groupEnd, console), o.warn = Function.prototype.bind.call(
429
- console.warn,
430
- console,
431
- ...s
432
- ), o.error = Function.prototype.bind.call(
433
- console.error,
434
- console,
435
- ...s
436
- );
437
- }, l = (c) => {
438
- n !== c && (n = c, r(n));
439
- };
440
- return r(n), o;
441
- }; p();
442
-
443
- const AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
444
- `mp3`,
445
- `m4a`,
446
- `m4b`,
447
- `aac`,
448
- `ogg`,
449
- `oga`,
450
- `wav`,
451
- `flac`,
452
- `opus`
453
- ]);
454
- const getExtensionFromHref = (href) => {
455
- const [pathname = ``] = href.split(/[?#]/);
456
- const segments = pathname.split(`/`);
457
- const basename = segments.at(-1) ?? ``;
458
- const extension = basename.split(`.`).at(-1);
459
- return extension?.toLowerCase();
460
- };
461
- const isAudioSpineItem = (item) => {
462
- const mediaType = item.mediaType?.split(`;`).at(0)?.trim().toLowerCase();
463
- if (mediaType?.startsWith(`audio/`)) {
464
- return true;
465
- }
466
- const extension = getExtensionFromHref(item.href);
467
- return extension ? AUDIO_EXTENSIONS.has(extension) : false;
468
- };
469
-
470
- const getTrackAtSpineItemIndex = (tracks, index) => {
471
- if (index === void 0) return void 0;
472
- return tracks.find((track) => track.index === index);
473
- };
474
- const getVisibleTracks = (tracks, pagination) => {
475
- const beginTrack = getTrackAtSpineItemIndex(
476
- tracks,
477
- pagination.beginSpineItemIndex
478
- );
479
- const endTrack = getTrackAtSpineItemIndex(
480
- tracks,
481
- pagination.endSpineItemIndex
482
- );
483
- return [beginTrack, endTrack].filter(
484
- (track, i, arr) => track !== void 0 && arr.indexOf(track) === i
485
- );
486
- };
487
- function createTrackStreams(reader, state$) {
488
- const tracks$ = reader.context.manifest$.pipe(
489
- map(
490
- (manifest) => manifest.spineItems.filter(isAudioSpineItem).map((item) => ({
491
- id: item.id,
492
- href: item.href,
493
- index: item.index,
494
- mediaType: item.mediaType
495
- }))
496
- ),
497
- shareReplay({ bufferSize: 1, refCount: true })
498
- );
499
- const pagination$ = reader.pagination.state$.pipe(
500
- mapKeysTo([`beginSpineItemIndex`, `endSpineItemIndex`]),
501
- distinctUntilChanged(F),
502
- shareReplay({ bufferSize: 1, refCount: true })
503
- );
504
- const visibleTrackIds$ = combineLatest([tracks$, pagination$]).pipe(
505
- map(
506
- ([tracks, pagination]) => getVisibleTracks(tracks, pagination).map(({ id }) => id)
507
- ),
508
- distinctUntilChanged(y),
509
- shareReplay({ bufferSize: 1, refCount: true })
510
- );
511
- const currentTrack$ = state$.pipe(
512
- map((state) => state.currentTrack),
513
- distinctUntilChanged()
514
- );
515
- const nextTrack$ = combineLatest([tracks$, pagination$, currentTrack$]).pipe(
516
- map(([tracks, { endSpineItemIndex }, currentTrack]) => {
517
- const nextTrackInPaginationWindow = currentTrack && endSpineItemIndex !== void 0 ? tracks.find(
518
- ({ index }) => index > currentTrack.index && index <= endSpineItemIndex
519
- ) : void 0;
520
- const nextTrackAfterCurrentTrack = currentTrack ? tracks.find(({ index }) => index > currentTrack.index) : void 0;
521
- return { nextTrackInPaginationWindow, nextTrackAfterCurrentTrack };
522
- }),
523
- shareReplay({ bufferSize: 1, refCount: true })
524
- );
525
- return {
526
- tracks$,
527
- visibleTrackIds$,
528
- nextTrack$
529
- };
530
- }
531
-
532
- const initialDesiredPlayback = {
533
- shouldPlay: false,
534
- trackId: void 0
535
- };
536
- const initialState = {
537
- tracks: [],
538
- currentTrack: void 0,
539
- isPlaying: false,
540
- isLoading: false,
541
- hasError: false,
542
- currentTime: 0,
543
- duration: void 0
196
+ enabled: e?.enabled,
197
+ options: e ?? t
198
+ }, U = (e) => e.at(-1)?.color, W = (e) => {
199
+ if (!e.length) return;
200
+ if (!e.some((e) => e.color)) return [e.map((e) => e.label).join(" ")];
201
+ let t = "", n = [];
202
+ for (let r of e) t += `%c${t ? ` ${r.label}` : r.label}`, n.push(r.color ? `color: ${r.color}` : "");
203
+ return [t, ...n];
204
+ }, G = (e = [], t = V()) => {
205
+ let n = t, r = {
206
+ enable: (e) => {
207
+ a(e);
208
+ },
209
+ namespace: (t, r, i) => {
210
+ let a = H(r, i), o = a.options?.color ?? U(e);
211
+ return G([...e, {
212
+ label: `[${t}]`,
213
+ color: o
214
+ }], a.enabled ?? n);
215
+ },
216
+ isEnabled: () => n,
217
+ debug: () => {},
218
+ info: () => {},
219
+ log: () => {},
220
+ groupCollapsed: () => {},
221
+ groupEnd: () => {},
222
+ getGroupArgs: (t) => {
223
+ let n = W(e);
224
+ if (!n) return [t];
225
+ let [r, ...i] = n;
226
+ return [`${r} ${t}`, ...i];
227
+ },
228
+ warn: () => {},
229
+ error: () => {}
230
+ }, i = (t) => {
231
+ if (!t) {
232
+ r.debug = () => {}, r.info = () => {}, r.log = () => {}, r.groupCollapsed = () => {}, r.groupEnd = () => {}, r.warn = () => {}, r.error = () => {};
233
+ return;
234
+ }
235
+ let n = W(e) ?? [];
236
+ r.debug = Function.prototype.bind.call(console.debug, console, ...n), r.info = Function.prototype.bind.call(console.info, console, ...n), r.log = Function.prototype.bind.call(console.log, console, ...n), r.groupCollapsed = Function.prototype.bind.call(console.groupCollapsed, console), r.groupEnd = Function.prototype.bind.call(console.groupEnd, console), r.warn = Function.prototype.bind.call(console.warn, console, ...n), r.error = Function.prototype.bind.call(console.error, console, ...n);
237
+ }, a = (e) => {
238
+ n !== e && (n = e, i(n));
239
+ };
240
+ return i(n), r;
544
241
  };
545
- class AudioController extends ReactiveEntity {
546
- reader;
547
- audio;
548
- visualizer$;
549
- resourcesResolver = new ResourcesResolver();
550
- visibleTrackIds$;
551
- playCommandSubject = new Subject();
552
- pauseCommandSubject = new Subject();
553
- selectCommandSubject = new Subject();
554
- desiredPlayback$ = new BehaviorSubject(
555
- initialDesiredPlayback
556
- );
557
- subscriptions = new Subscription();
558
- constructor(reader, audio = new AudioElementAdapter()) {
559
- super(initialState);
560
- this.reader = reader;
561
- this.audio = audio;
562
- this.visualizer$ = new AudioVisualizer(this.audio.element);
563
- const { tracks$, visibleTrackIds$, nextTrack$ } = createTrackStreams(
564
- this.reader,
565
- this.state$
566
- );
567
- this.visibleTrackIds$ = visibleTrackIds$;
568
- const firstVisibleTrackId$ = this.visibleTrackIds$.pipe(
569
- map((trackIds) => trackIds[0]),
570
- distinctUntilChanged(),
571
- share()
572
- );
573
- const visibleTrackReset$ = firstVisibleTrackId$.pipe(
574
- withLatestFrom(this.state$),
575
- filter(
576
- ([trackId, state]) => trackId === void 0 && (state.currentTrack?.id !== void 0 || state.isLoading)
577
- ),
578
- map(([, state]) => state.tracks)
579
- );
580
- const visibleTrackSelectionIntent$ = firstVisibleTrackId$.pipe(
581
- filter((trackId) => trackId !== void 0),
582
- withLatestFrom(this.desiredPlayback$),
583
- map(([trackId, { shouldPlay }]) => ({
584
- trackId,
585
- options: {
586
- navigate: false,
587
- play: shouldPlay ? true : void 0
588
- }
589
- }))
590
- );
591
- const tracksChanged$ = tracks$.pipe(
592
- tap(() => this.resourcesResolver.releaseAll())
593
- );
594
- const playbackReset$ = merge(visibleTrackReset$, tracksChanged$).pipe(
595
- tap((tracks) => {
596
- this.emitDesiredPlayback({ shouldPlay: false, trackId: void 0 });
597
- this.visualizer$.stop({ resetLevels: true });
598
- this.unmountCurrentSource();
599
- this.mergeCompare({
600
- tracks,
601
- currentTrack: void 0,
602
- isLoading: false,
603
- isPlaying: false,
604
- hasError: false,
605
- currentTime: 0,
606
- duration: void 0
607
- });
608
- this.visualizer$.reset(void 0);
609
- }),
610
- share()
611
- );
612
- const playSelectionIntent$ = this.playCommandSubject.pipe(
613
- filter(() => !this.audio.hasSource),
614
- map(() => this.state.currentTrack ?? this.state.tracks[0]),
615
- filter(isDefined),
616
- map((track) => ({
617
- trackId: track.id,
618
- options: { navigate: false, play: true }
619
- }))
620
- );
621
- const resumePlayback$ = this.playCommandSubject.pipe(
622
- filter(() => this.audio.hasSource),
623
- tap(() => {
624
- this.emitDesiredPlayback({
625
- shouldPlay: true,
626
- trackId: this.state.currentTrack?.id
627
- });
628
- })
629
- );
630
- const endedSelectionIntent$ = this.audio.ended$.pipe(
631
- withLatestFrom(nextTrack$),
632
- switchMap(
633
- ([, { nextTrackAfterCurrentTrack, nextTrackInPaginationWindow }]) => {
634
- this.visualizer$.stop({ resetLevels: true });
635
- if (nextTrackInPaginationWindow) {
636
- return of({
637
- trackId: nextTrackInPaginationWindow.id,
638
- options: { navigate: false, play: true }
639
- });
640
- }
641
- if (!nextTrackAfterCurrentTrack) {
642
- this.emitDesiredPlayback({ shouldPlay: false, trackId: void 0 });
643
- }
644
- this.reader.navigation.goToRightOrBottomSpineItem();
645
- return EMPTY;
646
- }
647
- )
648
- );
649
- const selection$ = merge(
650
- this.selectCommandSubject,
651
- visibleTrackSelectionIntent$,
652
- endedSelectionIntent$,
653
- playSelectionIntent$
654
- ).pipe(
655
- withLatestFrom(this.state$),
656
- filter(
657
- ([selectionIntent, state]) => state.tracks.some(({ id }) => id === selectionIntent.trackId)
658
- ),
659
- map(([selectionIntent, state]) => ({
660
- selectionIntent,
661
- state,
662
- isReselectionWhileLoading: state.currentTrack?.id === selectionIntent.trackId && state.isLoading
663
- })),
664
- tap(({ selectionIntent, state, isReselectionWhileLoading }) => {
665
- if (selectionIntent.options.navigate !== false) {
666
- this.reader.navigation.navigate({
667
- spineItem: selectionIntent.trackId,
668
- animation: `turn`
669
- });
670
- }
671
- if (isReselectionWhileLoading && selectionIntent.options.play !== void 0) {
672
- this.emitDesiredPlayback({
673
- shouldPlay: selectionIntent.options.play,
674
- trackId: state.currentTrack?.id
675
- });
676
- }
677
- }),
678
- filter(({ isReselectionWhileLoading }) => !isReselectionWhileLoading),
679
- switchMap(
680
- ({ selectionIntent: { trackId, options } }) => this.selectTrack$({ trackId, options, playbackReset$ })
681
- )
682
- );
683
- const playback$ = merge(resumePlayback$, selection$).pipe(
684
- withLatestFrom(this.desiredPlayback$),
685
- switchMap(([, { shouldPlay, trackId }]) => {
686
- if (!shouldPlay || !this.audio.hasSource || this.state.currentTrack?.id !== trackId) {
687
- return EMPTY;
688
- }
689
- this.mergeCompare({ hasError: false });
690
- return this.audio.play$().pipe(
691
- catchError(() => {
692
- this.mergeCompare({ hasError: true });
693
- return EMPTY;
694
- })
695
- );
696
- })
697
- );
698
- this.subscriptions.add(playbackReset$.subscribe());
699
- this.subscriptions.add(playback$.subscribe());
700
- this.subscriptions.add(
701
- this.pauseCommandSubject.subscribe(() => {
702
- this.emitDesiredPlayback({ shouldPlay: false, trackId: void 0 });
703
- })
704
- );
705
- this.subscriptions.add(
706
- this.audio.isPlaying$.subscribe((isPlaying) => {
707
- this.mergeCompare({ isPlaying });
708
- if (!isPlaying) {
709
- this.visualizer$.stop();
710
- return;
711
- }
712
- if (!this.state.currentTrack) return;
713
- this.visualizer$.start(this.state.currentTrack);
714
- })
715
- );
716
- this.subscriptions.add(
717
- this.audio.metrics$.subscribe(({ currentTime, duration }) => {
718
- this.mergeCompare({ currentTime, duration });
719
- })
720
- );
721
- }
722
- get state() {
723
- return this.value;
724
- }
725
- get visualizer() {
726
- return this.visualizer$.value;
727
- }
728
- resetTrackSelection({
729
- currentTrack,
730
- isLoading
731
- }) {
732
- this.mergeCompare({
733
- currentTrack,
734
- isLoading,
735
- isPlaying: false,
736
- hasError: false,
737
- currentTime: 0,
738
- duration: void 0
739
- });
740
- this.visualizer$.reset(currentTrack?.id);
741
- }
742
- emitDesiredPlayback({ shouldPlay, trackId }) {
743
- this.desiredPlayback$.next({
744
- shouldPlay,
745
- trackId: shouldPlay ? trackId : void 0
746
- });
747
- if (!shouldPlay) {
748
- this.audio.pause();
749
- }
750
- }
751
- unmountCurrentSource() {
752
- if (!this.audio.hasSource) return;
753
- const trackId = this.state.currentTrack?.id;
754
- if (!this.audio.paused) {
755
- this.audio.pause();
756
- }
757
- this.audio.unloadSource();
758
- if (trackId) {
759
- this.resourcesResolver.releaseTrackSource(trackId);
760
- }
761
- }
762
- mountSource(source) {
763
- this.unmountCurrentSource();
764
- this.audio.loadSource(source);
765
- }
766
- resolveTrackSource(track) {
767
- const spineItem = this.reader.spineItemsManager.get(track.index);
768
- if (!spineItem) return EMPTY;
769
- return this.resourcesResolver.getTrackResourceUrl$(
770
- track,
771
- spineItem.resourcesHandler
772
- );
773
- }
774
- selectTrack$({
775
- trackId,
776
- options,
777
- playbackReset$
778
- }) {
779
- const track = this.state.tracks.find(({ id }) => id === trackId);
780
- if (!track) return EMPTY;
781
- const currentTrack = this.state.currentTrack;
782
- const shouldPlay = options.play ?? (!this.audio.paused && currentTrack !== void 0);
783
- this.emitDesiredPlayback({ shouldPlay, trackId: track.id });
784
- if (currentTrack?.id === track.id && this.audio.hasSource) {
785
- this.visualizer$.setTrack(track.id);
786
- return shouldPlay ? of(void 0) : EMPTY;
787
- }
788
- this.unmountCurrentSource();
789
- this.resetTrackSelection({
790
- currentTrack: track,
791
- isLoading: true
792
- });
793
- return this.resolveTrackSource(track).pipe(
794
- map((source) => ({ source })),
795
- defaultIfEmpty({ source: void 0 }),
796
- catchError(() => of({ source: void 0 })),
797
- takeUntil(playbackReset$),
798
- tap(({ source }) => {
799
- if (!source) {
800
- this.emitDesiredPlayback({ shouldPlay: false, trackId: void 0 });
801
- } else {
802
- this.mountSource(source);
803
- }
804
- this.mergeCompare({ isLoading: false });
805
- }),
806
- filter(({ source }) => source !== void 0),
807
- map(() => void 0)
808
- );
809
- }
810
- select(trackId, options = {}) {
811
- this.selectCommandSubject.next({ trackId, options });
812
- }
813
- play() {
814
- this.playCommandSubject.next();
815
- }
816
- pause() {
817
- this.pauseCommandSubject.next();
818
- }
819
- toggle() {
820
- if (this.audio.paused) {
821
- this.play();
822
- return;
823
- }
824
- this.pause();
825
- }
826
- setCurrentTime(value) {
827
- this.mergeCompare({ currentTime: value });
828
- this.audio.setCurrentTime(value);
829
- }
830
- destroy() {
831
- this.subscriptions.unsubscribe();
832
- this.playCommandSubject.complete();
833
- this.pauseCommandSubject.complete();
834
- this.selectCommandSubject.complete();
835
- this.desiredPlayback$.complete();
836
- this.visualizer$.destroy();
837
- this.unmountCurrentSource();
838
- this.resourcesResolver.destroy();
839
- super.destroy();
840
- }
242
+ G();
243
+ //#endregion
244
+ //#region src/utils.ts
245
+ var K = new Set([
246
+ "mp3",
247
+ "m4a",
248
+ "m4b",
249
+ "aac",
250
+ "ogg",
251
+ "oga",
252
+ "wav",
253
+ "flac",
254
+ "opus"
255
+ ]), q = (e) => {
256
+ let [t = ""] = e.split(/[?#]/);
257
+ return (t.split("/").at(-1) ?? "").split(".").at(-1)?.toLowerCase();
258
+ }, J = (e) => {
259
+ if ((e.mediaType?.split(";").at(0)?.trim().toLowerCase())?.startsWith("audio/")) return !0;
260
+ let t = q(e.href);
261
+ return t ? K.has(t) : !1;
262
+ }, Y = (e, t) => {
263
+ if (t !== void 0) return e.find((e) => e.index === t);
264
+ }, X = (e, t) => [Y(e, t.beginSpineItemIndex), Y(e, t.endSpineItemIndex)].filter((e, t, n) => e !== void 0 && n.indexOf(e) === t);
265
+ function Z(e, t) {
266
+ let n = e.context.manifest$.pipe(y((e) => e.spineItems.filter(J).map((e) => ({
267
+ id: e.id,
268
+ href: e.href,
269
+ index: e.index,
270
+ mediaType: e.mediaType
271
+ }))), w({
272
+ bufferSize: 1,
273
+ refCount: !0
274
+ })), i = e.pagination.state$.pipe(r(["beginSpineItemIndex", "endSpineItemIndex"]), h(z), w({
275
+ bufferSize: 1,
276
+ refCount: !0
277
+ }));
278
+ return {
279
+ tracks$: n,
280
+ visibleTrackIds$: d([n, i]).pipe(y(([e, t]) => X(e, t).map(({ id: e }) => e)), h(R), w({
281
+ bufferSize: 1,
282
+ refCount: !0
283
+ })),
284
+ nextTrack$: d([
285
+ n,
286
+ i,
287
+ t.pipe(y((e) => e.currentTrack), h())
288
+ ]).pipe(y(([e, { endSpineItemIndex: t }, n]) => ({
289
+ nextTrackInPaginationWindow: n && t !== void 0 ? e.find(({ index: e }) => e > n.index && e <= t) : void 0,
290
+ nextTrackAfterCurrentTrack: n ? e.find(({ index: e }) => e > n.index) : void 0
291
+ })), w({
292
+ bufferSize: 1,
293
+ refCount: !0
294
+ }))
295
+ };
841
296
  }
842
-
843
- class AudioRenderer extends DocumentRenderer {
844
- onCreateDocument() {
845
- const ownerDocument = this.containerElement.ownerDocument;
846
- const rootElement = ownerDocument.createElement(`div`);
847
- rootElement.style.cssText = `
848
- box-sizing: border-box;
849
- width: 100%;
850
- height: 100%;
851
- display: block;
852
- `;
853
- rootElement.setAttribute(`data-prose-reader-audio-page`, this.item.id);
854
- this.setDocumentContainer(rootElement);
855
- return of(rootElement);
856
- }
857
- onLoadDocument() {
858
- this.attach();
859
- return EMPTY;
860
- }
861
- onUnload() {
862
- this.detach();
863
- return EMPTY;
864
- }
865
- onLayout() {
866
- const { width, height } = this.viewport.pageSize;
867
- const rootElement = this.documentContainer;
868
- if (rootElement) {
869
- setStylePropertyIfChanged(rootElement.style, `width`, `${width}px`);
870
- setStylePropertyIfChanged(rootElement.style, `height`, `${height}px`);
871
- }
872
- return of({ width, height });
873
- }
874
- onRenderHeadless() {
875
- return EMPTY;
876
- }
877
- getDocumentFrame() {
878
- return void 0;
879
- }
880
- /**
881
- * Audio spine items are always pre-paginated (one track per page).
882
- * Mixed audio/text books with reflowable audio chapters may need
883
- * to revisit this if that use case arises.
884
- */
885
- get renditionLayout() {
886
- return `pre-paginated`;
887
- }
888
- }
889
-
890
- const audioEnhancer = (next) => (options) => {
891
- const readerOptions = { ...options };
892
- const reader = next({
893
- ...readerOptions,
894
- getRenderer(item) {
895
- const maybeFactory = options.getRenderer?.(item);
896
- if (maybeFactory) {
897
- return maybeFactory;
898
- }
899
- if (isAudioSpineItem(item)) {
900
- return (params) => new AudioRenderer(params);
901
- }
902
- return void 0;
903
- }
904
- });
905
- const controller = new AudioController(reader);
906
- const destroy = () => {
907
- controller.destroy();
908
- reader.destroy();
909
- };
910
- return {
911
- ...reader,
912
- __PROSE_READER_ENHANCER_AUDIO: true,
913
- destroy,
914
- audio: {
915
- state$: controller.state$,
916
- visualizer$: controller.visualizer$,
917
- visibleTrackIds$: controller.visibleTrackIds$,
918
- get state() {
919
- return controller.state;
920
- },
921
- get visualizer() {
922
- return controller.visualizer;
923
- },
924
- isAudioRenderer: (renderer) => renderer instanceof AudioRenderer,
925
- play: () => controller.play(),
926
- pause: () => controller.pause(),
927
- toggle: () => controller.toggle(),
928
- setCurrentTime: (value) => controller.setCurrentTime(value),
929
- select: (trackId, options2) => controller.select(trackId, options2)
930
- }
931
- };
297
+ //#endregion
298
+ //#region src/playback/AudioController.ts
299
+ var Q = {
300
+ shouldPlay: !1,
301
+ trackId: void 0
302
+ }, te = {
303
+ tracks: [],
304
+ currentTrack: void 0,
305
+ isPlaying: !1,
306
+ isLoading: !1,
307
+ hasError: !1,
308
+ currentTime: 0,
309
+ duration: void 0
310
+ }, ne = class extends t {
311
+ reader;
312
+ audio;
313
+ visualizer$;
314
+ resourcesResolver = new L();
315
+ visibleTrackIds$;
316
+ playCommandSubject = new s();
317
+ pauseCommandSubject = new s();
318
+ selectCommandSubject = new s();
319
+ desiredPlayback$ = new a(Q);
320
+ subscriptions = new c();
321
+ constructor(e, t = new I()) {
322
+ super(te), this.reader = e, this.audio = t, this.visualizer$ = new F(this.audio.element);
323
+ let { tracks$: r, visibleTrackIds$: i, nextTrack$: a } = Z(this.reader, this.state$);
324
+ this.visibleTrackIds$ = i;
325
+ let s = this.visibleTrackIds$.pipe(y((e) => e[0]), h(), C()), c = s.pipe(k(this.state$), g(([e, t]) => e === void 0 && (t.currentTrack?.id !== void 0 || t.isLoading)), y(([, e]) => e.tracks)), l = s.pipe(g((e) => e !== void 0), k(this.desiredPlayback$), y(([e, { shouldPlay: t }]) => ({
326
+ trackId: e,
327
+ options: {
328
+ navigate: !1,
329
+ play: t ? !0 : void 0
330
+ }
331
+ }))), d = b(c, r.pipe(O(() => this.resourcesResolver.releaseAll()))).pipe(O((e) => {
332
+ this.emitDesiredPlayback({
333
+ shouldPlay: !1,
334
+ trackId: void 0
335
+ }), this.visualizer$.stop({ resetLevels: !0 }), this.unmountCurrentSource(), this.mergeCompare({
336
+ tracks: e,
337
+ currentTrack: void 0,
338
+ isLoading: !1,
339
+ isPlaying: !1,
340
+ hasError: !1,
341
+ currentTime: 0,
342
+ duration: void 0
343
+ }), this.visualizer$.reset(void 0);
344
+ }), C()), f = this.playCommandSubject.pipe(g(() => !this.audio.hasSource), y(() => this.state.currentTrack ?? this.state.tracks[0]), g(n), y((e) => ({
345
+ trackId: e.id,
346
+ options: {
347
+ navigate: !1,
348
+ play: !0
349
+ }
350
+ }))), p = this.playCommandSubject.pipe(g(() => this.audio.hasSource), O(() => {
351
+ this.emitDesiredPlayback({
352
+ shouldPlay: !0,
353
+ trackId: this.state.currentTrack?.id
354
+ });
355
+ })), m = this.audio.ended$.pipe(k(a), T(([, { nextTrackAfterCurrentTrack: e, nextTrackInPaginationWindow: t }]) => (this.visualizer$.stop({ resetLevels: !0 }), t ? x({
356
+ trackId: t.id,
357
+ options: {
358
+ navigate: !1,
359
+ play: !0
360
+ }
361
+ }) : (e || this.emitDesiredPlayback({
362
+ shouldPlay: !1,
363
+ trackId: void 0
364
+ }), this.reader.navigation.goToRightOrBottomSpineItem(), o)))), _ = b(p, b(this.selectCommandSubject, l, m, f).pipe(k(this.state$), g(([e, t]) => t.tracks.some(({ id: t }) => t === e.trackId)), y(([e, t]) => ({
365
+ selectionIntent: e,
366
+ state: t,
367
+ isReselectionWhileLoading: t.currentTrack?.id === e.trackId && t.isLoading
368
+ })), O(({ selectionIntent: e, state: t, isReselectionWhileLoading: n }) => {
369
+ e.options.navigate !== !1 && this.reader.navigation.navigate({
370
+ spineItem: e.trackId,
371
+ animation: "turn"
372
+ }), n && e.options.play !== void 0 && this.emitDesiredPlayback({
373
+ shouldPlay: e.options.play,
374
+ trackId: t.currentTrack?.id
375
+ });
376
+ }), g(({ isReselectionWhileLoading: e }) => !e), T(({ selectionIntent: { trackId: e, options: t } }) => this.selectTrack$({
377
+ trackId: e,
378
+ options: t,
379
+ playbackReset$: d
380
+ })))).pipe(k(this.desiredPlayback$), T(([, { shouldPlay: e, trackId: t }]) => !e || !this.audio.hasSource || this.state.currentTrack?.id !== t ? o : (this.mergeCompare({ hasError: !1 }), this.audio.play$().pipe(u(() => (this.mergeCompare({ hasError: !0 }), o))))));
381
+ this.subscriptions.add(d.subscribe()), this.subscriptions.add(_.subscribe()), this.subscriptions.add(this.pauseCommandSubject.subscribe(() => {
382
+ this.emitDesiredPlayback({
383
+ shouldPlay: !1,
384
+ trackId: void 0
385
+ });
386
+ })), this.subscriptions.add(this.audio.isPlaying$.subscribe((e) => {
387
+ if (this.mergeCompare({ isPlaying: e }), !e) {
388
+ this.visualizer$.stop();
389
+ return;
390
+ }
391
+ this.state.currentTrack && this.visualizer$.start(this.state.currentTrack);
392
+ })), this.subscriptions.add(this.audio.metrics$.subscribe(({ currentTime: e, duration: t }) => {
393
+ this.mergeCompare({
394
+ currentTime: e,
395
+ duration: t
396
+ });
397
+ }));
398
+ }
399
+ get state() {
400
+ return this.value;
401
+ }
402
+ get visualizer() {
403
+ return this.visualizer$.value;
404
+ }
405
+ resetTrackSelection({ currentTrack: e, isLoading: t }) {
406
+ this.mergeCompare({
407
+ currentTrack: e,
408
+ isLoading: t,
409
+ isPlaying: !1,
410
+ hasError: !1,
411
+ currentTime: 0,
412
+ duration: void 0
413
+ }), this.visualizer$.reset(e?.id);
414
+ }
415
+ emitDesiredPlayback({ shouldPlay: e, trackId: t }) {
416
+ this.desiredPlayback$.next({
417
+ shouldPlay: e,
418
+ trackId: e ? t : void 0
419
+ }), e || this.audio.pause();
420
+ }
421
+ unmountCurrentSource() {
422
+ if (!this.audio.hasSource) return;
423
+ let e = this.state.currentTrack?.id;
424
+ this.audio.paused || this.audio.pause(), this.audio.unloadSource(), e && this.resourcesResolver.releaseTrackSource(e);
425
+ }
426
+ mountSource(e) {
427
+ this.unmountCurrentSource(), this.audio.loadSource(e);
428
+ }
429
+ resolveTrackSource(e) {
430
+ let t = this.reader.spineItemsManager.get(e.index);
431
+ return t ? this.resourcesResolver.getTrackResourceUrl$(e, t.resourcesHandler) : o;
432
+ }
433
+ selectTrack$({ trackId: e, options: t, playbackReset$: n }) {
434
+ let r = this.state.tracks.find(({ id: t }) => t === e);
435
+ if (!r) return o;
436
+ let i = this.state.currentTrack, a = t.play ?? (!this.audio.paused && i !== void 0);
437
+ return this.emitDesiredPlayback({
438
+ shouldPlay: a,
439
+ trackId: r.id
440
+ }), i?.id === r.id && this.audio.hasSource ? (this.visualizer$.setTrack(r.id), a ? x(void 0) : o) : (this.unmountCurrentSource(), this.resetTrackSelection({
441
+ currentTrack: r,
442
+ isLoading: !0
443
+ }), this.resolveTrackSource(r).pipe(y((e) => ({ source: e })), p({ source: void 0 }), u(() => x({ source: void 0 })), D(n), O(({ source: e }) => {
444
+ e ? this.mountSource(e) : this.emitDesiredPlayback({
445
+ shouldPlay: !1,
446
+ trackId: void 0
447
+ }), this.mergeCompare({ isLoading: !1 });
448
+ }), g(({ source: e }) => e !== void 0), y(() => void 0)));
449
+ }
450
+ select(e, t = {}) {
451
+ this.selectCommandSubject.next({
452
+ trackId: e,
453
+ options: t
454
+ });
455
+ }
456
+ play() {
457
+ this.playCommandSubject.next();
458
+ }
459
+ pause() {
460
+ this.pauseCommandSubject.next();
461
+ }
462
+ toggle() {
463
+ if (this.audio.paused) {
464
+ this.play();
465
+ return;
466
+ }
467
+ this.pause();
468
+ }
469
+ setCurrentTime(e) {
470
+ this.mergeCompare({ currentTime: e }), this.audio.setCurrentTime(e);
471
+ }
472
+ destroy() {
473
+ this.subscriptions.unsubscribe(), this.playCommandSubject.complete(), this.pauseCommandSubject.complete(), this.selectCommandSubject.complete(), this.desiredPlayback$.complete(), this.visualizer$.destroy(), this.unmountCurrentSource(), this.resourcesResolver.destroy(), super.destroy();
474
+ }
475
+ }, $ = class extends e {
476
+ onCreateDocument() {
477
+ let e = this.containerElement.ownerDocument.createElement("div");
478
+ return e.style.cssText = "\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n display: block;\n ", e.setAttribute("data-prose-reader-audio-page", this.item.id), this.setDocumentContainer(e), x(e);
479
+ }
480
+ onLoadDocument() {
481
+ return this.attach(), o;
482
+ }
483
+ onUnload() {
484
+ return this.detach(), o;
485
+ }
486
+ onLayout() {
487
+ let { width: e, height: t } = this.viewport.pageSize, n = this.documentContainer;
488
+ return n && (i(n.style, "width", `${e}px`), i(n.style, "height", `${t}px`)), x({
489
+ width: e,
490
+ height: t
491
+ });
492
+ }
493
+ onRenderHeadless() {
494
+ return o;
495
+ }
496
+ getDocumentFrame() {}
497
+ get renditionLayout() {
498
+ return "pre-paginated";
499
+ }
500
+ }, re = (e) => (t) => {
501
+ let n = e({
502
+ ...t,
503
+ getRenderer(e) {
504
+ let n = t.getRenderer?.(e);
505
+ if (n) return n;
506
+ if (J(e)) return (e) => new $(e);
507
+ }
508
+ }), r = new ne(n), i = () => {
509
+ r.destroy(), n.destroy();
510
+ };
511
+ return {
512
+ ...n,
513
+ __PROSE_READER_ENHANCER_AUDIO: !0,
514
+ destroy: i,
515
+ audio: {
516
+ state$: r.state$,
517
+ visualizer$: r.visualizer$,
518
+ visibleTrackIds$: r.visibleTrackIds$,
519
+ get state() {
520
+ return r.state;
521
+ },
522
+ get visualizer() {
523
+ return r.visualizer;
524
+ },
525
+ isAudioRenderer: (e) => e instanceof $,
526
+ play: () => r.play(),
527
+ pause: () => r.pause(),
528
+ toggle: () => r.toggle(),
529
+ setCurrentTime: (e) => r.setCurrentTime(e),
530
+ select: (e, t) => r.select(e, t)
531
+ }
532
+ };
932
533
  };
534
+ //#endregion
535
+ export { $ as AudioRenderer, re as audioEnhancer, J as isAudioSpineItem };
933
536
 
934
- export { AudioRenderer, audioEnhancer, isAudioSpineItem };
935
- //# sourceMappingURL=index.js.map
537
+ //# sourceMappingURL=index.js.map