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
|
@@ -7,6 +7,8 @@ import {TimeUtils} from '../utils/TimeUtils.js';
|
|
|
7
7
|
import {createIconElement} from '../icons/Icons.js';
|
|
8
8
|
import {i18n} from '../i18n/i18n.js';
|
|
9
9
|
import {focusElement, focusFirstElement} from '../utils/FocusUtils.js';
|
|
10
|
+
import {isMobile} from '../utils/PerformanceUtils.js';
|
|
11
|
+
import {captureVideoFrame} from '../utils/VideoFrameCapture.js';
|
|
10
12
|
|
|
11
13
|
export class ControlBar {
|
|
12
14
|
constructor(player) {
|
|
@@ -30,11 +32,6 @@ export class ControlBar {
|
|
|
30
32
|
this.setupOverflowDetection();
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
// Helper method to check if we're on a mobile device
|
|
34
|
-
isMobile() {
|
|
35
|
-
return window.innerWidth < 768;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
35
|
// Helper method to detect touch devices
|
|
39
36
|
isTouchDevice() {
|
|
40
37
|
return (
|
|
@@ -46,7 +43,7 @@ export class ControlBar {
|
|
|
46
43
|
|
|
47
44
|
// Smart menu positioning to avoid overflow
|
|
48
45
|
positionMenu(menu, button, immediate = false) {
|
|
49
|
-
const
|
|
46
|
+
const mobile = isMobile();
|
|
50
47
|
const isOverflowMenu = menu.classList.contains(`${this.player.options.classPrefix}-overflow-menu-list`);
|
|
51
48
|
const isFullscreen = this.player.state.fullscreen;
|
|
52
49
|
|
|
@@ -101,7 +98,7 @@ export class ControlBar {
|
|
|
101
98
|
return;
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
if (
|
|
101
|
+
if (mobile) {
|
|
105
102
|
// On mobile, ensure menus stay within viewport
|
|
106
103
|
const isVolumeMenu = menu.classList.contains(`${this.player.options.classPrefix}-volume-menu`);
|
|
107
104
|
|
|
@@ -913,6 +910,21 @@ export class ControlBar {
|
|
|
913
910
|
className: `${this.player.options.classPrefix}-progress-tooltip`
|
|
914
911
|
});
|
|
915
912
|
|
|
913
|
+
// Preview thumbnail (for video only)
|
|
914
|
+
this.controls.progressPreview = DOMUtils.createElement('div', {
|
|
915
|
+
className: `${this.player.options.classPrefix}-progress-preview`,
|
|
916
|
+
attributes: {
|
|
917
|
+
'aria-hidden': 'true'
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
this.controls.progressTooltip.appendChild(this.controls.progressPreview);
|
|
921
|
+
|
|
922
|
+
// Time text
|
|
923
|
+
this.controls.progressTooltipTime = DOMUtils.createElement('div', {
|
|
924
|
+
className: `${this.player.options.classPrefix}-progress-tooltip-time`
|
|
925
|
+
});
|
|
926
|
+
this.controls.progressTooltip.appendChild(this.controls.progressTooltipTime);
|
|
927
|
+
|
|
916
928
|
progressContainer.appendChild(this.controls.buffered);
|
|
917
929
|
progressContainer.appendChild(this.controls.played);
|
|
918
930
|
this.controls.played.appendChild(this.controls.progressHandle);
|
|
@@ -920,10 +932,139 @@ export class ControlBar {
|
|
|
920
932
|
|
|
921
933
|
this.controls.progress = progressContainer;
|
|
922
934
|
|
|
935
|
+
// Initialize preview functionality
|
|
936
|
+
this.initPreviewThumbnail();
|
|
937
|
+
|
|
923
938
|
// Progress bar events
|
|
924
939
|
this.setupProgressBarEvents();
|
|
925
940
|
}
|
|
926
941
|
|
|
942
|
+
/**
|
|
943
|
+
* Initialize preview thumbnail functionality for HTML5 video
|
|
944
|
+
*/
|
|
945
|
+
initPreviewThumbnail() {
|
|
946
|
+
this.previewThumbnailCache = new Map();
|
|
947
|
+
this.previewVideo = null;
|
|
948
|
+
this.currentPreviewTime = null;
|
|
949
|
+
this.previewThumbnailTimeout = null;
|
|
950
|
+
this.previewSupported = false;
|
|
951
|
+
|
|
952
|
+
// Check if preview is supported (HTML5 video only)
|
|
953
|
+
// Check if element is a video
|
|
954
|
+
const isVideo = this.player.element && this.player.element.tagName === 'VIDEO';
|
|
955
|
+
|
|
956
|
+
if (!isVideo) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Check if renderer supports preview (HTML5Renderer or HLSRenderer with native support)
|
|
961
|
+
// We check if renderer has a media property that is a video element
|
|
962
|
+
const renderer = this.player.renderer;
|
|
963
|
+
const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === 'VIDEO';
|
|
964
|
+
const isHTML5Renderer = renderer && (
|
|
965
|
+
renderer.constructor.name === 'HTML5Renderer' ||
|
|
966
|
+
(renderer.constructor.name === 'HLSRenderer' && hasVideoMedia)
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
this.previewSupported = isHTML5Renderer && hasVideoMedia;
|
|
970
|
+
|
|
971
|
+
if (this.previewSupported) {
|
|
972
|
+
// Create a hidden video element for capturing frames
|
|
973
|
+
this.previewVideo = document.createElement('video');
|
|
974
|
+
this.previewVideo.muted = true;
|
|
975
|
+
this.previewVideo.preload = 'metadata';
|
|
976
|
+
this.previewVideo.style.position = 'absolute';
|
|
977
|
+
this.previewVideo.style.visibility = 'hidden';
|
|
978
|
+
this.previewVideo.style.width = '1px';
|
|
979
|
+
this.previewVideo.style.height = '1px';
|
|
980
|
+
this.previewVideo.style.top = '-9999px';
|
|
981
|
+
|
|
982
|
+
// Copy source from main video
|
|
983
|
+
const mainVideo = renderer.media || this.player.element;
|
|
984
|
+
if (mainVideo.src) {
|
|
985
|
+
this.previewVideo.src = mainVideo.src;
|
|
986
|
+
} else {
|
|
987
|
+
const source = mainVideo.querySelector('source');
|
|
988
|
+
if (source) {
|
|
989
|
+
this.previewVideo.src = source.src;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Handle errors gracefully
|
|
994
|
+
this.previewVideo.addEventListener('error', () => {
|
|
995
|
+
this.player.log('Preview video failed to load', 'warn');
|
|
996
|
+
this.previewSupported = false;
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
// Append to player container (hidden)
|
|
1000
|
+
if (this.player.container) {
|
|
1001
|
+
this.player.container.appendChild(this.previewVideo);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Generate preview thumbnail for a specific time
|
|
1008
|
+
* @param {number} time - Time in seconds
|
|
1009
|
+
* @returns {Promise<string>} Data URL of the thumbnail
|
|
1010
|
+
*/
|
|
1011
|
+
async generatePreviewThumbnail(time) {
|
|
1012
|
+
if (!this.previewSupported || !this.previewVideo) {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Check cache first
|
|
1017
|
+
const cacheKey = Math.floor(time);
|
|
1018
|
+
if (this.previewThumbnailCache.has(cacheKey)) {
|
|
1019
|
+
return this.previewThumbnailCache.get(cacheKey);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Use shared frame capture utility
|
|
1023
|
+
// Don't restore state since preview video is always muted and hidden
|
|
1024
|
+
const dataURL = await captureVideoFrame(this.previewVideo, time, {
|
|
1025
|
+
restoreState: false,
|
|
1026
|
+
quality: 0.8,
|
|
1027
|
+
maxWidth: 160,
|
|
1028
|
+
maxHeight: 90
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
if (dataURL) {
|
|
1032
|
+
// Cache the thumbnail (limit cache size)
|
|
1033
|
+
if (this.previewThumbnailCache.size > 20) {
|
|
1034
|
+
const firstKey = this.previewThumbnailCache.keys().next().value;
|
|
1035
|
+
this.previewThumbnailCache.delete(firstKey);
|
|
1036
|
+
}
|
|
1037
|
+
this.previewThumbnailCache.set(cacheKey, dataURL);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return dataURL;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Update preview thumbnail display
|
|
1045
|
+
* @param {number} time - Time in seconds
|
|
1046
|
+
*/
|
|
1047
|
+
async updatePreviewThumbnail(time) {
|
|
1048
|
+
if (!this.previewSupported) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Clear any pending updates
|
|
1053
|
+
if (this.previewThumbnailTimeout) {
|
|
1054
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Debounce thumbnail generation to avoid excessive seeking
|
|
1058
|
+
this.previewThumbnailTimeout = setTimeout(async () => {
|
|
1059
|
+
const thumbnail = await this.generatePreviewThumbnail(time);
|
|
1060
|
+
if (thumbnail && this.controls.progressPreview) {
|
|
1061
|
+
this.controls.progressPreview.style.backgroundImage = `url(${thumbnail})`;
|
|
1062
|
+
this.controls.progressPreview.style.display = 'block';
|
|
1063
|
+
}
|
|
1064
|
+
this.currentPreviewTime = time;
|
|
1065
|
+
}, 100);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
927
1068
|
setupProgressBarEvents() {
|
|
928
1069
|
const progress = this.controls.progress;
|
|
929
1070
|
|
|
@@ -958,15 +1099,26 @@ export class ControlBar {
|
|
|
958
1099
|
progress.addEventListener('mousemove', (e) => {
|
|
959
1100
|
if (!this.isDraggingProgress) {
|
|
960
1101
|
const {time} = updateProgress(e.clientX);
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1102
|
+
const rect = progress.getBoundingClientRect();
|
|
1103
|
+
const left = e.clientX - rect.left;
|
|
1104
|
+
|
|
1105
|
+
// Update tooltip time text
|
|
1106
|
+
this.controls.progressTooltipTime.textContent = TimeUtils.formatTime(time);
|
|
1107
|
+
|
|
1108
|
+
// Update tooltip position
|
|
1109
|
+
this.controls.progressTooltip.style.left = `${left}px`;
|
|
964
1110
|
this.controls.progressTooltip.style.display = 'block';
|
|
1111
|
+
|
|
1112
|
+
// Update preview thumbnail
|
|
1113
|
+
this.updatePreviewThumbnail(time);
|
|
965
1114
|
}
|
|
966
1115
|
});
|
|
967
1116
|
|
|
968
1117
|
progress.addEventListener('mouseleave', () => {
|
|
969
1118
|
this.controls.progressTooltip.style.display = 'none';
|
|
1119
|
+
if (this.previewThumbnailTimeout) {
|
|
1120
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
1121
|
+
}
|
|
970
1122
|
});
|
|
971
1123
|
|
|
972
1124
|
// Keyboard navigation
|
|
@@ -3324,6 +3476,23 @@ export class ControlBar {
|
|
|
3324
3476
|
this.element.style.display = 'none';
|
|
3325
3477
|
}
|
|
3326
3478
|
|
|
3479
|
+
/**
|
|
3480
|
+
* Cleanup preview thumbnail resources
|
|
3481
|
+
*/
|
|
3482
|
+
cleanupPreviewThumbnail() {
|
|
3483
|
+
if (this.previewThumbnailTimeout) {
|
|
3484
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
3485
|
+
this.previewThumbnailTimeout = null;
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
if (this.previewVideo && this.previewVideo.parentNode) {
|
|
3489
|
+
this.previewVideo.parentNode.removeChild(this.previewVideo);
|
|
3490
|
+
this.previewVideo = null;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
this.previewThumbnailCache.clear();
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3327
3496
|
destroy() {
|
|
3328
3497
|
if (this.hideTimeout) {
|
|
3329
3498
|
clearTimeout(this.hideTimeout);
|
|
@@ -3333,6 +3502,9 @@ export class ControlBar {
|
|
|
3333
3502
|
this.overflowResizeObserver.disconnect();
|
|
3334
3503
|
}
|
|
3335
3504
|
|
|
3505
|
+
// Cleanup preview thumbnail resources
|
|
3506
|
+
this.cleanupPreviewThumbnail();
|
|
3507
|
+
|
|
3336
3508
|
if (this.element && this.element.parentNode) {
|
|
3337
3509
|
this.element.parentNode.removeChild(this.element);
|
|
3338
3510
|
}
|
|
@@ -750,15 +750,13 @@ export class TranscriptManager {
|
|
|
750
750
|
const metadataTrack = textTracks.find(track => track.kind === 'metadata');
|
|
751
751
|
|
|
752
752
|
// We need at least one track type available for display
|
|
753
|
-
//
|
|
754
|
-
|
|
755
|
-
if (!captionTrack && !hasDescriptionTrack && !metadataTrack) {
|
|
753
|
+
// (captions, descriptions, or metadata - though metadata is not displayed)
|
|
754
|
+
if (!captionTrack && !descriptionTrack && !metadataTrack) {
|
|
756
755
|
this.showNoTranscriptMessage();
|
|
757
756
|
return;
|
|
758
757
|
}
|
|
759
758
|
|
|
760
|
-
// Enable all tracks to load cues
|
|
761
|
-
// This ensures descriptions are ready when audio description is enabled
|
|
759
|
+
// Enable all tracks to load cues so they're available for the transcript
|
|
762
760
|
const tracksToLoad = [captionTrack, descriptionTrack, metadataTrack].filter(Boolean);
|
|
763
761
|
tracksToLoad.forEach(track => {
|
|
764
762
|
if (track.mode === 'disabled') {
|
|
@@ -806,8 +804,10 @@ export class TranscriptManager {
|
|
|
806
804
|
});
|
|
807
805
|
}
|
|
808
806
|
|
|
809
|
-
//
|
|
810
|
-
|
|
807
|
+
// Always include description cues in transcript for completeness
|
|
808
|
+
// (Audio description toggle only controls whether they're read aloud,
|
|
809
|
+
// but the transcript should always show all content for accessibility)
|
|
810
|
+
if (descriptionTrack && descriptionTrack.cues) {
|
|
811
811
|
Array.from(descriptionTrack.cues).forEach(cue => {
|
|
812
812
|
allCues.push({ cue, type: 'description' });
|
|
813
813
|
});
|