Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 36 additions & 38 deletions lib/src/publication/remote.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ class RemoteTrackPublication<T extends RemoteTrack> extends TrackPublication<T>
// used to report renderer visibility to the server
// and optimize
lk_rtc.UpdateTrackSettings? _lastSentTrackSettings;
Timer? _visibilityTimer;

Function(lk_rtc.UpdateTrackSettings)? _setPendingTrackSettingsUpdateRequest;
Function? _cancelPendingTrackSettingsUpdateRequest;
Expand All @@ -112,7 +111,6 @@ class RemoteTrackPublication<T extends RemoteTrack> extends TrackPublication<T>
// register dispose func
onDispose(() async {
_cancelPendingTrackSettingsUpdateRequest?.call();
_visibilityTimer?.cancel();
// this object is responsible for disposing track
await this.track?.dispose();
});
Expand All @@ -133,41 +131,44 @@ class RemoteTrackPublication<T extends RemoteTrack> extends TrackPublication<T>
_metadataMuted = info.muted;
}

void _computeVideoViewVisibility({
bool quick = false,
}) {
//
Size maxOfSizes(Size s1, Size s2) => Size(
max(s1.width, s2.width),
max(s1.height, s2.height),
);
Size _maxOfSizes(Size s1, Size s2) => Size(
max(s1.width, s2.width),
max(s1.height, s2.height),
);

final videoTrack = track as VideoTrack;
/// Requests the server to send a new track with the given settings.
@internal
void updateVideoViewSize(String viewViewId, Size size, {bool quick = false}) {
assert(track is VideoTrack, 'updateVideoViewSize can only be called on video tracks');
final videoTrack = track as VideoTrack?;
if (videoTrack == null) return;
if (videoTrack.viewSizes[viewViewId] == null) {
logger.warning(
'Trying to update video view size ${size} for a view ${viewViewId} that is not registered or was unregistered',
);
return;
}
if (videoTrack.viewSizes[viewViewId] == size) return;

final settings = lk_rtc.UpdateTrackSettings(
trackSids: [sid],
disabled: true,
);
print('[Visibility] VideoView did resize to ${size.width}x${size.height}, quick: ${quick}');
videoTrack.viewSizes[viewViewId] = size;

final settings = lk_rtc.UpdateTrackSettings(trackSids: [sid], disabled: true);

// filter visible build contexts
final viewSizes = videoTrack.viewKeys
.map((e) => e.currentContext)
.nonNulls
.map((e) => e.findRenderObject() as RenderBox?)
.nonNulls
.where((e) => e.hasSize)
.map((e) => e.size);
final videoViewsSizes = <Size>[
for (final MapEntry(key: _, value: size) in videoTrack.viewSizes.entries)
if (size > Size.zero) size,
];

logger.finer('[Visibility] ${track?.sid} watching ${viewSizes.length} views...');
logger.finer('[Visibility] ${track?.sid} watching ${videoViewsSizes.length} views...');

if (viewSizes.isNotEmpty) {
// compute largest size
final largestSize = viewSizes.reduce((value, element) => maxOfSizes(value, element));
if (videoViewsSizes.isNotEmpty) {
final largestVideoView = videoViewsSizes.reduce((value, element) => _maxOfSizes(value, element));

settings
..disabled = false
..width = largestSize.width.ceil()
..height = largestSize.height.ceil();
..width = largestVideoView.width.round()
..height = largestVideoView.height.round();
}

// Only send new settings to server if it changed
Expand Down Expand Up @@ -196,24 +197,21 @@ class RemoteTrackPublication<T extends RemoteTrack> extends TrackPublication<T>
if (didUpdate) {
// Stop current visibility timer (if exists)
_cancelPendingTrackSettingsUpdateRequest?.call();
_visibilityTimer?.cancel();

final roomOptions = participant.room.roomOptions;
if (roomOptions.adaptiveStream && newValue is RemoteVideoTrack) {
// Start monitoring visibility
_visibilityTimer = Timer.periodic(
const Duration(milliseconds: 300),
(_) => _computeVideoViewVisibility(),
);

newValue.onVideoViewBuild = (_) {
newValue.onVideoViewBuild = (viewId, size) {
logger.finer('[Visibility] VideoView did build');
if (_lastSentTrackSettings?.disabled == true) {
// quick enable
_cancelPendingTrackSettingsUpdateRequest?.call();
_computeVideoViewVisibility(quick: true);
updateVideoViewSize(viewId, size, quick: true);
}
};
newValue.onViewViewResize = (viewId, size) {
// schedule update to server
updateVideoViewSize(viewId, size);
};
}

if (newValue != null) {
Expand Down
19 changes: 11 additions & 8 deletions lib/src/track/local/local.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,24 @@ import 'video.dart';
/// Used to group [LocalVideoTrack] and [RemoteVideoTrack].
mixin VideoTrack on Track {
@internal
final List<GlobalKey> viewKeys = [];
final Map<String, Size> viewSizes = {};

@internal
Function(Key)? onVideoViewBuild;
Function(String, Size)? onVideoViewBuild;

@internal
GlobalKey addViewKey() {
final key = GlobalKey();
viewKeys.add(key);
return key;
Function(String, Size)? onViewViewResize;

@internal
String registerVideoView([Size size = Size.zero]) {
final id = Track.uuid.v4();
viewSizes[id] = size;
return id;
}

@internal
void removeViewKey(GlobalKey key) {
viewKeys.remove(key);
void unregisterVideoView(String id) {
viewSizes.remove(id);
}
}

Expand Down
51 changes: 34 additions & 17 deletions lib/src/widgets/video_track_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
bool _rendererReadyForWeb = false;
double? _aspectRatio;
EventsListener<TrackEvent>? _listener;
// Used to compute visibility information
late GlobalKey _internalKey;
late String _internalId;
Key get _internalKey => ValueKey('${widget.track.sid}_$_internalId');

Future<rtc.VideoRenderer> _initializeRenderer() async {
if (lkPlatformIs(PlatformType.iOS) && widget.renderMode == VideoRenderMode.platformView) {
Expand Down Expand Up @@ -142,7 +142,11 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
if (widget.cachedRenderer != null) {
_renderer = widget.cachedRenderer;
}
_internalKey = widget.track.addViewKey();
_internalId = widget.track.registerVideoView();
WidgetsBindingCompatible.instance?.addPostFrameCallback((timeStamp) {
widget.track.onVideoViewBuild?.call(_internalId, _computedSize);
});

if (kIsWeb) {
unawaited(() async {
await _initializeRenderer();
Expand All @@ -154,14 +158,21 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {

@override
void dispose() {
widget.track.removeViewKey(_internalKey);
widget.track.unregisterVideoView(_internalId);
unawaited(_listener?.dispose());
if (widget.autoDisposeRenderer) {
disposeRenderer();
}
super.dispose();
}

Size get _computedSize {
if (!mounted) return Size.zero;
final box = context.findRenderObject() as RenderBox?;
if (box == null) return Size.zero;
return box.size * MediaQuery.of(context).devicePixelRatio;
}

Future<void> _attach() async {
_renderer?.srcObject = widget.track.mediaStream;
await _listener?.dispose();
Expand All @@ -188,8 +199,9 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
void didUpdateWidget(covariant VideoTrackRenderer oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.track != oldWidget.track) {
oldWidget.track.removeViewKey(_internalKey);
_internalKey = widget.track.addViewKey();
oldWidget.track.unregisterVideoView(_internalId);
_internalId = widget.track.registerVideoView();

unawaited(() async {
await _attach();
}());
Expand All @@ -200,14 +212,14 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
}
}

Widget _videoViewForWeb() => !_rendererReadyForWeb
Widget _videoViewForWeb(Size size) => !_rendererReadyForWeb
? Container()
: Builder(
key: _internalKey,
builder: (ctx) {
// let it render before notifying build
WidgetsBindingCompatible.instance?.addPostFrameCallback((timeStamp) {
widget.track.onVideoViewBuild?.call(_internalKey);
widget.track.onViewViewResize?.call(_internalId, size);
});
return rtc.RTCVideoView(
_renderer! as rtc.RTCVideoRenderer,
Expand Down Expand Up @@ -238,7 +250,7 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
);
}

Widget _videoViewForNative() => FutureBuilder(
Widget _videoViewForNative(Size size) => FutureBuilder(
future: _initializeRenderer(),
builder: (context, snapshot) {
if ((snapshot.hasData && _renderer != null) ||
Expand All @@ -248,7 +260,7 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
builder: (ctx) {
// let it render before notifying build
WidgetsBindingCompatible.instance?.addPostFrameCallback((timeStamp) {
widget.track.onVideoViewBuild?.call(_internalKey);
widget.track.onViewViewResize?.call(_internalId, size);
});

if (!lkPlatformIsMobile() || widget.track is! LocalVideoTrack) {
Expand Down Expand Up @@ -278,14 +290,19 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
// different rendering methods for web and native.
@override
Widget build(BuildContext context) {
final child = kIsWeb ? _videoViewForWeb() : _videoViewForNative();

if (widget.fit == VideoViewFit.cover) {
return child;
}

final videoView = LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final size = constraints.biggest * MediaQuery.of(context).devicePixelRatio;

final child = kIsWeb ? _videoViewForWeb(size) : _videoViewForNative(size);

if (widget.fit == VideoViewFit.cover) {
return child;
}

if (size.isEmpty) {
return child;
}
if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) {
return child;
}
Expand Down Expand Up @@ -316,7 +333,7 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
},
);

if (widget.autoCenter) {
if (widget.autoCenter && widget.fit == VideoViewFit.contain) {
return Center(child: videoView);
} else {
return videoView;
Expand Down
Loading