vidply 1.2.0 → 1.2.1

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 (164) hide show
  1. package/README.md +946 -946
  2. package/dist/dev/{vidply.AudioDescriptionManager-R47Q4YR4.js → vidply.AudioDescriptionManager-6RFVF27Z.js} +275 -19
  3. package/dist/dev/vidply.AudioDescriptionManager-6RFVF27Z.js.map +7 -0
  4. package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js → vidply.CaptionStyleMenu-R2SCQ6VP.js} +4 -4
  5. package/dist/dev/{vidply.DASHRenderer-PWEMC3BT.js → vidply.DASHRenderer-EOVEW73H.js} +3 -3
  6. package/dist/dev/vidply.DASHRenderer-EOVEW73H.js.map +7 -0
  7. package/dist/dev/{vidply.FloatingPlayerManager-PKNNNAS2.js → vidply.FloatingPlayerManager-V5XQWS75.js} +5 -5
  8. package/dist/dev/vidply.FloatingPlayerManager-V5XQWS75.js.map +7 -0
  9. package/dist/dev/{vidply.HLSRenderer-E7RQ74HI.js → vidply.HLSRenderer-ZBEFVH5W.js} +4 -4
  10. package/dist/dev/vidply.HLSRenderer-ZBEFVH5W.js.map +7 -0
  11. package/dist/dev/vidply.HTML5Renderer-P7ICKYU4.js +12 -0
  12. package/dist/dev/{vidply.SignLanguageManager-FET5FQZ7.js → vidply.SignLanguageManager-7IAJOVF6.js} +8 -8
  13. package/dist/dev/vidply.SignLanguageManager-7IAJOVF6.js.map +7 -0
  14. package/dist/dev/{vidply.SoundCloudRenderer-RVSJZODO.js → vidply.SoundCloudRenderer-OA7QQYGN.js} +3 -3
  15. package/dist/dev/vidply.SoundCloudRenderer-OA7QQYGN.js.map +7 -0
  16. package/dist/dev/{vidply.TranscriptManager-5GXOQCLX.js → vidply.TranscriptManager-BPM3T23L.js} +10 -10
  17. package/dist/dev/vidply.TranscriptManager-BPM3T23L.js.map +7 -0
  18. package/dist/dev/{vidply.VimeoRenderer-ER2LSBBW.js → vidply.VimeoRenderer-BRHFYPNJ.js} +3 -3
  19. package/dist/dev/vidply.VimeoRenderer-BRHFYPNJ.js.map +7 -0
  20. package/dist/dev/{vidply.YouTubeRenderer-NFTOWJAB.js → vidply.YouTubeRenderer-HRTN4EJ5.js} +3 -3
  21. package/dist/dev/vidply.YouTubeRenderer-HRTN4EJ5.js.map +7 -0
  22. package/dist/dev/{vidply.chunk-6RQA4ROD.js → vidply.chunk-5IK3IC5V.js} +2 -2
  23. package/dist/dev/{vidply.chunk-JJ47NO24.js → vidply.chunk-C55OBT5H.js} +2 -2
  24. package/dist/dev/vidply.chunk-C55OBT5H.js.map +7 -0
  25. package/dist/dev/{vidply.chunk-DZSDO3B7.js → vidply.chunk-HOYEMJSD.js} +2 -2
  26. package/dist/dev/{vidply.chunk-VMSQAZM7.js → vidply.chunk-JAKZNGOR.js} +2 -2
  27. package/dist/dev/{vidply.chunk-64WXMTGL.js → vidply.chunk-LDVFAFGC.js} +5 -5
  28. package/dist/dev/vidply.chunk-LDVFAFGC.js.map +7 -0
  29. package/dist/dev/{vidply.chunk-RZWZGDV7.js → vidply.chunk-NSMAZCMB.js} +6 -6
  30. package/dist/dev/vidply.chunk-NSMAZCMB.js.map +7 -0
  31. package/dist/dev/{vidply.chunk-KI7C3QVL.js → vidply.chunk-RQG3MA7W.js} +2 -2
  32. package/dist/dev/vidply.chunk-RQG3MA7W.js.map +7 -0
  33. package/dist/dev/{vidply.chunk-BNFIUL2Z.js → vidply.chunk-UHMVDBDB.js} +3 -3
  34. package/dist/dev/vidply.chunk-UHMVDBDB.js.map +7 -0
  35. package/dist/dev/{vidply.chunk-NQPURMQY.js → vidply.chunk-W5BR65JY.js} +6 -6
  36. package/dist/dev/vidply.chunk-W5BR65JY.js.map +7 -0
  37. package/dist/dev/{vidply.chunk-W25HUCHS.js → vidply.chunk-XAK3DPTO.js} +2 -2
  38. package/dist/dev/vidply.chunk-XAK3DPTO.js.map +7 -0
  39. package/dist/dev/{vidply.chunk-SK4V2DAU.js → vidply.chunk-YVCYQZJI.js} +3 -3
  40. package/dist/dev/{vidply.de-4YAT7FFM.js → vidply.de-5W5LBFSJ.js} +2 -2
  41. package/dist/dev/vidply.de-5W5LBFSJ.js.map +7 -0
  42. package/dist/dev/{vidply.es-LEWIRUBH.js → vidply.es-SHDAT6L6.js} +2 -2
  43. package/dist/dev/vidply.es-SHDAT6L6.js.map +7 -0
  44. package/dist/dev/vidply.esm.js +73 -34
  45. package/dist/dev/vidply.esm.js.map +2 -2
  46. package/dist/dev/{vidply.fr-Q4GV2NOE.js → vidply.fr-2JANOMNB.js} +2 -2
  47. package/dist/dev/vidply.fr-2JANOMNB.js.map +7 -0
  48. package/dist/dev/{vidply.ja-S6ITA6WU.js → vidply.ja-WWMSTEJH.js} +2 -2
  49. package/dist/dev/vidply.ja-WWMSTEJH.js.map +7 -0
  50. package/dist/legacy/vidply.js +345 -32
  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 +66 -47
  54. package/dist/prod/vidply.AudioDescriptionManager-YTDVDQBO.min.js +6 -0
  55. package/dist/prod/{vidply.CaptionStyleMenu-M3KVQWB5.min.js → vidply.CaptionStyleMenu-2AFF3HF2.min.js} +2 -2
  56. package/dist/prod/{vidply.DASHRenderer-DN3S27AV.min.js → vidply.DASHRenderer-URDOTOKS.min.js} +2 -2
  57. package/dist/prod/{vidply.FloatingPlayerManager-SKAZ4J43.min.js → vidply.FloatingPlayerManager-QUBFK7CQ.min.js} +2 -2
  58. package/dist/prod/{vidply.HLSRenderer-62HH4YTK.min.js → vidply.HLSRenderer-46KNUVAB.min.js} +2 -2
  59. package/dist/prod/vidply.HTML5Renderer-7TQDMIGY.min.js +6 -0
  60. package/dist/prod/{vidply.SignLanguageManager-V3OBTSSF.min.js → vidply.SignLanguageManager-H4C36EVL.min.js} +2 -2
  61. package/dist/prod/{vidply.SoundCloudRenderer-CXRVZIAJ.min.js → vidply.SoundCloudRenderer-OYSWMW7D.min.js} +2 -2
  62. package/dist/prod/{vidply.TranscriptManager-SYM3DFUY.min.js → vidply.TranscriptManager-BA3DAS63.min.js} +2 -2
  63. package/dist/prod/{vidply.VimeoRenderer-72TWRYFQ.min.js → vidply.VimeoRenderer-NF3COTHU.min.js} +2 -2
  64. package/dist/prod/{vidply.YouTubeRenderer-UMHEV3KD.min.js → vidply.YouTubeRenderer-UCQKYIJ4.min.js} +2 -2
  65. package/dist/prod/{vidply.chunk-Z3YDSA7O.min.js → vidply.chunk-2HNCP64N.min.js} +1 -1
  66. package/dist/prod/{vidply.chunk-EKEYJWMG.min.js → vidply.chunk-6ECBIYVI.min.js} +2 -2
  67. package/dist/prod/{vidply.chunk-YEENJS3Q.min.js → vidply.chunk-7S5AFTST.min.js} +2 -2
  68. package/dist/prod/{vidply.chunk-P4UXOHWA.min.js → vidply.chunk-EUOA2DJK.min.js} +1 -1
  69. package/dist/prod/{vidply.chunk-2TB6MUHO.min.js → vidply.chunk-G6KGXNEW.min.js} +1 -1
  70. package/dist/prod/{vidply.chunk-ABPXVDLZ.min.js → vidply.chunk-JPMAK3NS.min.js} +1 -1
  71. package/dist/prod/{vidply.chunk-P3PQBY7N.min.js → vidply.chunk-L4O44K63.min.js} +1 -1
  72. package/dist/prod/{vidply.chunk-WX3MVEAL.min.js → vidply.chunk-M76ABMKB.min.js} +1 -1
  73. package/dist/prod/{vidply.chunk-TM6AXJVD.min.js → vidply.chunk-WRVDVD33.min.js} +2 -2
  74. package/dist/prod/{vidply.chunk-RI3X7WHK.min.js → vidply.chunk-X2FQEQYR.min.js} +2 -2
  75. package/dist/prod/{vidply.chunk-UK7YGOB5.min.js → vidply.chunk-XXUH7ADU.min.js} +2 -2
  76. package/dist/prod/{vidply.de-BCHXRQ53.min.js → vidply.de-JMCATTKZ.min.js} +1 -1
  77. package/dist/prod/{vidply.es-PICAKMZD.min.js → vidply.es-YVG37V7Z.min.js} +1 -1
  78. package/dist/prod/vidply.esm.min.js +2 -2
  79. package/dist/prod/{vidply.fr-ZILI7CDR.min.js → vidply.fr-OGKGN2R4.min.js} +1 -1
  80. package/dist/prod/{vidply.ja-5WQ4OK4J.min.js → vidply.ja-Z6DS7QLS.min.js} +1 -1
  81. package/dist/vidply.css +5796 -5796
  82. package/dist/vidply.esm.min.meta.json +148 -135
  83. package/package.json +7 -7
  84. package/src/controls/CaptionManager.ts +598 -598
  85. package/src/controls/ControlBar.ts +4159 -4159
  86. package/src/controls/KeyboardManager.ts +383 -383
  87. package/src/controls/SettingsDialog.ts +488 -488
  88. package/src/controls/TranscriptManager.ts +2426 -2426
  89. package/src/core/AudioDescriptionManager.ts +863 -774
  90. package/src/core/DescriptionSpeechManager.ts +232 -0
  91. package/src/core/FloatingPlayerManager.ts +674 -674
  92. package/src/core/LazyInit.ts +80 -80
  93. package/src/core/MetadataAlertsManager.ts +514 -514
  94. package/src/core/Player.ts +2915 -2872
  95. package/src/core/PosterManager.ts +169 -169
  96. package/src/core/PseudoFullscreen.ts +179 -179
  97. package/src/core/ResponsiveManager.ts +231 -231
  98. package/src/core/ResumeManager.ts +236 -236
  99. package/src/core/SignLanguageManager.ts +1308 -1308
  100. package/src/core/ThemeManager.ts +157 -157
  101. package/src/features/PlaylistManager.ts +1838 -1827
  102. package/src/i18n/i18n.ts +223 -223
  103. package/src/i18n/languages/de.ts +217 -217
  104. package/src/i18n/languages/en.ts +218 -218
  105. package/src/i18n/languages/es.ts +214 -214
  106. package/src/i18n/languages/fr.ts +214 -214
  107. package/src/i18n/languages/ja.ts +214 -214
  108. package/src/icons/Icons.ts +167 -167
  109. package/src/index.ts +196 -193
  110. package/src/renderers/DASHRenderer.ts +1077 -1077
  111. package/src/renderers/HLSRenderer.ts +944 -944
  112. package/src/renderers/HTML5Renderer.ts +389 -389
  113. package/src/renderers/SoundCloudRenderer.ts +437 -437
  114. package/src/renderers/VimeoRenderer.ts +298 -298
  115. package/src/renderers/YouTubeRenderer.ts +290 -290
  116. package/src/styles/vidply.css +5796 -5796
  117. package/src/types/events.ts +124 -122
  118. package/src/types/globals.d.ts +313 -313
  119. package/src/types/options.ts +185 -182
  120. package/src/types/renderer.ts +58 -58
  121. package/src/utils/DOMUtils.ts +281 -281
  122. package/src/utils/DownloadInfo.ts +171 -171
  123. package/src/utils/DraggablePanel.ts +539 -539
  124. package/src/utils/DraggablePanelMenu.ts +195 -195
  125. package/src/utils/DraggableResizable.ts +997 -997
  126. package/src/utils/EventEmitter.ts +59 -59
  127. package/src/utils/FormUtils.ts +95 -95
  128. package/src/utils/MenuFactory.ts +483 -483
  129. package/src/utils/Sanitize.ts +70 -70
  130. package/src/utils/SettingsMenuFactory.ts +289 -289
  131. package/src/utils/StorageManager.ts +287 -287
  132. package/src/utils/TrackLabelUtils.ts +52 -52
  133. package/src/utils/UrlSafe.ts +69 -69
  134. package/src/utils/VideoFrameCapture.ts +114 -114
  135. package/src/utils/WindowComponents.ts +250 -250
  136. package/dist/dev/vidply.AudioDescriptionManager-R47Q4YR4.js.map +0 -7
  137. package/dist/dev/vidply.DASHRenderer-PWEMC3BT.js.map +0 -7
  138. package/dist/dev/vidply.FloatingPlayerManager-PKNNNAS2.js.map +0 -7
  139. package/dist/dev/vidply.HLSRenderer-E7RQ74HI.js.map +0 -7
  140. package/dist/dev/vidply.HTML5Renderer-BU4QJJJY.js +0 -12
  141. package/dist/dev/vidply.SignLanguageManager-FET5FQZ7.js.map +0 -7
  142. package/dist/dev/vidply.SoundCloudRenderer-RVSJZODO.js.map +0 -7
  143. package/dist/dev/vidply.TranscriptManager-5GXOQCLX.js.map +0 -7
  144. package/dist/dev/vidply.VimeoRenderer-ER2LSBBW.js.map +0 -7
  145. package/dist/dev/vidply.YouTubeRenderer-NFTOWJAB.js.map +0 -7
  146. package/dist/dev/vidply.chunk-64WXMTGL.js.map +0 -7
  147. package/dist/dev/vidply.chunk-BNFIUL2Z.js.map +0 -7
  148. package/dist/dev/vidply.chunk-JJ47NO24.js.map +0 -7
  149. package/dist/dev/vidply.chunk-KI7C3QVL.js.map +0 -7
  150. package/dist/dev/vidply.chunk-NQPURMQY.js.map +0 -7
  151. package/dist/dev/vidply.chunk-RZWZGDV7.js.map +0 -7
  152. package/dist/dev/vidply.chunk-W25HUCHS.js.map +0 -7
  153. package/dist/dev/vidply.de-4YAT7FFM.js.map +0 -7
  154. package/dist/dev/vidply.es-LEWIRUBH.js.map +0 -7
  155. package/dist/dev/vidply.fr-Q4GV2NOE.js.map +0 -7
  156. package/dist/dev/vidply.ja-S6ITA6WU.js.map +0 -7
  157. package/dist/prod/vidply.AudioDescriptionManager-RHLIJW5C.min.js +0 -6
  158. package/dist/prod/vidply.HTML5Renderer-IU5T3WEW.min.js +0 -6
  159. /package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js.map → vidply.CaptionStyleMenu-R2SCQ6VP.js.map} +0 -0
  160. /package/dist/dev/{vidply.HTML5Renderer-BU4QJJJY.js.map → vidply.HTML5Renderer-P7ICKYU4.js.map} +0 -0
  161. /package/dist/dev/{vidply.chunk-6RQA4ROD.js.map → vidply.chunk-5IK3IC5V.js.map} +0 -0
  162. /package/dist/dev/{vidply.chunk-DZSDO3B7.js.map → vidply.chunk-HOYEMJSD.js.map} +0 -0
  163. /package/dist/dev/{vidply.chunk-VMSQAZM7.js.map → vidply.chunk-JAKZNGOR.js.map} +0 -0
  164. /package/dist/dev/{vidply.chunk-SK4V2DAU.js.map → vidply.chunk-YVCYQZJI.js.map} +0 -0
