From ed0b1516eb337c1b20e443043b179adf2f6c1b5b Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:59:27 +0800 Subject: [PATCH 01/14] impl1 --- lib/livekit_client.dart | 2 + lib/src/core/engine.dart | 4 ++ lib/src/options.dart | 17 ++++++- lib/src/participant/local.dart | 14 +++++- lib/src/types/audio_encoding.dart | 83 +++++++++++++++++++++++++++++++ lib/src/types/other.dart | 5 ++ lib/src/types/priority.dart | 42 ++++++++++++++++ lib/src/types/video_encoding.dart | 23 +++++++-- lib/src/utils.dart | 3 ++ 9 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 lib/src/types/audio_encoding.dart create mode 100644 lib/src/types/priority.dart diff --git a/lib/livekit_client.dart b/lib/livekit_client.dart index a0f6b21c6..27276733b 100644 --- a/lib/livekit_client.dart +++ b/lib/livekit_client.dart @@ -59,9 +59,11 @@ export 'src/track/remote/video.dart'; export 'src/track/track.dart'; export 'src/json/agent_attributes.dart'; export 'src/types/data_stream.dart'; +export 'src/types/audio_encoding.dart'; export 'src/types/other.dart'; export 'src/types/participant_permissions.dart'; export 'src/types/participant_state.dart'; +export 'src/types/priority.dart'; export 'src/types/rpc.dart'; export 'src/types/transcription_segment.dart'; export 'src/types/video_dimensions.dart'; diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index 0a1ca490f..c85046490 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -576,6 +576,10 @@ class Engine extends Disposable with EventsEmittable { rtcConfiguration = rtcConfiguration.copyWith(encodedInsertableStreams: true); } + if (connectOptions.enableDscp) { + rtcConfiguration = rtcConfiguration.copyWith(enableDscp: true); + } + return rtcConfiguration; } diff --git a/lib/src/options.dart b/lib/src/options.dart index 765b4e4bb..9a62859e0 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -17,6 +17,7 @@ import 'e2ee/options.dart'; import 'track/local/audio.dart'; import 'track/local/video.dart'; import 'track/options.dart'; +import 'types/audio_encoding.dart'; import 'types/other.dart'; import 'types/video_encoding.dart'; import 'types/video_parameters.dart'; @@ -60,11 +61,16 @@ class ConnectOptions { final Timeouts timeouts; + /// Allows DSCP codes to be set on outgoing packets when network priority is used. + /// Defaults to false. + final bool enableDscp; + const ConnectOptions({ this.autoSubscribe = true, this.rtcConfiguration = const RTCConfiguration(), this.protocolVersion = ProtocolVersion.v12, this.timeouts = Timeouts.defaultTimeouts, + this.enableDscp = false, }); } @@ -314,6 +320,9 @@ class AudioPreset { class AudioPublishOptions extends PublishOptions { static const defaultMicrophoneName = 'microphone'; + /// Preferred encoding parameters. + final AudioEncoding? encoding; + /// Whether to enable DTX (Discontinuous Transmission) or not. /// https://en.wikipedia.org/wiki/Discontinuous_transmission /// Defaults to true. @@ -322,7 +331,8 @@ class AudioPublishOptions extends PublishOptions { /// red (Redundant Audio Data) final bool? red; - /// max audio bitrate + /// Max audio bitrate used when [encoding] is not set. + /// Ignored if [encoding] is provided. final int audioBitrate; /// Mark this audio as originating from a pre-connect buffer. @@ -332,6 +342,7 @@ class AudioPublishOptions extends PublishOptions { const AudioPublishOptions({ super.name, super.stream, + this.encoding, this.dtx = true, this.red = true, this.audioBitrate = AudioPreset.music, @@ -339,6 +350,7 @@ class AudioPublishOptions extends PublishOptions { }); AudioPublishOptions copyWith({ + AudioEncoding? encoding, bool? dtx, int? audioBitrate, String? name, @@ -347,6 +359,7 @@ class AudioPublishOptions extends PublishOptions { bool? preConnect, }) => AudioPublishOptions( + encoding: encoding ?? this.encoding, dtx: dtx ?? this.dtx, audioBitrate: audioBitrate ?? this.audioBitrate, name: name ?? this.name, @@ -357,7 +370,7 @@ class AudioPublishOptions extends PublishOptions { @override String toString() => - '${runtimeType}(dtx: ${dtx}, audioBitrate: ${audioBitrate}, red: ${red}, preConnect: ${preConnect})'; + '${runtimeType}(encoding: ${encoding}, dtx: ${dtx}, audioBitrate: ${audioBitrate}, red: ${red}, preConnect: ${preConnect})'; } final backupCodecs = ['vp8', 'h264']; diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index 8b2526675..9d1336e62 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -51,6 +51,7 @@ import '../track/options.dart'; import '../types/data_stream.dart'; import '../types/other.dart'; import '../types/participant_permissions.dart'; +import '../types/priority.dart'; import '../types/rpc.dart'; import '../types/video_dimensions.dart'; import '../utils.dart' show buildStreamId, mimeTypeToVideoCodecString, Utils, compareVersions, isSVCCodec; @@ -123,9 +124,13 @@ class LocalParticipant extends Participant { // Use defaultPublishOptions if options is null publishOptions ??= track.lastPublishOptions ?? room.roomOptions.defaultAudioPublishOptions; + final audioEncoding = publishOptions.encoding; + final maxAudioBitrate = audioEncoding?.maxBitrate ?? publishOptions.audioBitrate; final List encodings = [ rtc.RTCRtpEncoding( - maxBitrate: publishOptions.audioBitrate, + maxBitrate: maxAudioBitrate, + priority: (audioEncoding?.bitratePriority ?? Priority.low).toRtcpPriorityType(), + networkPriority: audioEncoding?.networkPriority?.toRtcpPriorityType(), ) ]; @@ -162,7 +167,12 @@ class LocalParticipant extends Participant { final transceiverInit = rtc.RTCRtpTransceiverInit( direction: rtc.TransceiverDirection.SendOnly, sendEncodings: [ - if (publishOptions.audioBitrate > 0) rtc.RTCRtpEncoding(maxBitrate: publishOptions.audioBitrate), + if (maxAudioBitrate > 0) + rtc.RTCRtpEncoding( + maxBitrate: maxAudioBitrate, + priority: (audioEncoding?.bitratePriority ?? Priority.low).toRtcpPriorityType(), + networkPriority: audioEncoding?.networkPriority?.toRtcpPriorityType(), + ), ], ); // addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web) diff --git a/lib/src/types/audio_encoding.dart b/lib/src/types/audio_encoding.dart new file mode 100644 index 000000000..e0ee91a54 --- /dev/null +++ b/lib/src/types/audio_encoding.dart @@ -0,0 +1,83 @@ +// Copyright 2024 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; +import 'package:meta/meta.dart'; + +import 'priority.dart'; + +/// A type that represents audio encoding information. +@immutable +class AudioEncoding { + final int maxBitrate; + final Priority? bitratePriority; + final Priority? networkPriority; + + const AudioEncoding({ + required this.maxBitrate, + this.bitratePriority, + this.networkPriority, + }); + + AudioEncoding copyWith({ + int? maxBitrate, + Priority? bitratePriority, + Priority? networkPriority, + }) => + AudioEncoding( + maxBitrate: maxBitrate ?? this.maxBitrate, + bitratePriority: bitratePriority ?? this.bitratePriority, + networkPriority: networkPriority ?? this.networkPriority, + ); + + @override + String toString() => + '${runtimeType}(maxBitrate: ${maxBitrate}, bitratePriority: ${bitratePriority}, networkPriority: ${networkPriority})'; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AudioEncoding && + maxBitrate == other.maxBitrate && + bitratePriority == other.bitratePriority && + networkPriority == other.networkPriority; + + @override + int get hashCode => Object.hash(maxBitrate, bitratePriority, networkPriority); + + static const presetTelephone = AudioEncoding(maxBitrate: 12000); + static const presetSpeech = AudioEncoding(maxBitrate: 24000); + static const presetMusic = AudioEncoding(maxBitrate: 48000); + static const presetMusicStereo = AudioEncoding(maxBitrate: 64000); + static const presetMusicHighQuality = AudioEncoding(maxBitrate: 96000); + static const presetMusicHighQualityStereo = AudioEncoding(maxBitrate: 128000); + + static const presets = [ + presetTelephone, + presetSpeech, + presetMusic, + presetMusicStereo, + presetMusicHighQuality, + presetMusicHighQualityStereo, + ]; +} + +/// Convenience extension for [AudioEncoding]. +extension AudioEncodingExt on AudioEncoding { + rtc.RTCRtpEncoding toRTCRtpEncoding() => rtc.RTCRtpEncoding( + maxBitrate: maxBitrate, + priority: (bitratePriority ?? Priority.low).toRtcpPriorityType(), + networkPriority: networkPriority?.toRtcpPriorityType(), + ); +} diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index f5c1dd7bc..a9a580424 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -140,12 +140,14 @@ class RTCConfiguration { final List? iceServers; final RTCIceTransportPolicy? iceTransportPolicy; final bool? encodedInsertableStreams; + final bool? enableDscp; const RTCConfiguration({ this.iceCandidatePoolSize, this.iceServers, this.iceTransportPolicy, this.encodedInsertableStreams, + this.enableDscp, }); Map toMap() { @@ -158,6 +160,7 @@ class RTCConfiguration { // only supports unified plan 'sdpSemantics': 'unified-plan', if (encodedInsertableStreams != null) 'encodedInsertableStreams': encodedInsertableStreams, + if (enableDscp != null) 'enableDscp': enableDscp, if (iceServersMap.isNotEmpty) 'iceServers': iceServersMap, if (iceCandidatePoolSize != null) 'iceCandidatePoolSize': iceCandidatePoolSize, if (iceTransportPolicy != null) 'iceTransportPolicy': iceTransportPolicy!.toStringValue(), @@ -170,12 +173,14 @@ class RTCConfiguration { List? iceServers, RTCIceTransportPolicy? iceTransportPolicy, bool? encodedInsertableStreams, + bool? enableDscp, }) => RTCConfiguration( iceCandidatePoolSize: iceCandidatePoolSize ?? this.iceCandidatePoolSize, iceServers: iceServers ?? this.iceServers, iceTransportPolicy: iceTransportPolicy ?? this.iceTransportPolicy, encodedInsertableStreams: encodedInsertableStreams ?? this.encodedInsertableStreams, + enableDscp: enableDscp ?? this.enableDscp, ); } diff --git a/lib/src/types/priority.dart b/lib/src/types/priority.dart new file mode 100644 index 000000000..24cdccd49 --- /dev/null +++ b/lib/src/types/priority.dart @@ -0,0 +1,42 @@ +// Copyright 2024 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; + +/// Priority levels for RTP encoding parameters. +/// +/// - `bitratePriority` controls WebRTC internal bandwidth allocation between streams. +/// - `networkPriority` controls DSCP marking for network-level QoS. +/// Requires `ConnectOptions.enableDscp` to be true. +enum Priority { + veryLow, + low, + medium, + high, +} + +extension PriorityExt on Priority { + rtc.RTCPriorityType toRtcpPriorityType() { + switch (this) { + case Priority.veryLow: + return rtc.RTCPriorityType.veryLow; + case Priority.low: + return rtc.RTCPriorityType.low; + case Priority.medium: + return rtc.RTCPriorityType.medium; + case Priority.high: + return rtc.RTCPriorityType.high; + } + } +} diff --git a/lib/src/types/video_encoding.dart b/lib/src/types/video_encoding.dart index 72c309fa9..62fd582e7 100644 --- a/lib/src/types/video_encoding.dart +++ b/lib/src/types/video_encoding.dart @@ -15,28 +15,39 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:meta/meta.dart'; +import 'priority.dart'; + /// A type that represents video encoding information. @immutable class VideoEncoding implements Comparable { final int maxFramerate; final int maxBitrate; + final Priority? bitratePriority; + final Priority? networkPriority; const VideoEncoding({ required this.maxFramerate, required this.maxBitrate, + this.bitratePriority, + this.networkPriority, }); VideoEncoding copyWith({ int? maxFramerate, int? maxBitrate, + Priority? bitratePriority, + Priority? networkPriority, }) => VideoEncoding( maxFramerate: maxFramerate ?? this.maxFramerate, maxBitrate: maxBitrate ?? this.maxBitrate, + bitratePriority: bitratePriority ?? this.bitratePriority, + networkPriority: networkPriority ?? this.networkPriority, ); @override - String toString() => '${runtimeType}(maxFramerate: ${maxFramerate}, maxBitrate: ${maxBitrate})'; + String toString() => + '${runtimeType}(maxFramerate: ${maxFramerate}, maxBitrate: ${maxBitrate}, bitratePriority: ${bitratePriority}, networkPriority: ${networkPriority})'; // ---------------------------------------------------------------------- // equality @@ -44,10 +55,14 @@ class VideoEncoding implements Comparable { @override bool operator ==(Object other) => identical(this, other) || - other is VideoEncoding && maxFramerate == other.maxFramerate && maxBitrate == other.maxBitrate; + other is VideoEncoding && + maxFramerate == other.maxFramerate && + maxBitrate == other.maxBitrate && + bitratePriority == other.bitratePriority && + networkPriority == other.networkPriority; @override - int get hashCode => Object.hash(maxFramerate, maxBitrate); + int get hashCode => Object.hash(maxFramerate, maxBitrate, bitratePriority, networkPriority); // ---------------------------------------------------------------------- // Comparable @@ -78,5 +93,7 @@ extension VideoEncodingExt on VideoEncoding { maxFramerate: maxFramerate, maxBitrate: maxBitrate, numTemporalLayers: numTemporalLayers, + priority: (bitratePriority ?? Priority.low).toRtcpPriorityType(), + networkPriority: networkPriority?.toRtcpPriorityType(), ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index d2390de9e..071948c85 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -33,6 +33,7 @@ import 'options.dart'; import 'support/platform.dart'; import 'track/local/video.dart'; import 'types/other.dart'; +import 'types/priority.dart'; import 'types/video_dimensions.dart'; import 'types/video_encoding.dart'; import 'types/video_parameters.dart'; @@ -428,6 +429,8 @@ class Utils { rid: videoRids[2 - i], maxBitrate: videoEncoding.maxBitrate ~/ math.pow(3, i), maxFramerate: original.encoding!.maxFramerate, + priority: (videoEncoding.bitratePriority ?? Priority.low).toRtcpPriorityType(), + networkPriority: videoEncoding.networkPriority?.toRtcpPriorityType(), )); } } else { From ec9ab84fb7e67014506ce80e7d096cb322290cb3 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:16:04 +0800 Subject: [PATCH 02/14] move dscp option --- lib/src/core/engine.dart | 4 ---- lib/src/options.dart | 5 ----- 2 files changed, 9 deletions(-) diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index c85046490..0a1ca490f 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -576,10 +576,6 @@ class Engine extends Disposable with EventsEmittable { rtcConfiguration = rtcConfiguration.copyWith(encodedInsertableStreams: true); } - if (connectOptions.enableDscp) { - rtcConfiguration = rtcConfiguration.copyWith(enableDscp: true); - } - return rtcConfiguration; } diff --git a/lib/src/options.dart b/lib/src/options.dart index 9a62859e0..f81048bdc 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -61,16 +61,11 @@ class ConnectOptions { final Timeouts timeouts; - /// Allows DSCP codes to be set on outgoing packets when network priority is used. - /// Defaults to false. - final bool enableDscp; - const ConnectOptions({ this.autoSubscribe = true, this.rtcConfiguration = const RTCConfiguration(), this.protocolVersion = ProtocolVersion.v12, this.timeouts = Timeouts.defaultTimeouts, - this.enableDscp = false, }); } From e194f1c9b81a6c035cad7215bbd4b8436e35db0b Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:16:10 +0800 Subject: [PATCH 03/14] docs --- lib/src/types/audio_encoding.dart | 5 +++++ lib/src/types/other.dart | 2 ++ lib/src/types/priority.dart | 5 ++--- lib/src/types/video_encoding.dart | 7 +++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/src/types/audio_encoding.dart b/lib/src/types/audio_encoding.dart index e0ee91a54..aa6c9418d 100644 --- a/lib/src/types/audio_encoding.dart +++ b/lib/src/types/audio_encoding.dart @@ -20,8 +20,13 @@ import 'priority.dart'; /// A type that represents audio encoding information. @immutable class AudioEncoding { + /// Maximum bitrate for the audio track. final int maxBitrate; + + /// Priority for bandwidth allocation. final Priority? bitratePriority; + + /// Priority for DSCP marking. Requires `RTCConfiguration.enableDscp` to be true. final Priority? networkPriority; const AudioEncoding({ diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index a9a580424..31e4712f8 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -140,6 +140,8 @@ class RTCConfiguration { final List? iceServers; final RTCIceTransportPolicy? iceTransportPolicy; final bool? encodedInsertableStreams; + /// Allows DSCP codes to be set on outgoing packets. + /// No effect on web platforms. final bool? enableDscp; const RTCConfiguration({ diff --git a/lib/src/types/priority.dart b/lib/src/types/priority.dart index 24cdccd49..63dc9a260 100644 --- a/lib/src/types/priority.dart +++ b/lib/src/types/priority.dart @@ -16,9 +16,8 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; /// Priority levels for RTP encoding parameters. /// -/// - `bitratePriority` controls WebRTC internal bandwidth allocation between streams. -/// - `networkPriority` controls DSCP marking for network-level QoS. -/// Requires `ConnectOptions.enableDscp` to be true. +/// `bitratePriority` controls WebRTC internal bandwidth allocation between streams. +/// `networkPriority` controls DSCP marking for network-level QoS. enum Priority { veryLow, low, diff --git a/lib/src/types/video_encoding.dart b/lib/src/types/video_encoding.dart index 62fd582e7..e23ea0c0b 100644 --- a/lib/src/types/video_encoding.dart +++ b/lib/src/types/video_encoding.dart @@ -20,9 +20,16 @@ import 'priority.dart'; /// A type that represents video encoding information. @immutable class VideoEncoding implements Comparable { + /// Maximum framerate for the video track. final int maxFramerate; + + /// Maximum bitrate for the video track. final int maxBitrate; + + /// Priority for bandwidth allocation. final Priority? bitratePriority; + + /// Priority for DSCP marking. Requires `RTCConfiguration.enableDscp` to be true. final Priority? networkPriority; const VideoEncoding({ From 3d33ae826ada89a6fece2f2cf1b8fdd24b493efe Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:50:19 +0800 Subject: [PATCH 04/14] bump to flutter-webrtc 1.3.0 --- lib/src/track/web/_audio_analyser.dart | 3 +-- lib/src/types/other.dart | 1 + pubspec.lock | 12 ++++++------ pubspec.yaml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/src/track/web/_audio_analyser.dart b/lib/src/track/web/_audio_analyser.dart index e3c25c343..087b1bbc3 100644 --- a/lib/src/track/web/_audio_analyser.dart +++ b/lib/src/track/web/_audio_analyser.dart @@ -2,8 +2,7 @@ import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:math' as math; -import 'package:dart_webrtc/dart_webrtc.dart' show MediaStreamTrackWeb; -import 'package:dart_webrtc/dart_webrtc.dart' show MediaStreamWeb; +import 'package:dart_webrtc/dart_webrtc.dart' show MediaStreamTrackWeb, MediaStreamWeb; import 'package:web/web.dart' as web; import '../../track/local/local.dart' show AudioTrack; diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index 31e4712f8..54d0c8917 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -140,6 +140,7 @@ class RTCConfiguration { final List? iceServers; final RTCIceTransportPolicy? iceTransportPolicy; final bool? encodedInsertableStreams; + /// Allows DSCP codes to be set on outgoing packets. /// No effect on web platforms. final bool? enableDscp; diff --git a/pubspec.lock b/pubspec.lock index 8d498a6df..d5b313878 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -205,10 +205,10 @@ packages: dependency: "direct main" description: name: dart_webrtc - sha256: "51bcda4ba5d7dd9e65a309244ce3ac0b58025e6e1f6d7442cee4cd02134ef65f" + sha256: "4ed7b9fa9924e5a81eb39271e2c2356739dd1039d60a13b86ba6c5f448625086" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.7.0" dbus: dependency: transitive description: @@ -292,10 +292,10 @@ packages: dependency: "direct main" description: name: flutter_webrtc - sha256: "71a38363a5b50603e405c275f30de2eb90f980b0cc94b0e1e9d8b9d6a6b03bf0" + sha256: "0f86b518e9349e71a136a96e0ea11294cad8a8531b2bc9ae99e69df332ac898a" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" frontend_server_client: dependency: transitive description: @@ -809,10 +809,10 @@ packages: dependency: transitive description: name: webrtc_interface - sha256: "2e604a31703ad26781782fb14fa8a4ee621154ee2c513d2b9938e486fa695233" + sha256: ad0e5786b2acd3be72a3219ef1dde9e1cac071cf4604c685f11b61d63cdd6eb3 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 27150709b..a5d8a6fbf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,8 +46,8 @@ dependencies: json_annotation: ^4.9.0 # Fix version to avoid version conflicts between WebRTC-SDK pods, which both this package and flutter_webrtc depend on. - flutter_webrtc: 1.2.1 - dart_webrtc: ^1.6.0 + flutter_webrtc: 1.3.0 + dart_webrtc: ^1.7.0 dev_dependencies: flutter_test: From 4658ecc7a34877412536eae5396282b83d355b93 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:08:09 +0800 Subject: [PATCH 05/14] update AudioEncoding --- lib/src/options.dart | 19 ++----------------- lib/src/participant/local.dart | 18 +++++++++--------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/lib/src/options.dart b/lib/src/options.dart index f81048bdc..f541a28a8 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -302,20 +302,12 @@ class VideoPublishOptions extends PublishOptions { String toString() => '${runtimeType}(videoEncoding: ${videoEncoding}, simulcast: ${simulcast})'; } -class AudioPreset { - static const telephone = 12000; - static const speech = 24000; - static const music = 48000; - static const musicStereo = 64000; - static const musicHighQuality = 96000; - static const musicHighQualityStereo = 128000; -} - /// Options used when publishing audio. class AudioPublishOptions extends PublishOptions { static const defaultMicrophoneName = 'microphone'; /// Preferred encoding parameters. + /// Defaults to [AudioEncoding.presetMusic] when not set. final AudioEncoding? encoding; /// Whether to enable DTX (Discontinuous Transmission) or not. @@ -326,10 +318,6 @@ class AudioPublishOptions extends PublishOptions { /// red (Redundant Audio Data) final bool? red; - /// Max audio bitrate used when [encoding] is not set. - /// Ignored if [encoding] is provided. - final int audioBitrate; - /// Mark this audio as originating from a pre-connect buffer. /// Used to populate protobuf audioFeatures (TF_PRECONNECT_BUFFER). final bool preConnect; @@ -340,14 +328,12 @@ class AudioPublishOptions extends PublishOptions { this.encoding, this.dtx = true, this.red = true, - this.audioBitrate = AudioPreset.music, this.preConnect = false, }); AudioPublishOptions copyWith({ AudioEncoding? encoding, bool? dtx, - int? audioBitrate, String? name, String? stream, bool? red, @@ -356,7 +342,6 @@ class AudioPublishOptions extends PublishOptions { AudioPublishOptions( encoding: encoding ?? this.encoding, dtx: dtx ?? this.dtx, - audioBitrate: audioBitrate ?? this.audioBitrate, name: name ?? this.name, stream: stream ?? this.stream, red: red ?? this.red, @@ -365,7 +350,7 @@ class AudioPublishOptions extends PublishOptions { @override String toString() => - '${runtimeType}(encoding: ${encoding}, dtx: ${dtx}, audioBitrate: ${audioBitrate}, red: ${red}, preConnect: ${preConnect})'; + '${runtimeType}(encoding: ${encoding}, dtx: ${dtx}, red: ${red}, preConnect: ${preConnect})'; } final backupCodecs = ['vp8', 'h264']; diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index 8563f9e62..751ce84fa 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -48,6 +48,7 @@ import '../track/local/audio.dart'; import '../track/local/local.dart'; import '../track/local/video.dart'; import '../track/options.dart'; +import '../types/audio_encoding.dart'; import '../types/data_stream.dart'; import '../types/other.dart'; import '../types/participant_permissions.dart'; @@ -124,13 +125,12 @@ class LocalParticipant extends Participant { // Use defaultPublishOptions if options is null publishOptions ??= track.lastPublishOptions ?? room.roomOptions.defaultAudioPublishOptions; - final audioEncoding = publishOptions.encoding; - final maxAudioBitrate = audioEncoding?.maxBitrate ?? publishOptions.audioBitrate; + final audioEncoding = publishOptions.encoding ?? AudioEncoding.presetMusic; final List encodings = [ rtc.RTCRtpEncoding( - maxBitrate: maxAudioBitrate, - priority: (audioEncoding?.bitratePriority ?? Priority.low).toRtcpPriorityType(), - networkPriority: audioEncoding?.networkPriority?.toRtcpPriorityType(), + maxBitrate: audioEncoding.maxBitrate, + priority: (audioEncoding.bitratePriority ?? Priority.low).toRtcpPriorityType(), + networkPriority: audioEncoding.networkPriority?.toRtcpPriorityType(), ) ]; @@ -168,11 +168,11 @@ class LocalParticipant extends Participant { final transceiverInit = rtc.RTCRtpTransceiverInit( direction: rtc.TransceiverDirection.SendOnly, sendEncodings: [ - if (maxAudioBitrate > 0) + if (audioEncoding.maxBitrate > 0) rtc.RTCRtpEncoding( - maxBitrate: maxAudioBitrate, - priority: (audioEncoding?.bitratePriority ?? Priority.low).toRtcpPriorityType(), - networkPriority: audioEncoding?.networkPriority?.toRtcpPriorityType(), + maxBitrate: audioEncoding.maxBitrate, + priority: (audioEncoding.bitratePriority ?? Priority.low).toRtcpPriorityType(), + networkPriority: audioEncoding.networkPriority?.toRtcpPriorityType(), ), ], ); From c0a41ec0fc836239f0c41d95ec33dd2a985a11e0 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:18:38 +0800 Subject: [PATCH 06/14] changes --- .changes/priority-control | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/priority-control diff --git a/.changes/priority-control b/.changes/priority-control new file mode 100644 index 000000000..141c3e4c0 --- /dev/null +++ b/.changes/priority-control @@ -0,0 +1 @@ +patch type="added" "Bitrate priority control APIs" From 8f38862e4fb0a5803730548347354bf847de4763 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:26:50 +0800 Subject: [PATCH 07/14] simplify --- lib/src/options.dart | 3 +-- lib/src/participant/local.dart | 17 ++--------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/lib/src/options.dart b/lib/src/options.dart index f541a28a8..af31ce5fd 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -349,8 +349,7 @@ class AudioPublishOptions extends PublishOptions { ); @override - String toString() => - '${runtimeType}(encoding: ${encoding}, dtx: ${dtx}, red: ${red}, preConnect: ${preConnect})'; + String toString() => '${runtimeType}(encoding: ${encoding}, dtx: ${dtx}, red: ${red}, preConnect: ${preConnect})'; } final backupCodecs = ['vp8', 'h264']; diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index 751ce84fa..7346345d9 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -126,13 +126,7 @@ class LocalParticipant extends Participant { publishOptions ??= track.lastPublishOptions ?? room.roomOptions.defaultAudioPublishOptions; final audioEncoding = publishOptions.encoding ?? AudioEncoding.presetMusic; - final List encodings = [ - rtc.RTCRtpEncoding( - maxBitrate: audioEncoding.maxBitrate, - priority: (audioEncoding.bitratePriority ?? Priority.low).toRtcpPriorityType(), - networkPriority: audioEncoding.networkPriority?.toRtcpPriorityType(), - ) - ]; + final List encodings = [audioEncoding.toRTCRtpEncoding()]; final req = lk_rtc.AddTrackRequest( cid: track.getCid(), @@ -167,14 +161,7 @@ class LocalParticipant extends Participant { final transceiverInit = rtc.RTCRtpTransceiverInit( direction: rtc.TransceiverDirection.SendOnly, - sendEncodings: [ - if (audioEncoding.maxBitrate > 0) - rtc.RTCRtpEncoding( - maxBitrate: audioEncoding.maxBitrate, - priority: (audioEncoding.bitratePriority ?? Priority.low).toRtcpPriorityType(), - networkPriority: audioEncoding.networkPriority?.toRtcpPriorityType(), - ), - ], + sendEncodings: encodings, ); // addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web) track.transceiver = await room.engine.publisher?.pc.addTransceiver( From 58d7b4e5d9a699e7a2758db68099402bcce0af5d Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:53:26 +0800 Subject: [PATCH 08/14] fix lint --- lib/src/participant/local.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index 7346345d9..dc0f39948 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -52,7 +52,6 @@ import '../types/audio_encoding.dart'; import '../types/data_stream.dart'; import '../types/other.dart'; import '../types/participant_permissions.dart'; -import '../types/priority.dart'; import '../types/rpc.dart'; import '../types/video_dimensions.dart'; import '../utils.dart' show buildStreamId, mimeTypeToVideoCodecString, Utils, compareVersions, isSVCCodec; From 447957fad273202f24c19836a9642d79d1a3e0df Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:43:39 +0800 Subject: [PATCH 09/14] refactor var name --- lib/src/types/audio_encoding.dart | 2 +- lib/src/types/other.dart | 10 +++++----- lib/src/types/video_encoding.dart | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/types/audio_encoding.dart b/lib/src/types/audio_encoding.dart index aa6c9418d..3c0e7b94f 100644 --- a/lib/src/types/audio_encoding.dart +++ b/lib/src/types/audio_encoding.dart @@ -26,7 +26,7 @@ class AudioEncoding { /// Priority for bandwidth allocation. final Priority? bitratePriority; - /// Priority for DSCP marking. Requires `RTCConfiguration.enableDscp` to be true. + /// Priority for DSCP marking. Requires `RTCConfiguration.isDscpEnabled` to be true. final Priority? networkPriority; const AudioEncoding({ diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index 54d0c8917..9542ab4c9 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -143,14 +143,14 @@ class RTCConfiguration { /// Allows DSCP codes to be set on outgoing packets. /// No effect on web platforms. - final bool? enableDscp; + final bool? isDscpEnabled; const RTCConfiguration({ this.iceCandidatePoolSize, this.iceServers, this.iceTransportPolicy, this.encodedInsertableStreams, - this.enableDscp, + this.isDscpEnabled, }); Map toMap() { @@ -163,7 +163,7 @@ class RTCConfiguration { // only supports unified plan 'sdpSemantics': 'unified-plan', if (encodedInsertableStreams != null) 'encodedInsertableStreams': encodedInsertableStreams, - if (enableDscp != null) 'enableDscp': enableDscp, + if (isDscpEnabled != null) 'enableDscp': isDscpEnabled, if (iceServersMap.isNotEmpty) 'iceServers': iceServersMap, if (iceCandidatePoolSize != null) 'iceCandidatePoolSize': iceCandidatePoolSize, if (iceTransportPolicy != null) 'iceTransportPolicy': iceTransportPolicy!.toStringValue(), @@ -176,14 +176,14 @@ class RTCConfiguration { List? iceServers, RTCIceTransportPolicy? iceTransportPolicy, bool? encodedInsertableStreams, - bool? enableDscp, + bool? isDscpEnabled, }) => RTCConfiguration( iceCandidatePoolSize: iceCandidatePoolSize ?? this.iceCandidatePoolSize, iceServers: iceServers ?? this.iceServers, iceTransportPolicy: iceTransportPolicy ?? this.iceTransportPolicy, encodedInsertableStreams: encodedInsertableStreams ?? this.encodedInsertableStreams, - enableDscp: enableDscp ?? this.enableDscp, + isDscpEnabled: isDscpEnabled ?? this.isDscpEnabled, ); } diff --git a/lib/src/types/video_encoding.dart b/lib/src/types/video_encoding.dart index e23ea0c0b..97db59aaf 100644 --- a/lib/src/types/video_encoding.dart +++ b/lib/src/types/video_encoding.dart @@ -29,7 +29,7 @@ class VideoEncoding implements Comparable { /// Priority for bandwidth allocation. final Priority? bitratePriority; - /// Priority for DSCP marking. Requires `RTCConfiguration.enableDscp` to be true. + /// Priority for DSCP marking. Requires `RTCConfiguration.isDscpEnabled` to be true. final Priority? networkPriority; const VideoEncoding({ From 5e0333722585494a890dcad4b219aa1c00b935f5 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:49:01 +0800 Subject: [PATCH 10/14] nit --- lib/src/types/audio_encoding.dart | 2 +- lib/src/types/video_encoding.dart | 2 +- lib/src/utils.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/types/audio_encoding.dart b/lib/src/types/audio_encoding.dart index 3c0e7b94f..ee277299a 100644 --- a/lib/src/types/audio_encoding.dart +++ b/lib/src/types/audio_encoding.dart @@ -82,7 +82,7 @@ class AudioEncoding { extension AudioEncodingExt on AudioEncoding { rtc.RTCRtpEncoding toRTCRtpEncoding() => rtc.RTCRtpEncoding( maxBitrate: maxBitrate, - priority: (bitratePriority ?? Priority.low).toRtcpPriorityType(), + priority: bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, networkPriority: networkPriority?.toRtcpPriorityType(), ); } diff --git a/lib/src/types/video_encoding.dart b/lib/src/types/video_encoding.dart index 97db59aaf..1fe82b94f 100644 --- a/lib/src/types/video_encoding.dart +++ b/lib/src/types/video_encoding.dart @@ -100,7 +100,7 @@ extension VideoEncodingExt on VideoEncoding { maxFramerate: maxFramerate, maxBitrate: maxBitrate, numTemporalLayers: numTemporalLayers, - priority: (bitratePriority ?? Priority.low).toRtcpPriorityType(), + priority: bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, networkPriority: networkPriority?.toRtcpPriorityType(), ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 071948c85..d242e6d90 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -429,7 +429,7 @@ class Utils { rid: videoRids[2 - i], maxBitrate: videoEncoding.maxBitrate ~/ math.pow(3, i), maxFramerate: original.encoding!.maxFramerate, - priority: (videoEncoding.bitratePriority ?? Priority.low).toRtcpPriorityType(), + priority: videoEncoding.bitratePriority?.toRtcpPriorityType() ?? rtc.RTCPriorityType.low, networkPriority: videoEncoding.networkPriority?.toRtcpPriorityType(), )); } From aa3ef450d4b7a06db52eaf2a952c687d5f1fd530 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:55:44 +0800 Subject: [PATCH 11/14] compareTo --- lib/src/types/video_encoding.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/src/types/video_encoding.dart b/lib/src/types/video_encoding.dart index 1fe82b94f..4f24694bb 100644 --- a/lib/src/types/video_encoding.dart +++ b/lib/src/types/video_encoding.dart @@ -77,13 +77,18 @@ class VideoEncoding implements Comparable { @override int compareTo(VideoEncoding other) { // compare bitrates - final result = maxBitrate.compareTo(other.maxBitrate); - // if bitrates are the same, compare by fps - if (result == 0) { - return maxFramerate.compareTo(other.maxFramerate); - } + var result = maxBitrate.compareTo(other.maxBitrate); + if (result != 0) return result; - return result; + // compare by fps + result = maxFramerate.compareTo(other.maxFramerate); + if (result != 0) return result; + + // compare by priority fields for consistency with == and hashCode + result = (bitratePriority?.index ?? -1).compareTo(other.bitratePriority?.index ?? -1); + if (result != 0) return result; + + return (networkPriority?.index ?? -1).compareTo(other.networkPriority?.index ?? -1); } } From 1a7b6b852ce3941e706ecfa93aa313f42837d9c6 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:01:58 +0800 Subject: [PATCH 12/14] doc --- lib/src/types/other.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index 9542ab4c9..46f39b07b 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -141,8 +141,13 @@ class RTCConfiguration { final RTCIceTransportPolicy? iceTransportPolicy; final bool? encodedInsertableStreams; - /// Allows DSCP codes to be set on outgoing packets. - /// No effect on web platforms. + /// Allows DSCP (Differentiated Services Code Point) codes to be set on + /// outgoing packets for network level QoS. + /// + /// This is a best effort hint and network routers may ignore DSCP markings. + /// Required for `networkPriority` to take effect. + /// + /// Ignored on web platforms. final bool? isDscpEnabled; const RTCConfiguration({ From 62f77ae4f0e7c8d4e5d799e37fb095b2413732e6 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:04:11 +0800 Subject: [PATCH 13/14] ValueOrAbsent class --- lib/src/support/value_or_absent.dart | 54 ++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 lib/src/support/value_or_absent.dart diff --git a/lib/src/support/value_or_absent.dart b/lib/src/support/value_or_absent.dart new file mode 100644 index 000000000..df9e0b14f --- /dev/null +++ b/lib/src/support/value_or_absent.dart @@ -0,0 +1,54 @@ +// Copyright 2024 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Allows distinguishing between setting null and no-op in copyWith operations. +/// +/// Example usage: +/// ```dart +/// class MyClass { +/// final String? name; +/// MyClass({this.name}); +/// +/// MyClass copyWith({ValueOrAbsent name = const Absent()}) => +/// MyClass(name: name.valueOr(this.name)); +/// } +/// +/// // Usage: +/// obj.copyWith(name: Value(null)); // explicitly set to null +/// obj.copyWith(name: Value('test')); // set to a value +/// obj.copyWith(); // keep existing (absent) +/// ``` +sealed class ValueOrAbsent { + const ValueOrAbsent(); + + /// Returns the contained value if present, otherwise returns [other]. + T valueOr(T other); +} + +/// Represents an explicitly provided value (including null). +class Value extends ValueOrAbsent { + final T value; + const Value(this.value); + + @override + T valueOr(T other) => value; +} + +/// Represents the absence of a value (use existing). +class Absent extends ValueOrAbsent { + const Absent(); + + @override + T valueOr(T other) => other; +} From b5207712719c1de9f968f417f5c4cdd7d177511b Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:17:35 +0800 Subject: [PATCH 14/14] all copyWith --- lib/livekit_client.dart | 1 + lib/src/core/engine.dart | 7 +- lib/src/core/room.dart | 33 +++---- lib/src/hardware/hardware.dart | 5 +- lib/src/options.dart | 133 ++++++++++++++-------------- lib/src/participant/local.dart | 21 ++--- lib/src/support/native_audio.dart | 18 ++-- lib/src/track/audio_management.dart | 5 +- lib/src/track/local/audio.dart | 3 +- lib/src/track/local/video.dart | 7 +- lib/src/track/options.dart | 82 +++++++++-------- lib/src/types/audio_encoding.dart | 13 +-- lib/src/types/other.dart | 21 ++--- lib/src/types/video_dimensions.dart | 10 ++- lib/src/types/video_encoding.dart | 17 ++-- lib/src/utils.dart | 5 +- 16 files changed, 202 insertions(+), 179 deletions(-) diff --git a/lib/livekit_client.dart b/lib/livekit_client.dart index 27276733b..fda515d9f 100644 --- a/lib/livekit_client.dart +++ b/lib/livekit_client.dart @@ -46,6 +46,7 @@ export 'src/publication/local.dart'; export 'src/publication/remote.dart'; export 'src/publication/track_publication.dart'; export 'src/support/platform.dart'; +export 'src/support/value_or_absent.dart'; export 'src/track/audio_visualizer.dart'; export 'src/track/local/audio.dart'; export 'src/track/local/local.dart'; diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index 0a1ca490f..19ac84cf4 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -39,6 +39,7 @@ import '../publication/local.dart'; import '../support/disposable.dart'; import '../support/platform.dart' show lkPlatformIsTest, lkPlatformIs, PlatformType; import '../support/region_url_provider.dart'; +import '../support/value_or_absent.dart'; import '../support/websocket.dart'; import '../track/local/local.dart'; import '../track/local/video.dart'; @@ -562,18 +563,18 @@ class Engine extends Disposable with EventsEmittable { // The server provided iceServers are only used if // the client's iceServers are not set. if (rtcConfiguration.iceServers == null && serverProvidedIceServers.isNotEmpty) { - rtcConfiguration = connectOptions.rtcConfiguration.copyWith(iceServers: serverProvidedIceServers); + rtcConfiguration = connectOptions.rtcConfiguration.copyWith(iceServers: Value(serverProvidedIceServers)); } // set forceRelay if server response is enabled if (serverResponseForceRelay == lk_models.ClientConfigSetting.ENABLED) { rtcConfiguration = rtcConfiguration.copyWith( - iceTransportPolicy: RTCIceTransportPolicy.relay, + iceTransportPolicy: Value(RTCIceTransportPolicy.relay), ); } if (kIsWeb && (roomOptions.e2eeOptions != null || roomOptions.encryption != null)) { - rtcConfiguration = rtcConfiguration.copyWith(encodedInsertableStreams: true); + rtcConfiguration = rtcConfiguration.copyWith(encodedInsertableStreams: Value(true)); } return rtcConfiguration; diff --git a/lib/src/core/room.dart b/lib/src/core/room.dart index 2508063f8..8e864d96f 100644 --- a/lib/src/core/room.dart +++ b/lib/src/core/room.dart @@ -42,6 +42,7 @@ import '../proto/livekit_rtc.pb.dart' as lk_rtc; import '../support/disposable.dart'; import '../support/platform.dart'; import '../support/region_url_provider.dart'; +import '../support/value_or_absent.dart'; import '../support/websocket.dart' show WebSocketException; import '../track/audio_management.dart'; import '../track/local/audio.dart'; @@ -262,9 +263,9 @@ class Room extends DisposableChangeNotifier with EventsEmittable { if (_e2eeManager != null) { // Disable backup codec when e2ee is enabled roomOptions = roomOptions.copyWith( - defaultVideoPublishOptions: roomOptions.defaultVideoPublishOptions.copyWith( - backupVideoCodec: const BackupVideoCodec(enabled: false), - ), + defaultVideoPublishOptions: Value(roomOptions.defaultVideoPublishOptions.copyWith( + backupVideoCodec: Value(const BackupVideoCodec(enabled: false)), + )), ); } @@ -449,7 +450,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { logger.info('Publishing preconnect audio track'); await _localParticipant!.publishAudioTrack( preConnectAudioBuffer.localTrack!, - publishOptions: roomOptions.defaultAudioPublishOptions.copyWith(preConnect: true), + publishOptions: roomOptions.defaultAudioPublishOptions.copyWith(preConnect: Value(true)), ); } @@ -1095,9 +1096,9 @@ extension RoomHardwareManagementMethods on Room { await Hardware.instance.selectAudioOutput(device); } engine.roomOptions = engine.roomOptions.copyWith( - defaultAudioOutputOptions: roomOptions.defaultAudioOutputOptions.copyWith( - deviceId: device.deviceId, - ), + defaultAudioOutputOptions: Value(roomOptions.defaultAudioOutputOptions.copyWith( + deviceId: Value(device.deviceId), + )), ); } @@ -1112,9 +1113,9 @@ extension RoomHardwareManagementMethods on Room { await Hardware.instance.selectAudioInput(device); } engine.roomOptions = engine.roomOptions.copyWith( - defaultAudioCaptureOptions: roomOptions.defaultAudioCaptureOptions.copyWith( - deviceId: device.deviceId, - ), + defaultAudioCaptureOptions: Value(roomOptions.defaultAudioCaptureOptions.copyWith( + deviceId: Value(device.deviceId), + )), ); } @@ -1126,7 +1127,8 @@ extension RoomHardwareManagementMethods on Room { // Always update roomOptions so future tracks use the correct device engine.roomOptions = engine.roomOptions.copyWith( - defaultCameraCaptureOptions: roomOptions.defaultCameraCaptureOptions.copyWith(deviceId: device.deviceId), + defaultCameraCaptureOptions: + Value(roomOptions.defaultCameraCaptureOptions.copyWith(deviceId: Value(device.deviceId))), ); try { @@ -1137,7 +1139,8 @@ extension RoomHardwareManagementMethods on Room { } catch (e) { // if the switching actually fails, reset it to the previous deviceId engine.roomOptions = engine.roomOptions.copyWith( - defaultCameraCaptureOptions: roomOptions.defaultCameraCaptureOptions.copyWith(deviceId: currentDeviceId), + defaultCameraCaptureOptions: + Value(roomOptions.defaultCameraCaptureOptions.copyWith(deviceId: Value(currentDeviceId))), ); } } @@ -1150,9 +1153,9 @@ extension RoomHardwareManagementMethods on Room { if (lkPlatformIsMobile()) { await Hardware.instance.setSpeakerphoneOn(speakerOn, forceSpeakerOutput: forceSpeakerOutput); engine.roomOptions = engine.roomOptions.copyWith( - defaultAudioOutputOptions: roomOptions.defaultAudioOutputOptions.copyWith( - speakerOn: speakerOn, - ), + defaultAudioOutputOptions: Value(roomOptions.defaultAudioOutputOptions.copyWith( + speakerOn: Value(speakerOn), + )), ); } } diff --git a/lib/src/hardware/hardware.dart b/lib/src/hardware/hardware.dart index 505a4d3d8..133d6b514 100644 --- a/lib/src/hardware/hardware.dart +++ b/lib/src/hardware/hardware.dart @@ -21,6 +21,7 @@ import '../logger.dart'; import '../support/native.dart'; import '../support/native_audio.dart'; import '../support/platform.dart'; +import '../support/value_or_absent.dart'; import '../track/audio_management.dart'; class MediaDevice { @@ -151,9 +152,9 @@ class Hardware { config = await onConfigureNativeAudio.call(audioTrackState); if (_preferSpeakerOutput && _forceSpeakerOutput) { config = config.copyWith( - appleAudioCategoryOptions: { + appleAudioCategoryOptions: Value({ AppleAudioCategoryOption.defaultToSpeaker, - }, + }), ); } logger.fine('configuring for ${audioTrackState} using ${config}...'); diff --git a/lib/src/options.dart b/lib/src/options.dart index af31ce5fd..803b8da69 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -14,6 +14,7 @@ import 'constants.dart'; import 'e2ee/options.dart'; +import 'support/value_or_absent.dart'; import 'track/local/audio.dart'; import 'track/local/video.dart'; import 'track/options.dart'; @@ -143,33 +144,33 @@ class RoomOptions { }); RoomOptions copyWith({ - CameraCaptureOptions? defaultCameraCaptureOptions, - ScreenShareCaptureOptions? defaultScreenShareCaptureOptions, - AudioCaptureOptions? defaultAudioCaptureOptions, - VideoPublishOptions? defaultVideoPublishOptions, - AudioPublishOptions? defaultAudioPublishOptions, - AudioOutputOptions? defaultAudioOutputOptions, - bool? adaptiveStream, - bool? dynacast, - bool? stopLocalTrackOnUnpublish, - E2EEOptions? e2eeOptions, - E2EEOptions? encryption, - bool? fastPublish, + ValueOrAbsent defaultCameraCaptureOptions = const Absent(), + ValueOrAbsent defaultScreenShareCaptureOptions = const Absent(), + ValueOrAbsent defaultAudioCaptureOptions = const Absent(), + ValueOrAbsent defaultVideoPublishOptions = const Absent(), + ValueOrAbsent defaultAudioPublishOptions = const Absent(), + ValueOrAbsent defaultAudioOutputOptions = const Absent(), + ValueOrAbsent adaptiveStream = const Absent(), + ValueOrAbsent dynacast = const Absent(), + ValueOrAbsent stopLocalTrackOnUnpublish = const Absent(), + ValueOrAbsent e2eeOptions = const Absent(), + ValueOrAbsent encryption = const Absent(), + ValueOrAbsent fastPublish = const Absent(), }) { return RoomOptions( - defaultCameraCaptureOptions: defaultCameraCaptureOptions ?? this.defaultCameraCaptureOptions, - defaultScreenShareCaptureOptions: defaultScreenShareCaptureOptions ?? this.defaultScreenShareCaptureOptions, - defaultAudioCaptureOptions: defaultAudioCaptureOptions ?? this.defaultAudioCaptureOptions, - defaultVideoPublishOptions: defaultVideoPublishOptions ?? this.defaultVideoPublishOptions, - defaultAudioPublishOptions: defaultAudioPublishOptions ?? this.defaultAudioPublishOptions, - defaultAudioOutputOptions: defaultAudioOutputOptions ?? this.defaultAudioOutputOptions, - adaptiveStream: adaptiveStream ?? this.adaptiveStream, - dynacast: dynacast ?? this.dynacast, - stopLocalTrackOnUnpublish: stopLocalTrackOnUnpublish ?? this.stopLocalTrackOnUnpublish, + defaultCameraCaptureOptions: defaultCameraCaptureOptions.valueOr(this.defaultCameraCaptureOptions), + defaultScreenShareCaptureOptions: defaultScreenShareCaptureOptions.valueOr(this.defaultScreenShareCaptureOptions), + defaultAudioCaptureOptions: defaultAudioCaptureOptions.valueOr(this.defaultAudioCaptureOptions), + defaultVideoPublishOptions: defaultVideoPublishOptions.valueOr(this.defaultVideoPublishOptions), + defaultAudioPublishOptions: defaultAudioPublishOptions.valueOr(this.defaultAudioPublishOptions), + defaultAudioOutputOptions: defaultAudioOutputOptions.valueOr(this.defaultAudioOutputOptions), + adaptiveStream: adaptiveStream.valueOr(this.adaptiveStream), + dynacast: dynacast.valueOr(this.dynacast), + stopLocalTrackOnUnpublish: stopLocalTrackOnUnpublish.valueOr(this.stopLocalTrackOnUnpublish), // ignore: deprecated_member_use_from_same_package - e2eeOptions: e2eeOptions ?? this.e2eeOptions, - encryption: encryption ?? this.encryption, - fastPublish: fastPublish ?? this.fastPublish, + e2eeOptions: e2eeOptions.valueOr(this.e2eeOptions), + encryption: encryption.valueOr(this.encryption), + fastPublish: fastPublish.valueOr(this.fastPublish), ); } } @@ -194,16 +195,16 @@ class BackupVideoCodec { final VideoEncoding? encoding; final bool simulcast; BackupVideoCodec copyWith({ - bool? enabled, - String? codec, - VideoEncoding? encoding, - bool? simulcast, + ValueOrAbsent enabled = const Absent(), + ValueOrAbsent codec = const Absent(), + ValueOrAbsent encoding = const Absent(), + ValueOrAbsent simulcast = const Absent(), }) { return BackupVideoCodec( - enabled: enabled ?? this.enabled, - codec: codec ?? this.codec, - encoding: encoding ?? this.encoding, - simulcast: simulcast ?? this.simulcast, + enabled: enabled.valueOr(this.enabled), + codec: codec.valueOr(this.codec), + encoding: encoding.valueOr(this.encoding), + simulcast: simulcast.valueOr(this.simulcast), ); } } @@ -272,30 +273,30 @@ class VideoPublishOptions extends PublishOptions { this.degradationPreference}); VideoPublishOptions copyWith({ - VideoEncoding? videoEncoding, - VideoEncoding? screenShareEncoding, - bool? simulcast, - List? videoSimulcastLayers, - List? screenShareSimulcastLayers, - String? videoCodec, - BackupVideoCodec? backupVideoCodec, - DegradationPreference? degradationPreference, - String? scalabilityMode, - String? name, - String? stream, + ValueOrAbsent videoEncoding = const Absent(), + ValueOrAbsent screenShareEncoding = const Absent(), + ValueOrAbsent simulcast = const Absent(), + ValueOrAbsent> videoSimulcastLayers = const Absent(), + ValueOrAbsent> screenShareSimulcastLayers = const Absent(), + ValueOrAbsent videoCodec = const Absent(), + ValueOrAbsent backupVideoCodec = const Absent(), + ValueOrAbsent degradationPreference = const Absent(), + ValueOrAbsent scalabilityMode = const Absent(), + ValueOrAbsent name = const Absent(), + ValueOrAbsent stream = const Absent(), }) => VideoPublishOptions( - videoEncoding: videoEncoding ?? this.videoEncoding, - screenShareEncoding: screenShareEncoding ?? this.screenShareEncoding, - simulcast: simulcast ?? this.simulcast, - videoSimulcastLayers: videoSimulcastLayers ?? this.videoSimulcastLayers, - screenShareSimulcastLayers: 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, + videoEncoding: videoEncoding.valueOr(this.videoEncoding), + screenShareEncoding: screenShareEncoding.valueOr(this.screenShareEncoding), + simulcast: simulcast.valueOr(this.simulcast), + videoSimulcastLayers: videoSimulcastLayers.valueOr(this.videoSimulcastLayers), + screenShareSimulcastLayers: screenShareSimulcastLayers.valueOr(this.screenShareSimulcastLayers), + videoCodec: videoCodec.valueOr(this.videoCodec), + backupVideoCodec: backupVideoCodec.valueOr(this.backupVideoCodec), + degradationPreference: degradationPreference.valueOr(this.degradationPreference), + scalabilityMode: scalabilityMode.valueOr(this.scalabilityMode), + name: name.valueOr(this.name), + stream: stream.valueOr(this.stream), ); @override @@ -332,20 +333,20 @@ class AudioPublishOptions extends PublishOptions { }); AudioPublishOptions copyWith({ - AudioEncoding? encoding, - bool? dtx, - String? name, - String? stream, - bool? red, - bool? preConnect, + ValueOrAbsent encoding = const Absent(), + ValueOrAbsent dtx = const Absent(), + ValueOrAbsent name = const Absent(), + ValueOrAbsent stream = const Absent(), + ValueOrAbsent red = const Absent(), + ValueOrAbsent preConnect = const Absent(), }) => AudioPublishOptions( - encoding: encoding ?? this.encoding, - dtx: dtx ?? this.dtx, - name: name ?? this.name, - stream: stream ?? this.stream, - red: red ?? this.red, - preConnect: preConnect ?? this.preConnect, + encoding: encoding.valueOr(this.encoding), + dtx: dtx.valueOr(this.dtx), + name: name.valueOr(this.name), + stream: stream.valueOr(this.stream), + red: red.valueOr(this.red), + preConnect: preConnect.valueOr(this.preConnect), ); @override diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index dc0f39948..8b21de47f 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -44,6 +44,7 @@ import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_rtc.pb.dart' as lk_rtc; import '../publication/local.dart'; import '../support/platform.dart'; +import '../support/value_or_absent.dart'; import '../track/local/audio.dart'; import '../track/local/local.dart'; import '../track/local/video.dart'; @@ -220,7 +221,7 @@ class LocalParticipant extends Participant { if (publishOptions.videoCodec.toLowerCase() != publishOptions.videoCodec) { publishOptions = publishOptions.copyWith( - videoCodec: publishOptions.videoCodec.toLowerCase(), + videoCodec: Value(publishOptions.videoCodec.toLowerCase()), ); } @@ -231,7 +232,7 @@ class LocalParticipant extends Participant { .where((c) => videoCodecs.any((v) => c.mime.toLowerCase().endsWith(v))) .any((c) => publishOptions?.videoCodec == mimeTypeToVideoCodecString(c.mime))) { publishOptions = publishOptions.copyWith( - videoCodec: mimeTypeToVideoCodecString(room.engine.enabledPublishCodecs![0].mime).toLowerCase(), + videoCodec: Value(mimeTypeToVideoCodecString(room.engine.enabledPublishCodecs![0].mime).toLowerCase()), ); } } @@ -240,19 +241,19 @@ class LocalParticipant extends Participant { final isSVC = isSVCCodec(publishOptions.videoCodec); if (isSVC) { if (!room.roomOptions.dynacast) { - room.engine.roomOptions = room.roomOptions.copyWith(dynacast: true); + room.engine.roomOptions = room.roomOptions.copyWith(dynacast: Value(true)); } if (publishOptions.scalabilityMode == null) { publishOptions = publishOptions.copyWith( - scalabilityMode: 'L3T3_KEY', + scalabilityMode: Value('L3T3_KEY'), ); } // vp9 svc with screenshare has problem to encode, always use L1T3 here if (track.source == TrackSource.screenShareVideo) { publishOptions = publishOptions.copyWith( - scalabilityMode: 'L1T3', + scalabilityMode: Value('L1T3'), ); } } @@ -267,8 +268,8 @@ class LocalParticipant extends Participant { final settings = track.mediaStreamTrack.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); - dimensions = dimensions.copyWith(height: settings['height'] as int); + dimensions = dimensions.copyWith(width: Value(settings['width'] as int)); + dimensions = dimensions.copyWith(height: Value(settings['height'] as int)); } } catch (_) { logger.warning('Failed to call `mediaStreamTrack.getSettings()`'); @@ -392,7 +393,7 @@ class LocalParticipant extends Participant { 'requested a different codec than specified by serverRequested: ${publishOptions.videoCodec}, server: ${updatedCodec}', ); publishOptions = publishOptions.copyWith( - videoCodec: updatedCodec, + videoCodec: Value(updatedCodec), ); // recompute encodings since bitrates/etc could have changed encodings = Utils.computeVideoEncodings( @@ -734,7 +735,7 @@ class LocalParticipant extends Participant { /// track separately, it has to be returned once in getDisplayMedia, /// so we publish it twice here, but only return videoTrack to user. if (captureScreenAudio ?? false) { - captureOptions = captureOptions.copyWith(captureScreenAudio: true); + captureOptions = captureOptions.copyWith(captureScreenAudio: Value(true)); final tracks = await LocalVideoTrack.createScreenShareTracksWithAudio(captureOptions); LocalTrackPublication? publication; for (final track in tracks) { @@ -828,7 +829,7 @@ class LocalParticipant extends Participant { } var options = room.roomOptions.defaultVideoPublishOptions; - options = options.copyWith(simulcast: backupCodecOpts.simulcast); + options = options.copyWith(simulcast: Value(backupCodecOpts.simulcast)); if (backupCodec.toLowerCase() == publication.track?.codec?.toLowerCase()) { // not needed, same codec already published diff --git a/lib/src/support/native_audio.dart b/lib/src/support/native_audio.dart index 70bc59dce..036707bf9 100644 --- a/lib/src/support/native_audio.dart +++ b/lib/src/support/native_audio.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'value_or_absent.dart'; + // https://developer.apple.com/documentation/avfaudio/avaudiosession/category enum AppleAudioCategory { soloAmbient, @@ -139,15 +141,15 @@ class NativeAudioConfiguration { }; NativeAudioConfiguration copyWith({ - AppleAudioCategory? appleAudioCategory, - Set? appleAudioCategoryOptions, - AppleAudioMode? appleAudioMode, - bool? preferSpeakerOutput, + ValueOrAbsent appleAudioCategory = const Absent(), + ValueOrAbsent?> appleAudioCategoryOptions = const Absent(), + ValueOrAbsent appleAudioMode = const Absent(), + ValueOrAbsent preferSpeakerOutput = const Absent(), }) => NativeAudioConfiguration( - appleAudioCategory: appleAudioCategory ?? this.appleAudioCategory, - appleAudioCategoryOptions: appleAudioCategoryOptions ?? this.appleAudioCategoryOptions, - appleAudioMode: appleAudioMode ?? this.appleAudioMode, - preferSpeakerOutput: preferSpeakerOutput ?? this.preferSpeakerOutput, + appleAudioCategory: appleAudioCategory.valueOr(this.appleAudioCategory), + appleAudioCategoryOptions: appleAudioCategoryOptions.valueOr(this.appleAudioCategoryOptions), + appleAudioMode: appleAudioMode.valueOr(this.appleAudioMode), + preferSpeakerOutput: preferSpeakerOutput.valueOr(this.preferSpeakerOutput), ); } diff --git a/lib/src/track/audio_management.dart b/lib/src/track/audio_management.dart index 95e0fa499..4e691780b 100644 --- a/lib/src/track/audio_management.dart +++ b/lib/src/track/audio_management.dart @@ -20,6 +20,7 @@ import '../logger.dart'; import '../support/native.dart'; import '../support/native_audio.dart'; import '../support/platform.dart'; +import '../support/value_or_absent.dart'; import 'local/local.dart'; import 'remote/remote.dart'; @@ -115,9 +116,9 @@ Future _onAudioTrackCountDidChange() async { if (Hardware.instance.forceSpeakerOutput) { config = config.copyWith( - appleAudioCategoryOptions: { + appleAudioCategoryOptions: Value({ AppleAudioCategoryOption.defaultToSpeaker, - }, + }), ); } } diff --git a/lib/src/track/local/audio.dart b/lib/src/track/local/audio.dart index e3675a340..66369dc92 100644 --- a/lib/src/track/local/audio.dart +++ b/lib/src/track/local/audio.dart @@ -23,6 +23,7 @@ import '../../logger.dart'; import '../../options.dart'; import '../../stats/audio_source_stats.dart'; import '../../stats/stats.dart'; +import '../../support/value_or_absent.dart'; import '../../types/other.dart'; import '../audio_management.dart'; import '../options.dart'; @@ -39,7 +40,7 @@ class LocalAudioTrack extends LocalTrack with AudioTrack, LocalAudioManagementMi if (currentOptions.deviceId == deviceId) { return; } - currentOptions = currentOptions.copyWith(deviceId: deviceId); + currentOptions = currentOptions.copyWith(deviceId: Value(deviceId)); if (!muted) { await restartTrack(); } diff --git a/lib/src/track/local/video.dart b/lib/src/track/local/video.dart index 4c35205e5..f5ba04cda 100644 --- a/lib/src/track/local/video.dart +++ b/lib/src/track/local/video.dart @@ -26,6 +26,7 @@ import '../../proto/livekit_models.pb.dart' as lk_models; import '../../proto/livekit_rtc.pb.dart' as lk_rtc; import '../../stats/stats.dart'; import '../../support/platform.dart'; +import '../../support/value_or_absent.dart'; import '../../types/other.dart'; import '../options.dart'; import 'audio.dart'; @@ -242,7 +243,7 @@ class LocalVideoTrack extends LocalTrack with VideoTrack { if (options == null) { options = const ScreenShareCaptureOptions(captureScreenAudio: true); } else { - options = options.copyWith(captureScreenAudio: true); + options = options.copyWith(captureScreenAudio: Value(true)); } final stream = await LocalTrack.createStream(options); @@ -299,12 +300,12 @@ extension LocalVideoTrackExt on LocalVideoTrack { } if (fastSwitch) { - currentOptions = options.copyWith(deviceId: deviceId); + currentOptions = options.copyWith(deviceId: Value(deviceId)); await rtc.Helper.switchCamera(mediaStreamTrack, deviceId, mediaStream); return; } - await restartTrack(options.copyWith(deviceId: deviceId)); + await restartTrack(options.copyWith(deviceId: Value(deviceId))); await replaceTrackForMultiCodecSimulcast(mediaStreamTrack); } diff --git a/lib/src/track/options.dart b/lib/src/track/options.dart index e60d49270..edd27c48b 100644 --- a/lib/src/track/options.dart +++ b/lib/src/track/options.dart @@ -16,6 +16,7 @@ import 'package:flutter/foundation.dart' show kIsWeb; import '../support/native.dart'; import '../support/platform.dart'; +import '../support/value_or_absent.dart'; import '../track/local/audio.dart'; import '../track/local/video.dart'; import '../types/video_parameters.dart'; @@ -108,18 +109,18 @@ class CameraCaptureOptions extends VideoCaptureOptions { // Returns new options with updated properties CameraCaptureOptions copyWith({ - VideoParameters? params, - CameraPosition? cameraPosition, - String? deviceId, - double? maxFrameRate, - bool? stopCameraCaptureOnMute, + ValueOrAbsent params = const Absent(), + ValueOrAbsent cameraPosition = const Absent(), + ValueOrAbsent deviceId = const Absent(), + ValueOrAbsent maxFrameRate = const Absent(), + ValueOrAbsent stopCameraCaptureOnMute = const Absent(), }) => CameraCaptureOptions( - params: params ?? this.params, - cameraPosition: cameraPosition ?? this.cameraPosition, - deviceId: deviceId ?? this.deviceId, - maxFrameRate: maxFrameRate ?? this.maxFrameRate, - stopCameraCaptureOnMute: stopCameraCaptureOnMute ?? this.stopCameraCaptureOnMute, + params: params.valueOr(this.params), + cameraPosition: cameraPosition.valueOr(this.cameraPosition), + deviceId: deviceId.valueOr(this.deviceId), + maxFrameRate: maxFrameRate.valueOr(this.maxFrameRate), + stopCameraCaptureOnMute: stopCameraCaptureOnMute.valueOr(this.stopCameraCaptureOnMute), ); } @@ -158,22 +159,22 @@ class ScreenShareCaptureOptions extends VideoCaptureOptions { : super(params: captureOptions.params); ScreenShareCaptureOptions copyWith({ - bool? useiOSBroadcastExtension, - bool? captureScreenAudio, - VideoParameters? params, - String? sourceId, - double? maxFrameRate, - bool? preferCurrentTab, - String? selfBrowserSurface, + ValueOrAbsent useiOSBroadcastExtension = const Absent(), + ValueOrAbsent captureScreenAudio = const Absent(), + ValueOrAbsent params = const Absent(), + ValueOrAbsent sourceId = const Absent(), + ValueOrAbsent maxFrameRate = const Absent(), + ValueOrAbsent preferCurrentTab = const Absent(), + ValueOrAbsent selfBrowserSurface = const Absent(), }) => ScreenShareCaptureOptions( - useiOSBroadcastExtension: useiOSBroadcastExtension ?? this.useiOSBroadcastExtension, - captureScreenAudio: captureScreenAudio ?? this.captureScreenAudio, - params: params ?? this.params, - sourceId: sourceId ?? deviceId, - maxFrameRate: maxFrameRate ?? this.maxFrameRate, - preferCurrentTab: preferCurrentTab ?? this.preferCurrentTab, - selfBrowserSurface: selfBrowserSurface ?? this.selfBrowserSurface, + useiOSBroadcastExtension: useiOSBroadcastExtension.valueOr(this.useiOSBroadcastExtension), + captureScreenAudio: captureScreenAudio.valueOr(this.captureScreenAudio), + params: params.valueOr(this.params), + sourceId: sourceId.valueOr(deviceId), + maxFrameRate: maxFrameRate.valueOr(this.maxFrameRate), + preferCurrentTab: preferCurrentTab.valueOr(this.preferCurrentTab), + selfBrowserSurface: selfBrowserSurface.valueOr(this.selfBrowserSurface), ); @override @@ -346,20 +347,20 @@ class AudioCaptureOptions extends LocalTrackOptions { } AudioCaptureOptions copyWith({ - String? deviceId, - bool? noiseSuppression, - bool? echoCancellation, - bool? autoGainControl, - bool? highPassFilter, - bool? typingNoiseDetection, + ValueOrAbsent deviceId = const Absent(), + ValueOrAbsent noiseSuppression = const Absent(), + ValueOrAbsent echoCancellation = const Absent(), + ValueOrAbsent autoGainControl = const Absent(), + ValueOrAbsent highPassFilter = const Absent(), + ValueOrAbsent typingNoiseDetection = const Absent(), }) { return AudioCaptureOptions( - deviceId: deviceId ?? this.deviceId, - noiseSuppression: noiseSuppression ?? this.noiseSuppression, - echoCancellation: echoCancellation ?? this.echoCancellation, - autoGainControl: autoGainControl ?? this.autoGainControl, - highPassFilter: highPassFilter ?? this.highPassFilter, - typingNoiseDetection: typingNoiseDetection ?? this.typingNoiseDetection, + deviceId: deviceId.valueOr(this.deviceId), + noiseSuppression: noiseSuppression.valueOr(this.noiseSuppression), + echoCancellation: echoCancellation.valueOr(this.echoCancellation), + autoGainControl: autoGainControl.valueOr(this.autoGainControl), + highPassFilter: highPassFilter.valueOr(this.highPassFilter), + typingNoiseDetection: typingNoiseDetection.valueOr(this.typingNoiseDetection), ); } } @@ -374,10 +375,13 @@ class AudioOutputOptions { const AudioOutputOptions({this.deviceId, this.speakerOn}); - AudioOutputOptions copyWith({String? deviceId, bool? speakerOn}) { + AudioOutputOptions copyWith({ + ValueOrAbsent deviceId = const Absent(), + ValueOrAbsent speakerOn = const Absent(), + }) { return AudioOutputOptions( - deviceId: deviceId ?? this.deviceId, - speakerOn: speakerOn ?? this.speakerOn, + deviceId: deviceId.valueOr(this.deviceId), + speakerOn: speakerOn.valueOr(this.speakerOn), ); } } diff --git a/lib/src/types/audio_encoding.dart b/lib/src/types/audio_encoding.dart index ee277299a..122019be1 100644 --- a/lib/src/types/audio_encoding.dart +++ b/lib/src/types/audio_encoding.dart @@ -15,6 +15,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:meta/meta.dart'; +import '../support/value_or_absent.dart'; import 'priority.dart'; /// A type that represents audio encoding information. @@ -36,14 +37,14 @@ class AudioEncoding { }); AudioEncoding copyWith({ - int? maxBitrate, - Priority? bitratePriority, - Priority? networkPriority, + ValueOrAbsent maxBitrate = const Absent(), + ValueOrAbsent bitratePriority = const Absent(), + ValueOrAbsent networkPriority = const Absent(), }) => AudioEncoding( - maxBitrate: maxBitrate ?? this.maxBitrate, - bitratePriority: bitratePriority ?? this.bitratePriority, - networkPriority: networkPriority ?? this.networkPriority, + maxBitrate: maxBitrate.valueOr(this.maxBitrate), + bitratePriority: bitratePriority.valueOr(this.bitratePriority), + networkPriority: networkPriority.valueOr(this.networkPriority), ); @override diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index 46f39b07b..887e0f2cc 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import '../extensions.dart'; import '../participant/participant.dart'; +import '../support/value_or_absent.dart'; typedef CancelListenFunc = Future Function(); @@ -177,18 +178,18 @@ class RTCConfiguration { // Returns new options with updated properties RTCConfiguration copyWith({ - int? iceCandidatePoolSize, - List? iceServers, - RTCIceTransportPolicy? iceTransportPolicy, - bool? encodedInsertableStreams, - bool? isDscpEnabled, + ValueOrAbsent iceCandidatePoolSize = const Absent(), + ValueOrAbsent?> iceServers = const Absent(), + ValueOrAbsent iceTransportPolicy = const Absent(), + ValueOrAbsent encodedInsertableStreams = const Absent(), + ValueOrAbsent isDscpEnabled = const Absent(), }) => RTCConfiguration( - iceCandidatePoolSize: iceCandidatePoolSize ?? this.iceCandidatePoolSize, - iceServers: iceServers ?? this.iceServers, - iceTransportPolicy: iceTransportPolicy ?? this.iceTransportPolicy, - encodedInsertableStreams: encodedInsertableStreams ?? this.encodedInsertableStreams, - isDscpEnabled: isDscpEnabled ?? this.isDscpEnabled, + iceCandidatePoolSize: iceCandidatePoolSize.valueOr(this.iceCandidatePoolSize), + iceServers: iceServers.valueOr(this.iceServers), + iceTransportPolicy: iceTransportPolicy.valueOr(this.iceTransportPolicy), + encodedInsertableStreams: encodedInsertableStreams.valueOr(this.encodedInsertableStreams), + isDscpEnabled: isDscpEnabled.valueOr(this.isDscpEnabled), ); } diff --git a/lib/src/types/video_dimensions.dart b/lib/src/types/video_dimensions.dart index c975e4b0d..adbec71a0 100644 --- a/lib/src/types/video_dimensions.dart +++ b/lib/src/types/video_dimensions.dart @@ -16,6 +16,8 @@ import 'dart:math' as math; import 'package:meta/meta.dart'; +import '../support/value_or_absent.dart'; + /// A simple class that represents dimensions of video. @immutable class VideoDimensions { @@ -31,12 +33,12 @@ class VideoDimensions { String toString() => '${runtimeType}(${width}x${height})'; VideoDimensions copyWith({ - int? width, - int? height, + ValueOrAbsent width = const Absent(), + ValueOrAbsent height = const Absent(), }) => VideoDimensions( - width ?? this.width, - height ?? this.height, + width.valueOr(this.width), + height.valueOr(this.height), ); // ---------------------------------------------------------------------- diff --git a/lib/src/types/video_encoding.dart b/lib/src/types/video_encoding.dart index 4f24694bb..a1af12229 100644 --- a/lib/src/types/video_encoding.dart +++ b/lib/src/types/video_encoding.dart @@ -15,6 +15,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:meta/meta.dart'; +import '../support/value_or_absent.dart'; import 'priority.dart'; /// A type that represents video encoding information. @@ -40,16 +41,16 @@ class VideoEncoding implements Comparable { }); VideoEncoding copyWith({ - int? maxFramerate, - int? maxBitrate, - Priority? bitratePriority, - Priority? networkPriority, + ValueOrAbsent maxFramerate = const Absent(), + ValueOrAbsent maxBitrate = const Absent(), + ValueOrAbsent bitratePriority = const Absent(), + ValueOrAbsent networkPriority = const Absent(), }) => VideoEncoding( - maxFramerate: maxFramerate ?? this.maxFramerate, - maxBitrate: maxBitrate ?? this.maxBitrate, - bitratePriority: bitratePriority ?? this.bitratePriority, - networkPriority: networkPriority ?? this.networkPriority, + maxFramerate: maxFramerate.valueOr(this.maxFramerate), + maxBitrate: maxBitrate.valueOr(this.maxBitrate), + bitratePriority: bitratePriority.valueOr(this.bitratePriority), + networkPriority: networkPriority.valueOr(this.networkPriority), ); @override diff --git a/lib/src/utils.dart b/lib/src/utils.dart index d242e6d90..fad3bf5b1 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -26,6 +26,7 @@ import 'package:meta/meta.dart'; import './proto/livekit_models.pb.dart' as lk_models; import './support/native.dart'; +import './support/value_or_absent.dart'; import 'extensions.dart'; import 'livekit.dart'; import 'logger.dart'; @@ -285,10 +286,10 @@ class Utils { if (codec != null) { switch (codec) { case 'av1': - result = result.copyWith(maxBitrate: (result.maxBitrate * 0.7).toInt()); + result = result.copyWith(maxBitrate: Value((result.maxBitrate * 0.7).toInt())); break; case 'vp9': - result = result.copyWith(maxBitrate: (result.maxBitrate * 0.85).toInt()); + result = result.copyWith(maxBitrate: Value((result.maxBitrate * 0.85).toInt())); break; default: break;