const { useRef, useEffect, useState } = React; const getWebSocketURL = () => { const url = new URL(window.location.href); const hostname = url.hostname.replace('-web', '-moshi-web'); const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; return `${protocol}//${hostname}/ws`; } const App = () => { const [audioContext] = useState(() => new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 48000 })); const [text, setText] = useState(''); const socketRef = useRef(null); const decoderRef = useRef(null); const scheduledEndRef = useRef(null); const startRecording = async () => { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const rec = new Recorder({ encoderPath: "https://cdn.jsdelivr.net/npm/opus-recorder@latest/dist/encoderWorker.min.js", streamPages: true, encoderApplication: 2049, encoderFrameSize: 80, encoderSampleRate: 24000, maxFramesPerPage: 1, numberOfChannels: 1, }); rec.ondataavailable = async (data) => { if (socketRef.current?.readyState === WebSocket.OPEN) { await socketRef.current.send(data); } }; await rec.start(); }; useEffect(() => { const init = async () => { const decoder = new window['ogg-opus-decoder'].OggOpusDecoder(); await decoder.ready; decoderRef.current = decoder; }; init(); return () => decoderRef.current?.free(); }, []); const playAudio = (audioData)=> { const buffer = audioContext.createBuffer(1, audioData.length, audioContext.sampleRate); buffer.copyToChannel(audioData, 0); const source = audioContext.createBufferSource(); source.buffer = buffer; source.connect(audioContext.destination); const startTime = Math.max(scheduledEndRef.current, audioContext.currentTime); source.start(startTime); scheduledEndRef.current = startTime + buffer.duration; }; useEffect(() => { const socket = new WebSocket(getWebSocketURL()); socketRef.current = socket; socket.onopen = () => startRecording(); socket.onmessage = async (event) => { const buffer = await event.data.arrayBuffer(); const tag = new Uint8Array(buffer)[0]; const payload = buffer.slice(1); if (tag === 1) { const { channelData, samplesDecoded } = await decoderRef.current.decode(new Uint8Array(payload)); if (samplesDecoded > 0) playAudio(channelData[0]); } else if (tag === 2) { const newText = new TextDecoder().decode(payload); setText(prev => prev + newText); } }; return () => socket.close(); }, []); return (
{text || 'Connecting...'}