zanovaw
This commit is contained in:
parent
6027da6a92
commit
8a9b66b1fd
@ -2,99 +2,120 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import './ExploreContainer.css';
|
import './ExploreContainer.css';
|
||||||
import InputContainer from './Input';
|
import InputContainer from './Input';
|
||||||
import {
|
import {
|
||||||
IonButton, IonContent, IonFooter, IonToolbar, IonText, IonModal, IonIcon
|
IonButton, IonContent, IonText, IonModal, IonIcon
|
||||||
} from '@ionic/react';
|
} from '@ionic/react';
|
||||||
import { call as callIcon, close as hangUpIcon } from 'ionicons/icons';
|
import { call as callIcon, close as hangUpIcon } from 'ionicons/icons';
|
||||||
import Peer, { MediaConnection } from 'peerjs';
|
|
||||||
|
|
||||||
const ExploreContainer: React.FC = () => {
|
const ExploreContainer: React.FC = () => {
|
||||||
const [callString, setCallString] = useState("");
|
const [callString, setCallString] = useState(""); // Здесь будет храниться IP-адрес получателя звонка
|
||||||
const [isEnable, setIsEnable] = useState(true);
|
const [isEnable, setIsEnable] = useState(true);
|
||||||
const [peer, setPeer] = useState<Peer | null>(null);
|
|
||||||
const [localStream, setLocalStream] = useState<MediaStream | null>(null);
|
const [localStream, setLocalStream] = useState<MediaStream | null>(null);
|
||||||
const [connectionInfo, setConnectionInfo] = useState<string | null>(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isCallActive, setIsCallActive] = useState(false);
|
const [isCallActive, setIsCallActive] = useState(false);
|
||||||
const [callDuration, setCallDuration] = useState<number>(0);
|
const [callDuration, setCallDuration] = useState<number>(0);
|
||||||
const [callInterval, setCallInterval] = useState<NodeJS.Timeout | null>(null);
|
const [callInterval, setCallInterval] = useState<NodeJS.Timeout | null>(null);
|
||||||
const [currentCallId, setCurrentCallId] = useState<string | null>(null);
|
const [peerConnection, setPeerConnection] = useState<RTCPeerConnection | null>(null);
|
||||||
|
const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null);
|
||||||
|
|
||||||
|
// Создаем RTCPeerConnection без использования ICE-серверов
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializePeer = () => {
|
const pc = new RTCPeerConnection();
|
||||||
const newPeer = new Peer({ debug: 3 });
|
setPeerConnection(pc);
|
||||||
setPeer(newPeer);
|
|
||||||
|
|
||||||
newPeer.on('open', (id) => {
|
// Получаем удаленные медиа-потоки
|
||||||
console.log(`Peer успешно открыт с ID: ${id}`);
|
pc.ontrack = (event) => {
|
||||||
setConnectionInfo(id);
|
const [stream] = event.streams;
|
||||||
});
|
setRemoteStream(stream);
|
||||||
|
playAudio(stream);
|
||||||
newPeer.on('call', (call: MediaConnection) => handleIncomingCall(call));
|
|
||||||
newPeer.on('error', (err) => console.error('Ошибка Peer:', err));
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
newPeer.destroy();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanup = initializePeer();
|
pc.onicecandidate = (event) => {
|
||||||
return cleanup;
|
if (event.candidate) {
|
||||||
|
console.log('ICE Candidate:', event.candidate);
|
||||||
|
// В локальной сети с фиксированными IP это обычно не требуется, но можно отправить кандидата вручную
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
pc.close();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleIncomingCall = useCallback((call: MediaConnection) => {
|
// Запуск вызова (инициатор соединения)
|
||||||
console.log('Получен входящий звонок');
|
const makeCall = useCallback(async () => {
|
||||||
setCurrentCallId(call.peer);
|
if (peerConnection && callString) {
|
||||||
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
|
||||||
setLocalStream(stream);
|
|
||||||
call.answer(stream);
|
|
||||||
call.on('stream', (remoteStream: MediaStream) => {
|
|
||||||
console.log('Получен удалённый поток');
|
|
||||||
playAudio(remoteStream);
|
|
||||||
startCallTimer();
|
|
||||||
setIsCallActive(true);
|
|
||||||
});
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error('Не удалось получить локальный поток для ответа', err);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const makeCall = useCallback(() => {
|
|
||||||
if (peer && callString) {
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
console.log(`Попытка звонка на: ${callString}`);
|
try {
|
||||||
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
setLocalStream(stream);
|
setLocalStream(stream);
|
||||||
const call = peer.call(callString, stream);
|
|
||||||
setCurrentCallId(callString);
|
// Добавляем треки к соединению
|
||||||
call.on('stream', (remoteStream: MediaStream) => {
|
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
|
||||||
console.log('Получен удалённый поток во время вызова');
|
|
||||||
playAudio(remoteStream);
|
// Создаем SDP предложение для вызова
|
||||||
setIsLoading(false);
|
const offer = await peerConnection.createOffer();
|
||||||
startCallTimer();
|
await peerConnection.setLocalDescription(offer);
|
||||||
setIsCallActive(true);
|
|
||||||
});
|
// Отправляем SDP предложение получателю (на callString, можно использовать HTTP запрос)
|
||||||
}).catch((err) => {
|
console.log('SDP Offer:', offer);
|
||||||
|
// Здесь можно использовать WebSocket или другой способ обмена SDP в локальной сети
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsCallActive(true);
|
||||||
|
startCallTimer();
|
||||||
|
} catch (err) {
|
||||||
console.error('Не удалось получить локальный поток', err);
|
console.error('Не удалось получить локальный поток', err);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Звонок невозможен: отсутствует Peer или callString');
|
console.warn('Звонок невозможен: отсутствует соединение или IP-адрес');
|
||||||
}
|
}
|
||||||
}, [peer, callString]);
|
}, [peerConnection, callString]);
|
||||||
|
|
||||||
|
// Принятие входящего звонка (ответ)
|
||||||
|
const handleIncomingCall = useCallback(async (offer: RTCSessionDescriptionInit) => {
|
||||||
|
if (peerConnection) {
|
||||||
|
try {
|
||||||
|
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
setLocalStream(stream);
|
||||||
|
|
||||||
|
// Добавляем треки к соединению
|
||||||
|
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
|
||||||
|
|
||||||
|
// Создаем SDP ответ
|
||||||
|
const answer = await peerConnection.createAnswer();
|
||||||
|
await peerConnection.setLocalDescription(answer);
|
||||||
|
|
||||||
|
// Отправляем SDP ответ инициатору
|
||||||
|
console.log('SDP Answer:', answer);
|
||||||
|
// Также отправляем SDP ответ инициатору через HTTP/WebSocket
|
||||||
|
|
||||||
|
setIsCallActive(true);
|
||||||
|
startCallTimer();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Ошибка обработки входящего вызова', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [peerConnection]);
|
||||||
|
|
||||||
|
// Завершение звонка
|
||||||
const endCall = useCallback(() => {
|
const endCall = useCallback(() => {
|
||||||
if (localStream) {
|
if (localStream) {
|
||||||
localStream.getTracks().forEach(track => track.stop());
|
localStream.getTracks().forEach(track => track.stop());
|
||||||
}
|
}
|
||||||
|
if (peerConnection) {
|
||||||
|
peerConnection.close();
|
||||||
|
}
|
||||||
setIsCallActive(false);
|
setIsCallActive(false);
|
||||||
setIsEnable(true);
|
setIsEnable(true);
|
||||||
if (callInterval) {
|
if (callInterval) {
|
||||||
clearInterval(callInterval);
|
clearInterval(callInterval);
|
||||||
}
|
}
|
||||||
setCallDuration(0);
|
setCallDuration(0);
|
||||||
setCurrentCallId(null);
|
}, [localStream, peerConnection, callInterval]);
|
||||||
}, [localStream, callInterval]);
|
|
||||||
|
|
||||||
|
// Таймер звонка
|
||||||
const startCallTimer = useCallback(() => {
|
const startCallTimer = useCallback(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setCallDuration(prevDuration => prevDuration + 1);
|
setCallDuration(prevDuration => prevDuration + 1);
|
||||||
@ -102,6 +123,7 @@ const ExploreContainer: React.FC = () => {
|
|||||||
setCallInterval(interval);
|
setCallInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Воспроизведение аудио потока
|
||||||
const playAudio = useCallback((stream: MediaStream) => {
|
const playAudio = useCallback((stream: MediaStream) => {
|
||||||
const audioElement = document.createElement('audio');
|
const audioElement = document.createElement('audio');
|
||||||
audioElement.srcObject = stream;
|
audioElement.srcObject = stream;
|
||||||
@ -112,57 +134,34 @@ const ExploreContainer: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const copyPeerId = useCallback(() => {
|
|
||||||
if (connectionInfo) {
|
|
||||||
navigator.clipboard.writeText(connectionInfo)
|
|
||||||
.then(() => {
|
|
||||||
console.log('Peer ID скопирован в буфер обмена');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Не удалось скопировать Peer ID', err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [connectionInfo]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<IonContent fullscreen={true} className="ion-padding">
|
||||||
<IonContent fullscreen={true} className="ion-padding">
|
<div id="container">
|
||||||
<div id="container">
|
<div className="input-container">
|
||||||
<div className="peer-id-container">
|
<InputContainer
|
||||||
<IonText className="peer-id-text">Ваш Peer ID: {connectionInfo}</IonText>
|
callString={callString}
|
||||||
<button className="copy-button" onClick={copyPeerId}>Копировать</button>
|
setCallString={setCallString}
|
||||||
</div>
|
isEnable={isEnable}
|
||||||
<div className="input-container">
|
/>
|
||||||
<InputContainer
|
<IonButton className="call-button" onClick={makeCall} disabled={!isEnable || !callString || isLoading}>
|
||||||
callString={callString}
|
<IonIcon icon={callIcon} />
|
||||||
setCallString={setCallString}
|
</IonButton>
|
||||||
isEnable={isEnable}
|
</div>
|
||||||
/>
|
</div>
|
||||||
<IonButton className="call-button" onClick={makeCall} disabled={!isEnable || !callString || isLoading}>
|
|
||||||
<IonIcon icon={callIcon} />
|
<IonModal isOpen={isCallActive}>
|
||||||
|
<IonContent className="ion-padding">
|
||||||
|
<div className="call-info-container">
|
||||||
|
<IonText className="call-info-text">Звонок на IP: {callString}</IonText>
|
||||||
|
<IonText className="call-duration-text">{Math.floor(callDuration / 60)}:{callDuration % 60}</IonText>
|
||||||
|
<IonButton onClick={endCall}>
|
||||||
|
<IonIcon icon={hangUpIcon} />
|
||||||
</IonButton>
|
</IonButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</IonContent>
|
||||||
|
</IonModal>
|
||||||
{/* Модальное окно для звонка */}
|
</IonContent>
|
||||||
<IonModal isOpen={isCallActive}>
|
|
||||||
<IonContent className="ion-padding">
|
|
||||||
<div className="call-info-container">
|
|
||||||
{/* Добавляем гифку */}
|
|
||||||
<img src="/monkey-monkey-with-drone.gif" alt="Monkey with Drone" className="drone-gif" />
|
|
||||||
<IonText className="call-info-text">Peer ID: {currentCallId}</IonText>
|
|
||||||
<IonText className="call-duration-text">{Math.floor(callDuration / 60)}:{callDuration % 60}</IonText>
|
|
||||||
<button onClick={endCall} className="custom-hang-up-button">
|
|
||||||
<svg width="51" height="51" fill="#cd1818" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M18.327 22.5c-.915 0-2.2-.331-4.125-1.407-2.34-1.312-4.15-2.524-6.478-4.846-2.245-2.243-3.337-3.695-4.865-6.476C1.132 6.63 1.426 4.984 1.755 4.28c.392-.842.97-1.345 1.718-1.844a8.263 8.263 0 0 1 1.343-.712l.13-.057c.231-.105.583-.263 1.028-.094.297.112.562.34.978.75.852.84 2.015 2.71 2.445 3.63.288.619.479 1.028.48 1.486 0 .537-.27.95-.598 1.397l-.182.242c-.356.469-.435.604-.383.846.104.486.884 1.933 2.165 3.212 1.281 1.278 2.686 2.008 3.174 2.112.253.054.39-.027.875-.397.069-.053.14-.107.215-.162.5-.372.894-.635 1.418-.635h.003c.456 0 .847.198 1.493.524.844.426 2.771 1.575 3.616 2.427.412.415.64.679.753.976.169.447.01.797-.094 1.031l-.057.129a8.27 8.27 0 0 1-.716 1.34c-.499.745-1.004 1.322-1.846 1.714a3.16 3.16 0 0 1-1.386.304Z"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</IonContent>
|
|
||||||
</IonModal>
|
|
||||||
</IonContent>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ExploreContainer;
|
export default ExploreContainer;
|
||||||
|
Loading…
Reference in New Issue
Block a user