[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion internal void PostMessage(MQ_RTMPMessage message, bool waitForEmptyQueue = false, int timeOutinMS = -1) { if (waitForEmptyQueue) { // Wait before posting that the messagequeue is empty DateTime timeStamp = DateTime.Now; while (true) { lock (lockVAR) { if (messageQueue == null || messageQueue.Count == 0) { break; } }//lock Thread.Sleep(10); if (timeOutinMS >= 0 && (DateTime.Now - timeStamp).TotalMilliseconds >= timeOutinMS) { // we exiten because of timeout break; } }// } if (message != null) { AddMessageToPump(message); } }
/// <summary> /// Create a logical channel for message communication for use /// in publishing of audio or video and metadata carrying /// </summary> /// <returns></returns> public void CreateStream(NetStream ns) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.CreateStream; message.Params = new object[] { ns }; ; AddMessageToPump(message); }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion internal void DeleteStream(int stream_id) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.DeleteStream; message.Params = new object[] { stream_id }; ; AddMessageToPump(message); }
/// <summary> /// Make connection with a RTMP server like Wowza, red5 or FMS /// </summary> /// <returns></returns> public void Connect(ServerLink serverLink, NC_ResultCallBackConnect resultCallBackConnect, params AMFObjectProperty[] amfProperties) { // First check if there isn't a build connection in the message queue lock (lockVAR) { if (messageQueue != null) { foreach (MQ_RTMPMessage msg in messageQueue) { if (msg.MethodCall == MethodCall.ConnectRTMPServer) { // cancel, we're already trying to do it! return; } } //foreach } } //lock netConnectionConnectInfo.Clear(); MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.ConnectRTMPServer; message.Params = new object[] { serverLink, resultCallBackConnect, amfProperties }; AddMessageToPump(message); }
public void CloseConnection() { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.CloseConnectionRTMPServer; message.Params = null; AddMessageToPump(message); }
/// <summary> /// Add new message to queue. Thread safe /// </summary> private void AddMessageToPump(MQ_RTMPMessage message) { lock (lockVAR) { if (messageQueue != null) { messageQueue.Add(message); } } //lock }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion virtual internal bool HandleOnMediaPacket(RTMPPacket packet) { if (blockMediaPackets > 0) { LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetStream.HandleOnMediaPacket] blockingMediaPacket Timestamp : {0}, Size : {0}", TimeSpan.FromMilliseconds(packet.TimeStamp), packet.BodySize)); return true; } mediaChannel = packet.Channel; //need for pause to have lastest timestamp stored in NetConnection."channelTimestamp" LastMediaPacket = packet; TimeStamp = TimeSpan.FromMilliseconds(packet.TimeStamp); if (packet.PacketType == PacketType.Audio) { if (packet.BodySize <= 1) { LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetStream.HandleOnMediaPacket] Ignoring too small audio packet: size: {0}", packet.BodySize)); return true; } // Store audio data in audio buffer int offset = 1; if (syncAfterPauseNeeded) { LibRTMPLogger.Log(LibRTMPLogLevel.Info, "[CDR.LibRTMP.NetStream.HandleOnMediaPacket] Unpause event code started"); // Match last audio packet with this packet (must both be atleast 10 bytes big otherwhise match // will be bad if (packet.BodySize > 10 && lastAudioPacket.Length > 10) { byte[] pattern = new byte[10]; Buffer.BlockCopy(packet.Body, 1, pattern, 0, 10); LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetStream.HandleOnMediaPacket] Duplicate data, packetsize={0}", packet.BodySize - 1)); int[] hits = lastAudioPacket.Locate(pattern); if (hits.Length > 0) { LibRTMPLogger.Log(LibRTMPLogLevel.Info, "[CDR.LibRTMP.NetStream.HandleOnMediaPacket] Duplicate data detected"); // free data savedPackets = null; syncAfterPauseNeeded = false; // we're in sync // we only look at the first hit, and try to match it up with as much // data as we have in "packet.body[1]" (this is start of pattern) int j = hits[0]; // set offset so, it matches the entire packet! offset = (int)packet.BodySize - 1; for (int i = 1; i < packet.BodySize; i++) { if (j >= lastAudioPacket.Length || packet.Body[i] != lastAudioPacket[j]) { // New offset for packet data, where new data starts offset = i; // we're ready break; } j++; } //for // remove unused data from lastAudioPacket (or all) if (offset >= (lastAudioPacket.Length - 1)) { lastAudioPacket = null; } else { // there is some data left int left = (lastAudioPacket.Length - 1) - offset; byte[] leftB = new byte[left]; Buffer.BlockCopy(lastAudioPacket, j, leftB, 0, left); // make sure we run this routine also for the next packet lock (lockVAR) { syncAfterPauseNeeded = true; } //lock } LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetStream.HandleOnMediaPacket] Duplicate data detected up till offset: {0}", offset)); } else { LibRTMPLogger.Log(LibRTMPLogLevel.Info, "[CDR.LibRTMP.NetStream.HandleOnMediaPacket] NO Duplicate data detected"); if (savedPackets == null) { savedPackets = new List<RTMPPacket>(); } savedPackets.Add(packet); // After 10 packets received , just quit trying! if (savedPackets.Count >= 10) { ReplaySavedPackets(); } return true; } } } // if got unpause event if (offset >= (int)packet.BodySize - 1) { int newSize = (int)packet.BodySize - 1; LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetStream.HandleOnMediaPacket] Duplicate data, skipped entire packet (size={0})", newSize)); // no data left to work with. so skip this packet return true; } msAudioBuffer.Write(packet.Body, offset, (int)packet.BodySize - offset); mediaBytesReceived += packet.BodySize; // lastAudioPacket is needed to rematch data after a pause (streaming server // seems to send the same packet and manipulation of position doesn't seem the fix it) // small optimalization (most of the time packets are of the same size! if (lastAudioPacket == null || lastAudioPacket.Length != (packet.BodySize - 1)) { lastAudioPacket = new byte[packet.BodySize - 1]; } Buffer.BlockCopy(packet.Body, 1, lastAudioPacket, 0, (int)(packet.BodySize - 1)); if ((atBeginOfAudio && msAudioBuffer.Position >= 8192) || (!atBeginOfAudio && msAudioBuffer.Position > 0)) { atBeginOfAudio = false; byte[] tmpBuffer = new byte[msAudioBuffer.Position]; Buffer.BlockCopy(msAudioBuffer.GetBuffer(), 0, tmpBuffer, 0, Convert.ToInt32(msAudioBuffer.Position)); // Reset position to start from beginning msAudioBuffer.Position = 0; Internal_OnAudioPacket(TimeStamp, tmpBuffer); if (OnAudioPacket != null) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnAudioPacket, this, TimeStamp, tmpBuffer.Clone() }; netConnection.PostOnEventUserCallCodeMessage(message); } } return true; } else if (OnVideoPacket != null && packet.PacketType == PacketType.Video) { // skip video info/command packets if (packet.BodySize == 2 && ((packet.Body[0] & 0xf0) == 0x50)) { return true; } else if (packet.BodySize <= 5) { LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetStream.HandleOnMediaPacket] Ignoring too small video packet: size: {0}", packet.BodySize)); return true; } else { // TODO // Probaly when using pause/unpause the first packet contains data which we already got // should check for this and repair as done in audio part // Store video data in buffer msVideoBuffer.Write(packet.Body, 1, (int)packet.BodySize - 1); if (OnVideoPacket != null) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnVideoPacket, this, TimeStamp, msVideoBuffer.ToArray().Clone() }; netConnection.PostOnEventUserCallCodeMessage(message); } // Clear Buffer again msVideoBuffer.Seek(0, SeekOrigin.Begin); msVideoBuffer.SetLength(0); return true; } } return false; }
private void MQ_Connect(ServerLink sl, NC_ResultCallBackConnect onResult, params AMFObjectProperty[] amfProperties) { MQ_Close(); netConnectionState = NetConnectionState.Connecting; serverLink = sl; bool success = MQInternal_MakeConnection(amfProperties); if (success) { netConnectionState = NetConnectionState.Connected; } if (success) { success = false; // Now we have to wait for the "onConnect" event to be send by the wowza server, before we can say the // connection was successful. We do this for a max of 7 seconds, if no "onConnect" then // disconnect and say failed DateTime timeStamp = DateTime.Now.AddMilliseconds(onConnectWaitTimeoutMS); while (!success && (timeStamp - DateTime.Now).TotalMilliseconds > 0) { if (netConnectionState == NetConnectionState.Connected) { if (MQInternal_IsConnected) { if (MQInternal_DataInSocket) { RTMPPacket packet = null; ReadPacket(out packet); // When we get an Invoke while connecting this is the "connect" event! // where we have been waiting for. if (packet.PacketType == PacketType.Invoke) { success = true; } HandleClientPacket(packet); } } else { // There is no connection anymore break; } } // don't use cpu 100% if (!success) { Thread.Sleep(10); } } //while // Close connection when failed! if (!success || (DateTime.Now - timeStamp).TotalMilliseconds > 0) { #if SSL if (sslStream != null) { try { sslStream.Close(); sslStream.Dispose(); } catch { } } sslStream = null; #endif if (tcpSocket != null) { try { tcpSocket.Shutdown(SocketShutdown.Both); tcpSocket.Close(); } catch { } } tcpSocket = null; // temporary before we set the state to disconnected! netConnectionState = NetConnectionState.Connecting; dtNetConnectionKeepAlive = DateTime.Now; } } if (onResult != null) { // this is delegate given when connecting (used by Mediaplayer class) // makes event OnConnect not needed DoNC_ResultCallBackConnectEvent(onResult, success); } // Now we do a "global" onConnect if (success && OnConnect != null) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnConnect, this }; AddOnEventUserCallCodeToPump(message); } if (!success) { // Set state netConnectionState = NetConnectionState.Disconnected; } }
private void MQ_Close() { lock (lockVAR) { // delete all streams if there were any bool wasConnected = (tcpSocket != null && netConnectionState == NetConnectionState.Connected); if (MQInternal_IsConnected) { foreach (NetStream netStream in netStreamsList) { if (netStream.Stream_ID >= 0) { MQ_SendDeleteStream(netStream.Stream_ID); // this will generate result packets but we're cclosing the // connect. Really we should wait to get all result before disconnecting } } //foreach } #if SSL if (sslStream != null) { try { sslStream.Close(); sslStream.Dispose(); } catch { } } sslStream = null; #endif if (tcpSocket != null) { try { tcpSocket.Shutdown(SocketShutdown.Both); tcpSocket.Close(); } catch { } } tcpSocket = null; netConnectionState = NetConnectionState.Disconnected; dtNetConnectionKeepAlive = DateTime.MaxValue; receiveTimeoutMS = RTMPConst.TIMEOUT_RECEIVE; InChunkSize = RTMPConst.RTMP_DEFAULT_CHUNKSIZE; outChunkSize = RTMPConst.RTMP_DEFAULT_CHUNKSIZE; bwCheckCounter = 0; clientBW = 2500000; clientBW2 = 2; serverBW = 2500000; bytesReadTotal = 0; lastSentBytesRead = 0; numInvokes = 0; for (int i = 0; i < RTMPConst.RTMP_CHANNELS; i++) { vecChannelsIn[i] = null; vecChannelsOut[i] = null; channelTimestamp[i] = 0; } //for methodCallDictionary.Clear(); dMethodLookup.Clear(); if (wasConnected) { foreach (NetStream netStream in netStreamsList) { netStream.internal_NetStreamDisconnectNotify(); // } //foreach if (OnDisconnect != null) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnDisconnect, this }; AddOnEventUserCallCodeToPump(message); } } // reset is for the next connect DisconnectEventSend = false; } //lock }
public void Pause(bool doPause) { if (!CheckConnection()) { return; } pauseIsActive = true; MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.Pause; message.Params = new object[] { this, doPause }; netConnection.PostMessage(message, true); }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion virtual protected bool Internal_HandleOnStatusDecoded(RTMPPacket packet, string eventStr, string codeStr, string levelStr, AMFObject obj) { //LibRTMPLogger.Log(LibRTMPLogLevel.Error, string.Format("[CDR.LibRTMP.NetStream.Internal_HandleOnStatusDecoded] event={0} code={1} level={2} Timestamp={3}", eventStr, codeStr, levelStr, packet.TimeStamp)); //Console.WriteLine(string.Format("event={0} code={1} level={2} Timestamp={3} seekIsActive={4}", eventStr, codeStr, levelStr, packet.TimeStamp, seekIsActive.ToString())); if (OnStatus != null) { NetStreamStatusEvent netStreamStatusEvent = new NetStreamStatusEvent(); netStreamStatusEvent.Clear(); netStreamStatusEvent.Event = eventStr; netStreamStatusEvent.Code = codeStr; netStreamStatusEvent.Level = levelStr; netStreamStatusEvent.EventInfo = (AMFObject)obj.Clone(); // for thread safety MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnStatus, this, netStreamStatusEvent }; netConnection.PostOnEventUserCallCodeMessage(message); } // Make sure we point to the right record which is buffered! if (codeStr == "NetStream.Play.Switch") { // end of stream (adjust duration to correct for inaccuracy!) lock (lockVAR) { seekIsActive = false; } //lock } // Now do our thing switch (codeStr) { // We need to flush the existing buffers and wait until // new data arrives case "NetStream.Seek.Notify": lock (lockVAR) { seekIsActive = true; // is probably already set blockMediaPackets++; deltaTimeStampInMS = packet.TimeStamp; } Internal_OnSeekNotify(packet.TimeStamp); break; case "NetStream.Play.Reset": // send when playlist starts at the beginning lock (lockVAR) { atBeginOfAudio = true; mediaBytesReceived = 0; deltaTimeStampInMS = 0; } //lock break; case "NetStream.Play.Switch": // Tell we have to stop playing (and drain the buffers!) if (mediaBytesReceived > 0) // only when we are streaming already { blockMediaPackets++; } break; case "NetStream.Data.Start": mediaBytesReceived = 0; break; // stream begins to play (that is data is send) // deblock if needed case "NetStream.Play.Start": lock (lockVAR) { if (blockMediaPackets > 0) { blockMediaPackets--; } seekIsActive = false; // is probably already set } //lock break; case "NetStream.Play.Stop": ReplaySavedPackets(); // needed in case packets where saved (we're at the end so a byte sync will not occure anymore) lock (lockVAR) { atBeginOfAudio = true; } break; // Pause logic case "NetStream.Pause.Notify": ReplaySavedPackets(); // needed in case packets where saved (we're at the end so a byte sync will not occure anymore) lock (lockVAR) { if (mediaBytesReceived > 0) // we're buffering { pauseIsActive = true; Internal_OnPauseStream(true); if (OnPauseStream != null) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnPauseStream, this, true }; netConnection.PostOnEventUserCallCodeMessage(message); } } } //lock break; case "NetStream.Unpause.Notify": lock (lockVAR) { syncAfterPauseNeeded = true; pauseIsActive = false; Internal_OnPauseStream(false); if (OnPauseStream != null) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnPauseStream, this, false }; netConnection.PostOnEventUserCallCodeMessage(message); } } //lock break; case "NetStream.Play.Failed": case "NetStream.Play.StreamNotFound": case "NetStream.Failed": break; case "NetStream.Play.Complete": // all data is send lock (lockVAR) { seekIsActive = false; // safety } break; } //switch // Code can be eg: // "NetStream.Failed" // "NetStream.Play.Failed" // "NetStream.Play.StreamNotFound" // "NetConnection.Connect.InvalidApp" // "NetStream.Play.Start" // "NetStream.Publish.Start" // "NetStream.Play.Complete" //audio // "NetStream.Play.Stop" // audio // "NetStream.Pause.Notify" // "NetStream.Seek.Notify" return true; }
virtual public void Seek(long positionInMS) { if (!CheckConnection() && seekIsActive) { return; } seekIsActive = true; MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.Seek; message.Params = new object[] { this, positionInMS }; netConnection.PostMessage(message); }
public void CloseStream() { // Only needed when there is a valid stream if (stream_id < 0) { return; } if (!CheckConnection()) { return; } // needed to make sure the channel wil start streaming again when a new play command // is send (verry important!) if (pauseIsActive) { Pause(false); } MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.CloseStream; message.Params = new object[] { this }; netConnection.PostMessage(message); }
public virtual void Play(string mediaFile, int start, int lenToPlay, bool resetPlayList, AMFObjectProperty properties) { if (!CheckConnection()) { return; } MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.Play; message.Params = new object[] { this, mediaFile, start, lenToPlay, resetPlayList, properties }; netConnection.PostMessage(message); }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion internal void PostOnEventUserCallCodeMessage(MQ_RTMPMessage message) { AddOnEventUserCallCodeToPump(message); }
/// <summary> /// Handle when we detect remote server is disconnected /// </summary> private void RemoteServerDisconnected() { // Handle disconnect by sending event (once) and resetting everything! if (!DisconnectEventSend) { DisconnectEventSend = true; // we do a disconnect by using the message pump makes it cleaner MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.RemoteDisconnect; AddMessageToPump(message); } }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion internal void SendPing(short nType, uint nObject, uint nTime) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.SendPing; message.Params = new object[] { nType, nObject, nTime }; AddMessageToPump(message); }
/// <summary> /// After this, this class is not useable again. You need to create a new one! /// </summary> public void Close() { try { // prevent continues recurve callback if (closeCalled) { return; } closeCalled = true; for (int i = netStreams.GetLowerBound(0); i < netStreams.GetUpperBound(0); i++) { if (netStreams[i] != null) { netStreams[i].Close(); netStreams[i] = null; } } //for netStreamsList.Clear(); // Fire event if (OnDisconnect != null) { // We have to explcitly start this event, because after this // the thread will be killed! MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnDisconnect, this }; SynchronizationContext sc; lock (lockVAR) { sc = synchronizationContext; } //lock if (sc != null) { sc.Send(HandleOnEventCallUserCode, message); } else { HandleOnEventCallUserCode(message); } } } catch (Exception e) { LibRTMPLogger.LogError(e); } KillThread(); }
/// <summary> /// Needed especially for OnEventUserCallCode. We want the user code to get /// the event as soon as possible. Because of lock we need to call it from /// the messagepump (then the usercode can call the NetLibRTMP code /// wihtout locking problems /// </summary> private void AddOnEventUserCallCodeToPump(MQ_RTMPMessage message) { if (message.MethodCall != MethodCall.OnEventCallUserCode) { return; } lock (lockVAR) { if (messageQueue == null) { return; } // Add as "last" OnEventUserCallCode but before rother events for (int i = 0; i < messageQueue.Count; i++) { if (messageQueue[i].MethodCall != MethodCall.OnEventCallUserCode) { // added it before [i] and return. We're ready messageQueue.Insert(i, message); return; } } //for // It's probably the first message which is inserted messageQueue.Add(message); } //lock }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion virtual internal bool HandleOnID3(AMFObject obj) { LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection.NetStream.HandleOnID3]"); obj.Dump(); audioMetaData.Clear(); audioMetaData.Valid = true; List<AMFObjectProperty> props = new List<AMFObjectProperty>(); props.Clear(); obj.FindMatchingProperty("v1SongTitle", props, 1); if (props.Count > 0) { audioMetaData.SongTitle = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("v1LeadArtist", props, 1); if (props.Count > 0) { audioMetaData.LeadArtist = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("v1AlbumTitle", props, 1); if (props.Count > 0) { audioMetaData.AlbumTitle = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("v1YearReleased", props, 1); if (props.Count > 0) { audioMetaData.YearReleased = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("v1SongComment", props, 1); if (props.Count > 0) { audioMetaData.SongComment = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("v1SongGenre", props, 1); if (props.Count > 0) { audioMetaData.SongGenre = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("v1TrackNumberOnAlbum", props, 1); if (props.Count > 0) { audioMetaData.TrackNumberOnAlbum = props[0].StringValue; } // handle event if (OnID3 != null) { MQ_RTMPMessage message = new MQ_RTMPMessage(); message.MethodCall = MethodCall.OnEventCallUserCode; message.Params = new object[] { OnID3, this, (AudioMetaData)audioMetaData.Clone(), obj }; netConnection.PostOnEventUserCallCodeMessage(message); } return true; }