@therealtinhtute/baroiplayer 1.2.0 → 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 (305) hide show
  1. package/README.md +91 -73
  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 +2 -112
  64. package/dist/index.js.map +1 -1
  65. package/dist/index.mjs +786 -6271
  66. package/dist/index.mjs.map +1 -1
  67. package/dist/package.json +3 -0
  68. package/package.json +110 -108
  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 -2
  78. package/dist/context/media.js.map +0 -1
  79. package/dist/context/media.mjs +0 -15
  80. package/dist/context/media.mjs.map +0 -1
  81. package/dist/context/store.js +0 -2
  82. package/dist/context/store.js.map +0 -1
  83. package/dist/context/store.mjs +0 -5
  84. package/dist/context/store.mjs.map +0 -1
  85. package/dist/context-BYwDoSRX.js +0 -1172
  86. package/dist/context-BYwDoSRX.js.map +0 -1
  87. package/dist/context-eNS62_ac.cjs +0 -23
  88. package/dist/context-eNS62_ac.cjs.map +0 -1
  89. package/dist/core-events-B1iM2F46.cjs +0 -2
  90. package/dist/core-events-B1iM2F46.cjs.map +0 -1
  91. package/dist/core-events-DunPHKRE.js +0 -484
  92. package/dist/core-events-DunPHKRE.js.map +0 -1
  93. package/dist/core-plugins-By-4-kGs.cjs +0 -2
  94. package/dist/core-plugins-By-4-kGs.cjs.map +0 -1
  95. package/dist/core-plugins-DSBWcTuX.js +0 -821
  96. package/dist/core-plugins-DSBWcTuX.js.map +0 -1
  97. package/dist/core.js +0 -37
  98. package/dist/core.js.map +0 -1
  99. package/dist/core.mjs +0 -1233
  100. package/dist/core.mjs.map +0 -1
  101. package/dist/enhanced-components-B_l3cist.js +0 -1142
  102. package/dist/enhanced-components-B_l3cist.js.map +0 -1
  103. package/dist/enhanced-components-BspFs9OO.cjs +0 -2
  104. package/dist/enhanced-components-BspFs9OO.cjs.map +0 -1
  105. package/dist/hooks-BXwI8QG6.cjs +0 -4
  106. package/dist/hooks-BXwI8QG6.cjs.map +0 -1
  107. package/dist/hooks-Bpi-jDiO.js +0 -1816
  108. package/dist/hooks-Bpi-jDiO.js.map +0 -1
  109. package/dist/minimal-components-C4z900Ro.js +0 -1926
  110. package/dist/minimal-components-C4z900Ro.js.map +0 -1
  111. package/dist/minimal-components-v4qBp6jv.cjs +0 -2
  112. package/dist/minimal-components-v4qBp6jv.cjs.map +0 -1
  113. package/dist/minimal-components.css +0 -1
  114. package/dist/mona-loading.gif +0 -0
  115. package/dist/performance-monitor-C5k6FDXw.js +0 -301
  116. package/dist/performance-monitor-C5k6FDXw.js.map +0 -1
  117. package/dist/performance-monitor-lJmuUjQQ.cjs +0 -2
  118. package/dist/performance-monitor-lJmuUjQQ.cjs.map +0 -1
  119. package/dist/utils-CFOue_9K.cjs +0 -9
  120. package/dist/utils-CFOue_9K.cjs.map +0 -1
  121. package/dist/utils-Dzpl2ArK.js +0 -2844
  122. package/dist/utils-Dzpl2ArK.js.map +0 -1
  123. package/dist/utils.js +0 -2
  124. package/dist/utils.js.map +0 -1
  125. package/dist/utils.mjs +0 -11
  126. package/dist/utils.mjs.map +0 -1
  127. package/src/adapters/videojs-adapter.tsx +0 -470
  128. package/src/assets/mona-loading.gif +0 -0
  129. package/src/components/AnimateLoading.tsx +0 -107
  130. package/src/components/LoadingState.tsx +0 -239
  131. package/src/components/__tests__/media-player-accessibility.test.tsx +0 -510
  132. package/src/components/__tests__/media-player-react19.test.tsx +0 -457
  133. package/src/components/accessibility/keyboard-help-overlay.tsx +0 -207
  134. package/src/components/audio-player.tsx +0 -85
  135. package/src/components/basic/index.ts +0 -37
  136. package/src/components/enhanced/advanced-features.tsx +0 -311
  137. package/src/components/enhanced/debug-panel.tsx +0 -448
  138. package/src/components/enhanced/enhanced-controls.tsx +0 -597
  139. package/src/components/enhanced/enhanced-media-player.tsx +0 -566
  140. package/src/components/enhanced/error-handling.tsx +0 -338
  141. package/src/components/enhanced/index.ts +0 -39
  142. package/src/components/enhanced/media-player-enhanced.tsx +0 -383
  143. package/src/components/examples/basic-video-player.tsx +0 -50
  144. package/src/components/examples/custom-controls-example.tsx +0 -63
  145. package/src/components/examples/enhanced-controls-example.tsx +0 -100
  146. package/src/components/examples/enhanced-media-player-example.tsx +0 -302
  147. package/src/components/examples/loading-states-demo.tsx +0 -142
  148. package/src/components/examples/react-19-demo.tsx +0 -353
  149. package/src/components/examples/volume-orientation-demo.tsx +0 -106
  150. package/src/components/icon-demo.tsx +0 -196
  151. package/src/components/legacy.tsx +0 -87
  152. package/src/components/media-metadata-display.tsx +0 -99
  153. package/src/components/media-player-audio.tsx +0 -39
  154. package/src/components/media-player-controls.tsx +0 -223
  155. package/src/components/media-player-error.tsx +0 -163
  156. package/src/components/media-player-fullscreen.tsx +0 -109
  157. package/src/components/media-player-hls.tsx +0 -111
  158. package/src/components/media-player-loading.tsx +0 -76
  159. package/src/components/media-player-play.tsx +0 -75
  160. package/src/components/media-player-root.tsx +0 -338
  161. package/src/components/media-player-seek.tsx +0 -168
  162. package/src/components/media-player-time.tsx +0 -99
  163. package/src/components/media-player-tooltip.tsx +0 -58
  164. package/src/components/media-player-video.tsx +0 -54
  165. package/src/components/media-player-volume.tsx +0 -155
  166. package/src/components/media-player.tsx +0 -292
  167. package/src/components/media-suspense.tsx +0 -344
  168. package/src/components/player-settings-form.tsx +0 -158
  169. package/src/components/react-player-wrapper.tsx +0 -651
  170. package/src/components/simple-video-player.tsx +0 -62
  171. package/src/components/ui/blur-poster.tsx +0 -93
  172. package/src/components/ui/empty-state.tsx +0 -91
  173. package/src/components/ui/enhanced-seek-bar.tsx +0 -254
  174. package/src/components/ui/error-state.tsx +0 -124
  175. package/src/components/ui/fullscreen-button.tsx +0 -81
  176. package/src/components/ui/loading-skeleton.tsx +0 -107
  177. package/src/components/ui/media-resource-preloader.tsx +0 -458
  178. package/src/components/ui/offline-message.tsx +0 -98
  179. package/src/components/ui/play-button.tsx +0 -99
  180. package/src/components/ui/seek-bar.tsx +0 -191
  181. package/src/components/ui/time-display.tsx +0 -61
  182. package/src/components/ui/volume-control.tsx +0 -190
  183. package/src/components/unified/base-media-player.tsx +0 -252
  184. package/src/components/unified/media-player.tsx +0 -75
  185. package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +0 -587
  186. package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +0 -573
  187. package/src/components/variants/minimal/MinimalControls.tsx +0 -124
  188. package/src/components/variants/minimal/MinimalLoading.tsx +0 -28
  189. package/src/components/variants/minimal/MinimalMediaPlayer.tsx +0 -318
  190. package/src/components/variants/minimal/MinimalOverlay.tsx +0 -58
  191. package/src/components/variants/minimal/MinimalPlayButton.tsx +0 -56
  192. package/src/components/variants/minimal/MinimalSeekBar.tsx +0 -140
  193. package/src/components/variants/minimal/MinimalVolumeControl.tsx +0 -75
  194. package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +0 -206
  195. package/src/components/variants/minimal/components/CenterPlayButton.tsx +0 -22
  196. package/src/components/variants/minimal/components/ControlBar.tsx +0 -142
  197. package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +0 -412
  198. package/src/components/variants/minimal/components/PlaybackControls.tsx +0 -46
  199. package/src/components/variants/minimal/components/RightControls.tsx +0 -164
  200. package/src/components/variants/minimal/components/SeekBar.tsx +0 -127
  201. package/src/components/variants/minimal/components/SettingsDropdown.tsx +0 -68
  202. package/src/components/variants/minimal/components/SubtitleDisplay.tsx +0 -21
  203. package/src/components/variants/minimal/components/TimeDisplay.tsx +0 -15
  204. package/src/components/variants/minimal/components/VideoElement.tsx +0 -33
  205. package/src/components/variants/minimal/components/VolumeControl.tsx +0 -220
  206. package/src/components/variants/minimal/index.ts +0 -23
  207. package/src/components/video-player.tsx +0 -100
  208. package/src/context/__tests__/media-store.test.ts +0 -509
  209. package/src/context/error-handling.ts +0 -398
  210. package/src/context/media-context.tsx +0 -254
  211. package/src/context/media-provider.tsx +0 -264
  212. package/src/context/media-store.ts +0 -387
  213. package/src/context/persistence.ts +0 -462
  214. package/src/core/events/event-system.ts +0 -448
  215. package/src/core/events/hooks.ts +0 -325
  216. package/src/core/events/index.ts +0 -22
  217. package/src/core/events/middleware.ts +0 -286
  218. package/src/core/index.ts +0 -22
  219. package/src/core/optimization/bundle-optimizer.tsx +0 -312
  220. package/src/core/performance/cicd.ts +0 -98
  221. package/src/core/performance/index.ts +0 -38
  222. package/src/core/performance/media-loading-optimizer.ts +0 -544
  223. package/src/core/performance/performance-budgets.ts +0 -437
  224. package/src/core/performance/performance-monitor.tsx +0 -488
  225. package/src/core/performance/performance-testing.ts +0 -461
  226. package/src/core/plugins/__tests__/registry.test.ts +0 -595
  227. package/src/core/plugins/base-plugin.ts +0 -225
  228. package/src/core/plugins/enhanced-plugin-system.ts +0 -435
  229. package/src/core/plugins/examples/analytics-plugin.ts +0 -241
  230. package/src/core/plugins/index.ts +0 -39
  231. package/src/core/plugins/registry.ts +0 -546
  232. package/src/core/plugins/types.ts +0 -169
  233. package/src/core/plugins/utils.ts +0 -317
  234. package/src/core/progressive-loading.tsx +0 -331
  235. package/src/core/providers/enhanced-hls-provider.ts +0 -307
  236. package/src/core/providers/lazy-provider.tsx +0 -14
  237. package/src/core/providers/service-provider.tsx +0 -164
  238. package/src/core/utils.tsx +0 -34
  239. package/src/features/media-session/media-session-manager.ts +0 -411
  240. package/src/features/picture-in-picture/pip-manager.ts +0 -428
  241. package/src/features/playlist/playlist-manager.ts +0 -626
  242. package/src/features/subtitles/subtitle-manager.ts +0 -435
  243. package/src/features/subtitles/subtitle-parser.ts +0 -284
  244. package/src/features/thumbnails/thumbnail-generator.ts +0 -528
  245. package/src/hooks/__tests__/currentTime-fix.test.ts +0 -149
  246. package/src/hooks/__tests__/use-media-player.test.ts +0 -712
  247. package/src/hooks/__tests__/use-media-state.test.tsx +0 -316
  248. package/src/hooks/accessibility/index.ts +0 -27
  249. package/src/hooks/accessibility/use-accessibility-preferences.ts +0 -190
  250. package/src/hooks/accessibility/use-focus-management.ts +0 -183
  251. package/src/hooks/accessibility/use-keyboard-shortcuts.ts +0 -152
  252. package/src/hooks/accessibility/use-screen-reader.ts +0 -212
  253. package/src/hooks/core/index.ts +0 -47
  254. package/src/hooks/legacy.ts +0 -21
  255. package/src/hooks/use-buffer-health.ts +0 -218
  256. package/src/hooks/use-enhanced-media-player.ts +0 -575
  257. package/src/hooks/use-focus-trap.tsx +0 -162
  258. package/src/hooks/use-intelligent-preloading.ts +0 -387
  259. package/src/hooks/use-keyboard-controls.ts +0 -115
  260. package/src/hooks/use-keyboard-shortcuts.ts +0 -255
  261. package/src/hooks/use-media-actions.ts +0 -184
  262. package/src/hooks/use-media-metadata.ts +0 -91
  263. package/src/hooks/use-media-optimistic.ts +0 -264
  264. package/src/hooks/use-media-player.ts +0 -343
  265. package/src/hooks/use-media-state.ts +0 -194
  266. package/src/hooks/use-memory-optimization.ts +0 -360
  267. package/src/hooks/use-network-status.ts +0 -124
  268. package/src/hooks/use-optimistic-controls.ts +0 -95
  269. package/src/hooks/use-optimized-media.ts +0 -283
  270. package/src/hooks/use-smart-preload.ts +0 -229
  271. package/src/hooks/use-touch-gestures.ts +0 -461
  272. package/src/hooks/useEnhancedSubtitles.ts +0 -258
  273. package/src/hooks/useKeyboardShortcuts.ts +0 -106
  274. package/src/hooks/useMediaControls.ts +0 -52
  275. package/src/hooks/useScreenshot.ts +0 -61
  276. package/src/hooks/useSubtitles.ts +0 -91
  277. package/src/hooks/useVideoPlayer.ts +0 -278
  278. package/src/icons/components/FullscreenIcon.tsx +0 -36
  279. package/src/icons/components/SettingsIcon.tsx +0 -37
  280. package/src/icons/components/animated/LoadingIcon.tsx +0 -27
  281. package/src/icons/components/animated/PlayPauseIcon.tsx +0 -42
  282. package/src/icons/components/media/PauseIcon.tsx +0 -38
  283. package/src/icons/components/media/PlayIcon.tsx +0 -37
  284. package/src/icons/components/volume/VolumeIcon.tsx +0 -38
  285. package/src/icons/components/volume/VolumeOffIcon.tsx +0 -38
  286. package/src/icons/index.ts +0 -56
  287. package/src/icons/provider.tsx +0 -58
  288. package/src/icons/types.ts +0 -60
  289. package/src/index-enhanced.ts +0 -35
  290. package/src/index-legacy.ts +0 -324
  291. package/src/index.css +0 -102
  292. package/src/lib/__tests__/async-utils.test.ts +0 -291
  293. package/src/lib/async-utils.ts +0 -194
  294. package/src/lib/deprecation.ts +0 -51
  295. package/src/lib/format-time.ts +0 -37
  296. package/src/lib/index.ts +0 -4
  297. package/src/lib/utils.ts +0 -6
  298. package/src/styles/globals.css +0 -462
  299. package/src/styles/minimal-player.css +0 -621
  300. package/src/styles/pixel-art.css +0 -41
  301. package/src/styles/videojs.css +0 -100
  302. package/src/types/events.ts +0 -47
  303. package/src/types/formats.ts +0 -79
  304. package/src/types/index.ts +0 -203
  305. 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
- })