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
@@ -1,11 +1,11 @@
1
1
  /*!
2
2
  * Universal, Accessible Video Player
3
- * (c) 2025 Matthias Peltzer
3
+ * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
6
6
  import {
7
7
  HTML5Renderer
8
- } from "./vidply.chunk-W2LSBD6Y.js";
8
+ } from "./vidply.chunk-IIN4G4UQ.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-74NJTDQI.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();
@@ -755,18 +757,28 @@ var ControlBar = class {
755
757
  hasChapterTracks() {
756
758
  const textTracks = this.player.element.textTracks;
757
759
  for (let i = 0; i < textTracks.length; i++) {
758
- if (textTracks[i].kind === "chapters") {
759
- return true;
760
- }
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");
761
767
  }
762
768
  return false;
763
769
  }
764
770
  hasCaptionTracks() {
765
771
  const textTracks = this.player.element.textTracks;
766
772
  for (let i = 0; i < textTracks.length; i++) {
767
- if (textTracks[i].kind === "captions" || textTracks[i].kind === "subtitles") {
768
- return true;
769
- }
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");
770
782
  }
771
783
  return false;
772
784
  }
@@ -841,55 +853,64 @@ var ControlBar = class {
841
853
  this.currentPreviewTime = null;
842
854
  this.previewThumbnailTimeout = null;
843
855
  this.previewSupported = false;
856
+ this.previewVideoReady = false;
857
+ this.previewVideoInitialized = false;
844
858
  const isVideo = this.player.element && this.player.element.tagName === "VIDEO";
845
859
  if (!isVideo) {
846
860
  return;
847
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;
848
869
  const renderer = this.player.renderer;
849
870
  const hasVideoMedia = renderer && renderer.media && renderer.media.tagName === "VIDEO";
850
871
  const isHTML5Renderer = hasVideoMedia && renderer.media === this.player.element && !renderer.hls && typeof renderer.seek === "function";
851
872
  this.previewSupported = isHTML5Renderer && hasVideoMedia;
852
- if (this.previewSupported) {
853
- this.previewVideo = document.createElement("video");
854
- this.previewVideo.muted = true;
855
- this.previewVideo.preload = "auto";
856
- this.previewVideo.playsInline = true;
857
- this.previewVideo.style.position = "absolute";
858
- this.previewVideo.style.visibility = "hidden";
859
- this.previewVideo.style.width = "1px";
860
- this.previewVideo.style.height = "1px";
861
- this.previewVideo.style.top = "-9999px";
862
- const mainVideo = renderer.media || this.player.element;
863
- let videoSrc = null;
864
- if (mainVideo.src) {
865
- videoSrc = mainVideo.src;
866
- } else {
867
- const source = mainVideo.querySelector("source");
868
- if (source) {
869
- videoSrc = source.src;
870
- }
871
- }
872
- if (!videoSrc) {
873
- this.player.log("No video source found for preview", "warn");
874
- this.previewSupported = false;
875
- return;
876
- }
877
- if (mainVideo.crossOrigin) {
878
- this.previewVideo.crossOrigin = mainVideo.crossOrigin;
879
- }
880
- this.previewVideo.addEventListener("error", (e) => {
881
- this.player.log("Preview video failed to load:", e, "warn");
882
- this.previewSupported = false;
883
- });
884
- this.previewVideo.addEventListener("loadedmetadata", () => {
885
- this.previewVideoReady = true;
886
- }, { once: true });
887
- if (this.player.container) {
888
- 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;
889
882
  }
890
- this.previewVideo.src = videoSrc;
891
- this.previewVideoReady = false;
892
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;
893
914
  }
894
915
  /**
895
916
  * Generate preview thumbnail for a specific time
@@ -1022,8 +1043,17 @@ var ControlBar = class {
1022
1043
  this.controls.progressTooltipTime.textContent = TimeUtils.formatTime(time);
1023
1044
  this.controls.progressTooltip.style.left = `${left}px`;
1024
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();
1025
1053
  if (this.previewSupported) {
1026
1054
  this.updatePreviewThumbnail(time);
1055
+ } else if (this.controls.progressPreview) {
1056
+ this.controls.progressPreview.style.display = "none";
1027
1057
  }
1028
1058
  }
1029
1059
  });
@@ -4867,6 +4897,17 @@ var Player = class _Player extends EventEmitter {
4867
4897
  volume: 0.8,
4868
4898
  playbackSpeed: 1,
4869
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,
4870
4911
  startTime: 0,
4871
4912
  playsInline: true,
4872
4913
  // Enable inline playback on iOS (prevents native fullscreen)
@@ -4965,6 +5006,8 @@ var Player = class _Player extends EventEmitter {
4965
5006
  };
4966
5007
  this.options.metadataAlerts = this.options.metadataAlerts || {};
4967
5008
  this.options.metadataHashtags = this.options.metadataHashtags || {};
5009
+ this.noticeElement = null;
5010
+ this.noticeTimeout = null;
4968
5011
  this.storage = new StorageManager("vidply");
4969
5012
  const savedPrefs = this.storage.getPlayerPreferences();
4970
5013
  if (savedPrefs) {
@@ -4979,10 +5022,11 @@ var Player = class _Player extends EventEmitter {
4979
5022
  ended: false,
4980
5023
  buffering: false,
4981
5024
  seeking: false,
5025
+ hasStartedPlayback: false,
4982
5026
  muted: this.options.muted,
4983
5027
  volume: this.options.volume,
4984
5028
  currentTime: 0,
4985
- duration: 0,
5029
+ duration: Number(this.options.initialDuration) > 0 ? Number(this.options.initialDuration) : 0,
4986
5030
  playbackSpeed: this.options.playbackSpeed,
4987
5031
  fullscreen: false,
4988
5032
  pip: false,
@@ -5071,6 +5115,51 @@ var Player = class _Player extends EventEmitter {
5071
5115
  });
5072
5116
  this.init();
5073
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
+ }
5074
5163
  async init() {
5075
5164
  try {
5076
5165
  this.log("Initializing VidPly player");
@@ -5162,7 +5251,7 @@ var Player = class _Player extends EventEmitter {
5162
5251
  if (!this.options.transcript && !this.options.transcriptButton) {
5163
5252
  return null;
5164
5253
  }
5165
- const module = await import("./vidply.TranscriptManager-T677KF4N.js");
5254
+ const module = await import("./vidply.TranscriptManager-T3BVTZHZ.js");
5166
5255
  const Manager = module.TranscriptManager || module.default;
5167
5256
  if (!Manager) {
5168
5257
  return null;
@@ -5246,8 +5335,23 @@ var Player = class _Player extends EventEmitter {
5246
5335
  if (this.options.height) {
5247
5336
  this.container.style.height = typeof this.options.height === "number" ? `${this.options.height}px` : this.options.height;
5248
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
+ }
5249
5351
  if (this.options.poster && this.element.tagName === "VIDEO") {
5250
- 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);
5251
5355
  }
5252
5356
  if (this.element.tagName === "VIDEO") {
5253
5357
  this.createPlayButtonOverlay();
@@ -5261,6 +5365,7 @@ var Player = class _Player extends EventEmitter {
5261
5365
  }
5262
5366
  });
5263
5367
  this.on("play", () => {
5368
+ this.state.hasStartedPlayback = true;
5264
5369
  this.hidePosterOverlay();
5265
5370
  });
5266
5371
  this.on("timeupdate", () => {
@@ -5274,6 +5379,34 @@ var Player = class _Player extends EventEmitter {
5274
5379
  }
5275
5380
  }, { once: true });
5276
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
+ }
5277
5410
  createPlayButtonOverlay() {
5278
5411
  this.playButtonOverlay = createPlayOverlay();
5279
5412
  this.playButtonOverlay.addEventListener("click", () => {
@@ -5335,16 +5468,16 @@ var Player = class _Player extends EventEmitter {
5335
5468
  }
5336
5469
  let rendererClass = HTML5Renderer;
5337
5470
  if (src.includes("youtube.com") || src.includes("youtu.be")) {
5338
- const module = await import("./vidply.YouTubeRenderer-6MGKEFTZ.js");
5471
+ const module = await import("./vidply.YouTubeRenderer-EVXXE34A.js");
5339
5472
  rendererClass = module.YouTubeRenderer || module.default;
5340
5473
  } else if (src.includes("vimeo.com")) {
5341
- const module = await import("./vidply.VimeoRenderer-VPH4RNES.js");
5474
+ const module = await import("./vidply.VimeoRenderer-DY2FG7LZ.js");
5342
5475
  rendererClass = module.VimeoRenderer || module.default;
5343
5476
  } else if (src.includes(".m3u8")) {
5344
- const module = await import("./vidply.HLSRenderer-5MJZR4D2.js");
5477
+ const module = await import("./vidply.HLSRenderer-YGWCAICA.js");
5345
5478
  rendererClass = module.HLSRenderer || module.default;
5346
5479
  } else if (src.includes("soundcloud.com") || src.includes("api.soundcloud.com")) {
5347
- const module = await import("./vidply.SoundCloudRenderer-CD7VJKNS.js");
5480
+ const module = await import("./vidply.SoundCloudRenderer-RIA3QKP3.js");
5348
5481
  rendererClass = module.SoundCloudRenderer || module.default;
5349
5482
  }
5350
5483
  this.log(`Using ${rendererClass?.name || "HTML5Renderer"} renderer`);
@@ -5670,7 +5803,25 @@ var Player = class _Player extends EventEmitter {
5670
5803
  await this.initializeRenderer();
5671
5804
  } else {
5672
5805
  this.renderer.media = this.element;
5673
- 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
+ }
5674
5825
  }
5675
5826
  if (isExternalRenderer) {
5676
5827
  setTimeout(() => {
@@ -5715,6 +5866,20 @@ var Player = class _Player extends EventEmitter {
5715
5866
  this.handleError(error);
5716
5867
  }
5717
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
+ }
5718
5883
  /**
5719
5884
  * Check if we need to change renderer type
5720
5885
  * @param {string} src - New source URL
@@ -5750,6 +5915,11 @@ var Player = class _Player extends EventEmitter {
5750
5915
  play() {
5751
5916
  if (this.renderer) {
5752
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);
5753
5923
  }
5754
5924
  }
5755
5925
  pause() {
@@ -7078,6 +7248,17 @@ var Player = class _Player extends EventEmitter {
7078
7248
  this.emit("audiodescriptiondisabled");
7079
7249
  }
7080
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
+ }
7081
7262
  return this.audioDescriptionManager.toggle();
7082
7263
  }
7083
7264
  // Sign Language (delegated to SignLanguageManager)
@@ -7339,6 +7520,18 @@ var Player = class _Player extends EventEmitter {
7339
7520
  return this.signLanguageManager.disable();
7340
7521
  }
7341
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
+ }
7342
7535
  return this.signLanguageManager.toggle();
7343
7536
  }
7344
7537
  setupSignLanguageInteraction() {
@@ -8466,6 +8659,7 @@ var PlaylistManager = class {
8466
8659
  if (this.playlistPanel && this.playlistPanel.parentNode) {
8467
8660
  this.playlistPanel.parentNode.removeChild(this.playlistPanel);
8468
8661
  }
8662
+ const preservedPlayerOptions = this.player?.options ? { ...this.player.options } : {};
8469
8663
  if (this.player) {
8470
8664
  this.player.off("ended", this.handleTrackEnd);
8471
8665
  this.player.off("error", this.handleTrackError);
@@ -8473,7 +8667,8 @@ var PlaylistManager = class {
8473
8667
  }
8474
8668
  this.hostElement.innerHTML = "";
8475
8669
  const mediaElement = document.createElement(elementType);
8476
- mediaElement.setAttribute("preload", "metadata");
8670
+ const preloadValue = preservedPlayerOptions.preload || "metadata";
8671
+ mediaElement.setAttribute("preload", preloadValue);
8477
8672
  if (elementType === "video" && track.poster && (mediaType === "video" || mediaType === "hls")) {
8478
8673
  mediaElement.setAttribute("poster", track.poster);
8479
8674
  }
@@ -8507,6 +8702,7 @@ var PlaylistManager = class {
8507
8702
  audioDescriptionDuration: track.audioDescriptionDuration || null,
8508
8703
  signLanguageSrc: track.signLanguageSrc || null
8509
8704
  };
8705
+ Object.assign(playerOptions, preservedPlayerOptions);
8510
8706
  this.player = new this.PlayerClass(mediaElement, playerOptions);
8511
8707
  this.player.playlistManager = this;
8512
8708
  await new Promise((resolve) => {
@@ -8669,13 +8865,17 @@ var PlaylistManager = class {
8669
8865
  if (this.options.autoPlayFirst) {
8670
8866
  this.play(0);
8671
8867
  } else {
8672
- this.loadTrack(0);
8868
+ void this.loadTrack(0).catch(() => {
8869
+ });
8673
8870
  }
8674
8871
  }
8675
8872
  this.updatePlaylistVisibilityInFullscreen();
8676
8873
  }
8677
8874
  /**
8678
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.
8679
8879
  * @param {number} index - Track index
8680
8880
  */
