diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index 7e065093..4eb5f47c 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -5,6 +5,7 @@ import { Dashboard } from "../dashboard"; import useWebRTC from "../useWebRTC"; import useTranscript from "../useTranscript"; import { useWebSockets } from "../useWebSockets"; +import useAudioDevice from "../useAudioDevice"; import "../../styles/button.css"; import { Topic } from "../webSocketTypes"; import getApi from "../../lib/getApi"; @@ -28,29 +29,71 @@ const App = () => { const transcript = useTranscript(); const webRTC = useWebRTC(stream, transcript.response?.id, api); const webSockets = useWebSockets(transcript.response?.id); + const { + loading, + permissionOk, + permissionDenied, + audioDevices, + requestPermission, + getAudioStream, + } = useAudioDevice(); return ( - <> - { - webRTC?.peer?.send(JSON.stringify({ cmd: "STOP" })); - setStream(null); - }} - topics={webSockets.topics} - useActiveTopic={useActiveTopic} - /> +
+ {permissionOk ? ( + <> + { + webRTC?.peer?.send(JSON.stringify({ cmd: "STOP" })); + setStream(null); + }} + topics={webSockets.topics} + getAudioStream={getAudioStream} + audioDevices={audioDevices} + useActiveTopic={useActiveTopic} + /> -
- - - + + + ) : ( + <> +
+

+ Audio Permissions +

+ {loading ? ( +

+ Checking permission... +

+ ) : ( + <> +

+ Reflector needs access to your microphone to work. +
+ {permissionDenied + ? "Please reset microphone permissions to continue." + : "Please grant permission to continue."} +

+ + + )} +
+ + )} +
); }; diff --git a/www/app/transcripts/recorder.tsx b/www/app/transcripts/recorder.tsx index 6d0731ac..6467494f 100644 --- a/www/app/transcripts/recorder.tsx +++ b/www/app/transcripts/recorder.tsx @@ -11,32 +11,21 @@ import Dropdown, { Option } from "react-dropdown"; import "react-dropdown/style.css"; import { formatTime } from "../lib/time"; +import { Topic } from "./webSocketTypes"; const AudioInputsDropdown: React.FC<{ + audioDevices: Option[]; setDeviceId: React.Dispatch>; disabled: boolean; }> = (props) => { - const [ddOptions, setDdOptions] = useState>([]); + const [ddOptions, setDdOptions] = useState([]); useEffect(() => { - const init = async () => { - // Request permission to use audio inputs - await navigator.mediaDevices - .getUserMedia({ audio: true }) - .then((stream) => stream.getTracks().forEach((t) => t.stop())); - - const devices = await navigator.mediaDevices.enumerateDevices(); - const audioDevices = devices - .filter((d) => d.kind === "audioinput" && d.deviceId != "") - .map((d) => ({ value: d.deviceId, label: d.label })); - - if (audioDevices.length < 1) return console.log("no audio input devices"); - - setDdOptions(audioDevices); - props.setDeviceId(audioDevices[0].value); - }; - init(); - }, []); + setDdOptions(props.audioDevices); + props.setDeviceId( + props.audioDevices.length > 0 ? props.audioDevices[0].value : null, + ); + }, [props.audioDevices]); const handleDropdownChange = (option: Option) => { props.setDeviceId(option.value); @@ -52,7 +41,19 @@ const AudioInputsDropdown: React.FC<{ ); }; -export default function Recorder(props: any) { +type RecorderProps = { + setStream: React.Dispatch>; + onStop: () => void; + topics: Topic[]; + getAudioStream: (deviceId: string | null) => Promise; + audioDevices: Option[]; + useActiveTopic: [ + Topic | null, + React.Dispatch>, + ]; +}; + +export default function Recorder(props: RecorderProps) { const waveformRef = useRef(null); const [wavesurfer, setWavesurfer] = useState(null); const [record, setRecord] = useState(null); @@ -212,17 +213,13 @@ export default function Recorder(props: any) { const playBtn = document.getElementById("play-btn"); if (playBtn) playBtn.removeAttribute("disabled"); } else { - const stream = await navigator.mediaDevices.getUserMedia({ - audio: { - deviceId: deviceId as string, - noiseSuppression: false, - echoCancellation: false, - }, - }); - await record.startRecording(stream); + const stream = await props.getAudioStream(deviceId); props.setStream(stream); - setIsRecording(true); waveRegions?.clearRegions(); + if (stream) { + await record.startRecording(stream); + setIsRecording(true); + } } }; @@ -232,15 +229,18 @@ export default function Recorder(props: any) { const timeLabel = () => { if (isRecording) return formatTime(currentTime); - else if (duration) - return `${formatTime(currentTime)}/${formatTime(duration)}`; - else ""; + if (duration) return `${formatTime(currentTime)}/${formatTime(duration)}`; + return ""; }; return (
- +