signal & webrtc connections established
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
library livekit_client;
|
||||
|
||||
export 'src/livekit.dart';
|
||||
export 'src/errors.dart';
|
||||
export 'src/room.dart';
|
||||
export 'src/rtc_engine.dart';
|
||||
export 'src/participant/participant.dart';
|
||||
export 'src/participant/local_participant.dart';
|
||||
export 'src/participant/remote_participant.dart';
|
||||
|
||||
@@ -9,9 +9,11 @@ class RTCConfiguration {
|
||||
iceServersMap.add(element.toMap());
|
||||
});
|
||||
return {
|
||||
// only supports unified plan
|
||||
'sdpSemantics': 'unified-plan',
|
||||
if (iceCandidatePoolSize != null)
|
||||
"iceCandidatePoolSize": iceCandidatePoolSize,
|
||||
if (iceServersMap.isNotEmpty) "iceServers": iceServersMap,
|
||||
"iceServers": iceServersMap,
|
||||
if (iceTransportPolicy != null) "iceTransportPolicy": iceTransportPolicy,
|
||||
};
|
||||
}
|
||||
@@ -27,10 +29,8 @@ class RTCIceServer {
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
"urls": urls,
|
||||
if (username != null)
|
||||
"username": username,
|
||||
if (credential != null)
|
||||
"credential": credential,
|
||||
if (username != null) "username": username,
|
||||
if (credential != null) "credential": credential,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'room.dart';
|
||||
|
||||
class LiveKitClient {
|
||||
// TODO: take in connect options
|
||||
static Future<Room> connect(String url, String token) {
|
||||
var room = Room();
|
||||
return room.connect(url, token);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ class LocalParticipant extends Participant {
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// publish an audio track to the room
|
||||
Future<TrackPublication> publishAudioTrack(LocalAudioTrack track) async {
|
||||
if (audioTracks.values.any(
|
||||
(element) => element.track?.mediaTrack.id == track.mediaTrack.id)) {
|
||||
@@ -59,6 +60,7 @@ class LocalParticipant extends Participant {
|
||||
return pub;
|
||||
}
|
||||
|
||||
/// publish a video track to the room
|
||||
Future<TrackPublication> publishVideoTrack(LocalVideoTrack track) async {
|
||||
if (audioTracks.values.any(
|
||||
(element) => element.track?.mediaTrack.id == track.mediaTrack.id)) {
|
||||
|
||||
+7
-7
@@ -73,8 +73,8 @@ class Room with ParticipantDelegate {
|
||||
|
||||
Completer<Room>? _connectCompleter;
|
||||
|
||||
Room(SignalClient client, RTCConfiguration? rtcConfig)
|
||||
: _engine = new RTCEngine(client, rtcConfig) {
|
||||
Room([RTCConfiguration? rtcConfig])
|
||||
: _engine = new RTCEngine(SignalClient(), rtcConfig) {
|
||||
_engine.onTrack = _onTrackAdded;
|
||||
_engine.onICEConnected = _handleICEConnected;
|
||||
_engine.onDisconnected = _handleDisconnect;
|
||||
@@ -85,7 +85,7 @@ class Room with ParticipantDelegate {
|
||||
// TODO: handle reconnecting & reconnected events
|
||||
}
|
||||
|
||||
Future<Room> connect(String url, String token, JoinOptions? opts) async {
|
||||
Future<Room> connect(String url, String token, [JoinOptions? opts]) async {
|
||||
var completer = new Completer<Room>();
|
||||
_connectCompleter = completer;
|
||||
|
||||
@@ -109,10 +109,10 @@ class Room with ParticipantDelegate {
|
||||
|
||||
// room is not ready until ICE is connected. so we would return a completer for now
|
||||
// if it times out, we'll fail the completer
|
||||
Timer(Duration(seconds: 5), () {
|
||||
_connectCompleter?.completeError(ConnectError());
|
||||
_connectCompleter = null;
|
||||
});
|
||||
// Timer(Duration(seconds: 5), () {
|
||||
// _connectCompleter?.completeError(ConnectError());
|
||||
// _connectCompleter = null;
|
||||
// });
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
+24
-11
@@ -64,6 +64,8 @@ class RTCEngine with SignalClientDelegate {
|
||||
var completer = new Completer<JoinResponse>();
|
||||
joinCompleter = completer;
|
||||
|
||||
client.join(url, token, opts);
|
||||
|
||||
// if it's not complete after 5 seconds, fail
|
||||
new Timer(connectionTimeout, () {
|
||||
joinCompleter?.completeError(new ConnectError());
|
||||
@@ -116,7 +118,12 @@ class RTCEngine with SignalClientDelegate {
|
||||
return;
|
||||
}
|
||||
|
||||
var remoteDesc = await pub.pc.getRemoteDescription();
|
||||
RTCSessionDescription? remoteDesc;
|
||||
if (pub.pc.iceConnectionState != null) {
|
||||
// when not initially connected, this crashes on iOS
|
||||
remoteDesc = await pub.pc.getRemoteDescription();
|
||||
}
|
||||
|
||||
// handle cases that we couldn't create a new offer due to a pending answer
|
||||
// that's lost in transit
|
||||
if (remoteDesc != null &&
|
||||
@@ -151,8 +158,9 @@ class RTCEngine with SignalClientDelegate {
|
||||
};
|
||||
|
||||
pubPC.onRenegotiationNeeded = () {
|
||||
if (pubPC.iceConnectionState ==
|
||||
RTCIceConnectionState.RTCIceConnectionStateNew) {
|
||||
if (pubPC.iceConnectionState == null ||
|
||||
pubPC.iceConnectionState ==
|
||||
RTCIceConnectionState.RTCIceConnectionStateNew) {
|
||||
return;
|
||||
}
|
||||
negotiate();
|
||||
@@ -185,13 +193,16 @@ class RTCEngine with SignalClientDelegate {
|
||||
};
|
||||
|
||||
// create data channels
|
||||
var lossyInit = new RTCDataChannelInit();
|
||||
lossyInit.ordered = true;
|
||||
lossyInit.maxRetransmits = 1;
|
||||
var lossyInit = new RTCDataChannelInit()
|
||||
..maxRetransmits = 1
|
||||
..ordered = true
|
||||
..binaryType = 'binary';
|
||||
lossyDC = await pubPC.createDataChannel(lossyDataChannel, lossyInit);
|
||||
|
||||
var reliableInit = new RTCDataChannelInit();
|
||||
reliableInit.ordered = true;
|
||||
var reliableInit = new RTCDataChannelInit()
|
||||
..ordered = true
|
||||
..maxRetransmits = 50
|
||||
..binaryType = 'binary';
|
||||
reliableDC =
|
||||
await pubPC.createDataChannel(reliableDataChannel, reliableInit);
|
||||
|
||||
@@ -224,7 +235,7 @@ class RTCEngine with SignalClientDelegate {
|
||||
|
||||
//------------------ SignalClient Delegate methods -------------------------//
|
||||
|
||||
void onConnected(JoinResponse response) {
|
||||
void onConnected(JoinResponse response) async {
|
||||
// create peer connections
|
||||
this.isClosed = false;
|
||||
|
||||
@@ -243,7 +254,7 @@ class RTCEngine with SignalClientDelegate {
|
||||
rtcConfig.iceServers = iceServers;
|
||||
}
|
||||
|
||||
_configurePeerConnections();
|
||||
await _configurePeerConnections();
|
||||
|
||||
negotiate();
|
||||
|
||||
@@ -251,7 +262,9 @@ class RTCEngine with SignalClientDelegate {
|
||||
joinCompleter = null;
|
||||
}
|
||||
|
||||
void onClose(String? reason) {}
|
||||
void onClose([String? reason]) {
|
||||
// TODO: handle reconnect when signal interrupted
|
||||
}
|
||||
|
||||
void onOffer(RTCSessionDescription sd) async {
|
||||
var sub = subscriber;
|
||||
|
||||
+19
-13
@@ -19,7 +19,7 @@ mixin SignalClientDelegate {
|
||||
// initial connection established
|
||||
void onConnected(JoinResponse response);
|
||||
// websocket has closed
|
||||
void onClose(String? reason);
|
||||
void onClose([String? reason]);
|
||||
// when a server offer is received
|
||||
void onOffer(RTCSessionDescription sd);
|
||||
// when an answer from server is received
|
||||
@@ -37,19 +37,19 @@ mixin SignalClientDelegate {
|
||||
}
|
||||
|
||||
class SignalClient {
|
||||
SignalClientDelegate delegate;
|
||||
SignalClientDelegate? delegate;
|
||||
|
||||
bool _connected = false;
|
||||
WebSocketChannel? _ws;
|
||||
|
||||
SignalClient(this.delegate);
|
||||
SignalClient();
|
||||
|
||||
bool get connected => this._connected;
|
||||
|
||||
join(String url, String token, JoinOptions options) {
|
||||
join(String url, String token, JoinOptions? options) {
|
||||
url += '/rtc';
|
||||
var params = _paramsForToken(token);
|
||||
if (options.autoSubscribe != null) {
|
||||
if (options != null && options.autoSubscribe != null) {
|
||||
params += '&auto_subscribe=${options.autoSubscribe! ? '1' : '0'}';
|
||||
}
|
||||
var uri = Uri.parse(url + params);
|
||||
@@ -61,6 +61,7 @@ class SignalClient {
|
||||
_ws = ws;
|
||||
} catch (e) {
|
||||
// failed before error handler is installed, fail immediately
|
||||
_handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,30 +165,30 @@ class SignalClient {
|
||||
case SignalResponse_Message.join:
|
||||
if (!_connected) {
|
||||
_connected = true;
|
||||
delegate.onConnected(msg.join);
|
||||
delegate?.onConnected(msg.join);
|
||||
}
|
||||
break;
|
||||
case SignalResponse_Message.answer:
|
||||
delegate.onAnswer(toRTCSessionDescription(msg.answer));
|
||||
delegate?.onAnswer(toRTCSessionDescription(msg.answer));
|
||||
break;
|
||||
case SignalResponse_Message.offer:
|
||||
delegate.onOffer(toRTCSessionDescription(msg.offer));
|
||||
delegate?.onOffer(toRTCSessionDescription(msg.offer));
|
||||
break;
|
||||
case SignalResponse_Message.trickle:
|
||||
delegate.onTrickle(
|
||||
delegate?.onTrickle(
|
||||
toRTCIceCandidate(msg.trickle.candidateInit), msg.trickle.target);
|
||||
break;
|
||||
case SignalResponse_Message.update:
|
||||
delegate.onParticipantUpdate(msg.update.participants);
|
||||
delegate?.onParticipantUpdate(msg.update.participants);
|
||||
break;
|
||||
case SignalResponse_Message.trackPublished:
|
||||
delegate.onLocalTrackPublished(msg.trackPublished);
|
||||
delegate?.onLocalTrackPublished(msg.trackPublished);
|
||||
break;
|
||||
case SignalResponse_Message.speaker:
|
||||
delegate.onActiveSpeakersChanged(msg.speaker.speakers);
|
||||
delegate?.onActiveSpeakersChanged(msg.speaker.speakers);
|
||||
break;
|
||||
case SignalResponse_Message.leave:
|
||||
delegate.onLeave(msg.leave);
|
||||
delegate?.onLeave(msg.leave);
|
||||
break;
|
||||
default:
|
||||
log('unsupported message: ' + jsonEncode(msg));
|
||||
@@ -199,7 +200,12 @@ class SignalClient {
|
||||
}
|
||||
|
||||
_handleDone() {
|
||||
if (!_connected) {
|
||||
return;
|
||||
}
|
||||
_ws = null;
|
||||
_connected = false;
|
||||
delegate?.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
name: livekit_client_flutter
|
||||
name: livekit_client
|
||||
description: Flutter client for LiveKit
|
||||
version: 0.0.1
|
||||
homepage: https://livekit.io
|
||||
|
||||
Reference in New Issue
Block a user