8681
8881
  async loadTrack(index) {
@@ -8684,16 +8884,15 @@ var PlaylistManager = class {
8684
8884
  return;
8685
8885
  }
8686
8886
  const track = this.tracks[index];
8887
+ this.selectTrack(index);
8687
8888
  this.isChangingTrack = true;
8688
- this.currentIndex = index;
8689
8889
  if (this.options.recreatePlayers && this.hostElement && this.PlayerClass) {
8690
8890
  const currentMediaType = this.player ? this.player.element.tagName === "AUDIO" ? "audio" : "video" : null;
8691
8891
  const newMediaType = this.getTrackMediaType(track);
8692
8892
  const newElementType = newMediaType === "audio" || newMediaType === "soundcloud" ? "audio" : "video";
8693
8893
  if (currentMediaType !== newElementType) {
8694
8894
  await this.recreatePlayerForTrack(track, false);
8695
- this.updateTrackInfo(track);
8696
- this.updatePlaylistUI();
8895
+ this.selectTrack(index);
8697
8896
  this.player.emit("playlisttrackchange", {
8698
8897
  index,
8699
8898
  item: track,
@@ -8705,16 +8904,19 @@ var PlaylistManager = class {
8705
8904
  return;
8706
8905
  }
8707
8906
  }
8708
- this.player.load({
8907
+ const loadPromise = this.player.load({
8709
8908
  src: track.src,
8710
8909
  type: track.type,
8711
8910
  poster: track.poster,
8712
8911
  tracks: track.tracks || [],
8713
8912
  audioDescriptionSrc: track.audioDescriptionSrc || null,
8714
- signLanguageSrc: track.signLanguageSrc || null
8913
+ signLanguageSrc: track.signLanguageSrc || null,
8914
+ signLanguageSources: track.signLanguageSources || {}
8715
8915
  });
8716
- this.updateTrackInfo(track);
8717
- this.updatePlaylistUI();
8916
+ if (this.player?.options?.deferLoad && typeof this.player.ensureLoaded === "function") {
8917
+ Promise.resolve(loadPromise).then(() => this.player?.ensureLoaded?.()).catch(() => {
8918
+ });
8919
+ }
8718
8920
  this.player.emit("playlisttrackchange", {
8719
8921
  index,
8720
8922
  item: track,
@@ -8724,6 +8926,97 @@ var PlaylistManager = class {
8724
8926
  this.isChangingTrack = false;
8725
8927
  }, 150);
8726
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
+ }
8727
9020
  /**
8728
9021
  * Play a specific track
8729
9022
  * @param {number} index - Track index
@@ -8756,13 +9049,21 @@ var PlaylistManager = class {
8756
9049
  return;
8757
9050
  }
8758
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
+ }
8759
9059
  this.player.load({
8760
- src: track.src,
9060
+ src: srcToLoad,
8761
9061
  type: track.type,
8762
9062
  poster: track.poster,
8763
9063
  tracks: track.tracks || [],
8764
9064
  audioDescriptionSrc: track.audioDescriptionSrc || null,
8765
- signLanguageSrc: track.signLanguageSrc || null
9065
+ signLanguageSrc: track.signLanguageSrc || null,
9066
+ signLanguageSources: track.signLanguageSources || {}
8766
9067
  });
8767
9068
  this.updateTrackInfo(track);
8768
9069
  this.updatePlaylistUI();
@@ -8943,21 +9244,6 @@ var PlaylistManager = class {
8943
9244
  console.warn("VidPly Playlist: No container found");
8944
9245
  return;
8945
9246
  }
8946
- if (this.player.element.tagName === "AUDIO") {
8947
- this.trackArtworkElement = DOMUtils.createElement("div", {
8948
- className: "vidply-track-artwork",
8949
- attributes: {
8950
- "aria-hidden": "true"
8951
- }
8952
- });
8953
- this.trackArtworkElement.style.display = "none";
8954
- const videoWrapper = this.container.querySelector(".vidply-video-wrapper");
8955
- if (videoWrapper) {
8956
- this.container.insertBefore(this.trackArtworkElement, videoWrapper);
8957
- } else {
8958
- this.container.appendChild(this.trackArtworkElement);
8959
- }
8960
- }
8961
9247
  this.trackInfoElement = DOMUtils.createElement("div", {
8962
9248
  className: "vidply-track-info",
8963
9249
  attributes: {
@@ -9030,6 +9316,27 @@ var PlaylistManager = class {
9030
9316
  * Update track artwork display (for audio playlists)
9031
9317
  */
9032
9318
  updateTrackArtwork(track) {
9319
+ if (this.player?.element?.tagName !== "AUDIO") {
9320
+ if (this.trackArtworkElement) {
9321
+ this.trackArtworkElement.style.display = "none";
9322
+ }
9323
+ return;
9324
+ }
9325
+ if (!this.trackArtworkElement && this.container) {
9326
+ this.trackArtworkElement = DOMUtils.createElement("div", {
9327
+ className: "vidply-track-artwork",
9328
+ attributes: {
9329
+ "aria-hidden": "true"
9330
+ }
9331
+ });
9332
+ this.trackArtworkElement.style.display = "none";
9333
+ const videoWrapper = this.container.querySelector(".vidply-video-wrapper");
9334
+ if (videoWrapper) {
9335
+ this.container.insertBefore(this.trackArtworkElement, videoWrapper);
9336
+ } else {
9337
+ this.container.appendChild(this.trackArtworkElement);
9338
+ }
9339
+ }
9033
9340
  if (!this.trackArtworkElement) return;
9034
9341
  if (track.poster) {
9035
9342
  this.trackArtworkElement.style.backgroundImage = `url(${track.poster})`;