vidply 1.0.32 → 1.0.34

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 (63) hide show
  1. package/README.md +7 -7
  2. package/dist/dev/{vidply.HLSRenderer-5MJZR4D2.js → vidply.HLSRenderer-YGWCAICA.js} +49 -4
  3. package/dist/dev/vidply.HLSRenderer-YGWCAICA.js.map +7 -0
  4. package/dist/dev/{vidply.HTML5Renderer-FXBZQL6Y.js → vidply.HTML5Renderer-PMNFHAKW.js} +3 -3
  5. package/dist/dev/{vidply.SoundCloudRenderer-CD7VJKNS.js → vidply.SoundCloudRenderer-RIA3QKP3.js} +2 -2
  6. package/dist/dev/{vidply.TranscriptManager-T677KF4N.js → vidply.TranscriptManager-T3BVTZHZ.js} +3 -3
  7. package/dist/dev/{vidply.VimeoRenderer-VPH4RNES.js → vidply.VimeoRenderer-DY2FG7LZ.js} +2 -2
  8. package/dist/dev/{vidply.YouTubeRenderer-6MGKEFTZ.js → vidply.YouTubeRenderer-EVXXE34A.js} +2 -2
  9. package/dist/dev/{vidply.chunk-GS2JX5RQ.js → vidply.chunk-74NJTDQI.js} +9 -6
  10. package/dist/dev/vidply.chunk-74NJTDQI.js.map +7 -0
  11. package/dist/dev/{vidply.chunk-W2LSBD6Y.js → vidply.chunk-IIN4G4UQ.js} +34 -4
  12. package/dist/dev/vidply.chunk-IIN4G4UQ.js.map +7 -0
  13. package/dist/dev/{vidply.de-SNL6AJ4D.js → vidply.de-YBEYEXBL.js} +5 -2
  14. package/dist/dev/vidply.de-YBEYEXBL.js.map +7 -0
  15. package/dist/dev/{vidply.es-2QCQKZ4U.js → vidply.es-QA4YSA5S.js} +2 -2
  16. package/dist/dev/vidply.esm.js +389 -82
  17. package/dist/dev/vidply.esm.js.map +2 -2
  18. package/dist/dev/{vidply.fr-FJAZRL4L.js → vidply.fr-LAM3XJZI.js} +2 -2
  19. package/dist/dev/{vidply.ja-2XQOW53T.js → vidply.ja-FTBFZD66.js} +2 -2
  20. package/dist/legacy/vidply.js +482 -79
  21. package/dist/legacy/vidply.js.map +3 -3
  22. package/dist/legacy/vidply.min.js +2 -2
  23. package/dist/legacy/vidply.min.meta.json +15 -15
  24. package/dist/prod/vidply.HLSRenderer-D2KTBEEI.min.js +6 -0
  25. package/dist/prod/{vidply.HTML5Renderer-KKW3OLHM.min.js → vidply.HTML5Renderer-ZSV6PDOH.min.js} +2 -2
  26. package/dist/prod/{vidply.SoundCloudRenderer-MOR2CUFH.min.js → vidply.SoundCloudRenderer-BFV5SSIU.min.js} +1 -1
  27. package/dist/prod/{vidply.TranscriptManager-WFZSW6NR.min.js → vidply.TranscriptManager-GPAOXEK4.min.js} +2 -2
  28. package/dist/prod/{vidply.VimeoRenderer-3HBMM2WR.min.js → vidply.VimeoRenderer-UQWHQ4LC.min.js} +1 -1
  29. package/dist/prod/{vidply.YouTubeRenderer-MFC2GMAC.min.js → vidply.YouTubeRenderer-K7A57ICA.min.js} +1 -1
  30. package/dist/prod/vidply.chunk-OM7DNW5P.min.js +6 -0
  31. package/dist/prod/vidply.chunk-SQVOYVKH.min.js +6 -0
  32. package/dist/prod/vidply.de-WCUZUF3T.min.js +6 -0
  33. package/dist/prod/{vidply.es-3IJCQLJ7.min.js → vidply.es-54CIIDMO.min.js} +1 -1
  34. package/dist/prod/vidply.esm.min.js +5 -5
  35. package/dist/prod/{vidply.fr-NC4VEAPH.min.js → vidply.fr-7FYGFFK2.min.js} +1 -1
  36. package/dist/prod/{vidply.ja-4ZC6ZQLV.min.js → vidply.ja-E4UTAURP.min.js} +1 -1
  37. package/dist/vidply.css +1 -1
  38. package/dist/vidply.esm.min.meta.json +45 -45
  39. package/dist/vidply.min.css +1 -1
  40. package/package.json +1 -1
  41. package/src/controls/ControlBar.js +104 -70
  42. package/src/core/Player.js +217 -3
  43. package/src/features/PlaylistManager.js +206 -36
  44. package/src/i18n/languages/de.js +3 -0
  45. package/src/i18n/languages/en.js +3 -0
  46. package/src/renderers/HLSRenderer.js +60 -1
  47. package/src/renderers/HTML5Renderer.js +43 -5
  48. package/dist/dev/vidply.HLSRenderer-5MJZR4D2.js.map +0 -7
  49. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +0 -7
  50. package/dist/dev/vidply.chunk-W2LSBD6Y.js.map +0 -7
  51. package/dist/dev/vidply.de-SNL6AJ4D.js.map +0 -7
  52. package/dist/prod/vidply.HLSRenderer-VWNJD2CB.min.js +0 -6
  53. package/dist/prod/vidply.chunk-34RH2THY.min.js +0 -6
  54. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +0 -6
  55. package/dist/prod/vidply.de-FR3XX54P.min.js +0 -6
  56. /package/dist/dev/{vidply.HTML5Renderer-FXBZQL6Y.js.map → vidply.HTML5Renderer-PMNFHAKW.js.map} +0 -0
  57. /package/dist/dev/{vidply.SoundCloudRenderer-CD7VJKNS.js.map → vidply.SoundCloudRenderer-RIA3QKP3.js.map} +0 -0
  58. /package/dist/dev/{vidply.TranscriptManager-T677KF4N.js.map → vidply.TranscriptManager-T3BVTZHZ.js.map} +0 -0
  59. /package/dist/dev/{vidply.VimeoRenderer-VPH4RNES.js.map → vidply.VimeoRenderer-DY2FG7LZ.js.map} +0 -0
  60. /package/dist/dev/{vidply.YouTubeRenderer-6MGKEFTZ.js.map → vidply.YouTubeRenderer-EVXXE34A.js.map} +0 -0
  61. /package/dist/dev/{vidply.es-2QCQKZ4U.js.map → vidply.es-QA4YSA5S.js.map} +0 -0
  62. /package/dist/dev/{vidply.fr-FJAZRL4L.js.map → vidply.fr-LAM3XJZI.js.map} +0 -0
  63. /package/dist/dev/{vidply.ja-2XQOW53T.js.map → vidply.ja-FTBFZD66.js.map} +0 -0
