104 lines
3.4 KiB
Dart
104 lines
3.4 KiB
Dart
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<String, dynamic> _config = {
|
|
"iceServers": [
|
|
{"urls": "stun:stun.l.google.com:19302"},
|
|
{"urls": "stun:stun1.l.google.com:19302"},
|
|
]
|
|
};
|
|
|
|
/// Инициализация PeerConnection
|
|
Future<void> 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<void> 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<void> 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<void> 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<void> addRemoteIceCandidate(Map<String, dynamic> candidateMap) async {
|
|
await _peerConnection!.addCandidate(
|
|
RTCIceCandidate(
|
|
candidateMap['candidate'],
|
|
candidateMap['sdpMid'],
|
|
candidateMap['sdpMLineIndex'],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Очистка ресурсов
|
|
void dispose() {
|
|
_localStream?.getTracks().forEach((track) => track.stop());
|
|
_localStream?.dispose();
|
|
_peerConnection?.close();
|
|
}
|
|
} |