@mango-iiif/av 0.0.1

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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/annotations.d.ts +12 -0
  4. package/dist/annotations.js +15 -0
  5. package/dist/annotations.js.map +1 -0
  6. package/dist/chapters.d.ts +12 -0
  7. package/dist/chapters.js +14 -0
  8. package/dist/chapters.js.map +1 -0
  9. package/dist/chunk-3LLEY7MA.js +126 -0
  10. package/dist/chunk-3LLEY7MA.js.map +1 -0
  11. package/dist/chunk-CDLYT3KB.js +17 -0
  12. package/dist/chunk-CDLYT3KB.js.map +1 -0
  13. package/dist/chunk-GAVG7CPU.js +1 -0
  14. package/dist/chunk-GAVG7CPU.js.map +1 -0
  15. package/dist/chunk-GX7UU6V4.js +547 -0
  16. package/dist/chunk-GX7UU6V4.js.map +1 -0
  17. package/dist/chunk-IFTSCRCU.js +83 -0
  18. package/dist/chunk-IFTSCRCU.js.map +1 -0
  19. package/dist/chunk-JXUHKKIX.js +94 -0
  20. package/dist/chunk-JXUHKKIX.js.map +1 -0
  21. package/dist/chunk-ML77YRJL.js +221 -0
  22. package/dist/chunk-ML77YRJL.js.map +1 -0
  23. package/dist/chunk-NTHMZZYJ.js +38 -0
  24. package/dist/chunk-NTHMZZYJ.js.map +1 -0
  25. package/dist/chunk-PZ6GV6ZJ.js +163 -0
  26. package/dist/chunk-PZ6GV6ZJ.js.map +1 -0
  27. package/dist/chunk-R4OUWZ3R.js +1038 -0
  28. package/dist/chunk-R4OUWZ3R.js.map +1 -0
  29. package/dist/chunk-YH6DS7J6.js +74 -0
  30. package/dist/chunk-YH6DS7J6.js.map +1 -0
  31. package/dist/controller-DLy38ais.d.ts +356 -0
  32. package/dist/controller-element-BOKM9v2S.d.ts +15 -0
  33. package/dist/core.d.ts +28 -0
  34. package/dist/core.js +28 -0
  35. package/dist/core.js.map +1 -0
  36. package/dist/index.d.ts +25 -0
  37. package/dist/index.js +70 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/media.d.ts +28 -0
  40. package/dist/media.js +16 -0
  41. package/dist/media.js.map +1 -0
  42. package/dist/player.d.ts +27 -0
  43. package/dist/player.js +15 -0
  44. package/dist/player.js.map +1 -0
  45. package/dist/transcript-element.d.ts +12 -0
  46. package/dist/transcript-element.js +16 -0
  47. package/dist/transcript-element.js.map +1 -0
  48. package/docs/API.md +264 -0
  49. package/docs/FEATURE_PARITY.md +43 -0
  50. package/package.json +94 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mango IIIF contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # @mango-iiif/av
