public bool InboundMessageAvailable(BaseRTMPProtocol pFrom, Variant messageBody, Channel channel,out bool recycleBody)
{
recycleBody = true;
AmfMessage message;
message.Header = channel.lastInHeader;
message.Body = messageBody;
var parameters = pFrom.CustomParameters;
if (parameters["authState"] == null)
{
parameters["authState"] = Variant.GetMap(new VariantMapHelper
{
{"stage","authenticated"},
{"canPublish",true},
{"canOverrideStreamName",false}
});
}
var authState = parameters["authState"];
if (pFrom.Type == ProtocolTypes.PT_INBOUND_RTMP && !string.IsNullOrEmpty(_authMethod))
{
if (!AuthenticateInbound(pFrom, message, authState))
{
Logger.FATAL("Unable to authenticate");
return false;
}
}
uint size;
Dictionary<uint, IStream> possibleStreams;
InNetRTMPStream pInNetRTMPStream;
switch (message.MessageType)
{
case Defines.RM_HEADER_MESSAGETYPE_WINACKSIZE:
if (messageBody[Defines.RM_WINACKSIZE] != VariantType.Numberic)
{
Logger.FATAL("Invalid message:{0}", messageBody.ToString());
return false;
}
size = messageBody[Defines.RM_WINACKSIZE];
if ((size > 4 * 1024 * 1024) || size == 0)
{
Logger.FATAL("Invalid message:{0}", messageBody.ToString());
return false;
}
pFrom.SetWinAckSize(size);
return true;
case Defines.RM_HEADER_MESSAGETYPE_PEERBW:
Logger.WARN("ProcessPeerBW");
return true;
case Defines.RM_HEADER_MESSAGETYPE_ACK:
return true;
case Defines.RM_HEADER_MESSAGETYPE_CHUNKSIZE:
if (messageBody[Defines.RM_CHUNKSIZE] != VariantType.Numberic)
{
Logger.FATAL("Invalid message:{0}", messageBody.ToString());
return false;
}
size = messageBody[Defines.RM_CHUNKSIZE];
if ((size > 4 * 1024 * 1024) || size == 0)
{
Logger.FATAL("Invalid message:{0}", messageBody.ToString());
return false;
}
if (!pFrom.SetInboundChunkSize(size))
{
Logger.FATAL("Unable to set chunk size:{0}", messageBody.ToString());
return false;
}
return true;
case Defines.RM_HEADER_MESSAGETYPE_USRCTRL:
switch ((ushort)messageBody[Defines.RM_USRCTRL, Defines.RM_USRCTRL_TYPE])
{
case Defines.RM_USRCTRL_TYPE_PING_REQUEST:
return SendRTMPMessage(pFrom, ConnectionMessageFactory.GetPong());
case Defines.RM_USRCTRL_TYPE_STREAM_BEGIN:
case Defines.RM_USRCTRL_TYPE_STREAM_SET_BUFFER_LENGTH:
case Defines.RM_USRCTRL_TYPE_STREAM_IS_RECORDED:
case Defines.RM_USRCTRL_TYPE_PING_RESPONSE:
Logger.WARN("User control message type: {0}",
messageBody[Defines.RM_USRCTRL, Defines.RM_USRCTRL_TYPE_STRING]);
return true;
case Defines.RM_USRCTRL_TYPE_UNKNOWN1:
case Defines.RM_USRCTRL_TYPE_UNKNOWN2:
return true;
default:
Logger.FATAL("Invalid user ctrl:\n{0}", messageBody.ToString());
return false;
}
case Defines.RM_HEADER_MESSAGETYPE_NOTIFY:
//1. Find the corresponding inbound stream
possibleStreams = Application.StreamsManager.FindByProtocolIdByType(pFrom.Id,
StreamTypes.ST_IN_NET_RTMP, false);
pInNetRTMPStream = possibleStreams.Select(x => x.Value as InNetRTMPStream)
.SingleOrDefault(
x => x.RtmpStreamId == message.StreamId);
if (pInNetRTMPStream == null)
{
Logger.WARN("No stream found. Serached for {0}:{1}. Message was:{2}", pFrom.Id, message.StreamId, messageBody.ToString());
return true;
}
//2. Remove all string values starting with @
foreach (var item in messageBody[Defines.RM_NOTIFY, Defines.RM_NOTIFY_PARAMS].Children.Where(
x => x.Value == VariantType.String && ((string)x.Value).StartsWith("@")).Select(x => x.Key).ToArray())
{
messageBody[Defines.RM_NOTIFY, Defines.RM_NOTIFY_PARAMS].Children.Remove(item);
}
recycleBody = false;
//3. Brodcast the message on the inbound stream
pInNetRTMPStream.SendStreamMessage(new BufferWithOffset(((MemoryStream)channel.inputData.BaseStream).GetBuffer(), length: (int)message.MessageLength));
return true;
case Defines.RM_HEADER_MESSAGETYPE_FLEXSTREAMSEND:
recycleBody = false;
//1. Find the corresponding inbound stream
possibleStreams = Application.StreamsManager.FindByProtocolIdByType(pFrom.Id, StreamTypes.ST_IN_NET_RTMP, false);
pInNetRTMPStream = possibleStreams.Select(x => x.Value as InNetRTMPStream)
.SingleOrDefault(x => x.RtmpStreamId == message.StreamId);
if (pInNetRTMPStream == null)
{
Logger.WARN("No stream found. Serached for {0}:{1}. Message was:{2}", pFrom.Id, message.StreamId, messageBody.ToString());
return true;
}
//2. Remove all string values starting with @
foreach (var item in messageBody[Defines.RM_FLEXSTREAMSEND, Defines.RM_FLEXSTREAMSEND_PARAMS].Children.Where(
x => x.Value == VariantType.String && ((string)x.Value).StartsWith("@")).Select(x => x.Key).ToArray())
{
messageBody[Defines.RM_FLEXSTREAMSEND, Defines.RM_FLEXSTREAMSEND_PARAMS].Children.Remove(item);
}
//写入文件
//pInNetRTMPStream.SendStreamMessage(channel);
//3. Brodcast the message on the inbound stream
pInNetRTMPStream.SendStreamMessage(new BufferWithOffset(((MemoryStream)channel.inputData.BaseStream).GetBuffer(),length: (int)message.MessageLength));
return true;
case Defines.RM_HEADER_MESSAGETYPE_SHAREDOBJECT:
case Defines.RM_HEADER_MESSAGETYPE_FLEXSHAREDOBJECT:
return Application.SOManager.Process(pFrom, messageBody);
case Defines.RM_HEADER_MESSAGETYPE_INVOKE:
case Defines.RM_HEADER_MESSAGETYPE_FLEX:
string functionName = messageBody[Defines.RM_INVOKE][Defines.RM_INVOKE_FUNCTION];
Logger.INFO(functionName);
uint currentInvokeId = messageBody[Defines.RM_INVOKE, Defines.RM_INVOKE_ID];
if (currentInvokeId != 0 && _nextInvokeId[pFrom.Id] <= currentInvokeId)
{
_nextInvokeId[pFrom.Id] = currentInvokeId + 1;
}
string streamName;
BaseOutNetRTMPStream pBaseOutNetRTMPStream = null;
double timeOffset;
Variant metadata;
switch (functionName)
{
case Defines.RM_INVOKE_FUNCTION_CONNECT:
return ProcessInvokeConnect(pFrom, message);
case Defines.RM_INVOKE_FUNCTION_CREATESTREAM:
//1. Create the neutral stream
uint id = 0;
if (pFrom.CreateNeutralStream(ref id) == null)
{
Logger.FATAL("Unable to create stream");
return false;
}
//2. Send the response
return SendRTMPMessage(pFrom, StreamMessageFactory.GetInvokeCreateStreamResult(message, id));
case Defines.RM_INVOKE_FUNCTION_PUBLISH:
//1. gather the required data from the request
var param1 = messageBody[Defines.RM_INVOKE, Defines.RM_INVOKE_PARAMS][1];
if (param1 != VariantType.String && param1 != VariantType.Boolean)
{
Logger.FATAL("Invalid request:{0}", messageBody.ToString());
return false;
}
if (param1 == VariantType.Boolean)
{
if (param1 != false)
{
Logger.FATAL("Invalid request {0}", messageBody.ToString());
return false;
}
this.Log().Info("Closing stream via publish(false)");
return pFrom.CloseStream(message.StreamId, true);
}
streamName = param1;
//2. Check to see if we are allowed to create inbound streams
if (!pFrom.CustomParameters["authState"]["canPublish"])
{
return
pFrom.SendMessage(StreamMessageFactory.GetInvokeOnStatusStreamPublishBadName(
message, streamName),true);
}
var recording = string.Equals(message.InvokeParam[2], Defines.RM_INVOKE_PARAMS_PUBLISH_TYPERECORD);
var appending = string.Equals(message.InvokeParam[2], Defines.RM_INVOKE_PARAMS_PUBLISH_TYPEAPPEND);
//3. Test to see if this stream name is already published somewhere
if (Application.AllowDuplicateInboundNetworkStreams)
{
var existingStreams =
Application.StreamsManager.FindByTypeByName(StreamTypes.ST_IN_NET_RTMP, streamName,false, false);
if (existingStreams.Count > 0)
{
if (pFrom.CustomParameters["authState"]["canOverrideStreamName"])
{
foreach (var existingStream in existingStreams.Values.OfType<InNetRTMPStream>().Where(existingStream => existingStream.Protocol != null))
{
Logger.WARN(
"Overriding stream {0}:{1} with name {2} from conneciton {3}",
existingStream.RtmpStreamId, existingStream.UniqueId,
existingStream.Name, existingStream.Protocol.Id);
(existingStream.Protocol as BaseRTMPProtocol).CloseStream(
existingStream.RtmpStreamId, true);
}
}
else
{
Logger.WARN(
"Unable to override stream {0} because this connection dosen't have the right",
streamName);
return
pFrom.SendMessage(
StreamMessageFactory.GetInvokeOnStatusStreamPublishBadName(message,
streamName),true);
}
}
}
else if (!Application.StreamNameAvailable(streamName, pFrom))
{
Logger.WARN("Stream name {0} already occupied and application doesn't allow duplicated inbound network streams", streamName);
return
pFrom.SendMessage( StreamMessageFactory.GetInvokeOnStatusStreamPublishBadName(
message, streamName), true);
}
//4. Create the network inbound stream
pInNetRTMPStream = pFrom.CreateINS(message.ChannelId, message.StreamId, streamName);
if (pInNetRTMPStream == null)
{
Logger.FATAL("Unable to create inbound stream");
return false;
}
//7. Bind the waiting subscribers
Application.OnPublish(pFrom, pInNetRTMPStream, message.InvokeParam[2]);
//8. Send the streamPublished status message
if (!pInNetRTMPStream.SendOnStatusStreamPublished())
{
Logger.FATAL("Unable to send OnStatusStreamPublished");
return false;
}
//9. Start recording if necessary
//if (recording || appending)
//{
// var meta = GetMetaData(streamName, false);
// var pOutFileStream = CreateOutFileStream(pFrom, meta, appending);
// if (pOutFileStream != null && pOutFileStream.Link(pInNetRTMPStream)) return true;
// Logger.FATAL("Unable to bind the recording stream");
// return false;
//}
return true;
case Defines.RM_INVOKE_FUNCTION_PLAY:
//1. Minimal validation
if (message.InvokeParam[1] != VariantType.String)
{
Logger.FATAL("Invalid request:{0}", message.Body.ToString());
return false;
}
//2. Close any streams left open
if (!pFrom.CloseStream(message.StreamId, true))
{
Logger.FATAL("Unable to close stream {0} {1}", pFrom.Id, message.StreamId);
return false;
}
//3. Gather required data from the request
streamName = message.InvokeParam[1];
double startTime = message.InvokeParam[2] == VariantType.Double ? (double)message.InvokeParam[2] : -2000;
double length = message.InvokeParam[3] == VariantType.Double ? (double)message.InvokeParam[3] : -1000;
if (startTime < 0 && startTime != -2000 && startTime != -1000) startTime = -2000;
if (length < 0 && length != -1) length = -1;
Logger.INFO("Play request for stream name `{0}`. Start:{1}; length: {2}", streamName, startTime, length);
//6. bind the network outbound stream to the inbound stream
//depending on the type of the outbound stream
switch ((int)startTime)
{
case -2000:
bool linked;
//7. try to link to live stream
if (!TryLinkToLiveStream(pFrom, message.StreamId, streamName, out linked))
{
Logger.FATAL("Unable to link streams");
return false;
}
if (linked) return true;
metadata = GetMetaData(streamName, true);
//8. try to link to file stream
if (!TryLinkToFileStream(pFrom, message.StreamId, metadata, streamName, startTime, length, out linked))
{
Logger.FATAL("Unable to link streams");
return false;
}
if (linked) return true;
//9. Ok, no live/file stream. Just wait for the live stream now...
Logger.WARN("We are going to wait for the live stream `{0}`", streamName);
pBaseOutNetRTMPStream = pFrom.CreateONS(message.StreamId, streamName, StreamTypes.ST_IN_NET_RTMP);
goto case -999;
case -1000://only live
if (!TryLinkToLiveStream(pFrom, message.StreamId, streamName, out linked))
{
Logger.FATAL("Unable to link streams");
return false;
}
if (linked) return true;
Logger.WARN("We are going to wait for the live stream `%s`", streamName);
pBaseOutNetRTMPStream = pFrom.CreateONS(
message.StreamId, streamName, StreamTypes.ST_IN_NET_RTMP);
goto case -999;
case -999:
//Application.ClusterAppProtocolHandler.PlayStream(Application.InstanceName,streamName);
if (ClientApplicationManager.ClusterApplication != null)
{
ClientApplicationManager.ClusterApplication.GetProtocolHandler<BaseClusterAppProtocolHandler>().PlayStream(Application.Id, streamName);
}
//request["waitForLiveStream"] = true;
//request["streamName"] = streamName;
//if (pFrom.CustomParameters["origin"] != null)
//{
//if (_externalStreamProtocol == null)
// Application.PullExternalStream(new Variant
// {
// {"uri", "rtmp://192.168.20.56/live"},
// {"localStreamName", streamName},
// {"emulateUserAgent", "MAC 10,1,82,76"},
// {"swfUrl", "my crtmpserver"},
// {"pageUrl", "linkage"}
// });
//else
//{
// PlayAnotherStream(_externalStreamProtocol, streamName);
//}
//}
return pBaseOutNetRTMPStream != null;
default://only recorded
metadata = GetMetaData(streamName, true);
//12. Perform little adjustment on metadata
if ((string)metadata[Defines.META_MEDIA_TYPE] == Defines.MEDIA_TYPE_LIVE_OR_FLV)
{
metadata[Defines.META_MEDIA_TYPE] = Defines.MEDIA_TYPE_FLV;
}
//13. try to link to file stream
if (!TryLinkToFileStream(pFrom, message.StreamId, metadata, streamName,
startTime, length, out linked))
{
Logger.FATAL("Unable to link streams");
return false;
}
return linked;
}
case Defines.RM_INVOKE_FUNCTION_PAUSERAW:
case Defines.RM_INVOKE_FUNCTION_PAUSE:
pBaseOutNetRTMPStream = Application.StreamsManager.FindByProtocolIdByType(pFrom.Id,
StreamTypes.ST_OUT_NET_RTMP, true).Values.OfType<BaseOutNetRTMPStream>().SingleOrDefault(x => x.RTMPStreamId == message.StreamId);
if (pBaseOutNetRTMPStream == null)
{
Logger.FATAL("No out stream");
return false;
}
//3. get the operation
bool pause = message.InvokeParam[1];
if (pause)
{
//4. Pause it
return pBaseOutNetRTMPStream.Pause();
}
else
{
timeOffset = 0.0;
if (message.InvokeParam[2] == VariantType.Numberic)
timeOffset = message.InvokeParam[2];
//8. Perform seek
if (!pBaseOutNetRTMPStream.Seek(timeOffset))
{
Logger.FATAL("Unable to seek");
return false;
}
//9. Resume
return pBaseOutNetRTMPStream.Resume();
}
case Defines.RM_INVOKE_FUNCTION_CLOSESTREAM:
return pFrom.CloseStream(message.StreamId, true);
case Defines.RM_INVOKE_FUNCTION_SEEK:
//1. Read stream index and offset in millisecond
timeOffset = 0.0;
if (message.InvokeParam[1] == VariantType.Numberic)
timeOffset = message.InvokeParam[1];
//2. Find the corresponding outbound stream
pBaseOutNetRTMPStream = Application.StreamsManager.FindByProtocolIdByType(pFrom.Id,
StreamTypes.ST_OUT_NET_RTMP, true).Values.OfType<BaseOutNetRTMPStream>().SingleOrDefault(x => x.RTMPStreamId == message.StreamId);
if (pBaseOutNetRTMPStream == null)
{
Logger.FATAL("No out stream");
return false;
}
return pBaseOutNetRTMPStream.Seek(timeOffset);
case Defines.RM_INVOKE_FUNCTION_RELEASESTREAM:
//1. Attempt to find the stream
var streams = Application.StreamsManager.FindByProtocolIdByName(pFrom.Id,
message.InvokeParam[1], false);
uint streamId = 0;
if (streams.Count > 0)
{
//2. Is this the correct kind?
if (streams.Values.First().Type.TagKindOf(StreamTypes.ST_IN_NET_RTMP))
{
//3. get the rtmp stream id
pInNetRTMPStream = (InNetRTMPStream)streams.Values.First();
streamId = pInNetRTMPStream.RtmpStreamId;
//4. close the stream
if (!pFrom.CloseStream(streamId, true))
{
Logger.FATAL("Unable to close stream");
return true;
}
}
}
if (streamId > 0)
{
//5. Send the response
if (!pFrom.SendMessage( StreamMessageFactory.GetInvokeReleaseStreamResult(3,
streamId, message.InvokeId, streamId), true))
{
Logger.FATAL("Unable to send message to client");
return false;
}
}
else
{
if (!pFrom.SendMessage( StreamMessageFactory.GetInvokeReleaseStreamErrorNotFound(message), true))
{
Logger.FATAL("Unable to send message to client");
return false;
}
}
//3. Done
return true;
case Defines.RM_INVOKE_FUNCTION_DELETESTREAM:
return pFrom.CloseStream(message.InvokeParam[1], false);
case Defines.RM_INVOKE_FUNCTION_RESULT:
case Defines.RM_INVOKE_FUNCTION_ERROR:
if (!_resultMessageTracking.ContainsKey(pFrom.Id) ||
!_resultMessageTracking[pFrom.Id].ContainsKey(message.InvokeId))
return true;
var request0 = _resultMessageTracking[pFrom.Id][message.InvokeId];
switch (request0.InvokeFunction)
{
case Defines.RM_INVOKE_FUNCTION_CONNECT:
return ProcessInvokeConnectResult(pFrom, request0, message);
case Defines.RM_INVOKE_FUNCTION_CREATESTREAM:
return ProcessInvokeCreateStreamResult(pFrom, request0, message);
case Defines.RM_INVOKE_FUNCTION_FCSUBSCRIBE:
return true;
case Defines.RM_INVOKE_FUNCTION_ONBWCHECK:
startTime = pFrom.CustomParameters["lastOnnBWCheckMessage"];
double totalTime = (DateTime.Now.Ticks - startTime) / (double)1000000;
var speed = (int)(ONBWCHECK_SIZE / totalTime / 1024.0 * 8.0);
return SendRTMPMessage(pFrom, GenericMessageFactory.GetInvokeOnBWDone(speed));
default:
Logger.WARN("Invoke result not yet implemented: Request:{0} Response:{1}", request0.ToString(), message.ToString());
return true;
}
case Defines.RM_INVOKE_FUNCTION_ONSTATUS:
return ProcessInvokeOnStatus(pFrom, message);
case Defines.RM_INVOKE_FUNCTION_FCPUBLISH:
//1. Get the stream name
streamName = message.InvokeParam[1];
//2. Send the release stream response. Is identical to the one
//needed by this f****r
//TODO: this is a nasty hack
if (!pFrom.SendMessage( StreamMessageFactory.GetInvokeReleaseStreamResult(3, 0, message.InvokeId, 0), true))
{
Logger.FATAL("Unable to send message to client");
return false;
}
//3. send the onFCPublish message
if (!SendRTMPMessage(pFrom, StreamMessageFactory.GetInvokeOnFCPublish(3, 0, 0, false, 0,
Defines.RM_INVOKE_PARAMS_ONSTATUS_CODE_NETSTREAMPUBLISHSTART, streamName)))
{
Logger.FATAL("Unable to send message to client");
return false;
}
//4. Done
return true;
case Defines.RM_INVOKE_FUNCTION_GETSTREAMLENGTH:
metadata = GetMetaData(message.InvokeParam[1], true);
var _params = Variant.GetList(Variant.Get(),metadata == VariantType.Map
? metadata[Defines.META_FILE_DURATION]/1000.00 : 0.0);
if (!SendRTMPMessage(pFrom, GenericMessageFactory.GetInvokeResult(message, _params)))
{
Logger.FATAL("Unable to send message to client");
return false;
}
return true;
case Defines.RM_INVOKE_FUNCTION_ONBWDONE:
return true;
case Defines.RM_INVOKE_FUNCTION_CHECKBANDWIDTH:
case "_checkbw":
if (!_enableCheckBandwidth)
{
Logger.WARN("checkBandwidth is disabled.");
return true;
}
if (!SendRTMPMessage(pFrom, _onBWCheckMessage,true, false))
{
Logger.FATAL("Unable to send message to flash player");
return false;
}
pFrom.CustomParameters["lastOnnBWCheckMessage"] = DateTime.Now.Ticks;
return true;
case "receiveAudio":
pBaseOutNetRTMPStream = Application.StreamsManager.FindByProtocolIdByType(pFrom.Id,
StreamTypes.ST_OUT_NET_RTMP, true).Values.OfType<BaseOutNetRTMPStream>().SingleOrDefault(x => x.RTMPStreamId ==message.StreamId);
if (pBaseOutNetRTMPStream != null)
pBaseOutNetRTMPStream.ReceiveAudio = message.InvokeParam[1];
return true;
case "receiveVideo":
pBaseOutNetRTMPStream = Application.StreamsManager.FindByProtocolIdByType(pFrom.Id,
StreamTypes.ST_OUT_NET_RTMP, true).Values.OfType<BaseOutNetRTMPStream>().SingleOrDefault(x => x.RTMPStreamId == message.StreamId);
if (pBaseOutNetRTMPStream != null)
pBaseOutNetRTMPStream.ReceiveVideo = message.InvokeParam[1];
return true;
default:
return ProcessInvokeGeneric(pFrom, message);
}
case Defines.RM_HEADER_MESSAGETYPE_ABORTMESSAGE:
if (messageBody[Defines.RM_ABORTMESSAGE] != VariantType.Numberic)
{
Logger.FATAL("Invalid message {0}", messageBody.ToString());
return false;
}
return pFrom.ResetChannel(messageBody[Defines.RM_ABORTMESSAGE]);
default:
Logger.FATAL("Request type not yet implemented:{0}", messageBody.ToString());
return false;
}
}