public object Clone()
        {
            RTMPPacket clone = new RTMPPacket();

            clone.headerType = headerType;
            clone.packetType = packetType;
            clone.hasAbsTimestamp = hasAbsTimestamp;
            clone.channel = channel;
            clone.timeStamp = timeStamp;
            clone.infoField2 = infoField2;
            clone.bytesRead = bytesRead;
            clone.bodySize = bodySize;
            clone.body = (byte[])body.Clone();

            return clone;
        }
Beispiel #2
0
        public object Clone()
        {
            RTMPPacket clone = new RTMPPacket();

            clone.headerType      = headerType;
            clone.packetType      = packetType;
            clone.hasAbsTimestamp = hasAbsTimestamp;
            clone.channel         = channel;
            clone.timeStamp       = timeStamp;
            clone.infoField2      = infoField2;
            clone.bytesRead       = bytesRead;
            clone.bodySize        = bodySize;
            clone.body            = (byte[])body.Clone();

            return(clone);
        }
        private bool MQInternal_SendBytesReceived()
        {
            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x02;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.BytesReadReport;

            packet.AllocPacket(4);
            packet.BodySize = 4;

            List<byte> enc = new List<byte>();
            lock (lockVAR)
            {
                RTMPHelper.EncodeInt32(enc, bytesReadTotal);
                packet.BodySize = (uint)enc.Count;
                packet.Body = enc.ToArray();

                lastSentBytesRead = bytesReadTotal;
                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] Send bytes report. ({0} bytes)", bytesReadTotal));
            }
            return MQInternal_SendPacket(packet, false);
        }
        private bool MQInternal_SendPause(NetStream netStream, bool doPause)
        {
            int transactionNum = ++numInvokes;

            RTMPPacket packet = new RTMPPacket();
            packet.Channel = netStream.CommandChannel; // 0x08 ???
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            List<byte> enc = new List<byte>();

            RTMPHelper.EncodeString(enc, "pause");
            RTMPHelper.EncodeNumber(enc, transactionNum);
            enc.Add(0x05); // NULL
            RTMPHelper.EncodeBoolean(enc, doPause);
            RTMPHelper.EncodeNumber(enc, (double)channelTimestamp[netStream.MediaChannel]);

            packet.Body = enc.ToArray();
            packet.BodySize = (uint)enc.Count;

            LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetConnection] Sending pause: ({0}), Time = {1}/{2}", doPause.ToString(), channelTimestamp[netStream.MediaChannel], TimeSpan.FromMilliseconds(Convert.ToInt32(channelTimestamp[netStream.MediaChannel]))));

            methodCallDictionary.Add(transactionNum, "pause");

            return MQInternal_SendPacket(packet);
        }
        private bool MQInternal_SendCheckBW()
        {
            int transactionNum = ++numInvokes;

            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x03;   // control channel (invoke)

            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            //packet.m_nInfoField1 = System.Environment.TickCount;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection] Sending _checkbw");
            List<byte> enc = new List<byte>();
            RTMPHelper.EncodeString(enc, "_checkbw");
            RTMPHelper.EncodeNumber(enc, transactionNum);
            enc.Add(0x05); // NULL

            packet.BodySize = (uint)enc.Count;
            packet.Body = enc.ToArray();

            methodCallDictionary.Add(transactionNum, "_checkbw");

            // triggers _onbwcheck and eventually results in _onbwdone
            return MQInternal_SendPacket(packet, false);
        }
        private bool MQInternal_SendSecureTokenResponse(string resp)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x03;	/* control channel (invoke) */
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] Sending SecureTokenResponse: {0}", resp));
            List<byte> enc = new List<byte>();
            RTMPHelper.EncodeString(enc, "secureTokenResponse");
            RTMPHelper.EncodeNumber(enc, 0.0);
            enc.Add(0x05); // NULL
            RTMPHelper.EncodeString(enc, resp);

            packet.BodySize = (uint)enc.Count;
            packet.Body = enc.ToArray();

            return MQInternal_SendPacket(packet, false);
        }
        /// <summary>
        /// Stream_id checks "netStreams"
        /// Is orginal call pattern: private bool MQInternal_SendPlay(NetStream netStream, string mediafile, int start, int lenToPlay, bool resetPlayList, AMFObjectProperty properties)
        /// </summary>
        private bool MQInternal_SendPlay(NetStream netStream, string mediaFile, int start, int lenToPlay, bool resetPlayList, AMFObjectProperty properties)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.Channel = netStream.CommandChannel; // 0x08 ???
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            packet.InfoField2 = netStream.Stream_ID;

            List<byte> enc = new List<byte>();

            RTMPHelper.EncodeString(enc, "play");
            RTMPHelper.EncodeNumber(enc, 0); // 0 according to spec adobe
            enc.Add(0x05); // NULL

            RTMPHelper.EncodeString(enc, mediaFile);

            /* Optional parameters start and len.
             *
             * start: -2, -1, 0, positive number
             *  -2: looks for a live stream, then a recorded stream, if not found any open a live stream
             *  -1: plays a live stream
             * >=0: plays a recorded streams from 'start' milliseconds
            */
            // RTMPHelper.EncodeNumber(enc, -1000.0d); (liveStream)
            if (start > 0)
            {
                RTMPHelper.EncodeNumber(enc, start);
            }
            else
            {
                RTMPHelper.EncodeNumber(enc, 0.0d);
            }

            // len: -1, 0, positive number
            //  -1: plays live or recorded stream to the end (default)
            //   0: plays a frame 'start' ms away from the beginning
            //  >0: plays a live or recoded stream for 'len' milliseconds
            RTMPHelper.EncodeNumber(enc, lenToPlay);

            // Reset. Optional wether to flush previous playlist
            if (properties == null)
            {
                RTMPHelper.EncodeBoolean(enc, resetPlayList);
            }
            else
            {
                properties.Encode(enc);
            }

            packet.Body = enc.ToArray();
            packet.BodySize = (uint)enc.Count;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] Sending play: '{0}'", mediaFile));

            return MQInternal_SendPacket(packet);
        }
        private bool MQ_SendCreateStream(NetStream netStream)
        {
            int transactionNum = ++numInvokes;
            // Put netStream in transaction reference table so we can match it up when the rtmp server
            // give us the result back
            transactionIDReferenceTable[transactionNum] = netStream;

            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection] Sending createStream");
            packet.AllocPacket(256); // should be enough
            List<byte> enc = new List<byte>();
            RTMPHelper.EncodeString(enc, "createStream");
            RTMPHelper.EncodeNumber(enc, transactionNum);
            enc.Add(0x05); // NULL

            packet.BodySize = (uint)enc.Count;
            packet.Body = enc.ToArray();

            methodCallDictionary.Add(transactionNum, "createStream");

            return MQInternal_SendPacket(packet);
        }
        /// <summary>
        /// Send Connect after NetConnection Handshake has finished
        /// </summary>
        private bool MQInternal_NetConnection_SendConnect(params AMFObjectProperty[] amfProperties)
        {
            // Work with copy of var so we don't have to lock to much, for to long
            ServerLink link;
            lock (lockVAR)
            {
                link = (ServerLink)serverLink.Clone();
            }

            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            packet.AllocPacket(4096);

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection] Sending connect");
            List<byte> enc = new List<byte>();
            RTMPHelper.EncodeString(enc, "connect");
            int transactionNum = ++numInvokes;
            RTMPHelper.EncodeNumber(enc, transactionNum);
            methodCallDictionary.Add(transactionNum, "connect");
            enc.Add(0x03); //Object Datatype
            RTMPHelper.EncodeString(enc, "app", link.Application); LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] app : {0}", link.Application));
            if (String.IsNullOrEmpty(swfFlashVer))
            {
                RTMPHelper.EncodeString(enc, "flashVer", "WIN 10,0,32,18");
            }
            else
            {
                RTMPHelper.EncodeString(enc, "flashVer", swfFlashVer);
            }
            if (!string.IsNullOrEmpty(swfURL)) RTMPHelper.EncodeString(enc, "swfUrl", swfURL);
            RTMPHelper.EncodeString(enc, "tcUrl", link.URL); LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] tcUrl : {0}", link.URL));
            RTMPHelper.EncodeBoolean(enc, "fpad", false);
            RTMPHelper.EncodeNumber(enc, "capabilities", 15.0);
            RTMPHelper.EncodeNumber(enc, "audioCodecs", 3191.0);
            RTMPHelper.EncodeNumber(enc, "videoCodecs", 252.0);
            RTMPHelper.EncodeNumber(enc, "videoFunction", 1.0);
            if (!string.IsNullOrEmpty(swfPageURL))
            {
                RTMPHelper.EncodeString(enc, "pageUrl", swfPageURL);
            }
            enc.Add(0); enc.Add(0); enc.Add(0x09); // end of object - 0x00 0x00 0x09
            // add auth string
            if (!string.IsNullOrEmpty(swfAuth))
            {
                RTMPHelper.EncodeBoolean(enc, true);
                RTMPHelper.EncodeString(enc, swfAuth);
            }
            //EncodeNumber(enc, "objectEncoding", 0.0);
            if (amfProperties != null)
            {
                foreach (AMFObjectProperty prop in amfProperties)
                {
                    prop.Encode(enc);
                } //foreach

                List<byte> objEnc = new List<byte>();
            }

            Array.Copy(enc.ToArray(), packet.Body, enc.Count);
            packet.BodySize = (uint)enc.Count;

            return MQInternal_SendPacket(packet);
        }
        /// <summary>
        /// Handle a received packet.
        /// </summary>
        /// <returns>0 - no media packet, 1 - media packet, 2 - play complete</returns>
        private HandlePacketResult HandleClientPacket(RTMPPacket packet)
        {
            HandlePacketResult result = HandlePacketResult.NoneMediaPacket;

            switch (packet.PacketType)
            {
                case PacketType.ChunkSize:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: ChunkSize."));
                    HandleChangeChunkSize(packet);
                    break;
                case PacketType.BytesReadReport:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: Bytes read report."));
                    break;
                case PacketType.Control:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: Control."));
                    HandlePing(packet);
                    break;
                case PacketType.ServerBW:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: ServerBW."));
                    HandleServerBW(packet);
                    break;
                case PacketType.ClientBW:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: ClientBW."));
                    HandleClientBW(packet);
                    break;
                case PacketType.Audio:
                case PacketType.Video:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: [Media(Audio/Video)] {0} bytes.", packet.BodySize));
                    HandleMedia(packet);
                    result = HandlePacketResult.MediaPacket;
                    break;
                case PacketType.Metadata:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: [MetaData] {0} bytes.", packet.BodySize));
                    HandleMetadata(packet);
                    result = HandlePacketResult.MediaPacket;
                    break;
                case PacketType.Invoke:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: [Invoke] {0} bytes.", packet.BodySize));
                    HandleInvoke(packet);
                    break;
                case PacketType.FlvTags:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: [FlvTags] {0} bytes.", packet.BodySize));
                    HandleFlvTags(packet);
                    result = HandlePacketResult.MediaPacket;
                    break;
                default:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection.HandleClientPacket] PACKET received: [Unknown packet] type {0}.", packet.PacketType));
                    break;
            }

            return result;
        }
        /// <summary>
        /// Decode a RTMP packet from the stream
        /// </summary>
        private bool ReadPacket(out RTMPPacket packet)
        {
            packet = null;

            // eerst checken of er wel dat is
            if (!MQInternal_IsConnected)
            {
                RemoteServerDisconnected();

                // niks te doen
                return false;
            }

            // Chunk Basic Header (1, 2 or 3 bytes)
            // the two most significant bits hold the chunk type
            // value in the 6 least significant bits gives the chunk stream id (0,1,2 are reserved): 0 -> 3 byte header | 1 -> 2 byte header | 2 -> low level protocol message | 3-63 -> stream id
            byte[] singleByteToReadBuffer = new byte[1];
            if (ReadN(singleByteToReadBuffer, 0, 1) != 1)
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection] failed to read RTMP packet header");
                return false;
            }

            byte type = singleByteToReadBuffer[0];

            byte headerType = (byte)((type & 0xc0) >> 6);
            int channel = (byte)(type & 0x3f);

            if (channel == 0)
            {
                if (ReadN(singleByteToReadBuffer, 0, 1) != 1)
                {
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection] failed to read RTMP packet header 2nd byte");
                    return false;
                }
                channel = singleByteToReadBuffer[0];
                channel += 64;
                //header++;
            }
            else if (channel == 1)
            {
                int tmp;
                byte[] hbuf = new byte[2];

                if (ReadN(hbuf, 0, 2) != 2)
                {
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection] failed to read RTMP packet header 3rd and 4th byte");
                    return false;
                }
                tmp = ((hbuf[2]) << 8) + hbuf[1];
                channel = tmp + 64;
                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] channel: {0}", channel));
                //header += 2;
            }

            uint nSize = RTMPConst.PacketSize[headerType];

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] reading RTMP packet chunk on channel {0}, headersz {1}", channel, nSize));

            if (nSize < RTMPConst.RTMP_LARGE_HEADER_SIZE)
            {
                // using values from the last message of this channel
                packet = vecChannelsIn[channel];
            }
            else
            {
                packet = new RTMPPacket() { HeaderType = (HeaderType)headerType, Channel = channel, HasAbsTimestamp = true }; // new packet
            }

            nSize--;

            byte[] header = new byte[RTMPConst.RTMP_LARGE_HEADER_SIZE];
            if (nSize > 0 && ReadN(header, 0, (int)nSize) != nSize)
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection] failed to read RTMP packet header. type: {0}", type));
                return false;
            }

            if (nSize >= 3)
            {
                packet.TimeStamp = (uint)RTMPHelper.ReadInt24(header, 0);

                if (nSize >= 6)
                {
                    packet.BodySize = (uint)RTMPHelper.ReadInt24(header, 3);
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] new packet body to read {0}", packet.BodySize));
                    packet.BytesRead = 0;
                    packet.Free(); // new packet body

                    if (nSize > 6)
                    {
                        if (Enum.IsDefined(typeof(PacketType), header[6]))
                        {
                            packet.PacketType = (PacketType)header[6];
                        }
                        else
                        {
                            LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection] Unknown packet type received: {0}", header[6]));
                        }

                        if (nSize == 11)
                        {
                            packet.InfoField2 = RTMPHelper.ReadInt32LE(header, 7);
                        }
                    }
                }

                if (packet.TimeStamp == 0xffffff)
                {
                    byte[] extendedTimestampDate = new byte[4];
                    if (ReadN(extendedTimestampDate, 0, 4) != 4)
                    {
                        LibRTMPLogger.Log(LibRTMPLogLevel.Warning, "[CDR.LibRTMP.NetConnection] failed to read extended timestamp");
                        return false;
                    }
                    packet.TimeStamp = (uint)RTMPHelper.ReadInt32(extendedTimestampDate, 0);
                }
            }

            if (packet.BodySize >= 0 && packet.Body == null && !packet.AllocPacket((int)packet.BodySize))
            {
                //CLog::Log(LOGDEBUG,"%s, failed to allocate packet", __FUNCTION__);
                return false;
            }

            uint nToRead = packet.BodySize - packet.BytesRead;
            uint nChunk = (uint)InChunkSize;
            if (nToRead < nChunk)
                nChunk = nToRead;

            int read = ReadN(packet.Body, (int)packet.BytesRead, (int)nChunk);
            if (read != nChunk)
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection] failed to read RTMP packet body. total:{0}/{1} chunk:{2}/{3}", packet.BytesRead, packet.BodySize, read, nChunk));
                packet.Body = null; // we dont want it deleted since its pointed to from the stored packets (m_vecChannelsIn)
                return false;
            }

            packet.BytesRead += nChunk;

            // keep the packet as ref for other packets on this channel
            vecChannelsIn[packet.Channel] = packet.ShallowCopy();

            if (packet.IsReady())
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] packet with {0} bytes read", packet.BytesRead));

                // make packet's timestamp absolute
                if (!packet.HasAbsTimestamp)
                {
                    packet.TimeStamp += channelTimestamp[packet.Channel]; // timestamps seem to be always relative!!
                }
                channelTimestamp[packet.Channel] = packet.TimeStamp;

                // reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel
                // arrives and requests to re-use some info (small packet header)
                vecChannelsIn[packet.Channel].Body = null;
                vecChannelsIn[packet.Channel].BytesRead = 0;
                vecChannelsIn[packet.Channel].HasAbsTimestamp = false; // can only be false if we reuse header
            }

            return true;
        }
