WebRTC implementation
parent
1200dbeb70
commit
98837b2298
@ -0,0 +1,242 @@
|
||||
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<String, dynamic> 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<String> 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<String, dynamic> 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<String, dynamic> data = snapshot.data() as Map<String, dynamic>;
|
||||
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<String, dynamic> data = change.doc.data() as Map<String, dynamic>;
|
||||
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<void> 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<String, dynamic>;
|
||||
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<String, dynamic> 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<String, dynamic>;
|
||||
// print(data);
|
||||
// print('Got new remote ICE candidate: $data');
|
||||
// peerConnection.addCandidate(
|
||||
// RTCIceCandidate(
|
||||
// data['candidate'],
|
||||
// data['sdpMid'],
|
||||
// data['sdpMLineIndex'],
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> 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<void> hangUp(RTCVideoRenderer localVideo) async {
|
||||
List<MediaStreamTrack> 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue