@mango-iiif/av 0.0.1 → 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.
Files changed (44) hide show
  1. package/README.md +6 -0
  2. package/dist/annotations.d.ts +2 -2
  3. package/dist/annotations.js +3 -3
  4. package/dist/chapters.d.ts +2 -2
  5. package/dist/chapters.js +3 -3
  6. package/dist/chunk-6JIJRMNE.js +218 -0
  7. package/dist/chunk-6JIJRMNE.js.map +1 -0
  8. package/dist/{chunk-3LLEY7MA.js → chunk-AZFOTEAO.js} +2 -2
  9. package/dist/{chunk-GX7UU6V4.js → chunk-CSE4GIHB.js} +61 -21
  10. package/dist/chunk-CSE4GIHB.js.map +1 -0
  11. package/dist/{chunk-ML77YRJL.js → chunk-D7W5UINF.js} +92 -18
  12. package/dist/chunk-D7W5UINF.js.map +1 -0
  13. package/dist/{chunk-R4OUWZ3R.js → chunk-EV6YN7WV.js} +22 -8
  14. package/dist/chunk-EV6YN7WV.js.map +1 -0
  15. package/dist/{chunk-JXUHKKIX.js → chunk-HM72GHCH.js} +2 -2
  16. package/dist/{chunk-IFTSCRCU.js → chunk-NH6PEDNF.js} +3 -3
  17. package/dist/{chunk-PZ6GV6ZJ.js → chunk-RP47ZLOD.js} +6 -6
  18. package/dist/{chunk-YH6DS7J6.js → chunk-U3H552X2.js} +5 -4
  19. package/dist/chunk-U3H552X2.js.map +1 -0
  20. package/dist/{controller-DLy38ais.d.ts → controller-BLA8XNsG.d.ts} +10 -2
  21. package/dist/{controller-element-BOKM9v2S.d.ts → controller-element-BY0157ve.d.ts} +1 -1
  22. package/dist/core.d.ts +2 -2
  23. package/dist/core.js +2 -2
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +9 -9
  26. package/dist/media.d.ts +1 -1
  27. package/dist/media.js +4 -4
  28. package/dist/player.d.ts +3 -2
  29. package/dist/player.js +5 -4
  30. package/dist/player.js.map +1 -1
  31. package/dist/transcript-element.d.ts +2 -2
  32. package/dist/transcript-element.js +4 -4
  33. package/docs/API.md +10 -0
  34. package/package.json +1 -1
  35. package/dist/chunk-GX7UU6V4.js.map +0 -1
  36. package/dist/chunk-ML77YRJL.js.map +0 -1
  37. package/dist/chunk-NTHMZZYJ.js +0 -38
  38. package/dist/chunk-NTHMZZYJ.js.map +0 -1
  39. package/dist/chunk-R4OUWZ3R.js.map +0 -1
  40. package/dist/chunk-YH6DS7J6.js.map +0 -1
  41. /package/dist/{chunk-3LLEY7MA.js.map → chunk-AZFOTEAO.js.map} +0 -0
  42. /package/dist/{chunk-JXUHKKIX.js.map → chunk-HM72GHCH.js.map} +0 -0
  43. /package/dist/{chunk-IFTSCRCU.js.map → chunk-NH6PEDNF.js.map} +0 -0
  44. /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
 
@@ -1,5 +1,5 @@
1
- import { A as AVControllerElement } from './controller-element-BOKM9v2S.js';
2
- import { i as AVPlayerEventMap } from './controller-DLy38ais.js';
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;
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MangoAVAnnotationsElement,
3
3
  defineMangoAVAnnotations
4
- } from "./chunk-IFTSCRCU.js";
4
+ } from "./chunk-NH6PEDNF.js";
5
5
  import "./chunk-CDLYT3KB.js";
6
- import "./chunk-JXUHKKIX.js";
7
- import "./chunk-R4OUWZ3R.js";
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();
@@ -1,5 +1,5 @@
1
- import { A as AVControllerElement } from './controller-element-BOKM9v2S.js';
2
- import { i as AVPlayerEventMap } from './controller-DLy38ais.js';
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-YH6DS7J6.js";
5
- import "./chunk-JXUHKKIX.js";
6
- import "./chunk-R4OUWZ3R.js";
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-R4OUWZ3R.js";
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(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').trim();
@@ -123,4 +123,4 @@ export {
123
123
  parseTranscript,
124
124
  loadTranscript
125
125
  };
126
- //# sourceMappingURL=chunk-3LLEY7MA.js.map
126
+ //# sourceMappingURL=chunk-AZFOTEAO.js.map
@@ -1,19 +1,24 @@
1
1
  import {
2
- ensureMediaChrome
3
- } from "./chunk-NTHMZZYJ.js";
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-3LLEY7MA.js";
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-R4OUWZ3R.js";
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(135deg, var(--mango-av-accent), #111); color: white; display: flex; justify-content: center; padding: 2rem; text-align: center; }
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 span { font-size: clamp(1.1rem, 3vw, 2rem); font-weight: 700; }
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) this.player.attachMedia(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"><span>${escapeHtml(canvas.label)}</span></div>` : ""}
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
- <div class="player-toolbar">
323
- <div class="group">
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
- <div class="group">
328
- ${controls.sourceSelector && canvas.sources.length > 1 ? `<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>` : ""}
329
- ${controls.download && canvas.rendering.length > 0 ? `<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>` : ""}
330
- <label><input type="checkbox" data-action="auto-advance" ${this.player.state.autoAdvance ? "checked" : ""}> ${escapeHtml(ui.autoAdvance)}</label>
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
- const time = this.player.state.currentTime;
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-GX7UU6V4.js.map
587
+ //# sourceMappingURL=chunk-CSE4GIHB.js.map