vidply 1.2.0 → 1.2.3

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 (167) hide show
  1. package/README.md +974 -946
  2. package/dist/dev/{vidply.AudioDescriptionManager-R47Q4YR4.js → vidply.AudioDescriptionManager-JYYLK5KB.js} +275 -19
  3. package/dist/dev/vidply.AudioDescriptionManager-JYYLK5KB.js.map +7 -0
  4. package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js → vidply.CaptionStyleMenu-47RCKQF5.js} +4 -4
  5. package/dist/dev/{vidply.DASHRenderer-PWEMC3BT.js → vidply.DASHRenderer-VEJ4HDHO.js} +3 -3
  6. package/dist/dev/vidply.DASHRenderer-VEJ4HDHO.js.map +7 -0
  7. package/dist/dev/{vidply.FloatingPlayerManager-PKNNNAS2.js → vidply.FloatingPlayerManager-C2UVRPZY.js} +5 -5
  8. package/dist/dev/vidply.FloatingPlayerManager-C2UVRPZY.js.map +7 -0
  9. package/dist/dev/{vidply.HLSRenderer-E7RQ74HI.js → vidply.HLSRenderer-ZVBK2TNQ.js} +4 -4
  10. package/dist/dev/vidply.HLSRenderer-ZVBK2TNQ.js.map +7 -0
  11. package/dist/dev/vidply.HTML5Renderer-HLRQUOCU.js +12 -0
  12. package/dist/dev/{vidply.SignLanguageManager-FET5FQZ7.js → vidply.SignLanguageManager-FLUCBW5H.js} +8 -8
  13. package/dist/dev/vidply.SignLanguageManager-FLUCBW5H.js.map +7 -0
  14. package/dist/dev/{vidply.SoundCloudRenderer-RVSJZODO.js → vidply.SoundCloudRenderer-MGMAJ7NK.js} +3 -3
  15. package/dist/dev/vidply.SoundCloudRenderer-MGMAJ7NK.js.map +7 -0
  16. package/dist/dev/{vidply.TranscriptManager-5GXOQCLX.js → vidply.TranscriptManager-Y2RYXHJW.js} +10 -10
  17. package/dist/dev/vidply.TranscriptManager-Y2RYXHJW.js.map +7 -0
  18. package/dist/dev/{vidply.VimeoRenderer-ER2LSBBW.js → vidply.VimeoRenderer-RQFQD7OE.js} +3 -3
  19. package/dist/dev/vidply.VimeoRenderer-RQFQD7OE.js.map +7 -0
  20. package/dist/dev/{vidply.YouTubeRenderer-NFTOWJAB.js → vidply.YouTubeRenderer-EU2BARZF.js} +3 -3
  21. package/dist/dev/vidply.YouTubeRenderer-EU2BARZF.js.map +7 -0
  22. package/dist/dev/{vidply.chunk-BNFIUL2Z.js → vidply.chunk-6LYZYI5G.js} +3 -3
  23. package/dist/dev/vidply.chunk-6LYZYI5G.js.map +7 -0
  24. package/dist/dev/{vidply.chunk-NQPURMQY.js → vidply.chunk-A4ESEOQ2.js} +6 -6
  25. package/dist/dev/vidply.chunk-A4ESEOQ2.js.map +7 -0
  26. package/dist/dev/{vidply.chunk-W25HUCHS.js → vidply.chunk-CGYIED2I.js} +2 -2
  27. package/dist/dev/vidply.chunk-CGYIED2I.js.map +7 -0
  28. package/dist/dev/{vidply.chunk-SK4V2DAU.js → vidply.chunk-HZ7UOKOU.js} +3 -3
  29. package/dist/dev/{vidply.chunk-RZWZGDV7.js → vidply.chunk-I6EDRYGJ.js} +33 -6
  30. package/dist/dev/vidply.chunk-I6EDRYGJ.js.map +7 -0
  31. package/dist/dev/{vidply.chunk-64WXMTGL.js → vidply.chunk-JKMQGLCJ.js} +5 -5
  32. package/dist/dev/vidply.chunk-JKMQGLCJ.js.map +7 -0
  33. package/dist/dev/{vidply.chunk-DZSDO3B7.js → vidply.chunk-JZNZX32Z.js} +2 -2
  34. package/dist/dev/{vidply.chunk-VMSQAZM7.js → vidply.chunk-P25MRJ6O.js} +2 -2
  35. package/dist/dev/{vidply.chunk-6RQA4ROD.js → vidply.chunk-PRFNESYA.js} +2 -2
  36. package/dist/dev/{vidply.chunk-KI7C3QVL.js → vidply.chunk-UI3RYCVF.js} +4 -3
  37. package/dist/dev/vidply.chunk-UI3RYCVF.js.map +7 -0
  38. package/dist/dev/{vidply.chunk-JJ47NO24.js → vidply.chunk-UNFGMABP.js} +2 -2
  39. package/dist/dev/vidply.chunk-UNFGMABP.js.map +7 -0
  40. package/dist/dev/{vidply.de-4YAT7FFM.js → vidply.de-VAUVSQXC.js} +29 -2
  41. package/dist/dev/vidply.de-VAUVSQXC.js.map +7 -0
  42. package/dist/dev/{vidply.es-LEWIRUBH.js → vidply.es-G65HFWO6.js} +29 -2
  43. package/dist/dev/vidply.es-G65HFWO6.js.map +7 -0
  44. package/dist/dev/vidply.esm.js +648 -35
  45. package/dist/dev/vidply.esm.js.map +3 -3
  46. package/dist/dev/{vidply.fr-Q4GV2NOE.js → vidply.fr-MJNOZELM.js} +29 -2
  47. package/dist/dev/vidply.fr-MJNOZELM.js.map +7 -0
  48. package/dist/dev/{vidply.ja-S6ITA6WU.js → vidply.ja-INMDHIYJ.js} +29 -2
  49. package/dist/dev/vidply.ja-INMDHIYJ.js.map +7 -0
  50. package/dist/legacy/vidply.js +1062 -34
  51. package/dist/legacy/vidply.js.map +3 -3
  52. package/dist/legacy/vidply.min.js +2 -2
  53. package/dist/legacy/vidply.min.meta.json +127 -55
  54. package/dist/prod/vidply.AudioDescriptionManager-EKWUNOPE.min.js +6 -0
  55. package/dist/prod/{vidply.CaptionStyleMenu-M3KVQWB5.min.js → vidply.CaptionStyleMenu-JTWSNHRQ.min.js} +2 -2
  56. package/dist/prod/{vidply.DASHRenderer-DN3S27AV.min.js → vidply.DASHRenderer-CIDAHFWM.min.js} +2 -2
  57. package/dist/prod/{vidply.FloatingPlayerManager-SKAZ4J43.min.js → vidply.FloatingPlayerManager-VZXHPCPM.min.js} +2 -2
  58. package/dist/prod/{vidply.HLSRenderer-62HH4YTK.min.js → vidply.HLSRenderer-Q6EFYKID.min.js} +2 -2
  59. package/dist/prod/vidply.HTML5Renderer-XRDJUTOO.min.js +6 -0
  60. package/dist/prod/{vidply.SignLanguageManager-V3OBTSSF.min.js → vidply.SignLanguageManager-JUOSA5AM.min.js} +2 -2
  61. package/dist/prod/{vidply.SoundCloudRenderer-CXRVZIAJ.min.js → vidply.SoundCloudRenderer-7ODD46PO.min.js} +2 -2
  62. package/dist/prod/{vidply.TranscriptManager-SYM3DFUY.min.js → vidply.TranscriptManager-4B2QKYGQ.min.js} +2 -2
  63. package/dist/prod/{vidply.VimeoRenderer-72TWRYFQ.min.js → vidply.VimeoRenderer-MR3UYLY7.min.js} +2 -2
  64. package/dist/prod/{vidply.YouTubeRenderer-UMHEV3KD.min.js → vidply.YouTubeRenderer-ITRTC7NR.min.js} +2 -2
  65. package/dist/prod/{vidply.chunk-YEENJS3Q.min.js → vidply.chunk-3EEDXU4T.min.js} +2 -2
  66. package/dist/prod/{vidply.chunk-P4UXOHWA.min.js → vidply.chunk-5HTX5QXW.min.js} +1 -1
  67. package/dist/prod/{vidply.chunk-ABPXVDLZ.min.js → vidply.chunk-6OGHHIYP.min.js} +2 -2
  68. package/dist/prod/{vidply.chunk-UK7YGOB5.min.js → vidply.chunk-GUPGMFUZ.min.js} +2 -2
  69. package/dist/prod/{vidply.chunk-WX3MVEAL.min.js → vidply.chunk-MWEBITCP.min.js} +1 -1
  70. package/dist/prod/{vidply.chunk-2TB6MUHO.min.js → vidply.chunk-PL3P3JHR.min.js} +1 -1
  71. package/dist/prod/{vidply.chunk-EKEYJWMG.min.js → vidply.chunk-QQZGV7TD.min.js} +2 -2
  72. package/dist/prod/{vidply.chunk-Z3YDSA7O.min.js → vidply.chunk-U5IRRYG5.min.js} +1 -1
  73. package/dist/prod/{vidply.chunk-TM6AXJVD.min.js → vidply.chunk-VKHV4QHA.min.js} +2 -2
  74. package/dist/prod/{vidply.chunk-P3PQBY7N.min.js → vidply.chunk-XA4M574A.min.js} +1 -1
  75. package/dist/prod/{vidply.chunk-RI3X7WHK.min.js → vidply.chunk-ZPTV4JEP.min.js} +2 -2
  76. package/dist/prod/{vidply.de-BCHXRQ53.min.js → vidply.de-UZHFCEZU.min.js} +2 -2
  77. package/dist/prod/{vidply.es-PICAKMZD.min.js → vidply.es-BOISUV2S.min.js} +2 -2
  78. package/dist/prod/vidply.esm.min.js +2 -2
  79. package/dist/prod/{vidply.fr-ZILI7CDR.min.js → vidply.fr-ICSMKPFM.min.js} +2 -2
  80. package/dist/prod/{vidply.ja-5WQ4OK4J.min.js → vidply.ja-VQ2HKGAY.min.js} +2 -2
  81. package/dist/vidply.css +5852 -5796
  82. package/dist/vidply.esm.min.meta.json +204 -149
  83. package/dist/vidply.min.css +1 -1
  84. package/package.json +11 -11
  85. package/src/controls/CaptionManager.ts +598 -598
  86. package/src/controls/ControlBar.ts +4191 -4159
  87. package/src/controls/KeyboardHelp.ts +326 -0
  88. package/src/controls/KeyboardManager.ts +387 -383
  89. package/src/controls/SettingsDialog.ts +488 -488
  90. package/src/controls/TranscriptManager.ts +2426 -2426
  91. package/src/core/AudioDescriptionManager.ts +863 -774
  92. package/src/core/DescriptionSpeechManager.ts +232 -0
  93. package/src/core/FloatingPlayerManager.ts +674 -674
  94. package/src/core/LazyInit.ts +80 -80
  95. package/src/core/MediaSessionManager.ts +341 -0
  96. package/src/core/MetadataAlertsManager.ts +514 -514
  97. package/src/core/Player.ts +2973 -2872
  98. package/src/core/PosterManager.ts +169 -169
  99. package/src/core/PseudoFullscreen.ts +179 -179
  100. package/src/core/ResponsiveManager.ts +231 -231
  101. package/src/core/ResumeManager.ts +236 -236
  102. package/src/core/SignLanguageManager.ts +1308 -1308
  103. package/src/core/ThemeManager.ts +157 -157
  104. package/src/features/PlaylistManager.ts +1838 -1827
  105. package/src/i18n/i18n.ts +223 -223
  106. package/src/i18n/languages/de.ts +244 -217
  107. package/src/i18n/languages/en.ts +245 -218
  108. package/src/i18n/languages/es.ts +241 -214
  109. package/src/i18n/languages/fr.ts +241 -214
  110. package/src/i18n/languages/ja.ts +241 -214
  111. package/src/icons/Icons.ts +168 -167
  112. package/src/index.ts +196 -193
  113. package/src/renderers/DASHRenderer.ts +1077 -1077
  114. package/src/renderers/HLSRenderer.ts +944 -944
  115. package/src/renderers/HTML5Renderer.ts +389 -389
  116. package/src/renderers/SoundCloudRenderer.ts +437 -437
  117. package/src/renderers/VimeoRenderer.ts +298 -298
  118. package/src/renderers/YouTubeRenderer.ts +290 -290
  119. package/src/styles/vidply.css +5852 -5796
  120. package/src/types/events.ts +128 -122
  121. package/src/types/globals.d.ts +313 -313
  122. package/src/types/options.ts +199 -182
  123. package/src/types/renderer.ts +58 -58
  124. package/src/utils/DOMUtils.ts +281 -281
  125. package/src/utils/DownloadInfo.ts +171 -171
  126. package/src/utils/DraggablePanel.ts +539 -539
  127. package/src/utils/DraggablePanelMenu.ts +195 -195
  128. package/src/utils/DraggableResizable.ts +997 -997
  129. package/src/utils/EventEmitter.ts +59 -59
  130. package/src/utils/FormUtils.ts +95 -95
  131. package/src/utils/MenuFactory.ts +483 -483
  132. package/src/utils/Sanitize.ts +70 -70
  133. package/src/utils/SettingsMenuFactory.ts +289 -289
  134. package/src/utils/StorageManager.ts +287 -287
  135. package/src/utils/TrackLabelUtils.ts +52 -52
  136. package/src/utils/UrlSafe.ts +69 -69
  137. package/src/utils/VideoFrameCapture.ts +114 -114
  138. package/src/utils/WindowComponents.ts +250 -250
  139. package/dist/dev/vidply.AudioDescriptionManager-R47Q4YR4.js.map +0 -7
  140. package/dist/dev/vidply.DASHRenderer-PWEMC3BT.js.map +0 -7
  141. package/dist/dev/vidply.FloatingPlayerManager-PKNNNAS2.js.map +0 -7
  142. package/dist/dev/vidply.HLSRenderer-E7RQ74HI.js.map +0 -7
  143. package/dist/dev/vidply.HTML5Renderer-BU4QJJJY.js +0 -12
  144. package/dist/dev/vidply.SignLanguageManager-FET5FQZ7.js.map +0 -7
  145. package/dist/dev/vidply.SoundCloudRenderer-RVSJZODO.js.map +0 -7
  146. package/dist/dev/vidply.TranscriptManager-5GXOQCLX.js.map +0 -7
  147. package/dist/dev/vidply.VimeoRenderer-ER2LSBBW.js.map +0 -7
  148. package/dist/dev/vidply.YouTubeRenderer-NFTOWJAB.js.map +0 -7
  149. package/dist/dev/vidply.chunk-64WXMTGL.js.map +0 -7
  150. package/dist/dev/vidply.chunk-BNFIUL2Z.js.map +0 -7
  151. package/dist/dev/vidply.chunk-JJ47NO24.js.map +0 -7
  152. package/dist/dev/vidply.chunk-KI7C3QVL.js.map +0 -7
  153. package/dist/dev/vidply.chunk-NQPURMQY.js.map +0 -7
  154. package/dist/dev/vidply.chunk-RZWZGDV7.js.map +0 -7
  155. package/dist/dev/vidply.chunk-W25HUCHS.js.map +0 -7
  156. package/dist/dev/vidply.de-4YAT7FFM.js.map +0 -7
  157. package/dist/dev/vidply.es-LEWIRUBH.js.map +0 -7
  158. package/dist/dev/vidply.fr-Q4GV2NOE.js.map +0 -7
  159. package/dist/dev/vidply.ja-S6ITA6WU.js.map +0 -7
  160. package/dist/prod/vidply.AudioDescriptionManager-RHLIJW5C.min.js +0 -6
  161. package/dist/prod/vidply.HTML5Renderer-IU5T3WEW.min.js +0 -6
  162. /package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js.map → vidply.CaptionStyleMenu-47RCKQF5.js.map} +0 -0
  163. /package/dist/dev/{vidply.HTML5Renderer-BU4QJJJY.js.map → vidply.HTML5Renderer-HLRQUOCU.js.map} +0 -0
  164. /package/dist/dev/{vidply.chunk-SK4V2DAU.js.map → vidply.chunk-HZ7UOKOU.js.map} +0 -0
  165. /package/dist/dev/{vidply.chunk-DZSDO3B7.js.map → vidply.chunk-JZNZX32Z.js.map} +0 -0
  166. /package/dist/dev/{vidply.chunk-VMSQAZM7.js.map → vidply.chunk-P25MRJ6O.js.map} +0 -0
  167. /package/dist/dev/{vidply.chunk-6RQA4ROD.js.map → vidply.chunk-PRFNESYA.js.map} +0 -0
@@ -1,39 +1,39 @@
1
1
  /*!
2
- * VidPly v1.2.0 - Universal, Accessible Video Player
2
+ * VidPly v1.2.3 - Universal, Accessible Video Player
3
3
  * (c) 2026 Matthias Peltzer
4
4
  * Released under GPL-2.0-or-later License
5
5
  */
6
6
  import {
7
7
  TimeUtils
8
- } from "./vidply.chunk-SK4V2DAU.js";
8
+ } from "./vidply.chunk-HZ7UOKOU.js";
9
9
  import {
10
10
  createIconElement,
11
11
  createPlayOverlay
12
- } from "./vidply.chunk-KI7C3QVL.js";
12
+ } from "./vidply.chunk-UI3RYCVF.js";
13
13
  import {
14
14
  focusElement
15
- } from "./vidply.chunk-VMSQAZM7.js";
15
+ } from "./vidply.chunk-P25MRJ6O.js";
16
16
  import {
17
17
  HTML5Renderer
18
- } from "./vidply.chunk-W25HUCHS.js";
18
+ } from "./vidply.chunk-CGYIED2I.js";
19
19
  import {
20
20
  CaptionManager
21
- } from "./vidply.chunk-64WXMTGL.js";
21
+ } from "./vidply.chunk-JKMQGLCJ.js";
22
22
  import {
23
23
  StorageManager
24
- } from "./vidply.chunk-BNFIUL2Z.js";
24
+ } from "./vidply.chunk-6LYZYI5G.js";
25
25
  import {
26
26
  debounce,
27
27
  isMobile,
28
28
  rafWithTimeout,
29
29
  reducedMotionScrollOptions,
30
30
  throttle
31
- } from "./vidply.chunk-6RQA4ROD.js";
31
+ } from "./vidply.chunk-PRFNESYA.js";
32
32
  import {
33
33
  DOMUtils,
34
34
  i18n,
35
35
  isForbiddenKey
36
- } from "./vidply.chunk-RZWZGDV7.js";
36
+ } from "./vidply.chunk-I6EDRYGJ.js";
37
37
 
38
38
  // src/utils/EventEmitter.ts
39
39
  var EventEmitter = class {
@@ -960,6 +960,12 @@ var ControlBar = class {
960
960
  }
961
961
  this.rightButtons.appendChild(btn);
962
962
  }
963
+ if (this.player.options.helpButton && this.player.options.keyboard) {
964
+ const btn = this.createHelpButton();
965
+ btn.dataset.overflowPriority = "3";
966
+ btn.dataset.overflowPriorityMobile = "3";
967
+ this.rightButtons.appendChild(btn);
968
+ }
963
969
  this.overflowMenuButton = this.createOverflowMenuButton();
964
970
  this.overflowMenuButton.style.display = "none";
965
971
  this.rightButtons.appendChild(this.overflowMenuButton);
@@ -2051,7 +2057,7 @@ var ControlBar = class {
2051
2057
  return button;
2052
2058
  }
2053
2059
  showCaptionStyleMenu(button) {
2054
- import("./vidply.CaptionStyleMenu-7XHRBWUM.js").then(({ showCaptionStyleMenu }) => showCaptionStyleMenu(this, button)).catch((error) => this.player.log("Failed to load caption style menu:", error, "error"));
2060
+ import("./vidply.CaptionStyleMenu-47RCKQF5.js").then(({ showCaptionStyleMenu }) => showCaptionStyleMenu(this, button)).catch((error) => this.player.log("Failed to load caption style menu:", error, "error"));
2055
2061
  }
