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')}
-            
-
-
-
-
- ); -}