From ff65370cbcd816abf0b84b5959643606da096af3 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 17 May 2022 10:51:38 +0900 Subject: [PATCH] Automatic `mirrorMode` for VideoView (#119) * update protos * impl 1 * re-compute when options update * minor fix --- lib/src/internal/events.dart | 12 ++++++++ lib/src/track/local/local.dart | 7 +++++ lib/src/widget/video_track_renderer.dart | 39 +++++++++++++++++++++--- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/lib/src/internal/events.dart b/lib/src/internal/events.dart index 7b223a0..ed051f1 100644 --- a/lib/src/internal/events.dart +++ b/lib/src/internal/events.dart @@ -4,6 +4,8 @@ import 'package:meta/meta.dart'; import '../events.dart'; import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_rtc.pb.dart' as lk_rtc; +import '../track/local/local.dart'; +import '../track/options.dart'; import '../track/track.dart'; import '../types/other.dart'; @@ -59,6 +61,16 @@ class TrackStreamUpdatedEvent with TrackEvent, InternalEvent { }); } +@internal +class LocalTrackOptionsUpdatedEvent with TrackEvent, InternalEvent { + final LocalTrack track; + final LocalTrackOptions options; + const LocalTrackOptionsUpdatedEvent({ + required this.track, + required this.options, + }); +} + // Used to notify muted state from Track to TrackPublication. @internal class InternalTrackMuteUpdatedEvent with TrackEvent, InternalEvent { diff --git a/lib/src/track/local/local.dart b/lib/src/track/local/local.dart index 69e4aa5..3e1b1a8 100644 --- a/lib/src/track/local/local.dart +++ b/lib/src/track/local/local.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import '../../events.dart'; import '../../exceptions.dart'; import '../../extensions.dart'; +import '../../internal/events.dart'; import '../../logger.dart'; import '../../participant/remote.dart'; import '../../proto/livekit_models.pb.dart' as lk_models; @@ -172,6 +173,12 @@ abstract class LocalTrack extends Track { // mark as started await start(); + + // notify so VideoView can re-compute mirror mode if necessary + events.emit(LocalTrackOptionsUpdatedEvent( + track: this, + options: currentOptions, + )); } @internal diff --git a/lib/src/widget/video_track_renderer.dart b/lib/src/widget/video_track_renderer.dart index 51b579c..66dc156 100644 --- a/lib/src/widget/video_track_renderer.dart +++ b/lib/src/widget/video_track_renderer.dart @@ -7,15 +7,24 @@ import '../internal/events.dart'; import '../managers/event.dart'; import '../track/local/local.dart'; import '../track/local/video.dart'; +import '../track/options.dart'; + +enum VideoViewMirrorMode { + auto, + off, + mirror, +} /// Widget that renders a [VideoTrack]. class VideoTrackRenderer extends StatefulWidget { final VideoTrack track; final rtc.RTCVideoViewObjectFit fit; + final VideoViewMirrorMode mirrorMode; const VideoTrackRenderer( this.track, { this.fit = rtc.RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, + this.mirrorMode = VideoViewMirrorMode.auto, Key? key, }) : super(key: key); @@ -56,9 +65,13 @@ class _VideoTrackRendererState extends State { await _listener?.dispose(); _listener = widget.track.createListener() ..on((event) { - if (mounted) { - _renderer.srcObject = event.stream; - } + if (!mounted) return; + _renderer.srcObject = event.stream; + }) + ..on((event) { + if (!mounted) return; + // force recompute of mirror mode + setState(() {}); }); } @@ -89,10 +102,28 @@ class _VideoTrackRendererState extends State { }); return rtc.RTCVideoView( _renderer, - mirror: widget.track is LocalVideoTrack, + mirror: _shouldMirror(), filterQuality: FilterQuality.medium, objectFit: widget.fit, ); }, ); + + bool _shouldMirror() { + // on + if (widget.mirrorMode == VideoViewMirrorMode.mirror) return true; + // auto + if (widget.mirrorMode == VideoViewMirrorMode.auto) { + final track = widget.track; + if (track is LocalVideoTrack) { + final options = track.currentOptions; + if (options is CameraCaptureOptions) { + // mirror if front camera + return options.cameraPosition == CameraPosition.front; + } + } + } + // default to false + return false; + } }