From 46f44189caaf667f0c3c1d6025f12dae8e328b45 Mon Sep 17 00:00:00 2001 From: Mirlan Date: Wed, 21 Oct 2020 16:13:27 +0600 Subject: [PATCH] feat(#489): saving player quality and sound settings in local storage (#187) --- .../MultiSourcePlayer/hooks/index.tsx | 4 +- .../hooks/usePlayingState.tsx | 14 +++--- .../hooks/useVideoQuality.tsx | 16 +++++-- .../MultiSourcePlayer/hooks/useVolume.tsx | 45 +++++++++---------- src/features/StreamPlayer/hooks/index.tsx | 10 ++++- .../StreamPlayer/hooks/useVideoQuality.tsx | 32 ++++++++++--- src/features/StreamPlayer/hooks/useVolume.tsx | 34 +++++++------- src/features/StreamPlayer/styled.tsx | 4 ++ 8 files changed, 96 insertions(+), 63 deletions(-) diff --git a/src/features/MultiSourcePlayer/hooks/index.tsx b/src/features/MultiSourcePlayer/hooks/index.tsx index ed9245e0..a54b7690 100644 --- a/src/features/MultiSourcePlayer/hooks/index.tsx +++ b/src/features/MultiSourcePlayer/hooks/index.tsx @@ -43,7 +43,6 @@ export const useMultiSourcePlayer = ({ const [playedProgress, setPlayedProgress] = useState(0) const { continuePlaying, - firstTimeStart, playing, startPlaying, stopPlaying, @@ -114,11 +113,10 @@ export const useMultiSourcePlayer = ({ const url = getActiveChapterUrl() const chapterStartMs = getActiveChapterStart() const from = resumeFrom.second - (chapterStartMs / 1000) - if (url && firstTimeStart) { + if (url) { startPlaying(url, from) } }, [ - firstTimeStart, resumeFrom, getActiveChapterUrl, getActiveChapterStart, diff --git a/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx b/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx index ecc21f17..33e6082b 100644 --- a/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx +++ b/src/features/MultiSourcePlayer/hooks/usePlayingState.tsx @@ -4,13 +4,12 @@ import { useState, useCallback } from 'react' import { preparePlayer } from '../helpers' export const usePlayingState = (videoRef: RefObject) => { - const [firstTimeStart, setFirstTimeStart] = useState(true) const [playing, setPlaying] = useState(false) const togglePlaying = useCallback(() => { setPlaying((isPlaying) => { const video = videoRef.current - if (!video || firstTimeStart) return false + if (!video) return false const nextIsPlaying = !isPlaying if (nextIsPlaying) { @@ -20,7 +19,7 @@ export const usePlayingState = (videoRef: RefObject) => { } return nextIsPlaying }) - }, [firstTimeStart, videoRef]) + }, [videoRef]) const stopPlaying = useCallback((nextUrl: string = '') => { preparePlayer({ url: nextUrl, videoRef }) @@ -33,9 +32,11 @@ export const usePlayingState = (videoRef: RefObject) => { url, videoRef, }) - videoRef.current?.play() - setFirstTimeStart(false) - setPlaying(true) + // автовоспроизведение со звуком иногда может не сработать + // https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#new-behaviors + videoRef.current?.play().then(() => { + setPlaying(true) + }) }, [videoRef]) const continuePlaying = useCallback(( @@ -58,7 +59,6 @@ export const usePlayingState = (videoRef: RefObject) => { return { continuePlaying, - firstTimeStart, playing, startPlaying, stopPlaying, diff --git a/src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx b/src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx index d17339bd..d48152b5 100644 --- a/src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx +++ b/src/features/MultiSourcePlayer/hooks/useVideoQuality.tsx @@ -1,10 +1,10 @@ -import { useState } from 'react' - import map from 'lodash/map' import uniq from 'lodash/uniq' import orderBy from 'lodash/orderBy' +import includes from 'lodash/includes' import type { Videos } from 'requests' +import { useLocalStore } from 'hooks' const getVideoQualities = (videos: Videos) => { const qualities = uniq(map(videos, 'quality')) @@ -17,7 +17,17 @@ const getVideoQualities = (videos: Videos) => { export const useVideoQuality = (videos: Videos) => { const videoQualities = getVideoQualities(videos) - const [selectedQuality, setSelectedQuality] = useState(videoQualities[0]) + + const qualityValidator = (localStorageQuality: string) => ( + includes(videoQualities, localStorageQuality) + ) + + const [selectedQuality, setSelectedQuality] = useLocalStore({ + // по умолчанию наилучшее качество + defaultValue: videoQualities[0], + key: 'player_quality', + validator: qualityValidator, + }) return { selectedQuality, diff --git a/src/features/MultiSourcePlayer/hooks/useVolume.tsx b/src/features/MultiSourcePlayer/hooks/useVolume.tsx index 96e6788f..22e9d757 100644 --- a/src/features/MultiSourcePlayer/hooks/useVolume.tsx +++ b/src/features/MultiSourcePlayer/hooks/useVolume.tsx @@ -1,47 +1,44 @@ import type { RefObject } from 'react' -import { useState, useCallback } from 'react' +import { useEffect, useRef } from 'react' -import { useToggle } from 'hooks' +import isNumber from 'lodash/isNumber' + +import { useLocalStore } from 'hooks' + +const defaultVolume = 1 const useVolumeState = (videoRef: RefObject) => { - const [volume, setVolume] = useState(0.5) - const setVideoVolume = useCallback((value: number) => { + 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 = value + videoRef.current.volume = volume } - setVolume(value) - }, [videoRef]) - return [volume, setVideoVolume] as const + }, [volume, videoRef]) + + return [volume, setVolume] as const } export const useVolume = (videoRef: RefObject) => { - const { - close: unmute, - isOpen: muted, - open: mute, - toggle: toggleMuted, - } = useToggle(true) + const prevVolumeRef = useRef(defaultVolume) const [volume, setVolume] = useVolumeState(videoRef) const onVolumeChange = (value: number) => { - if (value === 0) { - mute() - } else { - unmute() - } setVolume(value) } const onVolumeClick = () => { - if (muted && volume === 0) { - setVolume(0.1) - } - toggleMuted() + setVolume(volume === 0 ? prevVolumeRef.current : 0) + prevVolumeRef.current = volume || defaultVolume } return { - muted, + muted: volume === 0, onVolumeChange, onVolumeClick, volume, diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index f44042d2..3c8b5695 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -44,8 +44,14 @@ export const useVideoPlayer = ({ const startPlaying = useCallback(once(() => { setReady(true) - setPlaying(true) - onPlayingChange(true) + + // автовоспроизведение со звуком иногда может не сработать + // https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#new-behaviors + const video = playerRef.current?.getInternalPlayer() as HTMLVideoElement | undefined + video?.play().then(() => { + setPlaying(true) + onPlayingChange(true) + }) }), []) const togglePlaying = () => { diff --git a/src/features/StreamPlayer/hooks/useVideoQuality.tsx b/src/features/StreamPlayer/hooks/useVideoQuality.tsx index 30a380a0..dfc42417 100644 --- a/src/features/StreamPlayer/hooks/useVideoQuality.tsx +++ b/src/features/StreamPlayer/hooks/useVideoQuality.tsx @@ -1,7 +1,6 @@ import type { RefObject } from 'react' import { useMemo, - useState, useEffect, useCallback, } from 'react' @@ -14,6 +13,9 @@ import find from 'lodash/find' import uniqBy from 'lodash/uniqBy' import isEmpty from 'lodash/isEmpty' import orderBy from 'lodash/orderBy' +import isString from 'lodash/isString' + +import { useLocalStore } from 'hooks' const autoQuality = { label: 'Auto', @@ -38,7 +40,11 @@ const getVideoQualities = (hls: Hls | null) => { export const useVideoQuality = (ref: RefObject) => { const hls = ref.current?.getInternalPlayer('hls') as Hls | null const videoQualities = useMemo(() => getVideoQualities(hls), [hls]) - const [selectedQuality, setSelectedQuality] = useState('') + const [selectedQuality, setSelectedQuality] = useLocalStore({ + defaultValue: autoQuality.label, + key: 'player_quality', + validator: isString, + }) const onQualitySelect = useCallback((label: string) => { const item = find(videoQualities, { label }) @@ -46,11 +52,27 @@ export const useVideoQuality = (ref: RefObject) => { hls.currentLevel = item.level setSelectedQuality(item.label) } - }, [hls, videoQualities]) + }, [ + setSelectedQuality, + hls, + videoQualities, + ]) useEffect(() => { - onQualitySelect(autoQuality.label) - }, [onQualitySelect]) + if (!hls) return + + const quality = find(videoQualities, { label: selectedQuality }) || autoQuality + + if (quality.level === hls.currentLevel) return + + hls.currentLevel = quality.level + setSelectedQuality(quality.label) + }, [ + selectedQuality, + setSelectedQuality, + videoQualities, + hls, + ]) return { onQualitySelect, diff --git a/src/features/StreamPlayer/hooks/useVolume.tsx b/src/features/StreamPlayer/hooks/useVolume.tsx index b29a9db6..7c2e6db7 100644 --- a/src/features/StreamPlayer/hooks/useVolume.tsx +++ b/src/features/StreamPlayer/hooks/useVolume.tsx @@ -1,34 +1,30 @@ -import { useState } from 'react' +import { useRef } from 'react' -import { useToggle } from 'hooks' +import isNumber from 'lodash/isNumber' + +import { useLocalStore } from 'hooks' + +const defaultVolume = 1 export const useVolume = () => { - const { - close: unmute, - isOpen: muted, - open: mute, - toggle: toggleMuted, - } = useToggle(true) - const [volume, setVolume] = useState(0.5) + const prevVolumeRef = useRef(defaultVolume) + const [volume, setVolume] = useLocalStore({ + defaultValue: defaultVolume, + key: 'player_volume', + validator: isNumber, + }) const onVolumeChange = (value: number) => { - if (value === 0) { - mute() - } else { - unmute() - } setVolume(value) } const onVolumeClick = () => { - if (muted && volume === 0) { - setVolume(0.1) - } - toggleMuted() + setVolume(volume === 0 ? prevVolumeRef.current : 0) + prevVolumeRef.current = volume || defaultVolume } return { - muted, + muted: volume === 0, onVolumeChange, onVolumeClick, volume, diff --git a/src/features/StreamPlayer/styled.tsx b/src/features/StreamPlayer/styled.tsx index 7d24204b..4996f699 100644 --- a/src/features/StreamPlayer/styled.tsx +++ b/src/features/StreamPlayer/styled.tsx @@ -25,6 +25,10 @@ export const PlayerWrapper = styled.div` padding-top: 56.25%; background-color: #000; + :fullscreen { + padding-top: 0; + } + :hover ${Controls} { opacity: 1; }