@mango-iiif/av 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +25 -13
  3. package/dist/annotations.d.ts +2 -2
  4. package/dist/annotations.js +4 -4
  5. package/dist/captions.d.ts +23 -0
  6. package/dist/captions.js +16 -0
  7. package/dist/captions.js.map +1 -0
  8. package/dist/chapters.d.ts +2 -2
  9. package/dist/chapters.js +3 -3
  10. package/dist/{chunk-4NOWGMI5.js → chunk-53ZN4Y4L.js} +28 -66
  11. package/dist/chunk-53ZN4Y4L.js.map +1 -0
  12. package/dist/chunk-AWLDINQR.js +73 -0
  13. package/dist/chunk-AWLDINQR.js.map +1 -0
  14. package/dist/chunk-BB7RE7EO.js +407 -0
  15. package/dist/chunk-BB7RE7EO.js.map +1 -0
  16. package/dist/chunk-BMMQFCD3.js +86 -0
  17. package/dist/chunk-BMMQFCD3.js.map +1 -0
  18. package/dist/chunk-IZYEGSGT.js +148 -0
  19. package/dist/chunk-IZYEGSGT.js.map +1 -0
  20. package/dist/chunk-JXZR5DL2.js +160 -0
  21. package/dist/chunk-JXZR5DL2.js.map +1 -0
  22. package/dist/{chunk-CIAQZ2EB.js → chunk-LMZIFVHN.js} +8 -4
  23. package/dist/chunk-LMZIFVHN.js.map +1 -0
  24. package/dist/chunk-TQNEVWUH.js +39 -0
  25. package/dist/chunk-TQNEVWUH.js.map +1 -0
  26. package/dist/chunk-XXM4G3K5.js +296 -0
  27. package/dist/chunk-XXM4G3K5.js.map +1 -0
  28. package/dist/chunk-YVI7J43Q.js +29 -0
  29. package/dist/chunk-YVI7J43Q.js.map +1 -0
  30. package/dist/chunk-Z2OF3RKT.js +152 -0
  31. package/dist/chunk-Z2OF3RKT.js.map +1 -0
  32. package/dist/{controller-SkwGkVE8.d.ts → controller-Ag3nFtwP.d.ts} +16 -24
  33. package/dist/{controller-element-CkXIA6w2.d.ts → controller-element-Cn5cxKnD.d.ts} +4 -2
  34. package/dist/core.d.ts +2 -2
  35. package/dist/core.js +2 -2
  36. package/dist/index.d.ts +6 -3
  37. package/dist/index.js +21 -14
  38. package/dist/index.js.map +1 -1
  39. package/dist/media.d.ts +3 -3
  40. package/dist/media.js +11 -5
  41. package/dist/media.js.map +1 -1
  42. package/dist/player.d.ts +8 -2
  43. package/dist/player.js +3 -5
  44. package/dist/player.js.map +1 -1
  45. package/dist/ramp-2XKALEGA.js +98 -0
  46. package/dist/ramp-2XKALEGA.js.map +1 -0
  47. package/dist/transcript-element.d.ts +2 -2
  48. package/dist/transcript-element.js +6 -5
  49. package/dist/transcript-element.js.map +1 -1
  50. package/docs/API.md +70 -32
  51. package/package.json +18 -11
  52. package/dist/chunk-4NOWGMI5.js.map +0 -1
  53. package/dist/chunk-4V6FME6B.js +0 -643
  54. package/dist/chunk-4V6FME6B.js.map +0 -1
  55. package/dist/chunk-C4H46HDS.js +0 -95
  56. package/dist/chunk-C4H46HDS.js.map +0 -1
  57. package/dist/chunk-CDLYT3KB.js +0 -17
  58. package/dist/chunk-CDLYT3KB.js.map +0 -1
  59. package/dist/chunk-CIAQZ2EB.js.map +0 -1
  60. package/dist/chunk-EHIKFRFV.js +0 -75
  61. package/dist/chunk-EHIKFRFV.js.map +0 -1
  62. package/dist/chunk-ELGGWWDS.js +0 -257
  63. package/dist/chunk-ELGGWWDS.js.map +0 -1
  64. package/dist/chunk-FVYEV2LO.js +0 -163
  65. package/dist/chunk-FVYEV2LO.js.map +0 -1
  66. package/dist/chunk-YX32ZQPR.js +0 -83
  67. package/dist/chunk-YX32ZQPR.js.map +0 -1
  68. package/dist/chunk-YX6L6CUF.js +0 -355
  69. package/dist/chunk-YX6L6CUF.js.map +0 -1
