/// <summary> /// Decode the amfobject when NetConnection has a successfull connnection. /// Don't know if it's of any use and if the same info is available for /// different RTMP servers. (Only tested with wowza 3.5.2) /// </summary> /// <param name="obj"></param> private void DecodeNetConnectionInfo_Connect_Result(AMFObject obj) { netConnectionConnectInfo.Clear(); // WOWZA: In position 2 and 3 is the info we want as // Red5: has all it's info in [3] it seems if (obj.Count < 3) { return; } List<AMFObjectProperty> props = new List<AMFObjectProperty>(); props.Clear(); obj.FindMatchingProperty("fmsVer", props, 1); if (props.Count > 0) { netConnectionConnectInfo.FMSVer = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("capabilities", props, 1); if (props.Count > 0) { try { netConnectionConnectInfo.Capabilities = Convert.ToInt32(props[0].NumberValue); } catch { } } props.Clear(); obj.FindMatchingProperty("mode", props, 1); if (props.Count > 0) { try { netConnectionConnectInfo.Mode = Convert.ToInt32(props[0].NumberValue); } catch { } } props.Clear(); obj.FindMatchingProperty("code", props, 1); if (props.Count > 0) { netConnectionConnectInfo.Code = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("level", props, 1); if (props.Count > 0) { netConnectionConnectInfo.Level = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("description", props, 1); if (props.Count > 0) { netConnectionConnectInfo.Description = props[0].StringValue; } props.Clear(); obj.FindMatchingProperty("data", props, 1); if (props.Count > 0) { AMFObject obj2 = props[0].ObjectValue; props.Clear(); obj2.FindMatchingProperty("version", props, 1); if (props.Count > 0) { try { netConnectionConnectInfo.Version = new Version(props[0].StringValue.Replace(',', '.')); realRTMPServerVersion = netConnectionConnectInfo.Version; } catch { } } } // Red5 doesn't seem to have this property props.Clear(); obj.FindMatchingProperty("clientid", props, 1); if (props.Count > 0) { try { netConnectionConnectInfo.ClientID = Convert.ToInt64(props[0].NumberValue); } catch { } } // Red5 doesn't seem to have this property props.Clear(); obj.FindMatchingProperty("objectEncoding", props, 1); if (props.Count > 0) { try { netConnectionConnectInfo.ObjectEncoding = Convert.ToInt32(props[0].NumberValue); } catch { } } }
[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; }
/// <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; }
[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; }