Ott 1731 live match procs polling (#519)

* refactor(1731): added SubscriptionGuard component (#516)

* Ott 1731 part 2 (#518)

* refactor(1731): events and playlists polling, wip

* fix(1731): 0 duration on playlist button
keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Mirlan 4 years ago
parent c63149f34f
commit e467cd64d1
  1. 4
      src/features/BuyMatchPopup/store/helpers.tsx
  2. 2
      src/features/BuyMatchPopup/store/hooks/index.tsx
  3. 41
      src/features/MatchPage/components/FinishedMatch/hooks/index.tsx
  4. 15
      src/features/MatchPage/components/FinishedMatch/index.tsx
  5. 45
      src/features/MatchPage/components/LiveMatch/hooks/index.tsx
  6. 0
      src/features/MatchPage/components/LiveMatch/hooks/useLastPlayPosition.tsx
  7. 0
      src/features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter.tsx
  8. 0
      src/features/MatchPage/components/LiveMatch/hooks/useUrlParam.tsx
  9. 7
      src/features/MatchPage/components/LiveMatch/index.tsx
  10. 41
      src/features/MatchPage/components/LiveMatchSidePlaylists/index.tsx
  11. 42
      src/features/MatchPage/components/SubscriptionGuard/index.tsx
  12. 1
      src/features/MatchPage/config.tsx
  13. 2
      src/features/MatchPage/helpers/buildPlaylists.tsx
  14. 25
      src/features/MatchPage/helpers/prepareMatchProfile.tsx
  15. 0
      src/features/MatchPage/hooks/useEvents.tsx
  16. 0
      src/features/MatchPage/hooks/useEventsLexics.tsx
  17. 64
      src/features/MatchPage/hooks/useMatchData.tsx
  18. 57
      src/features/MatchPage/hooks/useMatchProfile.tsx
  19. 34
      src/features/MatchPage/index.tsx
  20. 2
      src/features/MatchPage/types.tsx
  21. 34
      src/features/MatchPopup/store/hooks/index.tsx
  22. 4
      src/features/MatchSidePlaylists/components/PlayButton/index.tsx
  23. 8
      src/requests/getMatchPlaylists.tsx

@ -85,7 +85,7 @@ export const transformSubsciptions = ({
[SubscriptionType.Month]: transformByType(SubscriptionType.Month),
[SubscriptionType.Year]: transformByType(SubscriptionType.Year),
[SubscriptionType.PayPerView]: [{
currency: currencySymbols[payPerView.currency_iso],
currency: currencySymbols[payPerView?.currency_iso],
description: {
lexic: 'description_match_live_and_on_demand',
values: {},
@ -94,7 +94,7 @@ export const transformSubsciptions = ({
name: `${team1Name} - ${team2Name}`,
originalObject: payPerView,
pass: 'pass_match_access',
price: payPerView.price,
price: payPerView?.price,
type: SubscriptionType.PayPerView,
}],
}

@ -65,7 +65,7 @@ export const useBuyMatchPopup = () => {
return newState
}), [])
const openPopup = useCallback((matchData: Match | null) => {
const openPopup = useCallback((matchData: Match) => {
setMatch(matchData)
setSteps([Steps.Subscriptions])
}, [])

@ -1,8 +1,3 @@
import { useEffect } from 'react'
import type { MatchInfo } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import { useToggle } from 'hooks/useToggle'
import type { Settings } from 'features/MatchPopup'
@ -10,23 +5,15 @@ import { useMatchPopupStore } from 'features/MatchPopup'
import { usePlayerLogger } from './usePlayerLogger'
import { useEpisodes } from './useEpisodes'
import { useEvents } from './useEvents'
import { useChapters } from './useChapters'
export type Props = {
profile: MatchInfo,
}
export const useFinishedMatch = ({ profile }: Props) => {
export const useFinishedMatch = () => {
const {
fetchMatchPlaylists,
handlePlaylistClick,
matchPlaylists,
selectedPlaylist,
setMatch,
setSettings,
} = useMatchPopupStore()
const { profileId: matchId, sportType } = usePageParams()
const {
close: closeSettingsPopup,
isOpen: isSettingsPopupOpen,
@ -34,33 +21,9 @@ export const useFinishedMatch = ({ profile }: Props) => {
} = useToggle()
const { episodes } = useEpisodes()
const { events, fetchMatchEvents } = useEvents()
const { logPlaylistChange, onPlayingChange } = usePlayerLogger()
useEffect(() => {
if (profile) {
const match = {
calc: false,
id: matchId,
live: false,
sportType,
team1: profile.team1,
team2: profile.team2,
}
setMatch(match)
fetchMatchPlaylists(match)
fetchMatchEvents()
}
}, [
matchId,
profile,
setMatch,
sportType,
fetchMatchPlaylists,
fetchMatchEvents,
])
const setEpisodesSettings = (newSettings: Settings) => {
setSettings(newSettings)
closeSettingsPopup()
@ -75,13 +38,11 @@ export const useFinishedMatch = ({ profile }: Props) => {
return {
closeSettingsPopup,
events,
isSettingsPopupOpen,
onPlayingChange,
onPlaylistSelect,
openSettingsPopup,
playlists: matchPlaylists,
profile,
selectedPlaylist,
setEpisodesSettings,
...useChapters({

@ -2,29 +2,34 @@ import { Fragment } from 'react'
import isEmpty from 'lodash/isEmpty'
import type { Events } from 'requests/getMatchEvents'
import type { MatchInfo } from 'requests/getMatchInfo'
import { MatchSidePlaylists } from 'features/MatchSidePlaylists'
import { MultiSourcePlayer } from 'features/MultiSourcePlayer'
import { SettingsPopup } from '../SettingsPopup'
import type { Props } from './hooks'
import { useFinishedMatch } from './hooks'
import { Container } from '../../styled'
import { Modal } from './styled'
export const FinishedMatch = (props: Props) => {
const { profile } = props
type Props = {
events: Events,
profile: MatchInfo,
}
export const FinishedMatch = ({ events, profile }: Props) => {
const {
chapters,
closeSettingsPopup,
events,
isSettingsPopupOpen,
onPlayingChange,
onPlaylistSelect,
playlists,
selectedPlaylist,
setEpisodesSettings,
} = useFinishedMatch(props)
} = useFinishedMatch()
return (
<Fragment>

@ -1,61 +1,24 @@
import { useCallback, useEffect } from 'react'
import { API_ROOT } from 'config'
import type { MatchInfo } from 'requests/getMatchInfo'
import { usePageParams } from 'hooks/usePageParams'
import { useInterval } from 'hooks/useInterval'
import { usePlayerProgressReporter } from 'features/MatchPage/hooks/usePlayerProgressReporter'
import { useLastPlayPosition } from 'features/MatchPage/hooks/useLastPlayPosition'
import { useUrlParam } from 'features/MatchPage/hooks/useUrlParam'
import { MATCH_UPDATE_INTERVAL } from 'features/MatchPage/config'
import { useMatchPopupStore } from 'features/MatchPopup'
import { useEvents } from '../../FinishedMatch/hooks/useEvents'
import { usePlayerProgressReporter } from './usePlayerProgressReporter'
import { useLastPlayPosition } from './useLastPlayPosition'
import { useUrlParam } from './useUrlParam'
export const useLiveMatch = (profile: MatchInfo) => {
export const useLiveMatch = () => {
const {
fetchMatchPlaylists,
handlePlaylistClick,
matchPlaylists,
selectedPlaylist,
} = useMatchPopupStore()
const { events, fetchMatchEvents } = useEvents()
const { profileId: matchId, sportType } = usePageParams()
const resume = useUrlParam()
const fetchMatchData = useCallback(() => {
if (profile) {
const match = {
...profile,
id: matchId,
sportType,
team1: profile.team1,
team2: profile.team2,
}
fetchMatchPlaylists(match)
fetchMatchEvents()
}
}, [
matchId,
profile,
sportType,
fetchMatchPlaylists,
fetchMatchEvents,
])
useEffect(fetchMatchData, [fetchMatchData])
useInterval({
callback: fetchMatchData,
intervalDuration: MATCH_UPDATE_INTERVAL,
})
return {
events,
matchPlaylists,
onPlaylistSelect: handlePlaylistClick,
resume,

@ -1,5 +1,6 @@
import { Fragment } from 'react'
import type { Events } from 'requests/getMatchEvents'
import type { MatchInfo } from 'requests/getMatchInfo'
import { StreamPlayer } from 'features/StreamPlayer'
@ -9,12 +10,12 @@ import { useLiveMatch } from './hooks'
import { Container } from '../../styled'
type Props = {
events: Events,
profile: MatchInfo,
}
export const LiveMatch = ({ profile }: Props) => {
export const LiveMatch = ({ events, profile }: Props) => {
const {
events,
matchPlaylists,
onPlayerProgressChange,
onPlayingChange,
@ -22,7 +23,7 @@ export const LiveMatch = ({ profile }: Props) => {
resume,
selectedPlaylist,
streamUrl,
} = useLiveMatch(profile)
} = useLiveMatch()
return (
<Fragment>

@ -1,41 +0,0 @@
import styled from 'styled-components/macro'
import { devices, MATCH_SIDE_PLAYLIST_WIDTH } from 'config'
import { T9n } from 'features/T9n'
import { Tab, TabsGroup } from 'features/Common'
const Wrapper = styled.div``
const Container = styled.div`
margin-left: 14px;
margin-right: 18px;
@media ${devices.tablet} {
width: 80%;
margin: 0 auto;
padding: 18px 12px 20px 12px;
}
@media ${devices.mobile} {
width: 100%;
}
`
export const LiveMatchSidePlaylists = () => (
<Wrapper>
<Container>
<TabsGroup buttons={3}>
<Tab width={MATCH_SIDE_PLAYLIST_WIDTH[0]} selected>
<T9n t='watch' />
</Tab>
<Tab width={MATCH_SIDE_PLAYLIST_WIDTH[1]}>
<T9n t='actions' />
</Tab>
<Tab width={MATCH_SIDE_PLAYLIST_WIDTH[2]}>
<T9n t='commentators' />
</Tab>
</TabsGroup>
</Container>
</Wrapper>
)

@ -0,0 +1,42 @@
import type { ReactNode } from 'react'
import { Fragment, useEffect } from 'react'
import type { MatchInfo } from 'requests/getMatchInfo'
import { usePageParams } from 'hooks/usePageParams'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import { prepareMatchProfile } from '../../helpers/prepareMatchProfile'
type Props = {
children: ReactNode,
matchProfile: MatchInfo,
}
export const SubscriptionGuard = ({ children, matchProfile }: Props) => {
const { open: openBuyMatchPopup } = useBuyMatchPopupStore()
const { profileId: matchId, sportType } = usePageParams()
useEffect(() => {
if (matchProfile && !matchProfile.sub) {
const profile = prepareMatchProfile({
matchId,
profile: matchProfile,
sportType,
})
openBuyMatchPopup(profile)
}
}, [
matchId,
openBuyMatchPopup,
matchProfile,
sportType,
])
return (
<Fragment>
{matchProfile?.sub ? children : null}
</Fragment>
)
}

@ -1 +0,0 @@
export const MATCH_UPDATE_INTERVAL = 20000

@ -52,6 +52,8 @@ export const buildPlaylists = (matchPlaylists: MatchPlaylists | null) => {
team1: getPlayerPlaylists(matchPlaylists?.players1),
team2: getPlayerPlaylists(matchPlaylists?.players2),
},
score1: matchPlaylists?.score1 || 0,
score2: matchPlaylists?.score2 || 0,
}
return playlists
}

@ -1,12 +1,12 @@
import type { MatchInfo } from 'requests'
import { SportTypes } from 'config'
import type { SportTypes } from 'config'
import type { Match } from 'features/BuyMatchPopup/types'
type Args = {
matchId: number,
profile: MatchInfo,
profile: NonNullable<MatchInfo>,
sportType: SportTypes,
}
@ -14,15 +14,12 @@ export const prepareMatchProfile = ({
matchId,
profile,
sportType,
}: Args): Match | null => {
if (!profile) return null
return {
calc: profile.calc,
hasVideo: profile.has_video,
id: matchId,
sportType,
team1: profile.team1,
team2: profile.team2,
tournament: profile.tournament,
}
}
}: Args): Match => ({
calc: profile.calc,
hasVideo: profile.has_video,
id: matchId,
sportType,
team1: profile.team1,
team2: profile.team2,
tournament: profile.tournament,
})

@ -0,0 +1,64 @@
import { useEffect, useMemo } from 'react'
import debounce from 'lodash/debounce'
import { usePageParams } from 'hooks/usePageParams'
import { useInterval } from 'hooks/useInterval'
import { useMatchPopupStore } from 'features/MatchPopup'
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, matchPlaylists } = useMatchPopupStore()
const { events, fetchMatchEvents } = useEvents()
const fetchPlaylistsDebounced = useMemo(
() => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY),
[fetchMatchPlaylists],
)
useEffect(() => {
fetchMatchPlaylists({
id: matchId,
sportType,
withFullMatchDuration: !live,
})
fetchMatchEvents()
}, [
live,
matchId,
sportType,
fetchMatchPlaylists,
fetchMatchEvents,
])
const intervalCallback = () => {
fetchPlaylistsDebounced({
id: matchId,
sportType,
withFullMatchDuration: !live,
})
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, matchPlaylists }
}

@ -1,44 +1,53 @@
import {
useCallback,
useEffect,
useState,
useMemo,
} from 'react'
import type { MatchInfo } from 'requests'
import { getMatchInfo } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import { useInterval } from 'hooks/useInterval'
import { MATCH_UPDATE_INTERVAL } from '../config'
import type { Playlists } from '../types'
import { useMatchData } from './useMatchData'
const addScoresFromPlaylists = (
profile: MatchInfo,
playlists: Playlists,
): MatchInfo => (
profile
? {
...profile,
team1: {
...profile?.team1,
score: playlists.score1,
},
team2: {
...profile?.team2,
score: playlists.score2,
},
}
: null
)
export const useMatchProfile = () => {
const [matchProfile, setMatchProfile] = useState<MatchInfo>(null)
const { profileId: matchId, sportType } = usePageParams()
const fetchMatchProfile = useCallback(() => {
useEffect(() => {
getMatchInfo(sportType, matchId).then(setMatchProfile)
},
[sportType, matchId])
}, [sportType, matchId])
const { start, stop } = useInterval({
callback: fetchMatchProfile,
intervalDuration: MATCH_UPDATE_INTERVAL,
startImmediate: false,
})
const { events, matchPlaylists } = useMatchData(matchProfile?.live)
useEffect(fetchMatchProfile, [fetchMatchProfile])
useEffect(() => {
if (matchProfile?.live) {
start()
} else {
stop()
}
}, [
matchProfile,
start,
stop,
])
const profile = useMemo(
() => addScoresFromPlaylists(matchProfile, matchPlaylists),
[matchProfile, matchPlaylists],
)
return matchProfile
return {
events,
profile,
}
}

@ -1,48 +1,26 @@
import { useEffect } from 'react'
import { usePageLogger } from 'hooks/usePageLogger'
import { usePageParams } from 'hooks/usePageParams'
import { ProfileHeader } from 'features/ProfileHeader'
import { UserFavorites } from 'features/UserFavorites'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import {
PageWrapper,
Main,
} from 'features/PageLayout'
import { SubscriptionGuard } from './components/SubscriptionGuard'
import { MatchProfileCard } from './components/MatchProfileCard'
import { FinishedMatch } from './components/FinishedMatch'
import { LiveMatch } from './components/LiveMatch'
import { prepareMatchProfile } from './helpers/prepareMatchProfile'
import { useMatchProfile } from './hooks/useMatchProfile'
import { Wrapper } from './styled'
const MatchPage = () => {
usePageLogger()
const profile = useMatchProfile()
const { open: openBuyMatchPopup } = useBuyMatchPopupStore()
const { profileId: matchId, sportType } = usePageParams()
const { events, profile } = useMatchProfile()
const playFromScout = profile?.has_video && !profile?.live
const playFromOTT = !profile?.has_video && (profile?.live || profile?.storage)
useEffect(() => {
if (profile && !profile?.sub) {
const matchProfile = prepareMatchProfile({
matchId,
profile,
sportType,
})
openBuyMatchPopup(matchProfile)
}
}, [
matchId,
openBuyMatchPopup,
profile,
sportType,
])
return (
<PageWrapper>
<ProfileHeader color='rgb(0,0,0)' height={4.5}>
@ -50,12 +28,12 @@ const MatchPage = () => {
</ProfileHeader>
<Main>
<UserFavorites />
{profile?.sub && (
<SubscriptionGuard matchProfile={profile}>
<Wrapper>
{playFromOTT && <LiveMatch profile={profile} />}
{playFromScout && <FinishedMatch profile={profile} />}
{playFromOTT && <LiveMatch events={events} profile={profile} />}
{playFromScout && <FinishedMatch events={events} profile={profile} />}
</Wrapper>
)}
</SubscriptionGuard>
</Main>
</PageWrapper>
)

@ -60,4 +60,6 @@ export type Playlists = {
team1: PlayerPlaylistOptions,
team2: PlayerPlaylistOptions,
},
score1: number,
score2: number,
}

@ -6,6 +6,8 @@ import {
import isEmpty from 'lodash/isEmpty'
import type { SportTypes } from 'config'
import { getMatchPlaylists } from 'requests'
import { useToggle } from 'hooks'
@ -16,11 +18,17 @@ import { Playlists } from 'features/MatchPage/types'
import { useSettingsState } from './useSettingsState'
import { useSportActions } from './useSportActions'
import type { MatchData, SelectedActions } from '../../types'
import type { MatchData } from '../../types'
import { PlayerPlaylistFormats } from '../../types'
import { usePlayerClickHandler } from './usePlayerClickHandler'
import { usePlaylistLexics } from './usePlaylistLexics'
type ArgsFetchMatchPlaylists = {
id: number,
sportType: SportTypes,
withFullMatchDuration?: boolean,
}
const initialPlaylists = buildPlaylists(null)
export const useMatchPopup = () => {
@ -73,25 +81,29 @@ export const useMatchPopup = () => {
setSelectedPlaylistFormat,
])
const fetchMatchPlaylists = useCallback((
matchData: MatchData,
selected: SelectedActions = [],
) => {
if (!matchData) return
const fetchMatchPlaylists = useCallback(({
id,
sportType,
withFullMatchDuration = true,
}: ArgsFetchMatchPlaylists) => {
getMatchPlaylists({
matchId: matchData.id,
selectedActions: selected,
sportType: matchData.sportType,
matchId: id,
selectedActions: [],
sportType,
withFullMatchDuration,
}).then(fetchLexics)
.then(buildPlaylists)
.then(setMatchPlaylists)
}, [fetchLexics])
const openMatchPopup = (selectedMatch: MatchData) => {
if (!selectedMatch) return
setMatch(selectedMatch)
openPopup()
fetchMatchPlaylists(selectedMatch)
fetchMatchPlaylists({
id: selectedMatch.id,
sportType: selectedMatch.sportType,
})
}
return {

@ -2,6 +2,8 @@ import type { ReactNode } from 'react'
import styled from 'styled-components/macro'
import isUndefined from 'lodash/isUndefined'
import { secondsToHms } from 'helpers'
import { Button, Title } from '../../styled'
@ -37,6 +39,6 @@ export const PlayButton = ({
<Title>
{children}
</Title>
{duration && <Duration>{secondsToHms(duration)}</Duration>}
{!isUndefined(duration) && <Duration>{secondsToHms(duration)}</Duration>}
</Button>
)

@ -15,6 +15,7 @@ type Args = {
matchId: number,
selectedActions: Array<number>,
sportType: SportTypes,
withFullMatchDuration?: boolean,
}
export type Episode = {
@ -66,6 +67,8 @@ export type MatchPlaylists = {
lexics: Lexics,
players1: Players,
players2: Players,
score1: number,
score2: number,
}
type Response = {
@ -76,6 +79,7 @@ export const getMatchPlaylists = async ({
matchId,
selectedActions,
sportType,
withFullMatchDuration,
}: Args) => {
const actions = isEmpty(selectedActions) ? null : selectedActions
@ -94,7 +98,9 @@ export const getMatchPlaylists = async ({
url: `${DATA_URL}/${getSportLexic(sportType)}`,
})
const matchDurationPromise = getFullMatchDuration(sportType, matchId)
const matchDurationPromise = withFullMatchDuration
? getFullMatchDuration(sportType, matchId)
: Promise.resolve(undefined)
const [playlist, fullMatchDuration] = await Promise.all(
[playlistPromise, matchDurationPromise],

Loading…
Cancel
Save