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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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({