|
|
|
|
@ -2,71 +2,99 @@ import type { MouseEvent } from 'react' |
|
|
|
|
import { |
|
|
|
|
useCallback, |
|
|
|
|
useEffect, |
|
|
|
|
useMemo, |
|
|
|
|
useState, |
|
|
|
|
} from 'react' |
|
|
|
|
|
|
|
|
|
import once from 'lodash/once' |
|
|
|
|
import size from 'lodash/size' |
|
|
|
|
import isNumber from 'lodash/isNumber' |
|
|
|
|
|
|
|
|
|
import { useVolume } from 'features/VideoPlayer/hooks/useVolume' |
|
|
|
|
import { REWIND_SECONDS } from 'features/MultiSourcePlayer/config' |
|
|
|
|
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup' |
|
|
|
|
import { isIOS } from 'config/userAgent' |
|
|
|
|
|
|
|
|
|
import { useObjectState } from 'hooks' |
|
|
|
|
import { useObjectState } from 'hooks/useObjectState' |
|
|
|
|
|
|
|
|
|
import type { MatchInfo } from 'requests/getMatchInfo' |
|
|
|
|
import { useVolume } from 'features/VideoPlayer/hooks/useVolume' |
|
|
|
|
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup' |
|
|
|
|
|
|
|
|
|
import { isIOS } from 'config/userAgent' |
|
|
|
|
import type { Chapters } from 'features/StreamPlayer/types' |
|
|
|
|
|
|
|
|
|
import { REWIND_SECONDS } from '../config' |
|
|
|
|
import { useHlsPlayer } from './useHlsPlayer' |
|
|
|
|
import { useFullscreen } from './useFullscreen' |
|
|
|
|
import { useVideoQuality } from './useVideoQuality' |
|
|
|
|
import { useControlsVisibility } from './useControlsVisibility' |
|
|
|
|
import { useProgressChangeHandler } from './useProgressChangeHandler' |
|
|
|
|
import { usePlayingHandlers } from './usePlayingHandlers' |
|
|
|
|
import { useDuration } from './useDuration' |
|
|
|
|
|
|
|
|
|
export type PlayerState = typeof initialState |
|
|
|
|
|
|
|
|
|
const toMilliSeconds = (seconds: number) => seconds * 1000 |
|
|
|
|
|
|
|
|
|
const initialState = { |
|
|
|
|
activeChapterIndex: 0, |
|
|
|
|
buffering: true, |
|
|
|
|
duration: 0, |
|
|
|
|
loadedProgress: 0, |
|
|
|
|
playedProgress: 0, |
|
|
|
|
playing: false, |
|
|
|
|
ready: false, |
|
|
|
|
seek: 0, |
|
|
|
|
seeking: false, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export type Props = { |
|
|
|
|
chapters: Chapters, |
|
|
|
|
isLive?: boolean, |
|
|
|
|
onDurationChange?: (duration: number) => void, |
|
|
|
|
onPlayingChange: (playing: boolean) => void, |
|
|
|
|
onProgressChange: (seconds: number) => void, |
|
|
|
|
profile: MatchInfo, |
|
|
|
|
resumeFrom?: number, |
|
|
|
|
url: string, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export const useVideoPlayer = ({ |
|
|
|
|
chapters, |
|
|
|
|
isLive, |
|
|
|
|
onDurationChange, |
|
|
|
|
onPlayingChange, |
|
|
|
|
onProgressChange: progressChangeCallback, |
|
|
|
|
resumeFrom, |
|
|
|
|
url, |
|
|
|
|
}: Props) => { |
|
|
|
|
const { url } = chapters[0] |
|
|
|
|
const numberOfChapters = size(chapters) |
|
|
|
|
const { hls, videoRef } = useHlsPlayer(url, resumeFrom) |
|
|
|
|
|
|
|
|
|
const [{ |
|
|
|
|
duration, |
|
|
|
|
activeChapterIndex, |
|
|
|
|
buffering, |
|
|
|
|
duration: fullMatchDuration, |
|
|
|
|
loadedProgress, |
|
|
|
|
playedProgress, |
|
|
|
|
playing, |
|
|
|
|
ready, |
|
|
|
|
seek, |
|
|
|
|
seeking, |
|
|
|
|
}, setPlayerState] = useObjectState({ |
|
|
|
|
...initialState, |
|
|
|
|
playedProgress: toMilliSeconds(resumeFrom || 0), |
|
|
|
|
seek: resumeFrom || 0, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const startPlaying = useMemo(() => once(() => { |
|
|
|
|
setPlayerState({ playing: true, ready: true }) |
|
|
|
|
onPlayingChange(true) |
|
|
|
|
}), [onPlayingChange, setPlayerState]) |
|
|
|
|
const chaptersDuration = useDuration(chapters) |
|
|
|
|
|
|
|
|
|
const duration = isLive ? fullMatchDuration : chaptersDuration |
|
|
|
|
|
|
|
|
|
const { |
|
|
|
|
onReady, |
|
|
|
|
playNextChapter, |
|
|
|
|
playPrevChapter, |
|
|
|
|
stopPlaying, |
|
|
|
|
togglePlaying, |
|
|
|
|
} = usePlayingHandlers(setPlayerState, chapters) |
|
|
|
|
|
|
|
|
|
const getActiveChapter = useCallback( |
|
|
|
|
(index: number = activeChapterIndex) => chapters[index], |
|
|
|
|
[chapters, activeChapterIndex], |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
const { |
|
|
|
|
isFullscreen, |
|
|
|
|
@ -78,18 +106,39 @@ export const useVideoPlayer = ({ |
|
|
|
|
width: wrapperRef.current?.clientWidth, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const togglePlaying = () => { |
|
|
|
|
if (ready) { |
|
|
|
|
setPlayerState({ playing: !playing }) |
|
|
|
|
onPlayingChange(!playing) |
|
|
|
|
const isFirstChapterPlaying = activeChapterIndex === 0 |
|
|
|
|
const isLastChapterPlaying = activeChapterIndex === numberOfChapters - 1 |
|
|
|
|
const seekTo = useCallback((progressMs: number) => { |
|
|
|
|
if (!videoRef.current) return |
|
|
|
|
videoRef.current.currentTime = progressMs / 1000 |
|
|
|
|
}, [videoRef]) |
|
|
|
|
|
|
|
|
|
const rewindForward = () => { |
|
|
|
|
const chapter = getActiveChapter() |
|
|
|
|
const newProgress = playedProgress + REWIND_SECONDS * 1000 |
|
|
|
|
if (newProgress <= chapter.duration) { |
|
|
|
|
seekTo(chapter.startOffsetMs + newProgress) |
|
|
|
|
} else if (isLastChapterPlaying) { |
|
|
|
|
playNextChapter() |
|
|
|
|
} else { |
|
|
|
|
const nextChapter = getActiveChapter(activeChapterIndex + 1) |
|
|
|
|
const fromMs = newProgress - chapter.duration |
|
|
|
|
playNextChapter(fromMs, nextChapter.startOffsetMs) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const rewind = (seconds: number) => () => { |
|
|
|
|
if (!videoRef.current) return |
|
|
|
|
const { currentTime } = videoRef.current |
|
|
|
|
const newProgress = currentTime + seconds |
|
|
|
|
videoRef.current.currentTime = newProgress |
|
|
|
|
const rewindBackward = () => { |
|
|
|
|
const chapter = getActiveChapter() |
|
|
|
|
const newProgress = playedProgress - REWIND_SECONDS * 1000 |
|
|
|
|
if (newProgress >= 0) { |
|
|
|
|
seekTo(chapter.startOffsetMs + newProgress) |
|
|
|
|
} else if (isFirstChapterPlaying) { |
|
|
|
|
seekTo(chapter.startOffsetMs) |
|
|
|
|
} else { |
|
|
|
|
const prevChapter = getActiveChapter(activeChapterIndex - 1) |
|
|
|
|
const fromMs = prevChapter.duration + newProgress |
|
|
|
|
playPrevChapter(fromMs, prevChapter.startOffsetMs) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onError = useCallback(() => { |
|
|
|
|
@ -102,22 +151,45 @@ export const useVideoPlayer = ({ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onWaiting = () => { |
|
|
|
|
setPlayerState({ buffering: true }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onPlaying = () => { |
|
|
|
|
setPlayerState({ buffering: false }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onPause = () => { |
|
|
|
|
setPlayerState({ playing: false }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onPlay = () => { |
|
|
|
|
setPlayerState({ playing: true }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onDuration = (durationSeconds: number) => { |
|
|
|
|
setPlayerState({ duration: toMilliSeconds(durationSeconds) }) |
|
|
|
|
onDurationChange?.(durationSeconds) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onProgressChange = useCallback((progress: number) => { |
|
|
|
|
const progressMs = progress * duration |
|
|
|
|
setPlayerState({ playedProgress: progressMs, seek: progressMs / 1000 }) |
|
|
|
|
}, [duration, setPlayerState]) |
|
|
|
|
const onProgressChange = useProgressChangeHandler({ |
|
|
|
|
chapters, |
|
|
|
|
duration, |
|
|
|
|
setPlayerState, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const onLoadedProgress = (loadedMs: number) => { |
|
|
|
|
setPlayerState({ loadedProgress: loadedMs }) |
|
|
|
|
const chapter = getActiveChapter() |
|
|
|
|
const value = loadedMs - chapter.startOffsetMs |
|
|
|
|
setPlayerState({ loadedProgress: value }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onPlayedProgress = (playedMs: number) => { |
|
|
|
|
setPlayerState({ playedProgress: playedMs }) |
|
|
|
|
progressChangeCallback(playedMs / 1000) |
|
|
|
|
const chapter = getActiveChapter() |
|
|
|
|
const value = Math.max(playedMs - chapter.startOffsetMs, 0) |
|
|
|
|
setPlayerState({ playedProgress: value }) |
|
|
|
|
|
|
|
|
|
progressChangeCallback(value / 1000) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const backToLive = useCallback(() => { |
|
|
|
|
@ -125,6 +197,36 @@ export const useVideoPlayer = ({ |
|
|
|
|
setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 }) |
|
|
|
|
}, [duration, setPlayerState]) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
if (isNumber(seek)) { |
|
|
|
|
setPlayerState({ seek: undefined }) |
|
|
|
|
} |
|
|
|
|
}, [seek, setPlayerState]) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
onPlayingChange(playing) |
|
|
|
|
}, [playing, onPlayingChange]) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
setPlayerState({ |
|
|
|
|
...initialState, |
|
|
|
|
playing: true, |
|
|
|
|
seek: chapters[0].startOffsetMs / 1000, |
|
|
|
|
}) |
|
|
|
|
}, [chapters, setPlayerState]) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
const { duration: chapterDuration } = getActiveChapter() |
|
|
|
|
if (playedProgress >= chapterDuration && !seeking) { |
|
|
|
|
playNextChapter() |
|
|
|
|
} |
|
|
|
|
}, [ |
|
|
|
|
getActiveChapter, |
|
|
|
|
playedProgress, |
|
|
|
|
seeking, |
|
|
|
|
playNextChapter, |
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
const { isOnline } = useNoNetworkPopupStore() |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
@ -138,13 +240,11 @@ export const useVideoPlayer = ({ |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
if (!isOnline) { |
|
|
|
|
setPlayerState({ playing: false }) |
|
|
|
|
onPlayingChange(false) |
|
|
|
|
stopPlaying() |
|
|
|
|
} |
|
|
|
|
}, [ |
|
|
|
|
isOnline, |
|
|
|
|
onPlayingChange, |
|
|
|
|
setPlayerState, |
|
|
|
|
stopPlaying, |
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
@ -160,26 +260,40 @@ export const useVideoPlayer = ({ |
|
|
|
|
}, [setPlayerState]) |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
activeChapterIndex, |
|
|
|
|
allPlayedProgress: playedProgress + getActiveChapter().startMs, |
|
|
|
|
backToLive, |
|
|
|
|
buffering, |
|
|
|
|
duration, |
|
|
|
|
isFirstChapterPlaying, |
|
|
|
|
isFullscreen, |
|
|
|
|
isLastChapterPlaying, |
|
|
|
|
loadedProgress, |
|
|
|
|
numberOfChapters, |
|
|
|
|
onDuration, |
|
|
|
|
onError, |
|
|
|
|
onFullscreenClick, |
|
|
|
|
onLoadedProgress, |
|
|
|
|
onPause, |
|
|
|
|
onPlay, |
|
|
|
|
onPlayedProgress, |
|
|
|
|
onPlayerClick, |
|
|
|
|
onPlaying, |
|
|
|
|
onProgressChange, |
|
|
|
|
onReady, |
|
|
|
|
onWaiting, |
|
|
|
|
playNextChapter, |
|
|
|
|
playPrevChapter, |
|
|
|
|
playedProgress, |
|
|
|
|
playing, |
|
|
|
|
ready, |
|
|
|
|
rewindBackward: rewind(-REWIND_SECONDS), |
|
|
|
|
rewindForward: rewind(REWIND_SECONDS), |
|
|
|
|
rewindBackward, |
|
|
|
|
rewindForward, |
|
|
|
|
seek, |
|
|
|
|
sizeOptions, |
|
|
|
|
startPlaying, |
|
|
|
|
togglePlaying, |
|
|
|
|
url, |
|
|
|
|
videoRef, |
|
|
|
|
wrapperRef, |
|
|
|
|
...useControlsVisibility(isFullscreen), |
|
|
|
|
|