finished up most of the WebRTC implementation - closes #13
parent
6272dd1cad
commit
3d24417faa
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue