diff --git a/package.json b/package.json index 89c26bab..1e0bc8ab 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "build-storybook": "build-storybook -s public" }, "dependencies": { - "@reach/combobox": "^0.10.4", "date-fns": "^2.14.0", "history": "^4.10.1", "hls.js": "^0.14.15", @@ -22,7 +21,6 @@ "react": "^16.13.1", "react-datepicker": "^3.1.3", "react-dom": "^16.13.1", - "react-player": "^2.6.0", "react-responsive": "^8.1.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", diff --git a/src/features/MultiSourcePlayer/hooks/index.tsx b/src/features/MultiSourcePlayer/hooks/index.tsx index a54b7690..ef283c52 100644 --- a/src/features/MultiSourcePlayer/hooks/index.tsx +++ b/src/features/MultiSourcePlayer/hooks/index.tsx @@ -10,15 +10,13 @@ import size from 'lodash/size' import type { LastPlayPosition, Videos } from 'requests' -import { useEventListener } from 'hooks' import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen' +import { useVolume } from 'features/VideoPlayer/hooks/useVolume' import { useProgressChangeHandler } from './useProgressChangeHandler' -import { useLoadedEvent, usePlayedEvent } from './useProgressEvents' import { usePlayingState } from './usePlayingState' import { useVideoQuality } from './useVideoQuality' import { useDuration } from './useDuration' -import { useVolume } from './useVolume' import { useVideos } from './useVideos' export type Props = { @@ -30,24 +28,24 @@ export type Props = { } export const useMultiSourcePlayer = ({ - resumeFrom, - onError = () => {}, - videos, + onError, onPlayingChange, onProgressChange: onProgressChangeCallback, + resumeFrom, + videos, }: Props) => { const activeChapterIndex = useRef(resumeFrom.half) - const wrapperRef = useRef(null) const videoRef = useRef(null) + const [seek, setSeek] = useState(resumeFrom.second) const [loadedProgress, setLoadedProgress] = useState(0) const [playedProgress, setPlayedProgress] = useState(0) const { - continuePlaying, + onReady, playing, startPlaying, stopPlaying, togglePlaying, - } = usePlayingState(videoRef) + } = usePlayingState() const { selectedQuality, @@ -58,14 +56,17 @@ export const useMultiSourcePlayer = ({ const { chapters } = useVideos(videos) const duration = useDuration(chapters) + const handleError = useCallback(() => { + stopPlaying() + onError?.() + }, [onError, stopPlaying]) + const onProgressChange = useProgressChangeHandler({ activeChapterIndex, chapters, - continuePlaying, duration, - selectedQuality, setPlayedProgress, - videoRef, + setSeek, }) const getActiveChapterUrl = useCallback((quality: string = selectedQuality) => ( @@ -77,9 +78,8 @@ export const useMultiSourcePlayer = ({ ), [chapters]) const onQualitySelect = (quality: string) => { - const from = videoRef.current?.currentTime + setSeek(videoRef.current?.currentTime || 0) setSelectedQuality(quality) - continuePlaying(getActiveChapterUrl(quality), from) } const playNextChapter = () => { @@ -87,9 +87,7 @@ export const useMultiSourcePlayer = ({ const isLastChapterPlayed = activeChapterIndex.current === size(chapters) if (isLastChapterPlayed) { activeChapterIndex.current = 0 - stopPlaying(getActiveChapterUrl()) - } else { - continuePlaying(getActiveChapterUrl()) + stopPlaying() } } @@ -99,52 +97,16 @@ export const useMultiSourcePlayer = ({ } } - const onLoadedChange = (loadedMs: number) => { + const onLoadedProgress = (loadedMs: number) => { const chapterStart = getActiveChapterStart() setLoadedProgress(chapterStart + loadedMs) } - const onPlayedChange = (playedMs: number) => { + const onPlayedProgress = (playedMs: number) => { const chapterStart = getActiveChapterStart() setPlayedProgress(chapterStart + playedMs) } - useEffect(() => { - const url = getActiveChapterUrl() - const chapterStartMs = getActiveChapterStart() - const from = resumeFrom.second - (chapterStartMs / 1000) - if (url) { - startPlaying(url, from) - } - }, [ - resumeFrom, - getActiveChapterUrl, - getActiveChapterStart, - startPlaying, - ]) - - useLoadedEvent({ - onChange: onLoadedChange, - videoRef, - }) - - usePlayedEvent({ - onChange: onPlayedChange, - videoRef, - }) - - useEventListener({ - callback: playNextChapter, - event: 'ended', - target: videoRef, - }) - - useEventListener({ - callback: onError, - event: 'error', - target: videoRef, - }) - useEffect(() => { onPlayingChange(playing) }, [playing, onPlayingChange]) @@ -160,20 +122,27 @@ export const useMultiSourcePlayer = ({ ]) return { + activeSrc: getActiveChapterUrl(), chapters, duration, loadedProgress, + onError: handleError, + onLoadedProgress, + onPlayedProgress, onPlayerClick, onProgressChange, onQualitySelect, + onReady, + playNextChapter, playedProgress, playing, + seek, selectedQuality, + startPlaying, togglePlaying, videoQualities, videoRef, - wrapperRef, - ...useFullscreen(wrapperRef), - ...useVolume(videoRef), + ...useFullscreen(), + ...useVolume(), } } diff --git a/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx b/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx index 33e6082b..77ff8921 100644 --- a/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx +++ b/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx @@ -1,64 +1,32 @@ -import type { RefObject } from 'react' import { useState, useCallback } from 'react' -import { preparePlayer } from '../helpers' - -export const usePlayingState = (videoRef: RefObject) => { +export const usePlayingState = () => { + const [ready, setReady] = useState(false) const [playing, setPlaying] = useState(false) - const togglePlaying = useCallback(() => { - setPlaying((isPlaying) => { - const video = videoRef.current - if (!video) return false + const onReady = useCallback(() => { + if (ready) return + + setReady(true) + setPlaying(true) + }, [ready]) - const nextIsPlaying = !isPlaying - if (nextIsPlaying) { - video.play() - } else { - video.pause() - } - return nextIsPlaying - }) - }, [videoRef]) + const togglePlaying = useCallback(() => { + setPlaying((isPlaying) => (ready ? !isPlaying : false)) + }, [ready]) - const stopPlaying = useCallback((nextUrl: string = '') => { - preparePlayer({ url: nextUrl, videoRef }) + const stopPlaying = useCallback(() => { setPlaying(false) - }, [videoRef]) + }, []) - const startPlaying = useCallback((url: string, from?: number) => { - preparePlayer({ - from, - url, - videoRef, - }) - // автовоспроизведение со звуком иногда может не сработать - // https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#new-behaviors - videoRef.current?.play().then(() => { + const startPlaying = useCallback(() => { + if (ready) { setPlaying(true) - }) - }, [videoRef]) - - const continuePlaying = useCallback(( - url: string, - from?: number, - ) => { - preparePlayer({ - from, - url, - videoRef, - }) - - setPlaying((isPlaying) => { - if (isPlaying) { - videoRef.current?.play() - } - return isPlaying - }) - }, [videoRef]) + } + }, [ready]) return { - continuePlaying, + onReady, playing, startPlaying, stopPlaying, diff --git a/src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx b/src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx index 3af8f94f..54b7a3cd 100644 --- a/src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx +++ b/src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx @@ -1,43 +1,24 @@ -import type { MutableRefObject, RefObject } from 'react' -import { - useCallback, -} from 'react' - -import throttle from 'lodash/throttle' +import type { MutableRefObject } from 'react' +import { useCallback } from 'react' import type { Chapters } from '../types' -import { - findChapterByProgress, -} from '../helpers' +import { findChapterByProgress } from '../helpers' type Args = { activeChapterIndex: MutableRefObject, chapters: Chapters, - continuePlaying: (url: string) => void, duration: number, - selectedQuality: string, setPlayedProgress: (value: number) => void, - videoRef: RefObject, + setSeek: (value: number) => void, } export const useProgressChangeHandler = ({ activeChapterIndex, chapters, - continuePlaying, duration, - selectedQuality, setPlayedProgress, - videoRef, + setSeek, }: Args) => { - const setPlayerProgress = useCallback( - throttle((value: number) => { - const video = videoRef.current - if (!video) return - video.currentTime = value - }, 100), - [], - ) - const onProgressChange = useCallback((progress: number) => { // значение новой позиции ползунка в миллисекундах const progressMs = progress * duration @@ -49,7 +30,6 @@ export const useProgressChangeHandler = ({ ) // если ползунок остановили на другой главе if (isProgressOnDifferentChapter) { - continuePlaying(chapter.urls[selectedQuality]) // eslint-disable-next-line no-param-reassign activeChapterIndex.current = chapterIndex } @@ -58,14 +38,12 @@ export const useProgressChangeHandler = ({ // отнимаем начало главы на котором остановились от общего прогресса // чтобы получить прогресс текущей главы const chapterProgressSec = (progressMs - chapter.startMs) / 1000 - setPlayerProgress(chapterProgressSec) + setSeek(chapterProgressSec) }, [ - selectedQuality, chapters, duration, - continuePlaying, setPlayedProgress, - setPlayerProgress, + setSeek, activeChapterIndex, ]) diff --git a/src/features/MultiSourcePlayer/hooks/useProgressEvents.tsx b/src/features/MultiSourcePlayer/hooks/useProgressEvents.tsx deleted file mode 100644 index 29e7d963..00000000 --- a/src/features/MultiSourcePlayer/hooks/useProgressEvents.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import type { RefObject } from 'react' - -import size from 'lodash/size' - -import { useEventListener } from 'hooks' - -/** - * Возвращает значение отрезка буфера которое ближе - * к проигрываемому времени - */ -const getLoadedSeconds = (video: HTMLVideoElement | null) => { - const bufferLength = size(video?.buffered) - if (!video || bufferLength === 0) return 0 - - for (let i = 0; i < bufferLength; i++) { - const bufferPosition = bufferLength - 1 - i - if (video.buffered.start(bufferPosition) < video.currentTime) { - return video.buffered.end(bufferPosition) - } - } - return 0 -} - -type Args = { - onChange: (millis: number) => void, - videoRef: RefObject, -} - -/** - * Подписывается на прогресс буферизации и вызывает колбек - * со значением прогресса в милисекундах - */ -export const useLoadedEvent = ({ - onChange, - videoRef, -}: Args) => { - const listenLoadedProgress = () => { - const loadedSeconds = getLoadedSeconds(videoRef.current) - onChange(loadedSeconds * 1000) - } - - useEventListener({ - callback: listenLoadedProgress, - event: 'progress', - target: videoRef, - }) -} - -/** - * Подписывается на прогресс проигывания и вызывает колбек - * со значением прогресса в милисекундах - */ -export const usePlayedEvent = ({ - onChange, - videoRef, -}: Args) => { - const listenPlayedProgresses = () => { - const video = videoRef.current - if (!video) return - onChange(video.currentTime * 1000) - } - - useEventListener({ - callback: listenPlayedProgresses, - event: 'timeupdate', - target: videoRef, - }) -} diff --git a/src/features/MultiSourcePlayer/hooks/useVolume.tsx b/src/features/MultiSourcePlayer/hooks/useVolume.tsx deleted file mode 100644 index 22e9d757..00000000 --- a/src/features/MultiSourcePlayer/hooks/useVolume.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import type { RefObject } from 'react' -import { useEffect, useRef } from 'react' - -import isNumber from 'lodash/isNumber' - -import { useLocalStore } from 'hooks' - -const defaultVolume = 1 - -const useVolumeState = (videoRef: RefObject) => { - const [volume, setVolume] = useLocalStore({ - defaultValue: defaultVolume, - key: 'player_volume', - validator: isNumber, - }) - - useEffect(() => { - if (videoRef.current) { - // eslint-disable-next-line no-param-reassign - videoRef.current.volume = volume - } - }, [volume, videoRef]) - - return [volume, setVolume] as const -} - -export const useVolume = (videoRef: RefObject) => { - const prevVolumeRef = useRef(defaultVolume) - const [volume, setVolume] = useVolumeState(videoRef) - - const onVolumeChange = (value: number) => { - setVolume(value) - } - - const onVolumeClick = () => { - setVolume(volume === 0 ? prevVolumeRef.current : 0) - prevVolumeRef.current = volume || defaultVolume - } - - return { - muted: volume === 0, - onVolumeChange, - onVolumeClick, - volume, - volumeInPercent: volume * 100, - } -} diff --git a/src/features/MultiSourcePlayer/index.tsx b/src/features/MultiSourcePlayer/index.tsx index 16a8ae0b..1f9c3d6a 100644 --- a/src/features/MultiSourcePlayer/index.tsx +++ b/src/features/MultiSourcePlayer/index.tsx @@ -7,32 +7,40 @@ import { PlayStop, Fullscreen, } from 'features/StreamPlayer/styled' +import { VideoPlayer } from 'features/VideoPlayer' import { ProgressBar } from './components/ProgressBar' import { Settings } from './components/Settings' import type { Props } from './hooks' import { useMultiSourcePlayer } from './hooks' -import { Video } from './styled' export const MultiSourcePlayer = (props: Props) => { const { + activeSrc, chapters, duration, isFullscreen, loadedProgress, muted, + onError, onFullscreenClick, + onLoadedProgress, + onPlayedProgress, onPlayerClick, onProgressChange, onQualitySelect, + onReady, onVolumeChange, onVolumeClick, playedProgress, playing, + playNextChapter, + seek, selectedQuality, togglePlaying, videoQualities, videoRef, + volume, volumeInPercent, wrapperRef, } = useMultiSourcePlayer(props) @@ -42,9 +50,18 @@ export const MultiSourcePlayer = (props: Props) => { playing={playing} onClick={onPlayerClick} > -