|
"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<MediaStream | null>(null); |
|
const [peer, setPeer] = useState<Peer.Instance | null>(null); |
|
const [pitchShift, setPitchShift] = useState<number>(1); |
|
const myAudio = useRef<HTMLAudioElement>(null); |
|
const otherAudio = useRef<HTMLAudioElement>(null); |
|
const audioContextRef = useRef<AudioContext | null>(null); |
|
const sourceNodeRef = useRef<MediaStreamAudioSourceNode | null>(null); |
|
const pitchNodeRef = useRef<any>(null); |
|
|
|
const applyPitchEffect = (audioStream: MediaStream) => { |
|
if (!audioContextRef.current) { |
|
audioContextRef.current = new AudioContext(); |
|
} |
|
const audioContext = audioContextRef.current; |
|
|
|
|
|
sourceNodeRef.current = audioContext.createMediaStreamSource(audioStream); |
|
|
|
|
|
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); |
|
|
|
|
|
for (let i = 0; i < outputBuffer.length; i++) { |
|
const index = Math.floor(i * pitchShift); |
|
outputData[i] = index < inputBuffer.length ? inputData[index] : 0; |
|
} |
|
} |
|
}; |
|
|
|
|
|
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 ( |
|
<div> |
|
<audio ref={myAudio} autoPlay muted /> |
|
<audio ref={otherAudio} autoPlay /> |
|
<button onClick={callUser}>Call</button> |
|
<button onClick={receiveCall}>Receive Call</button> |
|
<div> |
|
<label> |
|
Pitch Shift: |
|
<input |
|
type="range" |
|
min="0.5" |
|
max="2" |
|
step="0.1" |
|
value={pitchShift} |
|
onChange={(e) => setPitchShift(Number(e.target.value))} |
|
/> |
|
</label> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|