Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changes/priority-control
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
minor type="added" "Expose separate bitrate/network priorities for media tracks"
8 changes: 8 additions & 0 deletions Sources/LiveKit/Core/RTC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ actor RTC {
result.scalabilityMode = scalabilityMode.rawStringValue
}

if let bitratePriority = encoding?.bitratePriority {
result.bitratePriority = bitratePriority.toBitratePriority()
}

if let networkPriority = encoding?.networkPriority {
result.networkPriority = networkPriority.toRTCPriority()
}

return result
}
}
2 changes: 2 additions & 0 deletions Sources/LiveKit/Core/Room+Engine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ extension Room {
rtcConfiguration.iceTransportPolicy = connectOptions.iceTransportPolicy.toRTCType()
}

rtcConfiguration.enableDscp = connectOptions.isDscpEnabled

return rtcConfiguration
}

Expand Down
10 changes: 6 additions & 4 deletions Sources/LiveKit/Extensions/CustomStringConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,12 @@ extension LKRTCRtpEncodingParameters {
"RTCRtpEncodingParameters(" +
"rid: \(String(describing: rid)), " +
"isActive: \(String(describing: isActive)), " +
"minBitrateBps: \(String(describing: minBitrateBps))" +
"maxBitrateBps: \(String(describing: maxBitrateBps))" +
"maxFramerate: \(String(describing: maxFramerate))" +
"scaleResolutionDownBy: \(String(describing: scaleResolutionDownBy))" +
"minBitrateBps: \(String(describing: minBitrateBps)), " +
"maxBitrateBps: \(String(describing: maxBitrateBps)), " +
"maxFramerate: \(String(describing: maxFramerate)), " +
"scaleResolutionDownBy: \(String(describing: scaleResolutionDownBy)), " +
"bitratePriority: \(bitratePriority), " +
"networkPriority: \(networkPriority)" +
")"
}
}
Expand Down
10 changes: 8 additions & 2 deletions Sources/LiveKit/Protocols/MediaEncoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@

import Foundation

@objc
public protocol MediaEncoding {
//
/// Maximum bitrate in bits per second.
var maxBitrate: Int { get }

/// Priority for bandwidth allocation.
var bitratePriority: Priority? { get }

/// Priority for DSCP marking.
/// Requires `ConnectOptions.isDscpEnabled` to be true.
var networkPriority: Priority? { get }
}
Comment on lines 19 to 29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid source-breaking public protocol changes by providing defaults.

Adding new requirements to a public protocol breaks downstream conformers. Provide default implementations returning nil (or introduce a new protocol) to preserve source compatibility.

🔧 Proposed fix
 public protocol MediaEncoding {
     /// Maximum bitrate in bits per second.
     var maxBitrate: Int { get }

     /// Priority for bandwidth allocation.
     var bitratePriority: Priority? { get }

     /// Priority for DSCP marking.
     /// Requires `ConnectOptions.isDscpEnabled` to be true.
     var networkPriority: Priority? { get }
 }
+
+public extension MediaEncoding {
+    var bitratePriority: Priority? { nil }
+    var networkPriority: Priority? { nil }
+}
🤖 Prompt for AI Agents
In `@Sources/LiveKit/Protocols/MediaEncoding.swift` around lines 19 - 29, The new
optional requirements on the public protocol MediaEncoding (bitratePriority and
networkPriority) will break downstream conformers; add a public extension for
MediaEncoding that provides default implementations returning nil for
bitratePriority and networkPriority (keeping maxBitrate required) so existing
conforming types remain source-compatible. Implement the defaults in an
extension on MediaEncoding that returns nil for both bitratePriority and
networkPriority.

21 changes: 20 additions & 1 deletion Sources/LiveKit/Types/AudioEncoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,40 @@ public final class AudioEncoding: NSObject, MediaEncoding, Sendable {
@objc
public let maxBitrate: Int

/// Priority for bandwidth allocation.
public let bitratePriority: Priority?

/// Priority for DSCP marking.
/// Requires `ConnectOptions.isDscpEnabled` to be true.
public let networkPriority: Priority?

@objc
public init(maxBitrate: Int) {
self.maxBitrate = maxBitrate
bitratePriority = nil
networkPriority = nil
}

public init(maxBitrate: Int, bitratePriority: Priority?, networkPriority: Priority?) {
self.maxBitrate = maxBitrate
self.bitratePriority = bitratePriority
self.networkPriority = networkPriority
}

// MARK: - Equal

override public func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Self else { return false }
return maxBitrate == other.maxBitrate
return maxBitrate == other.maxBitrate &&
bitratePriority == other.bitratePriority &&
networkPriority == other.networkPriority
}

