"use client"; import { useEffect, useRef, useState } from "react"; import io from "socket.io-client"; import Peer from "simple-peer"; const socket = io("http://localhost:5001"); export default function VoiceCall() { const [stream, setStream] = useState(null); const [peer, setPeer] = useState(null); const [pitchShift, setPitchShift] = useState(1); const myAudio = useRef(null); const otherAudio = useRef(null); const audioContextRef = useRef(null); const sourceNodeRef = useRef(null); const pitchNodeRef = useRef(null); const applyPitchEffect = (audioStream: MediaStream) => { if (!audioContextRef.current) { audioContextRef.current = new AudioContext(); } const audioContext = audioContextRef.current; // Create source node sourceNodeRef.current = audioContext.createMediaStreamSource(audioStream); // Create ScriptProcessor for pitch shifting const bufferSize = 4096; pitchNodeRef.current = audioContext.createScriptProcessor(bufferSize, 1, 1); pitchNodeRef.current.onaudioprocess = ( audioProcessingEvent: AudioProcessingEvent ) => { const inputBuffer = audioProcessingEvent.inputBuffer; const outputBuffer = audioProcessingEvent.outputBuffer; for ( let channel = 0; channel < outputBuffer.numberOfChannels; channel++ ) { const inputData = inputBuffer.getChannelData(channel); const outputData = outputBuffer.getChannelData(channel); // Simple pitch shift by resampling for (let i = 0; i < outputBuffer.length; i++) { const index = Math.floor(i * pitchShift); outputData[i] = index < inputBuffer.length ? inputData[index] : 0; } } }; // Connect nodes sourceNodeRef.current.connect(pitchNodeRef.current); pitchNodeRef.current.connect(audioContext.destination); return new MediaStream([audioStream.getAudioTracks()[0]]); }; useEffect(() => { navigator.mediaDevices .getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true, sampleRate: 48000, channelCount: 2, }, }) .then((audioStream) => { const processedStream = applyPitchEffect(audioStream); setStream(processedStream); if (myAudio.current) { myAudio.current.srcObject = processedStream; } }) .catch((err) => console.error("Audio error:", err)); return () => { if (pitchNodeRef.current) { pitchNodeRef.current.disconnect(); } if (sourceNodeRef.current) { sourceNodeRef.current.disconnect(); } if (audioContextRef.current) { audioContextRef.current.close(); } }; }, []); const callUser = () => { if (!stream) return; const peer = new Peer({ initiator: true, trickle: false, stream, config: { iceServers: [ { urls: "stun:stun.l.google.com:19302" }, { urls: "stun:global.stun.twilio.com:3478" }, ], }, }); peer.on("signal", (data) => socket.emit("offer", data)); peer.on("stream", (userStream) => { console.log("Received voice stream from peer"); if (otherAudio.current) { otherAudio.current.srcObject = userStream; console.log("Successfully set remote audio stream"); } }); socket.on("answer", (answer) => { console.log("Received answer from remote peer"); peer.signal(answer); }); socket.on("candidate", (candidate) => { console.log("Received ICE candidate"); peer.signal(candidate); }); setPeer(peer); }; const receiveCall = () => { if (!stream) return; const peer = new Peer({ initiator: false, trickle: false, stream, config: { iceServers: [ { urls: "stun:stun.l.google.com:19302" }, { urls: "stun:global.stun.twilio.com:3478" }, ], }, }); socket.on("offer", (offer) => { console.log("Received call offer"); peer.signal(offer); }); peer.on("signal", (data) => { console.log("Generated answer"); socket.emit("answer", data); }); peer.on("stream", (userStream) => { console.log("Received voice stream from caller"); if (otherAudio.current) { otherAudio.current.srcObject = userStream; console.log("Successfully set remote audio stream"); } }); setPeer(peer); }; return (
); }