From 95d0194c5b9773cbf6d4338684cecfcb981f77a2 Mon Sep 17 00:00:00 2001 From: Mirlan Date: Mon, 1 Nov 2021 19:26:19 +0600 Subject: [PATCH 01/31] Ott 1701 part 1 (#552) --- .../FinishedMatch/hooks/useEpisodes.tsx | 2 +- .../MatchPage/components/LiveMatch/index.tsx | 52 ++++++++---- .../components/SubscriptionGuard/index.tsx | 7 +- .../MatchPage/helpers/buildPlaylists.tsx | 5 +- src/features/MatchPage/index.tsx | 68 ++++++++------- src/features/MatchPage/store/hooks/index.tsx | 83 +++++++++++++++++++ .../MatchPage/store/hooks/useEvents.tsx | 28 +++++++ .../MatchPage/store/hooks/useEventsLexics.tsx | 25 ++++++ .../MatchPage/store/hooks/useMatchData.tsx | 72 ++++++++++++++++ .../store/hooks/useMatchPlaylists.tsx | 81 ++++++++++++++++++ .../store/hooks/usePlaylistLexics.tsx | 21 +++++ .../store/hooks/useSelectedPlaylist.tsx | 49 +++++++++++ src/features/MatchPage/store/index.tsx | 15 ++-- src/features/MatchPage/types.tsx | 6 +- .../components/EventsList/index.tsx | 2 +- 15 files changed, 448 insertions(+), 68 deletions(-) create mode 100644 src/features/MatchPage/store/hooks/index.tsx create mode 100644 src/features/MatchPage/store/hooks/useEvents.tsx create mode 100644 src/features/MatchPage/store/hooks/useEventsLexics.tsx create mode 100644 src/features/MatchPage/store/hooks/useMatchData.tsx create mode 100644 src/features/MatchPage/store/hooks/useMatchPlaylists.tsx create mode 100644 src/features/MatchPage/store/hooks/usePlaylistLexics.tsx create mode 100644 src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx diff --git a/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx b/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx index 04a2edaa..152638cf 100644 --- a/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx +++ b/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx @@ -41,7 +41,7 @@ export const useEpisodes = () => { }).then(setEpisodes) } else if (playlistOption.type === PlaylistTypes.MATCH || playlistOption.type === PlaylistTypes.EVENT) { - setEpisodes(playlistOption.data) + setEpisodes(playlistOption.episodes) } }, [matchId, sportType]) diff --git a/src/features/MatchPage/components/LiveMatch/index.tsx b/src/features/MatchPage/components/LiveMatch/index.tsx index 00492228..72846631 100644 --- a/src/features/MatchPage/components/LiveMatch/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/index.tsx @@ -1,38 +1,42 @@ import { Fragment } from 'react' -import type { Events } from 'requests/getMatchEvents' -import type { MatchInfo } from 'requests/getMatchInfo' +import isEmpty from 'lodash/isEmpty' +import { useMatchPageStore } from 'features/MatchPage/store' import { StreamPlayer } from 'features/StreamPlayer' import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer' import { MatchSidePlaylists } from 'features/MatchSidePlaylists' -import { isMobileDevice } from 'config/userAgent' - import { Container } from '../../styled' import { useLiveMatch } from './hooks' import { TournamentData } from '../../types' -import { MatchProfileCardMobile } from '../MatchProfileCardMobile' type Props = { - events: Events, - profile: MatchInfo, + // events: Events, + // profile: MatchInfo, tournamentData: TournamentData, } export const LiveMatch = ({ - events, - profile, + // events, + // profile, tournamentData, }: Props) => { const { + events, matchPlaylists, + profile, + selectedPlaylist, + } = useMatchPageStore() + + const { + chapters, + onDurationChange, onPlayerProgressChange, onPlayingChange, onPlaylistSelect, resume, - selectedPlaylist, streamUrl, } = useLiveMatch(profile) @@ -41,14 +45,26 @@ export const LiveMatch = ({ return ( - - {isMobileDevice ? : null} + {profile?.youtube_link ? ( + + ) : ( + !isEmpty(chapters) && ( + + ) + )} { +export const SubscriptionGuard = ({ children }: Props) => { + const { profile: matchProfile } = useMatchPageStore() const { open: openBuyMatchPopup } = useBuyMatchPopupStore() const { profileId: matchId, sportType } = usePageParams() diff --git a/src/features/MatchPage/helpers/buildPlaylists.tsx b/src/features/MatchPage/helpers/buildPlaylists.tsx index 89b17c0d..5e68480f 100644 --- a/src/features/MatchPage/helpers/buildPlaylists.tsx +++ b/src/features/MatchPage/helpers/buildPlaylists.tsx @@ -1,7 +1,7 @@ import map from 'lodash/map' import sortBy from 'lodash/sortBy' -import type { MatchPlaylists, Players } from 'requests' +import type { MatchPlaylists, Players } from 'requests/getMatchPlaylists' import type { Playlists, @@ -27,8 +27,8 @@ const getMatchPlaylists = (matchPlaylists: MatchPlaylists | null): MatchPlaylist const playlist = matchPlaylists[key] const lexic = matchPlaylists.lexics[key] return { - data: sortBy(playlist?.data, ['h', 's']), duration: playlist?.dur, + episodes: sortBy(playlist?.data, ['h', 's']), id: key, lexic, type: PlaylistTypes.MATCH, @@ -39,6 +39,7 @@ const getMatchPlaylists = (matchPlaylists: MatchPlaylists | null): MatchPlaylist const getPlayerPlaylists = (players?: Players): PlayerPlaylistOptions => ( map(players, (player) => ({ ...player, + episodes: [], type: PlaylistTypes.PLAYER, })) ) diff --git a/src/features/MatchPage/index.tsx b/src/features/MatchPage/index.tsx index 703245a7..4779b83b 100644 --- a/src/features/MatchPage/index.tsx +++ b/src/features/MatchPage/index.tsx @@ -11,20 +11,19 @@ import { import { FavoritesActions } from 'requests' import { ProfileTypes } from 'config' -import { isMobileDevice } from 'config/userAgent' import { usePageLogger } from 'hooks/usePageLogger' import { usePageParams } from 'hooks/usePageParams' +import { MatchPageStore } from './store' import { SubscriptionGuard } from './components/SubscriptionGuard' import { MatchProfileCard } from './components/MatchProfileCard' -import { FinishedMatch } from './components/FinishedMatch' import { LiveMatch } from './components/LiveMatch' +import { FinishedMatch } from './components/FinishedMatch' import { useMatchProfile } from './hooks/useMatchProfile' import { Wrapper } from './styled' -import { MatchPageStore } from './store' -const MatchPage = () => { +const MatchPageComponent = () => { usePageLogger() const history = useHistory() const { addRemoveFavorite, userFavorites } = useUserFavoritesStore() @@ -72,35 +71,40 @@ const MatchPage = () => { } return ( - - - - {isMobileDevice ? null : } - -
- - - - {playFromOTT && ( - - )} - {playFromScout && ( - - )} - - -
-
-
+ + + + +
+ + + + {playFromOTT && ( + + )} + {playFromScout && ( + + )} + + + +
+
) } +const MatchPage = () => ( + + + +) + export default MatchPage diff --git a/src/features/MatchPage/store/hooks/index.tsx b/src/features/MatchPage/store/hooks/index.tsx new file mode 100644 index 00000000..20ab2710 --- /dev/null +++ b/src/features/MatchPage/store/hooks/index.tsx @@ -0,0 +1,83 @@ +import { + useEffect, + useState, + useMemo, +} from 'react' + +import type { MatchInfo } from 'requests/getMatchInfo' +import { getMatchInfo } from 'requests/getMatchInfo' + +import { usePageParams } from 'hooks/usePageParams' + +import { parseDate } from 'helpers/parseDate' + +import { useMatchData } from './useMatchData' + +import type { Playlists } from '../../types' + +const addScoresFromPlaylists = ( + profile: MatchInfo, + playlists: Playlists, +): MatchInfo => ( + profile + ? { + ...profile, + team1: { + ...profile?.team1, + score: playlists.score1, + }, + team2: { + ...profile?.team2, + score: playlists.score2, + }, + } + : null +) + +export const useMatchPage = () => { + const [matchProfile, setMatchProfile] = useState(null) + const { profileId: matchId, sportType } = usePageParams() + + useEffect(() => { + getMatchInfo(sportType, matchId).then(setMatchProfile) + }, [sportType, matchId]) + + useEffect(() => { + let getIntervalMatch: ReturnType + if (matchProfile?.live && !matchProfile.youtube_link) { + getIntervalMatch = setInterval( + () => getMatchInfo(sportType, matchId).then(setMatchProfile), 1000 * 60 * 3, + ) + } + return () => clearInterval(getIntervalMatch) + }, [matchProfile, sportType, matchId]) + + const { + events, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } = useMatchData(matchProfile?.live) + + const profile = useMemo( + () => addScoresFromPlaylists(matchProfile, matchPlaylists), + [matchProfile, matchPlaylists], + ) + + const isStarted = useMemo(() => ( + profile?.date + ? parseDate(profile.date) < new Date() + : true + ), [profile?.date]) + + return { + events, + handlePlaylistClick, + isStarted, + matchPlaylists, + profile, + selectedPlaylist, + setFullMatchPlaylistDuration, + } +} diff --git a/src/features/MatchPage/store/hooks/useEvents.tsx b/src/features/MatchPage/store/hooks/useEvents.tsx new file mode 100644 index 00000000..14f7095c --- /dev/null +++ b/src/features/MatchPage/store/hooks/useEvents.tsx @@ -0,0 +1,28 @@ +import { useCallback, useState } from 'react' + +import type { Events } from 'requests' +import { getMatchEvents } from 'requests' + +import { usePageParams } from 'hooks/usePageParams' + +import { useEventsLexics } from './useEventsLexics' + +export const useEvents = () => { + const [events, setEvents] = useState([]) + const { fetchLexics } = useEventsLexics() + const { profileId: matchId, sportType } = usePageParams() + + const fetchMatchEvents = useCallback(() => { + getMatchEvents({ + matchId, + sportType, + }).then(fetchLexics) + .then(setEvents) + }, [ + fetchLexics, + matchId, + sportType, + ]) + + return { events, fetchMatchEvents } +} diff --git a/src/features/MatchPage/store/hooks/useEventsLexics.tsx b/src/features/MatchPage/store/hooks/useEventsLexics.tsx new file mode 100644 index 00000000..cc886d0f --- /dev/null +++ b/src/features/MatchPage/store/hooks/useEventsLexics.tsx @@ -0,0 +1,25 @@ +import { useCallback } from 'react' + +import isEmpty from 'lodash/isEmpty' +import map from 'lodash/map' +import uniq from 'lodash/uniq' + +import type { Events } from 'requests' + +import { useLexicsStore } from 'features/LexicsStore' + +export const useEventsLexics = () => { + const { addLexicsConfig } = useLexicsStore() + + const fetchLexics = useCallback((events: Events) => { + const lexics = uniq(map(events, ({ l }) => l)) + + if (!isEmpty(lexics)) { + addLexicsConfig(lexics) + } + + return events + }, [addLexicsConfig]) + + return { fetchLexics } +} diff --git a/src/features/MatchPage/store/hooks/useMatchData.tsx b/src/features/MatchPage/store/hooks/useMatchData.tsx new file mode 100644 index 00000000..016f24e7 --- /dev/null +++ b/src/features/MatchPage/store/hooks/useMatchData.tsx @@ -0,0 +1,72 @@ +import { useEffect, useMemo } from 'react' + +import debounce from 'lodash/debounce' + +import { usePageParams } from 'hooks/usePageParams' +import { useInterval } from 'hooks/useInterval' + +import { useMatchPlaylists } from './useMatchPlaylists' +import { useEvents } from './useEvents' + +const MATCH_DATA_POLL_INTERVAL = 60000 +const MATCH_PLAYLISTS_DELAY = 5000 + +export const useMatchData = (live: boolean = false) => { + const { profileId: matchId, sportType } = usePageParams() + const { + fetchMatchPlaylists, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } = useMatchPlaylists() + const { events, fetchMatchEvents } = useEvents() + + const fetchPlaylistsDebounced = useMemo( + () => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY), + [fetchMatchPlaylists], + ) + + useEffect(() => { + fetchMatchPlaylists({ + id: matchId, + sportType, + }) + fetchMatchEvents() + }, [ + matchId, + sportType, + fetchMatchPlaylists, + fetchMatchEvents, + ]) + + const intervalCallback = () => { + fetchPlaylistsDebounced({ + id: matchId, + sportType, + }) + fetchMatchEvents() + } + + const { start, stop } = useInterval({ + callback: intervalCallback, + intervalDuration: MATCH_DATA_POLL_INTERVAL, + startImmediate: false, + }) + + useEffect(() => { + if (live) { + start() + } else { + stop() + } + }, [live, start, stop]) + + return { + events, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } +} diff --git a/src/features/MatchPage/store/hooks/useMatchPlaylists.tsx b/src/features/MatchPage/store/hooks/useMatchPlaylists.tsx new file mode 100644 index 00000000..70feee6b --- /dev/null +++ b/src/features/MatchPage/store/hooks/useMatchPlaylists.tsx @@ -0,0 +1,81 @@ +import { + useState, + useCallback, +} from 'react' + +import isEmpty from 'lodash/isEmpty' + +import type { SportTypes } from 'config/sportTypes' + +import { getMatchPlaylists } from 'requests/getMatchPlaylists' + +import type { Playlists } from 'features/MatchPage/types' +import { buildPlaylists } from 'features/MatchPage/helpers/buildPlaylists' + +import { usePlaylistLexics } from './usePlaylistLexics' +import { useSelectedPlaylist } from './useSelectedPlaylist' + +type ArgsFetchMatchPlaylists = { + id: number, + sportType: SportTypes, +} + +const initialPlaylists = buildPlaylists(null) + +export const useMatchPlaylists = () => { + const [matchPlaylists, setMatchPlaylists] = useState(initialPlaylists) + + const { fetchLexics } = usePlaylistLexics() + const { + handlePlaylistClick, + selectedPlaylist, + setSelectedPlaylist, + } = useSelectedPlaylist() + + const setInitialSeletedPlaylist = useCallback((playlists: Playlists) => { + setSelectedPlaylist((playlist) => { + if (!playlist && !isEmpty(playlists.match)) { + return playlists.match[0] + } + return playlist + }) + return playlists + }, [setSelectedPlaylist]) + + const fetchMatchPlaylists = useCallback(({ + id, + sportType, + }: ArgsFetchMatchPlaylists) => { + getMatchPlaylists({ + matchId: id, + selectedActions: [], + sportType, + }).then(fetchLexics) + .then(buildPlaylists) + .then(setInitialSeletedPlaylist) + .then(setMatchPlaylists) + }, [fetchLexics, setInitialSeletedPlaylist]) + + const setFullMatchPlaylistDuration = (duration: number) => { + const playlists = [...matchPlaylists.match] + if (!playlists[0]) return + + playlists[0].duration = duration + setMatchPlaylists({ + ...matchPlaylists, + match: playlists, + }) + + if (selectedPlaylist) { + setSelectedPlaylist({ ...selectedPlaylist }) + } + } + + return { + fetchMatchPlaylists, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } +} diff --git a/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx b/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx new file mode 100644 index 00000000..82c6f79a --- /dev/null +++ b/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx @@ -0,0 +1,21 @@ +import { useCallback } from 'react' + +import isEmpty from 'lodash/isEmpty' +import values from 'lodash/values' + +import type { MatchPlaylists } from 'requests' + +import { useLexicsStore } from 'features/LexicsStore' + +export const usePlaylistLexics = () => { + const { addLexicsConfig } = useLexicsStore() + const fetchLexics = useCallback((playlist: MatchPlaylists | null) => { + const lexics = values(playlist?.lexics) + if (!isEmpty(lexics)) { + addLexicsConfig(lexics) + } + return playlist + }, [addLexicsConfig]) + + return { fetchLexics } +} diff --git a/src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx b/src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx new file mode 100644 index 00000000..ee732c5b --- /dev/null +++ b/src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx @@ -0,0 +1,49 @@ +import type { MouseEvent } from 'react' +import { useState, useCallback } from 'react' + +import { getPlayerPlaylists } from 'requests/getPlayerPlaylists' + +import { usePageParams } from 'hooks/usePageParams' + +import { + PlayerPlaylistOption, + PlaylistOption, + PlaylistTypes, +} from 'features/MatchPage/types' +import { defaultSettings } from 'features/MatchPopup/types' + +export const useSelectedPlaylist = () => { + const { profileId: matchId, sportType } = usePageParams() + const [selectedPlaylist, setSelectedPlaylist] = useState() + + const fetchPlayerEpisodes = useCallback((playlistOption: PlayerPlaylistOption) => ( + getPlayerPlaylists({ + matchId, + playerId: playlistOption.id, + settings: defaultSettings, + sportType, + }) + ), [matchId, sportType]) + + const handlePlaylistClick = useCallback((playlist: PlaylistOption, e?: MouseEvent) => { + e?.stopPropagation() + if (playlist === selectedPlaylist) return + + if (playlist.type === PlaylistTypes.PLAYER) { + fetchPlayerEpisodes(playlist).then((episodes) => { + setSelectedPlaylist({ + ...playlist, + episodes, + }) + }) + } else { + setSelectedPlaylist(playlist) + } + }, [fetchPlayerEpisodes, selectedPlaylist]) + + return { + handlePlaylistClick, + selectedPlaylist, + setSelectedPlaylist, + } +} diff --git a/src/features/MatchPage/store/index.tsx b/src/features/MatchPage/store/index.tsx index 674efdc2..2f5e34bd 100644 --- a/src/features/MatchPage/store/index.tsx +++ b/src/features/MatchPage/store/index.tsx @@ -1,7 +1,10 @@ import type { ReactNode } from 'react' -import { createContext, useContext } from 'react' +import { + createContext, + useContext, +} from 'react' -import { useMatchPage } from '../hooks' +import { useMatchPage } from './hooks' type Context = ReturnType type Props = { children: ReactNode } @@ -9,12 +12,8 @@ type Props = { children: ReactNode } const MatchPageContext = createContext({} as Context) export const MatchPageStore = ({ children }: Props) => { - const value = useMatchPage() - return ( - - {children} - - ) + const lexics = useMatchPage() + return {children} } export const useMatchPageStore = () => useContext(MatchPageContext) diff --git a/src/features/MatchPage/types.tsx b/src/features/MatchPage/types.tsx index 8a5b7892..8d19fd04 100644 --- a/src/features/MatchPage/types.tsx +++ b/src/features/MatchPage/types.tsx @@ -12,8 +12,8 @@ export enum PlaylistTypes { } export type MatchPlaylistOption = { - data: Episodes, duration?: number, + episodes: Episodes, id: MatchPlaylistIds, lexic: number | string, type: PlaylistTypes.MATCH, @@ -21,6 +21,7 @@ export type MatchPlaylistOption = { export type PlayerPlaylistOption = { dur: number, + episodes: Episodes, gk?: boolean, id: number, name_eng: string, @@ -31,6 +32,7 @@ export type PlayerPlaylistOption = { } export type InterviewPlaylistOption = { + episodes: Episodes, id: number, name_eng: string, name_rus: string, @@ -38,7 +40,7 @@ export type InterviewPlaylistOption = { } export type EventPlaylistOption = { - data: Episodes, + episodes: Episodes, id: number, type: PlaylistTypes.EVENT, } diff --git a/src/features/MatchSidePlaylists/components/EventsList/index.tsx b/src/features/MatchSidePlaylists/components/EventsList/index.tsx index 18cdfc5b..3a1eed3a 100644 --- a/src/features/MatchSidePlaylists/components/EventsList/index.tsx +++ b/src/features/MatchSidePlaylists/components/EventsList/index.tsx @@ -45,7 +45,7 @@ export const EventsList = ({ const repeatedEpisodes = event.rep || [] const eventPlaylist = { - data: [{ + episodes: [{ e: event.e, h: event.h, s: event.s, From 950a02c7ae9e79439a08db1407c651b2bbbf069b Mon Sep 17 00:00:00 2001 From: Mirlan Date: Wed, 3 Nov 2021 16:02:32 +0600 Subject: [PATCH 02/31] Ott 1701 part 2 (#553) * refactor(1725): video width 100% * refactor(1701): building playlists and chapters --- .../components/LiveMatch/helpers.tsx | 66 +++++++++++++++++++ .../components/LiveMatch/hooks/index.tsx | 65 +++++++++++++----- .../LiveMatch/hooks/useChapters.tsx | 28 ++++++++ .../hooks/usePlaylistLogger.tsx} | 2 +- .../LiveMatch/hooks/useResumeUrlParam.tsx | 23 +++++++ .../MatchPage/components/LiveMatch/index.tsx | 6 +- .../MatchPage/helpers/buildPlaylists.tsx | 2 +- .../store/hooks/usePlaylistLexics.tsx | 5 +- src/requests/getMatchEvents.tsx | 2 + src/requests/getMatchPlaylists.tsx | 32 ++++++--- 10 files changed, 198 insertions(+), 33 deletions(-) create mode 100644 src/features/MatchPage/components/LiveMatch/helpers.tsx create mode 100644 src/features/MatchPage/components/LiveMatch/hooks/useChapters.tsx rename src/features/MatchPage/components/{FinishedMatch/hooks/usePlayerLogger.tsx => LiveMatch/hooks/usePlaylistLogger.tsx} (97%) create mode 100644 src/features/MatchPage/components/LiveMatch/hooks/useResumeUrlParam.tsx 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, + } } From d3b25de38d1d615f5cc3751dfb9906f5730c6c44 Mon Sep 17 00:00:00 2001 From: Mirlan Date: Wed, 10 Nov 2021 13:32:15 +0600 Subject: [PATCH 03/31] refactor(1701): stream player (#558) --- .../components/Chapters/index.tsx | 7 +- .../components/Chapters/styled.tsx | 14 ++ .../__tests__/index.tsx | 0 .../helpers/calculateChapterStyles/index.tsx | 2 +- .../components/ProgressBar/hooks.tsx | 4 +- .../components/ProgressBar/index.tsx | 48 ++--- .../components/ProgressBar/stories.tsx | 44 +++- .../components/Settings/hooks.tsx | 0 .../components/Settings/index.tsx | 0 .../components/Settings/styled.tsx | 0 src/features/StreamPlayer/config.tsx | 4 + .../helpers/index.tsx | 4 +- src/features/StreamPlayer/hooks/index.tsx | 190 ++++++++++++++---- .../hooks/useDuration.tsx | 0 .../StreamPlayer/hooks/useFullscreen.tsx | 5 +- .../hooks/usePlayingHandlers.tsx | 47 +++-- .../hooks/useProgressChangeHandler.tsx | 5 +- src/features/StreamPlayer/index.tsx | 174 +++++++++++----- src/features/StreamPlayer/styled.tsx | 68 ++++++- .../types.tsx | 11 +- src/features/VideoPlayer/hooks/index.tsx | 3 + src/features/VideoPlayer/index.tsx | 6 + 22 files changed, 456 insertions(+), 180 deletions(-) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/Chapters/index.tsx (91%) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/Chapters/styled.tsx (55%) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/ProgressBar/helpers/calculateChapterStyles/__tests__/index.tsx (100%) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/ProgressBar/helpers/calculateChapterStyles/index.tsx (94%) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/ProgressBar/hooks.tsx (92%) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/Settings/hooks.tsx (100%) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/Settings/index.tsx (100%) rename src/features/{MultiSourcePlayer => StreamPlayer}/components/Settings/styled.tsx (100%) rename src/features/{MultiSourcePlayer => StreamPlayer}/helpers/index.tsx (82%) rename src/features/{MultiSourcePlayer => StreamPlayer}/hooks/useDuration.tsx (100%) rename src/features/{MultiSourcePlayer => StreamPlayer}/hooks/usePlayingHandlers.tsx (63%) rename src/features/{MultiSourcePlayer => StreamPlayer}/hooks/useProgressChangeHandler.tsx (94%) rename src/features/{MultiSourcePlayer => StreamPlayer}/types.tsx (55%) 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} /> From 7e0f83ad2a09db272a31897a45dce8557caa1f10 Mon Sep 17 00:00:00 2001 From: Mirlan Date: Wed, 24 Nov 2021 12:43:55 +0600 Subject: [PATCH 04/31] Ott 1701 part 4 (#573) * refactor(1701): removed MultiSourcePlayer * refactor(1701): duration fix on live stream --- src/features/MatchPage/store/index.tsx | 4 +- .../store/hooks/usePlayerClickHandler.tsx | 25 ---- .../components/ProgressBar/stories.tsx | 126 ------------------ src/features/MultiSourcePlayer/config.tsx | 3 - .../hooks/useVideoQuality.tsx | 38 ------ .../helpers/calculateChapterStyles/index.tsx | 4 +- src/features/StreamPlayer/hooks/index.tsx | 14 ++ .../StreamPlayer/hooks/usePlayingHandlers.tsx | 6 +- src/requests/getFullMatchDuration.tsx | 24 ---- src/requests/getVideos.tsx | 40 ------ 10 files changed, 24 insertions(+), 260 deletions(-) delete mode 100644 src/features/MatchPopup/store/hooks/usePlayerClickHandler.tsx delete mode 100644 src/features/MultiSourcePlayer/components/ProgressBar/stories.tsx delete mode 100644 src/features/MultiSourcePlayer/config.tsx delete mode 100644 src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx delete mode 100644 src/requests/getFullMatchDuration.tsx delete mode 100644 src/requests/getVideos.tsx diff --git a/src/features/MatchPage/store/index.tsx b/src/features/MatchPage/store/index.tsx index 2f5e34bd..778a5853 100644 --- a/src/features/MatchPage/store/index.tsx +++ b/src/features/MatchPage/store/index.tsx @@ -12,8 +12,8 @@ type Props = { children: ReactNode } const MatchPageContext = createContext({} as Context) export const MatchPageStore = ({ children }: Props) => { - const lexics = useMatchPage() - return {children} + const values = useMatchPage() + return {children} } export const useMatchPageStore = () => useContext(MatchPageContext) diff --git a/src/features/MatchPopup/store/hooks/usePlayerClickHandler.tsx b/src/features/MatchPopup/store/hooks/usePlayerClickHandler.tsx deleted file mode 100644 index 9538a0f3..00000000 --- a/src/features/MatchPopup/store/hooks/usePlayerClickHandler.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { MouseEvent } from 'react' -import { useState, useEffect } from 'react' -import { useLocation } from 'react-router' - -import type { PlaylistOption } from 'features/MatchPage/types' - -export const usePlayerClickHandler = () => { - const { pathname } = useLocation() - const [selectedPlaylist, setSelectedPlaylist] = useState() - const handlePlaylistClick = (playlist: PlaylistOption, e?: MouseEvent) => { - e?.stopPropagation() - if (playlist !== selectedPlaylist) { - setSelectedPlaylist(playlist) - } - } - - useEffect(() => { - setSelectedPlaylist(undefined) - }, [pathname]) - - return { - handlePlaylistClick, - selectedPlaylist, - } -} diff --git a/src/features/MultiSourcePlayer/components/ProgressBar/stories.tsx b/src/features/MultiSourcePlayer/components/ProgressBar/stories.tsx deleted file mode 100644 index 807640f6..00000000 --- a/src/features/MultiSourcePlayer/components/ProgressBar/stories.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import type { ReactElement } from 'react' - -import styled from 'styled-components/macro' - -import { VolumeBar } from 'features/StreamPlayer/components/VolumeBar' -import { - Controls, - Fullscreen, - PlayStop, -} from 'features/StreamPlayer/styled' - -import { ProgressBar } from '.' - -const Story = { - component: ProgressBar, - title: 'ProgressBarWithChapters', -} - -export default Story - -const Wrapper = styled.div` - position: relative; - width: 95vw; - height: 50vh; - left: 50%; - transform: translateX(-50%); - background-color: #000; -` - -const callback = () => {} - -const renderInControls = (progressBarElement: ReactElement) => ( - - - - - {progressBarElement} - - - -) - -const duration = 70000 - -const chapters = [ - { - duration: 30000, - endMs: 30000, - endOffsetMs: 0, - period: 0, - startMs: 0, - startOffsetMs: 0, - urls: {}, - }, - { - duration: 30000, - endMs: 60000, - endOffsetMs: 0, - period: 0, - startMs: 30000, - startOffsetMs: 0, - urls: {}, - }, - { - duration: 10000, - endMs: 70000, - endOffsetMs: 0, - period: 0, - startMs: 60000, - startOffsetMs: 0, - urls: {}, - }, -] - -export const Empty = () => renderInControls( - , -) - -export const HalfLoaded = () => renderInControls( - , -) - -export const HalfPlayed = () => renderInControls( - , -) - -export const Loaded40AndPlayed20 = () => renderInControls( - , -) diff --git a/src/features/MultiSourcePlayer/config.tsx b/src/features/MultiSourcePlayer/config.tsx deleted file mode 100644 index 4602985b..00000000 --- a/src/features/MultiSourcePlayer/config.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const REWIND_SECONDS = 5 - -export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000 diff --git a/src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx b/src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx deleted file mode 100644 index 4c810ab0..00000000 --- a/src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import keys from 'lodash/keys' -import uniq from 'lodash/uniq' -import orderBy from 'lodash/orderBy' -import includes from 'lodash/includes' - -import { useLocalStore } from 'hooks' - -import type { Chapters } from '../types' - -const getVideoQualities = (chapters: Chapters) => { - const qualities = uniq(keys(chapters[0]?.urls)) - return orderBy( - qualities, - Number, - 'desc', - ) -} - -export const useVideoQuality = (chapters: Chapters) => { - const videoQualities = getVideoQualities(chapters) - - const qualityValidator = (localStorageQuality: string) => ( - includes(videoQualities, localStorageQuality) - ) - - const [selectedQuality, setSelectedQuality] = useLocalStore({ - // по умолчанию наилучшее качество - defaultValue: videoQualities[0], - key: 'player_quality', - validator: qualityValidator, - }) - - return { - selectedQuality, - setSelectedQuality, - videoQualities, - } -} diff --git a/src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx b/src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx index 9a257fb9..498aac56 100644 --- a/src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx +++ b/src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx @@ -49,7 +49,9 @@ export const calculateChapterStyles = ({ ...chapter, loaded: calculateChapterProgress(loadedProgress, chapter), played: calculateChapterProgress(playedProgress, chapter), - width: chapter.duration * 100 / videoDuration, + width: chapter.duration + ? chapter.duration * 100 / videoDuration + : 100, } return [ ...playedChapters, diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index 443889e8..f0cab226 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -7,10 +7,12 @@ import { import size from 'lodash/size' import isNumber from 'lodash/isNumber' +import isEmpty from 'lodash/isEmpty' import { isIOS } from 'config/userAgent' import { useObjectState } from 'hooks/useObjectState' +import { useEventListener } from 'hooks/useEventListener' import { useVolume } from 'features/VideoPlayer/hooks/useVolume' import { useNoNetworkPopupStore } from 'features/NoNetworkPopup' @@ -197,6 +199,14 @@ export const useVideoPlayer = ({ setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 }) }, [duration, setPlayerState]) + useEventListener({ + callback: (e: KeyboardEvent) => { + if (e.code === 'ArrowLeft') rewindBackward() + else if (e.code === 'ArrowRight') rewindForward() + }, + event: 'keydown', + }) + useEffect(() => { if (isNumber(seek)) { setPlayerState({ seek: undefined }) @@ -216,11 +226,15 @@ export const useVideoPlayer = ({ }, [chapters, setPlayerState]) useEffect(() => { + if (isLive || isEmpty(chapters)) return + const { duration: chapterDuration } = getActiveChapter() if (playedProgress >= chapterDuration && !seeking) { playNextChapter() } }, [ + isLive, + chapters, getActiveChapter, playedProgress, seeking, diff --git a/src/features/StreamPlayer/hooks/usePlayingHandlers.tsx b/src/features/StreamPlayer/hooks/usePlayingHandlers.tsx index b223fbf2..7d7c698f 100644 --- a/src/features/StreamPlayer/hooks/usePlayingHandlers.tsx +++ b/src/features/StreamPlayer/hooks/usePlayingHandlers.tsx @@ -15,7 +15,11 @@ export const usePlayingHandlers = ( setPlayerState((state) => ( state.ready ? state - : { playing: true, ready: true } + : { + buffering: false, + playing: true, + ready: true, + } )) }, [setPlayerState]) diff --git a/src/requests/getFullMatchDuration.tsx b/src/requests/getFullMatchDuration.tsx deleted file mode 100644 index dd9a1e90..00000000 --- a/src/requests/getFullMatchDuration.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import pipe from 'lodash/fp/pipe' -import orderBy from 'lodash/fp/orderBy' -import sumBy from 'lodash/fp/sumBy' -import uniqBy from 'lodash/fp/uniqBy' - -import type { Videos, Video } from './getVideos' -import { getVideos } from './getVideos' - -const calculateDuration = (videos: Videos) => { - const durationMs = pipe( - orderBy(({ quality }: Video) => Number(quality), 'desc'), - uniqBy(({ period }: Video) => period), - sumBy(({ duration }: Video) => duration), - )(videos) - return durationMs / 1000 -} - -/** - * Временный способ получения длительности матча - */ -export const getFullMatchDuration = async (...args: Parameters) => { - const videos = await getVideos(...args) - return calculateDuration(videos) -} diff --git a/src/requests/getVideos.tsx b/src/requests/getVideos.tsx deleted file mode 100644 index 4364de67..00000000 --- a/src/requests/getVideos.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import isEmpty from 'lodash/isEmpty' -import filter from 'lodash/filter' - -import { API_ROOT, SportTypes } from 'config' -import { callApi } from 'helpers' - -const filterByIds = (videos: Videos) => { - const zeroIdVideos = filter(videos, { abc: '0' }) - return isEmpty(zeroIdVideos) ? videos : zeroIdVideos -} - -export type Video = { - /** id дорожки */ - abc: string, - duration: number, - period: number, - quality: string, - start_ms: number, - url: string, -} - -export type Videos = Array