Subscriber as primary (#8)

* Update protos

* Signal client

* Improve transport

* First implementation

* Fix publish

* Update `PCTransport` for debounce negotiation

* debounce func

* reconnect & events

* Update debounce func

* engine events

* make sure events don't emit after dispose

* prefix flutter_webrtc

* Cleaner `iceServers` update

* don't mutate user provided params

* fix tests

* event manager

* Fix: `Room.onDisconnected` gets fired multiple times

* data publish in example

* cleaner logic

* safer events

* un-prefix with LK

* organize imports

* Clean up

* remove `Tuple`

* `EventsEmitter` can now be directly listened to

`EventsEmitter` extends `EventsListenable`
This commit is contained in:
Hiroshi Horie
2021-09-18 00:10:16 +09:00
committed by GitHub
parent 50f56c5e1e
commit f4bd82a919
36 changed files with 1334 additions and 588 deletions
+36 -80
View File
@@ -2,17 +2,19 @@ import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:http/http.dart' as http;
import 'package:livekit_client/src/ws/interface.dart';
import 'package:synchronized/synchronized.dart' as sync;
import 'errors.dart';
import 'extensions.dart';
import 'logger.dart';
import 'options.dart';
import 'proto/livekit_models.pb.dart' as lk_models;
import 'proto/livekit_rtc.pb.dart' as lk_rtc;
import 'track/track.dart';
import 'utils.dart';
import 'ws/interface.dart';
mixin SignalClientDelegate {
// initial connection established
@@ -20,11 +22,11 @@ mixin SignalClientDelegate {
// websocket has closed
Future<void> onClose([String? reason]);
// when a server offer is received
Future<void> onOffer(RTCSessionDescription sd);
Future<void> onOffer(rtc.RTCSessionDescription sd);
// when an answer from server is received
Future<void> onAnswer(RTCSessionDescription sd);
Future<void> onAnswer(rtc.RTCSessionDescription sd);
// when server has a new ICE candidate
Future<void> onTrickle(RTCIceCandidate candidate, lk_rtc.SignalTarget target);
Future<void> onTrickle(rtc.RTCIceCandidate candidate, lk_rtc.SignalTarget target);
// participant has changed
Future<void> onParticipantUpdate(List<lk_models.ParticipantInfo> updates);
// when a track has been added successfully
@@ -37,48 +39,20 @@ mixin SignalClientDelegate {
Future<void> onMuteTrack(lk_rtc.MuteTrackRequest req);
}
extension LKUriExt on Uri {
bool get isSecureScheme => ['https', 'wss'].contains(scheme);
}
class SignalClient {
static const protocolVersion = 2;
final _lock = sync.Lock();
ProtocolVersion protocol;
SignalClientDelegate? delegate;
bool _connected = false;
LKWebSocket? _ws;
LiveKitWebSocket? _ws;
SignalClient();
SignalClient({
this.protocol = ProtocolVersion.protocol3,
});
bool get connected => _connected;
Uri _buildUri(
String uriOrString, {
required String token,
ConnectOptions? options,
bool reconnect = false,
bool validate = false,
bool forceSecure = false,
}) {
final Uri uri = Uri.parse(uriOrString);
final useSecure = uri.isSecureScheme || forceSecure;
final httpScheme = useSecure ? 'https' : 'http';
final wsScheme = useSecure ? 'wss' : 'ws';
return uri.replace(
scheme: validate ? httpScheme : wsScheme,
path: validate ? 'validate' : 'rtc',
queryParameters: <String, String>{
'access_token': token,
if (options != null) 'auto_subscribe': options.autoSubscribe ? '1' : '0',
if (reconnect) 'reconnect': '1',
'protocol': protocolVersion.toString(),
},
);
}
Future<void> join(
String uriString,
String token, {
@@ -87,16 +61,17 @@ class SignalClient {
// Create default options if null
options ??= const ConnectOptions();
final rtcUri = _buildUri(
final rtcUri = Utils.buildUri(
uriString,
token: token,
options: options,
protocol: protocol,
);
try {
_ws = await LKWebSocket.connect(
_ws = await LiveKitWebSocket.connect(
rtcUri,
LKWebSocketOptions(
WebSocketOptions(
onData: _onSocketData,
onDispose: _onSocketDone,
onError: _handleError,
@@ -104,24 +79,25 @@ class SignalClient {
);
} catch (socketError) {
// Re-build same uri for validate mode
final validateUri = _buildUri(
final validateUri = Utils.buildUri(
uriString,
token: token,
options: options,
validate: true,
forceSecure: rtcUri.isSecureScheme,
protocol: protocol,
);
// Attempt Validation
try {
final validateResponse = await http.get(validateUri);
if (validateResponse.statusCode != 200) throw ConnectError(validateResponse.body);
throw ConnectError();
if (validateResponse.statusCode != 200) throw ConnectException(validateResponse.body);
throw ConnectException();
} catch (error) {
// Pass it up if it's already a `ConnectError`
if (error is ConnectError) rethrow;
if (error is ConnectException) rethrow;
// HTTP doesn't work either
throw ConnectError();
throw ConnectException();
}
}
}
@@ -134,15 +110,16 @@ class SignalClient {
_ws?.dispose();
_ws = null;
final rtcUri = _buildUri(
final rtcUri = Utils.buildUri(
uriString,
token: token,
reconnect: true,
protocol: protocol,
);
_ws = await LKWebSocket.connect(
_ws = await LiveKitWebSocket.connect(
rtcUri,
LKWebSocketOptions(
WebSocketOptions(
onData: _onSocketData,
onDispose: _onSocketDone,
onError: _handleError,
@@ -157,18 +134,18 @@ class SignalClient {
_ws?.dispose();
}
void sendOffer(RTCSessionDescription offer) => _sendRequest(lk_rtc.SignalRequest(
offer: fromRTCSessionDescription(offer),
void sendOffer(rtc.RTCSessionDescription offer) => _sendRequest(lk_rtc.SignalRequest(
offer: offer.toSDKType(),
));
void sendAnswer(RTCSessionDescription answer) => _sendRequest(lk_rtc.SignalRequest(
answer: fromRTCSessionDescription(answer),
void sendAnswer(rtc.RTCSessionDescription answer) => _sendRequest(lk_rtc.SignalRequest(
answer: answer.toSDKType(),
));
void sendIceCandidate(RTCIceCandidate candidate, lk_rtc.SignalTarget target) => _sendRequest(
void sendIceCandidate(rtc.RTCIceCandidate candidate, lk_rtc.SignalTarget target) => _sendRequest(
lk_rtc.SignalRequest(
trickle: lk_rtc.TrickleRequest(
candidateInit: fromRTCIceCandidate(candidate),
candidateInit: candidate.toJson(),
target: target,
),
),
@@ -249,14 +226,14 @@ class SignalClient {
}
break;
case lk_rtc.SignalResponse_Message.answer:
await delegate?.onAnswer(toRTCSessionDescription(msg.answer));
await delegate?.onAnswer(msg.answer.toSDKType());
break;
case lk_rtc.SignalResponse_Message.offer:
await delegate?.onOffer(toRTCSessionDescription(msg.offer));
await delegate?.onOffer(msg.offer.toSDKType());
break;
case lk_rtc.SignalResponse_Message.trickle:
await delegate?.onTrickle(
toRTCIceCandidate(msg.trickle.candidateInit),
RTCIceCandidateExt.fromJson(msg.trickle.candidateInit),
msg.trickle.target,
);
break;
@@ -292,24 +269,3 @@ class SignalClient {
delegate?.onClose();
}
}
RTCSessionDescription toRTCSessionDescription(lk_rtc.SessionDescription sd) {
return RTCSessionDescription(sd.sdp, sd.type);
}
lk_rtc.SessionDescription fromRTCSessionDescription(RTCSessionDescription rsd) {
return lk_rtc.SessionDescription(type: rsd.type, sdp: rsd.sdp);
}
RTCIceCandidate toRTCIceCandidate(String candidateInit) {
final candInit = json.decode(candidateInit) as Map<String, dynamic>;
return RTCIceCandidate(
candInit['candidate'] as String?,
candInit['sdpMid'] as String?,
candInit['sdpMLineIndex'] as int?,
);
}
String fromRTCIceCandidate(RTCIceCandidate candidate) {
return json.encode(candidate.toMap());
}