fix(#2979): audio tracks selection

keep-around/9d11a525a1ee745f785976389c40d7a0601133e1
Rakov Roman 3 years ago
parent 8b484e1fd2
commit 41d7131610
  1. 4
      src/features/AirPlay/styled.tsx
  2. 98
      src/features/AudioTracks/index.tsx
  3. 97
      src/features/AudioTracks/styled.tsx
  4. 8
      src/features/ChromeCast/index.tsx
  5. 6
      src/features/ChromeCast/styled.tsx
  6. 4
      src/features/MultiSourcePlayer/components/Settings/styled.tsx
  7. 18
      src/features/StreamPlayer/components/Controls/Components/ControlsMobile/index.tsx
  8. 1
      src/features/StreamPlayer/components/Controls/Components/ControlsMobile/styled.tsx
  9. 9
      src/features/StreamPlayer/components/Controls/Components/ControlsWeb/index.tsx
  10. 5
      src/features/StreamPlayer/components/Controls/index.tsx
  11. 2
      src/features/StreamPlayer/hooks/index.tsx
  12. 43
      src/features/StreamPlayer/hooks/useAudioTrack.tsx
  13. 6
      src/features/StreamPlayer/index.tsx
  14. 5
      src/features/StreamPlayer/styled.tsx

@ -9,13 +9,13 @@ export const AirplayButton = styled(ButtonBase)`
height: 24px; height: 24px;
padding: 0; padding: 0;
display: none; display: none;
margin-left: 25px; margin-right: 25px;
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 22px; width: 22px;
height: 22px; height: 22px;
margin-left: 15px; margin-right: 15px;
` `
: ''}; : ''};
` `

@ -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<MediaPlaylist>,
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) => (
<AudioTrackItem
key={`${track.id}_${track.name}`}
isFullScreen={Boolean(isFullscreen)}
selected={track.id === selectedAudioTrack?.id}
onClick={() => {
changeAudioTrack(track.id)
close()
}}
>
<AudioTrackItemText>
{track.name}
</AudioTrackItemText>
</AudioTrackItem>
))
), [
audioTracks,
changeAudioTrack,
close,
isFullscreen,
selectedAudioTrack?.id,
])
const content = isMobileDevice && !isFullscreen
? (
<ScModal
closeSize={12}
close={close}
isOpen={isOpen}
>
<ScHeader>Audio</ScHeader>
{AudioTracksList}
</ScModal>
)
: (
<Fragment>
{isOpen && (
<AudioTracksWrapper>
<OutsideClick onClick={close}>
{AudioTracksList}
</OutsideClick>
</AudioTracksWrapper>
)}
</Fragment>
)
return (
<Fragment>
{audioTracks?.length > 1 && (
<SelectedAudioTrack onClick={open}>
{selectedAudioTrack?.name}
</SelectedAudioTrack>
)}
{content}
</Fragment>
)
}

@ -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<AudioTrackItemProps>`
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;
`

@ -19,6 +19,8 @@ type Props = {
src?: string, src?: string,
} }
const NO_DEVICES_AVAILABLE = 'NO_DEVICES_AVAILABLE'
export const ChromeCast = memo(({ src } : Props) => { export const ChromeCast = memo(({ src } : Props) => {
const [isCastAvailable, setIsCastAvailable] = useState(false) const [isCastAvailable, setIsCastAvailable] = useState(false)
@ -56,7 +58,11 @@ export const ChromeCast = memo(({ src } : Props) => {
(window as any).__onGCastApiAvailable = (isAvailable: boolean) => { (window as any).__onGCastApiAvailable = (isAvailable: boolean) => {
if (isAvailable) { if (isAvailable) {
castPlayer.initializeCastPlayer() castPlayer.initializeCastPlayer()
setIsCastAvailable(true)
if ((window as any).cast.framework.CastContext.getInstance().getCastState()
!== NO_DEVICES_AVAILABLE) {
setIsCastAvailable(true)
}
} }
} }

@ -5,15 +5,15 @@ import { isMobileDevice } from 'config/userAgent'
export const Container = styled.div` export const Container = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 25px; margin-right: 25px;
height: 24px; height: 24px;
width: 24px; width: 24px;
& #castbutton:hover { & #castbutton {
--disconnected-color: #FFFFFF; --disconnected-color: #FFFFFF;
} }
${isMobileDevice ? css` ${isMobileDevice ? css`
margin-left: 15px; margin-right: 15px;
` : ''} ` : ''}
` `

@ -5,14 +5,14 @@ import { ButtonBase } from 'features/StreamPlayer/styled'
export const SettingsButton = styled(ButtonBase)` export const SettingsButton = styled(ButtonBase)`
width: 22px; width: 22px;
height: 20px; height: 20px;
margin-left: 25px; margin-right: 25px;
background-image: url(/images/settings.svg); background-image: url(/images/settings.svg);
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 20px; width: 20px;
height: 18px; height: 18px;
margin-left: 15px; margin-right: 15px;
cursor: pointer; cursor: pointer;
` `
: ''}; : ''};

