@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
@@ -0,0 +1,148 @@
1
+ import {
2
+ loadCues
3
+ } from "./chunk-YVI7J43Q.js";
4
+ import {
5
+ AVControllerElement,
6
+ componentTheme
7
+ } from "./chunk-JXZR5DL2.js";
8
+ import {
9
+ escapeHtml,
10
+ resolveStrings
11
+ } from "./chunk-53ZN4Y4L.js";
12
+
13
+ // src/components/mango-av-captions.ts
14
+ var styles = `${componentTheme}
15
+ :host([hidden]) { display: none; }
16
+ .captions {
17
+ align-items: center;
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 4px;
21
+ padding: var(--mango-av-captions-padding, 4px 16px 16px);
22
+ text-align: center;
23
+ }
24
+ .line { margin: 0; max-width: 40em; }
25
+ .line:empty { display: none; }
26
+ .current {
27
+ background: var(--mango-av-captions-background, transparent);
28
+ border-radius: 4px;
29
+ color: var(--mango-av-captions-color, var(--mango-av-text));
30
+ font-size: var(--mango-av-captions-size, 18px);
31
+ font-weight: 600;
32
+ line-height: 1.4;
33
+ padding: 2px 8px;
34
+ }
35
+ .current.changed { animation: rise 280ms ease-out; }
36
+ .context {
37
+ background: var(--mango-av-captions-background, transparent);
38
+ border-radius: 4px;
39
+ color: var(--mango-av-captions-color, var(--mango-av-muted));
40
+ font-size: 14px;
41
+ line-height: 1.4;
42
+ opacity: .6;
43
+ padding: 0 8px;
44
+ }
45
+ .speaker { display: block; font-size: 11px; font-weight: 700; letter-spacing: .05em; opacity: .75; text-transform: uppercase; }
46
+ @keyframes rise { from { opacity: 0; transform: translateY(6px); } }
47
+ @media (prefers-reduced-motion: reduce) { .current.changed { animation: none; } }
48
+ `;
49
+ var lineHtml = (cue) => cue ? `${cue.speaker ? `<span class="speaker">${escapeHtml(cue.speaker)}</span>` : ""}${escapeHtml(cue.text)}` : "";
50
+ var MangoAVCaptionsElement = class extends AVControllerElement {
51
+ static observedAttributes = ["line", "audio-only"];
52
+ #root;
53
+ #cues = [];
54
+ #loadToken = 0;
55
+ #shown;
56
+ constructor() {
57
+ super();
58
+ this.#root = this.attachShadow({ mode: "open" });
59
+ }
60
+ attributeChangedCallback() {
61
+ if (this.isConnected) this.#render();
62
+ }
63
+ controllerChanged() {
64
+ this.#reset();
65
+ }
66
+ handleControllerEvent(type, _event) {
67
+ if (type === "av-ready" || type === "av-canvaschange") this.#reset();
68
+ else if (type === "av-configchange" || type === "av-sourcechange") this.#render();
69
+ else if (type === "av-timeupdate" || type === "av-seek" || type === "av-statechange") this.#sync();
70
+ }
71
+ #reset() {
72
+ this.#cues = [];
73
+ this.#render();
74
+ void this.#load();
75
+ }
76
+ async #load() {
77
+ const resource = this.controller.canvas?.transcripts[0];
78
+ if (!resource) return;
79
+ const token = ++this.#loadToken;
80
+ try {
81
+ const cues = await loadCues(this.controller, resource);
82
+ if (token !== this.#loadToken) return;
83
+ this.#cues = cues.filter((cue) => cue.type === "cue" && cue.start !== void 0);
84
+ } catch (cause) {
85
+ if (token !== this.#loadToken) return;
86
+ const error = cause instanceof Error ? cause : new Error(String(cause));
87
+ this.dispatchEvent(new CustomEvent("av-error", { detail: { error }, bubbles: true, composed: true }));
88
+ this.#cues = [];
89
+ }
90
+ this.#render();
91
+ }
92
+ #render() {
93
+ const ui = resolveStrings(this.controller.config.strings);
94
+ const context = !this.hasAttribute("line");
95
+ this.#root.innerHTML = `<style>${styles}</style><div class="captions" role="region" aria-label="${escapeHtml(ui.captions)}">
96
+ ${context ? '<p class="line context" data-line="previous"></p>' : ""}
97
+ <p class="line current" data-line="current"></p>
98
+ ${context ? '<p class="line context" data-line="next"></p>' : ""}
99
+ </div>`;
100
+ this.#shown = void 0;
101
+ this.#sync();
102
+ }
103
+ #sync() {
104
+ const playingVideo = this.hasAttribute("audio-only") && this.controller.source?.kind === "video";
105
+ this.hidden = this.#cues.length === 0 || playingVideo;
106
+ if (this.hidden) return;
107
+ const time = this.controller.state.currentTime;
108
+ let current = -1;
109
+ this.#cues.forEach((cue, index) => {
110
+ if ((cue.start ?? 0) <= time && (cue.end === void 0 || time < cue.end)) current = index;
111
+ });
112
+ const upcoming = this.#cues.findIndex((cue) => (cue.start ?? 0) > time);
113
+ const next = current >= 0 ? current + 1 : upcoming;
114
+ const previous = current >= 0 ? current - 1 : (upcoming >= 0 ? upcoming : this.#cues.length) - 1;
115
+ const key = `${previous}:${current}:${next}`;
116
+ if (key === this.#shown) return;
117
+ const changed = this.#shown?.split(":")[1] !== String(current);
118
+ this.#shown = key;
119
+ const line = (name) => this.#root.querySelector(`[data-line=${name}]`);
120
+ const currentLine = line("current");
121
+ if (currentLine) {
122
+ currentLine.innerHTML = lineHtml(this.#cues[current]);
123
+ if (changed && current >= 0) {
124
+ currentLine.classList.remove("changed");
125
+ void currentLine.offsetWidth;
126
+ currentLine.classList.add("changed");
127
+ }
128
+ }
129
+ const previousLine = line("previous");
130
+ if (previousLine) previousLine.innerHTML = lineHtml(this.#cues[previous]);
131
+ const nextLine = line("next");
132
+ if (nextLine) nextLine.innerHTML = lineHtml(this.#cues[next]);
133
+ }
134
+ };
135
+ var defineMangoAVCaptions = (tagName = "mango-av-captions") => {
136
+ if (typeof customElements === "undefined") return MangoAVCaptionsElement;
137
+ if (!MangoAVCaptionsElement.registeredTag && !customElements.get(tagName)) {
138
+ customElements.define(tagName, MangoAVCaptionsElement);
139
+ MangoAVCaptionsElement.registeredTag = tagName;
140
+ }
141
+ return MangoAVCaptionsElement;
142
+ };
143
+
144
+ export {
145
+ MangoAVCaptionsElement,
146
+ defineMangoAVCaptions
147
+ };
148
+ //# sourceMappingURL=chunk-IZYEGSGT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/mango-av-captions.ts"],"sourcesContent":["import { AVControllerElement } from './controller-element';\nimport { componentTheme } from './component-styles';\nimport { loadCues } from './transcript-cues';\nimport type { AVPlayerEventMap, TranscriptCue } from '../types';\nimport { escapeHtml, resolveStrings } from '../utils';\n\n/*\n * Colours are read with fallbacks rather than set on :host, so a player can\n * restyle captions it holds, as it does over video, from its own stylesheet.\n */\nconst styles = `${componentTheme}\n :host([hidden]) { display: none; }\n .captions {\n align-items: center;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: var(--mango-av-captions-padding, 4px 16px 16px);\n text-align: center;\n }\n .line { margin: 0; max-width: 40em; }\n .line:empty { display: none; }\n .current {\n background: var(--mango-av-captions-background, transparent);\n border-radius: 4px;\n color: var(--mango-av-captions-color, var(--mango-av-text));\n font-size: var(--mango-av-captions-size, 18px);\n font-weight: 600;\n line-height: 1.4;\n padding: 2px 8px;\n }\n .current.changed { animation: rise 280ms ease-out; }\n .context {\n background: var(--mango-av-captions-background, transparent);\n border-radius: 4px;\n color: var(--mango-av-captions-color, var(--mango-av-muted));\n font-size: 14px;\n line-height: 1.4;\n opacity: .6;\n padding: 0 8px;\n }\n .speaker { display: block; font-size: 11px; font-weight: 700; letter-spacing: .05em; opacity: .75; text-transform: uppercase; }\n @keyframes rise { from { opacity: 0; transform: translateY(6px); } }\n @media (prefers-reduced-motion: reduce) { .current.changed { animation: none; } }\n`;\n\nconst lineHtml = (cue: TranscriptCue | undefined): string =>\n cue ? `${cue.speaker ? `<span class=\"speaker\">${escapeHtml(cue.speaker)}</span>` : ''}${escapeHtml(cue.text)}` : '';\n\n/**\n * The line being spoken, as captions are shown on video, with the lines\n * either side faded. Reads the active Canvas transcript through the controller,\n * so it can sit anywhere: inside a player's `stage` slot, or in a layout of\n * the host's own.\n *\n * Attributes: `line` shows only the line being spoken; `audio-only` hides it\n * while a video plays, since video has captions of its own.\n */\nexport class MangoAVCaptionsElement extends AVControllerElement {\n static observedAttributes = ['line', 'audio-only'];\n\n #root: ShadowRoot;\n #cues: TranscriptCue[] = [];\n #loadToken = 0;\n #shown: string | undefined;\n\n constructor() {\n super();\n this.#root = this.attachShadow({ mode: 'open' });\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.#render();\n }\n\n protected controllerChanged(): void {\n this.#reset();\n }\n\n protected handleControllerEvent<K extends keyof AVPlayerEventMap>(type: K, _event: CustomEvent<AVPlayerEventMap[K]>): void {\n if (type === 'av-ready' || type === 'av-canvaschange') this.#reset();\n else if (type === 'av-configchange' || type === 'av-sourcechange') this.#render();\n else if (type === 'av-timeupdate' || type === 'av-seek' || type === 'av-statechange') this.#sync();\n }\n\n #reset(): void {\n this.#cues = [];\n this.#render();\n void this.#load();\n }\n\n async #load(): Promise<void> {\n const resource = this.controller.canvas?.transcripts[0];\n if (!resource) return;\n const token = ++this.#loadToken;\n try {\n const cues = await loadCues(this.controller, resource);\n if (token !== this.#loadToken) return;\n this.#cues = cues.filter((cue) => cue.type === 'cue' && cue.start !== undefined);\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 }\n this.#render();\n }\n\n #render(): void {\n const ui = resolveStrings(this.controller.config.strings);\n const context = !this.hasAttribute('line');\n this.#root.innerHTML = `<style>${styles}</style><div class=\"captions\" role=\"region\" aria-label=\"${escapeHtml(ui.captions)}\">\n ${context ? '<p class=\"line context\" data-line=\"previous\"></p>' : ''}\n <p class=\"line current\" data-line=\"current\"></p>\n ${context ? '<p class=\"line context\" data-line=\"next\"></p>' : ''}\n </div>`;\n this.#shown = undefined;\n this.#sync();\n }\n\n #sync(): void {\n const playingVideo = this.hasAttribute('audio-only') && this.controller.source?.kind === 'video';\n this.hidden = this.#cues.length === 0 || playingVideo;\n if (this.hidden) return;\n const time = this.controller.state.currentTime;\n // The line being spoken is the last one started, until the next begins or it ends.\n let current = -1;\n this.#cues.forEach((cue, index) => {\n if ((cue.start ?? 0) <= time && (cue.end === undefined || time < cue.end)) current = index;\n });\n const upcoming = this.#cues.findIndex((cue) => (cue.start ?? 0) > time);\n const next = current >= 0 ? current + 1 : upcoming;\n const previous = current >= 0 ? current - 1 : (upcoming >= 0 ? upcoming : this.#cues.length) - 1;\n const key = `${previous}:${current}:${next}`;\n if (key === this.#shown) return;\n const changed = this.#shown?.split(':')[1] !== String(current);\n this.#shown = key;\n const line = (name: string) => this.#root.querySelector<HTMLElement>(`[data-line=${name}]`);\n const currentLine = line('current');\n if (currentLine) {\n currentLine.innerHTML = lineHtml(this.#cues[current]);\n // Restart the entrance each time the spoken line changes.\n if (changed && current >= 0) {\n currentLine.classList.remove('changed');\n void currentLine.offsetWidth;\n currentLine.classList.add('changed');\n }\n }\n const previousLine = line('previous');\n if (previousLine) previousLine.innerHTML = lineHtml(this.#cues[previous]);\n const nextLine = line('next');\n if (nextLine) nextLine.innerHTML = lineHtml(this.#cues[next]);\n }\n}\n\nexport const defineMangoAVCaptions = (tagName = 'mango-av-captions'): typeof MangoAVCaptionsElement => {\n if (typeof customElements === 'undefined') return MangoAVCaptionsElement;\n if (!MangoAVCaptionsElement.registeredTag && !customElements.get(tagName)) {\n customElements.define(tagName, MangoAVCaptionsElement);\n MangoAVCaptionsElement.registeredTag = tagName;\n }\n return MangoAVCaptionsElement;\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,160 @@
1
+ import {
2
+ AVPlayerController
3
+ } from "./chunk-53ZN4Y4L.js";
4
+
5
+ // src/components/controller-element.ts
6
+ var HTMLElementBase = globalThis.HTMLElement ?? class {
7
+ };
8
+ var controllerEventTypes = [
9
+ "av-loadstart",
10
+ "av-ready",
11
+ "av-error",
12
+ "av-canvaschange",
13
+ "av-sourcechange",
14
+ "av-play",
15
+ "av-pause",
16
+ "av-timeupdate",
17
+ "av-seek",
18
+ "av-ended",
19
+ "av-annotationschange",
20
+ "av-configchange",
21
+ "av-statechange"
22
+ ];
23
+ var AVControllerElement = class extends HTMLElementBase {
24
+ /** The tag this class was defined under, which the complete element renders. */
25
+ static registeredTag;
26
+ #controller = new AVPlayerController();
27
+ #connected = false;
28
+ #listeners = /* @__PURE__ */ new Map();
29
+ get controller() {
30
+ return this.#controller;
31
+ }
32
+ set controller(value) {
33
+ if (value === this.#controller) return;
34
+ this.#detachController();
35
+ this.#controller = value;
36
+ if (this.#connected) this.#attachController();
37
+ this.controllerChanged();
38
+ }
39
+ connectedCallback() {
40
+ this.#connected = true;
41
+ this.#attachController();
42
+ this.controllerChanged();
43
+ }
44
+ disconnectedCallback() {
45
+ this.#connected = false;
46
+ this.#detachController();
47
+ }
48
+ get controllerEvents() {
49
+ return controllerEventTypes;
50
+ }
51
+ #attachController() {
52
+ if (this.#listeners.size > 0) return;
53
+ for (const type of this.controllerEvents) {
54
+ const listener = ((event) => {
55
+ this.handleControllerEvent(type, event);
56
+ });
57
+ this.#controller.addEventListener(type, listener);
58
+ this.#listeners.set(type, listener);
59
+ }
60
+ }
61
+ #detachController() {
62
+ for (const [type, listener] of this.#listeners) {
63
+ this.#controller.removeEventListener(type, listener);
64
+ }
65
+ this.#listeners.clear();
66
+ }
67
+ };
68
+
69
+ // src/components/component-styles.ts
70
+ var componentTheme = `
71
+ :host {
72
+ --mango-av-accent: #5b3fd6;
73
+ --mango-av-accent-contrast: #fff;
74
+ --mango-av-accent-tint: color-mix(in srgb, var(--mango-av-accent) 12%, transparent);
75
+ --mango-av-background: #fff;
76
+ --mango-av-surface: #f4f4f6;
77
+ --mango-av-text: #18181b;
78
+ --mango-av-muted: #62626a;
79
+ --mango-av-border: #d7d7de;
80
+ --mango-av-danger: #b42318;
81
+ --mango-av-radius: 10px;
82
+ --mango-av-font: ui-sans-serif, system-ui, sans-serif;
83
+ color: var(--mango-av-text);
84
+ display: block;
85
+ font-family: var(--mango-av-font);
86
+ }
87
+ *, *::before, *::after { box-sizing: border-box; }
88
+ button, input, select { font: inherit; }
89
+ button, select { cursor: pointer; }
90
+ .empty { color: var(--mango-av-muted); font-size: 14px; margin: 0; }
91
+ `;
92
+ var panelStyles = `
93
+ .panel {
94
+ background: var(--mango-av-panel-background, var(--mango-av-background));
95
+ border: var(--mango-av-panel-border, 1px solid var(--mango-av-border));
96
+ border-radius: var(--mango-av-radius);
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: 10px;
100
+ padding: var(--mango-av-panel-padding, 16px);
101
+ }
102
+ h2 { font-size: 1rem; margin: 0; }
103
+ .tools { align-items: end; display: flex; flex-wrap: wrap; gap: 8px; }
104
+ .field { display: flex; flex: 1; flex-direction: column; font-size: 13px; font-weight: 600; gap: 6px; min-width: 10rem; }
105
+ .tools a { color: var(--mango-av-accent); font-size: 14px; line-height: 36px; }
106
+ input, select {
107
+ background: var(--mango-av-surface);
108
+ border: 1px solid var(--mango-av-border);
109
+ border-radius: 6px;
110
+ color: inherit;
111
+ font-weight: 400;
112
+ height: 36px;
113
+ padding: 0 10px;
114
+ }
115
+ .list {
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: 2px;
119
+ list-style: none;
120
+ margin: 0;
121
+ max-height: var(--mango-av-list-height, 24rem);
122
+ overflow: auto;
123
+ padding: 0;
124
+ position: relative;
125
+ }
126
+ .list .list { border-left: 1px solid var(--mango-av-border); margin-left: 41px; max-height: none; overflow: visible; padding-left: 8px; }
127
+ .row {
128
+ background: transparent;
129
+ border: 0;
130
+ border-radius: 6px;
131
+ color: inherit;
132
+ display: grid;
133
+ font-size: 14px;
134
+ gap: 10px;
135
+ grid-template-columns: 72px 1fr;
136
+ line-height: 1.45;
137
+ padding: 8px;
138
+ text-align: left;
139
+ width: 100%;
140
+ }
141
+ .row:hover:not(:disabled) { background: var(--mango-av-surface); }
142
+ .row.active, .row.active:hover { background: var(--mango-av-accent-tint); }
143
+ .row.active time { color: var(--mango-av-accent); font-weight: 600; }
144
+ .row:disabled { cursor: default; }
145
+ .row time { color: var(--mango-av-muted); font-variant-numeric: tabular-nums; }
146
+ .row .text { display: grid; gap: 4px; }
147
+ .speaker { font-weight: 700; }
148
+ mark { background: color-mix(in srgb, var(--mango-av-accent) 22%, transparent); border-radius: 2px; color: inherit; }
149
+ .tags { display: flex; flex-wrap: wrap; gap: 4px; }
150
+ .tag { background: var(--mango-av-surface); border-radius: 999px; font-size: 12px; padding: 2px 8px; }
151
+ `;
152
+
153
+ export {
154
+ HTMLElementBase,
155
+ controllerEventTypes,
156
+ AVControllerElement,
157
+ componentTheme,
158
+ panelStyles
159
+ };
160
+ //# sourceMappingURL=chunk-JXZR5DL2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/controller-element.ts","../src/components/component-styles.ts"],"sourcesContent":["import { AVPlayerController } from '../controller';\nimport type { AVPlayerEventMap } from '../types';\n\nexport const HTMLElementBase = (globalThis.HTMLElement ?? class {}) as typeof HTMLElement;\n\nexport const controllerEventTypes: Array<keyof AVPlayerEventMap> = [\n 'av-loadstart',\n 'av-ready',\n 'av-error',\n 'av-canvaschange',\n 'av-sourcechange',\n 'av-play',\n 'av-pause',\n 'av-timeupdate',\n 'av-seek',\n 'av-ended',\n 'av-annotationschange',\n 'av-configchange',\n 'av-statechange',\n];\n\nexport abstract class AVControllerElement extends HTMLElementBase {\n /** The tag this class was defined under, which the complete element renders. */\n static registeredTag: string | undefined;\n\n #controller = new AVPlayerController();\n #connected = false;\n #listeners = new Map<keyof AVPlayerEventMap, EventListener>();\n\n get controller(): AVPlayerController {\n return this.#controller;\n }\n\n set controller(value: AVPlayerController) {\n if (value === this.#controller) return;\n this.#detachController();\n this.#controller = value;\n if (this.#connected) this.#attachController();\n this.controllerChanged();\n }\n\n connectedCallback(): void {\n this.#connected = true;\n this.#attachController();\n this.controllerChanged();\n }\n\n disconnectedCallback(): void {\n this.#connected = false;\n this.#detachController();\n }\n\n protected get controllerEvents(): Array<keyof AVPlayerEventMap> {\n return controllerEventTypes;\n }\n\n protected abstract controllerChanged(): void;\n\n protected abstract handleControllerEvent<K extends keyof AVPlayerEventMap>(\n type: K,\n event: CustomEvent<AVPlayerEventMap[K]>,\n ): void;\n\n #attachController(): void {\n if (this.#listeners.size > 0) return;\n for (const type of this.controllerEvents) {\n const listener = ((event: CustomEvent) => {\n this.handleControllerEvent(type, event as CustomEvent<AVPlayerEventMap[typeof type]>);\n }) as EventListener;\n this.#controller.addEventListener(type, listener);\n this.#listeners.set(type, listener);\n }\n }\n\n #detachController(): void {\n for (const [type, listener] of this.#listeners) {\n this.#controller.removeEventListener(type, listener);\n }\n this.#listeners.clear();\n }\n}\n","export const componentTheme = `\n :host {\n --mango-av-accent: #5b3fd6;\n --mango-av-accent-contrast: #fff;\n --mango-av-accent-tint: color-mix(in srgb, var(--mango-av-accent) 12%, transparent);\n --mango-av-background: #fff;\n --mango-av-surface: #f4f4f6;\n --mango-av-text: #18181b;\n --mango-av-muted: #62626a;\n --mango-av-border: #d7d7de;\n --mango-av-danger: #b42318;\n --mango-av-radius: 10px;\n --mango-av-font: ui-sans-serif, system-ui, sans-serif;\n color: var(--mango-av-text);\n display: block;\n font-family: var(--mango-av-font);\n }\n *, *::before, *::after { box-sizing: border-box; }\n button, input, select { font: inherit; }\n button, select { cursor: pointer; }\n .empty { color: var(--mango-av-muted); font-size: 14px; margin: 0; }\n`;\n\n/*\n * Transcript, chapters and annotations share one look: a list of rows, each a\n * time column and its text, with the passage being heard tinted. A standalone\n * panel is a card; the complete element sets the card properties to nothing.\n */\nexport const panelStyles = `\n .panel {\n background: var(--mango-av-panel-background, var(--mango-av-background));\n border: var(--mango-av-panel-border, 1px solid var(--mango-av-border));\n border-radius: var(--mango-av-radius);\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: var(--mango-av-panel-padding, 16px);\n }\n h2 { font-size: 1rem; margin: 0; }\n .tools { align-items: end; display: flex; flex-wrap: wrap; gap: 8px; }\n .field { display: flex; flex: 1; flex-direction: column; font-size: 13px; font-weight: 600; gap: 6px; min-width: 10rem; }\n .tools a { color: var(--mango-av-accent); font-size: 14px; line-height: 36px; }\n input, select {\n background: var(--mango-av-surface);\n border: 1px solid var(--mango-av-border);\n border-radius: 6px;\n color: inherit;\n font-weight: 400;\n height: 36px;\n padding: 0 10px;\n }\n .list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n list-style: none;\n margin: 0;\n max-height: var(--mango-av-list-height, 24rem);\n overflow: auto;\n padding: 0;\n position: relative;\n }\n .list .list { border-left: 1px solid var(--mango-av-border); margin-left: 41px; max-height: none; overflow: visible; padding-left: 8px; }\n .row {\n background: transparent;\n border: 0;\n border-radius: 6px;\n color: inherit;\n display: grid;\n font-size: 14px;\n gap: 10px;\n grid-template-columns: 72px 1fr;\n line-height: 1.45;\n padding: 8px;\n text-align: left;\n width: 100%;\n }\n .row:hover:not(:disabled) { background: var(--mango-av-surface); }\n .row.active, .row.active:hover { background: var(--mango-av-accent-tint); }\n .row.active time { color: var(--mango-av-accent); font-weight: 600; }\n .row:disabled { cursor: default; }\n .row time { color: var(--mango-av-muted); font-variant-numeric: tabular-nums; }\n .row .text { display: grid; gap: 4px; }\n .speaker { font-weight: 700; }\n mark { background: color-mix(in srgb, var(--mango-av-accent) 22%, transparent); border-radius: 2px; color: inherit; }\n .tags { display: flex; flex-wrap: wrap; gap: 4px; }\n .tag { background: var(--mango-av-surface); border-radius: 999px; font-size: 12px; padding: 2px 8px; }\n`;\n"],"mappings":";;;;;AAGO,IAAM,kBAAmB,WAAW,eAAe,MAAM;AAAC;AAE1D,IAAM,uBAAsD;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAe,sBAAf,cAA2C,gBAAgB;AAAA;AAAA,EAEhE,OAAO;AAAA,EAEP,cAAc,IAAI,mBAAmB;AAAA,EACrC,aAAa;AAAA,EACb,aAAa,oBAAI,IAA2C;AAAA,EAE5D,IAAI,aAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,WAAW,OAA2B;AACxC,QAAI,UAAU,KAAK,YAAa;AAChC,SAAK,kBAAkB;AACvB,SAAK,cAAc;AACnB,QAAI,KAAK,WAAY,MAAK,kBAAkB;AAC5C,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,oBAA0B;AACxB,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,uBAA6B;AAC3B,SAAK,aAAa;AAClB,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,IAAc,mBAAkD;AAC9D,WAAO;AAAA,EACT;AAAA,EASA,oBAA0B;AACxB,QAAI,KAAK,WAAW,OAAO,EAAG;AAC9B,eAAW,QAAQ,KAAK,kBAAkB;AACxC,YAAM,YAAY,CAAC,UAAuB;AACxC,aAAK,sBAAsB,MAAM,KAAmD;AAAA,MACtF;AACA,WAAK,YAAY,iBAAiB,MAAM,QAAQ;AAChD,WAAK,WAAW,IAAI,MAAM,QAAQ;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,oBAA0B;AACxB,eAAW,CAAC,MAAM,QAAQ,KAAK,KAAK,YAAY;AAC9C,WAAK,YAAY,oBAAoB,MAAM,QAAQ;AAAA,IACrD;AACA,SAAK,WAAW,MAAM;AAAA,EACxB;AACF;;;AChFO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BvB,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseTime
3
- } from "./chunk-4NOWGMI5.js";
3
+ } from "./chunk-53ZN4Y4L.js";
4
4
 
