From 6a2bd02403626895e9d43f269908dbd223de1277 Mon Sep 17 00:00:00 2001 From: Mirlan Date: Tue, 13 Oct 2020 18:56:31 +0600 Subject: [PATCH] Ott 475 watch match from last pause (#171) * feat(#475): added match last watch seconds request * feat(#475): resuming stream/full match --- src/config/procedures.tsx | 1 + .../MatchCard/CardLiveHover/index.tsx | 3 +- .../MatchPage/hooks/useLastPlayPosition.tsx | 65 +++++++++++++++++++ src/features/MatchPage/hooks/useVideoData.tsx | 3 + src/features/MatchPage/index.tsx | 13 +++- .../MultiSourcePlayer/helpers/index.tsx | 9 ++- .../MultiSourcePlayer/hooks/index.tsx | 33 ++++++---- .../hooks/usePlayingState.tsx | 12 ++-- src/features/StreamPlayer/hooks/index.tsx | 4 +- src/requests/getMatchLastWatchSeconds.tsx | 43 ++++++++++++ src/requests/index.tsx | 1 + 11 files changed, 160 insertions(+), 27 deletions(-) create mode 100644 src/features/MatchPage/hooks/useLastPlayPosition.tsx create mode 100644 src/requests/getMatchLastWatchSeconds.tsx diff --git a/src/config/procedures.tsx b/src/config/procedures.tsx index b90d06c6..b7bf6ccc 100644 --- a/src/config/procedures.tsx +++ b/src/config/procedures.tsx @@ -15,6 +15,7 @@ export const PROCEDURES = { get_tournament_matches: 'get_tournament_matches', get_user_favorites: 'get_user_favorites', get_user_info: 'get_user_info', + get_user_match_second: 'get_user_match_second', logout_user: 'logout_user', lst_c_country: 'lst_c_country', param_lexical: 'param_lexical', diff --git a/src/features/MatchCard/CardLiveHover/index.tsx b/src/features/MatchCard/CardLiveHover/index.tsx index 38b166d3..62a4ad11 100644 --- a/src/features/MatchCard/CardLiveHover/index.tsx +++ b/src/features/MatchCard/CardLiveHover/index.tsx @@ -3,6 +3,7 @@ import React from 'react' import { Link } from 'react-router-dom' +import { RESUME_KEY } from 'features/MatchPage/hooks/useLastPlayPosition' import { OutsideClick } from 'features/OutsideClick' import { @@ -44,7 +45,7 @@ export const CardLiveHover = ({ - + diff --git a/src/features/MatchPage/hooks/useLastPlayPosition.tsx b/src/features/MatchPage/hooks/useLastPlayPosition.tsx new file mode 100644 index 00000000..ebebf0fa --- /dev/null +++ b/src/features/MatchPage/hooks/useLastPlayPosition.tsx @@ -0,0 +1,65 @@ +import { + useEffect, + useState, + useMemo, +} from 'react' +import { useLocation } from 'react-router' + +import isBoolean from 'lodash/isBoolean' + +import type { LastPlayPosition } from 'requests' +import { getMatchLastWatchSeconds } from 'requests' + +import { + useSportNameParam, + usePageId, + useRequest, +} from 'hooks' + +export const RESUME_KEY = 'resume' + +const readResumeParam = (search: string) => { + const params = new URLSearchParams(search) + const rawValue = params.get(RESUME_KEY) + if (!rawValue) return false + + const value = JSON.parse(rawValue) + return isBoolean(value) && Boolean(value) +} + +const initialPosition = { + half: 0, + second: 0, +} + +export const useLastPlayPosition = () => { + const { search } = useLocation() + const { sportType } = useSportNameParam() + const matchId = usePageId() + const [ + lastPlayPosition, + setPosition, + ] = useState(initialPosition) + const { + isFetching: isLastPlayPositionFetching, + request: requestLastPlayPosition, + } = useRequest(getMatchLastWatchSeconds) + + const resume = useMemo(() => readResumeParam(search), [search]) + + useEffect(() => { + if (resume) { + requestLastPlayPosition(sportType, matchId).then(setPosition) + } + }, [ + sportType, + matchId, + resume, + requestLastPlayPosition, + ]) + + return { + isLastPlayPositionFetching, + lastPlayPosition, + } +} diff --git a/src/features/MatchPage/hooks/useVideoData.tsx b/src/features/MatchPage/hooks/useVideoData.tsx index ea76b245..c8e2c2e5 100644 --- a/src/features/MatchPage/hooks/useVideoData.tsx +++ b/src/features/MatchPage/hooks/useVideoData.tsx @@ -6,6 +6,8 @@ import { getLiveVideos, getVideos } from 'requests' import { useSportNameParam, usePageId } from 'hooks' import { isNull } from 'lodash' +import { useLastPlayPosition } from './useLastPlayPosition' + export const useVideoData = () => { const [videos, setVideos] = useState([]) const [liveVideos, setLiveVideos] = useState([]) @@ -32,5 +34,6 @@ export const useVideoData = () => { return { url: liveVideos[0] || '', videos, + ...useLastPlayPosition(), } } diff --git a/src/features/MatchPage/index.tsx b/src/features/MatchPage/index.tsx index b4e2f20c..523550e5 100644 --- a/src/features/MatchPage/index.tsx +++ b/src/features/MatchPage/index.tsx @@ -13,11 +13,16 @@ import { MainWrapper, Container } from './styled' export const MatchPage = () => { const profile = useMatchProfile() - const { url, videos } = useVideoData() + const { + isLastPlayPositionFetching, + lastPlayPosition, + url, + videos, + } = useVideoData() const { onPlayerProgressChange, onPlayingChange } = usePlayerProgressReporter() - const isLiveMatch = Boolean(url) - const isFinishedMatch = !isEmpty(videos) + const isLiveMatch = Boolean(url) && !isLastPlayPositionFetching + const isFinishedMatch = !isEmpty(videos) && !isLastPlayPositionFetching return ( @@ -29,6 +34,7 @@ export const MatchPage = () => { url={url} onPlayingChange={onPlayingChange} onProgressChange={onPlayerProgressChange} + resumeFrom={lastPlayPosition.second} /> ) } @@ -38,6 +44,7 @@ export const MatchPage = () => { videos={videos} onPlayingChange={onPlayingChange} onProgressChange={onPlayerProgressChange} + resumeFrom={lastPlayPosition} /> ) } diff --git a/src/features/MultiSourcePlayer/helpers/index.tsx b/src/features/MultiSourcePlayer/helpers/index.tsx index f1b24072..c4b4097b 100644 --- a/src/features/MultiSourcePlayer/helpers/index.tsx +++ b/src/features/MultiSourcePlayer/helpers/index.tsx @@ -5,24 +5,23 @@ import findIndex from 'lodash/findIndex' import type { Chapters } from '../types' type Args = { - resume?: boolean, + from?: number, url: string, videoRef: RefObject, } export const preparePlayer = ({ - resume = false, + from = 0, url, videoRef, }: Args) => { const video = videoRef?.current if (!video) return - const wasAtTime = video.currentTime // eslint-disable-next-line no-param-reassign video.src = url - if (resume) { - video.currentTime = wasAtTime + if (from) { + video.currentTime = from } video.load() } diff --git a/src/features/MultiSourcePlayer/hooks/index.tsx b/src/features/MultiSourcePlayer/hooks/index.tsx index df3bdad9..ed9245e0 100644 --- a/src/features/MultiSourcePlayer/hooks/index.tsx +++ b/src/features/MultiSourcePlayer/hooks/index.tsx @@ -8,7 +8,7 @@ import { import size from 'lodash/size' -import type { Videos } from 'requests' +import type { LastPlayPosition, Videos } from 'requests' import { useEventListener } from 'hooks' import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen' @@ -25,16 +25,18 @@ export type Props = { onError?: () => void, onPlayingChange: (playing: boolean) => void, onProgressChange: (seconds: number, period: number) => void, + resumeFrom: LastPlayPosition, videos: Videos, } export const useMultiSourcePlayer = ({ + resumeFrom, onError = () => {}, videos, onPlayingChange, onProgressChange: onProgressChangeCallback, }: Props) => { - const activeChapterIndex = useRef(0) + const activeChapterIndex = useRef(resumeFrom.half) const wrapperRef = useRef(null) const videoRef = useRef(null) const [loadedProgress, setLoadedProgress] = useState(0) @@ -67,13 +69,18 @@ export const useMultiSourcePlayer = ({ videoRef, }) - const getCurrentChapterUrl = useCallback((quality: string = selectedQuality) => ( + const getActiveChapterUrl = useCallback((quality: string = selectedQuality) => ( chapters[activeChapterIndex.current].urls[quality] ), [selectedQuality, chapters]) + const getActiveChapterStart = useCallback(() => ( + chapters[activeChapterIndex.current]?.startMs || 0 + ), [chapters]) + const onQualitySelect = (quality: string) => { + const from = videoRef.current?.currentTime setSelectedQuality(quality) - continuePlaying(getCurrentChapterUrl(quality), true) + continuePlaying(getActiveChapterUrl(quality), from) } const playNextChapter = () => { @@ -81,9 +88,9 @@ export const useMultiSourcePlayer = ({ const isLastChapterPlayed = activeChapterIndex.current === size(chapters) if (isLastChapterPlayed) { activeChapterIndex.current = 0 - stopPlaying(getCurrentChapterUrl()) + stopPlaying(getActiveChapterUrl()) } else { - continuePlaying(getCurrentChapterUrl()) + continuePlaying(getActiveChapterUrl()) } } @@ -93,10 +100,6 @@ export const useMultiSourcePlayer = ({ } } - const getActiveChapterStart = () => ( - chapters[activeChapterIndex.current]?.startMs || 0 - ) - const onLoadedChange = (loadedMs: number) => { const chapterStart = getActiveChapterStart() setLoadedProgress(chapterStart + loadedMs) @@ -108,13 +111,17 @@ export const useMultiSourcePlayer = ({ } useEffect(() => { - const url = getCurrentChapterUrl() + const url = getActiveChapterUrl() + const chapterStartMs = getActiveChapterStart() + const from = resumeFrom.second - (chapterStartMs / 1000) if (url && firstTimeStart) { - startPlaying(url) + startPlaying(url, from) } }, [ firstTimeStart, - getCurrentChapterUrl, + resumeFrom, + getActiveChapterUrl, + getActiveChapterStart, startPlaying, ]) diff --git a/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx b/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx index 0b677755..ecc21f17 100644 --- a/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx +++ b/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx @@ -27,8 +27,12 @@ export const usePlayingState = (videoRef: RefObject) => { setPlaying(false) }, [videoRef]) - const startPlaying = useCallback((url: string) => { - preparePlayer({ url, videoRef }) + const startPlaying = useCallback((url: string, from?: number) => { + preparePlayer({ + from, + url, + videoRef, + }) videoRef.current?.play() setFirstTimeStart(false) setPlaying(true) @@ -36,10 +40,10 @@ export const usePlayingState = (videoRef: RefObject) => { const continuePlaying = useCallback(( url: string, - rememberTime: boolean = false, + from?: number, ) => { preparePlayer({ - resume: rememberTime, + from, url, videoRef, }) diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index a60f65f4..feda4add 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -22,6 +22,7 @@ type ProgressState = { export type Props = { onPlayingChange: (playing: boolean) => void, onProgressChange: (seconds: number) => void, + resumeFrom: number, url: string, } @@ -30,12 +31,13 @@ const toMilliSeconds = (seconds: number) => seconds * 1000 export const useVideoPlayer = ({ onPlayingChange, onProgressChange: progressChangeCallback, + resumeFrom, }: Props) => { const [ready, setReady] = useState(false) const [playing, setPlaying] = useState(false) const [duration, setDuration] = useState(0) const [loadedProgress, setLoadedProgress] = useState(0) - const [playedProgress, setPlayedProgress] = useState(0) + const [playedProgress, setPlayedProgress] = useState(toMilliSeconds(resumeFrom)) const wrapperRef = useRef(null) const playerRef = useRef(null) diff --git a/src/requests/getMatchLastWatchSeconds.tsx b/src/requests/getMatchLastWatchSeconds.tsx new file mode 100644 index 00000000..47aa00d3 --- /dev/null +++ b/src/requests/getMatchLastWatchSeconds.tsx @@ -0,0 +1,43 @@ +import { + DATA_URL, + PROCEDURES, + SportTypes, +} from 'config' +import { callApi, getResponseData } from 'helpers' + +const proc = PROCEDURES.get_user_match_second + +type Response = { + _p_half: number | null, + _p_second: number | null, +} + +export type LastPlayPosition = { + half: number, + second: number, +} + +export const getMatchLastWatchSeconds = async ( + sportType: SportTypes, + matchId: number, +) => { + const config = { + body: { + params: { + _p_match_id: matchId, + _p_sport: sportType, + }, + proc, + }, + } + + const response: Response = await callApi({ + config, + url: DATA_URL, + }).then(getResponseData(proc)) + + return { + half: response?._p_half ?? 0, + second: response?._p_second ?? 0, + } +} diff --git a/src/requests/index.tsx b/src/requests/index.tsx index 265a7fd9..170f59d1 100644 --- a/src/requests/index.tsx +++ b/src/requests/index.tsx @@ -18,3 +18,4 @@ export * from './getVideos' export * from './saveUserInfo' export * from './getPlayerInfo' export * from './getLiveVideos' +export * from './getMatchLastWatchSeconds'