vidply 1.0.28 → 1.0.30

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 (75) hide show
  1. package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js +266 -0
  2. package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js.map +7 -0
  3. package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js +12 -0
  4. package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js.map +7 -0
  5. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
  6. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
  7. package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
  8. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
  9. package/dist/dev/vidply.chunk-W2LSBD6Y.js +251 -0
  10. package/dist/dev/vidply.chunk-W2LSBD6Y.js.map +7 -0
  11. package/dist/dev/vidply.esm.js +1880 -258
  12. package/dist/dev/vidply.esm.js.map +4 -4
  13. package/dist/legacy/vidply.js +2056 -365
  14. package/dist/legacy/vidply.js.map +4 -4
  15. package/dist/legacy/vidply.min.js +1 -1
  16. package/dist/legacy/vidply.min.meta.json +111 -25
  17. package/dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js +6 -0
  18. package/dist/prod/vidply.HTML5Renderer-KKW3OLHM.min.js +6 -0
  19. package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
  20. package/dist/prod/vidply.chunk-34RH2THY.min.js +6 -0
  21. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
  22. package/dist/prod/vidply.esm.min.js +8 -8
  23. package/dist/vidply.css +20 -1
  24. package/dist/vidply.esm.min.meta.json +120 -34
  25. package/dist/vidply.min.css +1 -1
  26. package/package.json +2 -2
  27. package/src/controls/ControlBar.js +182 -10
  28. package/src/controls/TranscriptManager.js +7 -7
  29. package/src/core/AudioDescriptionManager.js +701 -0
  30. package/src/core/Player.js +203 -256
  31. package/src/core/SignLanguageManager.js +1134 -0
  32. package/src/renderers/HTML5Renderer.js +7 -0
  33. package/src/styles/vidply.css +20 -1
  34. package/src/utils/DOMUtils.js +153 -114
  35. package/src/utils/MenuFactory.js +374 -0
  36. package/src/utils/VideoFrameCapture.js +110 -0
  37. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
  38. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
  39. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
  40. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
  41. package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
  42. package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
  43. package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
  44. package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
  45. package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
  46. package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
  47. package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
  48. package/dist/dev/vidply.de-THBIMP4S.js +0 -180
  49. package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
  50. package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
  51. package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
  52. package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
  53. package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
  54. package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
  55. package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
  56. package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
  57. package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
  58. package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
  59. package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
  60. package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
  61. package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
  62. package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
  63. package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
  64. package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
  65. package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
  66. package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
  67. package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
  68. package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
  69. package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
  70. package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
  71. package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
  72. package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
  73. package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
  74. package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
  75. package/dist/prod/vidply.ja-QTVU5C25.min.js +0 -6
