vidply 1.0.31 → 1.0.33

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 (52) hide show
  1. package/README.md +708 -708
  2. package/dist/dev/{vidply.HLSRenderer-ENLZE4QS.js → vidply.HLSRenderer-LIFBU6UD.js} +61 -10
  3. package/dist/dev/vidply.HLSRenderer-LIFBU6UD.js.map +7 -0
  4. package/dist/dev/{vidply.HTML5Renderer-6SBDI6S2.js → vidply.HTML5Renderer-YWMVYWFS.js} +2 -2
  5. package/dist/dev/{vidply.TranscriptManager-T677KF4N.js → vidply.TranscriptManager-R7NJRU7E.js} +2 -2
  6. package/dist/dev/{vidply.chunk-GS2JX5RQ.js → vidply.chunk-PMRKJBGH.js} +5 -2
  7. package/dist/dev/vidply.chunk-PMRKJBGH.js.map +7 -0
  8. package/dist/dev/{vidply.chunk-W2LSBD6Y.js → vidply.chunk-UVO24MXU.js} +33 -3
  9. package/dist/dev/vidply.chunk-UVO24MXU.js.map +7 -0
  10. package/dist/dev/{vidply.de-SNL6AJ4D.js → vidply.de-CEGBLV67.js} +4 -1
  11. package/dist/dev/vidply.de-CEGBLV67.js.map +7 -0
  12. package/dist/dev/vidply.esm.js +374 -64
  13. package/dist/dev/vidply.esm.js.map +2 -2
  14. package/dist/legacy/vidply.js +483 -71
  15. package/dist/legacy/vidply.js.map +3 -3
  16. package/dist/legacy/vidply.min.js +1 -1
  17. package/dist/legacy/vidply.min.meta.json +15 -15
  18. package/dist/prod/vidply.HLSRenderer-ESR6NAMI.min.js +6 -0
  19. package/dist/prod/{vidply.HTML5Renderer-KKW3OLHM.min.js → vidply.HTML5Renderer-6ROXQSQY.min.js} +1 -1
  20. package/dist/prod/{vidply.TranscriptManager-WFZSW6NR.min.js → vidply.TranscriptManager-B65LKXGG.min.js} +1 -1
  21. package/dist/prod/vidply.chunk-7HVHEUHH.min.js +6 -0
  22. package/dist/prod/vidply.chunk-IQKD4GUB.min.js +6 -0
  23. package/dist/prod/vidply.de-IHKC573T.min.js +6 -0
  24. package/dist/prod/vidply.esm.min.js +9 -9
  25. package/dist/vidply.esm.min.meta.json +33 -33
  26. package/package.json +1 -1
  27. package/src/controls/ControlBar.js +120 -71
  28. package/src/core/Player.js +5087 -4868
  29. package/src/features/PlaylistManager.js +1669 -1511
  30. package/src/i18n/languages/de.js +3 -0
  31. package/src/i18n/languages/en.js +3 -0
  32. package/src/renderers/HLSRenderer.js +77 -8
  33. package/src/renderers/HTML5Renderer.js +43 -5
  34. package/dist/dev/vidply.HLSRenderer-ENLZE4QS.js.map +0 -7
  35. package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js +0 -266
  36. package/dist/dev/vidply.HLSRenderer-UMPUDSYL.js.map +0 -7
  37. package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js +0 -12
  38. package/dist/dev/vidply.HTML5Renderer-FXBZQL6Y.js.map +0 -7
  39. package/dist/dev/vidply.chunk-BCOFCT6U.js +0 -246
  40. package/dist/dev/vidply.chunk-BCOFCT6U.js.map +0 -7
  41. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +0 -7
  42. package/dist/dev/vidply.chunk-W2LSBD6Y.js.map +0 -7
  43. package/dist/dev/vidply.de-SNL6AJ4D.js.map +0 -7
  44. package/dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js +0 -6
  45. package/dist/prod/vidply.HLSRenderer-CBXZ4RF2.min.js +0 -6
  46. package/dist/prod/vidply.HTML5Renderer-MY7XDV7R.min.js +0 -6
  47. package/dist/prod/vidply.chunk-34RH2THY.min.js +0 -6
  48. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +0 -6
  49. package/dist/prod/vidply.chunk-OXXPY2XB.min.js +0 -6
  50. package/dist/prod/vidply.de-FR3XX54P.min.js +0 -6
  51. /package/dist/dev/{vidply.HTML5Renderer-6SBDI6S2.js.map → vidply.HTML5Renderer-YWMVYWFS.js.map} +0 -0
  52. /package/dist/dev/{vidply.TranscriptManager-T677KF4N.js.map → vidply.TranscriptManager-R7NJRU7E.js.map} +0 -0
