diff --git a/src/features/MatchPage/components/LiveMatch/helpers.tsx b/src/features/MatchPage/components/LiveMatch/helpers.tsx new file mode 100644 index 00000000..01793814 --- /dev/null +++ b/src/features/MatchPage/components/LiveMatch/helpers.tsx @@ -0,0 +1,66 @@ +import last from 'lodash/last' +import reduce from 'lodash/reduce' +import concat from 'lodash/concat' + +import type { Episodes } from 'requests/getMatchPlaylists' + +import type { Chapters } from 'features/StreamPlayer/types' + +import type { MatchPlaylistOption, PlaylistOption } from '../../types' +import { FULL_GAME_KEY } from '../../helpers/buildPlaylists' + +const getFullMatchChapters = (url: string, playlist: MatchPlaylistOption) => { + const duration = (playlist.duration ?? 0) * 1000 + return [ + { + duration, + endMs: duration, + endOffsetMs: duration, + index: 0, + startMs: 0, + startOffsetMs: 0, + url, + }, + ] +} + +const getPlaylistChapters = (url: string, episodes: Episodes) => reduce( + episodes, + ( + acc: Chapters, + episode, + index, + ) => { + if (episode.s >= episode.e) return acc + + const episodeDuration = (episode.e - episode.s) * 1000 + const prevVideoEndMs = last(acc)?.endMs || 0 + const nextChapter = { + duration: episodeDuration, + endMs: prevVideoEndMs + episodeDuration, + endOffsetMs: episode.e * 1000, + index, + startMs: prevVideoEndMs, + startOffsetMs: episode.s * 1000, + url, + } + return concat(acc, nextChapter) + }, + [], +) + +type Args = { + selectedPlaylist?: PlaylistOption, + url: string, +} + +export const buildChapters = ({ + selectedPlaylist, + url, +}: Args): Chapters => { + if (!selectedPlaylist) return [] + if (selectedPlaylist.id === FULL_GAME_KEY) { + return getFullMatchChapters(url, selectedPlaylist) + } + return getPlaylistChapters(url, selectedPlaylist.episodes) +} diff --git a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx index 05cb804b..fe528730 100644 --- a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx @@ -2,25 +2,24 @@ import { useMemo } from 'react' import { API_ROOT } from 'config' -import type { MatchInfo } from 'requests/getMatchInfo' - import { usePageParams } from 'hooks/usePageParams' -import { useMatchPopupStore } from 'features/MatchPopup' +import { useMatchPageStore } from 'features/MatchPage/store' import { usePlayerProgressReporter } from './usePlayerProgressReporter' -import { useLastPlayPosition } from './useLastPlayPosition' -import { useUrlParam } from './useUrlParam' +import { useResumeUrlParam } from './useResumeUrlParam' +import { useChapters } from './useChapters' +import { usePlaylistLogger } from './usePlaylistLogger' -export const useLiveMatch = (profile: MatchInfo) => { +export const useLiveMatch = () => { const { handlePlaylistClick, - matchPlaylists, + profile, selectedPlaylist, - } = useMatchPopupStore() - + setFullMatchPlaylistDuration, + } = useMatchPageStore() const { profileId: matchId, sportType } = usePageParams() - const resume = useUrlParam() + const resume = useResumeUrlParam() const fromStartIfStreamPaused = useMemo( () => (profile && !profile.live ? 0 : undefined), @@ -30,13 +29,49 @@ export const useLiveMatch = (profile: MatchInfo) => { [], ) + const { chapters } = useChapters({ + selectedPlaylist, + url: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`, + }) + + const { + logPlaylistChange, + onPlayingChange: notifyPlaylistLogger, + } = usePlaylistLogger() + + const { + onPlayerProgressChange, + onPlayingChange: notifyProgressLogger, + } = usePlayerProgressReporter() + + const onDurationChange = (duration: number) => { + if (profile?.live) return + setFullMatchPlaylistDuration(duration) + } + + const onPlayingChange = (playing: boolean) => { + notifyPlaylistLogger(playing) + notifyProgressLogger(playing) + } + + const onPlaylistSelect: typeof handlePlaylistClick = (playlist, e) => { + if (selectedPlaylist) { + logPlaylistChange(selectedPlaylist) + } + handlePlaylistClick(playlist, e) + } + return { - matchPlaylists, - onPlaylistSelect: handlePlaylistClick, + chapters, + onDurationChange, + onPlayerProgressChange, + onPlayingChange, + onPlaylistSelect, resume: resume ?? fromStartIfStreamPaused, selectedPlaylist, - streamUrl: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`, - ...usePlayerProgressReporter(), - ...useLastPlayPosition(), + streamUrl: ( + profile?.playbackUrl + || `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8` + ), } } diff --git a/src/features/MatchPage/components/LiveMatch/hooks/useChapters.tsx b/src/features/MatchPage/components/LiveMatch/hooks/useChapters.tsx new file mode 100644 index 00000000..760f686d --- /dev/null +++ b/src/features/MatchPage/components/LiveMatch/hooks/useChapters.tsx @@ -0,0 +1,28 @@ +import { useMemo } from 'react' + +import type { PlaylistOption } from 'features/MatchPage/types' + +import { buildChapters } from '../helpers' + +type Args = { + selectedPlaylist?: PlaylistOption, + url: string, +} + +export const useChapters = ({ + selectedPlaylist, + url, +}: Args) => { + const chapters = useMemo( + () => buildChapters({ + selectedPlaylist, + url, + }), + [ + selectedPlaylist, + url, + ], + ) + + return { chapters } +} diff --git a/src/features/MatchPage/components/FinishedMatch/hooks/usePlayerLogger.tsx b/src/features/MatchPage/components/LiveMatch/hooks/usePlaylistLogger.tsx similarity index 97% rename from src/features/MatchPage/components/FinishedMatch/hooks/usePlayerLogger.tsx rename to src/features/MatchPage/components/LiveMatch/hooks/usePlaylistLogger.tsx index 52c4c978..49659866 100644 --- a/src/features/MatchPage/components/FinishedMatch/hooks/usePlayerLogger.tsx +++ b/src/features/MatchPage/components/LiveMatch/hooks/usePlaylistLogger.tsx @@ -21,7 +21,7 @@ const playlistTypeConfig = { const getInitialData = () => ({ dateVisit: new Date().toISOString(), seconds: 0 }) -export const usePlayerLogger = () => { +export const usePlaylistLogger = () => { const location = useLocation() const { profileId, sportType } = usePageParams() const data = useRef(getInitialData()) diff --git a/src/features/MatchPage/components/LiveMatch/hooks/useResumeUrlParam.tsx b/src/features/MatchPage/components/LiveMatch/hooks/useResumeUrlParam.tsx new file mode 100644 index 00000000..2f799cfa --- /dev/null +++ b/src/features/MatchPage/components/LiveMatch/hooks/useResumeUrlParam.tsx @@ -0,0 +1,23 @@ +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 useResumeUrlParam = () => { + const { search } = useLocation() + + const resume = useMemo(() => readResumeParam(search), [search]) + + return resume +} diff --git a/src/features/MatchPage/components/LiveMatch/index.tsx b/src/features/MatchPage/components/LiveMatch/index.tsx index 72846631..a1f35334 100644 --- a/src/features/MatchPage/components/LiveMatch/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/index.tsx @@ -38,15 +38,13 @@ export const LiveMatch = ({ onPlaylistSelect, resume, streamUrl, - } = useLiveMatch(profile) - - const Player = profile?.youtube_link ? YoutubePlayer : StreamPlayer + } = useLiveMatch() return ( {profile?.youtube_link ? ( - { const playlist = matchPlaylists[key] - const lexic = matchPlaylists.lexics[key] + const lexic = matchPlaylists.lexics[key] ?? '' return { duration: playlist?.dur, episodes: sortBy(playlist?.data, ['h', 's']), diff --git a/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx b/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx index 82c6f79a..e2f4cc5d 100644 --- a/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx +++ b/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx @@ -1,6 +1,7 @@ import { useCallback } from 'react' import isEmpty from 'lodash/isEmpty' +import compact from 'lodash/compact' import values from 'lodash/values' import type { MatchPlaylists } from 'requests' @@ -9,8 +10,8 @@ import { useLexicsStore } from 'features/LexicsStore' export const usePlaylistLexics = () => { const { addLexicsConfig } = useLexicsStore() - const fetchLexics = useCallback((playlist: MatchPlaylists | null) => { - const lexics = values(playlist?.lexics) + const fetchLexics = useCallback((playlist: MatchPlaylists) => { + const lexics = compact(values(playlist.lexics)) if (!isEmpty(lexics)) { addLexicsConfig(lexics) } diff --git a/src/requests/getMatchEvents.tsx b/src/requests/getMatchEvents.tsx index 8b95a494..51af4761 100644 --- a/src/requests/getMatchEvents.tsx +++ b/src/requests/getMatchEvents.tsx @@ -76,5 +76,7 @@ export const getMatchEvents = async ({ url: `${DATA_URL}/${getSportLexic(sportType)}`, }) + if (!response?.data) return Promise.reject(response) + return response?.data || [] } diff --git a/src/requests/getMatchPlaylists.tsx b/src/requests/getMatchPlaylists.tsx index 9c395904..937d9de3 100644 --- a/src/requests/getMatchPlaylists.tsx +++ b/src/requests/getMatchPlaylists.tsx @@ -49,12 +49,12 @@ type Player = { export type Players = Array export type Lexics = { - ball_in_play: number, - full_game: number, - goals: number, - highlights: number, - interview: number, - players: number, + ball_in_play?: number, + full_game?: number, + goals?: number, + highlights?: number, + interview?: number, + players?: number, } export type MatchPlaylists = { @@ -79,7 +79,7 @@ export const getMatchPlaylists = async ({ selectedActions, sportType, withFullMatchDuration, -}: Args) => { +}: Args): Promise => { const actions = isEmpty(selectedActions) ? null : selectedActions const config = { @@ -110,7 +110,19 @@ export const getMatchPlaylists = async ({ dur: fullMatchDuration, } - return playlist.data - ? { ...playlist.data, full_game } - : null + if (playlist.data) { + return { ...playlist.data, full_game } + } + + return { + ball_in_play: {}, + full_game, + goals: {}, + highlights: {}, + lexics: {}, + players1: [], + players2: [], + score1: 0, + score2: 0, + } }