@@ -1,1744 +0,0 @@
1
- /*!
2
- * Universal, Accessible Video Player
3
- * (c) 2025 Matthias Peltzer
4
- * Released under GPL-2.0-or-later License
5
- */
6
- import {
7
- DOMUtils,
8
- DraggableResizable,
9
- StorageManager,
10
- TimeUtils,
11
- attachMenuKeyboardNavigation,
12
- createIconElement,
13
- createLabeledSelect,
14
- createMenuItem,
15
- focusElement,
16
- i18n,
17
- preventDragOnElement
18
- } from "./vidply.chunk-5663PYKK.js";
19
-
20
- // src/controls/TranscriptManager.js
21
- var TranscriptManager = class {
22
- constructor(player) {
23
- this.player = player;
24
- this.transcriptWindow = null;
25
- this.transcriptEntries = [];
26
- this.metadataCues = [];
27
- this.currentActiveEntry = null;
28
- this.isVisible = false;
29
- this.storage = new StorageManager("vidply");
30
- this.draggableResizable = null;
31
- this.settingsMenuVisible = false;
32
- this.settingsMenu = null;
33
- this.settingsButton = null;
34
- this.settingsMenuJustOpened = false;
35
- this.resizeOptionButton = null;
36
- this.resizeOptionText = null;
37
- this.dragOptionButton = null;
38
- this.dragOptionText = null;
39
- this.resizeModeIndicator = null;
40
- this.resizeModeIndicatorTimeout = null;
41
- this.transcriptResizeHandles = [];
42
- this.liveRegion = null;
43
- this.styleDialog = null;
44
- this.styleDialogVisible = false;
45
- this.styleDialogJustOpened = false;
46
- this.languageSelector = null;
47
- this.languageLabel = null;
48
- this.currentTranscriptLanguage = null;
49
- this.availableTranscriptLanguages = [];
50
- this.languageSelectorHandler = null;
51
- const savedPreferences = this.storage.getTranscriptPreferences();
52
- this.autoscrollEnabled = savedPreferences?.autoscroll !== void 0 ? savedPreferences.autoscroll : true;
53
- this.showTimestamps = savedPreferences?.showTimestamps !== void 0 ? savedPreferences.showTimestamps : false;
54
- this.transcriptStyle = {
55
- fontSize: savedPreferences?.fontSize || this.player.options.transcriptFontSize || "100%",
56
- fontFamily: savedPreferences?.fontFamily || this.player.options.transcriptFontFamily || "sans-serif",
57
- color: savedPreferences?.color || this.player.options.transcriptColor || "#ffffff",
58
- backgroundColor: savedPreferences?.backgroundColor || this.player.options.transcriptBackgroundColor || "#1e1e1e",
59
- opacity: savedPreferences?.opacity ?? this.player.options.transcriptOpacity ?? 0.98
60
- };
61
- this.handlers = {
62
- timeupdate: () => this.updateActiveEntry(),
63
- audiodescriptionenabled: () => {
64
- if (this.isVisible) {
65
- this.loadTranscriptData();
66
- }
67
- },
68
- audiodescriptiondisabled: () => {
69
- if (this.isVisible) {
70
- this.loadTranscriptData();
71
- }
72
- },
73
- resize: null,
74
- settingsClick: null,
75
- settingsKeydown: null,
76
- documentClick: null,
77
- styleDialogKeydown: null
78
- };
79
- this.timeouts = /* @__PURE__ */ new Set();
80
- this.init();
81
- }
82
- init() {
83
- this.setupMetadataHandlingOnLoad();
84
- this.player.on("timeupdate", this.handlers.timeupdate);
85
- this.player.on("audiodescriptionenabled", this.handlers.audiodescriptionenabled);
86
- this.player.on("audiodescriptiondisabled", this.handlers.audiodescriptiondisabled);
87
- this.player.on("fullscreenchange", () => {
88
- if (this.isVisible) {
89
- const isMobile = window.innerWidth < 768;
90
- if (isMobile) {
91
- this.setupDragAndDrop();
92
- }
93
- if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
94
- this.setManagedTimeout(() => this.positionTranscript(), 100);
95
- }
96
- }
97
- });
98
- }
99
- /**
100
- * Toggle transcript window visibility
101
- */
102
- toggleTranscript() {
103
- if (this.isVisible) {
104
- this.hideTranscript();
105
- } else {
106
- this.showTranscript();
107
- }
108
- }
109
- /**
110
- * Show transcript window
111
- */
112
- showTranscript() {
113
- if (this.transcriptWindow) {
114
- this.transcriptWindow.style.display = "flex";
115
- this.isVisible = true;
116
- if (this.player.controlBar && typeof this.player.controlBar.updateTranscriptButton === "function") {
117
- this.player.controlBar.updateTranscriptButton();
118
- }
119
- focusElement(this.settingsButton, { delay: 150 });
120
- return;
121
- }
122
- this.createTranscriptWindow();
123
- this.loadTranscriptData();
124
- if (this.transcriptWindow) {
125
- this.transcriptWindow.style.display = "flex";
126
- if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
127
- this.setManagedTimeout(() => this.positionTranscript(), 0);
128
- }
129
- focusElement(this.settingsButton, { delay: 150 });
130
- }
131
- this.isVisible = true;
132
- }
133
- /**
134
- * Hide transcript window
135
- */
136
- hideTranscript({ focusButton = false } = {}) {
137
- if (this.transcriptWindow) {
138
- this.transcriptWindow.style.display = "none";
139
- this.isVisible = false;
140
- }
141
- if (this.draggableResizable && this.draggableResizable.pointerResizeMode) {
142
- this.draggableResizable.disablePointerResizeMode();
143
- this.updateResizeOptionState();
144
- }
145
- this.hideResizeModeIndicator();
146
- this.announceLive("");
147
- if (this.player.controlBar && typeof this.player.controlBar.updateTranscriptButton === "function") {
148
- this.player.controlBar.updateTranscriptButton();
149
- }
150
- if (focusButton) {
151
- const transcriptButton = this.player.controlBar?.controls?.transcript;
152
- if (transcriptButton && typeof transcriptButton.focus === "function") {
153
- transcriptButton.focus({ preventScroll: true });
154
- }
155
- }
156
- }
157
- /**
158
- * Create the transcript window UI
159
- */
160
- createTranscriptWindow() {
161
- this.transcriptWindow = DOMUtils.createElement("div", {
162
- className: `${this.player.options.classPrefix}-transcript-window`,
163
- attributes: {
164
- "role": "dialog",
165
- "aria-label": "Video Transcript",
166
- "tabindex": "-1"
167
- }
168
- });
169
- this.transcriptHeader = DOMUtils.createElement("div", {
170
- className: `${this.player.options.classPrefix}-transcript-header`,
171
- attributes: {
172
- "tabindex": "0"
173
- }
174
- });
175
- this.headerLeft = DOMUtils.createElement("div", {
176
- className: `${this.player.options.classPrefix}-transcript-header-left`
177
- });
178
- const settingsAriaLabel = i18n.t("transcript.settingsMenu");
179
- this.settingsButton = DOMUtils.createElement("button", {
180
- className: `${this.player.options.classPrefix}-transcript-settings`,
181
- attributes: {
182
- "type": "button",
183
- "aria-label": settingsAriaLabel,
184
- "aria-expanded": "false"
185
- }
186
- });
187
- this.settingsButton.appendChild(createIconElement("settings"));
188
- DOMUtils.attachTooltip(this.settingsButton, settingsAriaLabel, this.player.options.classPrefix);
189
- this.handlers.settingsClick = (e) => {
190
- e.preventDefault();
191
- e.stopPropagation();
192
- if (this.settingsMenuVisible) {
193
- this.hideSettingsMenu();
194
- } else {
195
- this.showSettingsMenu();
196
- }
197
- };
198
- this.settingsButton.addEventListener("click", this.handlers.settingsClick);
199
- this.handlers.settingsKeydown = (e) => {
200
- if (e.key === "d" || e.key === "D") {
201
- e.preventDefault();
202
- e.stopPropagation();
203
- this.toggleKeyboardDragMode();
204
- } else if (e.key === "r" || e.key === "R") {
205
- e.preventDefault();
206
- e.stopPropagation();
207
- this.toggleResizeMode();
208
- } else if (e.key === "Escape" && this.settingsMenuVisible) {
209
- e.preventDefault();
210
- e.stopPropagation();
211
- this.hideSettingsMenu();
212
- }
213
- };
214
- this.settingsButton.addEventListener("keydown", this.handlers.settingsKeydown);
215
- const title = DOMUtils.createElement("h3", {
216
- textContent: `${i18n.t("transcript.title")}. ${i18n.t("transcript.dragResizePrompt")}`
217
- });
218
- const autoscrollId = `${this.player.options.classPrefix}-transcript-autoscroll-${Date.now()}`;
219
- const autoscrollLabel = DOMUtils.createElement("label", {
220
- className: `${this.player.options.classPrefix}-transcript-autoscroll-label`,
221
- attributes: {
222
- "for": autoscrollId
223
- }
224
- });
225
- this.autoscrollCheckbox = DOMUtils.createElement("input", {
226
- attributes: {
227
- "id": autoscrollId,
228
- "type": "checkbox"
229
- }
230
- });
231
- if (this.autoscrollEnabled) {
232
- this.autoscrollCheckbox.checked = true;
233
- }
234
- const autoscrollText = DOMUtils.createElement("span", {
235
- textContent: i18n.t("transcript.autoscroll"),
236
- className: `${this.player.options.classPrefix}-transcript-autoscroll-text`
237
- });
238
- autoscrollLabel.appendChild(this.autoscrollCheckbox);
239
- autoscrollLabel.appendChild(autoscrollText);
240
- this.autoscrollCheckbox.addEventListener("change", (e) => {
241
- this.autoscrollEnabled = e.target.checked;
242
- this.saveAutoscrollPreference();
243
- });
244
- this.transcriptHeader.appendChild(title);
245
- this.headerLeft.appendChild(this.settingsButton);
246
- this.headerLeft.appendChild(autoscrollLabel);
247
- const selectId = `${this.player.options.classPrefix}-transcript-language-select-${Date.now()}`;
248
- const { label: languageLabel, select: languageSelector } = createLabeledSelect({
249
- classPrefix: this.player.options.classPrefix,
250
- labelClass: `${this.player.options.classPrefix}-transcript-language-label`,
251
- selectClass: `${this.player.options.classPrefix}-transcript-language-select`,
252
- labelText: "settings.language",
253
- selectId,
254
- hidden: false
255
- // Don't hide individual elements, we'll hide the wrapper instead
256
- });
257
- this.languageLabel = languageLabel;
258
- this.languageSelector = languageSelector;
259
- const languageSelectorWrapper = DOMUtils.createElement("div", {
260
- className: `${this.player.options.classPrefix}-transcript-language-wrapper`,
261
- attributes: {
262
- "style": "display: none;"
263
- // Hidden until we detect multiple languages
264
- }
265
- });
266
- languageSelectorWrapper.appendChild(this.languageLabel);
267
- languageSelectorWrapper.appendChild(this.languageSelector);
268
- this.languageSelectorWrapper = languageSelectorWrapper;
269
- preventDragOnElement(languageSelectorWrapper);
270
- this.headerLeft.appendChild(languageSelectorWrapper);
271
- const closeAriaLabel = i18n.t("transcript.close");
272
- const closeButton = DOMUtils.createElement("button", {
273
- className: `${this.player.options.classPrefix}-transcript-close`,
274
- attributes: {
275
- "type": "button",
276
- "aria-label": closeAriaLabel
277
- }
278
- });
279
- closeButton.appendChild(createIconElement("close"));
280
- DOMUtils.attachTooltip(closeButton, closeAriaLabel, this.player.options.classPrefix);
281
- closeButton.addEventListener("click", () => this.hideTranscript({ focusButton: true }));
282
- this.transcriptHeader.appendChild(this.headerLeft);
283
- this.transcriptHeader.appendChild(closeButton);
284
- this.transcriptContent = DOMUtils.createElement("div", {
285
- className: `${this.player.options.classPrefix}-transcript-content`
286
- });
287
- this.transcriptWindow.appendChild(this.transcriptHeader);
288
- this.transcriptWindow.appendChild(this.transcriptContent);
289
- this.createResizeHandles();
290
- this.liveRegion = DOMUtils.createElement("div", {
291
- className: "vidply-sr-only",
292
- attributes: {
293
- "aria-live": "polite",
294
- "aria-atomic": "true"
295
- }
296
- });
297
- this.transcriptWindow.appendChild(this.liveRegion);
298
- this.player.container.appendChild(this.transcriptWindow);
299
- this.setupDragAndDrop();
300
- if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
301
- this.positionTranscript();
302
- }
303
- this.handlers.documentClick = (e) => {
304
- if (this.settingsMenuJustOpened) {
305
- return;
306
- }
307
- if (this.styleDialogJustOpened) {
308
- return;
309
- }
310
- if (this.settingsButton && this.settingsButton.contains(e.target)) {
311
- return;
312
- }
313
- if (this.settingsMenu && this.settingsMenu.contains(e.target)) {
314
- return;
315
- }
316
- if (this.settingsMenuVisible) {
317
- this.hideSettingsMenu();
318
- }
319
- if (this.styleDialogVisible && this.styleDialog && !this.styleDialog.contains(e.target)) {
320
- this.hideStyleDialog();
321
- }
322
- };
323
- this.documentClickHandlerAdded = false;
324
- let resizeTimeout;
325
- this.handlers.resize = () => {
326
- if (resizeTimeout) {
327
- this.clearManagedTimeout(resizeTimeout);
328
- }
329
- resizeTimeout = this.setManagedTimeout(() => {
330
- if (!this.draggableResizable || !this.draggableResizable.manuallyPositioned) {
331
- this.positionTranscript();
332
- }
333
- }, 100);
334
- };
335
- window.addEventListener("resize", this.handlers.resize);
336
- }
337
- createResizeHandles() {
338
- if (!this.transcriptWindow) return;
339
- const directions = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
340
- this.transcriptResizeHandles = directions.map((direction) => {
341
- const handle = DOMUtils.createElement("div", {
342
- className: `${this.player.options.classPrefix}-transcript-resize-handle ${this.player.options.classPrefix}-transcript-resize-${direction}`,
343
- attributes: {
344
- "data-direction": direction,
345
- "data-vidply-managed-resize": "true",
346
- "aria-hidden": "true"
347
- }
348
- });
349
- handle.style.display = "none";
350
- this.transcriptWindow.appendChild(handle);
351
- return handle;
352
- });
353
- }
354
- /**
355
- * Position transcript window next to video
356
- */
357
- positionTranscript() {
358
- if (!this.transcriptWindow || !this.player.videoWrapper || !this.isVisible) return;
359
- if (this.draggableResizable && this.draggableResizable.manuallyPositioned) {
360
- return;
361
- }
362
- const isMobile = window.innerWidth < 768;
363
- const videoRect = this.player.videoWrapper.getBoundingClientRect();
364
- const isFullscreen = this.player.state.fullscreen;
365
- if (isMobile && !isFullscreen) {
366
- this.transcriptWindow.style.position = "relative";
367
- this.transcriptWindow.style.left = "0";
368
- this.transcriptWindow.style.right = "0";
369
- this.transcriptWindow.style.bottom = "auto";
370
- this.transcriptWindow.style.top = "auto";
371
- this.transcriptWindow.style.width = "100%";
372
- this.transcriptWindow.style.maxWidth = "100%";
373
- this.transcriptWindow.style.maxHeight = "400px";
374
- this.transcriptWindow.style.height = "auto";
375
- this.transcriptWindow.style.borderRadius = "0";
376
- this.transcriptWindow.style.transform = "none";
377
- this.transcriptWindow.style.border = "none";
378
- this.transcriptWindow.style.borderTop = "1px solid var(--vidply-border-light)";
379
- this.transcriptWindow.style.removeProperty("border-right");
380
- this.transcriptWindow.style.removeProperty("border-bottom");
381
- this.transcriptWindow.style.removeProperty("border-left");
382
- this.transcriptWindow.style.removeProperty("border-image");
383
- this.transcriptWindow.style.removeProperty("border-image-source");
384
- this.transcriptWindow.style.removeProperty("border-image-slice");
385
- this.transcriptWindow.style.removeProperty("border-image-width");
386
- this.transcriptWindow.style.removeProperty("border-image-outset");
387
- this.transcriptWindow.style.removeProperty("border-image-repeat");
388
- this.transcriptWindow.style.boxShadow = "none";
389
- if (this.transcriptHeader) {
390
- this.transcriptHeader.style.cursor = "default";
391
- }
392
- if (this.transcriptWindow.parentNode !== this.player.container) {
393
- this.player.container.appendChild(this.transcriptWindow);
394
- }
395
- } else if (isFullscreen) {
396
- this.transcriptWindow.style.position = "fixed";
397
- this.transcriptWindow.style.left = "auto";
398
- this.transcriptWindow.style.right = "20px";
399
- this.transcriptWindow.style.bottom = "80px";
400
- this.transcriptWindow.style.top = "auto";
401
- this.transcriptWindow.style.maxHeight = "calc(100vh - 180px)";
402
- this.transcriptWindow.style.height = "auto";
403
- const fullscreenMinWidth = 260;
404
- const fullscreenAvailable = Math.max(fullscreenMinWidth, window.innerWidth - 40);
405
- const fullscreenDesired = parseFloat(this.transcriptWindow.style.width) || 400;
406
- const fullscreenWidth = Math.max(fullscreenMinWidth, Math.min(fullscreenDesired, fullscreenAvailable));
407
- this.transcriptWindow.style.width = `${fullscreenWidth}px`;
408
- this.transcriptWindow.style.maxWidth = "none";
409
- this.transcriptWindow.style.borderRadius = "8px";
410
- this.transcriptWindow.style.border = "1px solid var(--vidply-border)";
411
- this.transcriptWindow.style.removeProperty("border-top");
412
- this.transcriptWindow.style.removeProperty("border-right");
413
- this.transcriptWindow.style.removeProperty("border-bottom");
414
- this.transcriptWindow.style.removeProperty("border-left");
415
- this.transcriptWindow.style.removeProperty("border-image");
416
- this.transcriptWindow.style.removeProperty("border-image-source");
417
- this.transcriptWindow.style.removeProperty("border-image-slice");
418
- this.transcriptWindow.style.removeProperty("border-image-width");
419
- this.transcriptWindow.style.removeProperty("border-image-outset");
420
- this.transcriptWindow.style.removeProperty("border-image-repeat");
421
- if (this.transcriptHeader) {
422
- this.transcriptHeader.style.cursor = "move";
423
- }
424
- if (this.transcriptWindow.parentNode !== this.player.container) {
425
- this.player.container.appendChild(this.transcriptWindow);
426
- }
427
- } else {
428
- const transcriptWidth = parseFloat(this.transcriptWindow.style.width) || 400;
429
- const padding = 20;
430
- const minWidth = 260;
431
- const containerRect = this.player.container.getBoundingClientRect();
432
- const ensureContainerPositioned = () => {
433
- const computed = window.getComputedStyle(this.player.container);
434
- if (computed.position === "static") {
435
- this.player.container.style.position = "relative";
436
- }
437
- };
438
- ensureContainerPositioned();
439
- const left = videoRect.right - containerRect.left + padding;
440
- const availableWidth = window.innerWidth - videoRect.right - padding;
441
- const appliedWidth = Math.max(minWidth, Math.min(transcriptWidth, availableWidth));
442
- const appliedHeight = videoRect.height;
443
- this.transcriptWindow.style.position = "absolute";
444
- this.transcriptWindow.style.left = `${left}px`;
445
- this.transcriptWindow.style.right = "auto";
446
- this.transcriptWindow.style.bottom = "auto";
447
- this.transcriptWindow.style.top = "0";
448
- this.transcriptWindow.style.height = `${appliedHeight}px`;
449
- this.transcriptWindow.style.maxHeight = "none";
450
- this.transcriptWindow.style.width = `${appliedWidth}px`;
451
- this.transcriptWindow.style.maxWidth = "none";
452
- this.transcriptWindow.style.borderRadius = "8px";
453
- this.transcriptWindow.style.border = "1px solid var(--vidply-border)";
454
- this.transcriptWindow.style.removeProperty("border-top");
455
- this.transcriptWindow.style.removeProperty("border-right");
456
- this.transcriptWindow.style.removeProperty("border-bottom");
457
- this.transcriptWindow.style.removeProperty("border-left");
458
- this.transcriptWindow.style.removeProperty("border-image");
459
- this.transcriptWindow.style.removeProperty("border-image-source");
460
- this.transcriptWindow.style.removeProperty("border-image-slice");
461
- this.transcriptWindow.style.removeProperty("border-image-width");
462
- this.transcriptWindow.style.removeProperty("border-image-outset");
463
- this.transcriptWindow.style.removeProperty("border-image-repeat");
464
- if (this.transcriptHeader) {
465
- this.transcriptHeader.style.cursor = "move";
466
- }
467
- if (this.transcriptWindow.parentNode !== this.player.container) {
468
- this.player.container.appendChild(this.transcriptWindow);
469
- }
470
- }
471
- }
472
- /**
473
- * Get available transcript languages from tracks
474
- */
475
- getAvailableTranscriptLanguages() {
476
- const textTracks = this.player.textTracks;
477
- const languages = /* @__PURE__ */ new Map();
478
- textTracks.forEach((track) => {
479
- if ((track.kind === "captions" || track.kind === "subtitles") && track.language) {
480
- if (!languages.has(track.language)) {
481
- languages.set(track.language, {
482
- language: track.language,
483
- label: track.label || track.language,
484
- track
485
- });
486
- }
487
- }
488
- });
489
- return Array.from(languages.values());
490
- }
491
- /**
492
- * Update language selector dropdown
493
- */
494
- updateLanguageSelector() {
495
- if (!this.languageSelector) return;
496
- this.availableTranscriptLanguages = this.getAvailableTranscriptLanguages();
497
- this.languageSelector.innerHTML = "";
498
- if (this.availableTranscriptLanguages.length < 2) {
499
- if (this.languageSelectorWrapper) {
500
- this.languageSelectorWrapper.style.display = "none";
501
- }
502
- return;
503
- }
504
- if (this.languageSelectorWrapper) {
505
- this.languageSelectorWrapper.style.display = "flex";
506
- }
507
- this.availableTranscriptLanguages.forEach((langInfo, index) => {
508
- const option = DOMUtils.createElement("option", {
509
- textContent: langInfo.label,
510
- attributes: {
511
- "value": langInfo.language,
512
- "lang": langInfo.language
513
- }
514
- });
515
- this.languageSelector.appendChild(option);
516
- });
517
- if (this.currentTranscriptLanguage) {
518
- this.languageSelector.value = this.currentTranscriptLanguage;
519
- } else if (this.availableTranscriptLanguages.length > 0) {
520
- const activeTrack = this.player.textTracks.find(
521
- (track) => (track.kind === "captions" || track.kind === "subtitles") && track.mode === "showing"
522
- );
523
- this.currentTranscriptLanguage = activeTrack ? activeTrack.language : this.availableTranscriptLanguages[0].language;
524
- this.languageSelector.value = this.currentTranscriptLanguage;
525
- }
526
- if (this.languageSelectorHandler) {
527
- this.languageSelector.removeEventListener("change", this.languageSelectorHandler);
528
- }
529
- this.languageSelectorHandler = (e) => {
530
- this.currentTranscriptLanguage = e.target.value;
531
- this.loadTranscriptData();
532
- if (this.transcriptContent && this.currentTranscriptLanguage) {
533
- this.transcriptContent.setAttribute("lang", this.currentTranscriptLanguage);
534
- }
535
- };
536
- this.languageSelector.addEventListener("change", this.languageSelectorHandler);
537
- }
538
- /**
539
- * Load transcript data from caption/subtitle tracks
540
- */
541
- loadTranscriptData() {
542
- this.transcriptEntries = [];
543
- this.transcriptContent.innerHTML = "";
544
- const textTracks = this.player.textTracks;
545
- let captionTrack = null;
546
- if (this.currentTranscriptLanguage) {
547
- captionTrack = textTracks.find(
548
- (track) => (track.kind === "captions" || track.kind === "subtitles") && track.language === this.currentTranscriptLanguage
549
- );
550
- }
551
- if (!captionTrack) {
552
- captionTrack = textTracks.find(
553
- (track) => track.kind === "captions" || track.kind === "subtitles"
554
- );
555
- if (captionTrack) {
556
- this.currentTranscriptLanguage = captionTrack.language;
557
- }
558
- }
559
- let descriptionTrack = null;
560
- if (this.currentTranscriptLanguage) {
561
- descriptionTrack = textTracks.find(
562
- (track) => track.kind === "descriptions" && track.language === this.currentTranscriptLanguage
563
- );
564
- }
565
- if (!descriptionTrack) {
566
- descriptionTrack = textTracks.find((track) => track.kind === "descriptions");
567
- }
568
- const metadataTrack = textTracks.find((track) => track.kind === "metadata");
569
- const hasDescriptionTrack = descriptionTrack && this.player.state.audioDescriptionEnabled;
570
- if (!captionTrack && !hasDescriptionTrack && !metadataTrack) {
571
- this.showNoTranscriptMessage();
572
- return;
573
- }
574
- const tracksToLoad = [captionTrack, descriptionTrack, metadataTrack].filter(Boolean);
575
- tracksToLoad.forEach((track) => {
576
- if (track.mode === "disabled") {
577
- track.mode = "hidden";
578
- }
579
- });
580
- const needsLoading = tracksToLoad.some((track) => !track.cues || track.cues.length === 0);
581
- if (needsLoading) {
582
- const loadingMessage = DOMUtils.createElement("div", {
583
- className: `${this.player.options.classPrefix}-transcript-loading`,
584
- textContent: i18n.t("transcript.loading")
585
- });
586
- this.transcriptContent.appendChild(loadingMessage);
587
- let loaded = 0;
588
- const onLoad = () => {
589
- loaded++;
590
- if (loaded >= tracksToLoad.length) {
591
- this.loadTranscriptData();
592
- }
593
- };
594
- tracksToLoad.forEach((track) => {
595
- track.addEventListener("load", onLoad, { once: true });
596
- });
597
- this.setManagedTimeout(() => {
598
- this.loadTranscriptData();
599
- }, 500);
600
- return;
601
- }
602
- const allCues = [];
603
- if (captionTrack && captionTrack.cues) {
604
- Array.from(captionTrack.cues).forEach((cue) => {
605
- allCues.push({ cue, type: "caption" });
606
- });
607
- }
608
- if (descriptionTrack && descriptionTrack.cues && this.player.state.audioDescriptionEnabled) {
609
- Array.from(descriptionTrack.cues).forEach((cue) => {
610
- allCues.push({ cue, type: "description" });
611
- });
612
- }
613
- if (metadataTrack && metadataTrack.cues) {
614
- this.metadataCues = Array.from(metadataTrack.cues);
615
- this.setupMetadataHandling();
616
- }
617
- allCues.sort((a, b) => a.cue.startTime - b.cue.startTime);
618
- allCues.forEach((item, index) => {
619
- const entry = this.createTranscriptEntry(item.cue, index, item.type);
620
- this.transcriptEntries.push({
621
- element: entry,
622
- cue: item.cue,
623
- type: item.type,
624
- startTime: item.cue.startTime,
625
- endTime: item.cue.endTime
626
- });
627
- this.transcriptContent.appendChild(entry);
628
- });
629
- this.applyTranscriptStyles();
630
- this.updateTimestampVisibility();
631
- if (this.transcriptContent && this.currentTranscriptLanguage) {
632
- this.transcriptContent.setAttribute("lang", this.currentTranscriptLanguage);
633
- }
634
- this.updateLanguageSelector();
635
- }
636
- /**
637
- * Setup metadata handling on player load
638
- * This runs independently of transcript loading
639
- */
640
- setupMetadataHandlingOnLoad() {
641
- const setupMetadata = () => {
642
- const textTracks = this.player.textTracks;
643
- const metadataTrack = textTracks.find((track) => track.kind === "metadata");
644
- if (metadataTrack) {
645
- if (metadataTrack.mode === "disabled") {
646
- metadataTrack.mode = "hidden";
647
- }
648
- if (this.metadataCueChangeHandler) {
649
- metadataTrack.removeEventListener("cuechange", this.metadataCueChangeHandler);
650
- }
651
- this.metadataCueChangeHandler = () => {
652
- const activeCues = Array.from(metadataTrack.activeCues || []);
653
- if (activeCues.length > 0) {
654
- if (this.player.options.debug) {
655
- console.log("[VidPly Metadata] Active cues:", activeCues.map((c) => ({
656
- start: c.startTime,
657
- end: c.endTime,
658
- text: c.text
659
- })));
660
- }
661
- }
662
- activeCues.forEach((cue) => {
663
- this.handleMetadataCue(cue);
664
- });
665
- };
666
- metadataTrack.addEventListener("cuechange", this.metadataCueChangeHandler);
667
- if (this.player.options.debug) {
668
- const cueCount = metadataTrack.cues ? metadataTrack.cues.length : 0;
669
- console.log("[VidPly Metadata] Track enabled,", cueCount, "cues available");
670
- }
671
- } else if (this.player.options.debug) {
672
- console.warn("[VidPly Metadata] No metadata track found");
673
- }
674
- };
675
- setupMetadata();
676
- this.player.on("loadedmetadata", setupMetadata);
677
- }
678
- /**
679
- * Setup metadata handling
680
- * Metadata cues are not displayed but can be used programmatically
681
- * This is called when transcript data is loaded (for storing cues)
682
- */
683
- setupMetadataHandling() {
684
- if (!this.metadataCues || this.metadataCues.length === 0) {
685
- return;
686
- }
687
- if (this.player.options.debug) {
688
- console.log("[VidPly Metadata]", this.metadataCues.length, "cues stored from transcript load");
689
- }
690
- }
691
- /**
692
- * Handle individual metadata cues
693
- * Parses metadata text and emits events or triggers actions
694
- */
695
- handleMetadataCue(cue) {
696
- const text = cue.text.trim();
697
- if (this.player.options.debug) {
698
- console.log("[VidPly Metadata] Processing cue:", {
699
- time: cue.startTime,
700
- text
701
- });
702
- }
703
- this.player.emit("metadata", {
704
- time: cue.startTime,
705
- endTime: cue.endTime,
706
- text,
707
- cue
708
- });
709
- if (text.includes("PAUSE")) {
710
- if (!this.player.state.paused) {
711
- if (this.player.options.debug) {
712
- console.log("[VidPly Metadata] Pausing video at", cue.startTime);
713
- }
714
- this.player.pause();
715
- }
716
- this.player.emit("metadata:pause", { time: cue.startTime, text });
717
- }
718
- const focusMatch = text.match(/FOCUS:([\w#-]+)/);
719
- if (focusMatch) {
720
- const targetSelector = focusMatch[1];
721
- const targetElement = document.querySelector(targetSelector);
722
- if (targetElement) {
723
- if (this.player.options.debug) {
724
- console.log("[VidPly Metadata] Focusing element:", targetSelector);
725
- }
726
- this.setManagedTimeout(() => {
727
- targetElement.focus({ preventScroll: true });
728
- }, 10);
729
- } else if (this.player.options.debug) {
730
- console.warn("[VidPly Metadata] Element not found:", targetSelector);
731
- }
732
- this.player.emit("metadata:focus", {
733
- time: cue.startTime,
734
- target: targetSelector,
735
- element: targetElement,
736
- text
737
- });
738
- }
739
- const hashtags = text.match(/#[\w-]+/g);
740
- if (hashtags) {
741
- if (this.player.options.debug) {
742
- console.log("[VidPly Metadata] Hashtags found:", hashtags);
743
- }
744
- this.player.emit("metadata:hashtags", {
745
- time: cue.startTime,
746
- hashtags,
747
- text
748
- });
749
- }
750
- }
751
- /**
752
- * Create a single transcript entry element
753
- */
754
- createTranscriptEntry(cue, index, type = "caption") {
755
- const entryText = this.stripVTTFormatting(cue.text);
756
- const entry = DOMUtils.createElement("div", {
757
- className: `${this.player.options.classPrefix}-transcript-entry ${this.player.options.classPrefix}-transcript-${type}`,
758
- attributes: {
759
- "tabindex": "0",
760
- "data-start": String(cue.startTime),
761
- "data-end": String(cue.endTime),
762
- "data-type": type
763
- }
764
- });
765
- const timestamp = DOMUtils.createElement("span", {
766
- className: `${this.player.options.classPrefix}-transcript-time`,
767
- textContent: TimeUtils.formatTime(cue.startTime),
768
- attributes: {
769
- "aria-hidden": "true"
770
- // Hide from screen readers - decorative timestamp
771
- }
772
- });
773
- const text = DOMUtils.createElement("span", {
774
- className: `${this.player.options.classPrefix}-transcript-text`,
775
- textContent: entryText
776
- });
777
- entry.appendChild(timestamp);
778
- entry.appendChild(text);
779
- const seekToTime = () => {
780
- this.player.seek(cue.startTime);
781
- if (this.player.state.paused) {
782
- this.player.play();
783
- }
784
- };
785
- entry.addEventListener("click", seekToTime);
786
- entry.addEventListener("keydown", (e) => {
787
- if (e.key === "Enter" || e.key === " ") {
788
- e.preventDefault();
789
- seekToTime();
790
- }
791
- });
792
- return entry;
793
- }
794
- /**
795
- * Strip VTT formatting tags from text
796
- */
797
- stripVTTFormatting(text) {
798
- return text.replace(/<[^>]+>/g, "").replace(/\n/g, " ").trim();
799
- }
800
- /**
801
- * Show message when no transcript is available
802
- */
803
- showNoTranscriptMessage() {
804
- const message = DOMUtils.createElement("div", {
805
- className: `${this.player.options.classPrefix}-transcript-empty`,
806
- textContent: i18n.t("transcript.noTranscript")
807
- });
808
- this.transcriptContent.appendChild(message);
809
- }
810
- /**
811
- * Update active transcript entry based on current time
812
- */
813
- updateActiveEntry() {
814
- if (!this.isVisible || this.transcriptEntries.length === 0) return;
815
- const currentTime = this.player.state.currentTime;
816
- const activeEntry = this.transcriptEntries.find(
817
- (entry) => currentTime >= entry.startTime && currentTime < entry.endTime
818
- );
819
- if (activeEntry && activeEntry !== this.currentActiveEntry) {
820
- if (this.currentActiveEntry) {
821
- this.currentActiveEntry.element.classList.remove(
822
- `${this.player.options.classPrefix}-transcript-entry-active`
823
- );
824
- }
825
- activeEntry.element.classList.add(
826
- `${this.player.options.classPrefix}-transcript-entry-active`
827
- );
828
- this.scrollToEntry(activeEntry.element);
829
- this.currentActiveEntry = activeEntry;
830
- } else if (!activeEntry && this.currentActiveEntry) {
831
- this.currentActiveEntry.element.classList.remove(
832
- `${this.player.options.classPrefix}-transcript-entry-active`
833
- );
834
- this.currentActiveEntry = null;
835
- }
836
- }
837
- /**
838
- * Scroll transcript window to show active entry
839
- */
840
- scrollToEntry(entryElement) {
841
- if (!this.transcriptContent || !this.autoscrollEnabled) return;
842
- const contentRect = this.transcriptContent.getBoundingClientRect();
843
- const entryRect = entryElement.getBoundingClientRect();
844
- if (entryRect.top < contentRect.top || entryRect.bottom > contentRect.bottom) {
845
- const scrollTop = entryElement.offsetTop - this.transcriptContent.clientHeight / 2 + entryElement.clientHeight / 2;
846
- this.transcriptContent.scrollTo({
847
- top: scrollTop,
848
- behavior: "smooth"
849
- });
850
- }
851
- }
852
- /**
853
- * Save autoscroll preference to localStorage
854
- */
855
- saveAutoscrollPreference() {
856
- const savedPreferences = this.storage.getTranscriptPreferences() || {};
857
- savedPreferences.autoscroll = this.autoscrollEnabled;
858
- this.storage.saveTranscriptPreferences(savedPreferences);
859
- }
860
- /**
861
- * Setup drag and drop functionality
862
- */
863
- setupDragAndDrop() {
864
- if (!this.transcriptHeader || !this.transcriptWindow) return;
865
- const isMobile = window.innerWidth < 768;
866
- const isFullscreen = this.player.state.fullscreen;
867
- if (isMobile && !isFullscreen) {
868
- if (this.draggableResizable) {
869
- this.draggableResizable.destroy();
870
- this.draggableResizable = null;
871
- }
872
- return;
873
- }
874
- if (this.draggableResizable) {
875
- return;
876
- }
877
- this.draggableResizable = new DraggableResizable(this.transcriptWindow, {
878
- dragHandle: this.transcriptHeader,
879
- resizeHandles: this.transcriptResizeHandles,
880
- constrainToViewport: true,
881
- classPrefix: `${this.player.options.classPrefix}-transcript`,
882
- keyboardDragKey: "d",
883
- keyboardResizeKey: "r",
884
- keyboardStep: 10,
885
- keyboardStepLarge: 50,
886
- minWidth: 300,
887
- minHeight: 200,
888
- maxWidth: () => Math.max(320, window.innerWidth - 40),
889
- maxHeight: () => Math.max(200, window.innerHeight - 120),
890
- pointerResizeIndicatorText: i18n.t("transcript.resizeModeHint"),
891
- onPointerResizeToggle: (enabled) => {
892
- this.transcriptResizeHandles.forEach((handle) => {
893
- handle.style.display = enabled ? "block" : "none";
894
- });
895
- this.onPointerResizeModeChange(enabled);
896
- },
897
- onDragStart: (e) => {
898
- const ignoreSelectors = [
899
- `.${this.player.options.classPrefix}-transcript-close`,
900
- `.${this.player.options.classPrefix}-transcript-settings`,
901
- `.${this.player.options.classPrefix}-transcript-language-select`,
902
- `.${this.player.options.classPrefix}-transcript-language-label`,
903
- `.${this.player.options.classPrefix}-transcript-settings-menu`,
904
- `.${this.player.options.classPrefix}-transcript-style-dialog`
905
- ];
906
- for (const selector of ignoreSelectors) {
907
- if (e.target.closest(selector)) {
908
- return false;
909
- }
910
- }
911
- return true;
912
- }
913
- });
914
- this.customKeyHandler = (e) => {
915
- const key = e.key.toLowerCase();
916
- const alreadyPrevented = e.defaultPrevented;
917
- if (this.settingsMenuVisible || this.styleDialogVisible) {
918
- return;
919
- }
920
- if (key === "home") {
921
- e.preventDefault();
922
- e.stopPropagation();
923
- if (this.draggableResizable) {
924
- if (this.draggableResizable.pointerResizeMode) {
925
- this.draggableResizable.disablePointerResizeMode();
926
- }
927
- this.draggableResizable.manuallyPositioned = false;
928
- this.positionTranscript();
929
- this.updateResizeOptionState();
930
- this.announceLive(i18n.t("transcript.positionReset"));
931
- }
932
- return;
933
- }
934
- if (key === "r") {
935
- if (alreadyPrevented) {
936
- return;
937
- }
938
- e.preventDefault();
939
- e.stopPropagation();
940
- const enabled = this.toggleResizeMode();
941
- if (enabled) {
942
- this.transcriptWindow.focus({ preventScroll: true });
943
- }
944
- return;
945
- }
946
- if (key === "escape") {
947
- if (this.draggableResizable && this.draggableResizable.pointerResizeMode) {
948
- e.preventDefault();
949
- e.stopPropagation();
950
- this.draggableResizable.disablePointerResizeMode();
951
- return;
952
- }
953
- if (this.draggableResizable && this.draggableResizable.keyboardDragMode) {
954
- e.preventDefault();
955
- e.stopPropagation();
956
- this.draggableResizable.disableKeyboardDragMode();
957
- this.announceLive(i18n.t("transcript.dragModeDisabled"));
958
- return;
959
- }
960
- e.preventDefault();
961
- e.stopPropagation();
962
- this.hideTranscript({ focusButton: true });
963
- return;
964
- }
965
- };
966
- this.transcriptWindow.addEventListener("keydown", this.customKeyHandler);
967
- }
968
- /**
969
- * Toggle keyboard drag mode
970
- */
971
- toggleKeyboardDragMode() {
972
- if (this.draggableResizable) {
973
- const wasEnabled = this.draggableResizable.keyboardDragMode;
974
- this.draggableResizable.toggleKeyboardDragMode();
975
- const isEnabled = this.draggableResizable.keyboardDragMode;
976
- if (!wasEnabled && isEnabled) {
977
- this.enableMoveMode();
978
- }
979
- this.updateDragOptionState();
980
- if (this.settingsMenuVisible) {
981
- this.hideSettingsMenu();
982
- }
983
- this.transcriptWindow.focus({ preventScroll: true });
984
- }
985
- }
986
- /**
987
- * Toggle settings menu visibility
988
- */
989
- toggleSettingsMenu() {
990
- if (this.settingsMenuVisible) {
991
- this.hideSettingsMenu();
992
- } else {
993
- this.showSettingsMenu();
994
- }
995
- }
996
- /**
997
- * Show settings menu
998
- */
999
- showSettingsMenu() {
1000
- this.settingsMenuJustOpened = true;
1001
- setTimeout(() => {
1002
- this.settingsMenuJustOpened = false;
1003
- }, 350);
1004
- if (!this.documentClickHandlerAdded) {
1005
- setTimeout(() => {
1006
- document.addEventListener("click", this.handlers.documentClick);
1007
- this.documentClickHandlerAdded = true;
1008
- }, 300);
1009
- }
1010
- if (this.settingsMenu) {
1011
- this.settingsMenu.style.display = "block";
1012
- this.settingsMenuVisible = true;
1013
- if (this.settingsButton) {
1014
- this.settingsButton.setAttribute("aria-expanded", "true");
1015
- }
1016
- this.attachSettingsMenuKeyboardNavigation();
1017
- this.positionSettingsMenuImmediate();
1018
- this.updateResizeOptionState();
1019
- setTimeout(() => {
1020
- const menuItems = this.settingsMenu.querySelectorAll(`.${this.player.options.classPrefix}-transcript-settings-item`);
1021
- if (menuItems.length > 0) {
1022
- menuItems[0].setAttribute("tabindex", "0");
1023
- for (let i = 1; i < menuItems.length; i++) {
1024
- menuItems[i].setAttribute("tabindex", "-1");
1025
- }
1026
- menuItems[0].focus({ preventScroll: true });
1027
- }
1028
- }, 50);
1029
- return;
1030
- }
1031
- this.settingsMenu = DOMUtils.createElement("div", {
1032
- className: `${this.player.options.classPrefix}-transcript-settings-menu`,
1033
- attributes: {
1034
- "role": "menu"
1035
- }
1036
- });
1037
- const keyboardDragOption = createMenuItem({
1038
- classPrefix: this.player.options.classPrefix,
1039
- itemClass: `${this.player.options.classPrefix}-transcript-settings-item`,
1040
- icon: "move",
1041
- label: "transcript.enableDragMode",
1042
- hasTextClass: true,
1043
- onClick: () => {
1044
- this.toggleKeyboardDragMode();
1045
- this.hideSettingsMenu();
1046
- }
1047
- });
1048
- keyboardDragOption.setAttribute("role", "switch");
1049
- keyboardDragOption.setAttribute("aria-checked", "false");
1050
- const dragTooltip = keyboardDragOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
1051
- if (dragTooltip) dragTooltip.remove();
1052
- const dragButtonText = keyboardDragOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
1053
- if (dragButtonText) dragButtonText.remove();
1054
- this.dragOptionButton = keyboardDragOption;
1055
- this.dragOptionText = keyboardDragOption.querySelector(`.${this.player.options.classPrefix}-settings-text`);
1056
- this.updateDragOptionState();
1057
- const styleOption = createMenuItem({
1058
- classPrefix: this.player.options.classPrefix,
1059
- itemClass: `${this.player.options.classPrefix}-transcript-settings-item`,
1060
- icon: "settings",
1061
- label: "transcript.styleTranscript",
1062
- onClick: (e) => {
1063
- e.preventDefault();
1064
- e.stopPropagation();
1065
- this.hideSettingsMenu();
1066
- setTimeout(() => {
1067
- this.showStyleDialog();
1068
- }, 50);
1069
- }
1070
- });
1071
- const styleTooltip = styleOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
1072
- if (styleTooltip) styleTooltip.remove();
1073
- const styleButtonText = styleOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
1074
- if (styleButtonText) styleButtonText.remove();
1075
- const resizeOption = createMenuItem({
1076
- classPrefix: this.player.options.classPrefix,
1077
- itemClass: `${this.player.options.classPrefix}-transcript-settings-item`,
1078
- icon: "resize",
1079
- label: "transcript.enableResizeMode",
1080
- hasTextClass: true,
1081
- onClick: (event) => {
1082
- event.preventDefault();
1083
- event.stopPropagation();
1084
- const enabled = this.toggleResizeMode({ focus: false });
1085
- if (enabled) {
1086
- this.hideSettingsMenu({ focusButton: false });
1087
- setTimeout(() => {
1088
- if (this.transcriptWindow) {
1089
- this.transcriptWindow.focus({ preventScroll: true });
1090
- }
1091
- }, 20);
1092
- } else {
1093
- this.hideSettingsMenu({ focusButton: true });
1094
- }
1095
- }
1096
- });
1097
- resizeOption.setAttribute("role", "switch");
1098
- resizeOption.setAttribute("aria-checked", "false");
1099
- const resizeTooltip = resizeOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
1100
- if (resizeTooltip) resizeTooltip.remove();
1101
- const resizeButtonText = resizeOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
1102
- if (resizeButtonText) resizeButtonText.remove();
1103
- this.resizeOptionButton = resizeOption;
1104
- this.resizeOptionText = resizeOption.querySelector(`.${this.player.options.classPrefix}-settings-text`);
1105
- this.updateResizeOptionState();
1106
- const showTimestampsOption = createMenuItem({
1107
- classPrefix: this.player.options.classPrefix,
1108
- itemClass: `${this.player.options.classPrefix}-transcript-settings-item`,
1109
- icon: "clock",
1110
- label: "transcript.showTimestamps",
1111
- hasTextClass: true,
1112
- onClick: () => {
1113
- this.toggleShowTimestamps();
1114
- }
1115
- });
1116
- showTimestampsOption.setAttribute("role", "switch");
1117
- showTimestampsOption.setAttribute("aria-checked", this.showTimestamps ? "true" : "false");
1118
- const timestampsTooltip = showTimestampsOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
1119
- if (timestampsTooltip) timestampsTooltip.remove();
1120
- const timestampsButtonText = showTimestampsOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
1121
- if (timestampsButtonText) timestampsButtonText.remove();
1122
- this.showTimestampsButton = showTimestampsOption;
1123
- this.showTimestampsText = showTimestampsOption.querySelector(`.${this.player.options.classPrefix}-settings-text`);
1124
- this.updateShowTimestampsState();
1125
- const closeOption = createMenuItem({
1126
- classPrefix: this.player.options.classPrefix,
1127
- itemClass: `${this.player.options.classPrefix}-transcript-settings-item`,
1128
- icon: "close",
1129
- label: "transcript.closeMenu",
1130
- onClick: () => {
1131
- this.hideSettingsMenu();
1132
- }
1133
- });
1134
- const closeTooltip = closeOption.querySelector(`.${this.player.options.classPrefix}-tooltip`);
1135
- if (closeTooltip) closeTooltip.remove();
1136
- const closeButtonText = closeOption.querySelector(`.${this.player.options.classPrefix}-button-text`);
1137
- if (closeButtonText) closeButtonText.remove();
1138
- this.settingsMenu.appendChild(keyboardDragOption);
1139
- this.settingsMenu.appendChild(resizeOption);
1140
- this.settingsMenu.appendChild(styleOption);
1141
- this.settingsMenu.appendChild(showTimestampsOption);
1142
- this.settingsMenu.appendChild(closeOption);
1143
- this.settingsMenu.style.visibility = "hidden";
1144
- this.settingsMenu.style.display = "block";
1145
- if (this.settingsButton && this.settingsButton.parentNode) {
1146
- this.settingsButton.insertAdjacentElement("afterend", this.settingsMenu);
1147
- } else if (this.headerLeft) {
1148
- this.headerLeft.appendChild(this.settingsMenu);
1149
- } else if (this.transcriptHeader) {
1150
- this.transcriptHeader.appendChild(this.settingsMenu);
1151
- } else {
1152
- this.transcriptWindow.appendChild(this.settingsMenu);
1153
- }
1154
- this.positionSettingsMenuImmediate();
1155
- requestAnimationFrame(() => {
1156
- if (this.settingsMenu) {
1157
- this.settingsMenu.style.visibility = "visible";
1158
- }
1159
- });
1160
- this.settingsMenuKeyHandler = attachMenuKeyboardNavigation(
1161
- this.settingsMenu,
1162
- this.settingsButton,
1163
- `.${this.player.options.classPrefix}-transcript-settings-item`,
1164
- () => this.hideSettingsMenu({ focusButton: true })
1165
- );
1166
- this.settingsMenuVisible = true;
1167
- this.settingsMenu.style.display = "block";
1168
- if (this.settingsButton) {
1169
- this.settingsButton.setAttribute("aria-expanded", "true");
1170
- }
1171
- this.updateResizeOptionState();
1172
- setTimeout(() => {
1173
- const menuItems = this.settingsMenu.querySelectorAll(`.${this.player.options.classPrefix}-transcript-settings-item`);
1174
- if (menuItems.length > 0) {
1175
- menuItems[0].setAttribute("tabindex", "0");
1176
- for (let i = 1; i < menuItems.length; i++) {
1177
- menuItems[i].setAttribute("tabindex", "-1");
1178
- }
1179
- menuItems[0].focus({ preventScroll: true });
1180
- }
1181
- }, 50);
1182
- }
1183
- /**
1184
- * Position settings menu relative to settings button (immediate/synchronous)
1185
- */
1186
- positionSettingsMenuImmediate() {
1187
- if (!this.settingsMenu || !this.settingsButton) return;
1188
- const container = this.settingsButton.parentElement;
1189
- if (!container) return;
1190
- const buttonRect = this.settingsButton.getBoundingClientRect();
1191
- const containerRect = container.getBoundingClientRect();
1192
- const menuRect = this.settingsMenu.getBoundingClientRect();
1193
- const viewportHeight = window.innerHeight;
1194
- const buttonLeft = buttonRect.left - containerRect.left;
1195
- const buttonBottom = buttonRect.bottom - containerRect.top;
1196
- const buttonTop = buttonRect.top - containerRect.top;
1197
- const spaceBelow = viewportHeight - buttonRect.bottom;
1198
- const spaceAbove = buttonRect.top;
1199
- let menuTop = buttonBottom + 4;
1200
- if (spaceBelow < menuRect.height + 20 && spaceAbove > spaceBelow) {
1201
- menuTop = buttonTop - menuRect.height - 4;
1202
- this.settingsMenu.classList.add("vidply-menu-above");
1203
- } else {
1204
- this.settingsMenu.classList.remove("vidply-menu-above");
1205
- }
1206
- this.settingsMenu.style.top = `${menuTop}px`;
1207
- this.settingsMenu.style.left = `${buttonLeft}px`;
1208
- this.settingsMenu.style.right = "auto";
1209
- this.settingsMenu.style.bottom = "auto";
1210
- }
1211
- /**
1212
- * Position settings menu relative to settings button (async for repositioning)
1213
- */
1214
- positionSettingsMenu() {
1215
- if (!this.settingsMenu || !this.settingsButton) return;
1216
- requestAnimationFrame(() => {
1217
- setTimeout(() => {
1218
- this.positionSettingsMenuImmediate();
1219
- }, 10);
1220
- });
1221
- }
1222
- /**
1223
- * Attach keyboard navigation to settings menu
1224
- */
1225
- attachSettingsMenuKeyboardNavigation() {
1226
- if (!this.settingsMenu) return;
1227
- if (this.settingsMenuKeyHandler) {
1228
- this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler, true);
1229
- }
1230
- const handler = attachMenuKeyboardNavigation(
1231
- this.settingsMenu,
1232
- this.settingsButton,
1233
- `.${this.player.options.classPrefix}-transcript-settings-item`,
1234
- () => this.hideSettingsMenu({ focusButton: true })
1235
- );
1236
- this.settingsMenuKeyHandler = handler;
1237
- }
1238
- /**
1239
- * Hide settings menu
1240
- */
1241
- hideSettingsMenu({ focusButton = true } = {}) {
1242
- if (this.settingsMenu) {
1243
- this.settingsMenu.style.display = "none";
1244
- this.settingsMenuVisible = false;
1245
- this.settingsMenuJustOpened = false;
1246
- if (this.settingsMenuKeyHandler) {
1247
- this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler, true);
1248
- this.settingsMenuKeyHandler = null;
1249
- }
1250
- if (this.settingsButton) {
1251
- this.settingsButton.setAttribute("aria-expanded", "false");
1252
- if (focusButton) {
1253
- this.settingsButton.focus({ preventScroll: true });
1254
- }
1255
- }
1256
- }
1257
- }
1258
- /**
1259
- * Enable move mode (gives visual feedback)
1260
- */
1261
- enableMoveMode() {
1262
- this.hideResizeModeIndicator();
1263
- this.transcriptWindow.classList.add(`${this.player.options.classPrefix}-transcript-move-mode`);
1264
- const tooltip = DOMUtils.createElement("div", {
1265
- className: `${this.player.options.classPrefix}-transcript-move-tooltip`,
1266
- textContent: "Drag with mouse or press D for keyboard drag mode"
1267
- });
1268
- this.transcriptHeader.appendChild(tooltip);
1269
- setTimeout(() => {
1270
- this.transcriptWindow.classList.remove(`${this.player.options.classPrefix}-transcript-move-mode`);
1271
- if (tooltip.parentNode) {
1272
- tooltip.remove();
1273
- }
1274
- }, 2e3);
1275
- }
1276
- /**
1277
- * Toggle resize mode
1278
- */
1279
- toggleResizeMode({ focus = true } = {}) {
1280
- if (!this.draggableResizable) {
1281
- return false;
1282
- }
1283
- if (this.draggableResizable.pointerResizeMode) {
1284
- this.draggableResizable.disablePointerResizeMode({ focus });
1285
- return false;
1286
- }
1287
- this.draggableResizable.enablePointerResizeMode({ focus });
1288
- return true;
1289
- }
1290
- updateDragOptionState() {
1291
- if (!this.dragOptionButton) {
1292
- return;
1293
- }
1294
- const isEnabled = !!(this.draggableResizable && this.draggableResizable.keyboardDragMode);
1295
- const text = isEnabled ? i18n.t("transcript.disableDragMode") : i18n.t("transcript.enableDragMode");
1296
- const ariaLabel = isEnabled ? i18n.t("transcript.disableDragModeAria") : i18n.t("transcript.enableDragModeAria");
1297
- this.dragOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
1298
- this.dragOptionButton.setAttribute("aria-label", ariaLabel);
1299
- if (this.dragOptionText) {
1300
- this.dragOptionText.textContent = text;
1301
- }
1302
- }
1303
- updateResizeOptionState() {
1304
- if (!this.resizeOptionButton) {
1305
- return;
1306
- }
1307
- const isEnabled = !!(this.draggableResizable && this.draggableResizable.pointerResizeMode);
1308
- const text = isEnabled ? i18n.t("transcript.disableResizeMode") : i18n.t("transcript.enableResizeMode");
1309
- const ariaLabel = isEnabled ? i18n.t("transcript.disableResizeModeAria") : i18n.t("transcript.enableResizeModeAria");
1310
- this.resizeOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
1311
- this.resizeOptionButton.setAttribute("aria-label", ariaLabel);
1312
- if (this.resizeOptionText) {
1313
- this.resizeOptionText.textContent = text;
1314
- }
1315
- }
1316
- toggleShowTimestamps() {
1317
- this.showTimestamps = !this.showTimestamps;
1318
- this.updateShowTimestampsState();
1319
- this.updateTimestampVisibility();
1320
- this.saveTimestampsPreference();
1321
- }
1322
- updateShowTimestampsState() {
1323
- if (!this.showTimestampsButton) {
1324
- return;
1325
- }
1326
- const text = this.showTimestamps ? i18n.t("transcript.hideTimestamps") : i18n.t("transcript.showTimestamps");
1327
- const ariaLabel = this.showTimestamps ? i18n.t("transcript.hideTimestampsAria") : i18n.t("transcript.showTimestampsAria");
1328
- this.showTimestampsButton.setAttribute("aria-checked", this.showTimestamps ? "true" : "false");
1329
- this.showTimestampsButton.setAttribute("aria-label", ariaLabel);
1330
- if (this.showTimestampsText) {
1331
- this.showTimestampsText.textContent = text;
1332
- }
1333
- }
1334
- updateTimestampVisibility() {
1335
- if (!this.transcriptContent) return;
1336
- const timestamps = this.transcriptContent.querySelectorAll(`.${this.player.options.classPrefix}-transcript-time`);
1337
- timestamps.forEach((timestamp) => {
1338
- timestamp.style.display = this.showTimestamps ? "" : "none";
1339
- });
1340
- }
1341
- saveTimestampsPreference() {
1342
- const savedPreferences = this.storage.getTranscriptPreferences() || {};
1343
- savedPreferences.showTimestamps = this.showTimestamps;
1344
- this.storage.saveTranscriptPreferences(savedPreferences);
1345
- }
1346
- showResizeModeIndicator() {
1347
- if (!this.transcriptHeader) {
1348
- return;
1349
- }
1350
- this.hideResizeModeIndicator();
1351
- const indicator = DOMUtils.createElement("div", {
1352
- className: `${this.player.options.classPrefix}-transcript-resize-tooltip`,
1353
- textContent: i18n.t("transcript.resizeModeHint") || "Resize handles enabled. Drag edges or corners to adjust. Press Esc or R to exit."
1354
- });
1355
- this.transcriptHeader.appendChild(indicator);
1356
- this.resizeModeIndicator = indicator;
1357
- this.resizeModeIndicatorTimeout = this.setManagedTimeout(() => {
1358
- this.hideResizeModeIndicator();
1359
- }, 3e3);
1360
- }
1361
- hideResizeModeIndicator() {
1362
- if (this.resizeModeIndicatorTimeout) {
1363
- this.clearManagedTimeout(this.resizeModeIndicatorTimeout);
1364
- this.resizeModeIndicatorTimeout = null;
1365
- }
1366
- if (this.resizeModeIndicator && this.resizeModeIndicator.parentNode) {
1367
- this.resizeModeIndicator.remove();
1368
- }
1369
- this.resizeModeIndicator = null;
1370
- }
1371
- onPointerResizeModeChange(enabled) {
1372
- this.updateResizeOptionState();
1373
- if (enabled) {
1374
- this.showResizeModeIndicator();
1375
- this.announceLive(i18n.t("transcript.resizeModeEnabled"));
1376
- } else {
1377
- this.hideResizeModeIndicator();
1378
- this.announceLive(i18n.t("transcript.resizeModeDisabled"));
1379
- }
1380
- }
1381
- /**
1382
- * Show style dialog
1383
- */
1384
- showStyleDialog() {
1385
- if (this.styleDialog) {
1386
- this.styleDialog.style.display = "block";
1387
- this.styleDialogVisible = true;
1388
- if (this.handlers.styleDialogKeydown) {
1389
- document.addEventListener("keydown", this.handlers.styleDialogKeydown);
1390
- }
1391
- this.styleDialogJustOpened = true;
1392
- setTimeout(() => {
1393
- this.styleDialogJustOpened = false;
1394
- }, 350);
1395
- setTimeout(() => {
1396
- const firstSelect = this.styleDialog.querySelector("select, input");
1397
- if (firstSelect) {
1398
- firstSelect.focus({ preventScroll: true });
1399
- }
1400
- }, 0);
1401
- return;
1402
- }
1403
- this.styleDialog = DOMUtils.createElement("div", {
1404
- className: `${this.player.options.classPrefix}-transcript-style-dialog`
1405
- });
1406
- const title = DOMUtils.createElement("h4", {
1407
- textContent: i18n.t("transcript.styleTitle"),
1408
- className: `${this.player.options.classPrefix}-transcript-style-title`
1409
- });
1410
- this.styleDialog.appendChild(title);
1411
- const fontSizeControl = this.createStyleSelectControl(
1412
- i18n.t("captions.fontSize"),
1413
- "fontSize",
1414
- [
1415
- { label: i18n.t("fontSizes.small"), value: "90%" },
1416
- { label: i18n.t("fontSizes.normal"), value: "100%" },
1417
- { label: i18n.t("fontSizes.large"), value: "110%" },
1418
- { label: i18n.t("fontSizes.xlarge"), value: "120%" }
1419
- ]
1420
- );
1421
- this.styleDialog.appendChild(fontSizeControl);
1422
- const fontFamilyControl = this.createStyleSelectControl(
1423
- i18n.t("captions.fontFamily"),
1424
- "fontFamily",
1425
- [
1426
- { label: i18n.t("fontFamilies.sansSerif"), value: "sans-serif" },
1427
- { label: i18n.t("fontFamilies.serif"), value: "serif" },
1428
- { label: i18n.t("fontFamilies.monospace"), value: "monospace" }
1429
- ]
1430
- );
1431
- this.styleDialog.appendChild(fontFamilyControl);
1432
- const colorControl = this.createStyleColorControl(i18n.t("captions.color"), "color");
1433
- this.styleDialog.appendChild(colorControl);
1434
- const bgColorControl = this.createStyleColorControl(i18n.t("captions.backgroundColor"), "backgroundColor");
1435
- this.styleDialog.appendChild(bgColorControl);
1436
- const opacityControl = this.createStyleOpacityControl(i18n.t("captions.opacity"), "opacity");
1437
- this.styleDialog.appendChild(opacityControl);
1438
- const closeBtn = DOMUtils.createElement("button", {
1439
- className: `${this.player.options.classPrefix}-transcript-style-close`,
1440
- textContent: i18n.t("settings.close"),
1441
- attributes: {
1442
- "type": "button"
1443
- }
1444
- });
1445
- closeBtn.addEventListener("click", () => this.hideStyleDialog());
1446
- this.styleDialog.appendChild(closeBtn);
1447
- this.handlers.styleDialogKeydown = (e) => {
1448
- if (!this.styleDialogVisible) return;
1449
- if (e.key === "Escape") {
1450
- e.preventDefault();
1451
- e.stopPropagation();
1452
- this.hideStyleDialog();
1453
- return;
1454
- }
1455
- if (e.key === "Tab") {
1456
- const focusableElements = this.styleDialog.querySelectorAll(
1457
- "select, input, button"
1458
- );
1459
- const firstElement = focusableElements[0];
1460
- const lastElement = focusableElements[focusableElements.length - 1];
1461
- if (e.shiftKey && document.activeElement === firstElement) {
1462
- e.preventDefault();
1463
- lastElement.focus({ preventScroll: true });
1464
- } else if (!e.shiftKey && document.activeElement === lastElement) {
1465
- e.preventDefault();
1466
- firstElement.focus({ preventScroll: true });
1467
- }
1468
- }
1469
- };
1470
- document.addEventListener("keydown", this.handlers.styleDialogKeydown);
1471
- if (this.headerLeft) {
1472
- this.headerLeft.appendChild(this.styleDialog);
1473
- } else {
1474
- this.transcriptHeader.appendChild(this.styleDialog);
1475
- }
1476
- this.applyTranscriptStyles();
1477
- this.styleDialogVisible = true;
1478
- this.styleDialog.style.display = "block";
1479
- this.styleDialogJustOpened = true;
1480
- setTimeout(() => {
1481
- this.styleDialogJustOpened = false;
1482
- }, 350);
1483
- setTimeout(() => {
1484
- const firstSelect = this.styleDialog.querySelector("select, input");
1485
- if (firstSelect) {
1486
- firstSelect.focus({ preventScroll: true });
1487
- }
1488
- }, 0);
1489
- }
1490
- /**
1491
- * Hide style dialog
1492
- */
1493
- hideStyleDialog() {
1494
- if (this.styleDialog) {
1495
- this.styleDialog.style.display = "none";
1496
- this.styleDialogVisible = false;
1497
- if (this.handlers.styleDialogKeydown) {
1498
- document.removeEventListener("keydown", this.handlers.styleDialogKeydown);
1499
- }
1500
- if (this.settingsButton) {
1501
- this.settingsButton.focus({ preventScroll: true });
1502
- }
1503
- }
1504
- }
1505
- /**
1506
- * Create style select control
1507
- */
1508
- createStyleSelectControl(label, property, options) {
1509
- const group = DOMUtils.createElement("div", {
1510
- className: `${this.player.options.classPrefix}-transcript-style-group`
1511
- });
1512
- const controlId = `${this.player.options.classPrefix}-transcript-${property}-${Date.now()}`;
1513
- const labelEl = DOMUtils.createElement("label", {
1514
- textContent: label,
1515
- attributes: {
1516
- "for": controlId
1517
- }
1518
- });
1519
- group.appendChild(labelEl);
1520
- const select = DOMUtils.createElement("select", {
1521
- className: `${this.player.options.classPrefix}-transcript-style-select`,
1522
- attributes: {
1523
- "id": controlId
1524
- }
1525
- });
1526
- options.forEach((opt) => {
1527
- const option = DOMUtils.createElement("option", {
1528
- textContent: opt.label,
1529
- attributes: {
1530
- "value": opt.value
1531
- }
1532
- });
1533
- if (this.transcriptStyle[property] === opt.value) {
1534
- option.selected = true;
1535
- }
1536
- select.appendChild(option);
1537
- });
1538
- select.addEventListener("change", (e) => {
1539
- this.transcriptStyle[property] = e.target.value;
1540
- this.applyTranscriptStyles();
1541
- this.savePreferences();
1542
- });
1543
- group.appendChild(select);
1544
- return group;
1545
- }
1546
- /**
1547
- * Create style color control
1548
- */
1549
- createStyleColorControl(label, property) {
1550
- const group = DOMUtils.createElement("div", {
1551
- className: `${this.player.options.classPrefix}-transcript-style-group`
1552
- });
1553
- const controlId = `${this.player.options.classPrefix}-transcript-${property}-${Date.now()}`;
1554
- const labelEl = DOMUtils.createElement("label", {
1555
- textContent: label,
1556
- attributes: {
1557
- "for": controlId
1558
- }
1559
- });
1560
- group.appendChild(labelEl);
1561
- const input = DOMUtils.createElement("input", {
1562
- attributes: {
1563
- "id": controlId,
1564
- "type": "color",
1565
- "value": this.transcriptStyle[property]
1566
- },
1567
- className: `${this.player.options.classPrefix}-transcript-style-color`
1568
- });
1569
- input.addEventListener("input", (e) => {
1570
- this.transcriptStyle[property] = e.target.value;
1571
- this.applyTranscriptStyles();
1572
- this.savePreferences();
1573
- });
1574
- group.appendChild(input);
1575
- return group;
1576
- }
1577
- /**
1578
- * Create style opacity control
1579
- */
1580
- createStyleOpacityControl(label, property) {
1581
- const group = DOMUtils.createElement("div", {
1582
- className: `${this.player.options.classPrefix}-transcript-style-group`
1583
- });
1584
- const controlId = `${this.player.options.classPrefix}-transcript-${property}-${Date.now()}`;
1585
- const labelEl = DOMUtils.createElement("label", {
1586
- textContent: label,
1587
- attributes: {
1588
- "for": controlId
1589
- }
1590
- });
1591
- group.appendChild(labelEl);
1592
- const valueDisplay = DOMUtils.createElement("span", {
1593
- textContent: Math.round(this.transcriptStyle[property] * 100) + "%",
1594
- className: `${this.player.options.classPrefix}-transcript-style-value`
1595
- });
1596
- const input = DOMUtils.createElement("input", {
1597
- attributes: {
1598
- "id": controlId,
1599
- "type": "range",
1600
- "min": "0",
1601
- "max": "1",
1602
- "step": "0.1",
1603
- "value": String(this.transcriptStyle[property])
1604
- },
1605
- className: `${this.player.options.classPrefix}-transcript-style-range`
1606
- });
1607
- input.addEventListener("input", (e) => {
1608
- const value = parseFloat(e.target.value);
1609
- this.transcriptStyle[property] = value;
1610
- valueDisplay.textContent = Math.round(value * 100) + "%";
1611
- this.applyTranscriptStyles();
1612
- this.savePreferences();
1613
- });
1614
- const inputContainer = DOMUtils.createElement("div", {
1615
- className: `${this.player.options.classPrefix}-transcript-style-range-container`
1616
- });
1617
- inputContainer.appendChild(input);
1618
- inputContainer.appendChild(valueDisplay);
1619
- group.appendChild(labelEl);
1620
- group.appendChild(inputContainer);
1621
- return group;
1622
- }
1623
- /**
1624
- * Save transcript preferences to localStorage
1625
- */
1626
- savePreferences() {
1627
- this.storage.saveTranscriptPreferences(this.transcriptStyle);
1628
- }
1629
- /**
1630
- * Apply transcript styles
1631
- */
1632
- applyTranscriptStyles() {
1633
- if (!this.transcriptWindow) return;
1634
- this.transcriptWindow.style.backgroundColor = this.transcriptStyle.backgroundColor;
1635
- this.transcriptWindow.style.opacity = String(this.transcriptStyle.opacity);
1636
- if (this.transcriptContent) {
1637
- this.transcriptContent.style.fontSize = this.transcriptStyle.fontSize;
1638
- this.transcriptContent.style.fontFamily = this.transcriptStyle.fontFamily;
1639
- this.transcriptContent.style.color = this.transcriptStyle.color;
1640
- }
1641
- const textEntries = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-text`);
1642
- textEntries.forEach((entry) => {
1643
- entry.style.fontSize = this.transcriptStyle.fontSize;
1644
- entry.style.fontFamily = this.transcriptStyle.fontFamily;
1645
- entry.style.color = this.transcriptStyle.color;
1646
- });
1647
- const timeEntries = this.transcriptWindow.querySelectorAll(`.${this.player.options.classPrefix}-transcript-time`);
1648
- timeEntries.forEach((entry) => {
1649
- entry.style.fontFamily = this.transcriptStyle.fontFamily;
1650
- });
1651
- }
1652
- /**
1653
- * Set a managed timeout that will be cleaned up on destroy
1654
- * @param {Function} callback - Callback function
1655
- * @param {number} delay - Delay in milliseconds
1656
- * @returns {number} Timeout ID
1657
- */
1658
- setManagedTimeout(callback, delay) {
1659
- const timeoutId = setTimeout(() => {
1660
- this.timeouts.delete(timeoutId);
1661
- callback();
1662
- }, delay);
1663
- this.timeouts.add(timeoutId);
1664
- return timeoutId;
1665
- }
1666
- /**
1667
- * Clear a managed timeout
1668
- * @param {number} timeoutId - Timeout ID to clear
1669
- */
1670
- clearManagedTimeout(timeoutId) {
1671
- if (timeoutId) {
1672
- clearTimeout(timeoutId);
1673
- this.timeouts.delete(timeoutId);
1674
- }
1675
- }
1676
- /**
1677
- * Cleanup
1678
- */
1679
- destroy() {
1680
- this.hideResizeModeIndicator();
1681
- if (this.draggableResizable) {
1682
- if (this.draggableResizable.pointerResizeMode) {
1683
- this.draggableResizable.disablePointerResizeMode();
1684
- this.updateResizeOptionState();
1685
- }
1686
- this.draggableResizable.destroy();
1687
- this.draggableResizable = null;
1688
- }
1689
- if (this.transcriptWindow && this.customKeyHandler) {
1690
- this.transcriptWindow.removeEventListener("keydown", this.customKeyHandler);
1691
- this.customKeyHandler = null;
1692
- }
1693
- if (this.handlers.timeupdate) {
1694
- this.player.off("timeupdate", this.handlers.timeupdate);
1695
- }
1696
- if (this.handlers.audiodescriptionenabled) {
1697
- this.player.off("audiodescriptionenabled", this.handlers.audiodescriptionenabled);
1698
- }
1699
- if (this.handlers.audiodescriptiondisabled) {
1700
- this.player.off("audiodescriptiondisabled", this.handlers.audiodescriptiondisabled);
1701
- }
1702
- if (this.settingsButton) {
1703
- if (this.handlers.settingsClick) {
1704
- this.settingsButton.removeEventListener("click", this.handlers.settingsClick);
1705
- }
1706
- if (this.handlers.settingsKeydown) {
1707
- this.settingsButton.removeEventListener("keydown", this.handlers.settingsKeydown);
1708
- }
1709
- }
1710
- if (this.handlers.styleDialogKeydown) {
1711
- document.removeEventListener("keydown", this.handlers.styleDialogKeydown);
1712
- }
1713
- if (this.handlers.documentClick) {
1714
- document.removeEventListener("click", this.handlers.documentClick);
1715
- }
1716
- if (this.handlers.resize) {
1717
- window.removeEventListener("resize", this.handlers.resize);
1718
- }
1719
- this.timeouts.forEach((timeoutId) => clearTimeout(timeoutId));
1720
- this.timeouts.clear();
1721
- this.handlers = null;
1722
- if (this.transcriptWindow && this.transcriptWindow.parentNode) {
1723
- this.transcriptWindow.parentNode.removeChild(this.transcriptWindow);
1724
- }
1725
- this.transcriptWindow = null;
1726
- this.transcriptHeader = null;
1727
- this.transcriptContent = null;
1728
- this.transcriptEntries = [];
1729
- this.settingsMenu = null;
1730
- this.styleDialog = null;
1731
- this.transcriptResizeHandles = [];
1732
- this.resizeOptionButton = null;
1733
- this.resizeOptionText = null;
1734
- this.liveRegion = null;
1735
- }
1736
- announceLive(message) {
1737
- if (!this.liveRegion) return;
1738
- this.liveRegion.textContent = message || "";
1739
- }
1740
- };
1741
- export {
1742
- TranscriptManager
1743
- };
1744
- //# sourceMappingURL=vidply.TranscriptManager-UTJBQC5B.js.map