React AudioRecorder - speech to text with Whisper API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useState, useRef } from "react"; | |
| import { reportError, getErrorMessage } from "~/utils/error"; | |
| import axios from "axios"; | |
| import { env } from "~/env.mjs"; | |
| type RecordingStatus = "inactive" | "recording" | "paused"; | |
| const mimeType = "audio/mp3"; | |
| const fileName = "recording.mp3"; | |
| const AudioRecorder = () => { | |
| const [permission, setPermission] = useState(false); | |
| const [stream, setStream] = useState<MediaStream | null>(null); | |
| const mediaRecorder = useRef<MediaRecorder | null>(null); | |
| const [recordingStatus, setRecordingStatus] = | |
| useState<RecordingStatus>("inactive"); | |
| const [audioChunks, setAudioChunks] = useState<Blob[]>([]); | |
| const [audio, setAudio] = useState<string>(""); | |
| const [audioBlob, setAudioBlob] = useState<Blob>(); | |
| const [audioFile, setAudioFile] = useState<File>(); | |
| const [text, setText] = useState<string>(""); | |
| const handelSendFile = async () => { | |
| try { | |
| const response = await axios({ | |
| method: "post", | |
| url: "https://api.openai.com/v1/audio/transcriptions", | |
| data: { file: audioFile, model: "whisper-1", language: "PL" }, | |
| headers: { | |
| "Content-Type": "multipart/form-data", | |
| Authorization: `Bearer ${env.NEXT_PUBLIC_WHISPER_KEY}`, | |
| }, | |
| }); | |
| setText(response.data.text); | |
| } catch (error) { | |
| console.log(error.response.data); | |
| } | |
| }; | |
| const getMicrophonePermission = async () => { | |
| if ("MediaRecorder" in window) { | |
| try { | |
| const streamData = await navigator.mediaDevices.getUserMedia({ | |
| audio: true, | |
| video: false, | |
| }); | |
| setPermission(true); | |
| setStream(streamData); | |
| } catch (error) { | |
| reportError({ message: getErrorMessage(error) }); | |
| } | |
| } else { | |
| alert("The MediaRecorder API is not supported in your browser."); | |
| } | |
| }; | |
| const startRecording = () => { | |
| setRecordingStatus("recording"); | |
| //create new Media recorder instance using the stream | |
| if (!stream) return; | |
| const media = new MediaRecorder(stream, { | |
| type: mimeType, | |
| } as MediaRecorderOptions); | |
| //set the MediaRecorder instance to the mediaRecorder ref | |
| mediaRecorder.current = media; | |
| //invokes the start method to start the recording process | |
| mediaRecorder.current.start(); | |
| const localAudioChunks = [] as Blob[]; | |
| mediaRecorder.current.ondataavailable = (event) => { | |
| if (typeof event.data === "undefined") return; | |
| if (event.data.size === 0) return; | |
| localAudioChunks.push(event.data); | |
| }; | |
| setAudioChunks(localAudioChunks); | |
| }; | |
| const stopRecording = () => { | |
| setRecordingStatus("inactive"); | |
| //stops the recording instance | |
| if (!mediaRecorder.current) return; | |
| mediaRecorder.current.stop(); | |
| mediaRecorder.current.onstop = () => { | |
| //creates a blob file from the audiochunks data | |
| const audioBlob = new Blob(audioChunks, { type: mimeType }); | |
| //creates a playable URL from the blob file. | |
| const audioUrl = URL.createObjectURL(audioBlob); | |
| setAudio(audioUrl); | |
| setAudioBlob(audioBlob); | |
| setAudioChunks([]); | |
| const file = new File([audioBlob], fileName, { type: mimeType }); | |
| setAudioFile(file); | |
| }; | |
| }; | |
| return ( | |
| <div className=" container mx-auto flex w-full flex-col"> | |
| <h2>Audio Recorder</h2> | |
| <main> | |
| <div> | |
| {!permission ? ( | |
| <button | |
| onClick={() => void getMicrophonePermission()} | |
| type="button" | |
| > | |
| Get Microphone | |
| </button> | |
| ) : null} | |
| {permission && recordingStatus === "inactive" ? ( | |
| <button onClick={startRecording} type="button"> | |
| Start Recording | |
| </button> | |
| ) : null} | |
| {recordingStatus === "recording" ? ( | |
| <button onClick={stopRecording} type="button"> | |
| Stop Recording | |
| </button> | |
| ) : null} | |
| </div> | |
| {audio ? ( | |
| <div className="audio-container"> | |
| <audio src={audio} controls></audio> | |
| <a download href={audio}> | |
| Download Recording | |
| </a> | |
| </div> | |
| ) : null} | |
| <button onClick={handelSendFile}>Send File</button> | |
| </main> | |
| {text ? <p className=" mt-4 text-lg">{text}</p> : null} | |
| </div> | |
| ); | |
| }; | |
| export default AudioRecorder; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment