vidply 1.0.28 → 1.0.29

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 (61) hide show
  1. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
  2. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
  3. package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
  4. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
  5. package/dist/dev/vidply.esm.js +1674 -310
  6. package/dist/dev/vidply.esm.js.map +4 -4
  7. package/dist/legacy/vidply.js +1776 -348
  8. package/dist/legacy/vidply.js.map +4 -4
  9. package/dist/legacy/vidply.min.js +1 -1
  10. package/dist/legacy/vidply.min.meta.json +92 -24
  11. package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
  12. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
  13. package/dist/prod/vidply.esm.min.js +8 -8
  14. package/dist/vidply.esm.min.meta.json +92 -24
  15. package/package.json +1 -1
  16. package/src/controls/ControlBar.js +3 -7
  17. package/src/controls/TranscriptManager.js +7 -7
  18. package/src/core/AudioDescriptionManager.js +701 -0
  19. package/src/core/Player.js +4776 -4921
  20. package/src/core/SignLanguageManager.js +1134 -0
  21. package/src/utils/DOMUtils.js +153 -114
  22. package/src/utils/MenuFactory.js +374 -0
  23. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
  24. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
  25. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
  26. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
  27. package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
  28. package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
  29. package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
  30. package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
  31. package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
  32. package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
  33. package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
  34. package/dist/dev/vidply.de-THBIMP4S.js +0 -180
  35. package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
  36. package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
  37. package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
  38. package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
  39. package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
  40. package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
  41. package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
  42. package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
  43. package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
  44. package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
  45. package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
  46. package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
  47. package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
  48. package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
  49. package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
  50. package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
  51. package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
  52. package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
  53. package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
  54. package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
  55. package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
  56. package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
  57. package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
  58. package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
  59. package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
  60. package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
  61. package/dist/prod/vidply.ja-QTVU5C25.min.js +0 -6
