add microphone recording and allow user to select audio device
This commit is contained in:
parent
48c2702701
commit
44e78b16d9
1 changed files with 42 additions and 19 deletions
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue