add microphone recording and allow user to select audio device

This commit is contained in:
MarconLP 2023-04-18 23:05:05 +02:00
parent 48c2702701
commit 44e78b16d9
No known key found for this signature in database
GPG key ID: A08A9C8B623F5EA5

View file

@ -1,27 +1,21 @@
import React, { useState, useRef, Fragment } from "react"; import React, { useState, useRef, Fragment, useEffect } from "react";
import RecordRTC, { invokeSaveAsDialog } from "recordrtc"; import RecordRTC, { invokeSaveAsDialog } from "recordrtc";
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid"; import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
const people = [
{ name: "Wade Cooper" },
{ name: "Arlene Mccoy" },
{ name: "Devon Webb" },
{ name: "Tom Cook" },
{ name: "Tanya Fox" },
{ name: "Hellen Schmidt" },
];
export default function Recorder() { export default function Recorder() {
const [steam, setStream] = useState<null | MediaStream>(null); const [steam, setStream] = useState<null | MediaStream>(null);
const [blob, setBlob] = useState<null | Blob>(null); const [blob, setBlob] = useState<null | Blob>(null);
const refVideo = useRef<null | HTMLVideoElement>(null); const refVideo = useRef<null | HTMLVideoElement>(null);
const recorderRef = useRef<null | RecordRTC>(null); const recorderRef = useRef<null | RecordRTC>(null);
const [pause, setPause] = useState<boolean>(false); const [pause, setPause] = useState<boolean>(false);
const [selected, setSelected] = useState<{ name: string }>(people[0]); const [audioDevices, setAudioDevices] = useState<MediaDeviceInfo[]>([]);
const [selectedDevice, setSelectedDevice] = useState<MediaDeviceInfo | null>(
null
);
const handleRecording = async () => { const handleRecording = async () => {
const mediaStream = await navigator.mediaDevices.getDisplayMedia({ const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: { video: {
width: 1920, width: 1920,
height: 1080, height: 1080,
@ -34,6 +28,16 @@ export default function Recorder() {
}, },
}); });
const micStream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: selectedDevice?.deviceId },
});
const mediaStream = new MediaStream();
micStream.getAudioTracks().forEach((track) => mediaStream.addTrack(track));
screenStream
.getVideoTracks()
.forEach((track) => mediaStream.addTrack(track));
setStream(mediaStream); setStream(mediaStream);
recorderRef.current = new RecordRTC(mediaStream, { type: "video" }); recorderRef.current = new RecordRTC(mediaStream, { type: "video" });
recorderRef.current.startRecording(); recorderRef.current.startRecording();
@ -61,6 +65,23 @@ export default function Recorder() {
} }
}; };
useEffect(() => {
async function getAudioDevices() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevices = devices.filter(
(device) => device.kind === "audioinput"
);
setAudioDevices(audioDevices);
if (audioDevices[0]) setSelectedDevice(audioDevices[0]);
} catch (error) {
console.error(error);
}
}
void getAudioDevices();
}, []);
const handleSave = () => { const handleSave = () => {
if (blob) { if (blob) {
invokeSaveAsDialog(blob); invokeSaveAsDialog(blob);
@ -98,11 +119,13 @@ export default function Recorder() {
> >
save save
</button> </button>
<div className="fixed top-16 w-72"> <div className="top-16 mb-52 w-72">
<Listbox value={selected} onChange={setSelected}> <Listbox value={selectedDevice} onChange={setSelectedDevice}>
<div className="relative mt-1"> <div className="relative mt-1">
<Listbox.Button className="relative w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm"> <Listbox.Button className="relative w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm">
<span className="block truncate">{selected.name}</span> <span className="block truncate">
{selectedDevice?.label ?? "No device selected"}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon <ChevronUpDownIcon
className="h-5 w-5 text-gray-400" className="h-5 w-5 text-gray-400"
@ -117,9 +140,9 @@ export default function Recorder() {
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> <Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{people.map((person, personIdx) => ( {audioDevices.map((audioDevice, i) => (
<Listbox.Option <Listbox.Option
key={personIdx} key={i}
className={({ active }) => className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${ `relative cursor-default select-none py-2 pl-10 pr-4 ${
active active
@ -127,7 +150,7 @@ export default function Recorder() {
: "text-gray-900" : "text-gray-900"
}` }`
} }
value={person} value={audioDevice}
> >
{({ selected }) => ( {({ selected }) => (
<> <>
@ -136,7 +159,7 @@ export default function Recorder() {
selected ? "font-medium" : "font-normal" selected ? "font-medium" : "font-normal"
}`} }`}
> >
{person.name} {audioDevice.label}
</span> </span>
{selected ? ( {selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600"> <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">