Ott 1701 part 5

keep-around/fdb88b04b32b9392e76795099e2ec47c9856b38b
Макситалиев Мирлан 4 years ago committed by Andrei Dekterev
parent 7e0f83ad2a
commit 86a9ed498d
  1. 3
      package.json
  2. 15
      src/features/MatchPage/components/LiveMatch/helpers.tsx
  3. 5
      src/features/MatchPage/store/hooks/useMatchPlaylists.tsx
  4. 6
      src/features/StreamPlayer/components/ProgressBar/helpers/calculateChapterStyles/index.tsx
  5. 4
      src/features/StreamPlayer/config.tsx
  6. 36
      src/features/StreamPlayer/helpers/index.tsx
  7. 6
      src/features/StreamPlayer/hooks/index.tsx
  8. 4
      src/features/StreamPlayer/hooks/useProgressChangeHandler.tsx
  9. 6
      src/features/StreamPlayer/hooks/useVideoQuality.tsx
  10. 3
      src/features/StreamPlayer/index.tsx
  11. 22
      src/features/StreamPlayer/types.tsx
  12. 10
      src/features/VideoPlayer/index.tsx

@ -24,7 +24,7 @@
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"date-fns": "^2.14.0", "date-fns": "^2.14.0",
"history": "^4.10.1", "history": "^4.10.1",
"hls.js": "^0.14.15", "hls.js": "^1.1.1",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"m3u8-parser": "^4.7.0", "m3u8-parser": "^4.7.0",
"oidc-client": "^1.11.5", "oidc-client": "^1.11.5",
@ -56,7 +56,6 @@
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"@types/history": "^4.7.6", "@types/history": "^4.7.6",
"@types/hls.js": "^0.13.2",
"@types/jest": "^26.0.15", "@types/jest": "^26.0.15",
"@types/lodash": "^4.14.154", "@types/lodash": "^4.14.154",
"@types/node": "^12.0.0", "@types/node": "^12.0.0",

