39948-vm/frontend/src/hooks/useVideoSoundControl.ts

101 lines
3.1 KiB
TypeScript

/**
* useVideoSoundControl Hook
*
* Manages video sound state with iOS autoplay compatibility.
* Videos start muted to ensure autoplay works on iOS WebKit browsers,
* then can be unmuted by user interaction.
*
* iOS WebKit autoplay policy:
* - Videos CAN autoplay if they have `playsinline` AND `muted` attributes
* - Unmuted videos require user interaction → native play button appears
* - By forcing muted start + custom sound button, we avoid native controls
*/
import { useState, useCallback, useRef, useEffect } from 'react';
import { logger } from '../lib/logger';
export interface UseVideoSoundControlOptions {
/** Whether page settings allow sound (background_video_muted === false) */
pageHasSound: boolean;
/** Whether page has a background video */
hasBackgroundVideo: boolean;
/** Current video URL - used to detect page changes (optional) */
videoUrl?: string;
}
export interface UseVideoSoundControlResult {
/** Current muted state (always starts true for iOS compatibility) */
isMuted: boolean;
/** Whether to show sound toggle button */
showSoundButton: boolean;
/** Toggle muted state */
toggleSound: () => void;
/** Force muted state (for page changes) */
setMuted: (muted: boolean) => void;
}
/**
* Hook for managing video sound state with iOS autoplay compatibility.
*
* @example
* const { isMuted, showSoundButton, toggleSound } = useVideoSoundControl({
* pageHasSound: selectedPage?.background_video_muted === false,
* hasBackgroundVideo: Boolean(backgroundVideoUrl),
* });
*
* // Pass to RuntimeControls
* <RuntimeControls
* showSoundButton={showSoundButton}
* isMuted={isMuted}
* onSoundToggle={toggleSound}
* />
*
* // Pass to CanvasBackground (always muted for autoplay)
* <CanvasBackground videoMuted={isMuted} />
*/
export function useVideoSoundControl({
pageHasSound,
hasBackgroundVideo,
videoUrl,
}: UseVideoSoundControlOptions): UseVideoSoundControlResult {
// Always start muted for iOS autoplay compatibility
const [isMuted, setIsMuted] = useState(true);
// Track previous video URL to detect page changes
const prevVideoUrl = useRef(videoUrl);
// Reset to muted when video changes (page navigation)
// This ensures iOS autoplay works on every page - new videos always start muted
useEffect(() => {
// Only reset if there's a new video (URL changed and we have a video)
if (prevVideoUrl.current !== videoUrl && videoUrl) {
setIsMuted(true);
logger.debug('[useVideoSoundControl] Video changed, resetting to muted', {
from: prevVideoUrl.current?.slice(-20),
to: videoUrl?.slice(-20),
});
}
prevVideoUrl.current = videoUrl;
}, [videoUrl]);
const toggleSound = useCallback(() => {
setIsMuted((prev) => {
logger.debug('[useVideoSoundControl] Toggle sound:', {
from: prev,
to: !prev,
});
return !prev;
});
}, []);
return {
isMuted,
// Show button only if page allows sound AND has a background video
showSoundButton: pageHasSound && hasBackgroundVideo,
toggleSound,
setMuted: setIsMuted,
};
}
export default useVideoSoundControl;