@@ -1,257 +0,0 @@
1
- import {
2
- escapeHtml
3
- } from "./chunk-4NOWGMI5.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, title, cues, enabled) => {
41
- if (!enabled || cues.length === 0) return title ? `<span class="audio-art-title">${escapeHtml(title)}</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, title, cues, transcriptEnabled, visualizerMode) => `<div class="audio-art-text">${audioArtTranscriptHtml(label, title, 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
- #media;
89
- #resume;
90
- connect(media, art, mode) {
91
- this.disconnect();
92
- const token = ++this.#token;
93
- if (typeof AudioContext === "undefined") return;
94
- try {
95
- if (token !== this.#token || !media.isConnected || !art.isConnected) return;
96
- const context = new AudioContext();
97
- this.#context = context;
98
- const source = context.createMediaElementSource(media);
99
- this.#source = source;
100
- const analyzer = context.createAnalyser();
101
- this.#analyzer = analyzer;
102
- analyzer.fftSize = 512;
103
- analyzer.smoothingTimeConstant = 0.95;
104
- source.connect(context.destination);
105
- source.connect(analyzer);
106
- this.#media = media;
107
- const samples = new Uint8Array(analyzer.fftSize);
108
- const frequencies = new Uint8Array(analyzer.frequencyBinCount);
109
- const canvas = art.querySelector(".audio-art-visualizer-canvas");
110
- const canvasContext = canvas?.getContext("2d");
111
- const reducedMotion = typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
112
- const resizeCanvas = () => {
113
- if (!canvas || !canvasContext) return;
114
- const scale = Math.min(2, devicePixelRatio || 1);
115
- const width = Math.max(1, Math.round(art.clientWidth * scale));
116
- const height = Math.max(1, Math.round(art.clientHeight * scale));
117
- if (canvas.width !== width || canvas.height !== height) {
118
- canvas.width = width;
119
- canvas.height = height;
120
- }
121
- canvasContext.setTransform(scale, 0, 0, scale, 0, 0);
122
- };
123
- const drawWaveform = (strength) => {
124
- if (!canvas || !canvasContext) return;
125
- resizeCanvas();
126
- const width = art.clientWidth;
127
- const height = art.clientHeight;
128
- canvasContext.clearRect(0, 0, width, height);
129
- if (strength === 0) return;
130
- const accent = getComputedStyle(art).getPropertyValue("--mango-av-accent").trim() || "#5b3fd6";
131
- canvasContext.globalAlpha = Math.min(0.7, 0.18 + strength / 100);
132
- canvasContext.strokeStyle = accent;
133
- canvasContext.lineWidth = 1.5;
134
- canvasContext.beginPath();
135
- const amplitude = reducedMotion ? 0 : Math.min(height * 0.16, 5 + this.#level * height * 0.12);
136
- for (let index = 0; index < samples.length; index += 1) {
137
- const x = index / (samples.length - 1) * width;
138
- const sample = (samples[index] - 128) / 128;
139
- const y = height * 0.72 + sample * amplitude;
140
- if (index === 0) canvasContext.moveTo(x, y);
141
- else canvasContext.lineTo(x, y);
142
- }
143
- canvasContext.stroke();
144
- canvasContext.globalAlpha = 1;
145
- };
146
- const drawSpectrum = (strength) => {
147
- if (!canvas || !canvasContext) return;
148
- resizeCanvas();
149
- const width = art.clientWidth;
150
- const height = art.clientHeight;
151
- canvasContext.clearRect(0, 0, width, height);
152
- if (strength === 0) return;
153
- analyzer.getByteFrequencyData(frequencies);
154
- const accent = getComputedStyle(art).getPropertyValue("--mango-av-accent").trim() || "#5b3fd6";
155
- const barCount = 24;
156
- const gap = 3;
157
- const barWidth = Math.max(1, (width - gap * (barCount - 1)) / barCount);
158
- canvasContext.fillStyle = accent;
159
- canvasContext.globalAlpha = reducedMotion ? 0.2 : Math.min(0.55, 0.15 + strength / 120);
160
- for (let index = 0; index < barCount; index += 1) {
161
- const frequencyIndex = Math.floor((index / barCount) ** 1.7 * frequencies.length * 0.7);
162
- const energy = reducedMotion ? 0.08 : (frequencies[frequencyIndex] ?? 0) / 255;
163
- const barHeight = Math.max(1, energy * height * 0.32);
164
- canvasContext.fillRect(index * (barWidth + gap), height - barHeight, barWidth, barHeight);
165
- }
166
- canvasContext.globalAlpha = 1;
167
- };
168
- const draw = () => {
169
- if (token !== this.#token) return;
170
- analyzer.getByteTimeDomainData(samples);
171
- let sum = 0;
172
- for (const sample of samples) {
173
- const normalized = (sample - 128) / 128;
174
- sum += normalized * normalized;
175
- }
176
- const rms = Math.sqrt(sum / samples.length);
177
- const target = media.paused || rms < 0.012 ? 0 : Math.min(1, (rms - 0.012) * 5);
178
- this.#level += (target - this.#level) * (target > this.#level ? 0.1 : 0.04);
179
- if (target === 0 && this.#level < 0.01) this.#level = 0;
180
- const strength = reducedMotion ? this.#level * 18 : this.#level === 0 ? 0 : Math.min(52, 12 + this.#level * 40);
181
- art.style.setProperty("--mango-av-wave-rise", `${(8 - (reducedMotion ? 0 : this.#level * 6)).toFixed(2)}%`);
182
- art.style.setProperty("--mango-av-wave-strength", `${strength.toFixed(1)}%`);
183
- art.style.setProperty("--mango-av-wave-mid-strength", `${(strength * 0.45).toFixed(1)}%`);
184
- art.style.setProperty("--mango-av-wave-edge-strength", `${(strength * 0.18).toFixed(1)}%`);
185
- if (mode === "waveform") drawWaveform(strength);
186
- else if (mode === "spectrum") drawSpectrum(strength);
187
- this.#frame = requestAnimationFrame(draw);
188
- };
189
- draw();
190
- const resume = () => void context.resume();
191
- this.#resume = resume;
192
- media.addEventListener("play", resume, { once: true });
193
- if (!media.paused) resume();
194
- } catch {
195
- this.disconnect();
196
- }
197
- }
198
- disconnect() {
199
- this.#token += 1;
200
- if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(this.#frame);
201
- this.#frame = 0;
202
- this.#level = 0;
203
- if (this.#media && this.#resume) this.#media.removeEventListener("play", this.#resume);
204
- try {
205
- this.#source?.disconnect();
206
- } catch {
207
- }
208
- try {
209
- this.#analyzer?.disconnect();
210
- } catch {
211
- }
212
- if (this.#context && this.#context.state !== "closed") void this.#context.close();
213
- this.#analyzer = void 0;
214
- this.#source = void 0;
215
- this.#context = void 0;
216
- this.#media = void 0;
217
- this.#resume = void 0;
218
- }
219
- };
220
-
221
- // src/components/media-policy.ts
222
- var isCrossOriginHttp = (sourceUrl) => {
223
- try {
224
- const url = new URL(sourceUrl, document.baseURI);
225
- return (url.protocol === "http:" || url.protocol === "https:") && url.origin !== location.origin;
226
- } catch {
227
- return false;
228
- }
229
- };
230
- var resolveAudioMediaPolicy = (sourceUrl, configuredCrossOrigin, crossOriginExplicit, visualizerMode, fallback) => {
231
- const crossOrigin = isCrossOriginHttp(sourceUrl);
232
- const implicitCors = Boolean(visualizerMode) && crossOrigin && !crossOriginExplicit && !fallback;
233
- const selectedCrossOrigin = implicitCors ? "anonymous" : configuredCrossOrigin || void 0;
234
- const visualize = Boolean(visualizerMode) && !fallback && !(crossOrigin && !selectedCrossOrigin);
235
- return {
236
- ...selectedCrossOrigin ? { crossOrigin: selectedCrossOrigin } : {},
237
- implicitCors,
238
- visualize
239
- };
240
- };
241
- var applyMediaSource = (media, sourceUrl, crossOrigin) => {
242
- if (crossOrigin) media.crossOrigin = crossOrigin;
243
- else media.removeAttribute("crossorigin");
244
- media.src = sourceUrl;
245
- };
246
-
247
- export {
248
- ensureMediaChrome,
249
- audioArtTranscriptHtml,
250
- resolveAudioVisualizerMode,
251
- audioArtContentHtml,
252
- syncAudioArtTranscript,
253
- AudioArtVisualizer,
254
- resolveAudioMediaPolicy,
255
- applyMediaSource
256
- };
257
- //# sourceMappingURL=chunk-ELGGWWDS.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/media-chrome.ts","../src/components/audio-art.ts","../src/components/media-policy.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, title: string, cues: TranscriptCue[], enabled: boolean): string => {\n if (!enabled || cues.length === 0) return title ? `<span class=\"audio-art-title\">${escapeHtml(title)}</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 title: string,\n cues: TranscriptCue[],\n transcriptEnabled: boolean,\n visualizerMode?: AVAudioVisualizerMode,\n): string => `<div class=\"audio-art-text\">${audioArtTranscriptHtml(label, title, 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 #media: HTMLMediaElement | undefined;\n #resume: (() => void) | undefined;\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 this.#context = context;\n const source = context.createMediaElementSource(media);\n this.#source = source;\n const analyzer = context.createAnalyser();\n this.#analyzer = analyzer;\n analyzer.fftSize = 512;\n analyzer.smoothingTimeConstant = .95;\n source.connect(context.destination);\n source.connect(analyzer);\n this.#media = media;\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 this.#resume = 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 if (this.#media && this.#resume) this.#media.removeEventListener('play', this.#resume);\n try { this.#source?.disconnect(); } catch { /* Already disconnected. */ }\n try { this.#analyzer?.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 this.#media = undefined;\n this.#resume = undefined;\n }\n}\n","import type { AVAudioVisualizerMode } from '../types';\n\nexport interface AudioMediaPolicy {\n crossOrigin?: 'anonymous' | 'use-credentials';\n implicitCors: boolean;\n visualize: boolean;\n}\n\nconst isCrossOriginHttp = (sourceUrl: string): boolean => {\n try {\n const url = new URL(sourceUrl, document.baseURI);\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.origin !== location.origin;\n } catch {\n return false;\n }\n};\n\nexport const resolveAudioMediaPolicy = (\n sourceUrl: string,\n configuredCrossOrigin: 'anonymous' | 'use-credentials' | '',\n crossOriginExplicit: boolean,\n visualizerMode: AVAudioVisualizerMode | undefined,\n fallback: boolean,\n): AudioMediaPolicy => {\n const crossOrigin = isCrossOriginHttp(sourceUrl);\n const implicitCors = Boolean(visualizerMode) && crossOrigin && !crossOriginExplicit && !fallback;\n const selectedCrossOrigin = implicitCors ? 'anonymous' : configuredCrossOrigin || undefined;\n // Explicit no-CORS loading is respected, but a cross-origin element loaded that\n // way cannot safely be connected to Web Audio without risking silent output.\n const visualize = Boolean(visualizerMode) && !fallback && !(crossOrigin && !selectedCrossOrigin);\n return {\n ...(selectedCrossOrigin ? { crossOrigin: selectedCrossOrigin } : {}),\n implicitCors,\n visualize,\n };\n};\n\nexport const applyMediaSource = (\n media: HTMLMediaElement,\n sourceUrl: string,\n crossOrigin?: 'anonymous' | 'use-credentials',\n): void => {\n if (crossOrigin) media.crossOrigin = crossOrigin;\n else media.removeAttribute('crossorigin');\n media.src = sourceUrl;\n};\n"],"mappings":"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","names":[]}
@@ -1,163 +0,0 @@
1
- import {
2
- playFromInteraction
3
- } from "./chunk-CDLYT3KB.js";
4
- import {
5
- AVControllerElement,
6
- componentTheme
7
- } from "./chunk-C4H46HDS.js";
8
- import {
9
- loadTranscript
10
- } from "./chunk-CIAQZ2EB.js";
11
- import {
12
- escapeHtml,
13
- formatTime,
14
- resolveStrings
15
- } from "./chunk-4NOWGMI5.js";
16
-
17
- // src/components/mango-av-transcript.ts
18
- var styles = `${componentTheme}
19
- .transcript { background: var(--mango-av-background); border: 1px solid var(--mango-av-border); border-radius: var(--mango-av-radius); padding: .75rem; }
20
- h2 { font-size: 1rem; margin: 0 0 .5rem; }
21
- .tools { display: flex; flex-wrap: wrap; gap: .45rem; margin-bottom: .75rem; }
22
- .tools input, .tools select { border: 1px solid var(--mango-av-border); border-radius: .35rem; min-width: 0; padding: .45rem; }
23
- .tools input { flex: 1; }
24
- .tools a { align-content: center; color: var(--mango-av-accent); }
25
- ol { list-style: none; margin: 0; max-height: var(--mango-av-transcript-height, 34rem); overflow: auto; padding: 0; }
26
- button { background: transparent; border: 0; border-radius: .35rem; color: inherit; display: grid; gap: .2rem; padding: .5rem; text-align: left; width: 100%; }
27
- button:hover { background: var(--mango-av-surface); }
28
- button.active { background: color-mix(in srgb, var(--mango-av-accent) 14%, transparent); box-shadow: inset 3px 0 var(--mango-av-accent); }
29
- mark { background: #ffe770; }
30
- .speaker { font-weight: 700; }
31
- `;
32
- var highlight = (text, query) => {
33
- const escaped = escapeHtml(text);
34
- if (!query) return escaped;
35
- const safeQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36
- return escaped.replace(new RegExp(`(${safeQuery})`, "gi"), "<mark>$1</mark>");
37
- };
38
- var MangoAVTranscriptElement = class extends AVControllerElement {
39
- #root;
40
- #cues = [];
41
- #transcriptIndex = 0;
42
- #query = "";
43
- #loadToken = 0;
44
- constructor() {
45
- super();
46
- this.#root = this.attachShadow({ mode: "open" });
47
- }
48
- controllerChanged() {
49
- this.#cues = [];
50
- this.#transcriptIndex = 0;
51
- this.#render();
52
- void this.#loadTranscript();
53
- }
54
- handleControllerEvent(type, _event) {
55
- if (type === "av-ready" || type === "av-canvaschange") {
56
- this.#cues = [];
57
- this.#transcriptIndex = 0;
58
- this.#render();
59
- void this.#loadTranscript();
60
- } else if (type === "av-configchange") {
61
- this.#render();
62
- } else if (type === "av-timeupdate" || type === "av-seek" || type === "av-statechange") {
63
- this.#sync();
64
- }
65
- }
66
- #render() {
67
- const canvas = this.controller.canvas;
68
- const transcripts = canvas?.transcripts ?? [];
69
- const resource = transcripts[this.#transcriptIndex];
70
- const query = this.#query.trim();
71
- const ui = resolveStrings(this.controller.config.strings);
72
- const cues = query ? this.#cues.filter((cue) => cue.text.toLowerCase().includes(query.toLowerCase())) : this.#cues;
73
- this.#root.innerHTML = `<style>${styles}</style><section class="transcript" aria-label="${escapeHtml(ui.transcript)}">
74
- <slot name="heading"><h2>${escapeHtml(ui.transcript)}</h2></slot>
75
- ${!canvas ? `<p class="empty">${escapeHtml(ui.noManifest)}</p>` : transcripts.length === 0 ? `<p class="empty">${escapeHtml(ui.noTranscript)}</p>` : `
76
- <div class="tools">
77
- ${transcripts.length > 1 ? `<select data-action="transcript" aria-label="${escapeHtml(ui.transcript)}">${transcripts.map((item, index) => `<option value="${index}" ${index === this.#transcriptIndex ? "selected" : ""}>${escapeHtml(item.label)}</option>`).join("")}</select>` : ""}
78
- ${this.controller.config.transcript.search ? `<input type="search" data-action="search" value="${escapeHtml(this.#query)}" placeholder="${escapeHtml(ui.searchTranscript)}" aria-label="${escapeHtml(ui.searchTranscript)}">` : ""}
79
- ${this.controller.config.transcript.download && resource?.src ? `<a href="${escapeHtml(resource.src)}" download>${escapeHtml(ui.download)}</a>` : ""}
80
- </div>
81
- ${this.#cues.length === 0 ? `<p class="empty">${escapeHtml(ui.loadingTranscript)}</p>` : `<ol>${cues.map((cue) => `<li data-cue-id="${escapeHtml(cue.id)}" ${cue.start !== void 0 ? `data-start="${cue.start}" data-end="${cue.end ?? cue.start}"` : ""}>
82
- <button type="button" ${cue.start === void 0 ? "disabled" : ""}>
83
- ${cue.start !== void 0 ? `<span class="time">${formatTime(cue.start)}</span>` : ""}
84
- ${cue.speaker ? `<span class="speaker">${escapeHtml(cue.speaker)}</span>` : ""}
85
- <span>${highlight(cue.text, query)}</span>
86
- </button>
87
- </li>`).join("")}</ol>`}
88
- `}
89
- </section>`;
90
- this.#bind();
91
- this.#sync();
92
- }
93
- #bind() {
94
- this.#root.querySelector("[data-action=transcript]")?.addEventListener("change", (event) => {
95
- this.#transcriptIndex = Number(event.target.value);
96
- this.#cues = [];
97
- this.#render();
98
- void this.#loadTranscript();
99
- });
100
- this.#root.querySelector("[data-action=search]")?.addEventListener("input", (event) => {
101
- const input = event.target;
102
- const selection = input.selectionStart;
103
- this.#query = input.value;
104
- this.#render();
105
- const next = this.#root.querySelector("[data-action=search]");
106
- next?.focus();
107
- if (selection !== null) next?.setSelectionRange(selection, selection);
108
- this.dispatchEvent(new CustomEvent("av-transcriptsearch", { detail: { query: this.#query }, bubbles: true, composed: true }));
109
- });
110
- this.#root.querySelectorAll("[data-start] button").forEach((button) => button.addEventListener("click", () => {
111
- const time = Number(button.parentElement?.dataset.start);
112
- if (!Number.isFinite(time)) return;
113
- this.dispatchEvent(new CustomEvent("av-transcriptcueselect", { detail: { time }, bubbles: true, composed: true }));
114
- this.controller.seekTo(time);
115
- playFromInteraction(this.controller, this);
116
- }));
117
- }
118
- async #loadTranscript() {
119
- const resource = this.controller.canvas?.transcripts[this.#transcriptIndex];
120
- if (!resource) return;
121
- const token = ++this.#loadToken;
122
- const options = {};
123
- if (this.controller.config.fetch) options.fetch = this.controller.config.fetch;
124
- if (this.controller.config.requestInit) options.requestInit = this.controller.config.requestInit;
125
- if (this.controller.config.transcript.showNotes !== void 0) options.showNotes = this.controller.config.transcript.showNotes;
126
- if (this.controller.config.transcript.showMetadata !== void 0) options.showMetadata = this.controller.config.transcript.showMetadata;
127
- try {
128
- const cues = await loadTranscript(resource, options);
129
- if (token !== this.#loadToken) return;
130
- this.#cues = cues;
131
- this.#render();
132
- } catch (cause) {
133
- if (token !== this.#loadToken) return;
134
- const error = cause instanceof Error ? cause : new Error(String(cause));
135
- this.dispatchEvent(new CustomEvent("av-error", { detail: { error }, bubbles: true, composed: true }));
136
- this.#cues = [];
137
- this.#render();
138
- }
139
- }
140
- #sync() {
141
- const time = this.controller.state.currentTime;
142
- let active;
143
- this.#root.querySelectorAll("[data-start][data-end]").forEach((row) => {
144
- const start = Number(row.dataset.start);
145
- const end = Number(row.dataset.end);
146
- const isActive = Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end);
147
- row.querySelector("button")?.classList.toggle("active", isActive);
148
- if (isActive) active = row;
149
- });
150
- if (active && this.controller.config.transcript.autoScroll) active.scrollIntoView({ block: "nearest", behavior: "smooth" });
151
- }
152
- };
153
- var defineMangoAVTranscript = (tagName = "mango-av-transcript") => {
154
- if (typeof customElements === "undefined") return MangoAVTranscriptElement;
155
- if (!customElements.get(tagName)) customElements.define(tagName, MangoAVTranscriptElement);
156
- return MangoAVTranscriptElement;
157
- };
158
-
159
- export {
160
- MangoAVTranscriptElement,
161
- defineMangoAVTranscript
162
- };
163
- //# sourceMappingURL=chunk-FVYEV2LO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/mango-av-transcript.ts"],"sourcesContent":["import { AVControllerElement } from './controller-element';\nimport { componentTheme } from './component-styles';\nimport { loadTranscript } from '../transcript';\nimport { playFromInteraction } from './playback';\nimport type { AVPlayerEventMap, TranscriptCue } from '../types';\nimport { escapeHtml, formatTime, resolveStrings } from '../utils';\n\nconst styles = `${componentTheme}\n .transcript { background: var(--mango-av-background); border: 1px solid var(--mango-av-border); border-radius: var(--mango-av-radius); padding: .75rem; }\n h2 { font-size: 1rem; margin: 0 0 .5rem; }\n .tools { display: flex; flex-wrap: wrap; gap: .45rem; margin-bottom: .75rem; }\n .tools input, .tools select { border: 1px solid var(--mango-av-border); border-radius: .35rem; min-width: 0; padding: .45rem; }\n .tools input { flex: 1; }\n .tools a { align-content: center; color: var(--mango-av-accent); }\n ol { list-style: none; margin: 0; max-height: var(--mango-av-transcript-height, 34rem); overflow: auto; padding: 0; }\n button { background: transparent; border: 0; border-radius: .35rem; color: inherit; display: grid; gap: .2rem; padding: .5rem; text-align: left; width: 100%; }\n button:hover { background: var(--mango-av-surface); }\n button.active { background: color-mix(in srgb, var(--mango-av-accent) 14%, transparent); box-shadow: inset 3px 0 var(--mango-av-accent); }\n mark { background: #ffe770; }\n .speaker { font-weight: 700; }\n`;\n\nconst highlight = (text: string, query: string): string => {\n const escaped = escapeHtml(text);\n if (!query) return escaped;\n const safeQuery = query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return escaped.replace(new RegExp(`(${safeQuery})`, 'gi'), '<mark>$1</mark>');\n};\n\nexport class MangoAVTranscriptElement extends AVControllerElement {\n #root: ShadowRoot;\n #cues: TranscriptCue[] = [];\n #transcriptIndex = 0;\n #query = '';\n #loadToken = 0;\n\n constructor() {\n super();\n this.#root = this.attachShadow({ mode: 'open' });\n }\n\n protected controllerChanged(): void {\n this.#cues = [];\n this.#transcriptIndex = 0;\n this.#render();\n void this.#loadTranscript();\n }\n\n protected handleControllerEvent<K extends keyof AVPlayerEventMap>(type: K, _event: CustomEvent<AVPlayerEventMap[K]>): void {\n if (type === 'av-ready' || type === 'av-canvaschange') {\n this.#cues = [];\n this.#transcriptIndex = 0;\n this.#render();\n void this.#loadTranscript();\n } else if (type === 'av-configchange') {\n this.#render();\n } else if (type === 'av-timeupdate' || type === 'av-seek' || type === 'av-statechange') {\n this.#sync();\n }\n }\n\n #render(): void {\n const canvas = this.controller.canvas;\n const transcripts = canvas?.transcripts ?? [];\n const resource = transcripts[this.#transcriptIndex];\n const query = this.#query.trim();\n const ui = resolveStrings(this.controller.config.strings);\n const cues = query ? this.#cues.filter((cue) => cue.text.toLowerCase().includes(query.toLowerCase())) : this.#cues;\n this.#root.innerHTML = `<style>${styles}</style><section class=\"transcript\" aria-label=\"${escapeHtml(ui.transcript)}\">\n <slot name=\"heading\"><h2>${escapeHtml(ui.transcript)}</h2></slot>\n ${!canvas ? `<p class=\"empty\">${escapeHtml(ui.noManifest)}</p>` : transcripts.length === 0 ? `<p class=\"empty\">${escapeHtml(ui.noTranscript)}</p>` : `\n <div class=\"tools\">\n ${transcripts.length > 1 ? `<select data-action=\"transcript\" aria-label=\"${escapeHtml(ui.transcript)}\">${transcripts.map((item, index) => `<option value=\"${index}\" ${index === this.#transcriptIndex ? 'selected' : ''}>${escapeHtml(item.label)}</option>`).join('')}</select>` : ''}\n ${this.controller.config.transcript.search ? `<input type=\"search\" data-action=\"search\" value=\"${escapeHtml(this.#query)}\" placeholder=\"${escapeHtml(ui.searchTranscript)}\" aria-label=\"${escapeHtml(ui.searchTranscript)}\">` : ''}\n ${this.controller.config.transcript.download && resource?.src ? `<a href=\"${escapeHtml(resource.src)}\" download>${escapeHtml(ui.download)}</a>` : ''}\n </div>\n ${this.#cues.length === 0 ? `<p class=\"empty\">${escapeHtml(ui.loadingTranscript)}</p>` : `<ol>${cues.map((cue) => `<li data-cue-id=\"${escapeHtml(cue.id)}\" ${cue.start !== undefined ? `data-start=\"${cue.start}\" data-end=\"${cue.end ?? cue.start}\"` : ''}>\n <button type=\"button\" ${cue.start === undefined ? 'disabled' : ''}>\n ${cue.start !== undefined ? `<span class=\"time\">${formatTime(cue.start)}</span>` : ''}\n ${cue.speaker ? `<span class=\"speaker\">${escapeHtml(cue.speaker)}</span>` : ''}\n <span>${highlight(cue.text, query)}</span>\n </button>\n </li>`).join('')}</ol>`}\n `}\n </section>`;\n this.#bind();\n this.#sync();\n }\n\n #bind(): void {\n this.#root.querySelector<HTMLSelectElement>('[data-action=transcript]')?.addEventListener('change', (event) => {\n this.#transcriptIndex = Number((event.target as HTMLSelectElement).value);\n this.#cues = [];\n this.#render();\n void this.#loadTranscript();\n });\n this.#root.querySelector<HTMLInputElement>('[data-action=search]')?.addEventListener('input', (event) => {\n const input = event.target as HTMLInputElement;\n const selection = input.selectionStart;\n this.#query = input.value;\n this.#render();\n const next = this.#root.querySelector<HTMLInputElement>('[data-action=search]');\n next?.focus();\n if (selection !== null) next?.setSelectionRange(selection, selection);\n this.dispatchEvent(new CustomEvent('av-transcriptsearch', { detail: { query: this.#query }, bubbles: true, composed: true }));\n });\n this.#root.querySelectorAll<HTMLElement>('[data-start] button').forEach((button) => button.addEventListener('click', () => {\n const time = Number(button.parentElement?.dataset.start);\n if (!Number.isFinite(time)) return;\n this.dispatchEvent(new CustomEvent('av-transcriptcueselect', { detail: { time }, bubbles: true, composed: true }));\n this.controller.seekTo(time);\n playFromInteraction(this.controller, this);\n }));\n }\n\n async #loadTranscript(): Promise<void> {\n const resource = this.controller.canvas?.transcripts[this.#transcriptIndex];\n if (!resource) return;\n const token = ++this.#loadToken;\n const options: Parameters<typeof loadTranscript>[1] = {};\n if (this.controller.config.fetch) options.fetch = this.controller.config.fetch;\n if (this.controller.config.requestInit) options.requestInit = this.controller.config.requestInit;\n if (this.controller.config.transcript.showNotes !== undefined) options.showNotes = this.controller.config.transcript.showNotes;\n if (this.controller.config.transcript.showMetadata !== undefined) options.showMetadata = this.controller.config.transcript.showMetadata;\n try {\n const cues = await loadTranscript(resource, options);\n if (token !== this.#loadToken) return;\n this.#cues = cues;\n this.#render();\n } catch (cause) {\n if (token !== this.#loadToken) return;\n const error = cause instanceof Error ? cause : new Error(String(cause));\n this.dispatchEvent(new CustomEvent('av-error', { detail: { error }, bubbles: true, composed: true }));\n this.#cues = [];\n this.#render();\n }\n }\n\n #sync(): void {\n const time = this.controller.state.currentTime;\n let active: HTMLElement | undefined;\n this.#root.querySelectorAll<HTMLElement>('[data-start][data-end]').forEach((row) => {\n const start = Number(row.dataset.start);\n const end = Number(row.dataset.end);\n const isActive = Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end);\n row.querySelector('button')?.classList.toggle('active', isActive);\n if (isActive) active = row;\n });\n if (active && this.controller.config.transcript.autoScroll) active.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n }\n}\n\nexport const defineMangoAVTranscript = (tagName = 'mango-av-transcript'): typeof MangoAVTranscriptElement => {\n if (typeof customElements === 'undefined') return MangoAVTranscriptElement;\n if (!customElements.get(tagName)) customElements.define(tagName, MangoAVTranscriptElement);\n return MangoAVTranscriptElement;\n};\n"],"mappings":"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","names":[]}
@@ -1,83 +0,0 @@
1
- import {
2
- playFromInteraction
3
- } from "./chunk-CDLYT3KB.js";
4
- import {
5
- AVControllerElement,
6
- componentTheme
7
- } from "./chunk-C4H46HDS.js";
8
- import {
9
- escapeHtml,
10
- formatTime,
11
- resolveStrings
12
- } from "./chunk-4NOWGMI5.js";
13
-
14
- // src/components/mango-av-annotations.ts
15
- var styles = `${componentTheme}
16
- .annotations { background: var(--mango-av-background); border: 1px solid var(--mango-av-border); border-radius: var(--mango-av-radius); padding: .75rem; }
17
- h2 { font-size: 1rem; margin: 0 0 .5rem; }
18
- ol { list-style: none; margin: 0; padding: 0; }
19
- button { background: transparent; border: 0; border-radius: .35rem; color: inherit; display: grid; gap: .2rem; padding: .5rem; text-align: left; width: 100%; }
20
- button:hover { background: var(--mango-av-surface); }
21
- button.active { background: color-mix(in srgb, var(--mango-av-accent) 14%, transparent); box-shadow: inset 3px 0 var(--mango-av-accent); }
22
- .tags { display: flex; flex-wrap: wrap; gap: .25rem; }
23
- .tag { background: var(--mango-av-surface); border-radius: 999px; font-size: .75rem; padding: .15rem .45rem; }
24
- `;
25
- var annotationsHtml = (annotations, ui) => {
26
- if (annotations.length === 0) return `<p class="empty">${escapeHtml(ui.noAnnotations)}</p>`;
27
- return `<ol>${annotations.map((annotation) => `<li data-start="${annotation.time?.start ?? ""}" data-end="${annotation.time?.end ?? annotation.time?.start ?? ""}">
28
- <button type="button" ${annotation.time ? "" : "disabled"}>
29
- ${annotation.time ? `<span class="time">${formatTime(annotation.time.start)}</span>` : ""}
30
- <span>${escapeHtml(annotation.text)}</span>
31
- ${annotation.tags.length > 0 ? `<span class="tags">${annotation.tags.map((tag) => `<span class="tag">${escapeHtml(tag)}</span>`).join("")}</span>` : ""}
32
- </button>
33
- </li>`).join("")}</ol>`;
34
- };
35
- var MangoAVAnnotationsElement = class extends AVControllerElement {
36
- #root;
37
- constructor() {
38
- super();
39
- this.#root = this.attachShadow({ mode: "open" });
40
- }
41
- controllerChanged() {
42
- this.#render();
43
- }
44
- handleControllerEvent(type, _event) {
45
- if (type === "av-ready" || type === "av-canvaschange" || type === "av-annotationschange" || type === "av-configchange") this.#render();
46
- else if (type === "av-timeupdate" || type === "av-seek" || type === "av-statechange") this.#sync();
47
- }
48
- #render() {
49
- const canvas = this.controller.canvas;
50
- const ui = resolveStrings(this.controller.config.strings);
51
- this.#root.innerHTML = `<style>${styles}</style><section class="annotations" aria-label="${escapeHtml(ui.annotations)}">
52
- <slot name="heading"><h2>${escapeHtml(ui.annotations)}</h2></slot>
53
- ${canvas ? annotationsHtml(canvas.annotations, ui) : `<p class="empty">${escapeHtml(ui.noManifest)}</p>`}
54
- </section>`;
55
- this.#root.querySelectorAll("[data-start] button").forEach((button) => button.addEventListener("click", () => {
56
- const start = Number(button.parentElement?.dataset.start);
57
- if (!Number.isFinite(start)) return;
58
- this.dispatchEvent(new CustomEvent("av-annotationselect", { detail: { time: start }, bubbles: true, composed: true }));
59
- this.controller.seekTo(start);
60
- playFromInteraction(this.controller, this);
61
- }));
62
- this.#sync();
63
- }
64
- #sync() {
65
- const time = this.controller.state.currentTime;
66
- this.#root.querySelectorAll("[data-start][data-end]").forEach((row) => {
67
- const start = Number(row.dataset.start);
68
- const end = Number(row.dataset.end);
69
- row.querySelector("button")?.classList.toggle("active", Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end));
70
- });
71
- }
72
- };
73
- var defineMangoAVAnnotations = (tagName = "mango-av-annotations") => {
74
- if (typeof customElements === "undefined") return MangoAVAnnotationsElement;
75
- if (!customElements.get(tagName)) customElements.define(tagName, MangoAVAnnotationsElement);
76
- return MangoAVAnnotationsElement;
77
- };
78
-
79
- export {
80
- MangoAVAnnotationsElement,
81
- defineMangoAVAnnotations
82
- };
83
- //# sourceMappingURL=chunk-YX32ZQPR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/mango-av-annotations.ts"],"sourcesContent":["import { AVControllerElement } from './controller-element';\nimport { componentTheme } from './component-styles';\nimport { playFromInteraction } from './playback';\nimport type { AVAnnotation, AVPlayerEventMap, AVStrings } from '../types';\nimport { escapeHtml, formatTime, resolveStrings } from '../utils';\n\nconst styles = `${componentTheme}\n .annotations { background: var(--mango-av-background); border: 1px solid var(--mango-av-border); border-radius: var(--mango-av-radius); padding: .75rem; }\n h2 { font-size: 1rem; margin: 0 0 .5rem; }\n ol { list-style: none; margin: 0; padding: 0; }\n button { background: transparent; border: 0; border-radius: .35rem; color: inherit; display: grid; gap: .2rem; padding: .5rem; text-align: left; width: 100%; }\n button:hover { background: var(--mango-av-surface); }\n button.active { background: color-mix(in srgb, var(--mango-av-accent) 14%, transparent); box-shadow: inset 3px 0 var(--mango-av-accent); }\n .tags { display: flex; flex-wrap: wrap; gap: .25rem; }\n .tag { background: var(--mango-av-surface); border-radius: 999px; font-size: .75rem; padding: .15rem .45rem; }\n`;\n\nconst annotationsHtml = (annotations: AVAnnotation[], ui: AVStrings): string => {\n if (annotations.length === 0) return `<p class=\"empty\">${escapeHtml(ui.noAnnotations)}</p>`;\n return `<ol>${annotations.map((annotation) => `<li data-start=\"${annotation.time?.start ?? ''}\" data-end=\"${annotation.time?.end ?? annotation.time?.start ?? ''}\">\n <button type=\"button\" ${annotation.time ? '' : 'disabled'}>\n ${annotation.time ? `<span class=\"time\">${formatTime(annotation.time.start)}</span>` : ''}\n <span>${escapeHtml(annotation.text)}</span>\n ${annotation.tags.length > 0 ? `<span class=\"tags\">${annotation.tags.map((tag) => `<span class=\"tag\">${escapeHtml(tag)}</span>`).join('')}</span>` : ''}\n </button>\n </li>`).join('')}</ol>`;\n};\n\nexport class MangoAVAnnotationsElement extends AVControllerElement {\n #root: ShadowRoot;\n\n constructor() {\n super();\n this.#root = this.attachShadow({ mode: 'open' });\n }\n\n protected controllerChanged(): void { this.#render(); }\n\n protected handleControllerEvent<K extends keyof AVPlayerEventMap>(type: K, _event: CustomEvent<AVPlayerEventMap[K]>): void {\n if (type === 'av-ready' || type === 'av-canvaschange' || type === 'av-annotationschange' || type === 'av-configchange') this.#render();\n else if (type === 'av-timeupdate' || type === 'av-seek' || type === 'av-statechange') this.#sync();\n }\n\n #render(): void {\n const canvas = this.controller.canvas;\n const ui = resolveStrings(this.controller.config.strings);\n this.#root.innerHTML = `<style>${styles}</style><section class=\"annotations\" aria-label=\"${escapeHtml(ui.annotations)}\">\n <slot name=\"heading\"><h2>${escapeHtml(ui.annotations)}</h2></slot>\n ${canvas ? annotationsHtml(canvas.annotations, ui) : `<p class=\"empty\">${escapeHtml(ui.noManifest)}</p>`}\n </section>`;\n this.#root.querySelectorAll<HTMLElement>('[data-start] button').forEach((button) => button.addEventListener('click', () => {\n const start = Number(button.parentElement?.dataset.start);\n if (!Number.isFinite(start)) return;\n this.dispatchEvent(new CustomEvent('av-annotationselect', { detail: { time: start }, bubbles: true, composed: true }));\n this.controller.seekTo(start);\n playFromInteraction(this.controller, this);\n }));\n this.#sync();\n }\n\n #sync(): void {\n const time = this.controller.state.currentTime;\n this.#root.querySelectorAll<HTMLElement>('[data-start][data-end]').forEach((row) => {\n const start = Number(row.dataset.start);\n const end = Number(row.dataset.end);\n row.querySelector('button')?.classList.toggle('active', Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end));\n });\n }\n}\n\nexport const defineMangoAVAnnotations = (tagName = 'mango-av-annotations'): typeof MangoAVAnnotationsElement => {\n if (typeof customElements === 'undefined') return MangoAVAnnotationsElement;\n if (!customElements.get(tagName)) customElements.define(tagName, MangoAVAnnotationsElement);\n return MangoAVAnnotationsElement;\n};\n"],"mappings":";;;;;;;;;;;;;;AAMA,IAAM,SAAS,GAAG,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWhC,IAAM,kBAAkB,CAAC,aAA6B,OAA0B;AAC9E,MAAI,YAAY,WAAW,EAAG,QAAO,oBAAoB,WAAW,GAAG,aAAa,CAAC;AACrF,SAAO,OAAO,YAAY,IAAI,CAAC,eAAe,mBAAmB,WAAW,MAAM,SAAS,EAAE,eAAe,WAAW,MAAM,OAAO,WAAW,MAAM,SAAS,EAAE;AAAA,4BACtI,WAAW,OAAO,KAAK,UAAU;AAAA,QACrD,WAAW,OAAO,sBAAsB,WAAW,WAAW,KAAK,KAAK,CAAC,YAAY,EAAE;AAAA,cACjF,WAAW,WAAW,IAAI,CAAC;AAAA,QACjC,WAAW,KAAK,SAAS,IAAI,sBAAsB,WAAW,KAAK,IAAI,CAAC,QAAQ,qBAAqB,WAAW,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,YAAY,EAAE;AAAA;AAAA,QAErJ,EAAE,KAAK,EAAE,CAAC;AAClB;AAEO,IAAM,4BAAN,cAAwC,oBAAoB;AAAA,EACjE;AAAA,EAEA,cAAc;AACZ,UAAM;AACN,SAAK,QAAQ,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAAA,EACjD;AAAA,EAEU,oBAA0B;AAAE,SAAK,QAAQ;AAAA,EAAG;AAAA,EAE5C,sBAAwD,MAAS,QAAgD;AACzH,QAAI,SAAS,cAAc,SAAS,qBAAqB,SAAS,0BAA0B,SAAS,kBAAmB,MAAK,QAAQ;AAAA,aAC5H,SAAS,mBAAmB,SAAS,aAAa,SAAS,iBAAkB,MAAK,MAAM;AAAA,EACnG;AAAA,EAEA,UAAgB;AACd,UAAM,SAAS,KAAK,WAAW;AAC/B,UAAM,KAAK,eAAe,KAAK,WAAW,OAAO,OAAO;AACxD,SAAK,MAAM,YAAY,UAAU,MAAM,oDAAoD,WAAW,GAAG,WAAW,CAAC;AAAA,iCACxF,WAAW,GAAG,WAAW,CAAC;AAAA,QACnD,SAAS,gBAAgB,OAAO,aAAa,EAAE,IAAI,oBAAoB,WAAW,GAAG,UAAU,CAAC,MAAM;AAAA;AAE1G,SAAK,MAAM,iBAA8B,qBAAqB,EAAE,QAAQ,CAAC,WAAW,OAAO,iBAAiB,SAAS,MAAM;AACzH,YAAM,QAAQ,OAAO,OAAO,eAAe,QAAQ,KAAK;AACxD,UAAI,CAAC,OAAO,SAAS,KAAK,EAAG;AAC7B,WAAK,cAAc,IAAI,YAAY,uBAAuB,EAAE,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAS,MAAM,UAAU,KAAK,CAAC,CAAC;AACrH,WAAK,WAAW,OAAO,KAAK;AAC5B,0BAAoB,KAAK,YAAY,IAAI;AAAA,IAC3C,CAAC,CAAC;AACF,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,QAAc;AACZ,UAAM,OAAO,KAAK,WAAW,MAAM;AACnC,SAAK,MAAM,iBAA8B,wBAAwB,EAAE,QAAQ,CAAC,QAAQ;AAClF,YAAM,QAAQ,OAAO,IAAI,QAAQ,KAAK;AACtC,YAAM,MAAM,OAAO,IAAI,QAAQ,GAAG;AAClC,UAAI,cAAc,QAAQ,GAAG,UAAU,OAAO,UAAU,OAAO,SAAS,KAAK,KAAK,QAAQ,UAAU,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,IAAI;AAAA,IAC1I,CAAC;AAAA,EACH;AACF;AAEO,IAAM,2BAA2B,CAAC,UAAU,2BAA6D;AAC9G,MAAI,OAAO,mBAAmB,YAAa,QAAO;AAClD,MAAI,CAAC,eAAe,IAAI,OAAO,EAAG,gBAAe,OAAO,SAAS,yBAAyB;AAC1F,SAAO;AACT;","names":[]}