2021-11-01 06:40:04 +09:00
import styles from "./Panes.module.scss" ;
2021-08-20 02:11:02 +10:00
import { Text } from "preact-i18n" ;
2021-09-09 23:02:47 +01:00
import { useEffect , useState } from "preact/hooks" ;
2021-08-20 02:11:02 +10:00
2021-09-08 14:12:15 +02:00
import { TextReact } from "../../../lib/i18n" ;
2021-09-09 23:02:47 +01:00
import { stopPropagation } from "../../../lib/stopPropagation" ;
import { voiceState } from "../../../lib/vortex/VoiceState" ;
import { connectState } from "../../../redux/connector" ;
2021-09-08 14:12:15 +02:00
2021-11-01 06:40:04 +09:00
import Button from "../../../components/ui/Button" ;
2021-08-20 02:11:02 +10:00
import ComboBox from "../../../components/ui/ComboBox" ;
2021-09-08 14:12:15 +02:00
import Overline from "../../../components/ui/Overline" ;
import Tip from "../../../components/ui/Tip" ;
2021-09-09 23:02:47 +01:00
const constraints = { audio : true } ;
2021-08-20 02:11:02 +10:00
export function Component() {
2021-09-09 23:02:47 +01:00
const [ mediaStream , setMediaStream ] = useState < MediaStream | undefined > (
undefined ,
) ;
const [ mediaDevices , setMediaDevices ] = useState <
MediaDeviceInfo [ ] | undefined
> ( undefined ) ;
const [ permission , setPermission ] = useState < PermissionState | undefined > (
undefined ,
) ;
const [ error , setError ] = useState < DOMException | undefined > ( undefined ) ;
2021-09-08 14:12:15 +02:00
const askOrGetPermission = async ( ) = > {
try {
2021-10-31 22:52:20 +01:00
const result = await navigator . mediaDevices . getUserMedia (
2021-09-09 23:02:47 +01:00
constraints ,
) ;
2021-10-31 22:52:20 +01:00
setMediaStream ( result ) ;
2021-09-08 14:12:15 +02:00
} catch ( err ) {
// The user has blocked the permission
2021-09-09 23:02:47 +01:00
setError ( err as DOMException ) ;
2021-09-08 14:12:15 +02:00
}
try {
2021-09-09 23:02:47 +01:00
const { state } = await navigator . permissions . query ( {
// eslint-disable-next-line
2021-10-31 22:52:20 +01:00
// @ts-ignore: very few browsers accept this `PermissionName`, but it has been drafted in https://www.w3.org/TR/permissions/#powerful-features-registry
2021-09-09 23:02:47 +01:00
name : "microphone" ,
} ) ;
2021-09-08 14:12:15 +02:00
2021-09-09 23:02:47 +01:00
setPermission ( state ) ;
2021-09-08 14:12:15 +02:00
} catch ( err ) {
2021-10-31 22:52:20 +01:00
// the browser might not support `query` functionnality or `PermissionName`
// nothing to do
2021-09-08 14:12:15 +02:00
}
2021-09-09 23:02:47 +01:00
} ;
2021-08-20 02:11:02 +10:00
2021-10-31 22:52:20 +01:00
useEffect ( ( ) = > {
return ( ) = > {
if ( mediaStream ) {
// close microphone access on unmount
mediaStream . getTracks ( ) . forEach ( track = > {
track . stop ( )
} )
}
}
} , [ mediaStream ] ) ;
2021-09-08 14:12:15 +02:00
useEffect ( ( ) = > {
if ( ! mediaStream ) {
return ;
}
2021-09-09 23:02:47 +01:00
navigator . mediaDevices . enumerateDevices ( ) . then (
( devices ) = > {
setMediaDevices ( devices ) ;
} ,
( err ) = > {
setError ( err as DOMException ) ;
} ,
) ;
} , [ mediaStream ] ) ;
const handleAskForPermission = (
2021-11-01 06:40:04 +09:00
ev : JSX.TargetedMouseEvent < HTMLElement > ,
2021-09-09 23:02:47 +01:00
) = > {
stopPropagation ( ev ) ;
setError ( undefined ) ;
askOrGetPermission ( ) ;
} ;
2021-08-20 02:11:02 +10:00
return (
< >
2021-11-01 06:40:04 +09:00
< div class = { styles . audio } >
2021-09-09 23:02:47 +01:00
< h3 >
< Text id = "app.settings.pages.audio.input_device" / >
< / h3 >
2021-11-01 06:40:04 +09:00
{ ! permission && (
< div className = { styles . grant_permission } >
< span className = { styles . description } >
< Text id = "app.settings.pages.audio.tip_grant_permission" / >
< / span >
< Button
compact
onClick = { ( e ) = > handleAskForPermission ( e ) }
error >
< Text id = "app.settings.pages.audio.button_grant" / >
< / Button >
< / div >
) }
2021-09-09 23:02:47 +01:00
< ComboBox
value = { window . localStorage . getItem ( "audioInputDevice" ) ? ? 0 }
onChange = { ( e ) = >
changeAudioDevice ( e . currentTarget . value , "input" )
} >
{ mediaDevices
? . filter ( ( device ) = > device . kind === "audioinput" )
. map ( ( device ) = > {
return (
< option
value = { device . deviceId }
key = { device . deviceId } >
{ device . label || (
< Text id = "app.settings.pages.audio.device_label_NA" / >
) }
< / option >
) ;
} ) }
< / ComboBox >
{ error && error . name === "NotAllowedError" && (
< Overline error = "AudioPermissionBlock" type = "error" block / >
) }
{ error && permission === "prompt" && (
< Tip >
< TextReact
id = "app.settings.pages.audio.tip_retry"
fields = { {
retryBtn : (
< a onClick = { handleAskForPermission } >
< Text id = "app.settings.pages.audio.button_retry" / >
< / a >
) ,
} }
/ >
< / Tip >
) }
< / div >
2021-08-20 02:11:02 +10:00
< / >
) ;
}
function changeAudioDevice ( deviceId : string , deviceType : string ) {
2021-09-09 23:02:47 +01:00
if ( deviceType === "input" ) {
window . localStorage . setItem ( "audioInputDevice" , deviceId ) ;
if ( voiceState . isProducing ( "audio" ) ) {
2021-08-20 02:11:02 +10:00
voiceState . stopProducing ( "audio" ) ;
voiceState . startProducing ( "audio" ) ;
}
2021-09-09 23:02:47 +01:00
} else if ( deviceType === "output" ) {
window . localStorage . setItem ( "audioOutputDevice" , deviceId ) ;
2021-08-20 02:11:02 +10:00
}
}
export const Audio = connectState ( Component , ( ) = > {
return ;
} ) ;