import type { MouseEvent } from 'react' import { useCallback, useEffect, useRef, } from 'react' import size from 'lodash/size' import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen' import { useVolume } from 'features/VideoPlayer/hooks/useVolume' import { useMatchPopupStore } from 'features/MatchPopup' 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' import { getNextPlayer } from '../helpers' import { REWIND_SECONDS } from '../config' const initialState = { activeChapterIndex: 0, activePlayer: Players.PLAYER1, loadedProgress: 0, playedProgress: 0, playing: false, ready: false, seek: { [Players.PLAYER1]: 0, [Players.PLAYER2]: 0, }, seeking: false, } export type PlayerState = typeof initialState export type Props = { chapters: Chapters, onError?: () => void, onPlayingChange: (playing: boolean) => void, } export const useMultiSourcePlayer = ({ chapters, onError, onPlayingChange, }: Props) => { const numberOfChapters = size(chapters) const [ { activeChapterIndex: chapterIndex, activePlayer, loadedProgress, playedProgress, playing, ready, seek, seeking, }, setPlayerState, ] = useObjectState(initialState) const video1Ref = useRef(null) const video2Ref = useRef(null) const activeChapterIndex = numberOfChapters >= chapterIndex ? chapterIndex : 0 const { onReady, playNextChapter, playPrevChapter, startPlaying, togglePlaying, } = usePlayingHandlers(setPlayerState, numberOfChapters) const { selectedQuality, setSelectedQuality, videoQualities, } = useVideoQuality(chapters) const duration = useDuration(chapters) const handleError = useCallback(() => { onError?.() }, [onError]) const getActiveChapter = useCallback( (index: number = activeChapterIndex) => chapters[index], [chapters, activeChapterIndex], ) const videoRef = [video1Ref, video2Ref][activePlayer] const isFirstChapterPlaying = activeChapterIndex === 0 const isLastChapterPlaying = activeChapterIndex === numberOfChapters - 1 const setPlayerTime = (progressMs: number) => { if (!videoRef.current) return videoRef.current.currentTime = progressMs / 1000 } const rewindForward = () => { const chapter = getActiveChapter() const newProgress = playedProgress + REWIND_SECONDS * 1000 if (newProgress <= chapter.duration) { setPlayerTime(chapter.startOffsetMs + newProgress) } else if (isLastChapterPlaying) { playNextChapter() } else { const nextChapter = getActiveChapter(activeChapterIndex + 1) const fromMs = newProgress - chapter.duration playNextChapter(fromMs, nextChapter.startOffsetMs) } } const rewindBackward = () => { const chapter = getActiveChapter() const newProgress = playedProgress - REWIND_SECONDS * 1000 if (newProgress >= 0) { setPlayerTime(chapter.startOffsetMs + newProgress) } else if (isFirstChapterPlaying) { setPlayerTime(chapter.startOffsetMs) } else { const prevChapter = getActiveChapter(activeChapterIndex - 1) const fromMs = prevChapter.duration + newProgress playPrevChapter(fromMs, prevChapter.startOffsetMs) } } const onProgressChange = useProgressChangeHandler({ chapters, duration, setPlayerState, }) const getChapterUrl = useCallback((index: number, quality: string = selectedQuality) => ( getActiveChapter(index)?.urls[quality] ), [selectedQuality, getActiveChapter]) 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 = getActiveChapter() const value = loadedMs - chapter.startOffsetMs setPlayerState({ loadedProgress: value }) } const onPlayedProgress = (playedMs: number) => { const chapter = getActiveChapter() const value = Math.max(playedMs - chapter.startOffsetMs, 0) setPlayerState({ playedProgress: value }) } const onEnded = () => { playNextChapter() } useEffect(() => { onPlayingChange(playing) }, [playing, onPlayingChange]) useEffect(() => { setPlayerState((state) => ({ ...initialState, activePlayer: getNextPlayer(state.activePlayer), playing: state.playing, ready: state.ready, })) }, [chapters, setPlayerState]) useEffect(() => { const { duration: chapterDuration } = getActiveChapter() if (playedProgress >= chapterDuration && !seeking) { playNextChapter() } }, [ getActiveChapter, playedProgress, seeking, playNextChapter, ]) const { selectedPlaylist } = useMatchPopupStore() useEffect(() => { if (selectedPlaylist) { startPlaying() } }, [selectedPlaylist, startPlaying]) return { activeChapterIndex, activePlayer, activeSrc: getChapterUrl(activeChapterIndex), allPlayedProgress: playedProgress + getActiveChapter().startMs, chapters, duration, isFirstChapterPlaying, isLastChapterPlaying, loadedProgress, nextSrc: getChapterUrl((activeChapterIndex + 1) % numberOfChapters), numberOfChapters, onEnded, onError: handleError, onLoadedProgress, onPlayedProgress, onPlayerClick, onProgressChange, onQualitySelect, onReady, playNextChapter, playPrevChapter, playedProgress, playing, ready, rewindBackward, rewindForward, seek, selectedQuality, togglePlaying, video1Ref, video2Ref, videoQualities, ...useFullscreen(), ...useVolume(), } }