import Foundation /// 通过 RTCDataChannel 传输的控制指令。 /// 与 control_message.proto (proto3) 二进制格式完全兼容(手写极简 protobuf 编解码, /// 免去 SwiftProtobuf 依赖与代码生成)。 /// 坐标 x/y/x1/y1/x2/y2 均为相对屏幕的百分比,取值范围 0.0 ~ 1.0。 struct ControlMessage { enum Action: Int { case unknown = 0 case touch = 1 case swipe = 2 case key = 3 case longPress = 4 case motionEvent = 5 case setResolution = 6 // 控制端请求被控端切换屏幕采集分辨率 case reportResolution = 7 // 被控端上报当前实际采集分辨率 case setStreamMode = 8 // 控制端请求被控端切换屏幕串流模式 case reportStreamMode = 9 // 被控端上报当前生效的串流模式 } var action: Action = .unknown // field 1, varint var x: Double = 0 // field 2, fixed64 var y: Double = 0 // field 3, fixed64 var x1: Double = 0 // field 4, fixed64 var y1: Double = 0 // field 5, fixed64 var x2: Double = 0 // field 6, fixed64 var y2: Double = 0 // field 7, fixed64 var duration: Int64 = 0 // field 8, varint var keyCode: Int32 = 0 // field 9, varint var keyAction: Int32 = 0 // field 10, varint (0=按下 1=抬起) var motionAction: Int32 = 0 // field 11, varint (0=DOWN 1=UP 2=MOVE 3=CANCEL) var width: Int32 = 0 // field 12, varint var height: Int32 = 0 // field 13, varint var fps: Int32 = 0 // field 14, varint var streamMode: Int32 = 0 // field 15, varint (0=WebRTC 1=自编码) /// 被控端支持的帧率档位列表(REPORT_RESOLUTION 上报,按屏幕刷新率筛选,升序)。 var supportedFps: [Int32] = [] // field 16, repeated varint (packed) // MARK: - Encode func serializedData() -> Data { var w = ProtoWriter() w.writeVarintField(1, UInt64(action.rawValue)) w.writeDoubleField(2, x) w.writeDoubleField(3, y) w.writeDoubleField(4, x1) w.writeDoubleField(5, y1) w.writeDoubleField(6, x2) w.writeDoubleField(7, y2) w.writeVarintField(8, UInt64(bitPattern: duration)) w.writeVarintField(9, UInt64(bitPattern: Int64(keyCode))) w.writeVarintField(10, UInt64(bitPattern: Int64(keyAction))) w.writeVarintField(11, UInt64(bitPattern: Int64(motionAction))) w.writeVarintField(12, UInt64(bitPattern: Int64(width))) w.writeVarintField(13, UInt64(bitPattern: Int64(height))) w.writeVarintField(14, UInt64(bitPattern: Int64(fps))) w.writeVarintField(15, UInt64(bitPattern: Int64(streamMode))) // repeated int32 supported_fps = 16(packed 编码);空列表不编码 if !supportedFps.isEmpty { var payload = ProtoWriter() for v in supportedFps { payload.writeVarint(UInt64(bitPattern: Int64(v))) } w.writeVarint(UInt64(16 << 3 | 2)) w.writeVarint(UInt64(payload.data.count)) w.data.append(payload.data) } return w.data } // MARK: - Decode static func parse(from data: Data) -> ControlMessage? { var msg = ControlMessage() var r = ProtoReader(data: data) while let (field, wire) = r.readTag() { switch (field, wire) { case (1, 0): guard let v = r.readVarint() else { return nil } msg.action = Action(rawValue: Int(v)) ?? .unknown case (2, 1): guard let v = r.readDouble() else { return nil }; msg.x = v case (3, 1): guard let v = r.readDouble() else { return nil }; msg.y = v case (4, 1): guard let v = r.readDouble() else { return nil }; msg.x1 = v case (5, 1): guard let v = r.readDouble() else { return nil }; msg.y1 = v case (6, 1): guard let v = r.readDouble() else { return nil }; msg.x2 = v case (7, 1): guard let v = r.readDouble() else { return nil }; msg.y2 = v case (8, 0): guard let v = r.readVarint() else { return nil }; msg.duration = Int64(bitPattern: v) case (9, 0): guard let v = r.readVarint() else { return nil }; msg.keyCode = Int32(truncatingIfNeeded: Int64(bitPattern: v)) case (10, 0): guard let v = r.readVarint() else { return nil }; msg.keyAction = Int32(truncatingIfNeeded: Int64(bitPattern: v)) case (11, 0): guard let v = r.readVarint() else { return nil }; msg.motionAction = Int32(truncatingIfNeeded: Int64(bitPattern: v)) case (12, 0): guard let v = r.readVarint() else { return nil }; msg.width = Int32(truncatingIfNeeded: Int64(bitPattern: v)) case (13, 0): guard let v = r.readVarint() else { return nil }; msg.height = Int32(truncatingIfNeeded: Int64(bitPattern: v)) case (14, 0): guard let v = r.readVarint() else { return nil }; msg.fps = Int32(truncatingIfNeeded: Int64(bitPattern: v)) case (15, 0): guard let v = r.readVarint() else { return nil }; msg.streamMode = Int32(truncatingIfNeeded: Int64(bitPattern: v)) case (16, 2): // packed repeated int32:先读长度,再在区间内逐个读 varint guard let len = r.readVarint() else { return nil } let end = r.offset + Int(len) guard end <= data.endIndex else { return nil } while r.offset < end { guard let v = r.readVarint() else { return nil } msg.supportedFps.append(Int32(truncatingIfNeeded: Int64(bitPattern: v))) } case (16, 0): // 兼容非 packed 编码(逐项 varint) guard let v = r.readVarint() else { return nil } msg.supportedFps.append(Int32(truncatingIfNeeded: Int64(bitPattern: v))) default: // 未知字段按 wire type 跳过 if !r.skip(wireType: wire) { return nil } } } return msg } } // MARK: - 极简 protobuf 读写器 private struct ProtoWriter { var data = Data() mutating func writeVarint(_ value: UInt64) { var v = value while v >= 0x80 { data.append(UInt8((v & 0x7F) | 0x80)) v >>= 7 } data.append(UInt8(v)) } /// proto3:默认值(0)不编码 mutating func writeVarintField(_ field: Int, _ value: UInt64) { guard value != 0 else { return } writeVarint(UInt64(field << 3 | 0)) writeVarint(value) } mutating func writeDoubleField(_ field: Int, _ value: Double) { guard value != 0 else { return } writeVarint(UInt64(field << 3 | 1)) var bits = value.bitPattern.littleEndian withUnsafeBytes(of: &bits) { data.append(contentsOf: $0) } } } private struct ProtoReader { let data: Data var offset: Int init(data: Data) { self.data = data self.offset = data.startIndex } mutating func readTag() -> (Int, Int)? { guard offset < data.endIndex, let key = readVarint() else { return nil } return (Int(key >> 3), Int(key & 0x7)) } mutating func readVarint() -> UInt64? { var result: UInt64 = 0 var shift: UInt64 = 0 while offset < data.endIndex { let byte = data[offset] offset += 1 result |= UInt64(byte & 0x7F) << shift if byte & 0x80 == 0 { return result } shift += 7 if shift >= 64 { return nil } } return nil } mutating func readDouble() -> Double? { guard offset + 8 <= data.endIndex else { return nil } var bits: UInt64 = 0 for i in (0..<8).reversed() { bits = (bits << 8) | UInt64(data[offset + i]) } offset += 8 return Double(bitPattern: bits) } mutating func skip(wireType: Int) -> Bool { switch wireType { case 0: return readVarint() != nil case 1: guard offset + 8 <= data.endIndex else { return false } offset += 8 return true case 2: guard let len = readVarint(), offset + Int(len) <= data.endIndex else { return false } offset += Int(len) return true case 5: guard offset + 4 <= data.endIndex else { return false } offset += 4 return true default: return false } } }