bool ReadSingleFrame(AmfReader reader) { if (!ChunkStream.ReadFrom1(reader, out var streamId, out var opaque)) { return(false); } if (!streams.TryGetValue(streamId, out var previous)) { previous = new ChunkStream.Snapshot() { ChunkStreamId = streamId } } ; if (!ChunkStream.ReadFrom2(reader, previous, opaque, out var next)) { return(false); } streams[streamId] = next; context.RequestReadAllocation(next.MessageLength); var key = (next.ChunkStreamId, next.MessageStreamId); var builder = messages.TryGetValue(key, out var packet) ? packet : messages[key] = new Builder(next.MessageLength); var length = Math.Min(chunkLength, builder.Remaining); if (!reader.HasLength(length)) { return(false); } builder.AddData( reader.ReadSpan(length)); if (builder.Current == builder.Length) { messages.Remove(key); using (builder) { var dereader = __readSingleFrameReader; dereader.Rebind(builder.Span); var message = Deserialize(next.ContentType, dereader); DispatchMessage(message); } } return(true); } void DispatchMessage(RtmpMessage message) { switch (message) { case ChunkLength chunk: Kon.Trace("received: chunk-length", new { length = chunk.Length }); if (chunk.Length < 0) { throw new ArgumentException("invalid chunk length"); } context.RequestReadAllocation(chunk.Length); chunkLength = chunk.Length; break; case WindowAcknowledgementSize acknowledgement: if (acknowledgement.Count < 0) { throw new ArgumentException("invalid acknowledgement window length"); } acknowledgementLength = acknowledgement.Count; break; case Abort abort: Kon.Trace("received: abort", new { chunk = abort.ChunkStreamId }); // delete the chunk stream streams.Remove(abort.ChunkStreamId); // then, delete all message streams associated with that chunk stream foreach (var(key, builder) in messages.FilterArray(x => x.Key.chunkStreamId == abort.ChunkStreamId)) { messages.Remove(key); builder.Dispose(); } break; default: owner.InternalReceiveEvent(message); break; } } // todo: refactor RtmpMessage Deserialize(PacketContentType contentType, AmfReader r) { // (this comment must be kept in sync at rtmpclient.reader.cs and rtmpclient.writer.cs) // // unsupported type summary: // // - aggregate: we have never encountered this packet in the wild // - shared objects: we have not found a use case for this // - data commands: we have not found a use case for this, though it should be extremely easy to // support. it's just a one-way equivalent of command (invoke). that is, we don't // generate an invoke id for it, and it does not contain headers. other than that, // they're identical. we can use existing logic and add if statements to surround // writing the invokeid + headers if needed. switch (contentType) { case PacketContentType.SetChunkSize: return(new ChunkLength( length: r.ReadInt32())); case PacketContentType.AbortMessage: return(new Abort( chunkStreamId: r.ReadInt32())); case PacketContentType.Acknowledgement: return(new Acknowledgement( read: r.ReadUInt32())); case PacketContentType.UserControlMessage: var type = r.ReadUInt16(); var values = EnumerableEx.Range(r.Remaining / 4, r.ReadUInt32); return(new UserControlMessage( type: (UserControlMessage.Type)type, values: values)); case PacketContentType.WindowAcknowledgementSize: return(new WindowAcknowledgementSize( count: r.ReadInt32())); case PacketContentType.SetPeerBandwith: return(new PeerBandwidth( acknowledgementWindowSize: r.ReadInt32(), type: r.ReadByte())); case PacketContentType.Audio: return(new AudioData( r.ReadBytes(r.Remaining))); case PacketContentType.Video: return(new VideoData( r.ReadBytes(r.Remaining))); case PacketContentType.DataAmf0: throw NotSupportedException("data-amf0"); case PacketContentType.SharedObjectAmf0: throw NotSupportedException("sharedobject-amf0"); case PacketContentType.CommandAmf0: return(ReadCommand(ObjectEncoding.Amf0, contentType, r)); case PacketContentType.DataAmf3: throw NotSupportedException("data-amf3"); case PacketContentType.SharedObjectAmf3: throw NotSupportedException("sharedobject-amf0"); case PacketContentType.CommandAmf3: var encoding = (ObjectEncoding)r.ReadByte(); return(ReadCommand(encoding, contentType, r)); case PacketContentType.Aggregate: throw NotSupportedException("aggregate"); default: throw NotSupportedException($"unknown ({contentType})"); } }