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/src/core/Player.js
CHANGED
|
@@ -16,6 +16,9 @@ import {DraggableResizable} from '../utils/DraggableResizable.js';
|
|
|
16
16
|
import {createMenuItem, attachMenuKeyboardNavigation, focusFirstMenuItem} from '../utils/MenuUtils.js';
|
|
17
17
|
import {createLabeledSelect, preventDragOnElement} from '../utils/FormUtils.js';
|
|
18
18
|
import {debounce, isMobile, rafWithTimeout} from '../utils/PerformanceUtils.js';
|
|
19
|
+
import {AudioDescriptionManager} from './AudioDescriptionManager.js';
|
|
20
|
+
import {SignLanguageManager} from './SignLanguageManager.js';
|
|
21
|
+
import {captureVideoFrame} from '../utils/VideoFrameCapture.js';
|
|
19
22
|
|
|
20
23
|
// Static counter for unique player instances
|
|
21
24
|
let playerInstanceCounter = 0;
|
|
@@ -252,6 +255,47 @@ export class Player extends EventEmitter {
|
|
|
252
255
|
this.metadataCueChangeHandler = null;
|
|
253
256
|
this.metadataAlertHandlers = new Map();
|
|
254
257
|
|
|
258
|
+
// Feature managers (modular refactoring)
|
|
259
|
+
this.audioDescriptionManager = new AudioDescriptionManager(this);
|
|
260
|
+
this.signLanguageManager = new SignLanguageManager(this);
|
|
261
|
+
|
|
262
|
+
// Backward-compatible property aliases for SignLanguageManager
|
|
263
|
+
// These allow existing code to reference this.signLanguageWrapper, etc.
|
|
264
|
+
Object.defineProperties(this, {
|
|
265
|
+
signLanguageWrapper: {
|
|
266
|
+
get: () => this.signLanguageManager.wrapper,
|
|
267
|
+
set: (v) => { this.signLanguageManager.wrapper = v; }
|
|
268
|
+
},
|
|
269
|
+
signLanguageVideo: {
|
|
270
|
+
get: () => this.signLanguageManager.video,
|
|
271
|
+
set: (v) => { this.signLanguageManager.video = v; }
|
|
272
|
+
},
|
|
273
|
+
signLanguageHeader: {
|
|
274
|
+
get: () => this.signLanguageManager.header,
|
|
275
|
+
set: (v) => { this.signLanguageManager.header = v; }
|
|
276
|
+
},
|
|
277
|
+
signLanguageSettingsButton: {
|
|
278
|
+
get: () => this.signLanguageManager.settingsButton,
|
|
279
|
+
set: (v) => { this.signLanguageManager.settingsButton = v; }
|
|
280
|
+
},
|
|
281
|
+
signLanguageSettingsMenu: {
|
|
282
|
+
get: () => this.signLanguageManager.settingsMenu,
|
|
283
|
+
set: (v) => { this.signLanguageManager.settingsMenu = v; }
|
|
284
|
+
},
|
|
285
|
+
signLanguageSettingsMenuVisible: {
|
|
286
|
+
get: () => this.signLanguageManager.settingsMenuVisible,
|
|
287
|
+
set: (v) => { this.signLanguageManager.settingsMenuVisible = v; }
|
|
288
|
+
},
|
|
289
|
+
signLanguageDraggable: {
|
|
290
|
+
get: () => this.signLanguageManager.draggable,
|
|
291
|
+
set: (v) => { this.signLanguageManager.draggable = v; }
|
|
292
|
+
},
|
|
293
|
+
currentSignLanguage: {
|
|
294
|
+
get: () => this.signLanguageManager.currentLanguage,
|
|
295
|
+
set: (v) => { this.signLanguageManager.currentLanguage = v; }
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
255
299
|
// Initialize
|
|
256
300
|
this.init();
|
|
257
301
|
}
|
|
@@ -651,77 +695,10 @@ export class Player extends EventEmitter {
|
|
|
651
695
|
// Clear pending source after using it
|
|
652
696
|
this._pendingSource = null;
|
|
653
697
|
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const origSrc = sourceEl.getAttribute('data-orig-src');
|
|
659
|
-
|
|
660
|
-
if (descSrc || origSrc) {
|
|
661
|
-
// Found a source element with audio description attributes
|
|
662
|
-
// Store the first one as reference, but we'll search all of them when toggling
|
|
663
|
-
if (!this.audioDescriptionSourceElement) {
|
|
664
|
-
this.audioDescriptionSourceElement = sourceEl;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (origSrc) {
|
|
668
|
-
// Store the original src from the attribute for this source
|
|
669
|
-
if (!this.originalAudioDescriptionSource) {
|
|
670
|
-
this.originalAudioDescriptionSource = origSrc;
|
|
671
|
-
}
|
|
672
|
-
// Store the original src from the first source element that has data-orig-src
|
|
673
|
-
if (!this.originalSrc) {
|
|
674
|
-
this.originalSrc = origSrc;
|
|
675
|
-
}
|
|
676
|
-
} else {
|
|
677
|
-
// If data-orig-src is not set, use the current src attribute
|
|
678
|
-
const currentSrcAttr = sourceEl.getAttribute('src');
|
|
679
|
-
if (!this.originalAudioDescriptionSource && currentSrcAttr) {
|
|
680
|
-
this.originalAudioDescriptionSource = currentSrcAttr;
|
|
681
|
-
}
|
|
682
|
-
if (!this.originalSrc && currentSrcAttr) {
|
|
683
|
-
this.originalSrc = currentSrcAttr;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Store audio description source from data-desc-src (use first one found)
|
|
688
|
-
if (descSrc && !this.audioDescriptionSrc) {
|
|
689
|
-
this.audioDescriptionSrc = descSrc;
|
|
690
|
-
}
|
|
691
|
-
// Continue checking all source elements to ensure we capture all audio description sources
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// Check for caption/subtitle tracks with audio description versions
|
|
696
|
-
// Only tracks with explicit data-desc-src attribute are swapped (no auto-detection to avoid 404 errors)
|
|
697
|
-
// Description tracks (kind="descriptions") are NOT swapped - they're for transcripts
|
|
698
|
-
const trackElements = this.trackElements;
|
|
699
|
-
trackElements.forEach(trackEl => {
|
|
700
|
-
const trackKind = trackEl.getAttribute('kind');
|
|
701
|
-
const trackDescSrc = trackEl.getAttribute('data-desc-src');
|
|
702
|
-
|
|
703
|
-
// Only handle caption/subtitle tracks (not description tracks)
|
|
704
|
-
// Description tracks stay as-is since they're for transcripts
|
|
705
|
-
// Include captions, subtitles, and chapters tracks that can be swapped for audio description
|
|
706
|
-
if (trackKind === 'captions' || trackKind === 'subtitles' || trackKind === 'chapters') {
|
|
707
|
-
if (trackDescSrc) {
|
|
708
|
-
// Found a track with explicit data-desc-src - this is the described version
|
|
709
|
-
this.audioDescriptionCaptionTracks.push({
|
|
710
|
-
trackElement: trackEl,
|
|
711
|
-
originalSrc: trackEl.getAttribute('src'),
|
|
712
|
-
describedSrc: trackDescSrc,
|
|
713
|
-
originalTrackSrc: trackEl.getAttribute('data-orig-src') || trackEl.getAttribute('src'),
|
|
714
|
-
explicit: true // Explicitly defined, so we should validate it
|
|
715
|
-
});
|
|
716
|
-
this.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute('src')} -> ${trackDescSrc}`);
|
|
717
|
-
}
|
|
718
|
-
// Note: Auto-detection disabled to avoid 404 console errors
|
|
719
|
-
// If you want described tracks, add data-desc-src attribute to the track element
|
|
720
|
-
}
|
|
721
|
-
// Description tracks (kind="descriptions") are ignored - they remain unchanged for transcripts
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
// Store original source for audio description toggling (fallback if not set above)
|
|
698
|
+
// Initialize audio description sources from elements
|
|
699
|
+
this.audioDescriptionManager.initFromSourceElements(this.sourceElements, this.trackElements);
|
|
700
|
+
|
|
701
|
+
// Store original source for audio description toggling (fallback if not set by manager)
|
|
725
702
|
if (!this.originalSrc) {
|
|
726
703
|
this.originalSrc = src;
|
|
727
704
|
}
|
|
@@ -858,6 +835,95 @@ export class Player extends EventEmitter {
|
|
|
858
835
|
}
|
|
859
836
|
}
|
|
860
837
|
|
|
838
|
+
/**
|
|
839
|
+
* Generate a poster image from video frame at specified time
|
|
840
|
+
* @param {number} time - Time in seconds (default: 10)
|
|
841
|
+
* @returns {Promise<string|null>} Data URL of the poster image or null if failed
|
|
842
|
+
*/
|
|
843
|
+
async generatePosterFromVideo(time = 10) {
|
|
844
|
+
// Only for HTML5 video
|
|
845
|
+
if (this.element.tagName !== 'VIDEO') {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Check if renderer supports this (HTML5Renderer only)
|
|
850
|
+
const renderer = this.renderer;
|
|
851
|
+
if (!renderer || !renderer.media || renderer.media.tagName !== 'VIDEO') {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const video = renderer.media;
|
|
856
|
+
|
|
857
|
+
// Check if video has enough duration
|
|
858
|
+
if (!video.duration || video.duration < time) {
|
|
859
|
+
// Use a smaller time if video is shorter
|
|
860
|
+
time = Math.min(time, Math.max(1, video.duration * 0.1));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Try to use preview video from ControlBar if available (avoids interfering with playback)
|
|
864
|
+
let videoToUse = video;
|
|
865
|
+
if (this.controlBar && this.controlBar.previewVideo && this.controlBar.previewSupported) {
|
|
866
|
+
videoToUse = this.controlBar.previewVideo;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Use shared frame capture utility
|
|
870
|
+
// For main video, restore state; for preview video, no need
|
|
871
|
+
const restoreState = videoToUse === video;
|
|
872
|
+
return await captureVideoFrame(videoToUse, time, {
|
|
873
|
+
restoreState,
|
|
874
|
+
quality: 0.9
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Auto-generate poster from video if none is provided
|
|
880
|
+
*/
|
|
881
|
+
async autoGeneratePoster() {
|
|
882
|
+
// Check if poster already exists
|
|
883
|
+
const hasPoster =
|
|
884
|
+
this.element.getAttribute('poster') ||
|
|
885
|
+
this.element.poster ||
|
|
886
|
+
this.options.poster;
|
|
887
|
+
|
|
888
|
+
if (hasPoster) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Only for HTML5 video
|
|
893
|
+
if (this.element.tagName !== 'VIDEO') {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Wait for metadata to be loaded
|
|
898
|
+
if (!this.state.duration || this.state.duration === 0) {
|
|
899
|
+
// Wait for loadedmetadata event
|
|
900
|
+
await new Promise((resolve) => {
|
|
901
|
+
const onLoadedMetadata = () => {
|
|
902
|
+
this.element.removeEventListener('loadedmetadata', onLoadedMetadata);
|
|
903
|
+
resolve();
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
if (this.element.readyState >= 1) {
|
|
907
|
+
resolve();
|
|
908
|
+
} else {
|
|
909
|
+
this.element.addEventListener('loadedmetadata', onLoadedMetadata);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Generate poster from second 10
|
|
915
|
+
const posterDataURL = await this.generatePosterFromVideo(10);
|
|
916
|
+
|
|
917
|
+
if (posterDataURL) {
|
|
918
|
+
// Set as poster
|
|
919
|
+
this.element.poster = posterDataURL;
|
|
920
|
+
this.log('Auto-generated poster from video frame at 10 seconds', 'info');
|
|
921
|
+
|
|
922
|
+
// Show the poster overlay
|
|
923
|
+
this.showPosterOverlay();
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
861
927
|
showPosterOverlay() {
|
|
862
928
|
if (!this.videoWrapper || this.element.tagName !== 'VIDEO') {
|
|
863
929
|
return;
|
|
@@ -872,8 +938,10 @@ export class Player extends EventEmitter {
|
|
|
872
938
|
return;
|
|
873
939
|
}
|
|
874
940
|
|
|
875
|
-
// Resolve relative paths to absolute URLs
|
|
876
|
-
const resolvedPoster =
|
|
941
|
+
// Resolve relative paths to absolute URLs (skip for data URLs)
|
|
942
|
+
const resolvedPoster = poster.startsWith('data:')
|
|
943
|
+
? poster
|
|
944
|
+
: this.resolvePosterPath(poster);
|
|
877
945
|
this.videoWrapper.style.setProperty('--vidply-poster-image', `url("${resolvedPoster}")`);
|
|
878
946
|
this.videoWrapper.classList.add('vidply-forced-poster');
|
|
879
947
|
|
|
@@ -1040,6 +1108,11 @@ export class Player extends EventEmitter {
|
|
|
1040
1108
|
if (trackConfig.default) {
|
|
1041
1109
|
track.default = true;
|
|
1042
1110
|
}
|
|
1111
|
+
|
|
1112
|
+
// Support described track sources for audio description track swapping
|
|
1113
|
+
if (trackConfig.describedSrc) {
|
|
1114
|
+
track.setAttribute('data-desc-src', trackConfig.describedSrc);
|
|
1115
|
+
}
|
|
1043
1116
|
|
|
1044
1117
|
// Insert tracks at the beginning (before any flow content) for HTML5 validity
|
|
1045
1118
|
const firstChild = this.element.firstChild;
|
|
@@ -1063,6 +1136,16 @@ export class Player extends EventEmitter {
|
|
|
1063
1136
|
// Update original source for toggling
|
|
1064
1137
|
this.originalSrc = config.src;
|
|
1065
1138
|
|
|
1139
|
+
// Update manager sources for playlist changes
|
|
1140
|
+
if (this.audioDescriptionManager) {
|
|
1141
|
+
this.audioDescriptionManager.updateSources(config.audioDescriptionSrc);
|
|
1142
|
+
// Reinitialize to pick up new track elements with data-desc-src attributes
|
|
1143
|
+
this.audioDescriptionManager.reinitialize();
|
|
1144
|
+
}
|
|
1145
|
+
if (this.signLanguageManager) {
|
|
1146
|
+
this.signLanguageManager.updateSources(config.signLanguageSrc, config.signLanguageSources);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1066
1149
|
// Hide accessibility features that were enabled (must happen AFTER updating sources)
|
|
1067
1150
|
if (wasAudioDescriptionEnabled) {
|
|
1068
1151
|
this.disableAudioDescription();
|
|
@@ -1607,8 +1690,13 @@ export class Player extends EventEmitter {
|
|
|
1607
1690
|
return null;
|
|
1608
1691
|
}
|
|
1609
1692
|
|
|
1610
|
-
// Audio Description
|
|
1693
|
+
// Audio Description (delegated to AudioDescriptionManager)
|
|
1611
1694
|
async enableAudioDescription() {
|
|
1695
|
+
return this.audioDescriptionManager.enable();
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
1699
|
+
async _legacyEnableAudioDescription() {
|
|
1612
1700
|
// Check if we have source elements with data-desc-src (even if audioDescriptionSrc is not set)
|
|
1613
1701
|
const hasSourceElementsWithDesc = this.sourceElements.some(el => el.getAttribute('data-desc-src'));
|
|
1614
1702
|
const hasTracksWithDesc = this.audioDescriptionCaptionTracks.length > 0;
|
|
@@ -2664,6 +2752,11 @@ export class Player extends EventEmitter {
|
|
|
2664
2752
|
}
|
|
2665
2753
|
|
|
2666
2754
|
async disableAudioDescription() {
|
|
2755
|
+
return this.audioDescriptionManager.disable();
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
2759
|
+
async _legacyDisableAudioDescription() {
|
|
2667
2760
|
if (!this.originalSrc) {
|
|
2668
2761
|
return;
|
|
2669
2762
|
}
|
|
@@ -3041,82 +3134,16 @@ export class Player extends EventEmitter {
|
|
|
3041
3134
|
}
|
|
3042
3135
|
|
|
3043
3136
|
async toggleAudioDescription() {
|
|
3044
|
-
|
|
3045
|
-
const descriptionTrack = this.findTextTrack('descriptions');
|
|
3046
|
-
|
|
3047
|
-
// Check if we have audio-described video source (either from options or source elements with data-desc-src)
|
|
3048
|
-
const hasAudioDescriptionSrc = this.audioDescriptionSrc ||
|
|
3049
|
-
this.sourceElements.some(el => el.getAttribute('data-desc-src'));
|
|
3050
|
-
|
|
3051
|
-
if (descriptionTrack && hasAudioDescriptionSrc) {
|
|
3052
|
-
// We have both: toggle description track AND swap caption tracks/sources
|
|
3053
|
-
if (this.state.audioDescriptionEnabled) {
|
|
3054
|
-
this._audioDescriptionDesiredState = false;
|
|
3055
|
-
// Disable: toggle description track off and swap captions/sources back
|
|
3056
|
-
descriptionTrack.mode = 'hidden';
|
|
3057
|
-
await this.disableAudioDescription();
|
|
3058
|
-
} else {
|
|
3059
|
-
this._audioDescriptionDesiredState = true;
|
|
3060
|
-
// Enable: swap caption tracks/sources and toggle description track on
|
|
3061
|
-
await this.enableAudioDescription();
|
|
3062
|
-
// Wait for tracks to be ready after source swap, then enable description track
|
|
3063
|
-
// Use a longer timeout to ensure tracks are loaded after source swap
|
|
3064
|
-
const enableDescriptionTrack = () => {
|
|
3065
|
-
this.invalidateTrackCache();
|
|
3066
|
-
const descTrack = this.findTextTrack('descriptions');
|
|
3067
|
-
if (descTrack) {
|
|
3068
|
-
// Set to 'hidden' first if it's in 'disabled' mode, then to 'showing'
|
|
3069
|
-
if (descTrack.mode === 'disabled') {
|
|
3070
|
-
descTrack.mode = 'hidden';
|
|
3071
|
-
// Use setTimeout to ensure the browser processes the mode change
|
|
3072
|
-
this.setManagedTimeout(() => {
|
|
3073
|
-
descTrack.mode = 'showing';
|
|
3074
|
-
}, 50);
|
|
3075
|
-
} else {
|
|
3076
|
-
descTrack.mode = 'showing';
|
|
3077
|
-
}
|
|
3078
|
-
} else if (this.element.readyState < 2) {
|
|
3079
|
-
// Tracks not ready yet, wait a bit more
|
|
3080
|
-
this.setManagedTimeout(enableDescriptionTrack, 100);
|
|
3081
|
-
}
|
|
3082
|
-
};
|
|
3083
|
-
// Wait for metadata to load first
|
|
3084
|
-
if (this.element.readyState >= 1) {
|
|
3085
|
-
this.setManagedTimeout(enableDescriptionTrack, 200);
|
|
3086
|
-
} else {
|
|
3087
|
-
this.element.addEventListener('loadedmetadata', () => {
|
|
3088
|
-
this.setManagedTimeout(enableDescriptionTrack, 200);
|
|
3089
|
-
}, { once: true });
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
} else if (descriptionTrack) {
|
|
3093
|
-
// Only description track, no audio-described video source to swap
|
|
3094
|
-
// Toggle description track
|
|
3095
|
-
if (descriptionTrack.mode === 'showing') {
|
|
3096
|
-
this._audioDescriptionDesiredState = false;
|
|
3097
|
-
descriptionTrack.mode = 'hidden';
|
|
3098
|
-
this.state.audioDescriptionEnabled = false;
|
|
3099
|
-
this.emit('audiodescriptiondisabled');
|
|
3100
|
-
} else {
|
|
3101
|
-
this._audioDescriptionDesiredState = true;
|
|
3102
|
-
descriptionTrack.mode = 'showing';
|
|
3103
|
-
this.state.audioDescriptionEnabled = true;
|
|
3104
|
-
this.emit('audiodescriptionenabled');
|
|
3105
|
-
}
|
|
3106
|
-
} else if (hasAudioDescriptionSrc) {
|
|
3107
|
-
// Use audio-described video source (no description track)
|
|
3108
|
-
if (this.state.audioDescriptionEnabled) {
|
|
3109
|
-
this._audioDescriptionDesiredState = false;
|
|
3110
|
-
await this.disableAudioDescription();
|
|
3111
|
-
} else {
|
|
3112
|
-
this._audioDescriptionDesiredState = true;
|
|
3113
|
-
await this.enableAudioDescription();
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3137
|
+
return this.audioDescriptionManager.toggle();
|
|
3116
3138
|
}
|
|
3117
3139
|
|
|
3118
|
-
// Sign Language
|
|
3140
|
+
// Sign Language (delegated to SignLanguageManager)
|
|
3119
3141
|
enableSignLanguage() {
|
|
3142
|
+
return this.signLanguageManager.enable();
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
// Legacy method body preserved for reference - can be removed after testing
|
|
3146
|
+
_legacyEnableSignLanguage() {
|
|
3120
3147
|
// Determine available sign language sources
|
|
3121
3148
|
const hasMultipleSources = Object.keys(this.signLanguageSources).length > 0;
|
|
3122
3149
|
const hasSingleSource = !!this.signLanguageSrc;
|
|
@@ -3452,27 +3479,19 @@ export class Player extends EventEmitter {
|
|
|
3452
3479
|
}
|
|
3453
3480
|
|
|
3454
3481
|
disableSignLanguage() {
|
|
3455
|
-
|
|
3456
|
-
if (this.signLanguageSettingsMenuVisible) {
|
|
3457
|
-
this.hideSignLanguageSettingsMenu({ focusButton: false });
|
|
3458
|
-
}
|
|
3459
|
-
|
|
3460
|
-
if (this.signLanguageWrapper) {
|
|
3461
|
-
this.signLanguageWrapper.style.display = 'none';
|
|
3462
|
-
}
|
|
3463
|
-
this.state.signLanguageEnabled = false;
|
|
3464
|
-
this.emit('signlanguagedisabled');
|
|
3482
|
+
return this.signLanguageManager.disable();
|
|
3465
3483
|
}
|
|
3466
3484
|
|
|
3467
3485
|
toggleSignLanguage() {
|
|
3468
|
-
|
|
3469
|
-
this.disableSignLanguage();
|
|
3470
|
-
} else {
|
|
3471
|
-
this.enableSignLanguage();
|
|
3472
|
-
}
|
|
3486
|
+
return this.signLanguageManager.toggle();
|
|
3473
3487
|
}
|
|
3474
3488
|
|
|
3475
3489
|
setupSignLanguageInteraction() {
|
|
3490
|
+
return this.signLanguageManager._setupInteraction();
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
// Legacy method preserved for reference
|
|
3494
|
+
_legacySetupSignLanguageInteraction() {
|
|
3476
3495
|
if (!this.signLanguageWrapper) return;
|
|
3477
3496
|
|
|
3478
3497
|
// Check if we're on mobile and not in fullscreen
|
|
@@ -3655,6 +3674,11 @@ export class Player extends EventEmitter {
|
|
|
3655
3674
|
}
|
|
3656
3675
|
|
|
3657
3676
|
switchSignLanguage(langCode) {
|
|
3677
|
+
return this.signLanguageManager.switchLanguage(langCode);
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
// Legacy method preserved for reference
|
|
3681
|
+
_legacySwitchSignLanguage(langCode) {
|
|
3658
3682
|
if (!this.signLanguageSources[langCode] || !this.signLanguageVideo) {
|
|
3659
3683
|
return;
|
|
3660
3684
|
}
|
|
@@ -3677,6 +3701,11 @@ export class Player extends EventEmitter {
|
|
|
3677
3701
|
}
|
|
3678
3702
|
|
|
3679
3703
|
showSignLanguageSettingsMenu() {
|
|
3704
|
+
return this.signLanguageManager.showSettingsMenu();
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
// Legacy method preserved for reference
|
|
3708
|
+
_legacyShowSignLanguageSettingsMenu() {
|
|
3680
3709
|
// Set flag to prevent immediate closing
|
|
3681
3710
|
this.signLanguageSettingsMenuJustOpened = true;
|
|
3682
3711
|
setTimeout(() => {
|
|
@@ -3869,32 +3898,7 @@ export class Player extends EventEmitter {
|
|
|
3869
3898
|
}
|
|
3870
3899
|
|
|
3871
3900
|
hideSignLanguageSettingsMenu({ focusButton = true } = {}) {
|
|
3872
|
-
|
|
3873
|
-
this.signLanguageSettingsMenu.style.display = 'none';
|
|
3874
|
-
this.signLanguageSettingsMenuVisible = false;
|
|
3875
|
-
this.signLanguageSettingsMenuJustOpened = false;
|
|
3876
|
-
|
|
3877
|
-
// Remove keyboard handler
|
|
3878
|
-
if (this.signLanguageSettingsMenuKeyHandler) {
|
|
3879
|
-
this.signLanguageSettingsMenu.removeEventListener('keydown', this.signLanguageSettingsMenuKeyHandler);
|
|
3880
|
-
this.signLanguageSettingsMenuKeyHandler = null;
|
|
3881
|
-
}
|
|
3882
|
-
|
|
3883
|
-
// Reset tabindex on menu items
|
|
3884
|
-
const menuItems = Array.from(this.signLanguageSettingsMenu.querySelectorAll(`.${this.options.classPrefix}-sign-language-settings-item`));
|
|
3885
|
-
menuItems.forEach(item => {
|
|
3886
|
-
item.setAttribute('tabindex', '-1');
|
|
3887
|
-
});
|
|
3888
|
-
|
|
3889
|
-
// Update aria-expanded
|
|
3890
|
-
if (this.signLanguageSettingsButton) {
|
|
3891
|
-
this.signLanguageSettingsButton.setAttribute('aria-expanded', 'false');
|
|
3892
|
-
if (focusButton) {
|
|
3893
|
-
// Return focus to settings button
|
|
3894
|
-
this.signLanguageSettingsButton.focus({ preventScroll: true });
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3901
|
+
return this.signLanguageManager.hideSettingsMenu({ focusButton });
|
|
3898
3902
|
}
|
|
3899
3903
|
|
|
3900
3904
|
positionSignLanguageSettingsMenuImmediate() {
|
|
@@ -4042,6 +4046,15 @@ export class Player extends EventEmitter {
|
|
|
4042
4046
|
}
|
|
4043
4047
|
|
|
4044
4048
|
constrainSignLanguagePosition() {
|
|
4049
|
+
return this.signLanguageManager.constrainPosition();
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
saveSignLanguagePreferences() {
|
|
4053
|
+
return this.signLanguageManager.savePreferences();
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
// Legacy methods preserved for reference - can be removed after testing
|
|
4057
|
+
_legacyConstrainSignLanguagePosition() {
|
|
4045
4058
|
if (!this.signLanguageWrapper || !this.videoWrapper) return;
|
|
4046
4059
|
|
|
4047
4060
|
// Don't auto-position if user has manually positioned it
|
|
@@ -4111,7 +4124,7 @@ export class Player extends EventEmitter {
|
|
|
4111
4124
|
this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter(c => c.startsWith('vidply-sign-position-')));
|
|
4112
4125
|
}
|
|
4113
4126
|
|
|
4114
|
-
|
|
4127
|
+
_legacySaveSignLanguagePreferences() {
|
|
4115
4128
|
if (!this.signLanguageWrapper) return;
|
|
4116
4129
|
|
|
4117
4130
|
// Only save width - position is always calculated fresh to bottom-right
|
|
@@ -4124,73 +4137,7 @@ export class Player extends EventEmitter {
|
|
|
4124
4137
|
}
|
|
4125
4138
|
|
|
4126
4139
|
cleanupSignLanguage() {
|
|
4127
|
-
|
|
4128
|
-
if (this.signLanguageSettingsMenuVisible) {
|
|
4129
|
-
this.hideSignLanguageSettingsMenu({ focusButton: false });
|
|
4130
|
-
}
|
|
4131
|
-
|
|
4132
|
-
// Remove document click handler
|
|
4133
|
-
if (this.signLanguageDocumentClickHandler && this.signLanguageDocumentClickHandlerAdded) {
|
|
4134
|
-
document.removeEventListener('mousedown', this.signLanguageDocumentClickHandler, true);
|
|
4135
|
-
this.signLanguageDocumentClickHandlerAdded = false;
|
|
4136
|
-
this.signLanguageDocumentClickHandler = null;
|
|
4137
|
-
}
|
|
4138
|
-
|
|
4139
|
-
// Remove settings menu event listeners
|
|
4140
|
-
if (this.signLanguageSettingsHandlers) {
|
|
4141
|
-
if (this.signLanguageSettingsButton) {
|
|
4142
|
-
this.signLanguageSettingsButton.removeEventListener('click', this.signLanguageSettingsHandlers.settingsClick);
|
|
4143
|
-
this.signLanguageSettingsButton.removeEventListener('keydown', this.signLanguageSettingsHandlers.settingsKeydown);
|
|
4144
|
-
}
|
|
4145
|
-
this.signLanguageSettingsHandlers = null;
|
|
4146
|
-
}
|
|
4147
|
-
|
|
4148
|
-
// Remove event listeners
|
|
4149
|
-
if (this.signLanguageHandlers) {
|
|
4150
|
-
this.off('play', this.signLanguageHandlers.play);
|
|
4151
|
-
this.off('pause', this.signLanguageHandlers.pause);
|
|
4152
|
-
this.off('timeupdate', this.signLanguageHandlers.timeupdate);
|
|
4153
|
-
this.off('ratechange', this.signLanguageHandlers.ratechange);
|
|
4154
|
-
if (this.signLanguageHandlers.captionChange) {
|
|
4155
|
-
this.off('captionsenabled', this.signLanguageHandlers.captionChange);
|
|
4156
|
-
}
|
|
4157
|
-
this.signLanguageHandlers = null;
|
|
4158
|
-
}
|
|
4159
|
-
|
|
4160
|
-
// Remove event listeners
|
|
4161
|
-
if (this.signLanguageInteractionHandlers) {
|
|
4162
|
-
if (this.signLanguageHeader && this.signLanguageInteractionHandlers.headerKeyHandler) {
|
|
4163
|
-
this.signLanguageHeader.removeEventListener('keydown', this.signLanguageInteractionHandlers.headerKeyHandler);
|
|
4164
|
-
}
|
|
4165
|
-
if (this.signLanguageWrapper && this.signLanguageInteractionHandlers.customKeyHandler) {
|
|
4166
|
-
this.signLanguageWrapper.removeEventListener('keydown', this.signLanguageInteractionHandlers.customKeyHandler);
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
|
|
4170
|
-
// Destroy draggable utility
|
|
4171
|
-
if (this.signLanguageDraggable) {
|
|
4172
|
-
if (this.signLanguageDraggable.pointerResizeMode) {
|
|
4173
|
-
this.signLanguageDraggable.disablePointerResizeMode();
|
|
4174
|
-
}
|
|
4175
|
-
this.signLanguageDraggable.destroy();
|
|
4176
|
-
this.signLanguageDraggable = null;
|
|
4177
|
-
}
|
|
4178
|
-
|
|
4179
|
-
// Clear interaction handlers reference
|
|
4180
|
-
this.signLanguageInteractionHandlers = null;
|
|
4181
|
-
|
|
4182
|
-
// Remove video and wrapper elements
|
|
4183
|
-
if (this.signLanguageWrapper && this.signLanguageWrapper.parentNode) {
|
|
4184
|
-
if (this.signLanguageVideo) {
|
|
4185
|
-
this.signLanguageVideo.pause();
|
|
4186
|
-
this.signLanguageVideo.src = '';
|
|
4187
|
-
}
|
|
4188
|
-
this.signLanguageWrapper.parentNode.removeChild(this.signLanguageWrapper);
|
|
4189
|
-
}
|
|
4190
|
-
this.signLanguageWrapper = null;
|
|
4191
|
-
this.signLanguageVideo = null;
|
|
4192
|
-
this.signLanguageSettingsButton = null;
|
|
4193
|
-
this.signLanguageSettingsMenu = null;
|
|
4140
|
+
return this.signLanguageManager.cleanup();
|
|
4194
4141
|
}
|
|
4195
4142
|
|
|
4196
4143
|
// Settings
|