@ -2,6 +2,7 @@ import { T9n } from 'features/T9n'
import { Settings } from 'features/MultiSourcePlayer/components/Settings' import { Settings } from 'features/MultiSourcePlayer/components/Settings'
import { AirPlay } from 'features/AirPlay' import { AirPlay } from 'features/AirPlay'
import { ChromeCast } from 'features/ChromeCast' import { ChromeCast } from 'features/ChromeCast'
import { AudioTracks } from 'features/AudioTracks'
import { ControlsPropsExtended } from '../..' import { ControlsPropsExtended } from '../..'
import { LiveBtn } from '../../../../styled' import { LiveBtn } from '../../../../styled'
@ -17,7 +18,9 @@ export const ControlsMobile = (controlsProps: {props: ControlsPropsExtended}) =>
const { props } = controlsProps const { props } = controlsProps
const { const {
audioTracks,
backToLive, backToLive,
changeAudioTrack,
controlsVisible, controlsVisible,
isFullscreen, isFullscreen,
isLive, isLive,
@ -25,6 +28,7 @@ export const ControlsMobile = (controlsProps: {props: ControlsPropsExtended}) =>
onQualitySelect, onQualitySelect,
playBackTime, playBackTime,
progressBarElement, progressBarElement,
selectedAudioTrack,
selectedQuality, selectedQuality,
src, src,
videoQualities, videoQualities,
@ -34,13 +38,17 @@ export const ControlsMobile = (controlsProps: {props: ControlsPropsExtended}) =>
return ( return (
<Controls isFullscreen={isFullscreen}> <Controls isFullscreen={isFullscreen}>
<ControlsRow visible={controlsVisible}> <ControlsRow visible={controlsVisible}>
<ControlsGroup> <PlaybackTime>
<PlaybackTime> {playBackTime}
{playBackTime} </PlaybackTime>
</PlaybackTime>
</ControlsGroup>
<ControlsGroup> <ControlsGroup>
<AudioTracks
audioTracks={audioTracks!}
changeAudioTrack={changeAudioTrack!}
isFullscreen={isFullscreen}
selectedAudioTrack={selectedAudioTrack!}
/>
{isLive && ( {isLive && (
<LiveBtn onClick={backToLive}> <LiveBtn onClick={backToLive}>
<T9n t='live' /> <T9n t='live' />

@ -56,7 +56,6 @@ export const ControlsGroup = styled.div`
export const Fullscreen = styled(ButtonBase)<FullscreenProps>` export const Fullscreen = styled(ButtonBase)<FullscreenProps>`
width: 20px; width: 20px;
height: 18px; height: 18px;
margin-left: 15px;
background-image: ${({ isFullscreen }) => ( background-image: ${({ isFullscreen }) => (
isFullscreen isFullscreen
? 'url(/images/player-fullscreen-off.svg)' ? 'url(/images/player-fullscreen-off.svg)'

@ -9,6 +9,7 @@ import {
import { Settings } from 'features/MultiSourcePlayer/components/Settings' import { Settings } from 'features/MultiSourcePlayer/components/Settings'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { ChromeCast } from 'features/ChromeCast' import { ChromeCast } from 'features/ChromeCast'
import { AudioTracks } from 'features/AudioTracks'
import { ControlsPropsExtended } from '../..' import { ControlsPropsExtended } from '../..'
import { VolumeBar } from '../../../VolumeBar' import { VolumeBar } from '../../../VolumeBar'
@ -28,7 +29,9 @@ export const ControlsWeb = (controlsProps: { props: ControlsPropsExtended }) =>
const { props } = controlsProps const { props } = controlsProps
const { const {
activeChapterIndex = 0, activeChapterIndex = 0,
audioTracks,
backToLive, backToLive,
changeAudioTrack,
controlsVisible, controlsVisible,
isFirstChapterPlaying, isFirstChapterPlaying,
isFullscreen, isFullscreen,
@ -48,6 +51,7 @@ export const ControlsWeb = (controlsProps: { props: ControlsPropsExtended }) =>
progressBarElement, progressBarElement,
rewindBackward, rewindBackward,
rewindForward, rewindForward,
selectedAudioTrack,
selectedQuality, selectedQuality,
src, src,
togglePlaying, togglePlaying,
@ -95,6 +99,11 @@ export const ControlsWeb = (controlsProps: { props: ControlsPropsExtended }) =>
</ControlsGroup> </ControlsGroup>
<ControlsGroup> <ControlsGroup>
<AudioTracks
audioTracks={audioTracks!}
changeAudioTrack={changeAudioTrack!}
selectedAudioTrack={selectedAudioTrack!}
/>
{isLive && ( {isLive && (
<LiveBtn onClick={backToLive}> <LiveBtn onClick={backToLive}>
<T9n t='live' /> <T9n t='live' />

@ -1,5 +1,7 @@
import { Fragment, useMemo } from 'react' import { Fragment, useMemo } from 'react'
import type { MediaPlaylist } from 'hls.js'
import { DebouncedFunc } from 'lodash' import { DebouncedFunc } from 'lodash'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
@ -17,7 +19,9 @@ import { ProgressBar } from '../ProgressBar'
export type ControlsProps = { export type ControlsProps = {
activeChapterIndex: number, activeChapterIndex: number,
allPlayedProgress: number, allPlayedProgress: number,
audioTracks?: Array<MediaPlaylist>,
backToLive?: () => void, backToLive?: () => void,
changeAudioTrack?: (trackId: number) => void,
controlsVisible: boolean, controlsVisible: boolean,
duration: number, duration: number,
isFirstChapterPlaying?: boolean, isFirstChapterPlaying?: boolean,
@ -43,6 +47,7 @@ export type ControlsProps = {
playing: boolean, playing: boolean,
rewindBackward: () => void, rewindBackward: () => void,
rewindForward: () => void, rewindForward: () => void,
selectedAudioTrack?: MediaPlaylist,
selectedQuality: string, selectedQuality: string,
src?: string, src?: string,
togglePlaying: () => void, togglePlaying: () => void,

@ -29,6 +29,7 @@ import { useControlsVisibility } from './useControlsVisibility'
import { useProgressChangeHandler } from './useProgressChangeHandler' import { useProgressChangeHandler } from './useProgressChangeHandler'
import { usePlayingHandlers } from './usePlayingHandlers' import { usePlayingHandlers } from './usePlayingHandlers'
import { useDuration } from './useDuration' import { useDuration } from './useDuration'
import { useAudioTrack } from './useAudioTrack'
export type PlayerState = typeof initialState export type PlayerState = typeof initialState
@ -458,5 +459,6 @@ export const useVideoPlayer = ({
...useControlsVisibility(isFullscreen, playing), ...useControlsVisibility(isFullscreen, playing),
...useVolume(), ...useVolume(),
...useVideoQuality(hls), ...useVideoQuality(hls),
...useAudioTrack(hls),
} }
} }

@ -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<Array<MediaPlaylist>>()
const [selectedAudioTrack, setSelectedAudioTrack] = useState<MediaPlaylist>()
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,
}
}

@ -36,9 +36,11 @@ export const StreamPlayer = (props: Props) => {
const { const {
activeChapterIndex, activeChapterIndex,
allPlayedProgress, allPlayedProgress,
audioTracks,
backToLive, backToLive,
buffering, buffering,
centerControlsVisible, centerControlsVisible,
changeAudioTrack,
chapters, chapters,
duration, duration,
hideCenterControls, hideCenterControls,
@ -72,6 +74,7 @@ export const StreamPlayer = (props: Props) => {
rewindBackward, rewindBackward,
rewindForward, rewindForward,
seek, seek,
selectedAudioTrack,
selectedQuality, selectedQuality,
showCenterControls, showCenterControls,
togglePlaying, togglePlaying,
@ -155,7 +158,9 @@ export const StreamPlayer = (props: Props) => {
<Controls <Controls
allPlayedProgress={allPlayedProgress} allPlayedProgress={allPlayedProgress}
audioTracks={audioTracks}
backToLive={backToLive} backToLive={backToLive}
changeAudioTrack={changeAudioTrack}
controlsVisible={mainControlsVisible} controlsVisible={mainControlsVisible}
duration={duration} duration={duration}
isFullscreen={isFullscreen} isFullscreen={isFullscreen}
@ -182,6 +187,7 @@ export const StreamPlayer = (props: Props) => {
volumeInPercent={volumeInPercent} volumeInPercent={volumeInPercent}
activeChapterIndex={activeChapterIndex} activeChapterIndex={activeChapterIndex}
liveChapters={chapters} liveChapters={chapters}
selectedAudioTrack={selectedAudioTrack}
/> />
<ControlsGradient isVisible={mainControlsVisible} /> <ControlsGradient isVisible={mainControlsVisible} />
</PlayerWrapper> </PlayerWrapper>

@ -195,7 +195,6 @@ type FullscreenProps = {
export const Fullscreen = styled(ButtonBase)<FullscreenProps>` export const Fullscreen = styled(ButtonBase)<FullscreenProps>`
width: 22px; width: 22px;
height: 20px; height: 20px;
margin-left: 26px;
background-image: ${({ isFullscreen }) => ( background-image: ${({ isFullscreen }) => (
isFullscreen isFullscreen
? 'url(/images/player-fullscreen-off.svg)' ? 'url(/images/player-fullscreen-off.svg)'
@ -205,7 +204,6 @@ export const Fullscreen = styled(ButtonBase)<FullscreenProps>`
? css` ? css`
width: 20px; width: 20px;
height: 18px; height: 18px;
margin-left: 15px;
` `
: ''}; : ''};
` `
@ -313,8 +311,11 @@ export const LiveBtn = styled(ButtonBase)`
padding: 4.5px 8px; padding: 4.5px 8px;
background-color: #CC0000; background-color: #CC0000;
border-radius: 1.3px; border-radius: 1.3px;
margin-right: 25px;
${isMobileDevice ${isMobileDevice
? css` ? css`
margin-right: 15px;
` `
: ''}; : ''};
` `

Loading…
Cancel
Save