import type { MouseEvent } from 'react' import { useCallback, useEffect, useRef, } from 'react' import size from 'lodash/size' import type { LastPlayPosition } from 'requests' import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen' import { useVolume } from 'features/VideoPlayer/hooks/useVolume' import { useObjectState } from 'hooks' import { useProgressChangeHandler } from './useProgressChangeHandler' import { usePlayingHandlers } from './usePlayingHandlers' import { useVideoQuality } from './useVideoQuality' import { useDuration } from './useDuration' import type { Chapters } from '../types' export type PlayerState = { activeChapterIndex: number, loadedProgress: number, playedProgress: number, playing: boolean, ready: boolean, seek: number, seeking: boolean, } const initialState: PlayerState = { activeChapterIndex: 0, loadedProgress: 0, playedProgress: 0, playing: false, ready: false, seek: 0, seeking: false, } export type Props = { chapters: Chapters, onError?: () => void, onPlayingChange: (playing: boolean) => void, onProgressChange: (seconds: number, period: number) => void, resumeFrom: LastPlayPosition, } export const useMultiSourcePlayer = ({ chapters, onError, onPlayingChange, onProgressChange: onProgressChangeCallback, resumeFrom, }: Props) => { const [ { activeChapterIndex, loadedProgress, playedProgress, playing, seek, seeking, }, setPlayerState, ] = useObjectState({ ...initialState, activeChapterIndex: resumeFrom.half }) const videoRef = useRef(null) const { onReady, startPlaying, stopPlaying, togglePlaying, } = usePlayingHandlers(setPlayerState) const { selectedQuality, setSelectedQuality, videoQualities, } = useVideoQuality(chapters) const duration = useDuration(chapters) const handleError = useCallback(() => { stopPlaying() onError?.() }, [onError, stopPlaying]) const onProgressChange = useProgressChangeHandler({ activeChapterIndex, chapters, duration, setPlayerState, }) const getActiveChapterUrl = useCallback((quality: string = selectedQuality) => ( chapters[activeChapterIndex].urls[quality] ), [selectedQuality, chapters, activeChapterIndex]) const onQualitySelect = (quality: string) => { setPlayerState({ seek: videoRef.current?.currentTime ?? 0, }) setSelectedQuality(quality) } const playNextChapter = useCallback(() => { const nextIndex = (activeChapterIndex + 1) % size(chapters) setPlayerState({ activeChapterIndex: nextIndex, loadedProgress: 0, playedProgress: 0, playing: nextIndex !== 0, }) }, [activeChapterIndex, chapters, setPlayerState]) const onPlayerClick = (e: MouseEvent) => { if (e.target === videoRef.current) { togglePlaying() } } const onLoadedProgress = (loadedMs: number) => { const chapter = chapters[activeChapterIndex] const value = loadedMs - chapter.startOffsetMs setPlayerState({ loadedProgress: value }) } const onPlayedProgress = (playedMs: number) => { const chapter = chapters[activeChapterIndex] const value = Math.max(playedMs - chapter.startOffsetMs, 0) setPlayerState({ playedProgress: value }) } useEffect(() => { onPlayingChange(playing) }, [playing, onPlayingChange]) useEffect(() => { const progressSeconds = playedProgress / 1000 const { period } = chapters[activeChapterIndex] onProgressChangeCallback(progressSeconds, Number(period)) }, [ playedProgress, chapters, onProgressChangeCallback, activeChapterIndex, ]) useEffect(() => { const { duration: chapterDuration } = chapters[activeChapterIndex] if (playedProgress >= chapterDuration && !seeking) { playNextChapter() } }, [ activeChapterIndex, playedProgress, seeking, playNextChapter, chapters, ]) return { activeChapterIndex, activeSrc: getActiveChapterUrl(), chapters, duration, loadedProgress, onError: handleError, onLoadedProgress, onPlayedProgress, onPlayerClick, onProgressChange, onQualitySelect, onReady, playNextChapter, playedProgress, playing, seek, selectedQuality, startPlaying, togglePlaying, videoQualities, videoRef, ...useFullscreen(), ...useVolume(), } }