@therealtinhtute/baroiplayer 1.0.0

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 (244) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/context/media.js +2 -0
  4. package/dist/context/media.js.map +1 -0
  5. package/dist/context/media.mjs +15 -0
  6. package/dist/context/media.mjs.map +1 -0
  7. package/dist/context/store.js +2 -0
  8. package/dist/context/store.js.map +1 -0
  9. package/dist/context/store.mjs +5 -0
  10. package/dist/context/store.mjs.map +1 -0
  11. package/dist/context-BMteAJDN.js +1140 -0
  12. package/dist/context-BMteAJDN.js.map +1 -0
  13. package/dist/context-DCJ_scmO.cjs +23 -0
  14. package/dist/context-DCJ_scmO.cjs.map +1 -0
  15. package/dist/core-events-B5W_j8yU.js +480 -0
  16. package/dist/core-events-B5W_j8yU.js.map +1 -0
  17. package/dist/core-events-CN0-Vhhq.cjs +2 -0
  18. package/dist/core-events-CN0-Vhhq.cjs.map +1 -0
  19. package/dist/core-plugins-Bq9KLany.js +696 -0
  20. package/dist/core-plugins-Bq9KLany.js.map +1 -0
  21. package/dist/core-plugins-CPDSHMiQ.cjs +2 -0
  22. package/dist/core-plugins-CPDSHMiQ.cjs.map +1 -0
  23. package/dist/core.js +37 -0
  24. package/dist/core.js.map +1 -0
  25. package/dist/core.mjs +1217 -0
  26. package/dist/core.mjs.map +1 -0
  27. package/dist/enhanced-components-DHv0MFvG.js +1073 -0
  28. package/dist/enhanced-components-DHv0MFvG.js.map +1 -0
  29. package/dist/enhanced-components-DTIObgB7.cjs +2 -0
  30. package/dist/enhanced-components-DTIObgB7.cjs.map +1 -0
  31. package/dist/hooks-C_r8wexn.cjs +4 -0
  32. package/dist/hooks-C_r8wexn.cjs.map +1 -0
  33. package/dist/hooks-Da2wVXlF.js +1630 -0
  34. package/dist/hooks-Da2wVXlF.js.map +1 -0
  35. package/dist/index.css +1 -0
  36. package/dist/index.js +112 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +6126 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/minimal-components-DSq5sQpy.js +2002 -0
  41. package/dist/minimal-components-DSq5sQpy.js.map +1 -0
  42. package/dist/minimal-components-DflWiihN.cjs +2 -0
  43. package/dist/minimal-components-DflWiihN.cjs.map +1 -0
  44. package/dist/minimal-components.css +1 -0
  45. package/dist/mona-loading.gif +0 -0
  46. package/dist/performance-monitor-CUW6f5ll.cjs +2 -0
  47. package/dist/performance-monitor-CUW6f5ll.cjs.map +1 -0
  48. package/dist/performance-monitor-rMW1RgV3.js +302 -0
  49. package/dist/performance-monitor-rMW1RgV3.js.map +1 -0
  50. package/dist/utils-BnscpYYd.js +2798 -0
  51. package/dist/utils-BnscpYYd.js.map +1 -0
  52. package/dist/utils-D_czS_0K.cjs +9 -0
  53. package/dist/utils-D_czS_0K.cjs.map +1 -0
  54. package/dist/utils.js +2 -0
  55. package/dist/utils.js.map +1 -0
  56. package/dist/utils.mjs +7 -0
  57. package/dist/utils.mjs.map +1 -0
  58. package/package.json +114 -0
  59. package/src/adapters/videojs-adapter.tsx +470 -0
  60. package/src/assets/mona-loading.gif +0 -0
  61. package/src/components/AnimateLoading.tsx +115 -0
  62. package/src/components/LoadingState.tsx +248 -0
  63. package/src/components/accessibility/keyboard-help-overlay.tsx +213 -0
  64. package/src/components/audio-player.tsx +85 -0
  65. package/src/components/basic/index.ts +37 -0
  66. package/src/components/enhanced/advanced-features.tsx +316 -0
  67. package/src/components/enhanced/debug-panel.tsx +422 -0
  68. package/src/components/enhanced/enhanced-controls.tsx +600 -0
  69. package/src/components/enhanced/enhanced-media-player.tsx +509 -0
  70. package/src/components/enhanced/error-handling.tsx +345 -0
  71. package/src/components/enhanced/index.ts +39 -0
  72. package/src/components/enhanced/media-player-enhanced.tsx +349 -0
  73. package/src/components/examples/basic-video-player.tsx +55 -0
  74. package/src/components/examples/custom-controls-example.tsx +63 -0
  75. package/src/components/examples/enhanced-controls-example.tsx +114 -0
  76. package/src/components/examples/enhanced-media-player-example.tsx +293 -0
  77. package/src/components/examples/loading-states-demo.tsx +142 -0
  78. package/src/components/examples/react-19-demo.tsx +381 -0
  79. package/src/components/examples/volume-orientation-demo.tsx +114 -0
  80. package/src/components/icon-demo.tsx +209 -0
  81. package/src/components/icons/index.tsx +166 -0
  82. package/src/components/icons/svg-icons/ArrowLeftIcon.tsx +18 -0
  83. package/src/components/icons/svg-icons/ArrowRightIcon.tsx +18 -0
  84. package/src/components/icons/svg-icons/AudioIcon.tsx +22 -0
  85. package/src/components/icons/svg-icons/BackwardIcon.tsx +12 -0
  86. package/src/components/icons/svg-icons/CameraIcon.tsx +19 -0
  87. package/src/components/icons/svg-icons/CheckIcon.tsx +19 -0
  88. package/src/components/icons/svg-icons/EnablePiPIcon.tsx +17 -0
  89. package/src/components/icons/svg-icons/ForwardIcon.tsx +12 -0
  90. package/src/components/icons/svg-icons/FullscreenEnterIcon.tsx +23 -0
  91. package/src/components/icons/svg-icons/FullscreenExitIcon.tsx +23 -0
  92. package/src/components/icons/svg-icons/LoadingIcon.tsx +32 -0
  93. package/src/components/icons/svg-icons/PauseIcon.tsx +23 -0
  94. package/src/components/icons/svg-icons/PlayIcon.tsx +21 -0
  95. package/src/components/icons/svg-icons/PlaybackSpeedIcon.tsx +23 -0
  96. package/src/components/icons/svg-icons/QualityIcon.tsx +21 -0
  97. package/src/components/icons/svg-icons/SettingsIcon.tsx +12 -0
  98. package/src/components/icons/svg-icons/SliderIcon.tsx +21 -0
  99. package/src/components/icons/svg-icons/SubtitleIcon.tsx +21 -0
  100. package/src/components/icons/svg-icons/SubtitleOffIcon.tsx +25 -0
  101. package/src/components/icons/svg-icons/UnPiPIcon.tsx +17 -0
  102. package/src/components/icons/svg-icons/VolumeMutedIcon.tsx +16 -0
  103. package/src/components/icons/svg-icons/VolumeOneIcon.tsx +22 -0
  104. package/src/components/icons/svg-icons/VolumeThreeIcon.tsx +16 -0
  105. package/src/components/icons/svg-icons/VolumeTwoIcon.tsx +16 -0
  106. package/src/components/legacy.tsx +93 -0
  107. package/src/components/media-metadata-display.tsx +100 -0
  108. package/src/components/media-player-audio.tsx +39 -0
  109. package/src/components/media-player-controls.tsx +226 -0
  110. package/src/components/media-player-error.tsx +163 -0
  111. package/src/components/media-player-fullscreen.tsx +109 -0
  112. package/src/components/media-player-hls.tsx +111 -0
  113. package/src/components/media-player-loading.tsx +76 -0
  114. package/src/components/media-player-play.tsx +75 -0
  115. package/src/components/media-player-root.tsx +340 -0
  116. package/src/components/media-player-seek.tsx +168 -0
  117. package/src/components/media-player-time.tsx +99 -0
  118. package/src/components/media-player-tooltip.tsx +58 -0
  119. package/src/components/media-player-video.tsx +39 -0
  120. package/src/components/media-player-volume.tsx +155 -0
  121. package/src/components/media-player.tsx +257 -0
  122. package/src/components/media-suspense.tsx +362 -0
  123. package/src/components/player-settings-form.tsx +163 -0
  124. package/src/components/react-player-wrapper.tsx +651 -0
  125. package/src/components/simple-video-player.tsx +60 -0
  126. package/src/components/ui/enhanced-seek-bar.tsx +235 -0
  127. package/src/components/ui/fullscreen-button.tsx +60 -0
  128. package/src/components/ui/media-resource-preloader.tsx +395 -0
  129. package/src/components/ui/play-button.tsx +107 -0
  130. package/src/components/ui/seek-bar.tsx +161 -0
  131. package/src/components/ui/time-display.tsx +40 -0
  132. package/src/components/ui/volume-control.tsx +143 -0
  133. package/src/components/unified/base-media-player.tsx +246 -0
  134. package/src/components/unified/media-player.tsx +75 -0
  135. package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +561 -0
  136. package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +552 -0
  137. package/src/components/variants/minimal/MinimalControls.tsx +142 -0
  138. package/src/components/variants/minimal/MinimalLoading.tsx +32 -0
  139. package/src/components/variants/minimal/MinimalMediaPlayer.tsx +315 -0
  140. package/src/components/variants/minimal/MinimalOverlay.tsx +64 -0
  141. package/src/components/variants/minimal/MinimalPlayButton.tsx +65 -0
  142. package/src/components/variants/minimal/MinimalSeekBar.tsx +150 -0
  143. package/src/components/variants/minimal/MinimalVolumeControl.tsx +80 -0
  144. package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +210 -0
  145. package/src/components/variants/minimal/components/CenterPlayButton.tsx +22 -0
  146. package/src/components/variants/minimal/components/ControlBar.tsx +146 -0
  147. package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +361 -0
  148. package/src/components/variants/minimal/components/PlaybackControls.tsx +51 -0
  149. package/src/components/variants/minimal/components/RightControls.tsx +173 -0
  150. package/src/components/variants/minimal/components/SeekBar.tsx +131 -0
  151. package/src/components/variants/minimal/components/SettingsDropdown.tsx +70 -0
  152. package/src/components/variants/minimal/components/SubtitleDisplay.tsx +21 -0
  153. package/src/components/variants/minimal/components/TimeDisplay.tsx +18 -0
  154. package/src/components/variants/minimal/components/VideoElement.tsx +40 -0
  155. package/src/components/variants/minimal/components/VolumeControl.tsx +217 -0
  156. package/src/components/variants/minimal/index.ts +23 -0
  157. package/src/components/video-player.tsx +100 -0
  158. package/src/context/error-handling.ts +394 -0
  159. package/src/context/media-context.tsx +208 -0
  160. package/src/context/media-provider.tsx +265 -0
  161. package/src/context/media-store.ts +379 -0
  162. package/src/context/persistence.ts +453 -0
  163. package/src/core/events/event-system.ts +442 -0
  164. package/src/core/events/hooks.ts +302 -0
  165. package/src/core/events/index.ts +22 -0
  166. package/src/core/events/middleware.ts +269 -0
  167. package/src/core/index.ts +22 -0
  168. package/src/core/optimization/bundle-optimizer.tsx +308 -0
  169. package/src/core/performance/cicd.ts +96 -0
  170. package/src/core/performance/index.ts +38 -0
  171. package/src/core/performance/media-loading-optimizer.ts +538 -0
  172. package/src/core/performance/performance-budgets.ts +432 -0
  173. package/src/core/performance/performance-monitor.tsx +476 -0
  174. package/src/core/performance/performance-testing.ts +459 -0
  175. package/src/core/plugins/base-plugin.ts +224 -0
  176. package/src/core/plugins/enhanced-plugin-system.ts +411 -0
  177. package/src/core/plugins/examples/analytics-plugin.ts +242 -0
  178. package/src/core/plugins/index.ts +39 -0
  179. package/src/core/plugins/registry.ts +327 -0
  180. package/src/core/plugins/types.ts +159 -0
  181. package/src/core/plugins/utils.ts +316 -0
  182. package/src/core/progressive-loading.tsx +322 -0
  183. package/src/core/providers/enhanced-hls-provider.ts +339 -0
  184. package/src/core/providers/service-provider.tsx +168 -0
  185. package/src/core/utils.tsx +38 -0
  186. package/src/features/media-session/media-session-manager.ts +429 -0
  187. package/src/features/picture-in-picture/pip-manager.ts +429 -0
  188. package/src/features/playlist/playlist-manager.ts +606 -0
  189. package/src/features/subtitles/subtitle-manager.ts +441 -0
  190. package/src/features/subtitles/subtitle-parser.ts +280 -0
  191. package/src/features/thumbnails/thumbnail-generator.ts +519 -0
  192. package/src/hooks/accessibility/index.ts +27 -0
  193. package/src/hooks/accessibility/use-accessibility-preferences.ts +188 -0
  194. package/src/hooks/accessibility/use-focus-management.ts +176 -0
  195. package/src/hooks/accessibility/use-keyboard-shortcuts.ts +144 -0
  196. package/src/hooks/accessibility/use-screen-reader.ts +182 -0
  197. package/src/hooks/core/index.ts +47 -0
  198. package/src/hooks/legacy.ts +27 -0
  199. package/src/hooks/use-enhanced-media-player.ts +468 -0
  200. package/src/hooks/use-intelligent-preloading.ts +358 -0
  201. package/src/hooks/use-keyboard-controls.ts +115 -0
  202. package/src/hooks/use-media-actions.ts +175 -0
  203. package/src/hooks/use-media-metadata.ts +89 -0
  204. package/src/hooks/use-media-optimistic.ts +270 -0
  205. package/src/hooks/use-media-player.ts +312 -0
  206. package/src/hooks/use-media-state.ts +190 -0
  207. package/src/hooks/use-memory-optimization.ts +358 -0
  208. package/src/hooks/use-optimistic-controls.ts +92 -0
  209. package/src/hooks/use-optimized-media.ts +263 -0
  210. package/src/hooks/use-touch-gestures.ts +417 -0
  211. package/src/hooks/useEnhancedSubtitles.ts +258 -0
  212. package/src/hooks/useKeyboardShortcuts.ts +74 -0
  213. package/src/hooks/useMediaControls.ts +52 -0
  214. package/src/hooks/useScreenshot.ts +57 -0
  215. package/src/hooks/useSubtitles.ts +91 -0
  216. package/src/hooks/useVideoPlayer.ts +232 -0
  217. package/src/icons/components/FullscreenIcon.tsx +37 -0
  218. package/src/icons/components/SettingsIcon.tsx +38 -0
  219. package/src/icons/components/animated/LoadingIcon.tsx +43 -0
  220. package/src/icons/components/animated/PlayPauseIcon.tsx +50 -0
  221. package/src/icons/components/media/PauseIcon.tsx +39 -0
  222. package/src/icons/components/media/PlayIcon.tsx +38 -0
  223. package/src/icons/components/volume/VolumeIcon.tsx +39 -0
  224. package/src/icons/components/volume/VolumeOffIcon.tsx +39 -0
  225. package/src/icons/index.ts +56 -0
  226. package/src/icons/provider.tsx +64 -0
  227. package/src/icons/types.ts +40 -0
  228. package/src/index-enhanced.ts +35 -0
  229. package/src/index-legacy.ts +326 -0
  230. package/src/index.css +102 -0
  231. package/src/index.ts +216 -0
  232. package/src/lib/deprecation.ts +51 -0
  233. package/src/lib/format-time.ts +37 -0
  234. package/src/lib/index.ts +3 -0
  235. package/src/lib/utils.ts +6 -0
  236. package/src/styles/globals.css +462 -0
  237. package/src/styles/minimal-player.css +589 -0
  238. package/src/styles/pixel-art.css +46 -0
  239. package/src/styles/videojs.css +100 -0
  240. package/src/test/setup.ts +79 -0
  241. package/src/types/events.ts +47 -0
  242. package/src/types/formats.ts +95 -0
  243. package/src/types/index.ts +190 -0
  244. package/src/types/videojs-plugins.d.ts +14 -0
