diff --git a/package.json b/package.json index 1434adb8..89c26bab 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@reach/combobox": "^0.10.4", "date-fns": "^2.14.0", "history": "^4.10.1", + "hls.js": "^0.14.15", "lodash": "^4.17.15", "node-sass": "^4.14.1", "react": "^16.13.1", @@ -43,6 +44,7 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/history": "^4.7.6", + "@types/hls.js": "^0.13.2", "@types/jest": "^24.0.0", "@types/lodash": "^4.14.154", "@types/node": "^12.0.0", diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index feda4add..f44042d2 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -9,6 +9,7 @@ import ReactPlayer from 'react-player' import once from 'lodash/once' import throttle from 'lodash/throttle' +import { useVideoQuality } from './useVideoQuality' import { useFullscreen } from './useFullscreen' import { useVolume } from './useVolume' @@ -107,5 +108,6 @@ export const useVideoPlayer = ({ wrapperRef, ...useFullscreen(wrapperRef), ...useVolume(), + ...useVideoQuality(playerRef), } } diff --git a/src/features/StreamPlayer/hooks/useVideoQuality.tsx b/src/features/StreamPlayer/hooks/useVideoQuality.tsx new file mode 100644 index 00000000..c1fb39cb --- /dev/null +++ b/src/features/StreamPlayer/hooks/useVideoQuality.tsx @@ -0,0 +1,73 @@ +import type { RefObject } from 'react' +import { + useMemo, + useState, + useEffect, + useCallback, +} from 'react' +import ReactPlayer from 'react-player' + +import Hls from 'hls.js' + +import map from 'lodash/map' +import find from 'lodash/find' +import uniqBy from 'lodash/uniqBy' +import orderBy from 'lodash/orderBy' + +const getVideoQualities = (hls: Hls | null) => { + const qualities = map(hls?.levels, (level, i) => ({ + level: i, + quality: String(level.height), + })) + const sorted = orderBy( + qualities, + Number, + 'desc', + ) + return uniqBy(sorted, 'quality') +} + +export const useVideoQuality = (ref: RefObject) => { + const hls = ref.current?.getInternalPlayer('hls') as Hls | null + const videoQualities = useMemo(() => getVideoQualities(hls), [hls]) + const [selectedQuality, setSelectedQuality] = useState('') + + useEffect(() => { + const initialQuality = find(videoQualities, { level: hls?.currentLevel }) + if (initialQuality) { + setSelectedQuality(initialQuality.quality) + } + }, [hls, videoQualities]) + + // подписываемся на изменение качества, + // тк плеер может сам подбирать качество пользователю + // на основе скорости интернета + useEffect(() => { + if (!hls) return undefined + + const listener = (event: string, { level }: Hls.levelSwitchedData) => { + const item = find(videoQualities, { level }) + if (item) { + setSelectedQuality(item.quality) + } + } + hls.on(Hls.Events.LEVEL_SWITCHED, listener) + + return () => { + hls.off(Hls.Events.LEVEL_SWITCHED, listener) + } + }, [hls, videoQualities]) + + const onQualitySelect = useCallback((quality: string) => { + const item = find(videoQualities, { quality }) + if (hls && item) { + hls.currentLevel = item.level + } + }, [hls, videoQualities]) + + return { + onQualitySelect, + selectedQuality, + videoQualities: map(videoQualities, 'quality'), + } +} diff --git a/src/features/StreamPlayer/index.tsx b/src/features/StreamPlayer/index.tsx index f6c98d2d..6aac171f 100644 --- a/src/features/StreamPlayer/index.tsx +++ b/src/features/StreamPlayer/index.tsx @@ -1,5 +1,7 @@ import React from 'react' +import { Settings } from 'features/MultiSourcePlayer/components/Settings' + import { streamConfig, progressCallbackInterval } from './config' import type { Props } from './hooks' import { useVideoPlayer } from './hooks' @@ -24,14 +26,17 @@ export const StreamPlayer = (props: Props) => { onFullscreenClick, onPlayerClick, onProgressChange, + onQualitySelect, onVolumeChange, onVolumeClick, playedProgress, playerRef, playing, + selectedQuality, setProgress, startPlaying, togglePlaying, + videoQualities, volume, wrapperRef, } = useVideoPlayer(props) @@ -73,6 +78,11 @@ export const StreamPlayer = (props: Props) => { playedProgress={playedProgress} loadedProgress={loadedProgress} /> +