diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index c456144c..f98b17d7 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -21,6 +21,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'e2ee/options.dart'; import 'events.dart'; import 'managers/event.dart'; +import 'options.dart'; import 'proto/livekit_models.pb.dart' as lk_models; import 'proto/livekit_rtc.pb.dart' as lk_rtc; import 'types/other.dart'; @@ -261,3 +262,14 @@ extension ParticipantTypeExt on lk_models.ParticipantInfo_Kind { lk_models.ParticipantInfo_Kind.AGENT: ParticipantKind.AGENT, }[this]!; } + +extension DegradationPreferenceExt on DegradationPreference { + rtc.RTCDegradationPreference toRTCType() => { + DegradationPreference.disabled: rtc.RTCDegradationPreference.DISABLED, + DegradationPreference.maintainFramerate: + rtc.RTCDegradationPreference.MAINTAIN_FRAMERATE, + DegradationPreference.maintainResolution: + rtc.RTCDegradationPreference.MAINTAIN_RESOLUTION, + DegradationPreference.balanced: rtc.RTCDegradationPreference.BALANCED, + }[this]!; +} diff --git a/lib/src/options.dart b/lib/src/options.dart index d9bab4bf..ed80b5da 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -162,6 +162,13 @@ class RoomOptions { } } +enum DegradationPreference { + disabled, + maintainFramerate, + maintainResolution, + balanced, +} + class BackupVideoCodec { const BackupVideoCodec({ this.enabled = true, @@ -227,6 +234,8 @@ class VideoPublishOptions extends PublishOptions { /// Defaults to true. final bool simulcast; + final DegradationPreference? degradationPreference; + final List videoSimulcastLayers; final List screenShareSimulcastLayers; @@ -235,17 +244,17 @@ class VideoPublishOptions extends PublishOptions { final BackupVideoCodec backupVideoCodec; - const VideoPublishOptions({ - super.name, - super.stream, - this.videoCodec = defaultVideoCodec, - this.videoEncoding, - this.simulcast = true, - this.videoSimulcastLayers = const [], - this.screenShareSimulcastLayers = const [], - this.backupVideoCodec = defualtBackupVideoCodec, - this.scalabilityMode, - }); + const VideoPublishOptions( + {super.name, + super.stream, + this.videoCodec = defaultVideoCodec, + this.videoEncoding, + this.simulcast = true, + this.videoSimulcastLayers = const [], + this.screenShareSimulcastLayers = const [], + this.backupVideoCodec = defualtBackupVideoCodec, + this.scalabilityMode, + this.degradationPreference}); VideoPublishOptions copyWith({ VideoEncoding? videoEncoding, @@ -254,6 +263,7 @@ class VideoPublishOptions extends PublishOptions { List? screenShareSimulcastLayers, String? videoCodec, BackupVideoCodec? backupVideoCodec, + DegradationPreference? degradationPreference, String? scalabilityMode, String? name, String? stream, @@ -266,6 +276,8 @@ class VideoPublishOptions extends PublishOptions { screenShareSimulcastLayers ?? this.screenShareSimulcastLayers, videoCodec: videoCodec ?? this.videoCodec, backupVideoCodec: backupVideoCodec ?? this.backupVideoCodec, + degradationPreference: + degradationPreference ?? this.degradationPreference, scalabilityMode: scalabilityMode ?? this.scalabilityMode, name: name ?? this.name, stream: stream ?? this.stream, diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index be48dbc0..32965977 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -296,13 +296,13 @@ class LocalParticipant extends Participant { track.codec = publishOptions.videoCodec; } - // prefer to maintainResolution for screen share - if (track.source == TrackSource.screenShareVideo) { - var sender = track.transceiver!.sender; - var parameters = sender.parameters; - parameters.degradationPreference = - rtc.RTCDegradationPreference.MAINTAIN_RESOLUTION; - await sender.setParameters(parameters); + if ([TrackSource.camera, TrackSource.screenShareVideo] + .contains(track.source)) { + var degradationPreference = publishOptions.degradationPreference ?? + getDefaultDegradationPreference( + track, + ); + track.setDegradationPreference(degradationPreference); } if (kIsWeb && @@ -397,6 +397,18 @@ class LocalParticipant extends Participant { await pub.dispose(); } + DegradationPreference getDefaultDegradationPreference(LocalVideoTrack track) { + // a few of reasons we have different default paths: + // 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue + // 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced` + VideoDimensions dimensions = track.currentOptions.params.dimensions; + if (track.source == TrackSource.screenShareVideo || + dimensions.height >= 1080) { + return DegradationPreference.maintainResolution; + } + return DegradationPreference.balanced; + } + /// Convenience method to unpublish all tracks. Future unpublishAllTracks( {bool notify = true, bool? stopOnUnpublish}) async { diff --git a/lib/src/track/local/video.dart b/lib/src/track/local/video.dart index 731ac044..c07ee101 100644 --- a/lib/src/track/local/video.dart +++ b/lib/src/track/local/video.dart @@ -17,6 +17,7 @@ import 'package:flutter/foundation.dart'; import 'package:collection/collection.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; +import 'package:livekit_client/src/extensions.dart'; import '../../events.dart'; import '../../exceptions.dart'; import '../../logger.dart'; @@ -490,4 +491,13 @@ extension LocalVideoTrackExt on LocalVideoTrack { simulcastCodecs[codec] = simulcastCodecInfo; return simulcastCodecInfo; } + + void setDegradationPreference(DegradationPreference preference) { + final params = sender?.parameters; + if (params == null) { + return; + } + params.degradationPreference = preference.toRTCType(); + sender?.setParameters(params); + } }