public void SendAmf0Data(RtmpMessage e) { var timestamp = (int)(DateTime.UtcNow - connectTime).TotalMilliseconds; e.Timestamp = timestamp; writer.Queue(e, e.Header.StreamId, e.Header.MessageStreamId); }
/// <summary> /// Stops the server-side stream. /// </summary> public override void Stop() { lock (this.SyncRoot) { if (_state != State.PLAYING && _state != State.PAUSED) { return; } if (_liveJobName != null) { _schedulingService.RemoveScheduledJob(_liveJobName); _liveJobName = null; } if (_vodJobName != null) { _schedulingService.RemoveScheduledJob(_vodJobName); _vodJobName = null; } if (_msgIn != null) { _msgIn.Unsubscribe(this); _msgIn = null; } if (_nextRTMPMessage != null) { _nextRTMPMessage = null; } _state = State.STOPPED; } }
protected override Task ProcessDataMessageAsync(RtmpMessage message) { AmfDataMessage command = new AmfDataMessage(); command.Decode(message.Payload.Span); _logger.LogInformation($"Data command received {command.Name} {command.AdditionalArguments[0]}"); return(Task.CompletedTask); }
private void HandleResponse(AmfCommandMessage command, RtmpMessage message) { var result = new CommandArgs { Command = command }; ResponseReceived?.Invoke(this, result); }
byte[] GetMessageBytes(RtmpMessage message, Action <AmfWriter, RtmpMessage> handler) { using (var stream = new MemoryStream()) using (var messageWriter = new AmfWriter(stream, _writer.SerializationContext, _objectEncoding)) { handler(messageWriter, message); return(stream.ToArray()); } }
public IMessage PullMessage(IPipe pipe) { lock (_syncLock) { if (_pipe != pipe) { return(null); } if (_reader == null) { Init(); } if (!_reader.HasMoreTags()) { // TODO send OOBCM to notify EOF // Do not unsubscribe as this kills VOD seek while in buffer // this.pipe.unsubscribe(this); return(null); } ITag tag = _reader.ReadTag(); IRtmpEvent msg = null; int timestamp = tag.Timestamp; switch (tag.DataType) { case Constants.TypeAudioData: msg = new AudioData(tag.Body); break; case Constants.TypeVideoData: msg = new VideoData(tag.Body); break; case Constants.TypeInvoke: msg = new Invoke(tag.Body); break; case Constants.TypeNotify: msg = new Notify(tag.Body); break; case Constants.TypeFlexStreamEnd: msg = new FlexStreamSend(tag.Body); break; default: log.Warn("Unexpected type " + tag.DataType); msg = new Unknown(tag.DataType, tag.Body); break; } msg.Timestamp = timestamp; RtmpMessage rtmpMsg = new RtmpMessage(); rtmpMsg.body = msg; return(rtmpMsg); } }
public void QueueWrite(RtmpMessage message, int chunkStreamId, bool external = true) { // we save ourselves from synchronizing on chunk length because we never modify it post-initialization if (external && message is ChunkLength) { throw new InvalidOperationException("cannot modify chunk length after stream has begun"); } queue.Enqueue(new Packet(chunkStreamId, message.ContentType, Serialize(message))); reset.Set(); }
public void Queue(RtmpMessage message, int streamId, int messageStreamId) { var header = new RtmpHeader(); var packet = new RtmpPacket(header, message); header.StreamId = streamId; header.Timestamp = message.Timestamp; header.MessageStreamId = messageStreamId; header.MessageType = message.MessageType; _packetQueue.Enqueue(packet); _packetAvailableEvent.Set(); }
/// <summary> /// Pull the next message from IMessageInput and schedule it for push according to the timestamp. /// </summary> protected void ScheduleNextMessage() { bool first = _nextRTMPMessage == null; long delta; while (true) { _nextRTMPMessage = GetNextRTMPMessage(); if (_nextRTMPMessage == null) { OnItemEnd(); return; } IRtmpEvent rtmpEvent = _nextRTMPMessage.body; // filter all non-AV messages if (!(rtmpEvent is VideoData) && !(rtmpEvent is AudioData)) { continue; } rtmpEvent = _nextRTMPMessage.body; _nextTS = rtmpEvent.Timestamp; if (first) { _vodStartTS = _nextTS; first = false; } delta = _nextTS - _vodStartTS - (System.Environment.TickCount - _serverStartTS); if (delta < WAIT_THRESHOLD) { if (!DoPushMessage()) { return; } if (_state != State.PLAYING) { // Stream is paused, don't load more messages _nextRTMPMessage = null; return; } } else { break; } } VODScheduledJob job = new VODScheduledJob(this); _vodJobName = _schedulingService.AddScheduledOnceJob(delta, job); }
void WriteData(AmfWriter writer, RtmpMessage o, ObjectEncoding encoding) { var command = o as Command; if (command.MethodCall == null) { WriteCommandOrData(writer, o, encoding); } else { writer.WriteBytes(command.Buffer); } }
protected override Task ProcessCommandAsync(AmfCommandMessage command, RtmpMessage message) { switch (command.Name) { case "_result": case "onStatus": HandleResponse(command, message); break; default: throw new InvalidOperationException($"Unknown command {command.Name} {command.CommandObject} "); } return(Task.CompletedTask); }
public bool CanSendPacket(RtmpMessage message, long pending) { IRtmpEvent body = message.body; if (!(body is VideoData)) { return(true); } VideoData data = body as VideoData; FrameType frameType = data.FrameType; bool flag = false; switch (this._state) { case FrameDropperState.SEND_ALL: return(true); case FrameDropperState.SEND_INTERFRAMES: if (frameType != FrameType.KEYFRAME) { if (frameType == FrameType.INTERFRAME) { flag = true; } return(flag); } if (pending == 0L) { this._state = FrameDropperState.SEND_ALL; } return(true); case FrameDropperState.SEND_KEYFRAMES: flag = frameType == FrameType.KEYFRAME; if (flag && (pending == 0L)) { this._state = FrameDropperState.SEND_KEYFRAMES_CHECK; } return(flag); case FrameDropperState.SEND_KEYFRAMES_CHECK: flag = frameType == FrameType.KEYFRAME; if (flag && (pending == 0L)) { this._state = FrameDropperState.SEND_INTERFRAMES; } return(flag); } return(flag); }
public void PushMessage(IPipe pipe, IMessage message) { lock (this.SyncRoot) { if (message is ResetMessage) { this._startTimestamp = -1; this._offset += this._lastTimestamp; } else if (!(message is StatusMessage) && (message is RtmpMessage)) { if (this._writer == null) { this.Init(); } RtmpMessage message2 = message as RtmpMessage; IRtmpEvent body = message2.body; if (this._startTimestamp == -1) { this._startTimestamp = body.Timestamp; } int num = body.Timestamp - this._startTimestamp; if (num < 0) { log.Warn("Skipping message with negative timestamp."); } else { this._lastTimestamp = num; ITag tag = new Tag { DataType = body.DataType, Timestamp = num + this._offset }; if (body is IStreamData) { tag.Body = (body as IStreamData).Data.ToArray(); } try { this._writer.WriteTag(tag); } catch (IOException exception) { log.Error("Error writing tag", exception); } } } } }
byte[] GetMessageBytes(FlvTagHeader header, RtmpMessage message) { switch (header.TagType) { case MessageType.Audio: case MessageType.Video: return(GetMessageBytes(message, (w, o) => w.WriteBytes(((ByteData)o).Data))); case MessageType.DataAmf0: return(GetMessageBytes(message, (w, o) => WriteCommandOrData(w, o, ObjectEncoding.Amf0))); default: throw new ArgumentOutOfRangeException("Unknown RTMP message type: " + (int)header.TagType); } }
private void StartBroadcastVOD() { _nextRTMPMessage = null; _vodStartTS = 0; _serverStartTS = System.Environment.TickCount; IStreamAwareScopeHandler handler = GetStreamAwareHandler(); if (handler != null) { if (_recordingFilename != null) handler.StreamRecordStart(this); else handler.StreamPublishStart(this); } NotifyBroadcastStart(); ScheduleNextMessage(); }
public void DropPacket(RtmpMessage message) { IRtmpEvent body = message.body; if (body is VideoData) { VideoData data = body as VideoData; FrameType frameType = data.FrameType; switch (this._state) { case FrameDropperState.SEND_ALL: if (frameType != FrameType.DISPOSABLE_INTERFRAME) { if (frameType == FrameType.INTERFRAME) { this._state = FrameDropperState.SEND_KEYFRAMES; } else if (frameType == FrameType.KEYFRAME) { this._state = FrameDropperState.SEND_KEYFRAMES; } break; } break; case FrameDropperState.SEND_INTERFRAMES: if (frameType != FrameType.INTERFRAME) { if (frameType == FrameType.KEYFRAME) { this._state = FrameDropperState.SEND_KEYFRAMES; } break; } this._state = FrameDropperState.SEND_KEYFRAMES_CHECK; break; case FrameDropperState.SEND_KEYFRAMES_CHECK: if (frameType == FrameType.KEYFRAME) { this._state = FrameDropperState.SEND_KEYFRAMES; } break; } } }
void WriteCommandOrData(AmfWriter writer, RtmpMessage o, ObjectEncoding encoding) { var command = o as Command; var methodCall = command.MethodCall; var isInvoke = command is Invoke; // write the method name or result type (first section) if (methodCall.Name == "@setDataFrame") { writer.WriteAmfItem(encoding, command.ConnectionParameters); } // write arguments foreach (var arg in methodCall.Parameters) { writer.WriteAmfItem(encoding, arg); } }
/// <summary> /// Send VOD seek control message /// </summary> /// <param name="msgIn">Message input</param> /// <param name="position">New timestamp to play from</param> private void SendVODSeekCM(IMessageInput msgIn, int position) { OOBControlMessage oobCtrlMsg = new OOBControlMessage(); oobCtrlMsg.Target = typeof(ISeekableProvider).Name; oobCtrlMsg.ServiceName = "seek"; oobCtrlMsg.ServiceParameterMap.Add("position", position); msgIn.SendOOBControlMessage(this, oobCtrlMsg); lock (this.SyncRoot) { // Reset properties _vodStartTS = 0; _serverStartTS = System.Environment.TickCount; if (_nextRTMPMessage != null) { try { PushMessage(_nextRTMPMessage); } catch (IOException ex) { log.Error("Error while sending message.", ex); } _nextRTMPMessage = null; } ResetMessage reset = new ResetMessage(); try { PushMessage(reset); } catch (IOException ex) { log.Error("Error while sending message.", ex); } ScheduleNextMessage(); } }
// most rtmp servers we are interested in only support amf3 via an amf0 envelope static void WriteCommand(ObjectEncoding encoding, AmfWriter w, RtmpMessage message) { switch (message) { case Notify notify: w.WriteBoxedAmf0Object(encoding, notify.Data); break; case Invoke request: w.WriteBoxedAmf0Object(encoding, request.MethodName); w.WriteBoxedAmf0Object(encoding, request.InvokeId); w.WriteBoxedAmf0Object(encoding, request.Headers); foreach (var arg in request.Arguments ?? EmptyArray <object> .Instance) { w.WriteBoxedAmf0Object(encoding, arg); } break; default: throw new ArgumentOutOfRangeException(); } }
protected override Task ProcessMediaMessageAsync(RtmpMessage message) { MediaReceived?.Invoke(this, new EventArgs <RtmpMessage>(message)); return(Task.CompletedTask); }
public bool CanSendPacket(RtmpMessage message, long pending) { IRtmpEvent packet = message.body; if (!(packet is VideoData)) { // We currently only drop video packets. return(true); } VideoData video = packet as VideoData; FrameType type = video.FrameType; bool result = false; switch (_state) { case FrameDropperState.SEND_ALL: // All packets will be sent. result = true; break; case FrameDropperState.SEND_INTERFRAMES: // Only keyframes and interframes will be sent. if (type == FrameType.Keyframe) { if (pending == 0) { // Send all frames from now on. _state = FrameDropperState.SEND_ALL; } result = true; } else if (type == FrameType.Interframe) { result = true; } break; case FrameDropperState.SEND_KEYFRAMES: // Only keyframes will be sent. result = (type == FrameType.Keyframe); if (result && pending == 0) { // Maybe switch back to SEND_INTERFRAMES after the next keyframe _state = FrameDropperState.SEND_KEYFRAMES_CHECK; } break; case FrameDropperState.SEND_KEYFRAMES_CHECK: // Only keyframes will be sent. result = (type == FrameType.Keyframe); if (result && pending == 0) { // Continue with sending interframes as well _state = FrameDropperState.SEND_INTERFRAMES; } break; default: break; } return(result); }
public void SendPacket(RtmpMessage message) { }
public void DropPacket(RtmpMessage message) { IRtmpEvent packet = message.body; if (!(packet is VideoData)) { // Only check video packets. return; } VideoData video = packet as VideoData; FrameType type = video.FrameType; switch (_state) { case FrameDropperState.SEND_ALL: if (type == FrameType.DisposableInterframe) { // Remain in state, packet is safe to drop. return; } else if (type == FrameType.Interframe) { // Drop all frames until the next keyframe. _state = FrameDropperState.SEND_KEYFRAMES; return; } else if (type == FrameType.Keyframe) { // Drop all frames until the next keyframe. _state = FrameDropperState.SEND_KEYFRAMES; return; } break; case FrameDropperState.SEND_INTERFRAMES: if (type == FrameType.Interframe) { // Drop all frames until the next keyframe. _state = FrameDropperState.SEND_KEYFRAMES_CHECK; return; } else if (type == FrameType.Keyframe) { // Drop all frames until the next keyframe. _state = FrameDropperState.SEND_KEYFRAMES; return; } break; case FrameDropperState.SEND_KEYFRAMES: // Remain in state. break; case FrameDropperState.SEND_KEYFRAMES_CHECK: if (type == FrameType.Keyframe) { // Switch back to sending keyframes, but don't move to SEND_INTERFRAMES afterwards. _state = FrameDropperState.SEND_KEYFRAMES; return; } break; default: break; } }
public EventReceivedEventArgs(RtmpMessage @event) { this.Event = @event; }
public void DispatchEvent(IEvent evt) { if (((!(evt is IRtmpEvent) && (evt.EventType != EventType.STREAM_CONTROL)) && (evt.EventType != EventType.STREAM_DATA)) || this._closed) { if (log.get_IsDebugEnabled()) { log.Debug("DispatchEvent: " + evt.EventType); } } else { IStreamCodecInfo codecInfo = base.CodecInfo; StreamCodecInfo info2 = null; if (codecInfo is StreamCodecInfo) { info2 = codecInfo as StreamCodecInfo; } IRtmpEvent event2 = evt as IRtmpEvent; int num = -1; if (this._firstPacketTime == -1) { this._firstPacketTime = event2.Timestamp; } if (event2 is AudioData) { if (info2 != null) { info2.HasAudio = true; } if (event2.Header.IsTimerRelative) { this._audioTime += event2.Timestamp; } else { this._audioTime = event2.Timestamp; } num = this._audioTime; } else if (event2 is VideoData) { IVideoStreamCodec videoCodec = null; if ((this._videoCodecFactory != null) && this._checkVideoCodec) { videoCodec = this._videoCodecFactory.GetVideoCodec((event2 as VideoData).Data); if (codecInfo is StreamCodecInfo) { (codecInfo as StreamCodecInfo).VideoCodec = videoCodec; } this._checkVideoCodec = false; } else if (codecInfo != null) { videoCodec = codecInfo.VideoCodec; } if (videoCodec != null) { videoCodec.AddData((event2 as VideoData).Data); } if (info2 != null) { info2.HasVideo = true; } if (event2.Header.IsTimerRelative) { this._videoTime += event2.Timestamp; } else { this._videoTime = event2.Timestamp; } num = this._videoTime; } else { if (event2 is Invoke) { if (event2.Header.IsTimerRelative) { this._dataTime += event2.Timestamp; } else { this._dataTime = event2.Timestamp; } return; } if (event2 is Notify) { if (event2.Header.IsTimerRelative) { this._dataTime += event2.Timestamp; } else { this._dataTime = event2.Timestamp; } num = this._dataTime; } } if ((event2 is IStreamData) && ((event2 as IStreamData).Data != null)) { this._bytesReceived += (event2 as IStreamData).Data.Limit; } this.CheckSendNotifications(evt); RtmpMessage message = new RtmpMessage { body = event2 }; message.body.Timestamp = num; try { if (this._livePipe != null) { this._livePipe.PushMessage(message); } if (this._recordPipe != null) { this._recordPipe.PushMessage(message); } } catch (IOException exception) { this.SendRecordFailedNotify(exception.Message); this.Stop(); } } }
public void PushMessage(IPipe pipe, IMessage message) { if (message is ResetMessage) { _timeStamper.Reset(); } else if (message is StatusMessage) { StatusMessage statusMsg = message as StatusMessage; _data.SendStatus(statusMsg.body as StatusASO); } else if (message is RtmpMessage) { // Make sure chunk size has been sent if (!_chunkSizeSent) { SendChunkSize(); } RtmpMessage rtmpMsg = message as RtmpMessage; IRtmpEvent msg = rtmpMsg.body; int eventTime = msg.Timestamp; #if !SILVERLIGHT if (log.IsDebugEnabled) { log.Debug(string.Format("Message timestamp: {0}", eventTime)); } #endif if (eventTime < 0) { #if !SILVERLIGHT if (log.IsDebugEnabled) { log.Debug(string.Format("Message has negative timestamp: {0}", eventTime)); } #endif return; } byte dataType = msg.DataType; // Create a new header for the consumer RtmpHeader header = _timeStamper.GetTimeStamp(dataType, eventTime); switch (msg.DataType) { case Constants.TypeStreamMetadata: Notify notify = new Notify((msg as Notify).Data); notify.Header = header; notify.Timestamp = header.Timer; _data.Write(notify); break; case Constants.TypeFlexStreamEnd: // TODO: okay to send this also to AMF0 clients? FlexStreamSend send = new FlexStreamSend((msg as Notify).Data); send.Header = header; send.Timestamp = header.Timer; _data.Write(send); break; case Constants.TypeVideoData: VideoData videoData = new VideoData((msg as VideoData).Data); videoData.Header = header; videoData.Timestamp = header.Timer; _video.Write(videoData); break; case Constants.TypeAudioData: AudioData audioData = new AudioData((msg as AudioData).Data); audioData.Header = header; audioData.Timestamp = header.Timer; _audio.Write(audioData); break; case Constants.TypePing: Ping ping = new Ping((msg as Ping).PingType, (msg as Ping).Value2, (msg as Ping).Value3, (msg as Ping).Value4); ping.Header = header; _connection.Ping(ping); break; case Constants.TypeBytesRead: BytesRead bytesRead = new BytesRead((msg as BytesRead).Bytes); bytesRead.Header = header; bytesRead.Timestamp = header.Timer; _connection.GetChannel((byte)2).Write(bytesRead); break; default: _data.Write(msg); break; } } }
public void DispatchEvent(IEvent @event) { try { if (@event is IRtmpEvent) { IRtmpEvent rtmpEvent = @event as IRtmpEvent; if (_livePipe != null) { RtmpMessage msg = new RtmpMessage(); msg.body = rtmpEvent; if (_creationTime == -1) { _creationTime = rtmpEvent.Timestamp; } try { if (@event is AudioData) { (_codecInfo as StreamCodecInfo).HasAudio = true; } else if (@event is VideoData) { IVideoStreamCodec videoStreamCodec = null; if (_codecInfo.VideoCodec == null) { videoStreamCodec = _videoCodecFactory.GetVideoCodec((@event as VideoData).Data); (_codecInfo as StreamCodecInfo).VideoCodec = videoStreamCodec; } else if (_codecInfo != null) { videoStreamCodec = _codecInfo.VideoCodec; } if (videoStreamCodec != null) { videoStreamCodec.AddData((rtmpEvent as VideoData).Data); } if (_codecInfo != null) { (_codecInfo as StreamCodecInfo).HasVideo = true; } } _livePipe.PushMessage(msg); // Notify listeners about received packet if (rtmpEvent is IStreamPacket) { foreach (IStreamListener listener in GetStreamListeners()) { try { listener.PacketReceived(this, rtmpEvent as IStreamPacket); } catch (Exception ex) { log.Error("Error while notifying listener " + listener, ex); } } } } catch (Exception ex) { // ignore log.Error("DispatchEvent exception", ex); } } } } finally { } }
protected override async Task ProcessCommandMessageAsync(RtmpMessage message) { await _commandProcessor.ProcessMessageAsync(message); }
public void PushMessage(IPipe pipe, IMessage message) { if (message is ResetMessage) { this._streamTracker.Reset(); } else if (message is StatusMessage) { StatusMessage message2 = message as StatusMessage; this._data.SendStatus(message2.body as StatusASO); } else if (message is RtmpMessage) { RtmpMessage message3 = message as RtmpMessage; IRtmpEvent body = message3.body; RtmpHeader header = new RtmpHeader(); int num = this._streamTracker.Add(body); if (num < 0) { log.Warn("Skipping message with negative timestamp."); } else { header.IsTimerRelative = this._streamTracker.IsRelative; header.Timer = num; switch (body.DataType) { case 3: { BytesRead read = new BytesRead((body as BytesRead).Bytes); header.IsTimerRelative = false; header.Timer = 0; read.Header = header; read.Timestamp = header.Timer; this._connection.GetChannel(2).Write(read); return; } case 4: { Ping ping = new Ping((body as Ping).Value1, (body as Ping).Value2, (body as Ping).Value3, (body as Ping).Value4); header.IsTimerRelative = false; header.Timer = 0; ping.Header = header; ping.Timestamp = header.Timer; this._connection.Ping(ping); return; } case 8: { AudioData data2 = new AudioData((body as AudioData).Data) { Header = header, Timestamp = header.Timer }; this._audio.Write(data2); return; } case 9: { VideoData data = new VideoData((body as VideoData).Data) { Header = header, Timestamp = header.Timer }; this._video.Write(data); return; } case 15: { FlexStreamSend send = new FlexStreamSend((body as Notify).Data) { Header = header, Timestamp = header.Timer }; this._data.Write(send); return; } case 0x12: { Notify notify = new Notify((body as Notify).Data) { Header = header, Timestamp = header.Timer }; this._data.Write(notify); return; } } this._data.Write(body); } } }
Space <byte> Serialize(RtmpMessage message) { // (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. const int WriteInitialBufferLength = 4192; var w = new AmfWriter(WriteInitialBufferLength, context); switch (message.ContentType) { case PacketContentType.SetChunkSize: var a = (ChunkLength)message; w.WriteInt32(a.Length); break; case PacketContentType.AbortMessage: var b = (Abort)message; w.WriteInt32(b.ChunkStreamId); break; case PacketContentType.Acknowledgement: var c = (Acknowledgement)message; w.WriteUInt32(c.TotalRead); break; case PacketContentType.UserControlMessage: var d = (UserControlMessage)message; w.WriteUInt16((ushort)d.EventType); foreach (var value in d.Values) { w.WriteUInt32(value); } break; case PacketContentType.WindowAcknowledgementSize: var e = (WindowAcknowledgementSize)message; w.WriteInt32(e.Count); break; case PacketContentType.SetPeerBandwith: var f = (PeerBandwidth)message; w.WriteInt32(f.AckWindowSize); w.WriteByte((byte)f.LimitType); break; case PacketContentType.Audio: case PacketContentType.Video: var g = (ByteData)message; w.WriteBytes(g.Data); break; case PacketContentType.DataAmf0: throw NotSupportedException("data-amf0"); case PacketContentType.SharedObjectAmf0: throw NotSupportedException("sharedobject-amf0"); case PacketContentType.CommandAmf0: WriteCommand(ObjectEncoding.Amf0, w, message); break; case PacketContentType.DataAmf3: throw NotSupportedException("data-amf3"); case PacketContentType.SharedObjectAmf3: throw NotSupportedException("sharedobject-amf0"); case PacketContentType.CommandAmf3: // first byte is an encoding specifier byte. // see `writecommand` comment below: specify amf0 object encoding and elevate into amf3. w.WriteByte((byte)ObjectEncoding.Amf0); WriteCommand(ObjectEncoding.Amf3, w, message); break; case PacketContentType.Aggregate: throw NotSupportedException("aggregate"); default: throw NotSupportedException($"unknown ({message.ContentType})"); } return(w.Span); }