feat(in-600): remove mp4 player

pull/200/head
Ruslan Khayrullin 3 years ago
parent ea00f09c7e
commit a56c44521b
  1. 62
      package-lock.json
  2. 1
      package.json
  3. 139
      src/features/MatchPage/components/FinishedMatch/helpers.tsx
  4. 71
      src/features/MatchPage/components/FinishedMatch/hooks/index.tsx
  5. 46
      src/features/MatchPage/components/FinishedMatch/hooks/useChapters.tsx
  6. 71
      src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx
  7. 72
      src/features/MatchPage/components/FinishedMatch/hooks/usePlayerLogger.tsx
  8. 74
      src/features/MatchPage/components/FinishedMatch/index.tsx
  9. 26
      src/features/MatchPage/components/FinishedMatch/styled.tsx
  10. 23
      src/features/MatchPage/components/LiveMatch/index.tsx
  11. 11
      src/features/MatchPage/index.tsx
  12. 7
      src/features/MatchPage/store/hooks/useMatchData.tsx
  13. 5
      src/features/MatchPopup/store/hooks/index.tsx
  14. 4
      src/features/MatchSidePlaylists/components/Matches/index.tsx
  15. 47
      src/features/MultiSourcePlayer/components/ProgressBar/helpers/calculateChapterStyles/__tests__/index.tsx
  16. 59
      src/features/MultiSourcePlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx
  17. 51
      src/features/MultiSourcePlayer/components/ProgressBar/hooks.tsx
  18. 52
      src/features/MultiSourcePlayer/components/ProgressBar/index.tsx
  19. 126
      src/features/MultiSourcePlayer/components/ProgressBar/stories.tsx
  20. 20
      src/features/MultiSourcePlayer/components/ProgressBar/styled.tsx
  21. 27
      src/features/MultiSourcePlayer/components/Settings/hooks.tsx
  22. 47
      src/features/MultiSourcePlayer/components/Settings/index.tsx
  23. 78
      src/features/MultiSourcePlayer/components/Settings/styled.tsx
  24. 5
      src/features/MultiSourcePlayer/config.tsx
  25. 35
      src/features/MultiSourcePlayer/helpers/index.tsx
  26. 494
      src/features/MultiSourcePlayer/hooks/index.tsx
  27. 102
      src/features/MultiSourcePlayer/hooks/usePlayingHandlers.tsx
  28. 51
      src/features/MultiSourcePlayer/hooks/useProgressChangeHandler.tsx
  29. 38
      src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx
  30. 268
      src/features/MultiSourcePlayer/index.tsx
  31. 48
      src/features/MultiSourcePlayer/styled.tsx
  32. 20
      src/features/MultiSourcePlayer/types.tsx
  33. 2
      src/features/StreamPlayer/components/Controls/Components/ControlsMobile/index.tsx
  34. 6
      src/features/StreamPlayer/components/Controls/Components/ControlsWeb/index.tsx
  35. 53
      src/features/StreamPlayer/components/Controls/index.tsx
  36. 2
      src/features/StreamPlayer/components/Settings/index.tsx
  37. 4
      src/features/StreamPlayer/components/Settings/styled.tsx
  38. 51
      src/features/StreamPlayer/components/YoutubePlayer/index.tsx
  39. 2
      src/features/StreamPlayer/hooks/index.tsx
  40. 11
      src/features/StreamPlayer/styled.tsx
  41. 1
      src/hooks/index.tsx
  42. 2
      src/hooks/useDuration.tsx

62
package-lock.json generated

