vidply 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js +266 -0
- package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js.map +7 -0
- package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js +12 -0
- package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js.map +7 -0
- package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
- package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
- package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
- package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
- package/dist/dev/vidply.chunk-W2LSBD6Y.js +251 -0
- package/dist/dev/vidply.chunk-W2LSBD6Y.js.map +7 -0
- package/dist/dev/vidply.esm.js +1880 -258
- package/dist/dev/vidply.esm.js.map +4 -4
- package/dist/legacy/vidply.js +2056 -365
- package/dist/legacy/vidply.js.map +4 -4
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +111 -25
- package/dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js +6 -0
- package/dist/prod/vidply.HTML5Renderer-KKW3OLHM.min.js +6 -0
- package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
- package/dist/prod/vidply.chunk-34RH2THY.min.js +6 -0
- package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +8 -8
- package/dist/vidply.css +20 -1
- package/dist/vidply.esm.min.meta.json +120 -34
- package/dist/vidply.min.css +1 -1
- package/package.json +2 -2
- package/src/controls/ControlBar.js +182 -10
- package/src/controls/TranscriptManager.js +7 -7
- package/src/core/AudioDescriptionManager.js +701 -0
- package/src/core/Player.js +203 -256
- package/src/core/SignLanguageManager.js +1134 -0
- package/src/renderers/HTML5Renderer.js +7 -0
- package/src/styles/vidply.css +20 -1
- package/src/utils/DOMUtils.js +153 -114
- package/src/utils/MenuFactory.js +374 -0
- package/src/utils/VideoFrameCapture.js +110 -0
- package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
- package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
- package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
- package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
- package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
- package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
- package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
- package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
- package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
- package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
- package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
- package/dist/dev/vidply.de-THBIMP4S.js +0 -180
- package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
- package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
- package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
- package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
- package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
- package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
- package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
- package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
- package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
- package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
- package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
- package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
- package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
- package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
- package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
- package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
- package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
- package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
- package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
- package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
- package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
- package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
- package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
- package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
- package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
- package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
- package/dist/prod/vidply.ja-QTVU5C25.min.js +0 -6
package/dist/legacy/vidply.js
CHANGED
|
@@ -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)
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
element.style.display = "none";
|
|
89
|
-
}
|
|
81
|
+
(element == null ? void 0 : element.style) && (element.style.display = "none");
|
|
90
82
|
},
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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.
|
|
129
|
-
left: rect.left + window.
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
const escapeMap = {
|
|
145
|
+
"&": "&",
|
|
146
|
+
"<": "<",
|
|
147
|
+
">": ">",
|
|
148
|
+
'"': """,
|
|
149
|
+
"'": "'"
|
|
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
|
|
166
|
+
* Create a tooltip element (aria-hidden)
|
|
147
167
|
* @param {string} text - Tooltip text
|
|
148
|
-
* @param {string} classPrefix - Class prefix
|
|
149
|
-
* @returns {HTMLElement}
|
|
168
|
+
* @param {string} classPrefix - Class prefix
|
|
169
|
+
* @returns {HTMLElement}
|
|
150
170
|
*/
|
|
151
171
|
createTooltip(text, classPrefix = "vidply") {
|
|
152
|
-
|
|
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 -
|
|
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
|
|
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
|
-
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
element.addEventListener("
|
|
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
|
|
199
|
+
* Create button text element (visible when CSS disabled)
|
|
188
200
|
* @param {string} text - Button text
|
|
189
|
-
* @param {string} classPrefix - Class prefix
|
|
190
|
-
* @returns {HTMLElement}
|
|
201
|
+
* @param {string} classPrefix - Class prefix
|
|
202
|
+
* @returns {HTMLElement}
|
|
191
203
|
*/
|
|
192
204
|
createButtonText(text, classPrefix = "vidply") {
|
|
193
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1695,6 +1741,11 @@
|
|
|
1695
1741
|
this.media.addEventListener("loadedmetadata", () => {
|
|
1696
1742
|
this.player.state.duration = this.media.duration;
|
|
1697
1743
|
this.player.emit("loadedmetadata");
|
|
1744
|
+
if (this.media.tagName === "VIDEO") {
|
|
1745
|
+
this.player.autoGeneratePoster().catch((error) => {
|
|
1746
|
+
this.player.log("Failed to auto-generate poster:", error, "warn");
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1698
1749
|
});
|
|
1699
1750
|
this.media.addEventListener("play", () => {
|
|
1700
1751
|
this.player.state.playing = true;
|
|
@@ -3268,8 +3319,7 @@
|
|
|
3268
3319
|
descriptionTrack = textTracks.find((track) => track.kind === "descriptions");
|
|
3269
3320
|
}
|
|
3270
3321
|
const metadataTrack = textTracks.find((track) => track.kind === "metadata");
|
|
3271
|
-
|
|
3272
|
-
if (!captionTrack && !hasDescriptionTrack && !metadataTrack) {
|
|
3322
|
+
if (!captionTrack && !descriptionTrack && !metadataTrack) {
|
|
3273
3323
|
this.showNoTranscriptMessage();
|
|
3274
3324
|
return;
|
|
3275
3325
|
}
|
|
@@ -3307,7 +3357,7 @@
|
|
|
3307
3357
|
allCues.push({ cue, type: "caption" });
|
|
3308
3358
|
});
|
|
3309
3359
|
}
|
|
3310
|
-
if (descriptionTrack && descriptionTrack.cues
|
|
3360
|
+
if (descriptionTrack && descriptionTrack.cues) {
|
|
3311
3361
|
Array.from(descriptionTrack.cues).forEach((cue) => {
|
|
3312
3362
|
allCues.push({ cue, type: "description" });
|
|
3313
3363
|
});
|
|
@@ -5501,6 +5551,110 @@
|
|
|
5501
5551
|
init_Icons();
|
|
5502
5552
|
init_i18n();
|
|
5503
5553
|
init_FocusUtils();
|
|
5554
|
+
|
|
5555
|
+
// src/utils/PerformanceUtils.js
|
|
5556
|
+
function debounce(func, wait = 100) {
|
|
5557
|
+
let timeout;
|
|
5558
|
+
return function executedFunction(...args) {
|
|
5559
|
+
const later = () => {
|
|
5560
|
+
clearTimeout(timeout);
|
|
5561
|
+
func(...args);
|
|
5562
|
+
};
|
|
5563
|
+
clearTimeout(timeout);
|
|
5564
|
+
timeout = setTimeout(later, wait);
|
|
5565
|
+
};
|
|
5566
|
+
}
|
|
5567
|
+
function isMobile(breakpoint = 768) {
|
|
5568
|
+
return window.innerWidth < breakpoint;
|
|
5569
|
+
}
|
|
5570
|
+
function rafWithTimeout(callback, timeout = 100) {
|
|
5571
|
+
let called = false;
|
|
5572
|
+
const execute = () => {
|
|
5573
|
+
if (!called) {
|
|
5574
|
+
called = true;
|
|
5575
|
+
callback();
|
|
5576
|
+
}
|
|
5577
|
+
};
|
|
5578
|
+
requestAnimationFrame(execute);
|
|
5579
|
+
setTimeout(execute, timeout);
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5582
|
+
// src/utils/VideoFrameCapture.js
|
|
5583
|
+
async function captureVideoFrame(video, time, options = {}) {
|
|
5584
|
+
if (!video || video.tagName !== "VIDEO") {
|
|
5585
|
+
return null;
|
|
5586
|
+
}
|
|
5587
|
+
const {
|
|
5588
|
+
restoreState = true,
|
|
5589
|
+
quality = 0.9,
|
|
5590
|
+
maxWidth,
|
|
5591
|
+
maxHeight
|
|
5592
|
+
} = options;
|
|
5593
|
+
const wasPlaying = !video.paused;
|
|
5594
|
+
const originalTime = video.currentTime;
|
|
5595
|
+
const originalMuted = video.muted;
|
|
5596
|
+
if (restoreState) {
|
|
5597
|
+
video.muted = true;
|
|
5598
|
+
}
|
|
5599
|
+
return new Promise((resolve) => {
|
|
5600
|
+
const captureFrame = () => {
|
|
5601
|
+
try {
|
|
5602
|
+
let width = video.videoWidth || 640;
|
|
5603
|
+
let height = video.videoHeight || 360;
|
|
5604
|
+
if (maxWidth && width > maxWidth) {
|
|
5605
|
+
const ratio = maxWidth / width;
|
|
5606
|
+
width = maxWidth;
|
|
5607
|
+
height = Math.round(height * ratio);
|
|
5608
|
+
}
|
|
5609
|
+
if (maxHeight && height > maxHeight) {
|
|
5610
|
+
const ratio = maxHeight / height;
|
|
5611
|
+
height = maxHeight;
|
|
5612
|
+
width = Math.round(width * ratio);
|
|
5613
|
+
}
|
|
5614
|
+
const canvas = document.createElement("canvas");
|
|
5615
|
+
canvas.width = width;
|
|
5616
|
+
canvas.height = height;
|
|
5617
|
+
const ctx = canvas.getContext("2d");
|
|
5618
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
5619
|
+
const dataURL = canvas.toDataURL("image/jpeg", quality);
|
|
5620
|
+
if (restoreState) {
|
|
5621
|
+
video.currentTime = originalTime;
|
|
5622
|
+
video.muted = originalMuted;
|
|
5623
|
+
if (wasPlaying && !video.paused) {
|
|
5624
|
+
video.play().catch(() => {
|
|
5625
|
+
});
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
resolve(dataURL);
|
|
5629
|
+
} catch (error) {
|
|
5630
|
+
if (restoreState) {
|
|
5631
|
+
video.currentTime = originalTime;
|
|
5632
|
+
video.muted = originalMuted;
|
|
5633
|
+
if (wasPlaying && !video.paused) {
|
|
5634
|
+
video.play().catch(() => {
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5638
|
+
resolve(null);
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
const onSeeked = () => {
|
|
5642
|
+
video.removeEventListener("seeked", onSeeked);
|
|
5643
|
+
requestAnimationFrame(() => {
|
|
5644
|
+
requestAnimationFrame(captureFrame);
|
|
5645
|
+
});
|
|
5646
|
+
};
|
|
5647
|
+
const timeDiff = Math.abs(video.currentTime - time);
|
|
5648
|
+
if (timeDiff < 0.1 && video.readyState >= 2) {
|
|
5649
|
+
captureFrame();
|
|
5650
|
+
} else {
|
|
5651
|
+
video.addEventListener("seeked", onSeeked);
|
|
5652
|
+
video.currentTime = time;
|
|
5653
|
+
}
|
|
5654
|
+
});
|
|
5655
|
+
}
|
|
5656
|
+
|
|
5657
|
+
// src/controls/ControlBar.js
|
|
5504
5658
|
var ControlBar = class {
|
|
5505
5659
|
constructor(player) {
|
|
5506
5660
|
this.player = player;
|
|
@@ -5520,17 +5674,13 @@
|
|
|
5520
5674
|
this.setupAutoHide();
|
|
5521
5675
|
this.setupOverflowDetection();
|
|
5522
5676
|
}
|
|
5523
|
-
// Helper method to check if we're on a mobile device
|
|
5524
|
-
isMobile() {
|
|
5525
|
-
return window.innerWidth < 768;
|
|
5526
|
-
}
|
|
5527
5677
|
// Helper method to detect touch devices
|
|
5528
5678
|
isTouchDevice() {
|
|
5529
5679
|
return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
|
5530
5680
|
}
|
|
5531
5681
|
// Smart menu positioning to avoid overflow
|
|
5532
5682
|
positionMenu(menu, button, immediate = false) {
|
|
5533
|
-
const
|
|
5683
|
+
const mobile = isMobile();
|
|
5534
5684
|
const isOverflowMenu = menu.classList.contains("".concat(this.player.options.classPrefix, "-overflow-menu-list"));
|
|
5535
5685
|
const isFullscreen = this.player.state.fullscreen;
|
|
5536
5686
|
if (isFullscreen && menu.parentElement === this.player.container) {
|
|
@@ -5571,7 +5721,7 @@
|
|
|
5571
5721
|
}
|
|
5572
5722
|
return;
|
|
5573
5723
|
}
|
|
5574
|
-
if (
|
|
5724
|
+
if (mobile) {
|
|
5575
5725
|
const isVolumeMenu = menu.classList.contains("".concat(this.player.options.classPrefix, "-volume-menu"));
|
|
5576
5726
|
const doMobilePositioning = () => {
|
|
5577
5727
|
const parentContainer = button.parentElement;
|
|
@@ -6141,13 +6291,117 @@
|
|
|
6141
6291
|
this.controls.progressTooltip = DOMUtils.createElement("div", {
|
|
6142
6292
|
className: "".concat(this.player.options.classPrefix, "-progress-tooltip")
|
|
6143
6293
|
});
|
|
6294
|
+
this.controls.progressPreview = DOMUtils.createElement("div", {
|
|
6295
|
+
className: "".concat(this.player.options.classPrefix, "-progress-preview"),
|
|
6296
|
+
attributes: {
|
|
6297
|
+
"aria-hidden": "true"
|
|
6298
|
+
}
|
|
6299
|
+
});
|
|
6300
|
+
this.controls.progressTooltip.appendChild(this.controls.progressPreview);
|
|
6301
|
+
this.controls.progressTooltipTime = DOMUtils.createElement("div", {
|
|
6302
|
+
className: "".concat(this.player.options.classPrefix, "-progress-tooltip-time")
|
|
6303
|
+
});
|
|
6304
|
+
this.controls.progressTooltip.appendChild(this.controls.progressTooltipTime);
|
|
6144
6305
|
progressContainer.appendChild(this.controls.buffered);
|
|
6145
6306
|
progressContainer.appendChild(this.controls.played);
|
|
6146
6307
|
this.controls.played.appendChild(this.controls.progressHandle);
|
|
6147
6308
|
progressContainer.appendChild(this.controls.progressTooltip);
|
|
6148
6309
|
this.controls.progress = progressContainer;
|
|
6310
|
+
this.initPreviewThumbnail();
|
|
6149
6311
|
this.setupProgressBarEvents();
|
|
6150
6312
|
}
|
|
6313
|
+
/**
|
|
6314
|
+
* Initialize preview thumbnail functionality for HTML5 video
|
|
6315
|
+
*/
|
|
6316
|
+
initPreviewThumbnail() {
|
|
6317
|
+
this.previewThumbnailCache = /* @__PURE__ */ new Map();
|
|
6318
|
+
this.previewVideo = null;
|
|
6319
|
+
this.currentPreviewTime = null;
|
|
6320
|
+
this.previewThumbnailTimeout = null;
|
|
6321
|
+
this.previewSupported = false;
|
|
6322
|
+
const isVideo = this.player.element && this.player.element.tagName === "VIDEO";
|
|
6323
|
+
if (!isVideo) {
|
|
6324
|
+
return;
|
|
6325
|
+
}
|
|
6326
|
+
const renderer = this.player.renderer;
|
|
6327
|
+
const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === "VIDEO";
|
|
6328
|
+
const isHTML5Renderer = renderer && (renderer.constructor.name === "HTML5Renderer" || renderer.constructor.name === "HLSRenderer" && hasVideoMedia);
|
|
6329
|
+
this.previewSupported = isHTML5Renderer && hasVideoMedia;
|
|
6330
|
+
if (this.previewSupported) {
|
|
6331
|
+
this.previewVideo = document.createElement("video");
|
|
6332
|
+
this.previewVideo.muted = true;
|
|
6333
|
+
this.previewVideo.preload = "metadata";
|
|
6334
|
+
this.previewVideo.style.position = "absolute";
|
|
6335
|
+
this.previewVideo.style.visibility = "hidden";
|
|
6336
|
+
this.previewVideo.style.width = "1px";
|
|
6337
|
+
this.previewVideo.style.height = "1px";
|
|
6338
|
+
this.previewVideo.style.top = "-9999px";
|
|
6339
|
+
const mainVideo = renderer.media || this.player.element;
|
|
6340
|
+
if (mainVideo.src) {
|
|
6341
|
+
this.previewVideo.src = mainVideo.src;
|
|
6342
|
+
} else {
|
|
6343
|
+
const source = mainVideo.querySelector("source");
|
|
6344
|
+
if (source) {
|
|
6345
|
+
this.previewVideo.src = source.src;
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
this.previewVideo.addEventListener("error", () => {
|
|
6349
|
+
this.player.log("Preview video failed to load", "warn");
|
|
6350
|
+
this.previewSupported = false;
|
|
6351
|
+
});
|
|
6352
|
+
if (this.player.container) {
|
|
6353
|
+
this.player.container.appendChild(this.previewVideo);
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
/**
|
|
6358
|
+
* Generate preview thumbnail for a specific time
|
|
6359
|
+
* @param {number} time - Time in seconds
|
|
6360
|
+
* @returns {Promise<string>} Data URL of the thumbnail
|
|
6361
|
+
*/
|
|
6362
|
+
async generatePreviewThumbnail(time) {
|
|
6363
|
+
if (!this.previewSupported || !this.previewVideo) {
|
|
6364
|
+
return null;
|
|
6365
|
+
}
|
|
6366
|
+
const cacheKey = Math.floor(time);
|
|
6367
|
+
if (this.previewThumbnailCache.has(cacheKey)) {
|
|
6368
|
+
return this.previewThumbnailCache.get(cacheKey);
|
|
6369
|
+
}
|
|
6370
|
+
const dataURL = await captureVideoFrame(this.previewVideo, time, {
|
|
6371
|
+
restoreState: false,
|
|
6372
|
+
quality: 0.8,
|
|
6373
|
+
maxWidth: 160,
|
|
6374
|
+
maxHeight: 90
|
|
6375
|
+
});
|
|
6376
|
+
if (dataURL) {
|
|
6377
|
+
if (this.previewThumbnailCache.size > 20) {
|
|
6378
|
+
const firstKey = this.previewThumbnailCache.keys().next().value;
|
|
6379
|
+
this.previewThumbnailCache.delete(firstKey);
|
|
6380
|
+
}
|
|
6381
|
+
this.previewThumbnailCache.set(cacheKey, dataURL);
|
|
6382
|
+
}
|
|
6383
|
+
return dataURL;
|
|
6384
|
+
}
|
|
6385
|
+
/**
|
|
6386
|
+
* Update preview thumbnail display
|
|
6387
|
+
* @param {number} time - Time in seconds
|
|
6388
|
+
*/
|
|
6389
|
+
async updatePreviewThumbnail(time) {
|
|
6390
|
+
if (!this.previewSupported) {
|
|
6391
|
+
return;
|
|
6392
|
+
}
|
|
6393
|
+
if (this.previewThumbnailTimeout) {
|
|
6394
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
6395
|
+
}
|
|
6396
|
+
this.previewThumbnailTimeout = setTimeout(async () => {
|
|
6397
|
+
const thumbnail = await this.generatePreviewThumbnail(time);
|
|
6398
|
+
if (thumbnail && this.controls.progressPreview) {
|
|
6399
|
+
this.controls.progressPreview.style.backgroundImage = "url(".concat(thumbnail, ")");
|
|
6400
|
+
this.controls.progressPreview.style.display = "block";
|
|
6401
|
+
}
|
|
6402
|
+
this.currentPreviewTime = time;
|
|
6403
|
+
}, 100);
|
|
6404
|
+
}
|
|
6151
6405
|
setupProgressBarEvents() {
|
|
6152
6406
|
const progress = this.controls.progress;
|
|
6153
6407
|
const updateProgress = (clientX) => {
|
|
@@ -6174,13 +6428,19 @@
|
|
|
6174
6428
|
progress.addEventListener("mousemove", (e) => {
|
|
6175
6429
|
if (!this.isDraggingProgress) {
|
|
6176
6430
|
const { time } = updateProgress(e.clientX);
|
|
6177
|
-
|
|
6178
|
-
|
|
6431
|
+
const rect = progress.getBoundingClientRect();
|
|
6432
|
+
const left = e.clientX - rect.left;
|
|
6433
|
+
this.controls.progressTooltipTime.textContent = TimeUtils.formatTime(time);
|
|
6434
|
+
this.controls.progressTooltip.style.left = "".concat(left, "px");
|
|
6179
6435
|
this.controls.progressTooltip.style.display = "block";
|
|
6436
|
+
this.updatePreviewThumbnail(time);
|
|
6180
6437
|
}
|
|
6181
6438
|
});
|
|
6182
6439
|
progress.addEventListener("mouseleave", () => {
|
|
6183
6440
|
this.controls.progressTooltip.style.display = "none";
|
|
6441
|
+
if (this.previewThumbnailTimeout) {
|
|
6442
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
6443
|
+
}
|
|
6184
6444
|
});
|
|
6185
6445
|
progress.addEventListener("keydown", (e) => {
|
|
6186
6446
|
if (e.key === "ArrowLeft") {
|
|
@@ -7927,6 +8187,20 @@
|
|
|
7927
8187
|
hide() {
|
|
7928
8188
|
this.element.style.display = "none";
|
|
7929
8189
|
}
|
|
8190
|
+
/**
|
|
8191
|
+
* Cleanup preview thumbnail resources
|
|
8192
|
+
*/
|
|
8193
|
+
cleanupPreviewThumbnail() {
|
|
8194
|
+
if (this.previewThumbnailTimeout) {
|
|
8195
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
8196
|
+
this.previewThumbnailTimeout = null;
|
|
8197
|
+
}
|
|
8198
|
+
if (this.previewVideo && this.previewVideo.parentNode) {
|
|
8199
|
+
this.previewVideo.parentNode.removeChild(this.previewVideo);
|
|
8200
|
+
this.previewVideo = null;
|
|
8201
|
+
}
|
|
8202
|
+
this.previewThumbnailCache.clear();
|
|
8203
|
+
}
|
|
7930
8204
|
destroy() {
|
|
7931
8205
|
if (this.hideTimeout) {
|
|
7932
8206
|
clearTimeout(this.hideTimeout);
|
|
@@ -7934,6 +8208,7 @@
|
|
|
7934
8208
|
if (this.overflowResizeObserver) {
|
|
7935
8209
|
this.overflowResizeObserver.disconnect();
|
|
7936
8210
|
}
|
|
8211
|
+
this.cleanupPreviewThumbnail();
|
|
7937
8212
|
if (this.element && this.element.parentNode) {
|
|
7938
8213
|
this.element.parentNode.removeChild(this.element);
|
|
7939
8214
|
}
|
|
@@ -7944,35 +8219,6 @@
|
|
|
7944
8219
|
init_DOMUtils();
|
|
7945
8220
|
init_i18n();
|
|
7946
8221
|
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
8222
|
var CaptionManager = class {
|
|
7977
8223
|
constructor(player) {
|
|
7978
8224
|
this.player = player;
|
|
@@ -8504,49 +8750,1523 @@
|
|
|
8504
8750
|
init_DraggableResizable();
|
|
8505
8751
|
init_MenuUtils();
|
|
8506
8752
|
init_FormUtils();
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
this.
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
this.
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8753
|
+
|
|
8754
|
+
// src/core/AudioDescriptionManager.js
|
|
8755
|
+
var AudioDescriptionManager = class {
|
|
8756
|
+
constructor(player) {
|
|
8757
|
+
this.player = player;
|
|
8758
|
+
this.enabled = false;
|
|
8759
|
+
this.desiredState = false;
|
|
8760
|
+
this.src = player.options.audioDescriptionSrc;
|
|
8761
|
+
this.sourceElement = null;
|
|
8762
|
+
this.originalSource = null;
|
|
8763
|
+
this.captionTracks = [];
|
|
8764
|
+
}
|
|
8765
|
+
/**
|
|
8766
|
+
* Initialize audio description from source elements
|
|
8767
|
+
* Called during player initialization
|
|
8768
|
+
*/
|
|
8769
|
+
initFromSourceElements(sourceElements, trackElements) {
|
|
8770
|
+
for (const sourceEl of sourceElements) {
|
|
8771
|
+
const descSrc = sourceEl.getAttribute("data-desc-src");
|
|
8772
|
+
const origSrc = sourceEl.getAttribute("data-orig-src");
|
|
8773
|
+
if (descSrc || origSrc) {
|
|
8774
|
+
if (!this.sourceElement) {
|
|
8775
|
+
this.sourceElement = sourceEl;
|
|
8523
8776
|
}
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8777
|
+
if (origSrc) {
|
|
8778
|
+
if (!this.originalSource) {
|
|
8779
|
+
this.originalSource = origSrc;
|
|
8780
|
+
}
|
|
8781
|
+
if (!this.player.originalSrc) {
|
|
8782
|
+
this.player.originalSrc = origSrc;
|
|
8783
|
+
}
|
|
8784
|
+
} else {
|
|
8785
|
+
const currentSrcAttr = sourceEl.getAttribute("src");
|
|
8786
|
+
if (!this.originalSource && currentSrcAttr) {
|
|
8787
|
+
this.originalSource = currentSrcAttr;
|
|
8788
|
+
}
|
|
8789
|
+
if (!this.player.originalSrc && currentSrcAttr) {
|
|
8790
|
+
this.player.originalSrc = currentSrcAttr;
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8793
|
+
if (descSrc && !this.src) {
|
|
8794
|
+
this.src = descSrc;
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8532
8797
|
}
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8798
|
+
trackElements.forEach((trackEl) => {
|
|
8799
|
+
const trackKind = trackEl.getAttribute("kind");
|
|
8800
|
+
const trackDescSrc = trackEl.getAttribute("data-desc-src");
|
|
8801
|
+
if ((trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters" || trackKind === "descriptions") && trackDescSrc) {
|
|
8802
|
+
this.captionTracks.push({
|
|
8803
|
+
trackElement: trackEl,
|
|
8804
|
+
originalSrc: trackEl.getAttribute("src"),
|
|
8805
|
+
describedSrc: trackDescSrc,
|
|
8806
|
+
originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
|
|
8807
|
+
explicit: true
|
|
8808
|
+
});
|
|
8809
|
+
this.player.log("Found explicit described ".concat(trackKind, " track: ").concat(trackEl.getAttribute("src"), " -> ").concat(trackDescSrc));
|
|
8810
|
+
}
|
|
8811
|
+
});
|
|
8812
|
+
}
|
|
8813
|
+
/**
|
|
8814
|
+
* Check if audio description is available
|
|
8815
|
+
*/
|
|
8816
|
+
isAvailable() {
|
|
8817
|
+
const hasSourceElementsWithDesc = this.player.sourceElements.some(
|
|
8818
|
+
(el) => el.getAttribute("data-desc-src")
|
|
8819
|
+
);
|
|
8820
|
+
return !!(this.src || hasSourceElementsWithDesc || this.captionTracks.length > 0);
|
|
8821
|
+
}
|
|
8822
|
+
/**
|
|
8823
|
+
* Enable audio description
|
|
8824
|
+
*/
|
|
8825
|
+
async enable() {
|
|
8826
|
+
const hasSourceElementsWithDesc = this.player.sourceElements.some(
|
|
8827
|
+
(el) => el.getAttribute("data-desc-src")
|
|
8828
|
+
);
|
|
8829
|
+
const hasTracksWithDesc = this.captionTracks.length > 0;
|
|
8830
|
+
if (!this.src && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
|
|
8831
|
+
console.warn("VidPly: No audio description source, source elements, or tracks provided");
|
|
8832
|
+
return;
|
|
8833
|
+
}
|
|
8834
|
+
this.desiredState = true;
|
|
8835
|
+
const currentTime = this.player.state.currentTime;
|
|
8836
|
+
const wasPlaying = this.player.state.playing;
|
|
8837
|
+
const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
|
|
8838
|
+
const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
|
|
8839
|
+
const currentCaptionText = this._getCurrentCaptionText();
|
|
8840
|
+
if (this.sourceElement) {
|
|
8841
|
+
await this._enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
|
|
8842
|
+
} else {
|
|
8843
|
+
await this._enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster);
|
|
8844
|
+
}
|
|
8845
|
+
}
|
|
8846
|
+
/**
|
|
8847
|
+
* Disable audio description
|
|
8848
|
+
*/
|
|
8849
|
+
async disable() {
|
|
8850
|
+
if (!this.player.originalSrc) {
|
|
8851
|
+
return;
|
|
8852
|
+
}
|
|
8853
|
+
this.desiredState = false;
|
|
8854
|
+
const currentTime = this.player.state.currentTime;
|
|
8855
|
+
const wasPlaying = this.player.state.playing;
|
|
8856
|
+
const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
|
|
8857
|
+
const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
|
|
8858
|
+
const currentCaptionText = this._getCurrentCaptionText();
|
|
8859
|
+
if (this.sourceElement) {
|
|
8860
|
+
await this._disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
|
|
8861
|
+
} else {
|
|
8862
|
+
await this._disableWithDirectSrc(currentTime, wasPlaying, posterValue);
|
|
8863
|
+
}
|
|
8864
|
+
}
|
|
8865
|
+
/**
|
|
8866
|
+
* Toggle audio description
|
|
8867
|
+
*/
|
|
8868
|
+
async toggle() {
|
|
8869
|
+
const descriptionTrack = this.player.findTextTrack("descriptions");
|
|
8870
|
+
const hasAudioDescriptionSrc = this.isAvailable();
|
|
8871
|
+
if (descriptionTrack && !hasAudioDescriptionSrc) {
|
|
8872
|
+
if (descriptionTrack.mode === "showing") {
|
|
8873
|
+
descriptionTrack.mode = "hidden";
|
|
8874
|
+
this.enabled = false;
|
|
8875
|
+
this.player.emit("audiodescriptiondisabled");
|
|
8876
|
+
} else {
|
|
8877
|
+
descriptionTrack.mode = "showing";
|
|
8878
|
+
this.enabled = true;
|
|
8879
|
+
this.player.emit("audiodescriptionenabled");
|
|
8880
|
+
}
|
|
8881
|
+
} else if (descriptionTrack && hasAudioDescriptionSrc) {
|
|
8882
|
+
if (this.enabled) {
|
|
8883
|
+
this.desiredState = false;
|
|
8884
|
+
await this.disable();
|
|
8885
|
+
} else {
|
|
8886
|
+
descriptionTrack.mode = "showing";
|
|
8887
|
+
this.desiredState = true;
|
|
8888
|
+
await this.enable();
|
|
8889
|
+
}
|
|
8890
|
+
} else if (hasAudioDescriptionSrc) {
|
|
8891
|
+
if (this.enabled) {
|
|
8892
|
+
this.desiredState = false;
|
|
8893
|
+
await this.disable();
|
|
8894
|
+
} else {
|
|
8895
|
+
this.desiredState = true;
|
|
8896
|
+
await this.enable();
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
/**
|
|
8901
|
+
* Get current caption text for synchronization
|
|
8902
|
+
*/
|
|
8903
|
+
_getCurrentCaptionText() {
|
|
8904
|
+
if (this.player.captionManager && this.player.captionManager.currentTrack && this.player.captionManager.currentCue) {
|
|
8905
|
+
return this.player.captionManager.currentCue.text;
|
|
8906
|
+
}
|
|
8907
|
+
return null;
|
|
8908
|
+
}
|
|
8909
|
+
/**
|
|
8910
|
+
* Validate that a track URL exists
|
|
8911
|
+
*/
|
|
8912
|
+
async _validateTrackExists(url) {
|
|
8913
|
+
try {
|
|
8914
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
8915
|
+
return response.ok;
|
|
8916
|
+
} catch (e) {
|
|
8917
|
+
return false;
|
|
8918
|
+
}
|
|
8919
|
+
}
|
|
8920
|
+
/**
|
|
8921
|
+
* Swap caption tracks to described versions
|
|
8922
|
+
*/
|
|
8923
|
+
async _swapCaptionTracks(toDescribed = true) {
|
|
8924
|
+
if (this.captionTracks.length === 0) return [];
|
|
8925
|
+
const swappedTracks = [];
|
|
8926
|
+
const validationPromises = this.captionTracks.map(async (trackInfo) => {
|
|
8927
|
+
if (trackInfo.trackElement && trackInfo.describedSrc) {
|
|
8928
|
+
if (trackInfo.explicit === true) {
|
|
8929
|
+
try {
|
|
8930
|
+
const exists = await this._validateTrackExists(
|
|
8931
|
+
toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc
|
|
8932
|
+
);
|
|
8933
|
+
return { trackInfo, exists };
|
|
8934
|
+
} catch (e) {
|
|
8935
|
+
return { trackInfo, exists: false };
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8939
|
+
return { trackInfo, exists: false };
|
|
8940
|
+
});
|
|
8941
|
+
const validationResults = await Promise.all(validationPromises);
|
|
8942
|
+
const tracksToSwap = validationResults.filter((result) => result.exists);
|
|
8943
|
+
if (tracksToSwap.length > 0) {
|
|
8944
|
+
const trackModes = /* @__PURE__ */ new Map();
|
|
8945
|
+
tracksToSwap.forEach(({ trackInfo }) => {
|
|
8946
|
+
const textTrack = trackInfo.trackElement.track;
|
|
8947
|
+
if (textTrack) {
|
|
8948
|
+
trackModes.set(trackInfo, {
|
|
8949
|
+
wasShowing: textTrack.mode === "showing",
|
|
8950
|
+
wasHidden: textTrack.mode === "hidden"
|
|
8951
|
+
});
|
|
8952
|
+
} else {
|
|
8953
|
+
trackModes.set(trackInfo, { wasShowing: false, wasHidden: false });
|
|
8954
|
+
}
|
|
8955
|
+
});
|
|
8956
|
+
const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
|
|
8957
|
+
const attributes = {};
|
|
8958
|
+
Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
|
|
8959
|
+
attributes[attr.name] = attr.value;
|
|
8960
|
+
});
|
|
8961
|
+
const result = {
|
|
8962
|
+
trackInfo,
|
|
8963
|
+
oldSrc: trackInfo.trackElement.getAttribute("src"),
|
|
8964
|
+
parent: trackInfo.trackElement.parentNode,
|
|
8965
|
+
nextSibling: trackInfo.trackElement.nextSibling,
|
|
8966
|
+
attributes
|
|
8967
|
+
};
|
|
8968
|
+
trackInfo.trackElement.remove();
|
|
8969
|
+
return result;
|
|
8970
|
+
});
|
|
8971
|
+
this.player.element.load();
|
|
8972
|
+
await new Promise((resolve) => {
|
|
8973
|
+
setTimeout(() => {
|
|
8974
|
+
tracksToReadd.forEach(({ trackInfo, parent, nextSibling, attributes }) => {
|
|
8975
|
+
swappedTracks.push(trackInfo);
|
|
8976
|
+
const newTrackElement = document.createElement("track");
|
|
8977
|
+
const newSrc = toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc;
|
|
8978
|
+
newTrackElement.setAttribute("src", newSrc);
|
|
8979
|
+
Object.keys(attributes).forEach((attrName) => {
|
|
8980
|
+
if (attrName !== "src" && attrName !== "data-desc-src") {
|
|
8981
|
+
newTrackElement.setAttribute(attrName, attributes[attrName]);
|
|
8982
|
+
}
|
|
8983
|
+
});
|
|
8984
|
+
const targetParent = parent || this.player.element;
|
|
8985
|
+
if (nextSibling && nextSibling.parentNode) {
|
|
8986
|
+
targetParent.insertBefore(newTrackElement, nextSibling);
|
|
8987
|
+
} else {
|
|
8988
|
+
targetParent.appendChild(newTrackElement);
|
|
8989
|
+
}
|
|
8990
|
+
trackInfo.trackElement = newTrackElement;
|
|
8991
|
+
});
|
|
8992
|
+
this.player.invalidateTrackCache();
|
|
8993
|
+
const setupNewTracks = () => {
|
|
8994
|
+
this.player.setManagedTimeout(() => {
|
|
8995
|
+
swappedTracks.forEach((trackInfo) => {
|
|
8996
|
+
const newTextTrack = trackInfo.trackElement.track;
|
|
8997
|
+
if (newTextTrack) {
|
|
8998
|
+
const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
|
|
8999
|
+
newTextTrack.mode = "hidden";
|
|
9000
|
+
const restoreMode = () => {
|
|
9001
|
+
if (modeInfo.wasShowing || modeInfo.wasHidden) {
|
|
9002
|
+
newTextTrack.mode = "hidden";
|
|
9003
|
+
} else {
|
|
9004
|
+
newTextTrack.mode = "disabled";
|
|
9005
|
+
}
|
|
9006
|
+
};
|
|
9007
|
+
if (newTextTrack.readyState >= 2) {
|
|
9008
|
+
restoreMode();
|
|
9009
|
+
} else {
|
|
9010
|
+
newTextTrack.addEventListener("load", restoreMode, { once: true });
|
|
9011
|
+
newTextTrack.addEventListener("error", restoreMode, { once: true });
|
|
9012
|
+
}
|
|
9013
|
+
}
|
|
9014
|
+
});
|
|
9015
|
+
}, 300);
|
|
9016
|
+
};
|
|
9017
|
+
if (this.player.element.readyState >= 1) {
|
|
9018
|
+
setTimeout(setupNewTracks, 200);
|
|
9019
|
+
} else {
|
|
9020
|
+
this.player.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
|
|
9021
|
+
setTimeout(setupNewTracks, 2e3);
|
|
9022
|
+
}
|
|
9023
|
+
resolve();
|
|
9024
|
+
}, 100);
|
|
9025
|
+
});
|
|
9026
|
+
}
|
|
9027
|
+
return swappedTracks;
|
|
9028
|
+
}
|
|
9029
|
+
/**
|
|
9030
|
+
* Update source elements to described versions
|
|
9031
|
+
*/
|
|
9032
|
+
_updateSourceElements(toDescribed = true) {
|
|
9033
|
+
const sourceElements = this.player.sourceElements;
|
|
9034
|
+
const sourcesToUpdate = [];
|
|
9035
|
+
sourceElements.forEach((sourceEl) => {
|
|
9036
|
+
const descSrcAttr = sourceEl.getAttribute("data-desc-src");
|
|
9037
|
+
const currentSrc = sourceEl.getAttribute("src");
|
|
9038
|
+
if (descSrcAttr) {
|
|
9039
|
+
const type = sourceEl.getAttribute("type");
|
|
9040
|
+
let origSrc = sourceEl.getAttribute("data-orig-src") || currentSrc;
|
|
9041
|
+
sourcesToUpdate.push({
|
|
9042
|
+
src: toDescribed ? descSrcAttr : origSrc,
|
|
9043
|
+
type,
|
|
9044
|
+
origSrc,
|
|
9045
|
+
descSrc: descSrcAttr
|
|
9046
|
+
});
|
|
9047
|
+
} else {
|
|
9048
|
+
sourcesToUpdate.push({
|
|
9049
|
+
src: sourceEl.getAttribute("src"),
|
|
9050
|
+
type: sourceEl.getAttribute("type"),
|
|
9051
|
+
origSrc: null,
|
|
9052
|
+
descSrc: null
|
|
9053
|
+
});
|
|
9054
|
+
}
|
|
9055
|
+
});
|
|
9056
|
+
if (this.player.element.hasAttribute("src")) {
|
|
9057
|
+
this.player.element.removeAttribute("src");
|
|
9058
|
+
}
|
|
9059
|
+
sourceElements.forEach((sourceEl) => sourceEl.remove());
|
|
9060
|
+
sourcesToUpdate.forEach((sourceInfo) => {
|
|
9061
|
+
const newSource = document.createElement("source");
|
|
9062
|
+
newSource.setAttribute("src", sourceInfo.src);
|
|
9063
|
+
if (sourceInfo.type) {
|
|
9064
|
+
newSource.setAttribute("type", sourceInfo.type);
|
|
9065
|
+
}
|
|
9066
|
+
if (sourceInfo.origSrc) {
|
|
9067
|
+
newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
|
|
9068
|
+
}
|
|
9069
|
+
if (sourceInfo.descSrc) {
|
|
9070
|
+
newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
|
|
9071
|
+
}
|
|
9072
|
+
const firstTrack = this.player.element.querySelector("track");
|
|
9073
|
+
if (firstTrack) {
|
|
9074
|
+
this.player.element.insertBefore(newSource, firstTrack);
|
|
9075
|
+
} else {
|
|
9076
|
+
this.player.element.appendChild(newSource);
|
|
9077
|
+
}
|
|
9078
|
+
});
|
|
9079
|
+
this.player._sourceElementsDirty = true;
|
|
9080
|
+
this.player._sourceElementsCache = null;
|
|
9081
|
+
}
|
|
9082
|
+
/**
|
|
9083
|
+
* Wait for media to be ready
|
|
9084
|
+
*/
|
|
9085
|
+
async _waitForMediaReady(needSeek = false) {
|
|
9086
|
+
await new Promise((resolve) => {
|
|
9087
|
+
if (this.player.element.readyState >= 1) {
|
|
9088
|
+
resolve();
|
|
9089
|
+
} else {
|
|
9090
|
+
const onLoad = () => {
|
|
9091
|
+
this.player.element.removeEventListener("loadedmetadata", onLoad);
|
|
9092
|
+
resolve();
|
|
9093
|
+
};
|
|
9094
|
+
this.player.element.addEventListener("loadedmetadata", onLoad);
|
|
9095
|
+
}
|
|
9096
|
+
});
|
|
9097
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
9098
|
+
if (needSeek) {
|
|
9099
|
+
await new Promise((resolve) => {
|
|
9100
|
+
if (this.player.element.readyState >= 3) {
|
|
9101
|
+
resolve();
|
|
9102
|
+
} else {
|
|
9103
|
+
const onCanPlay = () => {
|
|
9104
|
+
this.player.element.removeEventListener("canplay", onCanPlay);
|
|
9105
|
+
this.player.element.removeEventListener("canplaythrough", onCanPlay);
|
|
9106
|
+
resolve();
|
|
9107
|
+
};
|
|
9108
|
+
this.player.element.addEventListener("canplay", onCanPlay, { once: true });
|
|
9109
|
+
this.player.element.addEventListener("canplaythrough", onCanPlay, { once: true });
|
|
9110
|
+
setTimeout(() => {
|
|
9111
|
+
this.player.element.removeEventListener("canplay", onCanPlay);
|
|
9112
|
+
this.player.element.removeEventListener("canplaythrough", onCanPlay);
|
|
9113
|
+
resolve();
|
|
9114
|
+
}, 3e3);
|
|
9115
|
+
}
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
}
|
|
9119
|
+
/**
|
|
9120
|
+
* Restore playback state after source change
|
|
9121
|
+
*/
|
|
9122
|
+
async _restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText) {
|
|
9123
|
+
let syncTime = currentTime;
|
|
9124
|
+
if (currentCaptionText && this.player.captionManager && this.player.captionManager.tracks.length > 0) {
|
|
9125
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
9126
|
+
const matchingTime = this.player.findMatchingCaptionTime(
|
|
9127
|
+
currentCaptionText,
|
|
9128
|
+
this.player.captionManager.tracks
|
|
9129
|
+
);
|
|
9130
|
+
if (matchingTime !== null) {
|
|
9131
|
+
syncTime = matchingTime;
|
|
9132
|
+
if (this.player.options.debug) {
|
|
9133
|
+
this.player.log("[VidPly] Syncing via caption: ".concat(currentTime, "s -> ").concat(syncTime, "s"));
|
|
9134
|
+
}
|
|
9135
|
+
}
|
|
9136
|
+
}
|
|
9137
|
+
if (syncTime > 0) {
|
|
9138
|
+
this.player.seek(syncTime);
|
|
9139
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
9140
|
+
}
|
|
9141
|
+
if (wasPlaying) {
|
|
9142
|
+
await this.player.play();
|
|
9143
|
+
this.player.setManagedTimeout(() => {
|
|
9144
|
+
this.player.hidePosterOverlay();
|
|
9145
|
+
}, 100);
|
|
9146
|
+
} else {
|
|
9147
|
+
this.player.pause();
|
|
9148
|
+
if (!shouldKeepPoster) {
|
|
9149
|
+
this.player.hidePosterOverlay();
|
|
9150
|
+
}
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
9153
|
+
/**
|
|
9154
|
+
* Enable with source element method
|
|
9155
|
+
*/
|
|
9156
|
+
async _enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
|
|
9157
|
+
await this._swapCaptionTracks(true);
|
|
9158
|
+
this._updateSourceElements(true);
|
|
9159
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
9160
|
+
this.player.element.poster = posterValue;
|
|
9161
|
+
}
|
|
9162
|
+
this.player.element.load();
|
|
9163
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
9164
|
+
await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
|
|
9165
|
+
if (!this.desiredState) return;
|
|
9166
|
+
this.enabled = true;
|
|
9167
|
+
this.player.state.audioDescriptionEnabled = true;
|
|
9168
|
+
this.player.emit("audiodescriptionenabled");
|
|
9169
|
+
this._reloadTranscript();
|
|
9170
|
+
}
|
|
9171
|
+
/**
|
|
9172
|
+
* Enable with direct src method
|
|
9173
|
+
*/
|
|
9174
|
+
async _enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster) {
|
|
9175
|
+
await this._swapCaptionTracks(true);
|
|
9176
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
9177
|
+
this.player.element.poster = posterValue;
|
|
9178
|
+
}
|
|
9179
|
+
this.player.element.src = this.src;
|
|
9180
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
9181
|
+
if (currentTime > 0) {
|
|
9182
|
+
this.player.seek(currentTime);
|
|
9183
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
9184
|
+
}
|
|
9185
|
+
if (wasPlaying) {
|
|
9186
|
+
await this.player.play();
|
|
9187
|
+
} else {
|
|
9188
|
+
this.player.pause();
|
|
9189
|
+
if (!shouldKeepPoster) {
|
|
9190
|
+
this.player.hidePosterOverlay();
|
|
9191
|
+
}
|
|
9192
|
+
}
|
|
9193
|
+
if (!this.desiredState) return;
|
|
9194
|
+
this.enabled = true;
|
|
9195
|
+
this.player.state.audioDescriptionEnabled = true;
|
|
9196
|
+
this.player.emit("audiodescriptionenabled");
|
|
9197
|
+
this._reloadTranscript();
|
|
9198
|
+
}
|
|
9199
|
+
/**
|
|
9200
|
+
* Disable with source element method
|
|
9201
|
+
*/
|
|
9202
|
+
async _disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
|
|
9203
|
+
await this._swapCaptionTracks(false);
|
|
9204
|
+
this._updateSourceElements(false);
|
|
9205
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
9206
|
+
this.player.element.poster = posterValue;
|
|
9207
|
+
}
|
|
9208
|
+
this.player.element.load();
|
|
9209
|
+
this.player.invalidateTrackCache();
|
|
9210
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
9211
|
+
await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
|
|
9212
|
+
if (this.player.captionManager) {
|
|
9213
|
+
this.player.captionManager.destroy();
|
|
9214
|
+
this.player.captionManager = new CaptionManager(this.player);
|
|
9215
|
+
}
|
|
9216
|
+
if (this.desiredState) return;
|
|
9217
|
+
this.enabled = false;
|
|
9218
|
+
this.player.state.audioDescriptionEnabled = false;
|
|
9219
|
+
this.player.emit("audiodescriptiondisabled");
|
|
9220
|
+
this._reloadTranscript();
|
|
9221
|
+
}
|
|
9222
|
+
/**
|
|
9223
|
+
* Disable with direct src method
|
|
9224
|
+
*/
|
|
9225
|
+
async _disableWithDirectSrc(currentTime, wasPlaying, posterValue) {
|
|
9226
|
+
await this._swapCaptionTracks(false);
|
|
9227
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
9228
|
+
this.player.element.poster = posterValue;
|
|
9229
|
+
}
|
|
9230
|
+
const originalSrcToUse = this.originalSource || this.player.originalSrc;
|
|
9231
|
+
this.player.element.src = originalSrcToUse;
|
|
9232
|
+
this.player.element.load();
|
|
9233
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
9234
|
+
if (currentTime > 0) {
|
|
9235
|
+
this.player.seek(currentTime);
|
|
9236
|
+
}
|
|
9237
|
+
if (wasPlaying) {
|
|
9238
|
+
await this.player.play();
|
|
9239
|
+
}
|
|
9240
|
+
if (this.desiredState) return;
|
|
9241
|
+
this.enabled = false;
|
|
9242
|
+
this.player.state.audioDescriptionEnabled = false;
|
|
9243
|
+
this.player.emit("audiodescriptiondisabled");
|
|
9244
|
+
this._reloadTranscript();
|
|
9245
|
+
}
|
|
9246
|
+
/**
|
|
9247
|
+
* Reload transcript after audio description state change
|
|
9248
|
+
*/
|
|
9249
|
+
_reloadTranscript() {
|
|
9250
|
+
if (this.player.transcriptManager && this.player.transcriptManager.isVisible) {
|
|
9251
|
+
this.player.setManagedTimeout(() => {
|
|
9252
|
+
if (this.player.transcriptManager && this.player.transcriptManager.loadTranscriptData) {
|
|
9253
|
+
this.player.transcriptManager.loadTranscriptData();
|
|
9254
|
+
}
|
|
9255
|
+
}, 800);
|
|
9256
|
+
}
|
|
9257
|
+
}
|
|
9258
|
+
/**
|
|
9259
|
+
* Update sources (called when playlist changes)
|
|
9260
|
+
*/
|
|
9261
|
+
updateSources(audioDescriptionSrc) {
|
|
9262
|
+
this.src = audioDescriptionSrc || null;
|
|
9263
|
+
this.enabled = false;
|
|
9264
|
+
this.desiredState = false;
|
|
9265
|
+
this.sourceElement = null;
|
|
9266
|
+
this.originalSource = null;
|
|
9267
|
+
this.captionTracks = [];
|
|
9268
|
+
}
|
|
9269
|
+
/**
|
|
9270
|
+
* Reinitialize from current player elements (called after playlist loads new tracks)
|
|
9271
|
+
*/
|
|
9272
|
+
reinitialize() {
|
|
9273
|
+
this.player.invalidateTrackCache();
|
|
9274
|
+
this.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
|
|
9275
|
+
}
|
|
9276
|
+
/**
|
|
9277
|
+
* Cleanup
|
|
9278
|
+
*/
|
|
9279
|
+
destroy() {
|
|
9280
|
+
this.enabled = false;
|
|
9281
|
+
this.desiredState = false;
|
|
9282
|
+
this.captionTracks = [];
|
|
9283
|
+
this.sourceElement = null;
|
|
9284
|
+
this.originalSource = null;
|
|
9285
|
+
}
|
|
9286
|
+
};
|
|
9287
|
+
|
|
9288
|
+
// src/core/SignLanguageManager.js
|
|
9289
|
+
init_DOMUtils();
|
|
9290
|
+
init_Icons();
|
|
9291
|
+
init_i18n();
|
|
9292
|
+
init_DraggableResizable();
|
|
9293
|
+
init_MenuUtils();
|
|
9294
|
+
init_FormUtils();
|
|
9295
|
+
var SignLanguageManager = class {
|
|
9296
|
+
constructor(player) {
|
|
9297
|
+
this.player = player;
|
|
9298
|
+
this.src = player.options.signLanguageSrc;
|
|
9299
|
+
this.sources = player.options.signLanguageSources || {};
|
|
9300
|
+
this.currentLanguage = null;
|
|
9301
|
+
this.desiredPosition = player.options.signLanguagePosition || "bottom-right";
|
|
9302
|
+
this.wrapper = null;
|
|
9303
|
+
this.header = null;
|
|
9304
|
+
this.video = null;
|
|
9305
|
+
this.selector = null;
|
|
9306
|
+
this.settingsButton = null;
|
|
9307
|
+
this.settingsMenu = null;
|
|
9308
|
+
this.resizeHandles = [];
|
|
9309
|
+
this.enabled = false;
|
|
9310
|
+
this.settingsMenuVisible = false;
|
|
9311
|
+
this.settingsMenuJustOpened = false;
|
|
9312
|
+
this.documentClickHandlerAdded = false;
|
|
9313
|
+
this.handlers = null;
|
|
9314
|
+
this.settingsHandlers = null;
|
|
9315
|
+
this.interactionHandlers = null;
|
|
9316
|
+
this.draggable = null;
|
|
9317
|
+
this.documentClickHandler = null;
|
|
9318
|
+
this.settingsMenuKeyHandler = null;
|
|
9319
|
+
this.customKeyHandler = null;
|
|
9320
|
+
this.dragOptionButton = null;
|
|
9321
|
+
this.dragOptionText = null;
|
|
9322
|
+
this.resizeOptionButton = null;
|
|
9323
|
+
this.resizeOptionText = null;
|
|
9324
|
+
}
|
|
9325
|
+
/**
|
|
9326
|
+
* Check if sign language is available
|
|
9327
|
+
*/
|
|
9328
|
+
isAvailable() {
|
|
9329
|
+
return Object.keys(this.sources).length > 0 || !!this.src;
|
|
9330
|
+
}
|
|
9331
|
+
/**
|
|
9332
|
+
* Enable sign language video
|
|
9333
|
+
*/
|
|
9334
|
+
enable() {
|
|
9335
|
+
const hasMultipleSources = Object.keys(this.sources).length > 0;
|
|
9336
|
+
const hasSingleSource = !!this.src;
|
|
9337
|
+
if (!hasMultipleSources && !hasSingleSource) {
|
|
9338
|
+
console.warn("No sign language video source provided");
|
|
9339
|
+
return;
|
|
9340
|
+
}
|
|
9341
|
+
if (this.wrapper) {
|
|
9342
|
+
this.wrapper.style.display = "block";
|
|
9343
|
+
this.enabled = true;
|
|
9344
|
+
this.player.state.signLanguageEnabled = true;
|
|
9345
|
+
this.player.emit("signlanguageenabled");
|
|
9346
|
+
this.player.setManagedTimeout(() => {
|
|
9347
|
+
if (this.settingsButton && document.contains(this.settingsButton)) {
|
|
9348
|
+
this.settingsButton.focus({ preventScroll: true });
|
|
9349
|
+
}
|
|
9350
|
+
}, 150);
|
|
9351
|
+
return;
|
|
9352
|
+
}
|
|
9353
|
+
let initialLang = null;
|
|
9354
|
+
let initialSrc = null;
|
|
9355
|
+
if (hasMultipleSources) {
|
|
9356
|
+
initialLang = this._determineInitialLanguage();
|
|
9357
|
+
initialSrc = this.sources[initialLang];
|
|
9358
|
+
this.currentLanguage = initialLang;
|
|
9359
|
+
} else {
|
|
9360
|
+
initialSrc = this.src;
|
|
9361
|
+
}
|
|
9362
|
+
this._createWrapper();
|
|
9363
|
+
this._createHeader(hasMultipleSources, initialLang);
|
|
9364
|
+
this._createVideo(initialSrc);
|
|
9365
|
+
this._createResizeHandles();
|
|
9366
|
+
this.wrapper.appendChild(this.header);
|
|
9367
|
+
this.wrapper.appendChild(this.video);
|
|
9368
|
+
this.resizeHandles.forEach((handle) => this.wrapper.appendChild(handle));
|
|
9369
|
+
this._applyInitialSize();
|
|
9370
|
+
this.player.container.appendChild(this.wrapper);
|
|
9371
|
+
requestAnimationFrame(() => {
|
|
9372
|
+
this.constrainPosition();
|
|
9373
|
+
});
|
|
9374
|
+
this.video.currentTime = this.player.state.currentTime;
|
|
9375
|
+
if (!this.player.state.paused) {
|
|
9376
|
+
this.video.play();
|
|
9377
|
+
}
|
|
9378
|
+
this._setupInteraction();
|
|
9379
|
+
this._setupEventHandlers(hasMultipleSources);
|
|
9380
|
+
this.enabled = true;
|
|
9381
|
+
this.player.state.signLanguageEnabled = true;
|
|
9382
|
+
this.player.emit("signlanguageenabled");
|
|
9383
|
+
this.player.setManagedTimeout(() => {
|
|
9384
|
+
if (this.settingsButton && document.contains(this.settingsButton)) {
|
|
9385
|
+
this.settingsButton.focus({ preventScroll: true });
|
|
9386
|
+
}
|
|
9387
|
+
}, 150);
|
|
9388
|
+
}
|
|
9389
|
+
/**
|
|
9390
|
+
* Disable sign language video
|
|
9391
|
+
*/
|
|
9392
|
+
disable() {
|
|
9393
|
+
if (this.settingsMenuVisible) {
|
|
9394
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
9395
|
+
}
|
|
9396
|
+
if (this.wrapper) {
|
|
9397
|
+
this.wrapper.style.display = "none";
|
|
9398
|
+
}
|
|
9399
|
+
this.enabled = false;
|
|
9400
|
+
this.player.state.signLanguageEnabled = false;
|
|
9401
|
+
this.player.emit("signlanguagedisabled");
|
|
9402
|
+
}
|
|
9403
|
+
/**
|
|
9404
|
+
* Toggle sign language video
|
|
9405
|
+
*/
|
|
9406
|
+
toggle() {
|
|
9407
|
+
if (this.enabled) {
|
|
9408
|
+
this.disable();
|
|
9409
|
+
} else {
|
|
9410
|
+
this.enable();
|
|
9411
|
+
}
|
|
9412
|
+
}
|
|
9413
|
+
/**
|
|
9414
|
+
* Switch to a different sign language
|
|
9415
|
+
*/
|
|
9416
|
+
switchLanguage(langCode) {
|
|
9417
|
+
if (!this.sources[langCode] || !this.video) {
|
|
9418
|
+
return;
|
|
9419
|
+
}
|
|
9420
|
+
const currentTime = this.video.currentTime;
|
|
9421
|
+
const wasPlaying = !this.video.paused;
|
|
9422
|
+
this.video.src = this.sources[langCode];
|
|
9423
|
+
this.currentLanguage = langCode;
|
|
9424
|
+
this.video.currentTime = currentTime;
|
|
9425
|
+
if (wasPlaying) {
|
|
9426
|
+
this.video.play().catch(() => {
|
|
9427
|
+
});
|
|
9428
|
+
}
|
|
9429
|
+
this.player.emit("signlanguagelanguagechanged", langCode);
|
|
9430
|
+
}
|
|
9431
|
+
/**
|
|
9432
|
+
* Get language label
|
|
9433
|
+
*/
|
|
9434
|
+
getLanguageLabel(langCode) {
|
|
9435
|
+
const langNames = {
|
|
9436
|
+
"en": "English",
|
|
9437
|
+
"de": "Deutsch",
|
|
9438
|
+
"es": "Español",
|
|
9439
|
+
"fr": "Français",
|
|
9440
|
+
"it": "Italiano",
|
|
9441
|
+
"ja": "日本語",
|
|
9442
|
+
"pt": "Português",
|
|
9443
|
+
"ar": "العربية",
|
|
9444
|
+
"hi": "हिन्दी"
|
|
9445
|
+
};
|
|
9446
|
+
return langNames[langCode] || langCode.toUpperCase();
|
|
9447
|
+
}
|
|
9448
|
+
/**
|
|
9449
|
+
* Determine initial sign language
|
|
9450
|
+
*/
|
|
9451
|
+
_determineInitialLanguage() {
|
|
9452
|
+
var _a;
|
|
9453
|
+
if (this.player.captionManager && this.player.captionManager.currentTrack) {
|
|
9454
|
+
const captionLang = (_a = this.player.captionManager.currentTrack.language) == null ? void 0 : _a.toLowerCase().split("-")[0];
|
|
9455
|
+
if (captionLang && this.sources[captionLang]) {
|
|
9456
|
+
return captionLang;
|
|
9457
|
+
}
|
|
9458
|
+
}
|
|
9459
|
+
if (this.player.options.language) {
|
|
9460
|
+
const playerLang = this.player.options.language.toLowerCase().split("-")[0];
|
|
9461
|
+
if (this.sources[playerLang]) {
|
|
9462
|
+
return playerLang;
|
|
9463
|
+
}
|
|
9464
|
+
}
|
|
9465
|
+
return Object.keys(this.sources)[0];
|
|
9466
|
+
}
|
|
9467
|
+
/**
|
|
9468
|
+
* Create wrapper element
|
|
9469
|
+
*/
|
|
9470
|
+
_createWrapper() {
|
|
9471
|
+
this.wrapper = document.createElement("div");
|
|
9472
|
+
this.wrapper.className = "vidply-sign-language-wrapper";
|
|
9473
|
+
this.wrapper.setAttribute("tabindex", "0");
|
|
9474
|
+
this.wrapper.setAttribute("aria-label", i18n.t("player.signLanguageDragResize"));
|
|
9475
|
+
}
|
|
9476
|
+
/**
|
|
9477
|
+
* Create header element
|
|
9478
|
+
*/
|
|
9479
|
+
_createHeader(hasMultipleSources, initialLang) {
|
|
9480
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9481
|
+
this.header = DOMUtils.createElement("div", {
|
|
9482
|
+
className: "".concat(classPrefix, "-sign-language-header"),
|
|
9483
|
+
attributes: { "tabindex": "0" }
|
|
9484
|
+
});
|
|
9485
|
+
const headerLeft = DOMUtils.createElement("div", {
|
|
9486
|
+
className: "".concat(classPrefix, "-sign-language-header-left")
|
|
9487
|
+
});
|
|
9488
|
+
const title = DOMUtils.createElement("h3", {
|
|
9489
|
+
textContent: i18n.t("player.signLanguageVideo")
|
|
9490
|
+
});
|
|
9491
|
+
this._createSettingsButton(headerLeft);
|
|
9492
|
+
if (hasMultipleSources) {
|
|
9493
|
+
this._createLanguageSelector(headerLeft, initialLang);
|
|
9494
|
+
}
|
|
9495
|
+
headerLeft.appendChild(title);
|
|
9496
|
+
const closeButton = this._createCloseButton();
|
|
9497
|
+
this.header.appendChild(headerLeft);
|
|
9498
|
+
this.header.appendChild(closeButton);
|
|
9499
|
+
this.settingsMenuVisible = false;
|
|
9500
|
+
this.settingsMenu = null;
|
|
9501
|
+
this.settingsMenuJustOpened = false;
|
|
9502
|
+
}
|
|
9503
|
+
/**
|
|
9504
|
+
* Create settings button
|
|
9505
|
+
*/
|
|
9506
|
+
_createSettingsButton(container) {
|
|
9507
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9508
|
+
const ariaLabel = i18n.t("player.signLanguageSettings");
|
|
9509
|
+
this.settingsButton = DOMUtils.createElement("button", {
|
|
9510
|
+
className: "".concat(classPrefix, "-sign-language-settings"),
|
|
9511
|
+
attributes: {
|
|
9512
|
+
"type": "button",
|
|
9513
|
+
"aria-label": ariaLabel,
|
|
9514
|
+
"aria-expanded": "false"
|
|
9515
|
+
}
|
|
9516
|
+
});
|
|
9517
|
+
this.settingsButton.appendChild(createIconElement("settings"));
|
|
9518
|
+
DOMUtils.attachTooltip(this.settingsButton, ariaLabel, classPrefix);
|
|
9519
|
+
this.settingsHandlers = {
|
|
9520
|
+
click: (e) => {
|
|
9521
|
+
e.preventDefault();
|
|
9522
|
+
e.stopPropagation();
|
|
9523
|
+
if (this.documentClickHandler) {
|
|
9524
|
+
this.settingsMenuJustOpened = true;
|
|
9525
|
+
setTimeout(() => {
|
|
9526
|
+
this.settingsMenuJustOpened = false;
|
|
9527
|
+
}, 100);
|
|
9528
|
+
}
|
|
9529
|
+
if (this.settingsMenuVisible) {
|
|
9530
|
+
this.hideSettingsMenu();
|
|
9531
|
+
} else {
|
|
9532
|
+
this.showSettingsMenu();
|
|
9533
|
+
}
|
|
9534
|
+
},
|
|
9535
|
+
keydown: (e) => {
|
|
9536
|
+
if (e.key === "d" || e.key === "D") {
|
|
9537
|
+
e.preventDefault();
|
|
9538
|
+
e.stopPropagation();
|
|
9539
|
+
this.toggleKeyboardDragMode();
|
|
9540
|
+
} else if (e.key === "r" || e.key === "R") {
|
|
9541
|
+
e.preventDefault();
|
|
9542
|
+
e.stopPropagation();
|
|
9543
|
+
this.toggleResizeMode();
|
|
9544
|
+
} else if (e.key === "Escape" && this.settingsMenuVisible) {
|
|
9545
|
+
e.preventDefault();
|
|
9546
|
+
e.stopPropagation();
|
|
9547
|
+
this.hideSettingsMenu();
|
|
9548
|
+
}
|
|
9549
|
+
}
|
|
9550
|
+
};
|
|
9551
|
+
this.settingsButton.addEventListener("click", this.settingsHandlers.click);
|
|
9552
|
+
this.settingsButton.addEventListener("keydown", this.settingsHandlers.keydown);
|
|
9553
|
+
container.appendChild(this.settingsButton);
|
|
9554
|
+
}
|
|
9555
|
+
/**
|
|
9556
|
+
* Create language selector
|
|
9557
|
+
*/
|
|
9558
|
+
_createLanguageSelector(container, initialLang) {
|
|
9559
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9560
|
+
const selectId = "".concat(classPrefix, "-sign-language-select-").concat(Date.now());
|
|
9561
|
+
const options = Object.keys(this.sources).map((langCode) => ({
|
|
9562
|
+
value: langCode,
|
|
9563
|
+
text: this.getLanguageLabel(langCode),
|
|
9564
|
+
selected: langCode === initialLang
|
|
9565
|
+
}));
|
|
9566
|
+
const { label, select } = createLabeledSelect({
|
|
9567
|
+
classPrefix,
|
|
9568
|
+
labelClass: "".concat(classPrefix, "-sign-language-label"),
|
|
9569
|
+
selectClass: "".concat(classPrefix, "-sign-language-select"),
|
|
9570
|
+
labelText: "settings.language",
|
|
9571
|
+
selectId,
|
|
9572
|
+
options,
|
|
9573
|
+
onChange: (e) => {
|
|
9574
|
+
e.stopPropagation();
|
|
9575
|
+
this.switchLanguage(e.target.value);
|
|
9576
|
+
}
|
|
9577
|
+
});
|
|
9578
|
+
this.selector = select;
|
|
9579
|
+
const selectorWrapper = DOMUtils.createElement("div", {
|
|
9580
|
+
className: "".concat(classPrefix, "-sign-language-selector-wrapper")
|
|
9581
|
+
});
|
|
9582
|
+
selectorWrapper.appendChild(label);
|
|
9583
|
+
selectorWrapper.appendChild(this.selector);
|
|
9584
|
+
preventDragOnElement(selectorWrapper);
|
|
9585
|
+
container.appendChild(selectorWrapper);
|
|
9586
|
+
}
|
|
9587
|
+
/**
|
|
9588
|
+
* Create close button
|
|
9589
|
+
*/
|
|
9590
|
+
_createCloseButton() {
|
|
9591
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9592
|
+
const ariaLabel = i18n.t("player.closeSignLanguage");
|
|
9593
|
+
const closeButton = DOMUtils.createElement("button", {
|
|
9594
|
+
className: "".concat(classPrefix, "-sign-language-close"),
|
|
9595
|
+
attributes: {
|
|
9596
|
+
"type": "button",
|
|
9597
|
+
"aria-label": ariaLabel
|
|
9598
|
+
}
|
|
9599
|
+
});
|
|
9600
|
+
closeButton.appendChild(createIconElement("close"));
|
|
9601
|
+
DOMUtils.attachTooltip(closeButton, ariaLabel, classPrefix);
|
|
9602
|
+
closeButton.addEventListener("click", () => {
|
|
9603
|
+
var _a, _b;
|
|
9604
|
+
this.disable();
|
|
9605
|
+
if ((_b = (_a = this.player.controlBar) == null ? void 0 : _a.controls) == null ? void 0 : _b.signLanguage) {
|
|
9606
|
+
setTimeout(() => {
|
|
9607
|
+
this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
|
|
9608
|
+
}, 0);
|
|
9609
|
+
}
|
|
9610
|
+
});
|
|
9611
|
+
return closeButton;
|
|
9612
|
+
}
|
|
9613
|
+
/**
|
|
9614
|
+
* Create video element
|
|
9615
|
+
*/
|
|
9616
|
+
_createVideo(src) {
|
|
9617
|
+
this.video = document.createElement("video");
|
|
9618
|
+
this.video.className = "vidply-sign-language-video";
|
|
9619
|
+
this.video.src = src;
|
|
9620
|
+
this.video.setAttribute("aria-label", i18n.t("player.signLanguage"));
|
|
9621
|
+
this.video.muted = true;
|
|
9622
|
+
this.video.setAttribute("playsinline", "");
|
|
9623
|
+
}
|
|
9624
|
+
/**
|
|
9625
|
+
* Create resize handles
|
|
9626
|
+
*/
|
|
9627
|
+
_createResizeHandles() {
|
|
9628
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9629
|
+
this.resizeHandles = ["n", "s", "e", "w", "ne", "nw", "se", "sw"].map((dir) => {
|
|
9630
|
+
const handle = DOMUtils.createElement("div", {
|
|
9631
|
+
className: "".concat(classPrefix, "-sign-resize-handle ").concat(classPrefix, "-sign-resize-").concat(dir),
|
|
9632
|
+
attributes: {
|
|
9633
|
+
"data-direction": dir,
|
|
9634
|
+
"data-vidply-managed-resize": "true",
|
|
9635
|
+
"aria-hidden": "true"
|
|
9636
|
+
}
|
|
9637
|
+
});
|
|
9638
|
+
handle.style.display = "none";
|
|
9639
|
+
return handle;
|
|
9640
|
+
});
|
|
9641
|
+
}
|
|
9642
|
+
/**
|
|
9643
|
+
* Apply initial size
|
|
9644
|
+
*/
|
|
9645
|
+
_applyInitialSize() {
|
|
9646
|
+
var _a;
|
|
9647
|
+
const saved = this.player.storage.getSignLanguagePreferences();
|
|
9648
|
+
if ((_a = saved == null ? void 0 : saved.size) == null ? void 0 : _a.width) {
|
|
9649
|
+
this.wrapper.style.width = saved.size.width;
|
|
9650
|
+
} else {
|
|
9651
|
+
this.wrapper.style.width = "280px";
|
|
9652
|
+
}
|
|
9653
|
+
this.wrapper.style.height = "auto";
|
|
9654
|
+
}
|
|
9655
|
+
/**
|
|
9656
|
+
* Setup interaction (drag and resize)
|
|
9657
|
+
*/
|
|
9658
|
+
_setupInteraction() {
|
|
9659
|
+
const isMobile2 = window.innerWidth < 768;
|
|
9660
|
+
const isFullscreen = this.player.state.fullscreen;
|
|
9661
|
+
if (isMobile2 && !isFullscreen) {
|
|
9662
|
+
if (this.draggable) {
|
|
9663
|
+
this.draggable.destroy();
|
|
9664
|
+
this.draggable = null;
|
|
9665
|
+
}
|
|
9666
|
+
return;
|
|
9667
|
+
}
|
|
9668
|
+
if (this.draggable) return;
|
|
9669
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9670
|
+
this.draggable = new DraggableResizable(this.wrapper, {
|
|
9671
|
+
dragHandle: this.header,
|
|
9672
|
+
resizeHandles: this.resizeHandles,
|
|
9673
|
+
constrainToViewport: true,
|
|
9674
|
+
maintainAspectRatio: true,
|
|
9675
|
+
minWidth: 150,
|
|
9676
|
+
minHeight: 100,
|
|
9677
|
+
classPrefix: "".concat(classPrefix, "-sign"),
|
|
9678
|
+
keyboardDragKey: "d",
|
|
9679
|
+
keyboardResizeKey: "r",
|
|
9680
|
+
keyboardStep: 10,
|
|
9681
|
+
keyboardStepLarge: 50,
|
|
9682
|
+
pointerResizeIndicatorText: i18n.t("player.signLanguageResizeActive"),
|
|
9683
|
+
onPointerResizeToggle: (enabled) => {
|
|
9684
|
+
this.resizeHandles.forEach((handle) => {
|
|
9685
|
+
handle.style.display = enabled ? "block" : "none";
|
|
9686
|
+
});
|
|
9687
|
+
},
|
|
9688
|
+
onDragStart: (e) => {
|
|
9689
|
+
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"))) {
|
|
9690
|
+
return false;
|
|
9691
|
+
}
|
|
9692
|
+
return true;
|
|
9693
|
+
}
|
|
9694
|
+
});
|
|
9695
|
+
this._setupCustomKeyHandler();
|
|
9696
|
+
this.interactionHandlers = {
|
|
9697
|
+
draggable: this.draggable,
|
|
9698
|
+
customKeyHandler: this.customKeyHandler
|
|
9699
|
+
};
|
|
9700
|
+
}
|
|
9701
|
+
/**
|
|
9702
|
+
* Setup custom keyboard handler
|
|
9703
|
+
*/
|
|
9704
|
+
_setupCustomKeyHandler() {
|
|
9705
|
+
this.customKeyHandler = (e) => {
|
|
9706
|
+
var _a, _b, _c, _d;
|
|
9707
|
+
const key = e.key.toLowerCase();
|
|
9708
|
+
if (this.settingsMenuVisible) return;
|
|
9709
|
+
if (key === "home") {
|
|
9710
|
+
e.preventDefault();
|
|
9711
|
+
e.stopPropagation();
|
|
9712
|
+
if (this.draggable) {
|
|
9713
|
+
if (this.draggable.pointerResizeMode) {
|
|
9714
|
+
this.draggable.disablePointerResizeMode();
|
|
9715
|
+
}
|
|
9716
|
+
this.draggable.manuallyPositioned = false;
|
|
9717
|
+
this.constrainPosition();
|
|
9718
|
+
}
|
|
9719
|
+
return;
|
|
9720
|
+
}
|
|
9721
|
+
if (key === "r") {
|
|
9722
|
+
e.preventDefault();
|
|
9723
|
+
e.stopPropagation();
|
|
9724
|
+
if (this.toggleResizeMode()) {
|
|
9725
|
+
this.wrapper.focus({ preventScroll: true });
|
|
9726
|
+
}
|
|
9727
|
+
return;
|
|
9728
|
+
}
|
|
9729
|
+
if (key === "escape") {
|
|
9730
|
+
e.preventDefault();
|
|
9731
|
+
e.stopPropagation();
|
|
9732
|
+
if ((_a = this.draggable) == null ? void 0 : _a.pointerResizeMode) {
|
|
9733
|
+
this.draggable.disablePointerResizeMode();
|
|
9734
|
+
return;
|
|
9735
|
+
}
|
|
9736
|
+
if ((_b = this.draggable) == null ? void 0 : _b.keyboardDragMode) {
|
|
9737
|
+
this.draggable.disableKeyboardDragMode();
|
|
9738
|
+
return;
|
|
9739
|
+
}
|
|
9740
|
+
this.disable();
|
|
9741
|
+
if ((_d = (_c = this.player.controlBar) == null ? void 0 : _c.controls) == null ? void 0 : _d.signLanguage) {
|
|
9742
|
+
setTimeout(() => {
|
|
9743
|
+
this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
|
|
9744
|
+
}, 0);
|
|
9745
|
+
}
|
|
9746
|
+
}
|
|
9747
|
+
};
|
|
9748
|
+
this.wrapper.addEventListener("keydown", this.customKeyHandler);
|
|
9749
|
+
}
|
|
9750
|
+
/**
|
|
9751
|
+
* Setup event handlers
|
|
9752
|
+
*/
|
|
9753
|
+
_setupEventHandlers(hasMultipleSources) {
|
|
9754
|
+
this.handlers = {
|
|
9755
|
+
play: () => {
|
|
9756
|
+
if (this.video) this.video.play();
|
|
9757
|
+
},
|
|
9758
|
+
pause: () => {
|
|
9759
|
+
if (this.video) this.video.pause();
|
|
9760
|
+
},
|
|
9761
|
+
timeupdate: () => {
|
|
9762
|
+
if (this.video && Math.abs(this.video.currentTime - this.player.state.currentTime) > 0.5) {
|
|
9763
|
+
this.video.currentTime = this.player.state.currentTime;
|
|
9764
|
+
}
|
|
9765
|
+
},
|
|
9766
|
+
ratechange: () => {
|
|
9767
|
+
if (this.video) this.video.playbackRate = this.player.state.playbackSpeed;
|
|
9768
|
+
}
|
|
9769
|
+
};
|
|
9770
|
+
this.player.on("play", this.handlers.play);
|
|
9771
|
+
this.player.on("pause", this.handlers.pause);
|
|
9772
|
+
this.player.on("timeupdate", this.handlers.timeupdate);
|
|
9773
|
+
this.player.on("ratechange", this.handlers.ratechange);
|
|
9774
|
+
if (hasMultipleSources) {
|
|
9775
|
+
this.handlers.captionChange = () => {
|
|
9776
|
+
var _a, _b;
|
|
9777
|
+
if (((_a = this.player.captionManager) == null ? void 0 : _a.currentTrack) && this.selector) {
|
|
9778
|
+
const captionLang = (_b = this.player.captionManager.currentTrack.language) == null ? void 0 : _b.toLowerCase().split("-")[0];
|
|
9779
|
+
if (captionLang && this.sources[captionLang] && this.currentLanguage !== captionLang) {
|
|
9780
|
+
this.switchLanguage(captionLang);
|
|
9781
|
+
this.selector.value = captionLang;
|
|
9782
|
+
}
|
|
9783
|
+
}
|
|
9784
|
+
};
|
|
9785
|
+
this.player.on("captionsenabled", this.handlers.captionChange);
|
|
9786
|
+
}
|
|
9787
|
+
}
|
|
9788
|
+
/**
|
|
9789
|
+
* Constrain position within video wrapper
|
|
9790
|
+
*/
|
|
9791
|
+
constrainPosition() {
|
|
9792
|
+
var _a;
|
|
9793
|
+
if (!this.wrapper || !this.player.videoWrapper) return;
|
|
9794
|
+
if ((_a = this.draggable) == null ? void 0 : _a.manuallyPositioned) return;
|
|
9795
|
+
if (!this.wrapper.style.width) {
|
|
9796
|
+
this.wrapper.style.width = "280px";
|
|
9797
|
+
}
|
|
9798
|
+
const videoWrapperRect = this.player.videoWrapper.getBoundingClientRect();
|
|
9799
|
+
const containerRect = this.player.container.getBoundingClientRect();
|
|
9800
|
+
const wrapperRect = this.wrapper.getBoundingClientRect();
|
|
9801
|
+
const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
|
|
9802
|
+
const videoWrapperTop = videoWrapperRect.top - containerRect.top;
|
|
9803
|
+
const videoWrapperWidth = videoWrapperRect.width;
|
|
9804
|
+
const videoWrapperHeight = videoWrapperRect.height;
|
|
9805
|
+
let wrapperWidth = wrapperRect.width || 280;
|
|
9806
|
+
let wrapperHeight = wrapperRect.height || 280 * 9 / 16;
|
|
9807
|
+
let left, top;
|
|
9808
|
+
const margin = 16;
|
|
9809
|
+
const controlsHeight = 95;
|
|
9810
|
+
const position = this.desiredPosition || "bottom-right";
|
|
9811
|
+
switch (position) {
|
|
9812
|
+
case "bottom-right":
|
|
9813
|
+
left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
|
|
9814
|
+
top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
|
|
9815
|
+
break;
|
|
9816
|
+
case "bottom-left":
|
|
9817
|
+
left = videoWrapperLeft + margin;
|
|
9818
|
+
top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
|
|
9819
|
+
break;
|
|
9820
|
+
case "top-right":
|
|
9821
|
+
left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
|
|
9822
|
+
top = videoWrapperTop + margin;
|
|
9823
|
+
break;
|
|
9824
|
+
case "top-left":
|
|
9825
|
+
left = videoWrapperLeft + margin;
|
|
9826
|
+
top = videoWrapperTop + margin;
|
|
9827
|
+
break;
|
|
9828
|
+
default:
|
|
9829
|
+
left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
|
|
9830
|
+
top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
|
|
9831
|
+
}
|
|
9832
|
+
left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperWidth - wrapperWidth));
|
|
9833
|
+
top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight));
|
|
9834
|
+
this.wrapper.style.left = "".concat(left, "px");
|
|
9835
|
+
this.wrapper.style.top = "".concat(top, "px");
|
|
9836
|
+
this.wrapper.style.right = "auto";
|
|
9837
|
+
this.wrapper.style.bottom = "auto";
|
|
9838
|
+
}
|
|
9839
|
+
/**
|
|
9840
|
+
* Show settings menu
|
|
9841
|
+
*/
|
|
9842
|
+
showSettingsMenu() {
|
|
9843
|
+
var _a;
|
|
9844
|
+
this.settingsMenuJustOpened = true;
|
|
9845
|
+
setTimeout(() => {
|
|
9846
|
+
this.settingsMenuJustOpened = false;
|
|
9847
|
+
}, 350);
|
|
9848
|
+
this._addDocumentClickHandler();
|
|
9849
|
+
if (this.settingsMenu) {
|
|
9850
|
+
this.settingsMenu.style.display = "block";
|
|
9851
|
+
this.settingsMenuVisible = true;
|
|
9852
|
+
(_a = this.settingsButton) == null ? void 0 : _a.setAttribute("aria-expanded", "true");
|
|
9853
|
+
this._attachMenuKeyboardNavigation();
|
|
9854
|
+
this._positionSettingsMenu();
|
|
9855
|
+
this._updateDragOptionState();
|
|
9856
|
+
this._updateResizeOptionState();
|
|
9857
|
+
focusFirstMenuItem(this.settingsMenu, ".".concat(this.player.options.classPrefix, "-sign-language-settings-item"));
|
|
9858
|
+
return;
|
|
9859
|
+
}
|
|
9860
|
+
this._createSettingsMenu();
|
|
9861
|
+
}
|
|
9862
|
+
/**
|
|
9863
|
+
* Hide settings menu
|
|
9864
|
+
*/
|
|
9865
|
+
hideSettingsMenu({ focusButton = true } = {}) {
|
|
9866
|
+
if (this.settingsMenu) {
|
|
9867
|
+
this.settingsMenu.style.display = "none";
|
|
9868
|
+
this.settingsMenuVisible = false;
|
|
9869
|
+
this.settingsMenuJustOpened = false;
|
|
9870
|
+
if (this.settingsMenuKeyHandler) {
|
|
9871
|
+
this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
|
|
9872
|
+
this.settingsMenuKeyHandler = null;
|
|
9873
|
+
}
|
|
9874
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9875
|
+
const menuItems = Array.from(this.settingsMenu.querySelectorAll(".".concat(classPrefix, "-sign-language-settings-item")));
|
|
9876
|
+
menuItems.forEach((item) => item.setAttribute("tabindex", "-1"));
|
|
9877
|
+
if (this.settingsButton) {
|
|
9878
|
+
this.settingsButton.setAttribute("aria-expanded", "false");
|
|
9879
|
+
if (focusButton) {
|
|
9880
|
+
this.settingsButton.focus({ preventScroll: true });
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
}
|
|
9884
|
+
}
|
|
9885
|
+
/**
|
|
9886
|
+
* Add document click handler
|
|
9887
|
+
*/
|
|
9888
|
+
_addDocumentClickHandler() {
|
|
9889
|
+
if (this.documentClickHandlerAdded) return;
|
|
9890
|
+
this.documentClickHandler = (e) => {
|
|
9891
|
+
if (this.settingsMenuJustOpened) return;
|
|
9892
|
+
if (this.settingsButton && (this.settingsButton === e.target || this.settingsButton.contains(e.target))) {
|
|
9893
|
+
return;
|
|
9894
|
+
}
|
|
9895
|
+
if (this.settingsMenu && this.settingsMenu.contains(e.target)) {
|
|
9896
|
+
return;
|
|
9897
|
+
}
|
|
9898
|
+
if (this.settingsMenuVisible) {
|
|
9899
|
+
this.hideSettingsMenu();
|
|
9900
|
+
}
|
|
9901
|
+
};
|
|
9902
|
+
setTimeout(() => {
|
|
9903
|
+
document.addEventListener("mousedown", this.documentClickHandler, true);
|
|
9904
|
+
this.documentClickHandlerAdded = true;
|
|
9905
|
+
}, 300);
|
|
9906
|
+
}
|
|
9907
|
+
/**
|
|
9908
|
+
* Create settings menu
|
|
9909
|
+
*/
|
|
9910
|
+
_createSettingsMenu() {
|
|
9911
|
+
var _a, _b;
|
|
9912
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9913
|
+
this.settingsMenu = DOMUtils.createElement("div", {
|
|
9914
|
+
className: "".concat(classPrefix, "-sign-language-settings-menu"),
|
|
9915
|
+
attributes: { "role": "menu" }
|
|
9916
|
+
});
|
|
9917
|
+
const dragOption = createMenuItem({
|
|
9918
|
+
classPrefix,
|
|
9919
|
+
itemClass: "".concat(classPrefix, "-sign-language-settings-item"),
|
|
9920
|
+
icon: "move",
|
|
9921
|
+
label: "player.enableSignDragMode",
|
|
9922
|
+
hasTextClass: true,
|
|
9923
|
+
onClick: () => {
|
|
9924
|
+
this.toggleKeyboardDragMode();
|
|
9925
|
+
this.hideSettingsMenu();
|
|
9926
|
+
}
|
|
9927
|
+
});
|
|
9928
|
+
dragOption.setAttribute("role", "switch");
|
|
9929
|
+
dragOption.setAttribute("aria-checked", "false");
|
|
9930
|
+
this._removeTooltipFromMenuItem(dragOption);
|
|
9931
|
+
this.dragOptionButton = dragOption;
|
|
9932
|
+
this.dragOptionText = dragOption.querySelector(".".concat(classPrefix, "-settings-text"));
|
|
9933
|
+
this._updateDragOptionState();
|
|
9934
|
+
const resizeOption = createMenuItem({
|
|
9935
|
+
classPrefix,
|
|
9936
|
+
itemClass: "".concat(classPrefix, "-sign-language-settings-item"),
|
|
9937
|
+
icon: "resize",
|
|
9938
|
+
label: "player.enableSignResizeMode",
|
|
9939
|
+
hasTextClass: true,
|
|
9940
|
+
onClick: (event) => {
|
|
9941
|
+
event.preventDefault();
|
|
9942
|
+
event.stopPropagation();
|
|
9943
|
+
const enabled = this.toggleResizeMode({ focus: false });
|
|
9944
|
+
if (enabled) {
|
|
9945
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
9946
|
+
setTimeout(() => {
|
|
9947
|
+
if (this.wrapper) this.wrapper.focus({ preventScroll: true });
|
|
9948
|
+
}, 20);
|
|
9949
|
+
} else {
|
|
9950
|
+
this.hideSettingsMenu({ focusButton: true });
|
|
9951
|
+
}
|
|
9952
|
+
}
|
|
9953
|
+
});
|
|
9954
|
+
resizeOption.setAttribute("role", "switch");
|
|
9955
|
+
resizeOption.setAttribute("aria-checked", "false");
|
|
9956
|
+
this._removeTooltipFromMenuItem(resizeOption);
|
|
9957
|
+
this.resizeOptionButton = resizeOption;
|
|
9958
|
+
this.resizeOptionText = resizeOption.querySelector(".".concat(classPrefix, "-settings-text"));
|
|
9959
|
+
this._updateResizeOptionState();
|
|
9960
|
+
const closeOption = createMenuItem({
|
|
9961
|
+
classPrefix,
|
|
9962
|
+
itemClass: "".concat(classPrefix, "-sign-language-settings-item"),
|
|
9963
|
+
icon: "close",
|
|
9964
|
+
label: "transcript.closeMenu",
|
|
9965
|
+
onClick: () => this.hideSettingsMenu()
|
|
9966
|
+
});
|
|
9967
|
+
this._removeTooltipFromMenuItem(closeOption);
|
|
9968
|
+
this.settingsMenu.appendChild(dragOption);
|
|
9969
|
+
this.settingsMenu.appendChild(resizeOption);
|
|
9970
|
+
this.settingsMenu.appendChild(closeOption);
|
|
9971
|
+
this.settingsMenu.style.visibility = "hidden";
|
|
9972
|
+
this.settingsMenu.style.display = "block";
|
|
9973
|
+
if ((_a = this.settingsButton) == null ? void 0 : _a.parentNode) {
|
|
9974
|
+
this.settingsButton.insertAdjacentElement("afterend", this.settingsMenu);
|
|
9975
|
+
} else if (this.wrapper) {
|
|
9976
|
+
this.wrapper.appendChild(this.settingsMenu);
|
|
9977
|
+
}
|
|
9978
|
+
this._positionSettingsMenuImmediate();
|
|
9979
|
+
requestAnimationFrame(() => {
|
|
9980
|
+
if (this.settingsMenu) {
|
|
9981
|
+
this.settingsMenu.style.visibility = "visible";
|
|
9982
|
+
}
|
|
9983
|
+
});
|
|
9984
|
+
this._attachMenuKeyboardNavigation();
|
|
9985
|
+
this.settingsMenuVisible = true;
|
|
9986
|
+
(_b = this.settingsButton) == null ? void 0 : _b.setAttribute("aria-expanded", "true");
|
|
9987
|
+
this._updateDragOptionState();
|
|
9988
|
+
this._updateResizeOptionState();
|
|
9989
|
+
focusFirstMenuItem(this.settingsMenu, ".".concat(classPrefix, "-sign-language-settings-item"));
|
|
9990
|
+
}
|
|
9991
|
+
/**
|
|
9992
|
+
* Remove tooltip from menu item
|
|
9993
|
+
*/
|
|
9994
|
+
_removeTooltipFromMenuItem(item) {
|
|
9995
|
+
const classPrefix = this.player.options.classPrefix;
|
|
9996
|
+
const tooltip = item.querySelector(".".concat(classPrefix, "-tooltip"));
|
|
9997
|
+
if (tooltip) tooltip.remove();
|
|
9998
|
+
const buttonText = item.querySelector(".".concat(classPrefix, "-button-text"));
|
|
9999
|
+
if (buttonText) buttonText.remove();
|
|
10000
|
+
}
|
|
10001
|
+
/**
|
|
10002
|
+
* Attach menu keyboard navigation
|
|
10003
|
+
*/
|
|
10004
|
+
_attachMenuKeyboardNavigation() {
|
|
10005
|
+
if (this.settingsMenuKeyHandler) {
|
|
10006
|
+
this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
|
|
10007
|
+
}
|
|
10008
|
+
this.settingsMenuKeyHandler = attachMenuKeyboardNavigation(
|
|
10009
|
+
this.settingsMenu,
|
|
10010
|
+
this.settingsButton,
|
|
10011
|
+
".".concat(this.player.options.classPrefix, "-sign-language-settings-item"),
|
|
10012
|
+
() => this.hideSettingsMenu({ focusButton: true })
|
|
10013
|
+
);
|
|
10014
|
+
}
|
|
10015
|
+
/**
|
|
10016
|
+
* Position settings menu immediately
|
|
10017
|
+
*/
|
|
10018
|
+
_positionSettingsMenuImmediate() {
|
|
10019
|
+
if (!this.settingsMenu || !this.settingsButton) return;
|
|
10020
|
+
const buttonRect = this.settingsButton.getBoundingClientRect();
|
|
10021
|
+
const menuRect = this.settingsMenu.getBoundingClientRect();
|
|
10022
|
+
const viewportWidth = window.innerWidth;
|
|
10023
|
+
const viewportHeight = window.innerHeight;
|
|
10024
|
+
const parentContainer = this.settingsButton.parentElement;
|
|
10025
|
+
if (!parentContainer) return;
|
|
10026
|
+
const parentRect = parentContainer.getBoundingClientRect();
|
|
10027
|
+
const buttonCenterX = buttonRect.left + buttonRect.width / 2 - parentRect.left;
|
|
10028
|
+
const buttonBottom = buttonRect.bottom - parentRect.top;
|
|
10029
|
+
const buttonTop = buttonRect.top - parentRect.top;
|
|
10030
|
+
const spaceAbove = buttonRect.top;
|
|
10031
|
+
const spaceBelow = viewportHeight - buttonRect.bottom;
|
|
10032
|
+
let menuTop = buttonBottom + 8;
|
|
10033
|
+
let menuBottom = null;
|
|
10034
|
+
if (spaceBelow < menuRect.height + 20 && spaceAbove > spaceBelow) {
|
|
10035
|
+
menuTop = null;
|
|
10036
|
+
const parentHeight = parentRect.bottom - parentRect.top;
|
|
10037
|
+
menuBottom = parentHeight - buttonTop + 8;
|
|
10038
|
+
this.settingsMenu.classList.add("vidply-menu-above");
|
|
10039
|
+
} else {
|
|
10040
|
+
this.settingsMenu.classList.remove("vidply-menu-above");
|
|
10041
|
+
}
|
|
10042
|
+
let menuLeft = buttonCenterX - menuRect.width / 2;
|
|
10043
|
+
let menuRight = "auto";
|
|
10044
|
+
let transformX = "translateX(0)";
|
|
10045
|
+
const menuLeftAbsolute = buttonRect.left + buttonRect.width / 2 - menuRect.width / 2;
|
|
10046
|
+
if (menuLeftAbsolute < 10) {
|
|
10047
|
+
menuLeft = 0;
|
|
10048
|
+
} else if (menuLeftAbsolute + menuRect.width > viewportWidth - 10) {
|
|
10049
|
+
menuLeft = "auto";
|
|
10050
|
+
menuRight = 0;
|
|
10051
|
+
} else {
|
|
10052
|
+
menuLeft = buttonCenterX;
|
|
10053
|
+
transformX = "translateX(-50%)";
|
|
10054
|
+
}
|
|
10055
|
+
if (menuTop !== null) {
|
|
10056
|
+
this.settingsMenu.style.top = "".concat(menuTop, "px");
|
|
10057
|
+
this.settingsMenu.style.bottom = "auto";
|
|
10058
|
+
} else if (menuBottom !== null) {
|
|
10059
|
+
this.settingsMenu.style.top = "auto";
|
|
10060
|
+
this.settingsMenu.style.bottom = "".concat(menuBottom, "px");
|
|
10061
|
+
}
|
|
10062
|
+
if (menuLeft !== "auto") {
|
|
10063
|
+
this.settingsMenu.style.left = "".concat(menuLeft, "px");
|
|
10064
|
+
this.settingsMenu.style.right = "auto";
|
|
10065
|
+
} else {
|
|
10066
|
+
this.settingsMenu.style.left = "auto";
|
|
10067
|
+
this.settingsMenu.style.right = "".concat(menuRight, "px");
|
|
10068
|
+
}
|
|
10069
|
+
this.settingsMenu.style.transform = transformX;
|
|
10070
|
+
}
|
|
10071
|
+
/**
|
|
10072
|
+
* Position settings menu with RAF
|
|
10073
|
+
*/
|
|
10074
|
+
_positionSettingsMenu() {
|
|
10075
|
+
requestAnimationFrame(() => {
|
|
10076
|
+
setTimeout(() => {
|
|
10077
|
+
this._positionSettingsMenuImmediate();
|
|
10078
|
+
}, 10);
|
|
10079
|
+
});
|
|
10080
|
+
}
|
|
10081
|
+
/**
|
|
10082
|
+
* Toggle keyboard drag mode
|
|
10083
|
+
*/
|
|
10084
|
+
toggleKeyboardDragMode() {
|
|
10085
|
+
if (this.draggable) {
|
|
10086
|
+
const wasEnabled = this.draggable.keyboardDragMode;
|
|
10087
|
+
this.draggable.toggleKeyboardDragMode();
|
|
10088
|
+
const isEnabled = this.draggable.keyboardDragMode;
|
|
10089
|
+
if (!wasEnabled && isEnabled) {
|
|
10090
|
+
this._enableMoveMode();
|
|
10091
|
+
}
|
|
10092
|
+
this._updateDragOptionState();
|
|
10093
|
+
}
|
|
10094
|
+
}
|
|
10095
|
+
/**
|
|
10096
|
+
* Enable move mode visual feedback
|
|
10097
|
+
*/
|
|
10098
|
+
_enableMoveMode() {
|
|
10099
|
+
this.wrapper.classList.add("".concat(this.player.options.classPrefix, "-sign-move-mode"));
|
|
10100
|
+
this._updateResizeOptionState();
|
|
10101
|
+
setTimeout(() => {
|
|
10102
|
+
this.wrapper.classList.remove("".concat(this.player.options.classPrefix, "-sign-move-mode"));
|
|
10103
|
+
}, 2e3);
|
|
10104
|
+
}
|
|
10105
|
+
/**
|
|
10106
|
+
* Toggle resize mode
|
|
10107
|
+
*/
|
|
10108
|
+
toggleResizeMode({ focus = true } = {}) {
|
|
10109
|
+
if (!this.draggable) return false;
|
|
10110
|
+
if (this.draggable.pointerResizeMode) {
|
|
10111
|
+
this.draggable.disablePointerResizeMode({ focus });
|
|
10112
|
+
this._updateResizeOptionState();
|
|
10113
|
+
return false;
|
|
10114
|
+
}
|
|
10115
|
+
this.draggable.enablePointerResizeMode({ focus });
|
|
10116
|
+
this._updateResizeOptionState();
|
|
10117
|
+
return true;
|
|
10118
|
+
}
|
|
10119
|
+
/**
|
|
10120
|
+
* Update drag option state
|
|
10121
|
+
*/
|
|
10122
|
+
_updateDragOptionState() {
|
|
10123
|
+
var _a;
|
|
10124
|
+
if (!this.dragOptionButton) return;
|
|
10125
|
+
const isEnabled = !!((_a = this.draggable) == null ? void 0 : _a.keyboardDragMode);
|
|
10126
|
+
const text = isEnabled ? i18n.t("player.disableSignDragMode") : i18n.t("player.enableSignDragMode");
|
|
10127
|
+
const ariaLabel = isEnabled ? i18n.t("player.disableSignDragModeAria") : i18n.t("player.enableSignDragModeAria");
|
|
10128
|
+
this.dragOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
|
|
10129
|
+
this.dragOptionButton.setAttribute("aria-label", ariaLabel);
|
|
10130
|
+
if (this.dragOptionText) {
|
|
10131
|
+
this.dragOptionText.textContent = text;
|
|
10132
|
+
}
|
|
10133
|
+
}
|
|
10134
|
+
/**
|
|
10135
|
+
* Update resize option state
|
|
10136
|
+
*/
|
|
10137
|
+
_updateResizeOptionState() {
|
|
10138
|
+
var _a;
|
|
10139
|
+
if (!this.resizeOptionButton) return;
|
|
10140
|
+
const isEnabled = !!((_a = this.draggable) == null ? void 0 : _a.pointerResizeMode);
|
|
10141
|
+
const text = isEnabled ? i18n.t("player.disableSignResizeMode") : i18n.t("player.enableSignResizeMode");
|
|
10142
|
+
const ariaLabel = isEnabled ? i18n.t("player.disableSignResizeModeAria") : i18n.t("player.enableSignResizeModeAria");
|
|
10143
|
+
this.resizeOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
|
|
10144
|
+
this.resizeOptionButton.setAttribute("aria-label", ariaLabel);
|
|
10145
|
+
if (this.resizeOptionText) {
|
|
10146
|
+
this.resizeOptionText.textContent = text;
|
|
10147
|
+
}
|
|
10148
|
+
}
|
|
10149
|
+
/**
|
|
10150
|
+
* Save preferences
|
|
10151
|
+
*/
|
|
10152
|
+
savePreferences() {
|
|
10153
|
+
if (!this.wrapper) return;
|
|
10154
|
+
this.player.storage.saveSignLanguagePreferences({
|
|
10155
|
+
size: { width: this.wrapper.style.width }
|
|
10156
|
+
});
|
|
10157
|
+
}
|
|
10158
|
+
/**
|
|
10159
|
+
* Update sources (called when playlist changes)
|
|
10160
|
+
*/
|
|
10161
|
+
updateSources(signLanguageSrc, signLanguageSources) {
|
|
10162
|
+
this.src = signLanguageSrc || null;
|
|
10163
|
+
this.sources = signLanguageSources || {};
|
|
10164
|
+
this.currentLanguage = null;
|
|
10165
|
+
}
|
|
10166
|
+
/**
|
|
10167
|
+
* Cleanup
|
|
10168
|
+
*/
|
|
10169
|
+
cleanup() {
|
|
10170
|
+
var _a;
|
|
10171
|
+
if (this.settingsMenuVisible) {
|
|
10172
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
10173
|
+
}
|
|
10174
|
+
if (this.documentClickHandler && this.documentClickHandlerAdded) {
|
|
10175
|
+
document.removeEventListener("mousedown", this.documentClickHandler, true);
|
|
10176
|
+
this.documentClickHandlerAdded = false;
|
|
10177
|
+
this.documentClickHandler = null;
|
|
10178
|
+
}
|
|
10179
|
+
if (this.settingsHandlers && this.settingsButton) {
|
|
10180
|
+
this.settingsButton.removeEventListener("click", this.settingsHandlers.click);
|
|
10181
|
+
this.settingsButton.removeEventListener("keydown", this.settingsHandlers.keydown);
|
|
10182
|
+
}
|
|
10183
|
+
this.settingsHandlers = null;
|
|
10184
|
+
if (this.handlers) {
|
|
10185
|
+
this.player.off("play", this.handlers.play);
|
|
10186
|
+
this.player.off("pause", this.handlers.pause);
|
|
10187
|
+
this.player.off("timeupdate", this.handlers.timeupdate);
|
|
10188
|
+
this.player.off("ratechange", this.handlers.ratechange);
|
|
10189
|
+
if (this.handlers.captionChange) {
|
|
10190
|
+
this.player.off("captionsenabled", this.handlers.captionChange);
|
|
10191
|
+
}
|
|
10192
|
+
this.handlers = null;
|
|
10193
|
+
}
|
|
10194
|
+
if (this.wrapper && this.customKeyHandler) {
|
|
10195
|
+
this.wrapper.removeEventListener("keydown", this.customKeyHandler);
|
|
10196
|
+
}
|
|
10197
|
+
if (this.draggable) {
|
|
10198
|
+
if (this.draggable.pointerResizeMode) {
|
|
10199
|
+
this.draggable.disablePointerResizeMode();
|
|
10200
|
+
}
|
|
10201
|
+
this.draggable.destroy();
|
|
10202
|
+
this.draggable = null;
|
|
10203
|
+
}
|
|
10204
|
+
this.interactionHandlers = null;
|
|
10205
|
+
if ((_a = this.wrapper) == null ? void 0 : _a.parentNode) {
|
|
10206
|
+
if (this.video) {
|
|
10207
|
+
this.video.pause();
|
|
10208
|
+
this.video.src = "";
|
|
10209
|
+
}
|
|
10210
|
+
this.wrapper.parentNode.removeChild(this.wrapper);
|
|
10211
|
+
}
|
|
10212
|
+
this.wrapper = null;
|
|
10213
|
+
this.video = null;
|
|
10214
|
+
this.settingsButton = null;
|
|
10215
|
+
this.settingsMenu = null;
|
|
10216
|
+
}
|
|
10217
|
+
/**
|
|
10218
|
+
* Destroy
|
|
10219
|
+
*/
|
|
10220
|
+
destroy() {
|
|
10221
|
+
this.cleanup();
|
|
10222
|
+
this.enabled = false;
|
|
10223
|
+
}
|
|
10224
|
+
};
|
|
10225
|
+
|
|
10226
|
+
// src/core/Player.js
|
|
10227
|
+
var playerInstanceCounter = 0;
|
|
10228
|
+
var Player = class _Player extends EventEmitter {
|
|
10229
|
+
constructor(element, options = {}) {
|
|
10230
|
+
super();
|
|
10231
|
+
this.element = typeof element === "string" ? document.querySelector(element) : element;
|
|
10232
|
+
if (!this.element) {
|
|
10233
|
+
throw new Error("VidPly: Element not found");
|
|
10234
|
+
}
|
|
10235
|
+
playerInstanceCounter++;
|
|
10236
|
+
this.instanceId = playerInstanceCounter;
|
|
10237
|
+
if (this.element.tagName !== "VIDEO" && this.element.tagName !== "AUDIO") {
|
|
10238
|
+
const mediaType = options.mediaType || "video";
|
|
10239
|
+
const mediaElement = document.createElement(mediaType);
|
|
10240
|
+
Array.from(this.element.attributes).forEach((attr) => {
|
|
10241
|
+
if (attr.name !== "id" && attr.name !== "class" && !attr.name.startsWith("data-")) {
|
|
10242
|
+
mediaElement.setAttribute(attr.name, attr.value);
|
|
10243
|
+
}
|
|
10244
|
+
});
|
|
10245
|
+
const tracks = this.element.querySelectorAll("track");
|
|
10246
|
+
tracks.forEach((track) => {
|
|
10247
|
+
mediaElement.appendChild(track.cloneNode(true));
|
|
10248
|
+
});
|
|
10249
|
+
this.element.innerHTML = "";
|
|
10250
|
+
this.element.appendChild(mediaElement);
|
|
10251
|
+
this.element = mediaElement;
|
|
10252
|
+
}
|
|
10253
|
+
this._originalElement = this.element;
|
|
10254
|
+
this.options = __spreadValues({
|
|
10255
|
+
// Display
|
|
10256
|
+
width: null,
|
|
10257
|
+
height: null,
|
|
10258
|
+
poster: null,
|
|
10259
|
+
responsive: true,
|
|
10260
|
+
fillContainer: false,
|
|
10261
|
+
// Playback
|
|
10262
|
+
autoplay: false,
|
|
10263
|
+
loop: false,
|
|
10264
|
+
muted: false,
|
|
10265
|
+
volume: 0.8,
|
|
10266
|
+
playbackSpeed: 1,
|
|
10267
|
+
preload: "metadata",
|
|
10268
|
+
startTime: 0,
|
|
10269
|
+
playsInline: true,
|
|
8550
10270
|
// Enable inline playback on iOS (prevents native fullscreen)
|
|
8551
10271
|
// Controls
|
|
8552
10272
|
controls: true,
|
|
@@ -8689,6 +10409,58 @@
|
|
|
8689
10409
|
this.settingsDialog = null;
|
|
8690
10410
|
this.metadataCueChangeHandler = null;
|
|
8691
10411
|
this.metadataAlertHandlers = /* @__PURE__ */ new Map();
|
|
10412
|
+
this.audioDescriptionManager = new AudioDescriptionManager(this);
|
|
10413
|
+
this.signLanguageManager = new SignLanguageManager(this);
|
|
10414
|
+
Object.defineProperties(this, {
|
|
10415
|
+
signLanguageWrapper: {
|
|
10416
|
+
get: () => this.signLanguageManager.wrapper,
|
|
10417
|
+
set: (v) => {
|
|
10418
|
+
this.signLanguageManager.wrapper = v;
|
|
10419
|
+
}
|
|
10420
|
+
},
|
|
10421
|
+
signLanguageVideo: {
|
|
10422
|
+
get: () => this.signLanguageManager.video,
|
|
10423
|
+
set: (v) => {
|
|
10424
|
+
this.signLanguageManager.video = v;
|
|
10425
|
+
}
|
|
10426
|
+
},
|
|
10427
|
+
signLanguageHeader: {
|
|
10428
|
+
get: () => this.signLanguageManager.header,
|
|
10429
|
+
set: (v) => {
|
|
10430
|
+
this.signLanguageManager.header = v;
|
|
10431
|
+
}
|
|
10432
|
+
},
|
|
10433
|
+
signLanguageSettingsButton: {
|
|
10434
|
+
get: () => this.signLanguageManager.settingsButton,
|
|
10435
|
+
set: (v) => {
|
|
10436
|
+
this.signLanguageManager.settingsButton = v;
|
|
10437
|
+
}
|
|
10438
|
+
},
|
|
10439
|
+
signLanguageSettingsMenu: {
|
|
10440
|
+
get: () => this.signLanguageManager.settingsMenu,
|
|
10441
|
+
set: (v) => {
|
|
10442
|
+
this.signLanguageManager.settingsMenu = v;
|
|
10443
|
+
}
|
|
10444
|
+
},
|
|
10445
|
+
signLanguageSettingsMenuVisible: {
|
|
10446
|
+
get: () => this.signLanguageManager.settingsMenuVisible,
|
|
10447
|
+
set: (v) => {
|
|
10448
|
+
this.signLanguageManager.settingsMenuVisible = v;
|
|
10449
|
+
}
|
|
10450
|
+
},
|
|
10451
|
+
signLanguageDraggable: {
|
|
10452
|
+
get: () => this.signLanguageManager.draggable,
|
|
10453
|
+
set: (v) => {
|
|
10454
|
+
this.signLanguageManager.draggable = v;
|
|
10455
|
+
}
|
|
10456
|
+
},
|
|
10457
|
+
currentSignLanguage: {
|
|
10458
|
+
get: () => this.signLanguageManager.currentLanguage,
|
|
10459
|
+
set: (v) => {
|
|
10460
|
+
this.signLanguageManager.currentLanguage = v;
|
|
10461
|
+
}
|
|
10462
|
+
}
|
|
10463
|
+
});
|
|
8692
10464
|
this.init();
|
|
8693
10465
|
}
|
|
8694
10466
|
async init() {
|
|
@@ -8951,53 +10723,7 @@
|
|
|
8951
10723
|
}
|
|
8952
10724
|
this.currentSource = src;
|
|
8953
10725
|
this._pendingSource = null;
|
|
8954
|
-
|
|
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
|
-
});
|
|
10726
|
+
this.audioDescriptionManager.initFromSourceElements(this.sourceElements, this.trackElements);
|
|
9001
10727
|
if (!this.originalSrc) {
|
|
9002
10728
|
this.originalSrc = src;
|
|
9003
10729
|
}
|
|
@@ -9114,6 +10840,64 @@
|
|
|
9114
10840
|
return posterPath;
|
|
9115
10841
|
}
|
|
9116
10842
|
}
|
|
10843
|
+
/**
|
|
10844
|
+
* Generate a poster image from video frame at specified time
|
|
10845
|
+
* @param {number} time - Time in seconds (default: 10)
|
|
10846
|
+
* @returns {Promise<string|null>} Data URL of the poster image or null if failed
|
|
10847
|
+
*/
|
|
10848
|
+
async generatePosterFromVideo(time = 10) {
|
|
10849
|
+
if (this.element.tagName !== "VIDEO") {
|
|
10850
|
+
return null;
|
|
10851
|
+
}
|
|
10852
|
+
const renderer = this.renderer;
|
|
10853
|
+
if (!renderer || !renderer.media || renderer.media.tagName !== "VIDEO") {
|
|
10854
|
+
return null;
|
|
10855
|
+
}
|
|
10856
|
+
const video = renderer.media;
|
|
10857
|
+
if (!video.duration || video.duration < time) {
|
|
10858
|
+
time = Math.min(time, Math.max(1, video.duration * 0.1));
|
|
10859
|
+
}
|
|
10860
|
+
let videoToUse = video;
|
|
10861
|
+
if (this.controlBar && this.controlBar.previewVideo && this.controlBar.previewSupported) {
|
|
10862
|
+
videoToUse = this.controlBar.previewVideo;
|
|
10863
|
+
}
|
|
10864
|
+
const restoreState = videoToUse === video;
|
|
10865
|
+
return await captureVideoFrame(videoToUse, time, {
|
|
10866
|
+
restoreState,
|
|
10867
|
+
quality: 0.9
|
|
10868
|
+
});
|
|
10869
|
+
}
|
|
10870
|
+
/**
|
|
10871
|
+
* Auto-generate poster from video if none is provided
|
|
10872
|
+
*/
|
|
10873
|
+
async autoGeneratePoster() {
|
|
10874
|
+
const hasPoster = this.element.getAttribute("poster") || this.element.poster || this.options.poster;
|
|
10875
|
+
if (hasPoster) {
|
|
10876
|
+
return;
|
|
10877
|
+
}
|
|
10878
|
+
if (this.element.tagName !== "VIDEO") {
|
|
10879
|
+
return;
|
|
10880
|
+
}
|
|
10881
|
+
if (!this.state.duration || this.state.duration === 0) {
|
|
10882
|
+
await new Promise((resolve) => {
|
|
10883
|
+
const onLoadedMetadata = () => {
|
|
10884
|
+
this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
10885
|
+
resolve();
|
|
10886
|
+
};
|
|
10887
|
+
if (this.element.readyState >= 1) {
|
|
10888
|
+
resolve();
|
|
10889
|
+
} else {
|
|
10890
|
+
this.element.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
10891
|
+
}
|
|
10892
|
+
});
|
|
10893
|
+
}
|
|
10894
|
+
const posterDataURL = await this.generatePosterFromVideo(10);
|
|
10895
|
+
if (posterDataURL) {
|
|
10896
|
+
this.element.poster = posterDataURL;
|
|
10897
|
+
this.log("Auto-generated poster from video frame at 10 seconds", "info");
|
|
10898
|
+
this.showPosterOverlay();
|
|
10899
|
+
}
|
|
10900
|
+
}
|
|
9117
10901
|
showPosterOverlay() {
|
|
9118
10902
|
if (!this.videoWrapper || this.element.tagName !== "VIDEO") {
|
|
9119
10903
|
return;
|
|
@@ -9122,7 +10906,7 @@
|
|
|
9122
10906
|
if (!poster) {
|
|
9123
10907
|
return;
|
|
9124
10908
|
}
|
|
9125
|
-
const resolvedPoster = this.resolvePosterPath(poster);
|
|
10909
|
+
const resolvedPoster = poster.startsWith("data:") ? poster : this.resolvePosterPath(poster);
|
|
9126
10910
|
this.videoWrapper.style.setProperty("--vidply-poster-image", 'url("'.concat(resolvedPoster, '")'));
|
|
9127
10911
|
this.videoWrapper.classList.add("vidply-forced-poster");
|
|
9128
10912
|
if (this._isAudioContent && this.container) {
|
|
@@ -9242,6 +11026,9 @@
|
|
|
9242
11026
|
if (trackConfig.default) {
|
|
9243
11027
|
track.default = true;
|
|
9244
11028
|
}
|
|
11029
|
+
if (trackConfig.describedSrc) {
|
|
11030
|
+
track.setAttribute("data-desc-src", trackConfig.describedSrc);
|
|
11031
|
+
}
|
|
9245
11032
|
const firstChild = this.element.firstChild;
|
|
9246
11033
|
if (firstChild && firstChild.nodeType === Node.ELEMENT_NODE && firstChild.tagName !== "TRACK") {
|
|
9247
11034
|
this.element.insertBefore(track, firstChild);
|
|
@@ -9256,6 +11043,13 @@
|
|
|
9256
11043
|
this.audioDescriptionSrc = config.audioDescriptionSrc || null;
|
|
9257
11044
|
this.signLanguageSrc = config.signLanguageSrc || null;
|
|
9258
11045
|
this.originalSrc = config.src;
|
|
11046
|
+
if (this.audioDescriptionManager) {
|
|
11047
|
+
this.audioDescriptionManager.updateSources(config.audioDescriptionSrc);
|
|
11048
|
+
this.audioDescriptionManager.reinitialize();
|
|
11049
|
+
}
|
|
11050
|
+
if (this.signLanguageManager) {
|
|
11051
|
+
this.signLanguageManager.updateSources(config.signLanguageSrc, config.signLanguageSources);
|
|
11052
|
+
}
|
|
9259
11053
|
if (wasAudioDescriptionEnabled) {
|
|
9260
11054
|
this.disableAudioDescription();
|
|
9261
11055
|
}
|
|
@@ -9664,8 +11458,12 @@
|
|
|
9664
11458
|
}
|
|
9665
11459
|
return null;
|
|
9666
11460
|
}
|
|
9667
|
-
// Audio Description
|
|
11461
|
+
// Audio Description (delegated to AudioDescriptionManager)
|
|
9668
11462
|
async enableAudioDescription() {
|
|
11463
|
+
return this.audioDescriptionManager.enable();
|
|
11464
|
+
}
|
|
11465
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
11466
|
+
async _legacyEnableAudioDescription() {
|
|
9669
11467
|
const hasSourceElementsWithDesc = this.sourceElements.some((el) => el.getAttribute("data-desc-src"));
|
|
9670
11468
|
const hasTracksWithDesc = this.audioDescriptionCaptionTracks.length > 0;
|
|
9671
11469
|
if (!this.audioDescriptionSrc && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
|
|
@@ -10398,6 +12196,10 @@
|
|
|
10398
12196
|
this.emit("audiodescriptionenabled");
|
|
10399
12197
|
}
|
|
10400
12198
|
async disableAudioDescription() {
|
|
12199
|
+
return this.audioDescriptionManager.disable();
|
|
12200
|
+
}
|
|
12201
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
12202
|
+
async _legacyDisableAudioDescription() {
|
|
10401
12203
|
if (!this.originalSrc) {
|
|
10402
12204
|
return;
|
|
10403
12205
|
}
|
|
@@ -10672,64 +12474,14 @@
|
|
|
10672
12474
|
this.emit("audiodescriptiondisabled");
|
|
10673
12475
|
}
|
|
10674
12476
|
async toggleAudioDescription() {
|
|
10675
|
-
|
|
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
|
-
}
|
|
12477
|
+
return this.audioDescriptionManager.toggle();
|
|
10730
12478
|
}
|
|
10731
|
-
// Sign Language
|
|
12479
|
+
// Sign Language (delegated to SignLanguageManager)
|
|
10732
12480
|
enableSignLanguage() {
|
|
12481
|
+
return this.signLanguageManager.enable();
|
|
12482
|
+
}
|
|
12483
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
12484
|
+
_legacyEnableSignLanguage() {
|
|
10733
12485
|
var _a;
|
|
10734
12486
|
const hasMultipleSources = Object.keys(this.signLanguageSources).length > 0;
|
|
10735
12487
|
const hasSingleSource = !!this.signLanguageSrc;
|
|
@@ -10982,23 +12734,16 @@
|
|
|
10982
12734
|
}, 150);
|
|
10983
12735
|
}
|
|
10984
12736
|
disableSignLanguage() {
|
|
10985
|
-
|
|
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");
|
|
12737
|
+
return this.signLanguageManager.disable();
|
|
10993
12738
|
}
|
|
10994
12739
|
toggleSignLanguage() {
|
|
10995
|
-
|
|
10996
|
-
this.disableSignLanguage();
|
|
10997
|
-
} else {
|
|
10998
|
-
this.enableSignLanguage();
|
|
10999
|
-
}
|
|
12740
|
+
return this.signLanguageManager.toggle();
|
|
11000
12741
|
}
|
|
11001
12742
|
setupSignLanguageInteraction() {
|
|
12743
|
+
return this.signLanguageManager._setupInteraction();
|
|
12744
|
+
}
|
|
12745
|
+
// Legacy method preserved for reference
|
|
12746
|
+
_legacySetupSignLanguageInteraction() {
|
|
11002
12747
|
if (!this.signLanguageWrapper) return;
|
|
11003
12748
|
const isMobile2 = window.innerWidth < 768;
|
|
11004
12749
|
const isFullscreen = this.state.fullscreen;
|
|
@@ -11136,6 +12881,10 @@
|
|
|
11136
12881
|
return langNames[langCode] || langCode.toUpperCase();
|
|
11137
12882
|
}
|
|
11138
12883
|
switchSignLanguage(langCode) {
|
|
12884
|
+
return this.signLanguageManager.switchLanguage(langCode);
|
|
12885
|
+
}
|
|
12886
|
+
// Legacy method preserved for reference
|
|
12887
|
+
_legacySwitchSignLanguage(langCode) {
|
|
11139
12888
|
if (!this.signLanguageSources[langCode] || !this.signLanguageVideo) {
|
|
11140
12889
|
return;
|
|
11141
12890
|
}
|
|
@@ -11151,6 +12900,10 @@
|
|
|
11151
12900
|
this.emit("signlanguagelanguagechanged", langCode);
|
|
11152
12901
|
}
|
|
11153
12902
|
showSignLanguageSettingsMenu() {
|
|
12903
|
+
return this.signLanguageManager.showSettingsMenu();
|
|
12904
|
+
}
|
|
12905
|
+
// Legacy method preserved for reference
|
|
12906
|
+
_legacyShowSignLanguageSettingsMenu() {
|
|
11154
12907
|
this.signLanguageSettingsMenuJustOpened = true;
|
|
11155
12908
|
setTimeout(() => {
|
|
11156
12909
|
this.signLanguageSettingsMenuJustOpened = false;
|
|
@@ -11294,25 +13047,7 @@
|
|
|
11294
13047
|
focusFirstMenuItem(this.signLanguageSettingsMenu, ".".concat(this.options.classPrefix, "-sign-language-settings-item"));
|
|
11295
13048
|
}
|
|
11296
13049
|
hideSignLanguageSettingsMenu({ focusButton = true } = {}) {
|
|
11297
|
-
|
|
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
|
-
}
|
|
13050
|
+
return this.signLanguageManager.hideSettingsMenu({ focusButton });
|
|
11316
13051
|
}
|
|
11317
13052
|
positionSignLanguageSettingsMenuImmediate() {
|
|
11318
13053
|
if (!this.signLanguageSettingsMenu || !this.signLanguageSettingsButton) return;
|
|
@@ -11416,6 +13151,13 @@
|
|
|
11416
13151
|
}
|
|
11417
13152
|
}
|
|
11418
13153
|
constrainSignLanguagePosition() {
|
|
13154
|
+
return this.signLanguageManager.constrainPosition();
|
|
13155
|
+
}
|
|
13156
|
+
saveSignLanguagePreferences() {
|
|
13157
|
+
return this.signLanguageManager.savePreferences();
|
|
13158
|
+
}
|
|
13159
|
+
// Legacy methods preserved for reference - can be removed after testing
|
|
13160
|
+
_legacyConstrainSignLanguagePosition() {
|
|
11419
13161
|
if (!this.signLanguageWrapper || !this.videoWrapper) return;
|
|
11420
13162
|
if (this.signLanguageDraggable && this.signLanguageDraggable.manuallyPositioned) {
|
|
11421
13163
|
return;
|
|
@@ -11465,7 +13207,7 @@
|
|
|
11465
13207
|
this.signLanguageWrapper.style.bottom = "auto";
|
|
11466
13208
|
this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
|
|
11467
13209
|
}
|
|
11468
|
-
|
|
13210
|
+
_legacySaveSignLanguagePreferences() {
|
|
11469
13211
|
if (!this.signLanguageWrapper) return;
|
|
11470
13212
|
this.storage.saveSignLanguagePreferences({
|
|
11471
13213
|
size: {
|
|
@@ -11475,58 +13217,7 @@
|
|
|
11475
13217
|
});
|
|
11476
13218
|
}
|
|
11477
13219
|
cleanupSignLanguage() {
|
|
11478
|
-
|
|
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;
|
|
13220
|
+
return this.signLanguageManager.cleanup();
|
|
11530
13221
|
}
|
|
11531
13222
|
// Settings
|
|
11532
13223
|
// Settings dialog removed - using individual control buttons instead
|