2056
2062
  createSpeedButton() {
2057
2063
  const button = DOMUtils.createElement("button", {
@@ -2274,6 +2280,24 @@ var ControlBar = class {
2274
2280
  const isVisible = this.player.transcriptManager && this.player.transcriptManager.isVisible;
2275
2281
  this.controls.transcript.setAttribute("aria-expanded", isVisible ? "true" : "false");
2276
2282
  }
2283
+ createHelpButton() {
2284
+ const ariaLabel = i18n.t("help.button");
2285
+ const button = DOMUtils.createElement("button", {
2286
+ className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-help`,
2287
+ attributes: {
2288
+ "type": "button",
2289
+ "aria-label": ariaLabel,
2290
+ "aria-haspopup": "dialog"
2291
+ }
2292
+ });
2293
+ button.appendChild(createIconElement("help"));
2294
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
2295
+ button.addEventListener("click", () => {
2296
+ this.player.toggleKeyboardHelp();
2297
+ });
2298
+ this.controls.help = button;
2299
+ return button;
2300
+ }
2277
2301
  createAudioDescriptionButton() {
2278
2302
  const ariaLabel = i18n.t("player.audioDescription");
2279
2303
  const button = DOMUtils.createElement("button", {
@@ -3507,6 +3531,9 @@ var KeyboardManager = class {
3507
3531
  return true;
3508
3532
  }
3509
3533
  return false;
3534
+ case "help":
3535
+ this.player.toggleKeyboardHelp();
3536
+ return true;
3510
3537
  default:
3511
3538
  return false;
3512
3539
  }
@@ -3594,6 +3621,252 @@ var KeyboardManager = class {
3594
3621
  }
3595
3622
  };
3596
3623
 
3624
+ // src/core/MediaSessionManager.ts
3625
+ var POSITION_THROTTLE_MS = 1e3;
3626
+ var activeManager = null;
3627
+ var MediaSessionManager = class {
3628
+ player;
3629
+ supported;
3630
+ handlers = {};
3631
+ boundActions = [];
3632
+ lastPositionUpdate = 0;
3633
+ constructor(player) {
3634
+ this.player = player;
3635
+ this.supported = typeof navigator !== "undefined" && "mediaSession" in navigator;
3636
+ if (!this.supported) return;
3637
+ this.attachEvents();
3638
+ if (activeManager === null) {
3639
+ this.claimSession();
3640
+ }
3641
+ }
3642
+ /** Does this manager currently own the global media session? */
3643
+ isActive() {
3644
+ return activeManager === this;
3645
+ }
3646
+ /**
3647
+ * Take ownership of the global session: (re)register the action handlers so
3648
+ * the OS controls drive this player, and refresh metadata/state/position.
3649
+ */
3650
+ claimSession() {
3651
+ if (!this.supported) return;
3652
+ activeManager = this;
3653
+ this.setupActionHandlers();
3654
+ this.updateMetadata();
3655
+ this.updatePlaybackState();
3656
+ this.updatePositionState(true);
3657
+ }
3658
+ get session() {
3659
+ return navigator.mediaSession;
3660
+ }
3661
+ setActionHandler(action, handler) {
3662
+ try {
3663
+ this.session.setActionHandler(action, handler);
3664
+ if (handler && !this.boundActions.includes(action)) {
3665
+ this.boundActions.push(action);
3666
+ }
3667
+ } catch {
3668
+ }
3669
+ }
3670
+ setupActionHandlers() {
3671
+ this.setActionHandler("play", () => this.player.play());
3672
+ this.setActionHandler("pause", () => this.player.pause());
3673
+ this.setActionHandler("stop", () => this.player.stop());
3674
+ this.setActionHandler("seekbackward", (details) => {
3675
+ this.player.seekBackward(this.offsetFrom(details));
3676
+ });
3677
+ this.setActionHandler("seekforward", (details) => {
3678
+ this.player.seekForward(this.offsetFrom(details));
3679
+ });
3680
+ this.setActionHandler("seekto", (details) => {
3681
+ if (details && typeof details.seekTime === "number") {
3682
+ this.player.seek(details.seekTime);
3683
+ }
3684
+ });
3685
+ this.updateTrackHandlers();
3686
+ }
3687
+ offsetFrom(details) {
3688
+ const offset = details && typeof details.seekOffset === "number" ? details.seekOffset : void 0;
3689
+ return typeof offset === "number" && offset > 0 ? offset : void 0;
3690
+ }
3691
+ /**
3692
+ * previous/next track only make sense with a multi-item playlist; bind
3693
+ * or clear them whenever the playlist state changes so the OS shows the
3694
+ * correct affordances.
3695
+ */
3696
+ updateTrackHandlers() {
3697
+ const pm = this.player.playlistManager;
3698
+ const hasPlaylist = Boolean(pm && Array.isArray(pm.tracks) && pm.tracks.length > 1);
3699
+ if (hasPlaylist && pm) {
3700
+ this.setActionHandler("previoustrack", () => pm.previous());
3701
+ this.setActionHandler("nexttrack", () => pm.next());
3702
+ } else {
3703
+ this.setActionHandler("previoustrack", null);
3704
+ this.setActionHandler("nexttrack", null);
3705
+ }
3706
+ }
3707
+ attachEvents() {
3708
+ this.handlers = {
3709
+ // Starting playback makes this player the session owner, taking over
3710
+ // the OS controls from any other player on the page.
3711
+ play: () => this.claimSession(),
3712
+ pause: () => {
3713
+ if (!this.isActive()) return;
3714
+ this.updatePlaybackState();
3715
+ this.updatePositionState(true);
3716
+ },
3717
+ ended: () => {
3718
+ if (!this.isActive()) return;
3719
+ this.updatePlaybackState();
3720
+ },
3721
+ timeupdate: () => {
3722
+ if (!this.isActive()) return;
3723
+ this.updatePositionState();
3724
+ },
3725
+ durationchange: () => {
3726
+ if (!this.isActive()) return;
3727
+ this.updatePositionState(true);
3728
+ },
3729
+ ratechange: () => {
3730
+ if (!this.isActive()) return;
3731
+ this.updatePositionState(true);
3732
+ },
3733
+ loadedmetadata: () => {
3734
+ if (!this.isActive()) return;
3735
+ this.updateMetadata();
3736
+ this.updatePositionState(true);
3737
+ },
3738
+ playlisttrackchange: () => {
3739
+ if (!this.isActive()) return;
3740
+ this.updateMetadata();
3741
+ this.updateTrackHandlers();
3742
+ this.updatePositionState(true);
3743
+ }
3744
+ };
3745
+ for (const [event, handler] of Object.entries(this.handlers)) {
3746
+ if (handler) {
3747
+ this.player.on(event, handler);
3748
+ }
3749
+ }
3750
+ }
3751
+ resolveMetadata() {
3752
+ const opts = this.player.options;
3753
+ let title = opts.title || "";
3754
+ let artist = opts.artist || "";
3755
+ const album = opts.album || "";
3756
+ let poster = opts.poster || null;
3757
+ const pm = this.player.playlistManager;
3758
+ if (pm && Array.isArray(pm.tracks) && pm.currentIndex >= 0) {
3759
+ const track = pm.tracks[pm.currentIndex];
3760
+ if (track) {
3761
+ if (track.title) title = track.title;
3762
+ if (track.artist) artist = track.artist;
3763
+ if (track.poster) poster = track.poster;
3764
+ }
3765
+ }
3766
+ if (!title && typeof document !== "undefined") {
3767
+ title = document.title || "VidPly";
3768
+ }
3769
+ return { title, artist, album, poster };
3770
+ }
3771
+ updateMetadata() {
3772
+ if (!this.supported || typeof window === "undefined" || typeof window.MediaMetadata === "undefined") {
3773
+ return;
3774
+ }
3775
+ const { title, artist, album, poster } = this.resolveMetadata();
3776
+ const artwork = [];
3777
+ if (poster) {
3778
+ try {
3779
+ const src = this.player.resolvePosterPath(poster);
3780
+ if (src) {
3781
+ artwork.push({ src });
3782
+ }
3783
+ } catch {
3784
+ }
3785
+ }
3786
+ try {
3787
+ this.session.metadata = new window.MediaMetadata({
3788
+ title,
3789
+ artist,
3790
+ album,
3791
+ artwork
3792
+ });
3793
+ } catch {
3794
+ }
3795
+ }
3796
+ updatePlaybackState() {
3797
+ if (!this.supported) return;
3798
+ try {
3799
+ this.session.playbackState = this.player.state.playing ? "playing" : "paused";
3800
+ } catch {
3801
+ }
3802
+ }
3803
+ /**
3804
+ * Push the current position to the OS scrubber. `timeupdate` fires
3805
+ * several times a second, so non-forced updates are throttled.
3806
+ */
3807
+ updatePositionState(force = false) {
3808
+ if (!this.supported || typeof this.session.setPositionState !== "function") {
3809
+ return;
3810
+ }
3811
+ const now = Date.now();
3812
+ if (!force && now - this.lastPositionUpdate < POSITION_THROTTLE_MS) {
3813
+ return;
3814
+ }
3815
+ this.lastPositionUpdate = now;
3816
+ const duration = this.player.state.duration;
3817
+ if (!Number.isFinite(duration) || duration <= 0) {
3818
+ try {
3819
+ this.session.setPositionState();
3820
+ } catch {
3821
+ }
3822
+ return;
3823
+ }
3824
+ const playbackRate = this.player.state.playbackSpeed || 1;
3825
+ const position = Math.min(Math.max(0, this.player.state.currentTime || 0), duration);
3826
+ try {
3827
+ this.session.setPositionState({
3828
+ duration,
3829
+ playbackRate: playbackRate > 0 ? playbackRate : 1,
3830
+ position
3831
+ });
3832
+ } catch {
3833
+ }
3834
+ }
3835
+ destroy() {
3836
+ if (!this.supported) return;
3837
+ for (const [event, handler] of Object.entries(this.handlers)) {
3838
+ if (handler) {
3839
+ this.player.off(event, handler);
3840
+ }
3841
+ }
3842
+ this.handlers = {};
3843
+ if (this.isActive()) {
3844
+ for (const action of this.boundActions) {
3845
+ try {
3846
+ this.session.setActionHandler(action, null);
3847
+ } catch {
3848
+ }
3849
+ }
3850
+ try {
3851
+ this.session.metadata = null;
3852
+ } catch {
3853
+ }
3854
+ try {
3855
+ if (typeof this.session.setPositionState === "function") {
3856
+ this.session.setPositionState();
3857
+ }
3858
+ } catch {
3859
+ }
3860
+ try {
3861
+ this.session.playbackState = "none";
3862
+ } catch {
3863
+ }
3864
+ activeManager = null;
3865
+ }
3866
+ this.boundActions = [];
3867
+ }
3868
+ };
3869
+
3597
3870
  // src/utils/UrlSafe.ts
3598
3871
  function sanitizePosterUrl(input) {
3599
3872
  if (typeof input !== "string" || input.length === 0 || input.length > 4096) {
@@ -4694,27 +4967,280 @@ var MetadataAlertsManager = class {
4694
4967
  }
4695
4968
  };
4696
4969
 
4970
+ // src/controls/KeyboardHelp.ts
4971
+ var ACTION_ORDER = [
4972
+ "play-pause",
4973
+ "seek-backward",
4974
+ "seek-forward",
4975
+ "volume-up",
4976
+ "volume-down",
4977
+ "mute",
4978
+ "captions",
4979
+ "caption-style-menu",
4980
+ "speed-down",
4981
+ "speed-up",
4982
+ "speed-menu",
4983
+ "quality-menu",
4984
+ "chapters-menu",
4985
+ "transcript-toggle",
4986
+ "fullscreen",
4987
+ "help"
4988
+ ];
4989
+ var ACTION_REQUIRES_CONTROL = {
4990
+ captions: "captions",
4991
+ "caption-style-menu": "captionStyle",
4992
+ "speed-down": "speed",
4993
+ "speed-up": "speed",
4994
+ "speed-menu": "speed",
4995
+ "quality-menu": "quality",
4996
+ "chapters-menu": "chapters",
4997
+ "transcript-toggle": "transcript",
4998
+ fullscreen: "fullscreen"
4999
+ };
5000
+ var KeyboardHelp = class {
5001
+ player;
5002
+ isOpen = false;
5003
+ overlay = null;
5004
+ _triggerElement = null;
5005
+ _keydownHandler = null;
5006
+ _content = null;
5007
+ constructor(player) {
5008
+ this.player = player;
5009
+ }
5010
+ get prefix() {
5011
+ return this.player.options.classPrefix;
5012
+ }
5013
+ /**
5014
+ * Turn a raw KeyboardEvent.key value into a human-readable label. Arrow
5015
+ * keys become universally understood glyphs; the space bar and single
5016
+ * letters are normalised for legibility.
5017
+ */
5018
+ formatKey(key) {
5019
+ switch (key) {
5020
+ case " ":
5021
+ return i18n.t("help.keys.space");
5022
+ case "ArrowUp":
5023
+ return "↑";
5024
+ case "ArrowDown":
5025
+ return "↓";
5026
+ case "ArrowLeft":
5027
+ return "←";
5028
+ case "ArrowRight":
5029
+ return "→";
5030
+ case "Escape":
5031
+ return "Esc";
5032
+ default:
5033
+ return key.length === 1 ? key.toUpperCase() : key;
5034
+ }
5035
+ }
5036
+ createElement() {
5037
+ const titleId = `${this.prefix}-help-title-${this.player.instanceId}`;
5038
+ const overlay = DOMUtils.createElement("div", {
5039
+ className: `${this.prefix}-settings-overlay ${this.prefix}-help-overlay`,
5040
+ attributes: {
5041
+ role: "dialog",
5042
+ "aria-modal": "true",
5043
+ "aria-labelledby": titleId
5044
+ }
5045
+ });
5046
+ overlay.style.display = "none";
5047
+ const dialog = DOMUtils.createElement("div", {
5048
+ className: `${this.prefix}-settings-dialog ${this.prefix}-help-dialog`
5049
+ });
5050
+ const header = DOMUtils.createElement("div", {
5051
+ className: `${this.prefix}-settings-header`
5052
+ });
5053
+ const title = DOMUtils.createElement("h2", {
5054
+ textContent: i18n.t("help.title"),
5055
+ attributes: { id: titleId }
5056
+ });
5057
+ const closeButton = DOMUtils.createElement("button", {
5058
+ className: `${this.prefix}-button ${this.prefix}-settings-close`,
5059
+ attributes: {
5060
+ type: "button",
5061
+ "aria-label": i18n.t("help.close")
5062
+ }
5063
+ });
5064
+ closeButton.appendChild(createIconElement("close"));
5065
+ closeButton.addEventListener("click", () => this.hide());
5066
+ header.appendChild(title);
5067
+ header.appendChild(closeButton);
5068
+ const content = DOMUtils.createElement("div", {
5069
+ className: `${this.prefix}-settings-content`
5070
+ });
5071
+ this._content = content;
5072
+ content.appendChild(this.buildShortcutList());
5073
+ dialog.appendChild(header);
5074
+ dialog.appendChild(content);
5075
+ overlay.appendChild(dialog);
5076
+ overlay.addEventListener("click", (e) => {
5077
+ if (e.target === overlay) {
5078
+ this.hide();
5079
+ }
5080
+ });
5081
+ this._keydownHandler = (e) => {
5082
+ if (!this.isOpen || !this.overlay) return;
5083
+ if (e.key === "Escape") {
5084
+ e.preventDefault();
5085
+ this.hide();
5086
+ return;
5087
+ }
5088
+ if (e.key === "Tab") {
5089
+ const focusable = Array.from(
5090
+ this.overlay.querySelectorAll(
5091
+ 'button, [href], select, input, textarea, [tabindex]:not([tabindex="-1"])'
5092
+ )
5093
+ ).filter((el) => !el.hasAttribute("disabled") && el.offsetParent !== null);
5094
+ if (focusable.length === 0) return;
5095
+ const first = focusable[0];
5096
+ const last = focusable[focusable.length - 1];
5097
+ if (!first || !last) return;
5098
+ const active = document.activeElement;
5099
+ if (e.shiftKey && (active === first || !this.overlay.contains(active))) {
5100
+ e.preventDefault();
5101
+ last.focus({ preventScroll: true });
5102
+ } else if (!e.shiftKey && active === last) {
5103
+ e.preventDefault();
5104
+ first.focus({ preventScroll: true });
5105
+ }
5106
+ }
5107
+ };
5108
+ const lifecycleSignal = this.player.lifecycleSignal;
5109
+ document.addEventListener(
5110
+ "keydown",
5111
+ this._keydownHandler,
5112
+ lifecycleSignal ? { signal: lifecycleSignal } : void 0
5113
+ );
5114
+ return overlay;
5115
+ }
5116
+ /**
5117
+ * Whether a shortcut row is worth showing for *this* player. Feature actions
5118
+ * are hidden when their control isn't present (e.g. no captions track, an
5119
+ * audio-only player with no fullscreen). Core actions are always relevant.
5120
+ *
5121
+ * When the player has no control bar we can't infer availability, so nothing
5122
+ * is hidden — the shortcuts still work and we'd rather over-show than mislead.
5123
+ */
5124
+ isActionRelevant(action) {
5125
+ const requiredControl = ACTION_REQUIRES_CONTROL[action];
5126
+ if (!requiredControl) return true;
5127
+ const controlBar = this.player.controlBar;
5128
+ if (!controlBar || !controlBar.controls) return true;
5129
+ return Boolean(controlBar.controls[requiredControl]);
5130
+ }
5131
+ buildShortcutList() {
5132
+ const list = DOMUtils.createElement("dl", {
5133
+ className: `${this.prefix}-help-list`
5134
+ });
5135
+ const shortcuts = this.player.options.keyboardShortcuts;
5136
+ for (const action of ACTION_ORDER) {
5137
+ const keys = shortcuts[action];
5138
+ if (!Array.isArray(keys) || keys.length === 0) continue;
5139
+ if (!this.isActionRelevant(action)) continue;
5140
+ const term = DOMUtils.createElement("dt", {
5141
+ className: `${this.prefix}-help-action`,
5142
+ textContent: i18n.t(`help.actions.${action}`)
5143
+ });
5144
+ const desc = DOMUtils.createElement("dd", {
5145
+ className: `${this.prefix}-help-keys`
5146
+ });
5147
+ keys.forEach((key, index) => {
5148
+ if (index > 0) {
5149
+ desc.appendChild(
5150
+ DOMUtils.createElement("span", {
5151
+ className: `${this.prefix}-help-key-sep`,
5152
+ textContent: i18n.t("help.or")
5153
+ })
5154
+ );
5155
+ }
5156
+ desc.appendChild(
5157
+ DOMUtils.createElement("kbd", {
5158
+ className: `${this.prefix}-help-key`,
5159
+ textContent: this.formatKey(key)
5160
+ })
5161
+ );
5162
+ });
5163
+ list.appendChild(term);
5164
+ list.appendChild(desc);
5165
+ }
5166
+ return list;
5167
+ }
5168
+ show() {
5169
+ if (this.isOpen) return;
5170
+ if (!this.overlay) {
5171
+ this.overlay = this.createElement();
5172
+ this.player.container.appendChild(this.overlay);
5173
+ } else if (this._content) {
5174
+ this._content.replaceChildren(this.buildShortcutList());
5175
+ }
5176
+ const active = typeof document !== "undefined" ? document.activeElement : null;
5177
+ this._triggerElement = active && typeof active.focus === "function" ? active : null;
5178
+ this.overlay.style.display = "flex";
5179
+ this.isOpen = true;
5180
+ const closeButton = this.overlay.querySelector(`.${this.prefix}-settings-close`);
5181
+ closeButton?.focus({ preventScroll: true });
5182
+ this.player.emit("keyboardhelpopen");
5183
+ }
5184
+ hide() {
5185
+ if (!this.overlay) return;
5186
+ this.overlay.style.display = "none";
5187
+ this.isOpen = false;
5188
+ const trigger = this._triggerElement;
5189
+ this._triggerElement = null;
5190
+ if (trigger && document.contains(trigger)) {
5191
+ try {
5192
+ trigger.focus({ preventScroll: true });
5193
+ } catch {
5194
+ this.player.container?.focus();
5195
+ }
5196
+ } else {
5197
+ this.player.container?.focus();
5198
+ }
5199
+ this.player.emit("keyboardhelpclose");
5200
+ }
5201
+ toggle() {
5202
+ if (this.isOpen) {
5203
+ this.hide();
5204
+ } else {
5205
+ this.show();
5206
+ }
5207
+ }
5208
+ destroy() {
5209
+ if (this._keydownHandler) {
5210
+ document.removeEventListener("keydown", this._keydownHandler);
5211
+ this._keydownHandler = null;
5212
+ }
5213
+ if (this.overlay && this.overlay.parentNode) {
5214
+ this.overlay.parentNode.removeChild(this.overlay);
5215
+ }
5216
+ this.overlay = null;
5217
+ this._content = null;
5218
+ this._triggerElement = null;
5219
+ this.isOpen = false;
5220
+ }
5221
+ };
5222
+
4697
5223
  // src/core/Player.ts
4698
5224
  var AudioDescriptionManagerModule = null;
4699
5225
  var SignLanguageManagerModule = null;
4700
5226
  var FloatingPlayerManagerModule = null;
4701
5227
  async function loadAudioDescriptionManager() {
4702
5228
  if (!AudioDescriptionManagerModule) {
4703
- const module = await import("./vidply.AudioDescriptionManager-R47Q4YR4.js");
5229
+ const module = await import("./vidply.AudioDescriptionManager-JYYLK5KB.js");
4704
5230
  AudioDescriptionManagerModule = module.AudioDescriptionManager;
4705
5231
  }
4706
5232
  return AudioDescriptionManagerModule;
4707
5233
  }
4708
5234
  async function loadSignLanguageManager() {
4709
5235
  if (!SignLanguageManagerModule) {
4710
- const module = await import("./vidply.SignLanguageManager-FET5FQZ7.js");
5236
+ const module = await import("./vidply.SignLanguageManager-FLUCBW5H.js");
4711
5237
  SignLanguageManagerModule = module.SignLanguageManager;
4712
5238
  }
4713
5239
  return SignLanguageManagerModule;
4714
5240
  }
4715
5241
  async function loadFloatingPlayerManager() {
4716
5242
  if (!FloatingPlayerManagerModule) {
4717
- const module = await import("./vidply.FloatingPlayerManager-PKNNNAS2.js");
5243
+ const module = await import("./vidply.FloatingPlayerManager-C2UVRPZY.js");
4718
5244
  FloatingPlayerManagerModule = module.FloatingPlayerManager;
4719
5245
  }
4720
5246
  return FloatingPlayerManagerModule;
@@ -4777,9 +5303,11 @@ var Player = class _Player extends EventEmitter {
4777
5303
  controlBar = null;
4778
5304
  captionManager = null;
4779
5305
  keyboardManager = null;
5306
+ mediaSessionManager = null;
4780
5307
  transcriptManager = null;
4781
5308
  playlistManager = null;
4782
5309
  settingsDialog = null;
5310
+ keyboardHelp = null;
4783
5311
  audioDescriptionManager = null;
4784
5312
  signLanguageManager = null;
4785
5313
  floatingPlayerManager = null;
@@ -4895,6 +5423,11 @@ var Player = class _Player extends EventEmitter {
4895
5423
  poster: null,
4896
5424
  responsive: true,
4897
5425
  fillContainer: false,
5426
+ // Media metadata + OS media controls (Media Session API)
5427
+ title: null,
5428
+ artist: null,
5429
+ album: null,
5430
+ mediaSession: true,
4898
5431
  // Playback
4899
5432
  autoplay: false,
4900
5433
  loop: false,
@@ -4942,6 +5475,7 @@ var Player = class _Player extends EventEmitter {
4942
5475
  captionsButton: true,
4943
5476
  transcriptButton: true,
4944
5477
  fullscreenButton: true,
5478
+ helpButton: true,
4945
5479
  pipButton: false,
4946
5480
  floating: false,
4947
5481
  floatingPosition: "bottom-right",
@@ -4967,6 +5501,9 @@ var Player = class _Player extends EventEmitter {
4967
5501
  audioDescriptionSrc: null,
4968
5502
  // URL to audio-described version
4969
5503
  audioDescriptionButton: true,
5504
+ audioDescriptionMode: "auto",
5505
+ audioDescriptionSpeech: true,
5506
+ audioDescriptionExtended: true,
4970
5507
  // Sign Language
4971
5508
  signLanguage: true,
4972
5509
  signLanguageSrc: null,
@@ -4997,7 +5534,8 @@ var Player = class _Player extends EventEmitter {
4997
5534
  "speed-menu": ["s"],
4998
5535
  "quality-menu": ["q"],
4999
5536
  "chapters-menu": ["j"],
5000
- "transcript-toggle": ["t"]
5537
+ "transcript-toggle": ["t"],
5538
+ "help": ["?"]
5001
5539
  },
5002
5540
  // Accessibility
5003
5541
  ariaLabels: {},
@@ -5320,6 +5858,9 @@ var Player = class _Player extends EventEmitter {
5320
5858
  if (this.options.keyboard) {
5321
5859
  this.keyboardManager = new KeyboardManager(this);
5322
5860
  }
5861
+ if (this.options.mediaSession) {
5862
+ this.mediaSessionManager = new MediaSessionManager(this);
5863
+ }
5323
5864
  this.setupResponsiveHandlers();
5324
5865
  if (this.options.startTime > 0) {
5325
5866
  this.seek(this.options.startTime);
@@ -5364,7 +5905,7 @@ var Player = class _Player extends EventEmitter {
5364
5905
  if (!this.options.transcript && !this.options.transcriptButton) {
5365
5906
  return null;
5366
5907
  }
5367
- const module = await import("./vidply.TranscriptManager-5GXOQCLX.js");
5908
+ const module = await import("./vidply.TranscriptManager-Y2RYXHJW.js");
5368
5909
  const fallbackDefault = module.default;
5369
5910
  const Manager = module.TranscriptManager || fallbackDefault;
5370
5911
  if (!Manager) {
@@ -5392,15 +5933,33 @@ var Player = class _Player extends EventEmitter {
5392
5933
  if (this.audioDescriptionManager) {
5393
5934
  return this.audioDescriptionManager;
5394
5935
  }
5395
- const hasAudioDescSrc = this.options.audioDescriptionSrc || this.audioDescriptionSrc;
5396
- const hasAudioDescButton = this.options.audioDescriptionButton;
5397
- if (!hasAudioDescSrc && !hasAudioDescButton) {
5936
+ if (!this.hasAudioDescriptionContent()) {
5398
5937
  return null;
5399
5938
  }
5400
5939
  const AudioDescManager = await loadAudioDescriptionManager();
5401
5940
  this.audioDescriptionManager = new AudioDescManager(this);
5402
5941
  return this.audioDescriptionManager;
5403
5942
  }
5943
+ /**
5944
+ * True when the current media actually exposes audio-description content:
5945
+ * an explicit described-audio source, `<source>` elements carrying
5946
+ * `data-desc-src` / `data-orig-src`, or a `descriptions` text track.
5947
+ * Mirrors `ControlBar.hasAudioDescription()` so the chunk load and the
5948
+ * button visibility stay in lock-step.
5949
+ */
5950
+ hasAudioDescriptionContent() {
5951
+ if (this.options.audioDescriptionSrc || this.audioDescriptionSrc) {
5952
+ return true;
5953
+ }
5954
+ const hasSourceElementsWithDesc = this.sourceElements.some(
5955
+ (el) => el.getAttribute("data-desc-src") || el.getAttribute("data-orig-src")
5956
+ );
5957
+ if (hasSourceElementsWithDesc) {
5958
+ return true;
5959
+ }
5960
+ const textTracks = this.element ? Array.from(this.element.textTracks || []) : [];
5961
+ return textTracks.some((track) => track.kind === "descriptions");
5962
+ }
5404
5963
  // ============================================
5405
5964
  // Resume Playback Methods
5406
5965
  // ============================================
@@ -5412,16 +5971,24 @@ var Player = class _Player extends EventEmitter {
5412
5971
  if (this.signLanguageManager) {
5413
5972
  return this.signLanguageManager;
5414
5973
  }
5415
- const hasSignLangSrc = this.options.signLanguageSrc || this.signLanguageSrc;
5416
- const hasSignLangSources = this.options.signLanguageSources && Object.keys(this.options.signLanguageSources).length > 0;
5417
- const hasSignLangButton = this.options.signLanguageButton;
5418
- if (!hasSignLangSrc && !hasSignLangSources && !hasSignLangButton) {
5974
+ if (!this.hasSignLanguageContent()) {
5419
5975
  return null;
5420
5976
  }
5421
5977
  const SignLangManager = await loadSignLanguageManager();
5422
5978
  this.signLanguageManager = new SignLangManager(this);
5423
5979
  return this.signLanguageManager;
5424
5980
  }
5981
+ /**
5982
+ * True when a sign-language video source (single `signLanguageSrc` or a
5983
+ * `signLanguageSources` map) is configured. Mirrors
5984
+ * `ControlBar.hasSignLanguage()`.
5985
+ */
5986
+ hasSignLanguageContent() {
5987
+ if (this.options.signLanguageSrc || this.signLanguageSrc) {
5988
+ return true;
5989
+ }
5990
+ return !!(this.options.signLanguageSources && Object.keys(this.options.signLanguageSources).length > 0);
5991
+ }
5425
5992
  /**
5426
5993
  * Lazy-load and instantiate the floating (in-page PiP) manager. Only
5427
5994
  * created when `options.floating === true` and the media element is a
@@ -5446,13 +6013,10 @@ var Player = class _Player extends EventEmitter {
5446
6013
  */
5447
6014
  async initFeatureManagers() {
5448
6015
  const promises = [];
5449
- const hasSourceElementsWithDesc = this.sourceElements.some(
5450
- (el) => el.getAttribute("data-desc-src") || el.getAttribute("data-orig-src")
5451
- );
5452
- if (this.options.audioDescriptionButton || this.options.audioDescriptionSrc || this.audioDescriptionSrc || hasSourceElementsWithDesc) {
6016
+ if (this.hasAudioDescriptionContent()) {
5453
6017
  promises.push(this.ensureAudioDescriptionManager());
5454
6018
  }
5455
- if (this.options.signLanguageButton || this.options.signLanguageSrc || this.signLanguageSrc || this.options.signLanguageSources && Object.keys(this.options.signLanguageSources).length > 0) {
6019
+ if (this.hasSignLanguageContent()) {
5456
6020
  promises.push(this.ensureSignLanguageManager());
5457
6021
  }
5458
6022
  if (this.options.floating && this.element && this.element.tagName === "VIDEO") {
@@ -5782,6 +6346,9 @@ var Player = class _Player extends EventEmitter {
5782
6346
  }
5783
6347
  this.currentSource = src;
5784
6348
  this._pendingSource = null;
6349
+ if (this.hasAudioDescriptionContent()) {
6350
+ await this.ensureAudioDescriptionManager();
6351
+ }
5785
6352
  this.audioDescriptionManager?.initFromSourceElements(this.sourceElements, this.trackElements);
5786
6353
  if (!this.originalSrc) {
5787
6354
  this.originalSrc = src;
@@ -5807,19 +6374,19 @@ var Player = class _Player extends EventEmitter {
5807
6374
  }
5808
6375
  async _detectRendererClass(src) {
5809
6376
  if (src.includes("youtube.com") || src.includes("youtu.be")) {
5810
- const module = await import("./vidply.YouTubeRenderer-NFTOWJAB.js");
6377
+ const module = await import("./vidply.YouTubeRenderer-EU2BARZF.js");
5811
6378
  return module.YouTubeRenderer ?? module.default;
5812
6379
  } else if (src.includes("vimeo.com")) {
5813
- const module = await import("./vidply.VimeoRenderer-ER2LSBBW.js");
6380
+ const module = await import("./vidply.VimeoRenderer-RQFQD7OE.js");
5814
6381
  return module.VimeoRenderer ?? module.default;
5815
6382
  } else if (src.includes(".m3u8")) {
5816
- const module = await import("./vidply.HLSRenderer-E7RQ74HI.js");
6383
+ const module = await import("./vidply.HLSRenderer-ZVBK2TNQ.js");
5817
6384
  return module.HLSRenderer ?? module.default;
5818
6385
  } else if (src.includes(".mpd")) {
5819
- const module = await import("./vidply.DASHRenderer-PWEMC3BT.js");
6386
+ const module = await import("./vidply.DASHRenderer-VEJ4HDHO.js");
5820
6387
  return module.DASHRenderer ?? module.default;
5821
6388
  } else if (src.includes("soundcloud.com") || src.includes("api.soundcloud.com")) {
5822
- const module = await import("./vidply.SoundCloudRenderer-RVSJZODO.js");
6389
+ const module = await import("./vidply.SoundCloudRenderer-MGMAJ7NK.js");
5823
6390
  return module.SoundCloudRenderer ?? module.default;
5824
6391
  }
5825
6392
  return HTML5Renderer;
@@ -6611,6 +7178,26 @@ var Player = class _Player extends EventEmitter {
6611
7178
  }
6612
7179
  hideSettings() {
6613
7180
  }
7181
+ /**
7182
+ * Lazily build (on first use) and toggle the keyboard-shortcuts help
7183
+ * dialog. Reflects the live `keyboardShortcuts` bindings, including any
7184
+ * consumer overrides.
7185
+ */
7186
+ toggleKeyboardHelp() {
7187
+ if (!this.keyboardHelp) {
7188
+ this.keyboardHelp = new KeyboardHelp(this);
7189
+ }
7190
+ this.keyboardHelp.toggle();
7191
+ }
7192
+ showKeyboardHelp() {
7193
+ if (!this.keyboardHelp) {
7194
+ this.keyboardHelp = new KeyboardHelp(this);
7195
+ }
7196
+ this.keyboardHelp.show();
7197
+ }
7198
+ hideKeyboardHelp() {
7199
+ this.keyboardHelp?.hide();
7200
+ }
6614
7201
  // Utility methods
6615
7202
  getCurrentTime() {
6616
7203
  return this.state.currentTime;
@@ -6750,6 +7337,22 @@ var Player = class _Player extends EventEmitter {
6750
7337
  }
6751
7338
  this.settingsDialog = null;
6752
7339
  }
7340
+ if (this.keyboardHelp && typeof this.keyboardHelp.destroy === "function") {
7341
+ try {
7342
+ this.keyboardHelp.destroy();
7343
+ } catch (err) {
7344
+ this.log(`KeyboardHelp.destroy failed: ${err}`, "warn");
7345
+ }
7346
+ this.keyboardHelp = null;
7347
+ }
7348
+ if (this.mediaSessionManager && typeof this.mediaSessionManager.destroy === "function") {
7349
+ try {
7350
+ this.mediaSessionManager.destroy();
7351
+ } catch (err) {
7352
+ this.log(`MediaSessionManager.destroy failed: ${err}`, "warn");
7353
+ }
7354
+ this.mediaSessionManager = null;
7355
+ }
6753
7356
  if (this.floatingPlayerManager) {
6754
7357
  try {
6755
7358
  this.floatingPlayerManager.destroy();
@@ -7285,12 +7888,19 @@ var PlaylistManager = class {
7285
7888
  if (typeof this.player.invalidateTrackCache === "function") {
7286
7889
  this.player.invalidateTrackCache();
7287
7890
  }
7288
- if (this.player.audioDescriptionManager && typeof this.player.audioDescriptionManager.initFromSourceElements === "function") {
7891
+ const reinitAudioDescription = (adm) => {
7892
+ if (!adm || typeof adm.initFromSourceElements !== "function") return;
7289
7893
  try {
7290
- this.player.audioDescriptionManager.captionTracks = [];
7291
- this.player.audioDescriptionManager.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
7894
+ adm.captionTracks = [];
7895
+ adm.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
7292
7896
  } catch {
7293
7897
  }
7898
+ };
7899
+ if (this.player.audioDescriptionManager) {
7900
+ reinitAudioDescription(this.player.audioDescriptionManager);
7901
+ } else if (this.player.hasAudioDescriptionContent?.()) {
7902
+ void this.player.ensureAudioDescriptionManager().then(reinitAudioDescription).catch(() => {
7903
+ });
7294
7904
  }
7295
7905
  if (this.player.captionManager && typeof this.player.captionManager.loadTracks === "function") {
7296
7906
  try {
@@ -8152,6 +8762,9 @@ function parseDataAttributes(dataset) {
8152
8762
  signLanguageDisplayMode: "signLanguageDisplayMode",
8153
8763
  audioDescriptionSrc: "audioDescriptionSrc",
8154
8764
  audioDescriptionButton: "audioDescriptionButton",
8765
+ audioDescriptionMode: "audioDescriptionMode",
8766
+ audioDescriptionSpeech: "audioDescriptionSpeech",
8767
+ audioDescriptionExtended: "audioDescriptionExtended",
8155
8768
  autoplay: "autoplay",
8156
8769
  loop: "loop",
8157
8770
  muted: "muted",