@@ -0,0 +1,519 @@
1
+ export interface ThumbnailOptions {
2
+ width?: number
3
+ height?: number
4
+ quality?: number
5
+ format?: 'jpeg' | 'png' | 'webp'
6
+ time?: number
7
+ }
8
+
9
+ export interface ThumbnailConfig {
10
+ defaultWidth: number
11
+ defaultHeight: number
12
+ defaultQuality: number
13
+ defaultFormat: 'jpeg' | 'png' | 'webp'
14
+ maxCacheSize: number
15
+ enableCaching: boolean
16
+ }
17
+
18
+ export interface GeneratedThumbnail {
19
+ blob: Blob
20
+ dataUrl: string
21
+ objectUrl: string
22
+ width: number
23
+ height: number
24
+ timestamp: number
25
+ }
26
+
27
+ export interface ThumbnailSprite {
28
+ image: HTMLImageElement
29
+ thumbnails: Array<{
30
+ x: number
31
+ y: number
32
+ width: number
33
+ height: number
34
+ time: number
35
+ }>
36
+ spriteWidth: number
37
+ spriteHeight: number
38
+ thumbnailWidth: number
39
+ thumbnailHeight: number
40
+ }
41
+
42
+ export class ThumbnailGenerator {
43
+ private config: ThumbnailConfig
44
+ private cache: Map<string, GeneratedThumbnail> = new Map()
45
+ private videoElement: HTMLVideoElement | null = null
46
+ private canvas: HTMLCanvasElement
47
+ private context: CanvasRenderingContext2D
48
+
49
+ constructor(config?: Partial<ThumbnailConfig>) {
50
+ this.config = {
51
+ defaultWidth: 160,
52
+ defaultHeight: 90,
53
+ defaultQuality: 0.8,
54
+ defaultFormat: 'jpeg',
55
+ maxCacheSize: 50,
56
+ enableCaching: true,
57
+ ...config
58
+ }
59
+
60
+ this.canvas = document.createElement('canvas')
61
+ const ctx = this.canvas.getContext('2d')
62
+ if (!ctx) {
63
+ throw new Error('Canvas 2D context not supported')
64
+ }
65
+ this.context = ctx
66
+ }
67
+
68
+ /**
69
+ * Set the video element to generate thumbnails from
70
+ */
71
+ setVideoElement(video: HTMLVideoElement): void {
72
+ this.videoElement = video
73
+ }
74
+
75
+ /**
76
+ * Generate thumbnail at specific time
77
+ */
78
+ async generateThumbnail(time: number, options: ThumbnailOptions = {}): Promise<GeneratedThumbnail> {
79
+ if (!this.videoElement) {
80
+ throw new Error('Video element not set')
81
+ }
82
+
83
+ const cacheKey = this.getCacheKey(time, options)
84
+
85
+ // Check cache first
86
+ if (this.config.enableCaching && this.cache.has(cacheKey)) {
87
+ const cached = this.cache.get(cacheKey)!
88
+ return { ...cached }
89
+ }
90
+
91
+ const width = options.width || this.config.defaultWidth
92
+ const height = options.height || this.config.defaultHeight
93
+ const quality = options.quality || this.config.defaultQuality
94
+ const format = options.format || this.config.defaultFormat
95
+
96
+ // Setup canvas
97
+ this.canvas.width = width
98
+ this.canvas.height = height
99
+
100
+ // Set video time and wait for seek
101
+ const originalTime = this.videoElement.currentTime
102
+ await this.seekToTime(time)
103
+
104
+ // Draw video frame to canvas
105
+ this.context.clearRect(0, 0, width, height)
106
+
107
+ // Calculate aspect ratio preserving dimensions
108
+ const videoAspect = this.videoElement.videoWidth / this.videoElement.videoHeight
109
+ const canvasAspect = width / height
110
+
111
+ let drawWidth = width
112
+ let drawHeight = height
113
+ let offsetX = 0
114
+ let offsetY = 0
115
+
116
+ if (videoAspect > canvasAspect) {
117
+ // Video is wider, fit to height
118
+ drawHeight = height
119
+ drawWidth = height * videoAspect
120
+ offsetX = (width - drawWidth) / 2
121
+ } else {
122
+ // Video is taller, fit to width
123
+ drawWidth = width
124
+ drawHeight = width / videoAspect
125
+ offsetY = (height - drawHeight) / 2
126
+ }
127
+
128
+ this.context.drawImage(
129
+ this.videoElement,
130
+ offsetX, offsetY,
131
+ drawWidth, drawHeight
132
+ )
133
+
134
+ // Convert to blob
135
+ const blob = await this.canvasToBlob(format, quality)
136
+ const dataUrl = this.canvas.toDataURL(`image/${format}`, quality)
137
+ const objectUrl = URL.createObjectURL(blob)
138
+
139
+ const thumbnail: GeneratedThumbnail = {
140
+ blob,
141
+ dataUrl,
142
+ objectUrl,
143
+ width,
144
+ height,
145
+ timestamp: time
146
+ }
147
+
148
+ // Cache thumbnail
149
+ if (this.config.enableCaching) {
150
+ this.addToCache(cacheKey, thumbnail)
151
+ }
152
+
153
+ // Restore original time
154
+ if (originalTime !== time) {
155
+ await this.seekToTime(originalTime)
156
+ }
157
+
158
+ return thumbnail
159
+ }
160
+
161
+ /**
162
+ * Generate multiple thumbnails for scrub bar
163
+ */
164
+ async generateScrubThumbnails(
165
+ count: number,
166
+ options: ThumbnailOptions = {}
167
+ ): Promise<GeneratedThumbnail[]> {
168
+ if (!this.videoElement) {
169
+ throw new Error('Video element not set')
170
+ }
171
+
172
+ const duration = this.videoElement.duration
173
+ if (!duration || duration === 0) {
174
+ throw new Error('Video duration not available')
175
+ }
176
+
177
+ const thumbnails: GeneratedThumbnail[] = []
178
+ const interval = duration / count
179
+
180
+ for (let i = 0; i < count; i++) {
181
+ const time = i * interval
182
+ try {
183
+ const thumbnail = await this.generateThumbnail(time, options)
184
+ thumbnails.push(thumbnail)
185
+ } catch (error) {
186
+ console.warn(`Failed to generate thumbnail at ${time}s:`, error)
187
+ }
188
+ }
189
+
190
+ return thumbnails
191
+ }
192
+
193
+ /**
194
+ * Generate thumbnail sprite sheet
195
+ */
196
+ async generateSprite(
197
+ count: number,
198
+ options: ThumbnailOptions & {
199
+ columns?: number
200
+ rows?: number
201
+ } = {}
202
+ ): Promise<ThumbnailSprite> {
203
+ if (!this.videoElement) {
204
+ throw new Error('Video element not set')
205
+ }
206
+
207
+ const duration = this.videoElement.duration
208
+ if (!duration || duration === 0) {
209
+ throw new Error('Video duration not available')
210
+ }
211
+
212
+ const thumbnailWidth = options.width || this.config.defaultWidth
213
+ const thumbnailHeight = options.height || this.config.defaultHeight
214
+ const quality = options.quality || this.config.defaultQuality
215
+ const format = options.format || this.config.defaultFormat
216
+
217
+ // Calculate sprite dimensions
218
+ const columns = options.columns || Math.ceil(Math.sqrt(count))
219
+ const rows = Math.ceil(count / columns)
220
+
221
+ const spriteWidth = columns * thumbnailWidth
222
+ const spriteHeight = rows * thumbnailHeight
223
+
224
+ // Create sprite canvas
225
+ const spriteCanvas = document.createElement('canvas')
226
+ spriteCanvas.width = spriteWidth
227
+ spriteCanvas.height = spriteHeight
228
+
229
+ const spriteContext = spriteCanvas.getContext('2d')
230
+ if (!spriteContext) {
231
+ throw new Error('Canvas 2D context not supported')
232
+ }
233
+
234
+ const thumbnails: ThumbnailSprite['thumbnails'] = []
235
+ const interval = duration / count
236
+
237
+ // Generate each thumbnail
238
+ for (let i = 0; i < count; i++) {
239
+ const time = i * interval
240
+ const col = i % columns
241
+ const row = Math.floor(i / columns)
242
+ const x = col * thumbnailWidth
243
+ const y = row * thumbnailHeight
244
+
245
+ try {
246
+ // Generate individual thumbnail
247
+ const thumbnail = await this.generateThumbnail(time, {
248
+ width: thumbnailWidth,
249
+ height: thumbnailHeight,
250
+ quality,
251
+ format
252
+ })
253
+
254
+ // Draw thumbnail to sprite
255
+ const img = new Image()
256
+ img.src = thumbnail.dataUrl
257
+
258
+ await new Promise<void>((resolve, reject) => {
259
+ img.onload = () => {
260
+ spriteContext.drawImage(img, x, y, thumbnailWidth, thumbnailHeight)
261
+ resolve()
262
+ }
263
+ img.onerror = reject
264
+ })
265
+
266
+ thumbnails.push({
267
+ x,
268
+ y,
269
+ width: thumbnailWidth,
270
+ height: thumbnailHeight,
271
+ time
272
+ })
273
+
274
+ // Clean up individual thumbnail
275
+ URL.revokeObjectURL(thumbnail.objectUrl)
276
+
277
+ } catch (error) {
278
+ console.warn(`Failed to add thumbnail at ${time}s to sprite:`, error)
279
+ }
280
+ }
281
+
282
+ // Create final sprite image
283
+ const spriteImage = new Image()
284
+ spriteImage.src = spriteCanvas.toDataURL(`image/${format}`, quality)
285
+
286
+ const sprite: ThumbnailSprite = {
287
+ image: spriteImage,
288
+ thumbnails,
289
+ spriteWidth,
290
+ spriteHeight,
291
+ thumbnailWidth,
292
+ thumbnailHeight
293
+ }
294
+
295
+ return sprite
296
+ }
297
+
298
+ /**
299
+ * Extract thumbnail from sprite at specific time
300
+ */
301
+ getThumbnailFromSprite(sprite: ThumbnailSprite, time: number): HTMLCanvasElement | null {
302
+ // Find closest thumbnail
303
+ const thumbnail = sprite.thumbnails.reduce((closest, current) => {
304
+ return Math.abs(current.time - time) < Math.abs(closest.time - time)
305
+ ? current
306
+ : closest
307
+ })
308
+
309
+ if (!thumbnail) return null
310
+
311
+ // Create canvas for extracted thumbnail
312
+ const canvas = document.createElement('canvas')
313
+ canvas.width = thumbnail.width
314
+ canvas.height = thumbnail.height
315
+
316
+ const context = canvas.getContext('2d')
317
+ if (!context) return null
318
+
319
+ // Draw portion of sprite to canvas
320
+ context.drawImage(
321
+ sprite.image,
322
+ thumbnail.x, thumbnail.y, thumbnail.width, thumbnail.height,
323
+ 0, 0, thumbnail.width, thumbnail.height
324
+ )
325
+
326
+ return canvas
327
+ }
328
+
329
+ /**
330
+ * Clear cache
331
+ */
332
+ clearCache(): void {
333
+ // Revoke object URLs to prevent memory leaks
334
+ this.cache.forEach(thumbnail => {
335
+ URL.revokeObjectURL(thumbnail.objectUrl)
336
+ })
337
+ this.cache.clear()
338
+ }
339
+
340
+ /**
341
+ * Get cache size
342
+ */
343
+ getCacheSize(): number {
344
+ return this.cache.size
345
+ }
346
+
347
+ /**
348
+ * Get cache info
349
+ */
350
+ getCacheInfo(): Array<{ key: string; timestamp: number; size: number }> {
351
+ return Array.from(this.cache.entries()).map(([key, thumbnail]) => ({
352
+ key,
353
+ timestamp: thumbnail.timestamp,
354
+ size: thumbnail.blob.size
355
+ }))
356
+ }
357
+
358
+ /**
359
+ * Set configuration
360
+ */
361
+ updateConfig(config: Partial<ThumbnailConfig>): void {
362
+ this.config = { ...this.config, ...config }
363
+ }
364
+
365
+ /**
366
+ * Seek video to specific time
367
+ */
368
+ private async seekToTime(time: number): Promise<void> {
369
+ if (!this.videoElement) return
370
+
371
+ return new Promise((resolve, reject) => {
372
+ let timeoutId: number
373
+
374
+ const onSeeked = () => {
375
+ this.videoElement!.removeEventListener('seeked', onSeeked)
376
+ clearTimeout(timeoutId)
377
+ resolve()
378
+ }
379
+
380
+ const onError = () => {
381
+ this.videoElement!.removeEventListener('error', onError)
382
+ this.videoElement!.removeEventListener('seeked', onSeeked)
383
+ clearTimeout(timeoutId)
384
+ reject(new Error('Seek failed'))
385
+ }
386
+
387
+ // Set timeout to avoid hanging
388
+ timeoutId = window.setTimeout(() => {
389
+ this.videoElement!.removeEventListener('seeked', onSeeked)
390
+ this.videoElement!.removeEventListener('error', onError)
391
+ reject(new Error('Seek timeout'))
392
+ }, 5000)
393
+
394
+ this.videoElement.addEventListener('seeked', onSeeked)
395
+ this.videoElement.addEventListener('error', onError)
396
+ this.videoElement.currentTime = Math.max(0, Math.min(time, this.videoElement.duration))
397
+ })
398
+ }
399
+
400
+ /**
401
+ * Convert canvas to blob
402
+ */
403
+ private canvasToBlob(format: string, quality: number): Promise<Blob> {
404
+ return new Promise((resolve, reject) => {
405
+ this.canvas.toBlob(
406
+ (blob) => {
407
+ if (blob) {
408
+ resolve(blob)
409
+ } else {
410
+ reject(new Error('Failed to create blob from canvas'))
411
+ }
412
+ },
413
+ `image/${format}`,
414
+ quality
415
+ )
416
+ })
417
+ }
418
+
419
+ /**
420
+ * Generate cache key
421
+ */
422
+ private getCacheKey(time: number, options: ThumbnailOptions): string {
423
+ const width = options.width || this.config.defaultWidth
424
+ const height = options.height || this.config.defaultHeight
425
+ const quality = options.quality || this.config.defaultQuality
426
+ const format = options.format || this.config.defaultFormat
427
+
428
+ return `${time}-${width}x${height}-${quality}-${format}`
429
+ }
430
+
431
+ /**
432
+ * Add thumbnail to cache
433
+ */
434
+ private addToCache(key: string, thumbnail: GeneratedThumbnail): void {
435
+ // Remove oldest items if cache is full
436
+ if (this.cache.size >= this.config.maxCacheSize) {
437
+ const oldestKey = this.cache.keys().next().value
438
+ const oldThumbnail = this.cache.get(oldestKey)
439
+ if (oldThumbnail) {
440
+ URL.revokeObjectURL(oldThumbnail.objectUrl)
441
+ }
442
+ this.cache.delete(oldestKey)
443
+ }
444
+
445
+ this.cache.set(key, thumbnail)
446
+ }
447
+
448
+ /**
449
+ * Cleanup resources
450
+ */
451
+ destroy(): void {
452
+ this.clearCache()
453
+ this.videoElement = null
454
+ this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
455
+ }
456
+ }
457
+
458
+ // Utility functions for thumbnail operations
459
+ export const thumbnailUtils = {
460
+ /**
461
+ * Calculate optimal thumbnail dimensions maintaining aspect ratio
462
+ */
463
+ calculateDimensions(
464
+ sourceWidth: number,
465
+ sourceHeight: number,
466
+ targetWidth: number,
467
+ targetHeight: number
468
+ ): { width: number; height: number; offsetX: number; offsetY: number } {
469
+ const sourceAspect = sourceWidth / sourceHeight
470
+ const targetAspect = targetWidth / targetHeight
471
+
472
+ let width = targetWidth
473
+ let height = targetHeight
474
+ let offsetX = 0
475
+ let offsetY = 0
476
+
477
+ if (sourceAspect > targetAspect) {
478
+ height = targetHeight
479
+ width = targetHeight * sourceAspect
480
+ offsetX = (targetWidth - width) / 2
481
+ } else {
482
+ width = targetWidth
483
+ height = targetWidth / sourceAspect
484
+ offsetY = (targetHeight - height) / 2
485
+ }
486
+
487
+ return { width, height, offsetX, offsetY }
488
+ },
489
+
490
+ /**
491
+ * Create thumbnail URL from blob
492
+ */
493
+ createThumbnailUrl(blob: Blob): string {
494
+ return URL.createObjectURL(blob)
495
+ },
496
+
497
+ /**
498
+ * Revoke thumbnail URL
499
+ */
500
+ revokeThumbnailUrl(url: string): void {
501
+ URL.revokeObjectURL(url)
502
+ },
503
+
504
+ /**
505
+ * Convert time to thumbnail index
506
+ */
507
+ timeToIndex(time: number, duration: number, count: number): number {
508
+ const normalizedTime = Math.max(0, Math.min(time, duration))
509
+ const index = Math.floor((normalizedTime / duration) * count)
510
+ return Math.min(index, count - 1)
511
+ },
512
+
513
+ /**
514
+ * Convert index to time
515
+ */
516
+ indexToTime(index: number, duration: number, count: number): number {
517
+ return (index / count) * duration
518
+ }
519
+ }
@@ -0,0 +1,27 @@
1
+ export {
2
+ useKeyboardShortcuts,
3
+ createMediaKeyboardShortcuts,
4
+ type KeyboardShortcut,
5
+ type UseKeyboardShortcutsOptions
6
+ } from './use-keyboard-shortcuts'
7
+
8
+ export {
9
+ useFocusManagement,
10
+ useMediaPlayerFocus,
11
+ type FocusOptions,
12
+ type UseFocusManagementOptions
13
+ } from './use-focus-management'
14
+
15
+ export {
16
+ useScreenReader,
17
+ useMediaScreenReader,
18
+ type AnnouncementPriority,
19
+ type UseScreenReaderOptions
20
+ } from './use-screen-reader'
21
+
22
+ export {
23
+ useAccessibilityPreferences,
24
+ useAccessibilityStyles,
25
+ useFocusVisible,
26
+ type AccessibilityPreferences
27
+ } from './use-accessibility-preferences'