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' import { Players } from '../types' export type PlayerState = { activeChapterIndex: number, activePlayer: Players, loadedProgress: number, playedProgress: number, playing: boolean, ready: boolean, seek: Record, seeking: boolean, } const initialState: PlayerState = { activeChapterIndex: 0, activePlayer: 0, loadedProgress: 0, playedProgress: 0, playing: false, ready: false, seek: { [Players.PLAYER1]: 0, [Players.PLAYER2]: 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 numberOfChapters = size(chapters) const [ { activeChapterIndex, activePlayer, loadedProgress, playedProgress, playing, ready, seek, seeking, }, setPlayerState, ] = useObjectState({ ...initialState, activeChapterIndex: resumeFrom.half }) const video1Ref = useRef(null) const video2Ref = useRef(null) const { onReady, playNextChapter, playPrevChapter, startPlaying, stopPlaying, togglePlaying, } = usePlayingHandlers(setPlayerState, numberOfChapters) const { selectedQuality, setSelectedQuality, videoQualities, } = useVideoQuality(chapters) const duration = useDuration(chapters) const handleError = useCallback(() => { stopPlaying() onError?.() }, [onError, stopPlaying]) const onProgressChange = useProgressChangeHandler({ chapters, duration, setPlayerState, }) const getChapterUrl = useCallback((index: number, quality: string = selectedQuality) => ( chapters[index]?.urls[quality] ), [selectedQuality, chapters]) const videoRef = [video1Ref, video2Ref][activePlayer] const onQualitySelect = (quality: string) => { setPlayerState((state) => ({ seek: { ...state.seek, [state.activePlayer]: videoRef.current?.currentTime ?? 0, }, })) setSelectedQuality(quality) } 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, activePlayer, activeSrc: getChapterUrl(activeChapterIndex), chapters, duration, isFirstChapterPlaying: activeChapterIndex === 0, isLastChapterPlaying: activeChapterIndex === numberOfChapters - 1, loadedProgress, nextSrc: getChapterUrl(activeChapterIndex + 1), onError: handleError, onLoadedProgress, onPlayedProgress, onPlayerClick, onProgressChange, onQualitySelect, onReady, playNextChapter, playPrevChapter, playedProgress, playing, ready, seek, selectedQuality, startPlaying, togglePlaying, video1Ref, video2Ref, videoQualities, ...useFullscreen(), ...useVolume(), } }