feat(in-428): episodes playback changes #187

Merged
andrey.dekterev merged 1 commits from in-428 into develop 3 years ago
  1. 3
      .gitignore
  2. 4
      Makefile
  3. 2
      package.json
  4. 1
      src/features/MatchPage/components/FinishedMatch/helpers.tsx
  5. 1
      src/features/MatchPage/components/LiveMatch/helpers.tsx
  6. 47
      src/features/MatchPage/store/hooks/index.tsx
  7. 13
      src/features/MatchPage/store/hooks/useMatchData.tsx
  8. 13
      src/features/MatchPage/store/hooks/useMatchPlaylists.tsx
  9. 4
      src/features/MatchPage/store/hooks/usePlayersStats.tsx
  10. 15
      src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx
  11. 2
      src/features/MatchPage/types.tsx
  12. 7
      src/features/MatchSidePlaylists/components/CircleAnimationBar/index.tsx
  13. 16
      src/features/MatchSidePlaylists/components/EventsList/index.tsx
  14. 1
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx
  15. 38
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
  16. 4
      src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
  17. 31
      src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx
  18. 66
      src/features/MultiSourcePlayer/hooks/index.tsx
  19. 33
      src/features/MultiSourcePlayer/index.tsx
  20. 1
      src/features/MultiSourcePlayer/types.tsx
  21. 3
      src/features/PopupComponents/CloseButton/index.tsx
  22. 6
      src/features/StreamPlayer/components/Chapters/styled.tsx
  23. 7
      src/features/StreamPlayer/components/ProgressBar/styled.tsx
  24. 65
      src/features/StreamPlayer/hooks/index.tsx
  25. 37
      src/features/StreamPlayer/index.tsx
  26. 73
      src/features/StreamPlayer/styled.tsx
  27. 4
      src/features/StreamPlayer/types.tsx
  28. 3
      src/requests/getMatchPlaylists.tsx

3
.gitignore vendored

@ -34,3 +34,6 @@ yarn.lock
# IntelliJ IDEA products # IntelliJ IDEA products
.idea .idea
.eslintcache .eslintcache
# ssl keys
*.pem

@ -201,3 +201,7 @@ test:
npm test npm test
.PHONY: test .PHONY: test
generate-ssl-keys:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365

@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"start-https": "export HTTPS=true&&SSL_CRT_FILE=cert.pem&&SSL_KEY_FILE=key.pem react-scripts start", "start-https": "export PORT=443 HTTPS=true&&SSL_CRT_FILE=cert.pem&&SSL_KEY_FILE=key.pem react-scripts start",
"build": "GENERATE_SOURCEMAP=false react-scripts build && gzipper --verbose ./build", "build": "GENERATE_SOURCEMAP=false react-scripts build && gzipper --verbose ./build",
"test": "react-scripts test --testMatch '**/__tests__/*' --passWithNoTests --watchAll=false", "test": "react-scripts test --testMatch '**/__tests__/*' --passWithNoTests --watchAll=false",
"test:watch": "react-scripts test --testMatch '**/__tests__/*'", "test:watch": "react-scripts test --testMatch '**/__tests__/*'",