@@ -11,12 +11,12 @@
11
11
  "format": "esm"
12
12
  },
13
13
  "src/i18n/languages/en.js": {
14
- "bytes": 7390,
14
+ "bytes": 7637,
15
15
  "imports": [],
16
16
  "format": "esm"
17
17
  },
18
18
  "src/i18n/languages/de.js": {
19
- "bytes": 9108,
19
+ "bytes": 9427,
20
20
  "imports": [],
21
21
  "format": "esm"
22
22
  },
@@ -109,7 +109,7 @@
109
109
  "format": "esm"
110
110
  },
111
111
  "src/controls/ControlBar.js": {
112
- "bytes": 144875,
112
+ "bytes": 147152,
113
113
  "imports": [
114
114
  {
115
115
  "path": "src/utils/DOMUtils.js",
@@ -186,7 +186,7 @@
186
186
  "format": "esm"
187
187
  },
188
188
  "src/renderers/HTML5Renderer.js": {
189
- "bytes": 9645,
189
+ "bytes": 10921,
190
190
  "imports": [],
191
191
  "format": "esm"
192
192
  },
@@ -346,7 +346,7 @@
346
346
  "format": "esm"
347
347
  },
348
348
  "src/renderers/HLSRenderer.js": {
349
- "bytes": 9900,
349
+ "bytes": 12151,
350
350
  "imports": [
351
351
  {
352
352
  "path": "src/renderers/HTML5Renderer.js",
@@ -362,7 +362,7 @@
362
362
  "format": "esm"
363
363
  },
364
364
  "src/core/Player.js": {
365
- "bytes": 209146,
365
+ "bytes": 224782,
366
366
  "imports": [
367
367
  {
368
368
  "path": "src/utils/EventEmitter.js",
@@ -473,7 +473,7 @@
473
473
  "format": "esm"
474
474
  },
475
475
  "src/features/PlaylistManager.js": {
476
- "bytes": 48411,
476
+ "bytes": 57201,
477
477
  "imports": [
478
478
  {
479
479
  "path": "src/utils/DOMUtils.js",
@@ -529,10 +529,10 @@
529
529
  },
530
530
  "bytes": 4730
531
531
  },
532
- "dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js": {
532
+ "dist/prod/vidply.HLSRenderer-ESR6NAMI.min.js": {
533
533
  "imports": [
534
534
  {
535
- "path": "dist/prod/vidply.HTML5Renderer-KKW3OLHM.min.js",
535
+ "path": "dist/prod/vidply.HTML5Renderer-6ROXQSQY.min.js",
536
536
  "kind": "dynamic-import"
537
537
  }
538
538
  ],
@@ -542,10 +542,10 @@
542
542
  "entryPoint": "src/renderers/HLSRenderer.js",
543
543
  "inputs": {
544
544
  "src/renderers/HLSRenderer.js": {
545
- "bytesInOutput": 5781
545
+ "bytesInOutput": 6514
546
546
  }
547
547
  },
548
- "bytes": 5925
548
+ "bytes": 6658
549
549
  },
550
550
  "dist/prod/vidply.SoundCloudRenderer-MOR2CUFH.min.js": {
551
551
  "imports": [],
@@ -564,15 +564,15 @@
564
564
  "dist/prod/vidply.esm.min.js": {
565
565
  "imports": [
566
566
  {
567
- "path": "dist/prod/vidply.chunk-34RH2THY.min.js",
567
+ "path": "dist/prod/vidply.chunk-7HVHEUHH.min.js",
568
568
  "kind": "import-statement"
569
569
  },
570
570
  {
571
- "path": "dist/prod/vidply.chunk-LGTJRPUL.min.js",
571
+ "path": "dist/prod/vidply.chunk-IQKD4GUB.min.js",
572
572
  "kind": "import-statement"
573
573
  },
574
574
  {
575
- "path": "dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js",
575
+ "path": "dist/prod/vidply.TranscriptManager-B65LKXGG.min.js",
576
576
  "kind": "dynamic-import"
577
577
  },
578
578
  {
@@ -584,7 +584,7 @@
584
584
  "kind": "dynamic-import"
585
585
  },
586
586
  {
587
- "path": "dist/prod/vidply.HLSRenderer-3CG7BZKA.min.js",
587
+ "path": "dist/prod/vidply.HLSRenderer-ESR6NAMI.min.js",
588
588
  "kind": "dynamic-import"
589
589
  },
590
590
  {
@@ -609,7 +609,7 @@
609
609
  "bytesInOutput": 1009
610
610
  },
611
611
  "src/controls/ControlBar.js": {
612
- "bytesInOutput": 61881
612
+ "bytesInOutput": 63355
613
613
  },
614
614
  "src/controls/CaptionManager.js": {
615
615
  "bytesInOutput": 7279
@@ -624,18 +624,18 @@
624
624
  "bytesInOutput": 19117
625
625
  },
626
626
  "src/core/Player.js": {
627
- "bytesInOutput": 76504
627
+ "bytesInOutput": 80485
628
628
  },
629
629
  "src/features/PlaylistManager.js": {
630
- "bytesInOutput": 21345
630
+ "bytesInOutput": 24268
631
631
  },
632
632
  "src/index.js": {
633
633
  "bytesInOutput": 1869
634
634
  }
635
635
  },
636
- "bytes": 203700
636
+ "bytes": 212078
637
637
  },
638
- "dist/prod/vidply.de-FR3XX54P.min.js": {
638
+ "dist/prod/vidply.de-IHKC573T.min.js": {
639
639
  "imports": [],
640
640
  "exports": [
641
641
  "de"
@@ -643,10 +643,10 @@
643
643
  "entryPoint": "src/i18n/languages/de.js",
644
644
  "inputs": {
645
645
  "src/i18n/languages/de.js": {
646
- "bytesInOutput": 7226
646
+ "bytesInOutput": 7512
647
647
  }
648
648
  },
649
- "bytes": 7361
649
+ "bytes": 7647
650
650
  },
651
651
  "dist/prod/vidply.es-3IJCQLJ7.min.js": {
652
652
  "imports": [],
@@ -687,10 +687,10 @@
687
687
  },
688
688
  "bytes": 8204
689
689
  },
690
- "dist/prod/vidply.HTML5Renderer-KKW3OLHM.min.js": {
690
+ "dist/prod/vidply.HTML5Renderer-6ROXQSQY.min.js": {
691
691
  "imports": [
692
692
  {
693
- "path": "dist/prod/vidply.chunk-34RH2THY.min.js",
693
+ "path": "dist/prod/vidply.chunk-7HVHEUHH.min.js",
694
694
  "kind": "import-statement"
695
695
  }
696
696
  ],
@@ -701,22 +701,22 @@
701
701
  "inputs": {},
702
702
  "bytes": 192
703
703
  },
704
- "dist/prod/vidply.chunk-34RH2THY.min.js": {
704
+ "dist/prod/vidply.chunk-7HVHEUHH.min.js": {
705
705
  "imports": [],
706
706
  "exports": [
707
707
  "a"
708
708
  ],
709
709
  "inputs": {
710
710
  "src/renderers/HTML5Renderer.js": {
711
- "bytesInOutput": 4974
711
+ "bytesInOutput": 5387
712
712
  }
713
713
  },
714
- "bytes": 5108
714
+ "bytes": 5521
715
715
  },
716
- "dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js": {
716
+ "dist/prod/vidply.TranscriptManager-B65LKXGG.min.js": {
717
717
  "imports": [
718
718
  {
719
- "path": "dist/prod/vidply.chunk-LGTJRPUL.min.js",
719
+ "path": "dist/prod/vidply.chunk-IQKD4GUB.min.js",
720
720
  "kind": "import-statement"
721
721
  }
722
722
  ],
@@ -731,10 +731,10 @@
731
731
  },
732
732
  "bytes": 43042
733
733
  },
734
- "dist/prod/vidply.chunk-LGTJRPUL.min.js": {
734
+ "dist/prod/vidply.chunk-IQKD4GUB.min.js": {
735
735
  "imports": [
736
736
  {
737
- "path": "dist/prod/vidply.de-FR3XX54P.min.js",
737
+ "path": "dist/prod/vidply.de-IHKC573T.min.js",
738
738
  "kind": "dynamic-import"
739
739
  },
740
740
  {
@@ -771,7 +771,7 @@
771
771
  "bytesInOutput": 2348
772
772
  },
773
773
  "src/i18n/languages/en.js": {
774
- "bytesInOutput": 6159
774
+ "bytesInOutput": 6385
775
775
  },
776
776
  "src/i18n/translations.js": {
777
777
  "bytesInOutput": 340
@@ -801,7 +801,7 @@
801
801
  "bytesInOutput": 776
802
802
  }
803
803
  },
804
- "bytes": 40251
804
+ "bytes": 40477
805
805
  },
806
806
  "dist/prod/vidply.YouTubeRenderer-MFC2GMAC.min.js": {
807
807
  "imports": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidply",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "description": "Universal, accessible video & audio player with ES6 modules",
5
5
  "type": "module",
6
6
  "main": "dist/prod/vidply.esm.min.js",
@@ -27,6 +27,10 @@ export class ControlBar {
27
27
  init() {
28
28
  this.createElement();
29
29
  this.createControls();
30
+ // Ensure time UI reflects any prefilled state (e.g. initialDuration)
31
+ // even when media metadata is deferred and 'loadedmetadata' won't fire yet.
32
+ this.updateDuration();
33
+ this.updateProgress();
30
34
  this.attachEvents();
31
35
  this.setupAutoHide();
32
36
  this.setupOverflowDetection();
@@ -715,7 +719,22 @@ export class ControlBar {
715
719
  }
716
720
 
717
721
  // 4. Playback speed button
718
- if (this.player.options.speedButton) {
722
+ // IMPORTANT: Don't rely on renderer.constructor.name here.
723
+ // In production builds, class names are minified (e.g. "class s"), which would break the check.
724
+ // Instead, detect HLS by the current source URL.
725
+ const src = this.player.currentSource
726
+ || this.player.element?.getAttribute?.('src')
727
+ || this.player.element?.currentSrc
728
+ || this.player.element?.src
729
+ || this.player.element?.querySelector?.('source')?.getAttribute?.('src')
730
+ || this.player.element?.querySelector?.('source')?.src
731
+ || '';
732
+ const isHlsSource = typeof src === 'string' && src.includes('.m3u8');
733
+ const isVideoElement = this.player.element?.tagName?.toLowerCase() === 'video';
734
+ const hideSpeedForThisPlayer =
735
+ (!!this.player.options.hideSpeedForHls && isHlsSource)
736
+ || (!!this.player.options.hideSpeedForHlsVideo && isHlsSource && isVideoElement);
737
+ if (this.player.options.speedButton && !hideSpeedForThisPlayer) {
719
738
  const btn = this.createSpeedButton();
720
739
  btn.dataset.overflowPriority = '1';
721
740
  btn.dataset.overflowPriorityMobile = '3';
@@ -831,22 +850,44 @@ export class ControlBar {
831
850
 
832
851
  // Helper methods to check for available features
833
852
  hasChapterTracks() {
853
+ // 1) Prefer already-loaded TextTracks (fast + accurate)
834
854
  const textTracks = this.player.element.textTracks;
835
855
  for (let i = 0; i < textTracks.length; i++) {
836
- if (textTracks[i].kind === 'chapters') {
837
- return true;
856
+ if (textTracks[i].kind === 'chapters') return true;
838
857
  }
858
+
859
+ // 2) Fallback to DOM <track> elements (works before tracks are fully loaded)
860
+ const trackEls = Array.from(this.player.element.querySelectorAll('track[kind="chapters"]'));
861
+ if (trackEls.length > 0) return true;
862
+
863
+ // 3) Playlist metadata fallback (works even when we intentionally defer loading)
864
+ const current = this.player.playlistManager?.getCurrentTrack?.();
865
+ if (current?.tracks && Array.isArray(current.tracks)) {
866
+ return current.tracks.some(t => t?.kind === 'chapters');
839
867
  }
868
+
840
869
  return false;
841
870
  }
842
871
 
843
872
  hasCaptionTracks() {
873
+ // 1) Prefer already-loaded TextTracks
844
874
  const textTracks = this.player.element.textTracks;
845
875
  for (let i = 0; i < textTracks.length; i++) {
846
- if (textTracks[i].kind === 'captions' || textTracks[i].kind === 'subtitles') {
876
+ if (textTracks[i].kind === 'captions' || textTracks[i].kind === 'subtitles') return true;
877
+ }
878
+
879
+ // 2) Fallback to DOM <track> elements
880
+ const trackEls = Array.from(this.player.element.querySelectorAll('track'));
881
+ if (trackEls.some(el => (el.getAttribute('kind') === 'captions' || el.getAttribute('kind') === 'subtitles'))) {
847
882
  return true;
848
883
  }
884
+
885
+ // 3) Playlist metadata fallback
886
+ const current = this.player.playlistManager?.getCurrentTrack?.();
887
+ if (current?.tracks && Array.isArray(current.tracks)) {
888
+ return current.tracks.some(t => t?.kind === 'captions' || t?.kind === 'subtitles');
849
889
  }
890
+
850
891
  return false;
851
892
  }
852
893
 
@@ -948,6 +989,8 @@ export class ControlBar {
948
989
  this.currentPreviewTime = null;
949
990
  this.previewThumbnailTimeout = null;
950
991
  this.previewSupported = false;
992
+ this.previewVideoReady = false;
993
+ this.previewVideoInitialized = false;
951
994
 
952
995
  // Check if preview is supported (HTML5 video only)
953
996
  // Check if element is a video
@@ -957,80 +1000,75 @@ export class ControlBar {
957
1000
  return;
958
1001
  }
959
1002
 
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
- // Note: Don't rely on constructor.name as it's minified in production builds
1003
+ // IMPORTANT: do NOT create/load the preview video until the user has started playback at least once.
1004
+ // Otherwise we'd trigger heavy MP4 network traffic just by hovering the progress bar.
1005
+ }
1006
+
1007
+ /**
1008
+ * Lazily create the hidden preview video (only after playback started once)
1009
+ */
1010
+ ensurePreviewVideoInitialized() {
1011
+ if (this.previewVideoInitialized) return;
1012
+ if (!this.player?.state?.hasStartedPlayback) return;
1013
+
963
1014
  const renderer = this.player.renderer;
964
1015
  const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === 'VIDEO';
965
-
966
- // Check if it's HTML5Renderer by checking:
967
- // 1. Has media property that is a video element
968
- // 2. Media is the same as player.element (HTML5Renderer sets this.media = player.element)
969
- // 3. Doesn't have hls property (HLSRenderer has hls property)
970
- // 4. Has seek method (all renderers have this, but combined with above checks it's reliable)
971
- const isHTML5Renderer = hasVideoMedia &&
1016
+ const isHTML5Renderer = hasVideoMedia &&
972
1017
  renderer.media === this.player.element &&
973
1018
  !renderer.hls &&
974
1019
  typeof renderer.seek === 'function';
975
-
1020
+
976
1021
  this.previewSupported = isHTML5Renderer && hasVideoMedia;
1022
+ if (!this.previewSupported) return;
977
1023
 
978
- if (this.previewSupported) {
979
- // Create a hidden video element for capturing frames
980
- this.previewVideo = document.createElement('video');
981
- this.previewVideo.muted = true;
982
- this.previewVideo.preload = 'auto'; // Need more than metadata to capture frames
983
- this.previewVideo.playsInline = true;
984
- this.previewVideo.style.position = 'absolute';
985
- this.previewVideo.style.visibility = 'hidden';
986
- this.previewVideo.style.width = '1px';
987
- this.previewVideo.style.height = '1px';
988
- this.previewVideo.style.top = '-9999px';
989
-
990
- // Copy source and attributes from main video
991
- const mainVideo = renderer.media || this.player.element;
992
- let videoSrc = null;
993
-
994
- if (mainVideo.src) {
995
- videoSrc = mainVideo.src;
996
- } else {
997
- const source = mainVideo.querySelector('source');
998
- if (source) {
999
- videoSrc = source.src;
1000
- }
1001
- }
1002
-
1003
- if (!videoSrc) {
1004
- this.player.log('No video source found for preview', 'warn');
1005
- this.previewSupported = false;
1006
- return;
1007
- }
1008
-
1009
- // Copy crossOrigin if set (important for CORS)
1010
- if (mainVideo.crossOrigin) {
1011
- this.previewVideo.crossOrigin = mainVideo.crossOrigin;
1012
- }
1013
-
1014
- // Handle errors gracefully
1015
- this.previewVideo.addEventListener('error', (e) => {
1016
- this.player.log('Preview video failed to load:', e, 'warn');
1017
- this.previewSupported = false;
1018
- });
1019
-
1020
- // Wait for metadata to be loaded before using
1021
- this.previewVideo.addEventListener('loadedmetadata', () => {
1022
- this.previewVideoReady = true;
1023
- }, { once: true });
1024
-
1025
- // Append to player container (hidden) BEFORE setting src
1026
- if (this.player.container) {
1027
- this.player.container.appendChild(this.previewVideo);
1024
+ const mainVideo = renderer.media || this.player.element;
1025
+ let videoSrc = null;
1026
+ if (mainVideo.src) {
1027
+ videoSrc = mainVideo.src;
1028
+ } else {
1029
+ const source = mainVideo.querySelector('source');
1030
+ if (source) {
1031
+ videoSrc = source.src;
1028
1032
  }
1029
-
1030
- // Set source after appending to DOM
1031
- this.previewVideo.src = videoSrc;
1032
- this.previewVideoReady = false;
1033
1033
  }
1034
+
1035
+ if (!videoSrc) {
1036
+ this.player.log('No video source found for preview', 'warn');
1037
+ this.previewSupported = false;
1038
+ return;
1039
+ }
1040
+
1041
+ // Create a hidden video element for capturing frames
1042
+ this.previewVideo = document.createElement('video');
1043
+ this.previewVideo.muted = true;
1044
+ this.previewVideo.preload = 'auto'; // Need more than metadata to capture frames
1045
+ this.previewVideo.playsInline = true;
1046
+ this.previewVideo.style.position = 'absolute';
1047
+ this.previewVideo.style.visibility = 'hidden';
1048
+ this.previewVideo.style.width = '1px';
1049
+ this.previewVideo.style.height = '1px';
1050
+ this.previewVideo.style.top = '-9999px';
1051
+
1052
+ if (mainVideo.crossOrigin) {
1053
+ this.previewVideo.crossOrigin = mainVideo.crossOrigin;
1054
+ }
1055
+
1056
+ this.previewVideo.addEventListener('error', (e) => {
1057
+ this.player.log('Preview video failed to load:', e, 'warn');
1058
+ this.previewSupported = false;
1059
+ });
1060
+
1061
+ this.previewVideo.addEventListener('loadedmetadata', () => {
1062
+ this.previewVideoReady = true;
1063
+ }, { once: true });
1064
+
1065
+ if (this.player.container) {
1066
+ this.player.container.appendChild(this.previewVideo);
1067
+ }
1068
+
1069
+ this.previewVideo.src = videoSrc;
1070
+ this.previewVideoReady = false;
1071
+ this.previewVideoInitialized = true;
1034
1072
  }
1035
1073
 
1036
1074
  /**
@@ -1203,10 +1241,21 @@ export class ControlBar {
1203
1241
  // Update tooltip position
1204
1242
  this.controls.progressTooltip.style.left = `${left}px`;
1205
1243
  this.controls.progressTooltip.style.display = 'block';
1206
-
1207
- // Update preview thumbnail (only if supported)
1244
+
1245
+ // Only show preview thumbnails after the user has started playback at least once.
1246
+ // Before that, show just the timestamp (no empty preview box).
1247
+ if (!this.player?.state?.hasStartedPlayback) {
1248
+ if (this.controls.progressPreview) {
1249
+ this.controls.progressPreview.style.display = 'none';
1250
+ }
1251
+ return;
1252
+ }
1253
+
1254
+ this.ensurePreviewVideoInitialized();
1208
1255
  if (this.previewSupported) {
1209
1256
  this.updatePreviewThumbnail(time);
1257
+ } else if (this.controls.progressPreview) {
1258
+ this.controls.progressPreview.style.display = 'none';
1210
1259
  }
1211
1260
  }
1212
1261
  });