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