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
+34
View File
@@ -0,0 +1,34 @@
//
//
//
import 'package:async/async.dart';
class CancelableDelayManager {
//
final _delays = <CancelableOperation<void>>[];
// delay but cancelable
Future<void> waitFor(
Duration wait, {
Function? ifNotCancelled,
}) async {
final op = CancelableOperation<void>.fromFuture(
Future<void>.delayed(wait),
);
_delays.add(op);
await op.valueOrCancellation();
_delays.remove(op);
// if it was cancelled we probably don't want to execute it
if (!op.isCanceled) ifNotCancelled?.call();
}
Future<void> dispose() async {
// cancel all delays
if (_delays.isEmpty) return;
// make a copy so we don't mutate while iterating
final snapshot = List<CancelableOperation<void>>.from(_delays);
for (final op in snapshot) {
await op.cancel();
}
}
}
+114
View File
@@ -0,0 +1,114 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../errors.dart';
import '../events.dart';
import '../extensions.dart';
import '../logger.dart';
import '../types.dart';
// Type-safe, multi-listenable, dispose safe event handling
class EventsEmitter<T extends LiveKitEvent> extends EventsListenable<T> {
// suppport for multiple event listeners
final streamCtrl = StreamController<T>.broadcast(sync: false);
@override
EventsEmitter<T> get emitter => this;
void emit(T event) {
// do nothing if already closed
if (streamCtrl.isClosed) return;
// emit the event
streamCtrl.add(event);
}
@override
Future<void> dispose() async {
await streamCtrl.close();
await super.dispose();
}
}
// for listening only
class EventsListener<T extends LiveKitEvent> extends EventsListenable<T> {
@override
final EventsEmitter<T> emitter;
EventsListener({
required this.emitter,
});
}
// ensures all listeners will close on dispose
abstract class EventsListenable<T extends LiveKitEvent> {
// the emitter to listen to
EventsEmitter<T> get emitter;
// keep track of listeners to cancel later
final _listeners = <StreamSubscription<T>>[];
@mustCallSuper
Future<void> dispose() async {
// Stop listening to all events
logger.fine('${objectId} dispose() cancelling ${_listeners.length} event(s)');
for (final listener in _listeners) {
await listener.cancel();
}
}
// listens to all events, guaranteed to be cancelled on dispose
CancelListenFunc listen(Function(T) onEvent) {
final listener = emitter.streamCtrl.stream.listen(onEvent);
_listeners.add(listener);
// make a cancel func to cancel listening and remove from list in 1 call
_cancelFunc() async {
await listener.cancel();
_listeners.remove(listener);
logger.fine('${objectId} event was cancelled by func');
}
return _cancelFunc;
}
// convenience method to listen & filter a specific event type
CancelListenFunc on<E>(
Function(E) then, {
bool Function(E)? filter,
}) =>
listen((event) {
// event must be E
if (event is! E) return;
// filter must be true (if filter is used)
if (filter != null && !filter(event as E)) return;
// cast to E
then(event as E);
});
// waits for a specific event type
Future<void> waitFor<E>({
required Duration duration,
bool Function(E)? filter,
FutureOr<void> Function()? onTimeout,
}) async {
final completer = Completer<void>();
final _cancelFunc = on<E>(
(event) => completer.complete(),
filter: filter,
);
try {
// wait to complete with timeout
await completer.future.timeout(
duration,
onTimeout: onTimeout ?? () => throw TimeoutException(),
);
// do not catch exceptions and pass it up
} finally {
// always clean-up listener
await _cancelFunc.call();
}
}
}