Files
VibeCoding/webrtc_controller_ios/web_rtc_controller_ios/WebRTC/WebRTCClient.swift

322 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
import WebRTC
protocol WebRTCClientDelegate: AnyObject {
/// ICE
func webRTCClientDidConnect()
///
func webRTCClientDidDisconnect()
///
func webRTCClient(didFail error: String)
/// 0=WebRTC 1=
func webRTCClient(didReportStreamMode mode: Int)
///
func webRTCClient(didReportResolution width: Int, height: Int)
}
/// WebRTC PeerConnection Offer/Answer
/// ICE / DataChannel
/// Android WebRtcClient
final class WebRTCClient: NSObject {
static let streamModeWebRTC: Int32 = 0
static let streamModeSelfCodec: Int32 = 1
private static let controlChannelLabel = "control_channel"
private static let videoChannelLabel = "video_channel"
private static let factory: RTCPeerConnectionFactory = {
RTCInitializeSSL()
return RTCPeerConnectionFactory(
encoderFactory: RTCDefaultVideoEncoderFactory(),
decoderFactory: RTCDefaultVideoDecoderFactory())
}()
weak var delegate: WebRTCClientDelegate?
/// H.264 video_channel
var selfCodecDecoder: SelfCodecDecoder?
private let signaling: SignalingClient
private let myDeviceId: String
private var targetDeviceId: String = ""
private var peerConnection: RTCPeerConnection?
private var controlChannel: RTCDataChannel?
private var videoChannel: RTCDataChannel?
private weak var remoteRenderer: RTCVideoRenderer?
private var remoteVideoTrack: RTCVideoTrack?
private var connected = false
init(signaling: SignalingClient, myDeviceId: String) {
self.signaling = signaling
self.myDeviceId = myDeviceId
super.init()
}
var isDataChannelOpen: Bool {
controlChannel?.readyState == .open
}
// MARK: -
/// PeerConnection DataChannel Offer
func createOffer(targetDeviceId: String,
renderer: RTCVideoRenderer,
authType: String?,
authValue: String?) {
self.targetDeviceId = targetDeviceId
self.remoteRenderer = renderer
let config = RTCConfiguration()
config.iceServers = [
RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"]),
RTCIceServer(urlStrings: ["stun:www.ttstd.com:3478"]),
RTCIceServer(urlStrings: ["turn:www.ttstd.com:3478"],
username: "ttstd",
credential: "ttstd123")
]
config.sdpSemantics = .unifiedPlan
config.continualGatheringPolicy = .gatherContinually
config.iceCandidatePoolSize = 10
config.iceTransportPolicy = .all
let pcConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
guard let pc = Self.factory.peerConnection(with: config, constraints: pcConstraints, delegate: self) else {
notifyFail("PeerConnection 创建失败")
return
}
peerConnection = pc
//
let transceiverInit = RTCRtpTransceiverInit()
transceiverInit.direction = .recvOnly
pc.addTransceiver(of: .video, init: transceiverInit)
//
let controlConfig = RTCDataChannelConfiguration()
controlConfig.isOrdered = true
controlChannel = pc.dataChannel(forLabel: Self.controlChannelLabel, configuration: controlConfig)
controlChannel?.delegate = self
//
let videoConfig = RTCDataChannelConfiguration()
videoConfig.isOrdered = false
videoConfig.maxRetransmits = 0
videoChannel = pc.dataChannel(forLabel: Self.videoChannelLabel, configuration: videoConfig)
videoChannel?.delegate = self
let offerConstraints = RTCMediaConstraints(
mandatoryConstraints: [
"OfferToReceiveVideo": "true",
"OfferToReceiveAudio": "false"
],
optionalConstraints: nil)
pc.offer(for: offerConstraints) { [weak self] sdp, error in
guard let self else { return }
if let error {
self.notifyFail("创建 Offer 失败: \(error.localizedDescription)")
return
}
guard let sdp else {
self.notifyFail("创建 Offer 失败: SDP 为空")
return
}
pc.setLocalDescription(sdp) { [weak self] error in
guard let self else { return }
if let error {
self.notifyFail("设置本地 SDP 失败: \(error.localizedDescription)")
return
}
self.sendOffer(sdp: sdp.sdp, authType: authType, authValue: authValue)
}
}
}
private func sendOffer(sdp: String, authType: String?, authValue: String?) {
var msg = SignalMessage()
msg.type = "OFFER"
msg.fromDeviceId = myDeviceId
msg.toDeviceId = targetDeviceId
msg.payload = SignalMessage.encodePayload(["sdp": sdp])
msg.authType = authType
msg.authValue = authValue
signaling.send(msg)
}
/// Answer
func handleAnswer(sdp: String) {
let desc = RTCSessionDescription(type: .answer, sdp: sdp)
peerConnection?.setRemoteDescription(desc) { [weak self] error in
if let error {
self?.notifyFail("设置远端 SDP 失败: \(error.localizedDescription)")
}
}
}
/// ICE
func addIceCandidate(sdpMid: String, sdpMLineIndex: Int32, candidate: String) {
let ice = RTCIceCandidate(sdp: candidate, sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid)
peerConnection?.add(ice) { error in
if let error {
NSLog("[WebRTC] addIceCandidate error: \(error.localizedDescription)")
}
}
}
// MARK: -
func sendControlCommand(_ message: ControlMessage) {
guard let channel = controlChannel, channel.readyState == .open else { return }
let buffer = RTCDataBuffer(data: message.serializedData(), isBinary: true)
channel.sendData(buffer)
}
/// width=0
func requestResolutionChange(width: Int32, height: Int32, fps: Int32) {
var msg = ControlMessage()
msg.action = .setResolution
msg.width = width
msg.height = height
msg.fps = fps
sendControlCommand(msg)
}
/// 0=WebRTC 1=
func sendStreamMode(_ mode: Int32) {
var msg = ControlMessage()
msg.action = .setStreamMode
msg.streamMode = mode
sendControlCommand(msg)
}
// MARK: -
func stats(_ completion: @escaping (RTCStatisticsReport) -> Void) {
peerConnection?.statistics(completionHandler: completion)
}
// MARK: -
func close() {
connected = false
controlChannel?.close()
videoChannel?.close()
controlChannel = nil
videoChannel = nil
if let track = remoteVideoTrack, let renderer = remoteRenderer {
track.remove(renderer)
}
remoteVideoTrack = nil
peerConnection?.close()
peerConnection = nil
}
private func notifyFail(_ text: String) {
DispatchQueue.main.async {
self.delegate?.webRTCClient(didFail: text)
}
}
}
// MARK: - RTCPeerConnectionDelegate
extension WebRTCClient: RTCPeerConnectionDelegate {
func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {}
func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {}
func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {}
func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {}
func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
DispatchQueue.main.async {
switch newState {
case .connected, .completed:
if !self.connected {
self.connected = true
self.delegate?.webRTCClientDidConnect()
}
case .disconnected, .failed, .closed:
if self.connected {
self.connected = false
self.delegate?.webRTCClientDidDisconnect()
} else if newState == .failed {
self.delegate?.webRTCClient(didFail: "ICE 连接失败")
}
default:
break
}
}
}
func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {}
func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
var msg = SignalMessage()
msg.type = "ICE_CANDIDATE"
msg.fromDeviceId = myDeviceId
msg.toDeviceId = targetDeviceId
msg.payload = SignalMessage.encodePayload([
"sdpMid": candidate.sdpMid ?? "",
"sdpMLineIndex": Int(candidate.sdpMLineIndex),
"candidate": candidate.sdp
])
signaling.send(msg)
}
func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {}
func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
// Offer
dataChannel.delegate = self
if dataChannel.label == Self.videoChannelLabel {
videoChannel = dataChannel
} else if dataChannel.label == Self.controlChannelLabel {
controlChannel = dataChannel
}
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didAdd rtpReceiver: RTCRtpReceiver,
streams mediaStreams: [RTCMediaStream]) {
guard let track = rtpReceiver.track as? RTCVideoTrack else { return }
DispatchQueue.main.async {
self.remoteVideoTrack = track
if let renderer = self.remoteRenderer {
track.add(renderer)
}
}
}
}
// MARK: - RTCDataChannelDelegate
extension WebRTCClient: RTCDataChannelDelegate {
func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) {
NSLog("[WebRTC] DataChannel \(dataChannel.label) state: \(dataChannel.readyState.rawValue)")
}
func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) {
if dataChannel.label == Self.videoChannelLabel {
// H.264
selfCodecDecoder?.onBinaryMessage(buffer.data)
return
}
// /
guard buffer.isBinary, let msg = ControlMessage.parse(from: buffer.data) else { return }
DispatchQueue.main.async {
switch msg.action {
case .reportStreamMode:
self.delegate?.webRTCClient(didReportStreamMode: Int(msg.streamMode))
case .reportResolution:
self.delegate?.webRTCClient(didReportResolution: Int(msg.width), height: Int(msg.height))
default:
break
}
}
}
}