Skip to content

Commit 9418424

Browse files
committed
manual record button
1 parent 8d8d326 commit 9418424

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed

next/components/assembly-view.tsx

+53-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { FragmentComponent, type Fragment } from "./fragment";
1111
import { useAssembly } from "./assembly-context";
1212
import { AudioProcessor } from "@/lib/audio-processor";
1313
import { RecordYouButton } from "./record-you-button";
14+
import { transcribeAudio } from "@/lib/assembly-ai";
1415

1516
const AssemblyView: () => JSX.Element = () => {
1617
const [inputText, setInputText] = useState("");
@@ -39,6 +40,9 @@ const AssemblyView: () => JSX.Element = () => {
3940
sendAudio,
4041
isIdleError,
4142
} = useAssembly();
43+
const [isUserRecording, setIsUserRecording] = useState(false);
44+
const userRecorderRef = useRef<MediaRecorder | null>(null);
45+
const userChunksRef = useRef<Blob[]>([]);
4246

4347
const scrollToBottom = () => {
4448
if (!scrollContainerRef.current) return;
@@ -264,6 +268,51 @@ const AssemblyView: () => JSX.Element = () => {
264268
// eslint-disable-next-line react-hooks/exhaustive-deps
265269
}, []);
266270

271+
useEffect(() => {
272+
if (!microphone?.stream) return;
273+
274+
if (isUserRecording) {
275+
// pause live transcription
276+
setIsPaused(true);
277+
278+
userRecorderRef.current = new MediaRecorder(microphone.stream);
279+
userChunksRef.current = [];
280+
userRecorderRef.current.ondataavailable = (e) => {
281+
if (e.data.size) userChunksRef.current.push(e.data);
282+
};
283+
userRecorderRef.current.onstop = async () => {
284+
const finalBlob = new Blob(userChunksRef.current, {
285+
type: "audio/webm",
286+
});
287+
try {
288+
// transcribe the user's new recording
289+
const result = await transcribeAudio(finalBlob);
290+
setFragments((prev) => [
291+
...prev,
292+
{
293+
id: crypto.randomUUID(),
294+
text: result.text,
295+
type: "speech",
296+
createdAt: Date.now(),
297+
},
298+
]);
299+
} catch (err) {
300+
console.debug("[AssemblyView] user-recording transcribe error");
301+
}
302+
};
303+
304+
userRecorderRef.current.start();
305+
} else {
306+
// resume live transcription
307+
setIsPaused(false);
308+
309+
if (userRecorderRef.current?.state === "recording") {
310+
userRecorderRef.current.stop();
311+
}
312+
userRecorderRef.current = null;
313+
}
314+
}, [isUserRecording, microphone?.stream]);
315+
267316
const handleSubmit = () => {
268317
if (!inputText.trim()) return;
269318
setFragments((prev) => [
@@ -326,7 +375,10 @@ const AssemblyView: () => JSX.Element = () => {
326375
<div className="flex-none px-4 pt-2 pb-4 border-t">
327376
<div className="max-w-2xl mx-auto">
328377
<div className="pb-2 flex gap-4 justify-between items-center">
329-
<RecordYouButton language={describeLanguage(outputLanguage)} />
378+
<RecordYouButton
379+
language={describeLanguage(outputLanguage)}
380+
onToggle={setIsUserRecording}
381+
/>
330382
<CloningView
331383
speechState={managerRef.current}
332384
audioBlob={audioBlob}

next/components/record-you-button.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ interface RecordYouButtonProps {
99
language?: string;
1010
}
1111

12-
export function RecordYouButton({ language }: RecordYouButtonProps) {
12+
export function RecordYouButton({ language, onToggle }: RecordYouButtonProps) {
1313
const [isRecording, setIsRecording] = useState(false);
1414

1515
const handleClick = () => {
1616
const newState = !isRecording;
1717
setIsRecording(newState);
18+
onToggle?.(newState);
1819
};
1920

2021
return (

0 commit comments

Comments
 (0)