@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
@@ -0,0 +1,1038 @@
1
+ // src/types.ts
2
+ var defaultStrings = {
3
+ chapters: "Chapters",
4
+ transcript: "Transcript",
5
+ annotations: "Annotations",
6
+ metadata: "Metadata",
7
+ files: "Files",
8
+ rights: "Rights",
9
+ previous: "Previous",
10
+ next: "Next",
11
+ previousSection: "Previous section",
12
+ nextSection: "Next section",
13
+ autoAdvance: "Auto-advance",
14
+ mediaPlayer: "Media player",
15
+ mediaSource: "Media source",
16
+ mediaInformation: "Media information",
17
+ download: "Download",
18
+ downloadMediaFile: "Download media file",
19
+ searchTranscript: "Search transcript",
20
+ transcriptSearchMatches: "Transcript search matches",
21
+ searchMatchAt: "Search match at {time}",
22
+ resumePrompt: "Resume from {time}?",
23
+ resume: "Resume",
24
+ startOver: "Start over",
25
+ loadingManifest: "Loading IIIF manifest\u2026",
26
+ loadingTranscript: "Loading transcript\u2026",
27
+ unableToLoadPlayer: "Unable to load the player.",
28
+ unableToLoadMedia: "Unable to load media.",
29
+ noMediaLoaded: "No media loaded",
30
+ noPlayableMedia: "No playable audio or video source",
31
+ noManifest: "No manifest is loaded.",
32
+ noTranscript: "No transcript is available.",
33
+ noAnnotations: "No annotations are available.",
34
+ noMetadata: "No metadata is available.",
35
+ noChapters: "No structured navigation is available.",
36
+ noSupplementalFiles: "No supplemental files are available.",
37
+ autoSource: "Auto",
38
+ sourceNumber: "Source {number}",
39
+ transcriptNumber: "Transcript {number}",
40
+ sectionNumber: "Section {number}",
41
+ chapterNumber: "Chapter {number}",
42
+ untitledManifest: "Untitled manifest",
43
+ unableToLoadManifest: "Unable to load IIIF manifest ({status} {statusText}).",
44
+ expectedManifest: "Expected a IIIF Manifest, received {type}.",
45
+ manifestWithoutCanvases: "The IIIF Manifest does not contain any Canvases."
46
+ };
47
+ var defaultConfig = {
48
+ controls: {
49
+ seek: true,
50
+ volume: true,
51
+ captions: true,
52
+ playbackRate: true,
53
+ pictureInPicture: true,
54
+ fullscreen: true,
55
+ sourceSelector: true,
56
+ download: false,
57
+ skipSeconds: 10
58
+ },
59
+ panels: {
60
+ chapters: true,
61
+ transcript: true,
62
+ annotations: true,
63
+ metadata: true,
64
+ supplementalFiles: true
65
+ },
66
+ transcript: {
67
+ autoScroll: true,
68
+ search: true,
69
+ download: true,
70
+ showNotes: false,
71
+ showMetadata: false,
72
+ showSearchMarkers: true
73
+ },
74
+ resume: {
75
+ enabled: false,
76
+ maxItems: 200,
77
+ ttlDays: 30,
78
+ prompt: true
79
+ },
80
+ strings: defaultStrings,
81
+ preferredLanguages: ["en", "none"],
82
+ autoAdvance: false,
83
+ repeat: false,
84
+ activePanel: "chapters",
85
+ crossorigin: "anonymous",
86
+ preload: "metadata",
87
+ playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 2]
88
+ };
89
+
90
+ // src/utils.ts
91
+ var asObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
92
+ var asArray = (value) => {
93
+ if (value === null || value === void 0) return [];
94
+ return Array.isArray(value) ? value : [value];
95
+ };
96
+ var readId = (value) => {
97
+ if (typeof value === "string") return value;
98
+ const object = asObject(value);
99
+ const id = object?.id ?? object?.["@id"];
100
+ return typeof id === "string" ? id : void 0;
101
+ };
102
+ var readType = (value) => {
103
+ const object = asObject(value);
104
+ const type = object?.type ?? object?.["@type"];
105
+ return typeof type === "string" ? type : void 0;
106
+ };
107
+ var languageValues = (value) => {
108
+ if (typeof value === "string") return [{ value }];
109
+ if (Array.isArray(value)) {
110
+ return value.flatMap((item) => languageValues(item));
111
+ }
112
+ const object = asObject(value);
113
+ if (!object) return [];
114
+ if (typeof object["@value"] === "string") {
115
+ const result = { value: object["@value"] };
116
+ if (typeof object["@language"] === "string") result.language = object["@language"];
117
+ return [result];
118
+ }
119
+ return Object.entries(object).flatMap(
120
+ ([language, values]) => asArray(values).flatMap(
121
+ (item) => typeof item === "string" ? [{ language, value: item }] : languageValues(item).map((entry) => ({ ...entry, language: entry.language ?? language }))
122
+ )
123
+ );
124
+ };
125
+ var label = (value, preferredLanguages = ["en", "none"], fallback = "") => {
126
+ const values = languageValues(value);
127
+ for (const language of preferredLanguages) {
128
+ const match = values.find((item) => item.language === language);
129
+ if (match) return match.value;
130
+ }
131
+ return values[0]?.value ?? fallback;
132
+ };
133
+ var parseTime = (value) => {
134
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
135
+ if (!value) return void 0;
136
+ if (/^\d+(?:\.\d+)?$/.test(value)) return Number(value);
137
+ const parts = value.replace(",", ".").split(":").map(Number);
138
+ if (parts.some((part) => !Number.isFinite(part))) return void 0;
139
+ if (parts.length === 3) return (parts[0] ?? 0) * 3600 + (parts[1] ?? 0) * 60 + (parts[2] ?? 0);
140
+ if (parts.length === 2) return (parts[0] ?? 0) * 60 + (parts[1] ?? 0);
141
+ return void 0;
142
+ };
143
+ var parseTemporalFragment = (target) => {
144
+ let fragment;
145
+ if (typeof target === "string") fragment = target.split("#")[1];
146
+ const object = asObject(target);
147
+ if (!fragment && object) {
148
+ const selector = asObject(object.selector);
149
+ const value = selector?.value;
150
+ if (typeof value === "string") fragment = value.replace(/^#/, "");
151
+ if (selector?.type === "PointSelector" && typeof selector.t === "number") {
152
+ return { start: selector.t };
153
+ }
154
+ }
155
+ const time = fragment?.match(/(?:^|&)t=([^&]+)/)?.[1];
156
+ if (!time) return void 0;
157
+ const [startValue, endValue] = time.split(",");
158
+ const start = parseTime(startValue);
159
+ const end = parseTime(endValue);
160
+ if (start === void 0) return void 0;
161
+ return end === void 0 ? { start } : { start, end };
162
+ };
163
+ var withoutFragment = (value) => value.split("#")[0] ?? value;
164
+ var isHls = (src, format) => /(?:\.m3u8)(?:$|[?#])/i.test(src) || format === "application/vnd.apple.mpegurl" || format === "application/x-mpegURL";
165
+ var mediaKind = (type, format, src = "") => {
166
+ const haystack = `${type ?? ""} ${format ?? ""} ${src}`.toLowerCase();
167
+ if (/\baudio\b|\.mp3(?:$|[?#])|\.m4a(?:$|[?#])|\.wav(?:$|[?#])|\.aac(?:$|[?#])/.test(haystack)) return "audio";
168
+ if (/\bvideo\b|\.mp4(?:$|[?#])|\.webm(?:$|[?#])|\.m3u8(?:$|[?#])|mpegurl/.test(haystack)) return "video";
169
+ return void 0;
170
+ };
171
+ var formatTime = (seconds) => {
172
+ if (!Number.isFinite(seconds)) return "0:00";
173
+ const value = Math.max(0, Math.floor(seconds));
174
+ const hours = Math.floor(value / 3600);
175
+ const minutes = Math.floor(value % 3600 / 60);
176
+ const remaining = value % 60;
177
+ return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${String(remaining).padStart(2, "0")}` : `${minutes}:${String(remaining).padStart(2, "0")}`;
178
+ };
179
+ var resolveStrings = (strings2 = {}) => ({
180
+ ...defaultStrings,
181
+ ...strings2
182
+ });
183
+ var formatString = (template, values = {}) => template.replace(
184
+ /\{(\w+)\}/g,
185
+ (placeholder, name) => Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : placeholder
186
+ );
187
+ var mergeConfig = (base, update) => {
188
+ const result = { ...base };
189
+ for (const [key, value] of Object.entries(update)) {
190
+ if (value !== void 0 && asObject(value) && asObject(result[key])) {
191
+ result[key] = mergeConfig(
192
+ asObject(result[key]),
193
+ asObject(value)
194
+ );
195
+ } else if (value !== void 0) {
196
+ result[key] = value;
197
+ }
198
+ }
199
+ return result;
200
+ };
201
+ var escapeHtml = (value) => value.replace(/[&<>'"]/g, (character) => ({
202
+ "&": "&amp;",
203
+ "<": "&lt;",
204
+ ">": "&gt;",
205
+ "'": "&#039;",
206
+ '"': "&quot;"
207
+ })[character] ?? character);
208
+
209
+ // src/iiif/normalize.ts
210
+ var strings = (value) => asArray(value).flatMap((item) => typeof item === "string" ? [item] : []);
211
+ var number = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
212
+ var readFormat = (object) => typeof object.format === "string" ? object.format : void 0;
213
+ var metadata = (value, languages, ui) => asArray(value).flatMap((item) => {
214
+ const object = asObject(item);
215
+ if (!object) return [];
216
+ const values = languageValues(object.value);
217
+ if (values.length === 0) return [];
218
+ return [{ label: label(object.label, languages, ui.metadata), values }];
219
+ });
220
+ var downloads = (value, languages, ui) => asArray(value).flatMap((item) => {
221
+ const object = asObject(item);
222
+ const id = readId(item);
223
+ if (!object || !id) return [];
224
+ const result = {
225
+ id,
226
+ label: label(object.label, languages, id.split("/").pop() ?? ui.download)
227
+ };
228
+ const format = readFormat(object);
229
+ if (format) result.format = format;
230
+ const filename = typeof object.filename === "string" ? object.filename : void 0;
231
+ if (filename) result.filename = filename;
232
+ return [result];
233
+ });
234
+ var services = (value, languages) => asArray(value).flatMap((item) => {
235
+ const object = asObject(item);
236
+ const id = readId(item);
237
+ if (!object || !id) return [];
238
+ const profile = typeof object.profile === "string" ? object.profile : void 0;
239
+ const type = `${readType(object) ?? ""} ${profile ?? ""}`.toLowerCase();
240
+ let serviceType = "unknown";
241
+ if (type.includes("probe")) serviceType = "probe";
242
+ else if (type.includes("token")) serviceType = "token";
243
+ else if (type.includes("logout")) serviceType = "logout";
244
+ else if (type.includes("access") || type.includes("login") || type.includes("clickthrough") || type.includes("kiosk")) serviceType = "access";
245
+ const result = {
246
+ id,
247
+ serviceType,
248
+ services: services(object.service, languages)
249
+ };
250
+ if (profile) result.profile = profile;
251
+ const serviceLabel = label(object.label, languages);
252
+ if (serviceLabel) result.label = serviceLabel;
253
+ return [result];
254
+ });
255
+ var firstContentId = (canvasValue) => {
256
+ const canvas = asObject(canvasValue);
257
+ if (!canvas) return void 0;
258
+ const v3Page = asObject(asArray(canvas.items)[0]);
259
+ const v3Annotation = asObject(asArray(v3Page?.items)[0]);
260
+ const v3Body = asObject(asArray(v3Annotation?.body)[0]);
261
+ const v2Annotation = asObject(asArray(canvas.images)[0]);
262
+ const v2Body = asObject(v2Annotation?.resource);
263
+ return readId(v3Body) ?? readId(v2Body);
264
+ };
265
+ var bodyItems = (body) => {
266
+ const values = asArray(body).flatMap((item) => {
267
+ const object = asObject(item);
268
+ if (!object) return [];
269
+ const type = readType(object)?.toLowerCase();
270
+ if (type?.includes("choice") || Array.isArray(object.items) || Array.isArray(object.item)) {
271
+ return asArray(object.items ?? object.item ?? object.default).flatMap((choice) => {
272
+ const choiceObject = asObject(choice);
273
+ return choiceObject ? [choiceObject] : [];
274
+ });
275
+ }
276
+ return [object];
277
+ });
278
+ return values;
279
+ };
280
+ var sourceFromBody = (body, canvas, target, languages, index, ui, annotationTimeMode) => {
281
+ const originalId = readId(body);
282
+ if (!originalId) return void 0;
283
+ const format = readFormat(body);
284
+ const kind = mediaKind(readType(body), format, originalId);
285
+ if (!kind) return void 0;
286
+ const result = {
287
+ id: originalId,
288
+ src: withoutFragment(originalId),
289
+ kind,
290
+ isHls: isHls(originalId, format)
291
+ };
292
+ if (format) result.format = format;
293
+ const bodyLabel = label(body.label, languages, index === 0 ? ui.autoSource : formatString(ui.sourceNumber, { number: index + 1 }));
294
+ if (bodyLabel) result.label = bodyLabel;
295
+ const width = number(body.width) ?? number(canvas.width);
296
+ const height = number(body.height) ?? number(canvas.height);
297
+ const duration = number(body.duration) ?? number(canvas.duration);
298
+ if (width !== void 0) result.width = width;
299
+ if (height !== void 0) result.height = height;
300
+ if (duration !== void 0) result.duration = duration;
301
+ const quality = typeof body.quality === "string" ? body.quality : void 0;
302
+ if (quality) result.quality = quality;
303
+ const segment = parseTemporalFragment(originalId);
304
+ if (segment) result.segment = segment;
305
+ const canvasTarget = parseTemporalFragment(target);
306
+ if (canvasTarget) result.canvasTarget = canvasTarget;
307
+ const timeMode = body.timeMode ?? annotationTimeMode;
308
+ if (timeMode === "trim" || timeMode === "scale" || timeMode === "loop") result.timeMode = timeMode;
309
+ return result;
310
+ };
311
+ var textualBodies = (body) => bodyItems(body).filter((item) => {
312
+ const type = readType(item)?.toLowerCase();
313
+ return type?.includes("textualbody") || typeof item.value === "string";
314
+ });
315
+ var annotationMotivations = (annotation) => strings(annotation.motivation);
316
+ var annotationFrom = (value, canvasId, pageId, languages, index) => {
317
+ const annotation = asObject(value);
318
+ if (!annotation) return void 0;
319
+ const bodies = textualBodies(annotation.body ?? annotation.resource);
320
+ const text = bodies.flatMap((body) => typeof body.value === "string" ? [body.value] : languageValues(body.chars).map((entry) => entry.value)).join("\n");
321
+ if (!text) return void 0;
322
+ const motivation = annotationMotivations(annotation);
323
+ const purpose = bodies.flatMap((body) => strings(body.purpose));
324
+ const tags = bodies.filter((body) => strings(body.purpose).includes("tagging")).flatMap((body) => typeof body.value === "string" ? [body.value] : []);
325
+ const id = readId(annotation) ?? `${canvasId}/annotation/${index}`;
326
+ const result = {
327
+ id,
328
+ canvasId,
329
+ motivation,
330
+ purpose,
331
+ text,
332
+ tags
333
+ };
334
+ const annotationLabel = label(annotation.label, languages);
335
+ if (annotationLabel) result.label = annotationLabel;
336
+ const time = parseTemporalFragment(annotation.target ?? annotation.on);
337
+ if (time) result.time = time;
338
+ if (pageId) result.sourcePageId = pageId;
339
+ return result;
340
+ };
341
+ var classifySupplement = (body, annotation, canvasId, languages, index, ui) => {
342
+ const id = readId(body);
343
+ if (!id) return {};
344
+ const format = readFormat(body);
345
+ const bodyLabel = label(body.label, languages, formatString(ui.transcriptNumber, { number: index + 1 }));
346
+ const language = typeof body.language === "string" ? body.language : void 0;
347
+ const purpose = strings(body.purpose);
348
+ const lower = `${bodyLabel} ${id} ${purpose.join(" ")}`.toLowerCase();
349
+ const isTimedTrack = format === "text/vtt" || /\.vtt(?:$|[?#])/.test(id);
350
+ let kind = "subtitles";
351
+ if (lower.includes("caption")) kind = "captions";
352
+ if (lower.includes("description")) kind = "descriptions";
353
+ const result = {};
354
+ if (isTimedTrack) {
355
+ const track = { id, src: id, kind, label: bodyLabel };
356
+ if (language) track.language = language;
357
+ if (format) track.format = format;
358
+ if (lower.includes("forced")) track.forced = true;
359
+ result.track = track;
360
+ }
361
+ const transcript = {
362
+ id: `${canvasId}/transcript/${index}`,
363
+ src: id,
364
+ label: bodyLabel,
365
+ machineGenerated: /\(machine[ -]generated\)/i.test(bodyLabel)
366
+ };
367
+ if (format) transcript.format = format;
368
+ if (language) transcript.language = language;
369
+ result.transcript = transcript;
370
+ return result;
371
+ };
372
+ var annotationPageItems = (page) => asArray(page.items ?? page.resources);
373
+ var collectSupplementing = (pages, canvasId, languages, ui) => {
374
+ const tracks = [];
375
+ const transcripts = [];
376
+ const annotations = [];
377
+ let index = 0;
378
+ for (const page of pages) {
379
+ const pageId = readId(page);
380
+ for (const item of annotationPageItems(page)) {
381
+ const annotation = asObject(item);
382
+ if (!annotation) continue;
383
+ const motivations = annotationMotivations(annotation);
384
+ const normalizedAnnotation = annotationFrom(annotation, canvasId, pageId, languages, index);
385
+ if (normalizedAnnotation) annotations.push(normalizedAnnotation);
386
+ for (const body of bodyItems(annotation.body ?? annotation.resource)) {
387
+ const bodyId = readId(body);
388
+ if (bodyId && (motivations.includes("supplementing") || motivations.includes("transcribing") || motivations.length === 0)) {
389
+ const supplement = classifySupplement(body, annotation, canvasId, languages, index, ui);
390
+ if (supplement.track) tracks.push(supplement.track);
391
+ if (supplement.transcript) transcripts.push(supplement.transcript);
392
+ }
393
+ }
394
+ const inline = textualBodies(annotation.body ?? annotation.resource);
395
+ if (inline.length > 0 && (motivations.includes("supplementing") || motivations.includes("transcribing"))) {
396
+ const cues = inline.flatMap((body, bodyIndex) => {
397
+ const text = typeof body.value === "string" ? body.value : label(body.chars, languages);
398
+ if (!text) return [];
399
+ const cue = {
400
+ id: `${readId(annotation) ?? `${canvasId}/annotation/${index}`}/cue/${bodyIndex}`,
401
+ text,
402
+ type: "cue"
403
+ };
404
+ const time = parseTemporalFragment(annotation.target ?? annotation.on);
405
+ if (time) {
406
+ cue.start = time.start;
407
+ if (time.end !== void 0) cue.end = time.end;
408
+ }
409
+ return [cue];
410
+ });
411
+ if (cues.length > 0) {
412
+ transcripts.push({
413
+ id: `${pageId ?? canvasId}/inline-transcript`,
414
+ label: label(page.label, languages, ui.transcript),
415
+ machineGenerated: false,
416
+ cues
417
+ });
418
+ }
419
+ }
420
+ index += 1;
421
+ }
422
+ }
423
+ return { tracks, transcripts, annotations };
424
+ };
425
+ var paintingAnnotations = (canvas, version) => {
426
+ if (version === 2) {
427
+ return asArray(canvas.images).flatMap((item) => {
428
+ const object = asObject(item);
429
+ return object ? [object] : [];
430
+ });
431
+ }
432
+ return asArray(canvas.items).flatMap((page) => {
433
+ const object = asObject(page);
434
+ return object ? annotationPageItems(object).flatMap((item) => asObject(item) ? [asObject(item)] : []) : [];
435
+ });
436
+ };
437
+ var inlineSupplementPages = (canvas, version) => {
438
+ const raw = version === 2 ? canvas.otherContent : canvas.annotations;
439
+ return asArray(raw).flatMap((item) => {
440
+ const object = asObject(item);
441
+ return object && (Array.isArray(object.items) || Array.isArray(object.resources)) ? [object] : [];
442
+ });
443
+ };
444
+ var externalSupplementReferences = (canvas, version) => {
445
+ const raw = version === 2 ? canvas.otherContent : canvas.annotations;
446
+ return asArray(raw).flatMap((item) => {
447
+ const object = asObject(item);
448
+ if (object && (Array.isArray(object.items) || Array.isArray(object.resources))) return [];
449
+ const id = readId(item);
450
+ return id ? [id] : [];
451
+ });
452
+ };
453
+ var posterFrom = (canvas) => {
454
+ const placeholder = asObject(canvas.placeholderCanvas);
455
+ return firstContentId(placeholder);
456
+ };
457
+ var canvasFrom = (canvas, index, version, languages, ui) => {
458
+ const id = readId(canvas) ?? `urn:mango:canvas:${index}`;
459
+ const sources = paintingAnnotations(canvas, version).flatMap(
460
+ (annotation, annotationIndex) => bodyItems(annotation.body ?? annotation.resource).flatMap((body, bodyIndex) => {
461
+ const source = sourceFromBody(body, canvas, annotation.target ?? annotation.on, languages, annotationIndex + bodyIndex, ui, annotation.timeMode);
462
+ return source ? [source] : [];
463
+ })
464
+ );
465
+ const supplementing = collectSupplementing(inlineSupplementPages(canvas, version), id, languages, ui);
466
+ const result = {
467
+ id,
468
+ index,
469
+ label: label(canvas.label, languages, formatString(ui.sectionNumber, { number: index + 1 })),
470
+ behavior: strings(canvas.behavior ?? canvas.viewingHint),
471
+ sources,
472
+ tracks: supplementing.tracks,
473
+ transcripts: supplementing.transcripts,
474
+ annotations: supplementing.annotations,
475
+ rendering: downloads(canvas.rendering, languages, ui),
476
+ metadata: metadata(canvas.metadata, languages, ui),
477
+ authServices: services(canvas.service, languages)
478
+ };
479
+ const summary = label(canvas.summary ?? canvas.description, languages);
480
+ if (summary) result.summary = summary;
481
+ const duration = number(canvas.duration);
482
+ const width = number(canvas.width);
483
+ const height = number(canvas.height);
484
+ if (duration !== void 0) result.duration = duration;
485
+ if (width !== void 0) result.width = width;
486
+ if (height !== void 0) result.height = height;
487
+ const poster = posterFrom(canvas);
488
+ if (poster) result.poster = poster;
489
+ const accompanying = readId(canvas.accompanyingCanvas);
490
+ if (accompanying) result.accompanyingCanvas = accompanying;
491
+ const homepage = readId(asArray(canvas.homepage ?? canvas.related)[0]);
492
+ if (homepage) result.homepage = homepage;
493
+ return result;
494
+ };
495
+ var resolveRangeTarget = (value) => {
496
+ const id = readId(value) ?? readId(asObject(value)?.source);
497
+ if (!id) return {};
498
+ const result = { canvasId: withoutFragment(id) };
499
+ const time = parseTemporalFragment(id);
500
+ if (time) result.time = time;
501
+ return result;
502
+ };
503
+ var chaptersFrom = (manifest, version, languages, ui) => {
504
+ const rawStructures = asArray(manifest.structures).flatMap((item) => asObject(item) ? [asObject(item)] : []);
505
+ const byId = new Map(rawStructures.map((range) => [readId(range), range]));
506
+ const seen = /* @__PURE__ */ new Set();
507
+ const parseRange = (range, depth = 0) => {
508
+ if (depth > 50 || seen.has(range)) return void 0;
509
+ seen.add(range);
510
+ const rangeBehavior = strings(range.behavior ?? range.viewingHint);
511
+ if (rangeBehavior.includes("no-nav") || rangeBehavior.includes("thumbnail-nav")) return void 0;
512
+ const id = readId(range) ?? `urn:mango:range:${seen.size}`;
513
+ const members = version === 3 ? asArray(range.items) : asArray(range.members ?? range.ranges ?? range.canvases);
514
+ const children = [];
515
+ let target = {};
516
+ for (const member of members) {
517
+ const object = asObject(member);
518
+ const memberId = readId(member);
519
+ const memberType = readType(object)?.toLowerCase();
520
+ const referencedRange = memberId ? byId.get(memberId) : void 0;
521
+ if (referencedRange || memberType?.includes("range") || object && (object.items || object.ranges || object.members)) {
522
+ const child = parseRange(referencedRange ?? object, depth + 1);
523
+ if (child) children.push(child);
524
+ } else if (!target.canvasId && memberId) {
525
+ target = resolveRangeTarget(member);
526
+ }
527
+ }
528
+ const result = {
529
+ id,
530
+ label: label(range.label, languages, formatString(ui.chapterNumber, { number: seen.size })),
531
+ behavior: rangeBehavior,
532
+ children
533
+ };
534
+ if (target.canvasId) result.canvasId = target.canvasId;
535
+ if (target.time) result.time = target.time;
536
+ const summary = label(range.summary ?? range.description, languages);
537
+ if (summary) result.summary = summary;
538
+ const homepage = readId(asArray(range.homepage ?? range.related)[0]);
539
+ if (homepage) result.homepage = homepage;
540
+ return result;
541
+ };
542
+ return rawStructures.flatMap((range) => {
543
+ const chapter = parseRange(range);
544
+ return chapter ? [chapter] : [];
545
+ });
546
+ };
547
+ var startFrom = (manifest, version) => {
548
+ const value = version === 2 ? asObject(asArray(manifest.sequences)[0])?.startCanvas ?? manifest.startCanvas : manifest.start;
549
+ const id = readId(value) ?? readId(asObject(value)?.source);
550
+ if (!id) return void 0;
551
+ const result = { canvasId: withoutFragment(id) };
552
+ const time = parseTemporalFragment(value) ?? parseTemporalFragment(id);
553
+ if (time) result.time = time.start;
554
+ return result;
555
+ };
556
+ var manifestCanvases = (manifest, version) => {
557
+ const raw = version === 3 ? manifest.items : asObject(asArray(manifest.sequences)[0])?.canvases;
558
+ return asArray(raw).flatMap((item) => asObject(item) ? [asObject(item)] : []);
559
+ };
560
+ var detectVersion = (manifest) => {
561
+ const context = JSON.stringify(manifest["@context"] ?? "");
562
+ if (context.includes("/presentation/2/")) return 2;
563
+ if (Array.isArray(manifest.sequences) || readType(manifest)?.startsWith("sc:")) return 2;
564
+ return 3;
565
+ };
566
+ var normalizeManifest = async (input, options = {}) => {
567
+ const ui = resolveStrings(options.strings);
568
+ const fetcher = options.fetch ?? globalThis.fetch;
569
+ let source;
570
+ if (typeof input === "string") {
571
+ if (input.trim().startsWith("{")) {
572
+ source = JSON.parse(input);
573
+ } else {
574
+ if (!fetcher) throw new Error("No fetch implementation is available to load the IIIF manifest.");
575
+ const response = await fetcher(input, options.requestInit);
576
+ if (!response.ok) throw new Error(formatString(ui.unableToLoadManifest, {
577
+ status: response.status,
578
+ statusText: response.statusText
579
+ }));
580
+ source = await response.json();
581
+ }
582
+ } else {
583
+ source = input;
584
+ }
585
+ const type = readType(source)?.toLowerCase();
586
+ if (type && !type.includes("manifest")) throw new Error(formatString(ui.expectedManifest, {
587
+ type: readType(source) ?? ""
588
+ }));
589
+ const version = detectVersion(source);
590
+ const languages = options.preferredLanguages ?? ["en", "none"];
591
+ const canvases = manifestCanvases(source, version).map((canvas, index) => canvasFrom(canvas, index, version, languages, ui));
592
+ if (canvases.length === 0) throw new Error(ui.manifestWithoutCanvases);
593
+ if (options.loadExternalAnnotations !== false && fetcher) {
594
+ const rawCanvases = manifestCanvases(source, version);
595
+ await Promise.all(rawCanvases.map(async (canvas, index) => {
596
+ const target = canvases[index];
597
+ if (!target) return;
598
+ const references = externalSupplementReferences(canvas, version);
599
+ const loaded = await Promise.all(references.map(async (url) => {
600
+ try {
601
+ const response = await fetcher(url, options.requestInit);
602
+ if (!response.ok) return void 0;
603
+ return asObject(await response.json());
604
+ } catch {
605
+ return void 0;
606
+ }
607
+ }));
608
+ const parsed = collectSupplementing(loaded.filter((page) => Boolean(page)), target.id, languages, ui);
609
+ target.tracks.push(...parsed.tracks);
610
+ target.transcripts.push(...parsed.transcripts);
611
+ target.annotations.push(...parsed.annotations);
612
+ }));
613
+ }
614
+ const id = readId(source) ?? "urn:mango:manifest";
615
+ const result = {
616
+ id,
617
+ type: "Manifest",
618
+ version,
619
+ label: label(source.label, languages, ui.untitledManifest),
620
+ behavior: strings(source.behavior ?? source.viewingHint),
621
+ canvases,
622
+ chapters: chaptersFrom(source, version, languages, ui),
623
+ metadata: metadata(source.metadata, languages, ui),
624
+ rendering: downloads(source.rendering, languages, ui)
625
+ };
626
+ const summary = label(source.summary ?? source.description, languages);
627
+ if (summary) result.summary = summary;
628
+ const start = startFrom(source, version);
629
+ if (start) result.start = start;
630
+ if (typeof source.rights === "string") result.rights = source.rights;
631
+ const requiredStatement = metadata([source.requiredStatement ?? source.attribution], languages, ui)[0];
632
+ if (requiredStatement) result.requiredStatement = requiredStatement;
633
+ const homepage = readId(asArray(source.homepage ?? source.related)[0]);
634
+ if (homepage) result.homepage = homepage;
635
+ const logo = readId(asArray(source.logo ?? source.provider)[0]);
636
+ if (logo) result.logo = logo;
637
+ return result;
638
+ };
639
+
640
+ // src/controller.ts
641
+ var positionPrefix = "@mango-iiif/av:position:";
642
+ var AVPlayerController = class extends EventTarget {
643
+ #config;
644
+ #state = {
645
+ status: "idle",
646
+ canvasIndex: 0,
647
+ sourceIndex: 0,
648
+ currentTime: 0,
649
+ duration: 0,
650
+ volume: 1,
651
+ muted: false,
652
+ playbackRate: 1,
653
+ autoAdvance: false,
654
+ repeat: false
655
+ };
656
+ #media;
657
+ #lastTime = 0;
658
+ #timelineScale = 1;
659
+ #pendingTime;
660
+ #boundListeners = /* @__PURE__ */ new Map();
661
+ constructor(config = {}) {
662
+ super();
663
+ this.#config = mergeConfig(defaultConfig, config);
664
+ this.#state.autoAdvance = this.#config.autoAdvance;
665
+ this.#state.repeat = this.#config.repeat;
666
+ }
667
+ get config() {
668
+ return this.#config;
669
+ }
670
+ get state() {
671
+ return this.#state;
672
+ }
673
+ get manifest() {
674
+ return this.#state.manifest;
675
+ }
676
+ get canvas() {
677
+ return this.#state.manifest?.canvases[this.#state.canvasIndex];
678
+ }
679
+ get source() {
680
+ return this.canvas?.sources[this.#state.sourceIndex];
681
+ }
682
+ get media() {
683
+ return this.#media;
684
+ }
685
+ on(type, listener) {
686
+ const eventListener = listener;
687
+ this.addEventListener(type, eventListener);
688
+ return () => this.removeEventListener(type, eventListener);
689
+ }
690
+ configure(config) {
691
+ this.#config = mergeConfig(
692
+ this.#config,
693
+ config
694
+ );
695
+ if (config.autoAdvance !== void 0) this.#state.autoAdvance = config.autoAdvance;
696
+ if (config.repeat !== void 0) this.#state.repeat = config.repeat;
697
+ this.#emit("av-configchange", { config: this.#config });
698
+ this.#emitState();
699
+ }
700
+ async load(input) {
701
+ this.#state.status = "loading";
702
+ delete this.#state.error;
703
+ this.#emit("av-loadstart", { source: input });
704
+ this.#emitState();
705
+ try {
706
+ const normalizeOptions = {
707
+ preferredLanguages: this.#config.preferredLanguages,
708
+ strings: this.#config.strings
709
+ };
710
+ if (this.#config.fetch) normalizeOptions.fetch = this.#config.fetch;
711
+ if (this.#config.requestInit) normalizeOptions.requestInit = this.#config.requestInit;
712
+ const manifest = await normalizeManifest(input, normalizeOptions);
713
+ this.#state.manifest = manifest;
714
+ const requestedCanvas = this.#config.startCanvasId ?? manifest.start?.canvasId;
715
+ const canvasIndex = requestedCanvas ? Math.max(0, manifest.canvases.findIndex((canvas2) => canvas2.id === withoutFragment(requestedCanvas))) : 0;
716
+ this.#state.canvasIndex = canvasIndex;
717
+ this.#state.sourceIndex = 0;
718
+ this.#timelineScale = 1;
719
+ this.#state.currentTime = 0;
720
+ this.#state.duration = this.canvas?.duration ?? 0;
721
+ this.#state.autoAdvance = this.#config.autoAdvance || manifest.behavior.includes("auto-advance");
722
+ this.#state.repeat = this.#config.repeat || manifest.behavior.includes("repeat");
723
+ this.#state.status = "ready";
724
+ const initialTime = this.#config.startTime ?? manifest.start?.time ?? this.source?.canvasTarget?.start;
725
+ if (initialTime !== void 0) this.#pendingTime = initialTime;
726
+ this.#emit("av-ready", { manifest });
727
+ const canvas = this.canvas;
728
+ if (canvas) this.#emit("av-canvaschange", { canvas, canvasIndex });
729
+ this.#emitState();
730
+ return manifest;
731
+ } catch (cause) {
732
+ const error = cause instanceof Error ? cause : new Error(String(cause));
733
+ this.#state.status = "error";
734
+ this.#state.error = error;
735
+ this.#emit("av-error", { error });
736
+ this.#emitState();
737
+ throw error;
738
+ }
739
+ }
740
+ attachMedia(media) {
741
+ if (this.#media === media) return;
742
+ this.detachMedia();
743
+ this.#media = media;
744
+ const listeners = {
745
+ loadedmetadata: () => {
746
+ const segment = this.source?.segment;
747
+ const target = this.source?.canvasTarget;
748
+ if (this.source?.timeMode === "scale" && target?.end !== void 0 && Number.isFinite(media.duration)) {
749
+ const sourceDuration = segment?.end !== void 0 ? segment.end - segment.start : media.duration;
750
+ const targetDuration = target.end - target.start;
751
+ if (sourceDuration > 0 && targetDuration > 0) {
752
+ this.#timelineScale = sourceDuration / targetDuration;
753
+ media.playbackRate = this.#timelineScale;
754
+ }
755
+ }
756
+ const pending = this.#pendingTime ?? target?.start;
757
+ if (pending !== void 0) {
758
+ media.currentTime = this.#toMediaTime(pending);
759
+ this.#pendingTime = void 0;
760
+ }
761
+ this.#state.duration = this.canvas?.duration ?? target?.end ?? (Number.isFinite(media.duration) ? this.#toCanvasTime(media.duration) : 0);
762
+ this.#emitState();
763
+ },
764
+ play: () => {
765
+ this.#state.status = "playing";
766
+ this.#emit("av-play", { canvasId: this.canvas?.id ?? "", time: this.#toCanvasTime(media.currentTime) });
767
+ this.#emitState();
768
+ },
769
+ pause: () => {
770
+ if (!media.ended) this.#state.status = "paused";
771
+ this.#savePosition();
772
+ this.#emit("av-pause", { canvasId: this.canvas?.id ?? "", time: this.#toCanvasTime(media.currentTime) });
773
+ this.#emitState();
774
+ },
775
+ timeupdate: () => {
776
+ const time = this.#toCanvasTime(media.currentTime);
777
+ const duration = this.canvas?.duration ?? this.source?.canvasTarget?.end ?? (Number.isFinite(media.duration) ? this.#toCanvasTime(media.duration) : 0);
778
+ this.#state.currentTime = time;
779
+ this.#state.duration = duration;
780
+ this.#emit("av-timeupdate", { canvasId: this.canvas?.id ?? "", time, duration });
781
+ const end = this.source?.canvasTarget?.end ?? (this.source?.segment?.end !== void 0 ? this.#toCanvasTime(this.source.segment.end) : void 0);
782
+ if (end !== void 0 && time >= end) {
783
+ if (this.source?.timeMode === "loop") {
784
+ media.currentTime = this.source.segment?.start ?? 0;
785
+ } else {
786
+ media.pause();
787
+ void this.#advanceAfterEnd();
788
+ }
789
+ }
790
+ this.#lastTime = time;
791
+ this.#emitState();
792
+ },
793
+ seeking: () => {
794
+ this.#emit("av-seek", { canvasId: this.canvas?.id ?? "", from: this.#lastTime, to: this.#toCanvasTime(media.currentTime) });
795
+ },
796
+ volumechange: () => {
797
+ this.#state.volume = media.volume;
798
+ this.#state.muted = media.muted;
799
+ this.#emitState();
800
+ },
801
+ ratechange: () => {
802
+ this.#state.playbackRate = media.playbackRate;
803
+ this.#emitState();
804
+ },
805
+ ended: () => {
806
+ this.#state.status = "ended";
807
+ this.#emit("av-ended", { canvasId: this.canvas?.id ?? "" });
808
+ this.#clearPosition();
809
+ this.#emitState();
810
+ void this.#advanceAfterEnd();
811
+ },
812
+ error: () => {
813
+ const error = new Error(media.error?.message ?? "The media resource could not be played.");
814
+ this.#state.status = "error";
815
+ this.#state.error = error;
816
+ this.#emit("av-error", { error });
817
+ this.#emitState();
818
+ }
819
+ };
820
+ for (const [type, listener] of Object.entries(listeners)) {
821
+ media.addEventListener(type, listener);
822
+ this.#boundListeners.set(type, listener);
823
+ }
824
+ }
825
+ detachMedia() {
826
+ if (!this.#media) return;
827
+ for (const [type, listener] of this.#boundListeners) this.#media.removeEventListener(type, listener);
828
+ this.#boundListeners.clear();
829
+ this.#media = void 0;
830
+ }
831
+ async play() {
832
+ await this.#media?.play();
833
+ }
834
+ pause() {
835
+ this.#media?.pause();
836
+ }
837
+ stop() {
838
+ this.pause();
839
+ this.seekTo(this.source?.canvasTarget?.start ?? 0);
840
+ }
841
+ seekTo(time) {
842
+ const source = this.source;
843
+ const minimum = source?.canvasTarget?.start ?? 0;
844
+ const maximum = source?.canvasTarget?.end ?? this.#state.duration;
845
+ const target = maximum > 0 ? Math.min(Math.max(time, minimum), maximum) : Math.max(time, minimum);
846
+ if (this.#media && this.#media.readyState > 0) this.#media.currentTime = this.#toMediaTime(target);
847
+ else this.#pendingTime = target;
848
+ this.#state.currentTime = target;
849
+ this.#emitState();
850
+ }
851
+ seekBy(delta) {
852
+ this.seekTo(this.#state.currentTime + delta);
853
+ }
854
+ selectCanvas(indexOrId, options = {}) {
855
+ const canvases = this.#state.manifest?.canvases ?? [];
856
+ const index = typeof indexOrId === "number" ? indexOrId : canvases.findIndex((canvas2) => canvas2.id === withoutFragment(indexOrId));
857
+ if (index < 0 || index >= canvases.length) return void 0;
858
+ const wasPlaying = options.autoplay ?? this.#state.status === "playing";
859
+ this.#state.canvasIndex = index;
860
+ this.#state.sourceIndex = 0;
861
+ this.#timelineScale = 1;
862
+ this.#state.currentTime = options.time ?? canvases[index]?.sources[0]?.canvasTarget?.start ?? 0;
863
+ this.#state.duration = canvases[index]?.duration ?? 0;
864
+ this.#pendingTime = this.#state.currentTime;
865
+ const canvas = canvases[index];
866
+ if (canvas) this.#emit("av-canvaschange", { canvas, canvasIndex: index });
867
+ this.#emitState();
868
+ if (wasPlaying) queueMicrotask(() => void this.play());
869
+ return canvas;
870
+ }
871
+ next(options = {}) {
872
+ const count = this.#state.manifest?.canvases.length ?? 0;
873
+ if (this.#state.canvasIndex + 1 < count) return this.selectCanvas(this.#state.canvasIndex + 1, options);
874
+ if (this.#state.repeat && count > 0) return this.selectCanvas(0, options);
875
+ return void 0;
876
+ }
877
+ previous(options = {}) {
878
+ if (this.#state.canvasIndex > 0) return this.selectCanvas(this.#state.canvasIndex - 1, options);
879
+ return void 0;
880
+ }
881
+ selectSource(index) {
882
+ const sources = this.canvas?.sources ?? [];
883
+ const source = sources[index];
884
+ if (!source) return void 0;
885
+ const wasPlaying = this.#state.status === "playing";
886
+ const currentTime = this.#state.currentTime;
887
+ this.#state.sourceIndex = index;
888
+ this.#timelineScale = 1;
889
+ this.#pendingTime = currentTime;
890
+ this.#emit("av-sourcechange", { source, sourceIndex: index });
891
+ this.#emitState();
892
+ if (wasPlaying) queueMicrotask(() => void this.play());
893
+ return source;
894
+ }
895
+ async createAnnotation(draft) {
896
+ const canvas = this.canvas;
897
+ if (!canvas) throw new Error("A Canvas must be active before creating an annotation.");
898
+ const created = this.#config.annotationAdapter?.create ? await this.#config.annotationAdapter.create(canvas, draft) : this.#annotationFromDraft(canvas, draft);
899
+ canvas.annotations.push(created);
900
+ this.#emit("av-annotationschange", { canvasId: canvas.id, annotations: [...canvas.annotations] });
901
+ this.#emitState();
902
+ return created;
903
+ }
904
+ async updateAnnotation(id, update) {
905
+ const canvas = this.canvas;
906
+ if (!canvas) throw new Error("A Canvas must be active before updating an annotation.");
907
+ const index = canvas.annotations.findIndex((annotation) => annotation.id === id);
908
+ const current = canvas.annotations[index];
909
+ if (!current) throw new Error(`Annotation ${id} was not found on the active Canvas.`);
910
+ const candidate = {
911
+ ...current,
912
+ ...update,
913
+ motivation: update.motivation ?? current.motivation,
914
+ purpose: update.purpose ?? current.purpose,
915
+ tags: update.tags ?? current.tags
916
+ };
917
+ const saved = this.#config.annotationAdapter?.update ? await this.#config.annotationAdapter.update(canvas, candidate) : candidate;
918
+ canvas.annotations[index] = saved;
919
+ this.#emit("av-annotationschange", { canvasId: canvas.id, annotations: [...canvas.annotations] });
920
+ this.#emitState();
921
+ return saved;
922
+ }
923
+ async removeAnnotation(id) {
924
+ const canvas = this.canvas;
925
+ if (!canvas) throw new Error("A Canvas must be active before removing an annotation.");
926
+ const index = canvas.annotations.findIndex((annotation) => annotation.id === id);
927
+ const current = canvas.annotations[index];
928
+ if (!current) throw new Error(`Annotation ${id} was not found on the active Canvas.`);
929
+ if (this.#config.annotationAdapter?.remove) await this.#config.annotationAdapter.remove(canvas, current);
930
+ canvas.annotations.splice(index, 1);
931
+ this.#emit("av-annotationschange", { canvasId: canvas.id, annotations: [...canvas.annotations] });
932
+ this.#emitState();
933
+ }
934
+ setAutoAdvance(enabled) {
935
+ this.#state.autoAdvance = enabled;
936
+ this.#emitState();
937
+ }
938
+ setRepeat(enabled) {
939
+ this.#state.repeat = enabled;
940
+ this.#emitState();
941
+ }
942
+ getSavedPosition() {
943
+ if (!this.#config.resume.enabled || !this.canvas || typeof localStorage === "undefined") return void 0;
944
+ const raw = localStorage.getItem(`${positionPrefix}${this.#state.manifest?.id}:${this.canvas.id}`);
945
+ if (!raw) return void 0;
946
+ try {
947
+ const saved = JSON.parse(raw);
948
+ const ttl = (this.#config.resume.ttlDays ?? 30) * 864e5;
949
+ if (Date.now() - saved.savedAt > ttl) {
950
+ this.#clearPosition();
951
+ return void 0;
952
+ }
953
+ return saved.time;
954
+ } catch {
955
+ return void 0;
956
+ }
957
+ }
958
+ destroy() {
959
+ this.#savePosition();
960
+ this.detachMedia();
961
+ }
962
+ async resolvedSourceUrl() {
963
+ const source = this.source;
964
+ if (!source) return void 0;
965
+ if (!this.#config.resolveRequest) return source.src;
966
+ const context = {};
967
+ if (this.manifest) context.manifest = this.manifest;
968
+ if (this.canvas) context.canvas = this.canvas;
969
+ return await this.#config.resolveRequest(source.src, context);
970
+ }
971
+ async #advanceAfterEnd() {
972
+ if (!this.#state.autoAdvance) return;
973
+ const next = this.next({ autoplay: true });
974
+ if (!next) this.#state.status = "ended";
975
+ }
976
+ #savePosition() {
977
+ if (!this.#config.resume.enabled || !this.canvas || typeof localStorage === "undefined") return;
978
+ const time = this.#state.currentTime;
979
+ if (time <= 0 || this.#state.duration > 0 && time >= this.#state.duration - 2) return;
980
+ const key = `${positionPrefix}${this.#state.manifest?.id}:${this.canvas.id}`;
981
+ localStorage.setItem(key, JSON.stringify({ time, savedAt: Date.now() }));
982
+ const keys = Object.keys(localStorage).filter((item) => item.startsWith(positionPrefix));
983
+ const maxItems = this.#config.resume.maxItems ?? 200;
984
+ if (keys.length > maxItems) {
985
+ const oldest = keys.map((item) => ({ item, value: JSON.parse(localStorage.getItem(item) ?? "{}") })).sort((a, b) => (a.value.savedAt ?? 0) - (b.value.savedAt ?? 0))[0];
986
+ if (oldest) localStorage.removeItem(oldest.item);
987
+ }
988
+ }
989
+ #clearPosition() {
990
+ if (!this.canvas || typeof localStorage === "undefined") return;
991
+ localStorage.removeItem(`${positionPrefix}${this.#state.manifest?.id}:${this.canvas.id}`);
992
+ }
993
+ #emit(type, detail) {
994
+ this.dispatchEvent(new CustomEvent(type, { detail, bubbles: false, composed: false }));
995
+ }
996
+ #emitState() {
997
+ this.#emit("av-statechange", { ...this.#state });
998
+ }
999
+ #annotationFromDraft(canvas, draft) {
1000
+ const annotation = {
1001
+ id: `${canvas.id}/annotation/${crypto.randomUUID?.() ?? Date.now()}`,
1002
+ canvasId: canvas.id,
1003
+ motivation: draft.motivation ?? ["commenting"],
1004
+ purpose: draft.purpose ?? ["commenting"],
1005
+ text: draft.text,
1006
+ tags: draft.tags ?? []
1007
+ };
1008
+ if (draft.label) annotation.label = draft.label;
1009
+ if (draft.time) annotation.time = draft.time;
1010
+ return annotation;
1011
+ }
1012
+ #toCanvasTime(mediaTime) {
1013
+ const sourceStart = this.source?.segment?.start ?? 0;
1014
+ const targetStart = this.source?.canvasTarget?.start ?? 0;
1015
+ const scale = this.source?.timeMode === "scale" ? this.#timelineScale : 1;
1016
+ return targetStart + (mediaTime - sourceStart) / scale;
1017
+ }
1018
+ #toMediaTime(canvasTime) {
1019
+ const sourceStart = this.source?.segment?.start ?? 0;
1020
+ const targetStart = this.source?.canvasTarget?.start ?? 0;
1021
+ const scale = this.source?.timeMode === "scale" ? this.#timelineScale : 1;
1022
+ return sourceStart + (canvasTime - targetStart) * scale;
1023
+ }
1024
+ };
1025
+
1026
+ export {
1027
+ defaultStrings,
1028
+ defaultConfig,
1029
+ parseTime,
1030
+ parseTemporalFragment,
1031
+ formatTime,
1032
+ resolveStrings,
1033
+ formatString,
1034
+ escapeHtml,
1035
+ normalizeManifest,
1036
+ AVPlayerController
1037
+ };
1038
+ //# sourceMappingURL=chunk-R4OUWZ3R.js.map