import 'dart:convert'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; typedef void StreamStateCallback(MediaStream stream); class Signaling { Map configuration = { 'iceServers': [ { 'urls': ['stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302'] } ] }; RTCPeerConnection peerConnection; MediaStream localStream; MediaStream remoteStream; String roomId; String currentRoomText; StreamStateCallback onAddRemoteStream; Future createRoom(RTCVideoRenderer remoteRenderer) async { FirebaseFirestore db = FirebaseFirestore.instance; DocumentReference roomRef = db.collection('rooms').doc(); print('Create PeerConnection with configuration: $configuration'); peerConnection = await createPeerConnection(configuration); registerPeerConnectionListeners(); localStream.getTracks().forEach((track) { peerConnection?.addTrack(track, localStream); }); // Code for collecting ICE candidates below var callerCandidatesCollection = roomRef.collection('callerCandidates'); peerConnection?.onIceCandidate = (RTCIceCandidate candidate) { print('Got candidate: ${candidate.toMap()}'); callerCandidatesCollection.add(candidate.toMap()); }; // Finish Code for collecting ICE candidate // Add code for creating a room RTCSessionDescription offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); print('Created offer: $offer'); Map roomWithOffer = {'offer': offer.toMap()}; await roomRef.set(roomWithOffer); var roomId = roomRef.id; print('New room created with SDK offer. Room ID: $roomId'); currentRoomText = 'Current room is $roomId - You are the caller!'; // Created a Room peerConnection?.onTrack = (RTCTrackEvent event) { print('Got remote track: ${event.streams[0]}'); event.streams[0].getTracks().forEach((track) { print('Add a track to the remoteStream $track'); remoteStream?.addTrack(track); }); }; // Listening for remote session description below roomRef.snapshots().listen((snapshot) async { print('Got updated room: ${snapshot.data()}'); Map data = snapshot.data() as Map; if (peerConnection?.getRemoteDescription() != null && data['answer'] != null) { var answer = RTCSessionDescription( data['answer']['sdp'], data['answer']['type'], ); print("Someone tried to connect"); await peerConnection?.setRemoteDescription(answer); } }); // Listening for remote session description above // Listen for remote Ice candidates below roomRef.collection('calleeCandidates').snapshots().listen((snapshot) { snapshot.docChanges.forEach((change) { if (change.type == DocumentChangeType.added) { Map data = change.doc.data() as Map; print('Got new remote ICE candidate: ${jsonEncode(data)}'); peerConnection.addCandidate( RTCIceCandidate( data['candidate'], data['sdpMid'], data['sdpMLineIndex'], ), ); } }); }); // Listen for remote ICE candidates above return roomId; } Future joinRoom(String roomId, RTCVideoRenderer remoteVideo) async { FirebaseFirestore db = FirebaseFirestore.instance; DocumentReference roomRef = db.collection('rooms').doc('$roomId'); var roomSnapshot = await roomRef.get(); print('Got room ${roomSnapshot.exists}'); if (roomSnapshot.exists) { print('Create PeerConnection with configuration: $configuration'); peerConnection = await createPeerConnection(configuration); registerPeerConnectionListeners(); localStream.getTracks().forEach((track) { peerConnection?.addTrack(track, localStream); }); // Code for collecting ICE candidates below var calleeCandidatesCollection = roomRef.collection('calleeCandidates'); peerConnection.onIceCandidate = (RTCIceCandidate candidate) { if (candidate == null) { print('onIceCandidate: complete!'); return; } print('onIceCandidate: ${candidate.toMap()}'); calleeCandidatesCollection.add(candidate.toMap()); }; // Code for collecting ICE candidate above peerConnection?.onTrack = (RTCTrackEvent event) { print('Got remote track: ${event.streams[0]}'); event.streams[0].getTracks().forEach((track) { print('Add a track to the remoteStream: $track'); remoteStream?.addTrack(track); }); }; // Code for creating SDP answer below var data = roomSnapshot.data() as Map; print('Got offer $data'); var offer = data['offer']; await peerConnection?.setRemoteDescription( RTCSessionDescription(offer['sdp'], offer['type']), ); var answer = await peerConnection.createAnswer(); print('Created Answer $answer'); await peerConnection.setLocalDescription(answer); Map roomWithAnswer = { 'answer': {'type': answer.type, 'sdp': answer.sdp} }; await roomRef.update(roomWithAnswer); // Finished creating SDP answer // Listening for remote ICE candidates below // roomRef.collection('callerCandidates').snapshots().listen((snapshot) { // snapshot.docChanges.forEach((document) { // var data = document.doc.data() as Map; // print(data); // print('Got new remote ICE candidate: $data'); // peerConnection.addCandidate( // RTCIceCandidate( // data['candidate'], // data['sdpMid'], // data['sdpMLineIndex'], // ), // ); // }); // }); } } Future openUserMedia( RTCVideoRenderer localVideo, RTCVideoRenderer remoteVideo, ) async { var stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': false}); localVideo.srcObject = stream; localStream = stream; remoteVideo.srcObject = await createLocalMediaStream('key'); } Future hangUp(RTCVideoRenderer localVideo) async { List tracks = localVideo.srcObject.getTracks(); tracks.forEach((track) { track.stop(); }); if (remoteStream != null) { remoteStream.getTracks().forEach((track) => track.stop()); } if (peerConnection != null) peerConnection.close(); if (roomId != null) { var db = FirebaseFirestore.instance; var roomRef = db.collection('rooms').doc(roomId); var calleeCandidates = await roomRef.collection('calleeCandidates').get(); calleeCandidates.docs.forEach((document) => document.reference.delete()); var callerCandidates = await roomRef.collection('callerCandidates').get(); callerCandidates.docs.forEach((document) => document.reference.delete()); await roomRef.delete(); } localStream.dispose(); remoteStream?.dispose(); } void registerPeerConnectionListeners() { peerConnection?.onIceGatheringState = (RTCIceGatheringState state) { print('ICE gathering state changed: $state'); }; peerConnection?.onConnectionState = (RTCPeerConnectionState state) { print('Connection state change: $state'); }; peerConnection?.onSignalingState = (RTCSignalingState state) { print('Signaling state change: $state'); }; peerConnection?.onIceGatheringState = (RTCIceGatheringState state) { print('ICE connection state change: $state'); }; peerConnection?.onAddStream = (MediaStream stream) { print("Add remote stream"); onAddRemoteStream?.call(stream); remoteStream = stream; }; } }