vidply 1.0.29 → 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.chunk-W2LSBD6Y.js +251 -0
- package/dist/dev/vidply.chunk-W2LSBD6Y.js.map +7 -0
- package/dist/dev/vidply.esm.js +263 -5
- package/dist/dev/vidply.esm.js.map +3 -3
- package/dist/legacy/vidply.js +266 -3
- package/dist/legacy/vidply.js.map +3 -3
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +25 -7
- package/dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js +6 -0
- package/dist/prod/vidply.HTML5Renderer-KKW3OLHM.min.js +6 -0
- package/dist/prod/vidply.chunk-34RH2THY.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +4 -4
- package/dist/vidply.css +20 -1
- package/dist/vidply.esm.min.meta.json +34 -16
- package/dist/vidply.min.css +1 -1
- package/package.json +2 -2
- package/src/controls/ControlBar.js +179 -3
- package/src/core/Player.js +4868 -4776
- package/src/renderers/HTML5Renderer.js +7 -0
- package/src/styles/vidply.css +20 -1
- package/src/utils/VideoFrameCapture.js +110 -0
|
@@ -8,6 +8,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
10
|
import {isMobile} from '../utils/PerformanceUtils.js';
|
|
11
|
+
import {captureVideoFrame} from '../utils/VideoFrameCapture.js';
|
|
11
12
|
|
|
12
13
|
export class ControlBar {
|
|
13
14
|
constructor(player) {
|
|
@@ -909,6 +910,21 @@ export class ControlBar {
|
|
|
909
910
|
className: `${this.player.options.classPrefix}-progress-tooltip`
|
|
910
911
|
});
|
|
911
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
|
+
|
|
912
928
|
progressContainer.appendChild(this.controls.buffered);
|
|
913
929
|
progressContainer.appendChild(this.controls.played);
|
|
914
930
|
this.controls.played.appendChild(this.controls.progressHandle);
|
|
@@ -916,10 +932,139 @@ export class ControlBar {
|
|
|
916
932
|
|
|
917
933
|
this.controls.progress = progressContainer;
|
|
918
934
|
|
|
935
|
+
// Initialize preview functionality
|
|
936
|
+
this.initPreviewThumbnail();
|
|
937
|
+
|
|
919
938
|
// Progress bar events
|
|
920
939
|
this.setupProgressBarEvents();
|
|
921
940
|
}
|
|
922
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
|
+
|
|
923
1068
|
setupProgressBarEvents() {
|
|
924
1069
|
const progress = this.controls.progress;
|
|
925
1070
|
|
|
@@ -954,15 +1099,26 @@ export class ControlBar {
|
|
|
954
1099
|
progress.addEventListener('mousemove', (e) => {
|
|
955
1100
|
if (!this.isDraggingProgress) {
|
|
956
1101
|
const {time} = updateProgress(e.clientX);
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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`;
|
|
960
1110
|
this.controls.progressTooltip.style.display = 'block';
|
|
1111
|
+
|
|
1112
|
+
// Update preview thumbnail
|
|
1113
|
+
this.updatePreviewThumbnail(time);
|
|
961
1114
|
}
|
|
962
1115
|
});
|
|
963
1116
|
|
|
964
1117
|
progress.addEventListener('mouseleave', () => {
|
|
965
1118
|
this.controls.progressTooltip.style.display = 'none';
|
|
1119
|
+
if (this.previewThumbnailTimeout) {
|
|
1120
|
+
clearTimeout(this.previewThumbnailTimeout);
|
|
1121
|
+
}
|
|
966
1122
|
});
|
|
967
1123
|
|
|
968
1124
|
// Keyboard navigation
|
|
@@ -3320,6 +3476,23 @@ export class ControlBar {
|
|
|
3320
3476
|
this.element.style.display = 'none';
|
|
3321
3477
|
}
|
|
3322
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
|
+
|
|
3323
3496
|
destroy() {
|
|
3324
3497
|
if (this.hideTimeout) {
|
|
3325
3498
|
clearTimeout(this.hideTimeout);
|
|
@@ -3329,6 +3502,9 @@ export class ControlBar {
|
|
|
3329
3502
|
this.overflowResizeObserver.disconnect();
|
|
3330
3503
|
}
|
|
3331
3504
|
|
|
3505
|
+
// Cleanup preview thumbnail resources
|
|
3506
|
+
this.cleanupPreviewThumbnail();
|
|
3507
|
+
|
|
3332
3508
|
if (this.element && this.element.parentNode) {
|
|
3333
3509
|
this.element.parentNode.removeChild(this.element);
|
|
3334
3510
|
}
|