# Video Hooks Module ## Overview The Video Hooks module provides **8 primitive hooks** for video playback management. These hooks follow a composition pattern - smaller primitive hooks are combined to build complex video playback scenarios. **Location:** `frontend/src/hooks/video/` **Total Files:** 9 TypeScript files (8 hooks + 1 index file) **Architecture Pattern:** Primitive hooks that can be composed for different use cases: - **Transition video playback** - `useTransitionPlayback` uses `useVideoBlobUrl`, `useVideoTimeouts` - **UI element video player** - `useVideoPlayer` uses `useVideoPlaybackCore` - **Background video** - `useBackgroundVideoPlayback` uses primitives directly --- ## Hook Hierarchy ``` ┌─────────────────────────────┐ │ Application Hooks │ │ (useTransitionPlayback, │ │ useBackgroundVideoPlayback)│ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ Composite Hook │ │ useVideoPlaybackCore │ │ useVideoPlayer │ └──────────────┬──────────────┘ │ ┌──────────────────────────┼──────────────────────────┐ │ │ │ ┌───────▼───────┐ ┌─────────────▼─────────────┐ ┌─────────▼─────────┐ │ useVideoBlobUrl│ │ useVideoBufferingState │ │ useVideoFirstFrame│ │ URL resolution │ │ Buffering detection │ │ First frame detect│ └───────────────┘ └───────────────────────────┘ └───────────────────┘ │ │ │ ┌───────▼───────┐ ┌─────────────▼─────────────┐ ┌─────────▼─────────┐ │useVideoEvent │ │ useVideoErrorRecovery │ │ useVideoTimeouts │ │Manager │ │ Error handling + retry │ │ Timeout management│ └───────────────┘ └───────────────────────────┘ └───────────────────┘ ``` --- ## Primitive Hooks ### useVideoEventManager **File:** `useVideoEventManager.ts` (~90 LOC) **Purpose:** Centralizes video element event listener management with automatic cleanup. ```typescript type VideoEventType = 'play' | 'pause' | 'ended' | 'timeupdate' | 'waiting' | 'canplay' | 'playing' | 'loadedmetadata' | 'error' | 'progress'; interface UseVideoEventManagerOptions { videoRef: RefObject; handlers: Partial void>>; enabled?: boolean; } const useVideoEventManager = (options: UseVideoEventManagerOptions): void; ``` **Usage:** ```typescript useVideoEventManager({ videoRef, handlers: { playing: () => setIsPlaying(true), ended: () => setIsPlaying(false), waiting: () => setIsBuffering(true), canplay: () => setIsBuffering(false), }, enabled: !!videoUrl, }); ``` --- ### useVideoBufferingState **File:** `useVideoBufferingState.ts` (~180 LOC) **Purpose:** Tracks video buffering state with debouncing to avoid UI flicker. ```typescript interface UseVideoBufferingStateOptions { videoRef: RefObject; onBufferingChange?: (isBuffering: boolean) => void; debounceMs?: number; // Default: 100ms } interface UseVideoBufferingStateResult { isBuffering: boolean; isWaitingForData: boolean; bufferedRanges: TimeRanges | null; bufferedPercent: number; } ``` **Key Features:** - Debounced state changes to prevent rapid UI updates - Tracks `waiting` and `canplay` events - Computes buffered percentage for progress display - Handles edge cases like `readyState` checks --- ### useVideoBlobUrl **File:** `useVideoBlobUrl.ts` (~150 LOC) **Purpose:** Resolves video URLs to blob URLs from preload cache for instant playback. ```typescript interface PreloadCacheProvider { getReadyBlobUrl?: (url: string) => string | null; getReadyBlob?: (url: string) => Blob | null; getCachedBlobUrl?: (url: string) => Promise; } interface UseVideoBlobUrlOptions { videoUrl: string | undefined; storageKey?: string; preloadCache?: PreloadCacheProvider; enabled?: boolean; } interface UseVideoBlobUrlResult { resolvedUrl: string | null; isFromCache: boolean; isResolving: boolean; } ``` **Resolution Priority:** 1. `getReadyBlob(storageKey)` → Create fresh blob URL (avoids decoder issues) 2. `getReadyBlobUrl(storageKey)` → In-memory blob URL (O(1) lookup) 3. `getReadyBlobUrl(videoUrl)` → Fallback lookup by resolved URL 4. `getCachedBlobUrl()` → Cache API/IndexedDB lookup 5. Return original `videoUrl` → Network fetch **Usage:** ```typescript const { resolvedUrl, isFromCache } = useVideoBlobUrl({ videoUrl: transition.videoUrl, storageKey: transition.storageKey, preloadCache: preloadOrchestrator, enabled: !!transition, }); ``` --- ### useVideoFirstFrame **File:** `useVideoFirstFrame.ts` (~130 LOC) **Purpose:** Detects when the first video frame has been rendered using `requestVideoFrameCallback`. ```typescript interface UseVideoFirstFrameOptions { videoRef: RefObject; onFirstFrame?: () => void; enabled?: boolean; } interface UseVideoFirstFrameResult { isFirstFrameRendered: boolean; reset: () => void; } ``` **Key Features:** - Uses `requestVideoFrameCallback` API (Safari 15.4+, Chrome 83+) - Falls back to `playing` event for older browsers - Provides `reset()` for reuse across video changes --- ### useVideoErrorRecovery **File:** `useVideoErrorRecovery.ts` (~160 LOC) **Purpose:** Handles video errors with automatic retry and presigned URL fallback. ```typescript interface UseVideoErrorRecoveryOptions { videoRef: RefObject; currentUrl: string | null; storageKey?: string; maxRetries?: number; // Default: 2 onRecoveryAttempt?: (newUrl: string) => void; onRecoveryFailed?: (error: string) => void; } interface UseVideoErrorRecoveryResult { errorCount: number; lastError: string | null; isRecovering: boolean; currentResolvedUrl: string | null; } ``` **Recovery Strategy:** 1. First error → Mark presigned URL failed, retry with proxy URL 2. Second error → Retry with fresh presigned URL request 3. Third error → Call `onRecoveryFailed` --- ### useVideoTimeouts **File:** `useVideoTimeouts.ts` (~80 LOC) **Purpose:** Manages timeout IDs for video playback operations with automatic cleanup. ```typescript interface UseVideoTimeoutsResult { setLoadTimeout: (callback: () => void, ms: number) => void; setPlayTimeout: (callback: () => void, ms: number) => void; setFinishTimeout: (callback: () => void, ms: number) => void; clearAllTimeouts: () => void; } ``` **Usage:** ```typescript const timeouts = useVideoTimeouts(); // Set load timeout timeouts.setLoadTimeout(() => { console.warn('Video load timed out'); onError?.('load-timeout'); }, 10000); // Clear on success video.oncanplay = () => timeouts.clearAllTimeouts(); ``` --- ## Composite Hooks ### useVideoPlaybackCore **File:** `useVideoPlaybackCore.ts` (~280 LOC) **Purpose:** Core video playback logic combining multiple primitives. Used by `useVideoPlayer`. ```typescript interface UseVideoPlaybackCoreOptions { videoRef: RefObject; videoUrl: string | undefined; storageKey?: string; autoplay?: boolean; loop?: boolean; muted?: boolean; startTime?: number | null; endTime?: number | null; preloadCache?: PreloadCacheProvider; onReady?: () => void; onEnded?: () => void; onError?: (error: string) => void; } interface UseVideoPlaybackCoreResult { // State isReady: boolean; isPlaying: boolean; isBuffering: boolean; isEnded: boolean; currentTime: number; duration: number; // Resolved URL resolvedUrl: string | null; isFromCache: boolean; // Controls play: () => Promise; pause: () => void; seek: (time: number) => void; reset: () => void; } ``` **Composes:** - `useVideoBlobUrl` - URL resolution - `useVideoBufferingState` - Buffering tracking - `useVideoFirstFrame` - Ready detection - `useVideoErrorRecovery` - Error handling - `useVideoEventManager` - Event listeners --- ### useVideoPlayer **File:** `useVideoPlayer.ts` (~200 LOC) **Purpose:** Complete video player hook for UI elements (VideoPlayerElement). Wraps `useVideoPlaybackCore` with UI-specific features. ```typescript interface UseVideoPlayerOptions extends UseVideoPlaybackCoreOptions { // Inherited from UseVideoPlaybackCoreOptions showControls?: boolean; posterUrl?: string; onFirstPlay?: () => void; } interface UseVideoPlayerResult extends UseVideoPlaybackCoreResult { // Additional UI state hasPlayedOnce: boolean; showPoster: boolean; progress: number; // 0-100 // UI Controls togglePlay: () => void; toggleMute: () => void; setVolume: (volume: number) => void; } ``` **Usage in VideoPlayerElement:** ```typescript const player = useVideoPlayer({ videoRef, videoUrl: element.videoUrl, storageKey: element.videoStoragePath, autoplay: element.autoplay ?? false, loop: element.loop ?? false, muted: element.muted ?? true, preloadCache: preloadOrchestrator, onReady: () => console.log('Video ready'), }); return (
); ``` --- ## Index Exports **File:** `index.ts` (~60 LOC) ```typescript // Primitive hooks export { useVideoEventManager } from './useVideoEventManager'; export { useVideoBufferingState } from './useVideoBufferingState'; export { useVideoBlobUrl } from './useVideoBlobUrl'; export { useVideoFirstFrame } from './useVideoFirstFrame'; export { useVideoErrorRecovery } from './useVideoErrorRecovery'; export { useVideoTimeouts } from './useVideoTimeouts'; // Composite hooks export { useVideoPlaybackCore } from './useVideoPlaybackCore'; export { useVideoPlayer } from './useVideoPlayer'; // Types export type { PreloadCacheProvider } from './useVideoBlobUrl'; ``` --- ## Integration with useTransitionPlayback The `useTransitionPlayback` hook uses video primitives for transition video playback: ```typescript // useTransitionPlayback.ts import { useVideoBlobUrl, useVideoTimeouts, type PreloadCacheProvider } from './video'; export function useTransitionPlayback(options) { const { transition, videoRef, preloadCache } = options; // Resolve blob URL from cache const { resolvedUrl, isFromCache } = useVideoBlobUrl({ videoUrl: transition?.videoUrl, storageKey: transition?.storageKey, preloadCache, enabled: !!transition, }); // For back navigation, also resolve reverse video const { resolvedUrl: reverseUrl } = useVideoBlobUrl({ videoUrl: transition?.reverseVideoUrl, storageKey: transition?.reverseStorageKey, preloadCache, enabled: transition?.isBack && !!transition?.reverseVideoUrl, }); // Manage timeouts const timeouts = useVideoTimeouts(); // ... playback logic } ``` --- ## Design Patterns ### 1. Primitive Composition Each primitive hook handles one concern: - Event management → `useVideoEventManager` - Buffering state → `useVideoBufferingState` - URL resolution → `useVideoBlobUrl` Composite hooks combine primitives: ```typescript function useVideoPlaybackCore(options) { const { resolvedUrl } = useVideoBlobUrl(options); const { isBuffering } = useVideoBufferingState({ videoRef }); const { isFirstFrameRendered } = useVideoFirstFrame({ videoRef }); // ... combine state } ``` ### 2. Enabled Pattern All hooks accept an `enabled` prop to conditionally activate: ```typescript const { resolvedUrl } = useVideoBlobUrl({ videoUrl, enabled: !!videoUrl && isActive, // Only resolve when needed }); ``` ### 3. Callback Refs Expensive operations use refs to avoid re-renders: ```typescript const timeoutIdsRef = useRef>(new Map()); const setLoadTimeout = useCallback((cb, ms) => { const id = setTimeout(cb, ms); timeoutIdsRef.current.set('load', id); }, []); ``` ### 4. Cleanup on Unmount All hooks properly cleanup: ```typescript useEffect(() => { // Setup event listeners return () => { // Cleanup timeoutIdsRef.current.forEach(clearTimeout); timeoutIdsRef.current.clear(); }; }, []); ``` --- ## Related Documentation - [Hooks Module](./hooks-module.md) - Parent hooks documentation - [Navigation & Smooth Transitions](./navigation-smooth-transitions.md) - Transition playback integration - [Assets Preloading](../../documentation/assets-preloading.md) - Blob URL caching - [Video Playback](../../documentation/video-playback.md) - Background video implementation --- ## Summary | Hook | Type | LOC | Purpose | |------|------|-----|---------| | `useVideoEventManager` | Primitive | ~90 | Event listener management | | `useVideoBufferingState` | Primitive | ~180 | Buffering state tracking | | `useVideoBlobUrl` | Primitive | ~150 | URL resolution from cache | | `useVideoFirstFrame` | Primitive | ~130 | First frame detection | | `useVideoErrorRecovery` | Primitive | ~160 | Error handling + retry | | `useVideoTimeouts` | Primitive | ~80 | Timeout management | | `useVideoPlaybackCore` | Composite | ~280 | Core playback logic | | `useVideoPlayer` | Composite | ~200 | UI video player | | **Total** | | **~1,270** | | The video hooks module provides reusable primitives that can be composed for any video playback scenario - from transition videos to background videos to UI element players.