override public var hash: Int {
var hasher = Hasher()
hasher.combine(maxBitrate)
hasher.combine(bitratePriority)
hasher.combine(networkPriority)
return hasher.finalize()
}
}
Expand Down
10 changes: 10 additions & 0 deletions Sources/LiveKit/Types/Options/ConnectOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public final class ConnectOptions: NSObject, Sendable {
@objc
public let iceTransportPolicy: IceTransportPolicy

/// Allows DSCP codes to be set on outgoing packets when network priority is used.
/// Defaults to false.
@objc
public let isDscpEnabled: Bool

/// Enable microphone concurrently while connecting.
@objc
public let enableMicrophone: Bool
Expand All @@ -92,6 +97,7 @@ public final class ConnectOptions: NSObject, Sendable {
publisherTransportConnectTimeout = .defaultTransportState
iceServers = []
iceTransportPolicy = .all
isDscpEnabled = false
enableMicrophone = false
protocolVersion = .v16
}
Expand All @@ -106,6 +112,7 @@ public final class ConnectOptions: NSObject, Sendable {
publisherTransportConnectTimeout: TimeInterval = .defaultTransportState,
iceServers: [IceServer] = [],
iceTransportPolicy: IceTransportPolicy = .all,
isDscpEnabled: Bool = false,
enableMicrophone: Bool = false,
protocolVersion: ProtocolVersion = .v16)
{
Expand All @@ -118,6 +125,7 @@ public final class ConnectOptions: NSObject, Sendable {
self.publisherTransportConnectTimeout = publisherTransportConnectTimeout
self.iceServers = iceServers
self.iceTransportPolicy = iceTransportPolicy
self.isDscpEnabled = isDscpEnabled
self.enableMicrophone = enableMicrophone
self.protocolVersion = protocolVersion
}
Expand All @@ -135,6 +143,7 @@ public final class ConnectOptions: NSObject, Sendable {
publisherTransportConnectTimeout == other.publisherTransportConnectTimeout &&
iceServers == other.iceServers &&
iceTransportPolicy == other.iceTransportPolicy &&
isDscpEnabled == other.isDscpEnabled &&
enableMicrophone == other.enableMicrophone &&
protocolVersion == other.protocolVersion
}
Expand All @@ -150,6 +159,7 @@ public final class ConnectOptions: NSObject, Sendable {
hasher.combine(publisherTransportConnectTimeout)
hasher.combine(iceServers)
hasher.combine(iceTransportPolicy)
hasher.combine(isDscpEnabled)
hasher.combine(enableMicrophone)
hasher.combine(protocolVersion)
return hasher.finalize()
Expand Down
58 changes: 58 additions & 0 deletions Sources/LiveKit/Types/Priority.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2026 LiveKit
*
* 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.
*/

internal import LiveKitWebRTC

/// Priority levels for RTP encoding parameters.
///
/// `bitratePriority` controls WebRTC internal bandwidth allocation between streams.
/// When not set, WebRTC uses its default value equivalent to `.low` (1.0x).
///
/// `networkPriority` controls DSCP marking for network-level QoS.
/// Requires `ConnectOptions.isDscpEnabled` to be true.
@objc
public enum Priority: Int, Sendable {
case veryLow
case low
case medium
case high
}

extension Priority {
/// Converts to the native RTCPriority enum used for networkPriority.
func toRTCPriority() -> LKRTCPriority {
switch self {
case .veryLow: .veryLow
case .low: .low
case .medium: .medium
case .high: .high
}
}

/// Converts to bitratePriority double value.
/// - veryLow: 0.5x
/// - low: 1.0x (default)
/// - medium: 2.0x
/// - high: 4.0x
func toBitratePriority() -> Double {
switch self {
case .veryLow: 0.5
case .low: 1.0
case .medium: 2.0
case .high: 4.0
}
}
}
22 changes: 21 additions & 1 deletion Sources/LiveKit/Types/VideoEncoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,44 @@ public final class VideoEncoding: NSObject, MediaEncoding, Sendable {
@objc
public let maxFps: Int

/// Priority for bandwidth allocation.
public let bitratePriority: Priority?

/// Priority for DSCP marking.
/// Requires `ConnectOptions.isDscpEnabled` to be true.
public let networkPriority: Priority?

@objc
public init(maxBitrate: Int, maxFps: Int) {
self.maxBitrate = maxBitrate
self.maxFps = maxFps
bitratePriority = nil
networkPriority = nil
}

public init(maxBitrate: Int, maxFps: Int, bitratePriority: Priority?, networkPriority: Priority?) {
self.maxBitrate = maxBitrate
self.maxFps = maxFps
self.bitratePriority = bitratePriority
self.networkPriority = networkPriority
}

// MARK: - Equal

override public func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Self else { return false }
return maxBitrate == other.maxBitrate &&
maxFps == other.maxFps
maxFps == other.maxFps &&
bitratePriority == other.bitratePriority &&
networkPriority == other.networkPriority
}

override public var hash: Int {
var hasher = Hasher()
hasher.combine(maxBitrate)
hasher.combine(maxFps)
hasher.combine(bitratePriority)
hasher.combine(networkPriority)
return hasher.finalize()
}
}
11 changes: 8 additions & 3 deletions Sources/LiveKit/Types/VideoParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,14 @@ extension VideoParameters {
let dimensions = Dimensions(width: Int32((Double(dimensions.width) / $0.scaleDownBy).rounded(.down)),
height: Int32((Double(dimensions.height) / $0.scaleDownBy).rounded(.down)))
let bitrate2 = Int((Double(encoding.maxBitrate) / (pow(Double($0.scaleDownBy), 2) * (Double(encoding.maxFps) / Double($0.fps)))).rounded(.down))
let encoding = VideoEncoding(maxBitrate: Swift.max(150_000, bitrate2), maxFps: $0.fps)

return VideoParameters(dimensions: dimensions, encoding: encoding)
let layerEncoding = VideoEncoding(
maxBitrate: Swift.max(150_000, bitrate2),
maxFps: $0.fps,
bitratePriority: encoding.bitratePriority,
networkPriority: encoding.networkPriority
)

return VideoParameters(dimensions: dimensions, encoding: layerEncoding)
}
}

Expand Down
Loading