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,5 +1,5 @@
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
  */
@@ -8,8 +8,13 @@
8
8
  var __defProp = Object.defineProperty;
9
9
  var __getOwnPropNames = Object.getOwnPropertyNames;
10
10
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
- var __esm = (fn, res) => function __init() {
12
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ var __esm = (fn, res, err) => function __init() {
12
+ if (err) throw err[0];
13
+ try {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ } catch (e) {
16
+ throw err = [e], e;
17
+ }
13
18
  };
14
19
  var __export = (target, all) => {
15
20
  for (var name in all)
@@ -423,6 +428,33 @@
423
428
  reset: "Reset to defaults",
424
429
  close: "Close"
425
430
  },
431
+ help: {
432
+ title: "Keyboard shortcuts",
433
+ button: "Keyboard shortcuts",
434
+ close: "Close",
435
+ or: "or",
436
+ keys: {
437
+ space: "Space"
438
+ },
439
+ actions: {
440
+ "play-pause": "Play / Pause",
441
+ "seek-backward": "Seek backward",
442
+ "seek-forward": "Seek forward",
443
+ "volume-up": "Volume up",
444
+ "volume-down": "Volume down",
445
+ "mute": "Mute / Unmute",
446
+ "captions": "Toggle captions",
447
+ "caption-style-menu": "Caption styling",
448
+ "speed-down": "Decrease speed",
449
+ "speed-up": "Increase speed",
450
+ "speed-menu": "Playback speed",
451
+ "quality-menu": "Quality",
452
+ "chapters-menu": "Chapters",
453
+ "transcript-toggle": "Toggle transcript",
454
+ "fullscreen": "Toggle fullscreen",
455
+ "help": "Keyboard shortcuts"
456
+ }
457
+ },
426
458
  speeds: {
427
459
  normal: "Normal"
428
460
  },
@@ -647,6 +679,33 @@
647
679
  reset: "Zurücksetzen",
648
680
  close: "Schließen"
649
681
  },
682
+ help: {
683
+ title: "Tastaturkürzel",
684
+ button: "Tastaturkürzel",
685
+ close: "Schließen",
686
+ or: "oder",
687
+ keys: {
688
+ space: "Leertaste"
689
+ },
690
+ actions: {
691
+ "play-pause": "Wiedergabe / Pause",
692
+ "seek-backward": "Zurückspulen",
693
+ "seek-forward": "Vorspulen",
694
+ "volume-up": "Lauter",
695
+ "volume-down": "Leiser",
696
+ "mute": "Stumm / Ton an",
697
+ "captions": "Untertitel umschalten",
698
+ "caption-style-menu": "Untertitel-Stil",
699
+ "speed-down": "Langsamer",
700
+ "speed-up": "Schneller",
701
+ "speed-menu": "Wiedergabegeschwindigkeit",
702
+ "quality-menu": "Qualität",
703
+ "chapters-menu": "Kapitel",
704
+ "transcript-toggle": "Transkript umschalten",
705
+ "fullscreen": "Vollbild umschalten",
706
+ "help": "Tastaturkürzel"
707
+ }
708
+ },
650
709
  speeds: {
651
710
  normal: "Normal"
652
711
  },
@@ -868,6 +927,33 @@
868
927
  reset: "Restablecer",
869
928
  close: "Cerrar"
870
929
  },
930
+ help: {
931
+ title: "Atajos de teclado",
932
+ button: "Atajos de teclado",
933
+ close: "Cerrar",
934
+ or: "o",
935
+ keys: {
936
+ space: "Espacio"
937
+ },
938
+ actions: {
939
+ "play-pause": "Reproducir / Pausar",
940
+ "seek-backward": "Retroceder",
941
+ "seek-forward": "Avanzar",
942
+ "volume-up": "Subir volumen",
943
+ "volume-down": "Bajar volumen",
944
+ "mute": "Silenciar / Activar sonido",
945
+ "captions": "Alternar subtítulos",
946
+ "caption-style-menu": "Estilo de subtítulos",
947
+ "speed-down": "Reducir velocidad",
948
+ "speed-up": "Aumentar velocidad",
949
+ "speed-menu": "Velocidad de reproducción",
950
+ "quality-menu": "Calidad",
951
+ "chapters-menu": "Capítulos",
952
+ "transcript-toggle": "Alternar transcripción",
953
+ "fullscreen": "Alternar pantalla completa",
954
+ "help": "Atajos de teclado"
955
+ }
956
+ },
871
957
  speeds: {
872
958
  normal: "Normal"
873
959
  },
@@ -1089,6 +1175,33 @@
1089
1175
  reset: "Réinitialiser",
1090
1176
  close: "Fermer"
1091
1177
  },
1178
+ help: {
1179
+ title: "Raccourcis clavier",
1180
+ button: "Raccourcis clavier",
1181
+ close: "Fermer",
1182
+ or: "ou",
1183
+ keys: {
1184
+ space: "Espace"
1185
+ },
1186
+ actions: {
1187
+ "play-pause": "Lecture / Pause",
1188
+ "seek-backward": "Reculer",
1189
+ "seek-forward": "Avancer",
1190
+ "volume-up": "Augmenter le volume",
1191
+ "volume-down": "Baisser le volume",
1192
+ "mute": "Couper / Activer le son",
1193
+ "captions": "Activer/désactiver les sous-titres",
1194
+ "caption-style-menu": "Style des sous-titres",
1195
+ "speed-down": "Ralentir",
1196
+ "speed-up": "Accélérer",
1197
+ "speed-menu": "Vitesse de lecture",
1198
+ "quality-menu": "Qualité",
1199
+ "chapters-menu": "Chapitres",
1200
+ "transcript-toggle": "Activer/désactiver la transcription",
1201
+ "fullscreen": "Activer/désactiver le plein écran",
1202
+ "help": "Raccourcis clavier"
1203
+ }
1204
+ },
1092
1205
  speeds: {
1093
1206
  normal: "Normal"
1094
1207
  },
@@ -1310,6 +1423,33 @@
1310
1423
  reset: "リセット",
1311
1424
  close: "閉じる"
1312
1425
  },
1426
+ help: {
1427
+ title: "キーボードショートカット",
1428
+ button: "キーボードショートカット",
1429
+ close: "閉じる",
1430
+ or: "または",
1431
+ keys: {
1432
+ space: "スペース"
1433
+ },
1434
+ actions: {
1435
+ "play-pause": "再生 / 一時停止",
1436
+ "seek-backward": "巻き戻し",
1437
+ "seek-forward": "早送り",
1438
+ "volume-up": "音量を上げる",
1439
+ "volume-down": "音量を下げる",
1440
+ "mute": "ミュート / ミュート解除",
1441
+ "captions": "字幕の切り替え",
1442
+ "caption-style-menu": "字幕スタイル",
1443
+ "speed-down": "速度を下げる",
1444
+ "speed-up": "速度を上げる",
1445
+ "speed-menu": "再生速度",
1446
+ "quality-menu": "画質",
1447
+ "chapters-menu": "チャプター",
1448
+ "transcript-toggle": "文字起こしの切り替え",
1449
+ "fullscreen": "全画面表示の切り替え",
1450
+ "help": "キーボードショートカット"
1451
+ }
1452
+ },
1313
1453
  speeds: {
1314
1454
  normal: "通常"
1315
1455
  },
@@ -1762,7 +1902,8 @@
1762
1902
  move: `<path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/>`,
