diff --git a/apps/demo-nextjs-app-router/app/whisper/realtime/page.tsx b/apps/demo-nextjs-app-router/app/whisper/realtime/page.tsx
deleted file mode 100644
index 3bffa3c..0000000
--- a/apps/demo-nextjs-app-router/app/whisper/realtime/page.tsx
+++ /dev/null
@@ -1,299 +0,0 @@
-'use client';
-
-import * as fal from '@fal-ai/serverless-client';
-import { useCallback, useMemo, useRef, useState } from 'react'; // Add useRef here
-
-fal.config({
- // credentials: 'FAL_KEY_ID:FAL_KEY_SECRET',
- proxyUrl: '/api/fal/proxy',
-});
-
-type ErrorProps = {
- error: any;
-};
-
-function Error(props: ErrorProps) {
- if (!props.error) {
- return null;
- }
- return (
-
- Error {props.error.message}
-
- );
-}
-
-type RecorderOptions = {
- maxDuration?: number;
- onChunk?: (chunk: Blob) => void;
- sendInterval?: number; // Add this line
-};
-
-function useMediaRecorder({
- maxDuration = 20000,
- onChunk,
- sendInterval = 1000, // Add this line
-}: RecorderOptions = {}) {
- const [isRecording, setIsRecording] = useState(false);
- const [mediaRecorder, setMediaRecorder] = useState(
- null
- );
- const accumulatedChunks = useRef([]); // Use a ref to accumulate chunks
-
- const sendAccumulatedData = useCallback(() => {
- // Convert accumulated chunks to a single blob
- const audioBlob = new Blob(accumulatedChunks.current, {
- type: 'audio/wav',
- });
- // Optionally, here you can slice the Blob if you want to send data in smaller pieces
- // For example: audioBlob.slice(startByte, endByte)
- if (onChunk) {
- onChunk(audioBlob);
- }
- }, [onChunk]);
-
- const record = useCallback((): Promise => {
- setIsRecording(true);
- accumulatedChunks.current = []; // Reset accumulated chunks
- return new Promise(async (resolve, reject) => {
- // Explicitly type the Promise here
- try {
- const stream = await navigator.mediaDevices.getUserMedia({
- audio: true,
- });
- const recorder = new MediaRecorder(stream);
- setMediaRecorder(recorder);
-
- recorder.addEventListener('dataavailable', (event) => {
- accumulatedChunks.current.push(event.data);
- });
-
- recorder.addEventListener('stop', () => {
- const audioBlob = new Blob(accumulatedChunks.current, {
- type: 'audio/wav',
- });
- const audioFile = new File(
- [audioBlob],
- `recording_${Date.now()}.wav`,
- { type: 'audio/wav' }
- );
-
- sendAccumulatedData(); // Ensure final data is sent
- setIsRecording(false);
- resolve(audioFile); // Resolve the promise with the audio file
- });
-
- recorder.start(1000); // Configure how often you get 'dataavailable' events
-
- // Periodically send accumulated data
- const intervalId = setInterval(() => {
- sendAccumulatedData();
- }, sendInterval);
-
- setTimeout(() => {
- clearInterval(intervalId); // Stop the interval when recording stops
- recorder.stop();
- recorder.stream.getTracks().forEach((track) => track.stop());
- }, maxDuration);
- } catch (error) {
- reject(error);
- }
- });
- }, [maxDuration, sendAccumulatedData, sendInterval]);
-
- // Stop recording logic remains the same
- const stopRecording = useCallback(() => {
- setIsRecording(false);
- mediaRecorder?.stop();
- mediaRecorder?.stream.getTracks().forEach((track) => track.stop());
- }, [mediaRecorder]);
-
- return { record, stopRecording, isRecording };
-}
-
-interface RealTimeOutput {
- // Define the structure of your real-time output
- // Example:
- message: string;
-}
-
-export default function WhisperDemo() {
- const [realTimeOutputs, setRealTimeOutputs] = useState([]);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const [logs, setLogs] = useState([]);
- const [audioFile, setAudioFile] = useState(null);
- const [result, setResult] = useState(null); // eslint-disable-line @typescript-eslint/no-explicit-any
- const [elapsedTime, setElapsedTime] = useState(0);
- const { send } = fal.realtime.connect('20128202/rtw', {
- connectionKey: 'realtime-demo',
- throttleInterval: 128,
- onResult(result) {
- console.log('result', result);
- handleNewOutput({ message: result.output });
- },
- });
- const accumulatedChunksRef = useRef(new Uint8Array()); // To store accumulated chunks
-
- const { record, stopRecording, isRecording } = useMediaRecorder({
- onChunk: async (chunk) => {
- const buffer = await chunk.arrayBuffer();
- const newChunk = new Uint8Array(buffer);
-
- // Accumulate chunks
- const accumulatedChunks = new Uint8Array(
- accumulatedChunksRef.current.length + newChunk.length
- );
- accumulatedChunks.set(accumulatedChunksRef.current, 0);
- accumulatedChunks.set(newChunk, accumulatedChunksRef.current.length);
- accumulatedChunksRef.current = accumulatedChunks;
-
- console.log('Accumulated chunk', accumulatedChunks);
- // Send the accumulated chunks using your `send` method
- send({ content: accumulatedChunks });
- },
- });
-
- const handleNewOutput = (newOutput: RealTimeOutput) => {
- // Use `any` if you don't have a specific type
- // This could be a transcription chunk, analysis result, etc.
- setRealTimeOutputs((prevOutputs) => [...prevOutputs, newOutput]);
- };
-
- const reset = () => {
- setLoading(false);
- setError(null);
- setLogs([]);
- setElapsedTime(0);
- setResult(null);
- };
-
- const audioFileLocalUrl = useMemo(() => {
- if (!audioFile) {
- return null;
- }
- return URL.createObjectURL(audioFile);
- }, [audioFile]);
-
- const transcribeAudio = async (audioFile: File) => {
- reset();
- setLoading(true);
- const start = Date.now();
- try {
- const result = await fal.subscribe('fal-ai/whisper', {
- input: {
- file_name: 'recording.wav',
- audio_url: audioFile,
- },
- pollInterval: 1000,
- logs: true,
- onQueueUpdate(update) {
- setElapsedTime(Date.now() - start);
- if (
- update.status === 'IN_PROGRESS' ||
- update.status === 'COMPLETED'
- ) {
- setLogs((update.logs || []).map((log) => log.message));
- }
- },
- });
- setResult(result);
- } catch (error: any) {
- setError(error);
- } finally {
- setLoading(false);
- setElapsedTime(Date.now() - start);
- }
- };
- return (
-
-
-
- Hello fal
and{' '}
- whisper
-
-
-
-
-
-
-
- {audioFileLocalUrl && (
-
- )}
-
-
-
- {/* Real-Time Outputs Section */}
-
-
Real-Time Outputs
-
- {realTimeOutputs.map((output, index) => (
-
{output.message}
// Ensure 'message' is the correct property
- ))}
-
-
-
- {/* JSON Result Section */}
-
-
-
JSON Result
-
- {`Elapsed Time (seconds): ${(elapsedTime / 1000).toFixed(2)}`}
-
-
- {result
- ? JSON.stringify(result, null, 2)
- : '// result pending...'}
-
-
-
-
-
Logs
-
- {logs.filter(Boolean).join('\n')}
-
-
-
-
-
- );
-}