2024-12-21 14:36:01 +01:00
|
|
|
import { useState, useRef, useCallback } from 'react';
|
|
|
|
|
import { WebRTCService } from '../services/WebRTC/WebRTCService';
|
2024-12-21 16:18:23 +01:00
|
|
|
import type { RTCMessage } from '~/common';
|
|
|
|
|
import useWebSocket from './useWebSocket';
|
2024-12-21 14:36:01 +01:00
|
|
|
|
|
|
|
|
const SILENCE_THRESHOLD = -50;
|
|
|
|
|
const SILENCE_DURATION = 1000;
|
|
|
|
|
|
|
|
|
|
const useCall = () => {
|
2024-12-30 01:47:21 +01:00
|
|
|
const { sendMessage: wsMessage, isConnected } = useWebSocket();
|
2024-12-21 14:36:01 +01:00
|
|
|
const [isCalling, setIsCalling] = useState(false);
|
2024-12-21 16:18:23 +01:00
|
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
2024-12-21 14:36:01 +01:00
|
|
|
const audioContextRef = useRef<AudioContext | null>(null);
|
|
|
|
|
const analyserRef = useRef<AnalyserNode | null>(null);
|
2024-12-21 16:18:23 +01:00
|
|
|
const audioChunksRef = useRef<Blob[]>([]);
|
2024-12-21 14:36:01 +01:00
|
|
|
const silenceStartRef = useRef<number | null>(null);
|
|
|
|
|
const intervalRef = useRef<number | null>(null);
|
|
|
|
|
const webrtcServiceRef = useRef<WebRTCService | null>(null);
|
|
|
|
|
|
2024-12-21 16:18:23 +01:00
|
|
|
const sendAudioChunk = useCallback(() => {
|
|
|
|
|
if (audioChunksRef.current.length === 0) {
|
2024-12-21 14:36:01 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-12-21 16:18:23 +01:00
|
|
|
|
|
|
|
|
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
|
|
|
|
|
webrtcServiceRef.current?.sendAudioChunk(audioBlob);
|
|
|
|
|
wsMessage({ type: 'processing-start' });
|
|
|
|
|
|
|
|
|
|
audioChunksRef.current = [];
|
|
|
|
|
setIsProcessing(true);
|
|
|
|
|
}, [wsMessage]);
|
|
|
|
|
|
|
|
|
|
const handleRTCMessage = useCallback((message: RTCMessage) => {
|
|
|
|
|
if (message.type === 'audio-received') {
|
|
|
|
|
setIsProcessing(true);
|
2024-12-21 14:36:01 +01:00
|
|
|
}
|
2024-12-21 16:18:23 +01:00
|
|
|
}, []);
|
2024-12-21 14:36:01 +01:00
|
|
|
|
|
|
|
|
const startCall = useCallback(async () => {
|
2024-12-30 01:47:21 +01:00
|
|
|
if (!isConnected) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-21 16:18:23 +01:00
|
|
|
webrtcServiceRef.current = new WebRTCService(handleRTCMessage);
|
2024-12-21 14:36:01 +01:00
|
|
|
await webrtcServiceRef.current.initializeCall();
|
|
|
|
|
|
2024-12-21 16:18:23 +01:00
|
|
|
wsMessage({ type: 'call-start' });
|
|
|
|
|
|
2024-12-21 14:36:01 +01:00
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
|
|
|
audioContextRef.current = new AudioContext();
|
|
|
|
|
const source = audioContextRef.current.createMediaStreamSource(stream);
|
|
|
|
|
analyserRef.current = audioContextRef.current.createAnalyser();
|
|
|
|
|
source.connect(analyserRef.current);
|
|
|
|
|
|
2024-12-21 16:18:23 +01:00
|
|
|
intervalRef.current = window.setInterval(() => {
|
|
|
|
|
if (!analyserRef.current || !isCalling) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = new Float32Array(analyserRef.current.frequencyBinCount);
|
|
|
|
|
analyserRef.current.getFloatFrequencyData(data);
|
|
|
|
|
const avg = data.reduce((a, b) => a + b) / data.length;
|
|
|
|
|
|
|
|
|
|
if (avg < SILENCE_THRESHOLD) {
|
|
|
|
|
if (silenceStartRef.current === null) {
|
|
|
|
|
silenceStartRef.current = Date.now();
|
|
|
|
|
} else if (Date.now() - silenceStartRef.current > SILENCE_DURATION) {
|
|
|
|
|
sendAudioChunk();
|
|
|
|
|
silenceStartRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
silenceStartRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
}, 100);
|
|
|
|
|
|
2024-12-21 14:36:01 +01:00
|
|
|
setIsCalling(true);
|
2024-12-30 01:47:21 +01:00
|
|
|
}, [handleRTCMessage, isConnected, wsMessage, sendAudioChunk, isCalling]);
|
2024-12-21 14:36:01 +01:00
|
|
|
|
|
|
|
|
const hangUp = useCallback(async () => {
|
|
|
|
|
if (intervalRef.current) {
|
|
|
|
|
clearInterval(intervalRef.current);
|
|
|
|
|
}
|
2024-12-21 16:18:23 +01:00
|
|
|
|
2024-12-21 14:36:01 +01:00
|
|
|
analyserRef.current = null;
|
|
|
|
|
audioContextRef.current?.close();
|
|
|
|
|
audioContextRef.current = null;
|
|
|
|
|
|
|
|
|
|
await webrtcServiceRef.current?.endCall();
|
|
|
|
|
webrtcServiceRef.current = null;
|
2024-12-21 16:18:23 +01:00
|
|
|
|
2024-12-21 14:36:01 +01:00
|
|
|
setIsCalling(false);
|
2024-12-21 16:18:23 +01:00
|
|
|
setIsProcessing(false);
|
|
|
|
|
wsMessage({ type: 'call-ended' });
|
|
|
|
|
}, [wsMessage]);
|
2024-12-21 14:36:01 +01:00
|
|
|
|
2024-12-21 16:18:23 +01:00
|
|
|
return {
|
|
|
|
|
isCalling,
|
|
|
|
|
isProcessing,
|
|
|
|
|
startCall,
|
|
|
|
|
hangUp,
|
|
|
|
|
};
|
2024-12-21 14:36:01 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default useCall;
|