1- import './controls .css'
1+ import './commonControls .css'
22
33import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
44import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'
55import Box from '@mui/material/Box'
66import Button from '@mui/material/Button'
7- import Icon from '@mui/material/Icon'
8- import IconButton from '@mui/material/IconButton'
97import LinearProgress from '@mui/material/LinearProgress'
108import Slider from '@mui/material/Slider'
119import Typography from '@mui/material/Typography'
1210import React , { useState } from 'react'
1311
1412import { formatDurationTime } from '../../helper'
13+ import { MediaIconButton } from './mediaIconButton'
1514
16- interface MediaIconButtonProps {
17- onClick : ( ) => void
18- action : 'play' | 'pause' | 'forward' | 'replay' | 'volumeUp' | 'volumeOff'
19- className ?: string
15+ export interface MediaControls {
16+ mediaElement : HTMLMediaElement
2017}
2118
22- interface MediaControls {
23- mediaElement : HTMLMediaElement
19+ interface ToggleTranscriptButtonProps {
20+ isTranscriptVisible : boolean
21+ setIsTranscriptVisible : ( ) => void
2422}
2523
26- interface TranscriptToggleProps {
27- isTranscriptShown : boolean
28- setIsTranscriptShown : ( ) => void
24+ interface PlayOrPauseButtonProps extends MediaControls {
25+ onError : ( errorMessage : string ) => void
2926}
3027
3128interface MoveTenSecondsButtonProps extends MediaControls {
@@ -34,31 +31,6 @@ interface MoveTenSecondsButtonProps extends MediaControls {
3431
3532const percentMultiple = 100
3633
37- export function MediaIconButton ( props : MediaIconButtonProps ) {
38- const controls = {
39- play : { symbol : 'play_circle' , label : 'play' } ,
40- pause : { symbol : 'pause_circle' , label : 'pause' } ,
41- forward : { symbol : 'forward_10' , label : 'forward ten seconds' } ,
42- replay : { symbol : 'replay_10' , label : 'replay ten seconds' } ,
43- volumeUp : { symbol : 'volume_up' , label : 'mute' } ,
44- volumeOff : { symbol : 'volume_off' , label : 'unmute' } ,
45- }
46- return (
47- < IconButton
48- onClick = { props . onClick }
49- aria-label = { `click to ${ controls [ props . action ] . label } ` }
50- className = { props . className }
51- data-cy = { `${ props . action } -button` }
52- >
53- < Icon color = "primary" >
54- < span className = "material-symbols-rounded" >
55- { controls [ props . action ] . symbol }
56- </ span >
57- </ Icon >
58- </ IconButton >
59- )
60- }
61-
6234export function ProgressSlider ( props : MediaControls ) {
6335 const formattedElapsedTime = formatDurationTime (
6436 props . mediaElement . currentTime
@@ -116,30 +88,23 @@ export function ProgressSlider(props: MediaControls) {
11688}
11789
11890export function VolumeSettings ( props : MediaControls ) {
119- const [ volumeFraction , setVolumeFraction ] = useState ( 1 )
91+ const [ volumeFraction , setVolumeFraction ] = useState (
92+ props . mediaElement . volume
93+ )
94+ const [ previousVolume , setPreviousVolume ] = useState (
95+ props . mediaElement . volume
96+ )
12097
121- const isMuted = props . mediaElement . muted
122- const action = isMuted ? 'volumeOff' : 'volumeUp'
98+ const action = props . mediaElement . muted ? 'volumeOff' : 'volumeUp'
99+
100+ function handleMuteToggle ( ) {
101+ props . mediaElement . muted = ! props . mediaElement . muted
123102
124- props . mediaElement . onvolumechange = function ( ) {
125103 if ( props . mediaElement . muted ) {
104+ setPreviousVolume ( props . mediaElement . volume )
126105 setVolumeFraction ( 0 )
127106 } else {
128- setVolumeFraction ( props . mediaElement . volume )
129- }
130- }
131-
132- function handleMuteToggle ( ) {
133- if ( isMuted && props . mediaElement . volume === 0 ) {
134- // If audio was muted and volume was 0, unmute and restore to full volume
135- props . mediaElement . muted = false
136- props . mediaElement . volume = 1
137- } else if ( isMuted ) {
138- // If audio was muted, unmute, restoring previous volume
139- props . mediaElement . muted = false
140- } else {
141- // If audio was unmuted, mute
142- props . mediaElement . muted = true
107+ setVolumeFraction ( previousVolume )
143108 }
144109 }
145110
@@ -151,6 +116,7 @@ export function VolumeSettings(props: MediaControls) {
151116
152117 props . mediaElement . muted = updatedVolume === 0
153118 props . mediaElement . volume = updatedVolume as number
119+ setVolumeFraction ( props . mediaElement . volume )
154120 }
155121
156122 return (
@@ -175,55 +141,64 @@ export function VolumeSettings(props: MediaControls) {
175141 )
176142}
177143
178- export function TranscriptToggle ( props : TranscriptToggleProps ) {
179- const Icon = props . isTranscriptShown
144+ export function ToggleTranscriptButton ( props : ToggleTranscriptButtonProps ) {
145+ const Icon = props . isTranscriptVisible
180146 ? KeyboardArrowUpIcon
181147 : KeyboardArrowDownIcon
182148
183- const buttonText = `${ props . isTranscriptShown ? 'Hide' : 'Show' } Transcript`
149+ const buttonText = `${ props . isTranscriptVisible ? 'Hide' : 'Show' } Transcript`
184150
185151 return (
186152 < Button
187153 className = "rustic-transcript-toggle"
188154 data-cy = "transcript-toggle"
189- onClick = { props . setIsTranscriptShown }
155+ onClick = { props . setIsTranscriptVisible }
190156 endIcon = { < Icon /> }
191157 >
192158 < Typography variant = "overline" > { buttonText } </ Typography >
193159 </ Button >
194160 )
195161}
196162
197- export function PausePlayToggle ( props : MediaControls ) {
198- const [ isPlaying , setIsPlaying ] = useState ( false )
163+ export function PlayOrPauseButton ( props : PlayOrPauseButtonProps ) {
164+ const [ isPlaying , setIsPlaying ] = useState ( ! props . mediaElement . paused )
199165
200166 const action = isPlaying ? 'pause' : 'play'
201167
202- function handlePausePlayToggle ( ) {
168+ function handlePlayOrPauseToggle ( ) {
203169 if ( isPlaying ) {
204170 props . mediaElement . pause ( )
205- setIsPlaying ( false )
206171 } else {
207- props . mediaElement . play ( )
208- setIsPlaying ( true )
172+ props . mediaElement . play ( ) . catch ( ( error : DOMException ) => {
173+ props . onError ( `Failed to play the media. Error: ${ error . message } ` )
174+ } )
209175 }
210176 }
211177
178+ // State is updated by event listeners so that the icon is displayed correctly, even when play/pause is not initiated by the user or user initiates without direct use of this toggle (e.g. automatically pauses when picture-in-picture is exited, or pause and play can be toggled in the picture-in-picture window).
212179 props . mediaElement . onended = function ( ) {
213180 setIsPlaying ( false )
214181 }
182+ props . mediaElement . onpause = function ( ) {
183+ setIsPlaying ( false )
184+ }
185+ props . mediaElement . onplay = function ( ) {
186+ setIsPlaying ( true )
187+ }
215188
216189 return (
217190 < MediaIconButton
218- onClick = { handlePausePlayToggle }
191+ onClick = { handlePlayOrPauseToggle }
219192 action = { action }
220193 className = "rustic-pause-play-icon"
221194 />
222195 )
223196}
224197
225198export function PlaybackRateButton ( props : MediaControls ) {
226- const [ playbackRate , setPlaybackRate ] = useState ( 1 )
199+ const [ playbackRate , setPlaybackRate ] = useState (
200+ props . mediaElement . playbackRate
201+ )
227202
228203 function handlePlaybackRateChange ( ) {
229204 let newPlaybackRate = 1
@@ -245,9 +220,7 @@ export function PlaybackRateButton(props: MediaControls) {
245220 aria-label = { `Playback rate: ${ playbackRate } x, click to change` }
246221 data-cy = "playback-rate-button"
247222 >
248- < Typography variant = "body1" color = "primary.main" >
249- { playbackRate } X
250- </ Typography >
223+ < Typography variant = "body1" > { playbackRate } X</ Typography >
251224 </ Button >
252225 )
253226}
0 commit comments