@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.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/annotations.d.ts +12 -0
- package/dist/annotations.js +15 -0
- package/dist/annotations.js.map +1 -0
- package/dist/chapters.d.ts +12 -0
- package/dist/chapters.js +14 -0
- package/dist/chapters.js.map +1 -0
- package/dist/chunk-3LLEY7MA.js +126 -0
- package/dist/chunk-3LLEY7MA.js.map +1 -0
- package/dist/chunk-CDLYT3KB.js +17 -0
- package/dist/chunk-CDLYT3KB.js.map +1 -0
- package/dist/chunk-GAVG7CPU.js +1 -0
- package/dist/chunk-GAVG7CPU.js.map +1 -0
- package/dist/chunk-GX7UU6V4.js +547 -0
- package/dist/chunk-GX7UU6V4.js.map +1 -0
- package/dist/chunk-IFTSCRCU.js +83 -0
- package/dist/chunk-IFTSCRCU.js.map +1 -0
- package/dist/chunk-JXUHKKIX.js +94 -0
- package/dist/chunk-JXUHKKIX.js.map +1 -0
- package/dist/chunk-ML77YRJL.js +221 -0
- package/dist/chunk-ML77YRJL.js.map +1 -0
- package/dist/chunk-NTHMZZYJ.js +38 -0
- package/dist/chunk-NTHMZZYJ.js.map +1 -0
- package/dist/chunk-PZ6GV6ZJ.js +163 -0
- package/dist/chunk-PZ6GV6ZJ.js.map +1 -0
- package/dist/chunk-R4OUWZ3R.js +1038 -0
- package/dist/chunk-R4OUWZ3R.js.map +1 -0
- package/dist/chunk-YH6DS7J6.js +74 -0
- package/dist/chunk-YH6DS7J6.js.map +1 -0
- package/dist/controller-DLy38ais.d.ts +356 -0
- package/dist/controller-element-BOKM9v2S.d.ts +15 -0
- package/dist/core.d.ts +28 -0
- package/dist/core.js +28 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +28 -0
- package/dist/media.js +16 -0
- package/dist/media.js.map +1 -0
- package/dist/player.d.ts +27 -0
- package/dist/player.js +15 -0
- package/dist/player.js.map +1 -0
- package/dist/transcript-element.d.ts +12 -0
- package/dist/transcript-element.js +16 -0
- package/dist/transcript-element.js.map +1 -0
- package/docs/API.md +264 -0
- package/docs/FEATURE_PARITY.md +43 -0
- 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 };
|
package/dist/chapters.js
ADDED
|
@@ -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(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/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(/ /g, ' ')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/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":";;;;;AAQA,IAAM,cAAc,CAAC,UACnB,MACG,QAAQ,gBAAgB,IAAI,EAC5B,QAAQ,YAAY,EAAE,EACtB,QAAQ,WAAW,GAAG,EACtB,QAAQ,UAAU,GAAG,EACrB,QAAQ,SAAS,GAAG,EACpB,QAAQ,SAAS,GAAG,EACpB,QAAQ,WAAW,GAAG,EACtB,KAAK;AAEV,IAAM,eAAe,CACnB,OACA,OACA,UAC8B;AAC9B,QAAM,iBAAiB,MAAM,UAAU,CAAC,SAAS,KAAK,SAAS,KAAK,CAAC;AACrE,MAAI,iBAAiB,EAAG,QAAO;AAC/B,QAAM,YAAY,MAAM,cAAc;AACtC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,CAAC,WAAW,IAAI,SAAS,EAAE,IAAI,UAAU,MAAM,KAAK,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC,KAAK,EAAE;AAC3G,QAAM,QAAQ,UAAU,QAAQ,SAAS,QAAQ,KAAK,GAAG,IAAI,QAAQ;AACrE,QAAM,MAAM,UAAU,QAAQ,OAAO,QAAQ,KAAK,GAAG,IAAI,MAAM;AAC/D,MAAI,UAAU,UAAa,QAAQ,OAAW,QAAO;AACrD,QAAM,OAAO,YAAY,MAAM,MAAM,iBAAiB,CAAC,EAAE,KAAK,IAAI,CAAC;AACnE,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO;AAAA,IACL,IAAI,OAAO,KAAK;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,EACR;AACF;AAEO,IAAM,iBAAiB,CAC5B,OACA,QACA,UAAkC,CAAC,MACf;AACpB,QAAM,aAAa,MAAM,QAAQ,WAAW,EAAE,EAAE,QAAQ,UAAU,IAAI;AACtE,MAAI,WAAW,SAAS,CAAC,WAAW,UAAU,EAAE,WAAW,QAAQ,GAAG;AACpE,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,QAAM,QAAQ,WAAW,MAAM,IAAI;AACnC,QAAM,OAAwB,CAAC;AAC/B,MAAI,QAAkB,CAAC;AACvB,QAAM,QAAQ,MAAM;AAClB,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,QAAQ,MAAM,CAAC,GAAG,KAAK,KAAK;AAClC,QAAI,iBAAiB,KAAK,KAAK,GAAG;AAChC,UAAI,QAAQ,WAAW;AACrB,cAAM,OAAO,YAAY,MAAM,KAAK,IAAI,EAAE,QAAQ,aAAa,EAAE,CAAC;AAClE,YAAI,KAAM,MAAK,KAAK,EAAE,IAAI,QAAQ,KAAK,MAAM,IAAI,MAAM,MAAM,OAAO,CAAC;AAAA,MACvE;AAAA,IACF,WAAW,CAAC,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,KAAK,CAAC,GAAG;AACtD,YAAM,QAAQ,MAAM,YAAY;AAChC,UAAI,QAAQ,gBAAgB,SAAS,UAAU,YAAY,UAAU,WAAW,UAAU,UAAU;AAClG,aAAK,KAAK,EAAE,IAAI,YAAY,KAAK,MAAM,IAAI,MAAM,YAAY,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,WAAW,CAAC;AAAA,MACpG;AAAA,IACF,OAAO;AACL,YAAM,MAAM,aAAa,OAAO,KAAK,QAAQ,WAAW,KAAK;AAC7D,UAAI,IAAK,MAAK,KAAK,GAAG;AAAA,IACxB;AACA,YAAQ,CAAC;AAAA,EACX;AACA,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,KAAK,MAAM,GAAI,OAAM;AAAA,QACzB,OAAM,KAAK,IAAI;AAAA,EACtB;AACA,QAAM;AACN,SAAO;AACT;AAEA,IAAM,sBAAsB,CAAC,UAAoC;AAC/D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,OAAwB,CAAC;AAC/B,aAAW,SAAS,OAAO;AACzB,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AACzC,UAAM,SAAS;AACf,UAAM,QAAQ,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC,MAAM;AAClE,eAAW,aAAa,OAAO;AAC7B,UAAI,CAAC,aAAa,OAAO,cAAc,SAAU;AACjD,YAAM,OAAO;AACb,YAAM,YAAY,KAAK,QAAQ,KAAK,SAAS,KAAK;AAClD,UAAI,OAAO,cAAc,SAAU;AACnC,YAAM,MAAqB;AAAA,QACzB,IAAI,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK,OAAO,KAAK,MAAM;AAAA,QAC9D,MAAM,YAAY,SAAS;AAAA,QAC3B,MAAM;AAAA,MACR;AACA,YAAM,QAAQ,UAAW,KAAK,SAAS,KAAK,KAAqC;AACjF,YAAM,MAAM,UAAW,KAAK,OAAO,KAAK,IAAoC;AAC5E,UAAI,UAAU,OAAW,KAAI,QAAQ;AACrC,UAAI,QAAQ,OAAW,KAAI,MAAM;AACjC,UAAI,OAAO,OAAO,YAAY,SAAU,KAAI,UAAU,OAAO;AAAA,eACpD,OAAO,KAAK,YAAY,SAAU,KAAI,UAAU,KAAK;AAC9D,WAAK,KAAK,GAAG;AAAA,IACf;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,kBAAkB,CAC7B,OACA,QACA,UAAkC,CAAC,MACf;AACpB,QAAM,mBAAmB,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK;AAC/D,MAAI,iBAAiB,SAAS,MAAM,KAAM,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW;AACjG,UAAM,QAAQ,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAe;AACzE,WAAO,oBAAoB,KAAK;AAAA,EAClC;AACA,MAAI,OAAO,UAAU,SAAU,QAAO,CAAC;AACvC,MAAI,iBAAiB,SAAS,KAAK,KAAK,qBAAqB,MAAO,QAAO,eAAe,OAAO,OAAO,OAAO;AAC/G,MAAI,iBAAiB,SAAS,KAAK,KAAK,iBAAiB,SAAS,QAAQ,KAAK,qBAAqB,MAAO,QAAO,eAAe,OAAO,OAAO,OAAO;AACtJ,SAAO,MACJ,QAAQ,UAAU,IAAI,EACtB,MAAM,WAAW,EACjB,IAAI,CAAC,SAAS,YAAY,IAAI,CAAC,EAC/B,OAAO,OAAO,EACd,IAAI,CAAC,MAAM,WAAW,EAAE,IAAI,QAAQ,KAAK,IAAI,MAAM,MAAM,OAAgB,EAAE;AAChF;AAEA,IAAM,cAAc,CAAC,UAA8B,gBAAuC;AACxF,MAAI,SAAS,OAAQ,QAAO,SAAS;AACrC,MAAI,YAAa,QAAO;AACxB,QAAM,YAAY,SAAS,KAAK,MAAM,MAAM,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY;AAChF,SAAO,aAAa;AACtB;AAEO,IAAM,iBAAiB,OAC5B,UACA,UAAmG,CAAC,MACvE;AAC7B,MAAI,SAAS,KAAM,QAAO,SAAS;AACnC,MAAI,CAAC,SAAS,IAAK,QAAO,CAAC;AAC3B,MAAI,SAAS,QAAQ,SAAS,kBAAkB,KAAK,oBAAoB,KAAK,SAAS,GAAG,GAAG;AAC3F,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,QAAM,UAAU,QAAQ,SAAS,WAAW;AAC5C,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,8DAA8D;AAC5F,QAAM,WAAW,MAAM,QAAQ,SAAS,KAAK,QAAQ,WAAW;AAChE,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,8BAA8B,SAAS,MAAM,IAAI,SAAS,UAAU,IAAI;AAC1G,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc;AACvD,QAAM,SAAS,YAAY,UAAU,WAAW;AAChD,MAAI,OAAO,SAAS,MAAM,EAAG,QAAO,gBAAgB,MAAM,SAAS,KAAK,GAAG,QAAQ,OAAO;AAC1F,SAAO,gBAAgB,MAAM,SAAS,KAAK,GAAG,QAAQ,OAAO;AAC/D;","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":[]}
|