Beispiel #12
0
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion
        virtual internal bool HandleOnMetaData(RTMPPacket packet)
        {
            AMFObject obj = new AMFObject();
            int nRes = obj.Decode(packet.Body, 0, (int)packet.BodySize, false);
            if (nRes < 0)
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, "[CDR.LibRTMP.NetStream.HandleOnMetaData] Error decoding meta data packet");
                return false;
            }

            /* For video:
             *   canSeekToEnd = true
             *   videocodecid = 4
             *   framerate = 15
             *   videodatarate = 400
             *   height = 215
             *   width = 320
             *   duration = 7.347
             *   
             * For Audio (MP3): metastring =="onID3"
             * 
             */


            string metastring = obj.GetProperty(0).StringValue;
            switch (metastring)
            {
                case "onMetaData":
                    List<AMFObjectProperty> props = new List<AMFObjectProperty>();

                    props.Clear();
                    obj.FindMatchingProperty("audiodatarate", props, 1);
                    if (props.Count > 0)
                    {
                        int rate = (int)props[0].NumberValue;
                        audioDatarate += rate;
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetStream.HandleOnMetaData] audiodatarate: {0}", audioDatarate));
                    }
                    props.Clear();
                    obj.FindMatchingProperty("videodatarate", props, 1);
                    if (props.Count > 0)
                    {
                        int rate = (int)props[0].NumberValue;
                        videoDatarate += rate;
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetStream.HandleOnMetaData] videodatarate: {0}", videoDatarate));
                    }
                    if (audioDatarate == 0 && videoDatarate == 0)
                    {
                        props.Clear();
                        obj.FindMatchingProperty("filesize", props, int.MaxValue);
                        if (props.Count > 0)
                        {
                            combinedTracksLength = (int)props[0].NumberValue;
                            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetStream.HandleOnMetaData] Set CombinedTracksLength from filesize: {0}", combinedTracksLength));
                        }
                    }
                    if (combinedTracksLength == 0)
                    {
                        props.Clear();
                        obj.FindMatchingProperty("datasize", props, int.MaxValue);
                        if (props.Count > 0)
                        {
                            combinedTracksLength = (int)props[0].NumberValue;
                            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetStream.HandleOnMetaData] Set CombinedTracksLength from datasize: {0}", combinedTracksLength));
                        }
                    }
                    props.Clear();
                    obj.FindMatchingProperty("duration", props, 1);
                    if (props.Count > 0)
                    {
                        double duration = props[0].NumberValue;
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetStream.HandleOnMetaData] Set duration: {0}", duration));
                        lock (lockVAR)
                        {
                            metaDataDurationInMS = Convert.ToInt64(duration * 1000);
                        }

                        // Looks the same as the "onPlayStatus" (See NetConnection.HandleMetadata (we route it there through HandleOnStatusDecoded
                        // doing it here also)
                        Internal_HandleOnStatusDecoded(packet, "onStatus", "NetStream.Play.OnMetaData", "onPlayStatus", obj);
                    }
                    break;
                // Looks more as an invoke to me Let NetStream.OnStatus handle it
                case "onStatus": // -=> "NetStream.Data.Start"
                    Internal_HandleOnStatusDecoded(packet, "onStatus", obj.GetProperty(1).ObjectValue.GetProperty("code").StringValue, "", obj);
                    break;
                case "onPlayStatus":
                    // "code" = "NetStream.Play.Switch"
                    // "level"= "status" ,made it "onPlayStatus" 
                    // Has also "duration" and "bytes" as additional metadata (looks like a normal onMetaData to me with less options as the normal)
                    Internal_HandleOnStatusDecoded(packet, "onStatus", obj.GetProperty(1).ObjectValue.GetProperty("code").StringValue, "onPlayStatus", obj);
                    break;
                case "onID3":
                    return HandleOnID3(obj);
                default:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.NetStream.HandleOnMetaData] metastring= {0}", metastring));
                    break;
            } //switch

            return true;
        }
