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