@ -105,6 +105,7 @@ const getPlaylistChapters = (videos: Array<Video>, episodes: Episodes) => {
const episodeDuration = (episode.e - episode.s) * 1000 const episodeDuration = (episode.e - episode.s) * 1000
const prevVideoEndMs = last(acc)?.endMs || 0 const prevVideoEndMs = last(acc)?.endMs || 0
const nextChapter = { const nextChapter = {
count: episode.c,
duration: episodeDuration, duration: episodeDuration,
endMs: prevVideoEndMs + episodeDuration, endMs: prevVideoEndMs + episodeDuration,
endOffsetMs: episode.e * 1000, endOffsetMs: episode.e * 1000,

@ -86,6 +86,7 @@ const getPlaylistChapters = ({
const prevVideoEndMs = last(acc)?.endMs ?? 0 const prevVideoEndMs = last(acc)?.endMs ?? 0
const nextChapter: Chapter = { const nextChapter: Chapter = {
count: episode.c,
duration: episodeDuration, duration: episodeDuration,
endMs: prevVideoEndMs + episodeDuration, endMs: prevVideoEndMs + episodeDuration,
endOffsetMs: (boundStart + episode.e) * 1000, endOffsetMs: (boundStart + episode.e) * 1000,

@ -16,6 +16,7 @@ import { Tabs } from 'features/MatchSidePlaylists/config'
import { initialCircleAnimation } from 'features/CircleAnimationBar' import { initialCircleAnimation } from 'features/CircleAnimationBar'
import type { TCircleAnimation } from 'features/CircleAnimationBar' import type { TCircleAnimation } from 'features/CircleAnimationBar'
import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config' import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
import { PlaylistTypes } from 'features/MatchPage/types'
import type { MatchInfo } from 'requests/getMatchInfo' import type { MatchInfo } from 'requests/getMatchInfo'
import { getMatchInfo } from 'requests/getMatchInfo' import { getMatchInfo } from 'requests/getMatchInfo'
@ -46,6 +47,12 @@ type PlayingData = {
}, },
} }
type EpisodeInfo = {
paramName: string,
paramValue?: number,
playerOrTeamName: string,
}
const initPlayingData: PlayingData = { const initPlayingData: PlayingData = {
player: { player: {
id: null, id: null,
@ -69,6 +76,11 @@ export const useMatchPage = () => {
const [isPlayFilterEpisodes, setIsPlayingFiltersEpisodes] = useState(false) const [isPlayFilterEpisodes, setIsPlayingFiltersEpisodes] = useState(false)
const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.WATCH) const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.WATCH)
const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation) const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation)
const [episodeInfo, setEpisodeInfo] = useState<EpisodeInfo>({
paramName: '',
paramValue: 0,
playerOrTeamName: '',
})
const isStatsTab = selectedTab === Tabs.STATS const isStatsTab = selectedTab === Tabs.STATS
@ -100,10 +112,14 @@ export const useMatchPage = () => {
matchPlaylists, matchPlaylists,
selectedPlaylist, selectedPlaylist,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
} = useMatchData(matchProfile) } = useMatchData({ matchProfile, selectedTab })
const profile = matchProfile const profile = matchProfile
const isPlayingEpisode = selectedPlaylist.type === PlaylistTypes.EVENT
&& selectedPlaylist.tab === Tabs.STATS
&& selectedTab === Tabs.STATS
const { const {
activeEvents, activeEvents,
activeFirstTeamPlayers, activeFirstTeamPlayers,
@ -214,12 +230,34 @@ export const useMatchPage = () => {
userInfo, userInfo,
]) ])
const disablePlayingEpisodes = () => { const disablePlayingStatsEpisodes = () => {
setStatsIsPlayinFiltersEpisodes(false)
setStatsWatchAllEpisodesTimer(false)
setStatsCircleAnimation(initialCircleAnimation)
setStatsPlaingOrder(0)
}
const disablePlayingPlaysEpisodes = () => {
setIsPlayingFiltersEpisodes(false) setIsPlayingFiltersEpisodes(false)
setWatchAllEpisodesTimer(false) setWatchAllEpisodesTimer(false)
setCircleAnimation(initialCircleAnimation) setCircleAnimation(initialCircleAnimation)
setPlaingOrder(0)
}
const disablePlayingEpisodes = () => {
isStatsTab ? disablePlayingStatsEpisodes() : disablePlayingPlaysEpisodes()
} }
useEffect(() => {
if (selectedPlaylist.type !== PlaylistTypes.EVENT) {
disablePlayingStatsEpisodes()
disablePlayingPlaysEpisodes()
} else {
selectedTab === Tabs.EVENTS ? disablePlayingStatsEpisodes() : disablePlayingPlaysEpisodes()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedPlaylist])
const { const {
circleAnimation: statsCircleAnimation, circleAnimation: statsCircleAnimation,
filteredEvents: statsFilteredEvents, filteredEvents: statsFilteredEvents,
@ -324,6 +362,7 @@ export const useMatchPage = () => {
const currentOrder = order === 0 ? order : plaingOrder const currentOrder = order === 0 ? order : plaingOrder
if (isLastEpisode) { if (isLastEpisode) {
setIsPlayingFiltersEpisodes(false) setIsPlayingFiltersEpisodes(false)
setPlaingOrder(0)
return return
} }
@ -367,6 +406,7 @@ export const useMatchPage = () => {
closePopup, closePopup,
countOfFilters, countOfFilters,
disablePlayingEpisodes, disablePlayingEpisodes,
episodeInfo,
events, events,
filteredEvents: isStatsTab ? statsFilteredEvents : filteredEvents, filteredEvents: isStatsTab ? statsFilteredEvents : filteredEvents,
getFirstClickableParam, getFirstClickableParam,
@ -383,6 +423,7 @@ export const useMatchPage = () => {
isOpenFiltersPopup, isOpenFiltersPopup,
isPlayFilterEpisodes: isStatsTab ? isStatsPlayFilterEpisodes : isPlayFilterEpisodes, isPlayFilterEpisodes: isStatsTab ? isStatsPlayFilterEpisodes : isPlayFilterEpisodes,
isPlayersStatsFetching, isPlayersStatsFetching,
isPlayingEpisode,
isSecondTeamPlayersChecked, isSecondTeamPlayersChecked,
isStarted, isStarted,
isTeamsStatsFetching, isTeamsStatsFetching,
@ -405,6 +446,7 @@ export const useMatchPage = () => {
selectedStatsTable, selectedStatsTable,
selectedTab, selectedTab,
setCircleAnimation: isStatsTab ? setStatsCircleAnimation : setCircleAnimation, setCircleAnimation: isStatsTab ? setStatsCircleAnimation : setCircleAnimation,
setEpisodeInfo,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
setIsPlayingFiltersEpisodes: isStatsTab setIsPlayingFiltersEpisodes: isStatsTab
? setStatsIsPlayinFiltersEpisodes ? setStatsIsPlayinFiltersEpisodes
@ -419,6 +461,7 @@ export const useMatchPage = () => {
setUnreversed, setUnreversed,
setWatchAllEpisodesTimer: isStatsTab ? setStatsWatchAllEpisodesTimer : setWatchAllEpisodesTimer, setWatchAllEpisodesTimer: isStatsTab ? setStatsWatchAllEpisodesTimer : setWatchAllEpisodesTimer,
showProfileCard, showProfileCard,
statsPlaingOrder,
statsType, statsType,
teamsStats, teamsStats,
toggleActiveEvents, toggleActiveEvents,

@ -8,12 +8,12 @@ import debounce from 'lodash/debounce'
import type { MatchInfo } from 'requests/getMatchInfo' import type { MatchInfo } from 'requests/getMatchInfo'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams, useInterval } from 'hooks'
import { useInterval } from 'hooks/useInterval'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { useDuration } from 'features/MultiSourcePlayer/hooks/useDuration' import { useDuration } from 'features/MultiSourcePlayer/hooks/useDuration'
import { useMatchPopupStore } from 'features/MatchPopup' import { useMatchPopupStore } from 'features/MatchPopup'
import { Tabs } from 'features/MatchSidePlaylists/config'
import { useMatchPlaylists } from './useMatchPlaylists' import { useMatchPlaylists } from './useMatchPlaylists'
import { useEvents } from './useEvents' import { useEvents } from './useEvents'
@ -22,7 +22,12 @@ import { initialPlaylist } from './useSelectedPlaylist'
const MATCH_DATA_POLL_INTERVAL = 60000 const MATCH_DATA_POLL_INTERVAL = 60000
const MATCH_PLAYLISTS_DELAY = 5000 const MATCH_PLAYLISTS_DELAY = 5000
export const useMatchData = (profile: MatchInfo) => { type UseMatchDataArgs = {
matchProfile: MatchInfo,
selectedTab: Tabs,
}
export const useMatchData = ({ matchProfile: profile, selectedTab }: UseMatchDataArgs) => {
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()
const { chapters } = useMatchPopupStore() const { chapters } = useMatchPopupStore()
const [matchDuration, setMatchDuration] = useState(0) const [matchDuration, setMatchDuration] = useState(0)
@ -33,7 +38,7 @@ export const useMatchData = (profile: MatchInfo) => {
selectedPlaylist, selectedPlaylist,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
setSelectedPlaylist, setSelectedPlaylist,
} = useMatchPlaylists(profile) } = useMatchPlaylists({ matchProfile: profile, selectedTab })
const { events, fetchMatchEvents } = useEvents() const { events, fetchMatchEvents } = useEvents()

@ -13,6 +13,7 @@ import { usePageParams } from 'hooks/usePageParams'
import type { Playlists } from 'features/MatchPage/types' import type { Playlists } from 'features/MatchPage/types'
import { buildPlaylists, FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' import { buildPlaylists, FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { Tabs } from 'features/MatchSidePlaylists/config'
import { usePlaylistLexics } from './usePlaylistLexics' import { usePlaylistLexics } from './usePlaylistLexics'
import { initialPlaylist, useSelectedPlaylist } from './useSelectedPlaylist' import { initialPlaylist, useSelectedPlaylist } from './useSelectedPlaylist'
@ -23,9 +24,17 @@ type ArgsFetchMatchPlaylists = {
sportType: number, sportType: number,
} }
type UseMatchPlaylistsArgs = {
matchProfile: MatchInfo,
selectedTab: Tabs,
}
const initialPlaylists = buildPlaylists(null) const initialPlaylists = buildPlaylists(null)
export const useMatchPlaylists = (profile: MatchInfo) => { export const useMatchPlaylists = ({
matchProfile: profile,
selectedTab,
}: UseMatchPlaylistsArgs) => {
const [matchPlaylists, setMatchPlaylists] = useState<Playlists>(initialPlaylists) const [matchPlaylists, setMatchPlaylists] = useState<Playlists>(initialPlaylists)
const { fetchLexics } = usePlaylistLexics() const { fetchLexics } = usePlaylistLexics()
@ -34,7 +43,7 @@ export const useMatchPlaylists = (profile: MatchInfo) => {
handlePlaylistClick, handlePlaylistClick,
selectedPlaylist, selectedPlaylist,
setSelectedPlaylist, setSelectedPlaylist,
} = useSelectedPlaylist() } = useSelectedPlaylist({ selectedTab })
const setInitialSeletedPlaylist = useCallback((playlists: Playlists) => { const setInitialSeletedPlaylist = useCallback((playlists: Playlists) => {
setSelectedPlaylist((playlist) => { setSelectedPlaylist((playlist) => {

@ -41,8 +41,6 @@ import { TOUR_COMPLETED_STORAGE_KEY } from 'features/MatchTour'
import { DISPLAYED_PARAMS_COLUMNS } from 'features/MatchSidePlaylists/components/PlayersTable/config' import { DISPLAYED_PARAMS_COLUMNS } from 'features/MatchSidePlaylists/components/PlayersTable/config'
import { useFakeData } from './useFakeData' import { useFakeData } from './useFakeData'
type HeaderParam = Pick<PlayerParam, 'id' | 'lexica_short' | 'lexic'>
const REQUEST_DELAY = 3000 const REQUEST_DELAY = 3000
const STATS_POLL_INTERVAL = 30000 const STATS_POLL_INTERVAL = 30000
@ -90,7 +88,7 @@ export const usePlayersStats = ({
) )
const getParams = useCallback((stats: PlayersStats) => ( const getParams = useCallback((stats: PlayersStats) => (
uniqBy(flatMapDepth(stats, values), 'id') as unknown as Record<string, HeaderParam> uniqBy(flatMapDepth(stats, values), 'id') as Array<PlayerParam>
), []) ), [])
const fetchPlayers = useMemo(() => throttle(async (second?: number) => { const fetchPlayers = useMemo(() => throttle(async (second?: number) => {

@ -14,6 +14,7 @@ import {
} from 'features/MatchPage/types' } from 'features/MatchPage/types'
import { defaultSettings } from 'features/MatchPopup/types' import { defaultSettings } from 'features/MatchPopup/types'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { Tabs } from 'features/MatchSidePlaylists/config'
export const initialPlaylist = { export const initialPlaylist = {
duration: 0, duration: 0,
@ -23,7 +24,11 @@ export const initialPlaylist = {
type: 0, type: 0,
} }
export const useSelectedPlaylist = () => { type UseSelectedPlaylistArgs = {
selectedTab: Tabs,
}
export const useSelectedPlaylist = ({ selectedTab }: UseSelectedPlaylistArgs) => {
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()
const [selectedPlaylist, setSelectedPlaylist] = useState<PlaylistOption>(initialPlaylist) const [selectedPlaylist, setSelectedPlaylist] = useState<PlaylistOption>(initialPlaylist)
@ -48,9 +53,13 @@ export const useSelectedPlaylist = () => {
}) })
}) })
} else { } else {
setSelectedPlaylist(playlist) setSelectedPlaylist({
...playlist,
// @ts-expect-error
tab: selectedTab,
})
} }
}, [fetchPlayerEpisodes, selectedPlaylist]) }, [fetchPlayerEpisodes, selectedPlaylist, selectedTab])
return { return {
handlePlaylistClick, handlePlaylistClick,

@ -1,6 +1,7 @@
import type { Lexics, Episodes } from 'requests' import type { Lexics, Episodes } from 'requests'
import type { Match } from 'features/Matches' import type { Match } from 'features/Matches'
import { Tabs } from 'features/MatchSidePlaylists/config'
import type { MatchPlaylistIds } from './helpers/buildPlaylists' import type { MatchPlaylistIds } from './helpers/buildPlaylists'
@ -42,6 +43,7 @@ export type InterviewPlaylistOption = {
export type EventPlaylistOption = { export type EventPlaylistOption = {
episodes: Episodes, episodes: Episodes,
id: number, id: number,
tab?: Tabs,
type: PlaylistTypes.EVENT, type: PlaylistTypes.EVENT,
} }

@ -7,12 +7,15 @@ export const CircleAnimationBar = styled(CircleAnimationBarBase)`
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
background-color: ${({ theme }) => theme.colors.white};
circle { circle {
stroke: #4086C6; stroke: #5EB2FF;
stroke-width: 3.5;
} }
text { text {
fill: ${({ theme }) => theme.colors.white}; fill: #5EB2FF;
font-weight: 700;
} }
` `

@ -1,4 +1,5 @@
import map from 'lodash/map' import map from 'lodash/map'
// import find from 'lodash/find'
import type { Events, MatchInfo } from 'requests' import type { Events, MatchInfo } from 'requests'
@ -11,6 +12,10 @@ import type {
} from 'features/MatchPage/types' } from 'features/MatchPage/types'
import { PlaylistTypes } from 'features/MatchPage/types' import { PlaylistTypes } from 'features/MatchPage/types'
// import { useMatchPageStore } from 'features/MatchPage/store'
// import { useLexicsStore } from 'features/LexicsStore'
// import { Tabs } from 'features/MatchSidePlaylists/config'
import { isEqual } from '../../helpers' import { isEqual } from '../../helpers'
import { EventButton } from '../EventButton' import { EventButton } from '../EventButton'
import { import {
@ -74,6 +79,17 @@ export const EventsList = ({
if (disablePlayingEpisodes) { if (disablePlayingEpisodes) {
disablePlayingEpisodes() disablePlayingEpisodes()
} }
// const playerName = event.pl?.[`name_${suffix}`]
// const teamName = event.t === profile?.team1.id
// ? profile?.team1[`name_${suffix}`]
// : profile?.team2[`name_${suffix}`]
// setEpisodeInfo({
// paramName: translate(event.l),
// paramValue: filteredEvents.length,
// playerOrTeamName: playerName || teamName || '',
// })
} }
return ( return (

@ -39,6 +39,7 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
tableWrapperRef, tableWrapperRef,
toggleIsExpanded, toggleIsExpanded,
} = useTable({ } = useTable({
getPlayerName,
setSortCondition, setSortCondition,
teamId, teamId,
}) })

@ -21,13 +21,17 @@ import map from 'lodash/map'
import { isMobileDevice, querieKeys } from 'config' import { isMobileDevice, querieKeys } from 'config'
import type { PlayerParam, MatchScore } from 'requests' import type {
PlayerParam,
MatchScore,
Player,
} from 'requests'
import { getStatsEvents } from 'requests' import { getStatsEvents } from 'requests'
import { usePageParams } from 'hooks' import { usePageParams } from 'hooks'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsConfig } from 'features/LexicsStore' import { useLexicsConfig, useLexicsStore } from 'features/LexicsStore'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime' import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import type { SortCondition } from '../types' import type { SortCondition } from '../types'
@ -40,13 +44,13 @@ import {
import { StatsType } from '../../TabStats/config' import { StatsType } from '../../TabStats/config'
type UseTableArgs = { type UseTableArgs = {
getPlayerName: (player: Player) => string,
setSortCondition: Dispatch<SetStateAction<SortCondition>>, setSortCondition: Dispatch<SetStateAction<SortCondition>>,
teamId: number, teamId: number,
} }
type HeaderParam = Pick<PlayerParam, 'id' | 'lexica_short' | 'lexic'>
export const useTable = ({ export const useTable = ({
getPlayerName,
setSortCondition, setSortCondition,
teamId, teamId,
}: UseTableArgs) => { }: UseTableArgs) => {
@ -58,6 +62,7 @@ export const useTable = ({
const [paramColumnWidth, setParamColumnWidth] = useState(PARAM_COLUMN_WIDTH_DEFAULT) const [paramColumnWidth, setParamColumnWidth] = useState(PARAM_COLUMN_WIDTH_DEFAULT)
const { const {
disablePlayingEpisodes,
getParams, getParams,
isExpanded, isExpanded,
playersStats, playersStats,
@ -65,13 +70,14 @@ export const useTable = ({
playStatsEpisodes, playStatsEpisodes,
profile, profile,
reduceTable, reduceTable,
setIsPlayingFiltersEpisodes, setEpisodeInfo,
setPlayingData, setPlayingData,
setWatchAllEpisodesTimer,
statsType, statsType,
toggleIsExpanded, toggleIsExpanded,
} = useMatchPageStore() } = useMatchPageStore()
const { translate } = useLexicsStore()
const { profileId, sportType } = usePageParams() const { profileId, sportType } = usePageParams()
const client = useQueryClient() const client = useQueryClient()
@ -81,7 +87,7 @@ export const useTable = ({
const params = useMemo(() => getParams(playersStats[teamId]), [getParams, playersStats, teamId]) const params = useMemo(() => getParams(playersStats[teamId]), [getParams, playersStats, teamId])
const lexics = useMemo(() => ( const lexics = useMemo(() => (
reduce<HeaderParam, Array<number>>( reduce<PlayerParam, Array<number>>(
values(params), values(params),
(acc, { lexic, lexica_short }) => { (acc, { lexic, lexica_short }) => {
if (lexic) acc.push(lexic) if (lexic) acc.push(lexic)
@ -161,16 +167,15 @@ export const useTable = ({
}) })
} }
const handleParamClick = async (paramId: number, playerId: number) => { const handleParamClick = async (param: PlayerParam, player: Player) => {
setWatchAllEpisodesTimer(false) disablePlayingEpisodes()
setIsPlayingFiltersEpisodes(false)
const videoBounds = matchScore?.video_bounds || profile?.video_bounds const videoBounds = matchScore?.video_bounds || profile?.video_bounds
setPlayingData({ setPlayingData({
player: { player: {
id: playerId, id: player.id,
paramId, paramId: param.id,
}, },
team: { team: {
id: null, id: null,
@ -181,8 +186,8 @@ export const useTable = ({
try { try {
const events = await getStatsEvents({ const events = await getStatsEvents({
matchId: profileId, matchId: profileId,
paramId, paramId: param.id,
playerId, playerId: player.id,
sportType, sportType,
teamId, teamId,
...(statsType === StatsType.CURRENT_STATS && videoBounds && ( ...(statsType === StatsType.CURRENT_STATS && videoBounds && (
@ -191,6 +196,11 @@ export const useTable = ({
}) })
playStatsEpisodes(events) playStatsEpisodes(events)
setEpisodeInfo({
paramName: translate(param.lexic),
paramValue: param.val!,
playerOrTeamName: getPlayerName(player),
})
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
} catch (e) {} } catch (e) {}
} }

@ -180,7 +180,7 @@ export const PlayersTable = (props: PlayersTableProps) => {
const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value) const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value)
const sorted = sortCondition.paramId === param.id const sorted = sortCondition.paramId === param.id
const onClick = () => { const onClick = () => {
clickable && handleParamClick(param.id, player.id) clickable && playerParam && handleParamClick(playerParam, player)
} }
return ( return (
@ -198,7 +198,7 @@ export const PlayersTable = (props: PlayersTableProps) => {
? ( ? (
<CircleAnimationBar <CircleAnimationBar
text={value} text={value}
size={20} size={22}
/> />
) )
: value} : value}

@ -18,6 +18,7 @@ import { usePageParams, useEventListener } from 'hooks'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime' import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsStore } from 'features/LexicsStore'
import { Spotlight, Steps } from 'features/MatchTour' import { Spotlight, Steps } from 'features/MatchTour'
import { StatsType } from '../TabStats/config' import { StatsType } from '../TabStats/config'
@ -46,18 +47,20 @@ export const Cell = ({
const { profileId, sportType } = usePageParams() const { profileId, sportType } = usePageParams()
const { const {
disablePlayingEpisodes,
isClickable, isClickable,
playingData, playingData,
playingProgress, playingProgress,
playStatsEpisodes, playStatsEpisodes,
profile, profile,
setIsPlayingFiltersEpisodes, setEpisodeInfo,
setPlayingData, setPlayingData,
setWatchAllEpisodesTimer,
statsType, statsType,
watchAllEpisodesTimer, watchAllEpisodesTimer,
} = useMatchPageStore() } = useMatchPageStore()
const { shortSuffix, suffix } = useLexicsStore()
const { currentStep, isOpen } = useTour() const { currentStep, isOpen } = useTour()
const client = useQueryClient() const client = useQueryClient()
@ -68,13 +71,12 @@ export const Cell = ({
isNumber(val) ? String(val) : '-' isNumber(val) ? String(val) : '-'
) )
const onParamClick = async (param: Param) => { const onParamClick = async (param: Param, paramName: string) => {
if (!isClickable(param)) return if (!isClickable(param)) return
const videoBounds = matchScore?.video_bounds || profile?.video_bounds disablePlayingEpisodes()
setWatchAllEpisodesTimer(false) const videoBounds = matchScore?.video_bounds || profile?.video_bounds
setIsPlayingFiltersEpisodes(false)
setPlayingData({ setPlayingData({
player: { player: {
@ -99,6 +101,13 @@ export const Cell = ({
}) })
playStatsEpisodes(events) playStatsEpisodes(events)
setEpisodeInfo({
paramName,
paramValue: param.val!,
playerOrTeamName: teamId === profile?.team1.id
? profile.team1[`name_${suffix}`]
: profile?.team2[`name_${suffix}`] || '',
})
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
} catch (e) {} } catch (e) {}
} }
@ -113,7 +122,7 @@ export const Cell = ({
? teamStatItem.param1 ? teamStatItem.param1
: teamStatItem.param2) : teamStatItem.param2)
param && onParamClick(param) param && onParamClick(param, teamStatItem[`name_${shortSuffix}`])
}, },
event: 'keydown', event: 'keydown',
target: paramValueContainerRef, target: paramValueContainerRef,
@ -131,14 +140,14 @@ export const Cell = ({
<ParamValue> <ParamValue>
<CircleAnimationBar <CircleAnimationBar
text={getDisplayedValue(teamStatItem.param1.val)} text={getDisplayedValue(teamStatItem.param1.val)}
size={20} size={22}
/> />
</ParamValue> </ParamValue>
) )
: ( : (
<ParamValue <ParamValue
clickable={isClickable(teamStatItem.param1)} clickable={isClickable(teamStatItem.param1)}
onClick={() => onParamClick(teamStatItem.param1)} onClick={() => onParamClick(teamStatItem.param1, teamStatItem[`name_${shortSuffix}`])}
data-param-id={teamStatItem.param1.id} data-param-id={teamStatItem.param1.id}
hasValue={Boolean(teamStatItem.param1.val)} hasValue={Boolean(teamStatItem.param1.val)}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
@ -162,7 +171,7 @@ export const Cell = ({
<ParamValue> <ParamValue>
<CircleAnimationBar <CircleAnimationBar
text={getDisplayedValue(teamStatItem.param2.val)} text={getDisplayedValue(teamStatItem.param2.val)}
size={20} size={22}
/> />
</ParamValue> </ParamValue>
) )
@ -171,7 +180,7 @@ export const Cell = ({
<Divider>/</Divider> <Divider>/</Divider>
<ParamValue <ParamValue
clickable={isClickable(teamStatItem.param2)} clickable={isClickable(teamStatItem.param2)}
onClick={() => onParamClick(teamStatItem.param2!)} onClick={() => onParamClick(teamStatItem.param2!, teamStatItem[`name_${shortSuffix}`])}
data-param-id={teamStatItem.param2.id} data-param-id={teamStatItem.param2.id}
hasValue={Boolean(teamStatItem.param2.val)} hasValue={Boolean(teamStatItem.param2.val)}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading

@ -3,11 +3,16 @@ import {
useCallback, useCallback,
useEffect, useEffect,
useRef, useRef,
useState,
} from 'react' } from 'react'
import { useTour } from '@reactour/tour' import { useTour } from '@reactour/tour'
import size from 'lodash/size' import size from 'lodash/size'
import findIndex from 'lodash/findIndex'
import inRange from 'lodash/inRange'
import sum from 'lodash/sum'
import values from 'lodash/values'
import { KEYBOARD_KEYS } from 'config' import { KEYBOARD_KEYS } from 'config'
@ -77,10 +82,17 @@ export const useMultiSourcePlayer = ({
pausedData, pausedData,
setPausedData, setPausedData,
}: Props) => { }: Props) => {
const [currentPlayingOrder, setCurrentPlayingOrder] = useState(0)
const ordersObj = useRef<Record<string, number>>({})
const { const {
disablePlayingEpisodes,
handlePlaylistClick, handlePlaylistClick,
isPlayFilterEpisodes, isPlayFilterEpisodes,
isPlayingEpisode,
matchPlaylists, matchPlaylists,
plaingOrder,
playingProgress,
playNextEpisode, playNextEpisode,
selectedPlaylist, selectedPlaylist,
setCircleAnimation, setCircleAnimation,
@ -261,6 +273,11 @@ export const useMultiSourcePlayer = ({
setPlayerState({ playing: false }) setPlayerState({ playing: false })
} }
const stopPlayingEpisodes = () => {
disablePlayingEpisodes()
backToPausedTime()
}
useEffect(() => { useEffect(() => {
onPlayingChange(playing) onPlayingChange(playing)
}, [playing, onPlayingChange]) }, [playing, onPlayingChange])
@ -337,13 +354,13 @@ export const useMultiSourcePlayer = ({
]) ])
useEffect(() => { useEffect(() => {
if (ready && videoRef) { if (ready && videoRef && !isPlayingEpisode) {
videoRef.current?.scrollIntoView({ videoRef.current?.scrollIntoView({
behavior: 'smooth', behavior: 'smooth',
block: 'start', block: 'start',
}) })
} }
}, [ready, videoRef]) }, [ready, videoRef, isPlayingEpisode])
// ведем статистику просмотра матча // ведем статистику просмотра матча
const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({ const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({
@ -373,12 +390,56 @@ export const useMultiSourcePlayer = ({
profileId, profileId,
]) ])
useEffect(() => {
const splitEpisodeDuration = (n: number, m: number) => {
const result: Array<[number, number]> = []
for (let i = 0; i < n; i += m) {
const start = i
const end = Math.min(i + m, n)
result.push([start, end])
}
return result
}
const getCurrentPlayingOrder = () => {
const { count, duration: dur } = getActiveChapter()
const arr = splitEpisodeDuration(dur, dur / count!)
const index = findIndex(arr, (elem) => inRange(
playingProgress * 1000,
elem[0],
elem[1] + 1,
))
if (index !== -1 && ordersObj.current[plaingOrder] !== index) {
ordersObj.current = {
...ordersObj.current,
[plaingOrder]: index,
}
}
return plaingOrder + sum(values(ordersObj.current))
}
setCurrentPlayingOrder(getCurrentPlayingOrder())
}, [getActiveChapter, isPlayingEpisode, plaingOrder, playingProgress])
useEffect(() => {
if (!isPlayingEpisode) {
ordersObj.current = {}
}
}, [isPlayingEpisode])
return { return {
activeChapterIndex, activeChapterIndex,
activePlayer, activePlayer,
activeSrc: getChapterUrl(activeChapterIndex), activeSrc: getChapterUrl(activeChapterIndex),
allPlayedProgress: playedProgress + getActiveChapter().startMs, allPlayedProgress: playedProgress + getActiveChapter().startMs,
chapters, chapters,
currentPlayingOrder,
duration, duration,
isFirstChapterPlaying, isFirstChapterPlaying,
isFullscreen, isFullscreen,
@ -405,6 +466,7 @@ export const useMultiSourcePlayer = ({
rewindForward, rewindForward,
seek, seek,
selectedQuality, selectedQuality,
stopPlayingEpisodes,
togglePlaying, togglePlaying,
video1Ref, video1Ref,
video2Ref, video2Ref,

@ -1,3 +1,5 @@
import { Fragment } from 'react'
import { Loader } from 'features/Loader' import { Loader } from 'features/Loader'
import { import {
PlayerWrapper, PlayerWrapper,
@ -8,6 +10,11 @@ import {
Forward, Forward,
ControlsGradient, ControlsGradient,
TeamsDetailsWrapper, TeamsDetailsWrapper,
EpisodeInfo,
EpisodeInfoName,
EpisodeInfoOrder,
EpisodeInfoDivider,
CloseButton,
} from 'features/StreamPlayer/styled' } from 'features/StreamPlayer/styled'
import { VideoPlayer } from 'features/VideoPlayer' import { VideoPlayer } from 'features/VideoPlayer'
import { Controls } from 'features/StreamPlayer/components/Controls' import { Controls } from 'features/StreamPlayer/components/Controls'
@ -15,6 +22,7 @@ import { Name } from 'features/Name'
import RewindMobile from 'features/StreamPlayer/components/RewindMobile' import RewindMobile from 'features/StreamPlayer/components/RewindMobile'
import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup' import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { useAuthStore } from 'features/AuthStore' import { useAuthStore } from 'features/AuthStore'
import { useMatchPageStore } from 'features/MatchPage/store'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
@ -39,6 +47,7 @@ export const MultiSourcePlayer = (props: Props) => {
allPlayedProgress, allPlayedProgress,
centerControlsVisible, centerControlsVisible,
chapters, chapters,
currentPlayingOrder,
duration, duration,
hideCenterControls, hideCenterControls,
isFirstChapterPlaying, isFirstChapterPlaying,
@ -76,6 +85,7 @@ export const MultiSourcePlayer = (props: Props) => {
seek, seek,
selectedQuality, selectedQuality,
showCenterControls, showCenterControls,
stopPlayingEpisodes,
togglePlaying, togglePlaying,
video1Ref, video1Ref,
video2Ref, video2Ref,
@ -84,6 +94,8 @@ export const MultiSourcePlayer = (props: Props) => {
volumeInPercent, volumeInPercent,
wrapperRef, wrapperRef,
} = useMultiSourcePlayer(props) } = useMultiSourcePlayer(props)
const { episodeInfo, isPlayingEpisode } = useMatchPageStore()
const firstPlayerActive = activePlayer === Players.PLAYER1 const firstPlayerActive = activePlayer === Players.PLAYER1
const currentVideo = firstPlayerActive ? video1Ref : video2Ref const currentVideo = firstPlayerActive ? video1Ref : video2Ref
const { user } = useAuthStore() const { user } = useAuthStore()
@ -99,6 +111,27 @@ export const MultiSourcePlayer = (props: Props) => {
onTouchStart={onTouchStart} onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd} onTouchEnd={onTouchEnd}
> >
{isPlayingEpisode && (
<EpisodeInfo>
<EpisodeInfoName>
{isMobileDevice
? (
<Fragment>
{episodeInfo.playerOrTeamName} <br /> {episodeInfo.paramName}
</Fragment>
)
: `${episodeInfo.playerOrTeamName}${episodeInfo.playerOrTeamName ? ' - ' : ''}${episodeInfo.paramName}`}
</EpisodeInfoName>
{currentPlayingOrder > 0 && (
<EpisodeInfoOrder>
{currentPlayingOrder}
<EpisodeInfoDivider>/</EpisodeInfoDivider>
{episodeInfo.paramValue}
</EpisodeInfoOrder>
)}
<CloseButton onClick={stopPlayingEpisodes} />
</EpisodeInfo>
)}
{ {
!ready && ( !ready && (
<LoaderWrapper buffering> <LoaderWrapper buffering>

@ -1,6 +1,7 @@
export type Urls = { [quality: string]: string } export type Urls = { [quality: string]: string }
export type Chapter = { export type Chapter = {
count?: number,
duration: number, duration: number,
endMs: number, endMs: number,
endOffsetMs: number, endOffsetMs: number,

@ -5,15 +5,18 @@ import { Close } from '../../../libs/objects/Close'
import { BaseButton } from '../BaseButton' import { BaseButton } from '../BaseButton'
type Props = { type Props = {
className?: string,
onClick: (e: MouseEvent<HTMLButtonElement>) => void, onClick: (e: MouseEvent<HTMLButtonElement>) => void,
size?: number, size?: number,
} }
export const CloseButton = ({ export const CloseButton = ({
className,
onClick, onClick,
size, size,
}: Props) => ( }: Props) => (
<BaseButton <BaseButton
className={className}
width={1.35} width={1.35}
height={1.35} height={1.35}
padding={8} padding={8}

@ -39,10 +39,4 @@ export const PlayedProgress = styled.div`
background-color: #CC0000; background-color: #CC0000;
height: 100%; height: 100%;
max-width: 100%; max-width: 100%;
${isMobileDevice
? css`
background-color: rgba(255, 255, 255, 1);
`
: ''}
` `

@ -13,8 +13,8 @@ export const ProgressBarList = styled.div`
${isMobileDevice ${isMobileDevice
? css` ? css`
height: 3px; height: 6px;
margin: 0 15px; margin: 0 15px 12px;
` `
: ''} : ''}
` `
@ -49,10 +49,9 @@ export const Scrubber = styled.div`
${isMobileDevice ${isMobileDevice
? css` ? css`
background-color: #FFFFFF;
width: 27px; width: 27px;
height: 27px; height: 27px;
transform: translate(-50%, -53%); transform: translate(-50%, -65%);
` `
: ''} : ''}
` `

@ -13,6 +13,10 @@ import size from 'lodash/size'
import isNumber from 'lodash/isNumber' import isNumber from 'lodash/isNumber'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
import isUndefined from 'lodash/isUndefined' import isUndefined from 'lodash/isUndefined'
import findIndex from 'lodash/findIndex'
import inRange from 'lodash/inRange'
import sum from 'lodash/sum'
import values from 'lodash/values'
import Hls from 'hls.js' import Hls from 'hls.js'
@ -81,6 +85,9 @@ export const useVideoPlayer = ({
onProgressChange: progressChangeCallback, onProgressChange: progressChangeCallback,
resumeFrom, resumeFrom,
}: Props) => { }: Props) => {
const [currentPlayingOrder, setCurrentPlayingOrder] = useState(0)
const ordersObj = useRef<Record<string, number>>({})
const [{ const [{
activeChapterIndex, activeChapterIndex,
buffering, buffering,
@ -98,12 +105,16 @@ export const useVideoPlayer = ({
const { lang } = useLexicsStore() const { lang } = useLexicsStore()
const { profileId, sportType } = usePageParams() const { profileId, sportType } = usePageParams()
const { const {
disablePlayingEpisodes,
isPlayFilterEpisodes, isPlayFilterEpisodes,
isPlayingEpisode,
matchPlaylists, matchPlaylists,
playingProgress,
playNextEpisode, playNextEpisode,
selectedPlaylist, selectedPlaylist,
setCircleAnimation, setCircleAnimation,
setPlayingProgress, setPlayingProgress,
statsPlaingOrder,
} = useMatchPageStore() } = useMatchPageStore()
const { isOpen } = useTour() const { isOpen } = useTour()
@ -138,6 +149,49 @@ export const useVideoPlayer = ({
[chapters, activeChapterIndex], [chapters, activeChapterIndex],
) )
useEffect(() => {
const splitEpisodeDuration = (duration: number, count: number) => {
const result: Array<[number, number]> = []
for (let i = 0; i < duration; i += count) {
const start = i
const end = Math.min(i + count, duration)
result.push([start, end])
}
return result
}
const getCurrentPlayingOrder = () => {
const { count, duration } = getActiveChapter()
const arr = splitEpisodeDuration(duration, duration / count!)
const index = findIndex(arr, (elem) => inRange(
playingProgress * 1000,
elem[0],
elem[1] + 1,
))
if (index !== -1 && ordersObj.current[statsPlaingOrder] !== index) {
ordersObj.current = {
...ordersObj.current,
[statsPlaingOrder]: index,
}
}
return statsPlaingOrder + sum(values(ordersObj.current))
}
setCurrentPlayingOrder(getCurrentPlayingOrder())
}, [getActiveChapter, isPlayingEpisode, statsPlaingOrder, playingProgress])
useEffect(() => {
if (!isPlayingEpisode) {
ordersObj.current = {}
}
}, [isPlayingEpisode])
const chaptersDuration = useDuration(chapters) const chaptersDuration = useDuration(chapters)
const duration = (isLive && chapters[0]?.isFullMatchChapter) const duration = (isLive && chapters[0]?.isFullMatchChapter)
@ -299,6 +353,11 @@ export const useVideoPlayer = ({
matchPlaylists.match, matchPlaylists.match,
]) ])
const stopPlayingEpisodes = () => {
disablePlayingEpisodes()
backToPausedTime()
}
useEffect(() => { useEffect(() => {
if (chapters[0]?.isFullMatchChapter) { if (chapters[0]?.isFullMatchChapter) {
setPausedProgress(playedProgress + chapters[0].startOffsetMs) setPausedProgress(playedProgress + chapters[0].startOffsetMs)
@ -463,13 +522,13 @@ export const useVideoPlayer = ({
}, [setPlayerState]) }, [setPlayerState])
useEffect(() => { useEffect(() => {
if (ready && videoRef) { if (ready && videoRef && !isPlayingEpisode) {
videoRef.current?.scrollIntoView({ videoRef.current?.scrollIntoView({
behavior: 'smooth', behavior: 'smooth',
block: 'start', block: 'start',
}) })
} }
}, [ready, videoRef]) }, [ready, videoRef, isPlayingEpisode])
useEffect(() => { useEffect(() => {
setCircleAnimation((state) => ({ setCircleAnimation((state) => ({
@ -523,6 +582,7 @@ export const useVideoPlayer = ({
backToLive, backToLive,
buffering, buffering,
chapters, chapters,
currentPlayingOrder,
duration, duration,
isFirstChapterPlaying, isFirstChapterPlaying,
isFullscreen, isFullscreen,
@ -552,6 +612,7 @@ export const useVideoPlayer = ({
rewindForward, rewindForward,
seek, seek,
sizeOptions, sizeOptions,
stopPlayingEpisodes,
togglePlaying, togglePlaying,
url, url,
videoRef, videoRef,

@ -1,3 +1,7 @@
import { Fragment } from 'react'
import { isMobileDevice } from 'config'
import { Loader } from 'features/Loader' import { Loader } from 'features/Loader'
import { VideoPlayer } from 'features/VideoPlayer' import { VideoPlayer } from 'features/VideoPlayer'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
@ -7,8 +11,6 @@ import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopu
import { WaterMark } from 'components/WaterMark' import { WaterMark } from 'components/WaterMark'
import { AccessTimer } from 'components/AccessTimer' import { AccessTimer } from 'components/AccessTimer'
import { isMobileDevice } from 'config/userAgent'
import { REWIND_SECONDS } from './config' import { REWIND_SECONDS } from './config'
import { import {
@ -20,6 +22,11 @@ import {
Forward, Forward,
ControlsGradient, ControlsGradient,
TeamsDetailsWrapper, TeamsDetailsWrapper,
EpisodeInfo,
EpisodeInfoName,
EpisodeInfoOrder,
EpisodeInfoDivider,
CloseButton,
} from './styled' } from './styled'
import type { Props } from './hooks' import type { Props } from './hooks'
import { useVideoPlayer } from './hooks' import { useVideoPlayer } from './hooks'
@ -37,7 +44,9 @@ const tournamentsWithWatermark = {
export const StreamPlayer = (props: Props) => { export const StreamPlayer = (props: Props) => {
const { const {
access, access,
episodeInfo,
isOpenFiltersPopup, isOpenFiltersPopup,
isPlayingEpisode,
profile, profile,
} = useMatchPageStore() } = useMatchPageStore()
const { user } = useAuthStore() const { user } = useAuthStore()
@ -51,6 +60,7 @@ export const StreamPlayer = (props: Props) => {
centerControlsVisible, centerControlsVisible,
changeAudioTrack, changeAudioTrack,
chapters, chapters,
currentPlayingOrder,
duration, duration,
hideCenterControls, hideCenterControls,
isFullscreen, isFullscreen,
@ -88,6 +98,7 @@ export const StreamPlayer = (props: Props) => {
selectedAudioTrack, selectedAudioTrack,
selectedQuality, selectedQuality,
showCenterControls, showCenterControls,
stopPlayingEpisodes,
togglePlaying, togglePlaying,
url, url,
videoQualities, videoQualities,
@ -107,7 +118,29 @@ export const StreamPlayer = (props: Props) => {
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onTouchStart={onTouchStart} onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd} onTouchEnd={onTouchEnd}
isPlayingEpisode={isPlayingEpisode}
> >
{isPlayingEpisode && (
<EpisodeInfo>
<EpisodeInfoName>
{isMobileDevice
? (
<Fragment>
{episodeInfo.playerOrTeamName} <br /> {episodeInfo.paramName}
</Fragment>
)
: `${episodeInfo.playerOrTeamName}${episodeInfo.playerOrTeamName ? ' - ' : ''}${episodeInfo.paramName}`}
</EpisodeInfoName>
{currentPlayingOrder > 0 && (
<EpisodeInfoOrder>
{currentPlayingOrder}
<EpisodeInfoDivider />
{episodeInfo.paramValue}
</EpisodeInfoOrder>
)}
<CloseButton onClick={stopPlayingEpisodes} />
</EpisodeInfo>
)}
{isOpenFiltersPopup && <FiltersPopup />} {isOpenFiltersPopup && <FiltersPopup />}
<LoaderWrapper buffering={buffering}> <LoaderWrapper buffering={buffering}>
<Loader color='#515151' /> <Loader color='#515151' />

@ -1,6 +1,8 @@
import styled, { css } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config'
import { CloseButton as CloseButtonBase } from 'features/PopupComponents'
export const ControlsGradient = styled.div<{ isVisible?: boolean }>` export const ControlsGradient = styled.div<{ isVisible?: boolean }>`
background-image: url(/images/player-controls-gradient.png); background-image: url(/images/player-controls-gradient.png);
@ -111,9 +113,21 @@ export const PlayerWrapper = styled.div<PlayStopProps>`
position: relative; position: relative;
background-color: #000; background-color: #000;
min-height: 100%; min-height: 100%;
${({ isPlayingEpisode }) => (isPlayingEpisode
? css`
border: 6px solid #5EB2FF;
video {
object-fit: ${isMobileDevice ? 'cover' : 'contain'};
}
`
: '')}
:fullscreen { :fullscreen {
padding-top: 0; padding-top: 0;
} }
${supportsAspectRatio ${supportsAspectRatio
? css`aspect-ratio: 16 / 9;` ? css`aspect-ratio: 16 / 9;`
: css` : css`
@ -161,6 +175,7 @@ const sizes = {
type PlayStopProps = { type PlayStopProps = {
fullWidth?: boolean, fullWidth?: boolean,
isPlayingEpisode?: boolean,
marginRight?: number, marginRight?: number,
playing: boolean, playing: boolean,
size?: keyof typeof sizes, size?: keyof typeof sizes,
@ -411,3 +426,59 @@ export const WarningIosText = styled.span`
text-align: center; text-align: center;
font-size: 14px; font-size: 14px;
` `
export const EpisodeInfo = styled.div`
position: absolute;
top: 13px;
right: 15px;
display: flex;
align-items: center;
gap: max(0.755rem, 12px);
font-weight: 600;
color: ${({ theme }) => theme.colors.white};
z-index: 1;
`
export const EpisodeInfoName = styled.span`
font-size: max(1.227rem, 10px);
text-align: right;
line-height: 1.1;
`
export const EpisodeInfoOrder = styled.span`
font-size: max(1.227rem, 24px);
font-weight: 700;
`
export const EpisodeInfoDivider = styled.div`
display: inline-block;
width: 3.5px;
height: max(1.25rem, 22px);
margin: 0 7px -2px;
transform: skew(-20deg, 0);
background-color: ${({ theme }) => theme.colors.white};
${isMobileDevice
? css`
margin-left: 9px;
`
: ''};
`
export const CloseButton = styled(CloseButtonBase)`
padding: 0;
background: none;
z-index: 1;
width: max(1.2rem, 23px);
height: max(1.2rem, 23px);
position: initial;
:hover {
background: none;
}
svg {
width: max(1.2rem, 23px);
height: max(1.2rem, 23px);
}
`

@ -4,6 +4,10 @@
*/ */
export type Chapter = { export type Chapter = {
/** число событий */
count?: number,
/** длительность */
duration: number, duration: number,
/** /**

@ -18,6 +18,9 @@ type Args = {
} }
export type Episode = { export type Episode = {
/** events count */
c: number,
/** episode end */ /** episode end */
e: number, e: number,

Loading…
Cancel
Save