private void writeCommandExpectingResult(Channel channel, Command command) {
   final int id = transactionId++;
   command.setTransactionId(id);
   transactionToCommandMap.put(id, command.getName());
   Log.i(this.getClass().getName(), "sending command (expecting result): " + command);
   channel.write(command);
 }
 @Override
 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
   final Object o = e.getMessage();
   final Channel channel = e.getChannel();
   final RtmpMessage message = (RtmpMessage) o;
   switch (message.getHeader().getMessageType()) {
     case CONTROL:
       Control control = (Control) message;
       Log.d(this.getClass().getName(), "server control: " + control);
       switch (control.getType()) {
         case PING_REQUEST:
           final int time = control.getTime();
           Log.d(this.getClass().getName(), "server ping: " + time);
           Control pong = Control.pingResponse(time);
           Log.d(this.getClass().getName(), "sending ping response: " + pong);
           channel.write(pong);
           break;
         case SWFV_REQUEST:
           if (swfvBytes == null) {
             Log.w(
                 this.getClass().getName(),
                 "swf verification not initialized!"
                     + " not sending response, server likely to stop responding / disconnect");
           } else {
             Control swfv = Control.swfvResponse(swfvBytes);
             Log.i(this.getClass().getName(), "sending swf verification response: " + swfv);
             channel.write(swfv);
           }
           break;
         default:
           Log.d(this.getClass().getName(), "ignoring control message: " + control);
       }
       break;
     case METADATA_AMF0:
     case METADATA_AMF3:
       Metadata metadata = (Metadata) message;
       if (metadata.getName().equals("onMetaData")) {
         Log.i(this.getClass().getName(), "writing server 'onMetaData': " + metadata);
         writer.write(message);
       } else {
         Log.i(this.getClass().getName(), "ignoring server metadata: " + metadata);
       }
       break;
     case AUDIO:
     case VIDEO:
     case AGGREGATE:
       writer.write(message);
       bytesRead += message.getHeader().getSize();
       if ((bytesRead - bytesReadLastSent) > bytesReadWindow) {
         Log.i(this.getClass().getName(), "sending bytes read ack " + bytesRead);
         bytesReadLastSent = bytesRead;
         channel.write(new BytesRead(bytesRead));
       }
       break;
     case COMMAND_AMF0:
     case COMMAND_AMF3:
       Command command = (Command) message;
       String name = command.getName();
       Log.i(this.getClass().getName(), "server command: " + name);
       if (name.equals("_result")) {
         String resultFor = transactionToCommandMap.get(command.getTransactionId());
         Log.i(this.getClass().getName(), "result for method call: " + resultFor);
         if (resultFor.equals("connect")) {
           writeCommandExpectingResult(channel, Command.createStream());
         } else if (resultFor.equals("createStream")) {
           final int streamId = ((Double) command.getArg(0)).intValue();
           Log.i(this.getClass().getName(), "streamId to play: " + streamId);
           channel.write(Command.play(streamId, session));
         } else {
           Log.w(this.getClass().getName(), "un-handled server result for: " + resultFor);
         }
       } else if (name.equals("onStatus")) {
         Map<String, Object> temp = (Map) command.getArg(0);
         String code = (String) temp.get("code");
         Log.i(this.getClass().getName(), "onStatus code: " + code);
         if (code.equals("NetStream.Failed")
             || code.equals("NetStream.Play.Failed")
             || code.equals("NetStream.Play.Stop")) {
           Log.i(this.getClass().getName(), "disconnecting, bytes read: " + bytesRead);
           writer.close();
           channel.close();
         }
       } else {
         Log.w(this.getClass().getName(), "ignoring server command: " + command);
       }
       break;
     case BYTES_READ:
       Log.i(this.getClass().getName(), "server bytes read: " + message);
       break;
     case WINDOW_ACK_SIZE:
       WindowAckSize was = (WindowAckSize) message;
       if (was.getValue() != bytesReadWindow) {
         channel.write(SetPeerBw.dynamic(bytesReadWindow));
       }
       break;
     case SET_PEER_BW:
       SetPeerBw spb = (SetPeerBw) message;
       if (spb.getValue() != bytesWrittenWindow) {
         channel.write(new WindowAckSize(bytesWrittenWindow));
       }
       break;
     default:
       Log.i(this.getClass().getName(), "ignoring rtmp message: " + message);
   }
 }
 @Override
 public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
   Log.i(this.getClass().getName(), "handshake complete, sending 'connect'");
   writeCommandExpectingResult(e.getChannel(), Command.connect(session));
 }