2
+
3
+ Framework-free IIIF audio and video components powered by [Media Chrome](https://www.media-chrome.org/). The package reads Presentation 2 and Presentation 3 manifests and exposes an all-in-one element, independently placeable components, and a typed controller API. It has no React or Svelte runtime dependency.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @mango-iiif/av
9
+ ```
10
+
11
+ ## Complete media experience
12
+
13
+ `<mango-av-media>` contains the player, chapters, transcript, annotations, metadata, and supplemental files:
14
+
15
+ ```ts
16
+ import '@mango-iiif/av';
17
+ import type { MangoAVMediaElement } from '@mango-iiif/av';
18
+
19
+ const media = document.querySelector<MangoAVMediaElement>('mango-av-media')!;
20
+
21
+ media.config = {
22
+ panels: {
23
+ chapters: true,
24
+ transcript: true,
25
+ annotations: true,
26
+ metadata: true,
27
+ supplementalFiles: true,
28
+ },
29
+ controls: {
30
+ captions: true,
31
+ playbackRate: true,
32
+ pictureInPicture: true,
33
+ fullscreen: true,
34
+ },
35
+ resume: { enabled: true },
36
+ };
37
+
38
+ media.manifest = 'https://example.org/iiif/manifest.json';
39
+ ```
40
+
41
+ It can also be configured from HTML when only JSON-compatible options are needed:
42
+
43
+ ```html
44
+ <mango-av-media
45
+ manifest-url="https://example.org/iiif/manifest.json"
46
+ config='{"panels":{"transcript":true},"autoAdvance":false}'
47
+ ></mango-av-media>
48
+ ```
49
+
50
+ ## Independently placeable components
51
+
52
+ The player, chapters, transcript, and annotations can be placed anywhere in an application. Assign the same `AVPlayerController` to each element so actions and state changes synchronize across all of them:
53
+
54
+ ```html
55
+ <main><mango-av-player></mango-av-player></main>
56
+ <aside><mango-av-chapters></mango-av-chapters></aside>
57
+ <section><mango-av-transcript></mango-av-transcript></section>
58
+ <aside><mango-av-annotations></mango-av-annotations></aside>
59
+ ```
60
+
61
+ ```ts
62
+ import { AVPlayerController } from '@mango-iiif/av/core';
63
+ import '@mango-iiif/av/player';
64
+ import '@mango-iiif/av/chapters';
65
+ import '@mango-iiif/av/transcript';
66
+ import '@mango-iiif/av/annotations';
67
+ import { type MangoAVPlayerElement } from '@mango-iiif/av/player';
68
+ import { type MangoAVChaptersElement } from '@mango-iiif/av/chapters';
69
+ import { type MangoAVTranscriptElement } from '@mango-iiif/av/transcript';
70
+ import { type MangoAVAnnotationsElement } from '@mango-iiif/av/annotations';
71
+
72
+ const controller = new AVPlayerController();
73
+
74
+ document.querySelector<MangoAVPlayerElement>('mango-av-player')!.controller = controller;
75
+ document.querySelector<MangoAVChaptersElement>('mango-av-chapters')!.controller = controller;
76
+ document.querySelector<MangoAVTranscriptElement>('mango-av-transcript')!.controller = controller;
77
+ document.querySelector<MangoAVAnnotationsElement>('mango-av-annotations')!.controller = controller;
78
+
79
+ await controller.load(manifest);
80
+ ```
81
+
82
+ The controller is an `EventTarget`. A chapter selection calls `selectCanvas`, a transcript or annotation selection calls `seekTo`, and every connected component reacts to the resulting typed controller events. Components do not need a common DOM parent.
83
+
84
+ Use one `<mango-av-player>` per controller. Any number of display or interaction components can share that controller, including components rendered in different framework components, panels, or portals.
85
+
86
+ ```ts
87
+ const unsubscribe = controller.on('av-timeupdate', ({ detail }) => {
88
+ console.log(detail.canvasId, detail.time);
89
+ });
90
+ ```
91
+
92
+ ## Translations
93
+
94
+ Override any package-owned interface string with the typed, partial `strings` configuration. Unspecified values keep their English defaults, and the same controller dictionary is used by every connected standalone component:
95
+
96
+ ```ts
97
+ const controller = new AVPlayerController({
98
+ preferredLanguages: ['cy', 'en', 'none'],
99
+ strings: {
100
+ chapters: 'Penodau',
101
+ transcript: 'Trawsgrifiad',
102
+ previous: 'Blaenorol',
103
+ next: 'Nesaf',
104
+ searchTranscript: 'Chwilio’r trawsgrifiad',
105
+ sourceNumber: 'Ffynhonnell {number}',
106
+ resumePrompt: 'Parhau o {time}?',
107
+ },
108
+ });
109
+ ```
110
+
111
+ `preferredLanguages` chooses localized IIIF labels and sets the Media Chrome language. The package automatically loads Media Chrome's bundled German, Spanish, French, Portuguese, and Chinese dictionaries. `strings` covers the Mango AV wrapper UI and generated fallback labels for any language. The complete English dictionary is exported as `defaultStrings` for building and validating translation files.
112
+
113
+ ## Svelte
114
+
115
+ The element works as a normal custom element. The future Mango integration does not need a wrapper library:
116
+
117
+ ```svelte
118
+ <script lang="ts">
119
+ import '@mango-iiif/av';
120
+ import type { AVPlayerConfig } from '@mango-iiif/av';
121
+
122
+ const config: AVPlayerConfig = {
123
+ panels: { chapters: true, transcript: true },
124
+ };
125
+ </script>
126
+
127
+ <mango-av-media manifest-url={manifestUrl} .config={config}></mango-av-media>
128
+ ```
129
+
130
+ For Svelte versions that do not accept property syntax in markup, bind the element and assign `element.config` in an effect.
131
+
132
+ ## Controller-only API
133
+
134
+ Use the controller when an application owns its own UI:
135
+
136
+ ```ts
137
+ import { AVPlayerController } from '@mango-iiif/av/core';
138
+
139
+ const controller = new AVPlayerController({ autoAdvance: true });
140
+ const manifest = await controller.load(manifestJson);
141
+
142
+ controller.attachMedia(document.querySelector('video')!);
143
+ controller.selectCanvas(manifest.canvases[1].id, { time: 12.5 });
144
+ await controller.play();
145
+ ```
146
+
147
+ ## Styling and themes
148
+
149
+ The component maps its theme to public CSS custom properties. This gives the host full control of the look without depending on private shadow-DOM selectors:
150
+
151
+ ```css
152
+ mango-av-media.tailwind-audio,
153
+ mango-av-player.tailwind-audio,
154
+ mango-av-chapters.tailwind-audio,
155
+ mango-av-transcript.tailwind-audio,
156
+ mango-av-annotations.tailwind-audio {
157
+ --mango-av-accent: #0f172a;
158
+ --mango-av-accent-contrast: #f8fafc;
159
+ --mango-av-background: #f8fafc;
160
+ --mango-av-surface: #e2e8f0;
161
+ --mango-av-text: #0f172a;
162
+ --mango-av-border: #cbd5e1;
163
+ --mango-av-radius: 0.875rem;
164
+ --mango-av-font: Inter, ui-sans-serif, system-ui;
165
+ --mango-av-panel-width: 26rem;
166
+ }
167
+ ```
168
+
169
+ Media Chrome variables such as `--media-primary-color` and `--media-secondary-color` can also be set directly. The complete element provides `header`, `before-player`, `player-overlay`, `controls`, and `after-player` slots. The standalone player provides `overlay` and `controls`; the information components provide a `heading` slot.
170
+
171
+ Run `npm run dev` to use the interactive configuration builder and the linked composable-components demonstration. They include Video, HLS, and Audio presets, feature switches, generated API configuration, independently positioned components, and multiple style recipes.
172
+
173
+ ## Supported IIIF AV behavior
174
+
175
+ - Presentation 2 sequences and Presentation 3 Canvas items
176
+ - Audio, video, native HLS, and hls.js-backed HLS through `<hls-video>`
177
+ - `Choice` and multiple painting bodies, media source selection, temporal fragments, and clipped playback
178
+ - Multi-Canvas previous/next, `start`/`startCanvas`, `auto-advance`, and `repeat`
179
+ - Nested Range navigation and temporal chapters
180
+ - Inline and external supplementing annotations
181
+ - Captions, subtitles, audio descriptions, transcript selection, search, download, and synchronized scrolling
182
+ - WebVTT, SRT, plain text, structured JSON, and inline TextualBody transcripts
183
+ - Commenting, supplementing, transcribing, tagging, and highlighting annotations
184
+ - Manifest/Canvas metadata, rights, required statements, homepages, and `rendering` downloads
185
+ - Placeholder posters, request resolution for protected media, and service metadata normalization
186
+ - Optional persisted playback positions
187
+ - Configurable annotation create/update/delete adapters
188
+
189
+ See [API.md](docs/API.md) for the complete API and [FEATURE_PARITY.md](docs/FEATURE_PARITY.md) for feature coverage.
190
+
191
+ ## Development
192
+
193
+ ```sh
194
+ npm install
195
+ npm run dev
196
+ npm run typecheck
197
+ npm test
198
+ npm run test:e2e
199
+ npm run build
200
+ npm run pack:check
201
+ ```
202
+
203
+ ## License
204
+
205
+ MIT
@@ -0,0 +1,12 @@
1
+ import { A as AVControllerElement } from './controller-element-BOKM9v2S.js';
2
+ import { i as AVPlayerEventMap } from './controller-DLy38ais.js';
3
+
4
+ declare class MangoAVAnnotationsElement extends AVControllerElement {
5
+ #private;
6
+ constructor();
7
+ protected controllerChanged(): void;
8
+ protected handleControllerEvent<K extends keyof AVPlayerEventMap>(type: K, _event: CustomEvent<AVPlayerEventMap[K]>): void;
9
+ }
10
+ declare const defineMangoAVAnnotations: (tagName?: string) => typeof MangoAVAnnotationsElement;
11
+
12
+ export { MangoAVAnnotationsElement, defineMangoAVAnnotations };
@@ -0,0 +1,15 @@
1
+ import {
2
+ MangoAVAnnotationsElement,
3
+ defineMangoAVAnnotations
4
+ } from "./chunk-IFTSCRCU.js";
5
+ import "./chunk-CDLYT3KB.js";
6
+ import "./chunk-JXUHKKIX.js";
7
+ import "./chunk-R4OUWZ3R.js";
8
+
9
+ // src/annotations.ts
10
+ if (typeof window !== "undefined" && "customElements" in window) defineMangoAVAnnotations();
11
+ export {
12
+ MangoAVAnnotationsElement,
13
+ defineMangoAVAnnotations
14
+ };
15
+ //# sourceMappingURL=annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/annotations.ts"],"sourcesContent":["export { defineMangoAVAnnotations, MangoAVAnnotationsElement } from './components/mango-av-annotations';\n\nimport { defineMangoAVAnnotations } from './components/mango-av-annotations';\nif (typeof window !== 'undefined' && 'customElements' in window) defineMangoAVAnnotations();\n"],"mappings":";;;;;;;;;AAGA,IAAI,OAAO,WAAW,eAAe,oBAAoB,OAAQ,0BAAyB;","names":[]}
@@ -0,0 +1,12 @@
1
+ import { A as AVControllerElement } from './controller-element-BOKM9v2S.js';
2
+ import { i as AVPlayerEventMap } from './controller-DLy38ais.js';
3
+
4
+ declare class MangoAVChaptersElement extends AVControllerElement {
5
+ #private;
6
+ constructor();
7
+ protected controllerChanged(): void;
8
+ protected handleControllerEvent<K extends keyof AVPlayerEventMap>(type: K, _event: CustomEvent<AVPlayerEventMap[K]>): void;
9
+ }
10
+ declare const defineMangoAVChapters: (tagName?: string) => typeof MangoAVChaptersElement;
11
+
12
+ export { MangoAVChaptersElement, defineMangoAVChapters };
@@ -0,0 +1,14 @@
1
+ import {
2
+ MangoAVChaptersElement,
3
+ defineMangoAVChapters
4
+ } from "./chunk-YH6DS7J6.js";
5
+ import "./chunk-JXUHKKIX.js";
6
+ import "./chunk-R4OUWZ3R.js";
7
+
8
+ // src/chapters.ts
9
+ if (typeof window !== "undefined" && "customElements" in window) defineMangoAVChapters();
10
+ export {
11
+ MangoAVChaptersElement,
12
+ defineMangoAVChapters
13
+ };
14
+ //# sourceMappingURL=chapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chapters.ts"],"sourcesContent":["export { defineMangoAVChapters, MangoAVChaptersElement } from './components/mango-av-chapters';\n\nimport { defineMangoAVChapters } from './components/mango-av-chapters';\nif (typeof window !== 'undefined' && 'customElements' in window) defineMangoAVChapters();\n"],"mappings":";;;;;;;;AAGA,IAAI,OAAO,WAAW,eAAe,oBAAoB,OAAQ,uBAAsB;","names":[]}
@@ -0,0 +1,126 @@
1
+ import {
2
+ parseTime
3
+ } from "./chunk-R4OUWZ3R.js";
4
+
5
+ // src/transcript.ts
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();
7
+ var cueFromBlock = (block, index, isSrt) => {
8
+ const timestampIndex = block.findIndex((line) => line.includes("-->"));
9
+ if (timestampIndex < 0) return void 0;
10
+ const timestamp = block[timestampIndex];
11
+ if (!timestamp) return void 0;
12
+ const [rawStart = "", rawEnd = ""] = timestamp.split("-->").map((part) => part.trim().split(/\s+/)[0] ?? "");
13
+ const start = parseTime(isSrt ? rawStart.replace(",", ".") : rawStart);
14
+ const end = parseTime(isSrt ? rawEnd.replace(",", ".") : rawEnd);
15
+ if (start === void 0 || end === void 0) return void 0;
16
+ const text = stripMarkup(block.slice(timestampIndex + 1).join("\n"));
17
+ if (!text) return void 0;
18
+ return {
19
+ id: `cue-${index}`,
20
+ start,
21
+ end,
22
+ text,
23
+ type: "cue"
24
+ };
25
+ };
26
+ var parseTimedText = (input, format, options = {}) => {
27
+ const normalized = input.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
28
+ if (format === "vtt" && !normalized.trimStart().startsWith("WEBVTT")) {
29
+ throw new Error("Invalid WebVTT transcript: missing WEBVTT header.");
30
+ }
31
+ const lines = normalized.split("\n");
32
+ const cues = [];
33
+ let block = [];
34
+ const flush = () => {
35
+ if (block.length === 0) return;
36
+ const first = block[0]?.trim() ?? "";
37
+ if (/^NOTE(?:\s|$)/i.test(first)) {
38
+ if (options.showNotes) {
39
+ const text = stripMarkup(block.join("\n").replace(/^NOTE\s*/i, ""));
40
+ if (text) cues.push({ id: `note-${cues.length}`, text, type: "note" });
41
+ }
42
+ } else if (!block.some((line) => line.includes("-->"))) {
43
+ const upper = first.toUpperCase();
44
+ if (options.showMetadata && first && first !== "WEBVTT" && upper !== "STYLE" && upper !== "REGION") {
45
+ cues.push({ id: `metadata-${cues.length}`, text: stripMarkup(block.join("\n")), type: "metadata" });
46
+ }
47
+ } else {
48
+ const cue = cueFromBlock(block, cues.length, format === "srt");
49
+ if (cue) cues.push(cue);
50
+ }
51
+ block = [];
52
+ };
53
+ for (const line of lines) {
54
+ if (line.trim() === "") flush();
55
+ else block.push(line);
56
+ }
57
+ flush();
58
+ return cues;
59
+ };
60
+ var parseJsonTranscript = (value) => {
61
+ if (!Array.isArray(value)) return [];
62
+ const cues = [];
63
+ for (const entry of value) {
64
+ if (!entry || typeof entry !== "object") continue;
65
+ const object = entry;
66
+ const spans = Array.isArray(object.spans) ? object.spans : [object];
67
+ for (const spanValue of spans) {
68
+ if (!spanValue || typeof spanValue !== "object") continue;
69
+ const span = spanValue;
70
+ const textValue = span.text ?? span.value ?? span.chars;
71
+ if (typeof textValue !== "string") continue;
72
+ const cue = {
73
+ id: typeof span.id === "string" ? span.id : `cue-${cues.length}`,
74
+ text: stripMarkup(textValue),
75
+ type: "cue"
76
+ };
77
+ const start = parseTime(span.begin ?? span.start);
78
+ const end = parseTime(span.end ?? span.stop);
79
+ if (start !== void 0) cue.start = start;
80
+ if (end !== void 0) cue.end = end;
81
+ if (typeof object.speaker === "string") cue.speaker = object.speaker;
82
+ else if (typeof span.speaker === "string") cue.speaker = span.speaker;
83
+ cues.push(cue);
84
+ }
85
+ }
86
+ return cues;
87
+ };
88
+ var parseTranscript = (input, format, options = {}) => {
89
+ const normalizedFormat = format.toLowerCase().split(";")[0] ?? "";
90
+ if (normalizedFormat.includes("json") || typeof input !== "string" && typeof input === "object") {
91
+ const value = typeof input === "string" ? JSON.parse(input) : input;
92
+ return parseJsonTranscript(value);
93
+ }
94
+ if (typeof input !== "string") return [];
95
+ if (normalizedFormat.includes("vtt") || normalizedFormat === "vtt") return parseTimedText(input, "vtt", options);
96
+ if (normalizedFormat.includes("srt") || normalizedFormat.includes("subrip") || normalizedFormat === "srt") return parseTimedText(input, "srt", options);
97
+ return input.replace(/\r\n?/g, "\n").split(/\n{2,}|\n/).map((text) => stripMarkup(text)).filter(Boolean).map((text, index) => ({ id: `text-${index}`, text, type: "text" }));
98
+ };
99
+ var inferFormat = (resource, contentType) => {
100
+ if (resource.format) return resource.format;
101
+ if (contentType) return contentType;
102
+ const extension = resource.src?.split(/[?#]/)[0]?.split(".").pop()?.toLowerCase();
103
+ return extension ?? "text/plain";
104
+ };
105
+ var loadTranscript = async (resource, options = {}) => {
106
+ if (resource.cues) return resource.cues;
107
+ if (!resource.src) return [];
108
+ if (resource.format?.includes("wordprocessingml") || /\.docx(?:$|[?#])/i.test(resource.src)) {
109
+ throw new Error("DOCX transcripts are not supported.");
110
+ }
111
+ const fetcher = options.fetch ?? globalThis.fetch;
112
+ if (!fetcher) throw new Error("No fetch implementation is available to load the transcript.");
113
+ const response = await fetcher(resource.src, options.requestInit);
114
+ if (!response.ok) throw new Error(`Unable to load transcript (${response.status} ${response.statusText}).`);
115
+ const contentType = response.headers.get("content-type");
116
+ const format = inferFormat(resource, contentType);
117
+ if (format.includes("json")) return parseTranscript(await response.json(), format, options);
118
+ return parseTranscript(await response.text(), format, options);
119
+ };
120
+
121
+ export {
122
+ parseTimedText,
123
+ parseTranscript,
124
+ loadTranscript
125
+ };
126
+ //# sourceMappingURL=chunk-3LLEY7MA.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 text = stripMarkup(block.slice(timestampIndex + 1).join('\\n'));\n if (!text) return undefined;\n return {\n id: `cue-${index}`,\n start,\n end,\n text,\n type: 'cue',\n };\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,17 @@
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
+ export {
15
+ playFromInteraction
16
+ };
17
+ //# sourceMappingURL=chunk-CDLYT3KB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/playback.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"],"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;","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-GAVG7CPU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}