feature: switch between front and back cameras
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user