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
6 changes: 3 additions & 3 deletions Sources/LiveKit/Extensions/RTCRtpTransceiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ extension LKRTCRtpTransceiver: Loggable {
let allVideoCodecs = RTC.videoSenderCapabilities.codecs

// Get the RTCRtpCodecCapability of the preferred codec
let preferredCodecCapability = allVideoCodecs.first { $0.name.lowercased() == codec.id }
let preferredCodecCapability = allVideoCodecs.first { $0.name.lowercased() == codec.name }

// Get list of capabilities other than the preferred one
let otherCapabilities = allVideoCodecs.filter {
$0.name.lowercased() != codec.id && $0.name.lowercased() != exceptCodec?.id
$0.name.lowercased() != codec.name && $0.name.lowercased() != exceptCodec?.name
}

// Bring preferredCodecCapability to the front and combine all capabilities
Expand All @@ -44,7 +44,7 @@ extension LKRTCRtpTransceiver: Loggable {

log("codecPreferences set: \(codecPreferences.map { String(describing: $0) }.joined(separator: ", "))")

if codecPreferences.first?.name.lowercased() != codec.id {
if codecPreferences.first?.name.lowercased() != codec.name {
log("Preferred codec is not first of codecPreferences", .error)
}
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/LiveKit/Participant/LocalParticipant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ extension LocalParticipant {
{
let room = try requireRoom()

let videoCodec = try subscribedCodec.toVideoCodec()
guard let videoCodec = subscribedCodec.toVideoCodec() else { return }

log("[Publish/Backup] Additional video codec: \(videoCodec)...")

Expand Down Expand Up @@ -468,7 +468,7 @@ extension LocalParticipant {
$0.simulcastCodecs = [
Livekit_SimulcastCodec.with { sc in
sc.cid = sender.senderId
sc.codec = videoCodec.id
sc.codec = videoCodec.name
},
]

Expand Down Expand Up @@ -563,7 +563,7 @@ private extension LocalParticipant {
Livekit_SimulcastCodec.with {
$0.cid = track.mediaTrack.trackId
if let preferredCodec = publishOptions.preferredCodec {
$0.codec = preferredCodec.id
$0.codec = preferredCodec.name
}
},
]
Expand All @@ -572,7 +572,7 @@ private extension LocalParticipant {
// Add backup codec to simulcast codecs...
let lkSimulcastCodec = Livekit_SimulcastCodec.with {
$0.cid = ""
$0.codec = backupCodec.id
$0.codec = backupCodec.name
}
simulcastCodecs.append(lkSimulcastCodec)
}
Expand Down Expand Up @@ -633,7 +633,7 @@ private extension LocalParticipant {

if track is LocalVideoTrack {
if let firstCodecMime = addTrackResult.trackInfo.codecs.first?.mimeType,
let firstVideoCodec = try? VideoCodec.from(mimeType: firstCodecMime)
let firstVideoCodec = VideoCodec.from(mimeType: firstCodecMime)
{
log("[Publish] First video codec: \(firstVideoCodec)")
track._state.mutate { $0.videoCodec = firstVideoCodec }
Expand Down
4 changes: 1 addition & 3 deletions Sources/LiveKit/Track/VideoTrack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ protocol VideoTrack_Internal where Self: Track {
extension VideoTrack {
// Update a single SubscribedCodec
func _set(subscribedCodec: Livekit_SubscribedCodec) throws -> Bool {
// ...
let videoCodec = try VideoCodec.from(id: subscribedCodec.codec)
guard let videoCodec = VideoCodec.from(name: subscribedCodec.codec) else { return false }

// Check if main sender is sending the codec...
if let rtpSender = _state.rtpSender, videoCodec == _state.videoCodec {
Expand All @@ -61,7 +60,6 @@ extension VideoTrack {

// Update an array of SubscribedCodecs
func _set(subscribedCodecs: [Livekit_SubscribedCodec]) throws -> [Livekit_SubscribedCodec] {
// ...
var missingCodecs: [Livekit_SubscribedCodec] = []

for subscribedCodec in subscribedCodecs {
Expand Down
33 changes: 33 additions & 0 deletions Sources/LiveKit/Types/Codec/Codec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2025 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.
*/

public protocol Codec: Identifiable, Sendable {
var name: String { get }
var mediaType: String { get }
static func from(name: String) -> Self?
static func from(mimeType: String) -> Self?
}

public extension Codec {
// Identifiable by mimeString
var id: String {
mimeType
}

var mimeType: String {
"\(mediaType)/\(name)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,71 +17,64 @@
import Foundation

@objc
public final class VideoCodec: NSObject, Identifiable, Sendable {
public static func from(id: String) throws -> VideoCodec {
// Try to find codec from id...
guard let codec = all.first(where: { $0.id == id }) else {
throw LiveKitError(.invalidState, message: "Failed to create VideoCodec from id")
}

public final class VideoCodec: NSObject, Codec {
public static func from(name: String) -> VideoCodec? {
guard let codec = all.first(where: { $0.name == name.lowercased() }) else { return nil }
return codec
}

public static func from(mimeType: String) throws -> VideoCodec {
public static func from(mimeType: String) -> VideoCodec? {
let parts = mimeType.lowercased().split(separator: "/")
var id = String(parts.first!)
if parts.count > 1 {
if parts[0] != "video" { throw LiveKitError(.invalidState, message: "MIME type must be video") }
id = String(parts[1])
}
return try from(id: id)
guard parts.count >= 2, parts[0] == "video" else { return nil }
return from(name: String(parts[1]))
}

public static let h264 = VideoCodec(id: "h264", backup: true)
public static let vp8 = VideoCodec(id: "vp8", backup: true)
public static let vp9 = VideoCodec(id: "vp9", isSVC: true)
public static let av1 = VideoCodec(id: "av1", isSVC: true)
public static let h264 = VideoCodec(name: "h264", isBackup: true)
public static let vp8 = VideoCodec(name: "vp8", isBackup: true)
public static let vp9 = VideoCodec(name: "vp9", isSVC: true)
public static let av1 = VideoCodec(name: "av1", isSVC: true)

public static let all: [VideoCodec] = [.h264, .vp8, .vp9, .av1]
public static let allBackup: [VideoCodec] = [.h264, .vp8]

// codec Id
public let id: String
public let mediaType = "video"
// Name of the codec such as "vp8".
public let name: String
// Whether the codec can be used as `backup`
public let isBackup: Bool
// Whether the codec can be used as `backup`
// Whether the codec is an SVC codec.
public let isSVC: Bool

// Internal only
init(id: String,
backup: Bool = false,
init(name: String,
isBackup: Bool = false,
isSVC: Bool = false)
{
self.id = id
isBackup = backup
self.name = name
self.isBackup = isBackup
self.isSVC = isSVC
}

// MARK: - Equal

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

override public var hash: Int {
var hasher = Hasher()
hasher.combine(id)
hasher.combine(name)
return hasher.finalize()
}

override public var description: String {
"VideoCodec(id: \(id))"
"VideoCodec(name: \(name))"
}
}

extension Livekit_SubscribedCodec {
func toVideoCodec() throws -> VideoCodec {
try VideoCodec.from(id: codec)
func toVideoCodec() -> VideoCodec? {
VideoCodec.from(name: codec)
}
}
35 changes: 35 additions & 0 deletions Tests/LiveKitTests/CodecTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2025 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.
*/

@testable import LiveKit
import XCTest

class CodecTests: LKTestCase {
func testParseCodec() throws {
// Video codecs
let vp8 = VideoCodec.from(mimeType: "video/vp8")
XCTAssert(vp8 == .vp8)

let vp9 = VideoCodec.from(mimeType: "video/vp9")
XCTAssert(vp9 == .vp9)

let h264 = VideoCodec.from(mimeType: "video/h264")
XCTAssert(h264 == .h264)

let av1 = VideoCodec.from(mimeType: "video/av1")
XCTAssert(av1 == .av1)
}
}
Loading