From 35aee5fbe0013128e5bc9207a8d3cdca5f27db9f Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 5 Jul 2026 20:46:57 +0900 Subject: [PATCH] refactor(track): deprecate flutter_webrtc-typed Track getters Begins removing flutter_webrtc types from the public API: Track.mediaStream/mediaStreamTrack/sender/receiver/mediaType are deprecated in favor of @internal rtcStream/rtcTrack/rtcSender/rtcReceiver accessors, transceiver is marked @internal, and all internal call sites are migrated off the deprecated getters. --- .changes/deprecate-webrtc-types | 1 + lib/src/core/engine.dart | 4 +- lib/src/e2ee/e2ee_manager.dart | 2 +- lib/src/participant/local.dart | 10 ++-- .../preconnect/pre_connect_audio_buffer.dart | 4 +- lib/src/publication/local.dart | 2 +- lib/src/track/audio_visualizer_native.dart | 2 +- lib/src/track/audio_visualizer_web.dart | 2 +- lib/src/track/local/audio.dart | 8 +-- lib/src/track/local/local.dart | 14 ++--- lib/src/track/local/video.dart | 24 ++++---- lib/src/track/remote/audio.dart | 10 ++-- lib/src/track/remote/video.dart | 8 +-- lib/src/track/track.dart | 57 +++++++++++++++---- lib/src/track/web/_audio_analyser.dart | 2 +- lib/src/widgets/video_track_renderer.dart | 8 +-- 16 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 .changes/deprecate-webrtc-types diff --git a/.changes/deprecate-webrtc-types b/.changes/deprecate-webrtc-types new file mode 100644 index 000000000..1bca800db --- /dev/null +++ b/.changes/deprecate-webrtc-types @@ -0,0 +1 @@ +minor type="changed" "Deprecate rtc-typed Track getters (mediaStream, mediaStreamTrack, sender, receiver, mediaType); flutter_webrtc types are being removed from the public API" diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index 97700d65d..7c0971dc8 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -1488,7 +1488,7 @@ extension EngineInternalMethods on Engine { throw UnexpectedConnectionState('publisher is closed'); } - if (track.mediaStreamTrack.kind == 'video' && opts is VideoPublishOptions) { + if (track.rtcTrack.kind == 'video' && opts is VideoPublishOptions) { track.codec = opts.videoCodec; } final transceiverInit = rtc.RTCRtpTransceiverInit( @@ -1498,7 +1498,7 @@ extension EngineInternalMethods on Engine { transceiverInit.sendEncodings = encodings; } final transceiver = await publisher!.pc.addTransceiver( - track: track.mediaStreamTrack, + track: track.rtcTrack, kind: track is LocalVideoTrack ? rtc.RTCRtpMediaType.RTCRtpMediaTypeVideo : rtc.RTCRtpMediaType.RTCRtpMediaTypeAudio, diff --git a/lib/src/e2ee/e2ee_manager.dart b/lib/src/e2ee/e2ee_manager.dart index 7f4cd1cb7..3e1424a59 100644 --- a/lib/src/e2ee/e2ee_manager.dart +++ b/lib/src/e2ee/e2ee_manager.dart @@ -54,7 +54,7 @@ class E2EEManager { return; } final frameCryptor = await _addRtpSender( - sender: event.publication.track!.sender!, + sender: event.publication.track!.rtcSender!, identity: event.participant.identity, sid: event.publication.sid); if (kIsWeb && event.publication.track!.codec != null) { diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index d8149a465..57582fa7d 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -162,7 +162,7 @@ class LocalParticipant extends Participant { LocalAudioTrack track, { AudioPublishOptions? publishOptions, }) async { - if (audioTrackPublications.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) { + if (audioTrackPublications.any((e) => e.track?.rtcTrack.id == track.rtcTrack.id)) { throw TrackPublishException('track already exists'); } @@ -215,7 +215,7 @@ class LocalParticipant extends Participant { ); // addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web) track.transceiver = await room.engine.publisher?.pc.addTransceiver( - track: track.mediaStreamTrack, + track: track.rtcTrack, kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeAudio, init: transceiverInit, ); @@ -276,7 +276,7 @@ class LocalParticipant extends Participant { LocalVideoTrack track, { VideoPublishOptions? publishOptions, }) async { - if (videoTrackPublications.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) { + if (videoTrackPublications.any((e) => e.track?.rtcTrack.id == track.rtcTrack.id)) { throw TrackPublishException('track already exists'); } @@ -329,7 +329,7 @@ class LocalParticipant extends Participant { // getSettings() is only implemented for Web & Mobile try { // try to use getSettings for more accurate resolution - final settings = track.mediaStreamTrack.getSettings(); + final settings = track.rtcTrack.getSettings(); if ((settings['width'] is int && settings['width'] as int > 0) && (settings['height'] is int && settings['height'] as int > 0)) { dimensions = dimensions.copyWith(width: settings['width'] as int); @@ -475,7 +475,7 @@ class LocalParticipant extends Participant { logger.fine('publishVideoTrack publisher: ${room.engine.publisher}'); track.transceiver = await room.engine.publisher?.pc.addTransceiver( - track: track.mediaStreamTrack, + track: track.rtcTrack, kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeVideo, init: transceiverInit, ); diff --git a/lib/src/preconnect/pre_connect_audio_buffer.dart b/lib/src/preconnect/pre_connect_audio_buffer.dart index c04eca8fd..20921d608 100644 --- a/lib/src/preconnect/pre_connect_audio_buffer.dart +++ b/lib/src/preconnect/pre_connect_audio_buffer.dart @@ -120,14 +120,14 @@ class PreConnectAudioBuffer { _agentReadyManager.setTimer(timeout, timeoutReason: 'Agent did not become ready within timeout'); _localTrack = await LocalAudioTrack.create(); - logger.fine('[Preconnect audio] created local track ${_localTrack!.mediaStreamTrack.id}'); + logger.fine('[Preconnect audio] created local track ${_localTrack!.rtcTrack.id}'); final rendererId = Uuid().v4(); logger.info('Starting audio renderer with rendererId: $rendererId'); _audioCapture = createAudioFrameCapture(); final result = await _audioCapture!.start( - track: _localTrack!.mediaStreamTrack, + track: _localTrack!.rtcTrack, rendererId: rendererId, sampleRate: _requestSampleRate, channels: 1, diff --git a/lib/src/publication/local.dart b/lib/src/publication/local.dart index 7521f85cb..1318ae274 100644 --- a/lib/src/publication/local.dart +++ b/lib/src/publication/local.dart @@ -46,7 +46,7 @@ class LocalTrackPublication extends TrackPublication { Future unmute({bool stopOnMute = true}) async => await track?.unmute(stopOnMute: stopOnMute); lk_rtc.TrackPublishedResponse toPBTrackPublishedResponse() => lk_rtc.TrackPublishedResponse( - cid: track?.mediaStreamTrack.id, + cid: track?.rtcTrack.id, track: latestInfo, ); } diff --git a/lib/src/track/audio_visualizer_native.dart b/lib/src/track/audio_visualizer_native.dart index 926049c45..f06b89b51 100644 --- a/lib/src/track/audio_visualizer_native.dart +++ b/lib/src/track/audio_visualizer_native.dart @@ -16,7 +16,7 @@ class AudioVisualizerNative extends AudioVisualizer { final AudioTrack? _audioTrack; final AudioVisualizerOptions visualizerOptions; - MediaStreamTrack get mediaStreamTrack => _audioTrack!.mediaStreamTrack; + MediaStreamTrack get mediaStreamTrack => _audioTrack!.rtcTrack; AudioVisualizerNative(this._audioTrack, {required this.visualizerOptions}) { onDispose(() async { diff --git a/lib/src/track/audio_visualizer_web.dart b/lib/src/track/audio_visualizer_web.dart index 8ab28276e..319905d4b 100644 --- a/lib/src/track/audio_visualizer_web.dart +++ b/lib/src/track/audio_visualizer_web.dart @@ -16,7 +16,7 @@ class AudioVisualizerWeb extends AudioVisualizer { MultiBandTrackVolumeOptions options = MultiBandTrackVolumeOptions(); Timer? _timer; final AudioTrack? _audioTrack; - MediaStreamTrack get mediaStreamTrack => _audioTrack!.mediaStreamTrack; + MediaStreamTrack get mediaStreamTrack => _audioTrack!.rtcTrack; final AudioVisualizerOptions visualizerOptions; diff --git a/lib/src/track/local/audio.dart b/lib/src/track/local/audio.dart index 77966d2b5..40ed099bf 100644 --- a/lib/src/track/local/audio.dart +++ b/lib/src/track/local/audio.dart @@ -59,7 +59,7 @@ class LocalAudioTrack extends LocalTrack with AudioTrack, LocalAudioManagementMi Future setAudioProcessingOptions(track_options.AudioProcessingOptions options) async { final nextOptions = currentOptions.copyWith(processing: options); final response = await Native.setAudioProcessingOptions( - mediaStreamTrack.id!, + rtcTrack.id!, options.toMap(), ); @@ -103,7 +103,7 @@ class LocalAudioTrack extends LocalTrack with AudioTrack, LocalAudioManagementMi try { final stats = await getSenderStats(); - if (stats != null && prevStats != null && sender != null) { + if (stats != null && prevStats != null && rtcSender != null) { final bitrate = computeBitrateForSenderStats(stats, prevStats); _currentBitrate = bitrate; events.emit(AudioSenderStatsEvent(stats: stats, currentBitrate: bitrate)); @@ -118,13 +118,13 @@ class LocalAudioTrack extends LocalTrack with AudioTrack, LocalAudioManagementMi } Future getSenderStats() async { - if (sender == null) { + if (rtcSender == null) { return null; } late List stats; try { - stats = await sender!.getStats(); + stats = await rtcSender!.getStats(); } catch (e) { rethrow; } diff --git a/lib/src/track/local/local.dart b/lib/src/track/local/local.dart index 3a3cea8c6..7c68abd28 100644 --- a/lib/src/track/local/local.dart +++ b/lib/src/track/local/local.dart @@ -82,7 +82,7 @@ mixin AudioTrack on Track { }) { final group = _captureGroups.putIfAbsent( options, - () => _AudioCaptureGroup(track: mediaStreamTrack, options: options), + () => _AudioCaptureGroup(track: rtcTrack, options: options), ); group.renderers.add(onFrame); @@ -219,12 +219,12 @@ abstract class LocalTrack extends Track { if (didStop) { logger.fine('Stopping mediaStreamTrack...'); try { - await mediaStreamTrack.stop(); + await rtcTrack.stop(); } catch (error) { logger.severe('MediaStreamTrack.stop() did throw $error'); } try { - await mediaStream.dispose(); + await rtcStream.dispose(); } catch (error) { logger.severe('MediaStreamTrack.dispose() did throw $error'); } @@ -289,7 +289,7 @@ abstract class LocalTrack extends Track { Future restartTrack([ LocalTrackOptions? options, ]) async { - if (sender == null) throw TrackCreateException('could not restart track'); + if (rtcSender == null) throw TrackCreateException('could not restart track'); if (options != null && currentOptions.runtimeType != options.runtimeType) { throw Exception('options must be a ${currentOptions.runtimeType}'); } @@ -309,7 +309,7 @@ abstract class LocalTrack extends Track { // replace track on sender try { - await sender?.replaceTrack(newTrack); + await rtcSender?.replaceTrack(newTrack); if (this is LocalVideoTrack) { final videoTrack = this as LocalVideoTrack; await videoTrack.replaceTrackForMultiCodecSimulcast(newTrack); @@ -347,8 +347,8 @@ abstract class LocalTrack extends Track { _processor = processor; final processorOptions = kind == TrackType.VIDEO - ? VideoProcessorOptions(track: mediaStreamTrack) - : AudioProcessorOptions(track: mediaStreamTrack); + ? VideoProcessorOptions(track: rtcTrack) + : AudioProcessorOptions(track: rtcTrack); await _processor!.init(processorOptions); diff --git a/lib/src/track/local/video.dart b/lib/src/track/local/video.dart index 47030edb6..609ce027e 100644 --- a/lib/src/track/local/video.dart +++ b/lib/src/track/local/video.dart @@ -119,11 +119,11 @@ class LocalVideoTrack extends LocalTrack with VideoTrack { } Future> getSenderStats() async { - if (sender == null) { + if (rtcSender == null) { return []; } - final stats = await sender!.getStats(); + final stats = await rtcSender!.getStats(); final List items = []; for (var v in stats) { if (v.type == 'outbound-rtp') { @@ -290,7 +290,7 @@ extension LocalVideoTrackExt on LocalVideoTrack { params: options.params, ); await restartTrack(newOptions); - await replaceTrackForMultiCodecSimulcast(mediaStreamTrack); + await replaceTrackForMultiCodecSimulcast(rtcTrack); currentOptions = newOptions; } @@ -303,13 +303,13 @@ extension LocalVideoTrackExt on LocalVideoTrack { if (fastSwitch) { currentOptions = options.copyWith(deviceId: deviceId); - await rtc.Helper.switchCamera(mediaStreamTrack, deviceId, mediaStream); + await rtc.Helper.switchCamera(rtcTrack, deviceId, rtcStream); return; } await restartTrack(options.copyWith(deviceId: deviceId)); - await replaceTrackForMultiCodecSimulcast(mediaStreamTrack); + await replaceTrackForMultiCodecSimulcast(rtcTrack); } Future replaceTrackForMultiCodecSimulcast( @@ -317,7 +317,7 @@ extension LocalVideoTrackExt on LocalVideoTrack { ) async { simulcastCodecs.forEach((key, simulcastTrack) async { await simulcastTrack.sender?.replaceTrack(newTrack); - simulcastTrack.mediaStreamTrack = mediaStreamTrack; + simulcastTrack.mediaStreamTrack = rtcTrack; }); } @@ -372,12 +372,12 @@ extension LocalVideoTrackExt on LocalVideoTrack { }) async { logger.fine('Update publishing layers: $layers'); - if (track?.sender == null) { + if (track?.rtcSender == null) { logger.fine('Update publishing layers: sender is null'); return; } - final params = track?.sender?.parameters; + final params = track?.rtcSender?.parameters; if (params == null) { logger.fine('Update publishing layers: sender params are null'); return; @@ -389,7 +389,7 @@ extension LocalVideoTrackExt on LocalVideoTrack { return; } - return setPublishingLayersForSender(track!.sender!, encodings, layers, isSVC: isSVC); + return setPublishingLayersForSender(track!.rtcSender!, encodings, layers, isSVC: isSVC); } lk_models.VideoQuality _videoQualityForRid(String rid) { @@ -495,7 +495,7 @@ extension LocalVideoTrackExt on LocalVideoTrack { final SimulcastTrackInfo simulcastCodecInfo = SimulcastTrackInfo( codec: codec, encodings: encodings, - mediaStreamTrack: mediaStreamTrack, + mediaStreamTrack: rtcTrack, ); simulcastCodecs[codec] = simulcastCodecInfo; @@ -503,11 +503,11 @@ extension LocalVideoTrackExt on LocalVideoTrack { } Future setDegradationPreference(DegradationPreference preference) async { - final params = sender?.parameters; + final params = rtcSender?.parameters; if (params == null) { return; } params.degradationPreference = preference.toRTCType(); - await sender?.setParameters(params); + await rtcSender?.setParameters(params); } } diff --git a/lib/src/track/remote/audio.dart b/lib/src/track/remote/audio.dart index 5038df572..0c5216602 100644 --- a/lib/src/track/remote/audio.dart +++ b/lib/src/track/remote/audio.dart @@ -44,7 +44,7 @@ class RemoteAudioTrack extends RemoteTrack with AudioTrack, RemoteAudioManagemen if (didStart) { try { // web support - await audio.startAudio(getCid(), mediaStreamTrack); + await audio.startAudio(getCid(), rtcTrack); if (_deviceId != null) { audio.setSinkId(getCid(), _deviceId!); } @@ -79,14 +79,14 @@ class RemoteAudioTrack extends RemoteTrack with AudioTrack, RemoteAudioManagemen @override Future monitorStats() async { - if (receiver == null || events.isDisposed || !isActive) { + if (rtcReceiver == null || events.isDisposed || !isActive) { _currentBitrate = 0; return false; } try { final stats = await getReceiverStats(); - if (stats != null && prevStats != null && receiver != null) { + if (stats != null && prevStats != null && rtcReceiver != null) { final bitrate = computeBitrateForReceiverStats(stats, prevStats); _currentBitrate = bitrate; events.emit(AudioReceiverStatsEvent(stats: stats, currentBitrate: bitrate)); @@ -101,13 +101,13 @@ class RemoteAudioTrack extends RemoteTrack with AudioTrack, RemoteAudioManagemen } Future getReceiverStats() async { - if (receiver == null) { + if (rtcReceiver == null) { return null; } late List stats; try { - stats = await receiver!.getStats(); + stats = await rtcReceiver!.getStats(); } catch (e) { rethrow; } diff --git a/lib/src/track/remote/video.dart b/lib/src/track/remote/video.dart index fbb8f72df..e41556fa3 100644 --- a/lib/src/track/remote/video.dart +++ b/lib/src/track/remote/video.dart @@ -45,14 +45,14 @@ class RemoteVideoTrack extends RemoteTrack with VideoTrack { @override Future monitorStats() async { - if (receiver == null || events.isDisposed || !isActive) { + if (rtcReceiver == null || events.isDisposed || !isActive) { _currentBitrate = 0; return false; } try { final stats = await getReceiverStats(); - if (stats != null && prevStats != null && receiver != null) { + if (stats != null && prevStats != null && rtcReceiver != null) { final bitrate = computeBitrateForReceiverStats(stats, prevStats); _currentBitrate = bitrate; events.emit(VideoReceiverStatsEvent(stats: stats, currentBitrate: bitrate)); @@ -68,13 +68,13 @@ class RemoteVideoTrack extends RemoteTrack with VideoTrack { } Future getReceiverStats() async { - if (receiver == null) { + if (rtcReceiver == null) { return null; } late List stats; try { - stats = await receiver!.getStats(); + stats = await rtcReceiver!.getStats(); } catch (e) { rethrow; } diff --git a/lib/src/track/track.dart b/lib/src/track/track.dart index 3e029b82b..06c5a48b2 100644 --- a/lib/src/track/track.dart +++ b/lib/src/track/track.dart @@ -37,17 +37,31 @@ abstract class Track extends DisposableChangeNotifier with EventsEmittable _mediaStream; + + /// The underlying flutter_webrtc stream. SDK-internal; not part of the + /// public API. + @internal + rtc.MediaStream get rtcStream => _mediaStream; rtc.MediaStream _mediaStream; - // read only + @Deprecated('flutter_webrtc types are being removed from the LiveKit public API ' + 'and this getter will be removed in a future release.') rtc.MediaStreamTrack get mediaStreamTrack => _mediaStreamTrack; + + /// The underlying flutter_webrtc track. SDK-internal; not part of the + /// public API. + @internal + rtc.MediaStreamTrack get rtcTrack => _mediaStreamTrack; rtc.MediaStreamTrack _mediaStreamTrack; rtc.MediaStreamTrack? _originalTrack; String? sid; + + @internal rtc.RTCRtpTransceiver? transceiver; String? _cid; @@ -58,11 +72,32 @@ abstract class Track extends DisposableChangeNotifier with EventsEmittable _muted; + @Deprecated('flutter_webrtc types are being removed from the LiveKit public API ' + 'and this getter will be removed in a future release.') rtc.RTCRtpSender? get sender => transceiver?.sender; - rtc.RTCRtpReceiver? receiver; + /// The underlying flutter_webrtc RTP sender. SDK-internal; not part of the + /// public API. + @internal + rtc.RTCRtpSender? get rtcSender => transceiver?.sender; + + @Deprecated('flutter_webrtc types are being removed from the LiveKit public API ' + 'and this getter will be removed in a future release.') + rtc.RTCRtpReceiver? get receiver => rtcReceiver; + + // receiver was previously a public mutable field; keep assignability until + // the deprecated surface is removed. + @Deprecated('flutter_webrtc types are being removed from the LiveKit public API ' + 'and this setter will be removed in a future release.') + set receiver(rtc.RTCRtpReceiver? receiver) => rtcReceiver = receiver; + + /// The underlying flutter_webrtc RTP receiver. SDK-internal; not part of + /// the public API. + @internal + rtc.RTCRtpReceiver? rtcReceiver; - Track(this.kind, this.source, this._mediaStream, this._mediaStreamTrack, {this.receiver}) { + Track(this.kind, this.source, this._mediaStream, this._mediaStreamTrack, {rtc.RTCRtpReceiver? receiver}) + : rtcReceiver = receiver { // Any event emitted will trigger ChangeNotifier events.listen((event) { logger.finer('[TrackEvent] $event, will notifyListeners()'); @@ -77,6 +112,8 @@ abstract class Track extends DisposableChangeNotifier with EventsEmittable enable() async { - logger.fine('$objectId.enable() enabling ${mediaStreamTrack.objectId}...'); + logger.fine('$objectId.enable() enabling ${rtcTrack.objectId}...'); try { if (_active) { - mediaStreamTrack.enabled = true; + rtcTrack.enabled = true; } } catch (e) { logger.warning('[$objectId] set rtc.mediaStreamTrack.enabled did throw $e'); @@ -157,10 +194,10 @@ abstract class Track extends DisposableChangeNotifier with EventsEmittable disable() async { - logger.fine('$objectId.disable() disabling ${mediaStreamTrack.objectId}...'); + logger.fine('$objectId.disable() disabling ${rtcTrack.objectId}...'); try { if (_active) { - mediaStreamTrack.enabled = false; + rtcTrack.enabled = false; } } catch (e) { logger.warning('[$objectId] set rtc.mediaStreamTrack.enabled did throw $e'); diff --git a/lib/src/track/web/_audio_analyser.dart b/lib/src/track/web/_audio_analyser.dart index 087b1bbc3..cc285f49e 100644 --- a/lib/src/track/web/_audio_analyser.dart +++ b/lib/src/track/web/_audio_analyser.dart @@ -65,7 +65,7 @@ AudioAnalyser? createAudioAnalyser( if (audioContext == null) { throw Exception('Audio Context not supported on this browser'); } - final streamTrack = opts.cloneTrack == true ? track.mediaStreamTrack.clone() : track.mediaStreamTrack; + final streamTrack = opts.cloneTrack == true ? track.rtcTrack.clone() : track.rtcTrack; final mediaStreamSource = audioContext.createMediaStreamSource( MediaStreamWeb(web.MediaStream([(streamTrack as MediaStreamTrackWeb).jsTrack].toJS), '').jsStream); final analyser = audioContext.createAnalyser(); diff --git a/lib/src/widgets/video_track_renderer.dart b/lib/src/widgets/video_track_renderer.dart index 9f96e8836..d165c3063 100644 --- a/lib/src/widgets/video_track_renderer.dart +++ b/lib/src/widgets/video_track_renderer.dart @@ -177,7 +177,7 @@ class _VideoTrackRendererState extends State { } Future _attach() async { - _renderer?.srcObject = widget.track.mediaStream; + _renderer?.srcObject = widget.track.rtcStream; await _listener?.dispose(); _listener = widget.track.createListener() ..on((event) { @@ -212,7 +212,7 @@ class _VideoTrackRendererState extends State { } if ([BrowserType.safari, BrowserType.firefox].contains(lkBrowser()) && oldWidget.key != widget.key) { - _renderer?.srcObject = widget.track.mediaStream; + _renderer?.srcObject = widget.track.rtcStream; } } @@ -242,7 +242,7 @@ class _VideoTrackRendererState extends State { objectFit: widget.fit.toRTCType(), onViewReady: (controller) async { _renderer = controller; - _renderer?.srcObject = widget.track.mediaStream; + _renderer?.srcObject = widget.track.rtcStream; await _attach(); }, ); @@ -350,7 +350,7 @@ class _VideoTrackRendererState extends State { if (widget.mirrorMode == VideoViewMirrorMode.auto) { final track = widget.track; if (track is LocalVideoTrack) { - final settings = track.mediaStreamTrack.getSettings(); + final settings = track.rtcTrack.getSettings(); final facingMode = settings['facingMode']; if (facingMode != null) { return facingMode == 'user';