refactor(1701): stream player (#558)

keep-around/fdb88b04b32b9392e76795099e2ec47c9856b38b
Mirlan 4 years ago committed by Andrei Dekterev
parent 950a02c7ae
commit d3b25de38d
  1. 7
      src/features/StreamPlayer/components/Chapters/index.tsx
  2. 14
      src/features/StreamPlayer/components/Chapters/styled.tsx
  3. 0
      src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/__tests__/index.tsx
  4. 2
      src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx
  5. 4
      src/features/StreamPlayer/components/ProgressBar/hooks.tsx
  6. 48
      src/features/StreamPlayer/components/ProgressBar/index.tsx
  7. 44
      src/features/StreamPlayer/components/ProgressBar/stories.tsx
  8. 0
      src/features/StreamPlayer/components/Settings/hooks.tsx
  9. 0
      src/features/StreamPlayer/components/Settings/index.tsx
  10. 0
      src/features/StreamPlayer/components/Settings/styled.tsx
  11. 4
      src/features/StreamPlayer/config.tsx
  12. 4
      src/features/StreamPlayer/helpers/index.tsx
  13. 190
      src/features/StreamPlayer/hooks/index.tsx
  14. 0
      src/features/StreamPlayer/hooks/useDuration.tsx
  15. 5
      src/features/StreamPlayer/hooks/useFullscreen.tsx
  16. 47
      src/features/StreamPlayer/hooks/usePlayingHandlers.tsx
  17. 5
      src/features/StreamPlayer/hooks/useProgressChangeHandler.tsx
  18. 174
      src/features/StreamPlayer/index.tsx
  19. 68
      src/features/StreamPlayer/styled.tsx
  20. 11
      src/features/StreamPlayer/types.tsx
  21. 3
      src/features/VideoPlayer/hooks/index.tsx
  22. 6
      src/features/VideoPlayer/index.tsx

@ -1,14 +1,11 @@
import map from 'lodash/map'
import {
LoadedProgress,
PlayedProgress,
} from 'features/StreamPlayer/components/ProgressBar/styled'
import type { Chapter } from '../../types'
import {
ChapterList,
ChapterContainer,
LoadedProgress,
PlayedProgress,
} from './styled'
type ChapterWithStyles = Chapter & {

@ -16,3 +16,17 @@ export const ChapterContainer = styled.div`
margin-right: 3px;
}
`
export const LoadedProgress = styled.div`
position: absolute;
z-index: 1;
background-color: rgba(255, 255, 255, 0.6);
height: 100%;
`
export const PlayedProgress = styled.div`
position: absolute;
z-index: 2;
background-color: #CC0000;
height: 100%;
`

@ -3,7 +3,7 @@ import pipe from 'lodash/fp/pipe'
import size from 'lodash/fp/size'
import slice from 'lodash/fp/slice'
import type { Chapters, Chapter } from 'features/MultiSourcePlayer/types'
import type { Chapters, Chapter } from 'features/StreamPlayer/types'
const calculateChapterProgress = (progress: number, chapter: Chapter) => (
Math.min(progress * 100 / chapter.duration, 100)

@ -2,13 +2,13 @@ import { useMemo } from 'react'
import { secondsToHms } from 'helpers'
import type { Chapters } from '../../types'
import type { Chapters } from '../../../StreamPlayer/types'
import { calculateChapterStyles } from './helpers/calculateChapterStyles'
export type Props = {
activeChapterIndex: number,
allPlayedProgress: number,
chapters?: Chapters,
chapters: Chapters,
duration: number,
loadedProgress: number,
onPlayedProgressChange: (progress: number, seeking: boolean) => void,

@ -1,43 +1,27 @@
import { secondsToHms } from 'helpers'
import { useSlider } from 'features/StreamPlayer/hooks/useSlider'
import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip'
import { Scrubber } from 'features/StreamPlayer/components/ProgressBar/styled'
import {
ProgressBarList,
LoadedProgress,
PlayedProgress,
Scrubber,
} from './styled'
type Props = {
duration: number,
isScrubberVisible?: boolean,
loadedProgress: number,
onPlayedProgressChange: (progress: number) => void,
playedProgress: number,
}
import { Chapters } from '../Chapters'
import type { Props } from './hooks'
import { useProgressBar } from './hooks'
import { ProgressBarList } from './styled'
export const ProgressBar = ({
duration,
isScrubberVisible,
loadedProgress,
onPlayedProgressChange,
playedProgress,
}: Props) => {
export const ProgressBar = (props: Props) => {
const { onPlayedProgressChange } = props
const progressBarRef = useSlider({ onChange: onPlayedProgressChange })
const loadedFraction = Math.min(loadedProgress * 100 / duration, 100)
const playedFraction = Math.min(playedProgress * 100 / duration, 100)
const {
calculatedChapters,
playedProgressInPercent,
time,
} = useProgressBar(props)
return (
<ProgressBarList ref={progressBarRef}>
<LoadedProgress style={{ width: `${loadedFraction}%` }} />
<PlayedProgress style={{ width: `${playedFraction}%` }} />
{isScrubberVisible === false ? null : (
<Scrubber style={{ left: `${playedFraction}%` }}>
<TimeTooltip time={secondsToHms(playedProgress / 1000)} />
</Scrubber>
)}
<Chapters chapters={calculatedChapters} />
<Scrubber style={{ left: `${playedProgressInPercent}%` }}>
<TimeTooltip time={time} />
</Scrubber>
</ProgressBarList>
)
}

@ -13,7 +13,7 @@ import { ProgressBar } from '.'
const Story = {
component: ProgressBar,
title: 'ProgressBar',
title: 'ProgressBarWithChapters',
}
export default Story
@ -47,9 +47,42 @@ const renderInControls = (progressBarElement: ReactElement) => (
const duration = 70000
const chapters = [
{
duration: 30000,
endMs: 30000,
endOffsetMs: 0,
period: 0,
startMs: 0,
startOffsetMs: 0,
url: '',
},
{
duration: 30000,
endMs: 60000,
endOffsetMs: 0,
period: 0,
startMs: 30000,
startOffsetMs: 0,
url: '',
},
{
duration: 10000,
endMs: 70000,
endOffsetMs: 0,
period: 0,
startMs: 60000,
startOffsetMs: 0,
url: '',
},
]
export const Empty = () => renderInControls(
<ProgressBar
activeChapterIndex={0}
allPlayedProgress={0}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={0}
loadedProgress={0}
@ -58,7 +91,10 @@ export const Empty = () => renderInControls(
export const HalfLoaded = () => renderInControls(
<ProgressBar
activeChapterIndex={0}
allPlayedProgress={0}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={0}
loadedProgress={30000}
@ -67,7 +103,10 @@ export const HalfLoaded = () => renderInControls(
export const HalfPlayed = () => renderInControls(
<ProgressBar
activeChapterIndex={1}
allPlayedProgress={1}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={30000}
loadedProgress={0}
@ -76,7 +115,10 @@ export const HalfPlayed = () => renderInControls(
export const Loaded40AndPlayed20 = () => renderInControls(
<ProgressBar
activeChapterIndex={0}
allPlayedProgress={0}
duration={duration}
chapters={chapters}
onPlayedProgressChange={callback}
playedProgress={20000}
loadedProgress={40000}

@ -11,3 +11,7 @@ export const streamConfig: Partial<Hls.Config> = {
xhr.open('GET', url.toString())
},
}
export const REWIND_SECONDS = 5
export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000

@ -2,7 +2,7 @@ import { RefObject } from 'react'
import findIndex from 'lodash/findIndex'
import type { Chapters, Players } from '../types'
import type { Chapters } from '../types'
type Args = {
from?: number,
@ -31,5 +31,3 @@ export const findChapterByProgress = (chapters: Chapters, progressMs: number) =>
startMs <= progressMs && progressMs <= endMs
))
)
export const getNextPlayer = (player: Players): Players => (player + 1) % 2

@ -2,71 +2,99 @@ import type { MouseEvent } from 'react'
import {
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import once from 'lodash/once'
import size from 'lodash/size'
import isNumber from 'lodash/isNumber'
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
import { REWIND_SECONDS } from 'features/MultiSourcePlayer/config'
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup'
import { isIOS } from 'config/userAgent'
import { useObjectState } from 'hooks'
import { useObjectState } from 'hooks/useObjectState'
import type { MatchInfo } from 'requests/getMatchInfo'
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup'
import { isIOS } from 'config/userAgent'
import type { Chapters } from 'features/StreamPlayer/types'
import { REWIND_SECONDS } from '../config'
import { useHlsPlayer } from './useHlsPlayer'
import { useFullscreen } from './useFullscreen'
import { useVideoQuality } from './useVideoQuality'
import { useControlsVisibility } from './useControlsVisibility'
import { useProgressChangeHandler } from './useProgressChangeHandler'
import { usePlayingHandlers } from './usePlayingHandlers'
import { useDuration } from './useDuration'
export type PlayerState = typeof initialState
const toMilliSeconds = (seconds: number) => seconds * 1000
const initialState = {
activeChapterIndex: 0,
buffering: true,
duration: 0,
loadedProgress: 0,
playedProgress: 0,
playing: false,
ready: false,
seek: 0,
seeking: false,
}
export type Props = {
chapters: Chapters,
isLive?: boolean,
onDurationChange?: (duration: number) => void,
onPlayingChange: (playing: boolean) => void,
onProgressChange: (seconds: number) => void,
profile: MatchInfo,
resumeFrom?: number,
url: string,
}
export const useVideoPlayer = ({
chapters,
isLive,
onDurationChange,
onPlayingChange,
onProgressChange: progressChangeCallback,
resumeFrom,
url,
}: Props) => {
const { url } = chapters[0]
const numberOfChapters = size(chapters)
const { hls, videoRef } = useHlsPlayer(url, resumeFrom)
const [{
duration,
activeChapterIndex,
buffering,
duration: fullMatchDuration,
loadedProgress,
playedProgress,
playing,
ready,
seek,
seeking,
}, setPlayerState] = useObjectState({
...initialState,
playedProgress: toMilliSeconds(resumeFrom || 0),
seek: resumeFrom || 0,
})
const startPlaying = useMemo(() => once(() => {
setPlayerState({ playing: true, ready: true })
onPlayingChange(true)
}), [onPlayingChange, setPlayerState])
const chaptersDuration = useDuration(chapters)
const duration = isLive ? fullMatchDuration : chaptersDuration
const {
onReady,
playNextChapter,
playPrevChapter,
stopPlaying,
togglePlaying,
} = usePlayingHandlers(setPlayerState, chapters)
const getActiveChapter = useCallback(
(index: number = activeChapterIndex) => chapters[index],
[chapters, activeChapterIndex],
)
const {
isFullscreen,
@ -78,18 +106,39 @@ export const useVideoPlayer = ({
width: wrapperRef.current?.clientWidth,
})
const togglePlaying = () => {
if (ready) {
setPlayerState({ playing: !playing })
onPlayingChange(!playing)
const isFirstChapterPlaying = activeChapterIndex === 0
const isLastChapterPlaying = activeChapterIndex === numberOfChapters - 1
const seekTo = useCallback((progressMs: number) => {
if (!videoRef.current) return
videoRef.current.currentTime = progressMs / 1000
}, [videoRef])
const rewindForward = () => {
const chapter = getActiveChapter()
const newProgress = playedProgress + REWIND_SECONDS * 1000
if (newProgress <= chapter.duration) {
seekTo(chapter.startOffsetMs + newProgress)
} else if (isLastChapterPlaying) {
playNextChapter()
} else {
const nextChapter = getActiveChapter(activeChapterIndex + 1)
const fromMs = newProgress - chapter.duration
playNextChapter(fromMs, nextChapter.startOffsetMs)
}
}
const rewind = (seconds: number) => () => {
if (!videoRef.current) return
const { currentTime } = videoRef.current
const newProgress = currentTime + seconds
videoRef.current.currentTime = newProgress
const rewindBackward = () => {
const chapter = getActiveChapter()
const newProgress = playedProgress - REWIND_SECONDS * 1000
if (newProgress >= 0) {
seekTo(chapter.startOffsetMs + newProgress)
} else if (isFirstChapterPlaying) {
seekTo(chapter.startOffsetMs)
} else {
const prevChapter = getActiveChapter(activeChapterIndex - 1)
const fromMs = prevChapter.duration + newProgress
playPrevChapter(fromMs, prevChapter.startOffsetMs)
}
}
const onError = useCallback(() => {
@ -102,22 +151,45 @@ export const useVideoPlayer = ({
}
}
const onWaiting = () => {
setPlayerState({ buffering: true })
}
const onPlaying = () => {
setPlayerState({ buffering: false })
}
const onPause = () => {
setPlayerState({ playing: false })
}
const onPlay = () => {
setPlayerState({ playing: true })
}
const onDuration = (durationSeconds: number) => {
setPlayerState({ duration: toMilliSeconds(durationSeconds) })
onDurationChange?.(durationSeconds)
}
const onProgressChange = useCallback((progress: number) => {
const progressMs = progress * duration
setPlayerState({ playedProgress: progressMs, seek: progressMs / 1000 })
}, [duration, setPlayerState])
const onProgressChange = useProgressChangeHandler({
chapters,
duration,
setPlayerState,
})
const onLoadedProgress = (loadedMs: number) => {
setPlayerState({ loadedProgress: loadedMs })
const chapter = getActiveChapter()
const value = loadedMs - chapter.startOffsetMs
setPlayerState({ loadedProgress: value })
}
const onPlayedProgress = (playedMs: number) => {
setPlayerState({ playedProgress: playedMs })
progressChangeCallback(playedMs / 1000)
const chapter = getActiveChapter()
const value = Math.max(playedMs - chapter.startOffsetMs, 0)
setPlayerState({ playedProgress: value })
progressChangeCallback(value / 1000)
}
const backToLive = useCallback(() => {
@ -125,6 +197,36 @@ export const useVideoPlayer = ({
setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 })
}, [duration, setPlayerState])
useEffect(() => {
if (isNumber(seek)) {
setPlayerState({ seek: undefined })
}
}, [seek, setPlayerState])
useEffect(() => {
onPlayingChange(playing)
}, [playing, onPlayingChange])
useEffect(() => {
setPlayerState({
...initialState,
playing: true,
seek: chapters[0].startOffsetMs / 1000,
})
}, [chapters, setPlayerState])
useEffect(() => {
const { duration: chapterDuration } = getActiveChapter()
if (playedProgress >= chapterDuration && !seeking) {
playNextChapter()
}
}, [
getActiveChapter,
playedProgress,
seeking,
playNextChapter,
])
const { isOnline } = useNoNetworkPopupStore()
useEffect(() => {
@ -138,13 +240,11 @@ export const useVideoPlayer = ({
useEffect(() => {
if (!isOnline) {
setPlayerState({ playing: false })
onPlayingChange(false)
stopPlaying()
}
}, [
isOnline,
onPlayingChange,
setPlayerState,
stopPlaying,
])
useEffect(() => {
@ -160,26 +260,40 @@ export const useVideoPlayer = ({
}, [setPlayerState])
return {
activeChapterIndex,
allPlayedProgress: playedProgress + getActiveChapter().startMs,
backToLive,
buffering,
duration,
isFirstChapterPlaying,
isFullscreen,
isLastChapterPlaying,
loadedProgress,
numberOfChapters,
onDuration,
onError,
onFullscreenClick,
onLoadedProgress,
onPause,
onPlay,
onPlayedProgress,
onPlayerClick,
onPlaying,
onProgressChange,
onReady,
onWaiting,
playNextChapter,
playPrevChapter,
playedProgress,
playing,
ready,
rewindBackward: rewind(-REWIND_SECONDS),
rewindForward: rewind(REWIND_SECONDS),
rewindBackward,
rewindForward,
seek,
sizeOptions,
startPlaying,
togglePlaying,
url,
videoRef,
wrapperRef,
...useControlsVisibility(isFullscreen),

@ -43,11 +43,8 @@ export const useFullscreen = () => {
}
}
/**
* В обертке могут быть 2 плеера, находим тот который играет сейчас, т.е. не скрыт
*/
const getPlayingVideoElement = () => (
wrapperRef.current?.querySelector('video:not([hidden])') as HTMLVideoElement | null
wrapperRef.current?.querySelector('video') as HTMLVideoElement | null
)
const toggleIOSFullscreen = () => {

@ -5,11 +5,11 @@ import isUndefined from 'lodash/isUndefined'
import type { SetPartialState } from 'hooks'
import type { PlayerState } from '.'
import { getNextPlayer } from '../helpers'
import type { Chapters } from '../types'
export const usePlayingHandlers = (
setPlayerState: SetPartialState<PlayerState>,
numberOfChapters: number,
chapters: Chapters,
) => {
const onReady = useCallback(() => {
setPlayerState((state) => (
@ -43,53 +43,60 @@ export const usePlayingHandlers = (
setPlayerState((state) => {
if (!state.ready) return state
const isLastChapter = state.activeChapterIndex + 1 === numberOfChapters
if (isLastChapter || isUndefined(fromMs) || isUndefined(startOffsetMs)) {
const nextChapterIndex = state.activeChapterIndex + 1
const nextChapter = chapters[nextChapterIndex]
if (!nextChapter) {
return {
activeChapterIndex: isLastChapter ? 0 : state.activeChapterIndex + 1,
activePlayer: getNextPlayer(state.activePlayer),
activeChapterIndex: 0,
loadedProgress: 0,
playedProgress: 0,
playing: isLastChapter ? false : state.playing,
playing: false,
seek: chapters[0].startOffsetMs / 1000,
seeking: false,
}
}
if (isUndefined(fromMs) || isUndefined(startOffsetMs)) {
return {
activeChapterIndex: nextChapterIndex,
loadedProgress: 0,
playedProgress: 0,
seek: nextChapter.startOffsetMs / 1000,
}
}
return {
activeChapterIndex: state.activeChapterIndex + 1,
activeChapterIndex: nextChapterIndex,
loadedProgress: 0,
playedProgress: fromMs,
playing: state.playing,
seek: {
...state.seek,
[state.activePlayer]: (startOffsetMs + fromMs) / 1000,
},
seek: (startOffsetMs + fromMs) / 1000,
}
})
}, [numberOfChapters, setPlayerState])
}, [chapters, setPlayerState])
const playPrevChapter = useCallback((fromMs?: number, startOffsetMs?: number) => {
setPlayerState((state) => {
if (!state.ready || state.activeChapterIndex === 0) return state
const prevChapterIndex = state.activeChapterIndex - 1
const prevChapter = chapters[prevChapterIndex]
if (isUndefined(fromMs) || isUndefined(startOffsetMs)) {
return {
activeChapterIndex: state.activeChapterIndex - 1,
activeChapterIndex: prevChapterIndex,
loadedProgress: 0,
playedProgress: 0,
seek: prevChapter.startOffsetMs / 1000,
}
}
return {
activeChapterIndex: state.activeChapterIndex - 1,
activeChapterIndex: prevChapterIndex,
loadedProgress: 0,
playedProgress: fromMs,
seek: {
...state.seek,
[state.activePlayer]: (startOffsetMs + fromMs) / 1000,
},
seek: (startOffsetMs + fromMs) / 1000,
}
})
}, [setPlayerState])
}, [chapters, setPlayerState])
return {
onReady,

@ -38,10 +38,7 @@ export const useProgressChangeHandler = ({
return {
activeChapterIndex: nextChapter,
playedProgress: chapterProgressMs,
seek: {
...state.seek,
[state.activePlayer]: seekMs / 1000,
},
seek: seekMs / 1000,
seeking,
}
})

@ -1,34 +1,53 @@
import { Fragment } from 'react'
import { T9n } from 'features/T9n'
import { Loader } from 'features/Loader'
import { REWIND_SECONDS } from 'features/MultiSourcePlayer/config'
import { VideoPlayer } from 'features/VideoPlayer'
import { Name } from 'features/Name'
import { isMobileDevice } from 'config/userAgent'
import { secondsToHms } from 'helpers'
import { HOUR_IN_MILLISECONDS, REWIND_SECONDS } from './config'
import { VolumeBar } from './components/VolumeBar'
import { Settings } from './components/Settings'
import { ProgressBar } from './components/ProgressBar'
import {
PlayerWrapper,
LoaderWrapper,
ControlsGradient,
Controls,
ControlsRow,
ControlsGroup,
CenterControls,
Backward,
PlayStop,
Fullscreen,
LoaderWrapper,
Backward,
Forward,
TeamsDetailsWrapper,
PlaybackTime,
ControlsGradient,
LiveBtn,
ChaptersText,
Next,
Prev,
} from './styled'
import type { Props } from './hooks'
import { useVideoPlayer } from './hooks'
import { Controls } from './components/Controls'
export const StreamPlayer = (props: Props) => {
const { profile, url } = props
const { chapters, isLive } = props
const {
activeChapterIndex,
allPlayedProgress,
backToLive,
buffering,
controlsVisible,
duration,
isFirstChapterPlaying,
isFullscreen,
isLastChapterPlaying,
loadedProgress,
muted,
numberOfChapters,
onDuration,
onError,
onFullscreenClick,
@ -36,23 +55,30 @@ export const StreamPlayer = (props: Props) => {
onMouseEnter,
onMouseLeave,
onMouseMove,
onPause,
onPlay,
onPlayedProgress,
onPlayerClick,
onPlaying,
onProgressChange,
onQualitySelect,
onReady,
onTouchEnd,
onTouchStart,
onVolumeChange,
onVolumeClick,
onWaiting,
playedProgress,
playing,
playNextChapter,
playPrevChapter,
ready,
rewindBackward,
rewindForward,
seek,
selectedQuality,
startPlaying,
togglePlaying,
url,
videoQualities,
videoRef,
volume,
@ -71,11 +97,9 @@ export const StreamPlayer = (props: Props) => {
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}
>
{!ready && (
<LoaderWrapper>
<Loader color='#515151' />
</LoaderWrapper>
)}
<LoaderWrapper buffering={buffering}>
<Loader color='#515151' />
</LoaderWrapper>
<VideoPlayer
width='100%'
height='100%'
@ -90,19 +114,14 @@ export const StreamPlayer = (props: Props) => {
onPlayedProgress={onPlayedProgress}
onDurationChange={onDuration}
onEnded={togglePlaying}
onReady={startPlaying}
onReady={onReady}
onPause={onPause}
onPlay={onPlay}
onPlaying={onPlaying}
onWaiting={onWaiting}
onError={onError}
crossOrigin='use-credentials'
/>
{isMobileDevice && isFullscreen && controlsVisible && profile && (
<TeamsDetailsWrapper>
<Name nameObj={profile.team1} />
{` ${profile.team1.score}-${profile.team2.score} `}
<Name nameObj={profile.team2} />
</TeamsDetailsWrapper>
)}
{ready && (
<CenterControls playing={playing}>
<Backward size='lg' onClick={rewindBackward}>
@ -120,34 +139,83 @@ export const StreamPlayer = (props: Props) => {
</CenterControls>
)}
<Controls
allPlayedProgress={playedProgress}
backToLive={backToLive}
controlsVisible={controlsVisible}
duration={duration}
isFullscreen={isFullscreen}
isLive={profile?.live}
isStorage={profile?.storage}
loadedProgress={loadedProgress}
muted={muted}
onFullscreenClick={onFullscreenClick}
onProgressChangeLive={onProgressChange}
onQualitySelect={onQualitySelect}
onTouchEnd={onTouchEnd}
onTouchStart={onTouchStart}
onVolumeChange={onVolumeChange}
onVolumeClick={onVolumeClick}
playedProgress={playedProgress}
playing={playing}
rewindBackward={rewindBackward}
rewindForward={rewindForward}
selectedQuality={selectedQuality}
togglePlaying={togglePlaying}
videoQualities={videoQualities}
volume={volume}
volumeInPercent={volumeInPercent}
/>
<ControlsGradient isVisible={controlsVisible} />
<Controls visible={controlsVisible}>
<ControlsRow>
<ProgressBar
activeChapterIndex={activeChapterIndex}
allPlayedProgress={allPlayedProgress}
duration={duration}
chapters={chapters}
onPlayedProgressChange={onProgressChange}
playedProgress={playedProgress}
loadedProgress={loadedProgress}
/>
</ControlsRow>
<ControlsRow>
<ControlsGroup>
<PlayStop onClickCapture={togglePlaying} playing={playing} />
{
numberOfChapters > 1 && (
<Fragment>
<Prev
disabled={isFirstChapterPlaying}
onClick={() => playPrevChapter()}
/>
<ChaptersText>
{activeChapterIndex + 1} / {numberOfChapters}
</ChaptersText>
<Next
disabled={isLastChapterPlaying}
onClick={() => playNextChapter()}
/>
</Fragment>
)
}
<VolumeBar
value={volumeInPercent}
muted={muted}
onChange={onVolumeChange}
onClick={onVolumeClick}
/>
{
isLive
? (
<PlaybackTime>
{secondsToHms(allPlayedProgress / 1000)}
</PlaybackTime>
)
: (
<PlaybackTime width={duration > HOUR_IN_MILLISECONDS ? 150 : 130}>
{secondsToHms(allPlayedProgress / 1000)}
{' / '}
{secondsToHms(duration / 1000)}
</PlaybackTime>
)
}
<Backward onClick={rewindBackward}>{REWIND_SECONDS}</Backward>
<Forward onClick={rewindForward}>{REWIND_SECONDS}</Forward>
</ControlsGroup>
<ControlsGroup>
{
isLive && (
<LiveBtn onClick={backToLive}>
<T9n t='live' />
</LiveBtn>
)
}
<Settings
onSelect={onQualitySelect}
selectedQuality={selectedQuality}
videoQualities={videoQualities}
/>
<Fullscreen
onClick={onFullscreenClick}
isFullscreen={isFullscreen}
/>
</ControlsGroup>
</ControlsRow>
</Controls>
<ControlsGradient />
</PlayerWrapper>
)
}

@ -128,15 +128,23 @@ export const PlayerWrapper = styled.div<PlayStopProps>`
: ''};
`
export const LoaderWrapper = styled.div`
type LoaderWrapperProps = {
buffering: boolean,
}
export const LoaderWrapper = styled.div<LoaderWrapperProps>`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
transition: opacity 0.3s ease-in-out;
opacity: ${({ buffering }) => (
buffering
? 1
: 0
)};
`
export const ButtonBase = styled.button`
@ -324,3 +332,47 @@ export const TeamsDetailsWrapper = styled.div`
color: #FFFFFF;
z-index: 50;
`
export const ChaptersText = styled.span`
margin: 0 14px;
font-weight: 500;
font-size: 16px;
color: #fff;
text-align: center;
${isMobileDevice
? css`
margin: 0 5px;
font-size: 12px;
width: 15%;
`
: ''};
`
type PrevProps = {
disabled?: boolean,
}
export const Prev = styled(ButtonBase)<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,18 +1,11 @@
export type Urls = { [quality: string]: string }
export type Chapter = {
duration: number,
endMs: number,
endOffsetMs: number,
period: number,
index?: number,
startMs: number,
startOffsetMs: number,
urls: Urls,
url: string,
}
export type Chapters = Array<Chapter>
export enum Players {
PLAYER1 = 0,
PLAYER2 = 1,
}

@ -25,8 +25,11 @@ export type Props = {
onError?: (e?: SyntheticEvent<HTMLVideoElement>) => void,
onLoadedProgress?: (loadedMs: number) => void,
onPause?: (e: SyntheticEvent<HTMLVideoElement>) => void,
onPlay?: (e: SyntheticEvent<HTMLVideoElement>) => void,
onPlayedProgress?: (playedMs: number) => void,
onPlaying?: () => void,
onReady?: () => void,
onWaiting?: () => void,
playing?: boolean,
ref?: Ref,
seek?: number | null,

@ -15,6 +15,9 @@ export const VideoPlayer = forwardRef<HTMLVideoElement, Props>((props: Props, re
onEnded,
onError,
onPause,
onPlay,
onPlaying,
onWaiting,
src,
width,
} = props
@ -40,8 +43,11 @@ export const VideoPlayer = forwardRef<HTMLVideoElement, Props>((props: Props, re
onProgress={handleLoadedChange}
onEnded={onEnded}
onDurationChange={handleDurationChange}
onPlay={onPlay}
onPause={onPause}
onError={onError}
onWaiting={onWaiting}
onPlaying={onPlaying}
crossOrigin={crossOrigin}
controls={controls}
/>

Loading…
Cancel
Save