organize ConnectOptions RoomOptions RTCConfiguration

This commit is contained in:
Hiroshi Horie
2021-12-10 00:23:45 +07:00
parent 303b6af427
commit a706a5e1fb
15 changed files with 140 additions and 142 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ class _ConnectPageState extends State<ConnectPage> {
final room = await LiveKitClient.connect( final room = await LiveKitClient.connect(
_uriCtrl.text, _uriCtrl.text,
_tokenCtrl.text, _tokenCtrl.text,
options: ConnectOptions( roomOptions: RoomOptions(
defaultVideoPublishOptions: VideoPublishOptions( defaultVideoPublishOptions: VideoPublishOptions(
simulcast: _simulcast, simulcast: _simulcast,
), ),
+4 -4
View File
@@ -38,10 +38,10 @@ extension ObjectExt on Object {
extension ProtocolVersionExt on ProtocolVersion { extension ProtocolVersionExt on ProtocolVersion {
String toStringValue() => { String toStringValue() => {
ProtocolVersion.protocol2: '2', ProtocolVersion.v2: '2',
ProtocolVersion.protocol3: '3', ProtocolVersion.v3: '3',
ProtocolVersion.protocol4: '4', ProtocolVersion.v4: '4',
ProtocolVersion.protocol5: '5', ProtocolVersion.v5: '5',
}[this]!; }[this]!;
} }
+4 -2
View File
@@ -12,14 +12,16 @@ class LiveKitClient {
static Future<Room> connect( static Future<Room> connect(
String url, String url,
String token, { String token, {
ConnectOptions? options, ConnectOptions? connectOptions,
RoomOptions? roomOptions,
}) async { }) async {
final room = Room(); final room = Room();
try { try {
await room.connect( await room.connect(
url, url,
token, token,
options: options, connectOptions: connectOptions,
roomOptions: roomOptions,
); );
return room; return room;
} catch (error) { } catch (error) {
+14 -4
View File
@@ -1,12 +1,23 @@
import 'package:livekit_client/src/types.dart';
import 'track/options.dart'; import 'track/options.dart';
import 'track/track.dart'; import 'track/track.dart';
/// Options when joining a room.
/// {@category Room}
class ConnectOptions { class ConnectOptions {
/// Auto-subscribe to room tracks upon connect, defaults to true. /// Auto-subscribe to room tracks upon connect, defaults to true.
final bool autoSubscribe; final bool autoSubscribe;
final RTCConfiguration rtcConfiguration;
final ProtocolVersion protocolVersion;
const ConnectOptions({
this.autoSubscribe = true,
this.rtcConfiguration = const RTCConfiguration(),
this.protocolVersion = ProtocolVersion.v5,
});
}
/// Options when joining a room.
/// {@category Room}
class RoomOptions {
/// Default options used when publishing a video track /// Default options used when publishing a video track
final VideoPublishOptions defaultVideoPublishOptions; final VideoPublishOptions defaultVideoPublishOptions;
@@ -26,8 +37,7 @@ class ConnectOptions {
/// defaults to true. /// defaults to true.
final bool stopLocalTrackOnUnpublish; final bool stopLocalTrackOnUnpublish;
const ConnectOptions({ const RoomOptions({
this.autoSubscribe = true,
this.defaultVideoPublishOptions = const VideoPublishOptions(), this.defaultVideoPublishOptions = const VideoPublishOptions(),
this.defaultAudioPublishOptions = const AudioPublishOptions(), this.defaultAudioPublishOptions = const AudioPublishOptions(),
this.optimizeVideo = true, this.optimizeVideo = true,
+29 -34
View File
@@ -7,12 +7,10 @@ import '../events.dart';
import '../exceptions.dart'; import '../exceptions.dart';
import '../extensions.dart'; import '../extensions.dart';
import '../logger.dart'; import '../logger.dart';
import '../managers/event.dart';
import '../options.dart'; import '../options.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../publication/local_track_publication.dart'; import '../publication/local_track_publication.dart';
import '../room.dart'; import '../room.dart';
import '../rtc_engine.dart';
import '../track/local.dart'; import '../track/local.dart';
import '../track/local/audio.dart'; import '../track/local/audio.dart';
import '../track/local/video.dart'; import '../track/local/video.dart';
@@ -23,22 +21,14 @@ import 'participant.dart';
/// Represents the current participant in the room. Instance of [LocalParticipant] is automatically /// Represents the current participant in the room. Instance of [LocalParticipant] is automatically
/// created after successfully connecting to a [Room] and will be accessible from [Room.localParticipant]. /// created after successfully connecting to a [Room] and will be accessible from [Room.localParticipant].
class LocalParticipant extends Participant<LocalTrackPublication> { class LocalParticipant extends Participant<LocalTrackPublication> {
@internal //
final VideoPublishOptions? defaultVideoPublishOptions;
@internal
final AudioPublishOptions? defaultAudioPublishOptions;
LocalParticipant({ LocalParticipant({
required RTCEngine engine, required Room room,
required lk_models.ParticipantInfo info, required lk_models.ParticipantInfo info,
this.defaultVideoPublishOptions,
this.defaultAudioPublishOptions,
required EventsEmitter<RoomEvent> roomEvents,
}) : super( }) : super(
engine: engine, room: room,
sid: info.sid, sid: info.sid,
identity: info.identity, identity: info.identity,
roomEvents: roomEvents,
) { ) {
updateFromInfo(info); updateFromInfo(info);
} }
@@ -47,7 +37,7 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
/// For most cases, using [setMicrophoneEnabled] would be simpler and recommended. /// For most cases, using [setMicrophoneEnabled] would be simpler and recommended.
Future<LocalTrackPublication<LocalAudioTrack>> publishAudioTrack( Future<LocalTrackPublication<LocalAudioTrack>> publishAudioTrack(
LocalAudioTrack track, { LocalAudioTrack track, {
AudioPublishOptions? options, AudioPublishOptions? publishOptions,
}) async { }) async {
if (audioTracks.any( if (audioTracks.any(
(e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) { (e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
@@ -55,14 +45,15 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
} }
// Use defaultPublishOptions if options is null // Use defaultPublishOptions if options is null
options = options ?? defaultAudioPublishOptions; publishOptions =
publishOptions ?? room.roomOptions?.defaultAudioPublishOptions;
final trackInfo = await engine.addTrack( final trackInfo = await room.engine.addTrack(
cid: track.getCid(), cid: track.getCid(),
name: track.name, name: track.name,
kind: track.kind, kind: track.kind,
source: track.source.toPBType(), source: track.source.toPBType(),
dtx: options?.dtx, dtx: publishOptions?.dtx,
); );
await track.start(); await track.start();
@@ -71,12 +62,13 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
direction: rtc.TransceiverDirection.SendOnly, direction: rtc.TransceiverDirection.SendOnly,
); );
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web) // addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
track.transceiver = await engine.publisher?.pc.addTransceiver( track.transceiver = await room.engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack, track: track.mediaStreamTrack,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeAudio, kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeAudio,
init: transceiverInit, init: transceiverInit,
); );
await engine.negotiate();
await room.engine.negotiate();
final pub = LocalTrackPublication<LocalAudioTrack>( final pub = LocalTrackPublication<LocalAudioTrack>(
participant: this, participant: this,
@@ -85,7 +77,7 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
); );
addTrackPublication(pub); addTrackPublication(pub);
[events, roomEvents].emit(LocalTrackPublishedEvent( [events, room.events].emit(LocalTrackPublishedEvent(
participant: this, participant: this,
publication: pub, publication: pub,
)); ));
@@ -96,7 +88,7 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
/// Publish a video track to the room /// Publish a video track to the room
Future<LocalTrackPublication<LocalVideoTrack>> publishVideoTrack( Future<LocalTrackPublication<LocalVideoTrack>> publishVideoTrack(
LocalVideoTrack track, { LocalVideoTrack track, {
VideoPublishOptions? options, VideoPublishOptions? publishOptions,
}) async { }) async {
if (videoTracks.any( if (videoTracks.any(
(e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) { (e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
@@ -104,7 +96,8 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
} }
// Use defaultPublishOptions if options is null // Use defaultPublishOptions if options is null
options = options ?? defaultVideoPublishOptions; publishOptions =
publishOptions ?? room.roomOptions?.defaultVideoPublishOptions;
// use constraints passed to getUserMedia by default // use constraints passed to getUserMedia by default
int width = track.currentOptions.params.width; int width = track.currentOptions.params.width;
@@ -126,7 +119,7 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
} }
} }
final trackInfo = await engine.addTrack( final trackInfo = await room.engine.addTrack(
cid: track.getCid(), cid: track.getCid(),
name: track.name, name: track.name,
kind: track.kind, kind: track.kind,
@@ -139,13 +132,13 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
await track.start(); await track.start();
logger.fine( logger.fine(
'Compute encodings with resolution: ${width}x${height}, options: ${options}'); 'Compute encodings with resolution: ${width}x${height}, options: ${publishOptions}');
// Video encodings and simulcasts // Video encodings and simulcasts
final encodings = Utils.computeVideoEncodings( final encodings = Utils.computeVideoEncodings(
width: width, width: width,
height: height, height: height,
options: options, options: publishOptions,
); );
logger.fine('Using encodings: ${encodings?.map((e) => e.toMap())}'); logger.fine('Using encodings: ${encodings?.map((e) => e.toMap())}');
@@ -156,14 +149,15 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
streams: [track.mediaStream], streams: [track.mediaStream],
); );
logger.fine('publishVideoTrack publisher: ${engine.publisher}'); logger.fine('publishVideoTrack publisher: ${room.engine.publisher}');
track.transceiver = await engine.publisher?.pc.addTransceiver( track.transceiver = await room.engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack, track: track.mediaStreamTrack,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeVideo, kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeVideo,
init: transceiverInit, init: transceiverInit,
); );
await engine.negotiate();
await room.engine.negotiate();
final pub = LocalTrackPublication<LocalVideoTrack>( final pub = LocalTrackPublication<LocalVideoTrack>(
participant: this, participant: this,
@@ -172,7 +166,7 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
); );
addTrackPublication(pub); addTrackPublication(pub);
[events, roomEvents].emit(LocalTrackPublishedEvent( [events, room.events].emit(LocalTrackPublishedEvent(
participant: this, participant: this,
publication: pub, publication: pub,
)); ));
@@ -193,14 +187,15 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
final track = pub.track; final track = pub.track;
if (track != null) { if (track != null) {
if (engine.connectOptions.stopLocalTrackOnUnpublish) { final roomOptions = room.roomOptions ?? const RoomOptions();
if (roomOptions.stopLocalTrackOnUnpublish) {
await track.stop(); await track.stop();
} }
final sender = track.transceiver?.sender; final sender = track.transceiver?.sender;
if (sender != null) { if (sender != null) {
try { try {
await engine.publisher?.pc.removeTrack(sender); await room.engine.publisher?.pc.removeTrack(sender);
} catch (_) { } catch (_) {
logger.warning('[$objectId] rtc.removeTrack() did throw ${_}'); logger.warning('[$objectId] rtc.removeTrack() did throw ${_}');
} }
@@ -208,13 +203,13 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
// doesn't make sense to negotiate if already disposed // doesn't make sense to negotiate if already disposed
if (!isDisposed) { if (!isDisposed) {
// manual negotiation since track changed // manual negotiation since track changed
await engine.negotiate(); await room.engine.negotiate();
} }
} }
} }
if (notify) { if (notify) {
[events, roomEvents].emit(LocalTrackUnpublishedEvent( [events, room.events].emit(LocalTrackUnpublishedEvent(
participant: this, participant: this,
publication: pub, publication: pub,
)); ));
@@ -239,7 +234,7 @@ class LocalParticipant extends Participant<LocalTrackPublication> {
), ),
); );
await engine.sendDataPacket(packet); await room.engine.sendDataPacket(packet);
} }
/// for internal use /// for internal use
+6 -10
View File
@@ -7,7 +7,7 @@ import '../logger.dart';
import '../managers/event.dart'; import '../managers/event.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../publication/track_publication.dart'; import '../publication/track_publication.dart';
import '../rtc_engine.dart'; import '../room.dart';
import '../support/disposable.dart'; import '../support/disposable.dart';
import '../track/track.dart'; import '../track/track.dart';
import '../types.dart'; import '../types.dart';
@@ -25,9 +25,9 @@ import 'remote_participant.dart';
/// can not be instantiated directly. /// can not be instantiated directly.
abstract class Participant<T extends TrackPublication> abstract class Participant<T extends TrackPublication>
extends DisposableChangeNotifier with EventsEmittable<ParticipantEvent> { extends DisposableChangeNotifier with EventsEmittable<ParticipantEvent> {
/// Reference to [RTCEngine] /// Reference to [Room]
@internal @internal
final RTCEngine engine; final Room room;
/// Map of track sid => published track /// Map of track sid => published track
final Map<String, T> trackPublications = {}; final Map<String, T> trackPublications = {};
@@ -53,9 +53,6 @@ abstract class Participant<T extends TrackPublication>
/// Connection quality between the [Participant] and the server. /// Connection quality between the [Participant] and the server.
ConnectionQuality _connectionQuality = ConnectionQuality.unknown; ConnectionQuality _connectionQuality = ConnectionQuality.unknown;
// Suppport for multiple event listeners.
final EventsEmitter<RoomEvent> roomEvents;
/// when the participant joined the room /// when the participant joined the room
DateTime get joinedAt { DateTime get joinedAt {
final pi = _participantInfo; final pi = _participantInfo;
@@ -95,10 +92,9 @@ abstract class Participant<T extends TrackPublication>
bool get hasInfo => _participantInfo != null; bool get hasInfo => _participantInfo != null;
Participant({ Participant({
required this.engine, required this.room,
required this.sid, required this.sid,
required this.identity, required this.identity,
required this.roomEvents,
}) { }) {
// Any event emitted will trigger ChangeNotifier // Any event emitted will trigger ChangeNotifier
events.listen((event) { events.listen((event) {
@@ -134,7 +130,7 @@ abstract class Participant<T extends TrackPublication>
final changed = _participantInfo?.metadata != md; final changed = _participantInfo?.metadata != md;
metadata = md; metadata = md;
if (changed) { if (changed) {
[events, roomEvents].emit(ParticipantMetadataUpdatedEvent( [events, room.events].emit(ParticipantMetadataUpdatedEvent(
participant: this, participant: this,
)); ));
} }
@@ -144,7 +140,7 @@ abstract class Participant<T extends TrackPublication>
void updateConnectionQuality(ConnectionQuality quality) { void updateConnectionQuality(ConnectionQuality quality) {
if (_connectionQuality == quality) return; if (_connectionQuality == quality) return;
_connectionQuality = quality; _connectionQuality = quality;
[events, roomEvents].emit(ParticipantConnectionQualityUpdatedEvent( [events, room.events].emit(ParticipantConnectionQualityUpdatedEvent(
participant: this, participant: this,
connectionQuality: _connectionQuality, connectionQuality: _connectionQuality,
)); ));
+8 -12
View File
@@ -9,7 +9,6 @@ import '../logger.dart';
import '../managers/event.dart'; import '../managers/event.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../publication/remote_track_publication.dart'; import '../publication/remote_track_publication.dart';
import '../rtc_engine.dart';
import '../track/remote/audio.dart'; import '../track/remote/audio.dart';
import '../track/remote/video.dart'; import '../track/remote/video.dart';
import '../types.dart'; import '../types.dart';
@@ -34,26 +33,23 @@ class RemoteParticipant extends Participant<RemoteTrackPublication> {
.toList(); .toList();
RemoteParticipant({ RemoteParticipant({
required RTCEngine engine, required Room room,
required String sid, required String sid,
required String identity, required String identity,
required EventsEmitter<RoomEvent> roomEvents,
}) : super( }) : super(
engine: engine, room: room,
sid: sid, sid: sid,
identity: identity, identity: identity,
roomEvents: roomEvents,
); );
RemoteParticipant.fromInfo({ RemoteParticipant.fromInfo({
required RTCEngine engine, required Room room,
required lk_models.ParticipantInfo info, required lk_models.ParticipantInfo info,
required EventsEmitter<RoomEvent> roomEvents, required EventsEmitter<RoomEvent> roomEvents,
}) : super( }) : super(
engine: engine, room: room,
sid: info.sid, sid: info.sid,
identity: info.identity, identity: info.identity,
roomEvents: roomEvents,
) { ) {
updateFromInfo(info); updateFromInfo(info);
} }
@@ -118,7 +114,7 @@ class RemoteParticipant extends Participant<RemoteTrackPublication> {
await pub.updateTrack(track); await pub.updateTrack(track);
addTrackPublication(pub); addTrackPublication(pub);
[events, roomEvents].emit(TrackSubscribedEvent( [events, room.events].emit(TrackSubscribedEvent(
participant: this, participant: this,
track: track, track: track,
publication: pub, publication: pub,
@@ -167,7 +163,7 @@ class RemoteParticipant extends Participant<RemoteTrackPublication> {
participant: this, participant: this,
publication: pub, publication: pub,
); );
[events, roomEvents].emit(event); [events, room.events].emit(event);
} }
} }
@@ -194,7 +190,7 @@ class RemoteParticipant extends Participant<RemoteTrackPublication> {
// if has track // if has track
if (track != null) { if (track != null) {
await track.stop(); await track.stop();
[events, roomEvents].emit(TrackUnsubscribedEvent( [events, room.events].emit(TrackUnsubscribedEvent(
participant: this, participant: this,
track: track, track: track,
publication: pub, publication: pub,
@@ -202,7 +198,7 @@ class RemoteParticipant extends Participant<RemoteTrackPublication> {
} }
if (notify) { if (notify) {
[events, roomEvents].emit(TrackUnpublishedEvent( [events, room.events].emit(TrackUnpublishedEvent(
participant: this, participant: this,
publication: pub, publication: pub,
)); ));
@@ -33,12 +33,12 @@ class LocalTrackPublication<T extends LocalTrack> extends TrackPublication<T> {
// listen for track muted events // listen for track muted events
..on<TrackMuteUpdatedEvent>((event) { ..on<TrackMuteUpdatedEvent>((event) {
// send signal to server // send signal to server
participant.engine.signalClient.sendMuteTrack(sid, event.muted); participant.room.engine.signalClient.sendMuteTrack(sid, event.muted);
// emit events // emit events
final newEvent = event.muted final newEvent = event.muted
? TrackMutedEvent(participant: participant, track: this) ? TrackMutedEvent(participant: participant, track: this)
: TrackUnmutedEvent(participant: participant, track: this); : TrackUnmutedEvent(participant: participant, track: this);
[participant.events, participant.roomEvents].emit(newEvent); [participant.events, participant.room.events].emit(newEvent);
}); });
// dispose listener when the track is disposed // dispose listener when the track is disposed
newValue.onDispose(() => listener.dispose()); newValue.onDispose(() => listener.dispose());
@@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:livekit_client/livekit_client.dart';
import '../events.dart'; import '../events.dart';
import '../extensions.dart'; import '../extensions.dart';
import '../internal/events.dart'; import '../internal/events.dart';
@@ -141,7 +142,7 @@ class RemoteTrackPublication<T extends RemoteTrack>
} }
logger.fine('[Visibility] Sending to server ${settings.toProto3Json()}'); logger.fine('[Visibility] Sending to server ${settings.toProto3Json()}');
participant.engine.signalClient.sendUpdateTrackSettings(settings); participant.room.engine.signalClient.sendUpdateTrackSettings(settings);
} }
@internal @internal
@@ -151,9 +152,11 @@ class RemoteTrackPublication<T extends RemoteTrack>
// Only listen for visibility updates if video optimization is on // Only listen for visibility updates if video optimization is on
// and the attached track is a video track // and the attached track is a video track
final roomOptions = participant.room.roomOptions ?? const RoomOptions();
//
if (didUpdate && if (didUpdate &&
newValue != null && newValue != null &&
participant.engine.connectOptions.optimizeVideo && roomOptions.optimizeVideo &&
newValue.kind == lk_models.TrackType.VIDEO) { newValue.kind == lk_models.TrackType.VIDEO) {
// //
// Attach visibility event listener (if video track) // Attach visibility event listener (if video track)
@@ -194,7 +197,7 @@ class RemoteTrackPublication<T extends RemoteTrack>
// Ideally, we should wait for WebRTC's onRemoveTrack event // Ideally, we should wait for WebRTC's onRemoveTrack event
// but it does not work reliably across platforms. // but it does not work reliably across platforms.
// So for now we will assume remove track succeeded. // So for now we will assume remove track succeeded.
[participant.events, participant.roomEvents].emit(TrackUnsubscribedEvent( [participant.events, participant.room.events].emit(TrackUnsubscribedEvent(
participant: participant, participant: participant,
track: track!, track: track!,
publication: this, publication: this,
@@ -210,7 +213,7 @@ class RemoteTrackPublication<T extends RemoteTrack>
trackSids: [sid], trackSids: [sid],
subscribe: subscribed, subscribe: subscribed,
); );
participant.engine.signalClient.sendUpdateSubscription(subscription); participant.room.engine.signalClient.sendUpdateSubscription(subscription);
} }
void _sendUpdateTrackSettings() { void _sendUpdateTrackSettings() {
@@ -221,6 +224,6 @@ class RemoteTrackPublication<T extends RemoteTrack>
if (kind == lk_models.TrackType.VIDEO) { if (kind == lk_models.TrackType.VIDEO) {
settings.quality = _videoQuality; settings.quality = _videoQuality;
} }
participant.engine.signalClient.sendUpdateTrackSettings(settings); participant.room.engine.signalClient.sendUpdateTrackSettings(settings);
} }
} }
+20 -18
View File
@@ -42,6 +42,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
UnmodifiableMapView<String, RemoteParticipant> get participants => UnmodifiableMapView<String, RemoteParticipant> get participants =>
UnmodifiableMapView(_participants); UnmodifiableMapView(_participants);
ConnectOptions? connectOptions;
RoomOptions? roomOptions;
/// the current participant /// the current participant
LocalParticipant? localParticipant; LocalParticipant? localParticipant;
@@ -57,15 +61,15 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
UnmodifiableListView<Participant> get activeSpeakers => UnmodifiableListView<Participant> get activeSpeakers =>
UnmodifiableListView<Participant>(_activeSpeakers); UnmodifiableListView<Participant>(_activeSpeakers);
final RTCEngine engine; late final engine = RTCEngine(room: this);
// suppport for multiple event listeners // suppport for multiple event listeners
late final _engineListener = engine.createListener(); late final _engineListener = engine.createListener();
Room({ Room({
RTCEngine? engine, this.connectOptions,
ConnectOptions? connectOptions, this.roomOptions,
}) : engine = engine ?? RTCEngine() { }) {
// //
_setUpListeners(); _setUpListeners();
@@ -83,21 +87,23 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
// dispose all listeners for RTCEngine // dispose all listeners for RTCEngine
await _engineListener.dispose(); await _engineListener.dispose();
// dispose the engine // dispose the engine
await this.engine.dispose(); await engine.dispose();
}); });
} }
Future<void> connect( Future<void> connect(
String url, String url,
String token, { String token, {
ConnectOptions? options, ConnectOptions? connectOptions,
RTCConfiguration? rtcConfig, RoomOptions? roomOptions,
}) async { }) async {
// // update options if provided
final joinResponse = await engine.join( this.connectOptions = connectOptions ?? this.connectOptions;
this.roomOptions = roomOptions ?? this.roomOptions;
final joinResponse = await engine.connect(
url, url,
token, token,
connectOptions: options,
); );
sid = joinResponse.room.sid; sid = joinResponse.room.sid;
@@ -115,11 +121,8 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
); );
localParticipant = LocalParticipant( localParticipant = LocalParticipant(
engine: engine, room: this,
info: joinResponse.participant, info: joinResponse.participant,
defaultVideoPublishOptions: options?.defaultVideoPublishOptions,
defaultAudioPublishOptions: options?.defaultAudioPublishOptions,
roomEvents: events,
); );
for (final info in joinResponse.otherParticipants) { for (final info in joinResponse.otherParticipants) {
@@ -177,7 +180,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
); );
} on TrackSubscriptionExceptionEvent catch (event) { } on TrackSubscriptionExceptionEvent catch (event) {
logger.warning('addSubscribedMediaTrack() throwed ${event}'); logger.warning('addSubscribedMediaTrack() throwed ${event}');
[participant.roomEvents, participant.events].emit(event); [participant.room.events, participant.events].emit(event);
} catch (exception) { } catch (exception) {
// We don't want to pass up any exception so catch everything here. // We don't want to pass up any exception so catch everything here.
logger.warning( logger.warning(
@@ -206,14 +209,13 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
if (info == null) { if (info == null) {
participant = RemoteParticipant( participant = RemoteParticipant(
engine: engine, room: this,
sid: sid, sid: sid,
identity: '', identity: '',
roomEvents: events,
); );
} else { } else {
participant = RemoteParticipant.fromInfo( participant = RemoteParticipant.fromInfo(
engine: engine, room: this,
info: info, info: info,
roomEvents: events, roomEvents: events,
); );
+27 -26
View File
@@ -28,11 +28,10 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
static const _reliableDCLabel = '_reliable'; static const _reliableDCLabel = '_reliable';
static const _maxReconnectAttempts = 5; static const _maxReconnectAttempts = 5;
final SignalClient signalClient; // Reference to the Room
// config for RTCPeerConnection final Room room;
RTCConfiguration? rtcConfig;
ConnectOptions connectOptions = const ConnectOptions(); final SignalClient signalClient;
@internal @internal
PCTransport? publisher; PCTransport? publisher;
@@ -63,7 +62,7 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
bool _subscriberPrimary = false; bool _subscriberPrimary = false;
// server-provided ice servers // server-provided ice servers
List<lk_rtc.ICEServer> _providedIceServers = []; List<lk_rtc.ICEServer> _serverProvidedIceServers = [];
// internal // internal
int _reconnectAttempts = 0; int _reconnectAttempts = 0;
@@ -73,6 +72,7 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
final delays = CancelableDelayManager(); final delays = CancelableDelayManager();
RTCEngine({ RTCEngine({
required this.room,
SignalClient? signalClient, SignalClient? signalClient,
}) : signalClient = signalClient ?? SignalClient() { }) : signalClient = signalClient ?? SignalClient() {
if (kDebugMode) { if (kDebugMode) {
@@ -91,25 +91,18 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
}); });
} }
Future<lk_rtc.JoinResponse> join( Future<lk_rtc.JoinResponse> connect(
String url, String url,
String token, { String token,
RTCConfiguration? rtcConfig, ) async {
ConnectOptions? connectOptions,
}) async {
this.url = url; this.url = url;
this.token = token; this.token = token;
this.rtcConfig = rtcConfig;
if (connectOptions != null) {
this.connectOptions = connectOptions;
}
// connect to rtc server // connect to rtc server
await signalClient.connect( await signalClient.connect(
url, url,
token, token,
options: this.connectOptions, connectOptions: room.connectOptions,
); );
// wait for join response // wait for join response
@@ -252,7 +245,11 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
try { try {
// isReconnecting = true; // isReconnecting = true;
_connectionState = ConnectionState.reconnecting; _connectionState = ConnectionState.reconnecting;
await signalClient.reconnect(url, token); await signalClient.reconnect(
url,
token,
connectOptions: room.connectOptions,
);
if (publisher == null || subscriber == null) { if (publisher == null || subscriber == null) {
throw UnexpectedStateException('publisher or subscribers is null'); throw UnexpectedStateException('publisher or subscribers is null');
@@ -294,17 +291,21 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
return; return;
} }
RTCConfiguration? config; // RTCConfiguration? config;
// use server-provided iceServers if not provided by user // use server-provided iceServers if not provided by user
if ((rtcConfig?.iceServers?.isEmpty ?? true) && final connectOptions = room.connectOptions ?? const ConnectOptions();
_providedIceServers.isNotEmpty) { final serverIceServers =
final iceServers = _providedIceServers.map((e) => e.toSDKType()).toList(); _serverProvidedIceServers.map((e) => e.toSDKType()).toList();
config = (rtcConfig ?? const RTCConfiguration())
.copyWith(iceServers: iceServers); RTCConfiguration rtcConfiguration = connectOptions.rtcConfiguration;
if (serverIceServers.isNotEmpty) {
// use server provided iceServers if exists
rtcConfiguration = connectOptions.rtcConfiguration
.copyWith(iceServers: serverIceServers);
} }
publisher = await PCTransport.create(config); publisher = await PCTransport.create(rtcConfiguration);
subscriber = await PCTransport.create(config); subscriber = await PCTransport.create(rtcConfiguration);
publisher?.pc.onIceCandidate = (rtc.RTCIceCandidate candidate) { publisher?.pc.onIceCandidate = (rtc.RTCIceCandidate candidate) {
logger.fine('publisher onIceCandidate'); logger.fine('publisher onIceCandidate');
@@ -497,7 +498,7 @@ class RTCEngine extends Disposable with EventsEmittable<EngineEvent> {
// create peer connections // create peer connections
_connectionState = ConnectionState.connected; _connectionState = ConnectionState.connected;
_subscriberPrimary = event.response.subscriberPrimary; _subscriberPrimary = event.response.subscriberPrimary;
_providedIceServers = event.response.iceServers; _serverProvidedIceServers = event.response.iceServers;
logger.fine('onConnected subscriberPrimary: ${_subscriberPrimary}, ' logger.fine('onConnected subscriberPrimary: ${_subscriberPrimary}, '
'serverVersion: ${event.response.serverVersion}, ' 'serverVersion: ${event.response.serverVersion}, '
+8 -13
View File
@@ -19,14 +19,10 @@ import 'utils.dart';
class SignalClient extends Disposable with EventsEmittable<SignalEvent> { class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
// //
final ProtocolVersion protocol;
bool _connected = false; bool _connected = false;
LiveKitWebSocket? _ws; LiveKitWebSocket? _ws;
SignalClient({ SignalClient() {
this.protocol = ProtocolVersion.protocol5,
}) {
events.listen((event) { events.listen((event) {
logger.fine('[SignalEvent] $event'); logger.fine('[SignalEvent] $event');
}); });
@@ -42,13 +38,12 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
Future<void> connect( Future<void> connect(
String uriString, String uriString,
String token, { String token, {
required ConnectOptions options, ConnectOptions? connectOptions,
}) async { }) async {
final rtcUri = Utils.buildUri( final rtcUri = Utils.buildUri(
uriString, uriString,
token: token, token: token,
options: options, connectOptions: connectOptions,
protocol: protocol,
); );
try { try {
@@ -65,10 +60,9 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
final validateUri = Utils.buildUri( final validateUri = Utils.buildUri(
uriString, uriString,
token: token, token: token,
options: options, connectOptions: connectOptions,
validate: true, validate: true,
forceSecure: rtcUri.isSecureScheme, forceSecure: rtcUri.isSecureScheme,
protocol: protocol,
); );
// Attempt Validation // Attempt Validation
@@ -89,8 +83,9 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
Future<void> reconnect( Future<void> reconnect(
String uriString, String uriString,
String token, String token, {
) async { ConnectOptions? connectOptions,
}) async {
_connected = false; _connected = false;
await _ws?.dispose(); await _ws?.dispose();
_ws = null; _ws = null;
@@ -99,7 +94,7 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
uriString, uriString,
token: token, token: token,
reconnect: true, reconnect: true,
protocol: protocol, connectOptions: connectOptions,
); );
_ws = await LiveKitWebSocket.connect( _ws = await LiveKitWebSocket.connect(
-1
View File
@@ -1,5 +1,4 @@
// ignore: avoid_web_libraries_in_flutter // ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html; import 'dart:html' as html;
// ignore: implementation_imports // ignore: implementation_imports
+4 -4
View File
@@ -5,10 +5,10 @@ import 'extensions.dart';
typedef CancelListenFunc = Function(); typedef CancelListenFunc = Function();
enum ProtocolVersion { enum ProtocolVersion {
protocol2, v2,
protocol3, v3,
protocol4, v4,
protocol5, v5,
} }
enum ConnectionState { enum ConnectionState {
+5 -6
View File
@@ -6,7 +6,6 @@ import 'extensions.dart';
import 'livekit.dart'; import 'livekit.dart';
import 'options.dart'; import 'options.dart';
import 'track/options.dart'; import 'track/options.dart';
import 'types.dart';
extension UriExt on Uri { extension UriExt on Uri {
bool get isSecureScheme => ['https', 'wss'].contains(scheme); bool get isSecureScheme => ['https', 'wss'].contains(scheme);
@@ -17,12 +16,13 @@ class Utils {
static Uri buildUri( static Uri buildUri(
String uriString, { String uriString, {
required String token, required String token,
ConnectOptions? options, ConnectOptions? connectOptions,
bool reconnect = false, bool reconnect = false,
bool validate = false, bool validate = false,
bool forceSecure = false, bool forceSecure = false,
required ProtocolVersion protocol,
}) { }) {
connectOptions ??= const ConnectOptions();
final Uri uri = Uri.parse(uriString); final Uri uri = Uri.parse(uriString);
final useSecure = uri.isSecureScheme || forceSecure; final useSecure = uri.isSecureScheme || forceSecure;
@@ -45,10 +45,9 @@ class Utils {
pathSegments: pathSegments, pathSegments: pathSegments,
queryParameters: <String, String>{ queryParameters: <String, String>{
'access_token': token, 'access_token': token,
if (options != null) 'auto_subscribe': connectOptions.autoSubscribe ? '1' : '0',
'auto_subscribe': options.autoSubscribe ? '1' : '0',
if (reconnect) 'reconnect': '1', if (reconnect) 'reconnect': '1',
'protocol': protocol.toStringValue(), 'protocol': connectOptions.protocolVersion.toStringValue(),
'sdk': 'flutter', 'sdk': 'flutter',
'version': LiveKitClient.version, 'version': LiveKitClient.version,
}, },