Switch to new event system (#9)

* `SignalEvents` and `synchronized` mode for `EventsListenable`

* cascade syntax

* clean up

* `RoomEvents`

* `ParticipantEvents`

* all events implemented

* make it compile with M1 macs

* dispose logic

* cleaner connect logic

* ask to publish

* fix initial EngineTrackAddedEvent glitch

* fix unpublishTrack bug

* clean up

* better events docs

* organize event types

* clean up

* cleaner wait logic

* wait for event instead of using Completer

* notifyListeners
This commit is contained in:
Hiroshi Horie
2021-09-24 23:37:26 +09:00
committed by GitHub
parent f4bd82a919
commit 6445b8d2a8
36 changed files with 1270 additions and 948 deletions
+19 -1
View File
@@ -2,6 +2,24 @@ import 'package:flutter/material.dart';
extension LKExampleExt on BuildContext {
//
Future<bool?> showPublishDialog() => showDialog<bool>(
context: this,
builder: (ctx) => AlertDialog(
title: const Text('Publish'),
content: const Text('Would you like to publish your Camera & Mic ?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('NO'),
),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('YES'),
),
],
),
);
Future<void> showErrorDialog(dynamic exception) => showDialog<void>(
context: this,
builder: (ctx) => AlertDialog(
@@ -74,7 +92,7 @@ extension LKExampleExt on BuildContext {
context: this,
builder: (ctx) => AlertDialog(
title: const Text('Received data'),
content: Text('"${data}"'),
content: Text(data),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, true),
+2
View File
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_example/theme.dart';
import 'package:logging/logging.dart';
import 'pages/connect.dart';
void main() {
print('This is a test for ${SignalTrickleEvent} test.');
// configure logs for debugging
Logger.root.level = Level.FINE;
Logger.root.onRecord.listen((record) {
+7 -3
View File
@@ -62,6 +62,9 @@ class _ConnectPageState extends State<ConnectPage> {
_busy = true;
});
// Save for next time
await _writePrefs();
print('Connecting with url: ${_uriCtrl.text}, token: ${_tokenCtrl.text}...');
final room = await LiveKitClient.connect(
@@ -74,9 +77,6 @@ class _ConnectPageState extends State<ConnectPage> {
),
);
// Save for next time
await _writePrefs();
await Navigator.push<void>(
ctx,
MaterialPageRoute(builder: (_) => RoomPage(room)),
@@ -122,10 +122,14 @@ class _ConnectPageState extends State<ConnectPage> {
mainAxisSize: MainAxisSize.min,
children: [
TextField(
enableSuggestions: false,
autocorrect: false,
controller: _uriCtrl,
decoration: const InputDecoration(labelText: 'URL'),
),
TextField(
enableSuggestions: false,
autocorrect: false,
controller: _tokenCtrl,
decoration: const InputDecoration(labelText: 'Token'),
),
+64 -57
View File
@@ -2,12 +2,12 @@ import 'dart:convert';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:provider/provider.dart';
import '../exts.dart';
import '../widgets/controls.dart';
import '../widgets/participant.dart';
import '../exts.dart';
class RoomPage extends StatefulWidget {
//
@@ -22,29 +22,51 @@ class RoomPage extends StatefulWidget {
State<StatefulWidget> createState() => _RoomPageState();
}
class _RoomPageState extends State<RoomPage> with RoomDelegate {
class _RoomPageState extends State<RoomPage> {
//
List<Participant> participants = [];
late final _listener = EventsListener<LiveKitEvent>(widget.room.events);
@override
void initState() {
super.initState();
widget.room.delegate = this;
widget.room.addListener(_onChange);
_onConnected();
widget.room.addListener(_onRoomDidUpdate);
_setUpListeners();
_sortParticipants();
WidgetsBinding.instance?.addPostFrameCallback((_) => _askPublish());
}
@override
void dispose() {
widget.room.delegate = null;
widget.room.removeListener(_onChange);
// always dispose listener
(() async {
widget.room.removeListener(_onRoomDidUpdate);
await _listener.dispose();
await widget.room.dispose();
})();
super.dispose();
}
void _onConnected() async {
void _setUpListeners() => _listener
..on<RoomDisconnectedEvent>((_) => Navigator.pop(context))
..on<DataReceivedEvent>((event) {
String decoded = 'Failed to decode';
try {
decoded = utf8.decode(event.data);
} catch (_) {
print('Failed to decode: $_');
}
context.showDataReceivedDialog(decoded);
});
void _askPublish() async {
final result = await context.showPublishDialog();
if (result != true) return;
// video will fail when running in ios simulator
try {
final localVideo = await LocalVideoTrack.createCameraTrack(); // Defaults to camera
// Create video track
final localVideo = await LocalVideoTrack.createCameraTrack();
// Try to publish the video
await widget.room.localParticipant.publishVideoTrack(
localVideo,
// options: TrackPublishOptions(
@@ -52,20 +74,22 @@ class _RoomPageState extends State<RoomPage> with RoomDelegate {
// videoEncoding: VideoParameters.presetQVGA169.encoding,
// ),
);
} catch (e) {
print('could not publish video: $e');
// Create mic track
final localAudio = await LocalAudioTrack.create();
// // Try to publish audio
await widget.room.localParticipant.publishAudioTrack(localAudio);
} catch (error) {
print('could not publish video: $error');
await context.showErrorDialog(error);
}
final localAudio = await LocalAudioTrack.create();
await widget.room.localParticipant.publishAudioTrack(localAudio);
sortParticipants();
}
void _onChange() {
sortParticipants();
void _onRoomDidUpdate() {
_sortParticipants();
}
void sortParticipants() {
void _sortParticipants() {
List<Participant> participants = [];
participants.addAll(widget.room.participants.values);
// sort speakers for the grid
@@ -106,48 +130,31 @@ class _RoomPageState extends State<RoomPage> with RoomDelegate {
});
}
@override
void onDataReceived(RemoteParticipant participant, List<int> data) async {
await context.showDataReceivedDialog(utf8.decode(data));
}
@override
void onDisconnected() {
print('disconnected: $context');
Navigator.pop(context);
}
@override
Widget build(BuildContext context) => Scaffold(
// with a provider, any child/descendent widget can be updated if they
// are a Consumer of Room.
body: ChangeNotifierProvider.value(
value: widget.room,
child: Column(
children: [
Expanded(
child: participants.isNotEmpty
? ParticipantWidget(participants.first)
: Container()),
SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: math.max(0, participants.length - 1),
itemBuilder: (BuildContext context, int index) => Container(
width: 100,
height: 100,
padding: const EdgeInsets.all(2),
child: ParticipantWidget(participants[index + 1], quality: VideoQuality.LOW),
),
body: Column(
children: [
Expanded(
child:
participants.isNotEmpty ? ParticipantWidget(participants.first) : Container()),
SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: math.max(0, participants.length - 1),
itemBuilder: (BuildContext context, int index) => Container(
width: 100,
height: 100,
padding: const EdgeInsets.all(2),
child: ParticipantWidget(participants[index + 1], quality: VideoQuality.LOW),
),
),
SafeArea(
top: false,
child: ControlsWidget(widget.room),
),
],
),
),
SafeArea(
top: false,
child: ControlsWidget(widget.room),
),
],
),
);
}
+2 -2
View File
@@ -102,8 +102,8 @@ class _ControlsWidgetState extends State<ControlsWidget> {
//
final lp = widget.room.localParticipant;
for (final tracks in lp.videoTracks) {
await lp.unpublishTrack(tracks.track!);
for (final track in lp.videoTracks) {
await lp.unpublishTrack(track.sid);
}
try {
+1 -1
View File
@@ -21,7 +21,7 @@ class ParticipantWidget extends StatefulWidget {
State<StatefulWidget> createState() => _ParticipantWidgetState();
}
class _ParticipantWidgetState extends State<ParticipantWidget> with ParticipantDelegate {
class _ParticipantWidgetState extends State<ParticipantWidget> {
//
TrackPublication? videoPub;
TrackPublication? audioPub;