@@ -34,15 +34,21 @@
34
34
  var init_DOMUtils = __esm({
35
35
  "src/utils/DOMUtils.js"() {
36
36
  DOMUtils = {
37
+ /**
38
+ * Create an element with options
39
+ * @param {string} tag - HTML tag name
40
+ * @param {Object} options - Element options
41
+ * @returns {HTMLElement}
42
+ */
37
43
  createElement(tag, options = {}) {
38
44
  const element = document.createElement(tag);
39
45
  if (options.className) {
40
46
  element.className = options.className;
41
47
  }
42
48
  if (options.attributes) {
43
- Object.entries(options.attributes).forEach(([key, value]) => {
49
+ for (const [key, value] of Object.entries(options.attributes)) {
44
50
  element.setAttribute(key, value);
45
- });
51
+ }
46
52
  }
47
53
  if (options.innerHTML) {
48
54
  element.innerHTML = options.innerHTML;
@@ -54,150 +60,190 @@
54
60
  Object.assign(element.style, options.style);
55
61
  }
56
62
  if (options.children) {
57
- options.children.forEach((child) => {
63
+ for (const child of options.children) {
58
64
  if (child) element.appendChild(child);
59
- });
65
+ }
60
66
  }
61
67
  return element;
62
68
  },
63
- addClass(element, className) {
64
- if (element && className) {
65
- element.classList.add(className);
66
- }
67
- },
68
- removeClass(element, className) {
69
- if (element && className) {
70
- element.classList.remove(className);
71
- }
72
- },
73
- toggleClass(element, className) {
74
- if (element && className) {
75
- element.classList.toggle(className);
76
- }
77
- },
78
- hasClass(element, className) {
79
- return element && element.classList.contains(className);
80
- },
69
+ /**
70
+ * Show element (remove display:none)
71
+ * @param {HTMLElement} element
72
+ */
81
73
  show(element) {
82
- if (element) {
83
- element.style.display = "";
84
- }
74
+ (element == null ? void 0 : element.style) && (element.style.display = "");
85
75
  },
76
+ /**
77
+ * Hide element
78
+ * @param {HTMLElement} element
79
+ */
86
80
  hide(element) {
87
- if (element) {
88
- element.style.display = "none";
89
- }
81
+ (element == null ? void 0 : element.style) && (element.style.display = "none");
90
82
  },
91
- fadeIn(element, duration = 300) {
83
+ /**
84
+ * Fade in element using CSS transitions (GPU accelerated)
85
+ * @param {HTMLElement} element
86
+ * @param {number} duration - Duration in ms
87
+ * @param {Function} [onComplete] - Callback when complete
88
+ */
89
+ fadeIn(element, duration = 300, onComplete) {
92
90
  if (!element) return;
93
91
  element.style.opacity = "0";
94
92
  element.style.display = "";
95
- let start = null;
96
- const animate = (timestamp) => {
97
- if (!start) start = timestamp;
98
- const progress = timestamp - start;
99
- const opacity = Math.min(progress / duration, 1);
100
- element.style.opacity = opacity;
101
- if (progress < duration) {
102
- requestAnimationFrame(animate);
103
- }
104
- };
105
- requestAnimationFrame(animate);
93
+ element.style.transition = "opacity ".concat(duration, "ms ease");
94
+ element.offsetHeight;
95
+ element.style.opacity = "1";
96
+ if (onComplete) {
97
+ const cleanup = () => {
98
+ element.removeEventListener("transitionend", cleanup);
99
+ onComplete();
100
+ };
101
+ element.addEventListener("transitionend", cleanup, { once: true });
102
+ setTimeout(cleanup, duration + 50);
103
+ }
106
104
  },
107
- fadeOut(element, duration = 300) {
105
+ /**
106
+ * Fade out element using CSS transitions (GPU accelerated)
107
+ * @param {HTMLElement} element
108
+ * @param {number} duration - Duration in ms
109
+ * @param {Function} [onComplete] - Callback when complete
110
+ */
111
+ fadeOut(element, duration = 300, onComplete) {
108
112
  if (!element) return;
109
- const startOpacity = parseFloat(getComputedStyle(element).opacity) || 1;
110
- let start = null;
111
- const animate = (timestamp) => {
112
- if (!start) start = timestamp;
113
- const progress = timestamp - start;
114
- const opacity = Math.max(startOpacity - progress / duration, 0);
115
- element.style.opacity = opacity;
116
- if (progress < duration) {
117
- requestAnimationFrame(animate);
118
- } else {
119
- element.style.display = "none";
120
- }
113
+ element.style.transition = "opacity ".concat(duration, "ms ease");
114
+ element.style.opacity = "0";
115
+ const cleanup = () => {
116
+ element.removeEventListener("transitionend", cleanup);
117
+ element.style.display = "none";
118
+ if (onComplete) onComplete();
121
119
  };
122
- requestAnimationFrame(animate);
120
+ element.addEventListener("transitionend", cleanup, { once: true });
121
+ setTimeout(cleanup, duration + 50);
123
122
  },
123
+ /**
124
+ * Get element's offset position and dimensions
125
+ * @param {HTMLElement} element
126
+ * @returns {Object} { top, left, width, height }
127
+ */
124
128
  offset(element) {
125
- if (!element) return { top: 0, left: 0 };
129
+ if (!element) return { top: 0, left: 0, width: 0, height: 0 };
126
130
  const rect = element.getBoundingClientRect();
127
131
  return {
128
- top: rect.top + window.pageYOffset,
129
- left: rect.left + window.pageXOffset,
132
+ top: rect.top + window.scrollY,
133
+ left: rect.left + window.scrollX,
130
134
  width: rect.width,
131
135
  height: rect.height
132
136
  };
133
137
  },
138
+ /**
139
+ * Escape HTML special characters
140
+ * @param {string} str - String to escape
141
+ * @returns {string} Escaped string
142
+ */
134
143
  escapeHTML(str) {
135
- const div = document.createElement("div");
136
- div.textContent = str;
137
- return div.innerHTML;
144
+ const escapeMap = {
145
+ "&": "&amp;",
146
+ "<": "&lt;",
147
+ ">": "&gt;",
148
+ '"': "&quot;",
149
+ "'": "&#x27;"
150
+ };
151
+ return str.replace(/[&<>"']/g, (char) => escapeMap[char]);
138
152
  },
153
+ /**
154
+ * Basic HTML sanitization for VTT captions
155
+ * Allows safe formatting tags, removes dangerous content
156
+ * @param {string} html - HTML string to sanitize
157
+ * @returns {string} Sanitized HTML
158
+ */
139
159
  sanitizeHTML(html) {
140
- const temp = document.createElement("div");
141
160
  const safeHtml = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "").replace(/on\w+\s*=/gi, "").replace(/javascript:/gi, "");
161
+ const temp = document.createElement("div");
142
162
  temp.innerHTML = safeHtml;
143
163
  return temp.innerHTML;
144
164
  },
145
165
  /**
146
- * Create a tooltip element that is aria-hidden (not read by screen readers)
166
+ * Create a tooltip element (aria-hidden)
147
167
  * @param {string} text - Tooltip text
148
- * @param {string} classPrefix - Class prefix for styling
149
- * @returns {HTMLElement} Tooltip element
168
+ * @param {string} classPrefix - Class prefix
169
+ * @returns {HTMLElement}
150
170
  */
151
171
  createTooltip(text, classPrefix = "vidply") {
152
- const tooltip = this.createElement("span", {
172
+ return this.createElement("span", {
153
173
  className: "".concat(classPrefix, "-tooltip"),
154
174
  textContent: text,
155
- attributes: {
156
- "aria-hidden": "true"
157
- }
175
+ attributes: { "aria-hidden": "true" }
158
176
  });
159
- return tooltip;
160
177
  },
161
178
  /**
162
- * Attach a tooltip to an element
163
- * @param {HTMLElement} element - Element to attach tooltip to
179
+ * Attach a tooltip to an element with hover/focus behavior
180
+ * @param {HTMLElement} element - Target element
164
181
  * @param {string} text - Tooltip text
165
- * @param {string} classPrefix - Class prefix for styling
182
+ * @param {string} classPrefix - Class prefix
166
183
  */
167
184
  attachTooltip(element, text, classPrefix = "vidply") {
185
+ var _a;
168
186
  if (!element || !text) return;
169
- const existingTooltip = element.querySelector(".".concat(classPrefix, "-tooltip"));
170
- if (existingTooltip) {
171
- existingTooltip.remove();
172
- }
187
+ (_a = element.querySelector(".".concat(classPrefix, "-tooltip"))) == null ? void 0 : _a.remove();
173
188
  const tooltip = this.createTooltip(text, classPrefix);
174
189
  element.appendChild(tooltip);
175
- const showTooltip = () => {
176
- tooltip.classList.add("".concat(classPrefix, "-tooltip-visible"));
177
- };
178
- const hideTooltip = () => {
179
- tooltip.classList.remove("".concat(classPrefix, "-tooltip-visible"));
180
- };
181
- element.addEventListener("mouseenter", showTooltip);
182
- element.addEventListener("mouseleave", hideTooltip);
183
- element.addEventListener("focus", showTooltip);
184
- element.addEventListener("blur", hideTooltip);
190
+ const visibleClass = "".concat(classPrefix, "-tooltip-visible");
191
+ const show = () => tooltip.classList.add(visibleClass);
192
+ const hide = () => tooltip.classList.remove(visibleClass);
193
+ element.addEventListener("mouseenter", show);
194
+ element.addEventListener("mouseleave", hide);
195
+ element.addEventListener("focus", show);
196
+ element.addEventListener("blur", hide);
185
197
  },
186
198
  /**
187
- * Create visible button text that is hidden by CSS but visible when CSS is disabled
199
+ * Create button text element (visible when CSS disabled)
188
200
  * @param {string} text - Button text
189
- * @param {string} classPrefix - Class prefix for styling
190
- * @returns {HTMLElement} Button text element
201
+ * @param {string} classPrefix - Class prefix
202
+ * @returns {HTMLElement}
191
203
  */
192
204
  createButtonText(text, classPrefix = "vidply") {
193
- const buttonText = this.createElement("span", {
205
+ return this.createElement("span", {
194
206
  className: "".concat(classPrefix, "-button-text"),
195
207
  textContent: text,
196
- attributes: {
197
- "aria-hidden": "true"
198
- }
208
+ attributes: { "aria-hidden": "true" }
199
209
  });
200
- return buttonText;
210
+ },
211
+ /**
212
+ * Add class to element (null-safe)
213
+ * @param {HTMLElement} element
214
+ * @param {string} className
215
+ */
216
+ addClass(element, className) {
217
+ var _a;
218
+ (_a = element == null ? void 0 : element.classList) == null ? void 0 : _a.add(className);
219
+ },
220
+ /**
221
+ * Remove class from element (null-safe)
222
+ * @param {HTMLElement} element
223
+ * @param {string} className
224
+ */
225
+ removeClass(element, className) {
226
+ var _a;
227
+ (_a = element == null ? void 0 : element.classList) == null ? void 0 : _a.remove(className);
228
+ },
229
+ /**
230
+ * Toggle class on element (null-safe)
231
+ * @param {HTMLElement} element
232
+ * @param {string} className
233
+ */
234
+ toggleClass(element, className) {
235
+ var _a;
236
+ (_a = element == null ? void 0 : element.classList) == null ? void 0 : _a.toggle(className);
237
+ },
238
+ /**
239
+ * Check if element has class (null-safe)
240
+ * @param {HTMLElement} element
241
+ * @param {string} className
242
+ * @returns {boolean}
243
+ */
244
+ hasClass(element, className) {
245
+ var _a, _b;
246
+ return (_b = (_a = element == null ? void 0 : element.classList) == null ? void 0 : _a.contains(className)) != null ? _b : false;
201
247
  }
202
248
  };
203
249
  }
@@ -3268,8 +3314,7 @@
3268
3314
  descriptionTrack = textTracks.find((track) => track.kind === "descriptions");
3269
3315
  }
3270
3316
  const metadataTrack = textTracks.find((track) => track.kind === "metadata");
3271
- const hasDescriptionTrack = descriptionTrack && this.player.state.audioDescriptionEnabled;
3272
- if (!captionTrack && !hasDescriptionTrack && !metadataTrack) {
3317
+ if (!captionTrack && !descriptionTrack && !metadataTrack) {
3273
3318
  this.showNoTranscriptMessage();
3274
3319
  return;
3275
3320
  }
@@ -3307,7 +3352,7 @@
3307
3352
  allCues.push({ cue, type: "caption" });
3308
3353
  });
3309
3354
  }
3310
- if (descriptionTrack && descriptionTrack.cues && this.player.state.audioDescriptionEnabled) {
3355
+ if (descriptionTrack && descriptionTrack.cues) {
3311
3356
  Array.from(descriptionTrack.cues).forEach((cue) => {
3312
3357
  allCues.push({ cue, type: "description" });
3313
3358
  });
@@ -5501,6 +5546,35 @@
5501
5546
  init_Icons();
5502
5547
  init_i18n();
5503
5548
  init_FocusUtils();
5549
+
5550
+ // src/utils/PerformanceUtils.js
5551
+ function debounce(func, wait = 100) {
5552
+ let timeout;
5553
+ return function executedFunction(...args) {
5554
+ const later = () => {
5555
+ clearTimeout(timeout);
5556
+ func(...args);
5557
+ };
5558
+ clearTimeout(timeout);
5559
+ timeout = setTimeout(later, wait);
5560
+ };
5561
+ }
5562
+ function isMobile(breakpoint = 768) {
5563
+ return window.innerWidth < breakpoint;
5564
+ }
5565
+ function rafWithTimeout(callback, timeout = 100) {
5566
+ let called = false;
5567
+ const execute = () => {
5568
+ if (!called) {
5569
+ called = true;
5570
+ callback();
5571
+ }
5572
+ };
5573
+ requestAnimationFrame(execute);
5574
+ setTimeout(execute, timeout);
5575
+ }
5576
+
5577
+ // src/controls/ControlBar.js
5504
5578
  var ControlBar = class {
5505
5579
  constructor(player) {
5506
5580
  this.player = player;
@@ -5520,17 +5594,13 @@
5520
5594
  this.setupAutoHide();
5521
5595
  this.setupOverflowDetection();
5522
5596
  }
5523
- // Helper method to check if we're on a mobile device
5524
- isMobile() {
5525
- return window.innerWidth < 768;
5526
- }
5527
5597
  // Helper method to detect touch devices
5528
5598
  isTouchDevice() {
5529
5599
  return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
5530
5600
  }
5531
5601
  // Smart menu positioning to avoid overflow
5532
5602
  positionMenu(menu, button, immediate = false) {
5533
- const isMobile2 = this.isMobile();
5603
+ const mobile = isMobile();
5534
5604
  const isOverflowMenu = menu.classList.contains("".concat(this.player.options.classPrefix, "-overflow-menu-list"));
5535
5605
  const isFullscreen = this.player.state.fullscreen;
5536
5606
  if (isFullscreen && menu.parentElement === this.player.container) {
@@ -5571,7 +5641,7 @@
5571
5641
  }
5572
5642
  return;
5573
5643
  }
5574
- if (isMobile2) {
5644
+ if (mobile) {
5575
5645
  const isVolumeMenu = menu.classList.contains("".concat(this.player.options.classPrefix, "-volume-menu"));
5576
5646
  const doMobilePositioning = () => {
5577
5647
  const parentContainer = button.parentElement;
@@ -7944,35 +8014,6 @@
7944
8014
  init_DOMUtils();
7945
8015
  init_i18n();
7946
8016
  init_StorageManager();
7947
-
7948
- // src/utils/PerformanceUtils.js
7949
- function debounce(func, wait = 100) {
7950
- let timeout;
7951
- return function executedFunction(...args) {
7952
- const later = () => {
7953
- clearTimeout(timeout);
7954
- func(...args);
7955
- };
7956
- clearTimeout(timeout);
7957
- timeout = setTimeout(later, wait);
7958
- };
7959
- }
7960
- function isMobile(breakpoint = 768) {
7961
- return window.innerWidth < breakpoint;
7962
- }
7963
- function rafWithTimeout(callback, timeout = 100) {
7964
- let called = false;
7965
- const execute = () => {
7966
- if (!called) {
7967
- called = true;
7968
- callback();
7969
- }
7970
- };
7971
- requestAnimationFrame(execute);
7972
- setTimeout(execute, timeout);
7973
- }
7974
-
7975
- // src/controls/CaptionManager.js
7976
8017
  var CaptionManager = class {
7977
8018
  constructor(player) {
7978
8019
  this.player = player;
@@ -8504,37 +8545,1511 @@
8504
8545
  init_DraggableResizable();
8505
8546
  init_MenuUtils();
8506
8547
  init_FormUtils();
8507
- var playerInstanceCounter = 0;
8508
- var Player = class _Player extends EventEmitter {
8509
- constructor(element, options = {}) {
8510
- super();
8511
- this.element = typeof element === "string" ? document.querySelector(element) : element;
8512
- if (!this.element) {
8513
- throw new Error("VidPly: Element not found");
8548
+
8549
+ // src/core/AudioDescriptionManager.js
8550
+ var AudioDescriptionManager = class {
8551
+ constructor(player) {
8552
+ this.player = player;
8553
+ this.enabled = false;
8554
+ this.desiredState = false;
8555
+ this.src = player.options.audioDescriptionSrc;
8556
+ this.sourceElement = null;
8557
+ this.originalSource = null;
8558
+ this.captionTracks = [];
8559
+ }
8560
+ /**
8561
+ * Initialize audio description from source elements
8562
+ * Called during player initialization
8563
+ */
8564
+ initFromSourceElements(sourceElements, trackElements) {
8565
+ for (const sourceEl of sourceElements) {
8566
+ const descSrc = sourceEl.getAttribute("data-desc-src");
8567
+ const origSrc = sourceEl.getAttribute("data-orig-src");
8568
+ if (descSrc || origSrc) {
8569
+ if (!this.sourceElement) {
8570
+ this.sourceElement = sourceEl;
8571
+ }
8572
+ if (origSrc) {
8573
+ if (!this.originalSource) {
8574
+ this.originalSource = origSrc;
8575
+ }
8576
+ if (!this.player.originalSrc) {
8577
+ this.player.originalSrc = origSrc;
8578
+ }
8579
+ } else {
8580
+ const currentSrcAttr = sourceEl.getAttribute("src");
8581
+ if (!this.originalSource && currentSrcAttr) {
8582
+ this.originalSource = currentSrcAttr;
8583
+ }
8584
+ if (!this.player.originalSrc && currentSrcAttr) {
8585
+ this.player.originalSrc = currentSrcAttr;
8586
+ }
8587
+ }
8588
+ if (descSrc && !this.src) {
8589
+ this.src = descSrc;
8590
+ }
8591
+ }
8514
8592
  }
8515
- playerInstanceCounter++;
8516
- this.instanceId = playerInstanceCounter;
8517
- if (this.element.tagName !== "VIDEO" && this.element.tagName !== "AUDIO") {
8518
- const mediaType = options.mediaType || "video";
8519
- const mediaElement = document.createElement(mediaType);
8520
- Array.from(this.element.attributes).forEach((attr) => {
8521
- if (attr.name !== "id" && attr.name !== "class" && !attr.name.startsWith("data-")) {
8522
- mediaElement.setAttribute(attr.name, attr.value);
8593
+ trackElements.forEach((trackEl) => {
8594
+ const trackKind = trackEl.getAttribute("kind");
8595
+ const trackDescSrc = trackEl.getAttribute("data-desc-src");
8596
+ if ((trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters" || trackKind === "descriptions") && trackDescSrc) {
8597
+ this.captionTracks.push({
8598
+ trackElement: trackEl,
8599
+ originalSrc: trackEl.getAttribute("src"),
8600
+ describedSrc: trackDescSrc,
8601
+ originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
8602
+ explicit: true
8603
+ });
8604
+ this.player.log("Found explicit described ".concat(trackKind, " track: ").concat(trackEl.getAttribute("src"), " -> ").concat(trackDescSrc));
8605
+ }
8606
+ });
8607
+ }
8608
+ /**
8609
+ * Check if audio description is available
8610
+ */
8611
+ isAvailable() {
8612
+ const hasSourceElementsWithDesc = this.player.sourceElements.some(
8613
+ (el) => el.getAttribute("data-desc-src")
8614
+ );
8615
+ return !!(this.src || hasSourceElementsWithDesc || this.captionTracks.length > 0);
8616
+ }
8617
+ /**
8618
+ * Enable audio description
8619
+ */
8620
+ async enable() {
8621
+ const hasSourceElementsWithDesc = this.player.sourceElements.some(
8622
+ (el) => el.getAttribute("data-desc-src")
8623
+ );
8624
+ const hasTracksWithDesc = this.captionTracks.length > 0;
8625
+ if (!this.src && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
8626
+ console.warn("VidPly: No audio description source, source elements, or tracks provided");
8627
+ return;
8628
+ }
8629
+ this.desiredState = true;
8630
+ const currentTime = this.player.state.currentTime;
8631
+ const wasPlaying = this.player.state.playing;
8632
+ const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
8633
+ const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
8634
+ const currentCaptionText = this._getCurrentCaptionText();
8635
+ if (this.sourceElement) {
8636
+ await this._enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
8637
+ } else {
8638
+ await this._enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster);
8639
+ }
8640
+ }
8641
+ /**
8642
+ * Disable audio description
8643
+ */
8644
+ async disable() {
8645
+ if (!this.player.originalSrc) {
8646
+ return;
8647
+ }
8648
+ this.desiredState = false;
8649
+ const currentTime = this.player.state.currentTime;
8650
+ const wasPlaying = this.player.state.playing;
8651
+ const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
8652
+ const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
8653
+ const currentCaptionText = this._getCurrentCaptionText();
8654
+ if (this.sourceElement) {
8655
+ await this._disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
8656
+ } else {
8657
+ await this._disableWithDirectSrc(currentTime, wasPlaying, posterValue);
8658
+ }
8659
+ }
8660
+ /**
8661
+ * Toggle audio description
8662
+ */
8663
+ async toggle() {
8664
+ const descriptionTrack = this.player.findTextTrack("descriptions");
8665
+ const hasAudioDescriptionSrc = this.isAvailable();
8666
+ if (descriptionTrack && !hasAudioDescriptionSrc) {
8667
+ if (descriptionTrack.mode === "showing") {
8668
+ descriptionTrack.mode = "hidden";
8669
+ this.enabled = false;
8670
+ this.player.emit("audiodescriptiondisabled");
8671
+ } else {
8672
+ descriptionTrack.mode = "showing";
8673
+ this.enabled = true;
8674
+ this.player.emit("audiodescriptionenabled");
8675
+ }
8676
+ } else if (descriptionTrack && hasAudioDescriptionSrc) {
8677
+ if (this.enabled) {
8678
+ this.desiredState = false;
8679
+ await this.disable();
8680
+ } else {
8681
+ descriptionTrack.mode = "showing";
8682
+ this.desiredState = true;
8683
+ await this.enable();
8684
+ }
8685
+ } else if (hasAudioDescriptionSrc) {
8686
+ if (this.enabled) {
8687
+ this.desiredState = false;
8688
+ await this.disable();
8689
+ } else {
8690
+ this.desiredState = true;
8691
+ await this.enable();
8692
+ }
8693
+ }
8694
+ }
8695
+ /**
8696
+ * Get current caption text for synchronization
8697
+ */
8698
+ _getCurrentCaptionText() {
8699
+ if (this.player.captionManager && this.player.captionManager.currentTrack && this.player.captionManager.currentCue) {
8700
+ return this.player.captionManager.currentCue.text;
8701
+ }
8702
+ return null;
8703
+ }
8704
+ /**
8705
+ * Validate that a track URL exists
8706
+ */
8707
+ async _validateTrackExists(url) {
8708
+ try {
8709
+ const response = await fetch(url, { method: "HEAD" });
8710
+ return response.ok;
8711
+ } catch (e) {
8712
+ return false;
8713
+ }
8714
+ }
8715
+ /**
8716
+ * Swap caption tracks to described versions
8717
+ */
8718
+ async _swapCaptionTracks(toDescribed = true) {
8719
+ if (this.captionTracks.length === 0) return [];
8720
+ const swappedTracks = [];
8721
+ const validationPromises = this.captionTracks.map(async (trackInfo) => {
8722
+ if (trackInfo.trackElement && trackInfo.describedSrc) {
8723
+ if (trackInfo.explicit === true) {
8724
+ try {
8725
+ const exists = await this._validateTrackExists(
8726
+ toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc
8727
+ );
8728
+ return { trackInfo, exists };
8729
+ } catch (e) {
8730
+ return { trackInfo, exists: false };
8731
+ }
8732
+ }
8733
+ }
8734
+ return { trackInfo, exists: false };
8735
+ });
8736
+ const validationResults = await Promise.all(validationPromises);
8737
+ const tracksToSwap = validationResults.filter((result) => result.exists);
8738
+ if (tracksToSwap.length > 0) {
8739
+ const trackModes = /* @__PURE__ */ new Map();
8740
+ tracksToSwap.forEach(({ trackInfo }) => {
8741
+ const textTrack = trackInfo.trackElement.track;
8742
+ if (textTrack) {
8743
+ trackModes.set(trackInfo, {
8744
+ wasShowing: textTrack.mode === "showing",
8745
+ wasHidden: textTrack.mode === "hidden"
8746
+ });
8747
+ } else {
8748
+ trackModes.set(trackInfo, { wasShowing: false, wasHidden: false });
8523
8749
  }
8524
8750
  });
8525
- const tracks = this.element.querySelectorAll("track");
8526
- tracks.forEach((track) => {
8527
- mediaElement.appendChild(track.cloneNode(true));
8751
+ const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
8752
+ const attributes = {};
8753
+ Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
8754
+ attributes[attr.name] = attr.value;
8755
+ });
8756
+ const result = {
8757
+ trackInfo,
8758
+ oldSrc: trackInfo.trackElement.getAttribute("src"),
8759
+ parent: trackInfo.trackElement.parentNode,
8760
+ nextSibling: trackInfo.trackElement.nextSibling,
8761
+ attributes
8762
+ };
8763
+ trackInfo.trackElement.remove();
8764
+ return result;
8528
8765
  });
8529
- this.element.innerHTML = "";
8530
- this.element.appendChild(mediaElement);
8531
- this.element = mediaElement;
8532
- }
8533
- this._originalElement = this.element;
8534
- this.options = __spreadValues({
8535
- // Display
8536
- width: null,
8537
- height: null,
8766
+ this.player.element.load();
8767
+ await new Promise((resolve) => {
8768
+ setTimeout(() => {
8769
+ tracksToReadd.forEach(({ trackInfo, parent, nextSibling, attributes }) => {
8770
+ swappedTracks.push(trackInfo);
8771
+ const newTrackElement = document.createElement("track");
8772
+ const newSrc = toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc;
8773
+ newTrackElement.setAttribute("src", newSrc);
8774
+ Object.keys(attributes).forEach((attrName) => {
8775
+ if (attrName !== "src" && attrName !== "data-desc-src") {
8776
+ newTrackElement.setAttribute(attrName, attributes[attrName]);
8777
+ }
8778
+ });
8779
+ const targetParent = parent || this.player.element;
8780
+ if (nextSibling && nextSibling.parentNode) {
8781
+ targetParent.insertBefore(newTrackElement, nextSibling);
8782
+ } else {
8783
+ targetParent.appendChild(newTrackElement);
8784
+ }
8785
+ trackInfo.trackElement = newTrackElement;
8786
+ });
8787
+ this.player.invalidateTrackCache();
8788
+ const setupNewTracks = () => {
8789
+ this.player.setManagedTimeout(() => {
8790
+ swappedTracks.forEach((trackInfo) => {
8791
+ const newTextTrack = trackInfo.trackElement.track;
8792
+ if (newTextTrack) {
8793
+ const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
8794
+ newTextTrack.mode = "hidden";
8795
+ const restoreMode = () => {
8796
+ if (modeInfo.wasShowing || modeInfo.wasHidden) {
8797
+ newTextTrack.mode = "hidden";
8798
+ } else {
8799
+ newTextTrack.mode = "disabled";
8800
+ }
8801
+ };
8802
+ if (newTextTrack.readyState >= 2) {
8803
+ restoreMode();
8804
+ } else {
8805
+ newTextTrack.addEventListener("load", restoreMode, { once: true });
8806
+ newTextTrack.addEventListener("error", restoreMode, { once: true });
8807
+ }
8808
+ }
8809
+ });
8810
+ }, 300);
8811
+ };
8812
+ if (this.player.element.readyState >= 1) {
8813
+ setTimeout(setupNewTracks, 200);
8814
+ } else {
8815
+ this.player.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
8816
+ setTimeout(setupNewTracks, 2e3);
8817
+ }
8818
+ resolve();
8819
+ }, 100);
8820
+ });
8821
+ }
8822
+ return swappedTracks;
8823
+ }
8824
+ /**
8825
+ * Update source elements to described versions
8826
+ */
8827
+ _updateSourceElements(toDescribed = true) {
8828
+ const sourceElements = this.player.sourceElements;
8829
+ const sourcesToUpdate = [];
8830
+ sourceElements.forEach((sourceEl) => {
8831
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
8832
+ const currentSrc = sourceEl.getAttribute("src");
8833
+ if (descSrcAttr) {
8834
+ const type = sourceEl.getAttribute("type");
8835
+ let origSrc = sourceEl.getAttribute("data-orig-src") || currentSrc;
8836
+ sourcesToUpdate.push({
8837
+ src: toDescribed ? descSrcAttr : origSrc,
8838
+ type,
8839
+ origSrc,
8840
+ descSrc: descSrcAttr
8841
+ });
8842
+ } else {
8843
+ sourcesToUpdate.push({
8844
+ src: sourceEl.getAttribute("src"),
8845
+ type: sourceEl.getAttribute("type"),
8846
+ origSrc: null,
8847
+ descSrc: null
8848
+ });
8849
+ }
8850
+ });
8851
+ if (this.player.element.hasAttribute("src")) {
8852
+ this.player.element.removeAttribute("src");
8853
+ }
8854
+ sourceElements.forEach((sourceEl) => sourceEl.remove());
8855
+ sourcesToUpdate.forEach((sourceInfo) => {
8856
+ const newSource = document.createElement("source");
8857
+ newSource.setAttribute("src", sourceInfo.src);
8858
+ if (sourceInfo.type) {
8859
+ newSource.setAttribute("type", sourceInfo.type);
8860
+ }
8861
+ if (sourceInfo.origSrc) {
8862
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
8863
+ }
8864
+ if (sourceInfo.descSrc) {
8865
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
8866
+ }
8867
+ const firstTrack = this.player.element.querySelector("track");
8868
+ if (firstTrack) {
8869
+ this.player.element.insertBefore(newSource, firstTrack);
8870
+ } else {
8871
+ this.player.element.appendChild(newSource);
8872
+ }
8873
+ });
8874
+ this.player._sourceElementsDirty = true;
8875
+ this.player._sourceElementsCache = null;
8876
+ }
8877
+ /**
8878
+ * Wait for media to be ready
8879
+ */
8880
+ async _waitForMediaReady(needSeek = false) {
8881
+ await new Promise((resolve) => {
8882
+ if (this.player.element.readyState >= 1) {
8883
+ resolve();
8884
+ } else {
8885
+ const onLoad = () => {
8886
+ this.player.element.removeEventListener("loadedmetadata", onLoad);
8887
+ resolve();
8888
+ };
8889
+ this.player.element.addEventListener("loadedmetadata", onLoad);
8890
+ }
8891
+ });
8892
+ await new Promise((resolve) => setTimeout(resolve, 300));
8893
+ if (needSeek) {
8894
+ await new Promise((resolve) => {
8895
+ if (this.player.element.readyState >= 3) {
8896
+ resolve();
8897
+ } else {
8898
+ const onCanPlay = () => {
8899
+ this.player.element.removeEventListener("canplay", onCanPlay);
8900
+ this.player.element.removeEventListener("canplaythrough", onCanPlay);
8901
+ resolve();
8902
+ };
8903
+ this.player.element.addEventListener("canplay", onCanPlay, { once: true });
8904
+ this.player.element.addEventListener("canplaythrough", onCanPlay, { once: true });
8905
+ setTimeout(() => {
8906
+ this.player.element.removeEventListener("canplay", onCanPlay);
8907
+ this.player.element.removeEventListener("canplaythrough", onCanPlay);
8908
+ resolve();
8909
+ }, 3e3);
8910
+ }
8911
+ });
8912
+ }
8913
+ }
8914
+ /**
8915
+ * Restore playback state after source change
8916
+ */
8917
+ async _restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText) {
8918
+ let syncTime = currentTime;
8919
+ if (currentCaptionText && this.player.captionManager && this.player.captionManager.tracks.length > 0) {
8920
+ await new Promise((resolve) => setTimeout(resolve, 500));
8921
+ const matchingTime = this.player.findMatchingCaptionTime(
8922
+ currentCaptionText,
8923
+ this.player.captionManager.tracks
8924
+ );
8925
+ if (matchingTime !== null) {
8926
+ syncTime = matchingTime;
8927
+ if (this.player.options.debug) {
8928
+ this.player.log("[VidPly] Syncing via caption: ".concat(currentTime, "s -> ").concat(syncTime, "s"));
8929
+ }
8930
+ }
8931
+ }
8932
+ if (syncTime > 0) {
8933
+ this.player.seek(syncTime);
8934
+ await new Promise((resolve) => setTimeout(resolve, 100));
8935
+ }
8936
+ if (wasPlaying) {
8937
+ await this.player.play();
8938
+ this.player.setManagedTimeout(() => {
8939
+ this.player.hidePosterOverlay();
8940
+ }, 100);
8941
+ } else {
8942
+ this.player.pause();
8943
+ if (!shouldKeepPoster) {
8944
+ this.player.hidePosterOverlay();
8945
+ }
8946
+ }
8947
+ }
8948
+ /**
8949
+ * Enable with source element method
8950
+ */
8951
+ async _enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
8952
+ await this._swapCaptionTracks(true);
8953
+ this._updateSourceElements(true);
8954
+ if (posterValue && this.player.element.tagName === "VIDEO") {
8955
+ this.player.element.poster = posterValue;
8956
+ }
8957
+ this.player.element.load();
8958
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
8959
+ await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
8960
+ if (!this.desiredState) return;
8961
+ this.enabled = true;
8962
+ this.player.state.audioDescriptionEnabled = true;
8963
+ this.player.emit("audiodescriptionenabled");
8964
+ this._reloadTranscript();
8965
+ }
8966
+ /**
8967
+ * Enable with direct src method
8968
+ */
8969
+ async _enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster) {
8970
+ await this._swapCaptionTracks(true);
8971
+ if (posterValue && this.player.element.tagName === "VIDEO") {
8972
+ this.player.element.poster = posterValue;
8973
+ }
8974
+ this.player.element.src = this.src;
8975
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
8976
+ if (currentTime > 0) {
8977
+ this.player.seek(currentTime);
8978
+ await new Promise((resolve) => setTimeout(resolve, 100));
8979
+ }
8980
+ if (wasPlaying) {
8981
+ await this.player.play();
8982
+ } else {
8983
+ this.player.pause();
8984
+ if (!shouldKeepPoster) {
8985
+ this.player.hidePosterOverlay();
8986
+ }
8987
+ }
8988
+ if (!this.desiredState) return;
8989
+ this.enabled = true;
8990
+ this.player.state.audioDescriptionEnabled = true;
8991
+ this.player.emit("audiodescriptionenabled");
8992
+ this._reloadTranscript();
8993
+ }
8994
+ /**
8995
+ * Disable with source element method
8996
+ */
8997
+ async _disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
8998
+ await this._swapCaptionTracks(false);
8999
+ this._updateSourceElements(false);
9000
+ if (posterValue && this.player.element.tagName === "VIDEO") {
9001
+ this.player.element.poster = posterValue;
9002
+ }
9003
+ this.player.element.load();
9004
+ this.player.invalidateTrackCache();
9005
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
9006
+ await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
9007
+ if (this.player.captionManager) {
9008
+ this.player.captionManager.destroy();
9009
+ this.player.captionManager = new CaptionManager(this.player);
9010
+ }
9011
+ if (this.desiredState) return;
9012
+ this.enabled = false;
9013
+ this.player.state.audioDescriptionEnabled = false;
9014
+ this.player.emit("audiodescriptiondisabled");
9015
+ this._reloadTranscript();
9016
+ }
9017
+ /**
9018
+ * Disable with direct src method
9019
+ */
9020
+ async _disableWithDirectSrc(currentTime, wasPlaying, posterValue) {
9021
+ await this._swapCaptionTracks(false);
9022
+ if (posterValue && this.player.element.tagName === "VIDEO") {
9023
+ this.player.element.poster = posterValue;
9024
+ }
9025
+ const originalSrcToUse = this.originalSource || this.player.originalSrc;
9026
+ this.player.element.src = originalSrcToUse;
9027
+ this.player.element.load();
9028
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
9029
+ if (currentTime > 0) {
9030
+ this.player.seek(currentTime);
9031
+ }
9032
+ if (wasPlaying) {
9033
+ await this.player.play();
9034
+ }
9035
+ if (this.desiredState) return;
9036
+ this.enabled = false;
9037
+ this.player.state.audioDescriptionEnabled = false;
9038
+ this.player.emit("audiodescriptiondisabled");
9039
+ this._reloadTranscript();
9040
+ }
9041
+ /**
9042
+ * Reload transcript after audio description state change
9043
+ */
9044
+ _reloadTranscript() {
9045
+ if (this.player.transcriptManager && this.player.transcriptManager.isVisible) {
9046
+ this.player.setManagedTimeout(() => {
9047
+ if (this.player.transcriptManager && this.player.transcriptManager.loadTranscriptData) {
9048
+ this.player.transcriptManager.loadTranscriptData();
9049
+ }
9050
+ }, 800);
9051
+ }
9052
+ }
9053
+ /**
9054
+ * Update sources (called when playlist changes)
9055
+ */
9056
+ updateSources(audioDescriptionSrc) {
9057
+ this.src = audioDescriptionSrc || null;
9058
+ this.enabled = false;
9059
+ this.desiredState = false;
9060
+ this.sourceElement = null;
9061
+ this.originalSource = null;
9062
+ this.captionTracks = [];
9063
+ }
9064
+ /**
9065
+ * Reinitialize from current player elements (called after playlist loads new tracks)
9066
+ */
9067
+ reinitialize() {
9068
+ this.player.invalidateTrackCache();
9069
+ this.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
9070
+ }
9071
+ /**
9072
+ * Cleanup
9073
+ */
9074
+ destroy() {
9075
+ this.enabled = false;
9076
+ this.desiredState = false;
9077
+ this.captionTracks = [];
9078
+ this.sourceElement = null;
9079
+ this.originalSource = null;
9080
+ }
9081
+ };
9082
+
9083
+ // src/core/SignLanguageManager.js
9084
+ init_DOMUtils();
9085
+ init_Icons();
9086
+ init_i18n();
9087
+ init_DraggableResizable();
9088
+ init_MenuUtils();
9089
+ init_FormUtils();
9090
+ var SignLanguageManager = class {
9091
+ constructor(player) {
9092
+ this.player = player;
9093
+ this.src = player.options.signLanguageSrc;
9094
+ this.sources = player.options.signLanguageSources || {};
9095
+ this.currentLanguage = null;
9096
+ this.desiredPosition = player.options.signLanguagePosition || "bottom-right";
9097
+ this.wrapper = null;
9098
+ this.header = null;
9099
+ this.video = null;
9100
+ this.selector = null;
9101
+ this.settingsButton = null;
9102
+ this.settingsMenu = null;
9103
+ this.resizeHandles = [];
9104
+ this.enabled = false;
9105
+ this.settingsMenuVisible = false;
9106
+ this.settingsMenuJustOpened = false;
9107
+ this.documentClickHandlerAdded = false;
9108
+ this.handlers = null;
9109
+ this.settingsHandlers = null;
9110
+ this.interactionHandlers = null;
9111
+ this.draggable = null;
9112
+ this.documentClickHandler = null;
9113
+ this.settingsMenuKeyHandler = null;
9114
+ this.customKeyHandler = null;
9115
+ this.dragOptionButton = null;
9116
+ this.dragOptionText = null;
9117
+ this.resizeOptionButton = null;
9118
+ this.resizeOptionText = null;
9119
+ }
9120
+ /**
9121
+ * Check if sign language is available
9122
+ */
9123
+ isAvailable() {
9124
+ return Object.keys(this.sources).length > 0 || !!this.src;
9125
+ }
9126
+ /**
9127
+ * Enable sign language video
9128
+ */
9129
+ enable() {
9130
+ const hasMultipleSources = Object.keys(this.sources).length > 0;
9131
+ const hasSingleSource = !!this.src;
9132
+ if (!hasMultipleSources && !hasSingleSource) {
9133
+ console.warn("No sign language video source provided");
9134
+ return;
9135
+ }
9136
+ if (this.wrapper) {
9137
+ this.wrapper.style.display = "block";
9138
+ this.enabled = true;
9139
+ this.player.state.signLanguageEnabled = true;
9140
+ this.player.emit("signlanguageenabled");
9141
+ this.player.setManagedTimeout(() => {
9142
+ if (this.settingsButton && document.contains(this.settingsButton)) {
9143
+ this.settingsButton.focus({ preventScroll: true });
9144
+ }
9145
+ }, 150);
9146
+ return;
9147
+ }
9148
+ let initialLang = null;
9149
+ let initialSrc = null;
9150
+ if (hasMultipleSources) {
9151
+ initialLang = this._determineInitialLanguage();
9152
+ initialSrc = this.sources[initialLang];
9153
+ this.currentLanguage = initialLang;
9154
+ } else {
9155
+ initialSrc = this.src;
9156
+ }
9157
+ this._createWrapper();
9158
+ this._createHeader(hasMultipleSources, initialLang);
9159
+ this._createVideo(initialSrc);
9160
+ this._createResizeHandles();
9161
+ this.wrapper.appendChild(this.header);
9162
+ this.wrapper.appendChild(this.video);
9163
+ this.resizeHandles.forEach((handle) => this.wrapper.appendChild(handle));
9164
+ this._applyInitialSize();
9165
+ this.player.container.appendChild(this.wrapper);
9166
+ requestAnimationFrame(() => {
9167
+ this.constrainPosition();
9168
+ });
9169
+ this.video.currentTime = this.player.state.currentTime;
9170
+ if (!this.player.state.paused) {
9171
+ this.video.play();
9172
+ }
9173
+ this._setupInteraction();
9174
+ this._setupEventHandlers(hasMultipleSources);
9175
+ this.enabled = true;
9176
+ this.player.state.signLanguageEnabled = true;
9177
+ this.player.emit("signlanguageenabled");
9178
+ this.player.setManagedTimeout(() => {
9179
+ if (this.settingsButton && document.contains(this.settingsButton)) {
9180
+ this.settingsButton.focus({ preventScroll: true });
9181
+ }
9182
+ }, 150);
9183
+ }
9184
+ /**
9185
+ * Disable sign language video
9186
+ */
9187
+ disable() {
9188
+ if (this.settingsMenuVisible) {
9189
+ this.hideSettingsMenu({ focusButton: false });
9190
+ }
9191
+ if (this.wrapper) {
9192
+ this.wrapper.style.display = "none";
9193
+ }
9194
+ this.enabled = false;
9195
+ this.player.state.signLanguageEnabled = false;
9196
+ this.player.emit("signlanguagedisabled");
9197
+ }
9198
+ /**
9199
+ * Toggle sign language video
9200
+ */
9201
+ toggle() {
9202
+ if (this.enabled) {
9203
+ this.disable();
9204
+ } else {
9205
+ this.enable();
9206
+ }
9207
+ }
9208
+ /**
9209
+ * Switch to a different sign language
9210
+ */
9211
+ switchLanguage(langCode) {
9212
+ if (!this.sources[langCode] || !this.video) {
9213
+ return;
9214
+ }
9215
+ const currentTime = this.video.currentTime;
9216
+ const wasPlaying = !this.video.paused;
9217
+ this.video.src = this.sources[langCode];
9218
+ this.currentLanguage = langCode;
9219
+ this.video.currentTime = currentTime;
9220
+ if (wasPlaying) {
9221
+ this.video.play().catch(() => {
9222
+ });
9223
+ }
9224
+ this.player.emit("signlanguagelanguagechanged", langCode);
9225
+ }
9226
+ /**
9227
+ * Get language label
9228
+ */
9229
+ getLanguageLabel(langCode) {
9230
+ const langNames = {
9231
+ "en": "English",
9232
+ "de": "Deutsch",
9233
+ "es": "Español",
9234
+ "fr": "Français",
9235
+ "it": "Italiano",
9236
+ "ja": "日本語",
9237
+ "pt": "Português",
9238
+ "ar": "العربية",
9239
+ "hi": "हिन्दी"
9240
+ };
9241
+ return langNames[langCode] || langCode.toUpperCase();
9242
+ }
9243
+ /**
9244
+ * Determine initial sign language
9245
+ */
9246
+ _determineInitialLanguage() {
9247
+ var _a;
9248
+ if (this.player.captionManager && this.player.captionManager.currentTrack) {
9249
+ const captionLang = (_a = this.player.captionManager.currentTrack.language) == null ? void 0 : _a.toLowerCase().split("-")[0];
9250
+ if (captionLang && this.sources[captionLang]) {
9251
+ return captionLang;
9252
+ }
9253
+ }
9254
+ if (this.player.options.language) {
9255
+ const playerLang = this.player.options.language.toLowerCase().split("-")[0];
9256
+ if (this.sources[playerLang]) {
9257
+ return playerLang;
9258
+ }
9259
+ }
9260
+ return Object.keys(this.sources)[0];
9261
+ }
9262
+ /**
9263
+ * Create wrapper element
9264
+ */
9265
+ _createWrapper() {
9266
+ this.wrapper = document.createElement("div");
9267
+ this.wrapper.className = "vidply-sign-language-wrapper";
9268
+ this.wrapper.setAttribute("tabindex", "0");
9269
+ this.wrapper.setAttribute("aria-label", i18n.t("player.signLanguageDragResize"));
9270
+ }
9271
+ /**
9272
+ * Create header element
9273
+ */
9274
+ _createHeader(hasMultipleSources, initialLang) {
9275
+ const classPrefix = this.player.options.classPrefix;
9276
+ this.header = DOMUtils.createElement("div", {
9277
+ className: "".concat(classPrefix, "-sign-language-header"),
9278
+ attributes: { "tabindex": "0" }
9279
+ });
9280
+ const headerLeft = DOMUtils.createElement("div", {
9281
+ className: "".concat(classPrefix, "-sign-language-header-left")
9282
+ });
9283
+ const title = DOMUtils.createElement("h3", {
9284
+ textContent: i18n.t("player.signLanguageVideo")
9285
+ });
9286
+ this._createSettingsButton(headerLeft);
9287
+ if (hasMultipleSources) {
9288
+ this._createLanguageSelector(headerLeft, initialLang);
9289
+ }
9290
+ headerLeft.appendChild(title);
9291
+ const closeButton = this._createCloseButton();
9292
+ this.header.appendChild(headerLeft);
9293
+ this.header.appendChild(closeButton);
9294
+ this.settingsMenuVisible = false;
9295
+ this.settingsMenu = null;
9296
+ this.settingsMenuJustOpened = false;
9297
+ }
9298
+ /**
9299
+ * Create settings button
9300
+ */
9301
+ _createSettingsButton(container) {
9302
+ const classPrefix = this.player.options.classPrefix;
9303
+ const ariaLabel = i18n.t("player.signLanguageSettings");
9304
+ this.settingsButton = DOMUtils.createElement("button", {
9305
+ className: "".concat(classPrefix, "-sign-language-settings"),
9306
+ attributes: {
9307
+ "type": "button",
9308
+ "aria-label": ariaLabel,
9309
+ "aria-expanded": "false"
9310
+ }
9311
+ });
9312
+ this.settingsButton.appendChild(createIconElement("settings"));
9313
+ DOMUtils.attachTooltip(this.settingsButton, ariaLabel, classPrefix);
9314
+ this.settingsHandlers = {
9315
+ click: (e) => {
9316
+ e.preventDefault();
9317
+ e.stopPropagation();
9318
+ if (this.documentClickHandler) {
9319
+ this.settingsMenuJustOpened = true;
9320
+ setTimeout(() => {
9321
+ this.settingsMenuJustOpened = false;
9322
+ }, 100);
9323
+ }
9324
+ if (this.settingsMenuVisible) {
9325
+ this.hideSettingsMenu();
9326
+ } else {
9327
+ this.showSettingsMenu();
9328
+ }
9329
+ },
9330
+ keydown: (e) => {
9331
+ if (e.key === "d" || e.key === "D") {
9332
+ e.preventDefault();
9333
+ e.stopPropagation();
9334
+ this.toggleKeyboardDragMode();
9335
+ } else if (e.key === "r" || e.key === "R") {
9336
+ e.preventDefault();
9337
+ e.stopPropagation();
9338
+ this.toggleResizeMode();
9339
+ } else if (e.key === "Escape" && this.settingsMenuVisible) {
9340
+ e.preventDefault();
9341
+ e.stopPropagation();
9342
+ this.hideSettingsMenu();
9343
+ }
9344
+ }
9345
+ };
9346
+ this.settingsButton.addEventListener("click", this.settingsHandlers.click);
9347
+ this.settingsButton.addEventListener("keydown", this.settingsHandlers.keydown);
9348
+ container.appendChild(this.settingsButton);
9349
+ }
9350
+ /**
9351
+ * Create language selector
9352
+ */
9353
+ _createLanguageSelector(container, initialLang) {
9354
+ const classPrefix = this.player.options.classPrefix;
9355
+ const selectId = "".concat(classPrefix, "-sign-language-select-").concat(Date.now());
9356
+ const options = Object.keys(this.sources).map((langCode) => ({
9357
+ value: langCode,
9358
+ text: this.getLanguageLabel(langCode),
9359
+ selected: langCode === initialLang
9360
+ }));
9361
+ const { label, select } = createLabeledSelect({
9362
+ classPrefix,
9363
+ labelClass: "".concat(classPrefix, "-sign-language-label"),
9364
+ selectClass: "".concat(classPrefix, "-sign-language-select"),
9365
+ labelText: "settings.language",
9366
+ selectId,
9367
+ options,
9368
+ onChange: (e) => {
9369
+ e.stopPropagation();
9370
+ this.switchLanguage(e.target.value);
9371
+ }
9372
+ });
9373
+ this.selector = select;
9374
+ const selectorWrapper = DOMUtils.createElement("div", {
9375
+ className: "".concat(classPrefix, "-sign-language-selector-wrapper")
9376
+ });
9377
+ selectorWrapper.appendChild(label);
9378
+ selectorWrapper.appendChild(this.selector);
9379
+ preventDragOnElement(selectorWrapper);
9380
+ container.appendChild(selectorWrapper);
9381
+ }
9382
+ /**
9383
+ * Create close button
9384
+ */
9385
+ _createCloseButton() {
9386
+ const classPrefix = this.player.options.classPrefix;
9387
+ const ariaLabel = i18n.t("player.closeSignLanguage");
9388
+ const closeButton = DOMUtils.createElement("button", {
9389
+ className: "".concat(classPrefix, "-sign-language-close"),
9390
+ attributes: {
9391
+ "type": "button",
9392
+ "aria-label": ariaLabel
9393
+ }
9394
+ });
9395
+ closeButton.appendChild(createIconElement("close"));
9396
+ DOMUtils.attachTooltip(closeButton, ariaLabel, classPrefix);
9397
+ closeButton.addEventListener("click", () => {
9398
+ var _a, _b;
9399
+ this.disable();
9400
+ if ((_b = (_a = this.player.controlBar) == null ? void 0 : _a.controls) == null ? void 0 : _b.signLanguage) {
9401
+ setTimeout(() => {
9402
+ this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
9403
+ }, 0);
9404
+ }
9405
+ });
9406
+ return closeButton;
9407
+ }
9408
+ /**
9409
+ * Create video element
9410
+ */
9411
+ _createVideo(src) {
9412
+ this.video = document.createElement("video");
9413
+ this.video.className = "vidply-sign-language-video";
9414
+ this.video.src = src;
9415
+ this.video.setAttribute("aria-label", i18n.t("player.signLanguage"));
9416
+ this.video.muted = true;
9417
+ this.video.setAttribute("playsinline", "");
9418
+ }
9419
+ /**
9420
+ * Create resize handles
9421
+ */
9422
+ _createResizeHandles() {
9423
+ const classPrefix = this.player.options.classPrefix;
9424
+ this.resizeHandles = ["n", "s", "e", "w", "ne", "nw", "se", "sw"].map((dir) => {
9425
+ const handle = DOMUtils.createElement("div", {
9426
+ className: "".concat(classPrefix, "-sign-resize-handle ").concat(classPrefix, "-sign-resize-").concat(dir),
9427
+ attributes: {
9428
+ "data-direction": dir,
9429
+ "data-vidply-managed-resize": "true",
9430
+ "aria-hidden": "true"
9431
+ }
9432
+ });
9433
+ handle.style.display = "none";
9434
+ return handle;
9435
+ });
9436
+ }
9437
+ /**
9438
+ * Apply initial size
9439
+ */
9440
+ _applyInitialSize() {
9441
+ var _a;
9442
+ const saved = this.player.storage.getSignLanguagePreferences();
9443
+ if ((_a = saved == null ? void 0 : saved.size) == null ? void 0 : _a.width) {
9444
+ this.wrapper.style.width = saved.size.width;
9445
+ } else {
9446
+ this.wrapper.style.width = "280px";
9447
+ }
9448
+ this.wrapper.style.height = "auto";
9449
+ }
9450
+ /**
9451
+ * Setup interaction (drag and resize)
9452
+ */
9453
+ _setupInteraction() {
9454
+ const isMobile2 = window.innerWidth < 768;
9455
+ const isFullscreen = this.player.state.fullscreen;
9456
+ if (isMobile2 && !isFullscreen) {
9457
+ if (this.draggable) {
9458
+ this.draggable.destroy();
9459
+ this.draggable = null;
9460
+ }
9461
+ return;
9462
+ }
9463
+ if (this.draggable) return;
9464
+ const classPrefix = this.player.options.classPrefix;
9465
+ this.draggable = new DraggableResizable(this.wrapper, {
9466
+ dragHandle: this.header,
9467
+ resizeHandles: this.resizeHandles,
9468
+ constrainToViewport: true,
9469
+ maintainAspectRatio: true,
9470
+ minWidth: 150,
9471
+ minHeight: 100,
9472
+ classPrefix: "".concat(classPrefix, "-sign"),
9473
+ keyboardDragKey: "d",
9474
+ keyboardResizeKey: "r",
9475
+ keyboardStep: 10,
9476
+ keyboardStepLarge: 50,
9477
+ pointerResizeIndicatorText: i18n.t("player.signLanguageResizeActive"),
9478
+ onPointerResizeToggle: (enabled) => {
9479
+ this.resizeHandles.forEach((handle) => {
9480
+ handle.style.display = enabled ? "block" : "none";
9481
+ });
9482
+ },
9483
+ onDragStart: (e) => {
9484
+ if (e.target.closest(".".concat(classPrefix, "-sign-language-close")) || e.target.closest(".".concat(classPrefix, "-sign-language-settings")) || e.target.closest(".".concat(classPrefix, "-sign-language-select")) || e.target.closest(".".concat(classPrefix, "-sign-language-label")) || e.target.closest(".".concat(classPrefix, "-sign-language-settings-menu"))) {
9485
+ return false;
9486
+ }
9487
+ return true;
9488
+ }
9489
+ });
9490
+ this._setupCustomKeyHandler();
9491
+ this.interactionHandlers = {
9492
+ draggable: this.draggable,
9493
+ customKeyHandler: this.customKeyHandler
9494
+ };
9495
+ }
9496
+ /**
9497
+ * Setup custom keyboard handler
9498
+ */
9499
+ _setupCustomKeyHandler() {
9500
+ this.customKeyHandler = (e) => {
9501
+ var _a, _b, _c, _d;
9502
+ const key = e.key.toLowerCase();
9503
+ if (this.settingsMenuVisible) return;
9504
+ if (key === "home") {
9505
+ e.preventDefault();
9506
+ e.stopPropagation();
9507
+ if (this.draggable) {
9508
+ if (this.draggable.pointerResizeMode) {
9509
+ this.draggable.disablePointerResizeMode();
9510
+ }
9511
+ this.draggable.manuallyPositioned = false;
9512
+ this.constrainPosition();
9513
+ }
9514
+ return;
9515
+ }
9516
+ if (key === "r") {
9517
+ e.preventDefault();
9518
+ e.stopPropagation();
9519
+ if (this.toggleResizeMode()) {
9520
+ this.wrapper.focus({ preventScroll: true });
9521
+ }
9522
+ return;
9523
+ }
9524
+ if (key === "escape") {
9525
+ e.preventDefault();
9526
+ e.stopPropagation();
9527
+ if ((_a = this.draggable) == null ? void 0 : _a.pointerResizeMode) {
9528
+ this.draggable.disablePointerResizeMode();
9529
+ return;
9530
+ }
9531
+ if ((_b = this.draggable) == null ? void 0 : _b.keyboardDragMode) {
9532
+ this.draggable.disableKeyboardDragMode();
9533
+ return;
9534
+ }
9535
+ this.disable();
9536
+ if ((_d = (_c = this.player.controlBar) == null ? void 0 : _c.controls) == null ? void 0 : _d.signLanguage) {
9537
+ setTimeout(() => {
9538
+ this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
9539
+ }, 0);
9540
+ }
9541
+ }
9542
+ };
9543
+ this.wrapper.addEventListener("keydown", this.customKeyHandler);
9544
+ }
9545
+ /**
9546
+ * Setup event handlers
9547
+ */
9548
+ _setupEventHandlers(hasMultipleSources) {
9549
+ this.handlers = {
9550
+ play: () => {
9551
+ if (this.video) this.video.play();
9552
+ },
9553
+ pause: () => {
9554
+ if (this.video) this.video.pause();
9555
+ },
9556
+ timeupdate: () => {
9557
+ if (this.video && Math.abs(this.video.currentTime - this.player.state.currentTime) > 0.5) {
9558
+ this.video.currentTime = this.player.state.currentTime;
9559
+ }
9560
+ },
9561
+ ratechange: () => {
9562
+ if (this.video) this.video.playbackRate = this.player.state.playbackSpeed;
9563
+ }
9564
+ };
9565
+ this.player.on("play", this.handlers.play);
9566
+ this.player.on("pause", this.handlers.pause);
9567
+ this.player.on("timeupdate", this.handlers.timeupdate);
9568
+ this.player.on("ratechange", this.handlers.ratechange);
9569
+ if (hasMultipleSources) {
9570
+ this.handlers.captionChange = () => {
9571
+ var _a, _b;
9572
+ if (((_a = this.player.captionManager) == null ? void 0 : _a.currentTrack) && this.selector) {
9573
+ const captionLang = (_b = this.player.captionManager.currentTrack.language) == null ? void 0 : _b.toLowerCase().split("-")[0];
9574
+ if (captionLang && this.sources[captionLang] && this.currentLanguage !== captionLang) {
9575
+ this.switchLanguage(captionLang);
9576
+ this.selector.value = captionLang;
9577
+ }
9578
+ }
9579
+ };
9580
+ this.player.on("captionsenabled", this.handlers.captionChange);
9581
+ }
9582
+ }
9583
+ /**
9584
+ * Constrain position within video wrapper
9585
+ */
9586
+ constrainPosition() {
9587
+ var _a;
9588
+ if (!this.wrapper || !this.player.videoWrapper) return;
9589
+ if ((_a = this.draggable) == null ? void 0 : _a.manuallyPositioned) return;
9590
+ if (!this.wrapper.style.width) {
9591
+ this.wrapper.style.width = "280px";
9592
+ }
9593
+ const videoWrapperRect = this.player.videoWrapper.getBoundingClientRect();
9594
+ const containerRect = this.player.container.getBoundingClientRect();
9595
+ const wrapperRect = this.wrapper.getBoundingClientRect();
9596
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
9597
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
9598
+ const videoWrapperWidth = videoWrapperRect.width;
9599
+ const videoWrapperHeight = videoWrapperRect.height;
9600
+ let wrapperWidth = wrapperRect.width || 280;
9601
+ let wrapperHeight = wrapperRect.height || 280 * 9 / 16;
9602
+ let left, top;
9603
+ const margin = 16;
9604
+ const controlsHeight = 95;
9605
+ const position = this.desiredPosition || "bottom-right";
9606
+ switch (position) {
9607
+ case "bottom-right":
9608
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
9609
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
9610
+ break;
9611
+ case "bottom-left":
9612
+ left = videoWrapperLeft + margin;
9613
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
9614
+ break;
9615
+ case "top-right":
9616
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
9617
+ top = videoWrapperTop + margin;
9618
+ break;
9619
+ case "top-left":
9620
+ left = videoWrapperLeft + margin;
9621
+ top = videoWrapperTop + margin;
9622
+ break;
9623
+ default:
9624
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
9625
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
9626
+ }
9627
+ left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperWidth - wrapperWidth));
9628
+ top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight));
9629
+ this.wrapper.style.left = "".concat(left, "px");
9630
+ this.wrapper.style.top = "".concat(top, "px");
9631
+ this.wrapper.style.right = "auto";
9632
+ this.wrapper.style.bottom = "auto";
9633
+ }
9634
+ /**
9635
+ * Show settings menu
9636
+ */
9637
+ showSettingsMenu() {
9638
+ var _a;
9639
+ this.settingsMenuJustOpened = true;
9640
+ setTimeout(() => {
9641
+ this.settingsMenuJustOpened = false;
9642
+ }, 350);
9643
+ this._addDocumentClickHandler();
9644
+ if (this.settingsMenu) {
9645
+ this.settingsMenu.style.display = "block";
9646
+ this.settingsMenuVisible = true;
9647
+ (_a = this.settingsButton) == null ? void 0 : _a.setAttribute("aria-expanded", "true");
9648
+ this._attachMenuKeyboardNavigation();
9649
+ this._positionSettingsMenu();
9650
+ this._updateDragOptionState();
9651
+ this._updateResizeOptionState();
9652
+ focusFirstMenuItem(this.settingsMenu, ".".concat(this.player.options.classPrefix, "-sign-language-settings-item"));
9653
+ return;
9654
+ }
9655
+ this._createSettingsMenu();
9656
+ }
9657
+ /**
9658
+ * Hide settings menu
9659
+ */
9660
+ hideSettingsMenu({ focusButton = true } = {}) {
9661
+ if (this.settingsMenu) {
9662
+ this.settingsMenu.style.display = "none";
9663
+ this.settingsMenuVisible = false;
9664
+ this.settingsMenuJustOpened = false;
9665
+ if (this.settingsMenuKeyHandler) {
9666
+ this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
9667
+ this.settingsMenuKeyHandler = null;
9668
+ }
9669
+ const classPrefix = this.player.options.classPrefix;
9670
+ const menuItems = Array.from(this.settingsMenu.querySelectorAll(".".concat(classPrefix, "-sign-language-settings-item")));
9671
+ menuItems.forEach((item) => item.setAttribute("tabindex", "-1"));
9672
+ if (this.settingsButton) {
9673
+ this.settingsButton.setAttribute("aria-expanded", "false");
9674
+ if (focusButton) {
9675
+ this.settingsButton.focus({ preventScroll: true });
9676
+ }
9677
+ }
9678
+ }
9679
+ }
9680
+ /**
9681
+ * Add document click handler
9682
+ */
9683
+ _addDocumentClickHandler() {
9684
+ if (this.documentClickHandlerAdded) return;
9685
+ this.documentClickHandler = (e) => {
9686
+ if (this.settingsMenuJustOpened) return;
9687
+ if (this.settingsButton && (this.settingsButton === e.target || this.settingsButton.contains(e.target))) {
9688
+ return;
9689
+ }
9690
+ if (this.settingsMenu && this.settingsMenu.contains(e.target)) {
9691
+ return;
9692
+ }
9693
+ if (this.settingsMenuVisible) {
9694
+ this.hideSettingsMenu();
9695
+ }
9696
+ };
9697
+ setTimeout(() => {
9698
+ document.addEventListener("mousedown", this.documentClickHandler, true);
9699
+ this.documentClickHandlerAdded = true;
9700
+ }, 300);
9701
+ }
9702
+ /**
9703
+ * Create settings menu
9704
+ */
9705
+ _createSettingsMenu() {
9706
+ var _a, _b;
9707
+ const classPrefix = this.player.options.classPrefix;
9708
+ this.settingsMenu = DOMUtils.createElement("div", {
9709
+ className: "".concat(classPrefix, "-sign-language-settings-menu"),
9710
+ attributes: { "role": "menu" }
9711
+ });
9712
+ const dragOption = createMenuItem({
9713
+ classPrefix,
9714
+ itemClass: "".concat(classPrefix, "-sign-language-settings-item"),
9715
+ icon: "move",
9716
+ label: "player.enableSignDragMode",
9717
+ hasTextClass: true,
9718
+ onClick: () => {
9719
+ this.toggleKeyboardDragMode();
9720
+ this.hideSettingsMenu();
9721
+ }
9722
+ });
9723
+ dragOption.setAttribute("role", "switch");
9724
+ dragOption.setAttribute("aria-checked", "false");
9725
+ this._removeTooltipFromMenuItem(dragOption);
9726
+ this.dragOptionButton = dragOption;
9727
+ this.dragOptionText = dragOption.querySelector(".".concat(classPrefix, "-settings-text"));
9728
+ this._updateDragOptionState();
9729
+ const resizeOption = createMenuItem({
9730
+ classPrefix,
9731
+ itemClass: "".concat(classPrefix, "-sign-language-settings-item"),
9732
+ icon: "resize",
9733
+ label: "player.enableSignResizeMode",
9734
+ hasTextClass: true,
9735
+ onClick: (event) => {
9736
+ event.preventDefault();
9737
+ event.stopPropagation();
9738
+ const enabled = this.toggleResizeMode({ focus: false });
9739
+ if (enabled) {
9740
+ this.hideSettingsMenu({ focusButton: false });
9741
+ setTimeout(() => {
9742
+ if (this.wrapper) this.wrapper.focus({ preventScroll: true });
9743
+ }, 20);
9744
+ } else {
9745
+ this.hideSettingsMenu({ focusButton: true });
9746
+ }
9747
+ }
9748
+ });
9749
+ resizeOption.setAttribute("role", "switch");
9750
+ resizeOption.setAttribute("aria-checked", "false");
9751
+ this._removeTooltipFromMenuItem(resizeOption);
9752
+ this.resizeOptionButton = resizeOption;
9753
+ this.resizeOptionText = resizeOption.querySelector(".".concat(classPrefix, "-settings-text"));
9754
+ this._updateResizeOptionState();
9755
+ const closeOption = createMenuItem({
9756
+ classPrefix,
9757
+ itemClass: "".concat(classPrefix, "-sign-language-settings-item"),
9758
+ icon: "close",
9759
+ label: "transcript.closeMenu",
9760
+ onClick: () => this.hideSettingsMenu()
9761
+ });
9762
+ this._removeTooltipFromMenuItem(closeOption);
9763
+ this.settingsMenu.appendChild(dragOption);
9764
+ this.settingsMenu.appendChild(resizeOption);
9765
+ this.settingsMenu.appendChild(closeOption);
9766
+ this.settingsMenu.style.visibility = "hidden";
9767
+ this.settingsMenu.style.display = "block";
9768
+ if ((_a = this.settingsButton) == null ? void 0 : _a.parentNode) {
9769
+ this.settingsButton.insertAdjacentElement("afterend", this.settingsMenu);
9770
+ } else if (this.wrapper) {
9771
+ this.wrapper.appendChild(this.settingsMenu);
9772
+ }
9773
+ this._positionSettingsMenuImmediate();
9774
+ requestAnimationFrame(() => {
9775
+ if (this.settingsMenu) {
9776
+ this.settingsMenu.style.visibility = "visible";
9777
+ }
9778
+ });
9779
+ this._attachMenuKeyboardNavigation();
9780
+ this.settingsMenuVisible = true;
9781
+ (_b = this.settingsButton) == null ? void 0 : _b.setAttribute("aria-expanded", "true");
9782
+ this._updateDragOptionState();
9783
+ this._updateResizeOptionState();
9784
+ focusFirstMenuItem(this.settingsMenu, ".".concat(classPrefix, "-sign-language-settings-item"));
9785
+ }
9786
+ /**
9787
+ * Remove tooltip from menu item
9788
+ */
9789
+ _removeTooltipFromMenuItem(item) {
9790
+ const classPrefix = this.player.options.classPrefix;
9791
+ const tooltip = item.querySelector(".".concat(classPrefix, "-tooltip"));
9792
+ if (tooltip) tooltip.remove();
9793
+ const buttonText = item.querySelector(".".concat(classPrefix, "-button-text"));
9794
+ if (buttonText) buttonText.remove();
9795
+ }
9796
+ /**
9797
+ * Attach menu keyboard navigation
9798
+ */
9799
+ _attachMenuKeyboardNavigation() {
9800
+ if (this.settingsMenuKeyHandler) {
9801
+ this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
9802
+ }
9803
+ this.settingsMenuKeyHandler = attachMenuKeyboardNavigation(
9804
+ this.settingsMenu,
9805
+ this.settingsButton,
9806
+ ".".concat(this.player.options.classPrefix, "-sign-language-settings-item"),
9807
+ () => this.hideSettingsMenu({ focusButton: true })
9808
+ );
9809
+ }
9810
+ /**
9811
+ * Position settings menu immediately
9812
+ */
9813
+ _positionSettingsMenuImmediate() {
9814
+ if (!this.settingsMenu || !this.settingsButton) return;
9815
+ const buttonRect = this.settingsButton.getBoundingClientRect();
9816
+ const menuRect = this.settingsMenu.getBoundingClientRect();
9817
+ const viewportWidth = window.innerWidth;
9818
+ const viewportHeight = window.innerHeight;
9819
+ const parentContainer = this.settingsButton.parentElement;
9820
+ if (!parentContainer) return;
9821
+ const parentRect = parentContainer.getBoundingClientRect();
9822
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2 - parentRect.left;
9823
+ const buttonBottom = buttonRect.bottom - parentRect.top;
9824
+ const buttonTop = buttonRect.top - parentRect.top;
9825
+ const spaceAbove = buttonRect.top;
9826
+ const spaceBelow = viewportHeight - buttonRect.bottom;
9827
+ let menuTop = buttonBottom + 8;
9828
+ let menuBottom = null;
9829
+ if (spaceBelow < menuRect.height + 20 && spaceAbove > spaceBelow) {
9830
+ menuTop = null;
9831
+ const parentHeight = parentRect.bottom - parentRect.top;
9832
+ menuBottom = parentHeight - buttonTop + 8;
9833
+ this.settingsMenu.classList.add("vidply-menu-above");
9834
+ } else {
9835
+ this.settingsMenu.classList.remove("vidply-menu-above");
9836
+ }
9837
+ let menuLeft = buttonCenterX - menuRect.width / 2;
9838
+ let menuRight = "auto";
9839
+ let transformX = "translateX(0)";
9840
+ const menuLeftAbsolute = buttonRect.left + buttonRect.width / 2 - menuRect.width / 2;
9841
+ if (menuLeftAbsolute < 10) {
9842
+ menuLeft = 0;
9843
+ } else if (menuLeftAbsolute + menuRect.width > viewportWidth - 10) {
9844
+ menuLeft = "auto";
9845
+ menuRight = 0;
9846
+ } else {
9847
+ menuLeft = buttonCenterX;
9848
+ transformX = "translateX(-50%)";
9849
+ }
9850
+ if (menuTop !== null) {
9851
+ this.settingsMenu.style.top = "".concat(menuTop, "px");
9852
+ this.settingsMenu.style.bottom = "auto";
9853
+ } else if (menuBottom !== null) {
9854
+ this.settingsMenu.style.top = "auto";
9855
+ this.settingsMenu.style.bottom = "".concat(menuBottom, "px");
9856
+ }
9857
+ if (menuLeft !== "auto") {
9858
+ this.settingsMenu.style.left = "".concat(menuLeft, "px");
9859
+ this.settingsMenu.style.right = "auto";
9860
+ } else {
9861
+ this.settingsMenu.style.left = "auto";
9862
+ this.settingsMenu.style.right = "".concat(menuRight, "px");
9863
+ }
9864
+ this.settingsMenu.style.transform = transformX;
9865
+ }
9866
+ /**
9867
+ * Position settings menu with RAF
9868
+ */
9869
+ _positionSettingsMenu() {
9870
+ requestAnimationFrame(() => {
9871
+ setTimeout(() => {
9872
+ this._positionSettingsMenuImmediate();
9873
+ }, 10);
9874
+ });
9875
+ }
9876
+ /**
9877
+ * Toggle keyboard drag mode
9878
+ */
9879
+ toggleKeyboardDragMode() {
9880
+ if (this.draggable) {
9881
+ const wasEnabled = this.draggable.keyboardDragMode;
9882
+ this.draggable.toggleKeyboardDragMode();
9883
+ const isEnabled = this.draggable.keyboardDragMode;
9884
+ if (!wasEnabled && isEnabled) {
9885
+ this._enableMoveMode();
9886
+ }
9887
+ this._updateDragOptionState();
9888
+ }
9889
+ }
9890
+ /**
9891
+ * Enable move mode visual feedback
9892
+ */
9893
+ _enableMoveMode() {
9894
+ this.wrapper.classList.add("".concat(this.player.options.classPrefix, "-sign-move-mode"));
9895
+ this._updateResizeOptionState();
9896
+ setTimeout(() => {
9897
+ this.wrapper.classList.remove("".concat(this.player.options.classPrefix, "-sign-move-mode"));
9898
+ }, 2e3);
9899
+ }
9900
+ /**
9901
+ * Toggle resize mode
9902
+ */
9903
+ toggleResizeMode({ focus = true } = {}) {
9904
+ if (!this.draggable) return false;
9905
+ if (this.draggable.pointerResizeMode) {
9906
+ this.draggable.disablePointerResizeMode({ focus });
9907
+ this._updateResizeOptionState();
9908
+ return false;
9909
+ }
9910
+ this.draggable.enablePointerResizeMode({ focus });
9911
+ this._updateResizeOptionState();
9912
+ return true;
9913
+ }
9914
+ /**
9915
+ * Update drag option state
9916
+ */
9917
+ _updateDragOptionState() {
9918
+ var _a;
9919
+ if (!this.dragOptionButton) return;
9920
+ const isEnabled = !!((_a = this.draggable) == null ? void 0 : _a.keyboardDragMode);
9921
+ const text = isEnabled ? i18n.t("player.disableSignDragMode") : i18n.t("player.enableSignDragMode");
9922
+ const ariaLabel = isEnabled ? i18n.t("player.disableSignDragModeAria") : i18n.t("player.enableSignDragModeAria");
9923
+ this.dragOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
9924
+ this.dragOptionButton.setAttribute("aria-label", ariaLabel);
9925
+ if (this.dragOptionText) {
9926
+ this.dragOptionText.textContent = text;
9927
+ }
9928
+ }
9929
+ /**
9930
+ * Update resize option state
9931
+ */
9932
+ _updateResizeOptionState() {
9933
+ var _a;
9934
+ if (!this.resizeOptionButton) return;
9935
+ const isEnabled = !!((_a = this.draggable) == null ? void 0 : _a.pointerResizeMode);
9936
+ const text = isEnabled ? i18n.t("player.disableSignResizeMode") : i18n.t("player.enableSignResizeMode");
9937
+ const ariaLabel = isEnabled ? i18n.t("player.disableSignResizeModeAria") : i18n.t("player.enableSignResizeModeAria");
9938
+ this.resizeOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
9939
+ this.resizeOptionButton.setAttribute("aria-label", ariaLabel);
9940
+ if (this.resizeOptionText) {
9941
+ this.resizeOptionText.textContent = text;
9942
+ }
9943
+ }
9944
+ /**
9945
+ * Save preferences
9946
+ */
9947
+ savePreferences() {
9948
+ if (!this.wrapper) return;
9949
+ this.player.storage.saveSignLanguagePreferences({
9950
+ size: { width: this.wrapper.style.width }
9951
+ });
9952
+ }
9953
+ /**
9954
+ * Update sources (called when playlist changes)
9955
+ */
9956
+ updateSources(signLanguageSrc, signLanguageSources) {
9957
+ this.src = signLanguageSrc || null;
9958
+ this.sources = signLanguageSources || {};
9959
+ this.currentLanguage = null;
9960
+ }
9961
+ /**
9962
+ * Cleanup
9963
+ */
9964
+ cleanup() {
9965
+ var _a;
9966
+ if (this.settingsMenuVisible) {
9967
+ this.hideSettingsMenu({ focusButton: false });
9968
+ }
9969
+ if (this.documentClickHandler && this.documentClickHandlerAdded) {
9970
+ document.removeEventListener("mousedown", this.documentClickHandler, true);
9971
+ this.documentClickHandlerAdded = false;
9972
+ this.documentClickHandler = null;
9973
+ }
9974
+ if (this.settingsHandlers && this.settingsButton) {
9975
+ this.settingsButton.removeEventListener("click", this.settingsHandlers.click);
9976
+ this.settingsButton.removeEventListener("keydown", this.settingsHandlers.keydown);
9977
+ }
9978
+ this.settingsHandlers = null;
9979
+ if (this.handlers) {
9980
+ this.player.off("play", this.handlers.play);
9981
+ this.player.off("pause", this.handlers.pause);
9982
+ this.player.off("timeupdate", this.handlers.timeupdate);
9983
+ this.player.off("ratechange", this.handlers.ratechange);
9984
+ if (this.handlers.captionChange) {
9985
+ this.player.off("captionsenabled", this.handlers.captionChange);
9986
+ }
9987
+ this.handlers = null;
9988
+ }
9989
+ if (this.wrapper && this.customKeyHandler) {
9990
+ this.wrapper.removeEventListener("keydown", this.customKeyHandler);
9991
+ }
9992
+ if (this.draggable) {
9993
+ if (this.draggable.pointerResizeMode) {
9994
+ this.draggable.disablePointerResizeMode();
9995
+ }
9996
+ this.draggable.destroy();
9997
+ this.draggable = null;
9998
+ }
9999
+ this.interactionHandlers = null;
10000
+ if ((_a = this.wrapper) == null ? void 0 : _a.parentNode) {
10001
+ if (this.video) {
10002
+ this.video.pause();
10003
+ this.video.src = "";
10004
+ }
10005
+ this.wrapper.parentNode.removeChild(this.wrapper);
10006
+ }
10007
+ this.wrapper = null;
10008
+ this.video = null;
10009
+ this.settingsButton = null;
10010
+ this.settingsMenu = null;
10011
+ }
10012
+ /**
10013
+ * Destroy
10014
+ */
10015
+ destroy() {
10016
+ this.cleanup();
10017
+ this.enabled = false;
10018
+ }
10019
+ };
10020
+
10021
+ // src/core/Player.js
10022
+ var playerInstanceCounter = 0;
10023
+ var Player = class _Player extends EventEmitter {
10024
+ constructor(element, options = {}) {
10025
+ super();
10026
+ this.element = typeof element === "string" ? document.querySelector(element) : element;
10027
+ if (!this.element) {
10028
+ throw new Error("VidPly: Element not found");
10029
+ }
10030
+ playerInstanceCounter++;
10031
+ this.instanceId = playerInstanceCounter;
10032
+ if (this.element.tagName !== "VIDEO" && this.element.tagName !== "AUDIO") {
10033
+ const mediaType = options.mediaType || "video";
10034
+ const mediaElement = document.createElement(mediaType);
10035
+ Array.from(this.element.attributes).forEach((attr) => {
10036
+ if (attr.name !== "id" && attr.name !== "class" && !attr.name.startsWith("data-")) {
10037
+ mediaElement.setAttribute(attr.name, attr.value);
10038
+ }
10039
+ });
10040
+ const tracks = this.element.querySelectorAll("track");
10041
+ tracks.forEach((track) => {
10042
+ mediaElement.appendChild(track.cloneNode(true));
10043
+ });
10044
+ this.element.innerHTML = "";
10045
+ this.element.appendChild(mediaElement);
10046
+ this.element = mediaElement;
10047
+ }
10048
+ this._originalElement = this.element;
10049
+ this.options = __spreadValues({
10050
+ // Display
10051
+ width: null,
10052
+ height: null,
8538
10053
  poster: null,
8539
10054
  responsive: true,
8540
10055
  fillContainer: false,
@@ -8689,6 +10204,58 @@
8689
10204
  this.settingsDialog = null;
8690
10205
  this.metadataCueChangeHandler = null;
8691
10206
  this.metadataAlertHandlers = /* @__PURE__ */ new Map();
10207
+ this.audioDescriptionManager = new AudioDescriptionManager(this);
10208
+ this.signLanguageManager = new SignLanguageManager(this);
10209
+ Object.defineProperties(this, {
10210
+ signLanguageWrapper: {
10211
+ get: () => this.signLanguageManager.wrapper,
10212
+ set: (v) => {
10213
+ this.signLanguageManager.wrapper = v;
10214
+ }
10215
+ },
10216
+ signLanguageVideo: {
10217
+ get: () => this.signLanguageManager.video,
10218
+ set: (v) => {
10219
+ this.signLanguageManager.video = v;
10220
+ }
10221
+ },
10222
+ signLanguageHeader: {
10223
+ get: () => this.signLanguageManager.header,
10224
+ set: (v) => {
10225
+ this.signLanguageManager.header = v;
10226
+ }
10227
+ },
10228
+ signLanguageSettingsButton: {
10229
+ get: () => this.signLanguageManager.settingsButton,
10230
+ set: (v) => {
10231
+ this.signLanguageManager.settingsButton = v;
10232
+ }
10233
+ },
10234
+ signLanguageSettingsMenu: {
10235
+ get: () => this.signLanguageManager.settingsMenu,
10236
+ set: (v) => {
10237
+ this.signLanguageManager.settingsMenu = v;
10238
+ }
10239
+ },
10240
+ signLanguageSettingsMenuVisible: {
10241
+ get: () => this.signLanguageManager.settingsMenuVisible,
10242
+ set: (v) => {
10243
+ this.signLanguageManager.settingsMenuVisible = v;
10244
+ }
10245
+ },
10246
+ signLanguageDraggable: {
10247
+ get: () => this.signLanguageManager.draggable,
10248
+ set: (v) => {
10249
+ this.signLanguageManager.draggable = v;
10250
+ }
10251
+ },
10252
+ currentSignLanguage: {
10253
+ get: () => this.signLanguageManager.currentLanguage,
10254
+ set: (v) => {
10255
+ this.signLanguageManager.currentLanguage = v;
10256
+ }
10257
+ }
10258
+ });
8692
10259
  this.init();
8693
10260
  }
8694
10261
  async init() {
@@ -8951,53 +10518,7 @@
8951
10518
  }
8952
10519
  this.currentSource = src;
8953
10520
  this._pendingSource = null;
8954
- const sourceElements = this.sourceElements;
8955
- for (const sourceEl of sourceElements) {
8956
- const descSrc = sourceEl.getAttribute("data-desc-src");
8957
- const origSrc = sourceEl.getAttribute("data-orig-src");
8958
- if (descSrc || origSrc) {
8959
- if (!this.audioDescriptionSourceElement) {
8960
- this.audioDescriptionSourceElement = sourceEl;
8961
- }
8962
- if (origSrc) {
8963
- if (!this.originalAudioDescriptionSource) {
8964
- this.originalAudioDescriptionSource = origSrc;
8965
- }
8966
- if (!this.originalSrc) {
8967
- this.originalSrc = origSrc;
8968
- }
8969
- } else {
8970
- const currentSrcAttr = sourceEl.getAttribute("src");
8971
- if (!this.originalAudioDescriptionSource && currentSrcAttr) {
8972
- this.originalAudioDescriptionSource = currentSrcAttr;
8973
- }
8974
- if (!this.originalSrc && currentSrcAttr) {
8975
- this.originalSrc = currentSrcAttr;
8976
- }
8977
- }
8978
- if (descSrc && !this.audioDescriptionSrc) {
8979
- this.audioDescriptionSrc = descSrc;
8980
- }
8981
- }
8982
- }
8983
- const trackElements = this.trackElements;
8984
- trackElements.forEach((trackEl) => {
8985
- const trackKind = trackEl.getAttribute("kind");
8986
- const trackDescSrc = trackEl.getAttribute("data-desc-src");
8987
- if (trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters") {
8988
- if (trackDescSrc) {
8989
- this.audioDescriptionCaptionTracks.push({
8990
- trackElement: trackEl,
8991
- originalSrc: trackEl.getAttribute("src"),
8992
- describedSrc: trackDescSrc,
8993
- originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
8994
- explicit: true
8995
- // Explicitly defined, so we should validate it
8996
- });
8997
- this.log("Found explicit described ".concat(trackKind, " track: ").concat(trackEl.getAttribute("src"), " -> ").concat(trackDescSrc));
8998
- }
8999
- }
9000
- });
10521
+ this.audioDescriptionManager.initFromSourceElements(this.sourceElements, this.trackElements);
9001
10522
  if (!this.originalSrc) {
9002
10523
  this.originalSrc = src;
9003
10524
  }
@@ -9242,6 +10763,9 @@
9242
10763
  if (trackConfig.default) {
9243
10764
  track.default = true;
9244
10765
  }
10766
+ if (trackConfig.describedSrc) {
10767
+ track.setAttribute("data-desc-src", trackConfig.describedSrc);
10768
+ }
9245
10769
  const firstChild = this.element.firstChild;
9246
10770
  if (firstChild && firstChild.nodeType === Node.ELEMENT_NODE && firstChild.tagName !== "TRACK") {
9247
10771
  this.element.insertBefore(track, firstChild);
@@ -9256,6 +10780,13 @@
9256
10780
  this.audioDescriptionSrc = config.audioDescriptionSrc || null;
9257
10781
  this.signLanguageSrc = config.signLanguageSrc || null;
9258
10782
  this.originalSrc = config.src;
10783
+ if (this.audioDescriptionManager) {
10784
+ this.audioDescriptionManager.updateSources(config.audioDescriptionSrc);
10785
+ this.audioDescriptionManager.reinitialize();
10786
+ }
10787
+ if (this.signLanguageManager) {
10788
+ this.signLanguageManager.updateSources(config.signLanguageSrc, config.signLanguageSources);
10789
+ }
9259
10790
  if (wasAudioDescriptionEnabled) {
9260
10791
  this.disableAudioDescription();
9261
10792
  }
@@ -9664,8 +11195,12 @@
9664
11195
  }
9665
11196
  return null;
9666
11197
  }
9667
- // Audio Description
11198
+ // Audio Description (delegated to AudioDescriptionManager)
9668
11199
  async enableAudioDescription() {
11200
+ return this.audioDescriptionManager.enable();
11201
+ }
11202
+ // Legacy method body preserved for reference - can be removed after testing
11203
+ async _legacyEnableAudioDescription() {
9669
11204
  const hasSourceElementsWithDesc = this.sourceElements.some((el) => el.getAttribute("data-desc-src"));
9670
11205
  const hasTracksWithDesc = this.audioDescriptionCaptionTracks.length > 0;
9671
11206
  if (!this.audioDescriptionSrc && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
@@ -10398,6 +11933,10 @@
10398
11933
  this.emit("audiodescriptionenabled");
10399
11934
  }
10400
11935
  async disableAudioDescription() {
11936
+ return this.audioDescriptionManager.disable();
11937
+ }
11938
+ // Legacy method body preserved for reference - can be removed after testing
11939
+ async _legacyDisableAudioDescription() {
10401
11940
  if (!this.originalSrc) {
10402
11941
  return;
10403
11942
  }
@@ -10672,64 +12211,14 @@
10672
12211
  this.emit("audiodescriptiondisabled");
10673
12212
  }
10674
12213
  async toggleAudioDescription() {
10675
- const descriptionTrack = this.findTextTrack("descriptions");
10676
- const hasAudioDescriptionSrc = this.audioDescriptionSrc || this.sourceElements.some((el) => el.getAttribute("data-desc-src"));
10677
- if (descriptionTrack && hasAudioDescriptionSrc) {
10678
- if (this.state.audioDescriptionEnabled) {
10679
- this._audioDescriptionDesiredState = false;
10680
- descriptionTrack.mode = "hidden";
10681
- await this.disableAudioDescription();
10682
- } else {
10683
- this._audioDescriptionDesiredState = true;
10684
- await this.enableAudioDescription();
10685
- const enableDescriptionTrack = () => {
10686
- this.invalidateTrackCache();
10687
- const descTrack = this.findTextTrack("descriptions");
10688
- if (descTrack) {
10689
- if (descTrack.mode === "disabled") {
10690
- descTrack.mode = "hidden";
10691
- this.setManagedTimeout(() => {
10692
- descTrack.mode = "showing";
10693
- }, 50);
10694
- } else {
10695
- descTrack.mode = "showing";
10696
- }
10697
- } else if (this.element.readyState < 2) {
10698
- this.setManagedTimeout(enableDescriptionTrack, 100);
10699
- }
10700
- };
10701
- if (this.element.readyState >= 1) {
10702
- this.setManagedTimeout(enableDescriptionTrack, 200);
10703
- } else {
10704
- this.element.addEventListener("loadedmetadata", () => {
10705
- this.setManagedTimeout(enableDescriptionTrack, 200);
10706
- }, { once: true });
10707
- }
10708
- }
10709
- } else if (descriptionTrack) {
10710
- if (descriptionTrack.mode === "showing") {
10711
- this._audioDescriptionDesiredState = false;
10712
- descriptionTrack.mode = "hidden";
10713
- this.state.audioDescriptionEnabled = false;
10714
- this.emit("audiodescriptiondisabled");
10715
- } else {
10716
- this._audioDescriptionDesiredState = true;
10717
- descriptionTrack.mode = "showing";
10718
- this.state.audioDescriptionEnabled = true;
10719
- this.emit("audiodescriptionenabled");
10720
- }
10721
- } else if (hasAudioDescriptionSrc) {
10722
- if (this.state.audioDescriptionEnabled) {
10723
- this._audioDescriptionDesiredState = false;
10724
- await this.disableAudioDescription();
10725
- } else {
10726
- this._audioDescriptionDesiredState = true;
10727
- await this.enableAudioDescription();
10728
- }
10729
- }
12214
+ return this.audioDescriptionManager.toggle();
10730
12215
  }
10731
- // Sign Language
12216
+ // Sign Language (delegated to SignLanguageManager)
10732
12217
  enableSignLanguage() {
12218
+ return this.signLanguageManager.enable();
12219
+ }
12220
+ // Legacy method body preserved for reference - can be removed after testing
12221
+ _legacyEnableSignLanguage() {
10733
12222
  var _a;
10734
12223
  const hasMultipleSources = Object.keys(this.signLanguageSources).length > 0;
10735
12224
  const hasSingleSource = !!this.signLanguageSrc;
@@ -10982,23 +12471,16 @@
10982
12471
  }, 150);
10983
12472
  }
10984
12473
  disableSignLanguage() {
10985
- if (this.signLanguageSettingsMenuVisible) {
10986
- this.hideSignLanguageSettingsMenu({ focusButton: false });
10987
- }
10988
- if (this.signLanguageWrapper) {
10989
- this.signLanguageWrapper.style.display = "none";
10990
- }
10991
- this.state.signLanguageEnabled = false;
10992
- this.emit("signlanguagedisabled");
12474
+ return this.signLanguageManager.disable();
10993
12475
  }
10994
12476
  toggleSignLanguage() {
10995
- if (this.state.signLanguageEnabled) {
10996
- this.disableSignLanguage();
10997
- } else {
10998
- this.enableSignLanguage();
10999
- }
12477
+ return this.signLanguageManager.toggle();
11000
12478
  }
11001
12479
  setupSignLanguageInteraction() {
12480
+ return this.signLanguageManager._setupInteraction();
12481
+ }
12482
+ // Legacy method preserved for reference
12483
+ _legacySetupSignLanguageInteraction() {
11002
12484
  if (!this.signLanguageWrapper) return;
11003
12485
  const isMobile2 = window.innerWidth < 768;
11004
12486
  const isFullscreen = this.state.fullscreen;
@@ -11136,6 +12618,10 @@
11136
12618
  return langNames[langCode] || langCode.toUpperCase();
11137
12619
  }
11138
12620
  switchSignLanguage(langCode) {
12621
+ return this.signLanguageManager.switchLanguage(langCode);
12622
+ }
12623
+ // Legacy method preserved for reference
12624
+ _legacySwitchSignLanguage(langCode) {
11139
12625
  if (!this.signLanguageSources[langCode] || !this.signLanguageVideo) {
11140
12626
  return;
11141
12627
  }
@@ -11151,6 +12637,10 @@
11151
12637
  this.emit("signlanguagelanguagechanged", langCode);
11152
12638
  }
11153
12639
  showSignLanguageSettingsMenu() {
12640
+ return this.signLanguageManager.showSettingsMenu();
12641
+ }
12642
+ // Legacy method preserved for reference
12643
+ _legacyShowSignLanguageSettingsMenu() {
11154
12644
  this.signLanguageSettingsMenuJustOpened = true;
11155
12645
  setTimeout(() => {
11156
12646
  this.signLanguageSettingsMenuJustOpened = false;
@@ -11294,25 +12784,7 @@
11294
12784
  focusFirstMenuItem(this.signLanguageSettingsMenu, ".".concat(this.options.classPrefix, "-sign-language-settings-item"));
11295
12785
  }
11296
12786
  hideSignLanguageSettingsMenu({ focusButton = true } = {}) {
11297
- if (this.signLanguageSettingsMenu) {
11298
- this.signLanguageSettingsMenu.style.display = "none";
11299
- this.signLanguageSettingsMenuVisible = false;
11300
- this.signLanguageSettingsMenuJustOpened = false;
11301
- if (this.signLanguageSettingsMenuKeyHandler) {
11302
- this.signLanguageSettingsMenu.removeEventListener("keydown", this.signLanguageSettingsMenuKeyHandler);
11303
- this.signLanguageSettingsMenuKeyHandler = null;
11304
- }
11305
- const menuItems = Array.from(this.signLanguageSettingsMenu.querySelectorAll(".".concat(this.options.classPrefix, "-sign-language-settings-item")));
11306
- menuItems.forEach((item) => {
11307
- item.setAttribute("tabindex", "-1");
11308
- });
11309
- if (this.signLanguageSettingsButton) {
11310
- this.signLanguageSettingsButton.setAttribute("aria-expanded", "false");
11311
- if (focusButton) {
11312
- this.signLanguageSettingsButton.focus({ preventScroll: true });
11313
- }
11314
- }
11315
- }
12787
+ return this.signLanguageManager.hideSettingsMenu({ focusButton });
11316
12788
  }
11317
12789
  positionSignLanguageSettingsMenuImmediate() {
11318
12790
  if (!this.signLanguageSettingsMenu || !this.signLanguageSettingsButton) return;
@@ -11416,6 +12888,13 @@
11416
12888
  }
11417
12889
  }
11418
12890
  constrainSignLanguagePosition() {
12891
+ return this.signLanguageManager.constrainPosition();
12892
+ }
12893
+ saveSignLanguagePreferences() {
12894
+ return this.signLanguageManager.savePreferences();
12895
+ }
12896
+ // Legacy methods preserved for reference - can be removed after testing
12897
+ _legacyConstrainSignLanguagePosition() {
11419
12898
  if (!this.signLanguageWrapper || !this.videoWrapper) return;
11420
12899
  if (this.signLanguageDraggable && this.signLanguageDraggable.manuallyPositioned) {
11421
12900
  return;
@@ -11465,7 +12944,7 @@
11465
12944
  this.signLanguageWrapper.style.bottom = "auto";
11466
12945
  this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
11467
12946
  }
11468
- saveSignLanguagePreferences() {
12947
+ _legacySaveSignLanguagePreferences() {
11469
12948
  if (!this.signLanguageWrapper) return;
11470
12949
  this.storage.saveSignLanguagePreferences({
11471
12950
  size: {
@@ -11475,58 +12954,7 @@
11475
12954
  });
11476
12955
  }
11477
12956
  cleanupSignLanguage() {
11478
- if (this.signLanguageSettingsMenuVisible) {
11479
- this.hideSignLanguageSettingsMenu({ focusButton: false });
11480
- }
11481
- if (this.signLanguageDocumentClickHandler && this.signLanguageDocumentClickHandlerAdded) {
11482
- document.removeEventListener("mousedown", this.signLanguageDocumentClickHandler, true);
11483
- this.signLanguageDocumentClickHandlerAdded = false;
11484
- this.signLanguageDocumentClickHandler = null;
11485
- }
11486
- if (this.signLanguageSettingsHandlers) {
11487
- if (this.signLanguageSettingsButton) {
11488
- this.signLanguageSettingsButton.removeEventListener("click", this.signLanguageSettingsHandlers.settingsClick);
11489
- this.signLanguageSettingsButton.removeEventListener("keydown", this.signLanguageSettingsHandlers.settingsKeydown);
11490
- }
11491
- this.signLanguageSettingsHandlers = null;
11492
- }
11493
- if (this.signLanguageHandlers) {
11494
- this.off("play", this.signLanguageHandlers.play);
11495
- this.off("pause", this.signLanguageHandlers.pause);
11496
- this.off("timeupdate", this.signLanguageHandlers.timeupdate);
11497
- this.off("ratechange", this.signLanguageHandlers.ratechange);
11498
- if (this.signLanguageHandlers.captionChange) {
11499
- this.off("captionsenabled", this.signLanguageHandlers.captionChange);
11500
- }
11501
- this.signLanguageHandlers = null;
11502
- }
11503
- if (this.signLanguageInteractionHandlers) {
11504
- if (this.signLanguageHeader && this.signLanguageInteractionHandlers.headerKeyHandler) {
11505
- this.signLanguageHeader.removeEventListener("keydown", this.signLanguageInteractionHandlers.headerKeyHandler);
11506
- }
11507
- if (this.signLanguageWrapper && this.signLanguageInteractionHandlers.customKeyHandler) {
11508
- this.signLanguageWrapper.removeEventListener("keydown", this.signLanguageInteractionHandlers.customKeyHandler);
11509
- }
11510
- }
11511
- if (this.signLanguageDraggable) {
11512
- if (this.signLanguageDraggable.pointerResizeMode) {
11513
- this.signLanguageDraggable.disablePointerResizeMode();
11514
- }
11515
- this.signLanguageDraggable.destroy();
11516
- this.signLanguageDraggable = null;
11517
- }
11518
- this.signLanguageInteractionHandlers = null;
11519
- if (this.signLanguageWrapper && this.signLanguageWrapper.parentNode) {
11520
- if (this.signLanguageVideo) {
11521
- this.signLanguageVideo.pause();
11522
- this.signLanguageVideo.src = "";
11523
- }
11524
- this.signLanguageWrapper.parentNode.removeChild(this.signLanguageWrapper);
11525
- }
11526
- this.signLanguageWrapper = null;
11527
- this.signLanguageVideo = null;
11528
- this.signLanguageSettingsButton = null;
11529
- this.signLanguageSettingsMenu = null;
12957
+ return this.signLanguageManager.cleanup();
11530
12958
  }
11531
12959
  // Settings
11532
12960
  // Settings dialog removed - using individual control buttons instead