Video optimizations (#18)
* use events for `VideoTrackRenderer` * logger * implemented visibility detection * optimize code with 1 param debounce func * stop pending visibility event when disposed * clean up * mark internal * report to track * call dispose on pub and track * use internal events to receive visibility events * clean up visibility for disposed track * listen to track visibility events on pub * re-design * quickly re-enable * clean up * clean up
This commit is contained in:
@@ -18,7 +18,7 @@ class LocalTrackPublication extends TrackPublication {
|
||||
// register dispose func
|
||||
onDispose(() async {
|
||||
// this object is responsible for disposing track
|
||||
await track.dispose();
|
||||
await this.track?.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:livekit_client/src/internal/events.dart';
|
||||
import 'package:livekit_client/src/logger.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@@ -6,9 +11,23 @@ import '../extensions.dart';
|
||||
import '../participant/remote_participant.dart';
|
||||
import '../proto/livekit_models.pb.dart' as lk_models;
|
||||
import '../proto/livekit_rtc.pb.dart' as lk_rtc;
|
||||
import '../utils.dart';
|
||||
import 'track.dart';
|
||||
import 'track_publication.dart';
|
||||
|
||||
class RendererVisibility {
|
||||
final String rendererId;
|
||||
final String trackId;
|
||||
final bool visible;
|
||||
final Size size;
|
||||
RendererVisibility({
|
||||
required this.rendererId,
|
||||
required this.trackId,
|
||||
required this.visible,
|
||||
required this.size,
|
||||
});
|
||||
}
|
||||
|
||||
/// Represents a track publication from a RemoteParticipant. Provides methods to
|
||||
/// control if we should subscribe to the track, and its quality (for video).
|
||||
class RemoteTrackPublication extends TrackPublication {
|
||||
@@ -17,17 +36,124 @@ class RemoteTrackPublication extends TrackPublication {
|
||||
lk_rtc.VideoQuality _videoQuality = lk_rtc.VideoQuality.HIGH;
|
||||
lk_rtc.VideoQuality get videoQuality => _videoQuality;
|
||||
|
||||
// used to report renderer visibility to the server
|
||||
// and optimize
|
||||
final _visibilities = <String, RendererVisibility>{};
|
||||
Function(void)? _visibilityDidUpdate;
|
||||
Function? _cancelVisibilityDebounceFunc;
|
||||
|
||||
RemoteTrackPublication(
|
||||
lk_models.TrackInfo info,
|
||||
this._participant, [
|
||||
Track? track,
|
||||
]) : super.fromInfo(info) {
|
||||
this.track = track;
|
||||
// register dispose func
|
||||
onDispose(() async {
|
||||
_cancelVisibilityDebounceFunc?.call();
|
||||
// this object is responsible for disposing track
|
||||
await track?.dispose();
|
||||
await this.track?.dispose();
|
||||
});
|
||||
|
||||
_visibilityDidUpdate = Utils.createDebounceFunc(
|
||||
_shouldComputeVisibilityUpdate,
|
||||
cancelFunc: (func) => _cancelVisibilityDebounceFunc = func,
|
||||
wait: const Duration(seconds: 2),
|
||||
);
|
||||
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
// called any time visibility info updates
|
||||
// from one of the renderers
|
||||
void _onVideoRendererVisibilityUpdateEvent(
|
||||
TrackVisibilityUpdatedEvent event) {
|
||||
final info = event.info;
|
||||
final trackSid = event.track.sid;
|
||||
if (trackSid != null && info != null) {
|
||||
logger.fine('[Visibility] ${event.rendererId} did update '
|
||||
'track: ${event.track.sid} '
|
||||
'visibleFraction: ${info.visibleFraction} '
|
||||
'size: ${info.size}');
|
||||
_visibilities[event.rendererId] = RendererVisibility(
|
||||
rendererId: event.rendererId,
|
||||
trackId: trackSid,
|
||||
visible: info.visibleFraction > 0,
|
||||
size: info.size,
|
||||
);
|
||||
|
||||
// quickly enable if currently disabled
|
||||
if (!enabled && _hasVisibleRenderers()) {
|
||||
logger.fine('[Visibility] Trying to re-enable quickly');
|
||||
_cancelVisibilityDebounceFunc?.call();
|
||||
_shouldComputeVisibilityUpdate(null);
|
||||
} else {
|
||||
_visibilityDidUpdate?.call(null);
|
||||
}
|
||||
} else {
|
||||
// widget as been disposed, but track still exists
|
||||
logger.fine('[Visibility] ${event.rendererId} was removed');
|
||||
_visibilities.remove(event.rendererId);
|
||||
_visibilityDidUpdate?.call(null);
|
||||
}
|
||||
|
||||
logger.fine(
|
||||
'[Visibility] Ids ${_visibilities.values.map((e) => e.rendererId)}');
|
||||
}
|
||||
|
||||
bool _hasVisibleRenderers() =>
|
||||
_visibilities.values.firstWhereOrNull((e) => e.visible) != null;
|
||||
|
||||
void _shouldComputeVisibilityUpdate(void _) {
|
||||
//
|
||||
Size maxSize(Size s1, Size s2) => Size(
|
||||
max(s1.width, s2.width),
|
||||
max(s1.height, s2.height),
|
||||
);
|
||||
|
||||
_disabled = !_hasVisibleRenderers();
|
||||
|
||||
final settings = lk_rtc.UpdateTrackSettings(
|
||||
trackSids: [sid],
|
||||
disabled: _disabled,
|
||||
);
|
||||
|
||||
if (!_disabled) {
|
||||
final largest = _visibilities.values
|
||||
.map((e) => e.size)
|
||||
.reduce((value, element) => maxSize(value, element));
|
||||
settings.width = largest.width.floor();
|
||||
settings.height = largest.height.floor();
|
||||
}
|
||||
|
||||
logger.fine('[Visibility] Sending to server ${settings.toProto3Json()}');
|
||||
_participant.client.sendUpdateTrackSettings(settings);
|
||||
}
|
||||
|
||||
@override
|
||||
set track(Track? newValue) {
|
||||
if (super.track != newValue) {
|
||||
logger.fine('setTrack ${newValue} $sid ${objectId}');
|
||||
// dispose previous track (if exists)
|
||||
super.track?.dispose();
|
||||
super.track = newValue;
|
||||
|
||||
if (newValue != null) {
|
||||
//
|
||||
// Attach visibility event listener
|
||||
//
|
||||
final listener = newValue.createListener();
|
||||
listener.on<TrackVisibilityUpdatedEvent>(
|
||||
_onVideoRendererVisibilityUpdateEvent);
|
||||
newValue.onDispose(() async {
|
||||
await listener.dispose();
|
||||
// consider all views are disposed when track is null
|
||||
_visibilities.clear();
|
||||
// _visibilityDidUpdate?.call(null);
|
||||
});
|
||||
}
|
||||
|
||||
_visibilityDidUpdate?.call(null);
|
||||
}
|
||||
}
|
||||
|
||||
set videoQuality(lk_rtc.VideoQuality val) {
|
||||
|
||||
@@ -3,15 +3,19 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../events.dart';
|
||||
import '../extensions.dart';
|
||||
import '../logger.dart';
|
||||
import '../managers/event.dart';
|
||||
import '../proto/livekit_models.pb.dart' as lk_models;
|
||||
import '../support/disposable.dart';
|
||||
|
||||
/// Wrapper around a MediaStreamTrack with additional metadata.
|
||||
/// Base for [AudioTrack] and [VideoTrack],
|
||||
/// can not be instantiated directly.
|
||||
abstract class Track extends DisposableChangeNotifier {
|
||||
abstract class Track extends DisposableChangeNotifier
|
||||
with EventsEmittable<TrackEvent> {
|
||||
static const uuid = Uuid();
|
||||
static const cameraName = 'camera';
|
||||
static const screenShareName = 'screen';
|
||||
|
||||
@@ -31,7 +35,13 @@ abstract class Track extends DisposableChangeNotifier {
|
||||
this.kind,
|
||||
this.name,
|
||||
this.mediaStreamTrack,
|
||||
);
|
||||
) {
|
||||
onDispose(() async {
|
||||
logger.fine('${objectId} onDispose()');
|
||||
// dispose events
|
||||
await events.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
bool get muted =>
|
||||
mediaStreamTrack.muted == null ? false : mediaStreamTrack.muted!;
|
||||
@@ -52,7 +62,6 @@ abstract class Track extends DisposableChangeNotifier {
|
||||
var cid = _cid ?? mediaStreamTrack.id;
|
||||
|
||||
if (cid == null) {
|
||||
const uuid = Uuid();
|
||||
cid = uuid.v4();
|
||||
_cid = cid;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../proto/livekit_models.pb.dart' as lk_models;
|
||||
import 'track.dart';
|
||||
|
||||
/// A video track will notify when its mediaTrack has changed.
|
||||
class VideoTrack extends Track {
|
||||
//
|
||||
rtc.MediaStream _mediaStream;
|
||||
|
||||
VideoTrack(
|
||||
String name,
|
||||
rtc.MediaStreamTrack mediaTrack,
|
||||
this._mediaStream,
|
||||
// this._client,
|
||||
) : super(
|
||||
lk_models.TrackType.VIDEO,
|
||||
name,
|
||||
@@ -22,6 +23,7 @@ class VideoTrack extends Track {
|
||||
|
||||
/// internal use
|
||||
/// {@nodoc}
|
||||
@internal
|
||||
void setMediaStream(rtc.MediaStream stream) {
|
||||
_mediaStream = stream;
|
||||
notifyListeners();
|
||||
|
||||
Reference in New Issue
Block a user