Событийные звуки звонка (сбросить, мутинг, и прочее...)

This commit is contained in:
RoyceDa
2026-03-18 18:27:39 +02:00
parent 7b9936dcc4
commit 88288317ab
10 changed files with 153 additions and 40 deletions

View File

@@ -10,7 +10,8 @@ import { PacketWebRTC, WebRTCSignalType } from "../ProtocolProvider/protocol/pac
import { PacketIceServers } from "../ProtocolProvider/protocol/packets/packet.ice.servers";
import { modals } from "@mantine/modals";
import { Button, Flex, Text } from "@mantine/core";
import { useSound } from "@/app/hooks/useSound";
import useWindow from "@/app/hooks/useWindow";
export interface CallContextValue {
call: (callable: string) => void;
@@ -59,8 +60,9 @@ export interface CallProviderProps {
export function CallProvider(props : CallProviderProps) {
const [activeCall, setActiveCall] = useState<string>("");
const [callState, setCallState] = useState<CallState>(CallState.ENDED);
const [muted, setMuted] = useState<boolean>(false);
const [sound, setSound] = useState<boolean>(true);
const [muted, setMutedState] = useState<boolean>(false);
const [sound, setSoundState] = useState<boolean>(true);
const durationIntervalRef = useRef<NodeJS.Timeout | null>(null);
const [duration, setDuration] = useState<number>(0);
const [showCallView, setShowCallView] = useState<boolean>(callState == CallState.INCOMING);
const {info} = useConsoleLogger("CallProvider");
@@ -73,8 +75,26 @@ export function CallProvider(props : CallProviderProps) {
const roleRef = useRef<CallRole | null>(null);
const [sharedSecret, setSharedSecret] = useState<string>("");
const iceServersRef = useRef<RTCIceServer[]>([]);
const remoteAudioRef = useRef<HTMLAudioElement>(null);
const remoteAudioRef = useRef<HTMLAudioElement | null>(null);
const iceCandidatesBufferRef = useRef<RTCIceCandidate[]>([]);
const mutedRef = useRef<boolean>(false);
const soundRef = useRef<boolean>(true);
const {playSound, stopSound, stopLoopSound} = useSound();
const {setWindowPriority} = useWindow();
useEffect(() => {
if(callState == CallState.ACTIVE){
stopLoopSound();
stopSound();
playSound("connected.mp3");
setWindowPriority(false);
durationIntervalRef.current = setInterval(() => {
setDuration(prev => prev + 1);
}, 1000);
}
}, [callState]);
useEffect(() => {
/**
@@ -83,6 +103,16 @@ export function CallProvider(props : CallProviderProps) {
*/
let packet = new PacketIceServers();
send(packet);
return () => {
stopSound();
if (remoteAudioRef.current) {
remoteAudioRef.current.pause();
remoteAudioRef.current.srcObject = null;
}
peerConnectionRef.current?.close();
peerConnectionRef.current = null;
};
}, []);
usePacket(28, async (packet: PacketIceServers) => {
@@ -168,43 +198,11 @@ export function CallProvider(props : CallProviderProps) {
usePacket(26, async (packet: PacketSignalPeer) => {
const signalType = packet.getSignalType();
if(signalType == SignalType.END_CALL_BECAUSE_BUSY) {
modals.open({
title: 'Busy',
centered: true,
children: (
<>
<Text size="sm">
Line is busy, the user is currently on another call. Please try again later.
</Text>
<Flex align={'center'} justify={'flex-end'}>
<Button color={'red'} variant={'subtle'} onClick={() => modals.closeAll()} mt="md">
Close
</Button>
</Flex>
</>
),
withCloseButton: false
});
openCallsModal("Line is busy, the user is currently on another call. Please try again later.");
end();
}
if(signalType == SignalType.END_CALL_BECAUSE_PEER_DISCONNECTED) {
modals.open({
title: 'Connection lost',
centered: true,
children: (
<>
<Text size="sm">
The connection with the user was lost. The call has ended.
</Text>
<Flex align={'center'} justify={'flex-end'}>
<Button color={'red'} variant={'subtle'} onClick={() => modals.closeAll()} mt="md">
Close
</Button>
</Flex>
</>
),
withCloseButton: false
});
openCallsModal("The connection with the user was lost. The call has ended.")
end();
}
if(activeCall){
@@ -228,6 +226,8 @@ export function CallProvider(props : CallProviderProps) {
/**
* Нам поступает звонок
*/
setWindowPriority(true);
playSound("ringtone.mp3", true);
setActiveCall(packet.getSrc());
setCallState(CallState.INCOMING);
setShowCallView(true);
@@ -325,10 +325,13 @@ export function CallProvider(props : CallProviderProps) {
/**
* При получении медиа-трека с другой стороны
*/
console.info("TRACK RECV!!!!!");
if(remoteAudioRef.current){
if(remoteAudioRef.current && event.streams[0]){
console.info(event.streams);
remoteAudioRef.current.srcObject = event.streams[0];
remoteAudioRef.current.muted = !soundRef.current;
void remoteAudioRef.current.play().catch((e) => {
console.error("Failed to play remote audio:", e);
});
}
}
@@ -355,6 +358,25 @@ export function CallProvider(props : CallProviderProps) {
}
}, [activeCall, sessionKeys]);
const openCallsModal = (text : string) => {
modals.open({
centered: true,
children: (
<>
<Text size="sm">
{text}
</Text>
<Flex align={'center'} justify={'flex-end'}>
<Button color={'red'} variant={'subtle'} onClick={() => modals.closeAll()} mt="md">
Close
</Button>
</Flex>
</>
),
withCloseButton: false
});
}
const generateSessionKeys = () => {
const sessionKeys = nacl.box.keyPair();
info("Generated keys for call session, len: " + sessionKeys.publicKey.length);
@@ -363,6 +385,14 @@ export function CallProvider(props : CallProviderProps) {
}
const call = (dialog: string) => {
if(callState == CallState.ACTIVE
|| callState == CallState.CONNECTING
|| callState == CallState.KEY_EXCHANGE
|| callState == CallState.WEB_RTC_EXCHANGE){
openCallsModal("You are already on a call, please end the current call before starting a new one.");
return;
}
setWindowPriority(false);
setActiveCall(dialog);
setCallState(CallState.CONNECTING);
setShowCallView(true);
@@ -372,6 +402,7 @@ export function CallProvider(props : CallProviderProps) {
signalPacket.setSignalType(SignalType.CALL);
send(signalPacket);
roleRef.current = CallRole.CALLER;
playSound("calling.mp3", true);
}
const close = () => {
@@ -384,14 +415,28 @@ export function CallProvider(props : CallProviderProps) {
}
const end = () => {
stopLoopSound();
stopSound();
if (remoteAudioRef.current) {
remoteAudioRef.current.pause();
remoteAudioRef.current.srcObject = null;
}
setDuration(0);
durationIntervalRef.current && clearInterval(durationIntervalRef.current);
setWindowPriority(false);
playSound("end_call.mp3");
peerConnectionRef.current?.close();
peerConnectionRef.current = null;
roomIdRef.current = "";
mutedRef.current = false;
soundRef.current = true;
setActiveCall("");
setCallState(CallState.ENDED);
setShowCallView(false);
setSessionKeys(null);
setDuration(0);
setMutedState(false);
setSoundState(true);
roleRef.current = null;
}
@@ -402,6 +447,9 @@ export function CallProvider(props : CallProviderProps) {
*/
return;
}
setWindowPriority(false);
stopLoopSound();
stopSound();
/**
* Звонок принят, генерируем ключи для сессии и отправляем их другой стороне для установления защищенного канала связи
*/
@@ -428,6 +476,46 @@ export function CallProvider(props : CallProviderProps) {
return sharedSecret;
}
const setMuted = (nextMuted: boolean) => {
if (mutedRef.current === nextMuted) {
return;
}
mutedRef.current = nextMuted;
playSound(nextMuted ? "micro_enable.mp3" : "micro_disable.mp3");
if(peerConnectionRef.current){
peerConnectionRef.current.getSenders().forEach(sender => {
if(sender.track?.kind == "audio"){
sender.track.enabled = !nextMuted;
}
});
}
setMutedState(nextMuted);
}
const setSound = (nextSound: boolean) => {
if (soundRef.current === nextSound) {
return;
}
soundRef.current = nextSound;
playSound(nextSound ? "sound_enable.mp3" : "sound_disable.mp3");
if(remoteAudioRef.current){
remoteAudioRef.current.muted = !nextSound;
if (nextSound) {
void remoteAudioRef.current.play().catch((e) => {
console.error("Failed to resume remote audio:", e);
});
}
}
setSoundState(nextSound);
}
const context = {
call,
close,