Ott 493 player refactoring (#192)
* Ott 493 player refactoring part 1 (#190) * chore(#493): turned off react prop types lint rule * refactor(#493): replaced react-player with own video wrapper compoonent * chore(#493): removed react-player and reach combobox * Revert "chore(#493): turned off react prop types lint rule" This reverts commit e449f859fa900aa24fafe2e28acf218852f7b1ad. * fix(#493): fix props type * Ott 493 player refactoring part 2 (#191) * refactor(#493): used VideoPlayer in MultiSource player * fix(#493): fixed selected quality restoringkeep-around/af30b88d367751c9e05a735e4a0467a96238ef47
parent
b61efae298
commit
9cc373a8d2
@ -1,68 +0,0 @@ |
||||
import type { RefObject } from 'react' |
||||
|
||||
import size from 'lodash/size' |
||||
|
||||
import { useEventListener } from 'hooks' |
||||
|
||||
/** |
||||
* Возвращает значение отрезка буфера которое ближе |
||||
* к проигрываемому времени |
||||
*/ |
||||
const getLoadedSeconds = (video: HTMLVideoElement | null) => { |
||||
const bufferLength = size(video?.buffered) |
||||
if (!video || bufferLength === 0) return 0 |
||||
|
||||
for (let i = 0; i < bufferLength; i++) { |
||||
const bufferPosition = bufferLength - 1 - i |
||||
if (video.buffered.start(bufferPosition) < video.currentTime) { |
||||
return video.buffered.end(bufferPosition) |
||||
} |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
type Args = { |
||||
onChange: (millis: number) => void, |
||||
videoRef: RefObject<HTMLVideoElement>, |
||||
} |
||||
|
||||
/** |
||||
* Подписывается на прогресс буферизации и вызывает колбек |
||||
* со значением прогресса в милисекундах |
||||
*/ |
||||
export const useLoadedEvent = ({ |
||||
onChange, |
||||
videoRef, |
||||
}: Args) => { |
||||
const listenLoadedProgress = () => { |
||||
const loadedSeconds = getLoadedSeconds(videoRef.current) |
||||
onChange(loadedSeconds * 1000) |
||||
} |
||||
|
||||
useEventListener({ |
||||
callback: listenLoadedProgress, |
||||
event: 'progress', |
||||
target: videoRef, |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Подписывается на прогресс проигывания и вызывает колбек |
||||
* со значением прогресса в милисекундах |
||||
*/ |
||||
export const usePlayedEvent = ({ |
||||
onChange, |
||||
videoRef, |
||||
}: Args) => { |
||||
const listenPlayedProgresses = () => { |
||||
const video = videoRef.current |
||||
if (!video) return |
||||
onChange(video.currentTime * 1000) |
||||
} |
||||
|
||||
useEventListener({ |
||||
callback: listenPlayedProgresses, |
||||
event: 'timeupdate', |
||||
target: videoRef, |
||||
}) |
||||
} |
||||
@ -1,47 +0,0 @@ |
||||
import type { RefObject } from 'react' |
||||
import { useEffect, useRef } from 'react' |
||||
|
||||
import isNumber from 'lodash/isNumber' |
||||
|
||||
import { useLocalStore } from 'hooks' |
||||
|
||||
const defaultVolume = 1 |
||||
|
||||
const useVolumeState = (videoRef: RefObject<HTMLVideoElement>) => { |
||||
const [volume, setVolume] = useLocalStore({ |
||||
defaultValue: defaultVolume, |
||||
key: 'player_volume', |
||||
validator: isNumber, |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
if (videoRef.current) { |
||||
// eslint-disable-next-line no-param-reassign
|
||||
videoRef.current.volume = volume |
||||
} |
||||
}, [volume, videoRef]) |
||||
|
||||
return [volume, setVolume] as const |
||||
} |
||||
|
||||
export const useVolume = (videoRef: RefObject<HTMLVideoElement>) => { |
||||
const prevVolumeRef = useRef(defaultVolume) |
||||
const [volume, setVolume] = useVolumeState(videoRef) |
||||
|
||||
const onVolumeChange = (value: number) => { |
||||
setVolume(value) |
||||
} |
||||
|
||||
const onVolumeClick = () => { |
||||
setVolume(volume === 0 ? prevVolumeRef.current : 0) |
||||
prevVolumeRef.current = volume || defaultVolume |
||||
} |
||||
|
||||
return { |
||||
muted: volume === 0, |
||||
onVolumeChange, |
||||
onVolumeClick, |
||||
volume, |
||||
volumeInPercent: volume * 100, |
||||
} |
||||
} |
||||
@ -1,9 +0,0 @@ |
||||
import styled from 'styled-components/macro' |
||||
|
||||
export const Video = styled.video` |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
` |
||||
@ -0,0 +1,26 @@ |
||||
import { |
||||
useEffect, |
||||
useMemo, |
||||
useRef, |
||||
} from 'react' |
||||
import Hls from 'hls.js' |
||||
|
||||
import { streamConfig } from '../config' |
||||
|
||||
export const useHlsPlayer = (src: string) => { |
||||
const hls = useMemo(() => new Hls(streamConfig), []) |
||||
const videoRef = useRef<HTMLVideoElement>(null) |
||||
|
||||
useEffect(() => { |
||||
const video = videoRef.current |
||||
if (!video) return |
||||
|
||||
hls.loadSource(src) |
||||
hls.attachMedia(video) |
||||
}, [src, hls]) |
||||
|
||||
return { |
||||
hls, |
||||
videoRef, |
||||
} |
||||
} |
||||
@ -0,0 +1,114 @@ |
||||
import type { ForwardRefRenderFunction, SyntheticEvent } from 'react' |
||||
import { |
||||
useEffect, |
||||
useState, |
||||
useRef, |
||||
} from 'react' |
||||
|
||||
import isUndefined from 'lodash/isUndefined' |
||||
import isNumber from 'lodash/isNumber' |
||||
|
||||
import { useProgressChange } from './useProgressChange' |
||||
|
||||
type Ref = Parameters<ForwardRefRenderFunction<HTMLVideoElement>>[1] |
||||
|
||||
export type Props = { |
||||
className?: string, |
||||
height?: string, |
||||
muted?: boolean, |
||||
onDurationChange?: (durationMs: number) => void, |
||||
onEnded?: (e: SyntheticEvent<HTMLVideoElement>) => void, |
||||
onError?: (e?: SyntheticEvent<HTMLVideoElement>) => void, |
||||
onLoadedProgress?: (loadedMs: number) => void, |
||||
onPlayedProgress?: (playedMs: number) => void, |
||||
onReady?: () => void, |
||||
playing?: boolean, |
||||
ref?: Ref, |
||||
seek?: number | null, |
||||
src: string, |
||||
volume?: number, |
||||
width?: string, |
||||
} |
||||
|
||||
const useVideoRef = (ref?: Ref) => { |
||||
const videoRef = useRef<HTMLVideoElement>(null) |
||||
if (ref && typeof ref === 'object') return ref |
||||
return videoRef |
||||
} |
||||
|
||||
export const useVideoPlayer = ({ |
||||
onDurationChange, |
||||
onError, |
||||
onLoadedProgress, |
||||
onPlayedProgress, |
||||
onReady, |
||||
playing, |
||||
ref, |
||||
seek = null, |
||||
src, |
||||
volume, |
||||
}: Props) => { |
||||
const [ready, setReady] = useState(false) |
||||
const videoRef = useVideoRef(ref) |
||||
|
||||
const handleReady = () => { |
||||
setReady(true) |
||||
onReady?.() |
||||
} |
||||
|
||||
const handleDurationChange = () => { |
||||
onDurationChange?.(videoRef.current?.duration || 0) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
const video = videoRef.current |
||||
if (!video) return |
||||
|
||||
video.src = src |
||||
video.load() |
||||
}, [src, videoRef]) |
||||
|
||||
useEffect(() => { |
||||
const video = videoRef.current |
||||
if (video && isNumber(seek)) { |
||||
video.currentTime = seek |
||||
} |
||||
}, [seek, videoRef]) |
||||
|
||||
useEffect(() => { |
||||
const video = videoRef.current |
||||
if (!video?.src || !ready || isUndefined(playing)) return |
||||
|
||||
if (playing) { |
||||
// автовоспроизведение со звуком иногда может не сработать
|
||||
// https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#new-behaviors
|
||||
video.play().catch(onError) |
||||
} else { |
||||
video.pause() |
||||
} |
||||
}, [ |
||||
ready, |
||||
playing, |
||||
src, |
||||
onError, |
||||
videoRef, |
||||
]) |
||||
|
||||
useEffect(() => { |
||||
const video = videoRef.current |
||||
if (video) { |
||||
video.volume = volume ?? 1 |
||||
} |
||||
}, [volume, videoRef]) |
||||
|
||||
return { |
||||
handleDurationChange, |
||||
handleReady, |
||||
videoRef, |
||||
...useProgressChange({ |
||||
onLoadedProgress, |
||||
onPlayedProgress, |
||||
videoRef, |
||||
}), |
||||
} |
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
import { RefObject } from 'react' |
||||
|
||||
import size from 'lodash/size' |
||||
|
||||
/** |
||||
* Возвращает значение отрезка буфера которое ближе |
||||
* к проигрываемому времени |
||||
*/ |
||||
const getLoadedSeconds = (video: HTMLVideoElement | null) => { |
||||
const bufferLength = size(video?.buffered) |
||||
if (!video || bufferLength === 0) return 0 |
||||
|
||||
for (let i = 0; i < bufferLength; i++) { |
||||
const bufferPosition = bufferLength - 1 - i |
||||
if (video.buffered.start(bufferPosition) < video.currentTime) { |
||||
return video.buffered.end(bufferPosition) |
||||
} |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
type Args = { |
||||
onLoadedProgress?: (millis: number) => void, |
||||
onPlayedProgress?: (millis: number) => void, |
||||
videoRef: RefObject<HTMLVideoElement>, |
||||
} |
||||
|
||||
export const useProgressChange = ({ |
||||
onLoadedProgress, |
||||
onPlayedProgress, |
||||
videoRef, |
||||
}: Args) => { |
||||
const handleLoadedChange = () => { |
||||
const loadedSeconds = getLoadedSeconds(videoRef.current) |
||||
onLoadedProgress?.(loadedSeconds * 1000) |
||||
} |
||||
|
||||
const handlePlayedChange = () => { |
||||
const video = videoRef.current |
||||
if (!video) return |
||||
onPlayedProgress?.(video.currentTime * 1000) |
||||
} |
||||
|
||||
return { |
||||
handleLoadedChange, |
||||
handlePlayedChange, |
||||
} |
||||
} |
||||
@ -0,0 +1,40 @@ |
||||
import React, { forwardRef } from 'react' |
||||
|
||||
import type { Props } from './hooks' |
||||
import { useVideoPlayer } from './hooks' |
||||
import { Video } from './styled' |
||||
|
||||
export const VideoPlayer = forwardRef<HTMLVideoElement, Props>((props: Props, ref) => { |
||||
const { |
||||
className, |
||||
height, |
||||
onEnded, |
||||
onError, |
||||
src, |
||||
volume, |
||||
width, |
||||
} = props |
||||
const { |
||||
handleDurationChange, |
||||
handleLoadedChange, |
||||
handlePlayedChange, |
||||
handleReady, |
||||
videoRef, |
||||
} = useVideoPlayer({ ...props, ref }) |
||||
return ( |
||||
<Video |
||||
className={className} |
||||
width={width} |
||||
height={height} |
||||
ref={videoRef} |
||||
src={src} |
||||
muted={volume === 0} |
||||
onCanPlay={handleReady} |
||||
onTimeUpdate={handlePlayedChange} |
||||
onProgress={handleLoadedChange} |
||||
onEnded={onEnded} |
||||
onDurationChange={handleDurationChange} |
||||
onError={onError} |
||||
/> |
||||
) |
||||
}) |
||||
@ -0,0 +1,9 @@ |
||||
import styled from 'styled-components/macro' |
||||
|
||||
export const Video = styled.video` |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
` |
||||
Loading…
Reference in new issue