From 0f5a758cd4ff5e2e8bf12c4a5536afe058d37c1b Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 7 Dec 2021 17:02:58 +0700 Subject: [PATCH] Create room without connecting (#49) * initial commit * fix example compile * format * clean up * fix check * missing import --- example/lib/pages/room.dart | 25 ++++--- example/lib/widgets/controls.dart | 10 +-- lib/src/livekit.dart | 12 +++- lib/src/room.dart | 106 +++++++++++------------------- lib/src/rtc_engine.dart | 7 +- 5 files changed, 73 insertions(+), 87 deletions(-) diff --git a/example/lib/pages/room.dart b/example/lib/pages/room.dart index 6627af1..c2dace2 100644 --- a/example/lib/pages/room.dart +++ b/example/lib/pages/room.dart @@ -68,13 +68,13 @@ class _RoomPageState extends State { if (result != true) return; // video will fail when running in ios simulator try { - await widget.room.localParticipant.setCameraEnabled(true); + await widget.room.localParticipant?.setCameraEnabled(true); } catch (error) { print('could not publish video: $error'); await context.showErrorDialog(error); } try { - await widget.room.localParticipant.setMicrophoneEnabled(true); + await widget.room.localParticipant?.setMicrophoneEnabled(true); } catch (error) { print('could not publish audio: $error'); await context.showErrorDialog(error); @@ -117,10 +117,13 @@ class _RoomPageState extends State { b.joinedAt.millisecondsSinceEpoch; }); - if (participants.length > 1) { - participants.insert(1, widget.room.localParticipant); - } else { - participants.add(widget.room.localParticipant); + final localParticipant = widget.room.localParticipant; + if (localParticipant != null) { + if (participants.length > 1) { + participants.insert(1, localParticipant); + } else { + participants.add(localParticipant); + } } setState(() { this.participants = participants; @@ -147,10 +150,12 @@ class _RoomPageState extends State { ), ), ), - SafeArea( - top: false, - child: ControlsWidget(widget.room), - ), + if (widget.room.localParticipant != null) + SafeArea( + top: false, + child: + ControlsWidget(widget.room, widget.room.localParticipant!), + ), ], ), ); diff --git a/example/lib/widgets/controls.dart b/example/lib/widgets/controls.dart index 69ea92b..5dc961a 100644 --- a/example/lib/widgets/controls.dart +++ b/example/lib/widgets/controls.dart @@ -14,11 +14,11 @@ class ControlsWidget extends StatefulWidget { final Room room; final LocalParticipant participant; - ControlsWidget( - this.room, { + const ControlsWidget( + this.room, + this.participant, { Key? key, - }) : participant = room.localParticipant, - super(key: key); + }) : super(key: key); @override State createState() => _ControlsWidgetState(); @@ -132,7 +132,7 @@ class _ControlsWidgetState extends State { void _onTapSendData() async { final result = await context.showSendDataDialog(); if (result == true) { - await widget.room.localParticipant.publishData( + await widget.participant.publishData( utf8.encode('This is a sample data message'), ); } diff --git a/lib/src/livekit.dart b/lib/src/livekit.dart index b8b02bf..af63094 100644 --- a/lib/src/livekit.dart +++ b/lib/src/livekit.dart @@ -11,10 +11,18 @@ class LiveKitClient { String url, String token, { ConnectOptions? options, - }) => - Room.connect( + }) async { + final room = Room(); + try { + await room.connect( url, token, options: options, ); + return room; + } catch (error) { + await room.dispose(); + rethrow; + } + } } diff --git a/lib/src/room.dart b/lib/src/room.dart index f939437..e4a69b8 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -42,13 +42,13 @@ class Room extends DisposableChangeNotifier with EventsEmittable { UnmodifiableMapView(_participants); /// the current participant - late final LocalParticipant localParticipant; + LocalParticipant? localParticipant; /// name of the room - final String name; + String? name; /// sid of the room - final String sid; + String? sid; List _activeSpeakers = []; @@ -61,29 +61,13 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // suppport for multiple event listeners late final _engineListener = engine.createListener(); - /// internal use - /// {@nodoc} - Room._({ - required this.engine, - required lk_rtc.JoinResponse joinResponse, + Room({ + RTCEngine? engine, ConnectOptions? connectOptions, - }) : sid = joinResponse.room.sid, - name = joinResponse.room.name { + }) : engine = engine ?? RTCEngine() { // _setUpListeners(); - localParticipant = LocalParticipant( - engine: engine, - info: joinResponse.participant, - defaultVideoPublishOptions: connectOptions?.defaultVideoPublishOptions, - defaultAudioPublishOptions: connectOptions?.defaultAudioPublishOptions, - roomEvents: events, - ); - - for (final info in joinResponse.otherParticipants) { - _getOrCreateRemoteParticipant(info.sid, info); - } - // Any event emitted will trigger ChangeNotifier events.listen((event) { logger.fine('[RoomEvent] $event, will notifyListeners()'); @@ -94,63 +78,51 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // dispose events await events.dispose(); // dispose local participant - await localParticipant.dispose(); + await localParticipant?.dispose(); // dispose all listeners for RTCEngine await _engineListener.dispose(); // dispose the engine - await engine.dispose(); + await this.engine.dispose(); }); } - static Future connect( + Future connect( String url, String token, { ConnectOptions? options, RTCConfiguration? rtcConfig, }) async { // - final engine = RTCEngine( - rtcConfig, + final joinResponse = await engine.join( + url, + token, + connectOptions: options, ); - Room? room; + sid = joinResponse.room.sid; + name = joinResponse.room.name; - try { - final joinResponse = await engine.join( - url, - token, - connectOptions: options, - ); + logger.fine( + 'Connected to LiveKit server, version: ${joinResponse.serverVersion}'); - logger.fine( - 'Connected to LiveKit server, version: ${joinResponse.serverVersion}'); + logger.fine('Waiting to engine connect...'); - // create Room first to listen to events - room = Room._( - engine: engine, - joinResponse: joinResponse, - connectOptions: options, - ); + // wait until engine is connected + await _engineListener.waitFor( + duration: Timeouts.connection, + onTimeout: () => throw ConnectException(), + ); - logger.fine('Waiting to engine connect...'); + localParticipant = LocalParticipant( + engine: engine, + info: joinResponse.participant, + defaultVideoPublishOptions: options?.defaultVideoPublishOptions, + defaultAudioPublishOptions: options?.defaultAudioPublishOptions, + roomEvents: events, + ); - // wait until engine is connected - await room._engineListener.waitFor( - duration: Timeouts.connection, - onTimeout: () => throw ConnectException(), - ); - - return room; - // catch any exception - } catch (_) { - // dispose engine if there was any exception while connecting - if (room != null) { - // room.dispose will also dispose engine - await room.dispose(); - } else { - await engine.dispose(); - } - rethrow; + for (final info in joinResponse.otherParticipants) { + _getOrCreateRemoteParticipant(info.sid, info); } } @@ -178,7 +150,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { (event) => _onSignalConnectionQualityUpdateEvent(event.updates)) ..on(_onDataMessageEvent) ..on((event) async { - final publication = localParticipant.trackPublications[event.sid]; + final publication = localParticipant?.trackPublications[event.sid]; if (event.muted) { await publication?.mute(); } else { @@ -266,7 +238,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { _participants.clear(); // clean up LocalParticipant - await localParticipant.unpublishAllTracks(); + await localParticipant?.unpublishAllTracks(); // clean up engine await engine.close(); @@ -285,8 +257,8 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // trigger change notifier only if list of participants membership is changed var hasChanged = false; for (final info in updates) { - if (localParticipant.sid == info.sid) { - localParticipant.updateFromInfo(info); + if (localParticipant?.sid == info.sid) { + localParticipant?.updateFromInfo(info); continue; } @@ -320,7 +292,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { for (final speaker in speakers) { Participant? p = _participants[speaker.sid]; - if (speaker.sid == localParticipant.sid) p = localParticipant; + if (speaker.sid == localParticipant?.sid) p = localParticipant; if (p == null) continue; p.audioLevel = speaker.level; @@ -346,7 +318,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // localParticipant & remote participants final allParticipants = { - localParticipant.sid: localParticipant, + if (localParticipant != null) localParticipant!.sid: localParticipant!, ..._participants, }; @@ -376,7 +348,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { List updates) { for (final entry in updates) { Participant? participant; - if (entry.participantSid == localParticipant.sid) { + if (entry.participantSid == localParticipant?.sid) { participant = localParticipant; } else { participant = _participants[entry.participantSid]; diff --git a/lib/src/rtc_engine.dart b/lib/src/rtc_engine.dart index bd53e65..0ccdf60 100644 --- a/lib/src/rtc_engine.dart +++ b/lib/src/rtc_engine.dart @@ -29,7 +29,7 @@ class RTCEngine extends Disposable with EventsEmittable { final SignalClient signalClient; // config for RTCPeerConnection - final RTCConfiguration? rtcConfig; + RTCConfiguration? rtcConfig; ConnectOptions connectOptions = const ConnectOptions(); @@ -71,8 +71,7 @@ class RTCEngine extends Disposable with EventsEmittable { final delays = CancelableDelayManager(); - RTCEngine( - this.rtcConfig, { + RTCEngine({ SignalClient? signalClient, }) : signalClient = signalClient ?? SignalClient() { if (kDebugMode) { @@ -94,11 +93,13 @@ class RTCEngine extends Disposable with EventsEmittable { Future join( String url, String token, { + RTCConfiguration? rtcConfig, ConnectOptions? connectOptions, }) async { this.url = url; this.token = token; + this.rtcConfig = rtcConfig; if (connectOptions != null) { this.connectOptions = connectOptions; }