1763
1903
  resize: `<path d="M21.71 11.29l-9-9c-.39-.39-1.02-.39-1.41 0l-9 9c-.39.39-.39 1.02 0 1.41l9 9c.39.39 1.02.39 1.41 0l9-9c.39-.38.39-1.01 0-1.41zM14 14.5V12h-4v2.5L7 11l3-3.5V10h4V7.5l3 3.5-3 3.5z"/>`,
1764
1904
  clock: `<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>`,
1765
- download: `<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>`
1905
+ download: `<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>`,
1906
+ help: `<path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/>`
1766
1907
  };
1767
1908
  svgWrapper = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
1768
1909
  Icons = Object.fromEntries(
@@ -3153,6 +3294,194 @@
3153
3294
  }
3154
3295
  });
3155
3296
 
3297
+ // src/core/DescriptionSpeechManager.ts
3298
+ var DescriptionSpeechManager;
3299
+ var init_DescriptionSpeechManager = __esm({
3300
+ "src/core/DescriptionSpeechManager.ts"() {
3301
+ "use strict";
3302
+ DescriptionSpeechManager = class {
3303
+ constructor(player) {
3304
+ __publicField(this, "player");
3305
+ __publicField(this, "enabled", false);
3306
+ __publicField(this, "descriptionTrack", null);
3307
+ __publicField(this, "cueChangeHandler", null);
3308
+ __publicField(this, "seekedHandler", null);
3309
+ __publicField(this, "wasPlayingBeforeCue", false);
3310
+ __publicField(this, "speaking", false);
3311
+ __publicField(this, "lastSpokenCueKey", null);
3312
+ __publicField(this, "_pendingUtterance", null);
3313
+ this.player = player;
3314
+ }
3315
+ /**
3316
+ * Whether speech synthesis is available and enabled in player options.
3317
+ */
3318
+ canUseSpeech() {
3319
+ if (this.player.options.audioDescriptionSpeech === false) {
3320
+ return false;
3321
+ }
3322
+ return typeof window !== "undefined" && "speechSynthesis" in window;
3323
+ }
3324
+ /**
3325
+ * Resolve the descriptions track, preferring the active caption language.
3326
+ */
3327
+ findDescriptionTrack() {
3328
+ var _a, _b, _c;
3329
+ const textTracks = this.player.element ? Array.from(this.player.element.textTracks || []) : [];
3330
+ const descriptionsTracks = textTracks.filter(
3331
+ (track) => track.kind === "descriptions" && !track._vidplyStale
3332
+ );
3333
+ if (descriptionsTracks.length === 0) {
3334
+ return null;
3335
+ }
3336
+ const captionLang = (_c = (_b = (_a = this.player.captionManager) == null ? void 0 : _a.currentTrack) == null ? void 0 : _b.track) == null ? void 0 : _c.language;
3337
+ if (captionLang) {
3338
+ const match = descriptionsTracks.find((track) => track.language === captionLang);
3339
+ if (match) {
3340
+ return match;
3341
+ }
3342
+ }
3343
+ return descriptionsTracks[0] ?? null;
3344
+ }
3345
+ /**
3346
+ * Enable VTT speech mode: wire cuechange/seeked listeners.
3347
+ */
3348
+ enable() {
3349
+ const track = this.findDescriptionTrack();
3350
+ if (!track) {
3351
+ return false;
3352
+ }
3353
+ if (!this.canUseSpeech()) {
3354
+ track.mode = "showing";
3355
+ return true;
3356
+ }
3357
+ this.descriptionTrack = track;
3358
+ track.mode = "hidden";
3359
+ this.cueChangeHandler = () => {
3360
+ this.handleCueChange();
3361
+ };
3362
+ track.addEventListener("cuechange", this.cueChangeHandler);
3363
+ this.seekedHandler = () => {
3364
+ this.cancelSpeech();
3365
+ this.lastSpokenCueKey = null;
3366
+ this.handleCueChange();
3367
+ };
3368
+ this.player.on("seeked", this.seekedHandler);
3369
+ this.enabled = true;
3370
+ this.handleCueChange();
3371
+ return true;
3372
+ }
3373
+ /**
3374
+ * Disable VTT speech mode and restore track state.
3375
+ */
3376
+ disable() {
3377
+ this.cancelSpeech();
3378
+ this.lastSpokenCueKey = null;
3379
+ this.enabled = false;
3380
+ if (this.descriptionTrack && this.cueChangeHandler) {
3381
+ this.descriptionTrack.removeEventListener("cuechange", this.cueChangeHandler);
3382
+ }
3383
+ if (this.seekedHandler) {
3384
+ this.player.off("seeked", this.seekedHandler);
3385
+ }
3386
+ if (this.descriptionTrack) {
3387
+ this.descriptionTrack.mode = "hidden";
3388
+ }
3389
+ this.cueChangeHandler = null;
3390
+ this.seekedHandler = null;
3391
+ this.descriptionTrack = null;
3392
+ this.wasPlayingBeforeCue = false;
3393
+ }
3394
+ /**
3395
+ * Handle active description cues on the wired track.
3396
+ */
3397
+ handleCueChange() {
3398
+ var _a;
3399
+ if (!this.enabled || !this.descriptionTrack || !this.canUseSpeech()) {
3400
+ return;
3401
+ }
3402
+ const activeCues = this.descriptionTrack.activeCues;
3403
+ if (!activeCues || activeCues.length === 0) {
3404
+ return;
3405
+ }
3406
+ const cue = activeCues[0];
3407
+ const text = (_a = cue.text) == null ? void 0 : _a.trim();
3408
+ if (!text) {
3409
+ return;
3410
+ }
3411
+ const cueKey = `${cue.startTime}:${cue.endTime}:${text}`;
3412
+ if (this.speaking && this.lastSpokenCueKey === cueKey) {
3413
+ return;
3414
+ }
3415
+ if (this.lastSpokenCueKey === cueKey && !this.player.state.playing) {
3416
+ return;
3417
+ }
3418
+ this.speakCue(cue, cueKey);
3419
+ }
3420
+ /**
3421
+ * Pause playback and speak a description cue.
3422
+ */
3423
+ speakCue(cue, cueKey) {
3424
+ var _a;
3425
+ if (!this.canUseSpeech()) {
3426
+ return;
3427
+ }
3428
+ if (this.player.state.paused && !this.speaking && !this.wasPlayingBeforeCue) {
3429
+ return;
3430
+ }
3431
+ this.cancelSpeech();
3432
+ this.wasPlayingBeforeCue = this.player.state.playing;
3433
+ if (this.wasPlayingBeforeCue) {
3434
+ this.player.pause();
3435
+ }
3436
+ this.speaking = true;
3437
+ this.lastSpokenCueKey = cueKey;
3438
+ const detail = {
3439
+ time: cue.startTime,
3440
+ endTime: cue.endTime,
3441
+ text: cue.text.trim(),
3442
+ cue
3443
+ };
3444
+ this.player.emit("audiodescriptioncuestart", detail);
3445
+ const utterance = new SpeechSynthesisUtterance(detail.text);
3446
+ const lang = ((_a = this.descriptionTrack) == null ? void 0 : _a.language) || this.player.options.language || "en";
3447
+ utterance.lang = lang;
3448
+ const finish = () => {
3449
+ if (this._pendingUtterance !== utterance) {
3450
+ return;
3451
+ }
3452
+ this._pendingUtterance = null;
3453
+ this.speaking = false;
3454
+ this.player.emit("audiodescriptioncueend", detail);
3455
+ const extended = this.player.options.audioDescriptionExtended !== false;
3456
+ const shouldResume = extended ? this.wasPlayingBeforeCue : this.wasPlayingBeforeCue && this.player.state.currentTime < cue.endTime;
3457
+ if (shouldResume && this.enabled) {
3458
+ void this.player.play();
3459
+ }
3460
+ this.wasPlayingBeforeCue = false;
3461
+ };
3462
+ utterance.onend = finish;
3463
+ utterance.onerror = finish;
3464
+ this._pendingUtterance = utterance;
3465
+ window.speechSynthesis.speak(utterance);
3466
+ }
3467
+ /**
3468
+ * Cancel any in-progress speech synthesis.
3469
+ */
3470
+ cancelSpeech() {
3471
+ if (typeof window !== "undefined" && window.speechSynthesis) {
3472
+ window.speechSynthesis.cancel();
3473
+ }
3474
+ this._pendingUtterance = null;
3475
+ this.speaking = false;
3476
+ this.wasPlayingBeforeCue = false;
3477
+ }
3478
+ destroy() {
3479
+ this.disable();
3480
+ }
3481
+ };
3482
+ }
3483
+ });
3484
+
3156
3485
  // src/core/AudioDescriptionManager.ts