@@ -1,169 +1,169 @@
1
- /**
2
- * Poster / artwork helpers lifted out of Player.
3
- *
4
- * The manager owns:
5
- * - relative -> absolute URL resolution;
6
- * - the canvas-based "grab a frame from the video as poster" logic;
7
- * - the `.vidply-forced-poster` overlay that shows the poster while
8
- * the underlying `<video>` is in a paused / not-yet-loaded state.
9
- *
10
- * Player keeps public delegating methods (`resolvePosterPath`,
11
- * `showPosterOverlay`, etc.) so the existing external API is
12
- * unchanged — only the implementation has moved.
13
- */
14
-
15
- import { captureVideoFrame } from '../utils/VideoFrameCapture.js';
16
- import type { Player } from './Player.js';
17
-
18
- export class PosterManager {
19
- private readonly player: Player;
20
-
21
- constructor(player: Player) {
22
- this.player = player;
23
- }
24
-
25
- /**
26
- * Convert a relative poster path into an absolute URL. Absolute URLs
27
- * (http/https) and root-relative paths (`/foo`) are returned as-is.
28
- * Falls back to the raw string on any parse error — a malformed URL
29
- * is still better than throwing and breaking the caller.
30
- */
31
- resolvePath(posterPath: string | null | undefined): string {
32
- if (!posterPath) return '';
33
-
34
- if (posterPath.match(/^(https?:|\/)/)) {
35
- return posterPath;
36
- }
37
-
38
- try {
39
- const posterUrl = new URL(posterPath, window.location.href);
40
- return posterUrl.href;
41
- } catch {
42
- return posterPath;
43
- }
44
- }
45
-
46
- /**
47
- * Capture a frame from the underlying video as a data URL suitable
48
- * for use as `<video>.poster`. Returns `null` when the element is
49
- * not a video, the renderer isn't ready, or the capture fails.
50
- *
51
- * When the control bar has a hidden "preview video" element (used
52
- * for the seek hover thumbnail), we prefer that so we don't disturb
53
- * the user's current playback position.
54
- */
55
- async generateFromVideo(time = 10): Promise<string | null> {
56
- const player = this.player;
57
- if (player.element.tagName !== 'VIDEO') return null;
58
-
59
- const renderer = player.renderer;
60
- if (!renderer || !renderer.media || renderer.media.tagName !== 'VIDEO') {
61
- return null;
62
- }
63
-
64
- const video = renderer.media as HTMLVideoElement;
65
-
66
- if (!video.duration || video.duration < time) {
67
- time = Math.min(time, Math.max(1, video.duration * 0.1));
68
- }
69
-
70
- let videoToUse: HTMLVideoElement = video;
71
- if (player.controlBar && player.controlBar.previewVideo && player.controlBar.previewSupported) {
72
- videoToUse = player.controlBar.previewVideo as HTMLVideoElement;
73
- }
74
-
75
- // If we're using the main video we have to put its currentTime /
76
- // paused state back; for the preview video nobody is watching so
77
- // we can skip the restore.
78
- const restoreState = videoToUse === video;
79
- return await captureVideoFrame(videoToUse, time, {
80
- restoreState,
81
- quality: 0.9
82
- });
83
- }
84
-
85
- /**
86
- * Auto-generate a poster from the video at the 10-second mark if the
87
- * content doesn't already have one. No-op for audio elements and for
88
- * media that ships with a poster attribute or option.
89
- */
90
- async autoGenerate(): Promise<void> {
91
- const player = this.player;
92
-
93
- const hasPoster =
94
- player.element.getAttribute('poster') ||
95
- (player.element as HTMLVideoElement).poster ||
96
- player.options.poster;
97
-
98
- if (hasPoster) return;
99
- if (player.element.tagName !== 'VIDEO') return;
100
-
101
- // Wait for metadata so `duration` is known before we seek.
102
- if (!player.state.duration || player.state.duration === 0) {
103
- await new Promise<void>((resolve) => {
104
- const onLoadedMetadata = () => {
105
- player.element.removeEventListener('loadedmetadata', onLoadedMetadata);
106
- resolve();
107
- };
108
-
109
- if (player.element.readyState >= 1) {
110
- resolve();
111
- } else {
112
- player.element.addEventListener('loadedmetadata', onLoadedMetadata);
113
- }
114
- });
115
- }
116
-
117
- const posterDataURL = await this.generateFromVideo(10);
118
-
119
- if (posterDataURL) {
120
- (player.element as HTMLVideoElement).poster = posterDataURL;
121
- player.log('Auto-generated poster from video frame at 10 seconds', 'info');
122
- this.showOverlay();
123
- }
124
- }
125
-
126
- /**
127
- * Apply the poster as a CSS background on the video wrapper. This is
128
- * used to keep the poster visible behind the play button when the
129
- * browser wouldn't render `<video>.poster` itself (e.g. during
130
- * fallback / transitional states).
131
- */
132
- showOverlay(): void {
133
- const player = this.player;
134
- if (!player.videoWrapper || player.element.tagName !== 'VIDEO') return;
135
-
136
- const poster =
137
- player.element.getAttribute('poster') ||
138
- (player.element as HTMLVideoElement).poster ||
139
- player.options.poster;
140
-
141
- if (!poster) return;
142
-
143
- // Data URLs are already absolute and opaque; only normal URLs need
144
- // resolving into absolute form for the CSS `url(...)` value.
145
- const resolvedPoster = poster.startsWith('data:') ? poster : this.resolvePath(poster);
146
- player.videoWrapper.style.setProperty('--vidply-poster-image', `url("${resolvedPoster}")`);
147
- player.videoWrapper.classList.add('vidply-forced-poster');
148
-
149
- // Audio-as-video content uses a wider aspect ratio so the poster
150
- // doesn't get stretched into a tall letter-boxed frame.
151
- if (player._isAudioContent && player.container) {
152
- player.container.classList.add('vidply-audio-content');
153
- } else if (player.container) {
154
- player.container.classList.remove('vidply-audio-content');
155
- }
156
- }
157
-
158
- hideOverlay(): void {
159
- const player = this.player;
160
- if (!player.videoWrapper) return;
161
-
162
- player.videoWrapper.classList.remove('vidply-forced-poster');
163
- player.videoWrapper.style.removeProperty('--vidply-poster-image');
164
-
165
- // NOTE: `vidply-audio-content` intentionally stays applied — it
166
- // drives the aspect ratio for the entire duration of audio-in-a-
167
- // video-element playback, not just while the poster is visible.
168
- }
169
- }
1
+ /**
2
+ * Poster / artwork helpers lifted out of Player.
3
+ *
4
+ * The manager owns:
5
+ * - relative -> absolute URL resolution;
6
+ * - the canvas-based "grab a frame from the video as poster" logic;
7
+ * - the `.vidply-forced-poster` overlay that shows the poster while
8
+ * the underlying `<video>` is in a paused / not-yet-loaded state.
9
+ *
10
+ * Player keeps public delegating methods (`resolvePosterPath`,
11
+ * `showPosterOverlay`, etc.) so the existing external API is
12
+ * unchanged — only the implementation has moved.
13
+ */
14
+
15
+ import { captureVideoFrame } from '../utils/VideoFrameCapture.js';
16
+ import type { Player } from './Player.js';
17
+
18
+ export class PosterManager {
19
+ private readonly player: Player;
20
+
21
+ constructor(player: Player) {
22
+ this.player = player;
23
+ }
24
+
25
+ /**
26
+ * Convert a relative poster path into an absolute URL. Absolute URLs
27
+ * (http/https) and root-relative paths (`/foo`) are returned as-is.
28
+ * Falls back to the raw string on any parse error — a malformed URL
29
+ * is still better than throwing and breaking the caller.
30
+ */
31
+ resolvePath(posterPath: string | null | undefined): string {
32
+ if (!posterPath) return '';
33
+
34
+ if (posterPath.match(/^(https?:|\/)/)) {
35
+ return posterPath;
36
+ }
37
+
38
+ try {
39
+ const posterUrl = new URL(posterPath, window.location.href);
40
+ return posterUrl.href;
41
+ } catch {
42
+ return posterPath;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Capture a frame from the underlying video as a data URL suitable
48
+ * for use as `<video>.poster`. Returns `null` when the element is
49
+ * not a video, the renderer isn't ready, or the capture fails.
50
+ *
51
+ * When the control bar has a hidden "preview video" element (used
52
+ * for the seek hover thumbnail), we prefer that so we don't disturb
53
+ * the user's current playback position.
54
+ */
55
+ async generateFromVideo(time = 10): Promise<string | null> {
56
+ const player = this.player;
57
+ if (player.element.tagName !== 'VIDEO') return null;
58
+
59
+ const renderer = player.renderer;
60
+ if (!renderer || !renderer.media || renderer.media.tagName !== 'VIDEO') {
61
+ return null;
62
+ }
63
+
64
+ const video = renderer.media as HTMLVideoElement;
65
+
66
+ if (!video.duration || video.duration < time) {
67
+ time = Math.min(time, Math.max(1, video.duration * 0.1));
68
+ }
69
+
70
+ let videoToUse: HTMLVideoElement = video;
71
+ if (player.controlBar && player.controlBar.previewVideo && player.controlBar.previewSupported) {
72
+ videoToUse = player.controlBar.previewVideo as HTMLVideoElement;
73
+ }
74
+
75
+ // If we're using the main video we have to put its currentTime /
76
+ // paused state back; for the preview video nobody is watching so
77
+ // we can skip the restore.
78
+ const restoreState = videoToUse === video;
79
+ return await captureVideoFrame(videoToUse, time, {
80
+ restoreState,
81
+ quality: 0.9
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Auto-generate a poster from the video at the 10-second mark if the
87
+ * content doesn't already have one. No-op for audio elements and for
88
+ * media that ships with a poster attribute or option.
89
+ */
90
+ async autoGenerate(): Promise<void> {
91
+ const player = this.player;
92
+
93
+ const hasPoster =
94
+ player.element.getAttribute('poster') ||
95
+ (player.element as HTMLVideoElement).poster ||
96
+ player.options.poster;
97
+
98
+ if (hasPoster) return;
99
+ if (player.element.tagName !== 'VIDEO') return;
100
+
101
+ // Wait for metadata so `duration` is known before we seek.
102
+ if (!player.state.duration || player.state.duration === 0) {
103
+ await new Promise<void>((resolve) => {
104
+ const onLoadedMetadata = () => {
105
+ player.element.removeEventListener('loadedmetadata', onLoadedMetadata);
106
+ resolve();
107
+ };
108
+
109
+ if (player.element.readyState >= 1) {
110
+ resolve();
111
+ } else {
112
+ player.element.addEventListener('loadedmetadata', onLoadedMetadata);
113
+ }
114
+ });
115
+ }
116
+
117
+ const posterDataURL = await this.generateFromVideo(10);
118
+
119
+ if (posterDataURL) {
120
+ (player.element as HTMLVideoElement).poster = posterDataURL;
121
+ player.log('Auto-generated poster from video frame at 10 seconds', 'info');
122
+ this.showOverlay();
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Apply the poster as a CSS background on the video wrapper. This is
128
+ * used to keep the poster visible behind the play button when the
129
+ * browser wouldn't render `<video>.poster` itself (e.g. during
130
+ * fallback / transitional states).
131
+ */
132
+ showOverlay(): void {
133
+ const player = this.player;
134
+ if (!player.videoWrapper || player.element.tagName !== 'VIDEO') return;
135
+
136
+ const poster =
137
+ player.element.getAttribute('poster') ||
138
+ (player.element as HTMLVideoElement).poster ||
139
+ player.options.poster;
140
+
141
+ if (!poster) return;
142
+
143
+ // Data URLs are already absolute and opaque; only normal URLs need
144
+ // resolving into absolute form for the CSS `url(...)` value.
145
+ const resolvedPoster = poster.startsWith('data:') ? poster : this.resolvePath(poster);
146
+ player.videoWrapper.style.setProperty('--vidply-poster-image', `url("${resolvedPoster}")`);
147
+ player.videoWrapper.classList.add('vidply-forced-poster');
148
+
149
+ // Audio-as-video content uses a wider aspect ratio so the poster
150
+ // doesn't get stretched into a tall letter-boxed frame.
151
+ if (player._isAudioContent && player.container) {
152
+ player.container.classList.add('vidply-audio-content');
153
+ } else if (player.container) {
154
+ player.container.classList.remove('vidply-audio-content');
155
+ }
156
+ }
157
+
158
+ hideOverlay(): void {
159
+ const player = this.player;
160
+ if (!player.videoWrapper) return;
161
+
162
+ player.videoWrapper.classList.remove('vidply-forced-poster');
163
+ player.videoWrapper.style.removeProperty('--vidply-poster-image');
164
+
165
+ // NOTE: `vidply-audio-content` intentionally stays applied — it
166
+ // drives the aspect ratio for the entire duration of audio-in-a-
167
+ // video-element playback, not just while the poster is visible.
168
+ }
169
+ }