@therealtinhtute/baroiplayer 1.1.0 → 1.2.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.
- package/dist/core-plugins-By-4-kGs.cjs +2 -0
- package/dist/core-plugins-By-4-kGs.cjs.map +1 -0
- package/dist/{core-plugins-CROGvQUy.js → core-plugins-DSBWcTuX.js} +444 -309
- package/dist/core-plugins-DSBWcTuX.js.map +1 -0
- package/dist/core.js +2 -2
- package/dist/core.mjs +19 -19
- package/dist/{enhanced-components-DUw2KrmQ.js → enhanced-components-B_l3cist.js} +2 -2
- package/dist/{enhanced-components-DUw2KrmQ.js.map → enhanced-components-B_l3cist.js.map} +1 -1
- package/dist/{enhanced-components-DXBCHU0v.cjs → enhanced-components-BspFs9OO.cjs} +2 -2
- package/dist/{enhanced-components-DXBCHU0v.cjs.map → enhanced-components-BspFs9OO.cjs.map} +1 -1
- package/dist/hooks-BXwI8QG6.cjs +4 -0
- package/dist/hooks-BXwI8QG6.cjs.map +1 -0
- package/dist/hooks-Bpi-jDiO.js +1816 -0
- package/dist/hooks-Bpi-jDiO.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -20
- package/dist/index.mjs.map +1 -1
- package/dist/{minimal-components-DlRK7Qu_.js → minimal-components-C4z900Ro.js} +297 -298
- package/dist/minimal-components-C4z900Ro.js.map +1 -0
- package/dist/minimal-components-v4qBp6jv.cjs +2 -0
- package/dist/minimal-components-v4qBp6jv.cjs.map +1 -0
- package/package.json +1 -1
- package/src/components/accessibility/keyboard-help-overlay.tsx +2 -0
- package/src/components/enhanced/debug-panel.tsx +1 -1
- package/src/components/enhanced/enhanced-media-player.tsx +68 -25
- package/src/components/legacy.tsx +6 -6
- package/src/components/media-player-video.tsx +5 -12
- package/src/components/media-player.tsx +2 -2
- package/src/components/ui/enhanced-seek-bar.tsx +2 -2
- package/src/components/ui/media-resource-preloader.tsx +21 -23
- package/src/components/unified/base-media-player.tsx +22 -10
- package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +58 -38
- package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +6 -6
- package/src/core/optimization/bundle-optimizer.tsx +26 -27
- package/src/core/plugins/__tests__/registry.test.ts +1 -1
- package/src/core/plugins/enhanced-plugin-system.ts +26 -8
- package/src/core/plugins/registry.ts +319 -92
- package/src/core/plugins/types.ts +21 -3
- package/src/core/providers/enhanced-hls-provider.ts +11 -40
- package/src/core/providers/lazy-provider.tsx +5 -42
- package/src/features/media-session/media-session-manager.ts +4 -26
- package/src/features/picture-in-picture/pip-manager.ts +2 -2
- package/src/features/playlist/playlist-manager.ts +26 -18
- package/src/features/subtitles/subtitle-manager.ts +1 -1
- package/src/features/subtitles/subtitle-parser.ts +19 -16
- package/src/features/thumbnails/thumbnail-generator.ts +12 -5
- package/src/hooks/__tests__/currentTime-fix.test.ts +149 -0
- package/src/hooks/accessibility/use-accessibility-preferences.ts +1 -1
- package/src/hooks/accessibility/use-focus-management.ts +2 -2
- package/src/hooks/accessibility/use-keyboard-shortcuts.ts +2 -2
- package/src/hooks/accessibility/use-screen-reader.ts +1 -1
- package/src/hooks/core/index.ts +1 -1
- package/src/hooks/use-buffer-health.ts +1 -1
- package/src/hooks/use-enhanced-media-player.ts +107 -9
- package/src/hooks/use-focus-trap.tsx +2 -2
- package/src/hooks/use-intelligent-preloading.ts +23 -3
- package/src/hooks/use-keyboard-shortcuts.ts +2 -2
- package/src/hooks/use-media-player.ts +74 -43
- package/src/hooks/use-memory-optimization.ts +15 -15
- package/src/hooks/use-smart-preload.ts +1 -2
- package/src/hooks/useKeyboardShortcuts.ts +34 -2
- package/src/hooks/useVideoPlayer.ts +50 -4
- package/src/index-legacy.ts +3 -5
- package/src/test/accessibility-utils.ts +7 -7
- package/src/test/media-mocks.ts +41 -28
- package/src/test/setup.ts +22 -4
- package/src/types/index.ts +13 -0
- package/dist/core-plugins-CROGvQUy.js.map +0 -1
- package/dist/core-plugins-DtsHBOtF.cjs +0 -2
- package/dist/core-plugins-DtsHBOtF.cjs.map +0 -1
- package/dist/hooks-BW-JjVgP.cjs +0 -4
- package/dist/hooks-BW-JjVgP.cjs.map +0 -1
- package/dist/hooks-CyX6De5M.js +0 -1693
- package/dist/hooks-CyX6De5M.js.map +0 -1
- package/dist/minimal-components-Bdcpxav5.cjs +0 -2
- package/dist/minimal-components-Bdcpxav5.cjs.map +0 -1
- package/dist/minimal-components-DlRK7Qu_.js.map +0 -1
package/src/hooks/core/index.ts
CHANGED
|
@@ -44,4 +44,4 @@ export type { VideoState, VideoControls } from "../useVideoPlayer"
|
|
|
44
44
|
|
|
45
45
|
// Specialized hooks
|
|
46
46
|
export { useMediaMetadata } from "../use-media-metadata"
|
|
47
|
-
export {
|
|
47
|
+
export { useOptimisticMediaState } from "../use-optimistic-controls"
|
|
@@ -120,7 +120,7 @@ export function useBufferHealth(
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
if ("audioTracks" in video) {
|
|
123
|
-
const tracks = (video as
|
|
123
|
+
const tracks = (video as any).audioTracks
|
|
124
124
|
if (tracks && tracks.length > 0) {
|
|
125
125
|
audioLevel = tracks[0]?.volume || null
|
|
126
126
|
}
|
|
@@ -34,6 +34,32 @@ interface UseEnhancedMediaPlayerOptions {
|
|
|
34
34
|
timeUpdateThrottleMs?: number
|
|
35
35
|
enableSeekPreview?: boolean
|
|
36
36
|
preloadStrategy?: "eager" | "lazy" | "progressive"
|
|
37
|
+
// Event handlers
|
|
38
|
+
onPlay?: () => void
|
|
39
|
+
onPause?: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Safely sets currentTime on a media element, ensuring the value is finite
|
|
44
|
+
*/
|
|
45
|
+
function safeSetCurrentTime(media: HTMLVideoElement | HTMLAudioElement, time: number): boolean {
|
|
46
|
+
if (!Number.isFinite(time)) {
|
|
47
|
+
console.warn(`Attempted to set non-finite currentTime: ${time}`)
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (time < 0) {
|
|
52
|
+
console.warn(`Attempted to set negative currentTime: ${time}`)
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
media.currentTime = time
|
|
58
|
+
return true
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`Failed to set currentTime to ${time}:`, error)
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
// Performance-optimized media player hook with React 19 features
|
|
@@ -51,7 +77,7 @@ export function useEnhancedMediaPlayer(options: UseEnhancedMediaPlayerOptions) {
|
|
|
51
77
|
const lastFrameTime = React.useRef<number>(0)
|
|
52
78
|
|
|
53
79
|
// Throttled time update
|
|
54
|
-
const timeUpdateTimeoutRef = useRef<
|
|
80
|
+
const timeUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
55
81
|
const lastTimeUpdate = useRef<number>(0)
|
|
56
82
|
|
|
57
83
|
// Store integration
|
|
@@ -331,14 +357,25 @@ export function useEnhancedMediaPlayer(options: UseEnhancedMediaPlayerOptions) {
|
|
|
331
357
|
const media = mediaRef.current
|
|
332
358
|
if (!media) return
|
|
333
359
|
|
|
334
|
-
const
|
|
360
|
+
const validDuration = Number.isFinite(duration) && duration > 0 ? duration : Number.isFinite(media.duration) ? media.duration : 0
|
|
361
|
+
if (validDuration === 0) {
|
|
362
|
+
console.warn("Cannot seek: duration is not available")
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const clampedTime = Math.max(0, Math.min(time, validDuration))
|
|
367
|
+
|
|
368
|
+
if (!Number.isFinite(clampedTime)) {
|
|
369
|
+
console.warn(`Computed non-finite seek time: ${clampedTime}`)
|
|
370
|
+
return
|
|
371
|
+
}
|
|
335
372
|
|
|
336
373
|
if (isEnhanced && actions) {
|
|
337
374
|
startSeekTransition(() => {
|
|
338
375
|
actions.seek(clampedTime, true) // Optimistic
|
|
339
376
|
})
|
|
340
377
|
} else {
|
|
341
|
-
media
|
|
378
|
+
safeSetCurrentTime(media, clampedTime)
|
|
342
379
|
}
|
|
343
380
|
}
|
|
344
381
|
|
|
@@ -396,18 +433,79 @@ export function useEnhancedMediaPlayer(options: UseEnhancedMediaPlayerOptions) {
|
|
|
396
433
|
}
|
|
397
434
|
}
|
|
398
435
|
|
|
436
|
+
const mute = () => {
|
|
437
|
+
const media = mediaRef.current
|
|
438
|
+
if (!media) return
|
|
439
|
+
|
|
440
|
+
if (isEnhanced && actions) {
|
|
441
|
+
actions.mute(true)
|
|
442
|
+
} else {
|
|
443
|
+
media.muted = true
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const unmute = () => {
|
|
448
|
+
const media = mediaRef.current
|
|
449
|
+
if (!media) return
|
|
450
|
+
|
|
451
|
+
if (isEnhanced && actions) {
|
|
452
|
+
actions.unmute(true)
|
|
453
|
+
} else {
|
|
454
|
+
media.muted = false
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const toggleFullscreen = async () => {
|
|
459
|
+
if (isFullscreen) {
|
|
460
|
+
await exitFullscreen()
|
|
461
|
+
} else {
|
|
462
|
+
await enterFullscreen()
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const setCurrentTime = (time: number) => {
|
|
467
|
+
const media = mediaRef.current
|
|
468
|
+
if (!media) return
|
|
469
|
+
|
|
470
|
+
const validDuration =
|
|
471
|
+
Number.isFinite(duration) && duration > 0
|
|
472
|
+
? duration
|
|
473
|
+
: Number.isFinite(media.duration)
|
|
474
|
+
? media.duration
|
|
475
|
+
: 0
|
|
476
|
+
|
|
477
|
+
if (validDuration === 0) {
|
|
478
|
+
console.warn("Cannot set currentTime: duration is not available")
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const clampedTime = Math.max(0, Math.min(time, validDuration))
|
|
483
|
+
|
|
484
|
+
if (!Number.isFinite(clampedTime)) {
|
|
485
|
+
console.warn(`Computed non-finite currentTime: ${clampedTime}`)
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
safeSetCurrentTime(media, clampedTime)
|
|
490
|
+
}
|
|
491
|
+
|
|
399
492
|
return {
|
|
400
493
|
play,
|
|
401
494
|
pause,
|
|
402
495
|
toggle,
|
|
403
496
|
seek,
|
|
497
|
+
setCurrentTime,
|
|
404
498
|
setVolume,
|
|
499
|
+
mute,
|
|
500
|
+
unmute,
|
|
405
501
|
toggleMute,
|
|
406
502
|
enterFullscreen,
|
|
407
|
-
exitFullscreen
|
|
503
|
+
exitFullscreen,
|
|
504
|
+
toggleFullscreen
|
|
408
505
|
}
|
|
409
506
|
}, [
|
|
410
507
|
isPlaying,
|
|
508
|
+
isFullscreen,
|
|
411
509
|
duration,
|
|
412
510
|
isEnhanced,
|
|
413
511
|
actions,
|
|
@@ -421,13 +519,13 @@ export function useEnhancedMediaPlayer(options: UseEnhancedMediaPlayerOptions) {
|
|
|
421
519
|
() => ({
|
|
422
520
|
isPlaying: optimisticState?.isPlaying ?? isPlaying,
|
|
423
521
|
currentTime: optimisticState?.currentTime ?? currentTime,
|
|
424
|
-
duration: optimisticState?.duration ?? duration,
|
|
522
|
+
duration: optimisticState?.actualState?.duration ?? duration,
|
|
425
523
|
volume: optimisticState?.volume ?? optimisticVolume,
|
|
426
524
|
isMuted: optimisticState?.isMuted ?? isMuted,
|
|
427
525
|
isLoading: optimisticState?.isBuffering ?? isLoading,
|
|
428
526
|
isBuffering: optimisticState?.isBuffering ?? isBuffering,
|
|
429
|
-
error: optimisticState?.error ?? error,
|
|
430
|
-
buffered: optimisticState?.buffered ?? buffered,
|
|
527
|
+
error: optimisticState?.actualState?.error ?? error,
|
|
528
|
+
buffered: optimisticState?.actualState?.buffered ?? buffered,
|
|
431
529
|
isFullscreen: optimisticState?.isFullscreen ?? isFullscreen,
|
|
432
530
|
progress: duration > 0 ? currentTime / duration : 0,
|
|
433
531
|
isTransitioning: isPending || isSeeking,
|
|
@@ -467,8 +565,8 @@ export function useEnhancedMediaPlayer(options: UseEnhancedMediaPlayerOptions) {
|
|
|
467
565
|
isTransitioning: isPending || isSeeking,
|
|
468
566
|
|
|
469
567
|
// Enhanced features
|
|
470
|
-
actions: optimisticState
|
|
471
|
-
actionState: optimisticState
|
|
568
|
+
actions: optimisticState,
|
|
569
|
+
actionState: optimisticState,
|
|
472
570
|
|
|
473
571
|
// Optimistic features
|
|
474
572
|
optimisticState,
|
|
@@ -98,14 +98,14 @@ export function useFocusTrap(
|
|
|
98
98
|
const focusableElements = getFocusableElements()
|
|
99
99
|
const firstElement = focusableElements[0]
|
|
100
100
|
firstElement?.focus()
|
|
101
|
-
lastFocusedElementRef.current = firstElement
|
|
101
|
+
lastFocusedElementRef.current = firstElement || null
|
|
102
102
|
}, [getFocusableElements])
|
|
103
103
|
|
|
104
104
|
const focusLastElement = useCallback(() => {
|
|
105
105
|
const focusableElements = getFocusableElements()
|
|
106
106
|
const lastElement = focusableElements[focusableElements.length - 1]
|
|
107
107
|
lastElement?.focus()
|
|
108
|
-
lastFocusedElementRef.current = lastElement
|
|
108
|
+
lastFocusedElementRef.current = lastElement || null
|
|
109
109
|
}, [getFocusableElements])
|
|
110
110
|
|
|
111
111
|
return {
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
import { useCallback, useEffect, useMemo, useRef } from "react"
|
|
7
7
|
import { MediaSource } from "../types"
|
|
8
8
|
|
|
9
|
+
interface NetworkInformation {
|
|
10
|
+
type?: string
|
|
11
|
+
downlink?: number
|
|
12
|
+
effectiveType?: "slow-2g" | "2g" | "3g" | "4g"
|
|
13
|
+
saveData?: boolean
|
|
14
|
+
rtt?: number
|
|
15
|
+
addEventListener?: (type: string, listener: EventListener) => void
|
|
16
|
+
removeEventListener?: (type: string, listener: EventListener) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
interface IntelligentPreloadingOptions {
|
|
10
20
|
/** Enable user behavior tracking */
|
|
11
21
|
trackUserBehavior?: boolean
|
|
@@ -158,8 +168,10 @@ export const useIntelligentPreloading = (
|
|
|
158
168
|
|
|
159
169
|
// Additional heuristics
|
|
160
170
|
if (shouldPreload) {
|
|
171
|
+
const sourceType = source.type as string
|
|
172
|
+
|
|
161
173
|
// Prioritize HLS and adaptive streaming
|
|
162
|
-
if (
|
|
174
|
+
if (sourceType === "hls" || sourceType === "m3u8" || sourceType === "application/x-mpegURL") {
|
|
163
175
|
priority = priority === "low" ? "medium" : "high"
|
|
164
176
|
}
|
|
165
177
|
|
|
@@ -171,7 +183,7 @@ export const useIntelligentPreloading = (
|
|
|
171
183
|
}
|
|
172
184
|
|
|
173
185
|
// Consider content type (video gets higher priority than audio)
|
|
174
|
-
if (
|
|
186
|
+
if (sourceType === "mp4" || sourceType === "webm" || sourceType === "video/mp4" || sourceType === "video/webm") {
|
|
175
187
|
priority = priority === "low" ? "medium" : priority
|
|
176
188
|
}
|
|
177
189
|
}
|
|
@@ -189,19 +201,27 @@ export const useIntelligentPreloading = (
|
|
|
189
201
|
link.rel = "preload"
|
|
190
202
|
link.href = source.src
|
|
191
203
|
|
|
204
|
+
const sourceType = source.type as string
|
|
205
|
+
|
|
192
206
|
// Determine the correct 'as' attribute
|
|
193
|
-
switch (
|
|
207
|
+
switch (sourceType) {
|
|
194
208
|
case "hls":
|
|
195
209
|
case "m3u8":
|
|
210
|
+
case "application/x-mpegURL":
|
|
196
211
|
link.as = "fetch"
|
|
197
212
|
break
|
|
198
213
|
case "mp4":
|
|
199
214
|
case "webm":
|
|
215
|
+
case "video/mp4":
|
|
216
|
+
case "video/webm":
|
|
200
217
|
link.as = "video"
|
|
201
218
|
break
|
|
202
219
|
case "mp3":
|
|
203
220
|
case "wav":
|
|
204
221
|
case "ogg":
|
|
222
|
+
case "audio/mpeg":
|
|
223
|
+
case "audio/wav":
|
|
224
|
+
case "audio/ogg":
|
|
205
225
|
link.as = "audio"
|
|
206
226
|
break
|
|
207
227
|
default:
|
|
@@ -63,10 +63,10 @@ export function useKeyboardShortcuts(
|
|
|
63
63
|
useEffect(() => {
|
|
64
64
|
const target = opts.capture ? window.document.body : window
|
|
65
65
|
|
|
66
|
-
target.addEventListener("keydown", handleKeyDown)
|
|
66
|
+
target.addEventListener("keydown", handleKeyDown as EventListener)
|
|
67
67
|
|
|
68
68
|
return () => {
|
|
69
|
-
target.removeEventListener("keydown", handleKeyDown)
|
|
69
|
+
target.removeEventListener("keydown", handleKeyDown as EventListener)
|
|
70
70
|
}
|
|
71
71
|
}, [handleKeyDown, opts.capture])
|
|
72
72
|
|
|
@@ -27,6 +27,29 @@ interface UseMediaPlayerOptions {
|
|
|
27
27
|
onEnded?: () => void
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Safely sets currentTime on a media element, ensuring the value is finite
|
|
32
|
+
*/
|
|
33
|
+
function safeSetCurrentTime(media: HTMLVideoElement | HTMLAudioElement, time: number): boolean {
|
|
34
|
+
if (!Number.isFinite(time)) {
|
|
35
|
+
console.warn(`Attempted to set non-finite currentTime: ${time}`)
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (time < 0) {
|
|
40
|
+
console.warn(`Attempted to set negative currentTime: ${time}`)
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
media.currentTime = time
|
|
46
|
+
return true
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`Failed to set currentTime to ${time}:`, error)
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
31
54
|
const mediaRef = useRef<HTMLVideoElement | HTMLAudioElement>(null)
|
|
32
55
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
@@ -73,53 +96,53 @@ export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
|
73
96
|
}, [options, setVolume, setMuted])
|
|
74
97
|
|
|
75
98
|
// Event handlers
|
|
76
|
-
const handlePlay = () => {
|
|
99
|
+
const handlePlay = useCallback(() => {
|
|
77
100
|
setPlaying(true)
|
|
78
101
|
options.onPlay?.()
|
|
79
|
-
}
|
|
102
|
+
}, [setPlaying, options])
|
|
80
103
|
|
|
81
|
-
const handlePause = () => {
|
|
104
|
+
const handlePause = useCallback(() => {
|
|
82
105
|
setPlaying(false)
|
|
83
106
|
options.onPause?.()
|
|
84
|
-
}
|
|
107
|
+
}, [setPlaying, options])
|
|
85
108
|
|
|
86
|
-
const handleTimeUpdate = () => {
|
|
109
|
+
const handleTimeUpdate = useCallback(() => {
|
|
87
110
|
const media = mediaRef.current
|
|
88
111
|
if (!media) return
|
|
89
112
|
|
|
90
113
|
setCurrentTime(media.currentTime)
|
|
91
114
|
options.onTimeUpdate?.(media.currentTime)
|
|
92
|
-
}
|
|
115
|
+
}, [setCurrentTime, options])
|
|
93
116
|
|
|
94
|
-
const handleDurationChange = () => {
|
|
117
|
+
const handleDurationChange = useCallback(() => {
|
|
95
118
|
const media = mediaRef.current
|
|
96
119
|
if (!media) return
|
|
97
120
|
|
|
98
121
|
setDuration(media.duration)
|
|
99
122
|
options.onDurationChange?.(media.duration)
|
|
100
|
-
}
|
|
123
|
+
}, [setDuration, options])
|
|
101
124
|
|
|
102
|
-
const handleVolumeChange = () => {
|
|
125
|
+
const handleVolumeChange = useCallback(() => {
|
|
103
126
|
const media = mediaRef.current
|
|
104
127
|
if (!media) return
|
|
105
128
|
|
|
106
129
|
setVolume(media.volume)
|
|
107
130
|
setMuted(media.muted)
|
|
108
131
|
options.onVolumeChange?.(media.volume)
|
|
109
|
-
}
|
|
132
|
+
}, [setVolume, setMuted, options])
|
|
110
133
|
|
|
111
|
-
const handleLoadStart = () => {
|
|
134
|
+
const handleLoadStart = useCallback(() => {
|
|
112
135
|
setLoading(true)
|
|
113
136
|
setError(null)
|
|
114
137
|
options.onLoadStart?.()
|
|
115
|
-
}
|
|
138
|
+
}, [setLoading, setError, options])
|
|
116
139
|
|
|
117
|
-
const handleLoadedData = () => {
|
|
140
|
+
const handleLoadedData = useCallback(() => {
|
|
118
141
|
setLoading(false)
|
|
119
142
|
options.onLoadedData?.()
|
|
120
|
-
}
|
|
143
|
+
}, [setLoading, options])
|
|
121
144
|
|
|
122
|
-
const handleError = () => {
|
|
145
|
+
const handleError = useCallback(() => {
|
|
123
146
|
const media = mediaRef.current
|
|
124
147
|
if (!media) return
|
|
125
148
|
|
|
@@ -127,19 +150,19 @@ export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
|
127
150
|
setError(errorMessage)
|
|
128
151
|
setLoading(false)
|
|
129
152
|
options.onError?.(errorMessage)
|
|
130
|
-
}
|
|
153
|
+
}, [setError, setLoading, options])
|
|
131
154
|
|
|
132
|
-
const handleEnded = () => {
|
|
155
|
+
const handleEnded = useCallback(() => {
|
|
133
156
|
setPlaying(false)
|
|
134
157
|
options.onEnded?.()
|
|
135
|
-
}
|
|
158
|
+
}, [setPlaying, options])
|
|
136
159
|
|
|
137
|
-
const handleProgress = () => {
|
|
160
|
+
const handleProgress = useCallback(() => {
|
|
138
161
|
const media = mediaRef.current
|
|
139
162
|
if (!media) return
|
|
140
163
|
|
|
141
164
|
setBuffered(media.buffered)
|
|
142
|
-
}
|
|
165
|
+
}, [setBuffered])
|
|
143
166
|
|
|
144
167
|
// Attach event listeners
|
|
145
168
|
useEffect(() => {
|
|
@@ -183,7 +206,7 @@ export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
|
183
206
|
])
|
|
184
207
|
|
|
185
208
|
// Control functions
|
|
186
|
-
const play = async () => {
|
|
209
|
+
const play = useCallback(async () => {
|
|
187
210
|
const media = mediaRef.current
|
|
188
211
|
if (!media) return
|
|
189
212
|
|
|
@@ -193,60 +216,67 @@ export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
|
193
216
|
console.error("Failed to play media:", error)
|
|
194
217
|
setError("Failed to play media")
|
|
195
218
|
}
|
|
196
|
-
}
|
|
219
|
+
}, [setError])
|
|
197
220
|
|
|
198
|
-
const pause = () => {
|
|
221
|
+
const pause = useCallback(() => {
|
|
199
222
|
const media = mediaRef.current
|
|
200
223
|
if (!media) return
|
|
201
224
|
|
|
202
225
|
media.pause()
|
|
203
|
-
}
|
|
226
|
+
}, [])
|
|
204
227
|
|
|
205
|
-
const toggle = () => {
|
|
228
|
+
const toggle = useCallback(() => {
|
|
206
229
|
if (state.isPlaying) {
|
|
207
230
|
pause()
|
|
208
231
|
} else {
|
|
209
232
|
play()
|
|
210
233
|
}
|
|
211
|
-
}
|
|
234
|
+
}, [state.isPlaying, pause, play])
|
|
212
235
|
|
|
213
|
-
const seek = (time: number) => {
|
|
236
|
+
const seek = useCallback((time: number) => {
|
|
214
237
|
const media = mediaRef.current
|
|
215
238
|
if (!media) return
|
|
216
239
|
|
|
217
|
-
|
|
218
|
-
|
|
240
|
+
const duration = Number.isFinite(media.duration) ? media.duration : 0
|
|
241
|
+
if (duration === 0) {
|
|
242
|
+
console.warn("Cannot seek: duration is not available")
|
|
243
|
+
return
|
|
244
|
+
}
|
|
219
245
|
|
|
220
|
-
|
|
246
|
+
const clampedTime = Math.max(0, Math.min(time, duration))
|
|
247
|
+
safeSetCurrentTime(media, clampedTime)
|
|
248
|
+
}, [])
|
|
249
|
+
|
|
250
|
+
const setVolumeControl = useCallback((volume: number) => {
|
|
221
251
|
const media = mediaRef.current
|
|
222
252
|
if (!media) return
|
|
223
253
|
|
|
224
254
|
const clampedVolume = Math.max(0, Math.min(1, volume))
|
|
225
255
|
media.volume = clampedVolume
|
|
226
|
-
}
|
|
256
|
+
}, [])
|
|
227
257
|
|
|
228
|
-
const mute = () => {
|
|
258
|
+
const mute = useCallback(() => {
|
|
229
259
|
const media = mediaRef.current
|
|
230
260
|
if (!media) return
|
|
231
261
|
|
|
232
262
|
media.muted = true
|
|
233
|
-
}
|
|
263
|
+
}, [])
|
|
234
264
|
|
|
235
|
-
const unmute = () => {
|
|
265
|
+
const unmute = useCallback(() => {
|
|
236
266
|
const media = mediaRef.current
|
|
237
267
|
if (!media) return
|
|
238
268
|
|
|
239
269
|
media.muted = false
|
|
240
|
-
}
|
|
270
|
+
}, [])
|
|
241
271
|
|
|
242
|
-
const toggleMute = () => {
|
|
272
|
+
const toggleMute = useCallback(() => {
|
|
243
273
|
const media = mediaRef.current
|
|
244
274
|
if (!media) return
|
|
245
275
|
|
|
246
276
|
media.muted = !media.muted
|
|
247
|
-
}
|
|
277
|
+
}, [])
|
|
248
278
|
|
|
249
|
-
const enterFullscreen = async () => {
|
|
279
|
+
const enterFullscreen = useCallback(async () => {
|
|
250
280
|
const container = containerRef.current
|
|
251
281
|
if (!container || !document.fullscreenEnabled) return
|
|
252
282
|
|
|
@@ -256,9 +286,9 @@ export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
|
256
286
|
} catch (error) {
|
|
257
287
|
console.error("Failed to enter fullscreen:", error)
|
|
258
288
|
}
|
|
259
|
-
}
|
|
289
|
+
}, [setFullscreen])
|
|
260
290
|
|
|
261
|
-
const exitFullscreen = async () => {
|
|
291
|
+
const exitFullscreen = useCallback(async () => {
|
|
262
292
|
if (!document.fullscreenElement) return
|
|
263
293
|
|
|
264
294
|
try {
|
|
@@ -267,15 +297,15 @@ export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
|
267
297
|
} catch (error) {
|
|
268
298
|
console.error("Failed to exit fullscreen:", error)
|
|
269
299
|
}
|
|
270
|
-
}
|
|
300
|
+
}, [setFullscreen])
|
|
271
301
|
|
|
272
|
-
const toggleFullscreen = () => {
|
|
302
|
+
const toggleFullscreen = useCallback(() => {
|
|
273
303
|
if (document.fullscreenElement) {
|
|
274
304
|
exitFullscreen()
|
|
275
305
|
} else {
|
|
276
306
|
enterFullscreen()
|
|
277
307
|
}
|
|
278
|
-
}
|
|
308
|
+
}, [exitFullscreen, enterFullscreen])
|
|
279
309
|
|
|
280
310
|
// Listen for fullscreen changes
|
|
281
311
|
useEffect(() => {
|
|
@@ -294,6 +324,7 @@ export function useMediaPlayer(options: UseMediaPlayerOptions) {
|
|
|
294
324
|
pause,
|
|
295
325
|
toggle,
|
|
296
326
|
seek,
|
|
327
|
+
setCurrentTime: seek,
|
|
297
328
|
setVolume: setVolumeControl,
|
|
298
329
|
mute,
|
|
299
330
|
unmute,
|
|
@@ -18,12 +18,12 @@ export function useMemoryOptimization(
|
|
|
18
18
|
monitorMemory = true
|
|
19
19
|
} = options
|
|
20
20
|
|
|
21
|
-
const cleanupTimeoutRef = useRef<
|
|
21
|
+
const cleanupTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
22
22
|
const observersRef = useRef<Set<MutationObserver>>(new Set())
|
|
23
23
|
const eventListenersRef = useRef<Map<Element, Array<{ event: string; handler: EventListener }>>>(
|
|
24
24
|
new Map()
|
|
25
25
|
)
|
|
26
|
-
const intervalRefs = useRef<Set<
|
|
26
|
+
const intervalRefs = useRef<Set<ReturnType<typeof setInterval>>>(new Set())
|
|
27
27
|
const animationFrameRefs = useRef<Set<number>>(new Set())
|
|
28
28
|
|
|
29
29
|
// Get current memory usage
|
|
@@ -85,11 +85,11 @@ export function useMemoryOptimization(
|
|
|
85
85
|
|
|
86
86
|
// Safe interval management (must be declared before use)
|
|
87
87
|
const useInterval = useCallback((callback: () => void, delay: number) => {
|
|
88
|
-
const timeoutId = setInterval(callback, delay)
|
|
88
|
+
const timeoutId = globalThis.setInterval(callback, delay)
|
|
89
89
|
intervalRefs.current.add(timeoutId)
|
|
90
90
|
|
|
91
91
|
return () => {
|
|
92
|
-
clearInterval(timeoutId)
|
|
92
|
+
globalThis.clearInterval(timeoutId)
|
|
93
93
|
intervalRefs.current.delete(timeoutId)
|
|
94
94
|
}
|
|
95
95
|
}, [])
|
|
@@ -153,23 +153,23 @@ export function useMemoryOptimization(
|
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
// Safe timeout management
|
|
156
|
-
const
|
|
157
|
-
const timeoutId = setTimeout(callback, delay)
|
|
156
|
+
const useSafeTimeout = useCallback((callback: () => void, delay: number) => {
|
|
157
|
+
const timeoutId = globalThis.setTimeout(callback, delay)
|
|
158
158
|
intervalRefs.current.add(timeoutId)
|
|
159
159
|
|
|
160
160
|
return () => {
|
|
161
|
-
clearTimeout(timeoutId)
|
|
161
|
+
globalThis.clearTimeout(timeoutId)
|
|
162
162
|
intervalRefs.current.delete(timeoutId)
|
|
163
163
|
}
|
|
164
164
|
}, [])
|
|
165
165
|
|
|
166
166
|
// Safe animation frame management
|
|
167
|
-
const
|
|
168
|
-
const frameId = requestAnimationFrame(callback)
|
|
167
|
+
const useSafeAnimationFrame = useCallback((callback: FrameRequestCallback) => {
|
|
168
|
+
const frameId = globalThis.requestAnimationFrame(callback)
|
|
169
169
|
animationFrameRefs.current.add(frameId)
|
|
170
170
|
|
|
171
171
|
return () => {
|
|
172
|
-
cancelAnimationFrame(frameId)
|
|
172
|
+
globalThis.cancelAnimationFrame(frameId)
|
|
173
173
|
animationFrameRefs.current.delete(frameId)
|
|
174
174
|
}
|
|
175
175
|
}, [])
|
|
@@ -201,9 +201,9 @@ export function useMemoryOptimization(
|
|
|
201
201
|
|
|
202
202
|
// Safe API functions
|
|
203
203
|
addEventListener,
|
|
204
|
-
setInterval,
|
|
205
|
-
setTimeout,
|
|
206
|
-
requestAnimationFrame,
|
|
204
|
+
setInterval: useInterval,
|
|
205
|
+
setTimeout: useSafeTimeout,
|
|
206
|
+
requestAnimationFrame: useSafeAnimationFrame,
|
|
207
207
|
addObserver,
|
|
208
208
|
|
|
209
209
|
// Utilities
|
|
@@ -230,7 +230,7 @@ export function useMediaResourceCleanup(
|
|
|
230
230
|
|
|
231
231
|
// Stop media playback
|
|
232
232
|
if (!media.paused) {
|
|
233
|
-
|
|
233
|
+
media.pause()
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// Clear source
|
|
@@ -286,7 +286,7 @@ export function usePerformanceMonitoring() {
|
|
|
286
286
|
|
|
287
287
|
const frameCountRef = useRef(0)
|
|
288
288
|
const lastFrameTimeRef = useRef(performance.now())
|
|
289
|
-
const longTaskObserverRef = useRef<PerformanceObserver>()
|
|
289
|
+
const longTaskObserverRef = useRef<PerformanceObserver | undefined>(undefined)
|
|
290
290
|
|
|
291
291
|
// FPS monitoring
|
|
292
292
|
useEffect(() => {
|
|
@@ -56,8 +56,7 @@ export function useSmartPreload(options: UseSmartPreloadOptions = {}): UseSmartP
|
|
|
56
56
|
const preloadPromise = (async () => {
|
|
57
57
|
try {
|
|
58
58
|
const response = await fetch(src, {
|
|
59
|
-
method: "HEAD"
|
|
60
|
-
cache: "preload"
|
|
59
|
+
method: "HEAD"
|
|
61
60
|
})
|
|
62
61
|
|
|
63
62
|
if (!response.ok) {
|