@ -17293,11 +17293,6 @@
"wrap-ansi": "^7.0.0" "wrap-ansi": "^7.0.0"
} }
}, },
"load-script": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
"integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA=="
},
"loader-runner": { "loader-runner": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
@ -21154,33 +21149,6 @@
"memoize-one": ">=3.1.1 <6" "memoize-one": ">=3.1.1 <6"
} }
}, },
"react-youtube": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-7.14.0.tgz",
"integrity": "sha512-SUHZ4F4pd1EHmQu0CV0KSQvAs5KHOT5cfYaq4WLCcDbU8fBo1ouTXaAOIASWbrz8fHwg+G1evfoSIYpV2AwSAg==",
"requires": {
"fast-deep-equal": "3.1.3",
"prop-types": "15.7.2",
"youtube-player": "5.5.2"
},
"dependencies": {
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"read-cache": { "read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -22493,11 +22461,6 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
}, },
"sister": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz",
"integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA=="
},
"sisteransi": { "sisteransi": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@ -26154,31 +26117,6 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
}, },
"youtube-player": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz",
"integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==",
"requires": {
"debug": "^2.6.6",
"load-script": "^1.0.0",
"sister": "^3.0.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}
}
},
"zwitch": { "zwitch": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz",

@ -42,7 +42,6 @@
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"react-window": "^1.8.6", "react-window": "^1.8.6",
"react-youtube": "^7.14.0",
"recoil": "^0.7.4", "recoil": "^0.7.4",
"screenfull": "^5.0.2", "screenfull": "^5.0.2",
"styled-components": "^5.3.3", "styled-components": "^5.3.3",

@ -1,139 +0,0 @@
import map from 'lodash/map'
import last from 'lodash/last'
import uniq from 'lodash/uniq'
import filter from 'lodash/filter'
import reduce from 'lodash/reduce'
import concat from 'lodash/concat'
import orderBy from 'lodash/orderBy'
import isEmpty from 'lodash/isEmpty'
import groupBy from 'lodash/groupBy'
import type {
Videos,
Episodes,
Episode,
} from 'requests'
import type { Chapters, Urls } from 'features/MultiSourcePlayer/types'
import type { PlaylistOption } from '../../types'
import { FULL_GAME_KEY } from '../../helpers/buildPlaylists'
const getUniquePeriods = (videos: Videos) => uniq(map(videos, ({ period }) => period))
type Video = {
duration: number,
period: number,
urls: Urls,
}
const getVideoByPeriod = (videos: Videos, period: number) => {
const videosWithSamePeriod = filter(videos, { period })
if (isEmpty(videosWithSamePeriod)) return null
const urls = reduce(
videosWithSamePeriod,
(acc: Urls, video) => ({
...acc,
[video.quality]: video.url,
}),
{},
)
const [video] = videosWithSamePeriod
return {
duration: video.duration,
period: video.period,
urls,
}
}
const getVideoByPeriods = (videos: Videos, periods: Array<number>) => (
reduce(
periods,
(acc: Array<Video>, period) => {
const video = getVideoByPeriod(videos, period)
return video ? concat(acc, video) : acc
},
[],
)
)
const getFullMatchChapters = (videos: Array<Video>) => {
const sortedVideos = orderBy(videos, ({ period }) => period)
return reduce(
sortedVideos,
(acc: Chapters, video) => {
const prevVideoEndMs = last(acc)?.endMs || 0
const endMs = prevVideoEndMs + video.duration
const nextChapter = {
duration: video.duration,
endMs,
endOffsetMs: endMs,
isFullMatchChapter: true,
period: video.period,
startMs: prevVideoEndMs,
startOffsetMs: 0,
urls: video.urls,
}
return concat(acc, nextChapter)
},
[],
)
}
const getEpisodeUrls = (urls: Urls, episode: Episode) => reduce(
urls,
(
acc: Urls,
url,
qulaity,
) => {
acc[qulaity] = `${url}#t=${episode.s},${episode.e}`
return acc
},
{},
)
const getPlaylistChapters = (videos: Array<Video>, episodes: Episodes) => {
const groupedByPeriods = groupBy(videos, ({ period }) => period)
return reduce(
episodes,
(acc: Chapters, episode) => {
const video = groupedByPeriods[episode.h]?.[0]
if (!video || episode.s >= episode.e) return acc
const episodeDuration = (episode.e - episode.s) * 1000
const prevVideoEndMs = last(acc)?.endMs || 0
const nextChapter = {
count: episode.c,
duration: episodeDuration,
endMs: prevVideoEndMs + episodeDuration,
endOffsetMs: episode.e * 1000,
period: video.period,
startMs: prevVideoEndMs,
startOffsetMs: episode.s * 1000,
urls: getEpisodeUrls(video.urls, episode),
}
return concat(acc, nextChapter)
},
[],
)
}
type Args = {
episodes: Episodes,
selectedPlaylist?: PlaylistOption,
videos: Videos,
}
export const buildChapters = ({
episodes,
selectedPlaylist,
videos,
}: Args) => {
const periods = getUniquePeriods(videos)
const highQualityVideos = getVideoByPeriods(videos, periods)
return selectedPlaylist?.id === FULL_GAME_KEY
? getFullMatchChapters(highQualityVideos)
: getPlaylistChapters(highQualityVideos, episodes)
}

@ -1,71 +0,0 @@
import { useEffect } from 'react'
import { useToggle, useObjectState } from 'hooks'
import type { Settings } from 'features/MatchPopup'
import { useMatchPopupStore } from 'features/MatchPopup'
import { useMatchPageStore } from 'features/MatchPage/store'
import { usePlayerLogger } from './usePlayerLogger'
import { useEpisodes } from './useEpisodes'
import { useChapters } from './useChapters'
const initPausedData = {
activeChapterIndex: 0,
activePlayer: 0 || 1,
playedProgress: 0,
}
export type PausedData = typeof initPausedData
export const useFinishedMatch = () => {
const [pausedData, setPausedData] = useObjectState<PausedData>(initPausedData)
const { setChapters, setSettings } = useMatchPopupStore()
const {
handlePlaylistClick,
matchPlaylists,
selectedPlaylist,
} = useMatchPageStore()
const {
close: closeSettingsPopup,
isOpen: isSettingsPopupOpen,
open: openSettingsPopup,
} = useToggle()
const { episodes } = useEpisodes()
const { logPlaylistChange, onPlayingChange } = usePlayerLogger()
const setEpisodesSettings = (newSettings: Settings) => {
setSettings(newSettings)
closeSettingsPopup()
}
// @ts-expect-error
const onPlaylistSelect: typeof handlePlaylistClick = (playlist, e) => {
if (selectedPlaylist) {
logPlaylistChange(selectedPlaylist)
}
// @ts-expect-error
handlePlaylistClick(playlist, e)
}
const { chapters } = useChapters({ episodes, selectedPlaylist })
useEffect(() => {
setChapters(chapters)
}, [chapters, setChapters])
return {
chapters,
closeSettingsPopup,
isSettingsPopupOpen,
onPlayingChange,
onPlaylistSelect,
openSettingsPopup,
pausedData,
playlists: matchPlaylists,
selectedPlaylist,
setEpisodesSettings,
setPausedData,
}
}

@ -1,46 +0,0 @@
import {
useEffect,
useMemo,
useState,
} from 'react'
import type { Episodes, Videos } from 'requests'
import { getVideos } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import type { PlaylistOption } from 'features/MatchPage/types'
import { buildChapters } from '../helpers'
type Args = {
episodes: Episodes,
selectedPlaylist?: PlaylistOption,
}
export const useChapters = ({
episodes,
selectedPlaylist,
}: Args) => {
const [videos, setVideos] = useState<Videos>([])
const { profileId: matchId, sportType } = usePageParams()
useEffect(() => {
getVideos(sportType, matchId).then(setVideos)
}, [sportType, matchId])
const chapters = useMemo(
() => buildChapters({
episodes,
selectedPlaylist,
videos,
}),
[
selectedPlaylist,
episodes,
videos,
],
)
return { chapters }
}

@ -1,71 +0,0 @@
import {
useCallback,
useEffect,
useState,
} from 'react'
import isEmpty from 'lodash/isEmpty'
import type { Episodes } from 'requests'
import { getPlayerPlaylists } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import { PlaylistOption, PlaylistTypes } from 'features/MatchPage/types'
import { useMatchPageStore } from 'features/MatchPage/store'
import {
defaultSettings,
Settings,
useMatchPopupStore,
} from 'features/MatchPopup'
export const useEpisodes = () => {
const { settings } = useMatchPopupStore()
const {
handlePlaylistClick,
matchPlaylists: playlists,
selectedPlaylist,
} = useMatchPageStore()
const [episodes, setEpisodes] = useState<Episodes>([])
const { profileId: matchId, sportType } = usePageParams()
const fetchEpisodes = useCallback((
playlistOption: PlaylistOption,
popupSettings: Settings = defaultSettings,
) => {
if (playlistOption.type === PlaylistTypes.PLAYER) {
getPlayerPlaylists({
matchId,
playerId: playlistOption.id,
settings: popupSettings,
sportType,
}).then(setEpisodes)
} else if (playlistOption.type === PlaylistTypes.MATCH
|| playlistOption.type === PlaylistTypes.EVENT) {
setEpisodes(playlistOption.episodes)
}
}, [matchId, sportType])
useEffect(() => {
if (!selectedPlaylist && playlists && !isEmpty(playlists.match)) {
// handlePlaylistClick(playlists.match[0])
}
}, [
selectedPlaylist,
playlists,
handlePlaylistClick,
])
useEffect(() => {
if (selectedPlaylist) {
fetchEpisodes(selectedPlaylist, settings)
}
}, [
settings,
selectedPlaylist,
fetchEpisodes,
])
return { episodes }
}

@ -1,72 +0,0 @@
import {
useCallback,
useRef,
} from 'react'
import { useLocation } from 'react-router'
import { LogActions, logUserAction } from 'requests/logUserAction'
import { useInterval } from 'hooks/useInterval'
import { usePageParams } from 'hooks/usePageParams'
import { PlaylistOption, PlaylistTypes } from 'features/MatchPage/types'
const playlistTypeConfig = {
ball_in_play: 2,
full_game: 1,
goals: 4,
highlights: 3,
players: 5,
}
const getInitialData = () => ({ dateVisit: new Date().toISOString(), seconds: 0 })
export const usePlayerLogger = () => {
const location = useLocation()
const { profileId, sportType } = usePageParams()
const data = useRef(getInitialData())
const incrementSeconds = () => data.current.seconds++
const resetData = () => {
data.current = getInitialData()
}
const { start, stop } = useInterval({
callback: incrementSeconds,
intervalDuration: 1000,
startImmediate: false,
})
const onPlayingChange = useCallback((playing: boolean) => {
if (playing) {
start()
} else {
stop()
}
}, [start, stop])
const logPlaylistChange = (prevPlaylist: PlaylistOption) => {
const args = prevPlaylist.type === PlaylistTypes.MATCH
? {
playlistType: playlistTypeConfig[prevPlaylist.id],
}
: {
playerId: prevPlaylist.id,
playlistType: playlistTypeConfig.players,
}
logUserAction({
actionType: LogActions.VideoChange,
dateVisit: data.current.dateVisit,
duration: data.current.seconds,
matchId: profileId,
sportType,
url: location.pathname,
...args,
})
resetData()
}
return { logPlaylistChange, onPlayingChange }
}

@ -1,74 +0,0 @@
import { Fragment } from 'react'
import isEmpty from 'lodash/isEmpty'
import { MatchSidePlaylists } from 'features/MatchSidePlaylists'
import { MultiSourcePlayer } from 'features/MultiSourcePlayer'
import { SettingsPopup } from '../SettingsPopup'
import { useFinishedMatch } from './hooks'
import { Container } from '../../styled'
import { Modal } from './styled'
import { MatchDescription } from '../MatchDescription'
import { useMatchPageStore } from '../../store'
export const FinishedMatch = () => {
const {
access,
isOpenFiltersPopup,
profile,
profileCardShown,
} = useMatchPageStore()
const {
chapters,
closeSettingsPopup,
isSettingsPopupOpen,
onPlayingChange,
onPlaylistSelect,
pausedData,
selectedPlaylist,
setEpisodesSettings,
setPausedData,
} = useFinishedMatch()
return (
<Fragment>
<Modal
close={closeSettingsPopup}
isOpen={isSettingsPopupOpen}
withCloseButton={false}
>
<SettingsPopup
onWatchEpisodesClick={setEpisodesSettings}
closePopup={closeSettingsPopup}
profile={profile}
selectedPlaylist={selectedPlaylist}
/>
</Modal>
<Container isHidden={!profileCardShown}>
{!isEmpty(chapters) && (
<Fragment>
<MultiSourcePlayer
pausedData={pausedData}
setPausedData={setPausedData}
access={access}
isOpenPopup={isOpenFiltersPopup}
chapters={chapters}
onPlayingChange={onPlayingChange}
profile={profile}
/>
<MatchDescription />
</Fragment>
)}
</Container>
<MatchSidePlaylists
selectedPlaylist={selectedPlaylist}
// @ts-expect-error
onSelect={onPlaylistSelect}
/>
</Fragment>
)
}

@ -1,26 +0,0 @@
import styled from 'styled-components/macro'
import { devices } from 'config/devices'
import { Modal as BaseModal } from 'features/Modal'
import { ModalWindow } from 'features/Modal/styled'
export const Modal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7);
${ModalWindow} {
width: 1222px;
padding: 20px 0;
border-radius: 5px;
@media ${devices.tablet} {
width: 100vw;
}
@media ${devices.mobile} {
height: 100vh;
padding: 0;
background-color: transparent;
}
}
`

@ -4,7 +4,6 @@ import isEmpty from 'lodash/isEmpty'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
import { StreamPlayer } from 'features/StreamPlayer' import { StreamPlayer } from 'features/StreamPlayer'
import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer'
import { MatchSidePlaylists } from 'features/MatchSidePlaylists' import { MatchSidePlaylists } from 'features/MatchSidePlaylists'
import { Container } from '../../styled' import { Container } from '../../styled'
@ -26,31 +25,19 @@ export const LiveMatch = () => {
onPlayingChange, onPlayingChange,
onPlaylistSelect, onPlaylistSelect,
resume, resume,
streamUrl,
} = useLiveMatch() } = useLiveMatch()
return ( return (
<Fragment> <Fragment>
<Container isHidden={!profileCardShown}> <Container isHidden={!profileCardShown}>
{profile?.youtube_link ? ( {!isEmpty(chapters) && (
<YoutubePlayer <StreamPlayer
chapters={chapters} onDurationChange={onDurationChange}
onPlayingChange={onPlayingChange} onPlayingChange={onPlayingChange}
isLive={profile.live}
onProgressChange={onPlayerProgressChange} onProgressChange={onPlayerProgressChange}
isLive={!!profile?.live}
resumeFrom={resume} resumeFrom={resume}
url={streamUrl} chapters={chapters}
/> />
) : (
!isEmpty(chapters) && (
<StreamPlayer
onDurationChange={onDurationChange}
onPlayingChange={onPlayingChange}
onProgressChange={onPlayerProgressChange}
isLive={!!profile?.live}
resumeFrom={resume}
chapters={chapters}
/>
)
)} )}
<MatchDescription /> <MatchDescription />
</Container> </Container>

@ -30,7 +30,6 @@ import { TourProvider } from 'features/MatchTour'
import { MatchPageStore, useMatchPageStore } from './store' import { MatchPageStore, useMatchPageStore } from './store'
import { SubscriptionGuard } from './components/SubscriptionGuard' import { SubscriptionGuard } from './components/SubscriptionGuard'
import { LiveMatch } from './components/LiveMatch' import { LiveMatch } from './components/LiveMatch'
import { FinishedMatch } from './components/FinishedMatch'
import { FavouriteTeamPopup } from './components/FavouriteTeam' import { FavouriteTeamPopup } from './components/FavouriteTeam'
import { Wrapper } from './styled' import { Wrapper } from './styled'
@ -90,9 +89,6 @@ const MatchPageComponent = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [profile]) }, [profile])
const playFromScout = profile?.has_video && !profile?.live && !profile.has_hls
const playFromOTT = (!profile?.has_video && (profile?.live || profile?.storage))
|| profile?.has_hls
// TODO Добавить попап 'Данный матч ещё не начался' // TODO Добавить попап 'Данный матч ещё не начался'
if (!isStarted && profile?.live === false) { if (!isStarted && profile?.live === false) {
const sportName = history.location.pathname.split('/')[1] const sportName = history.location.pathname.split('/')[1]
@ -109,12 +105,7 @@ const MatchPageComponent = () => {
<UserFavorites /> <UserFavorites />
<SubscriptionGuard> <SubscriptionGuard>
<Wrapper isTourOpen={Boolean(isOpen)}> <Wrapper isTourOpen={Boolean(isOpen)}>
{playFromOTT && ( <LiveMatch />
<LiveMatch />
)}
{playFromScout && (
<FinishedMatch />
)}
</Wrapper> </Wrapper>
</SubscriptionGuard> </SubscriptionGuard>
</Main> </Main>

@ -6,10 +6,13 @@ import {
import type { MatchInfo } from 'requests/getMatchInfo' import type { MatchInfo } from 'requests/getMatchInfo'
import { usePageParams, useInterval } from 'hooks' import {
usePageParams,
useInterval,
useDuration,
} from 'hooks'
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 { useMatchPopupStore } from 'features/MatchPopup' import { useMatchPopupStore } from 'features/MatchPopup'
import { useMatchPlaylists } from './useMatchPlaylists' import { useMatchPlaylists } from './useMatchPlaylists'

@ -9,12 +9,11 @@ import isEmpty from 'lodash/isEmpty'
import { getMatchPlaylists } from 'requests' import { getMatchPlaylists } from 'requests'
import { useToggle } from 'hooks' import { useToggle, useDuration } from 'hooks'
import { buildPlaylists } from 'features/MatchPage/helpers/buildPlaylists' import { buildPlaylists } from 'features/MatchPage/helpers/buildPlaylists'
import { Playlists } from 'features/MatchPage/types' import { Playlists } from 'features/MatchPage/types'
import { Chapters } from 'features/MultiSourcePlayer/types' import type { Chapters } from 'features/StreamPlayer/types'
import { useDuration } from 'features/MultiSourcePlayer/hooks/useDuration'
import { useSettingsState } from './useSettingsState' import { useSettingsState } from './useSettingsState'
import { useSportActions } from './useSportActions' import { useSportActions } from './useSportActions'

@ -38,12 +38,12 @@ export const Matches = ({
const { profileId } = usePageParams() const { profileId } = usePageParams()
const profileDate = useMemo(() => ( const profileDate = useMemo(() => (
formatDate(parseDate(profile?.date!)) profile?.date && formatDate(parseDate(profile.date))
), [profile?.date]) ), [profile?.date])
const matches = useMemo(() => ( const matches = useMemo(() => (
tournamentData.matches.filter((match) => ( tournamentData.matches.filter((match) => (
formatDate(match.date) === profileDate && match.id !== profileId profileDate && formatDate(match.date) === profileDate && match.id !== profileId
)) ))
), [profileDate, profileId, tournamentData.matches]) ), [profileDate, profileId, tournamentData.matches])

@ -1,47 +0,0 @@
import { calculateChapterStyles } from '..'
const videoDuration = 60000
it('return correct progress and width lengthes', () => {
let chapter = {
duration: 15000,
endMs: 20000,
period: 0,
startMs: 5000,
urls: {},
}
let expected = {
...chapter,
loaded: 100,
played: 100,
width: 25,
}
expect(calculateChapterStyles({
activeChapterIndex: 0,
chapters: [chapter],
loadedProgress: 30000,
playedProgress: 30000,
videoDuration,
})).toEqual([expected])
chapter = {
duration: 30000,
endMs: 30000,
period: 0,
startMs: 0,
urls: {},
}
expected = {
...chapter,
loaded: 50,
played: 50,
width: 50,
}
expect(calculateChapterStyles({
activeChapterIndex: 0,
chapters: [chapter],
loadedProgress: 15000,
playedProgress: 15000,
videoDuration,
})).toEqual([expected])
})

@ -1,59 +0,0 @@
import map from 'lodash/fp/map'
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'
const calculateChapterProgress = (progress: number, chapter: Chapter) => (
Math.min(progress * 100 / chapter.duration, 100)
)
type Args = {
activeChapterIndex: number,
chapters: Chapters,
loadedProgress: number,
playedProgress: number,
videoDuration: number,
}
export const calculateChapterStyles = ({
activeChapterIndex,
chapters,
loadedProgress,
playedProgress,
videoDuration,
}: Args) => {
const playedChapters = pipe(
slice(0, activeChapterIndex),
map((chapter: Chapter) => ({
...chapter,
loaded: 100,
played: 100,
width: chapter.duration * 100 / videoDuration,
})),
)(chapters)
const comingChapters = pipe(
slice(activeChapterIndex + 1, size(chapters)),
map((chapter: Chapter) => ({
...chapter,
loaded: 0,
played: 0,
width: chapter.duration * 100 / videoDuration,
})),
)(chapters)
const chapter = chapters[activeChapterIndex]
const activeChapter = {
...chapter,
loaded: calculateChapterProgress(loadedProgress, chapter),
played: calculateChapterProgress(playedProgress, chapter),
width: chapter.duration * 100 / videoDuration,
}
return [
...playedChapters,
activeChapter,
...comingChapters,
]
}

@ -1,51 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useMemo } from 'react'
import { secondsToHms } from 'helpers'
import type { Chapters } from '../../types'
import { calculateChapterStyles } from './helpers/calculateChapterStyles'
export type Props = {
activeChapterIndex: number,
allPlayedProgress: number,
chapters?: Chapters,
duration: number,
loadedProgress: number,
onPlayedProgressChange: (progress: number, seeking: boolean) => void,
onTouchEnd?: () => any,
onTouchStart?: () => any,
playedProgress: number,
}
export const useProgressBar = ({
activeChapterIndex,
allPlayedProgress,
chapters = [],
duration,
loadedProgress,
playedProgress,
}: Props) => {
const calculatedChapters = useMemo(
() => calculateChapterStyles({
activeChapterIndex,
chapters,
loadedProgress,
playedProgress,
videoDuration: duration,
}),
[
activeChapterIndex,
loadedProgress,
playedProgress,
duration,
chapters,
],
)
return {
calculatedChapters,
playedProgressInPercent: Math.min(allPlayedProgress * 100 / duration, 100),
time: secondsToHms(allPlayedProgress / 1000),
}
}

@ -1,52 +0,0 @@
import { useSlider } from 'features/StreamPlayer/hooks/useSlider'
import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip'
import { Scrubber, ScrubberContainer } from 'features/StreamPlayer/components/ProgressBar/styled'
import { Chapters } from 'features/StreamPlayer/components/Chapters'
import type { Props } from './hooks'
import { useProgressBar } from './hooks'
import { ProgressBarList } from './styled'
import { isIOS } from '../../../../config/userAgent'
export interface ProgressBarProps extends Props {
isScrubberVisible?: boolean,
}
export const ProgressBar = (props: ProgressBarProps) => {
const {
isScrubberVisible,
onPlayedProgressChange,
onTouchEnd,
onTouchStart,
} = props
const progressBarRef = useSlider({
onChange: onPlayedProgressChange,
onTouchEnd,
onTouchStart,
})
const {
calculatedChapters,
playedProgressInPercent,
time,
} = useProgressBar(props)
return (
<ProgressBarList
ref={progressBarRef}
isIOS={isIOS}
>
<Chapters chapters={calculatedChapters} />
{isScrubberVisible && (
<ScrubberContainer>
<Scrubber
style={{ left: `${playedProgressInPercent}%` }}
>
<TimeTooltip time={time} />
</Scrubber>
</ScrubberContainer>
)}
</ProgressBarList>
)
}

@ -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) => (
<Wrapper>
<Controls visible>
<PlayStop onClick={callback} playing={false} />
<VolumeBar
value={50}
muted={false}
onChange={callback}
onClick={callback}
/>
{progressBarElement}
<Fullscreen onClick={callback} isFullscreen={false} />
</Controls>
</Wrapper>
)
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(
<ProgressBar
activeChapterIndex={0}
allPlayedProgress={0}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={0}
loadedProgress={0}
/>,
)
export const HalfLoaded = () => renderInControls(
<ProgressBar
activeChapterIndex={0}
allPlayedProgress={0}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={0}
loadedProgress={30000}
/>,
)
export const HalfPlayed = () => renderInControls(
<ProgressBar
activeChapterIndex={1}
allPlayedProgress={1}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={30000}
loadedProgress={0}
/>,
)
export const Loaded40AndPlayed20 = () => renderInControls(
<ProgressBar
activeChapterIndex={0}
allPlayedProgress={0}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={20000}
loadedProgress={40000}
/>,
)

@ -1,20 +0,0 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
export const ProgressBarList = styled.div<{isIOS?: boolean}>`
flex-grow: 1;
height: 4px;
position: relative;
${isMobileDevice
? css`
height: 1px;
`
: ''}
${({ isIOS }) => (isIOS && isMobileDevice
&& css`
height: 3px;
margin: 0 24px;
`)}
`

@ -1,27 +0,0 @@
import { useToggle } from 'hooks'
export type Props = {
onSelect: (quality: string) => void,
selectedQuality: string,
videoQualities: Array<string>,
}
export const useSettings = ({ onSelect }: Props) => {
const {
close,
isOpen,
open,
} = useToggle()
const onItemClick = (quality: string) => {
onSelect(quality)
close()
}
return {
close,
isOpen,
onItemClick,
open,
}
}

@ -1,47 +0,0 @@
import { Fragment } from 'react'
import map from 'lodash/map'
import { OutsideClick } from 'features/OutsideClick'
import type { Props } from './hooks'
import { useSettings } from './hooks'
import {
SettingsButton,
QualitiesList,
QualityItem,
} from './styled'
export const Settings = (props: Props) => {
const { selectedQuality, videoQualities } = props
const {
close,
isOpen,
onItemClick,
open,
} = useSettings(props)
return (
<Fragment>
<SettingsButton onClick={open} id='match_video_quality' />
{
isOpen && (
<OutsideClick onClick={close}>
<QualitiesList>
{
map(videoQualities, (quality) => (
<QualityItem
key={quality}
active={quality === selectedQuality}
onClick={() => onItemClick(quality)}
>
{quality}
</QualityItem>
))
}
</QualitiesList>
</OutsideClick>
)
}
</Fragment>
)
}

@ -1,78 +0,0 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { ButtonBase } from 'features/StreamPlayer/styled'
export const SettingsButton = styled(ButtonBase)`
width: 22px;
height: 20px;
margin-right: 25px;
background-image: url(/images/settings.svg);
${isMobileDevice
? css`
width: 20px;
height: 18px;
margin-right: 15px;
cursor: pointer;
`
: ''};
`
export const QualitiesList = styled.ul`
position: absolute;
z-index: 1;
bottom: calc(100% + 14px);
right: 24px;
width: 52px;
list-style: none;
border-radius: 2px;
background-color: rgba(0, 0, 0, 0.5);
overflow: hidden;
${isMobileDevice
? css`
right: 0;
bottom: 35px;
`
: ''};
`
type QualityItemProps = {
active: boolean,
}
const activeIcon = css`
:before {
position: absolute;
top: 45%;
transform: rotate(-45deg);
content: '';
left: 8px;
width: 5px;
height: 3px;
border-left: 1px solid #fff;
border-bottom: 1px solid #fff;
}
`
export const QualityItem = styled.li<QualityItemProps>`
width: 100%;
padding: 5px 8px;
text-align: right;
font-style: normal;
font-weight: normal;
/* stylelint-disable-next-line */
font-family: Montserrat;
font-size: 10px;
line-height: 12px;
letter-spacing: 0.01em;
color: #fff;
cursor: pointer;
position: relative;
:hover, :focus {
background-color: rgba(255, 255, 255, 0.1);
}
${({ active }) => (active ? activeIcon : '')}
`

@ -1,5 +0,0 @@
import { isMobileDevice } from 'config/userAgent'
export const REWIND_SECONDS = isMobileDevice ? 10 : 5
export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000

@ -1,35 +0,0 @@
import { RefObject } from 'react'
import findIndex from 'lodash/findIndex'
import type { Chapters, Players } from '../types'
type Args = {
from?: number,
url: string,
videoRef: RefObject<HTMLVideoElement>,
}
export const preparePlayer = ({
from = 0,
url,
videoRef,
}: Args) => {
const video = videoRef?.current
if (!video) return
// eslint-disable-next-line no-param-reassign
video.src = url
if (from) {
video.currentTime = from
}
video.load()
}
export const findChapterByProgress = (chapters: Chapters, progressMs: number) => (
findIndex(chapters, ({ endMs, startMs }) => (
startMs <= progressMs && progressMs <= endMs
))
)
export const getNextPlayer = (player: Players): Players => (player + 1) % 2

@ -1,494 +0,0 @@
import type { MouseEvent } from 'react'
import {
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import { useTour } from '@reactour/tour'
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 { useControlsVisibility } from 'features/StreamPlayer/hooks/useControlsVisibility'
import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen'
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup'
import { useMatchPageStore } from 'features/MatchPage/store'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import {
useEventListener,
useInterval,
useObjectState,
usePageParams,
} from 'hooks'
import type { SetPartialState } from 'hooks'
import {
MatchInfo,
saveMatchStats,
VIEW_INTERVAL_MS,
} from 'requests'
import type { PausedData } from 'features/MatchPage/components/FinishedMatch/hooks'
import { useProgressChangeHandler } from './useProgressChangeHandler'
import { usePlayingHandlers } from './usePlayingHandlers'
import { useVideoQuality } from './useVideoQuality'
import { useDuration } from './useDuration'
import type { Chapters } from '../types'
import { Players } from '../types'
import { getNextPlayer } from '../helpers'
import { REWIND_SECONDS } from '../config'
const initialState = {
activeChapterIndex: 0,
activePlayer: Players.PLAYER1,
loadedProgress: 0,
playedProgress: 0,
playing: false,
ready: false,
seek: {
[Players.PLAYER1]: 0,
[Players.PLAYER2]: 0,
},
seeking: false,
}
export type PlayerState = typeof initialState
export type Props = {
access: boolean,
chapters: Chapters,
isOpenPopup?: boolean,
onError?: () => void,
onPlayingChange: (playing: boolean) => void,
pausedData: PausedData,
profile: MatchInfo,
setPausedData: SetPartialState<PausedData>,
}
export const useMultiSourcePlayer = ({
chapters,
onError,
onPlayingChange,
pausedData,
setPausedData,
}: Props) => {
const [currentPlayingOrder, setCurrentPlayingOrder] = useState(0)
const ordersObj = useRef<Record<string, number>>({})
const {
disablePlayingEpisodes,
handlePlaylistClick,
isPlayFilterEpisodes,
isPlayingEpisode,
matchPlaylists,
playingOrder,
playingProgress,
playNextEpisode,
selectedPlaylist,
setCircleAnimation,
setPlayingProgress,
} = useMatchPageStore()
const { isOpen } = useTour()
const { profileId, sportType } = usePageParams()
/** время для сохранения статистики просмотра матча */
const timeForStatistics = useRef(0)
const numberOfChapters = size(chapters)
const [
{
activeChapterIndex: chapterIndex,
activePlayer,
loadedProgress,
playedProgress,
playing: statePlaying,
ready,
seek,
seeking,
},
setPlayerState,
] = useObjectState(initialState)
const video1Ref = useRef<HTMLVideoElement>(null)
const video2Ref = useRef<HTMLVideoElement>(null)
const activeChapterIndex = numberOfChapters >= chapterIndex ? chapterIndex : 0
const {
onReady,
playNextChapter,
playPrevChapter,
stopPlaying,
togglePlaying,
} = usePlayingHandlers(setPlayerState, numberOfChapters)
const {
selectedQuality,
setSelectedQuality,
videoQualities,
} = useVideoQuality(chapters)
const duration = useDuration(chapters)
const playing = Boolean(statePlaying && !isOpen)
const handleError = useCallback(() => {
onError?.()
}, [onError])
const getActiveChapter = useCallback(
(index: number = activeChapterIndex) => chapters[index],
[chapters, activeChapterIndex],
)
const videoRef = [video1Ref, video2Ref][activePlayer]
const isFirstChapterPlaying = activeChapterIndex === 0
const isLastChapterPlaying = activeChapterIndex === numberOfChapters - 1
const setPlayerTime = (progressMs: number) => {
if (!videoRef.current) return
videoRef.current.currentTime = progressMs / 1000
}
const rewindForward = () => {
const chapter = getActiveChapter()
const newProgress = playedProgress + REWIND_SECONDS * 1000
if (newProgress <= chapter.duration) {
setPlayerTime(chapter.startOffsetMs + newProgress)
} else if (isLastChapterPlaying) {
playNextChapter()
} else {
const nextChapter = getActiveChapter(activeChapterIndex + 1)
const fromMs = newProgress - chapter.duration
playNextChapter(fromMs, nextChapter.startOffsetMs)
}
}
const rewindBackward = () => {
const chapter = getActiveChapter()
const newProgress = playedProgress - REWIND_SECONDS * 1000
if (newProgress >= 0) {
setPlayerTime(chapter.startOffsetMs + newProgress)
} else if (isFirstChapterPlaying) {
setPlayerTime(chapter.startOffsetMs)
} else {
const prevChapter = getActiveChapter(activeChapterIndex - 1)
const fromMs = prevChapter.duration + newProgress
playPrevChapter(fromMs, prevChapter.startOffsetMs)
}
}
const onProgressChange = useProgressChangeHandler({
chapters,
duration,
setPlayerState,
})
const {
isFullscreen,
onFullscreenClick,
wrapperRef,
} = useFullscreen(videoRef)
const getChapterUrl = useCallback((index: number, quality: string = selectedQuality) => (
getActiveChapter(index)?.urls[quality]
), [selectedQuality, getActiveChapter])
const onQualitySelect = (quality: string) => {
setPlayerState((state) => ({
seek: {
...state.seek,
[state.activePlayer]: videoRef.current?.currentTime ?? 0,
},
}))
setSelectedQuality(quality)
}
const onPlayerClick = (e: MouseEvent<HTMLDivElement>) => {
if (e.target === videoRef.current) {
togglePlaying()
}
}
const onLoadedProgress = (loadedMs: number) => {
const chapter = getActiveChapter()
const value = loadedMs - chapter.startOffsetMs
setPlayerState({ loadedProgress: value })
}
const onPlayedProgress = (playedMs: number) => {
const chapter = getActiveChapter()
const value = Math.max(playedMs - chapter.startOffsetMs, 0)
timeForStatistics.current = (value + chapter.startMs) / 1000
setPlayerState({ playedProgress: value })
setPlayingProgress(Math.floor(value / 1000))
if (chapter.isFullMatchChapter) {
setPausedData({
activeChapterIndex,
activePlayer,
playedProgress: value,
})
}
}
const backToPausedTime = useCallback(() => {
if (selectedPlaylist?.id !== FULL_GAME_KEY) {
// @ts-expect-error
handlePlaylistClick(matchPlaylists.match[0])
}
setTimeout(() => {
setPlayerState((state) => ({
activeChapterIndex: pausedData.activeChapterIndex,
playedProgress: pausedData.playedProgress,
seek: {
...state.seek,
[pausedData.activePlayer]: pausedData.playedProgress / 1000,
},
}))
}, 0)
}, [
selectedPlaylist?.id,
pausedData,
handlePlaylistClick,
matchPlaylists.match,
setPlayerState,
])
const onEnded = () => {
playNextChapter()
}
const onPause = () => {
setPlayerState({ playing: false })
}
const restartVideo = () => {
// @ts-expect-error
handlePlaylistClick(matchPlaylists.match[0])
}
const stopPlayingEpisodes = () => {
disablePlayingEpisodes()
restartVideo()
setTimeout(() => {
setPlayerState((state) => ({
activeChapterIndex: pausedData.activeChapterIndex,
playedProgress: pausedData.playedProgress,
seek: {
...state.seek,
[pausedData.activePlayer]: pausedData.playedProgress / 1000,
},
}))
}, 1000)
}
useEffect(() => {
onPlayingChange(playing)
}, [playing, onPlayingChange])
useEffect(() => {
setPlayerState({ ...initialState })
}, [profileId, setPlayerState])
useEffect(() => {
setCircleAnimation((state) => ({
...state,
playedProgress,
playing,
ready,
}))
}, [
playedProgress,
playing,
ready,
setCircleAnimation,
])
useEffect(() => {
setPlayerState((state) => ({
...initialState,
activePlayer: getNextPlayer(state.activePlayer),
playing: true,
}))
}, [chapters, setPlayerState])
useEffect(() => {
const { duration: chapterDuration } = getActiveChapter()
if (playedProgress >= chapterDuration && !seeking && isPlayFilterEpisodes) {
setPlayerState({ playedProgress: 0 })
playNextEpisode()
}
if (playedProgress >= chapterDuration && !seeking && !isPlayFilterEpisodes) {
if (isLastChapterPlaying) {
backToPausedTime()
} else {
playNextChapter()
}
}
}, [
isPlayFilterEpisodes,
playNextEpisode,
getActiveChapter,
playedProgress,
seeking,
playNextChapter,
setPlayerState,
isLastChapterPlaying,
backToPausedTime,
])
useEventListener({
callback: (e: KeyboardEvent) => {
if (isOpen) return
if (e.code === KEYBOARD_KEYS.ArrowLeft) rewindBackward()
else if (e.code === KEYBOARD_KEYS.ArrowRight) rewindForward()
},
event: 'keydown',
})
const { isOnline } = useNoNetworkPopupStore()
useEffect(() => {
if (!isOnline) {
stopPlaying()
}
}, [
isOnline,
stopPlaying,
])
useEffect(() => {
if (ready && videoRef && !isPlayingEpisode) {
videoRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
})
}
}, [ready, videoRef, isPlayingEpisode])
// ведем статистику просмотра матча
const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({
callback: () => {
if (timeForStatistics.current !== 0) {
saveMatchStats({
matchId: profileId,
matchSecond: timeForStatistics.current,
sportType,
})
}
},
intervalDuration: VIEW_INTERVAL_MS,
startImmediate: false,
})
useEffect(() => {
if (playing) {
startCollectingStats()
} else {
stopCollectingStats()
}
}, [
playing,
startCollectingStats,
stopCollectingStats,
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[playingOrder] !== index) {
ordersObj.current = {
...ordersObj.current,
[playingOrder]: index,
}
}
return playingOrder + sum(values(ordersObj.current))
}
setCurrentPlayingOrder(getCurrentPlayingOrder())
}, [getActiveChapter, isPlayingEpisode, playingOrder, playingProgress])
useEffect(() => {
if (!isPlayingEpisode) {
ordersObj.current = {}
}
}, [isPlayingEpisode])
return {
activeChapterIndex,
activePlayer,
activeSrc: getChapterUrl(activeChapterIndex),
allPlayedProgress: playedProgress + getActiveChapter().startMs,
chapters,
currentPlayingOrder,
duration,
isFirstChapterPlaying,
isFullscreen,
isLastChapterPlaying,
loadedProgress,
nextSrc: getChapterUrl((activeChapterIndex + 1) % numberOfChapters),
numberOfChapters,
onEnded,
onError: handleError,
onFullscreenClick,
onLoadedProgress,
onPause,
onPlayedProgress,
onPlayerClick,
onProgressChange,
onQualitySelect,
onReady,
playNextChapter,
playPrevChapter,
playedProgress,
playing,
ready,
rewindBackward,
rewindForward,
seek,
selectedQuality,
stopPlayingEpisodes,
togglePlaying,
video1Ref,
video2Ref,
videoQualities,
wrapperRef,
...useControlsVisibility(isFullscreen, playing),
...useVolume(),
}
}

@ -1,102 +0,0 @@
import { useCallback } from 'react'
import isUndefined from 'lodash/isUndefined'
import type { SetPartialState } from 'hooks'
import type { PlayerState } from '.'
import { getNextPlayer } from '../helpers'
export const usePlayingHandlers = (
setPlayerState: SetPartialState<PlayerState>,
numberOfChapters: number,
) => {
const onReady = useCallback(() => {
setPlayerState((state) => (
state.ready
? state
: { playing: true, ready: true }
))
}, [setPlayerState])
const togglePlaying = useCallback(() => {
setPlayerState((state) => (
state.ready
? { playing: !state.playing }
: state
))
}, [setPlayerState])
const stopPlaying = useCallback(() => {
setPlayerState({ playing: false })
}, [setPlayerState])
const startPlaying = useCallback(() => {
setPlayerState((state) => (
state.ready
? { playing: true }
: state
))
}, [setPlayerState])
const playNextChapter = useCallback((fromMs?: number, startOffsetMs?: number) => {
setPlayerState((state) => {
if (!state.ready) return state
const isLastChapter = state.activeChapterIndex + 1 === numberOfChapters
if (isLastChapter || isUndefined(fromMs) || isUndefined(startOffsetMs)) {
return {
activeChapterIndex: isLastChapter ? 0 : state.activeChapterIndex + 1,
activePlayer: getNextPlayer(state.activePlayer),
loadedProgress: 0,
playedProgress: 0,
playing: isLastChapter ? false : state.playing,
}
}
return {
activeChapterIndex: state.activeChapterIndex + 1,
loadedProgress: 0,
playedProgress: fromMs,
playing: state.playing,
seek: {
...state.seek,
[state.activePlayer]: (startOffsetMs + fromMs) / 1000,
},
}
})
}, [numberOfChapters, setPlayerState])
const playPrevChapter = useCallback((fromMs?: number, startOffsetMs?: number) => {
setPlayerState((state) => {
if (!state.ready || state.activeChapterIndex === 0) return state
if (isUndefined(fromMs) || isUndefined(startOffsetMs)) {
return {
activeChapterIndex: state.activeChapterIndex - 1,
loadedProgress: 0,
playedProgress: 0,
}
}
return {
activeChapterIndex: state.activeChapterIndex - 1,
loadedProgress: 0,
playedProgress: fromMs,
seek: {
...state.seek,
[state.activePlayer]: (startOffsetMs + fromMs) / 1000,
},
}
})
}, [setPlayerState])
return {
onReady,
playNextChapter,
playPrevChapter,
startPlaying,
stopPlaying,
togglePlaying,
}
}

@ -1,51 +0,0 @@
import { useCallback } from 'react'
import type { SetPartialState } from 'hooks'
import type { Chapters } from '../types'
import type { PlayerState } from '.'
import { findChapterByProgress } from '../helpers'
type Args = {
chapters: Chapters,
duration: number,
setPlayerState: SetPartialState<PlayerState>,
}
export const useProgressChangeHandler = ({
chapters,
duration,
setPlayerState,
}: Args) => {
const onProgressChange = useCallback((progress: number, seeking: boolean) => {
setPlayerState((state) => {
// значение новой позиции ползунка в миллисекундах
const progressMs = progress * duration
const chapterIndex = findChapterByProgress(chapters, progressMs)
const chapter = chapters[chapterIndex]
const isProgressOnDifferentChapter = (
chapterIndex !== -1
&& chapterIndex !== state.activeChapterIndex
)
const nextChapter = isProgressOnDifferentChapter
? chapterIndex
: state.activeChapterIndex
// отнимаем начало главы на котором остановились от общего прогресса
// чтобы получить прогресс текущей главы
const chapterProgressMs = (progressMs - chapter.startMs)
const seekMs = chapterProgressMs + chapter.startOffsetMs
return {
activeChapterIndex: nextChapter,
playedProgress: chapterProgressMs,
seek: {
...state.seek,
[state.activePlayer]: seekMs / 1000,
},
seeking,
}
})
}, [duration, chapters, setPlayerState])
return onProgressChange
}

@ -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,
}
}

@ -1,268 +0,0 @@
import { Fragment } from 'react'
import { Loader } from 'features/Loader'
import {
PlayerWrapper,
CenterControls,
PlayStop,
LoaderWrapper,
Backward,
Forward,
ControlsGradient,
TeamsDetailsWrapper,
EpisodeInfo,
EpisodeInfoName,
EpisodeInfoOrder,
EpisodeInfoDivider,
CloseButton,
} from 'features/StreamPlayer/styled'
import { VideoPlayer } from 'features/VideoPlayer'
import { Controls } from 'features/StreamPlayer/components/Controls'
import { Name } from 'features/Name'
import RewindMobile from 'features/StreamPlayer/components/RewindMobile'
import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { useAuthStore } from 'features/AuthStore'
import { useMatchPageStore } from 'features/MatchPage/store'
import { isMobileDevice } from 'config/userAgent'
import { AccessTimer } from 'components/AccessTimer'
import type { Props } from './hooks'
import { useMultiSourcePlayer } from './hooks'
import { Players } from './types'
import { REWIND_SECONDS } from './config'
export const MultiSourcePlayer = (props: Props) => {
const {
access,
isOpenPopup,
profile,
} = props
const {
activeChapterIndex,
activePlayer,
activeSrc,
allPlayedProgress,
centerControlsVisible,
chapters,
currentPlayingOrder,
duration,
hideCenterControls,
isFirstChapterPlaying,
isFullscreen,
isLastChapterPlaying,
loadedProgress,
mainControlsVisible,
muted,
nextSrc,
numberOfChapters,
onEnded,
onError,
onFullscreenClick,
onLoadedProgress,
onMouseEnter,
onMouseLeave,
onMouseMove,
onPause,
onPlayedProgress,
onPlayerClick,
onProgressChange,
onQualitySelect,
onReady,
onTouchEnd,
onTouchStart,
onVolumeChange,
onVolumeClick,
playedProgress,
playing,
playNextChapter,
playPrevChapter,
ready,
rewindBackward,
rewindForward,
seek,
selectedQuality,
showCenterControls,
stopPlayingEpisodes,
togglePlaying,
video1Ref,
video2Ref,
videoQualities,
volume,
volumeInPercent,
wrapperRef,
} = useMultiSourcePlayer(props)
const { episodeInfo, isPlayingEpisode } = useMatchPageStore()
const firstPlayerActive = activePlayer === Players.PLAYER1
const currentVideo = firstPlayerActive ? video1Ref : video2Ref
const { user } = useAuthStore()
return (
<PlayerWrapper
ref={wrapperRef}
playing={playing}
onClick={isMobileDevice ? showCenterControls : onPlayerClick}
onMouseMove={onMouseMove}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
>
{isPlayingEpisode && (
<EpisodeInfo>
<EpisodeInfoName>
{isMobileDevice
? (
<Fragment>
{episodeInfo.playerOrTeamName}
{episodeInfo.playerOrTeamName && <br />}
{episodeInfo.paramName}
</Fragment>
)
: `${episodeInfo.playerOrTeamName || ''}${episodeInfo.paramName && episodeInfo.playerOrTeamName ? ' - ' : ''}${episodeInfo.paramName || ''}`}
</EpisodeInfoName>
{currentPlayingOrder > 0 && (
<EpisodeInfoOrder>
{currentPlayingOrder}
<EpisodeInfoDivider>/</EpisodeInfoDivider>
{episodeInfo.episodesCount}
</EpisodeInfoOrder>
)}
<CloseButton onClick={stopPlayingEpisodes} />
</EpisodeInfo>
)}
{
!ready && (
<LoaderWrapper buffering>
<Loader color='#515151' />
</LoaderWrapper>
)
}
{isOpenPopup && <FiltersPopup />}
<VideoPlayer
src={firstPlayerActive ? activeSrc : nextSrc}
playing={firstPlayerActive ? playing : false}
hidden={!firstPlayerActive}
muted={muted}
volume={volume}
ref={video1Ref}
seek={seek[Players.PLAYER1]}
isFullscreen={isFullscreen}
onLoadedProgress={firstPlayerActive ? onLoadedProgress : undefined}
onPlayedProgress={firstPlayerActive ? onPlayedProgress : undefined}
onEnded={onEnded}
onPause={firstPlayerActive ? onPause : undefined}
onError={onError}
onReady={onReady}
/>
<VideoPlayer
src={!firstPlayerActive ? activeSrc : nextSrc}
playing={!firstPlayerActive ? playing : false}
hidden={firstPlayerActive}
muted={muted}
volume={volume}
ref={video2Ref}
seek={seek[Players.PLAYER2]}
isFullscreen={isFullscreen}
onLoadedProgress={!firstPlayerActive ? onLoadedProgress : undefined}
onPlayedProgress={!firstPlayerActive ? onPlayedProgress : undefined}
onEnded={onEnded}
onPause={!firstPlayerActive ? onPause : undefined}
onError={onError}
onReady={onReady}
/>
{isMobileDevice && isFullscreen && mainControlsVisible && profile && (
<TeamsDetailsWrapper>
<Name nameObj={profile.team1} />
{` ${profile.team1.score}-${profile.team2.score} `}
<Name nameObj={profile.team2} />
</TeamsDetailsWrapper>
)}
{ready && (
<CenterControls
controlsVisible={centerControlsVisible}
playing={!currentVideo.current?.paused}
>
{isMobileDevice && playing ? (
<RewindMobile isBackward rewindCallback={rewindBackward} />
) : (
<Backward
size={isMobileDevice ? 'sm' : 'lg'}
onClick={rewindBackward}
>
{REWIND_SECONDS}
</Backward>
)}
<PlayStop
size='lg'
marginRight={0}
playing={playing}
onClickCapture={() => {
togglePlaying()
hideCenterControls()
}}
/>
{isMobileDevice && playing
? <RewindMobile isForward rewindCallback={rewindForward} />
: (
<Forward
size={isMobileDevice ? 'sm' : 'lg'}
onClick={rewindForward}
>
{REWIND_SECONDS}
</Forward>
)}
</CenterControls>
)}
<Controls
activeChapterIndex={activeChapterIndex}
activePlayer={activePlayer}
allPlayedProgress={allPlayedProgress}
multiSourceChapters={chapters}
controlsVisible={mainControlsVisible}
duration={duration}
isFirstChapterPlaying={isFirstChapterPlaying}
isFullscreen={isFullscreen}
isLastChapterPlaying={isLastChapterPlaying}
loadedProgress={loadedProgress}
muted={muted}
numberOfChapters={numberOfChapters}
onFullscreenClick={onFullscreenClick}
onProgressChange={onProgressChange}
onQualitySelect={onQualitySelect}
onTouchEnd={onTouchEnd}
onTouchStart={onTouchStart}
onVolumeChange={onVolumeChange}
onVolumeClick={onVolumeClick}
playNextChapter={playNextChapter}
playPrevChapter={playPrevChapter}
playedProgress={playedProgress}
playing={playing}
rewindBackward={rewindBackward}
rewindForward={rewindForward}
selectedQuality={selectedQuality}
src={firstPlayerActive ? activeSrc : nextSrc}
togglePlaying={togglePlaying}
videoQualities={videoQualities}
videoRef={currentVideo}
volume={volume}
volumeInPercent={volumeInPercent}
/>
<ControlsGradient isVisible={mainControlsVisible} />
{!user && (
<AccessTimer
access={access}
isFullscreen={isFullscreen}
onFullscreenClick={onFullscreenClick}
playing={ready && playing && !!playedProgress}
videoRef={currentVideo}
/>
)}
</PlayerWrapper>
)
}

@ -1,48 +0,0 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { ButtonBase } from 'features/StreamPlayer/styled'
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: 25%;
`
: ''};
`
type PrevProps = {
disabled?: boolean,
}
export const Prev = styled(ButtonBase)<PrevProps>`
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);
`

@ -1,20 +0,0 @@
export type Urls = { [quality: string]: string }
export type Chapter = {
count?: number,
duration: number,
endMs: number,
endOffsetMs: number,
isFullMatchChapter?: boolean,
period: number,
startMs: number,
startOffsetMs: number,
urls: Urls,
}
export type Chapters = Array<Chapter>
export enum Players {
PLAYER1 = 0,
PLAYER2 = 1,
}

@ -1,5 +1,5 @@
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { Settings } from 'features/MultiSourcePlayer/components/Settings' import { Settings } from 'features/StreamPlayer/components/Settings'
import { AirPlay } from 'features/AirPlay' import { AirPlay } from 'features/AirPlay'
import { ChromeCast } from 'features/ChromeCast' import { ChromeCast } from 'features/ChromeCast'
import { AudioTracks } from 'features/AudioTracks' import { AudioTracks } from 'features/AudioTracks'

@ -1,12 +1,12 @@
import { Fragment } from 'react' import { Fragment } from 'react'
import { REWIND_SECONDS } from 'features/MultiSourcePlayer/config' import { REWIND_SECONDS } from 'features/StreamPlayer/config'
import { import {
ChaptersText, ChaptersText,
Next, Next,
Prev, Prev,
} from 'features/MultiSourcePlayer/styled' } from 'features/StreamPlayer/styled'
import { Settings } from 'features/MultiSourcePlayer/components/Settings' import { Settings } from 'features/StreamPlayer/components/Settings'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { ChromeCast } from 'features/ChromeCast' import { ChromeCast } from 'features/ChromeCast'
import { AudioTracks } from 'features/AudioTracks' import { AudioTracks } from 'features/AudioTracks'

@ -8,8 +8,6 @@ import { isMobileDevice } from 'config/userAgent'
import { secondsToHms } from 'helpers/secondsToHms' import { secondsToHms } from 'helpers/secondsToHms'
import { ProgressBar as ProgressBarMultiSource } from 'features/MultiSourcePlayer/components/ProgressBar'
import { Chapters as MultiSourceChapters } from 'features/MultiSourcePlayer/types'
import { Chapters as LiveChapters } from 'features/StreamPlayer/types' import { Chapters as LiveChapters } from 'features/StreamPlayer/types'
import { ControlsMobile } from './Components/ControlsMobile' import { ControlsMobile } from './Components/ControlsMobile'
@ -33,7 +31,6 @@ export type ControlsProps = {
isStorage?: boolean, isStorage?: boolean,
liveChapters?: LiveChapters, liveChapters?: LiveChapters,
loadedProgress: number, loadedProgress: number,
multiSourceChapters?: MultiSourceChapters,
muted: boolean, muted: boolean,
numberOfChapters?: number, numberOfChapters?: number,
onFullscreenClick: () => void, onFullscreenClick: () => void,
@ -74,10 +71,7 @@ export const Controls = ({ ...props }: ControlsProps) => {
isStorage, isStorage,
liveChapters, liveChapters,
loadedProgress, loadedProgress,
multiSourceChapters,
onProgressChange, onProgressChange,
onTouchEnd,
onTouchStart,
playedProgress, playedProgress,
} = props } = props
@ -94,48 +88,25 @@ export const Controls = ({ ...props }: ControlsProps) => {
isLive, isLive,
isStorage, isStorage,
]) ])
const progressBarElement = useMemo(() => { const progressBarElement = useMemo(() => (
if (isLive || isStorage) { <ProgressBar
return ( duration={duration}
<ProgressBar isScrubberVisible={controlsVisible}
duration={duration} onPlayedProgressChange={onProgressChange}
isScrubberVisible={controlsVisible} playedProgress={playedProgress}
onPlayedProgressChange={onProgressChange} loadedProgress={loadedProgress}
playedProgress={playedProgress} activeChapterIndex={activeChapterIndex}
loadedProgress={loadedProgress} allPlayedProgress={allPlayedProgress}
activeChapterIndex={activeChapterIndex} chapters={liveChapters!}
allPlayedProgress={allPlayedProgress} />
chapters={liveChapters!} ), [
/>
)
}
return (
<ProgressBarMultiSource
activeChapterIndex={activeChapterIndex}
allPlayedProgress={allPlayedProgress}
chapters={multiSourceChapters}
duration={duration}
isScrubberVisible={controlsVisible}
onTouchEnd={onTouchEnd}
onTouchStart={onTouchStart}
onPlayedProgressChange={onProgressChange!}
playedProgress={playedProgress}
loadedProgress={loadedProgress}
/>
)
}, [
activeChapterIndex, activeChapterIndex,
allPlayedProgress, allPlayedProgress,
liveChapters, liveChapters,
multiSourceChapters,
controlsVisible, controlsVisible,
duration, duration,
isLive,
isStorage,
loadedProgress, loadedProgress,
onProgressChange, onProgressChange,
onTouchEnd,
onTouchStart,
playedProgress, playedProgress,
]) ])

@ -22,7 +22,7 @@ export const Settings = (props: Props) => {
} = useSettings(props) } = useSettings(props)
return ( return (
<Fragment> <Fragment>
<SettingsButton onClick={open} /> <SettingsButton onClick={open} id='match_video_quality' />
{ {
isOpen && ( isOpen && (
<OutsideClick onClick={close}> <OutsideClick onClick={close}>

@ -5,14 +5,14 @@ import { ButtonBase } from 'features/StreamPlayer/styled'
export const SettingsButton = styled(ButtonBase)` export const SettingsButton = styled(ButtonBase)`
width: 22px; width: 22px;
height: 20px; height: 20px;
margin-left: 25px; margin-right: 25px;
background-image: url(/images/settings.svg); background-image: url(/images/settings.svg);
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 20px; width: 20px;
height: 18px; height: 18px;
margin-left: 10px; margin-right: 15px;
cursor: pointer; cursor: pointer;
` `
: ''}; : ''};

@ -1,51 +0,0 @@
import YouTube from 'react-youtube'
import { useMatchPageStore } from 'features/MatchPage/store'
import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { PlayerWrapper } from '../../styled'
import { useVideoPlayer, Props } from '../../hooks'
export const YoutubePlayer = (props: Props) => {
const { isOpenFiltersPopup, profile } = useMatchPageStore()
const {
onMouseMove,
onPlayerClick,
onTouchEnd,
onTouchStart,
playing,
sizeOptions: {
height,
width,
},
wrapperRef,
} = useVideoPlayer(props)
const youtube_link = profile?.youtube_link || ''
const key = youtube_link.slice(32, youtube_link.length)
return (
<PlayerWrapper
ref={wrapperRef}
playing={playing}
onClick={onPlayerClick}
onMouseMove={onMouseMove}
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
>
{isOpenFiltersPopup && <FiltersPopup />}
<YouTube
videoId={key}
opts={{
height: String(height),
playerVars: {
autoplay: 1,
modestbranding: 1,
},
width: String(width),
}}
/>
</PlayerWrapper>
)
}

@ -27,6 +27,7 @@ import {
useEventListener, useEventListener,
usePageParams, usePageParams,
useInterval, useInterval,
useDuration,
} from 'hooks' } from 'hooks'
import type { Chapters } from 'features/StreamPlayer/types' import type { Chapters } from 'features/StreamPlayer/types'
@ -45,7 +46,6 @@ import { useVideoQuality } from './useVideoQuality'
import { useControlsVisibility } from './useControlsVisibility' import { useControlsVisibility } from './useControlsVisibility'
import { useProgressChangeHandler } from './useProgressChangeHandler' import { useProgressChangeHandler } from './useProgressChangeHandler'
import { usePlayingHandlers } from './usePlayingHandlers' import { usePlayingHandlers } from './usePlayingHandlers'
import { useDuration } from './useDuration'
import { useAudioTrack } from './useAudioTrack' import { useAudioTrack } from './useAudioTrack'
import { FULL_GAME_KEY } from '../../MatchPage/helpers/buildPlaylists' import { FULL_GAME_KEY } from '../../MatchPage/helpers/buildPlaylists'

@ -367,13 +367,12 @@ export const ChaptersText = styled.span`
font-size: 16px; font-size: 16px;
color: #fff; color: #fff;
text-align: center; text-align: center;
white-space: nowrap;
${isMobileDevice ${isMobileDevice
? css` ? css`
margin: 0 5px; margin: 0 5px;
font-size: 12px; font-size: 12px;
width: 15%; width: 25%;
` `
: ''}; : ''};
` `
@ -386,16 +385,18 @@ export const Prev = styled(ButtonBase)<PrevProps>`
width: 29px; width: 29px;
height: 28px; height: 28px;
background-image: url(/images/player-prev.svg); background-image: url(/images/player-prev.svg);
${({ disabled }) => ( ${({ disabled }) => (
disabled disabled
? 'opacity: 0.5;' ? 'opacity: 0.5;'
: '' : ''
)} )}
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 20px; width: 20px;
height: 20px; height: 20px;
` `
: ''}; : ''};
` `

@ -8,3 +8,4 @@ export * from './usePageParams'
export * from './useTooltip' export * from './useTooltip'
export * from './useModalRoot' export * from './useModalRoot'
export * from './usePageLogger' export * from './usePageLogger'
export * from './useDuration'

@ -2,7 +2,7 @@ import { useMemo } from 'react'
import sumBy from 'lodash/sumBy' import sumBy from 'lodash/sumBy'
import type { Chapters } from '../types' import type { Chapters } from 'features/StreamPlayer/types'
export const useDuration = (chapters: Chapters) => ( export const useDuration = (chapters: Chapters) => (
useMemo(() => sumBy(chapters, 'duration'), [chapters]) useMemo(() => sumBy(chapters, 'duration'), [chapters])
Loading…
Cancel
Save