@mango-iiif/av 0.4.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 +33 -0
  2. package/README.md +24 -17
  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 -45
  51. package/package.json +18 -11
  52. package/dist/chunk-4NOWGMI5.js.map +0 -1
  53. package/dist/chunk-BAE4FKBJ.js +0 -645
  54. package/dist/chunk-BAE4FKBJ.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-FVYEV2LO.js +0 -163
  63. package/dist/chunk-FVYEV2LO.js.map +0 -1
  64. package/dist/chunk-GQD675FA.js +0 -289
  65. package/dist/chunk-GQD675FA.js.map +0 -1
  66. package/dist/chunk-M5ZZRBGD.js +0 -357
  67. package/dist/chunk-M5ZZRBGD.js.map +0 -1
  68. package/dist/chunk-YX32ZQPR.js +0 -83
  69. package/dist/chunk-YX32ZQPR.js.map +0 -1
@@ -1,645 +0,0 @@
1
- import {
2
- AudioArtVisualizer,
3
- applyMediaSource,
4
- audioArtContentHtml,
5
- audioArtTranscriptHtml,
6
- ensureMediaChrome,
7
- registerVisualizerProperties,
8
- resolveAudioMediaPolicy,
9
- resolveAudioVisualizerMode,
10
- syncAudioArtTranscript
11
- } from "./chunk-GQD675FA.js";
12
- import {
13
- playFromInteraction
14
- } from "./chunk-CDLYT3KB.js";
15
- import {
16
- loadTranscript
17
- } from "./chunk-CIAQZ2EB.js";
18
- import {
19
- AVPlayerController,
20
- escapeHtml,
21
- formatString,
22
- formatTime,
23
- resolveStrings
24
- } from "./chunk-4NOWGMI5.js";
25
-
26
- // src/components/mango-av-media.ts
27
- var HTMLElementBase = globalThis.HTMLElement ?? class {
28
- };
29
- var hlsElementRegistration;
30
- var ensureHlsElement = () => {
31
- hlsElementRegistration ??= import("hls-video-element");
32
- return hlsElementRegistration;
33
- };
34
- var eventTypes = [
35
- "av-loadstart",
36
- "av-ready",
37
- "av-error",
38
- "av-canvaschange",
39
- "av-sourcechange",
40
- "av-play",
41
- "av-pause",
42
- "av-timeupdate",
43
- "av-seek",
44
- "av-ended",
45
- "av-annotationschange",
46
- "av-configchange",
47
- "av-statechange",
48
- "av-visualizerfallback"
49
- ];
50
- var styles = `
51
- :host {
52
- --mango-av-accent: #5b3fd6;
53
- --mango-av-accent-contrast: #fff;
54
- --mango-av-background: #fff;
55
- --mango-av-surface: #f5f5f8;
56
- --mango-av-text: #18181b;
57
- --mango-av-muted: #62626a;
58
- --mango-av-border: #d7d7de;
59
- --mango-av-radius: .65rem;
60
- --mango-av-font: ui-sans-serif, system-ui, sans-serif;
61
- --mango-av-panel-width: 24rem;
62
- --mango-av-player-aspect-ratio: 16 / 9;
63
- --media-primary-color: var(--mango-av-accent-contrast);
64
- --media-secondary-color: var(--mango-av-accent);
65
- color: var(--mango-av-text);
66
- display: block;
67
- font-family: var(--mango-av-font);
68
- }
69
- *, *::before, *::after { box-sizing: border-box; }
70
- button, input, select { font: inherit; }
71
- button, select { cursor: pointer; }
72
- .shell { background: var(--mango-av-background); border: 1px solid var(--mango-av-border); border-radius: var(--mango-av-radius); overflow: clip; }
73
- .header { padding: 1rem 1.1rem; }
74
- .header h2 { font-size: 1.15rem; margin: 0; }
75
- .header p { color: var(--mango-av-muted); margin: .35rem 0 0; }
76
- .layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(18rem, var(--mango-av-panel-width)); }
77
- .player-column { align-self: start; background: #08080a; min-width: 0; position: relative; }
78
- media-controller { display: block; width: 100%; }
79
- media-controller:not([audio]) { aspect-ratio: var(--mango-av-player-aspect-ratio); }
80
- video, hls-video { display: block; height: 100%; object-fit: contain; width: 100%; }
81
- audio { width: 100%; }
82
- media-control-bar { width: 100%; }
83
- .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; }
84
- .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-visualizer-color, var(--mango-av-accent)) clamp(0%, calc(var(--mango-av-wave-strength) * var(--mango-av-visualizer-intensity, 1)), 100%), transparent) 0%, color-mix(in srgb, var(--mango-av-visualizer-color, var(--mango-av-accent)) clamp(0%, calc(var(--mango-av-wave-mid-strength) * var(--mango-av-visualizer-intensity, 1)), 100%), transparent) 40%, color-mix(in srgb, var(--mango-av-visualizer-color, var(--mango-av-accent)) clamp(0%, calc(var(--mango-av-wave-edge-strength) * var(--mango-av-visualizer-intensity, 1)), 100%), transparent) 62%, transparent 78%), linear-gradient(#09080d, #111019); }
85
- .audio-art-text { display: contents; }
86
- .audio-art-visualizer-canvas { height: 100%; inset: 0; pointer-events: none; position: absolute; width: 100%; }
87
- .audio-art img { height: 100%; inset: 0; object-fit: contain; position: absolute; width: 100%; }
88
- .audio-art-title { font-size: clamp(1.1rem, 3vw, 2rem); font-weight: 700; position: relative; z-index: 1; }
89
- .audio-art-transcript { height: 100%; inset: 0; overflow: hidden; position: absolute; z-index: 1; }
90
- .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); }
91
- .audio-art-transcript.seeking .audio-art-cue { transition: none; }
92
- .audio-art-cue.active { font-size: clamp(.78rem, 1.5vw, 1rem); font-weight: 650; }
93
- .audio-art-cue > span:last-child { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
94
- .audio-art-speaker { font-size: .7em; letter-spacing: .04em; text-transform: uppercase; }
95
- .player-toolbar { align-items: center; background: #151518; color: #fff; display: flex; flex-wrap: wrap; gap: .5rem; justify-content: space-between; padding: .55rem .7rem; }
96
- .player-toolbar button, .player-toolbar select { background: #29292e; border: 1px solid #44444a; border-radius: .35rem; color: white; padding: .4rem .55rem; }
97
- .player-toolbar .group { align-items: center; display: flex; gap: .45rem; }
98
- .search-markers { background: #242429; height: .45rem; position: relative; }
99
- .search-marker { background: #ffe24a; border: 0; border-radius: 50%; height: .7rem; padding: 0; position: absolute; top: 50%; transform: translate(-50%, -50%); width: .7rem; }
100
- .resume { align-items: center; background: #fff8d7; color: #312900; display: flex; gap: .5rem; justify-content: center; padding: .6rem; }
101
- .resume button { background: var(--mango-av-accent); border: 0; border-radius: .35rem; color: var(--mango-av-accent-contrast); padding: .35rem .65rem; }
102
- .panel { border-left: 1px solid var(--mango-av-border); display: flex; flex-direction: column; min-height: 22rem; min-width: 0; }
103
- .tabs { background: var(--mango-av-surface); display: flex; gap: .1rem; overflow-x: auto; padding: .35rem; }
104
- .tab { background: transparent; border: 0; border-bottom: 3px solid transparent; color: var(--mango-av-muted); padding: .55rem .6rem; white-space: nowrap; }
105
- .tab[aria-selected=true] { border-color: var(--mango-av-accent); color: var(--mango-av-text); font-weight: 650; }
106
- .panel-content { flex: 1; max-height: 34rem; overflow: auto; padding: .85rem; }
107
- .empty { color: var(--mango-av-muted); font-style: italic; }
108
- .chapter-list, .annotation-list, .cue-list, .file-list { list-style: none; margin: 0; padding: 0; }
109
- .chapter-list .chapter-list { border-left: 1px solid var(--mango-av-border); margin-left: .8rem; padding-left: .55rem; }
110
- .chapter { margin-block: .3rem; }
111
- .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%; }
112
- .chapter button { border-radius: 0; }
113
- .chapter button:hover, .annotation button:hover, .cue button:hover { background: var(--mango-av-surface); }
114
- .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); }
115
- .time { color: var(--mango-av-muted); font-size: .78rem; }
116
- .panel-tools { display: flex; flex-wrap: wrap; gap: .45rem; margin-bottom: .75rem; }
117
- .panel-tools input, .panel-tools select { border: 1px solid var(--mango-av-border); border-radius: .35rem; min-width: 0; padding: .45rem; }
118
- .panel-tools input { flex: 1; }
119
- .panel-tools a { align-content: center; color: var(--mango-av-accent); }
120
- .cue mark { background: #ffe770; }
121
- .speaker { font-weight: 700; }
122
- .tags { display: flex; flex-wrap: wrap; gap: .25rem; }
123
- .tag { background: var(--mango-av-surface); border-radius: 999px; font-size: .75rem; padding: .15rem .45rem; }
124
- dl { margin: 0; }
125
- dt { font-weight: 700; margin-top: .75rem; }
126
- dd { color: var(--mango-av-muted); margin: .2rem 0 0; }
127
- .file-list li { border-bottom: 1px solid var(--mango-av-border); padding: .55rem 0; }
128
- a { color: var(--mango-av-accent); }
129
- .error { background: #fff0f0; color: #8a1717; padding: 1rem; }
130
- .sr-only { clip: rect(0,0,0,0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px; }
131
- @media (max-width: 760px) {
132
- .layout { grid-template-columns: 1fr; }
133
- .panel { border-left: 0; border-top: 1px solid var(--mango-av-border); }
134
- }
135
- `;
136
- var metadataHtml = (entries, ui) => {
137
- if (entries.length === 0) return `<p class="empty">${escapeHtml(ui.noMetadata)}</p>`;
138
- return `<dl>${entries.map((entry) => `
139
- <dt>${escapeHtml(entry.label)}</dt>
140
- <dd>${entry.values.map((value) => escapeHtml(value.value)).join("<br>")}</dd>
141
- `).join("")}</dl>`;
142
- };
143
- var chapterHtml = (chapters, canvasId, ui, level = 1) => {
144
- if (chapters.length === 0 && level === 1) return `<p class="empty">${escapeHtml(ui.noChapters)}</p>`;
145
- return `<ul class="chapter-list">${chapters.map((chapter) => {
146
- const active = chapter.canvasId === canvasId;
147
- const target = chapter.canvasId ? `data-canvas="${escapeHtml(chapter.canvasId)}"` : "";
148
- const time = chapter.time ? `data-time="${chapter.time.start}"` : "";
149
- return `<li class="chapter">
150
- <button type="button" class="${active ? "active" : ""}" ${target} ${time} ${chapter.canvasId ? "" : "disabled"}>
151
- <span>${escapeHtml(chapter.label)}</span>
152
- ${chapter.time ? `<span class="time">${formatTime(chapter.time.start)}</span>` : ""}
153
- </button>
154
- ${chapterHtml(chapter.children, canvasId, ui, level + 1)}
155
- </li>`;
156
- }).join("")}</ul>`;
157
- };
158
- var highlight = (text, query) => {
159
- const escaped = escapeHtml(text);
160
- if (!query) return escaped;
161
- const safeQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
162
- return escaped.replace(new RegExp(`(${safeQuery})`, "gi"), "<mark>$1</mark>");
163
- };
164
- var MangoAVMediaElement = class extends HTMLElementBase {
165
- static observedAttributes = ["manifest-url", "config"];
166
- player;
167
- #root;
168
- #inputManifest;
169
- #config = {};
170
- #activePanel = "chapters";
171
- #transcriptCues = [];
172
- #transcriptIndex = 0;
173
- #transcriptQuery = "";
174
- #resumeTime;
175
- #loadingToken = 0;
176
- #resolvedSourceUrl;
177
- #sourceToken = 0;
178
- #fallbackSourceUrl;
179
- #visualizer = new AudioArtVisualizer();
180
- constructor() {
181
- super();
182
- this.#root = this.attachShadow({ mode: "open" });
183
- this.player = new AVPlayerController();
184
- for (const type of eventTypes) {
185
- this.player.addEventListener(type, ((event) => {
186
- if (type === "av-ready") {
187
- if (this.player.source?.isHls) void ensureHlsElement();
188
- this.#resetSourceLoad();
189
- this.#resumeTime = this.player.getSavedPosition();
190
- this.#activePanel = this.player.config.activePanel;
191
- this.#render();
192
- void this.#resolveSource();
193
- void this.#loadActiveTranscript();
194
- } else if (type === "av-canvaschange" || type === "av-sourcechange") {
195
- if (this.player.source?.isHls) void ensureHlsElement();
196
- this.#resetSourceLoad();
197
- this.#transcriptCues = [];
198
- this.#transcriptIndex = 0;
199
- this.#render();
200
- void this.#resolveSource();
201
- void this.#loadActiveTranscript();
202
- } else if (type === "av-configchange") {
203
- this.#resetSourceLoad();
204
- this.#activePanel = this.player.config.activePanel;
205
- void ensureMediaChrome(this.player.config.preferredLanguages[0]).then(() => {
206
- this.#render();
207
- void this.#resolveSource();
208
- });
209
- } else if (type === "av-error") {
210
- this.#render();
211
- } else if (type === "av-annotationschange") {
212
- if (this.#activePanel === "annotations") this.#renderPanelOnly();
213
- } else if (type === "av-timeupdate") {
214
- this.#syncTimedContent();
215
- } else if (type === "av-seek") {
216
- this.#syncTimedContent(event.detail.to, true);
217
- }
218
- this.dispatchEvent(new CustomEvent(type, {
219
- detail: event.detail,
220
- bubbles: true,
221
- composed: true
222
- }));
223
- }));
224
- }
225
- }
226
- get manifest() {
227
- return this.#inputManifest;
228
- }
229
- get controller() {
230
- return this.player;
231
- }
232
- set manifest(value) {
233
- this.#inputManifest = value;
234
- if (this.isConnected && value !== void 0) void this.load(value);
235
- }
236
- get config() {
237
- return this.#config;
238
- }
239
- set config(value) {
240
- this.#config = value;
241
- this.player.configure(value);
242
- this.#activePanel = value.activePanel ?? this.player.config.activePanel;
243
- if (this.isConnected) this.#render();
244
- }
245
- connectedCallback() {
246
- this.#readConfigAttribute();
247
- void ensureMediaChrome(this.player.config.preferredLanguages[0]);
248
- this.#render();
249
- const source = this.#inputManifest ?? this.getAttribute("manifest-url") ?? void 0;
250
- if (source !== void 0) void this.load(source);
251
- }
252
- disconnectedCallback() {
253
- this.#visualizer.disconnect();
254
- this.player.destroy();
255
- }
256
- attributeChangedCallback(name, oldValue, newValue) {
257
- if (!this.isConnected || oldValue === newValue) return;
258
- if (name === "manifest-url" && newValue) {
259
- this.#inputManifest = newValue;
260
- void this.load(newValue);
261
- }
262
- if (name === "config") {
263
- this.#readConfigAttribute();
264
- this.#render();
265
- }
266
- }
267
- async load(input) {
268
- this.#inputManifest = input;
269
- return await this.player.load(input);
270
- }
271
- play() {
272
- return this.player.play();
273
- }
274
- pause() {
275
- this.player.pause();
276
- }
277
- seekTo(time) {
278
- this.player.seekTo(time);
279
- }
280
- selectCanvas(indexOrId, options) {
281
- return this.player.selectCanvas(indexOrId, options);
282
- }
283
- #readConfigAttribute() {
284
- const attribute = this.getAttribute("config");
285
- if (!attribute) return;
286
- try {
287
- this.#config = JSON.parse(attribute);
288
- this.player.configure(this.#config);
289
- } catch (cause) {
290
- const error = cause instanceof Error ? cause : new Error(String(cause));
291
- this.dispatchEvent(new CustomEvent("av-error", { detail: { error }, bubbles: true, composed: true }));
292
- }
293
- }
294
- #render() {
295
- this.#visualizer.disconnect();
296
- const state = this.player.state;
297
- const manifest = state.manifest;
298
- const canvas = this.player.canvas;
299
- const source = this.player.source;
300
- const ui = resolveStrings(this.player.config.strings);
301
- this.#root.innerHTML = `<style>${styles}</style><div class="shell">
302
- <slot name="header">
303
- ${manifest ? `<header class="header"><h2>${escapeHtml(manifest.label)}</h2>${manifest.summary ? `<p>${escapeHtml(manifest.summary)}</p>` : ""}</header>` : ""}
304
- </slot>
305
- ${state.status === "error" ? `<div class="error" role="alert">${escapeHtml(state.error?.message ?? ui.unableToLoadPlayer)}</div>` : ""}
306
- ${state.status === "loading" ? `<p class="header" role="status">${escapeHtml(ui.loadingManifest)}</p>` : ""}
307
- ${manifest && canvas ? this.#renderReady(manifest, canvas, source?.kind ?? "video") : ""}
308
- </div>`;
309
- this.#bindInteractions();
310
- const media = this.#root.querySelector("video, audio, hls-video");
311
- if (media && this.#resolvedSourceUrl) this.#activateMedia(media, this.#resolvedSourceUrl);
312
- this.#syncTimedContent();
313
- }
314
- #renderReady(manifest, canvas, kind) {
315
- const source = this.player.source;
316
- const panels = this.player.config.panels;
317
- const controls = this.player.config.controls;
318
- const ui = resolveStrings(this.player.config.strings);
319
- const panelTabs = [
320
- panels.chapters ? ["chapters", ui.chapters] : void 0,
321
- panels.transcript ? ["transcript", ui.transcript] : void 0,
322
- panels.annotations ? ["annotations", ui.annotations] : void 0,
323
- panels.metadata ? ["metadata", ui.metadata] : void 0,
324
- panels.supplementalFiles ? ["files", ui.files] : void 0
325
- ].filter((value) => Boolean(value));
326
- const showPanel = this.#activePanel !== "none" && panelTabs.length > 0;
327
- const showSourceSelector = controls.sourceSelector && canvas.sources.length > 1;
328
- const showDownload = controls.download && canvas.rendering.length > 0;
329
- const showPlayerToolbar = controls.navigation || controls.autoAdvance || showSourceSelector || showDownload;
330
- const visualizerMode = resolveAudioVisualizerMode(this.player.config.audioArt.visualizer);
331
- return `
332
- ${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>` : ""}
333
- <div class="layout" ${showPanel ? "" : 'style="grid-template-columns: 1fr"'}>
334
- <section class="player-column" aria-label="${escapeHtml(ui.mediaPlayer)}">
335
- <slot name="before-player"></slot>
336
- <media-controller ${kind === "audio" ? "audio" : ""} ${this.#mediaChromeLanguage()}>
337
- ${kind === "audio" ? `<div class="audio-art ${visualizerMode ? `visualizer visualizer--${visualizerMode}` : ""}">${audioArtContentHtml(canvas.label, this.player.config.audioArt.title ?? "", this.#transcriptCues, Boolean(this.player.config.audioArt.transcript), visualizerMode)}</div>` : ""}
338
- ${source ? this.#mediaHtml(canvas, source.kind) : `<div class="audio-art"><span>${escapeHtml(ui.noPlayableMedia)}</span></div>`}
339
- <slot name="player-overlay"></slot>
340
- <slot name="controls">
341
- <media-control-bar>
342
- <media-play-button></media-play-button>
343
- ${controls.seek ? `<media-seek-backward-button seekoffset="${controls.skipSeconds ?? 10}"></media-seek-backward-button><media-seek-forward-button seekoffset="${controls.skipSeconds ?? 10}"></media-seek-forward-button>` : ""}
344
- <media-time-display showduration></media-time-display>
345
- ${controls.seek ? "<media-time-range></media-time-range>" : ""}
346
- ${controls.volume ? "<media-mute-button></media-mute-button><media-volume-range></media-volume-range>" : ""}
347
- ${controls.captions && canvas.tracks.length > 0 ? "<media-captions-menu-button></media-captions-menu-button>" : ""}
348
- ${controls.playbackRate ? "<media-playback-rate-menu-button></media-playback-rate-menu-button>" : ""}
349
- ${controls.pictureInPicture && kind === "video" ? "<media-pip-button></media-pip-button>" : ""}
350
- ${controls.fullscreen && kind === "video" ? "<media-fullscreen-button></media-fullscreen-button>" : ""}
351
- </media-control-bar>
352
- ${controls.captions && canvas.tracks.length > 0 ? "<media-captions-menu hidden></media-captions-menu>" : ""}
353
- ${controls.playbackRate ? `<media-playback-rate-menu rates="${this.player.config.playbackRates.join(" ")}" hidden></media-playback-rate-menu>` : ""}
354
- </slot>
355
- </media-controller>
356
- ${this.#searchMarkersHtml()}
357
- ${showPlayerToolbar ? `<div class="player-toolbar">
358
- ${controls.navigation ? `<div class="group">
359
- <button type="button" data-action="previous" ${canvas.index === 0 ? "disabled" : ""} aria-label="${escapeHtml(ui.previousSection)}">\u2190 ${escapeHtml(ui.previous)}</button>
360
- <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>
361
- </div>` : ""}
362
- ${showSourceSelector || showDownload || controls.autoAdvance ? `<div class="group">
363
- ${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>` : ""}
364
- ${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>` : ""}
365
- ${controls.autoAdvance ? `<label><input type="checkbox" data-action="auto-advance" ${this.player.state.autoAdvance ? "checked" : ""}> ${escapeHtml(ui.autoAdvance)}</label>` : ""}
366
- </div>` : ""}
367
- </div>` : ""}
368
- <slot name="after-player"></slot>
369
- </section>
370
- ${showPanel ? `<aside class="panel">
371
- <nav class="tabs" role="tablist" aria-label="${escapeHtml(ui.mediaInformation)}">${panelTabs.map(([id, tabLabel]) => `<button class="tab" type="button" role="tab" data-panel="${id}" aria-selected="${this.#activePanel === id}">${escapeHtml(tabLabel)}</button>`).join("")}</nav>
372
- <div class="panel-content" role="tabpanel">${this.#panelHtml(manifest, canvas)}</div>
373
- </aside>` : ""}
374
- </div>`;
375
- }
376
- #mediaHtml(canvas, kind) {
377
- const source = this.player.source;
378
- if (!source) return "";
379
- const tag = source.isHls ? "hls-video" : kind;
380
- const attributes = [
381
- 'slot="media"',
382
- `preload="${this.player.config.preload}"`,
383
- this.player.config.crossorigin ? `crossorigin="${this.player.config.crossorigin}"` : "",
384
- kind === "video" && canvas.poster ? `poster="${escapeHtml(canvas.poster)}"` : "",
385
- `aria-label="${escapeHtml(canvas.label)}"`,
386
- `data-canvasindex="${canvas.index}"`
387
- ].filter(Boolean).join(" ");
388
- const tracks = canvas.tracks.map((track) => `<track src="${escapeHtml(track.src)}" kind="${track.kind}" label="${escapeHtml(track.label)}" ${track.language ? `srclang="${escapeHtml(track.language)}"` : ""} ${track.default ? "default" : ""}>`).join("");
389
- return `<${tag} ${attributes}>${tracks}</${tag}>`;
390
- }
391
- #mediaChromeLanguage() {
392
- const language = this.player.config.preferredLanguages.find((value) => value !== "none");
393
- return language ? `lang="${escapeHtml(language)}"` : "";
394
- }
395
- #panelHtml(manifest, canvas) {
396
- const ui = resolveStrings(this.player.config.strings);
397
- switch (this.#activePanel) {
398
- case "chapters":
399
- return chapterHtml(manifest.chapters, canvas.id, ui);
400
- case "transcript":
401
- return this.#transcriptHtml(canvas);
402
- case "annotations":
403
- return this.#annotationsHtml(canvas.annotations);
404
- case "metadata":
405
- return `<h3>${escapeHtml(canvas.label)}</h3>${metadataHtml([...manifest.metadata, ...canvas.metadata], ui)}${manifest.requiredStatement ? metadataHtml([manifest.requiredStatement], ui) : ""}${manifest.rights ? `<p><strong>${escapeHtml(ui.rights)}:</strong> <a href="${escapeHtml(manifest.rights)}" target="_blank" rel="noreferrer">${escapeHtml(manifest.rights)}</a></p>` : ""}`;
406
- case "files": {
407
- const files = [...manifest.rendering, ...canvas.rendering];
408
- return files.length > 0 ? `<ul class="file-list">${files.map((file) => `<li><a href="${escapeHtml(file.id)}" download="${escapeHtml(file.filename ?? "")}">${escapeHtml(file.label)}</a>${file.format ? `<div class="time">${escapeHtml(file.format)}</div>` : ""}</li>`).join("")}</ul>` : `<p class="empty">${escapeHtml(ui.noSupplementalFiles)}</p>`;
409
- }
410
- default:
411
- return "";
412
- }
413
- }
414
- #transcriptHtml(canvas) {
415
- const ui = resolveStrings(this.player.config.strings);
416
- if (canvas.transcripts.length === 0) return `<p class="empty">${escapeHtml(ui.noTranscript)}</p>`;
417
- const resource = canvas.transcripts[this.#transcriptIndex];
418
- const query = this.#transcriptQuery.trim();
419
- const visible = query ? this.#transcriptCues.filter((cue) => cue.text.toLowerCase().includes(query.toLowerCase())) : this.#transcriptCues;
420
- return `<div class="panel-tools">
421
- ${canvas.transcripts.length > 1 ? `<select data-action="transcript" aria-label="${escapeHtml(ui.transcript)}">${canvas.transcripts.map((item, index) => `<option value="${index}" ${index === this.#transcriptIndex ? "selected" : ""}>${escapeHtml(item.label)}</option>`).join("")}</select>` : ""}
422
- ${this.player.config.transcript.search ? `<input type="search" data-action="transcript-search" value="${escapeHtml(this.#transcriptQuery)}" placeholder="${escapeHtml(ui.searchTranscript)}" aria-label="${escapeHtml(ui.searchTranscript)}">` : ""}
423
- ${this.player.config.transcript.download && resource?.src ? `<a href="${escapeHtml(resource.src)}" download>${escapeHtml(ui.download)}</a>` : ""}
424
- </div>
425
- ${this.#transcriptCues.length === 0 ? `<p class="empty">${escapeHtml(ui.loadingTranscript)}</p>` : `<ol class="cue-list">${visible.map((cue) => `<li class="cue" data-cue-id="${escapeHtml(cue.id)}" ${cue.start !== void 0 ? `data-start="${cue.start}" data-end="${cue.end ?? cue.start}"` : ""}><button type="button" ${cue.start === void 0 ? "disabled" : ""}>${cue.start !== void 0 ? `<span class="time">${formatTime(cue.start)}</span>` : ""}${cue.speaker ? `<span class="speaker">${escapeHtml(cue.speaker)}</span>` : ""}<span>${highlight(cue.text, query)}</span></button></li>`).join("")}</ol>`}`;
426
- }
427
- #annotationsHtml(annotations) {
428
- const ui = resolveStrings(this.player.config.strings);
429
- if (annotations.length === 0) return `<p class="empty">${escapeHtml(ui.noAnnotations)}</p>`;
430
- return `<ol class="annotation-list">${annotations.map((annotation) => `<li class="annotation" data-start="${annotation.time?.start ?? ""}" data-end="${annotation.time?.end ?? annotation.time?.start ?? ""}"><button type="button" ${annotation.time ? "" : "disabled"}>${annotation.time ? `<span class="time">${formatTime(annotation.time.start)}</span>` : ""}<span>${escapeHtml(annotation.text)}</span>${annotation.tags.length > 0 ? `<span class="tags">${annotation.tags.map((tag) => `<span class="tag">${escapeHtml(tag)}</span>`).join("")}</span>` : ""}</button></li>`).join("")}</ol>`;
431
- }
432
- #searchMarkersHtml() {
433
- const ui = resolveStrings(this.player.config.strings);
434
- const query = this.#transcriptQuery.trim().toLowerCase();
435
- const duration = this.player.state.duration || this.player.canvas?.duration || 0;
436
- if (!query || !this.player.config.transcript.showSearchMarkers || duration <= 0) return "";
437
- const matches = this.#transcriptCues.filter((cue) => cue.start !== void 0 && cue.text.toLowerCase().includes(query));
438
- if (matches.length === 0) return "";
439
- return `<div class="search-markers" aria-label="${escapeHtml(ui.transcriptSearchMatches)}">${matches.map((cue) => `<button type="button" class="search-marker" data-search-time="${cue.start}" style="left:${Math.min(100, Math.max(0, (cue.start ?? 0) / duration * 100))}%" aria-label="${escapeHtml(formatString(ui.searchMatchAt, { time: formatTime(cue.start ?? 0) }))}"></button>`).join("")}</div>`;
440
- }
441
- #bindInteractions() {
442
- this.#root.querySelectorAll("[data-panel]").forEach((button) => button.addEventListener("click", () => {
443
- this.#activePanel = button.dataset.panel;
444
- this.#renderPanelOnly();
445
- if (this.#activePanel === "transcript") void this.#loadActiveTranscript();
446
- }));
447
- this.#root.querySelector("[data-action=previous]")?.addEventListener("click", () => this.player.previous({ autoplay: this.player.state.status === "playing" }));
448
- this.#root.querySelector("[data-action=next]")?.addEventListener("click", () => this.player.next({ autoplay: this.player.state.status === "playing" }));
449
- this.#root.querySelector("[data-action=source]")?.addEventListener("change", (event) => this.player.selectSource(Number(event.target.value)));
450
- this.#root.querySelector("[data-action=download]")?.addEventListener("change", (event) => {
451
- const select = event.target;
452
- if (!select.value) return;
453
- const anchor = document.createElement("a");
454
- anchor.href = select.value;
455
- anchor.download = select.selectedOptions[0]?.dataset.filename ?? "";
456
- anchor.click();
457
- select.value = "";
458
- });
459
- this.#root.querySelector("[data-action=auto-advance]")?.addEventListener("change", (event) => this.player.setAutoAdvance(event.target.checked));
460
- this.#root.querySelector("[data-action=resume]")?.addEventListener("click", () => {
461
- if (this.#resumeTime !== void 0) this.player.seekTo(this.#resumeTime);
462
- this.#resumeTime = void 0;
463
- this.#render();
464
- });
465
- this.#root.querySelector("[data-action=dismiss-resume]")?.addEventListener("click", () => {
466
- this.#resumeTime = void 0;
467
- this.#render();
468
- });
469
- this.#bindPanelInteractions();
470
- }
471
- #bindPanelInteractions() {
472
- this.#root.querySelectorAll("[data-canvas]").forEach((button) => button.addEventListener("click", () => {
473
- const canvasId = button.dataset.canvas;
474
- if (canvasId) this.player.selectCanvas(canvasId, { time: Number(button.dataset.time ?? 0), autoplay: true });
475
- }));
476
- this.#root.querySelector("[data-action=transcript]")?.addEventListener("change", (event) => {
477
- this.#transcriptIndex = Number(event.target.value);
478
- this.#transcriptCues = [];
479
- this.#renderPanelOnly();
480
- void this.#loadActiveTranscript();
481
- });
482
- this.#root.querySelector("[data-action=transcript-search]")?.addEventListener("input", (event) => {
483
- this.#transcriptQuery = event.target.value;
484
- const selection = event.target.selectionStart;
485
- this.#renderPanelOnly();
486
- const input = this.#root.querySelector("[data-action=transcript-search]");
487
- input?.focus();
488
- if (selection !== null) input?.setSelectionRange(selection, selection);
489
- });
490
- this.#root.querySelectorAll(".cue[data-start] button, .annotation[data-start] button").forEach((button) => button.addEventListener("click", () => {
491
- const row = button.parentElement;
492
- const start = Number(row?.dataset.start);
493
- if (Number.isFinite(start)) {
494
- this.player.seekTo(start);
495
- playFromInteraction(this.player, this);
496
- }
497
- }));
498
- this.#root.querySelectorAll("[data-search-time]").forEach((button) => button.addEventListener("click", () => {
499
- const time = Number(button.dataset.searchTime);
500
- if (Number.isFinite(time)) this.player.seekTo(time);
501
- }));
502
- }
503
- #renderPanelOnly() {
504
- const manifest = this.player.manifest;
505
- const canvas = this.player.canvas;
506
- if (!manifest || !canvas) return;
507
- this.#root.querySelectorAll("[data-panel]").forEach((tab) => {
508
- tab.setAttribute("aria-selected", String(tab.dataset.panel === this.#activePanel));
509
- });
510
- const content = this.#root.querySelector(".panel-content");
511
- if (content) content.innerHTML = this.#panelHtml(manifest, canvas);
512
- this.#root.querySelector(".search-markers")?.remove();
513
- const toolbar = this.#root.querySelector(".player-toolbar");
514
- const markers = this.#searchMarkersHtml();
515
- if (toolbar && markers) toolbar.insertAdjacentHTML("beforebegin", markers);
516
- this.#bindPanelInteractions();
517
- }
518
- async #loadActiveTranscript() {
519
- const resource = this.player.canvas?.transcripts[this.#transcriptIndex];
520
- if (!resource) return;
521
- const token = ++this.#loadingToken;
522
- try {
523
- const transcriptOptions = {};
524
- if (this.player.config.fetch) transcriptOptions.fetch = this.player.config.fetch;
525
- if (this.player.config.requestInit) transcriptOptions.requestInit = this.player.config.requestInit;
526
- if (this.player.config.transcript.showNotes !== void 0) transcriptOptions.showNotes = this.player.config.transcript.showNotes;
527
- if (this.player.config.transcript.showMetadata !== void 0) transcriptOptions.showMetadata = this.player.config.transcript.showMetadata;
528
- const cues = await loadTranscript(resource, transcriptOptions);
529
- if (token !== this.#loadingToken) return;
530
- this.#transcriptCues = cues;
531
- this.#renderAudioArt();
532
- if (this.#activePanel === "transcript") this.#renderPanelOnly();
533
- } catch (cause) {
534
- if (token !== this.#loadingToken) return;
535
- const error = cause instanceof Error ? cause : new Error(String(cause));
536
- this.dispatchEvent(new CustomEvent("av-error", { detail: { error }, bubbles: true, composed: true }));
537
- this.#transcriptCues = [];
538
- if (this.#activePanel === "transcript") this.#renderPanelOnly();
539
- }
540
- }
541
- async #resolveSource() {
542
- const token = ++this.#sourceToken;
543
- try {
544
- const sourceUrl = await this.player.resolvedSourceUrl();
545
- if (token !== this.#sourceToken || !sourceUrl) return;
546
- this.#resolvedSourceUrl = sourceUrl;
547
- const media = this.#root.querySelector("video, audio, hls-video");
548
- if (media) this.#activateMedia(media, sourceUrl);
549
- } catch (cause) {
550
- const error = cause instanceof Error ? cause : new Error(String(cause));
551
- this.dispatchEvent(new CustomEvent("av-error", { detail: { error }, bubbles: true, composed: true }));
552
- }
553
- }
554
- #activateMedia(media, sourceUrl) {
555
- const mode = resolveAudioVisualizerMode(this.player.config.audioArt.visualizer);
556
- const policy = resolveAudioMediaPolicy(
557
- sourceUrl,
558
- this.player.config.crossorigin,
559
- this.player.crossoriginExplicit,
560
- media instanceof HTMLAudioElement ? mode : void 0,
561
- this.#fallbackSourceUrl === sourceUrl
562
- );
563
- if (media instanceof HTMLAudioElement && policy.implicitCors) {
564
- media.addEventListener("error", (event) => {
565
- if (this.#fallbackSourceUrl === sourceUrl) return;
566
- event.stopImmediatePropagation();
567
- this.#fallbackToOrdinaryAudio(media, sourceUrl);
568
- }, { once: true });
569
- }
570
- if (media instanceof HTMLAudioElement && mode && policy.visualize) {
571
- const connect = () => {
572
- const art = this.#root.querySelector(".audio-art.visualizer");
573
- if (art && media.isConnected) this.#visualizer.connect(media, art, mode);
574
- };
575
- media.addEventListener("loadedmetadata", connect, { once: true });
576
- if (media.readyState >= HTMLMediaElement.HAVE_METADATA) queueMicrotask(connect);
577
- }
578
- this.player.attachMedia(media);
579
- applyMediaSource(media, sourceUrl, policy.crossOrigin);
580
- }
581
- #fallbackToOrdinaryAudio(failed, sourceUrl) {
582
- this.#fallbackSourceUrl = sourceUrl;
583
- this.#visualizer.disconnect();
584
- const requestedTime = this.player.state.currentTime;
585
- const shouldPlay = this.player.state.status === "playing" || !failed.paused;
586
- const replacement = failed.cloneNode(true);
587
- replacement.removeAttribute("src");
588
- replacement.removeAttribute("crossorigin");
589
- replacement.volume = failed.volume;
590
- replacement.muted = failed.muted;
591
- replacement.playbackRate = failed.playbackRate;
592
- const hadFocus = this.#root.activeElement === failed;
593
- failed.replaceWith(replacement);
594
- this.player.attachMedia(replacement);
595
- this.player.seekTo(requestedTime);
596
- if (hadFocus) replacement.focus();
597
- this.#root.querySelector(".audio-art")?.classList.remove("visualizer");
598
- applyMediaSource(replacement, sourceUrl);
599
- if (shouldPlay) replacement.addEventListener("loadedmetadata", () => void replacement.play().catch(() => void 0), { once: true });
600
- this.player.dispatchEvent(new CustomEvent("av-visualizerfallback", {
601
- detail: { canvasId: this.player.canvas?.id ?? "", sourceUrl, reason: "cors" }
602
- }));
603
- }
604
- #resetSourceLoad() {
605
- this.#sourceToken += 1;
606
- this.#resolvedSourceUrl = void 0;
607
- this.#fallbackSourceUrl = void 0;
608
- this.#visualizer.disconnect();
609
- }
610
- #syncTimedContent(time = this.player.state.currentTime, instant = false) {
611
- syncAudioArtTranscript(this.#root, time, instant);
612
- const rows = this.#root.querySelectorAll("[data-start][data-end]");
613
- let active;
614
- rows.forEach((row) => {
615
- const start = Number(row.dataset.start);
616
- const end = Number(row.dataset.end);
617
- const isActive = Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end);
618
- row.querySelector("button")?.classList.toggle("active", isActive);
619
- if (isActive) active = row;
620
- });
621
- if (active && this.player.config.transcript.autoScroll) {
622
- active.scrollIntoView({ block: "nearest", behavior: "smooth" });
623
- }
624
- }
625
- #renderAudioArt() {
626
- const art = this.#root.querySelector(".audio-art");
627
- const canvas = this.player.canvas;
628
- if (!art || !canvas || this.player.source?.kind !== "audio") return;
629
- const text = art.querySelector(".audio-art-text");
630
- if (text) text.innerHTML = audioArtTranscriptHtml(canvas.label, this.player.config.audioArt.title ?? "", this.#transcriptCues, Boolean(this.player.config.audioArt.transcript));
631
- syncAudioArtTranscript(art, this.player.state.currentTime);
632
- }
633
- };
634
- var defineMangoAVMedia = (tagName = "mango-av-media") => {
635
- registerVisualizerProperties();
636
- if (typeof customElements === "undefined") return MangoAVMediaElement;
637
- if (!customElements.get(tagName)) customElements.define(tagName, MangoAVMediaElement);
638
- return MangoAVMediaElement;
639
- };
640
-
641
- export {
642
- MangoAVMediaElement,
643
- defineMangoAVMedia
644
- };
645
- //# sourceMappingURL=chunk-BAE4FKBJ.js.map