@therealtinhtute/baroiplayer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/context/media.js +2 -0
- package/dist/context/media.js.map +1 -0
- package/dist/context/media.mjs +15 -0
- package/dist/context/media.mjs.map +1 -0
- package/dist/context/store.js +2 -0
- package/dist/context/store.js.map +1 -0
- package/dist/context/store.mjs +5 -0
- package/dist/context/store.mjs.map +1 -0
- package/dist/context-BMteAJDN.js +1140 -0
- package/dist/context-BMteAJDN.js.map +1 -0
- package/dist/context-DCJ_scmO.cjs +23 -0
- package/dist/context-DCJ_scmO.cjs.map +1 -0
- package/dist/core-events-B5W_j8yU.js +480 -0
- package/dist/core-events-B5W_j8yU.js.map +1 -0
- package/dist/core-events-CN0-Vhhq.cjs +2 -0
- package/dist/core-events-CN0-Vhhq.cjs.map +1 -0
- package/dist/core-plugins-Bq9KLany.js +696 -0
- package/dist/core-plugins-Bq9KLany.js.map +1 -0
- package/dist/core-plugins-CPDSHMiQ.cjs +2 -0
- package/dist/core-plugins-CPDSHMiQ.cjs.map +1 -0
- package/dist/core.js +37 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +1217 -0
- package/dist/core.mjs.map +1 -0
- package/dist/enhanced-components-DHv0MFvG.js +1073 -0
- package/dist/enhanced-components-DHv0MFvG.js.map +1 -0
- package/dist/enhanced-components-DTIObgB7.cjs +2 -0
- package/dist/enhanced-components-DTIObgB7.cjs.map +1 -0
- package/dist/hooks-C_r8wexn.cjs +4 -0
- package/dist/hooks-C_r8wexn.cjs.map +1 -0
- package/dist/hooks-Da2wVXlF.js +1630 -0
- package/dist/hooks-Da2wVXlF.js.map +1 -0
- package/dist/index.css +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6126 -0
- package/dist/index.mjs.map +1 -0
- package/dist/minimal-components-DSq5sQpy.js +2002 -0
- package/dist/minimal-components-DSq5sQpy.js.map +1 -0
- package/dist/minimal-components-DflWiihN.cjs +2 -0
- package/dist/minimal-components-DflWiihN.cjs.map +1 -0
- package/dist/minimal-components.css +1 -0
- package/dist/mona-loading.gif +0 -0
- package/dist/performance-monitor-CUW6f5ll.cjs +2 -0
- package/dist/performance-monitor-CUW6f5ll.cjs.map +1 -0
- package/dist/performance-monitor-rMW1RgV3.js +302 -0
- package/dist/performance-monitor-rMW1RgV3.js.map +1 -0
- package/dist/utils-BnscpYYd.js +2798 -0
- package/dist/utils-BnscpYYd.js.map +1 -0
- package/dist/utils-D_czS_0K.cjs +9 -0
- package/dist/utils-D_czS_0K.cjs.map +1 -0
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +7 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +114 -0
- package/src/adapters/videojs-adapter.tsx +470 -0
- package/src/assets/mona-loading.gif +0 -0
- package/src/components/AnimateLoading.tsx +115 -0
- package/src/components/LoadingState.tsx +248 -0
- package/src/components/accessibility/keyboard-help-overlay.tsx +213 -0
- package/src/components/audio-player.tsx +85 -0
- package/src/components/basic/index.ts +37 -0
- package/src/components/enhanced/advanced-features.tsx +316 -0
- package/src/components/enhanced/debug-panel.tsx +422 -0
- package/src/components/enhanced/enhanced-controls.tsx +600 -0
- package/src/components/enhanced/enhanced-media-player.tsx +509 -0
- package/src/components/enhanced/error-handling.tsx +345 -0
- package/src/components/enhanced/index.ts +39 -0
- package/src/components/enhanced/media-player-enhanced.tsx +349 -0
- package/src/components/examples/basic-video-player.tsx +55 -0
- package/src/components/examples/custom-controls-example.tsx +63 -0
- package/src/components/examples/enhanced-controls-example.tsx +114 -0
- package/src/components/examples/enhanced-media-player-example.tsx +293 -0
- package/src/components/examples/loading-states-demo.tsx +142 -0
- package/src/components/examples/react-19-demo.tsx +381 -0
- package/src/components/examples/volume-orientation-demo.tsx +114 -0
- package/src/components/icon-demo.tsx +209 -0
- package/src/components/icons/index.tsx +166 -0
- package/src/components/icons/svg-icons/ArrowLeftIcon.tsx +18 -0
- package/src/components/icons/svg-icons/ArrowRightIcon.tsx +18 -0
- package/src/components/icons/svg-icons/AudioIcon.tsx +22 -0
- package/src/components/icons/svg-icons/BackwardIcon.tsx +12 -0
- package/src/components/icons/svg-icons/CameraIcon.tsx +19 -0
- package/src/components/icons/svg-icons/CheckIcon.tsx +19 -0
- package/src/components/icons/svg-icons/EnablePiPIcon.tsx +17 -0
- package/src/components/icons/svg-icons/ForwardIcon.tsx +12 -0
- package/src/components/icons/svg-icons/FullscreenEnterIcon.tsx +23 -0
- package/src/components/icons/svg-icons/FullscreenExitIcon.tsx +23 -0
- package/src/components/icons/svg-icons/LoadingIcon.tsx +32 -0
- package/src/components/icons/svg-icons/PauseIcon.tsx +23 -0
- package/src/components/icons/svg-icons/PlayIcon.tsx +21 -0
- package/src/components/icons/svg-icons/PlaybackSpeedIcon.tsx +23 -0
- package/src/components/icons/svg-icons/QualityIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SettingsIcon.tsx +12 -0
- package/src/components/icons/svg-icons/SliderIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SubtitleIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SubtitleOffIcon.tsx +25 -0
- package/src/components/icons/svg-icons/UnPiPIcon.tsx +17 -0
- package/src/components/icons/svg-icons/VolumeMutedIcon.tsx +16 -0
- package/src/components/icons/svg-icons/VolumeOneIcon.tsx +22 -0
- package/src/components/icons/svg-icons/VolumeThreeIcon.tsx +16 -0
- package/src/components/icons/svg-icons/VolumeTwoIcon.tsx +16 -0
- package/src/components/legacy.tsx +93 -0
- package/src/components/media-metadata-display.tsx +100 -0
- package/src/components/media-player-audio.tsx +39 -0
- package/src/components/media-player-controls.tsx +226 -0
- package/src/components/media-player-error.tsx +163 -0
- package/src/components/media-player-fullscreen.tsx +109 -0
- package/src/components/media-player-hls.tsx +111 -0
- package/src/components/media-player-loading.tsx +76 -0
- package/src/components/media-player-play.tsx +75 -0
- package/src/components/media-player-root.tsx +340 -0
- package/src/components/media-player-seek.tsx +168 -0
- package/src/components/media-player-time.tsx +99 -0
- package/src/components/media-player-tooltip.tsx +58 -0
- package/src/components/media-player-video.tsx +39 -0
- package/src/components/media-player-volume.tsx +155 -0
- package/src/components/media-player.tsx +257 -0
- package/src/components/media-suspense.tsx +362 -0
- package/src/components/player-settings-form.tsx +163 -0
- package/src/components/react-player-wrapper.tsx +651 -0
- package/src/components/simple-video-player.tsx +60 -0
- package/src/components/ui/enhanced-seek-bar.tsx +235 -0
- package/src/components/ui/fullscreen-button.tsx +60 -0
- package/src/components/ui/media-resource-preloader.tsx +395 -0
- package/src/components/ui/play-button.tsx +107 -0
- package/src/components/ui/seek-bar.tsx +161 -0
- package/src/components/ui/time-display.tsx +40 -0
- package/src/components/ui/volume-control.tsx +143 -0
- package/src/components/unified/base-media-player.tsx +246 -0
- package/src/components/unified/media-player.tsx +75 -0
- package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +561 -0
- package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +552 -0
- package/src/components/variants/minimal/MinimalControls.tsx +142 -0
- package/src/components/variants/minimal/MinimalLoading.tsx +32 -0
- package/src/components/variants/minimal/MinimalMediaPlayer.tsx +315 -0
- package/src/components/variants/minimal/MinimalOverlay.tsx +64 -0
- package/src/components/variants/minimal/MinimalPlayButton.tsx +65 -0
- package/src/components/variants/minimal/MinimalSeekBar.tsx +150 -0
- package/src/components/variants/minimal/MinimalVolumeControl.tsx +80 -0
- package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +210 -0
- package/src/components/variants/minimal/components/CenterPlayButton.tsx +22 -0
- package/src/components/variants/minimal/components/ControlBar.tsx +146 -0
- package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +361 -0
- package/src/components/variants/minimal/components/PlaybackControls.tsx +51 -0
- package/src/components/variants/minimal/components/RightControls.tsx +173 -0
- package/src/components/variants/minimal/components/SeekBar.tsx +131 -0
- package/src/components/variants/minimal/components/SettingsDropdown.tsx +70 -0
- package/src/components/variants/minimal/components/SubtitleDisplay.tsx +21 -0
- package/src/components/variants/minimal/components/TimeDisplay.tsx +18 -0
- package/src/components/variants/minimal/components/VideoElement.tsx +40 -0
- package/src/components/variants/minimal/components/VolumeControl.tsx +217 -0
- package/src/components/variants/minimal/index.ts +23 -0
- package/src/components/video-player.tsx +100 -0
- package/src/context/error-handling.ts +394 -0
- package/src/context/media-context.tsx +208 -0
- package/src/context/media-provider.tsx +265 -0
- package/src/context/media-store.ts +379 -0
- package/src/context/persistence.ts +453 -0
- package/src/core/events/event-system.ts +442 -0
- package/src/core/events/hooks.ts +302 -0
- package/src/core/events/index.ts +22 -0
- package/src/core/events/middleware.ts +269 -0
- package/src/core/index.ts +22 -0
- package/src/core/optimization/bundle-optimizer.tsx +308 -0
- package/src/core/performance/cicd.ts +96 -0
- package/src/core/performance/index.ts +38 -0
- package/src/core/performance/media-loading-optimizer.ts +538 -0
- package/src/core/performance/performance-budgets.ts +432 -0
- package/src/core/performance/performance-monitor.tsx +476 -0
- package/src/core/performance/performance-testing.ts +459 -0
- package/src/core/plugins/base-plugin.ts +224 -0
- package/src/core/plugins/enhanced-plugin-system.ts +411 -0
- package/src/core/plugins/examples/analytics-plugin.ts +242 -0
- package/src/core/plugins/index.ts +39 -0
- package/src/core/plugins/registry.ts +327 -0
- package/src/core/plugins/types.ts +159 -0
- package/src/core/plugins/utils.ts +316 -0
- package/src/core/progressive-loading.tsx +322 -0
- package/src/core/providers/enhanced-hls-provider.ts +339 -0
- package/src/core/providers/service-provider.tsx +168 -0
- package/src/core/utils.tsx +38 -0
- package/src/features/media-session/media-session-manager.ts +429 -0
- package/src/features/picture-in-picture/pip-manager.ts +429 -0
- package/src/features/playlist/playlist-manager.ts +606 -0
- package/src/features/subtitles/subtitle-manager.ts +441 -0
- package/src/features/subtitles/subtitle-parser.ts +280 -0
- package/src/features/thumbnails/thumbnail-generator.ts +519 -0
- package/src/hooks/accessibility/index.ts +27 -0
- package/src/hooks/accessibility/use-accessibility-preferences.ts +188 -0
- package/src/hooks/accessibility/use-focus-management.ts +176 -0
- package/src/hooks/accessibility/use-keyboard-shortcuts.ts +144 -0
- package/src/hooks/accessibility/use-screen-reader.ts +182 -0
- package/src/hooks/core/index.ts +47 -0
- package/src/hooks/legacy.ts +27 -0
- package/src/hooks/use-enhanced-media-player.ts +468 -0
- package/src/hooks/use-intelligent-preloading.ts +358 -0
- package/src/hooks/use-keyboard-controls.ts +115 -0
- package/src/hooks/use-media-actions.ts +175 -0
- package/src/hooks/use-media-metadata.ts +89 -0
- package/src/hooks/use-media-optimistic.ts +270 -0
- package/src/hooks/use-media-player.ts +312 -0
- package/src/hooks/use-media-state.ts +190 -0
- package/src/hooks/use-memory-optimization.ts +358 -0
- package/src/hooks/use-optimistic-controls.ts +92 -0
- package/src/hooks/use-optimized-media.ts +263 -0
- package/src/hooks/use-touch-gestures.ts +417 -0
- package/src/hooks/useEnhancedSubtitles.ts +258 -0
- package/src/hooks/useKeyboardShortcuts.ts +74 -0
- package/src/hooks/useMediaControls.ts +52 -0
- package/src/hooks/useScreenshot.ts +57 -0
- package/src/hooks/useSubtitles.ts +91 -0
- package/src/hooks/useVideoPlayer.ts +232 -0
- package/src/icons/components/FullscreenIcon.tsx +37 -0
- package/src/icons/components/SettingsIcon.tsx +38 -0
- package/src/icons/components/animated/LoadingIcon.tsx +43 -0
- package/src/icons/components/animated/PlayPauseIcon.tsx +50 -0
- package/src/icons/components/media/PauseIcon.tsx +39 -0
- package/src/icons/components/media/PlayIcon.tsx +38 -0
- package/src/icons/components/volume/VolumeIcon.tsx +39 -0
- package/src/icons/components/volume/VolumeOffIcon.tsx +39 -0
- package/src/icons/index.ts +56 -0
- package/src/icons/provider.tsx +64 -0
- package/src/icons/types.ts +40 -0
- package/src/index-enhanced.ts +35 -0
- package/src/index-legacy.ts +326 -0
- package/src/index.css +102 -0
- package/src/index.ts +216 -0
- package/src/lib/deprecation.ts +51 -0
- package/src/lib/format-time.ts +37 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/globals.css +462 -0
- package/src/styles/minimal-player.css +589 -0
- package/src/styles/pixel-art.css +46 -0
- package/src/styles/videojs.css +100 -0
- package/src/test/setup.ts +79 -0
- package/src/types/events.ts +47 -0
- package/src/types/formats.ts +95 -0
- package/src/types/index.ts +190 -0
- package/src/types/videojs-plugins.d.ts +14 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intelligent Preloading Hook
|
|
3
|
+
* Combines user behavior analysis with network conditions for optimal preloading
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useRef, useCallback, useMemo } from 'react';
|
|
7
|
+
import { MediaSource } from '../types';
|
|
8
|
+
|
|
9
|
+
interface IntelligentPreloadingOptions {
|
|
10
|
+
/** Enable user behavior tracking */
|
|
11
|
+
trackUserBehavior?: boolean;
|
|
12
|
+
/** Adapt to network conditions */
|
|
13
|
+
adaptToNetwork?: boolean;
|
|
14
|
+
/** Preload strategy */
|
|
15
|
+
strategy?: 'conservative' | 'balanced' | 'aggressive';
|
|
16
|
+
/** Maximum number of items to preload */
|
|
17
|
+
maxPreloadItems?: number;
|
|
18
|
+
/** Preload radius for playlist items */
|
|
19
|
+
preloadRadius?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UserBehaviorData {
|
|
23
|
+
mouseMovements: number;
|
|
24
|
+
scrollEvents: number;
|
|
25
|
+
keyPresses: number;
|
|
26
|
+
focusEvents: number;
|
|
27
|
+
timeOnPage: number;
|
|
28
|
+
engagementScore: number;
|
|
29
|
+
lastInteraction: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PreloadingDecision {
|
|
33
|
+
shouldPreload: boolean;
|
|
34
|
+
priority: 'high' | 'medium' | 'low';
|
|
35
|
+
reason: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const useIntelligentPreloading = (
|
|
39
|
+
sources: MediaSource[],
|
|
40
|
+
options: IntelligentPreloadingOptions = {}
|
|
41
|
+
) => {
|
|
42
|
+
const {
|
|
43
|
+
trackUserBehavior = true,
|
|
44
|
+
adaptToNetwork = true,
|
|
45
|
+
strategy = 'balanced',
|
|
46
|
+
maxPreloadItems = 3,
|
|
47
|
+
preloadRadius = 2,
|
|
48
|
+
} = options;
|
|
49
|
+
|
|
50
|
+
const userBehaviorRef = useRef<UserBehaviorData>({
|
|
51
|
+
mouseMovements: 0,
|
|
52
|
+
scrollEvents: 0,
|
|
53
|
+
keyPresses: 0,
|
|
54
|
+
focusEvents: 0,
|
|
55
|
+
timeOnPage: 0,
|
|
56
|
+
engagementScore: 0,
|
|
57
|
+
lastInteraction: Date.now(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const networkInfoRef = useRef<NetworkInformation | null>(null);
|
|
61
|
+
const preloadedItemsRef = useRef<Set<string>>(new Set());
|
|
62
|
+
const preloadQueueRef = useRef<Array<{ source: MediaSource; priority: number }>>([]);
|
|
63
|
+
|
|
64
|
+
// Calculate user engagement score
|
|
65
|
+
const calculateEngagementScore = useCallback((): number => {
|
|
66
|
+
const behavior = userBehaviorRef.current;
|
|
67
|
+
const timeFactor = Math.min(behavior.timeOnPage / 60000, 1); // Cap at 1 minute
|
|
68
|
+
const interactionFactor = Math.min(
|
|
69
|
+
(behavior.mouseMovements + behavior.scrollEvents + behavior.keyPresses) / 100,
|
|
70
|
+
1
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return (timeFactor * 0.4 + interactionFactor * 0.6) * 100;
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
// Update user behavior tracking
|
|
77
|
+
const updateUserBehavior = useCallback((type: keyof UserBehaviorData, increment = 1) => {
|
|
78
|
+
const behavior = userBehaviorRef.current;
|
|
79
|
+
behavior[type] = typeof behavior[type] === 'number' ? behavior[type] + increment : Date.now();
|
|
80
|
+
behavior.engagementScore = calculateEngagementScore();
|
|
81
|
+
behavior.lastInteraction = Date.now();
|
|
82
|
+
}, [calculateEngagementScore]);
|
|
83
|
+
|
|
84
|
+
// Get current network conditions
|
|
85
|
+
const getNetworkConditions = useCallback(() => {
|
|
86
|
+
if (!networkInfoRef.current || !adaptToNetwork) {
|
|
87
|
+
return { type: 'unknown', downlink: 10, effectiveType: '4g', saveData: false };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const network = networkInfoRef.current;
|
|
91
|
+
return {
|
|
92
|
+
type: network.type,
|
|
93
|
+
downlink: network.downlink || 10,
|
|
94
|
+
effectiveType: network.effectiveType || '4g',
|
|
95
|
+
saveData: network.saveData || false,
|
|
96
|
+
rtt: network.rtt || 100,
|
|
97
|
+
};
|
|
98
|
+
}, [adaptToNetwork]);
|
|
99
|
+
|
|
100
|
+
// Determine if a source should be preloaded
|
|
101
|
+
const shouldPreloadSource = useCallback((
|
|
102
|
+
source: MediaSource,
|
|
103
|
+
index: number,
|
|
104
|
+
currentIndex: number = 0
|
|
105
|
+
): PreloadingDecision => {
|
|
106
|
+
// Skip if already preloaded
|
|
107
|
+
if (preloadedItemsRef.current.has(source.src)) {
|
|
108
|
+
return { shouldPreload: false, priority: 'low', reason: 'Already preloaded' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if within preload radius
|
|
112
|
+
const distance = Math.abs(index - currentIndex);
|
|
113
|
+
if (distance > preloadRadius) {
|
|
114
|
+
return { shouldPreload: false, priority: 'low', reason: 'Outside preload radius' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const behavior = userBehaviorRef.current;
|
|
118
|
+
const network = getNetworkConditions();
|
|
119
|
+
const engagementScore = behavior.engagementScore;
|
|
120
|
+
const timeSinceInteraction = Date.now() - behavior.lastInteraction;
|
|
121
|
+
|
|
122
|
+
// Network-based decisions
|
|
123
|
+
if (network.saveData) {
|
|
124
|
+
return { shouldPreload: false, priority: 'low', reason: 'Data saver mode' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Strategy-based filtering
|
|
128
|
+
let shouldPreload = false;
|
|
129
|
+
let priority: 'high' | 'medium' | 'low' = 'medium';
|
|
130
|
+
let reason = '';
|
|
131
|
+
|
|
132
|
+
switch (strategy) {
|
|
133
|
+
case 'conservative':
|
|
134
|
+
shouldPreload = engagementScore > 70 && network.effectiveType !== '2g' && distance === 1;
|
|
135
|
+
priority = shouldPreload ? 'medium' : 'low';
|
|
136
|
+
reason = shouldPreload ? 'High engagement and optimal network' : 'Conservative strategy';
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'aggressive':
|
|
140
|
+
shouldPreload = engagementScore > 30 && network.effectiveType !== 'slow-2g';
|
|
141
|
+
priority = engagementScore > 70 ? 'high' : engagementScore > 50 ? 'medium' : 'low';
|
|
142
|
+
reason = shouldPreload ? 'Aggressive preloading' : 'Low engagement';
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case 'balanced':
|
|
146
|
+
default:
|
|
147
|
+
const networkMultiplier = network.effectiveType === '4g' ? 1 :
|
|
148
|
+
network.effectiveType === '3g' ? 0.7 : 0.3;
|
|
149
|
+
const adjustedScore = engagementScore * networkMultiplier;
|
|
150
|
+
|
|
151
|
+
shouldPreload = adjustedScore > 40 && distance <= 2;
|
|
152
|
+
priority = adjustedScore > 70 ? 'high' : adjustedScore > 55 ? 'medium' : 'low';
|
|
153
|
+
reason = shouldPreload ? `Balanced: score ${adjustedScore.toFixed(1)}` : 'Low adjusted score';
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Additional heuristics
|
|
158
|
+
if (shouldPreload) {
|
|
159
|
+
// Prioritize HLS and adaptive streaming
|
|
160
|
+
if (source.type === 'hls' || source.type === 'm3u8') {
|
|
161
|
+
priority = priority === 'low' ? 'medium' : 'high';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Boost priority for recent user interaction
|
|
165
|
+
if (timeSinceInteraction < 5000) { // Within 5 seconds
|
|
166
|
+
priority = 'high';
|
|
167
|
+
reason += ' + Recent interaction';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Consider content type (video gets higher priority than audio)
|
|
171
|
+
if (source.type === 'mp4' || source.type === 'webm') {
|
|
172
|
+
priority = priority === 'low' ? 'medium' : priority;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { shouldPreload, priority, reason };
|
|
177
|
+
}, [strategy, preloadRadius, getNetworkConditions, calculateEngagementScore]);
|
|
178
|
+
|
|
179
|
+
// Preload a specific source
|
|
180
|
+
const preloadSource = useCallback(async (source: MediaSource, priority: number) => {
|
|
181
|
+
try {
|
|
182
|
+
// Create a preload link element
|
|
183
|
+
const link = document.createElement('link');
|
|
184
|
+
link.rel = 'preload';
|
|
185
|
+
link.href = source.src;
|
|
186
|
+
|
|
187
|
+
// Determine the correct 'as' attribute
|
|
188
|
+
switch (source.type) {
|
|
189
|
+
case 'hls':
|
|
190
|
+
case 'm3u8':
|
|
191
|
+
link.as = 'fetch';
|
|
192
|
+
break;
|
|
193
|
+
case 'mp4':
|
|
194
|
+
case 'webm':
|
|
195
|
+
link.as = 'video';
|
|
196
|
+
break;
|
|
197
|
+
case 'mp3':
|
|
198
|
+
case 'wav':
|
|
199
|
+
case 'ogg':
|
|
200
|
+
link.as = 'audio';
|
|
201
|
+
break;
|
|
202
|
+
default:
|
|
203
|
+
link.as = 'fetch';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
link.crossOrigin = 'anonymous';
|
|
207
|
+
|
|
208
|
+
// Add to document head
|
|
209
|
+
document.head.appendChild(link);
|
|
210
|
+
|
|
211
|
+
// Mark as preloaded
|
|
212
|
+
preloadedItemsRef.current.add(source.src);
|
|
213
|
+
|
|
214
|
+
console.debug(`Preloaded: ${source.src} (${priority})`);
|
|
215
|
+
|
|
216
|
+
// Cleanup after 30 seconds to prevent memory issues
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
if (document.head.contains(link)) {
|
|
219
|
+
document.head.removeChild(link);
|
|
220
|
+
}
|
|
221
|
+
}, 30000);
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.warn('Failed to preload source:', source.src, error);
|
|
225
|
+
}
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
// Process preload queue
|
|
229
|
+
const processPreloadQueue = useCallback(() => {
|
|
230
|
+
if (preloadQueueRef.current.length === 0) return;
|
|
231
|
+
|
|
232
|
+
// Sort by priority (higher number = higher priority)
|
|
233
|
+
const sortedQueue = [...preloadQueueRef.current].sort((a, b) => b.priority - a.priority);
|
|
234
|
+
|
|
235
|
+
// Take top items based on maxPreloadItems
|
|
236
|
+
const itemsToPreload = sortedQueue.slice(0, maxPreloadItems);
|
|
237
|
+
|
|
238
|
+
// Clear queue
|
|
239
|
+
preloadQueueRef.current = [];
|
|
240
|
+
|
|
241
|
+
// Preload items
|
|
242
|
+
itemsToPreload.forEach(({ source, priority }) => {
|
|
243
|
+
preloadSource(source, priority);
|
|
244
|
+
});
|
|
245
|
+
}, [maxPreloadItems, preloadSource]);
|
|
246
|
+
|
|
247
|
+
// Update preloading decisions
|
|
248
|
+
const updatePreloading = useCallback((currentIndex: number = 0) => {
|
|
249
|
+
preloadQueueRef.current = [];
|
|
250
|
+
|
|
251
|
+
sources.forEach((source, index) => {
|
|
252
|
+
const decision = shouldPreloadSource(source, index, currentIndex);
|
|
253
|
+
|
|
254
|
+
if (decision.shouldPreload) {
|
|
255
|
+
const priority = decision.priority === 'high' ? 3 :
|
|
256
|
+
decision.priority === 'medium' ? 2 : 1;
|
|
257
|
+
|
|
258
|
+
preloadQueueRef.current.push({ source, priority });
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
processPreloadQueue();
|
|
263
|
+
}, [sources, shouldPreloadSource, processPreloadQueue]);
|
|
264
|
+
|
|
265
|
+
// Set up user behavior tracking
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (!trackUserBehavior) return;
|
|
268
|
+
|
|
269
|
+
const handleMouseMove = () => updateUserBehavior('mouseMovements');
|
|
270
|
+
const handleScroll = () => updateUserBehavior('scrollEvents');
|
|
271
|
+
const handleKeyPress = () => updateUserBehavior('keyPresses');
|
|
272
|
+
const handleFocus = () => updateUserBehavior('focusEvents');
|
|
273
|
+
|
|
274
|
+
// Add event listeners
|
|
275
|
+
document.addEventListener('mousemove', handleMouseMove, { passive: true });
|
|
276
|
+
document.addEventListener('scroll', handleScroll, { passive: true });
|
|
277
|
+
document.addEventListener('keydown', handleKeyPress, { passive: true });
|
|
278
|
+
document.addEventListener('focusin', handleFocus, { passive: true });
|
|
279
|
+
|
|
280
|
+
// Update time on page every second
|
|
281
|
+
const timeInterval = setInterval(() => {
|
|
282
|
+
updateUserBehavior('timeOnPage', 1);
|
|
283
|
+
}, 1000);
|
|
284
|
+
|
|
285
|
+
return () => {
|
|
286
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
287
|
+
document.removeEventListener('scroll', handleScroll);
|
|
288
|
+
document.removeEventListener('keydown', handleKeyPress);
|
|
289
|
+
document.removeEventListener('focusin', handleFocus);
|
|
290
|
+
clearInterval(timeInterval);
|
|
291
|
+
};
|
|
292
|
+
}, [trackUserBehavior, updateUserBehavior]);
|
|
293
|
+
|
|
294
|
+
// Set up network monitoring
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (!adaptToNetwork || !('connection' in navigator)) return;
|
|
297
|
+
|
|
298
|
+
const network = (navigator as any).connection;
|
|
299
|
+
networkInfoRef.current = network;
|
|
300
|
+
|
|
301
|
+
const handleNetworkChange = () => {
|
|
302
|
+
// Re-evaluate preloading when network conditions change
|
|
303
|
+
updatePreloading();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
network.addEventListener('change', handleNetworkChange);
|
|
307
|
+
|
|
308
|
+
return () => {
|
|
309
|
+
network.removeEventListener('change', handleNetworkChange);
|
|
310
|
+
};
|
|
311
|
+
}, [adaptToNetwork, updatePreloading]);
|
|
312
|
+
|
|
313
|
+
// Initial preloading
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
updatePreloading();
|
|
316
|
+
}, [updatePreloading]);
|
|
317
|
+
|
|
318
|
+
// Memoize return values
|
|
319
|
+
const controls = useMemo(() => ({
|
|
320
|
+
/**
|
|
321
|
+
* Manually trigger preloading for specific index
|
|
322
|
+
*/
|
|
323
|
+
preloadAtIndex: (index: number) => {
|
|
324
|
+
updatePreloading(index);
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get current user behavior data
|
|
329
|
+
*/
|
|
330
|
+
getUserBehavior: () => ({ ...userBehaviorRef.current }),
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get current network conditions
|
|
334
|
+
*/
|
|
335
|
+
getNetworkConditions: getNetworkConditions,
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Clear preloaded items cache
|
|
339
|
+
*/
|
|
340
|
+
clearCache: () => {
|
|
341
|
+
preloadedItemsRef.current.clear();
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get preloading statistics
|
|
346
|
+
*/
|
|
347
|
+
getStats: () => ({
|
|
348
|
+
preloadedItems: preloadedItemsRef.current.size,
|
|
349
|
+
engagementScore: userBehaviorRef.current.engagementScore,
|
|
350
|
+
queueLength: preloadQueueRef.current.length,
|
|
351
|
+
maxPreloadItems,
|
|
352
|
+
}),
|
|
353
|
+
}), [updatePreloading, getNetworkConditions, maxPreloadItems]);
|
|
354
|
+
|
|
355
|
+
return controls;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export default useIntelligentPreloading;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect } from "react"
|
|
4
|
+
import type { MediaPlayerControls, MediaPlayerState } from "../types/index"
|
|
5
|
+
|
|
6
|
+
interface UseKeyboardControlsOptions {
|
|
7
|
+
enabled?: boolean
|
|
8
|
+
state: MediaPlayerState
|
|
9
|
+
controls: MediaPlayerControls
|
|
10
|
+
seekStep?: number // seconds to seek forward/backward
|
|
11
|
+
volumeStep?: number // volume step (0-1)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useKeyboardControls({
|
|
15
|
+
enabled = true,
|
|
16
|
+
state,
|
|
17
|
+
controls,
|
|
18
|
+
seekStep = 10,
|
|
19
|
+
volumeStep = 0.1
|
|
20
|
+
}: UseKeyboardControlsOptions) {
|
|
21
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
22
|
+
if (!enabled) return
|
|
23
|
+
|
|
24
|
+
// Don't handle keyboard events if user is typing in an input
|
|
25
|
+
const target = event.target as HTMLElement
|
|
26
|
+
if (
|
|
27
|
+
target.tagName === "INPUT" ||
|
|
28
|
+
target.tagName === "TEXTAREA" ||
|
|
29
|
+
target.contentEditable === "true"
|
|
30
|
+
) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
switch (event.code) {
|
|
35
|
+
case "Space":
|
|
36
|
+
case "KeyK":
|
|
37
|
+
event.preventDefault()
|
|
38
|
+
controls.toggle()
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
case "KeyM":
|
|
42
|
+
event.preventDefault()
|
|
43
|
+
controls.toggleMute()
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
case "KeyF":
|
|
47
|
+
event.preventDefault()
|
|
48
|
+
controls.toggleFullscreen()
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
case "ArrowLeft":
|
|
52
|
+
event.preventDefault()
|
|
53
|
+
controls.seek(Math.max(0, state.currentTime - seekStep))
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
case "ArrowRight":
|
|
57
|
+
event.preventDefault()
|
|
58
|
+
controls.seek(Math.min(state.duration, state.currentTime + seekStep))
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
case "ArrowUp":
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
controls.setVolume(Math.min(1, state.volume + volumeStep))
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
case "ArrowDown":
|
|
67
|
+
event.preventDefault()
|
|
68
|
+
controls.setVolume(Math.max(0, state.volume - volumeStep))
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
case "Digit0":
|
|
72
|
+
case "Digit1":
|
|
73
|
+
case "Digit2":
|
|
74
|
+
case "Digit3":
|
|
75
|
+
case "Digit4":
|
|
76
|
+
case "Digit5":
|
|
77
|
+
case "Digit6":
|
|
78
|
+
case "Digit7":
|
|
79
|
+
case "Digit8":
|
|
80
|
+
case "Digit9":
|
|
81
|
+
event.preventDefault()
|
|
82
|
+
const digit = parseInt(event.code.replace("Digit", ""), 10)
|
|
83
|
+
const percentage = digit / 10
|
|
84
|
+
controls.seek(state.duration * percentage)
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
case "Home":
|
|
88
|
+
event.preventDefault()
|
|
89
|
+
controls.seek(0)
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
case "End":
|
|
93
|
+
event.preventDefault()
|
|
94
|
+
controls.seek(state.duration)
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
default:
|
|
98
|
+
// Don't prevent default for unhandled keys
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!enabled) return
|
|
105
|
+
|
|
106
|
+
document.addEventListener("keydown", handleKeyDown)
|
|
107
|
+
return () => {
|
|
108
|
+
document.removeEventListener("keydown", handleKeyDown)
|
|
109
|
+
}
|
|
110
|
+
}, [enabled, handleKeyDown])
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
handleKeyDown
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import type { MediaActionType, MediaState } from "../context/media-context"
|
|
5
|
+
import type { OptimisticMediaAction, MediaActionState, EnhancedMediaStore } from "../context/media-store"
|
|
6
|
+
import { MediaActionTypes } from "../context/media-context"
|
|
7
|
+
|
|
8
|
+
// Action payload types
|
|
9
|
+
export interface MediaActionPayloads {
|
|
10
|
+
[MediaActionTypes.PLAY]: void
|
|
11
|
+
[MediaActionTypes.PAUSE]: void
|
|
12
|
+
[MediaActionTypes.SEEK]: number
|
|
13
|
+
[MediaActionTypes.SET_VOLUME]: number
|
|
14
|
+
[MediaActionTypes.MUTE]: void
|
|
15
|
+
[MediaActionTypes.UNMUTE]: void
|
|
16
|
+
[MediaActionTypes.TOGGLE_MUTE]: void
|
|
17
|
+
[MediaActionTypes.SET_DURATION]: number
|
|
18
|
+
[MediaActionTypes.SET_CURRENT_TIME]: number
|
|
19
|
+
[MediaActionTypes.SET_BUFFERED]: TimeRanges | null
|
|
20
|
+
[MediaActionTypes.SET_LOADING]: boolean
|
|
21
|
+
[MediaActionTypes.SET_ERROR]: string | null
|
|
22
|
+
[MediaActionTypes.ENTER_FULLSCREEN]: void
|
|
23
|
+
[MediaActionTypes.EXIT_FULLSCREEN]: void
|
|
24
|
+
[MediaActionTypes.ENTER_PIP]: void
|
|
25
|
+
[MediaActionTypes.EXIT_PIP]: void
|
|
26
|
+
[MediaActionTypes.SET_PLAYBACK_RATE]: number
|
|
27
|
+
[MediaActionTypes.SET_SUBTITLE_TRACK]: TextTrack | null
|
|
28
|
+
[MediaActionTypes.SET_RENDITION]: string | null
|
|
29
|
+
[MediaActionTypes.TOGGLE_SUBTITLES]: void
|
|
30
|
+
[MediaActionTypes.PREVIEW_TIME]: number | null
|
|
31
|
+
[MediaActionTypes.CLEAR_PREVIEW]: void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Enhanced action creator that returns a Promise
|
|
35
|
+
type OptimisticActionCreator<T extends keyof MediaActionPayloads> = MediaActionPayloads[T] extends void
|
|
36
|
+
? (optimistic?: boolean) => Promise<void>
|
|
37
|
+
: (payload: MediaActionPayloads[T], optimistic?: boolean) => Promise<void>
|
|
38
|
+
|
|
39
|
+
// Action creator factory
|
|
40
|
+
function createOptimisticAction<T extends keyof MediaActionPayloads>(
|
|
41
|
+
type: T,
|
|
42
|
+
store: EnhancedMediaStore
|
|
43
|
+
): OptimisticActionCreator<T> {
|
|
44
|
+
return ((payload?: any, optimistic = false) => {
|
|
45
|
+
const action: OptimisticMediaAction = {
|
|
46
|
+
type,
|
|
47
|
+
detail: payload,
|
|
48
|
+
optimistic,
|
|
49
|
+
timestamp: Date.now()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (optimistic) {
|
|
53
|
+
return store.dispatchOptimistic(action)
|
|
54
|
+
} else {
|
|
55
|
+
store.dispatch(action)
|
|
56
|
+
return Promise.resolve()
|
|
57
|
+
}
|
|
58
|
+
}) as OptimisticActionCreator<T>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// React 19 useActionState integration
|
|
62
|
+
export function useMediaActionState(store: EnhancedMediaStore) {
|
|
63
|
+
const [actionState, setActionState] = React.useState<MediaActionState>(store.getActionState())
|
|
64
|
+
|
|
65
|
+
// Subscribe to action state changes
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
const unsubscribe = store.subscribe(() => {
|
|
68
|
+
const newActionState = store.getActionState()
|
|
69
|
+
setActionState(current =>
|
|
70
|
+
JSON.stringify(current) !== JSON.stringify(newActionState)
|
|
71
|
+
? newActionState
|
|
72
|
+
: current
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
return unsubscribe
|
|
76
|
+
}, [store])
|
|
77
|
+
|
|
78
|
+
return actionState
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Media actions hook with React 19 optimistic updates
|
|
82
|
+
export function useMediaActions(store: EnhancedMediaStore) {
|
|
83
|
+
// Create action creators with optimistic support
|
|
84
|
+
const actions = React.useMemo(() => ({
|
|
85
|
+
// Playback actions
|
|
86
|
+
play: createOptimisticAction(MediaActionTypes.PLAY, store),
|
|
87
|
+
pause: createOptimisticAction(MediaActionTypes.PAUSE, store),
|
|
88
|
+
seek: createOptimisticAction(MediaActionTypes.SEEK, store),
|
|
89
|
+
|
|
90
|
+
// Volume actions
|
|
91
|
+
setVolume: createOptimisticAction(MediaActionTypes.SET_VOLUME, store),
|
|
92
|
+
mute: createOptimisticAction(MediaActionTypes.MUTE, store),
|
|
93
|
+
unmute: createOptimisticAction(MediaActionTypes.UNMUTE, store),
|
|
94
|
+
toggleMute: createOptimisticAction(MediaActionTypes.TOGGLE_MUTE, store),
|
|
95
|
+
|
|
96
|
+
// Media state actions
|
|
97
|
+
setDuration: createOptimisticAction(MediaActionTypes.SET_DURATION, store),
|
|
98
|
+
setCurrentTime: createOptimisticAction(MediaActionTypes.SET_CURRENT_TIME, store),
|
|
99
|
+
setBuffered: createOptimisticAction(MediaActionTypes.SET_BUFFERED, store),
|
|
100
|
+
setLoading: createOptimisticAction(MediaActionTypes.SET_LOADING, store),
|
|
101
|
+
setError: createOptimisticAction(MediaActionTypes.SET_ERROR, store),
|
|
102
|
+
|
|
103
|
+
// Display actions
|
|
104
|
+
enterFullscreen: createOptimisticAction(MediaActionTypes.ENTER_FULLSCREEN, store),
|
|
105
|
+
exitFullscreen: createOptimisticAction(MediaActionTypes.EXIT_FULLSCREEN, store),
|
|
106
|
+
enterPiP: createOptimisticAction(MediaActionTypes.ENTER_PIP, store),
|
|
107
|
+
exitPiP: createOptimisticAction(MediaActionTypes.EXIT_PIP, store),
|
|
108
|
+
|
|
109
|
+
// Advanced actions
|
|
110
|
+
setPlaybackRate: createOptimisticAction(MediaActionTypes.SET_PLAYBACK_RATE, store),
|
|
111
|
+
setSubtitleTrack: createOptimisticAction(MediaActionTypes.SET_SUBTITLE_TRACK, store),
|
|
112
|
+
setRendition: createOptimisticAction(MediaActionTypes.SET_RENDITION, store),
|
|
113
|
+
toggleSubtitles: createOptimisticAction(MediaActionTypes.TOGGLE_SUBTITLES, store),
|
|
114
|
+
|
|
115
|
+
// Preview actions
|
|
116
|
+
previewTime: createOptimisticAction(MediaActionTypes.PREVIEW_TIME, store),
|
|
117
|
+
clearPreview: createOptimisticAction(MediaActionTypes.CLEAR_PREVIEW, store)
|
|
118
|
+
}), [store])
|
|
119
|
+
|
|
120
|
+
// Get action state for loading/error handling
|
|
121
|
+
const actionState = useMediaActionState(store)
|
|
122
|
+
|
|
123
|
+
// Convenience methods that commonly use optimistic updates
|
|
124
|
+
const optimisticActions = React.useMemo(() => ({
|
|
125
|
+
// These actions benefit from immediate UI feedback
|
|
126
|
+
togglePlayPause: async (isPlaying: boolean) => {
|
|
127
|
+
if (isPlaying) {
|
|
128
|
+
await actions.pause(true) // optimistic
|
|
129
|
+
} else {
|
|
130
|
+
await actions.play(true) // optimistic
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
seekWithPreview: async (time: number) => {
|
|
135
|
+
// Immediate preview update, then actual seek
|
|
136
|
+
await actions.previewTime(time, true)
|
|
137
|
+
await actions.seek(time, true)
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
adjustVolume: async (delta: number, currentVolume: number) => {
|
|
141
|
+
const newVolume = Math.max(0, Math.min(1, currentVolume + delta))
|
|
142
|
+
await actions.setVolume(newVolume, true)
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
toggleFullscreenOptimistic: async (isFullscreen: boolean) => {
|
|
146
|
+
if (isFullscreen) {
|
|
147
|
+
await actions.exitFullscreen(true)
|
|
148
|
+
} else {
|
|
149
|
+
await actions.enterFullscreen(true)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}), [actions])
|
|
153
|
+
|
|
154
|
+
// Retry failed action
|
|
155
|
+
const retryLastAction = React.useCallback(async () => {
|
|
156
|
+
const { lastAction } = actionState
|
|
157
|
+
if (lastAction && actionState.error) {
|
|
158
|
+
await store.dispatchOptimistic({
|
|
159
|
+
...lastAction,
|
|
160
|
+
retryCount: (lastAction.retryCount || 0) + 1
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
}, [store, actionState])
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...actions,
|
|
167
|
+
...optimisticActions,
|
|
168
|
+
actionState,
|
|
169
|
+
retryLastAction,
|
|
170
|
+
isLoading: actionState.pending,
|
|
171
|
+
hasError: !!actionState.error,
|
|
172
|
+
errorMessage: actionState.error,
|
|
173
|
+
retryCount: actionState.retryCount
|
|
174
|
+
}
|
|
175
|
+
}
|