@ -4,11 +4,15 @@ import concat from 'lodash/concat'
import type { Episodes } from 'requests/getMatchPlaylists' import type { Episodes } from 'requests/getMatchPlaylists'
import type { Chapters } from 'features/StreamPlayer/types' import type { Chapters, Chapter } from 'features/StreamPlayer/types'
import type { MatchPlaylistOption, PlaylistOption } from '../../types' import type { MatchPlaylistOption, PlaylistOption } from '../../types'
import { FULL_GAME_KEY } from '../../helpers/buildPlaylists' import { FULL_GAME_KEY } from '../../helpers/buildPlaylists'
/**
* Формирует эпизоды плейлиста Полный матч
* API не выдает полный матч как плейлист, формируем на фронте
* */
const getFullMatchChapters = (url: string, playlist: MatchPlaylistOption) => { const getFullMatchChapters = (url: string, playlist: MatchPlaylistOption) => {
const duration = (playlist.duration ?? 0) * 1000 const duration = (playlist.duration ?? 0) * 1000
return [ return [
@ -17,6 +21,7 @@ const getFullMatchChapters = (url: string, playlist: MatchPlaylistOption) => {
endMs: duration, endMs: duration,
endOffsetMs: duration, endOffsetMs: duration,
index: 0, index: 0,
isFullMatchChapter: true,
startMs: 0, startMs: 0,
startOffsetMs: 0, startOffsetMs: 0,
url, url,
@ -24,6 +29,9 @@ const getFullMatchChapters = (url: string, playlist: MatchPlaylistOption) => {
] ]
} }
/**
* Формирует эпизоды плейлистов матча и игроков
* */
const getPlaylistChapters = (url: string, episodes: Episodes) => reduce( const getPlaylistChapters = (url: string, episodes: Episodes) => reduce(
episodes, episodes,
( (
@ -35,7 +43,7 @@ const getPlaylistChapters = (url: string, episodes: Episodes) => reduce(
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: Chapter = {
duration: episodeDuration, duration: episodeDuration,
endMs: prevVideoEndMs + episodeDuration, endMs: prevVideoEndMs + episodeDuration,
endOffsetMs: episode.e * 1000, endOffsetMs: episode.e * 1000,
@ -54,6 +62,9 @@ type Args = {
url: string, url: string,
} }
/**
* Формирует список эпизодов из выбранного плейлиста для плеера
*/
export const buildChapters = ({ export const buildChapters = ({
selectedPlaylist, selectedPlaylist,
url, url,

@ -56,6 +56,11 @@ export const useMatchPlaylists = () => {
.then(setMatchPlaylists) .then(setMatchPlaylists)
}, [fetchLexics, setInitialSeletedPlaylist]) }, [fetchLexics, setInitialSeletedPlaylist])
/**
* API не выдает длительность Полного матча
* Здесь получаем его из самого видео
* и обновляем длительность плейлиста Полный матч
*/
const setFullMatchPlaylistDuration = (duration: number) => { const setFullMatchPlaylistDuration = (duration: number) => {
const playlists = [...matchPlaylists.match] const playlists = [...matchPlaylists.match]
if (!playlists[0]) return if (!playlists[0]) return

@ -49,9 +49,9 @@ export const calculateChapterStyles = ({
...chapter, ...chapter,
loaded: calculateChapterProgress(loadedProgress, chapter), loaded: calculateChapterProgress(loadedProgress, chapter),
played: calculateChapterProgress(playedProgress, chapter), played: calculateChapterProgress(playedProgress, chapter),
width: chapter.duration width: chapter.isFullMatchChapter
? chapter.duration * 100 / videoDuration ? 100
: 100, : chapter.duration * 100 / videoDuration,
} }
return [ return [
...playedChapters, ...playedChapters,

@ -1,8 +1,8 @@
import Hls from 'hls.js' import type { HlsConfig } from 'hls.js'
import { readToken } from 'helpers/token' import { readToken } from 'helpers/token'
export const streamConfig: Partial<Hls.Config> = { export const streamConfig: Partial<HlsConfig> = {
liveSyncDuration: 30, liveSyncDuration: 30,
maxBufferLength: 30, maxBufferLength: 30,
xhrSetup: (xhr, urlString) => { xhrSetup: (xhr, urlString) => {

@ -1,33 +1,13 @@
import { RefObject } from 'react'
import findIndex from 'lodash/findIndex' import findIndex from 'lodash/findIndex'
import size from 'lodash/size'
import type { Chapters } from '../types' import type { Chapters } from '../types'
type Args = { export const findChapterByProgress = (chapters: Chapters, progressMs: number) => {
from?: number, if (size(chapters) === 1 && chapters[0].isFullMatchChapter) return 0
url: string, return (
videoRef: RefObject<HTMLVideoElement>, findIndex(chapters, ({ endMs, startMs }) => (
startMs <= progressMs && progressMs <= endMs
))
)
} }
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
))
)

@ -75,11 +75,7 @@ export const useVideoPlayer = ({
ready, ready,
seek, seek,
seeking, seeking,
}, setPlayerState] = useObjectState({ }, setPlayerState] = useObjectState(initialState)
...initialState,
playedProgress: toMilliSeconds(resumeFrom || 0),
seek: resumeFrom || 0,
})
const chaptersDuration = useDuration(chapters) const chaptersDuration = useDuration(chapters)

@ -31,8 +31,8 @@ export const useProgressChangeHandler = ({
? chapterIndex ? chapterIndex
: state.activeChapterIndex : state.activeChapterIndex
// отнимаем начало главы на котором остановились от общего прогресса // отнимаем начало эпизода на котором остановились от общего прогресса
// чтобы получить прогресс текущей главы // чтобы получить прогресс текущего эпизода
const chapterProgressMs = (progressMs - chapter.startMs) const chapterProgressMs = (progressMs - chapter.startMs)
const seekMs = chapterProgressMs + chapter.startOffsetMs const seekMs = chapterProgressMs + chapter.startOffsetMs
return { return {

@ -4,7 +4,7 @@ import {
useCallback, useCallback,
} from 'react' } from 'react'
import Hls from 'hls.js' import Hls, { Level } from 'hls.js'
import map from 'lodash/map' import map from 'lodash/map'
import find from 'lodash/find' import find from 'lodash/find'
@ -28,9 +28,9 @@ const autoQuality = {
* непонятное качество без свойств height, width и тд для определения * непонятное качество без свойств height, width и тд для определения
* какое это качество * какое это качество
*/ */
const filterOutUnknownQualities = filter(({ height }: Hls.Level) => Boolean(height)) const filterOutUnknownQualities = filter(({ height }: Level) => Boolean(height))
const getVideoQualities = (levels: Array<Hls.Level>) => { const getVideoQualities = (levels: Array<Level>) => {
if (isEmpty(levels)) return [] if (isEmpty(levels)) return []
const filteredQualities = filterOutUnknownQualities(levels) const filteredQualities = filterOutUnknownQualities(levels)

@ -32,6 +32,9 @@ import {
import type { Props } from './hooks' import type { Props } from './hooks'
import { useVideoPlayer } from './hooks' import { useVideoPlayer } from './hooks'
/**
* HLS плеер, применяется на лайв и завершенных матчах
*/
export const StreamPlayer = (props: Props) => { export const StreamPlayer = (props: Props) => {
const { chapters, isLive } = props const { chapters, isLive } = props

@ -1,9 +1,31 @@
/**
* для примера матч с двумя эпизодами в плейлисте Голы, время в мс:
* [{start: 0, end: 20000}, {start: 60000, end: 80000}]
*/
export type Chapter = { export type Chapter = {
duration: number, duration: number,
/**
* конец эпизода в плейлисте
* в первом эпизоде - 20000, во втором - 40000
*/
endMs: number, endMs: number,
/** конец эпизода как отмечено в матче */
endOffsetMs: number, endOffsetMs: number,
/** индекс эпизода для дебага */
index?: number, index?: number,
isFullMatchChapter?: boolean,
/**
* начало эпизода в плейлисте
* в первом эпизоде - 0, во втором - 20000
*/
startMs: number, startMs: number,
/** начало эпизода как отмечено в матче */
startOffsetMs: number, startOffsetMs: number,
url: string, url: string,
} }

@ -4,6 +4,16 @@ import type { Props } from './hooks'
import { useVideoPlayer } from './hooks' import { useVideoPlayer } from './hooks'
import { Video } from './styled' import { Video } from './styled'
/**
* Низкоуровневый компонент для декларативной работы с HTMLVideoElement
* ```ts
* например старт и пауза плеера вместо
* video.play() | video.pause()
*
* контролируем через пропс playing
* <VideoPlayer playing={true | false} />
* ```
*/
export const VideoPlayer = forwardRef<HTMLVideoElement, Props>((props: Props, ref) => { export const VideoPlayer = forwardRef<HTMLVideoElement, Props>((props: Props, ref) => {
const { const {
className, className,

Loading…
Cancel
Save