4
2
Fork 0

finished up most of the WebRTC implementation - closes #13

pull/31/head
Sudharshan S. 2019-02-16 21:56:08 +08:00
parent 6272dd1cad
commit 3d24417faa
Signed by: sudharshan
GPG Key ID: C861C97AAF3D9559
4 changed files with 216 additions and 92 deletions

View File

@ -1,15 +1,15 @@
import "package:json_annotation/json_annotation.dart";
part 'user_model.g.dart';
part "user_model.g.dart";
@JsonSerializable()
class User {
final String id;
@JsonKey(name: 'first_name')
@JsonKey(name: "first_name")
final String firstName;
@JsonKey(name: 'last_name')
@JsonKey(name: "last_name")
final String lastName;
User(this.id, this.firstName, this.lastName);

View File

@ -0,0 +1,131 @@
import 'dart:async';
import 'dart:convert';
import 'package:eventsource/eventsource.dart';
import 'package:flutter_webrtc/webrtc.dart';
import 'package:http/http.dart' as http;
// Available utility enums
enum SignalingResponse { SUCCESSFULL, NO_DEVICE, NO_DATA }
enum SignalType { CANDIDATE, OFFER, ANSWER }
// Callback definitions
typedef void OnMessageCallback(Map<String, dynamic> data);
class PeerConnectionFactory {
final String _localUserId;
final String _localDeviceId;
EventSource _signalingServer;
MediaStream _stream;
// TODO: Future should use a model
JsonEncoder encoder = JsonEncoder();
JsonDecoder decoder = JsonDecoder();
//Callbacks
OnMessageCallback onMessageCallback;
// could be const
Map<String, dynamic> _iceServers = {
"iceServers": [
{"url": "stun:stun.l.google.com:19302"}
]
};
// could be const
final Map<String, dynamic> _config = {
"mandatory": {},
"optional": [
{"DtlsSrtpKeyAgreement": true}
]
};
// could be const
final Map<String, dynamic> _constraints = {
"mandatory": {
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": false,
},
"optional": []
};
PeerConnectionFactory(this._localUserId, this._localDeviceId, this._stream,
this.onMessageCallback);
// initialize() method sets up a subscription to the eventsource and
// attaches a callback to it
initialize() async {
_signalingServer = await EventSource.connect(
"localhost:10201/subscribe/$_localUserId/device/$_localDeviceId");
_signalingServer.listen((event) {
print(event.data);
onMessageCallback(decoder.convert(event.data));
});
}
newPeerConnection(String remoteUserId, String remoteDeviceId) async {
RTCPeerConnection connection =
await createPeerConnection(_iceServers, _config);
connection.addStream(_stream);
// Send candidates to remote
connection.onIceCandidate =
(candidate) => _sendToRemote(remoteUserId, remoteDeviceId, {
"fromUser": this._localUserId,
"fromDevice": this._localDeviceId,
"type": SignalType.CANDIDATE,
"sdpMLineIndex": candidate.sdpMlineIndex,
"sdpMid": candidate.sdpMid,
"candidate": candidate.candidate
});
// Create and send the offer
RTCSessionDescription session = await connection.createOffer(_constraints);
connection.setLocalDescription(session);
_sendToRemote(remoteUserId, remoteDeviceId, {
"fromUser": this._localUserId,
"fromDevice": this._localDeviceId,
"type": SignalType.OFFER,
"sdp": session.sdp,
"session": session.type,
});
return connection;
// NEED TO WAIT FOR ANSWER BEFORE CONNECTION IS ESTABLISHED
}
leavePeerConnection(RTCPeerConnection connection) {
connection.removeStream(_stream);
connection.close();
}
sendAnswer(RTCPeerConnection connection, String remoteUserId,
String remoteDeviceId) async {
RTCSessionDescription session = await connection.createAnswer(_constraints);
connection.setLocalDescription(session);
_sendToRemote(remoteUserId, remoteDeviceId, {
"fromUser": this._localUserId,
"fromDevice": this._localDeviceId,
"type": SignalType.ANSWER,
"sdp": session.sdp,
"session": session.type,
});
}
Future<SignalingResponse> _sendToRemote(String remoteUserId,
String remoteDeviceId, Map<String, dynamic> data) async {
var response = await http.post(
"localhost:10201/user/$remoteUserId/device/$remoteDeviceId",
body: {"data": encoder.convert(data)});
switch (response.statusCode) {
case 200:
return SignalingResponse.SUCCESSFULL;
case 400:
return SignalingResponse.NO_DATA;
case 404:
return SignalingResponse.NO_DEVICE;
default:
return SignalingResponse.NO_DEVICE;
}
}
}

View File

@ -1,3 +1,84 @@
import "package:flutter_webrtc/webrtc.dart";
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import "peer_connection_factory.dart";
class PeerManager {}
class PeerManager {
String _selfUserId;
String _selfDeviceId;
PeerConnectionFactory _peerConnectionFactory;
MediaStream _localStream;
JsonDecoder _decoder = JsonDecoder();
List<MediaStream> _currentStreams;
Map<String, RTCPeerConnection> _currentConnections;
PeerManager(this._selfUserId, this._selfDeviceId) {
_peerConnectionFactory =
PeerConnectionFactory(_selfUserId, _selfDeviceId, _localStream, signalEventHandler);
}
addPeer(String userId) async {
var response =
await http.get("http://localhost:10201/user/$userId/devices");
List<dynamic> deviceIds = _decoder.convert(response.body);
deviceIds.forEach((deviceId) async {
RTCPeerConnection connection = await _peerConnectionFactory
.newPeerConnection(userId, deviceId);
// Handle streams being added and removed remotely
connection.onAddStream =
(stream) => _currentStreams.add(stream);
connection.onRemoveStream =
(stream) => _currentStreams.removeWhere((it) => stream.id == it.id);
// Add peer connection to the map
_currentConnections["$userId-$deviceId"] = connection;
});
}
leaveAll(void) async {}
signalEventHandler(Map<String, dynamic> data) async {
switch (data["type"]) {
case SignalType.CANDIDATE:
String userId = data["fromUser"];
String deviceId = data["fromDevice"];
RTCPeerConnection connection = _currentConnections["$userId-$deviceId"];
if (connection != null) {
RTCIceCandidate candidate = RTCIceCandidate(
data["candidate"], data["sdpMid"], data["sdpMLineIndex"]);
connection.addCandidate(candidate);
}
break;
case SignalType.OFFER:
String userId = data["fromUser"];
String deviceId = data["fromDevice"];
RTCPeerConnection connection = await _peerConnectionFactory
.newPeerConnection(userId, deviceId);
_currentConnections["$userId-$deviceId"] = connection;
connection.setRemoteDescription(
RTCSessionDescription(data["sdp"], data["sessionType"]));
_peerConnectionFactory.sendAnswer(connection, userId, deviceId);
break;
case SignalType.ANSWER:
String userId = data["fromUser"];
String deviceId = data["fromDevice"];
RTCPeerConnection connection = _currentConnections["$userId-$deviceId"];
if (connection != null) {
connection.setRemoteDescription(
RTCSessionDescription(data["sdp"], data["sessionType"]));
break;
}
}
}
}

View File

@ -1,88 +0,0 @@
import 'dart:async';
import 'package:eventsource/eventsource.dart';
import 'package:flutter_webrtc/webrtc.dart';
import 'package:http/http.dart' as http;
enum SignalingResponse { SUCCESSFULL, NO_DEVICE, NO_DATA }
class PeerConnectionFactory {
final String _selfUserId;
final String _selfDeviceId;
EventSource _signalingServer;
// could be const
Map<String, dynamic> _iceServers = {
"iceServers": [
{"url": "stun:stun.l.google.com:19302"}
]
};
// could be const
final Map<String, dynamic> _config = {
"mandatory": {},
"optional": [
{"DtlsSrtpKeyAgreement": true}
]
};
// could be const
final Map<String, dynamic> _constraints = {
"mandatory": {
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": false,
},
"optional": []
};
PeerConnectionFactory(this._selfUserId, this._selfDeviceId);
// initialize() method sets up a subscription to the eventsource and
// attaches a callback to it
initialize() async {
_signalingServer = await EventSource.connect(
"localhost:10201/subscribe/$_selfUserId/device/$_selfDeviceId");
_signalingServer.listen((event) => print(event.data));
}
Future<RTCPeerConnection> newPeerConnection(
String remoteId, String remoteDevice, MediaStream stream) async {
RTCPeerConnection connection =
await createPeerConnection(_iceServers, _config);
connection.addStream(stream);
// Send candidates to remote (NOT_IMPLEMENTED TO SEND)
connection.onIceCandidate =
(candidate) => _sendToRemote(remoteId, remoteDevice);
// Create and send the offer
RTCSessionDescription session = await connection.createOffer(_constraints);
connection.setLocalDescription(session);
_sendToRemote(remoteId, remoteDevice);
return connection;
// NEED TO WAIT FOR ANSWER BEFORE CONNECTION IS ESTABLISHED
}
peerConnectionAnswer(RTCPeerConnection connection, String remoteId,
String remoteDevice) async {
RTCSessionDescription session = await connection.createAnswer(_constraints);
connection.setLocalDescription(session);
_sendToRemote(remoteId, remoteDevice);
}
Future<SignalingResponse> _sendToRemote(
String remoteId, String remoteDevice) async {
var response =
await http.post("localhost:10201/user/$remoteId/device/$remoteDevice");
switch (response.statusCode) {
case 200:
return SignalingResponse.SUCCESSFULL;
case 400:
return SignalingResponse.NO_DATA;
case 404:
return SignalingResponse.NO_DEVICE;
default:
return SignalingResponse.NO_DEVICE;
}
}
}