@mango-iiif/av 0.0.2 → 0.0.3
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/README.md +6 -0
- package/dist/annotations.d.ts +2 -2
- package/dist/annotations.js +3 -3
- package/dist/chapters.d.ts +2 -2
- package/dist/chapters.js +3 -3
- package/dist/chunk-6JIJRMNE.js +218 -0
- package/dist/chunk-6JIJRMNE.js.map +1 -0
- package/dist/{chunk-3LLEY7MA.js → chunk-AZFOTEAO.js} +2 -2
- package/dist/{chunk-GX7UU6V4.js → chunk-CSE4GIHB.js} +61 -21
- package/dist/chunk-CSE4GIHB.js.map +1 -0
- package/dist/{chunk-ML77YRJL.js → chunk-D7W5UINF.js} +92 -18
- package/dist/chunk-D7W5UINF.js.map +1 -0
- package/dist/{chunk-R4OUWZ3R.js → chunk-EV6YN7WV.js} +22 -8
- package/dist/chunk-EV6YN7WV.js.map +1 -0
- package/dist/{chunk-JXUHKKIX.js → chunk-HM72GHCH.js} +2 -2
- package/dist/{chunk-IFTSCRCU.js → chunk-NH6PEDNF.js} +3 -3
- package/dist/{chunk-PZ6GV6ZJ.js → chunk-RP47ZLOD.js} +6 -6
- package/dist/{chunk-YH6DS7J6.js → chunk-U3H552X2.js} +5 -4
- package/dist/chunk-U3H552X2.js.map +1 -0
- package/dist/{controller-DLy38ais.d.ts → controller-BLA8XNsG.d.ts} +10 -2
- package/dist/{controller-element-BOKM9v2S.d.ts → controller-element-BY0157ve.d.ts} +1 -1
- package/dist/core.d.ts +2 -2
- package/dist/core.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +9 -9
- package/dist/media.d.ts +1 -1
- package/dist/media.js +4 -4
- package/dist/player.d.ts +3 -2
- package/dist/player.js +5 -4
- package/dist/player.js.map +1 -1
- package/dist/transcript-element.d.ts +2 -2
- package/dist/transcript-element.js +4 -4
- package/docs/API.md +10 -0
- package/package.json +1 -1
- package/dist/chunk-GX7UU6V4.js.map +0 -1
- package/dist/chunk-ML77YRJL.js.map +0 -1
- package/dist/chunk-NTHMZZYJ.js +0 -38
- package/dist/chunk-NTHMZZYJ.js.map +0 -1
- package/dist/chunk-R4OUWZ3R.js.map +0 -1
- package/dist/chunk-YH6DS7J6.js.map +0 -1
- /package/dist/{chunk-3LLEY7MA.js.map → chunk-AZFOTEAO.js.map} +0 -0
- /package/dist/{chunk-JXUHKKIX.js.map → chunk-HM72GHCH.js.map} +0 -0
- /package/dist/{chunk-IFTSCRCU.js.map → chunk-NH6PEDNF.js.map} +0 -0
- /package/dist/{chunk-PZ6GV6ZJ.js.map → chunk-RP47ZLOD.js.map} +0 -0
package/README.md
CHANGED
|
@@ -27,11 +27,17 @@ media.config = {
|
|
|
27
27
|
supplementalFiles: true,
|
|
28
28
|
},
|
|
29
29
|
controls: {
|
|
30
|
+
navigation: false,
|
|
31
|
+
autoAdvance: false,
|
|
30
32
|
captions: true,
|
|
31
33
|
playbackRate: true,
|
|
32
34
|
pictureInPicture: true,
|
|
33
35
|
fullscreen: true,
|
|
34
36
|
},
|
|
37
|
+
audioArt: {
|
|
38
|
+
transcript: true,
|
|
39
|
+
visualizer: 'waveform',
|
|
40
|
+
},
|
|
35
41
|
resume: { enabled: true },
|
|
36
42
|
};
|
|
37
43
|
|
package/dist/annotations.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AVControllerElement } from './controller-element-
|
|
2
|
-
import {
|
|
1
|
+
import { A as AVControllerElement } from './controller-element-BY0157ve.js';
|
|
2
|
+
import { k as AVPlayerEventMap } from './controller-BLA8XNsG.js';
|
|
3
3
|
|
|
4
4
|
declare class MangoAVAnnotationsElement extends AVControllerElement {
|
|
5
5
|
#private;
|
package/dist/annotations.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MangoAVAnnotationsElement,
|
|
3
3
|
defineMangoAVAnnotations
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NH6PEDNF.js";
|
|
5
5
|
import "./chunk-CDLYT3KB.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-HM72GHCH.js";
|
|
7
|
+
import "./chunk-EV6YN7WV.js";
|
|
8
8
|
|
|
9
9
|
// src/annotations.ts
|
|
10
10
|
if (typeof window !== "undefined" && "customElements" in window) defineMangoAVAnnotations();
|
package/dist/chapters.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AVControllerElement } from './controller-element-
|
|
2
|
-
import {
|
|
1
|
+
import { A as AVControllerElement } from './controller-element-BY0157ve.js';
|
|
2
|
+
import { k as AVPlayerEventMap } from './controller-BLA8XNsG.js';
|
|
3
3
|
|
|
4
4
|
declare class MangoAVChaptersElement extends AVControllerElement {
|
|
5
5
|
#private;
|
package/dist/chapters.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MangoAVChaptersElement,
|
|
3
3
|
defineMangoAVChapters
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-U3H552X2.js";
|
|
5
|
+
import "./chunk-HM72GHCH.js";
|
|
6
|
+
import "./chunk-EV6YN7WV.js";
|
|
7
7
|
|
|
8
8
|
// src/chapters.ts
|
|
9
9
|
if (typeof window !== "undefined" && "customElements" in window) defineMangoAVChapters();
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeHtml
|
|
3
|
+
} from "./chunk-EV6YN7WV.js";
|
|
4
|
+
|
|
5
|
+
// src/components/media-chrome.ts
|
|
6
|
+
var registration;
|
|
7
|
+
var languageRegistrations = /* @__PURE__ */ new Map();
|
|
8
|
+
var loadLanguage = (language) => {
|
|
9
|
+
const normalized = language.toLowerCase();
|
|
10
|
+
const code = normalized === "zh-cn" || normalized === "zh-tw" ? normalized : normalized.split("-")[0] ?? normalized;
|
|
11
|
+
if (languageRegistrations.has(code)) return languageRegistrations.get(code);
|
|
12
|
+
const pending = (() => {
|
|
13
|
+
switch (code) {
|
|
14
|
+
case "de":
|
|
15
|
+
return import("media-chrome/lang/de");
|
|
16
|
+
case "es":
|
|
17
|
+
return import("media-chrome/lang/es");
|
|
18
|
+
case "fr":
|
|
19
|
+
return import("media-chrome/lang/fr");
|
|
20
|
+
case "pt":
|
|
21
|
+
return import("media-chrome/lang/pt");
|
|
22
|
+
case "zh-cn":
|
|
23
|
+
return import("media-chrome/lang/zh-CN");
|
|
24
|
+
case "zh-tw":
|
|
25
|
+
return import("media-chrome/lang/zh-TW");
|
|
26
|
+
default:
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
if (pending) languageRegistrations.set(code, pending);
|
|
31
|
+
return pending;
|
|
32
|
+
};
|
|
33
|
+
var ensureMediaChrome = async (language) => {
|
|
34
|
+
if (language) await loadLanguage(language);
|
|
35
|
+
registration ??= import("media-chrome");
|
|
36
|
+
await registration;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/components/audio-art.ts
|
|
40
|
+
var audioArtTranscriptHtml = (label, cues, enabled) => {
|
|
41
|
+
if (!enabled || cues.length === 0) return `<span class="audio-art-title">${escapeHtml(label)}</span>`;
|
|
42
|
+
return `<div class="audio-art-transcript" aria-live="polite" aria-label="${escapeHtml(label)}">
|
|
43
|
+
${cues.map((cue) => `<div class="audio-art-cue" data-audio-cue data-start="${cue.start ?? ""}" data-end="${cue.end ?? cue.start ?? ""}">${cue.speaker ? `<span class="audio-art-speaker">${escapeHtml(cue.speaker)}</span>` : ""}<span>${escapeHtml(cue.text)}</span></div>`).join("")}
|
|
44
|
+
</div>`;
|
|
45
|
+
};
|
|
46
|
+
var resolveAudioVisualizerMode = (value) => value === true ? "pulse" : value || void 0;
|
|
47
|
+
var audioArtContentHtml = (label, cues, transcriptEnabled, visualizerMode) => `<div class="audio-art-text">${audioArtTranscriptHtml(label, cues, transcriptEnabled)}</div>${visualizerMode === "waveform" || visualizerMode === "spectrum" ? `<canvas class="audio-art-visualizer-canvas" aria-hidden="true"></canvas>` : ""}`;
|
|
48
|
+
var syncAudioArtTranscript = (root, time, instant = false) => {
|
|
49
|
+
const cues = [...root.querySelectorAll("[data-audio-cue]")];
|
|
50
|
+
if (cues.length === 0) return;
|
|
51
|
+
const transcript = root.querySelector(".audio-art-transcript");
|
|
52
|
+
if (instant && transcript) {
|
|
53
|
+
transcript.classList.add("seeking");
|
|
54
|
+
requestAnimationFrame(() => transcript.classList.remove("seeking"));
|
|
55
|
+
}
|
|
56
|
+
let activeIndex = cues.findIndex((cue) => {
|
|
57
|
+
const start = Number(cue.dataset.start);
|
|
58
|
+
const end = Number(cue.dataset.end);
|
|
59
|
+
return Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end);
|
|
60
|
+
});
|
|
61
|
+
if (activeIndex < 0) {
|
|
62
|
+
for (let index = cues.length - 1; index >= 0; index -= 1) {
|
|
63
|
+
const cue = cues[index];
|
|
64
|
+
if (cue && Number.isFinite(Number(cue.dataset.start)) && time >= Number(cue.dataset.start)) {
|
|
65
|
+
activeIndex = index;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (activeIndex < 0) activeIndex = 0;
|
|
71
|
+
cues.forEach((cue, index) => {
|
|
72
|
+
const offset = index - activeIndex;
|
|
73
|
+
cue.classList.toggle("active", offset === 0);
|
|
74
|
+
cue.classList.toggle("adjacent", Math.abs(offset) === 1);
|
|
75
|
+
cue.toggleAttribute("aria-hidden", offset !== 0);
|
|
76
|
+
cue.style.opacity = offset === 0 ? "1" : Math.abs(offset) === 1 ? ".2" : "0";
|
|
77
|
+
cue.style.transform = `translateY(calc(-50% + ${offset * 4.25}rem))`;
|
|
78
|
+
cue.style.visibility = Math.abs(offset) <= 1 ? "visible" : "hidden";
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
var AudioArtVisualizer = class {
|
|
82
|
+
#token = 0;
|
|
83
|
+
#context;
|
|
84
|
+
#source;
|
|
85
|
+
#analyzer;
|
|
86
|
+
#frame = 0;
|
|
87
|
+
#level = 0;
|
|
88
|
+
connect(media, art, mode) {
|
|
89
|
+
this.disconnect();
|
|
90
|
+
const token = ++this.#token;
|
|
91
|
+
if (typeof AudioContext === "undefined") return;
|
|
92
|
+
try {
|
|
93
|
+
if (token !== this.#token || !media.isConnected || !art.isConnected) return;
|
|
94
|
+
const context = new AudioContext();
|
|
95
|
+
const source = context.createMediaElementSource(media);
|
|
96
|
+
const analyzer = context.createAnalyser();
|
|
97
|
+
analyzer.fftSize = 512;
|
|
98
|
+
analyzer.smoothingTimeConstant = 0.95;
|
|
99
|
+
source.connect(context.destination);
|
|
100
|
+
source.connect(analyzer);
|
|
101
|
+
this.#context = context;
|
|
102
|
+
this.#source = source;
|
|
103
|
+
this.#analyzer = analyzer;
|
|
104
|
+
const samples = new Uint8Array(analyzer.fftSize);
|
|
105
|
+
const frequencies = new Uint8Array(analyzer.frequencyBinCount);
|
|
106
|
+
const canvas = art.querySelector(".audio-art-visualizer-canvas");
|
|
107
|
+
const canvasContext = canvas?.getContext("2d");
|
|
108
|
+
const reducedMotion = typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
109
|
+
const resizeCanvas = () => {
|
|
110
|
+
if (!canvas || !canvasContext) return;
|
|
111
|
+
const scale = Math.min(2, devicePixelRatio || 1);
|
|
112
|
+
const width = Math.max(1, Math.round(art.clientWidth * scale));
|
|
113
|
+
const height = Math.max(1, Math.round(art.clientHeight * scale));
|
|
114
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
115
|
+
canvas.width = width;
|
|
116
|
+
canvas.height = height;
|
|
117
|
+
}
|
|
118
|
+
canvasContext.setTransform(scale, 0, 0, scale, 0, 0);
|
|
119
|
+
};
|
|
120
|
+
const drawWaveform = (strength) => {
|
|
121
|
+
if (!canvas || !canvasContext) return;
|
|
122
|
+
resizeCanvas();
|
|
123
|
+
const width = art.clientWidth;
|
|
124
|
+
const height = art.clientHeight;
|
|
125
|
+
canvasContext.clearRect(0, 0, width, height);
|
|
126
|
+
if (strength === 0) return;
|
|
127
|
+
const accent = getComputedStyle(art).getPropertyValue("--mango-av-accent").trim() || "#5b3fd6";
|
|
128
|
+
canvasContext.globalAlpha = Math.min(0.7, 0.18 + strength / 100);
|
|
129
|
+
canvasContext.strokeStyle = accent;
|
|
130
|
+
canvasContext.lineWidth = 1.5;
|
|
131
|
+
canvasContext.beginPath();
|
|
132
|
+
const amplitude = reducedMotion ? 0 : Math.min(height * 0.16, 5 + this.#level * height * 0.12);
|
|
133
|
+
for (let index = 0; index < samples.length; index += 1) {
|
|
134
|
+
const x = index / (samples.length - 1) * width;
|
|
135
|
+
const sample = (samples[index] - 128) / 128;
|
|
136
|
+
const y = height * 0.72 + sample * amplitude;
|
|
137
|
+
if (index === 0) canvasContext.moveTo(x, y);
|
|
138
|
+
else canvasContext.lineTo(x, y);
|
|
139
|
+
}
|
|
140
|
+
canvasContext.stroke();
|
|
141
|
+
canvasContext.globalAlpha = 1;
|
|
142
|
+
};
|
|
143
|
+
const drawSpectrum = (strength) => {
|
|
144
|
+
if (!canvas || !canvasContext) return;
|
|
145
|
+
resizeCanvas();
|
|
146
|
+
const width = art.clientWidth;
|
|
147
|
+
const height = art.clientHeight;
|
|
148
|
+
canvasContext.clearRect(0, 0, width, height);
|
|
149
|
+
if (strength === 0) return;
|
|
150
|
+
analyzer.getByteFrequencyData(frequencies);
|
|
151
|
+
const accent = getComputedStyle(art).getPropertyValue("--mango-av-accent").trim() || "#5b3fd6";
|
|
152
|
+
const barCount = 24;
|
|
153
|
+
const gap = 3;
|
|
154
|
+
const barWidth = Math.max(1, (width - gap * (barCount - 1)) / barCount);
|
|
155
|
+
canvasContext.fillStyle = accent;
|
|
156
|
+
canvasContext.globalAlpha = reducedMotion ? 0.2 : Math.min(0.55, 0.15 + strength / 120);
|
|
157
|
+
for (let index = 0; index < barCount; index += 1) {
|
|
158
|
+
const frequencyIndex = Math.floor((index / barCount) ** 1.7 * frequencies.length * 0.7);
|
|
159
|
+
const energy = reducedMotion ? 0.08 : (frequencies[frequencyIndex] ?? 0) / 255;
|
|
160
|
+
const barHeight = Math.max(1, energy * height * 0.32);
|
|
161
|
+
canvasContext.fillRect(index * (barWidth + gap), height - barHeight, barWidth, barHeight);
|
|
162
|
+
}
|
|
163
|
+
canvasContext.globalAlpha = 1;
|
|
164
|
+
};
|
|
165
|
+
const draw = () => {
|
|
166
|
+
if (token !== this.#token) return;
|
|
167
|
+
analyzer.getByteTimeDomainData(samples);
|
|
168
|
+
let sum = 0;
|
|
169
|
+
for (const sample of samples) {
|
|
170
|
+
const normalized = (sample - 128) / 128;
|
|
171
|
+
sum += normalized * normalized;
|
|
172
|
+
}
|
|
173
|
+
const rms = Math.sqrt(sum / samples.length);
|
|
174
|
+
const target = media.paused || rms < 0.012 ? 0 : Math.min(1, (rms - 0.012) * 5);
|
|
175
|
+
this.#level += (target - this.#level) * (target > this.#level ? 0.1 : 0.04);
|
|
176
|
+
if (target === 0 && this.#level < 0.01) this.#level = 0;
|
|
177
|
+
const strength = reducedMotion ? this.#level * 18 : this.#level === 0 ? 0 : Math.min(52, 12 + this.#level * 40);
|
|
178
|
+
art.style.setProperty("--mango-av-wave-rise", `${(8 - (reducedMotion ? 0 : this.#level * 6)).toFixed(2)}%`);
|
|
179
|
+
art.style.setProperty("--mango-av-wave-strength", `${strength.toFixed(1)}%`);
|
|
180
|
+
art.style.setProperty("--mango-av-wave-mid-strength", `${(strength * 0.45).toFixed(1)}%`);
|
|
181
|
+
art.style.setProperty("--mango-av-wave-edge-strength", `${(strength * 0.18).toFixed(1)}%`);
|
|
182
|
+
if (mode === "waveform") drawWaveform(strength);
|
|
183
|
+
else if (mode === "spectrum") drawSpectrum(strength);
|
|
184
|
+
this.#frame = requestAnimationFrame(draw);
|
|
185
|
+
};
|
|
186
|
+
draw();
|
|
187
|
+
const resume = () => void context.resume();
|
|
188
|
+
media.addEventListener("play", resume, { once: true });
|
|
189
|
+
if (!media.paused) resume();
|
|
190
|
+
} catch {
|
|
191
|
+
this.disconnect();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
disconnect() {
|
|
195
|
+
this.#token += 1;
|
|
196
|
+
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(this.#frame);
|
|
197
|
+
this.#frame = 0;
|
|
198
|
+
this.#level = 0;
|
|
199
|
+
try {
|
|
200
|
+
this.#source?.disconnect();
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
if (this.#context && this.#context.state !== "closed") void this.#context.close();
|
|
204
|
+
this.#analyzer = void 0;
|
|
205
|
+
this.#source = void 0;
|
|
206
|
+
this.#context = void 0;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export {
|
|
211
|
+
ensureMediaChrome,
|
|
212
|
+
audioArtTranscriptHtml,
|
|
213
|
+
resolveAudioVisualizerMode,
|
|
214
|
+
audioArtContentHtml,
|
|
215
|
+
syncAudioArtTranscript,
|
|
216
|
+
AudioArtVisualizer
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=chunk-6JIJRMNE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/media-chrome.ts","../src/components/audio-art.ts"],"sourcesContent":["let registration: Promise<unknown> | undefined;\nconst languageRegistrations = new Map<string, Promise<unknown>>();\n\nconst loadLanguage = (language: string): Promise<unknown> | undefined => {\n const normalized = language.toLowerCase();\n const code = normalized === 'zh-cn' || normalized === 'zh-tw'\n ? normalized\n : normalized.split('-')[0] ?? normalized;\n if (languageRegistrations.has(code)) return languageRegistrations.get(code);\n const pending = (() => {\n switch (code) {\n case 'de': return import('media-chrome/lang/de');\n case 'es': return import('media-chrome/lang/es');\n case 'fr': return import('media-chrome/lang/fr');\n case 'pt': return import('media-chrome/lang/pt');\n case 'zh-cn': return import('media-chrome/lang/zh-CN');\n case 'zh-tw': return import('media-chrome/lang/zh-TW');\n default: return undefined;\n }\n })();\n if (pending) languageRegistrations.set(code, pending);\n return pending;\n};\n\nexport const ensureMediaChrome = async (language?: string): Promise<void> => {\n if (language) await loadLanguage(language);\n registration ??= import('media-chrome');\n await registration;\n};\n","import type { AVAudioVisualizerMode, TranscriptCue } from '../types';\nimport { escapeHtml } from '../utils';\n\nexport const audioArtTranscriptHtml = (label: string, cues: TranscriptCue[], enabled: boolean): string => {\n if (!enabled || cues.length === 0) return `<span class=\"audio-art-title\">${escapeHtml(label)}</span>`;\n return `<div class=\"audio-art-transcript\" aria-live=\"polite\" aria-label=\"${escapeHtml(label)}\">\n ${cues.map((cue) => `<div class=\"audio-art-cue\" data-audio-cue data-start=\"${cue.start ?? ''}\" data-end=\"${cue.end ?? cue.start ?? ''}\">${cue.speaker ? `<span class=\"audio-art-speaker\">${escapeHtml(cue.speaker)}</span>` : ''}<span>${escapeHtml(cue.text)}</span></div>`).join('')}\n </div>`;\n};\n\nexport const resolveAudioVisualizerMode = (\n value: boolean | AVAudioVisualizerMode | undefined,\n): AVAudioVisualizerMode | undefined => value === true ? 'pulse' : value || undefined;\n\nexport const audioArtContentHtml = (\n label: string,\n cues: TranscriptCue[],\n transcriptEnabled: boolean,\n visualizerMode?: AVAudioVisualizerMode,\n): string => `<div class=\"audio-art-text\">${audioArtTranscriptHtml(label, cues, transcriptEnabled)}</div>${visualizerMode === 'waveform' || visualizerMode === 'spectrum' ? `<canvas class=\"audio-art-visualizer-canvas\" aria-hidden=\"true\"></canvas>` : ''}`;\n\nexport const syncAudioArtTranscript = (root: ParentNode, time: number, instant = false): void => {\n const cues = [...root.querySelectorAll<HTMLElement>('[data-audio-cue]')];\n if (cues.length === 0) return;\n const transcript = root.querySelector<HTMLElement>('.audio-art-transcript');\n if (instant && transcript) {\n transcript.classList.add('seeking');\n requestAnimationFrame(() => transcript.classList.remove('seeking'));\n }\n\n let activeIndex = cues.findIndex((cue) => {\n const start = Number(cue.dataset.start);\n const end = Number(cue.dataset.end);\n return Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end);\n });\n if (activeIndex < 0) {\n for (let index = cues.length - 1; index >= 0; index -= 1) {\n const cue = cues[index];\n if (cue && Number.isFinite(Number(cue.dataset.start)) && time >= Number(cue.dataset.start)) {\n activeIndex = index;\n break;\n }\n }\n }\n if (activeIndex < 0) activeIndex = 0;\n\n cues.forEach((cue, index) => {\n const offset = index - activeIndex;\n cue.classList.toggle('active', offset === 0);\n cue.classList.toggle('adjacent', Math.abs(offset) === 1);\n cue.toggleAttribute('aria-hidden', offset !== 0);\n cue.style.opacity = offset === 0 ? '1' : Math.abs(offset) === 1 ? '.2' : '0';\n cue.style.transform = `translateY(calc(-50% + ${offset * 4.25}rem))`;\n cue.style.visibility = Math.abs(offset) <= 1 ? 'visible' : 'hidden';\n });\n};\n\nexport class AudioArtVisualizer {\n #token = 0;\n #context: AudioContext | undefined;\n #source: MediaElementAudioSourceNode | undefined;\n #analyzer: AnalyserNode | undefined;\n #frame = 0;\n #level = 0;\n\n connect(media: HTMLMediaElement, art: HTMLElement, mode: AVAudioVisualizerMode): void {\n this.disconnect();\n const token = ++this.#token;\n if (typeof AudioContext === 'undefined') return;\n\n try {\n if (token !== this.#token || !media.isConnected || !art.isConnected) return;\n const context = new AudioContext();\n const source = context.createMediaElementSource(media);\n const analyzer = context.createAnalyser();\n analyzer.fftSize = 512;\n analyzer.smoothingTimeConstant = .95;\n source.connect(context.destination);\n source.connect(analyzer);\n this.#context = context;\n this.#source = source;\n this.#analyzer = analyzer;\n const samples = new Uint8Array(analyzer.fftSize);\n const frequencies = new Uint8Array(analyzer.frequencyBinCount);\n const canvas = art.querySelector<HTMLCanvasElement>('.audio-art-visualizer-canvas');\n const canvasContext = canvas?.getContext('2d');\n const reducedMotion = typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n const resizeCanvas = () => {\n if (!canvas || !canvasContext) return;\n const scale = Math.min(2, devicePixelRatio || 1);\n const width = Math.max(1, Math.round(art.clientWidth * scale));\n const height = Math.max(1, Math.round(art.clientHeight * scale));\n if (canvas.width !== width || canvas.height !== height) {\n canvas.width = width;\n canvas.height = height;\n }\n canvasContext.setTransform(scale, 0, 0, scale, 0, 0);\n };\n const drawWaveform = (strength: number) => {\n if (!canvas || !canvasContext) return;\n resizeCanvas();\n const width = art.clientWidth;\n const height = art.clientHeight;\n canvasContext.clearRect(0, 0, width, height);\n if (strength === 0) return;\n const accent = getComputedStyle(art).getPropertyValue('--mango-av-accent').trim() || '#5b3fd6';\n canvasContext.globalAlpha = Math.min(.7, .18 + strength / 100);\n canvasContext.strokeStyle = accent;\n canvasContext.lineWidth = 1.5;\n canvasContext.beginPath();\n const amplitude = reducedMotion ? 0 : Math.min(height * .16, 5 + this.#level * height * .12);\n for (let index = 0; index < samples.length; index += 1) {\n const x = (index / (samples.length - 1)) * width;\n const sample = (samples[index]! - 128) / 128;\n const y = height * .72 + sample * amplitude;\n if (index === 0) canvasContext.moveTo(x, y);\n else canvasContext.lineTo(x, y);\n }\n canvasContext.stroke();\n canvasContext.globalAlpha = 1;\n };\n const drawSpectrum = (strength: number) => {\n if (!canvas || !canvasContext) return;\n resizeCanvas();\n const width = art.clientWidth;\n const height = art.clientHeight;\n canvasContext.clearRect(0, 0, width, height);\n if (strength === 0) return;\n analyzer.getByteFrequencyData(frequencies);\n const accent = getComputedStyle(art).getPropertyValue('--mango-av-accent').trim() || '#5b3fd6';\n const barCount = 24;\n const gap = 3;\n const barWidth = Math.max(1, (width - gap * (barCount - 1)) / barCount);\n canvasContext.fillStyle = accent;\n canvasContext.globalAlpha = reducedMotion ? .2 : Math.min(.55, .15 + strength / 120);\n for (let index = 0; index < barCount; index += 1) {\n const frequencyIndex = Math.floor((index / barCount) ** 1.7 * frequencies.length * .7);\n const energy = reducedMotion ? .08 : (frequencies[frequencyIndex] ?? 0) / 255;\n const barHeight = Math.max(1, energy * height * .32);\n canvasContext.fillRect(index * (barWidth + gap), height - barHeight, barWidth, barHeight);\n }\n canvasContext.globalAlpha = 1;\n };\n const draw = () => {\n if (token !== this.#token) return;\n analyzer.getByteTimeDomainData(samples);\n let sum = 0;\n for (const sample of samples) {\n const normalized = (sample - 128) / 128;\n sum += normalized * normalized;\n }\n const rms = Math.sqrt(sum / samples.length);\n const target = media.paused || rms < .012 ? 0 : Math.min(1, (rms - .012) * 5);\n this.#level += (target - this.#level) * (target > this.#level ? .1 : .04);\n if (target === 0 && this.#level < .01) this.#level = 0;\n const strength = reducedMotion\n ? this.#level * 18\n : this.#level === 0 ? 0 : Math.min(52, 12 + this.#level * 40);\n art.style.setProperty('--mango-av-wave-rise', `${(8 - (reducedMotion ? 0 : this.#level * 6)).toFixed(2)}%`);\n art.style.setProperty('--mango-av-wave-strength', `${strength.toFixed(1)}%`);\n art.style.setProperty('--mango-av-wave-mid-strength', `${(strength * .45).toFixed(1)}%`);\n art.style.setProperty('--mango-av-wave-edge-strength', `${(strength * .18).toFixed(1)}%`);\n if (mode === 'waveform') drawWaveform(strength);\n else if (mode === 'spectrum') drawSpectrum(strength);\n this.#frame = requestAnimationFrame(draw);\n };\n draw();\n const resume = () => void context.resume();\n media.addEventListener('play', resume, { once: true });\n if (!media.paused) resume();\n } catch {\n this.disconnect();\n }\n }\n\n disconnect(): void {\n this.#token += 1;\n if (typeof cancelAnimationFrame !== 'undefined') cancelAnimationFrame(this.#frame);\n this.#frame = 0;\n this.#level = 0;\n try { this.#source?.disconnect(); } catch { /* Already disconnected. */ }\n if (this.#context && this.#context.state !== 'closed') void this.#context.close();\n this.#analyzer = undefined;\n this.#source = undefined;\n this.#context = undefined;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseTime
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EV6YN7WV.js";
|
|
4
4
|
|
|
5
5
|
// src/transcript.ts
|
|
6
6
|
var stripMarkup = (value) => value.replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').trim();
|
|
@@ -123,4 +123,4 @@ export {
|
|
|
123
123
|
parseTranscript,
|
|
124
124
|
loadTranscript
|
|
125
125
|
};
|
|
126
|
-
//# sourceMappingURL=chunk-
|
|
126
|
+
//# sourceMappingURL=chunk-AZFOTEAO.js.map
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
AudioArtVisualizer,
|
|
3
|
+
audioArtContentHtml,
|
|
4
|
+
audioArtTranscriptHtml,
|
|
5
|
+
ensureMediaChrome,
|
|
6
|
+
resolveAudioVisualizerMode,
|
|
7
|
+
syncAudioArtTranscript
|
|
8
|
+
} from "./chunk-6JIJRMNE.js";
|
|
4
9
|
import {
|
|
5
10
|
playFromInteraction
|
|
6
11
|
} from "./chunk-CDLYT3KB.js";
|
|
7
12
|
import {
|
|
8
13
|
loadTranscript
|
|
9
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-AZFOTEAO.js";
|
|
10
15
|
import {
|
|
11
16
|
AVPlayerController,
|
|
12
17
|
escapeHtml,
|
|
13
18
|
formatString,
|
|
14
19
|
formatTime,
|
|
15
20
|
resolveStrings
|
|
16
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-EV6YN7WV.js";
|
|
17
22
|
|
|
18
23
|
// src/components/mango-av-media.ts
|
|
19
24
|
var HTMLElementBase = globalThis.HTMLElement ?? class {
|
|
@@ -65,15 +70,24 @@ var styles = `
|
|
|
65
70
|
.header h2 { font-size: 1.15rem; margin: 0; }
|
|
66
71
|
.header p { color: var(--mango-av-muted); margin: .35rem 0 0; }
|
|
67
72
|
.layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(18rem, var(--mango-av-panel-width)); }
|
|
68
|
-
.player-column { background: #08080a; min-width: 0; position: relative; }
|
|
73
|
+
.player-column { align-self: start; background: #08080a; min-width: 0; position: relative; }
|
|
69
74
|
media-controller { display: block; width: 100%; }
|
|
70
75
|
media-controller:not([audio]) { aspect-ratio: var(--mango-av-player-aspect-ratio); }
|
|
71
76
|
video, hls-video { display: block; height: 100%; object-fit: contain; width: 100%; }
|
|
72
77
|
audio { width: 100%; }
|
|
73
78
|
media-control-bar { width: 100%; }
|
|
74
|
-
.audio-art { align-items: center; aspect-ratio: 16 / 7; background: linear-gradient(
|
|
79
|
+
.audio-art { --mango-av-wave-edge-strength: 0%; --mango-av-wave-mid-strength: 0%; --mango-av-wave-rise: 8%; --mango-av-wave-strength: 0%; align-items: center; aspect-ratio: 16 / 7; background: linear-gradient(#09080d, #111019); color: white; display: flex; justify-content: center; overflow: hidden; padding: 2rem; position: relative; text-align: center; }
|
|
80
|
+
.audio-art.visualizer--pulse { background: radial-gradient(ellipse 140% 72% at 50% calc(100% + var(--mango-av-wave-rise)), color-mix(in srgb, var(--mango-av-accent) var(--mango-av-wave-strength), transparent) 0%, color-mix(in srgb, var(--mango-av-accent) var(--mango-av-wave-mid-strength), transparent) 40%, color-mix(in srgb, var(--mango-av-accent) var(--mango-av-wave-edge-strength), transparent) 62%, transparent 78%), linear-gradient(#09080d, #111019); }
|
|
81
|
+
.audio-art-text { display: contents; }
|
|
82
|
+
.audio-art-visualizer-canvas { height: 100%; inset: 0; pointer-events: none; position: absolute; width: 100%; }
|
|
75
83
|
.audio-art img { height: 100%; inset: 0; object-fit: contain; position: absolute; width: 100%; }
|
|
76
|
-
.audio-art
|
|
84
|
+
.audio-art-title { font-size: clamp(1.1rem, 3vw, 2rem); font-weight: 700; position: relative; z-index: 1; }
|
|
85
|
+
.audio-art-transcript { height: 100%; inset: 0; overflow: hidden; position: absolute; z-index: 1; }
|
|
86
|
+
.audio-art-cue { align-items: center; display: flex; flex-direction: column; font-size: clamp(.7rem, 1.3vw, .875rem); font-weight: 550; gap: .15rem; left: 12%; line-height: 1.35; position: absolute; right: 12%; top: 50%; transition: opacity 360ms ease, transform 480ms cubic-bezier(.22, 1, .36, 1); }
|
|
87
|
+
.audio-art-transcript.seeking .audio-art-cue { transition: none; }
|
|
88
|
+
.audio-art-cue.active { font-size: clamp(.78rem, 1.5vw, 1rem); font-weight: 650; }
|
|
89
|
+
.audio-art-cue > span:last-child { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
|
90
|
+
.audio-art-speaker { font-size: .7em; letter-spacing: .04em; text-transform: uppercase; }
|
|
77
91
|
.player-toolbar { align-items: center; background: #151518; color: #fff; display: flex; flex-wrap: wrap; gap: .5rem; justify-content: space-between; padding: .55rem .7rem; }
|
|
78
92
|
.player-toolbar button, .player-toolbar select { background: #29292e; border: 1px solid #44444a; border-radius: .35rem; color: white; padding: .4rem .55rem; }
|
|
79
93
|
.player-toolbar .group { align-items: center; display: flex; gap: .45rem; }
|
|
@@ -89,7 +103,9 @@ var styles = `
|
|
|
89
103
|
.empty { color: var(--mango-av-muted); font-style: italic; }
|
|
90
104
|
.chapter-list, .annotation-list, .cue-list, .file-list { list-style: none; margin: 0; padding: 0; }
|
|
91
105
|
.chapter-list .chapter-list { border-left: 1px solid var(--mango-av-border); margin-left: .8rem; padding-left: .55rem; }
|
|
106
|
+
.chapter { margin-block: .3rem; }
|
|
92
107
|
.chapter button, .annotation button, .cue button { background: transparent; border: 0; border-radius: .35rem; color: inherit; display: grid; gap: .15rem; padding: .5rem; text-align: left; width: 100%; }
|
|
108
|
+
.chapter button { border-radius: 0; }
|
|
93
109
|
.chapter button:hover, .annotation button:hover, .cue button:hover { background: var(--mango-av-surface); }
|
|
94
110
|
.chapter button.active, .annotation button.active, .cue button.active { background: color-mix(in srgb, var(--mango-av-accent) 14%, transparent); box-shadow: inset 3px 0 var(--mango-av-accent); }
|
|
95
111
|
.time { color: var(--mango-av-muted); font-size: .78rem; }
|
|
@@ -155,6 +171,7 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
155
171
|
#loadingToken = 0;
|
|
156
172
|
#resolvedSourceUrl;
|
|
157
173
|
#sourceToken = 0;
|
|
174
|
+
#visualizer = new AudioArtVisualizer();
|
|
158
175
|
constructor() {
|
|
159
176
|
super();
|
|
160
177
|
this.#root = this.attachShadow({ mode: "open" });
|
|
@@ -186,6 +203,8 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
186
203
|
if (this.#activePanel === "annotations") this.#renderPanelOnly();
|
|
187
204
|
} else if (type === "av-timeupdate") {
|
|
188
205
|
this.#syncTimedContent();
|
|
206
|
+
} else if (type === "av-seek") {
|
|
207
|
+
this.#syncTimedContent(event.detail.to, true);
|
|
189
208
|
}
|
|
190
209
|
this.dispatchEvent(new CustomEvent(type, {
|
|
191
210
|
detail: event.detail,
|
|
@@ -222,6 +241,7 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
222
241
|
if (source !== void 0) void this.load(source);
|
|
223
242
|
}
|
|
224
243
|
disconnectedCallback() {
|
|
244
|
+
this.#visualizer.disconnect();
|
|
225
245
|
this.player.destroy();
|
|
226
246
|
}
|
|
227
247
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -263,6 +283,7 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
263
283
|
}
|
|
264
284
|
}
|
|
265
285
|
#render() {
|
|
286
|
+
this.#visualizer.disconnect();
|
|
266
287
|
const state = this.player.state;
|
|
267
288
|
const manifest = state.manifest;
|
|
268
289
|
const canvas = this.player.canvas;
|
|
@@ -278,7 +299,13 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
278
299
|
</div>`;
|
|
279
300
|
this.#bindInteractions();
|
|
280
301
|
const media = this.#root.querySelector("video, audio, hls-video");
|
|
281
|
-
if (media)
|
|
302
|
+
if (media) {
|
|
303
|
+
this.player.attachMedia(media);
|
|
304
|
+
const art = this.#root.querySelector(".audio-art.visualizer");
|
|
305
|
+
const visualizerMode = resolveAudioVisualizerMode(this.player.config.audioArt.visualizer);
|
|
306
|
+
if (art && media instanceof HTMLAudioElement && visualizerMode) this.#visualizer.connect(media, art, visualizerMode);
|
|
307
|
+
}
|
|
308
|
+
this.#syncTimedContent();
|
|
282
309
|
}
|
|
283
310
|
#renderReady(manifest, canvas, kind) {
|
|
284
311
|
const source = this.player.source;
|
|
@@ -293,13 +320,17 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
293
320
|
panels.supplementalFiles ? ["files", ui.files] : void 0
|
|
294
321
|
].filter((value) => Boolean(value));
|
|
295
322
|
const showPanel = this.#activePanel !== "none" && panelTabs.length > 0;
|
|
323
|
+
const showSourceSelector = controls.sourceSelector && canvas.sources.length > 1;
|
|
324
|
+
const showDownload = controls.download && canvas.rendering.length > 0;
|
|
325
|
+
const showPlayerToolbar = controls.navigation || controls.autoAdvance || showSourceSelector || showDownload;
|
|
326
|
+
const visualizerMode = resolveAudioVisualizerMode(this.player.config.audioArt.visualizer);
|
|
296
327
|
return `
|
|
297
328
|
${this.#resumeTime !== void 0 ? `<div class="resume">${escapeHtml(formatString(ui.resumePrompt, { time: formatTime(this.#resumeTime) }))} <button type="button" data-action="resume">${escapeHtml(ui.resume)}</button><button type="button" data-action="dismiss-resume">${escapeHtml(ui.startOver)}</button></div>` : ""}
|
|
298
329
|
<div class="layout" ${showPanel ? "" : 'style="grid-template-columns: 1fr"'}>
|
|
299
330
|
<section class="player-column" aria-label="${escapeHtml(ui.mediaPlayer)}">
|
|
300
331
|
<slot name="before-player"></slot>
|
|
301
332
|
<media-controller ${kind === "audio" ? "audio" : ""} ${this.#mediaChromeLanguage()}>
|
|
302
|
-
${kind === "audio" ? `<div class="audio-art"
|
|
333
|
+
${kind === "audio" ? `<div class="audio-art ${visualizerMode ? `visualizer visualizer--${visualizerMode}` : ""}">${audioArtContentHtml(canvas.label, this.#transcriptCues, Boolean(this.player.config.audioArt.transcript), visualizerMode)}</div>` : ""}
|
|
303
334
|
${source ? this.#mediaHtml(canvas, source.kind) : `<div class="audio-art"><span>${escapeHtml(ui.noPlayableMedia)}</span></div>`}
|
|
304
335
|
<slot name="player-overlay"></slot>
|
|
305
336
|
<slot name="controls">
|
|
@@ -319,17 +350,17 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
319
350
|
</slot>
|
|
320
351
|
</media-controller>
|
|
321
352
|
${this.#searchMarkersHtml()}
|
|
322
|
-
|
|
323
|
-
|
|
353
|
+
${showPlayerToolbar ? `<div class="player-toolbar">
|
|
354
|
+
${controls.navigation ? `<div class="group">
|
|
324
355
|
<button type="button" data-action="previous" ${canvas.index === 0 ? "disabled" : ""} aria-label="${escapeHtml(ui.previousSection)}">\u2190 ${escapeHtml(ui.previous)}</button>
|
|
325
356
|
<button type="button" data-action="next" ${canvas.index === manifest.canvases.length - 1 && !this.player.state.repeat ? "disabled" : ""} aria-label="${escapeHtml(ui.nextSection)}">${escapeHtml(ui.next)} \u2192</button>
|
|
326
|
-
</div
|
|
327
|
-
|
|
328
|
-
${
|
|
329
|
-
${
|
|
330
|
-
|
|
331
|
-
</div
|
|
332
|
-
</div
|
|
357
|
+
</div>` : ""}
|
|
358
|
+
${showSourceSelector || showDownload || controls.autoAdvance ? `<div class="group">
|
|
359
|
+
${showSourceSelector ? `<label><span class="sr-only">${escapeHtml(ui.mediaSource)}</span><select data-action="source">${canvas.sources.map((item, index) => `<option value="${index}" ${index === this.player.state.sourceIndex ? "selected" : ""}>${escapeHtml(item.label ?? item.format ?? formatString(ui.sourceNumber, { number: index + 1 }))}</option>`).join("")}</select></label>` : ""}
|
|
360
|
+
${showDownload ? `<label><span class="sr-only">${escapeHtml(ui.downloadMediaFile)}</span><select data-action="download"><option value="">${escapeHtml(ui.download)}\u2026</option>${canvas.rendering.map((file) => `<option value="${escapeHtml(file.id)}" data-filename="${escapeHtml(file.filename ?? "")}">${escapeHtml(file.label)}</option>`).join("")}</select></label>` : ""}
|
|
361
|
+
${controls.autoAdvance ? `<label><input type="checkbox" data-action="auto-advance" ${this.player.state.autoAdvance ? "checked" : ""}> ${escapeHtml(ui.autoAdvance)}</label>` : ""}
|
|
362
|
+
</div>` : ""}
|
|
363
|
+
</div>` : ""}
|
|
333
364
|
<slot name="after-player"></slot>
|
|
334
365
|
</section>
|
|
335
366
|
${showPanel ? `<aside class="panel">
|
|
@@ -495,6 +526,7 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
495
526
|
const cues = await loadTranscript(resource, transcriptOptions);
|
|
496
527
|
if (token !== this.#loadingToken) return;
|
|
497
528
|
this.#transcriptCues = cues;
|
|
529
|
+
this.#renderAudioArt();
|
|
498
530
|
if (this.#activePanel === "transcript") this.#renderPanelOnly();
|
|
499
531
|
} catch (cause) {
|
|
500
532
|
if (token !== this.#loadingToken) return;
|
|
@@ -518,8 +550,8 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
518
550
|
this.dispatchEvent(new CustomEvent("av-error", { detail: { error }, bubbles: true, composed: true }));
|
|
519
551
|
}
|
|
520
552
|
}
|
|
521
|
-
#syncTimedContent() {
|
|
522
|
-
|
|
553
|
+
#syncTimedContent(time = this.player.state.currentTime, instant = false) {
|
|
554
|
+
syncAudioArtTranscript(this.#root, time, instant);
|
|
523
555
|
const rows = this.#root.querySelectorAll("[data-start][data-end]");
|
|
524
556
|
let active;
|
|
525
557
|
rows.forEach((row) => {
|
|
@@ -533,6 +565,14 @@ var MangoAVMediaElement = class extends HTMLElementBase {
|
|
|
533
565
|
active.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
534
566
|
}
|
|
535
567
|
}
|
|
568
|
+
#renderAudioArt() {
|
|
569
|
+
const art = this.#root.querySelector(".audio-art");
|
|
570
|
+
const canvas = this.player.canvas;
|
|
571
|
+
if (!art || !canvas || this.player.source?.kind !== "audio") return;
|
|
572
|
+
const text = art.querySelector(".audio-art-text");
|
|
573
|
+
if (text) text.innerHTML = audioArtTranscriptHtml(canvas.label, this.#transcriptCues, Boolean(this.player.config.audioArt.transcript));
|
|
574
|
+
syncAudioArtTranscript(art, this.player.state.currentTime);
|
|
575
|
+
}
|
|
536
576
|
};
|
|
537
577
|
var defineMangoAVMedia = (tagName = "mango-av-media") => {
|
|
538
578
|
if (typeof customElements === "undefined") return MangoAVMediaElement;
|
|
@@ -544,4 +584,4 @@ export {
|
|
|
544
584
|
MangoAVMediaElement,
|
|
545
585
|
defineMangoAVMedia
|
|
546
586
|
};
|
|
547
|
-
//# sourceMappingURL=chunk-
|
|
587
|
+
//# sourceMappingURL=chunk-CSE4GIHB.js.map
|