Beispiel #13
0
        [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;
        }
Beispiel #14
0
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion
        internal bool HandleOnStatus(RTMPPacket packet)
        {
            AMFObject obj = new AMFObject();
            int nRes = obj.Decode(packet.Body, 0, (int)packet.BodySize, false);
            if (nRes < 0)
            {
                return false;
            }

            // string method = obj.GetProperty(0).GetString(); // always onStatus
            int stream_id = packet.InfoField2;
            string code = obj.GetProperty(3).ObjectValue.GetProperty("code").StringValue;
            string level = obj.GetProperty(3).ObjectValue.GetProperty("level").StringValue;
            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetStream.HandleOnStatus] code :{0}, level: {1}", code, level));

            return Internal_HandleOnStatusDecoded(packet, "onStatus", code, level, obj);
        }
Beispiel #15
0
        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] // hide it for code completion
        internal bool HandleOnAssignStream_ID(RTMPPacket packet, int stream_id, out int contentBufferTime)
        {
            this.stream_id = stream_id; // assign stream id
            this.contentBufferTime = NetConnection.DefaultContentBufferTime;

            NS_OnAssignStream_ID onAssignStream_ID = OnAssignStream_ID;
            if (onAssignStream_ID != null)
            {
                // -----------------------------------------------------------------------------------------
                SendOrPostCallback callback = new SendOrPostCallback(delegate(object state)
                {
                    // This is specified other thread
                    onAssignStream_ID(this, this.stream_id, ref this.contentBufferTime);
                });
                // -----------------------------------------------------------------------------------------
            
                SynchronizationContext sc = netConnection.SynchronizationContext;
                if (sc != null)
                {
                    // Use send because we need the changed "contentBufferTime" sto be able to continue
                    sc.Send(callback, null);
                }
                else
                {
                    callback(null);
                }
            }

            if (this.contentBufferTime <= 0)
            {
                this.contentBufferTime = NetConnection.DefaultContentBufferTime;
            }

            contentBufferTime = this.contentBufferTime;

            // NetConnection can handle it now
            return true;
        }
        /// <summary>
        /// The type of Ping packet is 0x4 and contains two mandatory parameters and two optional parameters.
        /// The first parameter is the type of Ping (short integer).
        /// The second parameter is the target of the ping.
        /// As Ping is always sent in Channel 2 (control channel) and the target object in RTMP header is always 0
        /// which means the Connection object,
        /// it's necessary to put an extra parameter to indicate the exact target object the Ping is sent to.
        /// The second parameter takes this responsibility.
        /// The value has the same meaning as the target object field in RTMP header.
        /// (The second value could also be used as other purposes, like RTT Ping/Pong. It is used as the timestamp.)
        /// The third and fourth parameters are optional and could be looked upon as the parameter of the Ping packet.
        /// Below is an unexhausted list of Ping messages.
        /// type 0: Clear the stream. No third and fourth parameters.
        ///         The second parameter could be 0. After the connection
        ///         is established, a Ping 0,0 will be sent from server
        ///         to client. The message will also be sent to client on
        ///         the start of Play and in response of a Seek or
        ///         Pause/Resume request. This Ping tells client
        ///         to re-calibrate the clock with the timestamp of the
        ///         next packet server sends.
        /// type 1: Tell the stream to clear the playing buffer.
        /// type 3: Buffer time of the client. The third parameter is the buffer time in millisecond.
        /// type 4: Reset a stream. Used together with type 0 in the case of VOD. Often sent before type 0.
        /// type 6: Ping the client from server. The second parameter is the current time.
        /// type 7: Pong reply from client. The second parameter is the time the server sent with his ping request.
        /// type 26: SWFVerification request
        /// type 27: SWFVerification response
        /// type 31: Buffer empty
        /// type 32: Buffer full
        /// </summary>
        /// <param name="nType"></param>
        /// <param name="nObject"></param>
        /// <param name="nTime"></param>
        /// <returns></returns>
        private bool MQ_SendPing(short nType, uint nObject, uint nTime)
        {
            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.MQ_SendPing] Sending ping type: {0}", nType));

            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x02;   // control channel (ping)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Control;
            //packet.m_nInfoField1 = System.Environment.TickCount;

            int nSize = (nType == 0x03 ? 10 : 6); // type 3 is the buffer time and requires all 3 parameters. all in all 10 bytes.
            if (nType == 0x1B) nSize = 44;
            packet.AllocPacket(nSize);
            packet.BodySize = (uint)nSize;

            List<byte> buf = new List<byte>();
            RTMPHelper.EncodeInt16(buf, nType);

            if (nType == 0x1B)
            {
                buf.AddRange(swfVerificationResponse);
            }
            else
            {
                if (nSize > 2)
                {
                    RTMPHelper.EncodeInt32(buf, (int)nObject);
                }

                if (nSize > 6)
                {
                    RTMPHelper.EncodeInt32(buf, (int)nTime);
                }
            }
            packet.Body = buf.ToArray();
            return MQInternal_SendPacket(packet, false);
        }
 private void HandleChangeChunkSize(RTMPPacket packet)
 {
     if (packet.BodySize >= 4)
     {
         InChunkSize = RTMPHelper.ReadInt32(packet.Body, 0);
         LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleChangeChunkSize] Chunk size change to {0}", InChunkSize));
     }
 }
        private bool MQ_SendCall(NetStream netStream, string methodName, object[] values)
        {
            // Let's try to encode the values infpo AMF
            List<byte> enc = new List<byte>();
            foreach (object value in values)
            {
                switch (value.GetType().ToString())
                {
                    case "System.Boolean":
                        RTMPHelper.EncodeBoolean(enc, Convert.ToBoolean(value));
                        break;
                    case "System.Byte":
                    case "System.SByte":
                    case "System.Decimal":
                    case "System.Double":
                    case "System.Single":
                    case "System.Int32":
                    case "System.UInt32":
                    case "System.Int64":
                    case "System.UInt64":
                    case "System.Int16":
                    case "System.UInt16":
                        RTMPHelper.EncodeNumber(enc, Convert.ToDouble(value));
                        break;
                    case "System.Char":
                    case "System.String":
                        RTMPHelper.EncodeString(enc, Convert.ToString(value));
                        break;
                    case "System.Object":
                    default:
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.MQ_SendCall] call(\"{0}\") parameter type not supported for encoding", methodName));
                        break;
                } //switch
            } //for


            int transactionNum = ++numInvokes; // needed incase there is a result which is send back
            // Put netStream in transaction reference table so we can match it up when the rtmp server
            // give us the result back
            transactionIDReferenceTable[transactionNum] = netStream;

            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] Sending call(\"{0}\")", methodName));
            RTMPHelper.EncodeString(enc, methodName);
            RTMPHelper.EncodeNumber(enc, transactionNum);
            enc.Add(0x05); // NULL

            packet.BodySize = (uint)enc.Count;
            packet.Body = enc.ToArray();

            methodCallDictionary.Add(transactionNum, methodName);

            return MQInternal_SendPacket(packet);
        }
        /// <summary>
        /// The type of Ping packet is 0x4 and contains two mandatory parameters and two optional parameters.
        /// The first parameter is the type of Ping (short integer).
        /// The second parameter is the target of the ping.
        /// As Ping is always sent in Channel 2 (control channel) and the target object in RTMP header is always 0
        /// which means the Connection object,
        /// it's necessary to put an extra parameter to indicate the exact target object the Ping is sent to.
        /// The second parameter takes this responsibility.
        /// The value has the same meaning as the target object field in RTMP header.
        /// (The second value could also be used as other purposes, like RTT Ping/Pong. It is used as the timestamp.)
        /// The third and fourth parameters are optional and could be looked upon as the parameter of the Ping packet.
        /// Below is an unexhausted list of Ping messages.
        /// type 0: Clear the stream. No third and fourth parameters.
        ///         The second parameter could be 0. After the connection
        ///         is established, a Ping 0,0 will be sent from server
        ///         to client. The message will also be sent to client on
        ///         the start of Play and in response of a Seek or
        ///         Pause/Resume request. This Ping tells client
        ///         to re-calibrate the clock with the timestamp of the
        ///         next packet server sends.
        /// type 1: Tell the stream to clear the playing buffer.
        /// type 3: Buffer time of the client. The third parameter is the buffer time in millisecond.
        /// type 4: Reset a stream. Used together with type 0 in the case of VOD. Often sent before type 0.
        /// type 6: Ping the client from server. The second parameter is the current time.
        /// type 7: Pong reply from client. The second parameter is the time the server sent with his ping request.
        /// type 26: SWFVerification request
        /// type 27: SWFVerification response
        /// type 31: Buffer empty
        /// type 32: Buffer full
        /// </summary>
        private void HandlePing(RTMPPacket packet)
        {
            short nType = -1;
            if (packet.Body != null && packet.BodySize >= 2)
            {
                nType = (short)RTMPHelper.ReadInt16(packet.Body, 0);
            }

            if (packet.BodySize >= 6)
            {
                uint nTime = (uint)RTMPHelper.ReadInt32(packet.Body, 2);
                switch (nType)
                {
                    case 0:
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Stream Begin {0}", nTime));
                        break;
                    case 1:
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Stream EOF {0}", nTime));
                        break;
                    case 2:
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Stream Dry {0}", nTime));
                        break;
                    case 4: // when this control message is sent, the stream is recorded
                        if (netStreams[nTime] != null) //nTime=stream_id
                        {
                            netStreams[nTime].LiveStream = false;
                            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] stream_id={0}", nTime));
                        }
                        break;
                    case 6:
                        // server ping. reply with pong.
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Ping {0}", nTime));
                        MQ_SendPing(0x07, nTime, 0);
                        break;
                    case 7:
                        // server pong. Do nothing
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Pong received {0}", nTime));
                        break;
                    case 31:
                        LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Stream BufferEmpty {0}", nTime));
                        //if (!mediaLiveStream)
                        {
                            // ---------------------- TODO!!!!!!!!!!!!!!! ---------------------------
                            /*
                            if (pausing == MediaStreamingState.Unknown)
                            {
                                MQInternal_SendPause(true);
                                pausing = MediaStreamingState.Pausing;
                            }
                            else if (pausing == MediaStreamingState.ReceivedEOFStream)
                            {
                                MQInternal_SendPause(false);
                                pausing = MediaStreamingState.ReadyForData;
                            }
                            */
                            // ---------------------- TODO!!!!!!!!!!!!!!! ---------------------------
                        }
                        break;
                    case 32:
                        LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Stream BufferReady {0}", nTime));
                        break;
                    default:
                        LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] Stream xx {0}", nTime));
                        break;
                }
            }

            if (nType == 0x1A)
            {
                if (packet.BodySize > 2 && packet.Body[2] > 0x01)
                {
                    LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection.HandlePing] SWFVerification Type {0} request not supported! Patches welcome...", packet.Body[2]));
                }
                else if (swfHash != null) // respond with HMAC SHA256 of decompressed SWF, key is the 30 byte player key, also the last 30 bytes of the server handshake are applied
                {
                    MQ_SendPing(0x1B, 0, 0);
                }
                else
                {
                    LibRTMPLogger.Log(LibRTMPLogLevel.Warning, "[CDR.LibRTMP.NetConnection.HandlePing] Ignoring SWFVerification request, swfhash and swfsize parameters not set!");
                }
            }
        }
        private bool MQ_SendDeleteStream(int stream_id)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection] Sending deleteStream");
            List<byte> enc = new List<byte>();
            RTMPHelper.EncodeString(enc, "deleteStream");
            RTMPHelper.EncodeNumber(enc, 0); // can be 0, because server send no response back
            enc.Add(0x05); // NULL
            RTMPHelper.EncodeNumber(enc, stream_id);

            packet.BodySize = (uint)enc.Count;
            packet.Body = enc.ToArray();

            // no response expected
            return MQInternal_SendPacket(packet, false);
        }
 private void HandleServerBW(RTMPPacket packet)
 {
     serverBW = RTMPHelper.ReadInt32(packet.Body, 0);
     LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleServerBW] server BW = {0}", serverBW));
 }
        private bool MQ_SendCloseStream(NetStream netStream)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.Channel = netStream.CommandChannel;// netStream.CommandChannel; or 0x03 both work!?
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            packet.InfoField2 = netStream.Stream_ID;

            List<byte> enc = new List<byte>();

            RTMPHelper.EncodeString(enc, "closeStream");
            RTMPHelper.EncodeNumber(enc, 0); // 0 according to spec adobe
            enc.Add(0x05); // NULL

            packet.Body = enc.ToArray();
            packet.BodySize = (uint)enc.Count;

            return MQInternal_SendPacket(packet);
        }
 private void HandleClientBW(RTMPPacket packet)
 {
     clientBW = RTMPHelper.ReadInt32(packet.Body, 0);
     if (packet.BodySize > 4)
     {
         clientBW2 = packet.Body[4];
     }
     else
     {
         clientBW2 = 0;
     }
     LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleClientBW] client BW = {0} {1}", clientBW, clientBW2));
 }
        private bool MQInternal_SendServerBW()
        {
            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x02;   // control channel (invoke)
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.ServerBW;

            packet.AllocPacket(4);
            packet.BodySize = 4;

            List<byte> bytesToSend = new List<byte>();
            RTMPHelper.EncodeInt32(bytesToSend, serverBW); // was hard coded : 0x001312d0
            packet.Body = bytesToSend.ToArray();
            return MQInternal_SendPacket(packet, false);
        }
        private void HandleMedia(RTMPPacket packet)
        {
            int stream_id = packet.InfoField2;

            if (netStreams[stream_id] != null)
            {
                netStreams[stream_id].HandleOnMediaPacket(packet);
            }
            else
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection.HandleMedia] No NetStream object found in channel {0} ignoring media packet", stream_id));
            }
        }
        private bool MQInternal_SendCheckBWResult(double txn)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.Channel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;
            packet.TimeStamp = (uint)(0x16 * bwCheckCounter); // temp inc value. till we figure it out.

            packet.AllocPacket(256); // should be enough
            List<byte> enc = new List<byte>();
            RTMPHelper.EncodeString(enc, "_result");
            RTMPHelper.EncodeNumber(enc, txn);
            enc.Add(0x05); // NULL
            RTMPHelper.EncodeNumber(enc, (double)bwCheckCounter++);

            packet.BodySize = (uint)enc.Count;
            packet.Body = enc.ToArray();

            return MQInternal_SendPacket(packet, false);
        }
        private void HandleMetadata(RTMPPacket packet)
        {
            int stream_id = packet.InfoField2;

            // Find matching NetStream and forward this packet.
            if (netStreams[stream_id] == null)
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection.HandleMetadata] No matching NetStream found for stream_id {0}", stream_id));
                return;
            }

            AMFObject obj = new AMFObject();
            int nRes = obj.Decode(packet.Body, 0, (int)packet.BodySize, false);
            if (nRes < 0)
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, "[CDR.LibRTMP.NetConnection.HandleMetadata] Error decoding meta data packet");
                return;
            }

            string metastring = obj.GetProperty(0).StringValue;
            switch (metastring)
            {
                case "|RtmpSampleAccess":
                    // Ignore this, unknown
                    return;

                // Looks more as an invoke to me Let NetStream.OnStatus handle it
                case "onStatus": // -=> "NetStream.Data.Start"
                case "onPlayStatus": // -=> "NetStream.Play.Switch"
                case "onMetaData": // This seams to me te real metadata
                case "onID3":
                    // Let NetStream Handle it
                    netStreams[stream_id].HandleOnMetaData(packet);
                    break;

                default:
                    LibRTMPLogger.Log(LibRTMPLogLevel.Warning, string.Format("[CDR.LibRTMP.NetConnection.HandleMetadata] Unknown metadata {0}", metastring));
                    break;
            } //switch


            return;
        }
        private bool MQInternal_SendSeek(NetStream netStream, long seekTimeInMS)
        {
            int transactionNum = ++numInvokes;

            RTMPPacket packet = new RTMPPacket();
            packet.Channel = netStream.CommandChannel; // 0x08 ???
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;
            packet.TimeStamp = 0;
            packet.InfoField2 = 2;
            packet.HasAbsTimestamp = false;

            List<byte> enc = new List<byte>();

            RTMPHelper.EncodeString(enc, "seek");
            RTMPHelper.EncodeNumber(enc, transactionNum);
            enc.Add(0x05); // NULL
            RTMPHelper.EncodeNumber(enc, seekTimeInMS); // number of milliseconds to seek into playlist

            packet.Body = enc.ToArray();
            packet.BodySize = (uint)enc.Count;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection] Sending seek: ({0})", seekTimeInMS.ToString()));

            methodCallDictionary.Add(transactionNum, "seek");

            return MQInternal_SendPacket(packet);
        }
        private void HandleFlvTags(RTMPPacket packet)
        {
            // go through FLV packets and handle metadata packets
            int pos = 0;
            uint nTimeStamp = packet.TimeStamp;

            while (pos + 11 < packet.BodySize)
            {
                int dataSize = RTMPHelper.ReadInt24(packet.Body, pos + 1); // size without header (11) and prevTagSize (4)

                if (pos + 11 + dataSize + 4 > packet.BodySize)
                {
                    LibRTMPLogger.Log(LibRTMPLogLevel.Warning, "[CDR.LibRTMP.NetConnection.HandleFlvTags] Stream corrupt?!");
                    break;
                }
                if (packet.Body[pos] == 0x12)
                {
                    // Create "fake packet" convert to a metadatapacket
                    RTMPPacket tmpPacket = (RTMPPacket)packet.Clone();
                    tmpPacket.PacketType = PacketType.Metadata;
                    byte[] newBody = new byte[dataSize];
                    Buffer.BlockCopy(packet.Body, pos + 11, newBody, 0, dataSize);
                    tmpPacket.Body = newBody;
                    tmpPacket.BodySize = Convert.ToUInt32(dataSize);

                    HandleMetadata(tmpPacket);
                }
                else if (packet.Body[pos] == 8 || packet.Body[pos] == 9)
                {
                    nTimeStamp = (uint)RTMPHelper.ReadInt24(packet.Body, pos + 4);
                    nTimeStamp |= (uint)(packet.Body[pos + 7] << 24);
                }
                pos += (11 + dataSize + 4);
            }
        }
        /// <summary>
        /// Do the last assembling and send the packet on it's way
        /// </summary>
        private bool MQInternal_SendPacket(RTMPPacket packet, bool queue = true)
        {
            uint last = 0;
            uint t = 0;

            RTMPPacket prevPacket = vecChannelsOut[packet.Channel];
            if (packet.HeaderType != HeaderType.Large && prevPacket != null)
            {
                // compress a bit by using the prev packet's attributes
                if (prevPacket.BodySize == packet.BodySize && prevPacket.PacketType == packet.PacketType &&
                    packet.HeaderType == HeaderType.Medium)
                {
                    packet.HeaderType = HeaderType.Small;
                }
                if (prevPacket.TimeStamp == packet.TimeStamp && packet.HeaderType == HeaderType.Small)
                {
                    packet.HeaderType = HeaderType.Minimum;
                }

                last = prevPacket.TimeStamp;
            }

            uint nSize = RTMPConst.PacketSize[(byte)packet.HeaderType];
            t = packet.TimeStamp - last;
            List<byte> header = new List<byte>();//byte[RTMP_LARGE_HEADER_SIZE];
            byte c = (byte)(((byte)packet.HeaderType << 6) | packet.Channel);
            header.Add(c);
            if (nSize > 1)
            {
                RTMPHelper.EncodeInt24(header, (int)t);
            }

            if (nSize > 4)
            {
                RTMPHelper.EncodeInt24(header, (int)packet.BodySize);
                header.Add((byte)packet.PacketType);
            }

            if (nSize > 8)
            {
                RTMPHelper.EncodeInt32LE(header, packet.InfoField2);
            }

            uint hSize = nSize;
            byte[] headerBuffer = header.ToArray();
            nSize = packet.BodySize;
            byte[] buffer = packet.Body;
            uint bufferOffset = 0;
            uint nChunkSize = (uint)outChunkSize;
            while (nSize + hSize > 0)
            {
                if (nSize < nChunkSize)
                {
                    nChunkSize = nSize;
                }

                if (hSize > 0)
                {
                    byte[] combinedBuffer = new byte[headerBuffer.Length + nChunkSize];
                    Array.Copy(headerBuffer, combinedBuffer, headerBuffer.Length);
                    Array.Copy(buffer, (int)bufferOffset, combinedBuffer, headerBuffer.Length, (int)nChunkSize);
                    WriteN(combinedBuffer, 0, combinedBuffer.Length);
                    hSize = 0;
                }
                else
                {
                    WriteN(buffer, (int)bufferOffset, (int)nChunkSize);
                }

                nSize -= nChunkSize;
                bufferOffset += nChunkSize;

                if (nSize > 0)
                {
                    byte sep = (byte)(0xc0 | c);
                    hSize = 1;
                    headerBuffer = new byte[1] { sep };
                }
            } //while

            vecChannelsOut[packet.Channel] = packet;

            return true;
        }
        /// <summary>
        /// Analyzes and responds if required to the given <see cref="RTMPPacket"/>.
        /// </summary>
        /// <param name="packet">The packet to inspect and react to.</param>
        /// <returns>0 (false) for OK/Failed/error, 1 for 'Stop or Complete' (true)</returns>
        private bool HandleInvoke(RTMPPacket packet)
        {
            bool ret = false;

            if (packet.Body[0] != 0x02) // make sure it is a string method name we start with
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, "[CDR.LibRTMP.NetConnection.HandleInvoke] Sanity failed. no string method in invoke packet");
                return false;
            }

            AMFObject obj = new AMFObject();
            int nRes = obj.Decode(packet.Body, 0, (int)packet.BodySize, false);
            if (nRes < 0)
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Warning, "[CDR.LibRTMP.NetConnection.HandleInvoke] Error decoding invoke packet");
                return false;
            }

            obj.Dump();
            string method = obj.GetProperty(0).StringValue;
            double txn = obj.GetProperty(1).NumberValue;

            LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleInvoke] Server invoking <{0}>", method));

            if (method == "_result")
            {
                int transactionResultNum = (int)obj.GetProperty(1).NumberValue;
                string methodInvoked = "";
                if (methodCallDictionary.ContainsKey(transactionResultNum))
                {
                    methodInvoked = methodCallDictionary[transactionResultNum];
                    methodCallDictionary.Remove(transactionResultNum);
                }

                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleInvoke] received result for method call <{0}>", methodInvoked));

                if (methodInvoked == "connect")
                {
                    // Get some info out of the result connection
                    DecodeNetConnectionInfo_Connect_Result(obj);

                    // Is SecureToken activate (when using wowza server)
                    string tmpSecureTokenPassword = string.Empty;
                    lock (lockVAR)
                    {
                        tmpSecureTokenPassword = secureTokenPassword;
                    }
                    if (!string.IsNullOrEmpty(tmpSecureTokenPassword))
                    {
                        List<AMFObjectProperty> props = new List<AMFObjectProperty>();
                        obj.FindMatchingProperty("secureToken", props, int.MaxValue);
                        if (props.Count > 0)
                        {
#if INCLUDE_TMPE
#endif
                        }
                    }
                    MQInternal_SendServerBW();

                    // Send OnConnect event
                }
                else if (methodInvoked == "createStream")
                {
                    int transactionNum = (int)obj.GetProperty(1).NumberValue;
                    int stream_id = (int)obj.GetProperty(3).NumberValue;

                    if (transactionIDReferenceTable.ContainsKey(transactionNum) && transactionIDReferenceTable[transactionNum] is NetStream)
                    {
                        NetStream netStream = (NetStream)transactionIDReferenceTable[transactionNum];
                        transactionIDReferenceTable.Remove(transactionNum);
                        LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetConnection.HandleInvoke] Received createStream(stream_id={0})", stream_id));

                        netStream.Stream_ID = stream_id;
                        // make sure we know which NetStreams use this NetCOnnection
                        RegisterNetStream(netStream);

                        int contentBufferTime = NetConnection.DefaultContentBufferTime;
                        netStream.HandleOnAssignStream_ID(packet, stream_id, out contentBufferTime);
                        if (contentBufferTime <= 0)
                        {
                            contentBufferTime = NetConnection.DefaultContentBufferTime;
                        }
                        // Tell buffer time we want to use for this channel
                        MQ_SendPing(3, (uint)netStream.Stream_ID, (uint)contentBufferTime);
                    }
                    else
                    {
                        // We haven't found a NetStream for which this is intended, so delete it again
                        MQ_SendDeleteStream(stream_id);
                    }
                }
                else if (methodInvoked == "play")
                {
                    // Server send the play command?
                }
            }
            else if (method == "onBWDone")
            {
                if (bwCheckCounter == 0)
                {
                    MQInternal_SendCheckBW();
                }
            }
            else if (method == "_onbwcheck")
            {
                MQInternal_SendCheckBWResult(txn);
            }
            else if (method == "_onbwdone")
            {
                int transactionResultNum = (int)obj.GetProperty(1).NumberValue;
                if (methodCallDictionary.ContainsValue("_checkbw"))
                {
                    var item = methodCallDictionary.First(x => x.Value == "_checkbw");
                    methodCallDictionary.Remove(item.Key);
                }
            }
            else if (method == "_error")
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection.HandleInvoke] rtmp server sent error");
            }
            else if (method == "close")
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, "[CDR.LibRTMP.NetConnection.HandleInvoke] rtmp server requested close");
                CloseConnection();
            }
            else if (method == "onStatus")
            {
                int stream_id = packet.InfoField2;
                string code = obj[3].ObjectValue.GetProperty("code").StringValue;
                string level = obj[3].ObjectValue.GetProperty("level").StringValue;

                LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetConnection.HandleInvoke] stream_id={0}, method={1}, code={2}, level={3}", stream_id, method, code, level));
                // Zoek NetStream op en geef OnStatus door
                if (netStreams[stream_id] != null)
                {
                    if (code == "NetStream.Pause.Notify")
                    {
                        // fix to help netstream
                        packet.TimeStamp = channelTimestamp[netStreams[stream_id].MediaChannel];
                    }
                    netStreams[stream_id].HandleOnStatus(packet);
                }
                else
                {
                    LibRTMPLogger.Log(LibRTMPLogLevel.Info, string.Format("[CDR.LibRTMP.NetConnection.HandleInvoke] UNHANDLED | stream_id={0}, method={1}, code={2}, level={3}", stream_id, method, code, level));
                }
            }
            else if (dMethodLookup.ContainsKey(method))
            {
                ret = true;

                SynchronizationContext sc;
                State_NC_MethodCall stateMethodCall = new State_NC_MethodCall();
                lock (lockVAR)
                {
                    sc = synchronizationContext;
                    stateMethodCall.Call = dMethodLookup[method];
                } //lock
                stateMethodCall.thisObject = this;
                stateMethodCall.MethodParam = obj;

                if (sc != null)
                {
                    switch (synchronizationContextMethod)
                    {
                        case SynchronizationContextMethod.Post:
                            sc.Post(HandleOnMethodCall, stateMethodCall);
                            break;
                        case SynchronizationContextMethod.Send:
                            sc.Send(HandleOnMethodCall, stateMethodCall);
                            break;
                    } //switch
                }
                else
                {
                    HandleOnMethodCall(stateMethodCall);
                }
            }
            else
            {
                LibRTMPLogger.Log(LibRTMPLogLevel.Trace, string.Format("[CDR.LibRTMP.NetConnection.HandleInvoke] [EVENT]={0}", method));
            }

            return ret;
        }