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.
Files changed (75) hide show
  1. package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js +266 -0
  2. package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js.map +7 -0
  3. package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js +12 -0
  4. package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js.map +7 -0
  5. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
  6. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
  7. package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
  8. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
  9. package/dist/dev/vidply.chunk-W2LSBD6Y.js +251 -0
  10. package/dist/dev/vidply.chunk-W2LSBD6Y.js.map +7 -0
  11. package/dist/dev/vidply.esm.js +1880 -258
  12. package/dist/dev/vidply.esm.js.map +4 -4
  13. package/dist/legacy/vidply.js +2056 -365
  14. package/dist/legacy/vidply.js.map +4 -4
  15. package/dist/legacy/vidply.min.js +1 -1
  16. package/dist/legacy/vidply.min.meta.json +111 -25
  17. package/dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js +6 -0
  18. package/dist/prod/vidply.HTML5Renderer-KKW3OLHM.min.js +6 -0
  19. package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
  20. package/dist/prod/vidply.chunk-34RH2THY.min.js +6 -0
  21. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
  22. package/dist/prod/vidply.esm.min.js +8 -8
  23. package/dist/vidply.css +20 -1
  24. package/dist/vidply.esm.min.meta.json +120 -34
  25. package/dist/vidply.min.css +1 -1
  26. package/package.json +2 -2
  27. package/src/controls/ControlBar.js +182 -10
  28. package/src/controls/TranscriptManager.js +7 -7
  29. package/src/core/AudioDescriptionManager.js +701 -0
  30. package/src/core/Player.js +203 -256
  31. package/src/core/SignLanguageManager.js +1134 -0
  32. package/src/renderers/HTML5Renderer.js +7 -0
  33. package/src/styles/vidply.css +20 -1
  34. package/src/utils/DOMUtils.js +153 -114
  35. package/src/utils/MenuFactory.js +374 -0
  36. package/src/utils/VideoFrameCapture.js +110 -0
  37. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
  38. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
  39. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
  40. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
  41. package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
  42. package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
  43. package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
  44. package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
  45. package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
  46. package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
  47. package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
  48. package/dist/dev/vidply.de-THBIMP4S.js +0 -180
  49. package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
  50. package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
  51. package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
  52. package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
  53. package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
  54. package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
  55. package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
  56. package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
  57. package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
  58. package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
  59. package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
  60. package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
  61. package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
  62. package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
  63. package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
  64. package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
  65. package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
  66. package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
  67. package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
  68. package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
  69. package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
  70. package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
  71. package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
  72. package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
  73. package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
  74. package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
  75. 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 isMobile = this.isMobile();
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 (isMobile) {
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
- // Update tooltip text content instead of aria-label (divs shouldn't have aria-label)
962
- this.controls.progressTooltip.textContent = TimeUtils.formatTime(time);
963
- this.controls.progressTooltip.style.left = `${e.clientX - progress.getBoundingClientRect().left}px`;
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
- // Description tracks are only included if audio description is enabled
754
- const hasDescriptionTrack = descriptionTrack && this.player.state.audioDescriptionEnabled;
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 (even if we won't display descriptions)
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
- // Only include description cues if audio description is enabled
810
- if (descriptionTrack && descriptionTrack.cues && this.player.state.audioDescriptionEnabled) {
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
  });