/* * myLib - https://github.com/taktod/myLib * Copyright (c) 2014 ttProject. All rights reserved. * * Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3. */ package com.ttProject.flazr.test.publish; import java.util.HashMap; import java.util.Map; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.MessageEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.flazr.rtmp.RtmpMessage; import com.flazr.rtmp.RtmpPublisher; import com.flazr.rtmp.RtmpReader; import com.flazr.rtmp.client.ClientHandler; import com.flazr.rtmp.client.ClientOptions; import com.flazr.rtmp.message.ChunkSize; import com.flazr.rtmp.message.Command; import com.flazr.rtmp.message.Control; /** * データを送信するのに使う、ClientHandler * 動作はJCasterのrtmpPublishPluginのほぼコピー * @author taktod */ public class SendClientHandler extends ClientHandler { /** ロガー */ private final Logger logger = LoggerFactory.getLogger(SendClientHandler.class); /** 接続情報 */ private final ClientOptions options; /** やりとりID */ private int transactionId = 1; /** 発行済IDと命令のひも付き保持 */ private Map<Integer, String> transactionToCommandMap; /** 配信制御 */ private RtmpPublisher publisher = null; /** 配信streamId */ private int streamId = 0; /** 命令の実行用チャンネル */ private Channel channel = null; /** データ転送に利用しているthread保持(よこから停止させるときに、blockされているthreadの中断をさせることでトリガーとするのに必要) */ private Thread thread = null; /** * コンストラクタ * @param options */ public SendClientHandler(ClientOptions options) { super(options); this.options = options; transactionToCommandMap = new HashMap<Integer, String>(); } /** * 配信開始処理 */ public void publish() { if(options.getPublishType() != null) { logger.info("send publish:" + streamId); RtmpReader reader = options.getReaderToPublish(); publisher = new SendRtmpPublisher(reader, streamId, options.getBuffer(), false, false); logger.info("channelHash2:" + channel.hashCode()); channel.write(Command.publish(streamId, options)); } } /** * 配信停止処理 */ public void unpublish() { if(thread != null) { // 送信threadを殺すことで強制的にとめてしまう。 thread.interrupt(); } } /** * コマンド発行処理 * @param channel * @param command */ private void writeCommandExpectingResult(Channel channel, Command command) { final int id = transactionId ++; command.setTransactionId(id); transactionToCommandMap.put(id, command.getName()); channel.write(command); } /** * 接続したときに接続メッセージをサーバーに送信する */ @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) { writeCommandExpectingResult(e.getChannel(), Command.connect(options)); } /** * チャンネルを閉じる処理 */ @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { super.channelClosed(ctx, e); if(publisher != null) { publisher.close(); publisher = null; } thread = null; // thread止めなくてOK?(現在動作しているthreadを保持しておくだけなので、別threadをたてているわけではない、よってinterrupt掛けるのがそもそもおかしい。) channel = null; } /** * メッセージ動作処理 */ @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent me) { channel = me.getChannel(); thread = Thread.currentThread(); if(publisher != null && publisher.handle(me)) { return; } if(!(me.getMessage() instanceof RtmpMessage)) { logger.warn("got non RtmpMessage."); logger.warn("class information:{}", me.getMessage().getClass()); return; } final RtmpMessage message = (RtmpMessage) me.getMessage(); switch(message.getHeader().getMessageType()) { case CONTROL: Control control = (Control)message; switch(control.getType()) { case STREAM_BEGIN: if(publisher != null && !publisher.isStarted()) { publisher.start(channel, options.getStart(), options.getLength(), new ChunkSize(4096)); return; } // TODO これは視聴側の命令かも if(streamId != 0) { channel.write(Control.setBuffer(streamId, options.getBuffer())); } break; default: break; } break; case COMMAND_AMF0: case COMMAND_AMF3: Command command = (Command)message; String name = command.getName(); logger.info("command name:" + name); if("_result".equals(name)) { String resultFor = transactionToCommandMap.get(command.getTransactionId()); if("connect".equals(resultFor)) { writeCommandExpectingResult(channel, Command.createStream()); } else if("createStream".equals(resultFor)) { streamId = ((Double) command.getArg(0)).intValue(); logger.info("streamId is confirmed:" + streamId); logger.info("channelHash:" + channel.hashCode()); // TODO このタイミングでpublishまで実行しておかないとだめだな。 publish(); } return; } else if("onStatus".equals(name)) { @SuppressWarnings("unchecked") final Map<String, Object> temp = (Map<String, Object>)command.getArg(0); final String code = (String)temp.get("code"); logger.info(code); if("NetStream.Publish.Start".equals(code)) { logger.info("publish"); if(publisher != null && !publisher.isStarted()) { publisher.start(channel, options.getStart(), options.getLength(), new ChunkSize(4096)); return; } } else if("NetStream.Unpublish.Success".equals(code)) { logger.info("unpublish"); ChannelFuture future = channel.write(Command.closeStream(streamId)); future.addListener(ChannelFutureListener.CLOSE); return; } } break; default: break; } // その他のメッセージは元のClientHandlerにゆだねる super.messageReceived(ctx, me); // publisherに実行させるなにかがあるなら、ここで実行 if(publisher != null && publisher.isStarted()) { publisher.fireNext(channel, 0); } } }