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
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import {
7
7
  HTML5Renderer
8
- } from "./vidply.chunk-W2LSBD6Y.js";
8
+ } from "./vidply.chunk-UVO24MXU.js";
9
9
  import {
10
10
  DOMUtils,
11
11
  DraggableResizable,
@@ -21,7 +21,7 @@ import {
21
21
  focusFirstMenuItem,
22
22
  i18n,
23
23
  preventDragOnElement
24
- } from "./vidply.chunk-GS2JX5RQ.js";
24
+ } from "./vidply.chunk-PMRKJBGH.js";
25
25
 
26
26
  // src/utils/EventEmitter.js
27
27
  var EventEmitter = class {
@@ -189,6 +189,8 @@ var ControlBar = class {
189
189
  init() {
190
190
  this.createElement();
191
191
  this.createControls();
192
+ this.updateDuration();
193
+ this.updateProgress();
192
194
  this.attachEvents();
193
195
  this.setupAutoHide();
194
196
  this.setupOverflowDetection();
@@ -667,7 +669,11 @@ var ControlBar = class {
667
669
  btn.dataset.overflowPriorityMobile = "3";
668
670
  this.rightButtons.appendChild(btn);
669
671
  }
670
- if (this.player.options.speedButton) {
672
+ const src = this.player.currentSource || this.player.element?.getAttribute?.("src") || this.player.element?.currentSrc || this.player.element?.src || this.player.element?.querySelector?.("source")?.getAttribute?.("src") || this.player.element?.querySelector?.("source")?.src || "";
673
+ const isHlsSource = typeof src === "string" && src.includes(".m3u8");
674
+ const isVideoElement = this.player.element?.tagName?.toLowerCase() === "video";
675
+ const hideSpeedForThisPlayer = !!this.player.options.hideSpeedForHls && isHlsSource || !!this.player.options.hideSpeedForHlsVideo && isHlsSource && isVideoElement;
676
+ if (this.player.options.speedButton && !hideSpeedForThisPlayer) {
671
677
  const btn = this.createSpeedButton();
672
678
  btn.dataset.overflowPriority = "1";
673
679
  btn.dataset.overflowPriorityMobile = "3";
@@ -751,18 +757,28 @@ var ControlBar = class {
751
757
  hasChapterTracks() {
752
758
  const textTracks = this.player.element.textTracks;
753
759
  for (let i = 0; i < textTracks.length; i++) {
754
- if (textTracks[i].kind === "chapters") {
755
- return true;
756
- }
760
+ if (textTracks[i].kind === "chapters") return true;
761
+ }
762
+ const trackEls = Array.from(this.player.element.querySelectorAll('track[kind="chapters"]'));
763
+ if (trackEls.length > 0) return true;
764
+ const current = this.player.playlistManager?.getCurrentTrack?.();
765
+ if (current?.tracks && Array.isArray(current.tracks)) {
766
+ return current.tracks.some((t) => t?.kind === "chapters");
757
767
  }
758
768
  return false;
759
769
  }
760
770
  hasCaptionTracks() {
761
771
  const textTracks = this.player.element.textTracks;
762
772
  for (let i = 0; i < textTracks.length; i++) {
763
- if (textTracks[i].kind === "captions" || textTracks[i].kind === "subtitles") {
764
- return true;
765
- }
773
+ if (textTracks[i].kind === "captions" || textTracks[i].kind === "subtitles") return true;
774
+ }
775
+ const trackEls = Array.from(this.player.element.querySelectorAll("track"));
776
+ if (trackEls.some((el) => el.getAttribute("kind") === "captions" || el.getAttribute("kind") === "subtitles")) {
777
+ return true;
778
+ }
779
+ const current = this.player.playlistManager?.getCurrentTrack?.();
780
+ if (current?.tracks && Array.isArray(current.tracks)) {
781
+ return current.tracks.some((t) => t?.kind === "captions" || t?.kind === "subtitles");
766
782
  }
767
783
  return false;
768
784
  }
@@ -837,55 +853,64 @@ var ControlBar = class {
837
853
  this.currentPreviewTime = null;
838
854
  this.previewThumbnailTimeout = null;
839
855
  this.previewSupported = false;
856
+ this.previewVideoReady = false;
857
+ this.previewVideoInitialized = false;
840
858
  const isVideo = this.player.element && this.player.element.tagName === "VIDEO";
841
859
  if (!isVideo) {
842
860
  return;
843
861
  }
862
+ }
863
+ /**
864
+ * Lazily create the hidden preview video (only after playback started once)
865
+ */
866
+ ensurePreviewVideoInitialized() {
867
+ if (this.previewVideoInitialized) return;
868
+ if (!this.player?.state?.hasStartedPlayback) return;
844
869
  const renderer = this.player.renderer;
845
870
  const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === "VIDEO";
846
871
  const isHTML5Renderer = hasVideoMedia && renderer.media === this.player.element && !renderer.hls && typeof renderer.seek === "function";
847
872
  this.previewSupported = isHTML5Renderer && hasVideoMedia;
848
- if (this.previewSupported) {
849
- this.previewVideo = document.createElement("video");
850
- this.previewVideo.muted = true;
851
- this.previewVideo.preload = "auto";
852
- this.previewVideo.playsInline = true;
853
- this.previewVideo.style.position = "absolute";
854
- this.previewVideo.style.visibility = "hidden";
855
- this.previewVideo.style.width = "1px";
856
- this.previewVideo.style.height = "1px";
857
- this.previewVideo.style.top = "-9999px";
858
- const mainVideo = renderer.media || this.player.element;
859
- let videoSrc = null;
860
- if (mainVideo.src) {
861
- videoSrc = mainVideo.src;
862
- } else {
863
- const source = mainVideo.querySelector("source");
864
- if (source) {
865
- videoSrc = source.src;
866
- }
867
- }
868
- if (!videoSrc) {
869
- this.player.log("No video source found for preview", "warn");
870
- this.previewSupported = false;
871
- return;
872
- }
873
- if (mainVideo.crossOrigin) {
874
- this.previewVideo.crossOrigin = mainVideo.crossOrigin;
875
- }
876
- this.previewVideo.addEventListener("error", (e) => {
877
- this.player.log("Preview video failed to load:", e, "warn");
878
- this.previewSupported = false;
879
- });
880
- this.previewVideo.addEventListener("loadedmetadata", () => {
881
- this.previewVideoReady = true;
882
- }, { once: true });
883
- if (this.player.container) {
884
- this.player.container.appendChild(this.previewVideo);
873
+ if (!this.previewSupported) return;
874
+ const mainVideo = renderer.media || this.player.element;
875
+ let videoSrc = null;
876
+ if (mainVideo.src) {
877
+ videoSrc = mainVideo.src;
878
+ } else {
879
+ const source = mainVideo.querySelector("source");
880
+ if (source) {
881
+ videoSrc = source.src;
885
882
  }
886
- this.previewVideo.src = videoSrc;
887
- this.previewVideoReady = false;
888
883
  }
884
+ if (!videoSrc) {
885
+ this.player.log("No video source found for preview", "warn");
886
+ this.previewSupported = false;
887
+ return;
888
+ }
889
+ this.previewVideo = document.createElement("video");
890
+ this.previewVideo.muted = true;
891
+ this.previewVideo.preload = "auto";
892
+ this.previewVideo.playsInline = true;
893
+ this.previewVideo.style.position = "absolute";
894
+ this.previewVideo.style.visibility = "hidden";
895
+ this.previewVideo.style.width = "1px";
896
+ this.previewVideo.style.height = "1px";
897
+ this.previewVideo.style.top = "-9999px";
898
+ if (mainVideo.crossOrigin) {
899
+ this.previewVideo.crossOrigin = mainVideo.crossOrigin;
900
+ }
901
+ this.previewVideo.addEventListener("error", (e) => {
902
+ this.player.log("Preview video failed to load:", e, "warn");
903
+ this.previewSupported = false;
904
+ });
905
+ this.previewVideo.addEventListener("loadedmetadata", () => {
906
+ this.previewVideoReady = true;
907
+ }, { once: true });
908
+ if (this.player.container) {
909
+ this.player.container.appendChild(this.previewVideo);
910
+ }
911
+ this.previewVideo.src = videoSrc;
912
+ this.previewVideoReady = false;
913
+ this.previewVideoInitialized = true;
889
914
  }
890
915
  /**
891
916
  * Generate preview thumbnail for a specific time
@@ -1018,8 +1043,17 @@ var ControlBar = class {
1018
1043
  this.controls.progressTooltipTime.textContent = TimeUtils.formatTime(time);
1019
1044
  this.controls.progressTooltip.style.left = `${left}px`;
1020
1045
  this.controls.progressTooltip.style.display = "block";
1046
+ if (!this.player?.state?.hasStartedPlayback) {
1047
+ if (this.controls.progressPreview) {
1048
+ this.controls.progressPreview.style.display = "none";
1049
+ }
1050
+ return;
1051
+ }
1052
+ this.ensurePreviewVideoInitialized();
1021
1053
  if (this.previewSupported) {
1022
1054
  this.updatePreviewThumbnail(time);
1055
+ } else if (this.controls.progressPreview) {
1056
+ this.controls.progressPreview.style.display = "none";
1023
1057
  }
1024
1058
  }
1025
1059
  });
@@ -4863,6 +4897,17 @@ var Player = class _Player extends EventEmitter {
4863
4897
  volume: 0.8,
4864
4898
  playbackSpeed: 1,
4865
4899
  preload: "metadata",
4900
+ // Optional initial duration (seconds) so UI can show duration
4901
+ // before media metadata is loaded (useful with deferLoad/preload=none).
4902
+ initialDuration: 0,
4903
+ // When enabled, VidPly will not start network loading during init().
4904
+ // - HTML5: does not call element.load() until the first user-initiated play()
4905
+ // - HLS (hls.js): does not load manifest/segments until the first play()
4906
+ // This is useful for pages with many players to avoid high initial bandwidth.
4907
+ deferLoad: false,
4908
+ // When enabled, clicking Audio Description / Sign Language before playback will show
4909
+ // a notice instead of implicitly starting playback/loading.
4910
+ requirePlaybackForAccessibilityToggles: false,
4866
4911
  startTime: 0,
4867
4912
  playsInline: true,
4868
4913
  // Enable inline playback on iOS (prevents native fullscreen)
@@ -4879,6 +4924,11 @@ var Player = class _Player extends EventEmitter {
4879
4924
  qualityButton: true,
4880
4925
  captionStyleButton: true,
4881
4926
  speedButton: true,
4927
+ // When enabled, the playback speed UI is suppressed for ALL HLS streams (audio + video).
4928
+ hideSpeedForHls: false,
4929
+ // When enabled, the playback speed UI is suppressed for HLS *video* streams only.
4930
+ // This is useful for live streams where speed controls don't make sense.
4931
+ hideSpeedForHlsVideo: false,
4882
4932
  captionsButton: true,
4883
4933
  transcriptButton: true,
4884
4934
  fullscreenButton: true,
@@ -4956,6 +5006,8 @@ var Player = class _Player extends EventEmitter {
4956
5006
  };
4957
5007
  this.options.metadataAlerts = this.options.metadataAlerts || {};
4958
5008
  this.options.metadataHashtags = this.options.metadataHashtags || {};
5009
+ this.noticeElement = null;
5010
+ this.noticeTimeout = null;
4959
5011
  this.storage = new StorageManager("vidply");
4960
5012
  const savedPrefs = this.storage.getPlayerPreferences();
4961
5013
  if (savedPrefs) {
@@ -4970,10 +5022,11 @@ var Player = class _Player extends EventEmitter {
4970
5022
  ended: false,
4971
5023
  buffering: false,
4972
5024
  seeking: false,
5025
+ hasStartedPlayback: false,
4973
5026
  muted: this.options.muted,
4974
5027
  volume: this.options.volume,
4975
5028
  currentTime: 0,
4976
- duration: 0,
5029
+ duration: Number(this.options.initialDuration) > 0 ? Number(this.options.initialDuration) : 0,
4977
5030
  playbackSpeed: this.options.playbackSpeed,
4978
5031
  fullscreen: false,
4979
5032
  pip: false,
@@ -5062,6 +5115,51 @@ var Player = class _Player extends EventEmitter {
5062
5115
  });
5063
5116
  this.init();
5064
5117
  }
5118
+ /**
5119
+ * Show a small in-player notice (non-blocking), also announced to screen readers.
5120
+ */
5121
+ showNotice(message, { timeout = 2500, priority = "polite" } = {}) {
5122
+ try {
5123
+ if (!message) return;
5124
+ if (!this.container) return;
5125
+ if (this.keyboardManager?.announce) {
5126
+ this.keyboardManager.announce(message, priority);
5127
+ }
5128
+ if (!this.noticeElement) {
5129
+ const el = document.createElement("div");
5130
+ el.className = `${this.options.classPrefix}-notice`;
5131
+ el.setAttribute("role", "status");
5132
+ el.setAttribute("aria-live", priority);
5133
+ el.setAttribute("aria-atomic", "true");
5134
+ el.style.position = "absolute";
5135
+ el.style.left = "0.75rem";
5136
+ el.style.right = "0.75rem";
5137
+ el.style.top = "0.75rem";
5138
+ el.style.zIndex = "9999";
5139
+ el.style.padding = "0.5rem 0.75rem";
5140
+ el.style.borderRadius = "0.5rem";
5141
+ el.style.background = "rgba(0, 0, 0, 0.75)";
5142
+ el.style.color = "#fff";
5143
+ el.style.fontSize = "0.875rem";
5144
+ el.style.lineHeight = "1.3";
5145
+ el.style.pointerEvents = "none";
5146
+ this.noticeElement = el;
5147
+ this.container.appendChild(el);
5148
+ }
5149
+ this.noticeElement.textContent = message;
5150
+ this.noticeElement.style.display = "block";
5151
+ if (this.noticeTimeout) {
5152
+ clearTimeout(this.noticeTimeout);
5153
+ this.noticeTimeout = null;
5154
+ }
5155
+ this.noticeTimeout = setTimeout(() => {
5156
+ if (this.noticeElement) {
5157
+ this.noticeElement.style.display = "none";
5158
+ }
5159
+ }, timeout);
5160
+ } catch (e) {
5161
+ }
5162
+ }
5065
5163
  async init() {
5066
5164
  try {
5067
5165
  this.log("Initializing VidPly player");
@@ -5153,7 +5251,7 @@ var Player = class _Player extends EventEmitter {
5153
5251
  if (!this.options.transcript && !this.options.transcriptButton) {
5154
5252
  return null;
5155
5253
  }
5156
- const module = await import("./vidply.TranscriptManager-T677KF4N.js");
5254
+ const module = await import("./vidply.TranscriptManager-R7NJRU7E.js");
5157
5255
  const Manager = module.TranscriptManager || module.default;
5158
5256
  if (!Manager) {
5159
5257
  return null;
@@ -5237,8 +5335,23 @@ var Player = class _Player extends EventEmitter {
5237
5335
  if (this.options.height) {
5238
5336
  this.container.style.height = typeof this.options.height === "number" ? `${this.options.height}px` : this.options.height;
5239
5337
  }
5338
+ if (this.element.tagName === "VIDEO" && !this.options.height) {
5339
+ const wAttr = parseInt(this.element.getAttribute("width") || "", 10);
5340
+ const hAttr = parseInt(this.element.getAttribute("height") || "", 10);
5341
+ if (Number.isFinite(wAttr) && Number.isFinite(hAttr) && wAttr > 0 && hAttr > 0) {
5342
+ if (!this.container.style.aspectRatio) {
5343
+ this.container.style.aspectRatio = `${wAttr} / ${hAttr}`;
5344
+ }
5345
+ if (this.videoWrapper && !this.videoWrapper.style.aspectRatio) {
5346
+ this.videoWrapper.style.aspectRatio = `${wAttr} / ${hAttr}`;
5347
+ this.videoWrapper.style.height = "auto";
5348
+ }
5349
+ }
5350
+ }
5240
5351
  if (this.options.poster && this.element.tagName === "VIDEO") {
5241
- this.element.poster = this.resolvePosterPath(this.options.poster);
5352
+ const resolvedPoster = this.resolvePosterPath(this.options.poster);
5353
+ this.element.poster = resolvedPoster;
5354
+ this.applyPosterAspectRatio(resolvedPoster);
5242
5355
  }
5243
5356
  if (this.element.tagName === "VIDEO") {
5244
5357
  this.createPlayButtonOverlay();
@@ -5252,6 +5365,7 @@ var Player = class _Player extends EventEmitter {
5252
5365
  }
5253
5366
  });
5254
5367
  this.on("play", () => {
5368
+ this.state.hasStartedPlayback = true;
5255
5369
  this.hidePosterOverlay();
5256
5370
  });
5257
5371
  this.on("timeupdate", () => {
@@ -5265,6 +5379,34 @@ var Player = class _Player extends EventEmitter {
5265
5379
  }
5266
5380
  }, { once: true });
5267
5381
  }
5382
+ /**
5383
+ * Apply aspect ratio to the video wrapper based on the poster's intrinsic size.
5384
+ * This helps render correct poster sizing before media metadata is available.
5385
+ */
5386
+ applyPosterAspectRatio(posterUrl) {
5387
+ try {
5388
+ if (!posterUrl) return;
5389
+ if (this.element.tagName !== "VIDEO") return;
5390
+ if (!this.videoWrapper) return;
5391
+ if (this.options.width || this.options.height) return;
5392
+ if (this._posterAspectAppliedFor === posterUrl) return;
5393
+ this._posterAspectAppliedFor = posterUrl;
5394
+ const img = new Image();
5395
+ img.decoding = "async";
5396
+ img.onload = () => {
5397
+ const w = img.naturalWidth;
5398
+ const h = img.naturalHeight;
5399
+ if (!w || !h) return;
5400
+ this.videoWrapper.style.aspectRatio = `${w} / ${h}`;
5401
+ this.videoWrapper.style.height = "auto";
5402
+ if (this.container && !this.container.style.aspectRatio) {
5403
+ this.container.style.aspectRatio = `${w} / ${h}`;
5404
+ }
5405
+ };
5406
+ img.src = posterUrl;
5407
+ } catch (e) {
5408
+ }
5409
+ }
5268
5410
  createPlayButtonOverlay() {
5269
5411
  this.playButtonOverlay = createPlayOverlay();
5270
5412
  this.playButtonOverlay.addEventListener("click", () => {
@@ -5332,7 +5474,7 @@ var Player = class _Player extends EventEmitter {
5332
5474
  const module = await import("./vidply.VimeoRenderer-VPH4RNES.js");
5333
5475
  rendererClass = module.VimeoRenderer || module.default;
5334
5476
  } else if (src.includes(".m3u8")) {
5335
- const module = await import("./vidply.HLSRenderer-UMPUDSYL.js");
5477
+ const module = await import("./vidply.HLSRenderer-LIFBU6UD.js");
5336
5478
  rendererClass = module.HLSRenderer || module.default;
5337
5479
  } else if (src.includes("soundcloud.com") || src.includes("api.soundcloud.com")) {
5338
5480
  const module = await import("./vidply.SoundCloudRenderer-CD7VJKNS.js");
@@ -5661,7 +5803,25 @@ var Player = class _Player extends EventEmitter {
5661
5803
  await this.initializeRenderer();
5662
5804
  } else {
5663
5805
  this.renderer.media = this.element;
5664
- this.element.load();
5806
+ if (this.options.deferLoad) {
5807
+ try {
5808
+ this.element.preload = this.options.preload || "metadata";
5809
+ } catch (e) {
5810
+ }
5811
+ if (this.renderer) {
5812
+ if (typeof this.renderer._didDeferredLoad === "boolean") {
5813
+ this.renderer._didDeferredLoad = false;
5814
+ }
5815
+ if (typeof this.renderer._hlsSourceLoaded === "boolean") {
5816
+ this.renderer._hlsSourceLoaded = false;
5817
+ }
5818
+ if ("_pendingSrc" in this.renderer) {
5819
+ this.renderer._pendingSrc = this._pendingSource || this.currentSource || null;
5820
+ }
5821
+ }
5822
+ } else {
5823
+ this.element.load();
5824
+ }
5665
5825
  }
5666
5826
  if (isExternalRenderer) {
5667
5827
  setTimeout(() => {
@@ -5706,6 +5866,20 @@ var Player = class _Player extends EventEmitter {
5706
5866
  this.handleError(error);
5707
5867
  }
5708
5868
  }
5869
+ /**
5870
+ * Ensure the current renderer has started its initial load (metadata/manifest)
5871
+ * without starting playback. This is useful for playlists to behave like
5872
+ * single videos on selection, while still keeping autoplay off.
5873
+ */
5874
+ ensureLoaded() {
5875
+ try {
5876
+ if (!this.renderer) return;
5877
+ if (typeof this.renderer.ensureLoaded === "function") {
5878
+ this.renderer.ensureLoaded();
5879
+ }
5880
+ } catch (e) {
5881
+ }
5882
+ }
5709
5883
  /**
5710
5884
  * Check if we need to change renderer type
5711
5885
  * @param {string} src - New source URL
@@ -5741,6 +5915,11 @@ var Player = class _Player extends EventEmitter {
5741
5915
  play() {
5742
5916
  if (this.renderer) {
5743
5917
  this.renderer.play();
5918
+ return;
5919
+ }
5920
+ if (this.playlistManager && Array.isArray(this.playlistManager.tracks) && this.playlistManager.tracks.length > 0) {
5921
+ const index = this.playlistManager.currentIndex >= 0 ? this.playlistManager.currentIndex : 0;
5922
+ this.playlistManager.play(index, true);
5744
5923
  }
5745
5924
  }
5746
5925
  pause() {
@@ -7069,6 +7248,17 @@ var Player = class _Player extends EventEmitter {
7069
7248
  this.emit("audiodescriptiondisabled");
7070
7249
  }
7071
7250
  async toggleAudioDescription() {
7251
+ if (this.options.requirePlaybackForAccessibilityToggles && !this.renderer && this.playlistManager?.tracks?.length) {
7252
+ this.showNotice(i18n.t("player.startPlaybackForAudioDescription"));
7253
+ return;
7254
+ }
7255
+ if (!this.renderer && this.playlistManager && this.playlistManager.tracks?.length) {
7256
+ this.audioDescriptionManager.desiredState = !this.audioDescriptionManager.desiredState;
7257
+ this.state.audioDescriptionEnabled = this.audioDescriptionManager.desiredState;
7258
+ this.emit(this.audioDescriptionManager.desiredState ? "audiodescriptionenabled" : "audiodescriptiondisabled");
7259
+ this.play();
7260
+ return;
7261
+ }
7072
7262
  return this.audioDescriptionManager.toggle();
7073
7263
  }
7074
7264
  // Sign Language (delegated to SignLanguageManager)
@@ -7330,6 +7520,18 @@ var Player = class _Player extends EventEmitter {
7330
7520
  return this.signLanguageManager.disable();
7331
7521
  }
7332
7522
  toggleSignLanguage() {
7523
+ if (this.options.requirePlaybackForAccessibilityToggles && !this.renderer && this.playlistManager?.tracks?.length) {
7524
+ this.showNotice(i18n.t("player.startPlaybackForSignLanguage"));
7525
+ return;
7526
+ }
7527
+ if (!this.renderer && this.playlistManager && this.playlistManager.tracks?.length) {
7528
+ const wasEnabled = this.signLanguageManager.enabled;
7529
+ const result = this.signLanguageManager.toggle();
7530
+ if (!wasEnabled && this.signLanguageManager.enabled) {
7531
+ this.play();
7532
+ }
7533
+ return result;
7534
+ }
7333
7535
  return this.signLanguageManager.toggle();
7334
7536
  }
7335
7537
  setupSignLanguageInteraction() {
@@ -8457,6 +8659,7 @@ var PlaylistManager = class {
8457
8659
  if (this.playlistPanel && this.playlistPanel.parentNode) {
8458
8660
  this.playlistPanel.parentNode.removeChild(this.playlistPanel);
8459
8661
  }
8662
+ const preservedPlayerOptions = this.player?.options ? { ...this.player.options } : {};
8460
8663
  if (this.player) {
8461
8664
  this.player.off("ended", this.handleTrackEnd);
8462
8665
  this.player.off("error", this.handleTrackError);
@@ -8464,7 +8667,8 @@ var PlaylistManager = class {
8464
8667
  }
8465
8668
  this.hostElement.innerHTML = "";
8466
8669
  const mediaElement = document.createElement(elementType);
8467
- mediaElement.setAttribute("preload", "metadata");
8670
+ const preloadValue = preservedPlayerOptions.preload || "metadata";
8671
+ mediaElement.setAttribute("preload", preloadValue);
8468
8672
  if (elementType === "video" && track.poster && (mediaType === "video" || mediaType === "hls")) {
8469
8673
  mediaElement.setAttribute("poster", track.poster);
8470
8674
  }
@@ -8498,6 +8702,7 @@ var PlaylistManager = class {
8498
8702
  audioDescriptionDuration: track.audioDescriptionDuration || null,
8499
8703
  signLanguageSrc: track.signLanguageSrc || null
8500
8704
  };
8705
+ Object.assign(playerOptions, preservedPlayerOptions);
8501
8706
  this.player = new this.PlayerClass(mediaElement, playerOptions);
8502
8707
  this.player.playlistManager = this;
8503
8708
  await new Promise((resolve) => {
@@ -8660,13 +8865,17 @@ var PlaylistManager = class {
8660
8865
  if (this.options.autoPlayFirst) {
8661
8866
  this.play(0);
8662
8867
  } else {
8663
- this.loadTrack(0);
8868
+ void this.loadTrack(0).catch(() => {
8869
+ });
8664
8870
  }
8665
8871
  }
8666
8872
  this.updatePlaylistVisibilityInFullscreen();
8667
8873
  }
8668
8874
  /**
8669
8875
  * Load a track without playing
8876
+ * This is the playlist equivalent of a "single video initialized but not started yet":
8877
+ * it updates UI selection and loads the media into the player so metadata/manifests
8878
+ * and feature managers can be ready, but it does not start playback.
8670
8879
  * @param {number} index - Track index
8671
8880
  */
8672
8881
  async loadTrack(index) {
@@ -8675,16 +8884,15 @@ var PlaylistManager = class {
8675
8884
  return;
8676
8885
  }
8677
8886
  const track = this.tracks[index];
8887
+ this.selectTrack(index);
8678
8888
  this.isChangingTrack = true;
8679
- this.currentIndex = index;
8680
8889
  if (this.options.recreatePlayers && this.hostElement && this.PlayerClass) {
8681
8890
  const currentMediaType = this.player ? this.player.element.tagName === "AUDIO" ? "audio" : "video" : null;
8682
8891
  const newMediaType = this.getTrackMediaType(track);
8683
8892
  const newElementType = newMediaType === "audio" || newMediaType === "soundcloud" ? "audio" : "video";
8684
8893
  if (currentMediaType !== newElementType) {
8685
8894
  await this.recreatePlayerForTrack(track, false);
8686
- this.updateTrackInfo(track);
8687
- this.updatePlaylistUI();
8895
+ this.selectTrack(index);
8688
8896
  this.player.emit("playlisttrackchange", {
8689
8897
  index,
8690
8898
  item: track,
@@ -8696,16 +8904,19 @@ var PlaylistManager = class {
8696
8904
  return;
8697
8905
  }
8698
8906
  }
8699
- this.player.load({
8907
+ const loadPromise = this.player.load({
8700
8908
  src: track.src,
8701
8909
  type: track.type,
8702
8910
  poster: track.poster,
8703
8911
  tracks: track.tracks || [],
8704
8912
  audioDescriptionSrc: track.audioDescriptionSrc || null,
8705
- signLanguageSrc: track.signLanguageSrc || null
8913
+ signLanguageSrc: track.signLanguageSrc || null,
8914
+ signLanguageSources: track.signLanguageSources || {}
8706
8915
  });
8707
- this.updateTrackInfo(track);
8708
- this.updatePlaylistUI();
8916
+ if (this.player?.options?.deferLoad && typeof this.player.ensureLoaded === "function") {
8917
+ Promise.resolve(loadPromise).then(() => this.player?.ensureLoaded?.()).catch(() => {
8918
+ });
8919
+ }
8709
8920
  this.player.emit("playlisttrackchange", {
8710
8921
  index,
8711
8922
  item: track,
@@ -8715,6 +8926,97 @@ var PlaylistManager = class {
8715
8926
  this.isChangingTrack = false;
8716
8927
  }, 150);
8717
8928
  }
8929
+ /**
8930
+ * Select a track (UI/selection only; does NOT set the media src / does NOT initialize renderer)
8931
+ *
8932
+ * In "B always" playlist mode, you typically want `loadTrack()` on selection so the
8933
+ * selected item behaves like a single video (metadata/manifest loaded, features ready)
8934
+ * without auto-playing.
8935
+ * @param {number} index - Track index
8936
+ */
8937
+ selectTrack(index) {
8938
+ if (index < 0 || index >= this.tracks.length) {
8939
+ console.warn("VidPly Playlist: Invalid track index", index);
8940
+ return;
8941
+ }
8942
+ const track = this.tracks[index];
8943
+ this.currentIndex = index;
8944
+ try {
8945
+ if (this.player?.element?.tagName === "VIDEO") {
8946
+ if (track.poster) {
8947
+ const posterUrl = typeof this.player.resolvePosterPath === "function" ? this.player.resolvePosterPath(track.poster) : track.poster;
8948
+ this.player.element.poster = posterUrl;
8949
+ this.player.applyPosterAspectRatio?.(posterUrl);
8950
+ } else {
8951
+ this.player.element.removeAttribute("poster");
8952
+ }
8953
+ }
8954
+ this.player.audioDescriptionSrc = track.audioDescriptionSrc || null;
8955
+ this.player.signLanguageSrc = track.signLanguageSrc || null;
8956
+ this.player.signLanguageSources = track.signLanguageSources || {};
8957
+ if (track.duration && Number(track.duration) > 0) {
8958
+ this.player.state.duration = Number(track.duration);
8959
+ }
8960
+ if (this.player.audioDescriptionManager) {
8961
+ this.player.audioDescriptionManager.src = track.audioDescriptionSrc || null;
8962
+ this.player.audioDescriptionManager.originalSource = track.src || this.player.originalSrc || null;
8963
+ }
8964
+ if (this.player.signLanguageManager) {
8965
+ this.player.signLanguageManager.src = track.signLanguageSrc || null;
8966
+ this.player.signLanguageManager.sources = track.signLanguageSources || {};
8967
+ this.player.signLanguageManager.currentLanguage = null;
8968
+ }
8969
+ if (track.src && !this.player.originalSrc) {
8970
+ this.player.originalSrc = track.src;
8971
+ }
8972
+ const existing = Array.from(this.player.element.querySelectorAll("track"));
8973
+ existing.forEach((t) => t.remove());
8974
+ if (Array.isArray(track.tracks)) {
8975
+ track.tracks.forEach((tc) => {
8976
+ if (!tc?.src) return;
8977
+ const el = document.createElement("track");
8978
+ el.src = tc.src;
8979
+ el.kind = tc.kind || "captions";
8980
+ el.srclang = tc.srclang || "en";
8981
+ el.label = tc.label || tc.srclang || "Track";
8982
+ if (tc.default) el.default = true;
8983
+ if (tc.describedSrc) {
8984
+ el.setAttribute("data-desc-src", tc.describedSrc);
8985
+ }
8986
+ this.player.element.appendChild(el);
8987
+ });
8988
+ }
8989
+ if (typeof this.player.invalidateTrackCache === "function") {
8990
+ this.player.invalidateTrackCache();
8991
+ }
8992
+ if (this.player.audioDescriptionManager && typeof this.player.audioDescriptionManager.initFromSourceElements === "function") {
8993
+ try {
8994
+ this.player.audioDescriptionManager.captionTracks = [];
8995
+ this.player.audioDescriptionManager.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
8996
+ } catch (e) {
8997
+ }
8998
+ }
8999
+ if (this.player.captionManager && typeof this.player.captionManager.loadTracks === "function") {
9000
+ try {
9001
+ this.player.captionManager.tracks = [];
9002
+ this.player.captionManager.currentTrack = null;
9003
+ this.player.captionManager.loadTracks();
9004
+ } catch (e) {
9005
+ }
9006
+ }
9007
+ if (typeof this.player.updateControlBar === "function") {
9008
+ this.player.updateControlBar();
9009
+ }
9010
+ } catch (e) {
9011
+ }
9012
+ this.updateTrackInfo(track);
9013
+ this.updatePlaylistUI();
9014
+ this.player.emit("playlisttrackselect", {
9015
+ index,
9016
+ item: track,
9017
+ total: this.tracks.length
9018
+ });
9019
+ }
8718
9020
  /**
8719
9021
  * Play a specific track
8720
9022
  * @param {number} index - Track index
@@ -8747,13 +9049,21 @@ var PlaylistManager = class {
8747
9049
  return;
8748
9050
  }
8749
9051
  }
9052
+ let srcToLoad = track.src;
9053
+ if (this.player?.audioDescriptionManager?.desiredState && track.audioDescriptionSrc) {
9054
+ this.player.originalSrc = track.src;
9055
+ this.player.audioDescriptionManager.originalSource = track.src;
9056
+ this.player.audioDescriptionManager.src = track.audioDescriptionSrc;
9057
+ srcToLoad = track.audioDescriptionSrc;
9058
+ }
8750
9059
  this.player.load({
8751
- src: track.src,
9060
+ src: srcToLoad,
8752
9061
  type: track.type,
8753
9062
  poster: track.poster,
8754
9063
  tracks: track.tracks || [],
8755
9064
  audioDescriptionSrc: track.audioDescriptionSrc || null,
8756
- signLanguageSrc: track.signLanguageSrc || null
9065
+ signLanguageSrc: track.signLanguageSrc || null,
9066
+ signLanguageSources: track.signLanguageSources || {}
8757
9067
  });
8758
9068
  this.updateTrackInfo(track);
8759
9069
  this.updatePlaylistUI();