From 41d7131610368a0db87c479147d00bb03fcfdb81 Mon Sep 17 00:00:00 2001 From: Rakov Roman Date: Mon, 7 Nov 2022 18:02:40 +0300 Subject: [PATCH] fix(#2979): audio tracks selection --- src/features/AirPlay/styled.tsx | 4 +- src/features/AudioTracks/index.tsx | 98 +++++++++++++++++++ src/features/AudioTracks/styled.tsx | 97 ++++++++++++++++++ src/features/ChromeCast/index.tsx | 8 +- src/features/ChromeCast/styled.tsx | 6 +- .../components/Settings/styled.tsx | 4 +- .../Components/ControlsMobile/index.tsx | 18 +++- .../Components/ControlsMobile/styled.tsx | 1 - .../Controls/Components/ControlsWeb/index.tsx | 9 ++ .../components/Controls/index.tsx | 5 + src/features/StreamPlayer/hooks/index.tsx | 2 + .../StreamPlayer/hooks/useAudioTrack.tsx | 43 ++++++++ src/features/StreamPlayer/index.tsx | 6 ++ src/features/StreamPlayer/styled.tsx | 5 +- 14 files changed, 290 insertions(+), 16 deletions(-) create mode 100644 src/features/AudioTracks/index.tsx create mode 100644 src/features/AudioTracks/styled.tsx create mode 100644 src/features/StreamPlayer/hooks/useAudioTrack.tsx diff --git a/src/features/AirPlay/styled.tsx b/src/features/AirPlay/styled.tsx index ed47b06e..642967d5 100644 --- a/src/features/AirPlay/styled.tsx +++ b/src/features/AirPlay/styled.tsx @@ -9,13 +9,13 @@ export const AirplayButton = styled(ButtonBase)` height: 24px; padding: 0; display: none; - margin-left: 25px; + margin-right: 25px; ${isMobileDevice ? css` width: 22px; height: 22px; - margin-left: 15px; + margin-right: 15px; ` : ''}; ` diff --git a/src/features/AudioTracks/index.tsx b/src/features/AudioTracks/index.tsx new file mode 100644 index 00000000..c9901ad1 --- /dev/null +++ b/src/features/AudioTracks/index.tsx @@ -0,0 +1,98 @@ +import { Fragment, useMemo } from 'react' + +import type { MediaPlaylist } from 'hls.js' + +import map from 'lodash/map' + +import { OutsideClick } from 'features/OutsideClick' + +import { useToggle } from 'hooks' + +import { isMobileDevice } from 'config/userAgent' + +import { + AudioTrackItem, + AudioTrackItemText, + AudioTracksWrapper, + ScHeader, + ScModal, + SelectedAudioTrack, +} from './styled' + +export type AudioTracksProps = { + audioTracks: Array, + changeAudioTrack: (trackId: number) => void, + isFullscreen?: boolean, + selectedAudioTrack: MediaPlaylist, +} + +export const AudioTracks = ({ + audioTracks, + changeAudioTrack, + isFullscreen, + selectedAudioTrack, +}: AudioTracksProps) => { + const { + close, + isOpen, + open, + } = useToggle() + + const AudioTracksList = useMemo(() => ( + map(audioTracks, (track) => ( + { + changeAudioTrack(track.id) + close() + }} + > + + {track.name} + + + )) + ), [ + audioTracks, + changeAudioTrack, + close, + isFullscreen, + selectedAudioTrack?.id, + ]) + + const content = isMobileDevice && !isFullscreen + ? ( + + Audio + {AudioTracksList} + + ) + : ( + + {isOpen && ( + + + {AudioTracksList} + + + )} + + ) + + return ( + + {audioTracks?.length > 1 && ( + + {selectedAudioTrack?.name} + + )} + {content} + + ) +} diff --git a/src/features/AudioTracks/styled.tsx b/src/features/AudioTracks/styled.tsx new file mode 100644 index 00000000..e4b1d120 --- /dev/null +++ b/src/features/AudioTracks/styled.tsx @@ -0,0 +1,97 @@ +import styled, { css } from 'styled-components/macro' + +import { ButtonBase } from 'features/StreamPlayer/styled' +import { Modal } from 'features/Modal' +import { ModalCloseButton, ModalWindow } from 'features/Modal/styled' + +import { isMobileDevice } from 'config/userAgent' + +export const SelectedAudioTrack = styled(ButtonBase)` + width: 200px; + height: 100%; + font-size: 16px; + color: rgba(255, 255, 255, 0.7); + font-weight: 600; + + ${isMobileDevice ? css` + width: 80px; + font-size: 12px; + ` : ''} +` + +export const AudioTracksWrapper = styled.div` + position: absolute; + bottom: 30px; + z-index: 5; + background: #333333; + filter: drop-shadow(0px 2px 40px rgba(0, 0, 0, 0.6)); + border-radius: 2px; + width: 200px; + + ${isMobileDevice ? css` + bottom: 45px; + width: 80px; + ` : ''} +` + +type AudioTrackItemProps = { + isFullScreen: boolean, + selected: boolean, +} + +export const AudioTrackItem = styled.div` + color: rgba(255, 255, 255, 0.7); + font-size: 12px; + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + cursor: pointer; + font-weight: normal; + + :hover, :focus { + background-color: rgba(255, 255, 255, 0.1); + } + + ${({ selected }) => (selected ? css` + font-weight: 600; + color: #FFFFFF; + ` : '')} + + ${({ isFullScreen }) => (isMobileDevice ? css` + justify-content: ${isFullScreen ? 'center' : 'flex-start'}; + padding: 10px 15px; + ` : '')} +` + +export const AudioTrackItemText = styled.span` + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +` + +export const ScModal = styled(Modal)` + ${ModalWindow} { + width: 280px; + position: fixed; + z-index: 5; + left: 0; + right: 0; + top: 3%; + padding: 0 0 5px; + border-radius: 3px; + } + + ${ModalCloseButton} { + background: transparent; + padding: 0; + } +` + +export const ScHeader = styled.div` + border-bottom: 1px solid #505050; + padding: 13px 15px; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; +` diff --git a/src/features/ChromeCast/index.tsx b/src/features/ChromeCast/index.tsx index 36ed8d40..ec632b8e 100644 --- a/src/features/ChromeCast/index.tsx +++ b/src/features/ChromeCast/index.tsx @@ -19,6 +19,8 @@ type Props = { src?: string, } +const NO_DEVICES_AVAILABLE = 'NO_DEVICES_AVAILABLE' + export const ChromeCast = memo(({ src } : Props) => { const [isCastAvailable, setIsCastAvailable] = useState(false) @@ -56,7 +58,11 @@ export const ChromeCast = memo(({ src } : Props) => { (window as any).__onGCastApiAvailable = (isAvailable: boolean) => { if (isAvailable) { castPlayer.initializeCastPlayer() - setIsCastAvailable(true) + + if ((window as any).cast.framework.CastContext.getInstance().getCastState() + !== NO_DEVICES_AVAILABLE) { + setIsCastAvailable(true) + } } } diff --git a/src/features/ChromeCast/styled.tsx b/src/features/ChromeCast/styled.tsx index 7d088ccb..66b039f1 100644 --- a/src/features/ChromeCast/styled.tsx +++ b/src/features/ChromeCast/styled.tsx @@ -5,15 +5,15 @@ import { isMobileDevice } from 'config/userAgent' export const Container = styled.div` display: flex; align-items: center; - margin-left: 25px; + margin-right: 25px; height: 24px; width: 24px; - & #castbutton:hover { + & #castbutton { --disconnected-color: #FFFFFF; } ${isMobileDevice ? css` - margin-left: 15px; + margin-right: 15px; ` : ''} ` diff --git a/src/features/MultiSourcePlayer/components/Settings/styled.tsx b/src/features/MultiSourcePlayer/components/Settings/styled.tsx index df3333a1..86a0f378 100644 --- a/src/features/MultiSourcePlayer/components/Settings/styled.tsx +++ b/src/features/MultiSourcePlayer/components/Settings/styled.tsx @@ -5,14 +5,14 @@ import { ButtonBase } from 'features/StreamPlayer/styled' export const SettingsButton = styled(ButtonBase)` width: 22px; height: 20px; - margin-left: 25px; + margin-right: 25px; background-image: url(/images/settings.svg); ${isMobileDevice ? css` width: 20px; height: 18px; - margin-left: 15px; + margin-right: 15px; cursor: pointer; ` : ''}; diff --git a/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/index.tsx b/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/index.tsx index 8634e78a..7bd1442c 100644 --- a/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/index.tsx +++ b/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/index.tsx @@ -2,6 +2,7 @@ import { T9n } from 'features/T9n' import { Settings } from 'features/MultiSourcePlayer/components/Settings' import { AirPlay } from 'features/AirPlay' import { ChromeCast } from 'features/ChromeCast' +import { AudioTracks } from 'features/AudioTracks' import { ControlsPropsExtended } from '../..' import { LiveBtn } from '../../../../styled' @@ -17,7 +18,9 @@ export const ControlsMobile = (controlsProps: {props: ControlsPropsExtended}) => const { props } = controlsProps const { + audioTracks, backToLive, + changeAudioTrack, controlsVisible, isFullscreen, isLive, @@ -25,6 +28,7 @@ export const ControlsMobile = (controlsProps: {props: ControlsPropsExtended}) => onQualitySelect, playBackTime, progressBarElement, + selectedAudioTrack, selectedQuality, src, videoQualities, @@ -34,13 +38,17 @@ export const ControlsMobile = (controlsProps: {props: ControlsPropsExtended}) => return ( - - - {playBackTime} - - + + {playBackTime} + + {isLive && ( diff --git a/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/styled.tsx b/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/styled.tsx index 97b3da1e..181ba721 100644 --- a/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/styled.tsx +++ b/src/features/StreamPlayer/components/Controls/Components/ControlsMobile/styled.tsx @@ -56,7 +56,6 @@ export const ControlsGroup = styled.div` export const Fullscreen = styled(ButtonBase)` width: 20px; height: 18px; - margin-left: 15px; background-image: ${({ isFullscreen }) => ( isFullscreen ? 'url(/images/player-fullscreen-off.svg)' diff --git a/src/features/StreamPlayer/components/Controls/Components/ControlsWeb/index.tsx b/src/features/StreamPlayer/components/Controls/Components/ControlsWeb/index.tsx index a62a169a..c2622203 100644 --- a/src/features/StreamPlayer/components/Controls/Components/ControlsWeb/index.tsx +++ b/src/features/StreamPlayer/components/Controls/Components/ControlsWeb/index.tsx @@ -9,6 +9,7 @@ import { import { Settings } from 'features/MultiSourcePlayer/components/Settings' import { T9n } from 'features/T9n' import { ChromeCast } from 'features/ChromeCast' +import { AudioTracks } from 'features/AudioTracks' import { ControlsPropsExtended } from '../..' import { VolumeBar } from '../../../VolumeBar' @@ -28,7 +29,9 @@ export const ControlsWeb = (controlsProps: { props: ControlsPropsExtended }) => const { props } = controlsProps const { activeChapterIndex = 0, + audioTracks, backToLive, + changeAudioTrack, controlsVisible, isFirstChapterPlaying, isFullscreen, @@ -48,6 +51,7 @@ export const ControlsWeb = (controlsProps: { props: ControlsPropsExtended }) => progressBarElement, rewindBackward, rewindForward, + selectedAudioTrack, selectedQuality, src, togglePlaying, @@ -95,6 +99,11 @@ export const ControlsWeb = (controlsProps: { props: ControlsPropsExtended }) => + {isLive && ( diff --git a/src/features/StreamPlayer/components/Controls/index.tsx b/src/features/StreamPlayer/components/Controls/index.tsx index 5b7c97c6..e17c8f1d 100644 --- a/src/features/StreamPlayer/components/Controls/index.tsx +++ b/src/features/StreamPlayer/components/Controls/index.tsx @@ -1,5 +1,7 @@ import { Fragment, useMemo } from 'react' +import type { MediaPlaylist } from 'hls.js' + import { DebouncedFunc } from 'lodash' import { isMobileDevice } from 'config/userAgent' @@ -17,7 +19,9 @@ import { ProgressBar } from '../ProgressBar' export type ControlsProps = { activeChapterIndex: number, allPlayedProgress: number, + audioTracks?: Array, backToLive?: () => void, + changeAudioTrack?: (trackId: number) => void, controlsVisible: boolean, duration: number, isFirstChapterPlaying?: boolean, @@ -43,6 +47,7 @@ export type ControlsProps = { playing: boolean, rewindBackward: () => void, rewindForward: () => void, + selectedAudioTrack?: MediaPlaylist, selectedQuality: string, src?: string, togglePlaying: () => void, diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index a1226cf2..92fd8ab6 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -29,6 +29,7 @@ import { useControlsVisibility } from './useControlsVisibility' import { useProgressChangeHandler } from './useProgressChangeHandler' import { usePlayingHandlers } from './usePlayingHandlers' import { useDuration } from './useDuration' +import { useAudioTrack } from './useAudioTrack' export type PlayerState = typeof initialState @@ -458,5 +459,6 @@ export const useVideoPlayer = ({ ...useControlsVisibility(isFullscreen, playing), ...useVolume(), ...useVideoQuality(hls), + ...useAudioTrack(hls), } } diff --git a/src/features/StreamPlayer/hooks/useAudioTrack.tsx b/src/features/StreamPlayer/hooks/useAudioTrack.tsx new file mode 100644 index 00000000..3ab212a7 --- /dev/null +++ b/src/features/StreamPlayer/hooks/useAudioTrack.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react' + +import find from 'lodash/find' + +import Hls, { MediaPlaylist } from 'hls.js' + +export const useAudioTrack = (hls: Hls | null) => { + const [audioTracks, setAudioTracks] = useState>() + const [selectedAudioTrack, setSelectedAudioTrack] = useState() + + const changeAudioTrack = (trackId: number) => { + if (!hls) return + + // eslint-disable-next-line no-param-reassign + hls.audioTrack = trackId + const track = find(audioTracks, { id: trackId }) + + setSelectedAudioTrack(track) + } + + useEffect(() => { + if (!hls) return undefined + + const listener = () => { + const defaultTrack = find(hls.audioTracks, { id: selectedAudioTrack?.id }) + ?? find(hls.audioTracks, { default: true }) + + setAudioTracks(hls.audioTracks) + setSelectedAudioTrack(defaultTrack) + } + hls.on(Hls.Events.AUDIO_TRACK_LOADED, listener) + + return () => { + hls.off(Hls.Events.AUDIO_TRACK_LOADED, listener) + } + }, [hls, selectedAudioTrack]) + + return { + audioTracks, + changeAudioTrack, + selectedAudioTrack, + } +} diff --git a/src/features/StreamPlayer/index.tsx b/src/features/StreamPlayer/index.tsx index e2c27fad..5a4b6bda 100644 --- a/src/features/StreamPlayer/index.tsx +++ b/src/features/StreamPlayer/index.tsx @@ -36,9 +36,11 @@ export const StreamPlayer = (props: Props) => { const { activeChapterIndex, allPlayedProgress, + audioTracks, backToLive, buffering, centerControlsVisible, + changeAudioTrack, chapters, duration, hideCenterControls, @@ -72,6 +74,7 @@ export const StreamPlayer = (props: Props) => { rewindBackward, rewindForward, seek, + selectedAudioTrack, selectedQuality, showCenterControls, togglePlaying, @@ -155,7 +158,9 @@ export const StreamPlayer = (props: Props) => { { volumeInPercent={volumeInPercent} activeChapterIndex={activeChapterIndex} liveChapters={chapters} + selectedAudioTrack={selectedAudioTrack} /> diff --git a/src/features/StreamPlayer/styled.tsx b/src/features/StreamPlayer/styled.tsx index c13215d5..0bf29a45 100644 --- a/src/features/StreamPlayer/styled.tsx +++ b/src/features/StreamPlayer/styled.tsx @@ -195,7 +195,6 @@ type FullscreenProps = { export const Fullscreen = styled(ButtonBase)` width: 22px; height: 20px; - margin-left: 26px; background-image: ${({ isFullscreen }) => ( isFullscreen ? 'url(/images/player-fullscreen-off.svg)' @@ -205,7 +204,6 @@ export const Fullscreen = styled(ButtonBase)` ? css` width: 20px; height: 18px; - margin-left: 15px; ` : ''}; ` @@ -313,8 +311,11 @@ export const LiveBtn = styled(ButtonBase)` padding: 4.5px 8px; background-color: #CC0000; border-radius: 1.3px; + margin-right: 25px; + ${isMobileDevice ? css` + margin-right: 15px; ` : ''}; `