diff --git a/src/config/procedures.tsx b/src/config/procedures.tsx index 834ef537..de9aa619 100644 --- a/src/config/procedures.tsx +++ b/src/config/procedures.tsx @@ -21,7 +21,6 @@ export const PROCEDURES = { get_user_agreemens: 'get_user_agreemens', get_user_favorites: 'get_user_favorites', get_user_info: 'get_user_info', - get_user_match_second: 'get_user_match_second', get_user_payments: 'get_user_payments', get_user_preferences: 'get_user_preferences', get_user_subscribes: 'get_user_subscribes', @@ -37,7 +36,6 @@ export const PROCEDURES = { save_user_custom_subscription: 'save_user_custom_subscription', save_user_favorite: 'save_user_favorite', save_user_info: 'save_user_info', - save_user_match_second: 'save_user_match_second', save_user_page: 'save_user_page', save_user_preferences: 'save_user_preferences', save_user_subscription: 'save_user_subscription', diff --git a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx index e41669e7..1f3a5919 100644 --- a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx @@ -4,11 +4,11 @@ import { API_ROOT } from 'config' import { readToken } from 'helpers/token' -import { usePageParams } from 'hooks/usePageParams' +import { usePageParams } from 'hooks' import { useMatchPageStore } from 'features/MatchPage/store' -import { usePlayerProgressReporter } from './usePlayerProgressReporter' +import { MatchSecondType, usePlayerProgressReporter } from './usePlayerProgressReporter' import { useResumeUrlParam } from './useResumeUrlParam' import { useChapters } from './useChapters' import { usePlaylistLogger } from './usePlaylistLogger' @@ -21,15 +21,17 @@ export const useLiveMatch = () => { setFullMatchPlaylistDuration, } = useMatchPageStore() const { profileId: matchId, sportType } = usePageParams() - const resume = useResumeUrlParam() + const resumeFromParam = useResumeUrlParam() - const fromStartIfStreamPaused = useMemo( - () => (profile && !profile.live ? 0 : undefined), - // deps намеренно оставляем пустым, - // не нужно реагировать на изменение live когда пользователь смотрит матч - // eslint-disable-next-line react-hooks/exhaustive-deps - [], - ) + const resumeFromLocalStorage = useMemo(() => { + const lastSecondLS = localStorage.getItem('matchLastWatchSecond') + const matchesLastWatchSecond: MatchSecondType | null = lastSecondLS && JSON.parse(lastSecondLS) + // undefined означает, что юзер будет смотреть лайв + return profile && !profile.live + ? matchesLastWatchSecond?.[sportType]?.[matchId]?.lastWatchSecond || 0 + : undefined + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [profile]) const { chapters } = useChapters({ profile, @@ -69,7 +71,7 @@ export const useLiveMatch = () => { onPlayerProgressChange, onPlayingChange, onPlaylistSelect, - resume: resume ?? fromStartIfStreamPaused, + resume: resumeFromParam ?? resumeFromLocalStorage, streamUrl: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`, } } diff --git a/src/features/MatchPage/components/LiveMatch/hooks/useLastPlayPosition.tsx b/src/features/MatchPage/components/LiveMatch/hooks/useLastPlayPosition.tsx deleted file mode 100644 index 885f0470..00000000 --- a/src/features/MatchPage/components/LiveMatch/hooks/useLastPlayPosition.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useEffect, useState } from 'react' - -import type { LastPlayPosition } from 'requests' -import { getMatchLastWatchSeconds } from 'requests' - -import { usePageParams } from 'hooks/usePageParams' -import { useRequest } from 'hooks/useRequest' - -const initialPosition = { - half: 0, - second: 0, -} - -export const useLastPlayPosition = () => { - const { profileId: matchId, sportType } = usePageParams() - const [ - lastPlayPosition, - setPosition, - ] = useState(initialPosition) - const { - isFetching: isLastPlayPositionFetching, - request: requestLastPlayPosition, - } = useRequest(getMatchLastWatchSeconds) - - useEffect(() => { - requestLastPlayPosition(sportType, matchId).then(setPosition) - }, [ - sportType, - matchId, - requestLastPlayPosition, - ]) - - return { - isLastPlayPositionFetching, - lastPlayPosition, - } -} diff --git a/src/features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter.tsx b/src/features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter.tsx index b393f8f3..52272ad9 100644 --- a/src/features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter.tsx +++ b/src/features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter.tsx @@ -1,25 +1,41 @@ import { useCallback, useRef } from 'react' -import { reportPlayerProgress } from 'requests' - import { usePageParams } from 'hooks/usePageParams' import { useInterval } from 'hooks/useInterval' const reportRequestInterval = 30000 +export type MatchSecondType = { + [sportType: number]: { + [matchId: number]: { + lastWatchSecond: number, + period: number, + }, + }, +} + export const usePlayerProgressReporter = () => { const { profileId: matchId, sportType } = usePageParams() const playerData = useRef({ period: 0, seconds: 0 }) const intervalCallback = () => { const { period, seconds } = playerData.current - reportPlayerProgress({ - half: period, - matchId, - seconds, - sport: sportType, - }) + + const matchSecond = localStorage.getItem('matchLastWatchSecond') + const matchSecondParsed: MatchSecondType = matchSecond ? JSON.parse(matchSecond) : {} + + localStorage.setItem('matchLastWatchSecond', JSON.stringify({ + ...matchSecondParsed, + [sportType]: { + ...matchSecondParsed?.[sportType], + [matchId]: { + lastWatchSecond: seconds, + period, + }, + }, + })) } + const { start, stop } = useInterval({ callback: intervalCallback, intervalDuration: reportRequestInterval, diff --git a/src/features/MatchPage/components/LiveMatch/hooks/useUrlParam.tsx b/src/features/MatchPage/components/LiveMatch/hooks/useUrlParam.tsx deleted file mode 100644 index 158b1fba..00000000 --- a/src/features/MatchPage/components/LiveMatch/hooks/useUrlParam.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useMemo } from 'react' -import { useLocation } from 'react-router' - -import isNumber from 'lodash/isNumber' - -export const RESUME_KEY = 'resume' - -const readResumeParam = (search: string) => { - const params = new URLSearchParams(search) - const rawValue = params.get(RESUME_KEY) - if (!rawValue) return undefined - - const value = JSON.parse(rawValue) - return isNumber(value) ? value : 0 -} - -export const useUrlParam = () => { - const { search } = useLocation() - - const resume = useMemo(() => readResumeParam(search), [search]) - - return resume -} diff --git a/src/features/MatchPage/store/hooks/useTournamentData.tsx b/src/features/MatchPage/store/hooks/useTournamentData.tsx index 5dca4d7c..e2450632 100644 --- a/src/features/MatchPage/store/hooks/useTournamentData.tsx +++ b/src/features/MatchPage/store/hooks/useTournamentData.tsx @@ -11,6 +11,7 @@ import sortBy from 'lodash/sortBy' import type { Match } from 'features/Matches' import { prepareMatches } from 'features/Matches/helpers/prepareMatches' +import { useAuthStore } from 'features/AuthStore' import type { MatchInfo } from 'requests' import { getTournamentMatches } from 'requests' @@ -23,6 +24,7 @@ import { TournamentData } from '../../types' export const useTournamentData = (matchProfile: MatchInfo) => { const { sportType } = usePageParams() + const { user } = useAuthStore() const [tournamentMatches, setTournamentMatches] = useState>([]) const [matchDates, setMatchDates] = useState>([]) @@ -44,7 +46,7 @@ export const useTournamentData = (matchProfile: MatchInfo) => { )).sort((a, b) => a.getTime() - b.getTime()) setMatchDates(sortedUniq(matchDateList.map((date) => format(date, 'yyyy-MM-dd')))) - setTournamentMatches(sortBy(prepareMatches(matchesBySection.broadcast), ['date'])) + setTournamentMatches(sortBy(prepareMatches(matchesBySection.broadcast, user), ['date'])) })() } }, [ @@ -52,6 +54,7 @@ export const useTournamentData = (matchProfile: MatchInfo) => { sportType, matchProfile?.live, matchProfile?.c_match_calc_status, + user, ]) const tournamentData: TournamentData = useMemo(() => ({ diff --git a/src/features/MatchPopup/components/LiveMatchPlaylist/index.tsx b/src/features/MatchPopup/components/LiveMatchPlaylist/index.tsx index 264f1ed6..72c02503 100644 --- a/src/features/MatchPopup/components/LiveMatchPlaylist/index.tsx +++ b/src/features/MatchPopup/components/LiveMatchPlaylist/index.tsx @@ -3,22 +3,38 @@ import { useState, useEffect } from 'react' import { PAGES } from 'config' import { isMobileDevice } from 'config/userAgent' import { getSportLexic } from 'helpers' -import { getMatchLastWatchSeconds, LastPlayPosition } from 'requests' import { useMatchPopupStore } from 'features/MatchPopup/store' +import type { MatchSecondType } from 'features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter' + +import { useLocalStore } from 'hooks' import { SimplePlaylistButton } from '../SimplePlaylistButton' import { List, Item } from './styled' +type LastPlayPosition = { + half: number, + second: number, +} + export const LiveMatchPlaylist = () => { const [lastPlayPosition, setLastPlayPosition] = useState(null) const { match } = useMatchPopupStore() + const [lastWatchSecond] = useLocalStore({ + key: 'matchLastWatchSecond', + }) + useEffect(() => { if (match) { - getMatchLastWatchSeconds(match?.sportType, match?.id) - .then((lastPlayPositionSecond) => setLastPlayPosition(lastPlayPositionSecond)) + const matchTime = lastWatchSecond?.[match.sportType]?.[match.id] + + setLastPlayPosition({ + half: matchTime?.period || 0, + second: matchTime?.lastWatchSecond || 0, + }) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [match]) if (!match) return null diff --git a/src/features/MatchPopup/components/LiveMatchPopup/styled.tsx b/src/features/MatchPopup/components/LiveMatchPopup/styled.tsx index 08055a9e..1072d709 100644 --- a/src/features/MatchPopup/components/LiveMatchPopup/styled.tsx +++ b/src/features/MatchPopup/components/LiveMatchPopup/styled.tsx @@ -11,7 +11,6 @@ export const Modal = styled(BaseModal)` ${ModalWindow} { width: 27.22rem; - min-height: 14.859rem; padding: 1.416rem 0.71rem; border-radius: 5px; diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index 3865ee1a..ab96e28b 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -1,5 +1,6 @@ -import type { MouseEvent } from 'react' import { + MouseEvent, + useMemo, useRef, useCallback, useEffect, @@ -9,6 +10,7 @@ import { import size from 'lodash/size' import isNumber from 'lodash/isNumber' import isEmpty from 'lodash/isEmpty' +import isUndefined from 'lodash/isUndefined' import Hls from 'hls.js' @@ -105,14 +107,23 @@ export const useVideoPlayer = ({ /** время для сохранения статистики просмотра матча */ 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, + resumeFrom: resumeTimeWithOffset, src: url, }) - const [isLivePlaying, setIsLivePlaying] = useState(false) + // временно закоментил, если ничего не сломается, удалю + // const [isLivePlaying, setIsLivePlaying] = useState(false) const [isPausedTime, setIsPausedTime] = useState(false) const [pausedProgress, setPausedProgress] = useState(0) @@ -247,12 +258,12 @@ export const useVideoPlayer = ({ if (selectedPlaylist?.id !== FULL_GAME_KEY) { restartVideo() - setIsLivePlaying(true) + // setIsLivePlaying(true) } const liveProgressMs = Math.max(fullMatchDuration - BUFFERING_TIME, 0) setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 }) - if (liveProgressMs > 0) setIsLivePlaying(false) + // if (liveProgressMs > 0) setIsLivePlaying(false) // eslint-disable-next-line react-hooks/exhaustive-deps }, [ duration, @@ -290,11 +301,11 @@ export const useVideoPlayer = ({ }, [selectedPlaylist]) useEffect(() => { - if (duration && isLivePlaying && chapters[0]?.isFullMatchChapter) { + if (duration && isUndefined(resumeFrom) && chaptersProps[0]?.isFullMatchChapter) { backToLive() } // eslint-disable-next-line - }, [duration, isLivePlaying]) + }, [chaptersProps]) useEffect(() => { if (duration @@ -321,12 +332,16 @@ export const useVideoPlayer = ({ }, [seek, setPlayerState]) useEffect(() => { - onPlayingChange(playing) + 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, onPlayingChange]) + }, [playing]) const regURL = /\d{6,20}/gi @@ -346,6 +361,16 @@ export const useVideoPlayer = ({ && chapters[0]?.url.match(regURL)?.[0] === chaptersProps[0]?.url.match(regURL)?.[0]) || (isEmpty(chapters) || isEmpty(chaptersProps))) return + if (!isUndefined(resumeFrom) && chaptersProps[0].isFullMatchChapter) { + setPlayerState({ + ...initialState, + chapters: chaptersProps, + playing: true, + seek: resumeFrom + chaptersProps[0].startOffsetMs / 1000, + }) + return + } + setPlayerState({ ...initialState, chapters: chaptersProps, diff --git a/src/requests/getMatchLastWatchSeconds.tsx b/src/requests/getMatchLastWatchSeconds.tsx deleted file mode 100644 index 6f976130..00000000 --- a/src/requests/getMatchLastWatchSeconds.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { - DATA_URL, - PROCEDURES, - -} from 'config' -import { callApi } 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: number, - matchId: number, -) => { - const config = { - body: { - params: { - _p_match_id: matchId, - _p_sport: sportType, - }, - proc, - }, - } - - const response: Response = await callApi({ - config, - url: DATA_URL, - }) - - return { - half: response?._p_half ?? 0, - second: response?._p_second ?? 0, - } -} diff --git a/src/requests/index.tsx b/src/requests/index.tsx index 494e9a3a..a2cff564 100644 --- a/src/requests/index.tsx +++ b/src/requests/index.tsx @@ -14,10 +14,8 @@ export * from './getUserInfo' export * from './getMatchInfo' export * from './getVideos' export * from './getUnauthenticatedMatch' -export * from './reportPlayerProgress' export * from './saveUserInfo' export * from './getPlayerInfo' -export * from './getMatchLastWatchSeconds' export * from './getMatchesPreviewImages' export * from './getSportActions' export * from './getMatchEvents' diff --git a/src/requests/reportPlayerProgress.tsx b/src/requests/reportPlayerProgress.tsx deleted file mode 100644 index 48155428..00000000 --- a/src/requests/reportPlayerProgress.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - DATA_URL, - PROCEDURES, -} from 'config' -import { callApi } from 'helpers' - -const proc = PROCEDURES.save_user_match_second - -type Args = { - half?: number, - matchId: number, - seconds: number, - sport: number, -} - -export const reportPlayerProgress = ({ - half, - matchId, - seconds, - sport, -}: Args) => { - const config = { - body: { - params: { - _p_half: half, - _p_match_id: matchId, - _p_second: seconds, - _p_sport: sport, - }, - proc, - }, - } - - callApi({ - config, - url: DATA_URL, - }) -}