import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:chepuhagram/data/datasources/ws_client.dart'; class WebRTCService { RTCPeerConnection? _peerConnection; MediaStream? _localStream; // Конфигурация STUN-серверов (Google STUN) final Map _config = { "iceServers": [ {"urls": "stun:stun.l.google.com:19302"}, {"urls": "stun:stun1.l.google.com:19302"}, ] }; /// Инициализация PeerConnection Future initPeerConnection(String callId, Function(MediaStream) onRemoteStream) async { _peerConnection = await createPeerConnection(_config); // Слушаем удаленный поток _peerConnection!.onAddStream = (stream) { onRemoteStream(stream); }; // Отправляем ICE-кандидаты на сервер через SocketService _peerConnection!.onIceCandidate = (candidate) { SocketService().sendMessage({ "type": "ice_candidate", "call_id": callId, "candidate": candidate.toMap(), }); }; // Получаем локальный поток (микрофон + камера) _localStream = await navigator.mediaDevices.getUserMedia({ 'audio': true, 'video': true, }); // Добавляем треки в соединение _localStream!.getTracks().forEach((track) { _peerConnection!.addTrack(track, _localStream!); }); } Future handleOffer(String callId, String remoteSdp) async { // 1. Инициализируем соединение, если оно еще не создано if (_peerConnection == null) { await initPeerConnection(callId, (stream) { // Здесь можно добавить callback для отрисовки видео, если нужно print("Remote stream received"); }); } // 2. Создаем ответ (это вызывает ваш метод createAnswer) await createAnswer(callId, remoteSdp); } /// Создание Offer (вызывает инициатор звонка) Future createOffer(String callId) async { RTCSessionDescription offer = await _peerConnection!.createOffer(); await _peerConnection!.setLocalDescription(offer); SocketService().sendMessage({ "type": "offer", "call_id": callId, "sdp": offer.sdp, }); } /// Создание Answer (вызывает получатель звонка) Future createAnswer(String callId, String remoteSdp) async { await _peerConnection!.setRemoteDescription( RTCSessionDescription(remoteSdp, 'offer'), ); RTCSessionDescription answer = await _peerConnection!.createAnswer(); await _peerConnection!.setLocalDescription(answer); SocketService().sendMessage({ "type": "answer", "call_id": callId, "sdp": answer.sdp, }); } /// Обработка ICE кандидатов от удаленного собеседника Future addRemoteIceCandidate(Map candidateMap) async { await _peerConnection!.addCandidate( RTCIceCandidate( candidateMap['candidate'], candidateMap['sdpMid'], candidateMap['sdpMLineIndex'], ), ); } /// Очистка ресурсов void dispose() { _localStream?.getTracks().forEach((track) => track.stop()); _localStream?.dispose(); _peerConnection?.close(); } }