feature: switch between front and back cameras

This commit is contained in:
David Zhao
2021-08-06 21:20:21 -07:00
parent f1a7591ec7
commit 509a037936
6 changed files with 77 additions and 10 deletions
+38 -1
View File
@@ -5,6 +5,8 @@ import 'options.dart';
import 'video_track.dart'; import 'video_track.dart';
class LocalVideoTrack extends VideoTrack { class LocalVideoTrack extends VideoTrack {
RTCRtpSender? get sender => transceiver?.sender;
LocalVideoTrack(String name, MediaStreamTrack mediaTrack, MediaStream stream) LocalVideoTrack(String name, MediaStreamTrack mediaTrack, MediaStream stream)
: super(name, mediaTrack, stream); : super(name, mediaTrack, stream);
@@ -14,6 +16,41 @@ class LocalVideoTrack extends VideoTrack {
options = LocalVideoTrackOptions(params: VideoPresets.qhd); 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<void> 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<MediaStream> _createCameraStream(
LocalVideoTrackOptions? options) async {
if (options == null) {
options = LocalVideoTrackOptions(params: VideoPresets.qhd);
}
try { try {
var stream = await navigator.mediaDevices.getUserMedia({ var stream = await navigator.mediaDevices.getUserMedia({
"audio": false, "audio": false,
@@ -24,7 +61,7 @@ class LocalVideoTrack extends VideoTrack {
return Future.error(TrackCreateError()); return Future.error(TrackCreateError());
} }
return LocalVideoTrack("camera", stream.getVideoTracks().first, stream); return stream;
} catch (e) { } catch (e) {
return Future.error(e); return Future.error(e);
} }
+5 -2
View File
@@ -3,9 +3,12 @@ class LocalVideoTrackOptions {
VideoParameter params; VideoParameter params;
LocalVideoTrackOptions({ LocalVideoTrackOptions({
required VideoParameter params, VideoParameter? params,
CameraPosition? position, CameraPosition? position,
}) : this.params = params { }) : params = VideoPresets.qhd {
if (params != null) {
this.params = params;
}
if (position != null) { if (position != null) {
this.position = position; this.position = position;
} }
@@ -49,6 +49,9 @@ class RemoteTrackPublication extends TrackPublication {
_participant.delegate?.onTrackUnmuted(_participant, this); _participant.delegate?.onTrackUnmuted(_participant, this);
_participant.roomDelegate?.onTrackUnmuted(_participant, this); _participant.roomDelegate?.onTrackUnmuted(_participant, this);
} }
if (subscribed) {
track?.mediaTrack.enabled = !val;
}
_participant.muteChanged(); _participant.muteChanged();
} }
+2
View File
@@ -22,6 +22,8 @@ class Track {
Track(this.kind, this.name, this.mediaTrack); Track(this.kind, this.name, this.mediaTrack);
bool get muted => mediaTrack.muted == null ? false : mediaTrack.muted!;
RTCRtpMediaType get mediaType { RTCRtpMediaType get mediaType {
switch (kind) { switch (kind) {
case TrackType.AUDIO: case TrackType.AUDIO:
+14 -5
View File
@@ -1,18 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../proto/livekit_models.pb.dart'; import '../proto/livekit_models.pb.dart';
import 'track.dart'; import 'track.dart';
class VideoTrack extends Track { /// A video track will notify when its mediaTrack has changed.
MediaStream? mediaStream; 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); : super(TrackType.VIDEO, name, mediaTrack);
MediaStream? get mediaStream => _mediaStream;
set mediaStream(MediaStream? stream) {
_mediaStream = stream;
notifyListeners();
}
@override @override
stop() { stop() {
super.stop(); super.stop();
mediaStream?.dispose(); _mediaStream?.dispose();
mediaStream = null; _mediaStream = null;
} }
} }
+15 -2
View File
@@ -23,22 +23,35 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
widget.track.addListener(_trackChanged);
_initRenderer(); _initRenderer();
} }
@override @override
void dispose() { void dispose() {
widget.track.removeListener(_trackChanged);
_renderer.dispose(); _renderer.dispose();
super.dispose(); super.dispose();
} }
_initRenderer() async { @override
await _renderer.initialize(); void didUpdateWidget(covariant VideoTrackRenderer oldWidget) {
oldWidget.track.removeListener(_trackChanged);
widget.track.addListener(_trackChanged);
super.didUpdateWidget(oldWidget);
}
_trackChanged() {
setState(() { setState(() {
_renderer.srcObject = widget.track.mediaStream; _renderer.srcObject = widget.track.mediaStream;
}); });
} }
_initRenderer() async {
await _renderer.initialize();
_trackChanged();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isLocal = widget.track is LocalVideoTrack; var isLocal = widget.track is LocalVideoTrack;