3157
3486
  var AudioDescriptionManager_exports = {};
3158
3487
  __export(AudioDescriptionManager_exports, {
@@ -3163,6 +3492,7 @@
3163
3492
  "src/core/AudioDescriptionManager.ts"() {
3164
3493
  "use strict";
3165
3494
  init_CaptionManager();
3495
+ init_DescriptionSpeechManager();
3166
3496
  AudioDescriptionManager = class {
3167
3497
  constructor(player) {
3168
3498
  __publicField(this, "player");
@@ -3172,6 +3502,7 @@
3172
3502
  __publicField(this, "originalSource");
3173
3503
  __publicField(this, "sourceElement");
3174
3504
  __publicField(this, "src");
3505
+ __publicField(this, "speechManager");
3175
3506
  this.player = player;
3176
3507
  this.enabled = false;
3177
3508
  this.desiredState = false;
@@ -3179,6 +3510,49 @@
3179
3510
  this.sourceElement = null;
3180
3511
  this.originalSource = null;
3181
3512
  this.captionTracks = [];
3513
+ this.speechManager = null;
3514
+ }
3515
+ /**
3516
+ * Whether a described video source swap is configured.
3517
+ */
3518
+ _hasSwapSource() {
3519
+ const hasSourceElementsWithDesc = this.player.sourceElements.some(
3520
+ (el) => el.getAttribute("data-desc-src")
3521
+ );
3522
+ return Boolean(this.src || hasSourceElementsWithDesc);
3523
+ }
3524
+ /**
3525
+ * Whether a descriptions VTT track is present on the media element.
3526
+ */
3527
+ _hasDescriptionsTrack() {
3528
+ return Boolean(this.player.findTextTrack("descriptions"));
3529
+ }
3530
+ /**
3531
+ * Resolve which audio-description delivery mode applies for the current media.
3532
+ */
3533
+ resolveDeliveryMode() {
3534
+ const configured = this.player.options.audioDescriptionMode ?? "auto";
3535
+ const hasSwap = this._hasSwapSource();
3536
+ const hasDescriptions = this._hasDescriptionsTrack();
3537
+ if (configured === "swap") {
3538
+ return hasSwap ? "swap" : "none";
3539
+ }
3540
+ if (configured === "vtt_speech") {
3541
+ return hasDescriptions ? "vtt_speech" : "none";
3542
+ }
3543
+ if (hasSwap) {
3544
+ return "swap";
3545
+ }
3546
+ if (hasDescriptions) {
3547
+ return "vtt_speech";
3548
+ }
3549
+ return "none";
3550
+ }
3551
+ _ensureSpeechManager() {
3552
+ if (!this.speechManager) {
3553
+ this.speechManager = new DescriptionSpeechManager(this.player);
3554
+ }
3555
+ return this.speechManager;
3182
3556
  }
3183
3557
  /**
3184
3558
  * Initialize audio description from source elements
@@ -3232,24 +3606,35 @@
3232
3606
  * Check if audio description is available
3233
3607
  */
3234
3608
  isAvailable() {
3235
- const hasSourceElementsWithDesc = this.player.sourceElements.some(
3236
- (el) => el.getAttribute("data-desc-src")
3237
- );
3238
- return Boolean(this.src || hasSourceElementsWithDesc || this.captionTracks.length > 0);
3609
+ const mode = this.resolveDeliveryMode();
3610
+ if (mode === "swap" || mode === "vtt_speech") {
3611
+ return true;
3612
+ }
3613
+ return this.captionTracks.length > 0;
3239
3614
  }
3240
3615
  /**
3241
3616
  * Enable audio description
3242
3617
  */
3243
3618
  async enable() {
3244
- const hasSourceElementsWithDesc = this.player.sourceElements.some(
3245
- (el) => el.getAttribute("data-desc-src")
3246
- );
3619
+ const deliveryMode = this.resolveDeliveryMode();
3247
3620
  const hasTracksWithDesc = this.captionTracks.length > 0;
3248
- if (!this.src && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
3249
- console.warn("VidPly: No audio description source, source elements, or tracks provided");
3621
+ if (deliveryMode === "none" && !hasTracksWithDesc) {
3622
+ console.warn("VidPly: No audio description source, descriptions track, or tracks provided");
3250
3623
  return;
3251
3624
  }
3252
3625
  this.desiredState = true;
3626
+ if (deliveryMode === "vtt_speech") {
3627
+ const speechManager = this._ensureSpeechManager();
3628
+ const started = speechManager.enable();
3629
+ if (!started) {
3630
+ console.warn("VidPly: No descriptions track available for VTT speech mode");
3631
+ return;
3632
+ }
3633
+ this.enabled = true;
3634
+ this.player.state.audioDescriptionEnabled = true;
3635
+ this.player.emit("audiodescriptionenabled");
3636
+ return;
3637
+ }
3253
3638
  const currentTime = this.player.state.currentTime;
3254
3639
  const wasPlaying = this.player.state.playing;
3255
3640
  const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
@@ -3269,7 +3654,15 @@
3269
3654
  * Disable audio description
3270
3655
  */
3271
3656
  async disable() {
3657
+ var _a;
3272
3658
  this.desiredState = false;
3659
+ if ((_a = this.speechManager) == null ? void 0 : _a.enabled) {
3660
+ this.speechManager.disable();
3661
+ this.enabled = false;
3662
+ this.player.state.audioDescriptionEnabled = false;
3663
+ this.player.emit("audiodescriptiondisabled");
3664
+ return;
3665
+ }
3273
3666
  const hasTracksWithDesc = this.captionTracks.length > 0;
3274
3667
  if (!this.sourceElement && !this.src && hasTracksWithDesc) {
3275
3668
  await this._swapCaptionTracks(false);
@@ -3295,19 +3688,30 @@
3295
3688
  * Toggle audio description
3296
3689
  */
3297
3690
  async toggle() {
3691
+ const deliveryMode = this.resolveDeliveryMode();
3298
3692
  const descriptionTrack = this.player.findTextTrack("descriptions");
3299
- const hasAudioDescriptionSrc = this.isAvailable();
3300
- if (descriptionTrack && !hasAudioDescriptionSrc) {
3693
+ const hasSwapOrTracks = this._hasSwapSource() || this.captionTracks.length > 0;
3694
+ if (deliveryMode === "vtt_speech") {
3695
+ if (this.enabled) {
3696
+ await this.disable();
3697
+ } else {
3698
+ await this.enable();
3699
+ }
3700
+ return;
3701
+ }
3702
+ if (descriptionTrack && !hasSwapOrTracks) {
3301
3703
  if (descriptionTrack.mode === "showing") {
3302
3704
  descriptionTrack.mode = "hidden";
3303
3705
  this.enabled = false;
3706
+ this.player.state.audioDescriptionEnabled = false;
3304
3707
  this.player.emit("audiodescriptiondisabled");
3305
3708
  } else {
3306
3709
  descriptionTrack.mode = "showing";
3307
3710
  this.enabled = true;
3711
+ this.player.state.audioDescriptionEnabled = true;
3308
3712
  this.player.emit("audiodescriptionenabled");
3309
3713
  }
3310
- } else if (descriptionTrack && hasAudioDescriptionSrc) {
3714
+ } else if (descriptionTrack && hasSwapOrTracks) {
3311
3715
  if (this.enabled) {
3312
3716
  this.desiredState = false;
3313
3717
  await this.disable();
@@ -3316,7 +3720,7 @@
3316
3720
  this.desiredState = true;
3317
3721
  await this.enable();
3318
3722
  }
3319
- } else if (hasAudioDescriptionSrc) {
3723
+ } else if (hasSwapOrTracks) {
3320
3724
  if (this.enabled) {
3321
3725
  this.desiredState = false;
3322
3726
  await this.disable();
@@ -3715,6 +4119,9 @@
3715
4119
  * Update sources (called when playlist changes)
3716
4120
  */
3717
4121
  updateSources(audioDescriptionSrc) {
4122
+ var _a;
4123
+ (_a = this.speechManager) == null ? void 0 : _a.destroy();
4124
+ this.speechManager = null;
3718
4125
  this.src = audioDescriptionSrc || null;
3719
4126
  this.enabled = false;
3720
4127
  this.desiredState = false;
@@ -3733,6 +4140,9 @@
3733
4140
  * Cleanup
3734
4141
  */
3735
4142
  destroy() {
4143
+ var _a;
4144
+ (_a = this.speechManager) == null ? void 0 : _a.destroy();
4145
+ this.speechManager = null;
3736
4146
  this.enabled = false;
3737
4147
  this.desiredState = false;
3738
4148
  this.captionTracks = [];
@@ -12102,6 +12512,12 @@
12102
12512
  }
12103
12513
  this.rightButtons.appendChild(btn);
12104
12514
  }
12515
+ if (this.player.options.helpButton && this.player.options.keyboard) {
12516
+ const btn = this.createHelpButton();
12517
+ btn.dataset.overflowPriority = "3";
12518
+ btn.dataset.overflowPriorityMobile = "3";
12519
+ this.rightButtons.appendChild(btn);
12520
+ }
12105
12521
  this.overflowMenuButton = this.createOverflowMenuButton();
12106
12522
  this.overflowMenuButton.style.display = "none";
12107
12523
  this.rightButtons.appendChild(this.overflowMenuButton);
@@ -13423,6 +13839,24 @@
13423
13839
  const isVisible = this.player.transcriptManager && this.player.transcriptManager.isVisible;
13424
13840
  this.controls.transcript.setAttribute("aria-expanded", isVisible ? "true" : "false");
13425
13841
  }
13842
+ createHelpButton() {
13843
+ const ariaLabel = i18n.t("help.button");
13844
+ const button = DOMUtils.createElement("button", {
13845
+ className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-help`,
13846
+ attributes: {
13847
+ "type": "button",
13848
+ "aria-label": ariaLabel,
13849
+ "aria-haspopup": "dialog"
13850
+ }
13851
+ });
13852
+ button.appendChild(createIconElement("help"));
13853
+ DOMUtils.attachTooltip(button, ariaLabel, this.player.options.classPrefix);
13854
+ button.addEventListener("click", () => {
13855
+ this.player.toggleKeyboardHelp();
13856
+ });
13857
+ this.controls.help = button;
13858
+ return button;
13859
+ }
13426
13860
  createAudioDescriptionButton() {
13427
13861
  const ariaLabel = i18n.t("player.audioDescription");
13428
13862
  const button = DOMUtils.createElement("button", {
@@ -14670,6 +15104,9 @@
14670
15104
  return true;
14671
15105
  }
14672
15106
  return false;
15107
+ case "help":
15108
+ this.player.toggleKeyboardHelp();
15109
+ return true;
14673
15110
  default:
14674
15111
  return false;
14675
15112
  }
@@ -14757,6 +15194,252 @@
14757
15194
  }
14758
15195
  };
14759
15196
 
15197
+ // src/core/MediaSessionManager.ts
15198
+ var POSITION_THROTTLE_MS = 1e3;
15199
+ var activeManager = null;
15200
+ var MediaSessionManager = class {
15201
+ constructor(player) {
15202
+ __publicField(this, "player");
15203
+ __publicField(this, "supported");
15204
+ __publicField(this, "handlers", {});
15205
+ __publicField(this, "boundActions", []);
15206
+ __publicField(this, "lastPositionUpdate", 0);
15207
+ this.player = player;
15208
+ this.supported = typeof navigator !== "undefined" && "mediaSession" in navigator;
15209
+ if (!this.supported) return;
15210
+ this.attachEvents();
15211
+ if (activeManager === null) {
15212
+ this.claimSession();
15213
+ }
15214
+ }
15215
+ /** Does this manager currently own the global media session? */
15216
+ isActive() {
15217
+ return activeManager === this;
15218
+ }
15219
+ /**
15220
+ * Take ownership of the global session: (re)register the action handlers so
15221
+ * the OS controls drive this player, and refresh metadata/state/position.
15222
+ */
15223
+ claimSession() {
15224
+ if (!this.supported) return;
15225
+ activeManager = this;
15226
+ this.setupActionHandlers();
15227
+ this.updateMetadata();
15228
+ this.updatePlaybackState();
15229
+ this.updatePositionState(true);
15230
+ }
15231
+ get session() {
15232
+ return navigator.mediaSession;
15233
+ }
15234
+ setActionHandler(action, handler) {
15235
+ try {
15236
+ this.session.setActionHandler(action, handler);
15237
+ if (handler && !this.boundActions.includes(action)) {
15238
+ this.boundActions.push(action);
15239
+ }
15240
+ } catch {
15241
+ }
15242
+ }
15243
+ setupActionHandlers() {
15244
+ this.setActionHandler("play", () => this.player.play());
15245
+ this.setActionHandler("pause", () => this.player.pause());
15246
+ this.setActionHandler("stop", () => this.player.stop());
15247
+ this.setActionHandler("seekbackward", (details) => {
15248
+ this.player.seekBackward(this.offsetFrom(details));
15249
+ });
15250
+ this.setActionHandler("seekforward", (details) => {
15251
+ this.player.seekForward(this.offsetFrom(details));
15252
+ });
15253
+ this.setActionHandler("seekto", (details) => {
15254
+ if (details && typeof details.seekTime === "number") {
15255
+ this.player.seek(details.seekTime);
15256
+ }
15257
+ });
15258
+ this.updateTrackHandlers();
15259
+ }
15260
+ offsetFrom(details) {
15261
+ const offset = details && typeof details.seekOffset === "number" ? details.seekOffset : void 0;
15262
+ return typeof offset === "number" && offset > 0 ? offset : void 0;
15263
+ }
15264
+ /**
15265
+ * previous/next track only make sense with a multi-item playlist; bind
15266
+ * or clear them whenever the playlist state changes so the OS shows the
15267
+ * correct affordances.
15268
+ */
15269
+ updateTrackHandlers() {
15270
+ const pm = this.player.playlistManager;
15271
+ const hasPlaylist = Boolean(pm && Array.isArray(pm.tracks) && pm.tracks.length > 1);
15272
+ if (hasPlaylist && pm) {
15273
+ this.setActionHandler("previoustrack", () => pm.previous());
15274
+ this.setActionHandler("nexttrack", () => pm.next());
15275
+ } else {
15276
+ this.setActionHandler("previoustrack", null);
15277
+ this.setActionHandler("nexttrack", null);
15278
+ }
15279
+ }
15280
+ attachEvents() {
15281
+ this.handlers = {
15282
+ // Starting playback makes this player the session owner, taking over
15283
+ // the OS controls from any other player on the page.
15284
+ play: () => this.claimSession(),
15285
+ pause: () => {
15286
+ if (!this.isActive()) return;
15287
+ this.updatePlaybackState();
15288
+ this.updatePositionState(true);
15289
+ },
15290
+ ended: () => {
15291
+ if (!this.isActive()) return;
15292
+ this.updatePlaybackState();
15293
+ },
15294
+ timeupdate: () => {
15295
+ if (!this.isActive()) return;
15296
+ this.updatePositionState();
15297
+ },
15298
+ durationchange: () => {
15299
+ if (!this.isActive()) return;
15300
+ this.updatePositionState(true);
15301
+ },
15302
+ ratechange: () => {
15303
+ if (!this.isActive()) return;
15304
+ this.updatePositionState(true);
15305
+ },
15306
+ loadedmetadata: () => {
15307
+ if (!this.isActive()) return;
15308
+ this.updateMetadata();
15309
+ this.updatePositionState(true);
15310
+ },
15311
+ playlisttrackchange: () => {
15312
+ if (!this.isActive()) return;
15313
+ this.updateMetadata();
15314
+ this.updateTrackHandlers();
15315
+ this.updatePositionState(true);
15316
+ }
15317
+ };
15318
+ for (const [event, handler] of Object.entries(this.handlers)) {
15319
+ if (handler) {
15320
+ this.player.on(event, handler);
15321
+ }
15322
+ }
15323
+ }
15324
+ resolveMetadata() {
15325
+ const opts = this.player.options;
15326
+ let title = opts.title || "";
15327
+ let artist = opts.artist || "";
15328
+ const album = opts.album || "";
15329
+ let poster = opts.poster || null;
15330
+ const pm = this.player.playlistManager;
15331
+ if (pm && Array.isArray(pm.tracks) && pm.currentIndex >= 0) {
15332
+ const track = pm.tracks[pm.currentIndex];
15333
+ if (track) {
15334
+ if (track.title) title = track.title;
15335
+ if (track.artist) artist = track.artist;
15336
+ if (track.poster) poster = track.poster;
15337
+ }
15338
+ }
15339
+ if (!title && typeof document !== "undefined") {
15340
+ title = document.title || "VidPly";
15341
+ }
15342
+ return { title, artist, album, poster };
15343
+ }
15344
+ updateMetadata() {
15345
+ if (!this.supported || typeof window === "undefined" || typeof window.MediaMetadata === "undefined") {
15346
+ return;
15347
+ }
15348
+ const { title, artist, album, poster } = this.resolveMetadata();
15349
+ const artwork = [];
15350
+ if (poster) {
15351
+ try {
15352
+ const src = this.player.resolvePosterPath(poster);
15353
+ if (src) {
15354
+ artwork.push({ src });
15355
+ }
15356
+ } catch {
15357
+ }
15358
+ }
15359
+ try {
15360
+ this.session.metadata = new window.MediaMetadata({
15361
+ title,
15362
+ artist,
15363
+ album,
15364
+ artwork
15365
+ });
15366
+ } catch {
15367
+ }
15368
+ }
15369
+ updatePlaybackState() {
15370
+ if (!this.supported) return;
15371
+ try {
15372
+ this.session.playbackState = this.player.state.playing ? "playing" : "paused";
15373
+ } catch {
15374
+ }
15375
+ }
15376
+ /**
15377
+ * Push the current position to the OS scrubber. `timeupdate` fires
15378
+ * several times a second, so non-forced updates are throttled.
15379
+ */
15380
+ updatePositionState(force = false) {
15381
+ if (!this.supported || typeof this.session.setPositionState !== "function") {
15382
+ return;
15383
+ }
15384
+ const now = Date.now();
15385
+ if (!force && now - this.lastPositionUpdate < POSITION_THROTTLE_MS) {
15386
+ return;
15387
+ }
15388
+ this.lastPositionUpdate = now;
15389
+ const duration = this.player.state.duration;
15390
+ if (!Number.isFinite(duration) || duration <= 0) {
15391
+ try {
15392
+ this.session.setPositionState();
15393
+ } catch {
15394
+ }
15395
+ return;
15396
+ }
15397
+ const playbackRate = this.player.state.playbackSpeed || 1;
15398
+ const position = Math.min(Math.max(0, this.player.state.currentTime || 0), duration);
15399
+ try {
15400
+ this.session.setPositionState({
15401
+ duration,
15402
+ playbackRate: playbackRate > 0 ? playbackRate : 1,
15403
+ position
15404
+ });
15405
+ } catch {
15406
+ }
15407
+ }
15408
+ destroy() {
15409
+ if (!this.supported) return;
15410
+ for (const [event, handler] of Object.entries(this.handlers)) {
15411
+ if (handler) {
15412
+ this.player.off(event, handler);
15413
+ }
15414
+ }
15415
+ this.handlers = {};
15416
+ if (this.isActive()) {
15417
+ for (const action of this.boundActions) {
15418
+ try {
15419
+ this.session.setActionHandler(action, null);
15420
+ } catch {
15421
+ }
15422
+ }
15423
+ try {
15424
+ this.session.metadata = null;
15425
+ } catch {
15426
+ }
15427
+ try {
15428
+ if (typeof this.session.setPositionState === "function") {
15429
+ this.session.setPositionState();
15430
+ }
15431
+ } catch {
15432
+ }
15433
+ try {
15434
+ this.session.playbackState = "none";
15435
+ } catch {
15436
+ }
15437
+ activeManager = null;
15438
+ }
15439
+ this.boundActions = [];
15440
+ }
15441
+ };
15442
+
14760
15443
  // src/core/Player.ts
14761
15444
  init_HTML5Renderer();
14762
15445
  init_Icons();
@@ -15873,6 +16556,263 @@
15873
16556
  }
15874
16557
  };
15875
16558
 
16559
+ // src/controls/KeyboardHelp.ts
16560
+ init_DOMUtils();
16561
+ init_Icons();
16562
+ init_i18n();
16563
+ var ACTION_ORDER = [
16564
+ "play-pause",
16565
+ "seek-backward",
16566
+ "seek-forward",
16567
+ "volume-up",
16568
+ "volume-down",
16569
+ "mute",
16570
+ "captions",
16571
+ "caption-style-menu",
16572
+ "speed-down",
16573
+ "speed-up",
16574
+ "speed-menu",
16575
+ "quality-menu",
16576
+ "chapters-menu",
16577
+ "transcript-toggle",
16578
+ "fullscreen",
16579
+ "help"
16580
+ ];
16581
+ var ACTION_REQUIRES_CONTROL = {
16582
+ captions: "captions",
16583
+ "caption-style-menu": "captionStyle",
16584
+ "speed-down": "speed",
16585
+ "speed-up": "speed",
16586
+ "speed-menu": "speed",
16587
+ "quality-menu": "quality",
16588
+ "chapters-menu": "chapters",
16589
+ "transcript-toggle": "transcript",
16590
+ fullscreen: "fullscreen"
16591
+ };
16592
+ var KeyboardHelp = class {
16593
+ constructor(player) {
16594
+ __publicField(this, "player");
16595
+ __publicField(this, "isOpen", false);
16596
+ __publicField(this, "overlay", null);
16597
+ __publicField(this, "_triggerElement", null);
16598
+ __publicField(this, "_keydownHandler", null);
16599
+ __publicField(this, "_content", null);
16600
+ this.player = player;
16601
+ }
16602
+ get prefix() {
16603
+ return this.player.options.classPrefix;
16604
+ }
16605
+ /**
16606
+ * Turn a raw KeyboardEvent.key value into a human-readable label. Arrow
16607
+ * keys become universally understood glyphs; the space bar and single
16608
+ * letters are normalised for legibility.
16609
+ */
16610
+ formatKey(key) {
16611
+ switch (key) {
16612
+ case " ":
16613
+ return i18n.t("help.keys.space");
16614
+ case "ArrowUp":
16615
+ return "↑";
16616
+ case "ArrowDown":
16617
+ return "↓";
16618
+ case "ArrowLeft":
16619
+ return "←";
16620
+ case "ArrowRight":
16621
+ return "→";
16622
+ case "Escape":
16623
+ return "Esc";
16624
+ default:
16625
+ return key.length === 1 ? key.toUpperCase() : key;
16626
+ }
16627
+ }
16628
+ createElement() {
16629
+ const titleId = `${this.prefix}-help-title-${this.player.instanceId}`;
16630
+ const overlay = DOMUtils.createElement("div", {
16631
+ className: `${this.prefix}-settings-overlay ${this.prefix}-help-overlay`,
16632
+ attributes: {
16633
+ role: "dialog",
16634
+ "aria-modal": "true",
16635
+ "aria-labelledby": titleId
16636
+ }
16637
+ });
16638
+ overlay.style.display = "none";
16639
+ const dialog = DOMUtils.createElement("div", {
16640
+ className: `${this.prefix}-settings-dialog ${this.prefix}-help-dialog`
16641
+ });
16642
+ const header = DOMUtils.createElement("div", {
16643
+ className: `${this.prefix}-settings-header`
16644
+ });
16645
+ const title = DOMUtils.createElement("h2", {
16646
+ textContent: i18n.t("help.title"),
16647
+ attributes: { id: titleId }
16648
+ });
16649
+ const closeButton = DOMUtils.createElement("button", {
16650
+ className: `${this.prefix}-button ${this.prefix}-settings-close`,
16651
+ attributes: {
16652
+ type: "button",
16653
+ "aria-label": i18n.t("help.close")
16654
+ }
16655
+ });
16656
+ closeButton.appendChild(createIconElement("close"));
16657
+ closeButton.addEventListener("click", () => this.hide());
16658
+ header.appendChild(title);
16659
+ header.appendChild(closeButton);
16660
+ const content = DOMUtils.createElement("div", {
16661
+ className: `${this.prefix}-settings-content`
16662
+ });
16663
+ this._content = content;
16664
+ content.appendChild(this.buildShortcutList());
16665
+ dialog.appendChild(header);
16666
+ dialog.appendChild(content);
16667
+ overlay.appendChild(dialog);
16668
+ overlay.addEventListener("click", (e) => {
16669
+ if (e.target === overlay) {
16670
+ this.hide();
16671
+ }
16672
+ });
16673
+ this._keydownHandler = (e) => {
16674
+ if (!this.isOpen || !this.overlay) return;
16675
+ if (e.key === "Escape") {
16676
+ e.preventDefault();
16677
+ this.hide();
16678
+ return;
16679
+ }
16680
+ if (e.key === "Tab") {
16681
+ const focusable = Array.from(
16682
+ this.overlay.querySelectorAll(
16683
+ 'button, [href], select, input, textarea, [tabindex]:not([tabindex="-1"])'
16684
+ )
16685
+ ).filter((el) => !el.hasAttribute("disabled") && el.offsetParent !== null);
16686
+ if (focusable.length === 0) return;
16687
+ const first = focusable[0];
16688
+ const last = focusable[focusable.length - 1];
16689
+ if (!first || !last) return;
16690
+ const active = document.activeElement;
16691
+ if (e.shiftKey && (active === first || !this.overlay.contains(active))) {
16692
+ e.preventDefault();
16693
+ last.focus({ preventScroll: true });
16694
+ } else if (!e.shiftKey && active === last) {
16695
+ e.preventDefault();
16696
+ first.focus({ preventScroll: true });
16697
+ }
16698
+ }
16699
+ };
16700
+ const lifecycleSignal = this.player.lifecycleSignal;
16701
+ document.addEventListener(
16702
+ "keydown",
16703
+ this._keydownHandler,
16704
+ lifecycleSignal ? { signal: lifecycleSignal } : void 0
16705
+ );
16706
+ return overlay;
16707
+ }
16708
+ /**
16709
+ * Whether a shortcut row is worth showing for *this* player. Feature actions
16710
+ * are hidden when their control isn't present (e.g. no captions track, an
16711
+ * audio-only player with no fullscreen). Core actions are always relevant.
16712
+ *
16713
+ * When the player has no control bar we can't infer availability, so nothing
16714
+ * is hidden — the shortcuts still work and we'd rather over-show than mislead.
16715
+ */
16716
+ isActionRelevant(action) {
16717
+ const requiredControl = ACTION_REQUIRES_CONTROL[action];
16718
+ if (!requiredControl) return true;
16719
+ const controlBar = this.player.controlBar;
16720
+ if (!controlBar || !controlBar.controls) return true;
16721
+ return Boolean(controlBar.controls[requiredControl]);
16722
+ }
16723
+ buildShortcutList() {
16724
+ const list = DOMUtils.createElement("dl", {
16725
+ className: `${this.prefix}-help-list`
16726
+ });
16727
+ const shortcuts = this.player.options.keyboardShortcuts;
16728
+ for (const action of ACTION_ORDER) {
16729
+ const keys = shortcuts[action];
16730
+ if (!Array.isArray(keys) || keys.length === 0) continue;
16731
+ if (!this.isActionRelevant(action)) continue;
16732
+ const term = DOMUtils.createElement("dt", {
16733
+ className: `${this.prefix}-help-action`,
16734
+ textContent: i18n.t(`help.actions.${action}`)
16735
+ });
16736
+ const desc = DOMUtils.createElement("dd", {
16737
+ className: `${this.prefix}-help-keys`
16738
+ });
16739
+ keys.forEach((key, index) => {
16740
+ if (index > 0) {
16741
+ desc.appendChild(
16742
+ DOMUtils.createElement("span", {
16743
+ className: `${this.prefix}-help-key-sep`,
16744
+ textContent: i18n.t("help.or")
16745
+ })
16746
+ );
16747
+ }
16748
+ desc.appendChild(
16749
+ DOMUtils.createElement("kbd", {
16750
+ className: `${this.prefix}-help-key`,
16751
+ textContent: this.formatKey(key)
16752
+ })
16753
+ );
16754
+ });
16755
+ list.appendChild(term);
16756
+ list.appendChild(desc);
16757
+ }
16758
+ return list;
16759
+ }
16760
+ show() {
16761
+ if (this.isOpen) return;
16762
+ if (!this.overlay) {
16763
+ this.overlay = this.createElement();
16764
+ this.player.container.appendChild(this.overlay);
16765
+ } else if (this._content) {
16766
+ this._content.replaceChildren(this.buildShortcutList());
16767
+ }
16768
+ const active = typeof document !== "undefined" ? document.activeElement : null;
16769
+ this._triggerElement = active && typeof active.focus === "function" ? active : null;
16770
+ this.overlay.style.display = "flex";
16771
+ this.isOpen = true;
16772
+ const closeButton = this.overlay.querySelector(`.${this.prefix}-settings-close`);
16773
+ closeButton == null ? void 0 : closeButton.focus({ preventScroll: true });
16774
+ this.player.emit("keyboardhelpopen");
16775
+ }
16776
+ hide() {
16777
+ var _a, _b;
16778
+ if (!this.overlay) return;
16779
+ this.overlay.style.display = "none";
16780
+ this.isOpen = false;
16781
+ const trigger = this._triggerElement;
16782
+ this._triggerElement = null;
16783
+ if (trigger && document.contains(trigger)) {
16784
+ try {
16785
+ trigger.focus({ preventScroll: true });
16786
+ } catch {
16787
+ (_a = this.player.container) == null ? void 0 : _a.focus();
16788
+ }
16789
+ } else {
16790
+ (_b = this.player.container) == null ? void 0 : _b.focus();
16791
+ }
16792
+ this.player.emit("keyboardhelpclose");
16793
+ }
16794
+ toggle() {
16795
+ if (this.isOpen) {
16796
+ this.hide();
16797
+ } else {
16798
+ this.show();
16799
+ }
16800
+ }
16801
+ destroy() {
16802
+ if (this._keydownHandler) {
16803
+ document.removeEventListener("keydown", this._keydownHandler);
16804
+ this._keydownHandler = null;
16805
+ }
16806
+ if (this.overlay && this.overlay.parentNode) {
16807
+ this.overlay.parentNode.removeChild(this.overlay);
16808
+ }
16809
+ this.overlay = null;
16810
+ this._content = null;
16811
+ this._triggerElement = null;
16812
+ this.isOpen = false;
16813
+ }
16814
+ };
16815
+
15876
16816
  // src/core/Player.ts
15877
16817
  var AudioDescriptionManagerModule = null;
15878
16818
  var SignLanguageManagerModule = null;
@@ -15916,9 +16856,11 @@
15916
16856
  __publicField(this, "controlBar", null);
15917
16857
  __publicField(this, "captionManager", null);
15918
16858
  __publicField(this, "keyboardManager", null);
16859
+ __publicField(this, "mediaSessionManager", null);
15919
16860
  __publicField(this, "transcriptManager", null);
15920
16861
  __publicField(this, "playlistManager", null);
15921
16862
  __publicField(this, "settingsDialog", null);
16863
+ __publicField(this, "keyboardHelp", null);
15922
16864
  __publicField(this, "audioDescriptionManager", null);
15923
16865
  __publicField(this, "signLanguageManager", null);
15924
16866
  __publicField(this, "floatingPlayerManager", null);
@@ -16032,6 +16974,11 @@
16032
16974
  poster: null,
16033
16975
  responsive: true,
16034
16976
  fillContainer: false,
16977
+ // Media metadata + OS media controls (Media Session API)
16978
+ title: null,
16979
+ artist: null,
16980
+ album: null,
16981
+ mediaSession: true,
16035
16982
  // Playback
16036
16983
  autoplay: false,
16037
16984
  loop: false,
@@ -16079,6 +17026,7 @@
16079
17026
  captionsButton: true,
16080
17027
  transcriptButton: true,
16081
17028
  fullscreenButton: true,
17029
+ helpButton: true,
16082
17030
  pipButton: false,
16083
17031
  floating: false,
16084
17032
  floatingPosition: "bottom-right",
@@ -16104,6 +17052,9 @@
16104
17052
  audioDescriptionSrc: null,
16105
17053
  // URL to audio-described version
16106
17054
  audioDescriptionButton: true,
17055
+ audioDescriptionMode: "auto",
17056
+ audioDescriptionSpeech: true,
17057
+ audioDescriptionExtended: true,
16107
17058
  // Sign Language
16108
17059
  signLanguage: true,
16109
17060
  signLanguageSrc: null,
@@ -16134,7 +17085,8 @@
16134
17085
  "speed-menu": ["s"],
16135
17086
  "quality-menu": ["q"],
16136
17087
  "chapters-menu": ["j"],
16137
- "transcript-toggle": ["t"]
17088
+ "transcript-toggle": ["t"],
17089
+ "help": ["?"]
16138
17090
  },
16139
17091
  // Accessibility
16140
17092
  ariaLabels: {},
@@ -16517,6 +17469,9 @@
16517
17469
  if (this.options.keyboard) {
16518
17470
  this.keyboardManager = new KeyboardManager(this);
16519
17471
  }
17472
+ if (this.options.mediaSession) {
17473
+ this.mediaSessionManager = new MediaSessionManager(this);
17474
+ }
16520
17475
  this.setupResponsiveHandlers();
16521
17476
  if (this.options.startTime > 0) {
16522
17477
  this.seek(this.options.startTime);
@@ -16589,15 +17544,33 @@
16589
17544
  if (this.audioDescriptionManager) {
16590
17545
  return this.audioDescriptionManager;
16591
17546
  }
16592
- const hasAudioDescSrc = this.options.audioDescriptionSrc || this.audioDescriptionSrc;
16593
- const hasAudioDescButton = this.options.audioDescriptionButton;
16594
- if (!hasAudioDescSrc && !hasAudioDescButton) {
17547
+ if (!this.hasAudioDescriptionContent()) {
16595
17548
  return null;
16596
17549
  }
16597
17550
  const AudioDescManager = await loadAudioDescriptionManager();
16598
17551
  this.audioDescriptionManager = new AudioDescManager(this);
16599
17552
  return this.audioDescriptionManager;
16600
17553
  }
17554
+ /**
17555
+ * True when the current media actually exposes audio-description content:
17556
+ * an explicit described-audio source, `<source>` elements carrying
17557
+ * `data-desc-src` / `data-orig-src`, or a `descriptions` text track.
17558
+ * Mirrors `ControlBar.hasAudioDescription()` so the chunk load and the
17559
+ * button visibility stay in lock-step.
17560
+ */
17561
+ hasAudioDescriptionContent() {
17562
+ if (this.options.audioDescriptionSrc || this.audioDescriptionSrc) {
17563
+ return true;
17564
+ }
17565
+ const hasSourceElementsWithDesc = this.sourceElements.some(
17566
+ (el) => el.getAttribute("data-desc-src") || el.getAttribute("data-orig-src")
17567
+ );
17568
+ if (hasSourceElementsWithDesc) {
17569
+ return true;
17570
+ }
17571
+ const textTracks = this.element ? Array.from(this.element.textTracks || []) : [];
17572
+ return textTracks.some((track) => track.kind === "descriptions");
17573
+ }
16601
17574
  // ============================================
16602
17575
  // Resume Playback Methods
16603
17576
  // ============================================
@@ -16609,16 +17582,24 @@
16609
17582
  if (this.signLanguageManager) {
16610
17583
  return this.signLanguageManager;
16611
17584
  }
16612
- const hasSignLangSrc = this.options.signLanguageSrc || this.signLanguageSrc;
16613
- const hasSignLangSources = this.options.signLanguageSources && Object.keys(this.options.signLanguageSources).length > 0;
16614
- const hasSignLangButton = this.options.signLanguageButton;
16615
- if (!hasSignLangSrc && !hasSignLangSources && !hasSignLangButton) {
17585
+ if (!this.hasSignLanguageContent()) {
16616
17586
  return null;
16617
17587
  }
16618
17588
  const SignLangManager = await loadSignLanguageManager();
16619
17589
  this.signLanguageManager = new SignLangManager(this);
16620
17590
  return this.signLanguageManager;
16621
17591
  }
17592
+ /**
17593
+ * True when a sign-language video source (single `signLanguageSrc` or a
17594
+ * `signLanguageSources` map) is configured. Mirrors
17595
+ * `ControlBar.hasSignLanguage()`.
17596
+ */
17597
+ hasSignLanguageContent() {
17598
+ if (this.options.signLanguageSrc || this.signLanguageSrc) {
17599
+ return true;
17600
+ }
17601
+ return !!(this.options.signLanguageSources && Object.keys(this.options.signLanguageSources).length > 0);
17602
+ }
16622
17603
  /**
16623
17604
  * Lazy-load and instantiate the floating (in-page PiP) manager. Only
16624
17605
  * created when `options.floating === true` and the media element is a
@@ -16643,13 +17624,10 @@
16643
17624
  */
16644
17625
  async initFeatureManagers() {
16645
17626
  const promises = [];
16646
- const hasSourceElementsWithDesc = this.sourceElements.some(
16647
- (el) => el.getAttribute("data-desc-src") || el.getAttribute("data-orig-src")
16648
- );
16649
- if (this.options.audioDescriptionButton || this.options.audioDescriptionSrc || this.audioDescriptionSrc || hasSourceElementsWithDesc) {
17627
+ if (this.hasAudioDescriptionContent()) {
16650
17628
  promises.push(this.ensureAudioDescriptionManager());
16651
17629
  }
16652
- if (this.options.signLanguageButton || this.options.signLanguageSrc || this.signLanguageSrc || this.options.signLanguageSources && Object.keys(this.options.signLanguageSources).length > 0) {
17630
+ if (this.hasSignLanguageContent()) {
16653
17631
  promises.push(this.ensureSignLanguageManager());
16654
17632
  }
16655
17633
  if (this.options.floating && this.element && this.element.tagName === "VIDEO") {
@@ -16991,6 +17969,9 @@
16991
17969
  }
16992
17970
  this.currentSource = src;
16993
17971
  this._pendingSource = null;
17972
+ if (this.hasAudioDescriptionContent()) {
17973
+ await this.ensureAudioDescriptionManager();
17974
+ }
16994
17975
  (_b = this.audioDescriptionManager) == null ? void 0 : _b.initFromSourceElements(this.sourceElements, this.trackElements);
16995
17976
  if (!this.originalSrc) {
16996
17977
  this.originalSrc = src;
@@ -17832,6 +18813,27 @@
17832
18813
  }
17833
18814
  hideSettings() {
17834
18815
  }
18816
+ /**
18817
+ * Lazily build (on first use) and toggle the keyboard-shortcuts help
18818
+ * dialog. Reflects the live `keyboardShortcuts` bindings, including any
18819
+ * consumer overrides.
18820
+ */
18821
+ toggleKeyboardHelp() {
18822
+ if (!this.keyboardHelp) {
18823
+ this.keyboardHelp = new KeyboardHelp(this);
18824
+ }
18825
+ this.keyboardHelp.toggle();
18826
+ }
18827
+ showKeyboardHelp() {
18828
+ if (!this.keyboardHelp) {
18829
+ this.keyboardHelp = new KeyboardHelp(this);
18830
+ }
18831
+ this.keyboardHelp.show();
18832
+ }
18833
+ hideKeyboardHelp() {
18834
+ var _a;
18835
+ (_a = this.keyboardHelp) == null ? void 0 : _a.hide();
18836
+ }
17835
18837
  // Utility methods
17836
18838
  getCurrentTime() {
17837
18839
  return this.state.currentTime;
@@ -17972,6 +18974,22 @@
17972
18974
  }
17973
18975
  this.settingsDialog = null;
17974
18976
  }
18977
+ if (this.keyboardHelp && typeof this.keyboardHelp.destroy === "function") {
18978
+ try {
18979
+ this.keyboardHelp.destroy();
18980
+ } catch (err) {
18981
+ this.log(`KeyboardHelp.destroy failed: ${err}`, "warn");
18982
+ }
18983
+ this.keyboardHelp = null;
18984
+ }
18985
+ if (this.mediaSessionManager && typeof this.mediaSessionManager.destroy === "function") {
18986
+ try {
18987
+ this.mediaSessionManager.destroy();
18988
+ } catch (err) {
18989
+ this.log(`MediaSessionManager.destroy failed: ${err}`, "warn");
18990
+ }
18991
+ this.mediaSessionManager = null;
18992
+ }
17975
18993
  if (this.floatingPlayerManager) {
17976
18994
  try {
17977
18995
  this.floatingPlayerManager.destroy();
@@ -18466,7 +19484,7 @@
18466
19484
  * @param {number} index - Track index
18467
19485
  */
18468
19486
  selectTrack(index) {
18469
- var _a, _b, _c, _d;
19487
+ var _a, _b, _c, _d, _e, _f;
18470
19488
  if (index < 0 || index >= this.tracks.length) {
18471
19489
  console.warn("VidPly Playlist: Invalid track index", index);
18472
19490
  return;
@@ -18527,12 +19545,19 @@
18527
19545
  if (typeof this.player.invalidateTrackCache === "function") {
18528
19546
  this.player.invalidateTrackCache();
18529
19547
  }
18530
- if (this.player.audioDescriptionManager && typeof this.player.audioDescriptionManager.initFromSourceElements === "function") {
19548
+ const reinitAudioDescription = (adm) => {
19549
+ if (!adm || typeof adm.initFromSourceElements !== "function") return;
18531
19550
  try {
18532
- this.player.audioDescriptionManager.captionTracks = [];
18533
- this.player.audioDescriptionManager.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
19551
+ adm.captionTracks = [];
19552
+ adm.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
18534
19553
  } catch {
18535
19554
  }
19555
+ };
19556
+ if (this.player.audioDescriptionManager) {
19557
+ reinitAudioDescription(this.player.audioDescriptionManager);
19558
+ } else if ((_f = (_e = this.player).hasAudioDescriptionContent) == null ? void 0 : _f.call(_e)) {
19559
+ void this.player.ensureAudioDescriptionManager().then(reinitAudioDescription).catch(() => {
19560
+ });
18536
19561
  }
18537
19562
  if (this.player.captionManager && typeof this.player.captionManager.loadTracks === "function") {
18538
19563
  try {
@@ -19397,6 +20422,9 @@
19397
20422
  signLanguageDisplayMode: "signLanguageDisplayMode",
19398
20423
  audioDescriptionSrc: "audioDescriptionSrc",
19399
20424
  audioDescriptionButton: "audioDescriptionButton",
20425
+ audioDescriptionMode: "audioDescriptionMode",
20426
+ audioDescriptionSpeech: "audioDescriptionSpeech",
20427
+ audioDescriptionExtended: "audioDescriptionExtended",
19400
20428
  autoplay: "autoplay",
19401
20429
  loop: "loop",
19402
20430
  muted: "muted",