You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
4.4 KiB
191 lines
4.4 KiB
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<HTMLVideoElement>(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<HTMLDivElement>) => {
|
|
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(),
|
|
}
|
|
}
|
|
|