5
5
  // src/transcript.ts
6
6
  var stripMarkup = (value) => value.replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').trim();
@@ -13,15 +13,19 @@ var cueFromBlock = (block, index, isSrt) => {
13
13
  const start = parseTime(isSrt ? rawStart.replace(",", ".") : rawStart);
14
14
  const end = parseTime(isSrt ? rawEnd.replace(",", ".") : rawEnd);
15
15
  if (start === void 0 || end === void 0) return void 0;
16
- const text = stripMarkup(block.slice(timestampIndex + 1).join("\n"));
16
+ const body = block.slice(timestampIndex + 1).join("\n");
17
+ const text = stripMarkup(body);
17
18
  if (!text) return void 0;
18
- return {
19
+ const cue = {
19
20
  id: `cue-${index}`,
20
21
  start,
21
22
  end,
22
23
  text,
23
24
  type: "cue"
24
25
  };
26
+ const speaker = /^<v(?:\.[^\s>]*)?\s+([^>]+)>/.exec(body.trimStart())?.[1]?.trim();
27
+ if (speaker) cue.speaker = speaker;
28
+ return cue;
25
29
  };
26
30
  var parseTimedText = (input, format, options = {}) => {
27
31
  const normalized = input.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
@@ -123,4 +127,4 @@ export {
123
127
  parseTranscript,
124
128
  loadTranscript
125
129
  };
126
- //# sourceMappingURL=chunk-CIAQZ2EB.js.map
130
+ //# sourceMappingURL=chunk-LMZIFVHN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transcript.ts"],"sourcesContent":["import type { TranscriptCue, TranscriptResource } from './types';\nimport { parseTime } from './utils';\n\nexport interface TranscriptParseOptions {\n showNotes?: boolean;\n showMetadata?: boolean;\n}\n\nconst stripMarkup = (value: string): string =>\n value\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/<[^>]+>/g, '')\n .replace(/&nbsp;/g, ' ')\n .replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .trim();\n\nconst cueFromBlock = (\n block: string[],\n index: number,\n isSrt: boolean,\n): TranscriptCue | undefined => {\n const timestampIndex = block.findIndex((line) => line.includes('-->'));\n if (timestampIndex < 0) return undefined;\n const timestamp = block[timestampIndex];\n if (!timestamp) return undefined;\n const [rawStart = '', rawEnd = ''] = timestamp.split('-->').map((part) => part.trim().split(/\\s+/)[0] ?? '');\n const start = parseTime(isSrt ? rawStart.replace(',', '.') : rawStart);\n const end = parseTime(isSrt ? rawEnd.replace(',', '.') : rawEnd);\n if (start === undefined || end === undefined) return undefined;\n const body = block.slice(timestampIndex + 1).join('\\n');\n const text = stripMarkup(body);\n if (!text) return undefined;\n const cue: TranscriptCue = {\n id: `cue-${index}`,\n start,\n end,\n text,\n type: 'cue',\n };\n // A WebVTT voice tag, `<v Ana>` or `<v.loud Ana>`, names who is speaking.\n const speaker = /^<v(?:\\.[^\\s>]*)?\\s+([^>]+)>/.exec(body.trimStart())?.[1]?.trim();\n if (speaker) cue.speaker = speaker;\n return cue;\n};\n\nexport const parseTimedText = (\n input: string,\n format: 'vtt' | 'srt',\n options: TranscriptParseOptions = {},\n): TranscriptCue[] => {\n const normalized = input.replace(/^\\uFEFF/, '').replace(/\\r\\n?/g, '\\n');\n if (format === 'vtt' && !normalized.trimStart().startsWith('WEBVTT')) {\n throw new Error('Invalid WebVTT transcript: missing WEBVTT header.');\n }\n const lines = normalized.split('\\n');\n const cues: TranscriptCue[] = [];\n let block: string[] = [];\n const flush = () => {\n if (block.length === 0) return;\n const first = block[0]?.trim() ?? '';\n if (/^NOTE(?:\\s|$)/i.test(first)) {\n if (options.showNotes) {\n const text = stripMarkup(block.join('\\n').replace(/^NOTE\\s*/i, ''));\n if (text) cues.push({ id: `note-${cues.length}`, text, type: 'note' });\n }\n } else if (!block.some((line) => line.includes('-->'))) {\n const upper = first.toUpperCase();\n if (options.showMetadata && first && first !== 'WEBVTT' && upper !== 'STYLE' && upper !== 'REGION') {\n cues.push({ id: `metadata-${cues.length}`, text: stripMarkup(block.join('\\n')), type: 'metadata' });\n }\n } else {\n const cue = cueFromBlock(block, cues.length, format === 'srt');\n if (cue) cues.push(cue);\n }\n block = [];\n };\n for (const line of lines) {\n if (line.trim() === '') flush();\n else block.push(line);\n }\n flush();\n return cues;\n};\n\nconst parseJsonTranscript = (value: unknown): TranscriptCue[] => {\n if (!Array.isArray(value)) return [];\n const cues: TranscriptCue[] = [];\n for (const entry of value) {\n if (!entry || typeof entry !== 'object') continue;\n const object = entry as Record<string, unknown>;\n const spans = Array.isArray(object.spans) ? object.spans : [object];\n for (const spanValue of spans) {\n if (!spanValue || typeof spanValue !== 'object') continue;\n const span = spanValue as Record<string, unknown>;\n const textValue = span.text ?? span.value ?? span.chars;\n if (typeof textValue !== 'string') continue;\n const cue: TranscriptCue = {\n id: typeof span.id === 'string' ? span.id : `cue-${cues.length}`,\n text: stripMarkup(textValue),\n type: 'cue',\n };\n const start = parseTime((span.begin ?? span.start) as string | number | undefined);\n const end = parseTime((span.end ?? span.stop) as string | number | undefined);\n if (start !== undefined) cue.start = start;\n if (end !== undefined) cue.end = end;\n if (typeof object.speaker === 'string') cue.speaker = object.speaker;\n else if (typeof span.speaker === 'string') cue.speaker = span.speaker;\n cues.push(cue);\n }\n }\n return cues;\n};\n\nexport const parseTranscript = (\n input: string | unknown,\n format: string,\n options: TranscriptParseOptions = {},\n): TranscriptCue[] => {\n const normalizedFormat = format.toLowerCase().split(';')[0] ?? '';\n if (normalizedFormat.includes('json') || (typeof input !== 'string' && typeof input === 'object')) {\n const value = typeof input === 'string' ? JSON.parse(input) as unknown : input;\n return parseJsonTranscript(value);\n }\n if (typeof input !== 'string') return [];\n if (normalizedFormat.includes('vtt') || normalizedFormat === 'vtt') return parseTimedText(input, 'vtt', options);\n if (normalizedFormat.includes('srt') || normalizedFormat.includes('subrip') || normalizedFormat === 'srt') return parseTimedText(input, 'srt', options);\n return input\n .replace(/\\r\\n?/g, '\\n')\n .split(/\\n{2,}|\\n/)\n .map((text) => stripMarkup(text))\n .filter(Boolean)\n .map((text, index) => ({ id: `text-${index}`, text, type: 'text' as const }));\n};\n\nconst inferFormat = (resource: TranscriptResource, contentType: string | null): string => {\n if (resource.format) return resource.format;\n if (contentType) return contentType;\n const extension = resource.src?.split(/[?#]/)[0]?.split('.').pop()?.toLowerCase();\n return extension ?? 'text/plain';\n};\n\nexport const loadTranscript = async (\n resource: TranscriptResource,\n options: TranscriptParseOptions & { fetch?: typeof globalThis.fetch; requestInit?: RequestInit } = {},\n): Promise<TranscriptCue[]> => {\n if (resource.cues) return resource.cues;\n if (!resource.src) return [];\n if (resource.format?.includes('wordprocessingml') || /\\.docx(?:$|[?#])/i.test(resource.src)) {\n throw new Error('DOCX transcripts are not supported.');\n }\n const fetcher = options.fetch ?? globalThis.fetch;\n if (!fetcher) throw new Error('No fetch implementation is available to load the transcript.');\n const response = await fetcher(resource.src, options.requestInit);\n if (!response.ok) throw new Error(`Unable to load transcript (${response.status} ${response.statusText}).`);\n const contentType = response.headers.get('content-type');\n const format = inferFormat(resource, contentType);\n if (format.includes('json')) return parseTranscript(await response.json(), format, options);\n return parseTranscript(await response.text(), format, options);\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,39 @@
1
+ // src/components/playback.ts
2
+ var playFromInteraction = (controller, target) => {
3
+ void controller.play().catch((cause) => {
4
+ if (cause instanceof Error && cause.name === "AbortError") return;
5
+ const error = cause instanceof Error ? cause : new Error(String(cause));
6
+ target.dispatchEvent(new CustomEvent("av-error", {
7
+ detail: { error },
8
+ bubbles: true,
9
+ composed: true
10
+ }));
11
+ });
12
+ };
13
+
14
+ // src/components/timed-rows.ts
15
+ var markActiveRow = (root, time) => {
16
+ let active;
17
+ root.querySelectorAll("[data-start]").forEach((row) => {
18
+ const start = Number(row.dataset.start);
19
+ const end = row.dataset.end ? Number(row.dataset.end) : Number.NaN;
20
+ const isActive = row.dataset.start !== "" && Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end);
21
+ const button = row.querySelector(":scope > button");
22
+ button?.classList.toggle("active", isActive);
23
+ if (isActive) button?.setAttribute("aria-current", "true");
24
+ else button?.removeAttribute("aria-current");
25
+ if (isActive) active = row;
26
+ });
27
+ return active;
28
+ };
29
+ var scrollRowToTop = (row) => {
30
+ const list = row.closest(".list");
31
+ list?.scrollTo?.({ top: row.offsetTop, behavior: "smooth" });
32
+ };
33
+
34
+ export {
35
+ playFromInteraction,
36
+ markActiveRow,
37
+ scrollRowToTop
38
+ };
39
+ //# sourceMappingURL=chunk-TQNEVWUH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/playback.ts","../src/components/timed-rows.ts"],"sourcesContent":["import type { AVPlayerController } from '../controller';\n\nexport const playFromInteraction = (controller: AVPlayerController, target: EventTarget): void => {\n void controller.play().catch((cause: unknown) => {\n if (cause instanceof Error && cause.name === 'AbortError') return;\n const error = cause instanceof Error ? cause : new Error(String(cause));\n target.dispatchEvent(new CustomEvent('av-error', {\n detail: { error },\n bubbles: true,\n composed: true,\n }));\n });\n};\n","/**\n * Tints the row being heard: the last one started, until the next begins or it\n * ends. Rows carry their span as `data-start` and `data-end`.\n */\nexport const markActiveRow = (root: ParentNode, time: number): HTMLElement | undefined => {\n let active: HTMLElement | undefined;\n root.querySelectorAll<HTMLElement>('[data-start]').forEach((row) => {\n const start = Number(row.dataset.start);\n const end = row.dataset.end ? Number(row.dataset.end) : Number.NaN;\n const isActive = row.dataset.start !== '' && Number.isFinite(start) && time >= start && (!Number.isFinite(end) || time < end);\n const button = row.querySelector(':scope > button');\n button?.classList.toggle('active', isActive);\n if (isActive) button?.setAttribute('aria-current', 'true');\n else button?.removeAttribute('aria-current');\n if (isActive) active = row;\n });\n return active;\n};\n\n/** Brings a row to the top of its list. The list is the rows' offset parent, so offsetTop is already within it. */\nexport const scrollRowToTop = (row: HTMLElement): void => {\n const list = row.closest<HTMLElement>('.list');\n list?.scrollTo?.({ top: row.offsetTop, behavior: 'smooth' });\n};\n"],"mappings":";AAEO,IAAM,sBAAsB,CAAC,YAAgC,WAA8B;AAChG,OAAK,WAAW,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/C,QAAI,iBAAiB,SAAS,MAAM,SAAS,aAAc;AAC3D,UAAM,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACtE,WAAO,cAAc,IAAI,YAAY,YAAY;AAAA,MAC/C,QAAQ,EAAE,MAAM;AAAA,MAChB,SAAS;AAAA,MACT,UAAU;AAAA,IACZ,CAAC,CAAC;AAAA,EACJ,CAAC;AACH;;;ACRO,IAAM,gBAAgB,CAAC,MAAkB,SAA0C;AACxF,MAAI;AACJ,OAAK,iBAA8B,cAAc,EAAE,QAAQ,CAAC,QAAQ;AAClE,UAAM,QAAQ,OAAO,IAAI,QAAQ,KAAK;AACtC,UAAM,MAAM,IAAI,QAAQ,MAAM,OAAO,IAAI,QAAQ,GAAG,IAAI,OAAO;AAC/D,UAAM,WAAW,IAAI,QAAQ,UAAU,MAAM,OAAO,SAAS,KAAK,KAAK,QAAQ,UAAU,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO;AACzH,UAAM,SAAS,IAAI,cAAc,iBAAiB;AAClD,YAAQ,UAAU,OAAO,UAAU,QAAQ;AAC3C,QAAI,SAAU,SAAQ,aAAa,gBAAgB,MAAM;AAAA,QACpD,SAAQ,gBAAgB,cAAc;AAC3C,QAAI,SAAU,UAAS;AAAA,EACzB,CAAC;AACD,SAAO;AACT;AAGO,IAAM,iBAAiB,CAAC,QAA2B;AACxD,QAAM,OAAO,IAAI,QAAqB,OAAO;AAC7C,QAAM,WAAW,EAAE,KAAK,IAAI,WAAW,UAAU,SAAS,CAAC;AAC7D;","names":[]}