Files
2025-11-19 00:07:09 +09:00

181 lines
5.5 KiB
Swift

import AVFAudio
import CoreMedia
import Foundation
import libdatachannel
protocol RTCTrackDelegate: AnyObject {
func track(_ track: RTCTrack, readyStateChanged readyState: RTCTrack.ReadyState)
func track(_ track: RTCTrack, didOutput buffer: CMSampleBuffer)
func track(_ track: RTCTrack, didOutput buffer: AVAudioCompressedBuffer, when: AVAudioTime)
}
class RTCTrack: RTCChannel {
enum ReadyState {
case connecting
case open
case closing
case closed
}
let id: Int32
weak var delegate: (any RTCTrackDelegate)?
var mid: String {
do {
return try CUtil.getString { buffer, size in
rtcGetTrackMid(id, buffer, size)
}
} catch {
logger.warn(error)
return ""
}
}
var description: String {
do {
return try CUtil.getString { buffer, size in
rtcGetTrackDescription(id, buffer, size)
}
} catch {
logger.warn(error)
return ""
}
}
var ssrc: UInt32 {
do {
return try CUtil.getUInt32 { buffer, size in
rtcGetSsrcsForTrack(id, buffer, size)
}
} catch {
logger.warn(error)
return 0
}
}
private(set) var readyState: ReadyState = .connecting {
didSet {
switch readyState {
case .connecting:
break
case .open:
do {
packetizer = try makePacketizer()
} catch {
logger.warn(error)
}
case .closing:
break
case .closed:
break
}
delegate?.track(self, readyStateChanged: readyState)
}
}
private var packetizer: (any RTPPacketizer)?
init(id: Int32) throws {
self.id = id
try RTCError.check(id)
do {
rtcSetUserPointer(id, Unmanaged.passUnretained(self).toOpaque())
try RTCError.check(rtcSetOpenCallback(id) { _, pointer in
guard let pointer else { return }
Unmanaged<RTCTrack>.fromOpaque(pointer).takeUnretainedValue().readyState = .open
})
try RTCError.check(rtcSetClosedCallback(id) { _, pointer in
guard let pointer else { return }
Unmanaged<RTCTrack>.fromOpaque(pointer).takeUnretainedValue().readyState = .closed
})
try RTCError.check(rtcSetMessageCallback(id) { _, bytes, size, pointer in
guard let bytes, let pointer else { return }
if 0 <= size {
let data = Data(bytes: bytes, count: Int(size))
Unmanaged<RTCTrack>.fromOpaque(pointer).takeUnretainedValue().didReceiveMessage(data)
}
})
try RTCError.check(rtcSetErrorCallback(id) { _, error, pointer in
guard let error, let pointer else { return }
Unmanaged<RTCTrack>.fromOpaque(pointer).takeUnretainedValue().errorOccurred(String(cString: error))
})
} catch {
rtcDeleteTrack(id)
throw error
}
}
deinit {
rtcDeleteTrack(id)
}
func send(_ buffer: CMSampleBuffer) {
packetizer?.append(buffer) { packet in
try? send(packet.data)
}
}
func send(_ buffer: AVAudioCompressedBuffer, when: AVAudioTime) {
packetizer?.append(buffer, when: when) { packet in
try? send(packet.data)
}
}
func didReceiveMessage(_ message: Data) {
do {
let packet = try RTPPacket(message)
packetizer?.append(packet)
} catch {
logger.warn(error)
}
}
private func errorOccurred(_ error: String) {
logger.warn(error)
}
private func makePacketizer() throws -> (any RTPPacketizer)? {
let description = try SDPMediaDescription(sdp: description)
var result: (any RTPPacketizer)?
let rtpmap = description.attributes.compactMap { attr -> (UInt8, String, Int, Int?)? in
if case let .rtpmap(payload, codec, clock, channel) = attr { return (payload, codec, clock, channel) }
return nil
}
guard !rtpmap.isEmpty else {
return nil
}
switch rtpmap[0].1.lowercased() {
case "opus":
let packetizer = RTPOpusPacketizer<RTCTrack>(ssrc: ssrc, payloadType: description.payload)
packetizer.delegate = self
result = packetizer
case "h264":
let packetizer = RTPH264Packetizer<RTCTrack>(ssrc: ssrc, payloadType: description.payload)
packetizer.delegate = self
result = packetizer
default:
break
}
for attribute in description.attributes {
switch attribute {
case .fmtp(_, let params):
result?.formatParameter = RTPFormatParameter(params)
default:
break
}
}
return result
}
}
extension RTCTrack: RTPPacketizerDelegate {
// MARK: RTPPacketizerDelegate
func packetizer(_ packetizer: some RTPPacketizer, didOutput buffer: CMSampleBuffer) {
delegate?.track(self, didOutput: buffer)
}
func packetizer(_ packetizer: some RTPPacketizer, didOutput buffer: AVAudioCompressedBuffer, when: AVAudioTime) {
delegate?.track(self, didOutput: buffer, when: when)
}
}