@@ -128,6 +128,9 @@ export class PlaylistManager {
128
128
  this.playlistPanel.parentNode.removeChild(this.playlistPanel);
129
129
  }
130
130
 
131
+ // Preserve existing player options so recreated players behave consistently
132
+ const preservedPlayerOptions = this.player?.options ? { ...this.player.options } : {};
133
+
131
134
  // Remove event listeners before destroying
132
135
  if (this.player) {
133
136
  this.player.off('ended', this.handleTrackEnd);
@@ -140,7 +143,9 @@ export class PlaylistManager {
140
143
 
141
144
  // Create new media element with appropriate type
142
145
  const mediaElement = document.createElement(elementType);
143
- mediaElement.setAttribute('preload', 'metadata');
146
+ // Respect configured preload (playlists should behave like single videos even with deferLoad)
147
+ const preloadValue = preservedPlayerOptions.preload || 'metadata';
148
+ mediaElement.setAttribute('preload', preloadValue);
144
149
 
145
150
  // For video elements with local media, set poster
146
151
  if (elementType === 'video' && track.poster &&
@@ -188,6 +193,8 @@ export class PlaylistManager {
188
193
  audioDescriptionDuration: track.audioDescriptionDuration || null,
189
194
  signLanguageSrc: track.signLanguageSrc || null
190
195
  };
196
+ // Merge back preserved options (so deferLoad/preload/etc remain active)
197
+ Object.assign(playerOptions, preservedPlayerOptions);
191
198
 
192
199
  this.player = new this.PlayerClass(mediaElement, playerOptions);
193
200
 
@@ -425,8 +432,11 @@ export class PlaylistManager {
425
432
  if (this.options.autoPlayFirst) {
426
433
  this.play(0);
427
434
  } else {
428
- // Load first track without playing
429
- this.loadTrack(0);
435
+ // Behave like a single video: load the first track (metadata/manifest)
436
+ // but do not start playback.
437
+ void this.loadTrack(0).catch(() => {
438
+ // ignore
439
+ });
430
440
  }
431
441
  }
432
442
 
@@ -436,6 +446,9 @@ export class PlaylistManager {
436
446
 
437
447
  /**
438
448
  * Load a track without playing
449
+ * This is the playlist equivalent of a "single video initialized but not started yet":
450
+ * it updates UI selection and loads the media into the player so metadata/manifests
451
+ * and feature managers can be ready, but it does not start playback.
439
452
  * @param {number} index - Track index
440
453
  */
441
454
  async loadTrack(index) {
@@ -446,12 +459,13 @@ export class PlaylistManager {
446
459
 
447
460
  const track = this.tracks[index];
448
461
 
462
+ // Always update UI immediately (poster, buttons, duration, etc.).
463
+ // Note: this is UI-only; actual media loading is performed by player.load() below.
464
+ this.selectTrack(index);
465
+
449
466
  // Set guard flag to prevent cascade of next() calls during track change
450
467
  this.isChangingTrack = true;
451
468
 
452
- // Update current index
453
- this.currentIndex = index;
454
-
455
469
  // Check if we should recreate the player for this track type
456
470
  if (this.options.recreatePlayers && this.hostElement && this.PlayerClass) {
457
471
  const currentMediaType = this.player ?
@@ -462,9 +476,8 @@ export class PlaylistManager {
462
476
  // Recreate if element type is different
463
477
  if (currentMediaType !== newElementType) {
464
478
  await this.recreatePlayerForTrack(track, false);
465
- // Update UI after recreation
466
- this.updateTrackInfo(track);
467
- this.updatePlaylistUI();
479
+ // Re-apply selection to the newly created player (poster/tracks/buttons)
480
+ this.selectTrack(index);
468
481
 
469
482
  // Emit event
470
483
  this.player.emit('playlisttrackchange', {
@@ -482,18 +495,25 @@ export class PlaylistManager {
482
495
  }
483
496
 
484
497
  // Load track into player (normal path)
485
- this.player.load({
498
+ const loadPromise = this.player.load({
486
499
  src: track.src,
487
500
  type: track.type,
488
501
  poster: track.poster,
489
502
  tracks: track.tracks || [],
490
503
  audioDescriptionSrc: track.audioDescriptionSrc || null,
491
- signLanguageSrc: track.signLanguageSrc || null
504
+ signLanguageSrc: track.signLanguageSrc || null,
505
+ signLanguageSources: track.signLanguageSources || {}
492
506
  });
493
-
494
- // Update UI
495
- this.updateTrackInfo(track);
496
- this.updatePlaylistUI();
507
+
508
+ // For playlist UX parity with single videos: fetch metadata/manifest now,
509
+ // but do not start playback.
510
+ if (this.player?.options?.deferLoad && typeof this.player.ensureLoaded === 'function') {
511
+ Promise.resolve(loadPromise)
512
+ .then(() => this.player?.ensureLoaded?.())
513
+ .catch(() => {
514
+ // ignore
515
+ });
516
+ }
497
517
 
498
518
  // Emit event
499
519
  this.player.emit('playlisttrackchange', {
@@ -507,6 +527,133 @@ export class PlaylistManager {
507
527
  this.isChangingTrack = false;
508
528
  }, 150);
509
529
  }
530
+
531
+ /**
532
+ * Select a track (UI/selection only; does NOT set the media src / does NOT initialize renderer)
533
+ *
534
+ * In "B always" playlist mode, you typically want `loadTrack()` on selection so the
535
+ * selected item behaves like a single video (metadata/manifest loaded, features ready)
536
+ * without auto-playing.
537
+ * @param {number} index - Track index
538
+ */
539
+ selectTrack(index) {
540
+ if (index < 0 || index >= this.tracks.length) {
541
+ console.warn('VidPly Playlist: Invalid track index', index);
542
+ return;
543
+ }
544
+
545
+ const track = this.tracks[index];
546
+ this.currentIndex = index;
547
+
548
+ // Apply per-track metadata without touching the media source.
549
+ // This ensures poster + feature buttons (chapters/captions/transcript/sign-language)
550
+ // can be updated instantly even before any media network activity happens.
551
+ try {
552
+ // Poster for video element
553
+ if (this.player?.element?.tagName === 'VIDEO') {
554
+ if (track.poster) {
555
+ const posterUrl = typeof this.player.resolvePosterPath === 'function'
556
+ ? this.player.resolvePosterPath(track.poster)
557
+ : track.poster;
558
+ this.player.element.poster = posterUrl;
559
+ this.player.applyPosterAspectRatio?.(posterUrl);
560
+ } else {
561
+ this.player.element.removeAttribute('poster');
562
+ }
563
+ }
564
+
565
+ // Update sign language / audio description sources (used for button visibility)
566
+ this.player.audioDescriptionSrc = track.audioDescriptionSrc || null;
567
+ this.player.signLanguageSrc = track.signLanguageSrc || null;
568
+ this.player.signLanguageSources = track.signLanguageSources || {};
569
+
570
+ // Fill duration early for UI (progress/time display) without loading media
571
+ if (track.duration && Number(track.duration) > 0) {
572
+ this.player.state.duration = Number(track.duration);
573
+ }
574
+ // Also sync feature managers (they keep their own copy of sources)
575
+ if (this.player.audioDescriptionManager) {
576
+ this.player.audioDescriptionManager.src = track.audioDescriptionSrc || null;
577
+ // Remember original (non-described) source for switching back later
578
+ this.player.audioDescriptionManager.originalSource = track.src || this.player.originalSrc || null;
579
+ }
580
+ if (this.player.signLanguageManager) {
581
+ this.player.signLanguageManager.src = track.signLanguageSrc || null;
582
+ this.player.signLanguageManager.sources = track.signLanguageSources || {};
583
+ this.player.signLanguageManager.currentLanguage = null;
584
+ }
585
+
586
+ // For audio description switching, remember original source even before first play
587
+ if (track.src && !this.player.originalSrc) {
588
+ this.player.originalSrc = track.src;
589
+ }
590
+
591
+ // Replace <track> elements so captions/chapters/transcript can be detected/loaded
592
+ const existing = Array.from(this.player.element.querySelectorAll('track'));
593
+ existing.forEach(t => t.remove());
594
+
595
+ if (Array.isArray(track.tracks)) {
596
+ track.tracks.forEach(tc => {
597
+ if (!tc?.src) return;
598
+ const el = document.createElement('track');
599
+ el.src = tc.src;
600
+ el.kind = tc.kind || 'captions';
601
+ el.srclang = tc.srclang || 'en';
602
+ el.label = tc.label || tc.srclang || 'Track';
603
+ if (tc.default) el.default = true;
604
+ if (tc.describedSrc) {
605
+ el.setAttribute('data-desc-src', tc.describedSrc);
606
+ }
607
+ this.player.element.appendChild(el);
608
+ });
609
+ }
610
+
611
+ if (typeof this.player.invalidateTrackCache === 'function') {
612
+ this.player.invalidateTrackCache();
613
+ }
614
+
615
+ // Re-scan described-track metadata for AudioDescriptionManager
616
+ if (this.player.audioDescriptionManager && typeof this.player.audioDescriptionManager.initFromSourceElements === 'function') {
617
+ try {
618
+ this.player.audioDescriptionManager.captionTracks = [];
619
+ this.player.audioDescriptionManager.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
620
+ } catch (e) {
621
+ // ignore
622
+ }
623
+ }
624
+
625
+ // Refresh caption/transcript managers so menus reflect newly injected <track> elements
626
+ // (important when we defer MP4/MP3 loading but still want VTT-based UI to work).
627
+ if (this.player.captionManager && typeof this.player.captionManager.loadTracks === 'function') {
628
+ try {
629
+ this.player.captionManager.tracks = [];
630
+ this.player.captionManager.currentTrack = null;
631
+ this.player.captionManager.loadTracks();
632
+ } catch (e) {
633
+ // ignore
634
+ }
635
+ }
636
+
637
+ // TranscriptManager reads from TextTracks too; it will be correct after media starts.
638
+ // For now, we just ensure control bar is rebuilt so the button is present.
639
+
640
+ // Rebuild controls so feature buttons appear immediately
641
+ if (typeof this.player.updateControlBar === 'function') {
642
+ this.player.updateControlBar();
643
+ }
644
+ } catch (e) {
645
+ // ignore preview errors; selection should still work
646
+ }
647
+
648
+ this.updateTrackInfo(track);
649
+ this.updatePlaylistUI();
650
+
651
+ this.player.emit('playlisttrackselect', {
652
+ index,
653
+ item: track,
654
+ total: this.tracks.length
655
+ });
656
+ }
510
657
 
511
658
  /**
512
659
  * Play a specific track
@@ -557,13 +704,24 @@ export class PlaylistManager {
557
704
  }
558
705
 
559
706
  // Load track into player (normal path)
707
+ // If audio description was toggled before the first play, load the described source directly.
708
+ let srcToLoad = track.src;
709
+ if (this.player?.audioDescriptionManager?.desiredState && track.audioDescriptionSrc) {
710
+ // Preserve original for later toggling back
711
+ this.player.originalSrc = track.src;
712
+ this.player.audioDescriptionManager.originalSource = track.src;
713
+ this.player.audioDescriptionManager.src = track.audioDescriptionSrc;
714
+ srcToLoad = track.audioDescriptionSrc;
715
+ }
716
+
560
717
  this.player.load({
561
- src: track.src,
718
+ src: srcToLoad,
562
719
  type: track.type,
563
720
  poster: track.poster,
564
721
  tracks: track.tracks || [],
565
722
  audioDescriptionSrc: track.audioDescriptionSrc || null,
566
- signLanguageSrc: track.signLanguageSrc || null
723
+ signLanguageSrc: track.signLanguageSrc || null,
724
+ signLanguageSources: track.signLanguageSources || {}
567
725
  });
568
726
 
569
727
  // Update UI
@@ -814,25 +972,9 @@ export class PlaylistManager {
814
972
  return;
815
973
  }
816
974
 
817
- // Create track artwork element (shows album art/poster for audio playlists)
818
- // Only create for audio players
819
- if (this.player.element.tagName === 'AUDIO') {
820
- this.trackArtworkElement = DOMUtils.createElement('div', {
821
- className: 'vidply-track-artwork',
822
- attributes: {
823
- 'aria-hidden': 'true'
824
- }
825
- });
826
- this.trackArtworkElement.style.display = 'none';
827
-
828
- // Insert before video wrapper
829
- const videoWrapper = this.container.querySelector('.vidply-video-wrapper');
830
- if (videoWrapper) {
831
- this.container.insertBefore(this.trackArtworkElement, videoWrapper);
832
- } else {
833
- this.container.appendChild(this.trackArtworkElement);
834
- }
835
- }
975
+ // Track artwork element (shows album art/poster for audio tracks).
976
+ // Important: in mixed playlists the player may start as <video> and later recreate to <audio>,
977
+ // so we create this lazily in `updateTrackArtwork()` when we actually have an audio element.
836
978
 
837
979
  // Create track info element (shows current track)
838
980
  this.trackInfoElement = DOMUtils.createElement('div', {
@@ -931,6 +1073,34 @@ export class PlaylistManager {
931
1073
  * Update track artwork display (for audio playlists)
932
1074
  */
933
1075
  updateTrackArtwork(track) {
1076
+ // Only show artwork for audio players.
1077
+ // In mixed playlists we may recreate from <video> -> <audio> later, so ensure the element exists lazily.
1078
+ if (this.player?.element?.tagName !== 'AUDIO') {
1079
+ if (this.trackArtworkElement) {
1080
+ this.trackArtworkElement.style.display = 'none';
1081
+ }
1082
+ return;
1083
+ }
1084
+
1085
+ // Lazily create artwork element once we have an audio element/container.
1086
+ if (!this.trackArtworkElement && this.container) {
1087
+ this.trackArtworkElement = DOMUtils.createElement('div', {
1088
+ className: 'vidply-track-artwork',
1089
+ attributes: {
1090
+ 'aria-hidden': 'true'
1091
+ }
1092
+ });
1093
+ this.trackArtworkElement.style.display = 'none';
1094
+
1095
+ // Insert before video wrapper (if present) for consistent layout.
1096
+ const videoWrapper = this.container.querySelector('.vidply-video-wrapper');
1097
+ if (videoWrapper) {
1098
+ this.container.insertBefore(this.trackArtworkElement, videoWrapper);
1099
+ } else {
1100
+ this.container.appendChild(this.trackArtworkElement);
1101
+ }
1102
+ }
1103
+
934
1104
  if (!this.trackArtworkElement) return;
935
1105
 
936
1106
  // If track has a poster/artwork, show it
@@ -45,6 +45,9 @@ export const de = {
45
45
  signLanguageVideo: 'Gebärdensprache-Video',
46
46
  closeSignLanguage: 'Gebärdensprache-Video schließen',
47
47
  signLanguageSettings: 'Gebärdensprache-Einstellungen',
48
+ startPlaybackFirst: 'Bitte starten Sie die Wiedergabe zuerst.',
49
+ startPlaybackForAudioDescription: 'Bitte starten Sie die Wiedergabe zuerst, um die Audiodeskription zu nutzen.',
50
+ startPlaybackForSignLanguage: 'Bitte starten Sie die Wiedergabe zuerst, um das Gebärdensprache-Video zu nutzen.',
48
51
  noChapters: 'Keine Kapitel verfügbar',
49
52
  noCaptions: 'Keine Untertitel verfügbar',
50
53
  auto: 'Automatisch',
@@ -45,6 +45,9 @@ export const en = {
45
45
  signLanguageVideo: 'Sign Language Video',
46
46
  closeSignLanguage: 'Close sign language video',
47
47
  signLanguageSettings: 'Sign language settings',
48
+ startPlaybackFirst: 'Please start playback first.',
49
+ startPlaybackForAudioDescription: 'Please start playback first to use audio description.',
50
+ startPlaybackForSignLanguage: 'Please start playback first to use sign language video.',
48
51
  noChapters: 'No chapters available',
49
52
  noCaptions: 'No captions available',
50
53
  auto: 'Auto',
@@ -8,6 +8,8 @@ export class HLSRenderer {
8
8
  this.player = player;
9
9
  this.media = player.element;
10
10
  this.hls = null;
11
+ this._hlsSourceLoaded = false;
12
+ this._pendingSrc = null;
11
13
  }
12
14
 
13
15
  async init() {
@@ -67,6 +69,8 @@ export class HLSRenderer {
67
69
  // Create hls.js instance with better error recovery
68
70
  this.hls = new window.Hls({
69
71
  debug: this.player.options.debug,
72
+ // When deferLoad is enabled, do not start loading until the first play().
73
+ autoStartLoad: !this.player.options.deferLoad,
70
74
  enableWorker: true,
71
75
  lowLatencyMode: false,
72
76
  backBufferLength: 90,
@@ -112,7 +116,13 @@ export class HLSRenderer {
112
116
  throw new Error('No HLS source found');
113
117
  }
114
118
 
115
- this.hls.loadSource(src);
119
+ if (this.player.options.deferLoad) {
120
+ // Defer manifest/segment loading until first play()
121
+ this._pendingSrc = src;
122
+ } else {
123
+ this.hls.loadSource(src);
124
+ this._hlsSourceLoaded = true;
125
+ }
116
126
 
117
127
  // Attach events
118
128
  this.attachHlsEvents();
@@ -261,10 +271,59 @@ export class HLSRenderer {
261
271
  }
262
272
  }
263
273
 
274
+ /**
275
+ * Ensure the HLS manifest/initial loading is started without starting playback.
276
+ * This makes playlist selection behave more like single-video initialization.
277
+ */
278
+ ensureLoaded() {
279
+ if (!this.player.options.deferLoad) {
280
+ return;
281
+ }
282
+
283
+ // Native HLS path delegates to HTML5Renderer; if we got here and have no hls.js instance,
284
+ // there's nothing to do.
285
+ if (!this.hls) {
286
+ return;
287
+ }
288
+
289
+ if (this._hlsSourceLoaded) {
290
+ return;
291
+ }
292
+
293
+ const src = this._pendingSrc || this.player._pendingSource || this.player.currentSource;
294
+ if (!src) {
295
+ return;
296
+ }
297
+
298
+ try {
299
+ this.hls.loadSource(src);
300
+ this._hlsSourceLoaded = true;
301
+ // Start loading so manifest is parsed and levels/tracks become available.
302
+ // Note: this may fetch initial fragments depending on stream/config.
303
+ this.hls.startLoad();
304
+ } catch (e) {
305
+ // ignore
306
+ }
307
+ }
308
+
264
309
  play() {
265
310
  // Save scroll position to prevent browser from scrolling to video
266
311
  const scrollX = window.scrollX;
267
312
  const scrollY = window.scrollY;
313
+
314
+ // If deferLoad is enabled, start HLS loading only on the first user play request.
315
+ if (this.player.options.deferLoad && this.hls && !this._hlsSourceLoaded) {
316
+ const src = this._pendingSrc || this.player.currentSource;
317
+ if (src) {
318
+ try {
319
+ this.hls.loadSource(src);
320
+ this.hls.startLoad();
321
+ this._hlsSourceLoaded = true;
322
+ } catch (e) {
323
+ // ignore and let media.play() surface errors if any
324
+ }
325
+ }
326
+ }
268
327
 
269
328
  const promise = this.media.play();
270
329
 
@@ -6,6 +6,7 @@ export class HTML5Renderer {
6
6
  constructor(player) {
7
7
  this.player = player;
8
8
  this.media = player.element;
9
+ this._didDeferredLoad = false;
9
10
  }
10
11
 
11
12
  async init() {
@@ -15,11 +16,16 @@ export class HTML5Renderer {
15
16
 
16
17
  this.attachEvents();
17
18
 
18
- // Set preload
19
- this.media.preload = this.player.options.preload;
20
-
21
- // Load media
22
- this.media.load();
19
+ // Set preload + optionally defer network loading until user play
20
+ if (this.player.options.deferLoad) {
21
+ // Allow metadata preload while still avoiding an explicit load() call.
22
+ // Note: browsers may still fetch metadata automatically when preload="metadata".
23
+ this.media.preload = this.player.options.preload || 'none';
24
+ } else {
25
+ this.media.preload = this.player.options.preload;
26
+ // Load media (eager)
27
+ this.media.load();
28
+ }
23
29
 
24
30
  // Show VidPly controls (remove external controls class if present)
25
31
  if (this.player.container) {
@@ -157,6 +163,19 @@ export class HTML5Renderer {
157
163
  // Save scroll position to prevent browser from scrolling to video
158
164
  const scrollX = window.scrollX;
159
165
  const scrollY = window.scrollY;
166
+
167
+ // If deferLoad is enabled, trigger load only on the first user play request.
168
+ if (this.player.options.deferLoad && !this._didDeferredLoad) {
169
+ try {
170
+ // Only call load() if browser hasn't loaded anything yet.
171
+ if (this.media.readyState === 0) {
172
+ this.media.load();
173
+ }
174
+ } catch (e) {
175
+ // ignore
176
+ }
177
+ this._didDeferredLoad = true;
178
+ }
160
179
 
161
180
  const promise = this.media.play();
162
181
 
@@ -185,6 +204,25 @@ export class HTML5Renderer {
185
204
  }
186
205
  }
187
206
 
207
+ /**
208
+ * Ensure the media element has been loaded at least once (metadata/initial state)
209
+ * without starting playback. Useful for playlists to behave like single videos.
210
+ */
211
+ ensureLoaded() {
212
+ if (!this.player.options.deferLoad || this._didDeferredLoad) {
213
+ return;
214
+ }
215
+
216
+ try {
217
+ if (this.media.readyState === 0) {
218
+ this.media.load();
219
+ }
220
+ } catch (e) {
221
+ // ignore
222
+ }
223
+ this._didDeferredLoad = true;
224
+ }
225
+
188
226
  pause() {
189
227
  this.media.pause();
190
228
  }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/renderers/HLSRenderer.js"],
4
- "sourcesContent": ["/**\r\n * HLS Streaming Renderer\r\n * Uses hls.js for browsers that don't natively support HLS\r\n */\r\n\r\nexport class HLSRenderer {\r\n constructor(player) {\r\n this.player = player;\r\n this.media = player.element;\r\n this.hls = null;\r\n }\r\n\r\n async init() {\r\n // Check if browser natively supports HLS (Safari)\r\n if (this.canPlayNatively()) {\r\n this.player.log('Using native HLS support');\r\n await this.initNative();\r\n } else {\r\n this.player.log('Using hls.js for HLS support');\r\n await this.initHlsJs();\r\n }\r\n }\r\n\r\n canPlayNatively() {\r\n // Only use native HLS on Safari/iOS where it actually works properly\r\n // Chrome reports it can play HLS but doesn't have proper quality switching\r\n const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\r\n const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;\r\n \r\n if (!isSafari && !isIOS) {\r\n // Force hls.js on non-Safari browsers for proper quality switching\r\n return false;\r\n }\r\n \r\n const video = document.createElement('video');\r\n return video.canPlayType('application/vnd.apple.mpegurl') !== '';\r\n }\r\n\r\n async initNative() {\r\n // Use HTML5 renderer for native HLS support\r\n const HTML5Renderer = (await import('./HTML5Renderer.js')).HTML5Renderer;\r\n const renderer = new HTML5Renderer(this.player);\r\n await renderer.init();\r\n \r\n // Copy methods\r\n Object.getOwnPropertyNames(Object.getPrototypeOf(renderer)).forEach(method => {\r\n if (method !== 'constructor' && typeof renderer[method] === 'function') {\r\n this[method] = renderer[method].bind(renderer);\r\n }\r\n });\r\n }\r\n\r\n async initHlsJs() {\r\n // Hide native controls\r\n this.media.controls = false;\r\n this.media.removeAttribute('controls');\r\n \r\n // Load hls.js if not already loaded\r\n if (!window.Hls) {\r\n await this.loadHlsJs();\r\n }\r\n\r\n if (!window.Hls.isSupported()) {\r\n throw new Error('HLS is not supported in this browser');\r\n }\r\n\r\n // Create hls.js instance with better error recovery\r\n this.hls = new window.Hls({\r\n debug: this.player.options.debug,\r\n enableWorker: true,\r\n lowLatencyMode: false,\r\n backBufferLength: 90,\r\n maxBufferLength: 30,\r\n maxMaxBufferLength: 600,\r\n maxBufferSize: 60 * 1000 * 1000,\r\n maxBufferHole: 0.5,\r\n // Network retry settings\r\n manifestLoadingTimeOut: 10000,\r\n manifestLoadingMaxRetry: 4,\r\n manifestLoadingRetryDelay: 1000,\r\n manifestLoadingMaxRetryTimeout: 64000,\r\n levelLoadingTimeOut: 10000,\r\n levelLoadingMaxRetry: 4,\r\n levelLoadingRetryDelay: 1000,\r\n levelLoadingMaxRetryTimeout: 64000,\r\n fragLoadingTimeOut: 20000,\r\n fragLoadingMaxRetry: 6,\r\n fragLoadingRetryDelay: 1000,\r\n fragLoadingMaxRetryTimeout: 64000\r\n });\r\n\r\n // Attach media element\r\n this.hls.attachMedia(this.media);\r\n\r\n // Load source - use currentSource for external renderers, or get from attribute\r\n let src = this.player.currentSource;\r\n \r\n if (!src) {\r\n const sourceElement = this.player.element.querySelector('source');\r\n if (sourceElement) {\r\n // Use getAttribute to get the original URL, not the blob-converted one\r\n src = sourceElement.getAttribute('src');\r\n } else {\r\n // Fallback to element's src attribute\r\n src = this.player.element.getAttribute('src') || this.player.element.src;\r\n }\r\n }\r\n \r\n this.player.log(`Loading HLS source: ${src}`, 'log');\r\n \r\n if (!src) {\r\n throw new Error('No HLS source found');\r\n }\r\n \r\n this.hls.loadSource(src);\r\n\r\n // Attach events\r\n this.attachHlsEvents();\r\n this.attachMediaEvents();\r\n }\r\n\r\n async loadHlsJs() {\r\n return new Promise((resolve, reject) => {\r\n const script = document.createElement('script');\r\n script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';\r\n script.onload = () => resolve();\r\n script.onerror = () => reject(new Error('Failed to load hls.js'));\r\n document.head.appendChild(script);\r\n });\r\n }\r\n\r\n attachHlsEvents() {\r\n this.hls.on(window.Hls.Events.MANIFEST_PARSED, (event, data) => {\r\n this.player.log('HLS manifest loaded, found ' + data.levels.length + ' quality levels');\r\n this.player.emit('hlsmanifestparsed', data);\r\n \r\n // Show VidPly controls (remove external controls class if present)\r\n if (this.player.container) {\r\n this.player.container.classList.remove('vidply-external-controls');\r\n }\r\n });\r\n\r\n this.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event, data) => {\r\n this.player.log('HLS level switched to ' + data.level);\r\n this.player.emit('hlslevelswitched', data);\r\n });\r\n\r\n this.hls.on(window.Hls.Events.ERROR, (event, data) => {\r\n this.handleHlsError(data);\r\n });\r\n\r\n this.hls.on(window.Hls.Events.FRAG_BUFFERED, () => {\r\n this.player.state.buffering = false;\r\n });\r\n }\r\n\r\n attachMediaEvents() {\r\n // Use same events as HTML5 renderer\r\n this.media.addEventListener('loadedmetadata', () => {\r\n this.player.state.duration = this.media.duration;\r\n this.player.emit('loadedmetadata');\r\n });\r\n\r\n this.media.addEventListener('play', () => {\r\n this.player.state.playing = true;\r\n this.player.state.paused = false;\r\n this.player.state.ended = false;\r\n this.player.emit('play');\r\n \r\n if (this.player.options.onPlay) {\r\n this.player.options.onPlay.call(this.player);\r\n }\r\n });\r\n\r\n this.media.addEventListener('pause', () => {\r\n this.player.state.playing = false;\r\n this.player.state.paused = true;\r\n this.player.emit('pause');\r\n \r\n if (this.player.options.onPause) {\r\n this.player.options.onPause.call(this.player);\r\n }\r\n });\r\n\r\n this.media.addEventListener('ended', () => {\r\n this.player.state.playing = false;\r\n this.player.state.paused = true;\r\n this.player.state.ended = true;\r\n this.player.emit('ended');\r\n \r\n if (this.player.options.onEnded) {\r\n this.player.options.onEnded.call(this.player);\r\n }\r\n \r\n if (this.player.options.loop) {\r\n this.player.seek(0);\r\n this.player.play();\r\n }\r\n });\r\n\r\n this.media.addEventListener('timeupdate', () => {\r\n this.player.state.currentTime = this.media.currentTime;\r\n this.player.emit('timeupdate', this.media.currentTime);\r\n \r\n if (this.player.options.onTimeUpdate) {\r\n this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);\r\n }\r\n });\r\n\r\n this.media.addEventListener('volumechange', () => {\r\n this.player.state.volume = this.media.volume;\r\n this.player.state.muted = this.media.muted;\r\n this.player.emit('volumechange', this.media.volume);\r\n });\r\n\r\n this.media.addEventListener('waiting', () => {\r\n this.player.state.buffering = true;\r\n this.player.emit('waiting');\r\n });\r\n\r\n this.media.addEventListener('canplay', () => {\r\n this.player.state.buffering = false;\r\n this.player.emit('canplay');\r\n });\r\n\r\n this.media.addEventListener('error', () => {\r\n this.player.handleError(this.media.error);\r\n });\r\n }\r\n\r\n handleHlsError(data) {\r\n // Log detailed error info\r\n this.player.log(`HLS Error - Type: ${data.type}, Details: ${data.details}, Fatal: ${data.fatal}`, 'warn');\r\n if (data.response) {\r\n this.player.log(`Response code: ${data.response.code}, URL: ${data.response.url}`, 'warn');\r\n }\r\n \r\n if (data.fatal) {\r\n switch (data.type) {\r\n case window.Hls.ErrorTypes.NETWORK_ERROR:\r\n this.player.log('Fatal network error, trying to recover...', 'error');\r\n this.player.log(`Network error details: ${data.details}`, 'error');\r\n setTimeout(() => {\r\n this.hls.startLoad();\r\n }, 1000);\r\n break;\r\n \r\n case window.Hls.ErrorTypes.MEDIA_ERROR:\r\n this.player.log('Fatal media error, trying to recover...', 'error');\r\n this.hls.recoverMediaError();\r\n break;\r\n \r\n default:\r\n this.player.log('Fatal error, cannot recover', 'error');\r\n this.player.handleError(new Error(`HLS Error: ${data.type} - ${data.details}`));\r\n this.hls.destroy();\r\n break;\r\n }\r\n } else {\r\n this.player.log('Non-fatal HLS error: ' + data.details, 'warn');\r\n }\r\n }\r\n\r\n play() {\r\n // Save scroll position to prevent browser from scrolling to video\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n \r\n const promise = this.media.play();\r\n \r\n // Restore scroll position immediately to prevent auto-scroll\r\n window.scrollTo(scrollX, scrollY);\r\n \r\n if (promise !== undefined) {\r\n promise.catch(error => {\r\n this.player.log('Play failed:', error, 'warn');\r\n });\r\n }\r\n }\r\n\r\n pause() {\r\n this.media.pause();\r\n }\r\n\r\n seek(time) {\r\n this.media.currentTime = time;\r\n }\r\n\r\n setVolume(volume) {\r\n this.media.volume = volume;\r\n }\r\n\r\n setMuted(muted) {\r\n this.media.muted = muted;\r\n }\r\n\r\n setPlaybackSpeed(speed) {\r\n this.media.playbackRate = speed;\r\n }\r\n\r\n switchQuality(levelIndex) {\r\n if (this.hls) {\r\n this.hls.currentLevel = levelIndex;\r\n }\r\n }\r\n\r\n getQualities() {\r\n if (this.hls && this.hls.levels) {\r\n return this.hls.levels.map((level, index) => {\r\n const height = Number(level.height) || 0;\r\n const bitrate = Number(level.bitrate) || 0;\r\n const kb = bitrate > 0 ? Math.round(bitrate / 1000) : 0;\r\n\r\n // Video HLS typically has height -> show \"720p\".\r\n // Audio-only HLS often has height=0 -> show bitrate label instead.\r\n const name = height > 0 ? `${height}p` : (kb > 0 ? `${kb} kb` : 'Auto');\r\n\r\n return {\r\n index,\r\n height: level.height,\r\n width: level.width,\r\n bitrate: level.bitrate,\r\n name\r\n };\r\n });\r\n }\r\n return [];\r\n }\r\n\r\n getCurrentQuality() {\r\n if (this.hls) {\r\n return this.hls.currentLevel;\r\n }\r\n return -1;\r\n }\r\n\r\n destroy() {\r\n if (this.hls) {\r\n this.hls.destroy();\r\n this.hls = null;\r\n }\r\n }\r\n}\r\n\r\n"],
5
- "mappings": "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",
6
- "names": []
7
- }