From 509a0379360ba55734f08f2eba59a01fa2da254b Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 6 Aug 2021 21:20:21 -0700 Subject: [PATCH] feature: switch between front and back cameras --- lib/src/track/local_video_track.dart | 39 ++++++++++++++++++++- lib/src/track/options.dart | 7 ++-- lib/src/track/remote_track_publication.dart | 3 ++ lib/src/track/track.dart | 2 ++ lib/src/track/video_track.dart | 19 +++++++--- lib/src/widget/video_track_renderer.dart | 17 +++++++-- 6 files changed, 77 insertions(+), 10 deletions(-) diff --git a/lib/src/track/local_video_track.dart b/lib/src/track/local_video_track.dart index 9523f6a..6048bd5 100644 --- a/lib/src/track/local_video_track.dart +++ b/lib/src/track/local_video_track.dart @@ -5,6 +5,8 @@ import 'options.dart'; import 'video_track.dart'; class LocalVideoTrack extends VideoTrack { + RTCRtpSender? get sender => transceiver?.sender; + LocalVideoTrack(String name, MediaStreamTrack mediaTrack, MediaStream stream) : super(name, mediaTrack, stream); @@ -14,6 +16,41 @@ class LocalVideoTrack extends VideoTrack { options = LocalVideoTrackOptions(params: VideoPresets.qhd); } + try { + var stream = await _createCameraStream(options); + return LocalVideoTrack("camera", stream.getVideoTracks().first, stream); + } catch (e) { + return Future.error(e); + } + } + + Future restartTrack([LocalVideoTrackOptions? options]) async { + if (sender == null) { + return Future.error(TrackCreateError('could not restart track')); + } + + if (options == null) { + options = LocalVideoTrackOptions(params: VideoPresets.qhd); + } + + try { + var stream = await _createCameraStream(options); + var track = stream.getVideoTracks().first; + mediaStream = stream; + await mediaTrack.stop(); + mediaTrack = track; + await sender?.replaceTrack(track); + } catch (e) { + return Future.error(e); + } + } + + static Future _createCameraStream( + LocalVideoTrackOptions? options) async { + if (options == null) { + options = LocalVideoTrackOptions(params: VideoPresets.qhd); + } + try { var stream = await navigator.mediaDevices.getUserMedia({ "audio": false, @@ -24,7 +61,7 @@ class LocalVideoTrack extends VideoTrack { return Future.error(TrackCreateError()); } - return LocalVideoTrack("camera", stream.getVideoTracks().first, stream); + return stream; } catch (e) { return Future.error(e); } diff --git a/lib/src/track/options.dart b/lib/src/track/options.dart index 8b31a27..8190841 100644 --- a/lib/src/track/options.dart +++ b/lib/src/track/options.dart @@ -3,9 +3,12 @@ class LocalVideoTrackOptions { VideoParameter params; LocalVideoTrackOptions({ - required VideoParameter params, + VideoParameter? params, CameraPosition? position, - }) : this.params = params { + }) : params = VideoPresets.qhd { + if (params != null) { + this.params = params; + } if (position != null) { this.position = position; } diff --git a/lib/src/track/remote_track_publication.dart b/lib/src/track/remote_track_publication.dart index 97bf2bd..3e53d01 100644 --- a/lib/src/track/remote_track_publication.dart +++ b/lib/src/track/remote_track_publication.dart @@ -49,6 +49,9 @@ class RemoteTrackPublication extends TrackPublication { _participant.delegate?.onTrackUnmuted(_participant, this); _participant.roomDelegate?.onTrackUnmuted(_participant, this); } + if (subscribed) { + track?.mediaTrack.enabled = !val; + } _participant.muteChanged(); } diff --git a/lib/src/track/track.dart b/lib/src/track/track.dart index 81ca266..bb837e2 100644 --- a/lib/src/track/track.dart +++ b/lib/src/track/track.dart @@ -22,6 +22,8 @@ class Track { Track(this.kind, this.name, this.mediaTrack); + bool get muted => mediaTrack.muted == null ? false : mediaTrack.muted!; + RTCRtpMediaType get mediaType { switch (kind) { case TrackType.AUDIO: diff --git a/lib/src/track/video_track.dart b/lib/src/track/video_track.dart index 8423a98..c09a2c0 100644 --- a/lib/src/track/video_track.dart +++ b/lib/src/track/video_track.dart @@ -1,18 +1,27 @@ +import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import '../proto/livekit_models.pb.dart'; import 'track.dart'; -class VideoTrack extends Track { - MediaStream? mediaStream; +/// A video track will notify when its mediaTrack has changed. +class VideoTrack extends Track with ChangeNotifier { + MediaStream? _mediaStream; - VideoTrack(String name, MediaStreamTrack mediaTrack, this.mediaStream) + VideoTrack(String name, MediaStreamTrack mediaTrack, this._mediaStream) : super(TrackType.VIDEO, name, mediaTrack); + MediaStream? get mediaStream => _mediaStream; + + set mediaStream(MediaStream? stream) { + _mediaStream = stream; + notifyListeners(); + } + @override stop() { super.stop(); - mediaStream?.dispose(); - mediaStream = null; + _mediaStream?.dispose(); + _mediaStream = null; } } diff --git a/lib/src/widget/video_track_renderer.dart b/lib/src/widget/video_track_renderer.dart index 65a0d1f..325f753 100644 --- a/lib/src/widget/video_track_renderer.dart +++ b/lib/src/widget/video_track_renderer.dart @@ -23,22 +23,35 @@ class _VideoTrackRendererState extends State { @override void initState() { super.initState(); + widget.track.addListener(_trackChanged); _initRenderer(); } @override void dispose() { + widget.track.removeListener(_trackChanged); _renderer.dispose(); super.dispose(); } - _initRenderer() async { - await _renderer.initialize(); + @override + void didUpdateWidget(covariant VideoTrackRenderer oldWidget) { + oldWidget.track.removeListener(_trackChanged); + widget.track.addListener(_trackChanged); + super.didUpdateWidget(oldWidget); + } + + _trackChanged() { setState(() { _renderer.srcObject = widget.track.mediaStream; }); } + _initRenderer() async { + await _renderer.initialize(); + _trackChanged(); + } + @override Widget build(BuildContext context) { var isLocal = widget.track is LocalVideoTrack;