@Override public void messageReceived(Object in, IoSession session) throws Exception { RTMPConnection conn = (RTMPConnection) session.getAttribute(RTMPConnection.RTMP_CONNECTION_KEY); RTMP state = (RTMP) session.getAttribute(ProtocolState.SESSION_KEY); IRTMPEvent message = null; final Packet packet = (Packet) in; message = packet.getMessage(); final Header header = packet.getHeader(); final Channel channel = conn.getChannel(header.getChannelId()); // Increase number of received messages conn.messageReceived(); if (header.getDataType() == TYPE_BYTES_READ) { // TODO need to sync the bytes read on edge and origin onStreamBytesRead(conn, channel, header, (BytesRead) message); } if (header.getDataType() == TYPE_INVOKE) { final IServiceCall call = ((Invoke) message).getCall(); final String action = call.getServiceMethodName(); if (call.getServiceName() == null && !conn.isConnected() && StreamAction.valueOf(action).equals(StreamAction.CONNECT)) { handleConnect(conn, channel, header, (Invoke) message, (RTMP) state); return; } } switch (header.getDataType()) { case TYPE_CHUNK_SIZE: case TYPE_INVOKE: case TYPE_FLEX_MESSAGE: case TYPE_NOTIFY: case TYPE_AUDIO_DATA: case TYPE_VIDEO_DATA: case TYPE_FLEX_SHARED_OBJECT: case TYPE_FLEX_STREAM_SEND: case TYPE_SHARED_OBJECT: case TYPE_BYTES_READ: forwardPacket(conn, packet); break; case TYPE_PING: onPing(conn, channel, header, (Ping) message); break; default: if (log.isDebugEnabled()) { log.debug("Unknown type: {}", header.getDataType()); } } if (message instanceof Unknown) { log.info(message.toString()); } if (message != null) { message.release(); } }
/** {@inheritDoc} */ public void messageReceived(RTMPConnection conn, Packet packet) throws Exception { log.trace("messageReceived connection: {}", conn.getSessionId()); if (conn != null) { IRTMPEvent message = null; try { message = packet.getMessage(); final Header header = packet.getHeader(); final Number streamId = header.getStreamId(); final Channel channel = conn.getChannel(header.getChannelId()); final IClientStream stream = conn.getStreamById(streamId); if (log.isTraceEnabled()) { log.trace("Message received - header: {}", header); } // set stream id on the connection conn.setStreamId(streamId); // increase number of received messages conn.messageReceived(); // set the source of the message message.setSource(conn); // process based on data type final byte headerDataType = header.getDataType(); if (log.isTraceEnabled()) { log.trace("Header / message data type: {}", headerDataType); } switch (headerDataType) { case TYPE_AGGREGATE: log.debug( "Aggregate type data - header timer: {} size: {}", header.getTimer(), header.getSize()); case TYPE_AUDIO_DATA: case TYPE_VIDEO_DATA: // mark the event as from a live source // log.trace("Marking message as originating from a Live source"); message.setSourceType(Constants.SOURCE_TYPE_LIVE); // NOTE: If we respond to "publish" with "NetStream.Publish.BadName", // the client sends a few stream packets before stopping. We need to ignore them if (stream != null) { ((IEventDispatcher) stream).dispatchEvent(message); } break; case TYPE_FLEX_SHARED_OBJECT: case TYPE_SHARED_OBJECT: onSharedObject(conn, channel, header, (SharedObjectMessage) message); break; case TYPE_INVOKE: case TYPE_FLEX_MESSAGE: onCommand(conn, channel, header, (Invoke) message); IPendingServiceCall call = ((Invoke) message).getCall(); if (message.getHeader().getStreamId().intValue() != 0 && call.getServiceName() == null && StreamAction.PUBLISH.equals(call.getServiceMethodName())) { if (stream != null) { // Only dispatch if stream really was created ((IEventDispatcher) stream).dispatchEvent(message); } } break; case TYPE_NOTIFY: // like an invoke, but does not return // anything and has a invoke / transaction // id of 0 case TYPE_FLEX_STREAM_SEND: if (((Notify) message).getData() != null && stream != null) { // Stream metadata ((IEventDispatcher) stream).dispatchEvent(message); } else { onCommand(conn, channel, header, (Notify) message); } break; case TYPE_PING: onPing(conn, channel, header, (Ping) message); break; case TYPE_BYTES_READ: onStreamBytesRead(conn, channel, header, (BytesRead) message); break; case TYPE_CHUNK_SIZE: onChunkSize(conn, channel, header, (ChunkSize) message); break; case Constants.TYPE_CLIENT_BANDWIDTH: // onBWDone / peer bw log.debug("Client bandwidth: {}", message); onClientBandwidth(conn, channel, (ClientBW) message); break; case Constants.TYPE_SERVER_BANDWIDTH: // window ack size log.debug("Server bandwidth: {}", message); onServerBandwidth(conn, channel, (ServerBW) message); break; default: log.debug("Unknown type: {}", header.getDataType()); } if (message instanceof Unknown) { log.info("Message type unknown: {}", message); } } catch (Throwable t) { log.error("Exception", t); } // XXX this may be causing 'missing' data if previous methods are // not making copies before buffering etc.. if (message != null) { message.release(); } } }
/** * Breaks-up the aggregate into its individual parts and returns them as a list. The parts are * returned based on the ordering of the aggregate itself. * * @return list of IRTMPEvent objects */ public LinkedList<IRTMPEvent> getParts() { LinkedList<IRTMPEvent> parts = new LinkedList<IRTMPEvent>(); log.trace("Aggregate data length: {}", data.limit()); int position = data.position(); do { try { // read the header // log.trace("Hex: {}", data.getHexDump()); byte subType = data.get(); // when we run into subtype 0 break out of here if (subType == 0) { log.debug("Subtype 0 encountered within this aggregate, processing with exit"); break; } int size = IOUtils.readUnsignedMediumInt(data); log.debug("Data subtype: {} size: {}", subType, size); // TODO ensure the data contains all the bytes to support the specified size int timestamp = IOUtils.readExtendedMediumInt(data); /*timestamp = ntohap((GETIBPOINTER(buffer) + 4)); 0x12345678 == 34 56 78 12*/ int streamId = IOUtils.readUnsignedMediumInt(data); log.debug("Data timestamp: {} stream id: {}", timestamp, streamId); Header partHeader = new Header(); partHeader.setChannelId(header.getChannelId()); partHeader.setDataType(subType); partHeader.setSize(size); // use the stream id from the aggregate's header partHeader.setStreamId(header.getStreamId()); partHeader.setTimer(timestamp); // timer delta == time stamp - timer base // the back pointer may be used to verify the size of the individual part // it will be equal to the data size + header size int backPointer = 0; switch (subType) { case TYPE_AUDIO_DATA: AudioData audio = new AudioData(data.getSlice(size)); audio.setTimestamp(timestamp); audio.setHeader(partHeader); log.debug("Audio header: {}", audio.getHeader()); parts.add(audio); // log.trace("Hex: {}", data.getHexDump()); // ensure 4 bytes left to read an int if (data.position() < data.limit() - 4) { backPointer = data.getInt(); // log.trace("Back pointer: {}", backPointer); if (backPointer != (size + 11)) { log.debug("Data size ({}) and back pointer ({}) did not match", size, backPointer); } } break; case TYPE_VIDEO_DATA: VideoData video = new VideoData(data.getSlice(size)); video.setTimestamp(timestamp); video.setHeader(partHeader); log.debug("Video header: {}", video.getHeader()); parts.add(video); // log.trace("Hex: {}", data.getHexDump()); // ensure 4 bytes left to read an int if (data.position() < data.limit() - 4) { backPointer = data.getInt(); // log.trace("Back pointer: {}", backPointer); if (backPointer != (size + 11)) { log.debug("Data size ({}) and back pointer ({}) did not match", size, backPointer); } } break; default: log.debug("Non-A/V subtype: {}", subType); Unknown unk = new Unknown(subType, data.getSlice(size)); unk.setTimestamp(timestamp); unk.setHeader(partHeader); parts.add(unk); // ensure 4 bytes left to read an int if (data.position() < data.limit() - 4) { backPointer = data.getInt(); } } position = data.position(); } catch (Exception e) { log.error("Exception decoding aggregate parts", e); break; } log.trace("Data position: {}", position); } while (position < data.limit()); log.trace("Aggregate processing complete, {} parts extracted", parts.size()); return parts; }
/** {@inheritDoc} */ @Override protected void onCommand(RTMPConnection conn, Channel channel, Header source, ICommand command) { log.trace("onCommand: {}, id: {}", command, command.getTransactionId()); final IServiceCall call = command.getCall(); final String methodName = call.getServiceMethodName(); log.debug( "Service name: {} args[0]: {}", methodName, (call.getArguments().length != 0 ? call.getArguments()[0] : "")); if ("_result".equals(methodName) || "_error".equals(methodName)) { final IPendingServiceCall pendingCall = conn.getPendingCall(command.getTransactionId()); log.debug("Received result for pending call - {}", pendingCall); if (pendingCall != null) { if ("connect".equals(methodName)) { Integer encoding = (Integer) connectionParams.get("objectEncoding"); if (encoding != null && encoding.intValue() == 3) { log.debug("Setting encoding to AMF3"); conn.getState().setEncoding(IConnection.Encoding.AMF3); } } } handlePendingCallResult(conn, (Invoke) command); return; } // potentially used twice so get the value once boolean onStatus = "onStatus".equals(methodName); log.debug("onStatus {}", onStatus); if (onStatus) { Integer streamId = source.getStreamId(); if (log.isDebugEnabled()) { log.debug("Stream id from header: {}", streamId); // XXX create better to serialize ObjectMap to Status object ObjectMap<?, ?> objMap = (ObjectMap<?, ?>) call.getArguments()[0]; // should keep this as an Object to stay compatible with FMS3 etc log.debug("Client id from status: {}", objMap.get("clientid")); } if (streamId != null) { // try lookup by stream id NetStreamPrivateData streamData = streamDataMap.get(streamId); // if null return the first one in the map if (streamData == null) { log.debug("Stream data was not found by id. Map contents: {}", streamDataMap); if (!streamDataMap.isEmpty()) { streamData = streamDataMap.values().iterator().next(); } } if (streamData == null) { log.warn("Stream data was null for id: {}", streamId); } if (streamData != null && streamData.handler != null) { log.debug("Got stream data and handler"); streamData.handler.onStreamEvent((Notify) command); } } } // if this client supports service methods, forward the call if (serviceProvider == null) { // client doesn't support calling methods on him call.setStatus(Call.STATUS_METHOD_NOT_FOUND); call.setException(new MethodNotFoundException(methodName)); log.info( "No service provider / method not found; to handle calls like onBWCheck, add a service provider"); } else { serviceInvoker.invoke(call, serviceProvider); } if (call instanceof IPendingServiceCall) { IPendingServiceCall psc = (IPendingServiceCall) call; Object result = psc.getResult(); log.debug("Pending call result is: {}", result); if (result instanceof DeferredResult) { DeferredResult dr = (DeferredResult) result; dr.setTransactionId(command.getTransactionId()); dr.setServiceCall(psc); dr.setChannel(channel); conn.registerDeferredResult(dr); } else if (!onStatus) { if ("onBWCheck".equals(methodName)) { onBWCheck(call.getArguments().length > 0 ? call.getArguments()[0] : null); Invoke reply = new Invoke(); reply.setCall(call); reply.setTransactionId(command.getTransactionId()); channel.write(reply); } else if ("onBWDone".equals(methodName)) { onBWDone(call.getArguments().length > 0 ? call.getArguments()[0] : null); } else { Invoke reply = new Invoke(); reply.setCall(call); reply.setTransactionId(command.getTransactionId()); log.debug("Sending empty call reply: {}", reply); channel.write(reply); } } } }