diff --git a/src/features/MultiSourcePlayer/components/Chapters/index.tsx b/src/features/StreamPlayer/components/Chapters/index.tsx similarity index 91% rename from src/features/MultiSourcePlayer/components/Chapters/index.tsx rename to src/features/StreamPlayer/components/Chapters/index.tsx index bde90511..5ff72fa4 100644 --- a/src/features/MultiSourcePlayer/components/Chapters/index.tsx +++ b/src/features/StreamPlayer/components/Chapters/index.tsx @@ -1,14 +1,11 @@ import map from 'lodash/map' -import { - LoadedProgress, - PlayedProgress, -} from 'features/StreamPlayer/components/ProgressBar/styled' - import type { Chapter } from '../../types' import { ChapterList, ChapterContainer, + LoadedProgress, + PlayedProgress, } from './styled' type ChapterWithStyles = Chapter & { diff --git a/src/features/MultiSourcePlayer/components/Chapters/styled.tsx b/src/features/StreamPlayer/components/Chapters/styled.tsx similarity index 55% rename from src/features/MultiSourcePlayer/components/Chapters/styled.tsx rename to src/features/StreamPlayer/components/Chapters/styled.tsx index 54060a58..0caa93a3 100644 --- a/src/features/MultiSourcePlayer/components/Chapters/styled.tsx +++ b/src/features/StreamPlayer/components/Chapters/styled.tsx @@ -16,3 +16,17 @@ export const ChapterContainer = styled.div` margin-right: 3px; } ` + +export const LoadedProgress = styled.div` + position: absolute; + z-index: 1; + background-color: rgba(255, 255, 255, 0.6); + height: 100%; +` + +export const PlayedProgress = styled.div` + position: absolute; + z-index: 2; + background-color: #CC0000; + height: 100%; +` diff --git a/src/features/MultiSourcePlayer/components/ProgressBar/helpers/calculateChapterStyles/__tests__/index.tsx b/src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/__tests__/index.tsx similarity index 100% rename from src/features/MultiSourcePlayer/components/ProgressBar/helpers/calculateChapterStyles/__tests__/index.tsx rename to src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/__tests__/index.tsx diff --git a/src/features/MultiSourcePlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx b/src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx similarity index 94% rename from src/features/MultiSourcePlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx rename to src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx index e0bc9fe7..9a257fb9 100644 --- a/src/features/MultiSourcePlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx +++ b/src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx @@ -3,7 +3,7 @@ import pipe from 'lodash/fp/pipe' import size from 'lodash/fp/size' import slice from 'lodash/fp/slice' -import type { Chapters, Chapter } from 'features/MultiSourcePlayer/types' +import type { Chapters, Chapter } from 'features/StreamPlayer/types' const calculateChapterProgress = (progress: number, chapter: Chapter) => ( Math.min(progress * 100 / chapter.duration, 100) diff --git a/src/features/MultiSourcePlayer/components/ProgressBar/hooks.tsx b/src/features/StreamPlayer/components/ProgressBar/hooks.tsx similarity index 92% rename from src/features/MultiSourcePlayer/components/ProgressBar/hooks.tsx rename to src/features/StreamPlayer/components/ProgressBar/hooks.tsx index b080af87..fea576e8 100644 --- a/src/features/MultiSourcePlayer/components/ProgressBar/hooks.tsx +++ b/src/features/StreamPlayer/components/ProgressBar/hooks.tsx @@ -2,13 +2,13 @@ import { useMemo } from 'react' import { secondsToHms } from 'helpers' -import type { Chapters } from '../../types' +import type { Chapters } from '../../../StreamPlayer/types' import { calculateChapterStyles } from './helpers/calculateChapterStyles' export type Props = { activeChapterIndex: number, allPlayedProgress: number, - chapters?: Chapters, + chapters: Chapters, duration: number, loadedProgress: number, onPlayedProgressChange: (progress: number, seeking: boolean) => void, diff --git a/src/features/StreamPlayer/components/ProgressBar/index.tsx b/src/features/StreamPlayer/components/ProgressBar/index.tsx index 2fc3f39c..741f09ea 100644 --- a/src/features/StreamPlayer/components/ProgressBar/index.tsx +++ b/src/features/StreamPlayer/components/ProgressBar/index.tsx @@ -1,43 +1,27 @@ -import { secondsToHms } from 'helpers' - import { useSlider } from 'features/StreamPlayer/hooks/useSlider' import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip' +import { Scrubber } from 'features/StreamPlayer/components/ProgressBar/styled' -import { - ProgressBarList, - LoadedProgress, - PlayedProgress, - Scrubber, -} from './styled' - -type Props = { - duration: number, - isScrubberVisible?: boolean, - loadedProgress: number, - onPlayedProgressChange: (progress: number) => void, - playedProgress: number, -} +import { Chapters } from '../Chapters' +import type { Props } from './hooks' +import { useProgressBar } from './hooks' +import { ProgressBarList } from './styled' -export const ProgressBar = ({ - duration, - isScrubberVisible, - loadedProgress, - onPlayedProgressChange, - playedProgress, -}: Props) => { +export const ProgressBar = (props: Props) => { + const { onPlayedProgressChange } = props const progressBarRef = useSlider({ onChange: onPlayedProgressChange }) - const loadedFraction = Math.min(loadedProgress * 100 / duration, 100) - const playedFraction = Math.min(playedProgress * 100 / duration, 100) + const { + calculatedChapters, + playedProgressInPercent, + time, + } = useProgressBar(props) return ( - - - {isScrubberVisible === false ? null : ( - - - - )} + + + + ) } diff --git a/src/features/StreamPlayer/components/ProgressBar/stories.tsx b/src/features/StreamPlayer/components/ProgressBar/stories.tsx index d62dfce6..bd301b29 100644 --- a/src/features/StreamPlayer/components/ProgressBar/stories.tsx +++ b/src/features/StreamPlayer/components/ProgressBar/stories.tsx @@ -13,7 +13,7 @@ import { ProgressBar } from '.' const Story = { component: ProgressBar, - title: 'ProgressBar', + title: 'ProgressBarWithChapters', } export default Story @@ -47,9 +47,42 @@ const renderInControls = (progressBarElement: ReactElement) => ( const duration = 70000 +const chapters = [ + { + duration: 30000, + endMs: 30000, + endOffsetMs: 0, + period: 0, + startMs: 0, + startOffsetMs: 0, + url: '', + }, + { + duration: 30000, + endMs: 60000, + endOffsetMs: 0, + period: 0, + startMs: 30000, + startOffsetMs: 0, + url: '', + }, + { + duration: 10000, + endMs: 70000, + endOffsetMs: 0, + period: 0, + startMs: 60000, + startOffsetMs: 0, + url: '', + }, +] + export const Empty = () => renderInControls( renderInControls( export const HalfLoaded = () => renderInControls( renderInControls( export const HalfPlayed = () => renderInControls( renderInControls( export const Loaded40AndPlayed20 = () => renderInControls( = { xhr.open('GET', url.toString()) }, } + +export const REWIND_SECONDS = 5 + +export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000 diff --git a/src/features/MultiSourcePlayer/helpers/index.tsx b/src/features/StreamPlayer/helpers/index.tsx similarity index 82% rename from src/features/MultiSourcePlayer/helpers/index.tsx rename to src/features/StreamPlayer/helpers/index.tsx index 41316ee0..062dd10c 100644 --- a/src/features/MultiSourcePlayer/helpers/index.tsx +++ b/src/features/StreamPlayer/helpers/index.tsx @@ -2,7 +2,7 @@ import { RefObject } from 'react' import findIndex from 'lodash/findIndex' -import type { Chapters, Players } from '../types' +import type { Chapters } from '../types' type Args = { from?: number, @@ -31,5 +31,3 @@ export const findChapterByProgress = (chapters: Chapters, progressMs: number) => startMs <= progressMs && progressMs <= endMs )) ) - -export const getNextPlayer = (player: Players): Players => (player + 1) % 2 diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index b2a90b8b..443889e8 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -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), diff --git a/src/features/MultiSourcePlayer/hooks/useDuration.tsx b/src/features/StreamPlayer/hooks/useDuration.tsx similarity index 100% rename from src/features/MultiSourcePlayer/hooks/useDuration.tsx rename to src/features/StreamPlayer/hooks/useDuration.tsx diff --git a/src/features/StreamPlayer/hooks/useFullscreen.tsx b/src/features/StreamPlayer/hooks/useFullscreen.tsx index ffbfb4a5..2a73e4f1 100644 --- a/src/features/StreamPlayer/hooks/useFullscreen.tsx +++ b/src/features/StreamPlayer/hooks/useFullscreen.tsx @@ -43,11 +43,8 @@ export const useFullscreen = () => { } } - /** - * В обертке могут быть 2 плеера, находим тот который играет сейчас, т.е. не скрыт - */ const getPlayingVideoElement = () => ( - wrapperRef.current?.querySelector('video:not([hidden])') as HTMLVideoElement | null + wrapperRef.current?.querySelector('video') as HTMLVideoElement | null ) const toggleIOSFullscreen = () => { diff --git a/src/features/MultiSourcePlayer/hooks/usePlayingHandlers.tsx b/src/features/StreamPlayer/hooks/usePlayingHandlers.tsx similarity index 63% rename from src/features/MultiSourcePlayer/hooks/usePlayingHandlers.tsx rename to src/features/StreamPlayer/hooks/usePlayingHandlers.tsx index edcc60cd..b223fbf2 100644 --- a/src/features/MultiSourcePlayer/hooks/usePlayingHandlers.tsx +++ b/src/features/StreamPlayer/hooks/usePlayingHandlers.tsx @@ -5,11 +5,11 @@ import isUndefined from 'lodash/isUndefined' import type { SetPartialState } from 'hooks' import type { PlayerState } from '.' -import { getNextPlayer } from '../helpers' +import type { Chapters } from '../types' export const usePlayingHandlers = ( setPlayerState: SetPartialState, - numberOfChapters: number, + chapters: Chapters, ) => { const onReady = useCallback(() => { setPlayerState((state) => ( @@ -43,53 +43,60 @@ export const usePlayingHandlers = ( setPlayerState((state) => { if (!state.ready) return state - const isLastChapter = state.activeChapterIndex + 1 === numberOfChapters - if (isLastChapter || isUndefined(fromMs) || isUndefined(startOffsetMs)) { + const nextChapterIndex = state.activeChapterIndex + 1 + const nextChapter = chapters[nextChapterIndex] + if (!nextChapter) { return { - activeChapterIndex: isLastChapter ? 0 : state.activeChapterIndex + 1, - activePlayer: getNextPlayer(state.activePlayer), + activeChapterIndex: 0, loadedProgress: 0, playedProgress: 0, - playing: isLastChapter ? false : state.playing, + playing: false, + seek: chapters[0].startOffsetMs / 1000, + seeking: false, + } + } + if (isUndefined(fromMs) || isUndefined(startOffsetMs)) { + return { + activeChapterIndex: nextChapterIndex, + loadedProgress: 0, + playedProgress: 0, + seek: nextChapter.startOffsetMs / 1000, } } return { - activeChapterIndex: state.activeChapterIndex + 1, + activeChapterIndex: nextChapterIndex, loadedProgress: 0, playedProgress: fromMs, playing: state.playing, - seek: { - ...state.seek, - [state.activePlayer]: (startOffsetMs + fromMs) / 1000, - }, + seek: (startOffsetMs + fromMs) / 1000, } }) - }, [numberOfChapters, setPlayerState]) + }, [chapters, setPlayerState]) const playPrevChapter = useCallback((fromMs?: number, startOffsetMs?: number) => { setPlayerState((state) => { if (!state.ready || state.activeChapterIndex === 0) return state + const prevChapterIndex = state.activeChapterIndex - 1 + const prevChapter = chapters[prevChapterIndex] if (isUndefined(fromMs) || isUndefined(startOffsetMs)) { return { - activeChapterIndex: state.activeChapterIndex - 1, + activeChapterIndex: prevChapterIndex, loadedProgress: 0, playedProgress: 0, + seek: prevChapter.startOffsetMs / 1000, } } return { - activeChapterIndex: state.activeChapterIndex - 1, + activeChapterIndex: prevChapterIndex, loadedProgress: 0, playedProgress: fromMs, - seek: { - ...state.seek, - [state.activePlayer]: (startOffsetMs + fromMs) / 1000, - }, + seek: (startOffsetMs + fromMs) / 1000, } }) - }, [setPlayerState]) + }, [chapters, setPlayerState]) return { onReady, diff --git a/src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx b/src/features/StreamPlayer/hooks/useProgressChangeHandler.tsx similarity index 94% rename from src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx rename to src/features/StreamPlayer/hooks/useProgressChangeHandler.tsx index ed1883d3..6ec28782 100644 --- a/src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx +++ b/src/features/StreamPlayer/hooks/useProgressChangeHandler.tsx @@ -38,10 +38,7 @@ export const useProgressChangeHandler = ({ return { activeChapterIndex: nextChapter, playedProgress: chapterProgressMs, - seek: { - ...state.seek, - [state.activePlayer]: seekMs / 1000, - }, + seek: seekMs / 1000, seeking, } }) diff --git a/src/features/StreamPlayer/index.tsx b/src/features/StreamPlayer/index.tsx index d6f9bd8b..60e75fd9 100644 --- a/src/features/StreamPlayer/index.tsx +++ b/src/features/StreamPlayer/index.tsx @@ -1,34 +1,53 @@ +import { Fragment } from 'react' + +import { T9n } from 'features/T9n' import { Loader } from 'features/Loader' -import { REWIND_SECONDS } from 'features/MultiSourcePlayer/config' import { VideoPlayer } from 'features/VideoPlayer' -import { Name } from 'features/Name' -import { isMobileDevice } from 'config/userAgent' +import { secondsToHms } from 'helpers' + +import { HOUR_IN_MILLISECONDS, REWIND_SECONDS } from './config' +import { VolumeBar } from './components/VolumeBar' +import { Settings } from './components/Settings' +import { ProgressBar } from './components/ProgressBar' import { PlayerWrapper, - LoaderWrapper, - ControlsGradient, + Controls, + ControlsRow, + ControlsGroup, CenterControls, - Backward, PlayStop, + Fullscreen, + LoaderWrapper, + Backward, Forward, - TeamsDetailsWrapper, + PlaybackTime, + ControlsGradient, + LiveBtn, + ChaptersText, + Next, + Prev, } from './styled' import type { Props } from './hooks' import { useVideoPlayer } from './hooks' -import { Controls } from './components/Controls' export const StreamPlayer = (props: Props) => { - const { profile, url } = props + const { chapters, isLive } = props const { + activeChapterIndex, + allPlayedProgress, backToLive, + buffering, controlsVisible, duration, + isFirstChapterPlaying, isFullscreen, + isLastChapterPlaying, loadedProgress, muted, + numberOfChapters, onDuration, onError, onFullscreenClick, @@ -36,23 +55,30 @@ export const StreamPlayer = (props: Props) => { onMouseEnter, onMouseLeave, onMouseMove, + onPause, + onPlay, onPlayedProgress, onPlayerClick, + onPlaying, onProgressChange, onQualitySelect, + onReady, onTouchEnd, onTouchStart, onVolumeChange, onVolumeClick, + onWaiting, playedProgress, playing, + playNextChapter, + playPrevChapter, ready, rewindBackward, rewindForward, seek, selectedQuality, - startPlaying, togglePlaying, + url, videoQualities, videoRef, volume, @@ -71,11 +97,9 @@ export const StreamPlayer = (props: Props) => { onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} > - {!ready && ( - - - - )} + + + { onPlayedProgress={onPlayedProgress} onDurationChange={onDuration} onEnded={togglePlaying} - onReady={startPlaying} + onReady={onReady} + onPause={onPause} + onPlay={onPlay} + onPlaying={onPlaying} + onWaiting={onWaiting} onError={onError} crossOrigin='use-credentials' /> - - {isMobileDevice && isFullscreen && controlsVisible && profile && ( - - - {` ${profile.team1.score}-${profile.team2.score} `} - - - )} - {ready && ( @@ -120,34 +139,83 @@ export const StreamPlayer = (props: Props) => { )} - - + + + + + + + + { + numberOfChapters > 1 && ( + + playPrevChapter()} + /> + + {activeChapterIndex + 1} / {numberOfChapters} + + playNextChapter()} + /> + + ) + } + + { + isLive + ? ( + + {secondsToHms(allPlayedProgress / 1000)} + + ) + : ( + HOUR_IN_MILLISECONDS ? 150 : 130}> + {secondsToHms(allPlayedProgress / 1000)} + {' / '} + {secondsToHms(duration / 1000)} + + ) + } + {REWIND_SECONDS} + {REWIND_SECONDS} + + + { + isLive && ( + + + + ) + } + + + + + + ) } diff --git a/src/features/StreamPlayer/styled.tsx b/src/features/StreamPlayer/styled.tsx index 9b1b11e4..026438c7 100644 --- a/src/features/StreamPlayer/styled.tsx +++ b/src/features/StreamPlayer/styled.tsx @@ -128,15 +128,23 @@ export const PlayerWrapper = styled.div` : ''}; ` -export const LoaderWrapper = styled.div` +type LoaderWrapperProps = { + buffering: boolean, +} + +export const LoaderWrapper = styled.div` position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1; + transition: opacity 0.3s ease-in-out; + + opacity: ${({ buffering }) => ( + buffering + ? 1 + : 0 + )}; ` export const ButtonBase = styled.button` @@ -324,3 +332,47 @@ export const TeamsDetailsWrapper = styled.div` color: #FFFFFF; z-index: 50; ` + +export const ChaptersText = styled.span` + margin: 0 14px; + font-weight: 500; + font-size: 16px; + color: #fff; + text-align: center; + + ${isMobileDevice + ? css` + margin: 0 5px; + font-size: 12px; + width: 15%; + ` + : ''}; +` + +type PrevProps = { + disabled?: boolean, +} + +export const Prev = styled(ButtonBase)` + width: 29px; + height: 28px; + background-image: url(/images/player-prev.svg); + + ${({ disabled }) => ( + disabled + ? 'opacity: 0.5;' + : '' + )} + + ${isMobileDevice + ? css` + width: 20px; + height: 20px; + ` + : ''}; +` + +export const Next = styled(Prev)` + margin-right: 10px; + transform: rotate(180deg); +` diff --git a/src/features/MultiSourcePlayer/types.tsx b/src/features/StreamPlayer/types.tsx similarity index 55% rename from src/features/MultiSourcePlayer/types.tsx rename to src/features/StreamPlayer/types.tsx index 50c6ab29..088a62b0 100644 --- a/src/features/MultiSourcePlayer/types.tsx +++ b/src/features/StreamPlayer/types.tsx @@ -1,18 +1,11 @@ -export type Urls = { [quality: string]: string } - export type Chapter = { duration: number, endMs: number, endOffsetMs: number, - period: number, + index?: number, startMs: number, startOffsetMs: number, - urls: Urls, + url: string, } export type Chapters = Array - -export enum Players { - PLAYER1 = 0, - PLAYER2 = 1, -} diff --git a/src/features/VideoPlayer/hooks/index.tsx b/src/features/VideoPlayer/hooks/index.tsx index bff60797..d0484023 100644 --- a/src/features/VideoPlayer/hooks/index.tsx +++ b/src/features/VideoPlayer/hooks/index.tsx @@ -25,8 +25,11 @@ export type Props = { onError?: (e?: SyntheticEvent) => void, onLoadedProgress?: (loadedMs: number) => void, onPause?: (e: SyntheticEvent) => void, + onPlay?: (e: SyntheticEvent) => void, onPlayedProgress?: (playedMs: number) => void, + onPlaying?: () => void, onReady?: () => void, + onWaiting?: () => void, playing?: boolean, ref?: Ref, seek?: number | null, diff --git a/src/features/VideoPlayer/index.tsx b/src/features/VideoPlayer/index.tsx index 05dac725..42b51e90 100644 --- a/src/features/VideoPlayer/index.tsx +++ b/src/features/VideoPlayer/index.tsx @@ -15,6 +15,9 @@ export const VideoPlayer = forwardRef((props: Props, re onEnded, onError, onPause, + onPlay, + onPlaying, + onWaiting, src, width, } = props @@ -40,8 +43,11 @@ export const VideoPlayer = forwardRef((props: Props, re onProgress={handleLoadedChange} onEnded={onEnded} onDurationChange={handleDurationChange} + onPlay={onPlay} onPause={onPause} onError={onError} + onWaiting={onWaiting} + onPlaying={onPlaying} crossOrigin={crossOrigin} controls={controls} />