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/dev/vidply.esm.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
HTML5Renderer
|
|
8
|
-
} from "./vidply.chunk-
|
|
8
|
+
} from "./vidply.chunk-W2LSBD6Y.js";
|
|
9
9
|
import {
|
|
10
10
|
DOMUtils,
|
|
11
11
|
DraggableResizable,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
focusFirstMenuItem,
|
|
22
22
|
i18n,
|
|
23
23
|
preventDragOnElement
|
|
24
|
-
} from "./vidply.chunk-
|
|
24
|
+
} from "./vidply.chunk-GS2JX5RQ.js";
|
|
25
25
|
|
|
26
26
|
// src/utils/EventEmitter.js
|
|
27
27
|
var EventEmitter = class {
|
|
@@ -64,6 +64,108 @@ var EventEmitter = class {
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
// src/utils/PerformanceUtils.js
|
|
68
|
+
function debounce(func, wait = 100) {
|
|
69
|
+
let timeout;
|
|
70
|
+
return function executedFunction(...args) {
|
|
71
|
+
const later = () => {
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
func(...args);
|
|
74
|
+
};
|
|
75
|
+
clearTimeout(timeout);
|
|
76
|
+
timeout = setTimeout(later, wait);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function isMobile(breakpoint = 768) {
|
|
80
|
+
return window.innerWidth < breakpoint;
|
|
81
|
+
}
|
|
82
|
+
function rafWithTimeout(callback, timeout = 100) {
|
|
83
|
+
let called = false;
|
|
84
|
+
const execute = () => {
|
|
85
|
+
if (!called) {
|
|
86
|
+
called = true;
|
|
87
|
+
callback();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
requestAnimationFrame(execute);
|
|
91
|
+
setTimeout(execute, timeout);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/utils/VideoFrameCapture.js
|
|
95
|
+
async function captureVideoFrame(video, time, options = {}) {
|
|
96
|
+
if (!video || video.tagName !== "VIDEO") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const {
|
|
100
|
+
restoreState = true,
|
|
101
|
+
quality = 0.9,
|
|
102
|
+
maxWidth,
|
|
103
|
+
maxHeight
|
|
104
|
+
} = options;
|
|
105
|
+
const wasPlaying = !video.paused;
|
|
106
|
+
const originalTime = video.currentTime;
|
|
107
|
+
const originalMuted = video.muted;
|
|
108
|
+
if (restoreState) {
|
|
109
|
+
video.muted = true;
|
|
110
|
+
}
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const captureFrame = () => {
|
|
113
|
+
try {
|
|
114
|
+
let width = video.videoWidth || 640;
|
|
115
|
+
let height = video.videoHeight || 360;
|
|
116
|
+
if (maxWidth && width > maxWidth) {
|
|
117
|
+
const ratio = maxWidth / width;
|
|
118
|
+
width = maxWidth;
|
|
119
|
+
height = Math.round(height * ratio);
|
|
120
|
+
}
|
|
121
|
+
if (maxHeight && height > maxHeight) {
|
|
122
|
+
const ratio = maxHeight / height;
|
|
123
|
+
height = maxHeight;
|
|
124
|
+
width = Math.round(width * ratio);
|
|
125
|
+
}
|
|
126
|
+
const canvas = document.createElement("canvas");
|
|
127
|
+
canvas.width = width;
|
|
128
|
+
canvas.height = height;
|
|
129
|
+
const ctx = canvas.getContext("2d");
|
|
130
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
131
|
+
const dataURL = canvas.toDataURL("image/jpeg", quality);
|
|
132
|
+
if (restoreState) {
|
|
133
|
+
video.currentTime = originalTime;
|
|
134
|
+
video.muted = originalMuted;
|
|
135
|
+
if (wasPlaying && !video.paused) {
|
|
136
|
+
video.play().catch(() => {
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
resolve(dataURL);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (restoreState) {
|
|
143
|
+
video.currentTime = originalTime;
|
|
144
|
+
video.muted = originalMuted;
|
|
145
|
+
if (wasPlaying && !video.paused) {
|
|
146
|
+
video.play().catch(() => {
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
resolve(null);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const onSeeked = () => {
|
|
154
|
+
video.removeEventListener("seeked", onSeeked);
|
|
155
|
+
requestAnimationFrame(() => {
|
|
156
|
+
requestAnimationFrame(captureFrame);
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
const timeDiff = Math.abs(video.currentTime - time);
|
|
160
|
+
if (timeDiff < 0.1 && video.readyState >= 2) {
|
|
161
|
+
captureFrame();
|
|
162
|
+
} else {
|
|
163
|
+
video.addEventListener("seeked", onSeeked);
|
|
164
|
+
video.currentTime = time;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
67
169
|
// src/controls/ControlBar.js
|
|
68
170
|
var ControlBar = class {
|
|
69
171
|
constructor(player) {
|
|
@@ -84,17 +186,13 @@ var ControlBar = class {
|
|
|
84
186
|
this.setupAutoHide();
|
|
85
187
|
this.setupOverflowDetection();
|
|
86
188
|
}
|
|
87
|
-
// Helper method to check if we're on a mobile device
|
|
88
|
-
isMobile() {
|
|
89
|
-
return window.innerWidth < 768;
|
|
90
|
-
}
|
|
91
189
|
// Helper method to detect touch devices
|
|
92
190
|
isTouchDevice() {
|
|
93
191
|
return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
|
94
192
|
}
|
|
95
193
|
// Smart menu positioning to avoid overflow
|
|
96
194
|
positionMenu(menu, button, immediate = false) {
|
|
97
|
-
const
|
|
195
|
+
const mobile = isMobile();
|
|
98
196
|
const isOverflowMenu = menu.classList.contains(`${this.player.options.classPrefix}-overflow-menu-list`);
|
|
99
197
|
const isFullscreen = this.player.state.fullscreen;
|
|
100
198
|
if (isFullscreen && menu.parentElement === this.player.container) {
|
|
@@ -135,7 +233,7 @@ var ControlBar = class {
|
|
|
135
233
|
}
|
|
136
234
|
return;
|
|
137
235
|
}
|
|
138
|
-
if (
|
|
236
|
+
if (mobile) {
|
|
139
237
|
const isVolumeMenu = menu.classList.contains(`${this.player.options.classPrefix}-volume-menu`);
|
|
140
238
|
const doMobilePositioning = () => {
|
|
141
239
|
const parentContainer = button.parentElement;
|
|
@@ -704,13 +802,117 @@ var ControlBar = class {
|
|
|
704
802
|
this.controls.progressTooltip = DOMUtils.createElement("div", {
|
|
705
803
|
className: `${this.player.options.classPrefix}-progress-tooltip`
|
|
706
804
|
});
|
|
805
|
+
this.controls.progressPreview = DOMUtils.createElement("div", {
|
|
806
|
+
className: `${this.player.options.classPrefix}-progress-preview`,
|
|
807
|
+
attributes: {
|
|
808
|
+
"aria-hidden": "true"
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
this.controls.progressTooltip.appendChild(this.controls.progressPreview);
|
|
812
|
+
this.controls.progressTooltipTime = DOMUtils.createElement("div", {
|
|
813
|
+
className: `${this.player.options.classPrefix}-progress-tooltip-time`
|
|
814
|
+
});
|
|
815
|
+
this.controls.progressTooltip.appendChild(this.controls.progressTooltipTime);
|
|
707
816
|
progressContainer.appendChild(this.controls.buffered);
|
|
708
817
|
progressContainer.appendChild(this.controls.played);
|
|
709
818
|
this.controls.played.appendChild(this.controls.progressHandle);
|
|
710
819
|
progressContainer.appendChild(this.controls.progressTooltip);
|
|
711
820
|
this.controls.progress = progressContainer;
|
|
821
|
+
this.initPreviewThumbnail();
|
|
712
822
|
this.setupProgressBarEvents();
|
|
713
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* Initialize preview thumbnail functionality for HTML5 video
|
|
826
|
+
*/
|
|
827
|
+
initPreviewThumbnail() {
|
|
828
|
+
this.previewThumbnailCache = /* @__PURE__ */ new Map();
|
|
829
|
+
this.previewVideo = null;
|
|
830
|
+
this.currentPreviewTime = null;
|
|
831
|
+
this.previewThumbnailTimeout = null;
|
|
832
|
+
this.previewSupported = false;
|
|
833
|
+
const isVideo = this.player.element && this.player.element.tagName === "VIDEO";
|
|
834
|
+
if (!isVideo) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const renderer = this.player.renderer;
|
|
838
|
+
const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === "VIDEO";
|
|
839
|
+
const isHTML5Renderer = renderer && (renderer.constructor.name === "HTML5Renderer" || renderer.constructor.name === "HLSRenderer" && hasVideoMedia);
|
|
840
|
+
this.previewSupported = isHTML5Renderer && hasVideoMedia;
|
|
841
|
+
if (this.previewSupported) {
|
|
842
|
+
this.previewVideo = document.createElement("video");
|
|
843
|
+
this.previewVideo.muted = true;
|
|
844
|
+
this.previewVideo.preload = "metadata";
|
|
845
|
+
this.previewVideo.style.position = "absolute";
|
|
846
|
+
this.previewVideo.style.visibility = "hidden";
|
|
847
|
+
this.previewVideo.style.width = "1px";
|
|
848
|
+
this.previewVideo.style.height = "1px";
|
|
849
|
+
this.previewVideo.style.top = "-9999px";
|
|
850
|
+
const mainVideo = renderer.media || this.player.element;
|
|
851
|
+
if (mainVideo.src) {
|
|
852
|
+
this.previewVideo.src = mainVideo.src;
|
|
853
|
+
} else {
|
|
854
|
+
const source = mainVideo.querySelector("source");
|
|
855
|
+
if (source) {
|
|
856
|
+
this.previewVideo.src = source.src;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
this.previewVideo.addEventListener("error", () => {
|
|
860
|
+
this.player.log("Preview video failed to load", "warn");
|
|
861
|
+
this.previewSupported = false;
|
|
862
|
+
});
|
|
863
|
+
if (this.player.container) {
|
|
864
|
+
this.player.container.appendChild(this.previewVideo);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Generate preview thumbnail for a specific time
|
|
870
|
+
* @param {number} time - Time in seconds
|
|
871
|
+
* @returns {Promise<string>} Data URL of the thumbnail
|
|
872
|
+
*/
|
|
873
|
+
async generatePreviewThumbnail(time) {
|
|
874
|
+
if (!this.previewSupported || !this.previewVideo) {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
const cacheKey = Math.floor(time);
|
|
878
|
+
if (this.previewThumbnailCache.has(cacheKey)) {
|
|
879
|
+
return this.previewThumbnailCache.get(cacheKey);
|
|
880
|
+
}
|
|
881
|
+
const dataURL = await captureVideoFrame(this.previewVideo, time, {
|
|
882
|
+
restoreState: false,
|
|
883
|
+
quality: 0.8,
|
|
884
|
+
maxWidth: 160,
|
|
885
|
+
maxHeight: 90
|
|
886
|
+
});
|
|
887
|
+
if (dataURL) {
|
|
888
|
+
if (this.previewThumbnailCache.size > 20) {
|
|
889
|
+
const firstKey = this.previewThumbnailCache.keys().next().value;
|
|
890
|
+
this.previewThumbnailCache.delete(firstKey);
|
|
891
|
+
}
|
|
892
|
+
this.previewThumbnailCache.set(cacheKey, dataURL);
|
|
893
|
+
}
|
|
894
|
+
return dataURL;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Update preview thumbnail display
|
|
898
|
+
* @param {number} time - Time in seconds
|
|
899
|
+
*/
|
|
900
|
+
async updatePreviewThumbnail(time) {
|
|
901
|
+
if (!this.previewSupported) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (this.previewThumbnailTimeout) {
|
|
905
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
906
|
+
}
|
|
907
|
+
this.previewThumbnailTimeout = setTimeout(async () => {
|
|
908
|
+
const thumbnail = await this.generatePreviewThumbnail(time);
|
|
909
|
+
if (thumbnail && this.controls.progressPreview) {
|
|
910
|
+
this.controls.progressPreview.style.backgroundImage = `url(${thumbnail})`;
|
|
911
|
+
this.controls.progressPreview.style.display = "block";
|
|
912
|
+
}
|
|
913
|
+
this.currentPreviewTime = time;
|
|
914
|
+
}, 100);
|
|
915
|
+
}
|
|
714
916
|
setupProgressBarEvents() {
|
|
715
917
|
const progress = this.controls.progress;
|
|
716
918
|
const updateProgress = (clientX) => {
|
|
@@ -737,13 +939,19 @@ var ControlBar = class {
|
|
|
737
939
|
progress.addEventListener("mousemove", (e) => {
|
|
738
940
|
if (!this.isDraggingProgress) {
|
|
739
941
|
const { time } = updateProgress(e.clientX);
|
|
740
|
-
|
|
741
|
-
|
|
942
|
+
const rect = progress.getBoundingClientRect();
|
|
943
|
+
const left = e.clientX - rect.left;
|
|
944
|
+
this.controls.progressTooltipTime.textContent = TimeUtils.formatTime(time);
|
|
945
|
+
this.controls.progressTooltip.style.left = `${left}px`;
|
|
742
946
|
this.controls.progressTooltip.style.display = "block";
|
|
947
|
+
this.updatePreviewThumbnail(time);
|
|
743
948
|
}
|
|
744
949
|
});
|
|
745
950
|
progress.addEventListener("mouseleave", () => {
|
|
746
951
|
this.controls.progressTooltip.style.display = "none";
|
|
952
|
+
if (this.previewThumbnailTimeout) {
|
|
953
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
954
|
+
}
|
|
747
955
|
});
|
|
748
956
|
progress.addEventListener("keydown", (e) => {
|
|
749
957
|
if (e.key === "ArrowLeft") {
|
|
@@ -2490,6 +2698,20 @@ var ControlBar = class {
|
|
|
2490
2698
|
hide() {
|
|
2491
2699
|
this.element.style.display = "none";
|
|
2492
2700
|
}
|
|
2701
|
+
/**
|
|
2702
|
+
* Cleanup preview thumbnail resources
|
|
2703
|
+
*/
|
|
2704
|
+
cleanupPreviewThumbnail() {
|
|
2705
|
+
if (this.previewThumbnailTimeout) {
|
|
2706
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
2707
|
+
this.previewThumbnailTimeout = null;
|
|
2708
|
+
}
|
|
2709
|
+
if (this.previewVideo && this.previewVideo.parentNode) {
|
|
2710
|
+
this.previewVideo.parentNode.removeChild(this.previewVideo);
|
|
2711
|
+
this.previewVideo = null;
|
|
2712
|
+
}
|
|
2713
|
+
this.previewThumbnailCache.clear();
|
|
2714
|
+
}
|
|
2493
2715
|
destroy() {
|
|
2494
2716
|
if (this.hideTimeout) {
|
|
2495
2717
|
clearTimeout(this.hideTimeout);
|
|
@@ -2497,39 +2719,13 @@ var ControlBar = class {
|
|
|
2497
2719
|
if (this.overflowResizeObserver) {
|
|
2498
2720
|
this.overflowResizeObserver.disconnect();
|
|
2499
2721
|
}
|
|
2722
|
+
this.cleanupPreviewThumbnail();
|
|
2500
2723
|
if (this.element && this.element.parentNode) {
|
|
2501
2724
|
this.element.parentNode.removeChild(this.element);
|
|
2502
2725
|
}
|
|
2503
2726
|
}
|
|
2504
2727
|
};
|
|
2505
2728
|
|
|
2506
|
-
// src/utils/PerformanceUtils.js
|
|
2507
|
-
function debounce(func, wait = 100) {
|
|
2508
|
-
let timeout;
|
|
2509
|
-
return function executedFunction(...args) {
|
|
2510
|
-
const later = () => {
|
|
2511
|
-
clearTimeout(timeout);
|
|
2512
|
-
func(...args);
|
|
2513
|
-
};
|
|
2514
|
-
clearTimeout(timeout);
|
|
2515
|
-
timeout = setTimeout(later, wait);
|
|
2516
|
-
};
|
|
2517
|
-
}
|
|
2518
|
-
function isMobile(breakpoint = 768) {
|
|
2519
|
-
return window.innerWidth < breakpoint;
|
|
2520
|
-
}
|
|
2521
|
-
function rafWithTimeout(callback, timeout = 100) {
|
|
2522
|
-
let called = false;
|
|
2523
|
-
const execute = () => {
|
|
2524
|
-
if (!called) {
|
|
2525
|
-
called = true;
|
|
2526
|
-
callback();
|
|
2527
|
-
}
|
|
2528
|
-
};
|
|
2529
|
-
requestAnimationFrame(execute);
|
|
2530
|
-
setTimeout(execute, timeout);
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
2729
|
// src/controls/CaptionManager.js
|
|
2534
2730
|
var CaptionManager = class {
|
|
2535
2731
|
constructor(player) {
|
|
@@ -3059,40 +3255,1495 @@ var KeyboardManager = class {
|
|
|
3059
3255
|
}
|
|
3060
3256
|
};
|
|
3061
3257
|
|
|
3062
|
-
// src/core/
|
|
3063
|
-
var
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
this.
|
|
3068
|
-
|
|
3069
|
-
|
|
3258
|
+
// src/core/AudioDescriptionManager.js
|
|
3259
|
+
var AudioDescriptionManager = class {
|
|
3260
|
+
constructor(player) {
|
|
3261
|
+
this.player = player;
|
|
3262
|
+
this.enabled = false;
|
|
3263
|
+
this.desiredState = false;
|
|
3264
|
+
this.src = player.options.audioDescriptionSrc;
|
|
3265
|
+
this.sourceElement = null;
|
|
3266
|
+
this.originalSource = null;
|
|
3267
|
+
this.captionTracks = [];
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Initialize audio description from source elements
|
|
3271
|
+
* Called during player initialization
|
|
3272
|
+
*/
|
|
3273
|
+
initFromSourceElements(sourceElements, trackElements) {
|
|
3274
|
+
for (const sourceEl of sourceElements) {
|
|
3275
|
+
const descSrc = sourceEl.getAttribute("data-desc-src");
|
|
3276
|
+
const origSrc = sourceEl.getAttribute("data-orig-src");
|
|
3277
|
+
if (descSrc || origSrc) {
|
|
3278
|
+
if (!this.sourceElement) {
|
|
3279
|
+
this.sourceElement = sourceEl;
|
|
3280
|
+
}
|
|
3281
|
+
if (origSrc) {
|
|
3282
|
+
if (!this.originalSource) {
|
|
3283
|
+
this.originalSource = origSrc;
|
|
3284
|
+
}
|
|
3285
|
+
if (!this.player.originalSrc) {
|
|
3286
|
+
this.player.originalSrc = origSrc;
|
|
3287
|
+
}
|
|
3288
|
+
} else {
|
|
3289
|
+
const currentSrcAttr = sourceEl.getAttribute("src");
|
|
3290
|
+
if (!this.originalSource && currentSrcAttr) {
|
|
3291
|
+
this.originalSource = currentSrcAttr;
|
|
3292
|
+
}
|
|
3293
|
+
if (!this.player.originalSrc && currentSrcAttr) {
|
|
3294
|
+
this.player.originalSrc = currentSrcAttr;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
if (descSrc && !this.src) {
|
|
3298
|
+
this.src = descSrc;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3070
3301
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3302
|
+
trackElements.forEach((trackEl) => {
|
|
3303
|
+
const trackKind = trackEl.getAttribute("kind");
|
|
3304
|
+
const trackDescSrc = trackEl.getAttribute("data-desc-src");
|
|
3305
|
+
if ((trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters" || trackKind === "descriptions") && trackDescSrc) {
|
|
3306
|
+
this.captionTracks.push({
|
|
3307
|
+
trackElement: trackEl,
|
|
3308
|
+
originalSrc: trackEl.getAttribute("src"),
|
|
3309
|
+
describedSrc: trackDescSrc,
|
|
3310
|
+
originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
|
|
3311
|
+
explicit: true
|
|
3312
|
+
});
|
|
3313
|
+
this.player.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute("src")} -> ${trackDescSrc}`);
|
|
3314
|
+
}
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Check if audio description is available
|
|
3319
|
+
*/
|
|
3320
|
+
isAvailable() {
|
|
3321
|
+
const hasSourceElementsWithDesc = this.player.sourceElements.some(
|
|
3322
|
+
(el) => el.getAttribute("data-desc-src")
|
|
3323
|
+
);
|
|
3324
|
+
return !!(this.src || hasSourceElementsWithDesc || this.captionTracks.length > 0);
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* Enable audio description
|
|
3328
|
+
*/
|
|
3329
|
+
async enable() {
|
|
3330
|
+
const hasSourceElementsWithDesc = this.player.sourceElements.some(
|
|
3331
|
+
(el) => el.getAttribute("data-desc-src")
|
|
3332
|
+
);
|
|
3333
|
+
const hasTracksWithDesc = this.captionTracks.length > 0;
|
|
3334
|
+
if (!this.src && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
|
|
3335
|
+
console.warn("VidPly: No audio description source, source elements, or tracks provided");
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
this.desiredState = true;
|
|
3339
|
+
const currentTime = this.player.state.currentTime;
|
|
3340
|
+
const wasPlaying = this.player.state.playing;
|
|
3341
|
+
const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
|
|
3342
|
+
const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
|
|
3343
|
+
const currentCaptionText = this._getCurrentCaptionText();
|
|
3344
|
+
if (this.sourceElement) {
|
|
3345
|
+
await this._enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
|
|
3346
|
+
} else {
|
|
3347
|
+
await this._enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Disable audio description
|
|
3352
|
+
*/
|
|
3353
|
+
async disable() {
|
|
3354
|
+
if (!this.player.originalSrc) {
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
this.desiredState = false;
|
|
3358
|
+
const currentTime = this.player.state.currentTime;
|
|
3359
|
+
const wasPlaying = this.player.state.playing;
|
|
3360
|
+
const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
|
|
3361
|
+
const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
|
|
3362
|
+
const currentCaptionText = this._getCurrentCaptionText();
|
|
3363
|
+
if (this.sourceElement) {
|
|
3364
|
+
await this._disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
|
|
3365
|
+
} else {
|
|
3366
|
+
await this._disableWithDirectSrc(currentTime, wasPlaying, posterValue);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
/**
|
|
3370
|
+
* Toggle audio description
|
|
3371
|
+
*/
|
|
3372
|
+
async toggle() {
|
|
3373
|
+
const descriptionTrack = this.player.findTextTrack("descriptions");
|
|
3374
|
+
const hasAudioDescriptionSrc = this.isAvailable();
|
|
3375
|
+
if (descriptionTrack && !hasAudioDescriptionSrc) {
|
|
3376
|
+
if (descriptionTrack.mode === "showing") {
|
|
3377
|
+
descriptionTrack.mode = "hidden";
|
|
3378
|
+
this.enabled = false;
|
|
3379
|
+
this.player.emit("audiodescriptiondisabled");
|
|
3380
|
+
} else {
|
|
3381
|
+
descriptionTrack.mode = "showing";
|
|
3382
|
+
this.enabled = true;
|
|
3383
|
+
this.player.emit("audiodescriptionenabled");
|
|
3384
|
+
}
|
|
3385
|
+
} else if (descriptionTrack && hasAudioDescriptionSrc) {
|
|
3386
|
+
if (this.enabled) {
|
|
3387
|
+
this.desiredState = false;
|
|
3388
|
+
await this.disable();
|
|
3389
|
+
} else {
|
|
3390
|
+
descriptionTrack.mode = "showing";
|
|
3391
|
+
this.desiredState = true;
|
|
3392
|
+
await this.enable();
|
|
3393
|
+
}
|
|
3394
|
+
} else if (hasAudioDescriptionSrc) {
|
|
3395
|
+
if (this.enabled) {
|
|
3396
|
+
this.desiredState = false;
|
|
3397
|
+
await this.disable();
|
|
3398
|
+
} else {
|
|
3399
|
+
this.desiredState = true;
|
|
3400
|
+
await this.enable();
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
/**
|
|
3405
|
+
* Get current caption text for synchronization
|
|
3406
|
+
*/
|
|
3407
|
+
_getCurrentCaptionText() {
|
|
3408
|
+
if (this.player.captionManager && this.player.captionManager.currentTrack && this.player.captionManager.currentCue) {
|
|
3409
|
+
return this.player.captionManager.currentCue.text;
|
|
3410
|
+
}
|
|
3411
|
+
return null;
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Validate that a track URL exists
|
|
3415
|
+
*/
|
|
3416
|
+
async _validateTrackExists(url) {
|
|
3417
|
+
try {
|
|
3418
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
3419
|
+
return response.ok;
|
|
3420
|
+
} catch {
|
|
3421
|
+
return false;
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
/**
|
|
3425
|
+
* Swap caption tracks to described versions
|
|
3426
|
+
*/
|
|
3427
|
+
async _swapCaptionTracks(toDescribed = true) {
|
|
3428
|
+
if (this.captionTracks.length === 0) return [];
|
|
3429
|
+
const swappedTracks = [];
|
|
3430
|
+
const validationPromises = this.captionTracks.map(async (trackInfo) => {
|
|
3431
|
+
if (trackInfo.trackElement && trackInfo.describedSrc) {
|
|
3432
|
+
if (trackInfo.explicit === true) {
|
|
3433
|
+
try {
|
|
3434
|
+
const exists = await this._validateTrackExists(
|
|
3435
|
+
toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc
|
|
3436
|
+
);
|
|
3437
|
+
return { trackInfo, exists };
|
|
3438
|
+
} catch {
|
|
3439
|
+
return { trackInfo, exists: false };
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
return { trackInfo, exists: false };
|
|
3444
|
+
});
|
|
3445
|
+
const validationResults = await Promise.all(validationPromises);
|
|
3446
|
+
const tracksToSwap = validationResults.filter((result) => result.exists);
|
|
3447
|
+
if (tracksToSwap.length > 0) {
|
|
3448
|
+
const trackModes = /* @__PURE__ */ new Map();
|
|
3449
|
+
tracksToSwap.forEach(({ trackInfo }) => {
|
|
3450
|
+
const textTrack = trackInfo.trackElement.track;
|
|
3451
|
+
if (textTrack) {
|
|
3452
|
+
trackModes.set(trackInfo, {
|
|
3453
|
+
wasShowing: textTrack.mode === "showing",
|
|
3454
|
+
wasHidden: textTrack.mode === "hidden"
|
|
3455
|
+
});
|
|
3456
|
+
} else {
|
|
3457
|
+
trackModes.set(trackInfo, { wasShowing: false, wasHidden: false });
|
|
3079
3458
|
}
|
|
3080
3459
|
});
|
|
3081
|
-
const
|
|
3082
|
-
|
|
3083
|
-
|
|
3460
|
+
const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
|
|
3461
|
+
const attributes = {};
|
|
3462
|
+
Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
|
|
3463
|
+
attributes[attr.name] = attr.value;
|
|
3464
|
+
});
|
|
3465
|
+
const result = {
|
|
3466
|
+
trackInfo,
|
|
3467
|
+
oldSrc: trackInfo.trackElement.getAttribute("src"),
|
|
3468
|
+
parent: trackInfo.trackElement.parentNode,
|
|
3469
|
+
nextSibling: trackInfo.trackElement.nextSibling,
|
|
3470
|
+
attributes
|
|
3471
|
+
};
|
|
3472
|
+
trackInfo.trackElement.remove();
|
|
3473
|
+
return result;
|
|
3474
|
+
});
|
|
3475
|
+
this.player.element.load();
|
|
3476
|
+
await new Promise((resolve) => {
|
|
3477
|
+
setTimeout(() => {
|
|
3478
|
+
tracksToReadd.forEach(({ trackInfo, parent, nextSibling, attributes }) => {
|
|
3479
|
+
swappedTracks.push(trackInfo);
|
|
3480
|
+
const newTrackElement = document.createElement("track");
|
|
3481
|
+
const newSrc = toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc;
|
|
3482
|
+
newTrackElement.setAttribute("src", newSrc);
|
|
3483
|
+
Object.keys(attributes).forEach((attrName) => {
|
|
3484
|
+
if (attrName !== "src" && attrName !== "data-desc-src") {
|
|
3485
|
+
newTrackElement.setAttribute(attrName, attributes[attrName]);
|
|
3486
|
+
}
|
|
3487
|
+
});
|
|
3488
|
+
const targetParent = parent || this.player.element;
|
|
3489
|
+
if (nextSibling && nextSibling.parentNode) {
|
|
3490
|
+
targetParent.insertBefore(newTrackElement, nextSibling);
|
|
3491
|
+
} else {
|
|
3492
|
+
targetParent.appendChild(newTrackElement);
|
|
3493
|
+
}
|
|
3494
|
+
trackInfo.trackElement = newTrackElement;
|
|
3495
|
+
});
|
|
3496
|
+
this.player.invalidateTrackCache();
|
|
3497
|
+
const setupNewTracks = () => {
|
|
3498
|
+
this.player.setManagedTimeout(() => {
|
|
3499
|
+
swappedTracks.forEach((trackInfo) => {
|
|
3500
|
+
const newTextTrack = trackInfo.trackElement.track;
|
|
3501
|
+
if (newTextTrack) {
|
|
3502
|
+
const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
|
|
3503
|
+
newTextTrack.mode = "hidden";
|
|
3504
|
+
const restoreMode = () => {
|
|
3505
|
+
if (modeInfo.wasShowing || modeInfo.wasHidden) {
|
|
3506
|
+
newTextTrack.mode = "hidden";
|
|
3507
|
+
} else {
|
|
3508
|
+
newTextTrack.mode = "disabled";
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
if (newTextTrack.readyState >= 2) {
|
|
3512
|
+
restoreMode();
|
|
3513
|
+
} else {
|
|
3514
|
+
newTextTrack.addEventListener("load", restoreMode, { once: true });
|
|
3515
|
+
newTextTrack.addEventListener("error", restoreMode, { once: true });
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
}, 300);
|
|
3520
|
+
};
|
|
3521
|
+
if (this.player.element.readyState >= 1) {
|
|
3522
|
+
setTimeout(setupNewTracks, 200);
|
|
3523
|
+
} else {
|
|
3524
|
+
this.player.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
|
|
3525
|
+
setTimeout(setupNewTracks, 2e3);
|
|
3526
|
+
}
|
|
3527
|
+
resolve();
|
|
3528
|
+
}, 100);
|
|
3084
3529
|
});
|
|
3085
|
-
this.element.innerHTML = "";
|
|
3086
|
-
this.element.appendChild(mediaElement);
|
|
3087
|
-
this.element = mediaElement;
|
|
3088
3530
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3531
|
+
return swappedTracks;
|
|
3532
|
+
}
|
|
3533
|
+
/**
|
|
3534
|
+
* Update source elements to described versions
|
|
3535
|
+
*/
|
|
3536
|
+
_updateSourceElements(toDescribed = true) {
|
|
3537
|
+
const sourceElements = this.player.sourceElements;
|
|
3538
|
+
const sourcesToUpdate = [];
|
|
3539
|
+
sourceElements.forEach((sourceEl) => {
|
|
3540
|
+
const descSrcAttr = sourceEl.getAttribute("data-desc-src");
|
|
3541
|
+
const currentSrc = sourceEl.getAttribute("src");
|
|
3542
|
+
if (descSrcAttr) {
|
|
3543
|
+
const type = sourceEl.getAttribute("type");
|
|
3544
|
+
let origSrc = sourceEl.getAttribute("data-orig-src") || currentSrc;
|
|
3545
|
+
sourcesToUpdate.push({
|
|
3546
|
+
src: toDescribed ? descSrcAttr : origSrc,
|
|
3547
|
+
type,
|
|
3548
|
+
origSrc,
|
|
3549
|
+
descSrc: descSrcAttr
|
|
3550
|
+
});
|
|
3551
|
+
} else {
|
|
3552
|
+
sourcesToUpdate.push({
|
|
3553
|
+
src: sourceEl.getAttribute("src"),
|
|
3554
|
+
type: sourceEl.getAttribute("type"),
|
|
3555
|
+
origSrc: null,
|
|
3556
|
+
descSrc: null
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
if (this.player.element.hasAttribute("src")) {
|
|
3561
|
+
this.player.element.removeAttribute("src");
|
|
3562
|
+
}
|
|
3563
|
+
sourceElements.forEach((sourceEl) => sourceEl.remove());
|
|
3564
|
+
sourcesToUpdate.forEach((sourceInfo) => {
|
|
3565
|
+
const newSource = document.createElement("source");
|
|
3566
|
+
newSource.setAttribute("src", sourceInfo.src);
|
|
3567
|
+
if (sourceInfo.type) {
|
|
3568
|
+
newSource.setAttribute("type", sourceInfo.type);
|
|
3569
|
+
}
|
|
3570
|
+
if (sourceInfo.origSrc) {
|
|
3571
|
+
newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
|
|
3572
|
+
}
|
|
3573
|
+
if (sourceInfo.descSrc) {
|
|
3574
|
+
newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
|
|
3575
|
+
}
|
|
3576
|
+
const firstTrack = this.player.element.querySelector("track");
|
|
3577
|
+
if (firstTrack) {
|
|
3578
|
+
this.player.element.insertBefore(newSource, firstTrack);
|
|
3579
|
+
} else {
|
|
3580
|
+
this.player.element.appendChild(newSource);
|
|
3581
|
+
}
|
|
3582
|
+
});
|
|
3583
|
+
this.player._sourceElementsDirty = true;
|
|
3584
|
+
this.player._sourceElementsCache = null;
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* Wait for media to be ready
|
|
3588
|
+
*/
|
|
3589
|
+
async _waitForMediaReady(needSeek = false) {
|
|
3590
|
+
await new Promise((resolve) => {
|
|
3591
|
+
if (this.player.element.readyState >= 1) {
|
|
3592
|
+
resolve();
|
|
3593
|
+
} else {
|
|
3594
|
+
const onLoad = () => {
|
|
3595
|
+
this.player.element.removeEventListener("loadedmetadata", onLoad);
|
|
3596
|
+
resolve();
|
|
3597
|
+
};
|
|
3598
|
+
this.player.element.addEventListener("loadedmetadata", onLoad);
|
|
3599
|
+
}
|
|
3600
|
+
});
|
|
3601
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
3602
|
+
if (needSeek) {
|
|
3603
|
+
await new Promise((resolve) => {
|
|
3604
|
+
if (this.player.element.readyState >= 3) {
|
|
3605
|
+
resolve();
|
|
3606
|
+
} else {
|
|
3607
|
+
const onCanPlay = () => {
|
|
3608
|
+
this.player.element.removeEventListener("canplay", onCanPlay);
|
|
3609
|
+
this.player.element.removeEventListener("canplaythrough", onCanPlay);
|
|
3610
|
+
resolve();
|
|
3611
|
+
};
|
|
3612
|
+
this.player.element.addEventListener("canplay", onCanPlay, { once: true });
|
|
3613
|
+
this.player.element.addEventListener("canplaythrough", onCanPlay, { once: true });
|
|
3614
|
+
setTimeout(() => {
|
|
3615
|
+
this.player.element.removeEventListener("canplay", onCanPlay);
|
|
3616
|
+
this.player.element.removeEventListener("canplaythrough", onCanPlay);
|
|
3617
|
+
resolve();
|
|
3618
|
+
}, 3e3);
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Restore playback state after source change
|
|
3625
|
+
*/
|
|
3626
|
+
async _restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText) {
|
|
3627
|
+
let syncTime = currentTime;
|
|
3628
|
+
if (currentCaptionText && this.player.captionManager && this.player.captionManager.tracks.length > 0) {
|
|
3629
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3630
|
+
const matchingTime = this.player.findMatchingCaptionTime(
|
|
3631
|
+
currentCaptionText,
|
|
3632
|
+
this.player.captionManager.tracks
|
|
3633
|
+
);
|
|
3634
|
+
if (matchingTime !== null) {
|
|
3635
|
+
syncTime = matchingTime;
|
|
3636
|
+
if (this.player.options.debug) {
|
|
3637
|
+
this.player.log(`[VidPly] Syncing via caption: ${currentTime}s -> ${syncTime}s`);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
if (syncTime > 0) {
|
|
3642
|
+
this.player.seek(syncTime);
|
|
3643
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3644
|
+
}
|
|
3645
|
+
if (wasPlaying) {
|
|
3646
|
+
await this.player.play();
|
|
3647
|
+
this.player.setManagedTimeout(() => {
|
|
3648
|
+
this.player.hidePosterOverlay();
|
|
3649
|
+
}, 100);
|
|
3650
|
+
} else {
|
|
3651
|
+
this.player.pause();
|
|
3652
|
+
if (!shouldKeepPoster) {
|
|
3653
|
+
this.player.hidePosterOverlay();
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
/**
|
|
3658
|
+
* Enable with source element method
|
|
3659
|
+
*/
|
|
3660
|
+
async _enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
|
|
3661
|
+
await this._swapCaptionTracks(true);
|
|
3662
|
+
this._updateSourceElements(true);
|
|
3663
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
3664
|
+
this.player.element.poster = posterValue;
|
|
3665
|
+
}
|
|
3666
|
+
this.player.element.load();
|
|
3667
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
3668
|
+
await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
|
|
3669
|
+
if (!this.desiredState) return;
|
|
3670
|
+
this.enabled = true;
|
|
3671
|
+
this.player.state.audioDescriptionEnabled = true;
|
|
3672
|
+
this.player.emit("audiodescriptionenabled");
|
|
3673
|
+
this._reloadTranscript();
|
|
3674
|
+
}
|
|
3675
|
+
/**
|
|
3676
|
+
* Enable with direct src method
|
|
3677
|
+
*/
|
|
3678
|
+
async _enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster) {
|
|
3679
|
+
await this._swapCaptionTracks(true);
|
|
3680
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
3681
|
+
this.player.element.poster = posterValue;
|
|
3682
|
+
}
|
|
3683
|
+
this.player.element.src = this.src;
|
|
3684
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
3685
|
+
if (currentTime > 0) {
|
|
3686
|
+
this.player.seek(currentTime);
|
|
3687
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3688
|
+
}
|
|
3689
|
+
if (wasPlaying) {
|
|
3690
|
+
await this.player.play();
|
|
3691
|
+
} else {
|
|
3692
|
+
this.player.pause();
|
|
3693
|
+
if (!shouldKeepPoster) {
|
|
3694
|
+
this.player.hidePosterOverlay();
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
if (!this.desiredState) return;
|
|
3698
|
+
this.enabled = true;
|
|
3699
|
+
this.player.state.audioDescriptionEnabled = true;
|
|
3700
|
+
this.player.emit("audiodescriptionenabled");
|
|
3701
|
+
this._reloadTranscript();
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* Disable with source element method
|
|
3705
|
+
*/
|
|
3706
|
+
async _disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
|
|
3707
|
+
await this._swapCaptionTracks(false);
|
|
3708
|
+
this._updateSourceElements(false);
|
|
3709
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
3710
|
+
this.player.element.poster = posterValue;
|
|
3711
|
+
}
|
|
3712
|
+
this.player.element.load();
|
|
3713
|
+
this.player.invalidateTrackCache();
|
|
3714
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
3715
|
+
await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
|
|
3716
|
+
if (this.player.captionManager) {
|
|
3717
|
+
this.player.captionManager.destroy();
|
|
3718
|
+
this.player.captionManager = new CaptionManager(this.player);
|
|
3719
|
+
}
|
|
3720
|
+
if (this.desiredState) return;
|
|
3721
|
+
this.enabled = false;
|
|
3722
|
+
this.player.state.audioDescriptionEnabled = false;
|
|
3723
|
+
this.player.emit("audiodescriptiondisabled");
|
|
3724
|
+
this._reloadTranscript();
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Disable with direct src method
|
|
3728
|
+
*/
|
|
3729
|
+
async _disableWithDirectSrc(currentTime, wasPlaying, posterValue) {
|
|
3730
|
+
await this._swapCaptionTracks(false);
|
|
3731
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
3732
|
+
this.player.element.poster = posterValue;
|
|
3733
|
+
}
|
|
3734
|
+
const originalSrcToUse = this.originalSource || this.player.originalSrc;
|
|
3735
|
+
this.player.element.src = originalSrcToUse;
|
|
3736
|
+
this.player.element.load();
|
|
3737
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
3738
|
+
if (currentTime > 0) {
|
|
3739
|
+
this.player.seek(currentTime);
|
|
3740
|
+
}
|
|
3741
|
+
if (wasPlaying) {
|
|
3742
|
+
await this.player.play();
|
|
3743
|
+
}
|
|
3744
|
+
if (this.desiredState) return;
|
|
3745
|
+
this.enabled = false;
|
|
3746
|
+
this.player.state.audioDescriptionEnabled = false;
|
|
3747
|
+
this.player.emit("audiodescriptiondisabled");
|
|
3748
|
+
this._reloadTranscript();
|
|
3749
|
+
}
|
|
3750
|
+
/**
|
|
3751
|
+
* Reload transcript after audio description state change
|
|
3752
|
+
*/
|
|
3753
|
+
_reloadTranscript() {
|
|
3754
|
+
if (this.player.transcriptManager && this.player.transcriptManager.isVisible) {
|
|
3755
|
+
this.player.setManagedTimeout(() => {
|
|
3756
|
+
if (this.player.transcriptManager && this.player.transcriptManager.loadTranscriptData) {
|
|
3757
|
+
this.player.transcriptManager.loadTranscriptData();
|
|
3758
|
+
}
|
|
3759
|
+
}, 800);
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
/**
|
|
3763
|
+
* Update sources (called when playlist changes)
|
|
3764
|
+
*/
|
|
3765
|
+
updateSources(audioDescriptionSrc) {
|
|
3766
|
+
this.src = audioDescriptionSrc || null;
|
|
3767
|
+
this.enabled = false;
|
|
3768
|
+
this.desiredState = false;
|
|
3769
|
+
this.sourceElement = null;
|
|
3770
|
+
this.originalSource = null;
|
|
3771
|
+
this.captionTracks = [];
|
|
3772
|
+
}
|
|
3773
|
+
/**
|
|
3774
|
+
* Reinitialize from current player elements (called after playlist loads new tracks)
|
|
3775
|
+
*/
|
|
3776
|
+
reinitialize() {
|
|
3777
|
+
this.player.invalidateTrackCache();
|
|
3778
|
+
this.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
|
|
3779
|
+
}
|
|
3780
|
+
/**
|
|
3781
|
+
* Cleanup
|
|
3782
|
+
*/
|
|
3783
|
+
destroy() {
|
|
3784
|
+
this.enabled = false;
|
|
3785
|
+
this.desiredState = false;
|
|
3786
|
+
this.captionTracks = [];
|
|
3787
|
+
this.sourceElement = null;
|
|
3788
|
+
this.originalSource = null;
|
|
3789
|
+
}
|
|
3790
|
+
};
|
|
3791
|
+
|
|
3792
|
+
// src/core/SignLanguageManager.js
|
|
3793
|
+
var SignLanguageManager = class {
|
|
3794
|
+
constructor(player) {
|
|
3795
|
+
this.player = player;
|
|
3796
|
+
this.src = player.options.signLanguageSrc;
|
|
3797
|
+
this.sources = player.options.signLanguageSources || {};
|
|
3798
|
+
this.currentLanguage = null;
|
|
3799
|
+
this.desiredPosition = player.options.signLanguagePosition || "bottom-right";
|
|
3800
|
+
this.wrapper = null;
|
|
3801
|
+
this.header = null;
|
|
3802
|
+
this.video = null;
|
|
3803
|
+
this.selector = null;
|
|
3804
|
+
this.settingsButton = null;
|
|
3805
|
+
this.settingsMenu = null;
|
|
3806
|
+
this.resizeHandles = [];
|
|
3807
|
+
this.enabled = false;
|
|
3808
|
+
this.settingsMenuVisible = false;
|
|
3809
|
+
this.settingsMenuJustOpened = false;
|
|
3810
|
+
this.documentClickHandlerAdded = false;
|
|
3811
|
+
this.handlers = null;
|
|
3812
|
+
this.settingsHandlers = null;
|
|
3813
|
+
this.interactionHandlers = null;
|
|
3814
|
+
this.draggable = null;
|
|
3815
|
+
this.documentClickHandler = null;
|
|
3816
|
+
this.settingsMenuKeyHandler = null;
|
|
3817
|
+
this.customKeyHandler = null;
|
|
3818
|
+
this.dragOptionButton = null;
|
|
3819
|
+
this.dragOptionText = null;
|
|
3820
|
+
this.resizeOptionButton = null;
|
|
3821
|
+
this.resizeOptionText = null;
|
|
3822
|
+
}
|
|
3823
|
+
/**
|
|
3824
|
+
* Check if sign language is available
|
|
3825
|
+
*/
|
|
3826
|
+
isAvailable() {
|
|
3827
|
+
return Object.keys(this.sources).length > 0 || !!this.src;
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Enable sign language video
|
|
3831
|
+
*/
|
|
3832
|
+
enable() {
|
|
3833
|
+
const hasMultipleSources = Object.keys(this.sources).length > 0;
|
|
3834
|
+
const hasSingleSource = !!this.src;
|
|
3835
|
+
if (!hasMultipleSources && !hasSingleSource) {
|
|
3836
|
+
console.warn("No sign language video source provided");
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
if (this.wrapper) {
|
|
3840
|
+
this.wrapper.style.display = "block";
|
|
3841
|
+
this.enabled = true;
|
|
3842
|
+
this.player.state.signLanguageEnabled = true;
|
|
3843
|
+
this.player.emit("signlanguageenabled");
|
|
3844
|
+
this.player.setManagedTimeout(() => {
|
|
3845
|
+
if (this.settingsButton && document.contains(this.settingsButton)) {
|
|
3846
|
+
this.settingsButton.focus({ preventScroll: true });
|
|
3847
|
+
}
|
|
3848
|
+
}, 150);
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3851
|
+
let initialLang = null;
|
|
3852
|
+
let initialSrc = null;
|
|
3853
|
+
if (hasMultipleSources) {
|
|
3854
|
+
initialLang = this._determineInitialLanguage();
|
|
3855
|
+
initialSrc = this.sources[initialLang];
|
|
3856
|
+
this.currentLanguage = initialLang;
|
|
3857
|
+
} else {
|
|
3858
|
+
initialSrc = this.src;
|
|
3859
|
+
}
|
|
3860
|
+
this._createWrapper();
|
|
3861
|
+
this._createHeader(hasMultipleSources, initialLang);
|
|
3862
|
+
this._createVideo(initialSrc);
|
|
3863
|
+
this._createResizeHandles();
|
|
3864
|
+
this.wrapper.appendChild(this.header);
|
|
3865
|
+
this.wrapper.appendChild(this.video);
|
|
3866
|
+
this.resizeHandles.forEach((handle) => this.wrapper.appendChild(handle));
|
|
3867
|
+
this._applyInitialSize();
|
|
3868
|
+
this.player.container.appendChild(this.wrapper);
|
|
3869
|
+
requestAnimationFrame(() => {
|
|
3870
|
+
this.constrainPosition();
|
|
3871
|
+
});
|
|
3872
|
+
this.video.currentTime = this.player.state.currentTime;
|
|
3873
|
+
if (!this.player.state.paused) {
|
|
3874
|
+
this.video.play();
|
|
3875
|
+
}
|
|
3876
|
+
this._setupInteraction();
|
|
3877
|
+
this._setupEventHandlers(hasMultipleSources);
|
|
3878
|
+
this.enabled = true;
|
|
3879
|
+
this.player.state.signLanguageEnabled = true;
|
|
3880
|
+
this.player.emit("signlanguageenabled");
|
|
3881
|
+
this.player.setManagedTimeout(() => {
|
|
3882
|
+
if (this.settingsButton && document.contains(this.settingsButton)) {
|
|
3883
|
+
this.settingsButton.focus({ preventScroll: true });
|
|
3884
|
+
}
|
|
3885
|
+
}, 150);
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* Disable sign language video
|
|
3889
|
+
*/
|
|
3890
|
+
disable() {
|
|
3891
|
+
if (this.settingsMenuVisible) {
|
|
3892
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
3893
|
+
}
|
|
3894
|
+
if (this.wrapper) {
|
|
3895
|
+
this.wrapper.style.display = "none";
|
|
3896
|
+
}
|
|
3897
|
+
this.enabled = false;
|
|
3898
|
+
this.player.state.signLanguageEnabled = false;
|
|
3899
|
+
this.player.emit("signlanguagedisabled");
|
|
3900
|
+
}
|
|
3901
|
+
/**
|
|
3902
|
+
* Toggle sign language video
|
|
3903
|
+
*/
|
|
3904
|
+
toggle() {
|
|
3905
|
+
if (this.enabled) {
|
|
3906
|
+
this.disable();
|
|
3907
|
+
} else {
|
|
3908
|
+
this.enable();
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
/**
|
|
3912
|
+
* Switch to a different sign language
|
|
3913
|
+
*/
|
|
3914
|
+
switchLanguage(langCode) {
|
|
3915
|
+
if (!this.sources[langCode] || !this.video) {
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
const currentTime = this.video.currentTime;
|
|
3919
|
+
const wasPlaying = !this.video.paused;
|
|
3920
|
+
this.video.src = this.sources[langCode];
|
|
3921
|
+
this.currentLanguage = langCode;
|
|
3922
|
+
this.video.currentTime = currentTime;
|
|
3923
|
+
if (wasPlaying) {
|
|
3924
|
+
this.video.play().catch(() => {
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
this.player.emit("signlanguagelanguagechanged", langCode);
|
|
3928
|
+
}
|
|
3929
|
+
/**
|
|
3930
|
+
* Get language label
|
|
3931
|
+
*/
|
|
3932
|
+
getLanguageLabel(langCode) {
|
|
3933
|
+
const langNames = {
|
|
3934
|
+
"en": "English",
|
|
3935
|
+
"de": "Deutsch",
|
|
3936
|
+
"es": "Español",
|
|
3937
|
+
"fr": "Français",
|
|
3938
|
+
"it": "Italiano",
|
|
3939
|
+
"ja": "日本語",
|
|
3940
|
+
"pt": "Português",
|
|
3941
|
+
"ar": "العربية",
|
|
3942
|
+
"hi": "हिन्दी"
|
|
3943
|
+
};
|
|
3944
|
+
return langNames[langCode] || langCode.toUpperCase();
|
|
3945
|
+
}
|
|
3946
|
+
/**
|
|
3947
|
+
* Determine initial sign language
|
|
3948
|
+
*/
|
|
3949
|
+
_determineInitialLanguage() {
|
|
3950
|
+
if (this.player.captionManager && this.player.captionManager.currentTrack) {
|
|
3951
|
+
const captionLang = this.player.captionManager.currentTrack.language?.toLowerCase().split("-")[0];
|
|
3952
|
+
if (captionLang && this.sources[captionLang]) {
|
|
3953
|
+
return captionLang;
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
if (this.player.options.language) {
|
|
3957
|
+
const playerLang = this.player.options.language.toLowerCase().split("-")[0];
|
|
3958
|
+
if (this.sources[playerLang]) {
|
|
3959
|
+
return playerLang;
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
return Object.keys(this.sources)[0];
|
|
3963
|
+
}
|
|
3964
|
+
/**
|
|
3965
|
+
* Create wrapper element
|
|
3966
|
+
*/
|
|
3967
|
+
_createWrapper() {
|
|
3968
|
+
this.wrapper = document.createElement("div");
|
|
3969
|
+
this.wrapper.className = "vidply-sign-language-wrapper";
|
|
3970
|
+
this.wrapper.setAttribute("tabindex", "0");
|
|
3971
|
+
this.wrapper.setAttribute("aria-label", i18n.t("player.signLanguageDragResize"));
|
|
3972
|
+
}
|
|
3973
|
+
/**
|
|
3974
|
+
* Create header element
|
|
3975
|
+
*/
|
|
3976
|
+
_createHeader(hasMultipleSources, initialLang) {
|
|
3977
|
+
const classPrefix = this.player.options.classPrefix;
|
|
3978
|
+
this.header = DOMUtils.createElement("div", {
|
|
3979
|
+
className: `${classPrefix}-sign-language-header`,
|
|
3980
|
+
attributes: { "tabindex": "0" }
|
|
3981
|
+
});
|
|
3982
|
+
const headerLeft = DOMUtils.createElement("div", {
|
|
3983
|
+
className: `${classPrefix}-sign-language-header-left`
|
|
3984
|
+
});
|
|
3985
|
+
const title = DOMUtils.createElement("h3", {
|
|
3986
|
+
textContent: i18n.t("player.signLanguageVideo")
|
|
3987
|
+
});
|
|
3988
|
+
this._createSettingsButton(headerLeft);
|
|
3989
|
+
if (hasMultipleSources) {
|
|
3990
|
+
this._createLanguageSelector(headerLeft, initialLang);
|
|
3991
|
+
}
|
|
3992
|
+
headerLeft.appendChild(title);
|
|
3993
|
+
const closeButton = this._createCloseButton();
|
|
3994
|
+
this.header.appendChild(headerLeft);
|
|
3995
|
+
this.header.appendChild(closeButton);
|
|
3996
|
+
this.settingsMenuVisible = false;
|
|
3997
|
+
this.settingsMenu = null;
|
|
3998
|
+
this.settingsMenuJustOpened = false;
|
|
3999
|
+
}
|
|
4000
|
+
/**
|
|
4001
|
+
* Create settings button
|
|
4002
|
+
*/
|
|
4003
|
+
_createSettingsButton(container) {
|
|
4004
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4005
|
+
const ariaLabel = i18n.t("player.signLanguageSettings");
|
|
4006
|
+
this.settingsButton = DOMUtils.createElement("button", {
|
|
4007
|
+
className: `${classPrefix}-sign-language-settings`,
|
|
4008
|
+
attributes: {
|
|
4009
|
+
"type": "button",
|
|
4010
|
+
"aria-label": ariaLabel,
|
|
4011
|
+
"aria-expanded": "false"
|
|
4012
|
+
}
|
|
4013
|
+
});
|
|
4014
|
+
this.settingsButton.appendChild(createIconElement("settings"));
|
|
4015
|
+
DOMUtils.attachTooltip(this.settingsButton, ariaLabel, classPrefix);
|
|
4016
|
+
this.settingsHandlers = {
|
|
4017
|
+
click: (e) => {
|
|
4018
|
+
e.preventDefault();
|
|
4019
|
+
e.stopPropagation();
|
|
4020
|
+
if (this.documentClickHandler) {
|
|
4021
|
+
this.settingsMenuJustOpened = true;
|
|
4022
|
+
setTimeout(() => {
|
|
4023
|
+
this.settingsMenuJustOpened = false;
|
|
4024
|
+
}, 100);
|
|
4025
|
+
}
|
|
4026
|
+
if (this.settingsMenuVisible) {
|
|
4027
|
+
this.hideSettingsMenu();
|
|
4028
|
+
} else {
|
|
4029
|
+
this.showSettingsMenu();
|
|
4030
|
+
}
|
|
4031
|
+
},
|
|
4032
|
+
keydown: (e) => {
|
|
4033
|
+
if (e.key === "d" || e.key === "D") {
|
|
4034
|
+
e.preventDefault();
|
|
4035
|
+
e.stopPropagation();
|
|
4036
|
+
this.toggleKeyboardDragMode();
|
|
4037
|
+
} else if (e.key === "r" || e.key === "R") {
|
|
4038
|
+
e.preventDefault();
|
|
4039
|
+
e.stopPropagation();
|
|
4040
|
+
this.toggleResizeMode();
|
|
4041
|
+
} else if (e.key === "Escape" && this.settingsMenuVisible) {
|
|
4042
|
+
e.preventDefault();
|
|
4043
|
+
e.stopPropagation();
|
|
4044
|
+
this.hideSettingsMenu();
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
};
|
|
4048
|
+
this.settingsButton.addEventListener("click", this.settingsHandlers.click);
|
|
4049
|
+
this.settingsButton.addEventListener("keydown", this.settingsHandlers.keydown);
|
|
4050
|
+
container.appendChild(this.settingsButton);
|
|
4051
|
+
}
|
|
4052
|
+
/**
|
|
4053
|
+
* Create language selector
|
|
4054
|
+
*/
|
|
4055
|
+
_createLanguageSelector(container, initialLang) {
|
|
4056
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4057
|
+
const selectId = `${classPrefix}-sign-language-select-${Date.now()}`;
|
|
4058
|
+
const options = Object.keys(this.sources).map((langCode) => ({
|
|
4059
|
+
value: langCode,
|
|
4060
|
+
text: this.getLanguageLabel(langCode),
|
|
4061
|
+
selected: langCode === initialLang
|
|
4062
|
+
}));
|
|
4063
|
+
const { label, select } = createLabeledSelect({
|
|
4064
|
+
classPrefix,
|
|
4065
|
+
labelClass: `${classPrefix}-sign-language-label`,
|
|
4066
|
+
selectClass: `${classPrefix}-sign-language-select`,
|
|
4067
|
+
labelText: "settings.language",
|
|
4068
|
+
selectId,
|
|
4069
|
+
options,
|
|
4070
|
+
onChange: (e) => {
|
|
4071
|
+
e.stopPropagation();
|
|
4072
|
+
this.switchLanguage(e.target.value);
|
|
4073
|
+
}
|
|
4074
|
+
});
|
|
4075
|
+
this.selector = select;
|
|
4076
|
+
const selectorWrapper = DOMUtils.createElement("div", {
|
|
4077
|
+
className: `${classPrefix}-sign-language-selector-wrapper`
|
|
4078
|
+
});
|
|
4079
|
+
selectorWrapper.appendChild(label);
|
|
4080
|
+
selectorWrapper.appendChild(this.selector);
|
|
4081
|
+
preventDragOnElement(selectorWrapper);
|
|
4082
|
+
container.appendChild(selectorWrapper);
|
|
4083
|
+
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Create close button
|
|
4086
|
+
*/
|
|
4087
|
+
_createCloseButton() {
|
|
4088
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4089
|
+
const ariaLabel = i18n.t("player.closeSignLanguage");
|
|
4090
|
+
const closeButton = DOMUtils.createElement("button", {
|
|
4091
|
+
className: `${classPrefix}-sign-language-close`,
|
|
4092
|
+
attributes: {
|
|
4093
|
+
"type": "button",
|
|
4094
|
+
"aria-label": ariaLabel
|
|
4095
|
+
}
|
|
4096
|
+
});
|
|
4097
|
+
closeButton.appendChild(createIconElement("close"));
|
|
4098
|
+
DOMUtils.attachTooltip(closeButton, ariaLabel, classPrefix);
|
|
4099
|
+
closeButton.addEventListener("click", () => {
|
|
4100
|
+
this.disable();
|
|
4101
|
+
if (this.player.controlBar?.controls?.signLanguage) {
|
|
4102
|
+
setTimeout(() => {
|
|
4103
|
+
this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
|
|
4104
|
+
}, 0);
|
|
4105
|
+
}
|
|
4106
|
+
});
|
|
4107
|
+
return closeButton;
|
|
4108
|
+
}
|
|
4109
|
+
/**
|
|
4110
|
+
* Create video element
|
|
4111
|
+
*/
|
|
4112
|
+
_createVideo(src) {
|
|
4113
|
+
this.video = document.createElement("video");
|
|
4114
|
+
this.video.className = "vidply-sign-language-video";
|
|
4115
|
+
this.video.src = src;
|
|
4116
|
+
this.video.setAttribute("aria-label", i18n.t("player.signLanguage"));
|
|
4117
|
+
this.video.muted = true;
|
|
4118
|
+
this.video.setAttribute("playsinline", "");
|
|
4119
|
+
}
|
|
4120
|
+
/**
|
|
4121
|
+
* Create resize handles
|
|
4122
|
+
*/
|
|
4123
|
+
_createResizeHandles() {
|
|
4124
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4125
|
+
this.resizeHandles = ["n", "s", "e", "w", "ne", "nw", "se", "sw"].map((dir) => {
|
|
4126
|
+
const handle = DOMUtils.createElement("div", {
|
|
4127
|
+
className: `${classPrefix}-sign-resize-handle ${classPrefix}-sign-resize-${dir}`,
|
|
4128
|
+
attributes: {
|
|
4129
|
+
"data-direction": dir,
|
|
4130
|
+
"data-vidply-managed-resize": "true",
|
|
4131
|
+
"aria-hidden": "true"
|
|
4132
|
+
}
|
|
4133
|
+
});
|
|
4134
|
+
handle.style.display = "none";
|
|
4135
|
+
return handle;
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
/**
|
|
4139
|
+
* Apply initial size
|
|
4140
|
+
*/
|
|
4141
|
+
_applyInitialSize() {
|
|
4142
|
+
const saved = this.player.storage.getSignLanguagePreferences();
|
|
4143
|
+
if (saved?.size?.width) {
|
|
4144
|
+
this.wrapper.style.width = saved.size.width;
|
|
4145
|
+
} else {
|
|
4146
|
+
this.wrapper.style.width = "280px";
|
|
4147
|
+
}
|
|
4148
|
+
this.wrapper.style.height = "auto";
|
|
4149
|
+
}
|
|
4150
|
+
/**
|
|
4151
|
+
* Setup interaction (drag and resize)
|
|
4152
|
+
*/
|
|
4153
|
+
_setupInteraction() {
|
|
4154
|
+
const isMobile2 = window.innerWidth < 768;
|
|
4155
|
+
const isFullscreen = this.player.state.fullscreen;
|
|
4156
|
+
if (isMobile2 && !isFullscreen) {
|
|
4157
|
+
if (this.draggable) {
|
|
4158
|
+
this.draggable.destroy();
|
|
4159
|
+
this.draggable = null;
|
|
4160
|
+
}
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
if (this.draggable) return;
|
|
4164
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4165
|
+
this.draggable = new DraggableResizable(this.wrapper, {
|
|
4166
|
+
dragHandle: this.header,
|
|
4167
|
+
resizeHandles: this.resizeHandles,
|
|
4168
|
+
constrainToViewport: true,
|
|
4169
|
+
maintainAspectRatio: true,
|
|
4170
|
+
minWidth: 150,
|
|
4171
|
+
minHeight: 100,
|
|
4172
|
+
classPrefix: `${classPrefix}-sign`,
|
|
4173
|
+
keyboardDragKey: "d",
|
|
4174
|
+
keyboardResizeKey: "r",
|
|
4175
|
+
keyboardStep: 10,
|
|
4176
|
+
keyboardStepLarge: 50,
|
|
4177
|
+
pointerResizeIndicatorText: i18n.t("player.signLanguageResizeActive"),
|
|
4178
|
+
onPointerResizeToggle: (enabled) => {
|
|
4179
|
+
this.resizeHandles.forEach((handle) => {
|
|
4180
|
+
handle.style.display = enabled ? "block" : "none";
|
|
4181
|
+
});
|
|
4182
|
+
},
|
|
4183
|
+
onDragStart: (e) => {
|
|
4184
|
+
if (e.target.closest(`.${classPrefix}-sign-language-close`) || e.target.closest(`.${classPrefix}-sign-language-settings`) || e.target.closest(`.${classPrefix}-sign-language-select`) || e.target.closest(`.${classPrefix}-sign-language-label`) || e.target.closest(`.${classPrefix}-sign-language-settings-menu`)) {
|
|
4185
|
+
return false;
|
|
4186
|
+
}
|
|
4187
|
+
return true;
|
|
4188
|
+
}
|
|
4189
|
+
});
|
|
4190
|
+
this._setupCustomKeyHandler();
|
|
4191
|
+
this.interactionHandlers = {
|
|
4192
|
+
draggable: this.draggable,
|
|
4193
|
+
customKeyHandler: this.customKeyHandler
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
/**
|
|
4197
|
+
* Setup custom keyboard handler
|
|
4198
|
+
*/
|
|
4199
|
+
_setupCustomKeyHandler() {
|
|
4200
|
+
this.customKeyHandler = (e) => {
|
|
4201
|
+
const key = e.key.toLowerCase();
|
|
4202
|
+
if (this.settingsMenuVisible) return;
|
|
4203
|
+
if (key === "home") {
|
|
4204
|
+
e.preventDefault();
|
|
4205
|
+
e.stopPropagation();
|
|
4206
|
+
if (this.draggable) {
|
|
4207
|
+
if (this.draggable.pointerResizeMode) {
|
|
4208
|
+
this.draggable.disablePointerResizeMode();
|
|
4209
|
+
}
|
|
4210
|
+
this.draggable.manuallyPositioned = false;
|
|
4211
|
+
this.constrainPosition();
|
|
4212
|
+
}
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
if (key === "r") {
|
|
4216
|
+
e.preventDefault();
|
|
4217
|
+
e.stopPropagation();
|
|
4218
|
+
if (this.toggleResizeMode()) {
|
|
4219
|
+
this.wrapper.focus({ preventScroll: true });
|
|
4220
|
+
}
|
|
4221
|
+
return;
|
|
4222
|
+
}
|
|
4223
|
+
if (key === "escape") {
|
|
4224
|
+
e.preventDefault();
|
|
4225
|
+
e.stopPropagation();
|
|
4226
|
+
if (this.draggable?.pointerResizeMode) {
|
|
4227
|
+
this.draggable.disablePointerResizeMode();
|
|
4228
|
+
return;
|
|
4229
|
+
}
|
|
4230
|
+
if (this.draggable?.keyboardDragMode) {
|
|
4231
|
+
this.draggable.disableKeyboardDragMode();
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
this.disable();
|
|
4235
|
+
if (this.player.controlBar?.controls?.signLanguage) {
|
|
4236
|
+
setTimeout(() => {
|
|
4237
|
+
this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
|
|
4238
|
+
}, 0);
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
};
|
|
4242
|
+
this.wrapper.addEventListener("keydown", this.customKeyHandler);
|
|
4243
|
+
}
|
|
4244
|
+
/**
|
|
4245
|
+
* Setup event handlers
|
|
4246
|
+
*/
|
|
4247
|
+
_setupEventHandlers(hasMultipleSources) {
|
|
4248
|
+
this.handlers = {
|
|
4249
|
+
play: () => {
|
|
4250
|
+
if (this.video) this.video.play();
|
|
4251
|
+
},
|
|
4252
|
+
pause: () => {
|
|
4253
|
+
if (this.video) this.video.pause();
|
|
4254
|
+
},
|
|
4255
|
+
timeupdate: () => {
|
|
4256
|
+
if (this.video && Math.abs(this.video.currentTime - this.player.state.currentTime) > 0.5) {
|
|
4257
|
+
this.video.currentTime = this.player.state.currentTime;
|
|
4258
|
+
}
|
|
4259
|
+
},
|
|
4260
|
+
ratechange: () => {
|
|
4261
|
+
if (this.video) this.video.playbackRate = this.player.state.playbackSpeed;
|
|
4262
|
+
}
|
|
4263
|
+
};
|
|
4264
|
+
this.player.on("play", this.handlers.play);
|
|
4265
|
+
this.player.on("pause", this.handlers.pause);
|
|
4266
|
+
this.player.on("timeupdate", this.handlers.timeupdate);
|
|
4267
|
+
this.player.on("ratechange", this.handlers.ratechange);
|
|
4268
|
+
if (hasMultipleSources) {
|
|
4269
|
+
this.handlers.captionChange = () => {
|
|
4270
|
+
if (this.player.captionManager?.currentTrack && this.selector) {
|
|
4271
|
+
const captionLang = this.player.captionManager.currentTrack.language?.toLowerCase().split("-")[0];
|
|
4272
|
+
if (captionLang && this.sources[captionLang] && this.currentLanguage !== captionLang) {
|
|
4273
|
+
this.switchLanguage(captionLang);
|
|
4274
|
+
this.selector.value = captionLang;
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
};
|
|
4278
|
+
this.player.on("captionsenabled", this.handlers.captionChange);
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
/**
|
|
4282
|
+
* Constrain position within video wrapper
|
|
4283
|
+
*/
|
|
4284
|
+
constrainPosition() {
|
|
4285
|
+
if (!this.wrapper || !this.player.videoWrapper) return;
|
|
4286
|
+
if (this.draggable?.manuallyPositioned) return;
|
|
4287
|
+
if (!this.wrapper.style.width) {
|
|
4288
|
+
this.wrapper.style.width = "280px";
|
|
4289
|
+
}
|
|
4290
|
+
const videoWrapperRect = this.player.videoWrapper.getBoundingClientRect();
|
|
4291
|
+
const containerRect = this.player.container.getBoundingClientRect();
|
|
4292
|
+
const wrapperRect = this.wrapper.getBoundingClientRect();
|
|
4293
|
+
const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
|
|
4294
|
+
const videoWrapperTop = videoWrapperRect.top - containerRect.top;
|
|
4295
|
+
const videoWrapperWidth = videoWrapperRect.width;
|
|
4296
|
+
const videoWrapperHeight = videoWrapperRect.height;
|
|
4297
|
+
let wrapperWidth = wrapperRect.width || 280;
|
|
4298
|
+
let wrapperHeight = wrapperRect.height || 280 * 9 / 16;
|
|
4299
|
+
let left, top;
|
|
4300
|
+
const margin = 16;
|
|
4301
|
+
const controlsHeight = 95;
|
|
4302
|
+
const position = this.desiredPosition || "bottom-right";
|
|
4303
|
+
switch (position) {
|
|
4304
|
+
case "bottom-right":
|
|
4305
|
+
left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
|
|
4306
|
+
top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
|
|
4307
|
+
break;
|
|
4308
|
+
case "bottom-left":
|
|
4309
|
+
left = videoWrapperLeft + margin;
|
|
4310
|
+
top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
|
|
4311
|
+
break;
|
|
4312
|
+
case "top-right":
|
|
4313
|
+
left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
|
|
4314
|
+
top = videoWrapperTop + margin;
|
|
4315
|
+
break;
|
|
4316
|
+
case "top-left":
|
|
4317
|
+
left = videoWrapperLeft + margin;
|
|
4318
|
+
top = videoWrapperTop + margin;
|
|
4319
|
+
break;
|
|
4320
|
+
default:
|
|
4321
|
+
left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
|
|
4322
|
+
top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
|
|
4323
|
+
}
|
|
4324
|
+
left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperWidth - wrapperWidth));
|
|
4325
|
+
top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight));
|
|
4326
|
+
this.wrapper.style.left = `${left}px`;
|
|
4327
|
+
this.wrapper.style.top = `${top}px`;
|
|
4328
|
+
this.wrapper.style.right = "auto";
|
|
4329
|
+
this.wrapper.style.bottom = "auto";
|
|
4330
|
+
}
|
|
4331
|
+
/**
|
|
4332
|
+
* Show settings menu
|
|
4333
|
+
*/
|
|
4334
|
+
showSettingsMenu() {
|
|
4335
|
+
this.settingsMenuJustOpened = true;
|
|
4336
|
+
setTimeout(() => {
|
|
4337
|
+
this.settingsMenuJustOpened = false;
|
|
4338
|
+
}, 350);
|
|
4339
|
+
this._addDocumentClickHandler();
|
|
4340
|
+
if (this.settingsMenu) {
|
|
4341
|
+
this.settingsMenu.style.display = "block";
|
|
4342
|
+
this.settingsMenuVisible = true;
|
|
4343
|
+
this.settingsButton?.setAttribute("aria-expanded", "true");
|
|
4344
|
+
this._attachMenuKeyboardNavigation();
|
|
4345
|
+
this._positionSettingsMenu();
|
|
4346
|
+
this._updateDragOptionState();
|
|
4347
|
+
this._updateResizeOptionState();
|
|
4348
|
+
focusFirstMenuItem(this.settingsMenu, `.${this.player.options.classPrefix}-sign-language-settings-item`);
|
|
4349
|
+
return;
|
|
4350
|
+
}
|
|
4351
|
+
this._createSettingsMenu();
|
|
4352
|
+
}
|
|
4353
|
+
/**
|
|
4354
|
+
* Hide settings menu
|
|
4355
|
+
*/
|
|
4356
|
+
hideSettingsMenu({ focusButton = true } = {}) {
|
|
4357
|
+
if (this.settingsMenu) {
|
|
4358
|
+
this.settingsMenu.style.display = "none";
|
|
4359
|
+
this.settingsMenuVisible = false;
|
|
4360
|
+
this.settingsMenuJustOpened = false;
|
|
4361
|
+
if (this.settingsMenuKeyHandler) {
|
|
4362
|
+
this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
|
|
4363
|
+
this.settingsMenuKeyHandler = null;
|
|
4364
|
+
}
|
|
4365
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4366
|
+
const menuItems = Array.from(this.settingsMenu.querySelectorAll(`.${classPrefix}-sign-language-settings-item`));
|
|
4367
|
+
menuItems.forEach((item) => item.setAttribute("tabindex", "-1"));
|
|
4368
|
+
if (this.settingsButton) {
|
|
4369
|
+
this.settingsButton.setAttribute("aria-expanded", "false");
|
|
4370
|
+
if (focusButton) {
|
|
4371
|
+
this.settingsButton.focus({ preventScroll: true });
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
/**
|
|
4377
|
+
* Add document click handler
|
|
4378
|
+
*/
|
|
4379
|
+
_addDocumentClickHandler() {
|
|
4380
|
+
if (this.documentClickHandlerAdded) return;
|
|
4381
|
+
this.documentClickHandler = (e) => {
|
|
4382
|
+
if (this.settingsMenuJustOpened) return;
|
|
4383
|
+
if (this.settingsButton && (this.settingsButton === e.target || this.settingsButton.contains(e.target))) {
|
|
4384
|
+
return;
|
|
4385
|
+
}
|
|
4386
|
+
if (this.settingsMenu && this.settingsMenu.contains(e.target)) {
|
|
4387
|
+
return;
|
|
4388
|
+
}
|
|
4389
|
+
if (this.settingsMenuVisible) {
|
|
4390
|
+
this.hideSettingsMenu();
|
|
4391
|
+
}
|
|
4392
|
+
};
|
|
4393
|
+
setTimeout(() => {
|
|
4394
|
+
document.addEventListener("mousedown", this.documentClickHandler, true);
|
|
4395
|
+
this.documentClickHandlerAdded = true;
|
|
4396
|
+
}, 300);
|
|
4397
|
+
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Create settings menu
|
|
4400
|
+
*/
|
|
4401
|
+
_createSettingsMenu() {
|
|
4402
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4403
|
+
this.settingsMenu = DOMUtils.createElement("div", {
|
|
4404
|
+
className: `${classPrefix}-sign-language-settings-menu`,
|
|
4405
|
+
attributes: { "role": "menu" }
|
|
4406
|
+
});
|
|
4407
|
+
const dragOption = createMenuItem({
|
|
4408
|
+
classPrefix,
|
|
4409
|
+
itemClass: `${classPrefix}-sign-language-settings-item`,
|
|
4410
|
+
icon: "move",
|
|
4411
|
+
label: "player.enableSignDragMode",
|
|
4412
|
+
hasTextClass: true,
|
|
4413
|
+
onClick: () => {
|
|
4414
|
+
this.toggleKeyboardDragMode();
|
|
4415
|
+
this.hideSettingsMenu();
|
|
4416
|
+
}
|
|
4417
|
+
});
|
|
4418
|
+
dragOption.setAttribute("role", "switch");
|
|
4419
|
+
dragOption.setAttribute("aria-checked", "false");
|
|
4420
|
+
this._removeTooltipFromMenuItem(dragOption);
|
|
4421
|
+
this.dragOptionButton = dragOption;
|
|
4422
|
+
this.dragOptionText = dragOption.querySelector(`.${classPrefix}-settings-text`);
|
|
4423
|
+
this._updateDragOptionState();
|
|
4424
|
+
const resizeOption = createMenuItem({
|
|
4425
|
+
classPrefix,
|
|
4426
|
+
itemClass: `${classPrefix}-sign-language-settings-item`,
|
|
4427
|
+
icon: "resize",
|
|
4428
|
+
label: "player.enableSignResizeMode",
|
|
4429
|
+
hasTextClass: true,
|
|
4430
|
+
onClick: (event) => {
|
|
4431
|
+
event.preventDefault();
|
|
4432
|
+
event.stopPropagation();
|
|
4433
|
+
const enabled = this.toggleResizeMode({ focus: false });
|
|
4434
|
+
if (enabled) {
|
|
4435
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
4436
|
+
setTimeout(() => {
|
|
4437
|
+
if (this.wrapper) this.wrapper.focus({ preventScroll: true });
|
|
4438
|
+
}, 20);
|
|
4439
|
+
} else {
|
|
4440
|
+
this.hideSettingsMenu({ focusButton: true });
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
});
|
|
4444
|
+
resizeOption.setAttribute("role", "switch");
|
|
4445
|
+
resizeOption.setAttribute("aria-checked", "false");
|
|
4446
|
+
this._removeTooltipFromMenuItem(resizeOption);
|
|
4447
|
+
this.resizeOptionButton = resizeOption;
|
|
4448
|
+
this.resizeOptionText = resizeOption.querySelector(`.${classPrefix}-settings-text`);
|
|
4449
|
+
this._updateResizeOptionState();
|
|
4450
|
+
const closeOption = createMenuItem({
|
|
4451
|
+
classPrefix,
|
|
4452
|
+
itemClass: `${classPrefix}-sign-language-settings-item`,
|
|
4453
|
+
icon: "close",
|
|
4454
|
+
label: "transcript.closeMenu",
|
|
4455
|
+
onClick: () => this.hideSettingsMenu()
|
|
4456
|
+
});
|
|
4457
|
+
this._removeTooltipFromMenuItem(closeOption);
|
|
4458
|
+
this.settingsMenu.appendChild(dragOption);
|
|
4459
|
+
this.settingsMenu.appendChild(resizeOption);
|
|
4460
|
+
this.settingsMenu.appendChild(closeOption);
|
|
4461
|
+
this.settingsMenu.style.visibility = "hidden";
|
|
4462
|
+
this.settingsMenu.style.display = "block";
|
|
4463
|
+
if (this.settingsButton?.parentNode) {
|
|
4464
|
+
this.settingsButton.insertAdjacentElement("afterend", this.settingsMenu);
|
|
4465
|
+
} else if (this.wrapper) {
|
|
4466
|
+
this.wrapper.appendChild(this.settingsMenu);
|
|
4467
|
+
}
|
|
4468
|
+
this._positionSettingsMenuImmediate();
|
|
4469
|
+
requestAnimationFrame(() => {
|
|
4470
|
+
if (this.settingsMenu) {
|
|
4471
|
+
this.settingsMenu.style.visibility = "visible";
|
|
4472
|
+
}
|
|
4473
|
+
});
|
|
4474
|
+
this._attachMenuKeyboardNavigation();
|
|
4475
|
+
this.settingsMenuVisible = true;
|
|
4476
|
+
this.settingsButton?.setAttribute("aria-expanded", "true");
|
|
4477
|
+
this._updateDragOptionState();
|
|
4478
|
+
this._updateResizeOptionState();
|
|
4479
|
+
focusFirstMenuItem(this.settingsMenu, `.${classPrefix}-sign-language-settings-item`);
|
|
4480
|
+
}
|
|
4481
|
+
/**
|
|
4482
|
+
* Remove tooltip from menu item
|
|
4483
|
+
*/
|
|
4484
|
+
_removeTooltipFromMenuItem(item) {
|
|
4485
|
+
const classPrefix = this.player.options.classPrefix;
|
|
4486
|
+
const tooltip = item.querySelector(`.${classPrefix}-tooltip`);
|
|
4487
|
+
if (tooltip) tooltip.remove();
|
|
4488
|
+
const buttonText = item.querySelector(`.${classPrefix}-button-text`);
|
|
4489
|
+
if (buttonText) buttonText.remove();
|
|
4490
|
+
}
|
|
4491
|
+
/**
|
|
4492
|
+
* Attach menu keyboard navigation
|
|
4493
|
+
*/
|
|
4494
|
+
_attachMenuKeyboardNavigation() {
|
|
4495
|
+
if (this.settingsMenuKeyHandler) {
|
|
4496
|
+
this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
|
|
4497
|
+
}
|
|
4498
|
+
this.settingsMenuKeyHandler = attachMenuKeyboardNavigation(
|
|
4499
|
+
this.settingsMenu,
|
|
4500
|
+
this.settingsButton,
|
|
4501
|
+
`.${this.player.options.classPrefix}-sign-language-settings-item`,
|
|
4502
|
+
() => this.hideSettingsMenu({ focusButton: true })
|
|
4503
|
+
);
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Position settings menu immediately
|
|
4507
|
+
*/
|
|
4508
|
+
_positionSettingsMenuImmediate() {
|
|
4509
|
+
if (!this.settingsMenu || !this.settingsButton) return;
|
|
4510
|
+
const buttonRect = this.settingsButton.getBoundingClientRect();
|
|
4511
|
+
const menuRect = this.settingsMenu.getBoundingClientRect();
|
|
4512
|
+
const viewportWidth = window.innerWidth;
|
|
4513
|
+
const viewportHeight = window.innerHeight;
|
|
4514
|
+
const parentContainer = this.settingsButton.parentElement;
|
|
4515
|
+
if (!parentContainer) return;
|
|
4516
|
+
const parentRect = parentContainer.getBoundingClientRect();
|
|
4517
|
+
const buttonCenterX = buttonRect.left + buttonRect.width / 2 - parentRect.left;
|
|
4518
|
+
const buttonBottom = buttonRect.bottom - parentRect.top;
|
|
4519
|
+
const buttonTop = buttonRect.top - parentRect.top;
|
|
4520
|
+
const spaceAbove = buttonRect.top;
|
|
4521
|
+
const spaceBelow = viewportHeight - buttonRect.bottom;
|
|
4522
|
+
let menuTop = buttonBottom + 8;
|
|
4523
|
+
let menuBottom = null;
|
|
4524
|
+
if (spaceBelow < menuRect.height + 20 && spaceAbove > spaceBelow) {
|
|
4525
|
+
menuTop = null;
|
|
4526
|
+
const parentHeight = parentRect.bottom - parentRect.top;
|
|
4527
|
+
menuBottom = parentHeight - buttonTop + 8;
|
|
4528
|
+
this.settingsMenu.classList.add("vidply-menu-above");
|
|
4529
|
+
} else {
|
|
4530
|
+
this.settingsMenu.classList.remove("vidply-menu-above");
|
|
4531
|
+
}
|
|
4532
|
+
let menuLeft = buttonCenterX - menuRect.width / 2;
|
|
4533
|
+
let menuRight = "auto";
|
|
4534
|
+
let transformX = "translateX(0)";
|
|
4535
|
+
const menuLeftAbsolute = buttonRect.left + buttonRect.width / 2 - menuRect.width / 2;
|
|
4536
|
+
if (menuLeftAbsolute < 10) {
|
|
4537
|
+
menuLeft = 0;
|
|
4538
|
+
} else if (menuLeftAbsolute + menuRect.width > viewportWidth - 10) {
|
|
4539
|
+
menuLeft = "auto";
|
|
4540
|
+
menuRight = 0;
|
|
4541
|
+
} else {
|
|
4542
|
+
menuLeft = buttonCenterX;
|
|
4543
|
+
transformX = "translateX(-50%)";
|
|
4544
|
+
}
|
|
4545
|
+
if (menuTop !== null) {
|
|
4546
|
+
this.settingsMenu.style.top = `${menuTop}px`;
|
|
4547
|
+
this.settingsMenu.style.bottom = "auto";
|
|
4548
|
+
} else if (menuBottom !== null) {
|
|
4549
|
+
this.settingsMenu.style.top = "auto";
|
|
4550
|
+
this.settingsMenu.style.bottom = `${menuBottom}px`;
|
|
4551
|
+
}
|
|
4552
|
+
if (menuLeft !== "auto") {
|
|
4553
|
+
this.settingsMenu.style.left = `${menuLeft}px`;
|
|
4554
|
+
this.settingsMenu.style.right = "auto";
|
|
4555
|
+
} else {
|
|
4556
|
+
this.settingsMenu.style.left = "auto";
|
|
4557
|
+
this.settingsMenu.style.right = `${menuRight}px`;
|
|
4558
|
+
}
|
|
4559
|
+
this.settingsMenu.style.transform = transformX;
|
|
4560
|
+
}
|
|
4561
|
+
/**
|
|
4562
|
+
* Position settings menu with RAF
|
|
4563
|
+
*/
|
|
4564
|
+
_positionSettingsMenu() {
|
|
4565
|
+
requestAnimationFrame(() => {
|
|
4566
|
+
setTimeout(() => {
|
|
4567
|
+
this._positionSettingsMenuImmediate();
|
|
4568
|
+
}, 10);
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
4572
|
+
* Toggle keyboard drag mode
|
|
4573
|
+
*/
|
|
4574
|
+
toggleKeyboardDragMode() {
|
|
4575
|
+
if (this.draggable) {
|
|
4576
|
+
const wasEnabled = this.draggable.keyboardDragMode;
|
|
4577
|
+
this.draggable.toggleKeyboardDragMode();
|
|
4578
|
+
const isEnabled = this.draggable.keyboardDragMode;
|
|
4579
|
+
if (!wasEnabled && isEnabled) {
|
|
4580
|
+
this._enableMoveMode();
|
|
4581
|
+
}
|
|
4582
|
+
this._updateDragOptionState();
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
/**
|
|
4586
|
+
* Enable move mode visual feedback
|
|
4587
|
+
*/
|
|
4588
|
+
_enableMoveMode() {
|
|
4589
|
+
this.wrapper.classList.add(`${this.player.options.classPrefix}-sign-move-mode`);
|
|
4590
|
+
this._updateResizeOptionState();
|
|
4591
|
+
setTimeout(() => {
|
|
4592
|
+
this.wrapper.classList.remove(`${this.player.options.classPrefix}-sign-move-mode`);
|
|
4593
|
+
}, 2e3);
|
|
4594
|
+
}
|
|
4595
|
+
/**
|
|
4596
|
+
* Toggle resize mode
|
|
4597
|
+
*/
|
|
4598
|
+
toggleResizeMode({ focus = true } = {}) {
|
|
4599
|
+
if (!this.draggable) return false;
|
|
4600
|
+
if (this.draggable.pointerResizeMode) {
|
|
4601
|
+
this.draggable.disablePointerResizeMode({ focus });
|
|
4602
|
+
this._updateResizeOptionState();
|
|
4603
|
+
return false;
|
|
4604
|
+
}
|
|
4605
|
+
this.draggable.enablePointerResizeMode({ focus });
|
|
4606
|
+
this._updateResizeOptionState();
|
|
4607
|
+
return true;
|
|
4608
|
+
}
|
|
4609
|
+
/**
|
|
4610
|
+
* Update drag option state
|
|
4611
|
+
*/
|
|
4612
|
+
_updateDragOptionState() {
|
|
4613
|
+
if (!this.dragOptionButton) return;
|
|
4614
|
+
const isEnabled = !!this.draggable?.keyboardDragMode;
|
|
4615
|
+
const text = isEnabled ? i18n.t("player.disableSignDragMode") : i18n.t("player.enableSignDragMode");
|
|
4616
|
+
const ariaLabel = isEnabled ? i18n.t("player.disableSignDragModeAria") : i18n.t("player.enableSignDragModeAria");
|
|
4617
|
+
this.dragOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
|
|
4618
|
+
this.dragOptionButton.setAttribute("aria-label", ariaLabel);
|
|
4619
|
+
if (this.dragOptionText) {
|
|
4620
|
+
this.dragOptionText.textContent = text;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
/**
|
|
4624
|
+
* Update resize option state
|
|
4625
|
+
*/
|
|
4626
|
+
_updateResizeOptionState() {
|
|
4627
|
+
if (!this.resizeOptionButton) return;
|
|
4628
|
+
const isEnabled = !!this.draggable?.pointerResizeMode;
|
|
4629
|
+
const text = isEnabled ? i18n.t("player.disableSignResizeMode") : i18n.t("player.enableSignResizeMode");
|
|
4630
|
+
const ariaLabel = isEnabled ? i18n.t("player.disableSignResizeModeAria") : i18n.t("player.enableSignResizeModeAria");
|
|
4631
|
+
this.resizeOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
|
|
4632
|
+
this.resizeOptionButton.setAttribute("aria-label", ariaLabel);
|
|
4633
|
+
if (this.resizeOptionText) {
|
|
4634
|
+
this.resizeOptionText.textContent = text;
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
/**
|
|
4638
|
+
* Save preferences
|
|
4639
|
+
*/
|
|
4640
|
+
savePreferences() {
|
|
4641
|
+
if (!this.wrapper) return;
|
|
4642
|
+
this.player.storage.saveSignLanguagePreferences({
|
|
4643
|
+
size: { width: this.wrapper.style.width }
|
|
4644
|
+
});
|
|
4645
|
+
}
|
|
4646
|
+
/**
|
|
4647
|
+
* Update sources (called when playlist changes)
|
|
4648
|
+
*/
|
|
4649
|
+
updateSources(signLanguageSrc, signLanguageSources) {
|
|
4650
|
+
this.src = signLanguageSrc || null;
|
|
4651
|
+
this.sources = signLanguageSources || {};
|
|
4652
|
+
this.currentLanguage = null;
|
|
4653
|
+
}
|
|
4654
|
+
/**
|
|
4655
|
+
* Cleanup
|
|
4656
|
+
*/
|
|
4657
|
+
cleanup() {
|
|
4658
|
+
if (this.settingsMenuVisible) {
|
|
4659
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
4660
|
+
}
|
|
4661
|
+
if (this.documentClickHandler && this.documentClickHandlerAdded) {
|
|
4662
|
+
document.removeEventListener("mousedown", this.documentClickHandler, true);
|
|
4663
|
+
this.documentClickHandlerAdded = false;
|
|
4664
|
+
this.documentClickHandler = null;
|
|
4665
|
+
}
|
|
4666
|
+
if (this.settingsHandlers && this.settingsButton) {
|
|
4667
|
+
this.settingsButton.removeEventListener("click", this.settingsHandlers.click);
|
|
4668
|
+
this.settingsButton.removeEventListener("keydown", this.settingsHandlers.keydown);
|
|
4669
|
+
}
|
|
4670
|
+
this.settingsHandlers = null;
|
|
4671
|
+
if (this.handlers) {
|
|
4672
|
+
this.player.off("play", this.handlers.play);
|
|
4673
|
+
this.player.off("pause", this.handlers.pause);
|
|
4674
|
+
this.player.off("timeupdate", this.handlers.timeupdate);
|
|
4675
|
+
this.player.off("ratechange", this.handlers.ratechange);
|
|
4676
|
+
if (this.handlers.captionChange) {
|
|
4677
|
+
this.player.off("captionsenabled", this.handlers.captionChange);
|
|
4678
|
+
}
|
|
4679
|
+
this.handlers = null;
|
|
4680
|
+
}
|
|
4681
|
+
if (this.wrapper && this.customKeyHandler) {
|
|
4682
|
+
this.wrapper.removeEventListener("keydown", this.customKeyHandler);
|
|
4683
|
+
}
|
|
4684
|
+
if (this.draggable) {
|
|
4685
|
+
if (this.draggable.pointerResizeMode) {
|
|
4686
|
+
this.draggable.disablePointerResizeMode();
|
|
4687
|
+
}
|
|
4688
|
+
this.draggable.destroy();
|
|
4689
|
+
this.draggable = null;
|
|
4690
|
+
}
|
|
4691
|
+
this.interactionHandlers = null;
|
|
4692
|
+
if (this.wrapper?.parentNode) {
|
|
4693
|
+
if (this.video) {
|
|
4694
|
+
this.video.pause();
|
|
4695
|
+
this.video.src = "";
|
|
4696
|
+
}
|
|
4697
|
+
this.wrapper.parentNode.removeChild(this.wrapper);
|
|
4698
|
+
}
|
|
4699
|
+
this.wrapper = null;
|
|
4700
|
+
this.video = null;
|
|
4701
|
+
this.settingsButton = null;
|
|
4702
|
+
this.settingsMenu = null;
|
|
4703
|
+
}
|
|
4704
|
+
/**
|
|
4705
|
+
* Destroy
|
|
4706
|
+
*/
|
|
4707
|
+
destroy() {
|
|
4708
|
+
this.cleanup();
|
|
4709
|
+
this.enabled = false;
|
|
4710
|
+
}
|
|
4711
|
+
};
|
|
4712
|
+
|
|
4713
|
+
// src/core/Player.js
|
|
4714
|
+
var playerInstanceCounter = 0;
|
|
4715
|
+
var Player = class _Player extends EventEmitter {
|
|
4716
|
+
constructor(element, options = {}) {
|
|
4717
|
+
super();
|
|
4718
|
+
this.element = typeof element === "string" ? document.querySelector(element) : element;
|
|
4719
|
+
if (!this.element) {
|
|
4720
|
+
throw new Error("VidPly: Element not found");
|
|
4721
|
+
}
|
|
4722
|
+
playerInstanceCounter++;
|
|
4723
|
+
this.instanceId = playerInstanceCounter;
|
|
4724
|
+
if (this.element.tagName !== "VIDEO" && this.element.tagName !== "AUDIO") {
|
|
4725
|
+
const mediaType = options.mediaType || "video";
|
|
4726
|
+
const mediaElement = document.createElement(mediaType);
|
|
4727
|
+
Array.from(this.element.attributes).forEach((attr) => {
|
|
4728
|
+
if (attr.name !== "id" && attr.name !== "class" && !attr.name.startsWith("data-")) {
|
|
4729
|
+
mediaElement.setAttribute(attr.name, attr.value);
|
|
4730
|
+
}
|
|
4731
|
+
});
|
|
4732
|
+
const tracks = this.element.querySelectorAll("track");
|
|
4733
|
+
tracks.forEach((track) => {
|
|
4734
|
+
mediaElement.appendChild(track.cloneNode(true));
|
|
4735
|
+
});
|
|
4736
|
+
this.element.innerHTML = "";
|
|
4737
|
+
this.element.appendChild(mediaElement);
|
|
4738
|
+
this.element = mediaElement;
|
|
4739
|
+
}
|
|
4740
|
+
this._originalElement = this.element;
|
|
4741
|
+
this.options = {
|
|
4742
|
+
// Display
|
|
4743
|
+
width: null,
|
|
4744
|
+
height: null,
|
|
4745
|
+
poster: null,
|
|
4746
|
+
responsive: true,
|
|
3096
4747
|
fillContainer: false,
|
|
3097
4748
|
// Playback
|
|
3098
4749
|
autoplay: false,
|
|
@@ -3246,6 +4897,58 @@ var Player = class _Player extends EventEmitter {
|
|
|
3246
4897
|
this.settingsDialog = null;
|
|
3247
4898
|
this.metadataCueChangeHandler = null;
|
|
3248
4899
|
this.metadataAlertHandlers = /* @__PURE__ */ new Map();
|
|
4900
|
+
this.audioDescriptionManager = new AudioDescriptionManager(this);
|
|
4901
|
+
this.signLanguageManager = new SignLanguageManager(this);
|
|
4902
|
+
Object.defineProperties(this, {
|
|
4903
|
+
signLanguageWrapper: {
|
|
4904
|
+
get: () => this.signLanguageManager.wrapper,
|
|
4905
|
+
set: (v) => {
|
|
4906
|
+
this.signLanguageManager.wrapper = v;
|
|
4907
|
+
}
|
|
4908
|
+
},
|
|
4909
|
+
signLanguageVideo: {
|
|
4910
|
+
get: () => this.signLanguageManager.video,
|
|
4911
|
+
set: (v) => {
|
|
4912
|
+
this.signLanguageManager.video = v;
|
|
4913
|
+
}
|
|
4914
|
+
},
|
|
4915
|
+
signLanguageHeader: {
|
|
4916
|
+
get: () => this.signLanguageManager.header,
|
|
4917
|
+
set: (v) => {
|
|
4918
|
+
this.signLanguageManager.header = v;
|
|
4919
|
+
}
|
|
4920
|
+
},
|
|
4921
|
+
signLanguageSettingsButton: {
|
|
4922
|
+
get: () => this.signLanguageManager.settingsButton,
|
|
4923
|
+
set: (v) => {
|
|
4924
|
+
this.signLanguageManager.settingsButton = v;
|
|
4925
|
+
}
|
|
4926
|
+
},
|
|
4927
|
+
signLanguageSettingsMenu: {
|
|
4928
|
+
get: () => this.signLanguageManager.settingsMenu,
|
|
4929
|
+
set: (v) => {
|
|
4930
|
+
this.signLanguageManager.settingsMenu = v;
|
|
4931
|
+
}
|
|
4932
|
+
},
|
|
4933
|
+
signLanguageSettingsMenuVisible: {
|
|
4934
|
+
get: () => this.signLanguageManager.settingsMenuVisible,
|
|
4935
|
+
set: (v) => {
|
|
4936
|
+
this.signLanguageManager.settingsMenuVisible = v;
|
|
4937
|
+
}
|
|
4938
|
+
},
|
|
4939
|
+
signLanguageDraggable: {
|
|
4940
|
+
get: () => this.signLanguageManager.draggable,
|
|
4941
|
+
set: (v) => {
|
|
4942
|
+
this.signLanguageManager.draggable = v;
|
|
4943
|
+
}
|
|
4944
|
+
},
|
|
4945
|
+
currentSignLanguage: {
|
|
4946
|
+
get: () => this.signLanguageManager.currentLanguage,
|
|
4947
|
+
set: (v) => {
|
|
4948
|
+
this.signLanguageManager.currentLanguage = v;
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
});
|
|
3249
4952
|
this.init();
|
|
3250
4953
|
}
|
|
3251
4954
|
async init() {
|
|
@@ -3339,7 +5042,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
3339
5042
|
if (!this.options.transcript && !this.options.transcriptButton) {
|
|
3340
5043
|
return null;
|
|
3341
5044
|
}
|
|
3342
|
-
const module = await import("./vidply.TranscriptManager-
|
|
5045
|
+
const module = await import("./vidply.TranscriptManager-T677KF4N.js");
|
|
3343
5046
|
const Manager = module.TranscriptManager || module.default;
|
|
3344
5047
|
if (!Manager) {
|
|
3345
5048
|
return null;
|
|
@@ -3506,53 +5209,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
3506
5209
|
}
|
|
3507
5210
|
this.currentSource = src;
|
|
3508
5211
|
this._pendingSource = null;
|
|
3509
|
-
|
|
3510
|
-
for (const sourceEl of sourceElements) {
|
|
3511
|
-
const descSrc = sourceEl.getAttribute("data-desc-src");
|
|
3512
|
-
const origSrc = sourceEl.getAttribute("data-orig-src");
|
|
3513
|
-
if (descSrc || origSrc) {
|
|
3514
|
-
if (!this.audioDescriptionSourceElement) {
|
|
3515
|
-
this.audioDescriptionSourceElement = sourceEl;
|
|
3516
|
-
}
|
|
3517
|
-
if (origSrc) {
|
|
3518
|
-
if (!this.originalAudioDescriptionSource) {
|
|
3519
|
-
this.originalAudioDescriptionSource = origSrc;
|
|
3520
|
-
}
|
|
3521
|
-
if (!this.originalSrc) {
|
|
3522
|
-
this.originalSrc = origSrc;
|
|
3523
|
-
}
|
|
3524
|
-
} else {
|
|
3525
|
-
const currentSrcAttr = sourceEl.getAttribute("src");
|
|
3526
|
-
if (!this.originalAudioDescriptionSource && currentSrcAttr) {
|
|
3527
|
-
this.originalAudioDescriptionSource = currentSrcAttr;
|
|
3528
|
-
}
|
|
3529
|
-
if (!this.originalSrc && currentSrcAttr) {
|
|
3530
|
-
this.originalSrc = currentSrcAttr;
|
|
3531
|
-
}
|
|
3532
|
-
}
|
|
3533
|
-
if (descSrc && !this.audioDescriptionSrc) {
|
|
3534
|
-
this.audioDescriptionSrc = descSrc;
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
}
|
|
3538
|
-
const trackElements = this.trackElements;
|
|
3539
|
-
trackElements.forEach((trackEl) => {
|
|
3540
|
-
const trackKind = trackEl.getAttribute("kind");
|
|
3541
|
-
const trackDescSrc = trackEl.getAttribute("data-desc-src");
|
|
3542
|
-
if (trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters") {
|
|
3543
|
-
if (trackDescSrc) {
|
|
3544
|
-
this.audioDescriptionCaptionTracks.push({
|
|
3545
|
-
trackElement: trackEl,
|
|
3546
|
-
originalSrc: trackEl.getAttribute("src"),
|
|
3547
|
-
describedSrc: trackDescSrc,
|
|
3548
|
-
originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
|
|
3549
|
-
explicit: true
|
|
3550
|
-
// Explicitly defined, so we should validate it
|
|
3551
|
-
});
|
|
3552
|
-
this.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute("src")} -> ${trackDescSrc}`);
|
|
3553
|
-
}
|
|
3554
|
-
}
|
|
3555
|
-
});
|
|
5212
|
+
this.audioDescriptionManager.initFromSourceElements(this.sourceElements, this.trackElements);
|
|
3556
5213
|
if (!this.originalSrc) {
|
|
3557
5214
|
this.originalSrc = src;
|
|
3558
5215
|
}
|
|
@@ -3564,7 +5221,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
3564
5221
|
const module = await import("./vidply.VimeoRenderer-VPH4RNES.js");
|
|
3565
5222
|
rendererClass = module.VimeoRenderer || module.default;
|
|
3566
5223
|
} else if (src.includes(".m3u8")) {
|
|
3567
|
-
const module = await import("./vidply.HLSRenderer-
|
|
5224
|
+
const module = await import("./vidply.HLSRenderer-UMPUDSYL.js");
|
|
3568
5225
|
rendererClass = module.HLSRenderer || module.default;
|
|
3569
5226
|
} else if (src.includes("soundcloud.com") || src.includes("api.soundcloud.com")) {
|
|
3570
5227
|
const module = await import("./vidply.SoundCloudRenderer-CD7VJKNS.js");
|
|
@@ -3669,6 +5326,64 @@ var Player = class _Player extends EventEmitter {
|
|
|
3669
5326
|
return posterPath;
|
|
3670
5327
|
}
|
|
3671
5328
|
}
|
|
5329
|
+
/**
|
|
5330
|
+
* Generate a poster image from video frame at specified time
|
|
5331
|
+
* @param {number} time - Time in seconds (default: 10)
|
|
5332
|
+
* @returns {Promise<string|null>} Data URL of the poster image or null if failed
|
|
5333
|
+
*/
|
|
5334
|
+
async generatePosterFromVideo(time = 10) {
|
|
5335
|
+
if (this.element.tagName !== "VIDEO") {
|
|
5336
|
+
return null;
|
|
5337
|
+
}
|
|
5338
|
+
const renderer = this.renderer;
|
|
5339
|
+
if (!renderer || !renderer.media || renderer.media.tagName !== "VIDEO") {
|
|
5340
|
+
return null;
|
|
5341
|
+
}
|
|
5342
|
+
const video = renderer.media;
|
|
5343
|
+
if (!video.duration || video.duration < time) {
|
|
5344
|
+
time = Math.min(time, Math.max(1, video.duration * 0.1));
|
|
5345
|
+
}
|
|
5346
|
+
let videoToUse = video;
|
|
5347
|
+
if (this.controlBar && this.controlBar.previewVideo && this.controlBar.previewSupported) {
|
|
5348
|
+
videoToUse = this.controlBar.previewVideo;
|
|
5349
|
+
}
|
|
5350
|
+
const restoreState = videoToUse === video;
|
|
5351
|
+
return await captureVideoFrame(videoToUse, time, {
|
|
5352
|
+
restoreState,
|
|
5353
|
+
quality: 0.9
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
/**
|
|
5357
|
+
* Auto-generate poster from video if none is provided
|
|
5358
|
+
*/
|
|
5359
|
+
async autoGeneratePoster() {
|
|
5360
|
+
const hasPoster = this.element.getAttribute("poster") || this.element.poster || this.options.poster;
|
|
5361
|
+
if (hasPoster) {
|
|
5362
|
+
return;
|
|
5363
|
+
}
|
|
5364
|
+
if (this.element.tagName !== "VIDEO") {
|
|
5365
|
+
return;
|
|
5366
|
+
}
|
|
5367
|
+
if (!this.state.duration || this.state.duration === 0) {
|
|
5368
|
+
await new Promise((resolve) => {
|
|
5369
|
+
const onLoadedMetadata = () => {
|
|
5370
|
+
this.element.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
5371
|
+
resolve();
|
|
5372
|
+
};
|
|
5373
|
+
if (this.element.readyState >= 1) {
|
|
5374
|
+
resolve();
|
|
5375
|
+
} else {
|
|
5376
|
+
this.element.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
5377
|
+
}
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
const posterDataURL = await this.generatePosterFromVideo(10);
|
|
5381
|
+
if (posterDataURL) {
|
|
5382
|
+
this.element.poster = posterDataURL;
|
|
5383
|
+
this.log("Auto-generated poster from video frame at 10 seconds", "info");
|
|
5384
|
+
this.showPosterOverlay();
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
3672
5387
|
showPosterOverlay() {
|
|
3673
5388
|
if (!this.videoWrapper || this.element.tagName !== "VIDEO") {
|
|
3674
5389
|
return;
|
|
@@ -3677,7 +5392,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
3677
5392
|
if (!poster) {
|
|
3678
5393
|
return;
|
|
3679
5394
|
}
|
|
3680
|
-
const resolvedPoster = this.resolvePosterPath(poster);
|
|
5395
|
+
const resolvedPoster = poster.startsWith("data:") ? poster : this.resolvePosterPath(poster);
|
|
3681
5396
|
this.videoWrapper.style.setProperty("--vidply-poster-image", `url("${resolvedPoster}")`);
|
|
3682
5397
|
this.videoWrapper.classList.add("vidply-forced-poster");
|
|
3683
5398
|
if (this._isAudioContent && this.container) {
|
|
@@ -3796,6 +5511,9 @@ var Player = class _Player extends EventEmitter {
|
|
|
3796
5511
|
if (trackConfig.default) {
|
|
3797
5512
|
track.default = true;
|
|
3798
5513
|
}
|
|
5514
|
+
if (trackConfig.describedSrc) {
|
|
5515
|
+
track.setAttribute("data-desc-src", trackConfig.describedSrc);
|
|
5516
|
+
}
|
|
3799
5517
|
const firstChild = this.element.firstChild;
|
|
3800
5518
|
if (firstChild && firstChild.nodeType === Node.ELEMENT_NODE && firstChild.tagName !== "TRACK") {
|
|
3801
5519
|
this.element.insertBefore(track, firstChild);
|
|
@@ -3810,6 +5528,13 @@ var Player = class _Player extends EventEmitter {
|
|
|
3810
5528
|
this.audioDescriptionSrc = config.audioDescriptionSrc || null;
|
|
3811
5529
|
this.signLanguageSrc = config.signLanguageSrc || null;
|
|
3812
5530
|
this.originalSrc = config.src;
|
|
5531
|
+
if (this.audioDescriptionManager) {
|
|
5532
|
+
this.audioDescriptionManager.updateSources(config.audioDescriptionSrc);
|
|
5533
|
+
this.audioDescriptionManager.reinitialize();
|
|
5534
|
+
}
|
|
5535
|
+
if (this.signLanguageManager) {
|
|
5536
|
+
this.signLanguageManager.updateSources(config.signLanguageSrc, config.signLanguageSources);
|
|
5537
|
+
}
|
|
3813
5538
|
if (wasAudioDescriptionEnabled) {
|
|
3814
5539
|
this.disableAudioDescription();
|
|
3815
5540
|
}
|
|
@@ -4217,8 +5942,12 @@ var Player = class _Player extends EventEmitter {
|
|
|
4217
5942
|
}
|
|
4218
5943
|
return null;
|
|
4219
5944
|
}
|
|
4220
|
-
// Audio Description
|
|
5945
|
+
// Audio Description (delegated to AudioDescriptionManager)
|
|
4221
5946
|
async enableAudioDescription() {
|
|
5947
|
+
return this.audioDescriptionManager.enable();
|
|
5948
|
+
}
|
|
5949
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
5950
|
+
async _legacyEnableAudioDescription() {
|
|
4222
5951
|
const hasSourceElementsWithDesc = this.sourceElements.some((el) => el.getAttribute("data-desc-src"));
|
|
4223
5952
|
const hasTracksWithDesc = this.audioDescriptionCaptionTracks.length > 0;
|
|
4224
5953
|
if (!this.audioDescriptionSrc && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
|
|
@@ -4951,6 +6680,10 @@ var Player = class _Player extends EventEmitter {
|
|
|
4951
6680
|
this.emit("audiodescriptionenabled");
|
|
4952
6681
|
}
|
|
4953
6682
|
async disableAudioDescription() {
|
|
6683
|
+
return this.audioDescriptionManager.disable();
|
|
6684
|
+
}
|
|
6685
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
6686
|
+
async _legacyDisableAudioDescription() {
|
|
4954
6687
|
if (!this.originalSrc) {
|
|
4955
6688
|
return;
|
|
4956
6689
|
}
|
|
@@ -5225,64 +6958,14 @@ var Player = class _Player extends EventEmitter {
|
|
|
5225
6958
|
this.emit("audiodescriptiondisabled");
|
|
5226
6959
|
}
|
|
5227
6960
|
async toggleAudioDescription() {
|
|
5228
|
-
|
|
5229
|
-
const hasAudioDescriptionSrc = this.audioDescriptionSrc || this.sourceElements.some((el) => el.getAttribute("data-desc-src"));
|
|
5230
|
-
if (descriptionTrack && hasAudioDescriptionSrc) {
|
|
5231
|
-
if (this.state.audioDescriptionEnabled) {
|
|
5232
|
-
this._audioDescriptionDesiredState = false;
|
|
5233
|
-
descriptionTrack.mode = "hidden";
|
|
5234
|
-
await this.disableAudioDescription();
|
|
5235
|
-
} else {
|
|
5236
|
-
this._audioDescriptionDesiredState = true;
|
|
5237
|
-
await this.enableAudioDescription();
|
|
5238
|
-
const enableDescriptionTrack = () => {
|
|
5239
|
-
this.invalidateTrackCache();
|
|
5240
|
-
const descTrack = this.findTextTrack("descriptions");
|
|
5241
|
-
if (descTrack) {
|
|
5242
|
-
if (descTrack.mode === "disabled") {
|
|
5243
|
-
descTrack.mode = "hidden";
|
|
5244
|
-
this.setManagedTimeout(() => {
|
|
5245
|
-
descTrack.mode = "showing";
|
|
5246
|
-
}, 50);
|
|
5247
|
-
} else {
|
|
5248
|
-
descTrack.mode = "showing";
|
|
5249
|
-
}
|
|
5250
|
-
} else if (this.element.readyState < 2) {
|
|
5251
|
-
this.setManagedTimeout(enableDescriptionTrack, 100);
|
|
5252
|
-
}
|
|
5253
|
-
};
|
|
5254
|
-
if (this.element.readyState >= 1) {
|
|
5255
|
-
this.setManagedTimeout(enableDescriptionTrack, 200);
|
|
5256
|
-
} else {
|
|
5257
|
-
this.element.addEventListener("loadedmetadata", () => {
|
|
5258
|
-
this.setManagedTimeout(enableDescriptionTrack, 200);
|
|
5259
|
-
}, { once: true });
|
|
5260
|
-
}
|
|
5261
|
-
}
|
|
5262
|
-
} else if (descriptionTrack) {
|
|
5263
|
-
if (descriptionTrack.mode === "showing") {
|
|
5264
|
-
this._audioDescriptionDesiredState = false;
|
|
5265
|
-
descriptionTrack.mode = "hidden";
|
|
5266
|
-
this.state.audioDescriptionEnabled = false;
|
|
5267
|
-
this.emit("audiodescriptiondisabled");
|
|
5268
|
-
} else {
|
|
5269
|
-
this._audioDescriptionDesiredState = true;
|
|
5270
|
-
descriptionTrack.mode = "showing";
|
|
5271
|
-
this.state.audioDescriptionEnabled = true;
|
|
5272
|
-
this.emit("audiodescriptionenabled");
|
|
5273
|
-
}
|
|
5274
|
-
} else if (hasAudioDescriptionSrc) {
|
|
5275
|
-
if (this.state.audioDescriptionEnabled) {
|
|
5276
|
-
this._audioDescriptionDesiredState = false;
|
|
5277
|
-
await this.disableAudioDescription();
|
|
5278
|
-
} else {
|
|
5279
|
-
this._audioDescriptionDesiredState = true;
|
|
5280
|
-
await this.enableAudioDescription();
|
|
5281
|
-
}
|
|
5282
|
-
}
|
|
6961
|
+
return this.audioDescriptionManager.toggle();
|
|
5283
6962
|
}
|
|
5284
|
-
// Sign Language
|
|
6963
|
+
// Sign Language (delegated to SignLanguageManager)
|
|
5285
6964
|
enableSignLanguage() {
|
|
6965
|
+
return this.signLanguageManager.enable();
|
|
6966
|
+
}
|
|
6967
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
6968
|
+
_legacyEnableSignLanguage() {
|
|
5286
6969
|
const hasMultipleSources = Object.keys(this.signLanguageSources).length > 0;
|
|
5287
6970
|
const hasSingleSource = !!this.signLanguageSrc;
|
|
5288
6971
|
if (!hasMultipleSources && !hasSingleSource) {
|
|
@@ -5533,23 +7216,16 @@ var Player = class _Player extends EventEmitter {
|
|
|
5533
7216
|
}, 150);
|
|
5534
7217
|
}
|
|
5535
7218
|
disableSignLanguage() {
|
|
5536
|
-
|
|
5537
|
-
this.hideSignLanguageSettingsMenu({ focusButton: false });
|
|
5538
|
-
}
|
|
5539
|
-
if (this.signLanguageWrapper) {
|
|
5540
|
-
this.signLanguageWrapper.style.display = "none";
|
|
5541
|
-
}
|
|
5542
|
-
this.state.signLanguageEnabled = false;
|
|
5543
|
-
this.emit("signlanguagedisabled");
|
|
7219
|
+
return this.signLanguageManager.disable();
|
|
5544
7220
|
}
|
|
5545
7221
|
toggleSignLanguage() {
|
|
5546
|
-
|
|
5547
|
-
this.disableSignLanguage();
|
|
5548
|
-
} else {
|
|
5549
|
-
this.enableSignLanguage();
|
|
5550
|
-
}
|
|
7222
|
+
return this.signLanguageManager.toggle();
|
|
5551
7223
|
}
|
|
5552
7224
|
setupSignLanguageInteraction() {
|
|
7225
|
+
return this.signLanguageManager._setupInteraction();
|
|
7226
|
+
}
|
|
7227
|
+
// Legacy method preserved for reference
|
|
7228
|
+
_legacySetupSignLanguageInteraction() {
|
|
5553
7229
|
if (!this.signLanguageWrapper) return;
|
|
5554
7230
|
const isMobile2 = window.innerWidth < 768;
|
|
5555
7231
|
const isFullscreen = this.state.fullscreen;
|
|
@@ -5687,6 +7363,10 @@ var Player = class _Player extends EventEmitter {
|
|
|
5687
7363
|
return langNames[langCode] || langCode.toUpperCase();
|
|
5688
7364
|
}
|
|
5689
7365
|
switchSignLanguage(langCode) {
|
|
7366
|
+
return this.signLanguageManager.switchLanguage(langCode);
|
|
7367
|
+
}
|
|
7368
|
+
// Legacy method preserved for reference
|
|
7369
|
+
_legacySwitchSignLanguage(langCode) {
|
|
5690
7370
|
if (!this.signLanguageSources[langCode] || !this.signLanguageVideo) {
|
|
5691
7371
|
return;
|
|
5692
7372
|
}
|
|
@@ -5702,6 +7382,10 @@ var Player = class _Player extends EventEmitter {
|
|
|
5702
7382
|
this.emit("signlanguagelanguagechanged", langCode);
|
|
5703
7383
|
}
|
|
5704
7384
|
showSignLanguageSettingsMenu() {
|
|
7385
|
+
return this.signLanguageManager.showSettingsMenu();
|
|
7386
|
+
}
|
|
7387
|
+
// Legacy method preserved for reference
|
|
7388
|
+
_legacyShowSignLanguageSettingsMenu() {
|
|
5705
7389
|
this.signLanguageSettingsMenuJustOpened = true;
|
|
5706
7390
|
setTimeout(() => {
|
|
5707
7391
|
this.signLanguageSettingsMenuJustOpened = false;
|
|
@@ -5845,25 +7529,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
5845
7529
|
focusFirstMenuItem(this.signLanguageSettingsMenu, `.${this.options.classPrefix}-sign-language-settings-item`);
|
|
5846
7530
|
}
|
|
5847
7531
|
hideSignLanguageSettingsMenu({ focusButton = true } = {}) {
|
|
5848
|
-
|
|
5849
|
-
this.signLanguageSettingsMenu.style.display = "none";
|
|
5850
|
-
this.signLanguageSettingsMenuVisible = false;
|
|
5851
|
-
this.signLanguageSettingsMenuJustOpened = false;
|
|
5852
|
-
if (this.signLanguageSettingsMenuKeyHandler) {
|
|
5853
|
-
this.signLanguageSettingsMenu.removeEventListener("keydown", this.signLanguageSettingsMenuKeyHandler);
|
|
5854
|
-
this.signLanguageSettingsMenuKeyHandler = null;
|
|
5855
|
-
}
|
|
5856
|
-
const menuItems = Array.from(this.signLanguageSettingsMenu.querySelectorAll(`.${this.options.classPrefix}-sign-language-settings-item`));
|
|
5857
|
-
menuItems.forEach((item) => {
|
|
5858
|
-
item.setAttribute("tabindex", "-1");
|
|
5859
|
-
});
|
|
5860
|
-
if (this.signLanguageSettingsButton) {
|
|
5861
|
-
this.signLanguageSettingsButton.setAttribute("aria-expanded", "false");
|
|
5862
|
-
if (focusButton) {
|
|
5863
|
-
this.signLanguageSettingsButton.focus({ preventScroll: true });
|
|
5864
|
-
}
|
|
5865
|
-
}
|
|
5866
|
-
}
|
|
7532
|
+
return this.signLanguageManager.hideSettingsMenu({ focusButton });
|
|
5867
7533
|
}
|
|
5868
7534
|
positionSignLanguageSettingsMenuImmediate() {
|
|
5869
7535
|
if (!this.signLanguageSettingsMenu || !this.signLanguageSettingsButton) return;
|
|
@@ -5967,6 +7633,13 @@ var Player = class _Player extends EventEmitter {
|
|
|
5967
7633
|
}
|
|
5968
7634
|
}
|
|
5969
7635
|
constrainSignLanguagePosition() {
|
|
7636
|
+
return this.signLanguageManager.constrainPosition();
|
|
7637
|
+
}
|
|
7638
|
+
saveSignLanguagePreferences() {
|
|
7639
|
+
return this.signLanguageManager.savePreferences();
|
|
7640
|
+
}
|
|
7641
|
+
// Legacy methods preserved for reference - can be removed after testing
|
|
7642
|
+
_legacyConstrainSignLanguagePosition() {
|
|
5970
7643
|
if (!this.signLanguageWrapper || !this.videoWrapper) return;
|
|
5971
7644
|
if (this.signLanguageDraggable && this.signLanguageDraggable.manuallyPositioned) {
|
|
5972
7645
|
return;
|
|
@@ -6016,7 +7689,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
6016
7689
|
this.signLanguageWrapper.style.bottom = "auto";
|
|
6017
7690
|
this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
|
|
6018
7691
|
}
|
|
6019
|
-
|
|
7692
|
+
_legacySaveSignLanguagePreferences() {
|
|
6020
7693
|
if (!this.signLanguageWrapper) return;
|
|
6021
7694
|
this.storage.saveSignLanguagePreferences({
|
|
6022
7695
|
size: {
|
|
@@ -6026,58 +7699,7 @@ var Player = class _Player extends EventEmitter {
|
|
|
6026
7699
|
});
|
|
6027
7700
|
}
|
|
6028
7701
|
cleanupSignLanguage() {
|
|
6029
|
-
|
|
6030
|
-
this.hideSignLanguageSettingsMenu({ focusButton: false });
|
|
6031
|
-
}
|
|
6032
|
-
if (this.signLanguageDocumentClickHandler && this.signLanguageDocumentClickHandlerAdded) {
|
|
6033
|
-
document.removeEventListener("mousedown", this.signLanguageDocumentClickHandler, true);
|
|
6034
|
-
this.signLanguageDocumentClickHandlerAdded = false;
|
|
6035
|
-
this.signLanguageDocumentClickHandler = null;
|
|
6036
|
-
}
|
|
6037
|
-
if (this.signLanguageSettingsHandlers) {
|
|
6038
|
-
if (this.signLanguageSettingsButton) {
|
|
6039
|
-
this.signLanguageSettingsButton.removeEventListener("click", this.signLanguageSettingsHandlers.settingsClick);
|
|
6040
|
-
this.signLanguageSettingsButton.removeEventListener("keydown", this.signLanguageSettingsHandlers.settingsKeydown);
|
|
6041
|
-
}
|
|
6042
|
-
this.signLanguageSettingsHandlers = null;
|
|
6043
|
-
}
|
|
6044
|
-
if (this.signLanguageHandlers) {
|
|
6045
|
-
this.off("play", this.signLanguageHandlers.play);
|
|
6046
|
-
this.off("pause", this.signLanguageHandlers.pause);
|
|
6047
|
-
this.off("timeupdate", this.signLanguageHandlers.timeupdate);
|
|
6048
|
-
this.off("ratechange", this.signLanguageHandlers.ratechange);
|
|
6049
|
-
if (this.signLanguageHandlers.captionChange) {
|
|
6050
|
-
this.off("captionsenabled", this.signLanguageHandlers.captionChange);
|
|
6051
|
-
}
|
|
6052
|
-
this.signLanguageHandlers = null;
|
|
6053
|
-
}
|
|
6054
|
-
if (this.signLanguageInteractionHandlers) {
|
|
6055
|
-
if (this.signLanguageHeader && this.signLanguageInteractionHandlers.headerKeyHandler) {
|
|
6056
|
-
this.signLanguageHeader.removeEventListener("keydown", this.signLanguageInteractionHandlers.headerKeyHandler);
|
|
6057
|
-
}
|
|
6058
|
-
if (this.signLanguageWrapper && this.signLanguageInteractionHandlers.customKeyHandler) {
|
|
6059
|
-
this.signLanguageWrapper.removeEventListener("keydown", this.signLanguageInteractionHandlers.customKeyHandler);
|
|
6060
|
-
}
|
|
6061
|
-
}
|
|
6062
|
-
if (this.signLanguageDraggable) {
|
|
6063
|
-
if (this.signLanguageDraggable.pointerResizeMode) {
|
|
6064
|
-
this.signLanguageDraggable.disablePointerResizeMode();
|
|
6065
|
-
}
|
|
6066
|
-
this.signLanguageDraggable.destroy();
|
|
6067
|
-
this.signLanguageDraggable = null;
|
|
6068
|
-
}
|
|
6069
|
-
this.signLanguageInteractionHandlers = null;
|
|
6070
|
-
if (this.signLanguageWrapper && this.signLanguageWrapper.parentNode) {
|
|
6071
|
-
if (this.signLanguageVideo) {
|
|
6072
|
-
this.signLanguageVideo.pause();
|
|
6073
|
-
this.signLanguageVideo.src = "";
|
|
6074
|
-
}
|
|
6075
|
-
this.signLanguageWrapper.parentNode.removeChild(this.signLanguageWrapper);
|
|
6076
|
-
}
|
|
6077
|
-
this.signLanguageWrapper = null;
|
|
6078
|
-
this.signLanguageVideo = null;
|
|
6079
|
-
this.signLanguageSettingsButton = null;
|
|
6080
|
-
this.signLanguageSettingsMenu = null;
|
|
7702
|
+
return this.signLanguageManager.cleanup();
|
|
6081
7703
|
}
|
|
6082
7704
|
// Settings
|
|
6083
7705
|
// Settings dialog removed - using individual control buttons instead
|