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.
748 lines
19 KiB
748 lines
19 KiB
import {
|
|
MouseEvent,
|
|
useMemo,
|
|
useRef,
|
|
useCallback,
|
|
useEffect,
|
|
useState,
|
|
} from 'react'
|
|
|
|
import { useTour } from '@reactour/tour'
|
|
|
|
import size from 'lodash/size'
|
|
import isNumber from 'lodash/isNumber'
|
|
import isEmpty from 'lodash/isEmpty'
|
|
import isUndefined from 'lodash/isUndefined'
|
|
import findIndex from 'lodash/findIndex'
|
|
import inRange from 'lodash/inRange'
|
|
import sum from 'lodash/sum'
|
|
import values from 'lodash/values'
|
|
|
|
import Hls from 'hls.js'
|
|
|
|
import {
|
|
isIOS,
|
|
isMobileDevice,
|
|
KEYBOARD_KEYS,
|
|
} from 'config'
|
|
|
|
import {
|
|
useObjectState,
|
|
useEventListener,
|
|
usePageParams,
|
|
useInterval,
|
|
useDuration,
|
|
} from 'hooks'
|
|
|
|
import type { Chapters } from 'features/StreamPlayer/types'
|
|
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
|
|
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup'
|
|
import { useLiveMatch } from 'features/MatchPage/components/LiveMatch/hooks'
|
|
import { useLexicsStore } from 'features/LexicsStore'
|
|
import { useMatchPageStore } from 'features/MatchPage/store'
|
|
|
|
import { VIEW_INTERVAL_MS, saveMatchStats } from 'requests'
|
|
|
|
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 { useAudioTrack } from './useAudioTrack'
|
|
import { FULL_GAME_KEY } from '../../MatchPage/helpers/buildPlaylists'
|
|
|
|
export type PlayerState = typeof initialState
|
|
|
|
const toMilliSeconds = (seconds: number) => seconds * 1000
|
|
const BUFFERING_TIME = 30 * 1000
|
|
|
|
const initialState = {
|
|
activeChapterIndex: 0,
|
|
buffering: true,
|
|
chapters: [] as Chapters,
|
|
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,
|
|
resumeFrom?: number,
|
|
url?: string,
|
|
}
|
|
|
|
export const useVideoPlayer = ({
|
|
chapters: chaptersProps,
|
|
isLive,
|
|
onDurationChange,
|
|
onPlayingChange,
|
|
onProgressChange: progressChangeCallback,
|
|
resumeFrom,
|
|
}: Props) => {
|
|
const [currentPlayingOrder, setCurrentPlayingOrder] = useState(0)
|
|
const ordersObj = useRef<Record<string, number>>({})
|
|
|
|
const [{
|
|
activeChapterIndex,
|
|
buffering,
|
|
chapters,
|
|
duration: fullMatchDuration,
|
|
loadedProgress,
|
|
playedProgress,
|
|
playing: statePlaying,
|
|
ready,
|
|
seek,
|
|
seeking,
|
|
}, setPlayerState] = useObjectState({ ...initialState, chapters: chaptersProps })
|
|
|
|
const { onPlaylistSelect } = useLiveMatch()
|
|
const { lang } = useLexicsStore()
|
|
const { profileId, sportType } = usePageParams()
|
|
const {
|
|
disablePlayingEpisodes,
|
|
isPlayFilterEpisodes,
|
|
isPlayingEpisode,
|
|
matchPlaylists,
|
|
playingOrder,
|
|
playingProgress,
|
|
playNextEpisode,
|
|
profile,
|
|
selectedPlaylist,
|
|
setCircleAnimation,
|
|
setIsFullScreen,
|
|
setPlayingProgress,
|
|
} = useMatchPageStore()
|
|
|
|
const { isOpen } = useTour()
|
|
|
|
const playing = Boolean(statePlaying && !isOpen)
|
|
|
|
/** время для сохранения статистики просмотра матча */
|
|
const timeForStatistics = useRef(0)
|
|
|
|
const resumeTimeWithOffset = useMemo(() => {
|
|
const chapterWithOffset = chapters[0].startOffsetMs / 1000
|
|
return !isUndefined(resumeFrom) && isLive && chapters[0].isFullMatchChapter
|
|
? resumeFrom + chapterWithOffset
|
|
: resumeFrom
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [resumeFrom])
|
|
|
|
const { url } = chapters[0] ?? { url: '' }
|
|
const numberOfChapters = size(chapters)
|
|
const { hls, videoRef } = useHlsPlayer({
|
|
isLive,
|
|
resumeFrom: resumeTimeWithOffset,
|
|
src: url,
|
|
})
|
|
|
|
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
const videoRefDurationMs = videoRef.current?.duration! * 1000
|
|
|
|
const [isLivePlaying, setIsLivePlaying] = useState(false)
|
|
const [isPausedTime, setIsPausedTime] = useState(false)
|
|
const pausedProgress = useRef(0)
|
|
|
|
const getActiveChapter = useCallback(
|
|
(index: number = activeChapterIndex) => chapters[index],
|
|
[chapters, activeChapterIndex],
|
|
)
|
|
|
|
useEffect(() => {
|
|
const splitEpisodeDuration = (duration: number, count: number) => {
|
|
const result: Array<[number, number]> = []
|
|
|
|
for (let i = 0; i < duration; i += count) {
|
|
const start = i
|
|
const end = Math.min(i + count, duration)
|
|
result.push([start, end])
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
const getCurrentPlayingOrder = () => {
|
|
const { count, duration } = getActiveChapter()
|
|
|
|
const arr = splitEpisodeDuration(duration, duration / count!)
|
|
|
|
const index = findIndex(arr, (elem) => inRange(
|
|
playingProgress * 1000,
|
|
elem[0],
|
|
elem[1] + 1,
|
|
))
|
|
|
|
if (index !== -1 && ordersObj.current[playingOrder] !== index) {
|
|
ordersObj.current = {
|
|
...ordersObj.current,
|
|
[playingOrder]: index,
|
|
}
|
|
}
|
|
|
|
return playingOrder + sum(values(ordersObj.current))
|
|
}
|
|
|
|
setCurrentPlayingOrder(getCurrentPlayingOrder())
|
|
}, [getActiveChapter, isPlayingEpisode, playingOrder, playingProgress])
|
|
|
|
useEffect(() => {
|
|
if (!isPlayingEpisode) {
|
|
ordersObj.current = {}
|
|
}
|
|
}, [isPlayingEpisode])
|
|
|
|
const chaptersDuration = useDuration(chapters)
|
|
|
|
const duration = useMemo(() => {
|
|
if (isLive && chapters[0]?.isFullMatchChapter) {
|
|
if (isIOS) {
|
|
return fullMatchDuration - getActiveChapter().startOffsetMs
|
|
} if (Number.isFinite(videoRefDurationMs)) {
|
|
return videoRefDurationMs - getActiveChapter().startOffsetMs
|
|
}
|
|
}
|
|
return chaptersDuration
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
chapters,
|
|
chaptersDuration,
|
|
fullMatchDuration,
|
|
getActiveChapter,
|
|
isLive,
|
|
])
|
|
|
|
const {
|
|
onReady,
|
|
playNextChapter,
|
|
playPrevChapter,
|
|
stopPlaying,
|
|
togglePlaying,
|
|
} = usePlayingHandlers(setPlayerState, chapters)
|
|
|
|
const restartVideo = useCallback(() => {
|
|
onPlaylistSelect(matchPlaylists.match[0])
|
|
}, [matchPlaylists.match, onPlaylistSelect])
|
|
|
|
const {
|
|
isFullscreen,
|
|
onFullscreenClick,
|
|
wrapperRef,
|
|
} = useFullscreen(videoRef)
|
|
|
|
const [sizeOptions, setSizeOptions] = useState({
|
|
height: wrapperRef.current?.clientHeight,
|
|
width: wrapperRef.current?.clientWidth,
|
|
})
|
|
|
|
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 || isLive) {
|
|
seekTo(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) {
|
|
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(() => {
|
|
setPlayerState({ playing: false })
|
|
}, [setPlayerState])
|
|
|
|
const onPlayerClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
if (e.target === videoRef.current) {
|
|
togglePlaying()
|
|
}
|
|
}
|
|
|
|
const onWaiting = () => {
|
|
setPlayerState({ buffering: true })
|
|
}
|
|
|
|
const onPlaying = () => {
|
|
setPlayerState({ buffering: false })
|
|
}
|
|
|
|
const onPause = () => {
|
|
setPlayerState({ playing: false })
|
|
}
|
|
|
|
const onPlay = () => {
|
|
setPlayerState({ playing: true })
|
|
}
|
|
|
|
const isLiveTime = useMemo(() => {
|
|
const matchDuration = duration === 0 && Number.isFinite(videoRefDurationMs)
|
|
? videoRefDurationMs
|
|
: duration
|
|
|
|
return chapters[0]?.isFullMatchChapter
|
|
&& isLive
|
|
&& playedProgress > matchDuration - BUFFERING_TIME * 1.5
|
|
}, [
|
|
chapters,
|
|
duration,
|
|
isLive,
|
|
playedProgress,
|
|
videoRefDurationMs,
|
|
])
|
|
|
|
const onDuration = (durationSeconds: number) => {
|
|
setPlayerState({ duration: toMilliSeconds(durationSeconds) })
|
|
onDurationChange?.(durationSeconds)
|
|
}
|
|
|
|
const onProgressChange = useProgressChangeHandler({
|
|
chapters,
|
|
duration,
|
|
setPlayerState,
|
|
})
|
|
|
|
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 })
|
|
timeForStatistics.current = (value + chapter.startMs) / 1000
|
|
|
|
chapter.isFullMatchChapter && setPlayingProgress(Math.floor(value / 1000))
|
|
progressChangeCallback(value / 1000)
|
|
}
|
|
|
|
const backToLive = useCallback(() => {
|
|
if (!duration) return
|
|
|
|
if (selectedPlaylist?.id !== FULL_GAME_KEY) {
|
|
restartVideo()
|
|
setIsLivePlaying(true)
|
|
return
|
|
}
|
|
const matchDuration = fullMatchDuration === 0 && Number.isFinite(videoRefDurationMs)
|
|
? videoRefDurationMs
|
|
: fullMatchDuration
|
|
|
|
const liveProgressMs = Math.max(matchDuration - BUFFERING_TIME, 0)
|
|
setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 })
|
|
if (liveProgressMs > 0 && isLiveTime) setIsLivePlaying(false)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
duration,
|
|
selectedPlaylist,
|
|
setPlayerState,
|
|
matchPlaylists.match,
|
|
isLiveTime,
|
|
fullMatchDuration,
|
|
videoRefDurationMs,
|
|
restartVideo,
|
|
])
|
|
|
|
const backToPausedTime = useCallback(() => {
|
|
if (!duration) return
|
|
|
|
if (selectedPlaylist?.id !== FULL_GAME_KEY) {
|
|
restartVideo()
|
|
setIsPausedTime(true)
|
|
return
|
|
}
|
|
setIsPausedTime(false)
|
|
// eslint-disable-next-line
|
|
}, [
|
|
duration,
|
|
selectedPlaylist,
|
|
restartVideo,
|
|
])
|
|
|
|
const stopPlayingEpisodes = () => {
|
|
disablePlayingEpisodes()
|
|
restartVideo()
|
|
setTimeout(() => {
|
|
setPlayerState({
|
|
playedProgress: pausedProgress.current,
|
|
seek: pausedProgress.current / 1000,
|
|
})
|
|
}, 100)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (chapters[0]?.isFullMatchChapter) {
|
|
pausedProgress.current = playedProgress + chapters[0].startOffsetMs
|
|
}
|
|
// eslint-disable-next-line
|
|
}, [selectedPlaylist])
|
|
|
|
useEffect(() => {
|
|
if (
|
|
isLive
|
|
&& isNumber(duration)
|
|
&& isUndefined(resumeFrom)
|
|
&& chaptersProps[0]?.isFullMatchChapter
|
|
) {
|
|
backToLive()
|
|
}
|
|
// eslint-disable-next-line
|
|
}, [])
|
|
|
|
useEventListener({
|
|
callback: (e: KeyboardEvent) => {
|
|
if (isOpen) return
|
|
if (e.code === KEYBOARD_KEYS.ArrowLeft) rewindBackward()
|
|
else if (e.code === KEYBOARD_KEYS.ArrowRight) rewindForward()
|
|
},
|
|
event: 'keydown',
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (isNumber(seek)) {
|
|
setPlayerState({ seek: undefined })
|
|
}
|
|
}, [seek, setPlayerState])
|
|
|
|
useEffect(() => {
|
|
onPlayingChange(selectedPlaylist?.id === FULL_GAME_KEY ? playing : false)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [playing, selectedPlaylist])
|
|
|
|
useEffect(() => {
|
|
if (playing) {
|
|
setPlayerState({ buffering: false })
|
|
}
|
|
// eslint-disable-next-line
|
|
}, [playing])
|
|
|
|
const regURL = /\d{6,20}/gi
|
|
|
|
useEffect(() => {
|
|
if (isLive) {
|
|
setPlayerState({
|
|
...initialState,
|
|
chapters: chaptersProps,
|
|
})
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isLive, chaptersProps[0].startOffsetMs])
|
|
|
|
useEffect(() => {
|
|
if (((isLive || chapters[0].duration === chaptersProps[0].duration)
|
|
&& chapters[0]?.endOffsetMs === chaptersProps[0]?.endOffsetMs
|
|
&& chapters[0]?.url.match(regURL)?.[0] === chaptersProps[0]?.url.match(regURL)?.[0])
|
|
|| (isEmpty(chapters) || isEmpty(chaptersProps))) return
|
|
|
|
if (
|
|
!isUndefined(resumeFrom)
|
|
&& chaptersProps[0].isFullMatchChapter
|
|
&& !isLivePlaying
|
|
&& !isPausedTime
|
|
) {
|
|
setPlayerState({
|
|
...initialState,
|
|
chapters: chaptersProps,
|
|
playing: true,
|
|
seek: resumeFrom + chaptersProps[0].startOffsetMs / 1000,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (
|
|
chaptersProps[0].isFullMatchChapter
|
|
&& isLive
|
|
&& selectedPlaylist.id === FULL_GAME_KEY
|
|
&& isLivePlaying
|
|
&& !isPausedTime
|
|
) {
|
|
setIsLivePlaying(false)
|
|
setPlayerState({
|
|
...initialState,
|
|
chapters: chaptersProps,
|
|
duration: videoRefDurationMs,
|
|
playing: true,
|
|
seek: Number.isFinite(videoRefDurationMs)
|
|
? (videoRefDurationMs - BUFFERING_TIME) / 1000
|
|
: 0,
|
|
})
|
|
return
|
|
}
|
|
|
|
if (
|
|
chaptersProps[0].isFullMatchChapter
|
|
&& selectedPlaylist.id === FULL_GAME_KEY
|
|
&& isPausedTime
|
|
&& !isLivePlaying
|
|
) {
|
|
setIsPausedTime(false)
|
|
setPlayerState({
|
|
...initialState,
|
|
chapters: chaptersProps,
|
|
duration: videoRefDurationMs,
|
|
playing: true,
|
|
seek: pausedProgress.current / 1000,
|
|
})
|
|
return
|
|
}
|
|
setPlayerState({
|
|
...initialState,
|
|
chapters: chaptersProps,
|
|
playing: true,
|
|
seek: chaptersProps[0].startOffsetMs / 1000,
|
|
})
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
chapters,
|
|
chaptersProps,
|
|
isLive,
|
|
setPlayerState,
|
|
isPausedTime,
|
|
isLivePlaying,
|
|
videoRef,
|
|
selectedPlaylist,
|
|
])
|
|
|
|
/**
|
|
* Для воcпроизведения нового эпизода, аналогичного текущему, с начала
|
|
*/
|
|
useEffect(() => {
|
|
if (chaptersProps[0].isFullMatchChapter) return
|
|
|
|
setPlayerState({
|
|
...initialState,
|
|
chapters: chaptersProps,
|
|
playing: true,
|
|
seek: chaptersProps[0].startOffsetMs / 1000,
|
|
})
|
|
}, [chaptersProps, setPlayerState])
|
|
|
|
useEffect(() => {
|
|
if ((
|
|
chapters[0]?.isFullMatchChapter)
|
|
|| isEmpty(chapters)
|
|
|| selectedPlaylist.id === FULL_GAME_KEY
|
|
) return
|
|
|
|
const { duration: chapterDuration } = getActiveChapter()
|
|
if (playedProgress >= chapterDuration && !seeking && !isPlayFilterEpisodes) {
|
|
if (isLastChapterPlaying) {
|
|
backToPausedTime()
|
|
} else {
|
|
playNextChapter()
|
|
}
|
|
}
|
|
if (playedProgress >= chapterDuration && !seeking && isPlayFilterEpisodes) {
|
|
setPlayerState({ playedProgress: 0 })
|
|
playNextEpisode()
|
|
}
|
|
// eslint-disable-next-line
|
|
}, [
|
|
isLive,
|
|
chapters,
|
|
getActiveChapter,
|
|
onPlaylistSelect,
|
|
playedProgress,
|
|
seeking,
|
|
playNextChapter,
|
|
playNextEpisode,
|
|
isPlayFilterEpisodes,
|
|
])
|
|
|
|
const { isOnline } = useNoNetworkPopupStore()
|
|
|
|
useEffect(() => {
|
|
if (wrapperRef.current) {
|
|
setSizeOptions({
|
|
height: wrapperRef.current?.clientHeight,
|
|
width: wrapperRef.current?.clientWidth,
|
|
})
|
|
}
|
|
}, [wrapperRef])
|
|
|
|
useEffect(() => {
|
|
if (!isOnline) {
|
|
stopPlaying()
|
|
}
|
|
}, [
|
|
isOnline,
|
|
stopPlaying,
|
|
])
|
|
|
|
useEffect(() => {
|
|
/* воспроизводим плейлист полного матча с начала при завершении трансляции */
|
|
if (isLive) hls?.on(Hls.Events.BUFFER_EOS, restartVideo)
|
|
|
|
return () => {
|
|
hls?.off(Hls.Events.BUFFER_EOS, restartVideo)
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [hls, isLive])
|
|
|
|
useEffect(() => {
|
|
if (!navigator.serviceWorker || !isIOS) return undefined
|
|
const listener = (event: MessageEvent<{ duration: number }>) => {
|
|
setPlayerState({ duration: toMilliSeconds(event.data.duration) })
|
|
}
|
|
|
|
navigator.serviceWorker.addEventListener('message', listener)
|
|
return () => {
|
|
navigator.serviceWorker.removeEventListener('message', listener)
|
|
}
|
|
}, [setPlayerState])
|
|
|
|
useEffect(() => {
|
|
if (ready && videoRef && !isMobileDevice) {
|
|
videoRef.current?.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start',
|
|
})
|
|
}
|
|
}, [ready, videoRef])
|
|
|
|
useEffect(() => {
|
|
setCircleAnimation((state) => ({
|
|
...state,
|
|
playedProgress,
|
|
playing,
|
|
ready,
|
|
}))
|
|
}, [
|
|
playedProgress,
|
|
playing,
|
|
ready,
|
|
setCircleAnimation,
|
|
])
|
|
|
|
const warningText = lang === 'es'
|
|
? 'La transmisión en vivo no está disponible temporalmente en dispositivos iOS'
|
|
: 'Live streaming is temporarily unavailable on iOS devices'
|
|
|
|
// ведем статистику просмотра матча
|
|
const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({
|
|
callback: useCallback(
|
|
() => {
|
|
if (timeForStatistics.current !== 0) {
|
|
saveMatchStats({
|
|
matchDate: profile?.date,
|
|
matchId: profileId,
|
|
matchSecond: timeForStatistics.current,
|
|
seasonId: profile?.season.id,
|
|
sportType,
|
|
teamFirst: profile?.team1.id,
|
|
teamSecond: profile?.team2.id,
|
|
tournamentId: profile?.tournament.id,
|
|
})
|
|
}
|
|
},
|
|
[profile?.date,
|
|
profile?.season.id,
|
|
profile?.team1.id, profile?.team2.id,
|
|
profile?.tournament.id,
|
|
profileId, sportType,
|
|
],
|
|
),
|
|
intervalDuration: VIEW_INTERVAL_MS,
|
|
startImmediate: false,
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (playing) {
|
|
startCollectingStats()
|
|
} else {
|
|
stopCollectingStats()
|
|
}
|
|
}, [
|
|
playing,
|
|
startCollectingStats,
|
|
stopCollectingStats,
|
|
profileId,
|
|
])
|
|
|
|
useEffect(() => {
|
|
setIsFullScreen(isFullscreen)
|
|
}, [
|
|
isFullscreen,
|
|
setIsFullScreen,
|
|
])
|
|
|
|
return {
|
|
activeChapterIndex,
|
|
allPlayedProgress: playedProgress + getActiveChapter().startMs,
|
|
backToLive,
|
|
buffering,
|
|
chapters,
|
|
currentPlayingOrder,
|
|
duration,
|
|
isFirstChapterPlaying,
|
|
isFullscreen,
|
|
isLastChapterPlaying,
|
|
isLive,
|
|
isLiveTime,
|
|
loadedProgress,
|
|
numberOfChapters,
|
|
onDuration,
|
|
onError,
|
|
onFullscreenClick,
|
|
onLoadedProgress,
|
|
onPause,
|
|
onPlay,
|
|
onPlayedProgress,
|
|
onPlayerClick,
|
|
onPlaying,
|
|
onProgressChange,
|
|
onReady,
|
|
onWaiting,
|
|
playNextChapter,
|
|
playPrevChapter,
|
|
playedProgress,
|
|
playing,
|
|
ready,
|
|
rewindBackward,
|
|
rewindForward,
|
|
seek,
|
|
sizeOptions,
|
|
stopPlayingEpisodes,
|
|
togglePlaying,
|
|
url,
|
|
videoRef,
|
|
warningText,
|
|
wrapperRef,
|
|
...useControlsVisibility(isFullscreen, playing),
|
|
...useVolume(),
|
|
...useVideoQuality(hls),
|
|
...useAudioTrack(hls),
|
|
}
|
|
}
|
|
|