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:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user