package nliveroid.nlr.main;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineException;
import org.jboss.netty.channel.ChannelState;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ChannelUpstreamHandler;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.DefaultChannelFuture;
import org.jboss.netty.channel.DefaultExceptionEvent;
import org.jboss.netty.channel.DownstreamChannelStateEvent;
import org.jboss.netty.channel.DownstreamMessageEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink;
import org.jboss.netty.channel.socket.nio.NioSocketChannel;
import org.jboss.netty.channel.socket.nio.SelectorUtil;
import org.jboss.netty.util.internal.ExecutorUtil;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import com.flazr.rtmp.LoopedReader;
import com.flazr.rtmp.RtmpDecoder;
import com.flazr.rtmp.RtmpEncoder;
import com.flazr.rtmp.RtmpMessage;
import com.flazr.rtmp.RtmpWriter;
import com.flazr.rtmp.client.ClientHandshakeHandler;
import com.flazr.rtmp.client.RtmpPublisher;
import com.flazr.rtmp.message.BytesRead;
import com.flazr.rtmp.message.ChunkSize;
import com.flazr.rtmp.message.Command;
import com.flazr.rtmp.message.Control;
import com.flazr.rtmp.message.MetadataAmf0;
import com.flazr.rtmp.message.SetPeerBw;
import com.flazr.rtmp.message.WindowAckSize;
import com.flazr.rtmp.reader.F4vReader;
import com.flazr.rtmp.reader.FlvReader;
import com.flazr.rtmp.reader.PictureReader;
import com.flazr.rtmp.reader.PreviewReader;
import com.flazr.rtmp.reader.RtmpReader;
import com.flazr.rtmp.reader.SnapReader;
import com.flazr.util.ChannelUtils;
import com.flazr.util.Utils;
public class ClientHandler implements ChannelUpstreamHandler{
private LiveSettings liveSetting;
private BCPlayer ACT;
private NioSocketChannel ch;
private int transactionId = 1;
private Map<Integer, String> transactionToCommandMap;
private byte[] swfvBytes;
private RtmpWriter writer;
private int bytesReadWindow = 2500000;
private long bytesRead;
private long bytesReadLastSent;
private int bytesWrittenWindow = 2500000;
private int streamId;
private RtmpPublisher publisher;
private Executor bossExecutor;
private Executor workerExecutor;
private NioClientSocketPipelineSink sink;
private ClientHandshakeHandler handshaker;
private RtmpDecoder decoder;
private RtmpEncoder encoder;
public static SocketAddress REMOTE_ADDR;
private PublishTask task;
private RtmpReader reader;
private boolean isUnpublished;
public ClientHandler(BCPlayer ACT,LiveSettings liveSetting){
this.ACT = ACT;
this.liveSetting = liveSetting;
}
public RtmpReader getReader(){
return reader;
}
class PublishTask extends AsyncTask<Void,Void,Integer>{
private ChannelFuture future;
@Override
protected Integer doInBackground(Void... arg0) {
int settingsError = prepareSettings();
if(settingsError < 0)return settingsError;
Log.d("ClientHandler","Start Stream ---");
try{
int connectionError = connectStream();
if(connectionError < 0)return connectionError;
startDataSend(getSink(),ch,future);
future.getChannel().getCloseFuture().awaitUninterruptibly();//切断待ち
releaseExternalResources();
return 0;
}catch(IllegalStateException e){
e.printStackTrace();
}catch(ClassCastException e){
e.printStackTrace();
}catch(ChannelPipelineException e){
e.printStackTrace();
}
return -6;
}
private int prepareSettings(){
final int count = liveSetting.getLoad();
if(count != 1)return -8;
if ("sdk".equals(Build.PRODUCT)) {
java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");// エミュレータの場合はIPv6を無効 ----(エミュもWi-Fiのホスト名でいける)
java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
}
Executor executor = Executors.newCachedThreadPool();
if (executor == null)return -7;
bossExecutor = executor;
workerExecutor = executor;
transactionToCommandMap = new HashMap<Integer, String>();
return 0;
}
private int connectStream(){
handshaker = new ClientHandshakeHandler(liveSetting,ClientHandler.this);
decoder = new RtmpDecoder(ClientHandler.this);
encoder = new RtmpEncoder();
//DEFAULT_BOSS_COUNT(1),SelectorUtil.DEFAULT_ID_THREADS(Runtime.getRuntime().availableProcessors() * 2)
sink = new NioClientSocketPipelineSink(
bossExecutor, workerExecutor, 1, SelectorUtil.DEFAULT_IO_THREADS,handshaker,decoder,encoder,ClientHandler.this,publisher);
handshaker.setSink(sink);
encoder.setSink(sink);
ch = new NioSocketChannel(ClientHandler.this,sink, sink.nextWorker());
Map<String, Object> options = new HashMap<String, Object>();
options.put("tcpNoDelay" , true);
options.put("keepAlive", true);
ch.getConfig().setOptions(new TreeMap<String, Object>(options));
//remoteAddressに接続
future = new DefaultChannelFuture(ch,true);
InetSocketAddress remoteAddress = new InetSocketAddress(liveSetting.getHost(), liveSetting.getPort());
getSink().connect(ch, future, remoteAddress);
if(future == null)return -4;//ARMじゃない(複数あり)
future.awaitUninterruptibly();//接続試行結果を待つ
Log.d("ClientHandler","AWAIT ------------ " + future.isSuccess());
if (future.isSuccess()) {
return 0;
}else{//接続失敗
future.getCause().printStackTrace();
final String msg = future.getCause().getMessage();
if(msg == null){
return -2;
}else if(msg.equals("connection timed out")){
return -3;
}else if(msg.equals("No route to host")){
return -4;
}else if(msg.equals("Broken pipe")){
return -5;
}else{
return -99;
}
}
}
/**
* データ送信開始
* まず、handshakerから
*
*/
private void startDataSend(NioClientSocketPipelineSink sink, NioSocketChannel nioSockCH,ChannelFuture future){
Log.d("ClientHandler","StartDataSend ------ ");
try {
REMOTE_ADDR = nioSockCH.getRemoteAddress();
sink.offerWrite(new DownstreamMessageEvent(nioSockCH, future, handshaker.getHandshake().encodeClient0(), nioSockCH.getRemoteAddress()));
sink.offerWrite(new DownstreamMessageEvent(nioSockCH, future, handshaker.getHandshake().encodeClient1(), nioSockCH.getRemoteAddress()));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onPostExecute(Integer arg){
if(arg < 0)liveSetting.setStreamStarted(false);
switch(arg){
case -1:
Toast.makeText(ACT, "ストリームロジック確立に失敗",Toast.LENGTH_LONG).show();
break;
case -2:
// Toast.makeText(ACT,"不明のエラー" , Toast.LENGTH_LONG).show();
break;
case -3:
Toast.makeText(ACT,"ストリーム接続がタイムアウトしました" , Toast.LENGTH_LONG).show();
break;
case -4:
Toast.makeText(ACT,"接続先がみつかりませんでした" , Toast.LENGTH_LONG).show();
break;
case -5:
Toast.makeText(ACT,"十分な帯域が無く、切断されました" , Toast.LENGTH_LONG).show();
break;
case -6:
Toast.makeText(ACT,"初期化処理失敗又は、ARMアーキテクチャでない" , Toast.LENGTH_LONG).show();
break;
case -7:
Toast.makeText(ACT,"スレッド初期化時に致命的エラー" , Toast.LENGTH_LONG).show();
break;
case -8:
Toast.makeText(ACT,"設定値不正エラー" , Toast.LENGTH_LONG).show();
break;
case -99:
Toast.makeText(ACT,"接続絡みで不明のエラー" , Toast.LENGTH_LONG).show();
break;
}
}
/**
* sinkを取得します。
* @return sink
*/
public NioClientSocketPipelineSink getSink() {
return sink;
}
public void releaseExternalResources() {
ExecutorUtil.terminate(bossExecutor, workerExecutor);
}
}//End of PublishTask
public void setSwfvBytes(byte[] swfvBytes) {
this.swfvBytes = swfvBytes;
Log.d("setSwfvBytes", Utils.toHex(swfvBytes,0,swfvBytes.length,false));
}
private void writeCommandExpectingResult(NioSocketChannel channel, Command command) {
final int id = transactionId++;
command.setTransactionId(id);
transactionToCommandMap.put(id, command.getName());//ChannelState.CONNECTEDなどを書き込み要求に追加する
Log.d("ClientHandler","SEND COMMAND "+ command);
ChannelFuture future = new DefaultChannelFuture(channel, false);
try {
sink.eventSunkMessageEvent(new DownstreamMessageEvent( channel,future, encoder.encode(command), channel.getRemoteAddress()));
} catch (Exception e1) {
e1.printStackTrace();
}
}
public void channelConnected( ChannelStateEvent e) {
Log.d("ClientHandler","channelConnected "+e.getValue());
Channels.setStreamPhase(1);//次のフェーズにする
writeCommandExpectingResult(e.getChannel(), Command.connect(liveSetting));
}
@Override
public void handleUpstream(
ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
Log.d("ClientHandler handleUpstream","ctx "+ctx.getName());
}
public void messageReceived(final MessageEvent me) {
Log.d("ClientHandler","messageReceived "+me);
if(publisher != null && publisher.isStarted()) {
Log.d("ClientHandler","startWriteLoop");
// publisher.startWriteLoop(me.getChannel());
// return;
}
final NioSocketChannel localChannel = me.getChannel();
final RtmpMessage message = (RtmpMessage) me.getMessage();
switch(message.getHeader().getMessageType()) {
case CHUNK_SIZE: // handled by decoder
break;
case CONTROL:
Control control = (Control) message;
Log.d("ClientHandler","Control "+control);
switch(control.getType()) {
case PING_REQUEST:
final int time = control.getTime();
Log.d("ClientHandler PING_REQUEST"," "+ time);
Control pong = Control.pingResponse(time);
Log.d("ClientHandler PING_REQUEST response"," "+ pong);
ChannelFuture pingFuture = new DefaultChannelFuture(localChannel, false);
try {
sink.eventSunkMessageEvent(new DownstreamMessageEvent(localChannel, pingFuture, encoder.encode(pong), null));
} catch (Exception e1) {//エラー時何もしてないが。。
e1.printStackTrace();
}
break;
case SWFV_REQUEST:
if(swfvBytes == null) {
Log.d("swf verification not initialized!"
, " not sending response, server likely to stop responding / disconnect");
} else {
Control swfv = Control.swfvResponse(swfvBytes);
Log.d("sending swf verification response","swfv "+ swfv);
ChannelFuture swfvFuture = new DefaultChannelFuture(localChannel, false);
try {
sink.eventSunkMessageEvent(new DownstreamMessageEvent(localChannel, swfvFuture, encoder.encode((RtmpMessage) swfv), null));
} catch (Exception e1) {
e1.printStackTrace();
}//エラー時何もしてないが。。
}
break;
case STREAM_BEGIN:
if(streamId !=0) {
Log.d("Buffer ------------- "," " + liveSetting.getBuffer());
ChannelFuture beginFuture = new DefaultChannelFuture(localChannel, false);
try {
sink.eventSunkMessageEvent(new DownstreamMessageEvent(localChannel, beginFuture, encoder.encode(Control.setBuffer(streamId, liveSetting.getBuffer())), null));
} catch (Exception e1) {
e1.printStackTrace();
}//エラー時何もしてないが。。
}
break;
default:
Log.d("ignoring control message", " "+control);
}
break;
case METADATA_AMF0:
case METADATA_AMF3:
Log.d("ClientHandler "," METADATA_AMF3");
MetadataAmf0 metadata = (MetadataAmf0) message;
if(metadata.getName().equals("onMetaData")) {
Log.d("writing 'onMetaData'"," "+ metadata);
writer.write(message);
} else {
Log.d("ignoring metadata: "," "+ metadata);
}
break;
case AUDIO:
case VIDEO:
case AGGREGATE:
Log.d("ClientHandler "," AGGREGATE");
writer.write(message);
bytesRead += message.getHeader().getSize();
if((bytesRead - bytesReadLastSent) > bytesReadWindow) {
Log.d("ClientHandler AGGREGATE"," bytes read ack "+bytesRead);
bytesReadLastSent = bytesRead;
localChannel.write(new BytesRead(bytesRead),null);
}
break;
case COMMAND_AMF0:
case COMMAND_AMF3:
Command command = (Command) message;
String name = command.getName();
Log.d("COMMAND_AMF0/3 ", "name:"+name);
if(name.equals("_result")) {//ストリームの開始
String resultFor = transactionToCommandMap.get(command.getTransactionId());
Log.d("COMMAND_AMF3 messageReceived ", " "+resultFor);
if(resultFor.equals("connect")) {
writeCommandExpectingResult(localChannel, Command.createStream());
} else if(resultFor.equals("createStream")) {
streamId = ((Double) command.getArg(0)).intValue();
Log.d("STREAM ID :", "["+streamId+"]");
reader = null;
if(liveSetting.getMode() == 0){//カメラモードを開始する
Log.d("ClientHandler","MODE= CAM READER");
if(reader == null){
reader = new PreviewReader(ACT,liveSetting);
}
if(!((PreviewReader)reader).isInited()){
int val = reader.init(null);
Log.d("ClientHandler","CamPreviewReader" + val);
if(val < 0)showNofileToast(val);
}
}else if(liveSetting.getMode() == 1){//スナップモードを開始する
Log.d("ClientHandler","MODE= CAM READER");
reader = new SnapReader(liveSetting);
int val = reader.init(null);
Log.d("ClientHandler","SnapReader" + val);
if(val < 0)showNofileToast(-2);
}else if(liveSetting.getMode() == 2){//静止画モードを開始する
Log.d("ClientHandler","MODE= PIC READER ");
//画像をエンコ済みの再接続の場合はnewしない
reader = new PictureReader(ACT, liveSetting);
int val = reader.init(null);//画像でかすぎとメッセージ分岐したい
Log.d("ClientHandler","PictureReader" + val);
if(val == -1)exceptionCaught(new DefaultExceptionEvent(localChannel,new Throwable("画像リーダの初期化に失敗")));
}else if(liveSetting.getMode() == 3){//動画再生モード
if(liveSetting.getFilePath() == null || liveSetting.getFilePath().equals("")){
showNofileToast(-2);
return;
}
if(liveSetting.getFilePath().toLowerCase().endsWith(".flv")) {
Log.d("ClientHandler","MODE= FLV READER");
reader = new FlvReader(liveSetting);
int val =reader.init(liveSetting.getFilePath());
if(val == -10){
ACT.runOnUiThread(new Runnable(){
public void run(){
MyToast.customToastShow(ACT, "ファイルが不正でした");
}
});
return ;
}else if(val < 0){
showNofileToast(-2);
return;
}
}else if(liveSetting.getFilePath().toLowerCase().startsWith("mp4:")) {
Log.d("ClientHandler","MODE= FL4V1 READER");
reader = new F4vReader();
int val = reader.init(null);
Log.d("ClientHandler","ReaderINIT" + val);
if(val < 0){
showNofileToast(-2);
return;
}
} else if (liveSetting.getFilePath().toLowerCase().endsWith(".f4v")) {
Log.d("ClientHandler","MODE= F4V2 READER");
reader = new F4vReader();
int val = reader.init(liveSetting.getFilePath());
if(val < 0){
showNofileToast(-2);
return;
}
}
}else{//パブリッシュモードが無い場合エラーに変更
Log.d("ClientHandler","NO PUBLISH MODE=(FilePath == null && !isUseCamera)");
this.exceptionCaught(new DefaultExceptionEvent(localChannel, new Exception("NO PUBLISH MODE")));
}
if(liveSetting.getLoopCount() > 1) {
reader = new LoopedReader(reader, liveSetting.getLoopCount());
}
publisher = new RtmpPublisher(reader,sink, streamId, liveSetting.getBuffer(), false, false, encoder);
localChannel.setPublisher(publisher);
try {
sink.eventSunkMessageEvent(new DownstreamMessageEvent(localChannel,new DefaultChannelFuture(localChannel, false), encoder.encode(Command.publish(streamId, liveSetting)), localChannel.getRemoteAddress()));
} catch (Exception e1) {
e1.printStackTrace();
}//エラー時何もしてないが。。
return;
} else {
Log.d("un-handled server result for", resultFor);
}
} else if(name.equals("onStatus")) {
final Map<String, Object> temp = (Map) command.getArg(0);
final String code = (String) temp.get("code");
Log.d("ClientHandler","onStatus code:"+code);
if (code.equals("NetStream.Failed") // TODO cleanup
|| code.equals("NetStream.Play.Failed")
|| code.equals("NetStream.Play.Stop")
|| code.equals("NetStream.Play.StreamNotFound")) {
Log.d("ClientHandler","disconnecting");
try {
sink.eventSunkStateEvent(new DownstreamChannelStateEvent(
localChannel, localChannel.getCloseFuture(), ChannelState.OPEN, Boolean.FALSE) );
} catch (Exception e) {
e.printStackTrace();
}
return;
}
if(code.equals("NetStream.Publish.Start")
&& publisher != null && !publisher.isStarted()) {
publisher.start(this,localChannel, liveSetting.getStart(),
liveSetting.getLength(), new ChunkSize(4096));
return;
}
if (publisher != null && code.equals("NetStream.Unpublish.Success")) {
Log.d("ClientHandler","unpublish");
ChannelFuture unpublishFuture = new DefaultChannelFuture(localChannel, false);
try {
sink.eventSunkMessageEvent(new DownstreamMessageEvent(localChannel, unpublishFuture, encoder.encode(Command.closeStream(streamId)), null));
} catch (Exception e1) {
e1.printStackTrace();
}//エラー時未チェック
unpublishFuture.addListener(ChannelFutureListener.CLOSE);
isUnpublished = true;
return;
}
} else if(name.equals("close")) {
Log.d("server called close, closing channel"," ");
localChannel.close();
return;
} else if(name.equals("_error")) {
Log.d("ERROR - ","closing channel server resonded with error"+ command);
localChannel.close();
return;
} else {
Log.d("ignoring server command", " "+command);
}
break;
case BYTES_READ://両サイドで x byte 読み込むごとに送信
message.decode(message.encode());
Log.d("ClientHandler","ack from serverBYTES_READ "+message.getHeader().getSize());
Log.d("ClientHandler","ack from serverBYTES_READ "+message);
Log.d("ClientHandler","ack from serverBYTES_READ "+message.encode());
byte[] d = message.encode().array();
Log.d("ClientHandler","ack from serverBYTES_READ "+Utils.toHex(d,0,d.length,true));
// int aggregateDuration = (d[0] )
int data = (d[0]<<24) | (d[1]<<16 & 0x00FF0000) | (d[2]<<8 & 0x0000FF00)| (d[3] & 0x000000FF);
Log.d("ClientHandler","BYTES_READ" + data);
if(liveSetting.getMode() == 3){
Log.d("ClientHandler","DataDuration " + this.publisher.getDataDuration());
Log.d("ClientHandler","DIFF " + (publisher.getDataDuration()-data));
// publisher.setAggregateDuration(data);
}
break;
case WINDOW_ACK_SIZE:
Log.d("ClientHandler","WINDOW_ACK_SIZE ");
WindowAckSize was = (WindowAckSize) message;
if(was.getValue() != bytesReadWindow) {
localChannel.write(SetPeerBw.dynamic(bytesReadWindow),null);
}
break;
case SET_PEER_BW:
Log.d("ClientHandler","SET_PEER_BW ");
SetPeerBw spb = (SetPeerBw) message;
if(spb.getValue() != bytesWrittenWindow) {
localChannel.write(new WindowAckSize(bytesWrittenWindow),null);
}
break;
default:
Log.d("ClientHandler","ignore "+message.toString());
}
if(publisher != null && publisher.isStarted()) { // TODO better state machine
// publisher.fireNext(channel, 0);
}
}//End of messageReveived
public void exceptionCaught(final ExceptionEvent e) {
ChannelUtils.exceptionCaught(e);
new AsyncTask<Void,Void,Void>(){
@Override
protected Void doInBackground(Void... arg0) {
ACT.stopStream();
return null;
}
@Override
protected void onPostExecute(Void arg){
Toast.makeText(ACT, e.getCause().toString(), Toast.LENGTH_LONG).show();
}
}.execute();
}
private void showNofileToast(final int val) {
ACT.runOnUiThread(new Runnable(){
@Override
public void run(){
Builder alert = new AlertDialog.Builder(ACT);
switch(val){
case -1:
break;
case -2:
alert.setMessage("ファイルが見つかりませんでした");
break;
}
alert.create().show();
}
});
}
public void startPublish() {
Log.d("ClientHandler","startPublish");
Channels.setStreamPhase(0);
if(task != null && task.getStatus() != AsyncTask.Status.FINISHED){
task.cancel(true);
}
task = new PublishTask();
task.execute();
}
public void stopStream() {
Log.d("ClientHandler","StopStream");
liveSetting.setStreamStarted(false);
if(publisher != null)publisher.close(ch);
}
public void stopStream(NioSocketChannel channel) {
Log.d("ClientHandler","StopStream from NioWorker ---- ");
liveSetting.setStreamStarted(false);
if(publisher != null)publisher.close(channel);
}
public boolean getUnpublish() {
if(isUnpublished){
isUnpublished = false;
return true;
}
return false;
}
public void setReader(RtmpReader reader2) {
this.reader = reader2;
}
}