import Foundation private let kRTMPExtendTimestampSize = 4 enum RTMPChunkError: Swift.Error { case bufferUnderflow case unknowChunkType(value: UInt8) } enum RTMPChunkType: UInt8 { case zero = 0 case one = 1 case two = 2 case three = 3 var headerSize: Int { switch self { case .zero: return 11 case .one: return 7 case .two: return 3 case .three: return 0 } } } enum RTMPChunkStreamId: UInt16 { case control = 0x02 case command = 0x03 case audio = 0x04 case video = 0x05 case data = 0x08 } final class RTMPChunkMessageHeader { static let chunkSize = 128 static let maxTimestamp: UInt32 = 0xFFFFFF var timestamp: UInt32 = 0 var messageLength: Int = 0 { didSet { guard payload.count != messageLength else { return } payload = Data(count: messageLength) position = 0 } } var messageTypeId: UInt8 = 0 var messageStreamId: UInt32 = 0 private(set) var payload = Data() private var position = 0 init() { } init(timestmap: UInt32, messageLength: Int, messageTypeId: UInt8, messageStreamId: UInt32) { self.timestamp = timestmap self.messageLength = messageLength self.messageTypeId = messageTypeId self.messageStreamId = messageStreamId self.payload = Data(count: messageLength) } func put(_ buffer: RTMPChunkBuffer, chunkSize: Int) throws { let length = min(chunkSize, messageLength - position) if buffer.remaining < length { throw RTMPChunkError.bufferUnderflow } self.payload.replaceSubrange(position.. (any RTMPMessage)? { if position < payload.count { return nil } switch messageTypeId { case 0x01: return RTMPSetChunkSizeMessage(self) case 0x02: return RTMPAbortMessge(self) case 0x03: return RTMPAcknowledgementMessage(self) case 0x04: return RTMPUserControlMessage(self) case 0x05: return RTMPWindowAcknowledgementSizeMessage(self) case 0x06: return RTMPSetPeerBandwidthMessage(self) case 0x08: return RTMPAudioMessage(self) case 0x09: return RTMPVideoMessage(self) case 0x0F: return RTMPDataMessage(self, objectEncoding: .amf3) case 0x10: return RTMPSharedObjectMessage(self, objectEncoding: .amf3) case 0x11: return RTMPCommandMessage(self, objectEncoding: .amf3) case 0x12: return RTMPDataMessage(self, objectEncoding: .amf0) case 0x13: return RTMPSharedObjectMessage(self, objectEncoding: .amf0) case 0x14: return RTMPCommandMessage(self, objectEncoding: .amf0) case 0x16: return RTMPAggregateMessage(self) default: return nil } } } final class RTMPChunkBuffer { static let headerSize = 3 + 11 + 4 var payload: Data { data[position.. Self { length = position position = 0 return self } func get(_ length: Int) -> Data { defer { position += length } return data[position.. (RTMPChunkType, UInt16) { let rawValue = (data[position] & 0b11000000) >> 6 guard let type = RTMPChunkType(rawValue: rawValue) else { throw RTMPChunkError.unknowChunkType(value: rawValue) } switch data[position] & 0b00111111 { case 0: defer { position += 2 } return (type, UInt16(data[position + 1]) + 64) case 1: defer { position += 3 } return (type, UInt16(data: data[position + 1...position + 2]) + 64) default: defer { position += 1 } return (type, UInt16(data[position] & 0b00111111)) } } func getMessageHeader(_ type: RTMPChunkType, messageHeader: RTMPChunkMessageHeader) throws { if remaining < type.headerSize { throw RTMPChunkError.bufferUnderflow } switch type { case .zero: messageHeader.timestamp = UInt32(data: data[position.. AnyIterator { let payload = message.payload let length = payload.count var offset = 0 var remaining = min(chunkSize, length) return AnyIterator { () -> Data? in guard 0 < remaining else { return nil } defer { self.position = 0 offset += remaining remaining = min(self.chunkSize, length - offset) } if offset == 0 { self.putBasicHeader(chunkType, chunkStreamId: chunkStreamId) self.putMessageHeader(chunkType, length: length, message: message) } else { self.putBasicHeader(.three, chunkStreamId: chunkStreamId) } self.data.replaceSubrange(self.position..