package jp.sourceforge.nicoro;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import static jp.sourceforge.nicoro.NicoroFFmpegPlayer.CODE_DECODE_FRAME_INVALID;
import static jp.sourceforge.nicoro.NicoroFFmpegPlayer.CODE_DECODE_FRAME_VIDEO;
import static jp.sourceforge.nicoro.NicoroFFmpegPlayer.CODE_DECODE_FRAME_AUDIO;
import static jp.sourceforge.nicoro.NicoroFFmpegPlayer.CODE_DECODE_FRAME_VIDEO_SKIP;
import static jp.sourceforge.nicoro.NicoroFFmpegPlayer.CODE_DECODE_FRAME_SKIP;
import static jp.sourceforge.nicoro.NicoroFFmpegPlayer.CODE_DECODE_FRAME_ERROR_OR_END;
import jp.sourceforge.nicoro.swf.ADPCMSOUNDDATA;
import jp.sourceforge.nicoro.swf.DefineShape3;
import jp.sourceforge.nicoro.swf.DefineShapeBase;
import jp.sourceforge.nicoro.swf.ENDSHAPERECORD;
import jp.sourceforge.nicoro.swf.FILLSTYLE;
import jp.sourceforge.nicoro.swf.FILLSTYLEARRAY;
import jp.sourceforge.nicoro.swf.LINESTYLE;
import jp.sourceforge.nicoro.swf.LINESTYLEARRAY;
import jp.sourceforge.nicoro.swf.MP3STREAMSOUNDDATA;
import jp.sourceforge.nicoro.swf.PlaceObject2;
import jp.sourceforge.nicoro.swf.PlaceObjectBase;
import jp.sourceforge.nicoro.swf.SHAPERECORD;
import jp.sourceforge.nicoro.swf.STRAIGHTEDGERECORD;
import jp.sourceforge.nicoro.swf.STYLECHANGERECORD;
import jp.sourceforge.nicoro.swf.SoundStreamHead;
import jp.sourceforge.nicoro.swf.StreamSoundDataBase;
import jp.sourceforge.nicoro.swf.SwfPlayer;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Shader;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageView;

public class NicoroSwfPlayer extends AbstractNicoroPlayer {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG && false;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;

	private static final int MSG_ID_VIDEO_DOWNLOAD_FINISHED = MSG_ID_SUB_OFFSET + 0;
	private static final int MSG_ID_SURFACE_CREATED = MSG_ID_SUB_OFFSET + 1;
	private static final int MSG_ID_SURFACE_DESTROYED = MSG_ID_SUB_OFFSET + 2;
	private static final int MSG_ID_SWF_PREPARED = MSG_ID_SUB_OFFSET + 3;
	
	private static String FWS_TEMP_FILE = "swffwstemp.dat";
	private static String AUDIO_TEMP_FILE = "swfaudiotemp.dat";
	
	private SurfaceView mSurfaceView;
	private SurfaceHolder mSurfaceHolder;
//	private ImageView mIconPause;
	/*private*/ volatile boolean mIsFinish;

	private int mWidthSurface = 0;
	private int mHeightSurface = 0;

    private int mDrawOffsetY = 0;
	
	private boolean mIsSurfaceOk;
	private boolean mIsVideoDownloadOk;
	private boolean mIsPrepareSwfOk;

    private android.graphics.Matrix mMatrixScale = new android.graphics.Matrix();
	
    private volatile boolean mIsPlaying = false;
	
    private boolean mIsPause = false;

	private int mWidth = 0;
	private int mHeight = 0;
	
	private RandomAccessFile mInSwf = null;
	private SwfPlayer mSwfPlayer = new SwfPlayer();
	private Thread mThreadSwf = null;
	private Thread mThreadPrepareSwf = null;
	
	private File mSwfFile = null;
	
	private long mStartFrameTime;
	private boolean mIsFirstShowFrame;
	private Rational mRationalVideoPlay = new Rational();
	
	private Rational mRationalDebugLog = (DEBUG_LOGV || DEBUG_LOGD) ? new Rational() : null;
	
	private boolean mUseMediaPlayerForAudio;
	
	private volatile int mRequestSeekSecond = -1;
	private volatile boolean mRequestSeek = false;
	
    private class SwfThread implements Runnable {
		@Override
		public void run() {
			try {
				mSwfPlayer.runMainLoop(mInSwf);
			} catch (IOException e) {
				Log.e(LOG_TAG, e.getMessage(), e);
				if (mHandler != null) {
					Message message = mHandler.obtainMessage(
							MSG_ID_PLAY_ERROR,
							getString(R.string.errormessage_player_io));
					mHandler.sendMessage(message);
				}
			} catch (OutOfMemoryError e) {
				Log.e(LOG_TAG, e.getMessage(), e);
				if (mHandler != null) {
					Message message = mHandler.obtainMessage(
							MSG_ID_PLAY_ERROR,
							getString(R.string.errormessage_player_outofmemory));
					mHandler.sendMessage(message);
				}
			} catch (FailAnalyzeSwfException e) {
				Log.e(LOG_TAG, e.getMessage(), e);
				if (mHandler != null) {
					Message message = mHandler.obtainMessage(
							MSG_ID_PLAY_ERROR,
							getString(R.string.errormessage_player_analyzeswf));
					mHandler.sendMessage(message);
				}
			}
		}
    }
    
    private class PrepareSwfThread implements Runnable {
		@Override
		public void run() {
			if (!VideoLoader.isStreamTempDirWritable()) {
				if (mHandler != null) {
					Message message = mHandler.obtainMessage(
							MSG_ID_PLAY_ERROR,
							getString(R.string.errormessage_player_strage_unmount));
					mHandler.sendMessage(message);
				}
				return;
			}
			
    		try {
		    	createAudioPlayerIfNeeded();
	    		String file = mVideoLoader.getFilePath();
	    		
	    		File outTempFile = new File(VideoLoader.STREAM_TEMP_DIR, FWS_TEMP_FILE);
	    		File inFile = new File(file);
				if (SwfPlayer.getFWSFile(inFile, outTempFile)) {
					inFile = outTempFile;
				}
				mSwfFile = inFile;
	        	mAudioPlayer.start(inFile);
	        	
				if (mHandler != null) {
					mHandler.sendEmptyMessage(MSG_ID_SWF_PREPARED);
				}
			} catch (IOException e) {
				Log.e(LOG_TAG, e.getMessage(), e);
				if (mHandler != null) {
					Message message = mHandler.obtainMessage(
							MSG_ID_PLAY_ERROR,
							getString(R.string.errormessage_player_io));
					mHandler.sendMessage(message);
				}
			} catch (FailAnalyzeSwfException e) {
				Log.e(LOG_TAG, e.getMessage(), e);
				if (mHandler != null) {
					Message message = mHandler.obtainMessage(
							MSG_ID_PLAY_ERROR,
							getString(R.string.errormessage_player_analyzeswf));
					mHandler.sendMessage(message);
				}
			}
		}
    }
	
	public NicoroSwfPlayer() {
    	mHandler = new MessageHandler() {
    		@Override
    		public void handleMessage(Message msg) {
    			if (mHandler == null) {
    				if (DEBUG_LOGD) {
    					Log.d(LOG_TAG, Log.buf().append("Activity was destroyed. ignore message=")
    							.append(msg.toString()).toString());
    				}
    				return;
    			}
    			switch (msg.what) {
				case MSG_ID_VIDEO_DOWNLOAD_FINISHED:
					mIsVideoDownloadOk = true;
					mThreadPrepareSwf = new Thread(new PrepareSwfThread());
					mThreadPrepareSwf.start();
					if (canStartPlay()) {
						startPlay();
					}
					break;
    			case MSG_ID_SURFACE_CREATED:
    				mIsSurfaceOk = true;
    				if (canStartPlay()) {
    					startPlay();
    				}
    				break;
    			case MSG_ID_SURFACE_DESTROYED:
    				mIsSurfaceOk = false;
    				break;
    			case MSG_ID_SWF_PREPARED:
    				mIsPrepareSwfOk = true;
    				if (canStartPlay()) {
    					startPlay();
    				}
    				break;
    			default:
    				super.handleMessage(msg);
    				break;
    			}
    		}
    	};
	}
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        Intent intent = getIntent();
        mVideoLoader = createVideoLoader(intent, getApplicationContext());
        if (mVideoLoader != null) {
        	mVideoLoader.setEventListener(new VideoLoader.EventListener() {
				@Override
				public void onCached(VideoLoader videoLoader) {
					// nothing
				}
				@Override
				public void onFinished(VideoLoader videoLoader) {
					if (mHandler != null) {
						mHandler.sendEmptyMessage(MSG_ID_VIDEO_DOWNLOAD_FINISHED);
					}
				}
				@Override
				public void onOccurredError(VideoLoader videoLoader, String errorMessage) {
					if (mHandler != null) {
						Message message = mHandler.obtainMessage(MSG_ID_VIDEO_OCCURRED_ERROR, errorMessage);
						mHandler.sendMessage(message);
					}
				}
				@Override
				public void onNotifyProgress(int num, int den) {
					if (mHandler != null) {
						if (!mIsPlaying) {
							mHandler.removeMessages(MSG_ID_VIDEO_NOTIFY_PROGRESS);
							Message message = mHandler.obtainMessage(MSG_ID_VIDEO_NOTIFY_PROGRESS, num, den);
							mHandler.sendMessage(message);
						}
					}
				}
        	});
        	mVideoLoader.startLoad();
        }

        mIsSurfaceOk = false;
        mIsVideoDownloadOk = false;
        mIsPrepareSwfOk = false;
        
        setContentView(R.layout.nicoro_swfplayer);
        mSurfaceView = (SurfaceView) findViewById(R.id.surface);
		DisplayMetrics metrics = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics(metrics);
		mSurfaceView.getLayoutParams().width =
//			(int) (metrics.heightPixels * 4 / 3 / metrics.density);
			(int) (metrics.heightPixels * 4 / 3);
		mSurfaceView.getLayoutParams().height =
//			(int) (metrics.heightPixels / metrics.density);
			(int) (metrics.heightPixels);
		mSurfaceView.requestLayout();
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
			@Override
			public void surfaceChanged(SurfaceHolder holder, int format,
					int width, int height) {
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, Log.buf().append("surfaceChanged: format=")
							.append(format).append(" width=").append(width)
							.append(" height=").append(height).toString());
				}
				mWidthSurface = width;
				mHeightSurface = height;
				setScaleMatrix();
			}
			@Override
			public void surfaceCreated(SurfaceHolder holder) {
				if (mHandler != null) {
					mHandler.sendEmptyMessage(MSG_ID_SURFACE_CREATED);
				}
			}
			@Override
			public void surfaceDestroyed(SurfaceHolder holder) {
				if (mHandler != null) {
					mHandler.sendEmptyMessage(MSG_ID_SURFACE_DESTROYED);
				}
				mIsFinish = true;
//				mStreamAudioPlayer.finish();
			}
        });
        
//        mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
//        	private int mLastPlaybackHeadPosition;
//        	
//			@Override
//			public boolean onTouch(View v, MotionEvent event) {
//				if (event.getAction() == MotionEvent.ACTION_DOWN) {
//					return switchPause();
//				}
//				return false;
//			}
//		});
		
//		mIconPause = (ImageView) findViewById(R.id.icon_pause);
//		mIconPause.setAlpha(192);
		
    	SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
		mUseMediaPlayerForAudio = sharedPreferences.getBoolean(
				getString(R.string.pref_key_swf_mediaplayer), true);
		mSwfPlayer.create(this);
		
		initializeView();

		mIsPlaying = false;
		mIsPause = false;
    }
    
    @Override
    protected void onDestroy() {
    	mIsFinish = true;
		if (mThreadPrepareSwf != null) {
	    	try {
	    		mThreadPrepareSwf.join(1000L * 2);
			} catch (InterruptedException e) {
	    		Log.d(LOG_TAG, e.getMessage(), e);
			}
			mThreadPrepareSwf = null;
		}
    	if (mThreadSwf != null) {
	    	try {
	    		mThreadSwf.join(1000L * 2);
			} catch (InterruptedException e) {
	    		Log.d(LOG_TAG, e.getMessage(), e);
			}
			mThreadSwf = null;
    	}
    	mAudioPlayer.finish();
    	super.onDestroy();
    	if (mInSwf != null) {
    		try {
				mInSwf.close();
			} catch (IOException e) {
				Log.e(LOG_TAG, e.getMessage(), e);
			}
			mInSwf = null;
    	}
    	
//    	// test
//    	if (mTestOutMP3 != null) {
//    		try {
//				mTestOutMP3.close();
//			} catch (IOException e) {
//				// TODO 自動生成された catch ブロック
//				e.printStackTrace();
//			}
//    		mTestOutMP3 = null;
//    	}
	}
    
    @Override
    protected void finalize() throws Throwable {
    	try {
    		super.finalize();
    	} finally {
        	mIsFinish = true;
        	try {
	    		if (mThreadPrepareSwf != null) {
	    			mThreadPrepareSwf.join(1000L * 2);
	    		}
    		} catch (InterruptedException e) {
        		Log.d(LOG_TAG, e.getMessage(), e);
    		}
        	try {
        		if (mThreadSwf != null) {
        			mThreadSwf.join(1000L * 2);
        		}
    		} catch (InterruptedException e) {
        		Log.d(LOG_TAG, e.getMessage(), e);
    		}
    	}
    }
	
	@Override
	protected StringBuilder appendCurrentPlayTime(StringBuilder builder) {
		if (mSwfPlayer.mHeaderMovieSwf == null) {
			return builder.append(INFO_TIME_DEFAULT);
		} else {
			if (DEBUG_LOGV) {
				Rational r = mRationalDebugLog;
				getCurrentPositionVideoPlay(r);
				if (r.den == 0) { r.den = 1; }
				Log.v(LOG_TAG, Log.buf().append("VideoPlayTime: ")
						.append(r.num).append("/").append(r.den).append(" ")
						.append(r.getDivideFloat()).toString());
				getCurrentPositionAudioPlay(r);
				if (r.den == 0) { r.den = 1; }
				Log.v(LOG_TAG, Log.buf().append("AudioPlayTime: ")
						.append(r.num).append("/").append(r.den).append(" ")
						.append(r.getDivideFloat()).toString());
				getCurrentPositionVideoDecode(r);
				if (r.den == 0) { r.den = 1; }
				Log.v(LOG_TAG, Log.buf().append("VideoDecodeTime: ")
						.append(r.num).append("/").append(r.den).append(" ")
						.append(r.getDivideFloat()).toString());
				getCurrentPositionAudioDecode(r);
				if (r.den == 0) { r.den = 1; }
				Log.v(LOG_TAG, Log.buf().append("AudioDecodeTime: ")
						.append(r.num).append("/").append(r.den).append(" ")
						.append(r.getDivideFloat()).toString());
			}
			
			getCurrentPositionVideoPlay(mRatinalCurrentPlayTime);
			final int posNum = mRatinalCurrentPlayTime.num;
			final int posDen = mRatinalCurrentPlayTime.den;
			return appendCurrentPlayTimeCommon(builder, posNum, posDen);
		}
	}

	@Override
	protected boolean canStartPlay() {
		return mIsSurfaceOk && mIsVideoDownloadOk && mMessageData.mIsMessageOk && mMessageDataFork.mIsMessageOk && mIsPrepareSwfOk;
	}

	@Override
    protected void startPlay() {
		super.startPlay();
    	System.gc();
    	
    	mSwfPlayer.preparePlay();
    	mIsFirstShowFrame = true;
    	mStartFrameTime = 0L;
    	
		mIsFinish = false;
    	
    	try {
        	assert mInSwf == null;
        	assert mSwfFile != null;
    		mInSwf = new RandomAccessFile(mSwfFile, "r");
    		
    		mSwfPlayer.readSWFFileHeaderWhole(mInSwf);
			
			assert mThreadSwf == null;
			mThreadSwf = new Thread(new SwfThread());
			mThreadSwf.start();
		} catch (IOException e) {
    		Log.e(LOG_TAG, e.getMessage(), e);
		} catch (FailAnalyzeSwfException e) {
    		Log.e(LOG_TAG, e.getMessage(), e);
		}
		
		mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
	}
	
	@Override
	protected void getCurrentPositionVideoPlay(Rational rational) {
		mSwfPlayer.getCurrentPosition(rational);
	}
	@Override
	protected void getCurrentPositionAudioPlay(Rational rational) {
		mAudioPlayer.getCurrentPositionAudioPlay(rational);
	}
	@Override
	protected void getCurrentPositionVideoDecode(Rational rational) {
		// Playと同様
		getCurrentPositionVideoPlay(rational);
	}
	@Override
	protected void getCurrentPositionAudioDecode(Rational rational) {
		mAudioPlayer.getCurrentPositionAudioDecode(rational);
	}
	
	@Override
	protected boolean switchPausePlay() {
		if (!mIsPause) {
			pausePlay();
			setButtonPauseImage();
			return (mIsPause != false);
		} else {
			restartPlay();
		}
		setButtonPauseImage();
		return true;
	}
	
	@Override
	protected void pausePlay() {
		if (mAudioPlayer.pause()) {
//			mIconPause.setVisibility(View.VISIBLE);
			mIsPause = true;
		}
	}
	
	@Override
	protected void restartPlay() {
		mAudioPlayer.restart();
//		mIconPause.setVisibility(View.INVISIBLE);
		updateStartFrameTime();
		mIsPause = false;
	}
	
	@Override
	protected boolean isPausePlay() {
		return mIsPause;
	}
	
	@Override
	protected void seekBySecond(int second) {
//		seekBySecondCommon(second);
		// TODO
		mAudioPlayer.clearBuffer();
		mRequestSeekSecond = second;
		mRequestSeek = true;
	}
	
	@Override
	protected StringBuilder appendVideoResolution(StringBuilder builder) {
		// swfは情報無しで
		return builder;
	}

	@Override
	protected StringBuilder appendPlayerInfo(StringBuilder builder) {
		return builder.append(getString(R.string.info_play_data_swf));
	}

	private int getFrameDurationMs() {
		return (1 << 8) * 1000 / ((mSwfPlayer.mHeaderMovieSwf.frameRateIntegral << 8) | mSwfPlayer.mHeaderMovieSwf.frameRateDecimal);
	}
	
	public boolean isFinish() {
		return mIsFinish;
	}
	
	public boolean isPause() {
		return mIsPause;
	}
	
	public boolean isSeeking() {
		return mRequestSeek;
	}
	
	public int getSeekTimeBySecond() {
		return mRequestSeekSecond;
	}
	
	public void notifySeekCompleted() {
		mAudioPlayer.seekBySecond(mRequestSeekSecond);
		updateStartFrameTime();
		mRequestSeekSecond = -1;
		mRequestSeek = false;
		
		// シークバー再有効化
		if (mHandler != null) {
			mHandler.sendEmptyMessage(MSG_ID_ENABLE_SEEK_BAR);
		}
	}
	
    private void setScaleMatrix() {
    	if (mMatrixScale == null) {
    		return;
    	}
    	mMatrixScale.reset();
    	if (mWidth == 0 || mHeight == 0 || mWidthSurface == 0 || mHeightSurface == 0) {
    		return;
    	}
    	float scaleWidth = (float) mWidthSurface / (float) mWidth;
    	float scaleHeight = (float) mHeightSurface / (float) mHeight;
    	float scale;
    	if (scaleWidth < scaleHeight) {
    		scale = scaleWidth;
        	mDrawOffsetY = (int) ((mHeightSurface / scale - mHeight) / 2);
    	} else {
    		scale = scaleHeight;
        	mDrawOffsetY = 0;
    	}
        mMatrixScale.postScale(scale, scale);
    }
    
    private void updateStartFrameTime() {
    	mStartFrameTime = SystemClock.elapsedRealtime();
    	// 既に進んでいるフレーム数ぶんだけ調整
		getCurrentPositionVideoPlay(mRationalVideoPlay);
    	final int posVideoMs = mRationalVideoPlay.getMs();
    	mStartFrameTime -= posVideoMs;
    }
    
    // SWFファイル解析
    
//    private void analyzeSwf() {
//    	assert mInSwf == null;
//    	InputStream inSwf = null;
//    	try {
//    		String file = mVideoLoader.getFilePath();
//    		inSwf = new FileInputStream(file);
//    		inSwf = new BufferedInputStream(inSwf);
//    		
//    		Header header = new Header();
//    		readHeader(inSwf, header);
//    		
//    		if (Arrays.equals(header.magic, Header.CWS)) {
////    			inSwf = new GZIPInputStream(inSwf);
//    			inSwf = new InflaterInputStream(inSwf, new Inflater(false));
//    		}
//    		
//    		HeaderMovie headerMovie = new HeaderMovie();
//    		readHeaderMovie(inSwf, headerMovie);
//    		
//    		while (true) {
//    			createTagBlock(inSwf);
//    		}
//    		
//    	} catch (FileNotFoundException e) {
//    		Log.e(LOG_TAG, e.getMessage(), e);
//		} catch (IOException e) {
//    		Log.e(LOG_TAG, e.getMessage(), e);
//		} catch (FailAnalyzeSwfException e) {
//    		Log.e(LOG_TAG, e.getMessage(), e);
//		} finally {
//    		if (inSwf != null) {
//    			try {
//    				inSwf.close();
//    			} catch (IOException e) {
//    	    		Log.e(LOG_TAG, e.getMessage(), e);
//    			}
//    		}
//    	}
//    	
//    }
    
//    private TagBlock createTagBlock(InputStream inSwf)
//    throws IOException, FailAnalyzeSwfException {
//    	byte[] taglength = new byte[2];
//    	int readSize = inSwf.read(taglength);
//    	if (readSize != taglength.length) {
//    		throw new FailAnalyzeSwfException("tag block tag & length read failed: " + readSize);
//    	}
//    	if (DEBUG_LOGD) {
//    		Log.d(LOG_TAG, "SWF tag block taglength=" + String.format("%02X,%02X", taglength[0], taglength[1]));
//    	}
//    	short tag = (short) (((taglength[1] << 2) | ((taglength[0] & 0xff) >>> 6)) & 0x3ff);
//    	byte length = (byte) (taglength[0] & 0x3f);
//    	if (DEBUG_LOGD) {
//    		Log.d(LOG_TAG, "SWF tag block tag=" + tag);
//    		Log.d(LOG_TAG, "SWF tag block length=" + length);
//    	}
//    	
//    	if (isTagBlockLong(tag, length)) {
//        	ByteBuffer lengthLongBuffer = ByteBuffer.allocate(4);
//        	lengthLongBuffer.order(ByteOrder.LITTLE_ENDIAN);
//        	readSize = inSwf.read(lengthLongBuffer.array());
//        	if (readSize != lengthLongBuffer.array().length) {
//        		throw new FailAnalyzeSwfException("tag block long length read failed: " + readSize);
//        	}
//        	int lengthLong = lengthLongBuffer.getInt();
//        	if (DEBUG_LOGD) {
//        		Log.d(LOG_TAG, "SWF tag block long length=" + lengthLong);
//        	}
//        	if (lengthLong > 0) {
//        		// TODO とりあえず実験でスキップ
//        		
////	    		byte[] contents = new byte[lengthLong];
////	    		readSize = inSwf.read(contents);
////	        	if (readSize != contents.length) {
////	        		throw new FailAnalyzeSwfException("tag block long contents read failed: " + readSize);
////	        	}
//        		
//	        	long skipSize = inSwf.skip(lengthLong);
//	        	if (skipSize != lengthLong) {
//	        		throw new FailAnalyzeSwfException("tag block long contents skip failed: " + skipSize);
//	        	}
//        	}
//    		
//    	} else {
//    		if (length > 0) {
//	    		byte[] contents = new byte[length];
//	    		readSize = inSwf.read(contents);
//	        	if (readSize != contents.length) {
//	        		throw new FailAnalyzeSwfException("tag block contents read failed: " + readSize);
//	        	}
//    		}
//    	}
//    	
//    	
//    	return null;
//    }
    
    public void showFrame() throws FailAnalyzeSwfException, IOException {
    	boolean frameSkip = false;
        if (!mIsFirstShowFrame) {
        	final long currentTime = SystemClock.elapsedRealtime();
        	assert currentTime >= mStartFrameTime;
        	
        	getCurrentPositionVideoPlay(mRationalVideoPlay);
        	final int posVideoMs = mRationalVideoPlay.getMs();
        	final int realPos = (int) (currentTime - mStartFrameTime);
        	final int diffPos = posVideoMs - realPos;
        	final int frameDuration = getFrameDurationMs();
        	assert posVideoMs >= 0;
        	assert realPos >= 0;
        	assert frameDuration > 0;
        	if (DEBUG_LOGV) {
        		Log.v(LOG_TAG, Log.buf().append("ShowFrame: PlayVideoTime=")
        				.append(posVideoMs).append(" RealTime=").append(realPos)
        				.append(" FrameDuration=").append(frameDuration).toString());
        	}
        	if (diffPos < -frameDuration) {
        		frameSkip = true;
        	} else if (diffPos > frameDuration) {
        		try {
					Thread.sleep(frameDuration);
				} catch (InterruptedException e) {
				}
        	}
        }
    	
        if (frameSkip) {
        	if (DEBUG_LOGD) {
        		Log.d(LOG_TAG, "frame skip");
        	}
        } else {
	    	Canvas canvas = mSurfaceHolder.lockCanvas();
	        if (canvas == null) {
	        	Log.d(LOG_TAG, "lockCanvas NG");
	        } else {
		        try {
		        	canvas.drawColor(mSwfPlayer.mBackgroundColor);
	    			Matrix matrixCanvas = new Matrix();
	    			float scaleX =
	    				(float) canvas.getWidth()
	    				/ (float) (mSwfPlayer.mHeaderMovieSwf.frameSize.xmax - mSwfPlayer.mHeaderMovieSwf.frameSize.xmin);
	    			float scaleY =
	    				(float) canvas.getHeight()
	    				/ (float) (mSwfPlayer.mHeaderMovieSwf.frameSize.ymax - mSwfPlayer.mHeaderMovieSwf.frameSize.ymin);
	    			matrixCanvas.setScale(scaleX, scaleY);
	    			matrixCanvas.postTranslate(
	    					-mSwfPlayer.mHeaderMovieSwf.frameSize.xmin,
	    					-mSwfPlayer.mHeaderMovieSwf.frameSize.ymin);
	    			if (DEBUG_LOGD) {
	    				Log.d(LOG_TAG, Log.buf().append("Canvas base Matrix: ")
	    						.append(matrixCanvas.toString()).toString());
	    			}
	    			
		    		for (PlaceObjectBase placeObject : mSwfPlayer.getDisplayListValues()) {
		    			
		    			Paint paint = new Paint();
		    			Matrix matrixPlaceObject = new Matrix();
		    			
		    			if (placeObject instanceof PlaceObject2) {
		    				PlaceObject2 placeObject2 = (PlaceObject2) placeObject;
		    				if (placeObject2.placeFlagHasClipActions) {
		    					// TODO
		    					throw new FailAnalyzeSwfException("TODO: PlaceObject2 PlaceFlagHasClipActions is umimplemented.");
		    				}
		    				if (placeObject2.placeFlagHasClipDepth) {
		    					// TODO
		    					throw new FailAnalyzeSwfException("TODO: PlaceObject2 PlaceFlagHasClipDepth is umimplemented.");
		    				}
		    				if (placeObject2.placeFlagHasName) {
		    					// TODO
		    					throw new FailAnalyzeSwfException("TODO: PlaceObject2 PlaceFlagHasName is umimplemented.");
		    				}
		    				if (placeObject2.placeFlagHasRatio) {
		    					// TODO
		    					throw new FailAnalyzeSwfException("TODO: PlaceObject2 PlaceFlagHasRatio is umimplemented.");
		    				}
		    				if (placeObject2.placeFlagHasColorTransform) {
		    					assert placeObject2.colorTransform != null;
		    					
		    					// 乗算→加算の順序
		    					// ColorMatrixColorFilterがそのまま使える？
		    					
		    					ColorMatrix colorMatrix = new ColorMatrix();
		    					if (placeObject2.colorTransform.hasMultTerms) {
		    						colorMatrix.setScale(
		    								(float) placeObject2.colorTransform.redMultTerm / 256.0f,
		    								(float) placeObject2.colorTransform.greenMultTerm / 256.0f,
		    								(float) placeObject2.colorTransform.blueMultTerm / 256.0f,
		    								(float) placeObject2.colorTransform.alphaMultTerm / 256.0f);
		    					}
		    					if (placeObject2.colorTransform.hasAddTerms) {
		    						float[] array = colorMatrix.getArray();
		    						array[4] = (float) placeObject2.colorTransform.redAddTerm / 256.0f;
		    						array[9] = (float) placeObject2.colorTransform.greenAddTerm / 256.0f;
		    						array[14] = (float) placeObject2.colorTransform.blueAddTerm / 256.0f;
		    						array[19] = (float) placeObject2.colorTransform.alphaAddTerm / 256.0f;
		    					}
		    					
		    					ColorMatrixColorFilter colorFilter = new ColorMatrixColorFilter(colorMatrix);
		    					paint.setColorFilter(colorFilter);
		    	    			if (DEBUG_LOGD) {
		    	    				Log.d(LOG_TAG, Log.buf().append("ColorMatrixColorFilter ColorMatrix: ")
		    	    						.append(Arrays.toString(colorMatrix.getArray())).toString());
		    	    			}
		    				}
		    				int offsetX = 0;
		    				int offsetY = 0;
		    				if (placeObject2.placeFlagHasMatrix) {
		    					placeObject2.matirx.toMatrix(matrixPlaceObject);
	    		    			if (DEBUG_LOGD) {
	    		    				Log.d(LOG_TAG, Log.buf().append("PlaceObject2 Matrix: ")
	    		    						.append(matrixPlaceObject.toString()).toString());
	    		    			}
	    		    			offsetX = placeObject2.matirx.translateX;
	    		    			offsetY = placeObject2.matirx.translateY;
		    				}
		    				if (placeObject2.placeFlagHasCharacter) {
		    					DefineShapeBase defineShapeBase =
		    						mSwfPlayer.getShapeFromDictionary(placeObject2.characterId);
		    					assert defineShapeBase != null;
		    					
		    					// Pathクラス使う？
		    					// BitmapはBitmapShaderでPaintに設定？
		    					
		    					if (defineShapeBase instanceof DefineShape3) {
		    						DefineShape3 defineShape3 = (DefineShape3) defineShapeBase;
		    						
		    						canvas.save(Canvas.CLIP_SAVE_FLAG);
		    						canvas.clipRect(
		    								defineShape3.shapeBounds.xmin,
		    								defineShape3.shapeBounds.ymin,
		    								defineShape3.shapeBounds.xmax,
		    								defineShape3.shapeBounds.ymax);
//		    								offsetX + defineShape3.shapeBounds.xmin,
//		    								offsetY + defineShape3.shapeBounds.ymin,
//		    								offsetX + defineShape3.shapeBounds.xmax,
//		    								offsetY + defineShape3.shapeBounds.ymax);
		    						
		    						FILLSTYLEARRAY fillStyles = defineShape3.shapes.fillStyles;
		    						LINESTYLEARRAY lineStyles = defineShape3.shapes.lineStyles;
		    						byte numFillBits = defineShape3.shapes.numFillBits;
		    						byte numLineBits = defineShape3.shapes.numLineBits;
		    						
		    						int x = 0;
		    						int y = 0;
		    						FILLSTYLE fillStyle0 = null;
		    						FILLSTYLE fillStyle1 = null;
		    						LINESTYLE lineStyle = null;
		    						
		    						Path path = null;
		    						
		    						boolean doneEndRecord = false;
		    						for (SHAPERECORD shapeRecord : defineShape3.shapes.shapeRecords) {
		    							if (shapeRecord instanceof STYLECHANGERECORD) {
		    								if (path == null) {
		    									path = new Path();
		    								} else {
		    									// ここで描画？
		    									drawShape(canvas, paint, path, matrixCanvas, matrixPlaceObject, fillStyle0, fillStyle1, lineStyle);
	//	    									path.rewind();
		    								}
		    								
		    								STYLECHANGERECORD styleChange = (STYLECHANGERECORD) shapeRecord;
		    								
		    								if (styleChange.stateMoveTo) {
		    									path.rMoveTo(
		    											styleChange.moveDeltaX,
		    											styleChange.moveDeltaY);
		    									x += styleChange.moveDeltaX;
		    									y += styleChange.moveDeltaY;
		    								}
		    								
		    								if (styleChange.stateFillStyle0) {
		    									fillStyle0 =
		    										fillStyles.fillStyles[styleChange.fillStyle0 - 1];
		    								}
		    								if (styleChange.stateFillStyle1) {
		    									fillStyle1 =
		    										fillStyles.fillStyles[styleChange.fillStyle1 - 1];
		    								}
		    								
		    								if (styleChange.stateLineStyle) {
		    									lineStyle =
		    										lineStyles.lineStyles[styleChange.lineStyle - 1];
		    								}
		    								
		    								if (styleChange.stateNewStyles) {
		    		    						fillStyles = styleChange.fillStyles;
		    		    						lineStyles = styleChange.lineStyles;
		    		    						numFillBits = styleChange.numFillBits;
		    		    						numLineBits = styleChange.numLineBits;
		    								}
		    							} else if (shapeRecord instanceof STRAIGHTEDGERECORD) {
		    								STRAIGHTEDGERECORD straightEdge = (STRAIGHTEDGERECORD) shapeRecord;
		    								
		    								if (straightEdge.generalLineFlag) {
		    									path.rLineTo(
		    											straightEdge.deltaX,
		    											straightEdge.deltaY);
		    									x += straightEdge.deltaX;
		    									y += straightEdge.deltaY;
		    								} else {
		    									if (straightEdge.vertLineFlag) {
			    									path.rLineTo(
			    											0.0f,
			    											straightEdge.deltaY);
			    									y += straightEdge.deltaY;
		    									} else {
			    									path.rLineTo(
			    											straightEdge.deltaX,
			    											0.0f);
			    									x += straightEdge.deltaX;
		    									}
		    								}
		    							} else if (shapeRecord instanceof ENDSHAPERECORD) {
		    								if (path != null) {
		    									// ここで描画？
		    									drawShape(canvas, paint, path, matrixCanvas, matrixPlaceObject, fillStyle0, fillStyle1, lineStyle);
		    									path.rewind();
		    								}
		    								doneEndRecord = true;
		    							} else {
		    	    						// TODO
		    								throw new FailAnalyzeSwfException("TODO: SHAPERECORD " + shapeRecord.toString() + " is umimplemented.");
		    							}
		    						}
		    						assert doneEndRecord;

		    						canvas.restore();
		    					} else {
		    						// TODO
		    						throw new FailAnalyzeSwfException("TODO: " + defineShapeBase.toString() + " is umimplemented.");
		    					}
		    				}
		    				if (placeObject2.placeFlagMove) {
		    					// TODO
		    					throw new FailAnalyzeSwfException("TODO: PlaceObject2 PlaceFlagMove is umimplemented.");
		    				}
		    				
		    				
		    			} else {
		    				// TODO
		    				throw new FailAnalyzeSwfException("TODO: " + placeObject.toString() + " is umimplemented.");
		    			}
		    		}
		    		
		    		// とりあえずコメント内部表示
			        drawMessage(canvas,
			        		mMessageData,
			        		mMessageDataFork,
			        		(int) (100 * mSwfPlayer.getFrameCount() / ((float) ((mSwfPlayer.mHeaderMovieSwf.frameRateIntegral << 8) | mSwfPlayer.mHeaderMovieSwf.frameRateDecimal) / 256.0f)),
	//		        		mWidth, mHeight);
			        		canvas.getWidth(), canvas.getHeight());
		        } finally {
		        	mSurfaceHolder.unlockCanvasAndPost(canvas);
		        }
	        }
        }
        
        if (mIsFirstShowFrame) {
        	updateStartFrameTime();
        	mIsFirstShowFrame = false;
        }
        
//        // TODO スリープ時間適当
//        float sleepTime = 1000.0f / (float) ((mHeaderMovieSwf.frameRateIntegral << 8) | mHeaderMovieSwf.frameRateDecimal) / 256.0f;
//        try {
//			Thread.sleep((long) sleepTime);
//		} catch (InterruptedException e) {
//			Log.d(LOG_TAG, e.getMessage(), e);
//		}
    }
    
//    private void draw() {
//    	Canvas canvas = mSurfaceHolder.lockCanvas();
//        if (canvas == null) {
//        	Log.d(LOG_TAG, "lockCanvas NG");
//        	return;
//        }
//        
//        try {
//        	canvas.drawColor(mBackgroundColor);
//        } finally {
//        	mSurfaceHolder.unlockCanvasAndPost(canvas);
//        }
//    }
//
//
//    private void drawTest(Bitmap bitmap) {
//    	Canvas canvas = mSurfaceHolder.lockCanvas();
//        if (canvas == null) {
//        	Log.d(LOG_TAG, "lockCanvas NG");
//        	return;
//        }
//        
//        try {
//        	canvas.drawColor(mBackgroundColor);
//        	canvas.drawBitmap(bitmap, 0, 0, null);
//        } finally {
//        	mSurfaceHolder.unlockCanvasAndPost(canvas);
//        }
//    }
    
    private void drawShape(Canvas canvas, Paint paint, Path path,
    		Matrix matrixCanvas, Matrix matrixPlaceObject, 
    		FILLSTYLE fillStyle0, FILLSTYLE fillStyle1, LINESTYLE lineStyle) throws FailAnalyzeSwfException, IOException {
    	
    	canvas.save(Canvas.MATRIX_SAVE_FLAG);
    	
    	Matrix matrix = new Matrix(matrixCanvas);
//    	matrix.postConcat(matrixPlaceObject);
    	matrix.preConcat(matrixPlaceObject);
    	canvas.setMatrix(matrix);
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, Log.buf().append("Canvas set Matrix: ")
					.append(matrixCanvas.toString()).toString());
		}
//    	canvas.setMatrix(matrixCanvas);
    	
//    	Path pathDraw = new Path();
//    	path.transform(matrixPlaceObject, pathDraw);
    	Path pathDraw = path;
    	
		Shader shader0 = null;
    	if (fillStyle0 != null) {
    		shader0 = fillStyle0.getShader(paint, mSwfPlayer);
    		paint.setShader(shader0);
    		// TODO FillTypeこれで合ってるか？
    		pathDraw.setFillType(Path.FillType.INVERSE_EVEN_ODD);
//    		pathDraw.setFillType(Path.FillType.EVEN_ODD);
        	canvas.drawPath(pathDraw, paint);
    	}
    	Shader shader1 = null;
    	if (fillStyle1 != null) {
    		shader1 = fillStyle1.getShader(paint, mSwfPlayer);
    		paint.setShader(shader1);
//    		pathDraw.setFillType(Path.FillType.INVERSE_EVEN_ODD);
    		pathDraw.setFillType(Path.FillType.EVEN_ODD);
        	canvas.drawPath(pathDraw, paint);
    	}
    	if (lineStyle != null) {
    		paint.setColor(lineStyle.color);
    		paint.setStrokeWidth(lineStyle.width);
        	canvas.drawPath(pathDraw, paint);
    	}
    	
    	canvas.restore();
    }
    
//    private void execSoundStreamBlock(ByteBuffer buffer) throws FailAnalyzeSwfException {
//		int sampleCount = buffer.getShort() & 0xffff;		// UI16
//		if (DEBUG_LOGD) {
//			Log.d(LOG_TAG, "SampleCount=" + sampleCount);
//		}
//		short seekSamples = buffer.getShort();		// SI16
//		if (DEBUG_LOGD) {
//			Log.d(LOG_TAG, "SeekSamples=" + seekSamples);
//		}
////		if (DEBUG_LOGD) {
////			Log.d(LOG_TAG, "test: " + Integer.toHexString(bufferArray[buffer.position() + 0] & 0xff)
////					 + "," + Integer.toHexString(bufferArray[buffer.position() + 1] & 0xff));
////		}
//		
////		if (!mIsFirstSoundStreamBlock) {
////			// skip MP3FRAME header
////			buffer.position(buffer.position() + 4);
////		}
//		
//		mSoundStreamBlockQueue.add(buffer);
//		
//		if (mIsFirstSoundStreamBlock) {
//			startAudio(mSoundStreamHead);
//			mIsFirstSoundStreamBlock = false;
//			
//			buffer.position(4);
//		} else {
//			// test すごい無駄
//			
////			soundStreamHead = mSoundStreamHead;
////			
////	    	// FFmpeg生成
////			destroyNativeInstance();
////	        mNativeInstance = createNative();
////	        
////	        int sampleRate;
////	        switch (soundStreamHead.streamSoundRate) {
////	        case SoundStreamHead.SOUNDRATE_5_5:
////	        	sampleRate = 5512;
////	        	break;
////	        case SoundStreamHead.SOUNDRATE_11:
////	        	sampleRate = 11025;
////	        	break;
////	        case SoundStreamHead.SOUNDRATE_22:
////	        	sampleRate = 22050;
////	        	break;
////	        case SoundStreamHead.SOUNDRATE_44:
////	        	sampleRate = 44100;
////	        	break;
////	        default:
////	        	assert false : "logic error: StreamSoundRate is invalid value=" + soundStreamHead.streamSoundRate;
////	        	sampleRate = 0;
////	        	break;
////	        }
////	        
////	        mAudioSampleByteSize = 2;
////	        
////	        int channels;
////	    	int channelConfig;
////	        switch (soundStreamHead.streamSoundType) {
////	        case SoundStreamHead.SOUNDTYPE_MONO:
////	        	channels = 1;
////	        	channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
////	        	mAudioSampleByteSize *= 1;
////	        	break;
////	        case SoundStreamHead.SOUNDTYPE_STEREO:
////	        	channels = 2;
////				channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
////	        	mAudioSampleByteSize *= 2;
////	        	break;
////	        default:
////	        	assert false : "logic error: StreamSoundType is invalid value=" + soundStreamHead.streamSoundType;
////	        	channels = 0;
////				channelConfig = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
////	        	break;
////	        }
////	        
////	        switch (soundStreamHead.streamSoundCompression) {
////	        case SoundStreamHead.SOUNDCOMPRESSION_ADPCM:
////	        	loadStreamADPCM(mNativeInstance, mReadBuffer, mIOCallback,
////	        			256, ((mHeaderMovieSwf.frameRateIntegral << 8) | mHeaderMovieSwf.frameRateDecimal),
////	        			sampleRate, channels);
////	        	break;
////	        case SoundStreamHead.SOUNDCOMPRESSION_MP3:
////	        	loadStreamMP3(mNativeInstance, mReadBuffer, mIOCallback,
////	        			256, ((mHeaderMovieSwf.frameRateIntegral << 8) | mHeaderMovieSwf.frameRateDecimal),
////	        			sampleRate, channels);
////	        	break;
////	        default:
////	        	throw new FailAnalyzeSwfException("SoundStreamHead: unknown StreamSoundCompression=" + soundStreamHead.streamSoundCompression);
////	        }
////	        
////	        // AudioBuffer準備
//////			mFFmpegData.mAudioBuffer = new short[AUDIO_BUFFER_BYTE_SIZE/2];
////			mFFmpegData.mAudioBufferSize = 0;
//			
////			reopenInputStream(mNativeInstance, mReadBuffer, mIOCallback);
//			buffer.position(4);
////			buffer.position(4+4);
//		}
//		
////		// test
////		try {
////			mTestOutMP3.write(buffer.array(), 4, buffer.limit() - 4);
////		} catch (IOException e) {
////			// TODO 自動生成された catch ブロック
////			e.printStackTrace();
////		}
//		
////		int bufferedSize = getSoundStreamBufferedSize();
////		if (bufferedSize >= mSoundStreamHead.streamSoundSampleCount * mAudioSampleByteSize) {
//		
//		LOOP : while (true) {
//    		int frameType = decodeFrame(mNativeInstance, null, false, mReadBuffer, mIOCallback, mFFmpegData);
//    		switch (frameType) {
//    		case CODE_DECODE_FRAME_AUDIO:
//    			
//    			mStreamAudioPlayer.writeBuffer(mFFmpegData.mAudioBuffer, mFFmpegData.mAudioBufferSize / 2);
//    			
////    			if (mAudioBufferSecondOffset > mAudioBufferSecond.length  * 3 / 4) {
//	    			// write buffer to AudioTrack
//    				mStreamAudioPlayer.flushBufferToAudioTrack();
////    			}
//    			break;
//    		case CODE_DECODE_FRAME_ERROR_OR_END:
//        		if (DEBUG_LOGD) {
//        			Log.d(LOG_TAG, "CODE_DECODE_FRAME_ERROR_OR_END");
//        		}
//    			break LOOP;
//    		default:
//    			assert false : "unknown decodeFrame return value=" + frameType;
//    			break LOOP;
//    		}
//		}
//		
//    	// test
//    	mSoundStreamBlockQueue.clear();
//    }
    
//    private int readStreamSoundData(StreamSoundDataBase streamSoundData, byte[] bufferArray, int offset) throws FailAnalyzeSwfException {
//    	int offsetNext;
//    	if (mSwfPlayer.mSoundStreamHead.streamSoundCompression == SoundStreamHead.SOUNDCOMPRESSION_ADPCM) {
//    		assert streamSoundData instanceof ADPCMSOUNDDATA;
//    		ADPCMSOUNDDATA adpcm = (ADPCMSOUNDDATA) streamSoundData;
//    		adpcm.data = new byte[bufferArray.length - offset];
//    		System.arraycopy(bufferArray, offset, adpcm.data, 0, adpcm.data.length);
//    		offsetNext = bufferArray.length;
//
//    		adpcm.readOffset = 0;
//    	} else if (mSwfPlayer.mSoundStreamHead.streamSoundCompression == SoundStreamHead.SOUNDCOMPRESSION_MP3) {
//    		assert streamSoundData instanceof MP3STREAMSOUNDDATA;
//    		MP3STREAMSOUNDDATA mp3 = (MP3STREAMSOUNDDATA) streamSoundData;
//    		
//    		offsetNext = offset;
//    		mp3.sampleCount = (bufferArray[offsetNext + 0] & 0xff) | ((bufferArray[offsetNext + 1] & 0xff) << 8);
//    		if (DEBUG_LOGD) {
//    			Log.d(LOG_TAG, "SampleCount=" + mp3.sampleCount);
//    		}
//    		offsetNext += 2;
//    		
//    		mp3.seekSamples = (short) ((bufferArray[offsetNext + 0] & 0xff) | (bufferArray[offsetNext + 1] << 8));
//    		if (DEBUG_LOGD) {
//    			Log.d(LOG_TAG, "SeekSamples=" + mp3.seekSamples);
//    		}
//    		offsetNext += 2;
//    		
//    		mp3.mp3Frames = new byte[bufferArray.length - offsetNext];
//    		System.arraycopy(bufferArray, offsetNext, mp3.mp3Frames, 0, mp3.mp3Frames.length);
//    		offsetNext = bufferArray.length;
//
//    		mp3.readOffset = 0;
//    	} else {
//    		throw new FailAnalyzeSwfException("not supported StreamSoundCompression=" + mSwfPlayer.mSoundStreamHead.streamSoundCompression);
//    	}
//    	return offsetNext;
//    }
    
    private abstract class AudioPlayer implements SwfPlayer.SoundStreamListener {
    	abstract public void start(File swfFile)
    	throws IOException, FailAnalyzeSwfException;
    	abstract public void finish();
    	abstract public boolean pause();
    	abstract public void restart();
    	abstract public void getCurrentPositionAudioPlay(Rational rational);
    	abstract public void getCurrentPositionAudioDecode(Rational rational);
    	abstract public void seekBySecond(int second);
    	abstract public void clearBuffer();
    };
    
    private class SoundStreamListenerFFmpeg extends AudioPlayer {
    	
//    	private LinkedList<ByteBuffer> mSoundStreamBlockQueue;
//    	private ConcurrentLinkedQueue<ByteBuffer> mSoundStreamBlockQueue;
    	private ConcurrentLinkedQueue<StreamSoundDataBase> mStreamSoundDataQueue;
//    	private ByteBuffer mSoundStreamBlockFirst;
    	
//    	private int mSeekSamplesMP3;
    	private short[] mSeekSamplesMP3;
    	
    	private boolean mIsFirstSoundStreamBlock;
    	private int mAudioDecodedBytes;
    	private int mFrameCountAudioPlay;
    	
    	/*private*/ StreamAudioPlayer mStreamAudioPlayer = new StreamAudioPlayer();
        
    	private Thread mSoundStreamThread = null;
    	
    	private byte[] mReadBuffer = new byte[1024*24];
    	
        // NativeのNicoroFFmpegPlayerクラスのポインタ
        /*private*/ volatile long mNativeInstance;
    	
        // Nativeからアクセスされるもの
        FFmpegData mFFmpegData = new FFmpegData();
        
        private FFmpegIOCallback mIOCallback = new FFmpegIOCallback() {
    		@Override
    		public int readFromNativeCallback(int bufSize, byte[] buffer) {
    			if (DEBUG_LOGV) {
    				Log.v(LOG_TAG, "readFromNativeCallback: bufSize=" + bufSize
    						+ " buffer=" + buffer.toString());
    			}
    			
//    			if (mSoundStreamBlockFirst != null) {
//    				// ダミーとして先頭フレームを使用し続ける
//    				int remaining = mSoundStreamBlockFirst.remaining();
//    				if (remaining >= bufSize) {
//    					mSoundStreamBlockFirst.get(buffer, 0, bufSize);
//    					if (DEBUG_LOGV) {
//    						Log.v(LOG_TAG, "readFromNativeCallback: return=" + bufSize);
//    					}
//    					return bufSize;
//    				} else {
//    					mSoundStreamBlockFirst.get(buffer, 0, remaining);
//    					assert mSoundStreamBlockFirst.remaining() == 0;
//    					mSoundStreamBlockFirst.position(0);
//    					if (DEBUG_LOGV) {
//    						Log.v(LOG_TAG, "readFromNativeCallback: return=" + remaining);
//    					}
//    					return remaining;
//    				}
//    			}
//    			assert mSoundStreamBlockFirst == null;
    			
    			int readBufferSize = bufSize;
    			int readBufferOffset = 0;
    			while (true) {
//    				ByteBuffer soundStreamBlockBuffer = mSoundStreamBlockQueue.peek();
    				StreamSoundDataBase streamSoundData = mStreamSoundDataQueue.peek();
//    				if (soundStreamBlockBuffer == null) {
    				if (streamSoundData == null) {
    					if (DEBUG_LOGV) {
//    						Log.v(LOG_TAG, "mSoundStreamBlockQueue.peek() return null");
    						Log.v(LOG_TAG, "mStreamSoundDataQueue.peek() return null");
    					}
//    					if (readBufferOffset == 0) {
//    						if (DEBUG_LOGV) {
//    							Log.v(LOG_TAG, "readFromNativeCallback: return=" + 0);
//    						}
////    						return -1;
//    						return 0;
//    					} else {
//    						if (DEBUG_LOGV) {
//    							Log.v(LOG_TAG, "readFromNativeCallback: return=" + readBufferOffset);
//    						}
//    						return readBufferOffset;
//    					}

    					// TODO
    					if (readBufferOffset > 0) {
    						if (DEBUG_LOGV) {
    							Log.v(LOG_TAG, "readFromNativeCallback: return=" + readBufferOffset);
    						}
    						return readBufferOffset;
    					}
    					
    					if (mIsFinish) {
    						if (readBufferOffset == 0) {
    							if (DEBUG_LOGV) {
    								Log.v(LOG_TAG, "readFromNativeCallback: return=" + 0);
    							}
//    							return -1;
    							return 0;
    						} else {
    							if (DEBUG_LOGV) {
    								Log.v(LOG_TAG, "readFromNativeCallback: return=" + readBufferOffset);
    							}
    							return readBufferOffset;
    						}
    					}
    					try {
    			    		synchronized (mSoundStreamThread) {
    			    			mSoundStreamThread.wait(1000L);
    			    		}
//    						Thread.sleep(33L);
    					} catch (InterruptedException e) {
    					}
    					continue;
    				}
    				
//    				int remaining = soundStreamBlockBuffer.remaining();
//    				if (DEBUG_LOGV) {
//    					Log.v(LOG_TAG, "soundStreamBlockBuffer.remaining()=" + remaining);
//    				}
//    				if (remaining >= readBufferSize) {
//    					soundStreamBlockBuffer.get(buffer, readBufferOffset, readBufferSize);
//    					if (remaining == readBufferSize) {
//    						assert soundStreamBlockBuffer.remaining() == 0;
//    						mSoundStreamBlockQueue.poll();
//    					}
//    					readBufferOffset += readBufferSize;
//    					readBufferSize -= readBufferSize;
//    					if (DEBUG_LOGV) {
//    						Log.v(LOG_TAG, "readFromNativeCallback: return=" + readBufferOffset);
//    					}
//    					return readBufferOffset;
//    				} else {
//    					soundStreamBlockBuffer.get(buffer, readBufferOffset, remaining);
//    					readBufferOffset += remaining;
//    					readBufferSize -= remaining;
//    					mSoundStreamBlockQueue.poll();
//    				}
    				
    				if (streamSoundData instanceof ADPCMSOUNDDATA) {
    		    		ADPCMSOUNDDATA adpcm = (ADPCMSOUNDDATA) streamSoundData;
    					int remaining = adpcm.data.length - adpcm.readOffset;
    					if (DEBUG_LOGV) {
    						Log.v(LOG_TAG, "remaining=" + remaining);
    					}
    					if (remaining >= readBufferSize) {
    						System.arraycopy(adpcm.data, adpcm.readOffset, buffer, readBufferOffset, readBufferSize);
    						if (remaining == readBufferSize) {
    							assert adpcm.readOffset + readBufferSize == adpcm.data.length;
    							mStreamSoundDataQueue.poll();
    						}
    						adpcm.readOffset += readBufferSize;
    						readBufferOffset += readBufferSize;
    						readBufferSize -= readBufferSize;
    						if (DEBUG_LOGV) {
    							Log.v(LOG_TAG, "readFromNativeCallback: return=" + readBufferOffset);
    						}
    						return readBufferOffset;
    					} else {
    						System.arraycopy(adpcm.data, adpcm.readOffset, buffer, readBufferOffset, remaining);
    						adpcm.readOffset += remaining;
    						readBufferOffset += remaining;
    						readBufferSize -= remaining;
    						mStreamSoundDataQueue.poll();
    					}
    				} else if (streamSoundData instanceof MP3STREAMSOUNDDATA) {
    		    		MP3STREAMSOUNDDATA mp3 = (MP3STREAMSOUNDDATA) streamSoundData;
    					int remaining = mp3.mp3Frames.length - mp3.readOffset;
    					if (DEBUG_LOGV) {
    						Log.v(LOG_TAG, "remaining=" + remaining);
    					}
    					if (remaining >= readBufferSize) {
    						System.arraycopy(mp3.mp3Frames, mp3.readOffset, buffer, readBufferOffset, readBufferSize);
    						if (remaining == readBufferSize) {
    							assert mp3.readOffset + readBufferSize == mp3.mp3Frames.length;
    							mStreamSoundDataQueue.poll();
    						}
    						mp3.readOffset += readBufferSize;
    						readBufferOffset += readBufferSize;
    						readBufferSize -= readBufferSize;
    						if (DEBUG_LOGV) {
    							Log.v(LOG_TAG, "readFromNativeCallback: return=" + readBufferOffset);
    						}
    						return readBufferOffset;
    					} else {
    						System.arraycopy(mp3.mp3Frames, mp3.readOffset, buffer, readBufferOffset, remaining);
    						mp3.readOffset += remaining;
    						readBufferOffset += remaining;
    						readBufferSize -= remaining;
    						mStreamSoundDataQueue.poll();
    					}
    				} else {
    					assert false : "unknown StreamSoundData=" + streamSoundData.toString();
    					return -1;
    				}
    			}
    			
//    			ByteBuffer soundStreamBlockBuffer = mSoundStreamBlockQueue.peek();
//    			if (soundStreamBlockBuffer == null) {
//    				if (DEBUG_LOGD) {
//    					Log.d(LOG_TAG, "mSoundStreamBlockQueue.peek() return null");
//    				}
////    				return -1;
//    				return 0;
//    			}
//    			if (DEBUG_LOGD) {
//    				Log.d(LOG_TAG, "soundStreamBlockBuffer.position()=" + soundStreamBlockBuffer.position());
//    				Log.d(LOG_TAG, "soundStreamBlockBuffer.limit()=" + soundStreamBlockBuffer.limit());
//    			}
//    			
//    			int readBufferSize;
//    			int remaining = soundStreamBlockBuffer.remaining();
//    			if (remaining > bufSize) {
//    				readBufferSize = bufSize;
//    			} else {
//    				readBufferSize = remaining;
//    			}
//    			soundStreamBlockBuffer.get(buffer, 0, readBufferSize);
//    			if (remaining <= bufSize) {
//    				assert soundStreamBlockBuffer.remaining() == 0;
//    				mSoundStreamBlockQueue.poll();
//    			}
//    			return readBufferSize;
    		}
    		
    		private static final int SEEK_SET = 0;
    		private static final int SEEK_CUR = 1;
    		private static final int SEEK_END = 2;
    		private static final int AVSEEK_SIZE = 0x10000;

    		@Override
    		public long seekFromNativeCallback(long offset, int whence) {
    			if (DEBUG_LOGV) {
    				Log.v(LOG_TAG, "seekFromNativeCallback: offset=" + offset + " whence=" + whence);
    			}
    			
//    			switch (whence) {
//    			case AVSEEK_SIZE:
//    				return Long.MAX_VALUE;
//    			}
    			
    			return -1L;
    		}
        };
    	
        private class SoundStreamThread implements Runnable {
    		@Override
    		public void run() {
    			try {
    				// begin
    				while (!mIsFinish) {
//    					int bufferedSize = getSoundStreamBufferedSize();
//    					if (bufferedSize < AUDIO_BUFFER_BYTE_SIZE) {		// TODO サイズ適当
//    					if (bufferedSize < 4096 * 2) {		// TODO サイズ適当
//    					if (bufferedSize == 0) {
    					if (mStreamSoundDataQueue.isEmpty()) {
    						try {
    				    		synchronized (mSoundStreamThread) {
    				    			mSoundStreamThread.wait(1000L);
    				    		}
    						} catch (InterruptedException e) {
    						}
    					} else {
//    						assert mSoundStreamBlockFirst != null;
//    						ByteBuffer buffer = mSoundStreamBlockQueue.peek();
//    						assert mSoundStreamBlockFirst != buffer;
//    						int sampleCount = buffer.getShort() & 0xffff;		// UI16
//    						if (DEBUG_LOGD) {
//    							Log.d(LOG_TAG, "SampleCount=" + sampleCount);
//    						}
//    						short seekSamples = buffer.getShort();		// SI16
//    						if (DEBUG_LOGD) {
//    							Log.d(LOG_TAG, "SeekSamples=" + seekSamples);
//    						}
    						
//    						int lastPosition = buffer.position();
    						startAudio(mSwfPlayer.mSoundStreamHead);
//    						buffer.position(lastPosition + 4);
    						
//    						// dummy
//    						int decodedBufferSize = 0;
//    						while (!mIsFinish) {
//    							int frameType = decodeFrame(mNativeInstance, null, false, mReadBuffer, mIOCallback, mFFmpegData);
//    							if (DEBUG_LOGD) {
//    								Log.d(LOG_TAG, "dummy decoded buffer size=" + mFFmpegData.mAudioBufferSize);
//    							}
//    							assert frameType == CODE_DECODE_FRAME_AUDIO;
//    							if (frameType == CODE_DECODE_FRAME_AUDIO) {
//    								decodedBufferSize += mFFmpegData.mAudioBufferSize;
//    							}
//    							assert mAudioDecodedBytes == 0;
//    							mFFmpegData.mAudioBufferSize = 0;
//    							mFFmpegData.mTimeNumAudio = 0;
//    							mFFmpegData.mTimeDenAudio = 0;
//    							if (decodedBufferSize >= AUDIO_BUFFER_BYTE_SIZE) {
//    								break;
//    							}
//    						}
//    						mSoundStreamBlockFirst = null;
//    						buffer.position(lastPosition + 4);
    						
    						break;
    					}
    				}
    				
    				// main
    				while (!mIsFinish) {
    					if (mIsPause) {
    						try {
    							Thread.sleep(10L);
    						} catch (InterruptedException e) {
    						}
    						continue;
    					}
    					LOOP : while (true) {
//    						if (mSoundStreamBlockQueue.isEmpty()) {
    						if (mStreamSoundDataQueue.isEmpty()) {
//    						if (getSoundStreamBufferedSize() < mSoundStreamHead.streamSoundSampleCount * mStreamAudioPlayer.getAudioSampleByteSize()) {
    							try {
    					    		synchronized (mSoundStreamThread) {
    					    			mSoundStreamThread.wait(1000L);
    					    		}
    							} catch (InterruptedException e) {
    							}
    							break LOOP;
    						}
    						
//    						assert getSoundStreamBufferedSize() > 0;
    			    		int frameType = readFrame(mNativeInstance, mReadBuffer, mIOCallback);
    			    		switch (frameType) {
    			    		case CODE_DECODE_FRAME_AUDIO:
    			    			if (decodeAudio(mNativeInstance, mFFmpegData)) {
        			    			mAudioDecodedBytes += mFFmpegData.mAudioBufferSize;
        			    			// TODO
        			    			int audioBufferSize = mFFmpegData.mAudioBufferSize;
//        			    			int audioBufferSize = mFFmpegData.mAudioBufferSize - mSeekSamplesMP3 * mStreamAudioPlayer.getAudioSampleByteSize();
        			    			if (mSeekSamplesMP3 != null) {
//        			    				audioBufferSize -= mSeekSamplesMP3[mFrameCountAudioPlay] * mStreamAudioPlayer.getAudioSampleByteSize();
//        			    				assert audioBufferSize >= 0;
        			    			}
        			    			if (audioBufferSize > 0) {
        			    				mStreamAudioPlayer.writeBuffer(mFFmpegData.mAudioBuffer, audioBufferSize / 2);
        			    			}
        			    			++mFrameCountAudioPlay;
        			    			
        			    			if (!mStreamAudioPlayer.canPlay()) {
        			    				Log.d(LOG_TAG, "mAudioTrack is null.");
        			    				break LOOP;
        			    			}
        			    			
//        			    			if (mStreamAudioPlayer.hasEnoughCache()) {
        				    			// write buffer to AudioTrack
        			    				mStreamAudioPlayer.flushBufferToAudioTrack();
//        			    				mStreamAudioPlayer.flushAudioTrack();
//        			    			}
    							} else {
    								if (DEBUG_LOGD) {
    									Log.d(LOG_TAG, "decodeAudio failed");
    								}
    							}
//    			    			break;
    			    			break LOOP;
    			    		case CODE_DECODE_FRAME_ERROR_OR_END:
    			        		if (DEBUG_LOGD) {
    			        			Log.d(LOG_TAG, "CODE_DECODE_FRAME_ERROR_OR_END");
    			        		}
    			    			break LOOP;
    			    		default:
    			    			assert false : "unknown decodeFrame return value=" + frameType;
    			    			break LOOP;
    			    		}
    					}
    				}
    				
    				// 音声データ残っていたら待つ： TODO こちらのスレッドの場合はいるか？
    				mStreamAudioPlayer.waitPlayEnd();
    				mStreamAudioPlayer.releaseAll();
    			} catch (FailAnalyzeSwfException e) {
    				Log.e(LOG_TAG, e.getMessage(), e);
    			} finally {
    		    	synchronized (this) {
    		    		destroyNativeInstance();
    				}
    			}
    		}
        }
        
	    @Override
	    public void onSoundStreamHead(
	    		SoundStreamHead soundStreamHead,
	    		SwfPlayer swfPlayer) {
			if (soundStreamHead.streamSoundCompression == SoundStreamHead.SOUNDCOMPRESSION_MP3) {
				mSeekSamplesMP3 = new short[swfPlayer.mHeaderMovieSwf.frameCount];
			}
			
	//		startAudio(soundStreamHead);
			
	//		mSoundStreamBlockQueue = new LinkedList<ByteBuffer>();
	//		mSoundStreamBlockQueue = new ConcurrentLinkedQueue<ByteBuffer>();
			mStreamSoundDataQueue = new ConcurrentLinkedQueue<StreamSoundDataBase>();
	    	mIsFirstSoundStreamBlock = true;
	        
			mSoundStreamThread = new Thread(new SoundStreamThread());
	//		mSoundStreamThread.setPriority(mSoundStreamThread.getPriority() + 1);
	//		mSoundStreamThread.setPriority(Thread.MAX_PRIORITY);
			mSoundStreamThread.start();
	    }
	    
	    @Override
	    public void onSoundStreamBlock(
	    		StreamSoundDataBase streamSoundData,
	    		SoundStreamHead soundStreamHead,
	    		SwfPlayer swfPlayer, boolean frameSkip) {
			if (soundStreamHead.streamSoundCompression == SoundStreamHead.SOUNDCOMPRESSION_MP3) {
	//			mSeekSamplesMP3[mFrameCount] = ((MP3STREAMSOUNDDATA) streamSoundData).seekSamples;
			}
			
	//		int sampleCount = buffer.getShort() & 0xffff;		// UI16
	//		if (DEBUG_LOGD) {
	//			Log.d(LOG_TAG, "SampleCount=" + sampleCount);
	//		}
	//		short seekSamples = buffer.getShort();		// SI16
	//		if (DEBUG_LOGD) {
	//			Log.d(LOG_TAG, "SeekSamples=" + seekSamples);
	//		}
			
	//		if (mIsFirstSoundStreamBlock) {
	//			mSoundStreamBlockFirst = ByteBuffer.allocate(buffer.remaining());
	//			System.arraycopy(bufferArray, buffer.position(), mSoundStreamBlockFirst.array(), 0, mSoundStreamBlockFirst.limit());
	//		}
			
	//		mSoundStreamBlockQueue.add(buffer);
	    	mStreamSoundDataQueue.add(streamSoundData);
			
			synchronized (mSoundStreamThread) {
	//			mSoundStreamThread.notify();
				mSoundStreamThread.notifyAll();
			}
	//		try {
	//			Thread.sleep(1L);
	//		} catch (InterruptedException e) {
	//		}
			
			if (mIsFirstSoundStreamBlock) {
				mIsFirstSoundStreamBlock = false;
	//	        // TODO
	//			while (mSoundStreamBlockFirst != null) {
	//				try {
	//					Thread.sleep(10L);
	//				} catch (InterruptedException e) {
	//				}
	//			}
				
		        mStreamAudioPlayer.waitRealStart();
			}
	    }
	    
	    @Override
	    public void start(File swfFile) {
	    	mAudioDecodedBytes = 0;
	    	mFrameCountAudioPlay = 0;
	    }
	    
	    @Override
    	public void finish() {
	    	mStreamAudioPlayer.finish();
			if (mSoundStreamThread != null) {
				try {
		    		synchronized (mSoundStreamThread) {
//		    			mSoundStreamThread.notify();
		    			mSoundStreamThread.notifyAll();
		    		}
	    			mSoundStreamThread.join(1000L * 2);
				} catch (InterruptedException e) {
		    		Log.d(LOG_TAG, e.getMessage(), e);
				}
				mSoundStreamThread = null;
			}
	    	synchronized (this) {
	    		destroyNativeInstance();
			}
	    }
	    
		@Override
    	public boolean pause() {
			if (mStreamAudioPlayer.isInDummyData()) {
				return false;
			} else {
				mStreamAudioPlayer.pause();
				return true;
			}
		}
		
		@Override
    	public void restart() {
			mStreamAudioPlayer.restart();
		}
	    
		@Override
		public void getCurrentPositionAudioPlay(Rational rational) {
			mStreamAudioPlayer.getCurrentPosition(rational);
		}
		
		@Override
		public void getCurrentPositionAudioDecode(Rational rational) {
			rational.num = mAudioDecodedBytes;
//			int sampleRate = getSampleRateInHzFromStreamSoundRate(mSoundStreamHead.streamSoundRate);
//	        int channels = getChannelsFromStreamSoundType(mSoundStreamHead.streamSoundType);
//			rational.den = sampleRate * channels * 2;
			rational.den = mStreamAudioPlayer.getSampleRate() * mStreamAudioPlayer.getAudioSampleByteSize();
		}
		
		@Override
    	public void seekBySecond(int second) {
			// TODO
			throw new UnsupportedOperationException(
					"SoundStreamListenerFFmpeg#seekBySecond not support");
		}
		
		@Override
    	public void clearBuffer() {
			// TODO
			throw new UnsupportedOperationException(
					"SoundStreamListenerFFmpeg#clearBuffer not support");
		}
		
	    @Override
	    protected void finalize() throws Throwable {
	    	try {
	    		super.finalize();
	    	} finally {
	        	mStreamAudioPlayer.finish();
				if (mSoundStreamThread != null) {
					try {
			    		synchronized (mSoundStreamThread) {
//			    			mSoundStreamThread.notify();
			    			mSoundStreamThread.notifyAll();
			    		}
		    			mSoundStreamThread.join(1000L * 2);
					} catch (InterruptedException e) {
			    		Log.d(LOG_TAG, e.getMessage(), e);
					}
				}
	    		synchronized (this) {
	    			destroyNativeInstance();
				}
	    	}
	    }
	    
	    private void startAudio(SoundStreamHead soundStreamHead) throws FailAnalyzeSwfException {
	    	// FFmpeg生成
			destroyNativeInstance();
	        mNativeInstance = createNative();
	        
	        int sampleRate = getSampleRateInHzFromStreamSoundRate(soundStreamHead.streamSoundRate);
	        int channels = getChannelsFromStreamSoundType(soundStreamHead.streamSoundType);
	        
			// Audio開始
	        mStreamAudioPlayer.startBySwf(sampleRate, channels);
	        
	        // AudioBuffer準備
			int audioBufferByteSize = mStreamAudioPlayer.getAudioBufferByteSize();
			mFFmpegData.mAudioBuffer = new short[audioBufferByteSize/2];
			mFFmpegData.mAudioBufferSize = 0;
			
			mStreamAudioPlayer.prepareStart();
	    	
//	        // TODO
//	        mStreamAudioPlayer.waitRealStart();
	        
	        switch (soundStreamHead.streamSoundCompression) {
	        case SoundStreamHead.SOUNDCOMPRESSION_ADPCM:
	        	loadStreamADPCM(mNativeInstance, mReadBuffer, mIOCallback,
	        			256, ((mSwfPlayer.mHeaderMovieSwf.frameRateIntegral << 8) | mSwfPlayer.mHeaderMovieSwf.frameRateDecimal),
	        			sampleRate, channels);
	        	break;
	        case SoundStreamHead.SOUNDCOMPRESSION_MP3:
	        	loadStreamMP3(mNativeInstance, mReadBuffer, mIOCallback,
	        			256, ((mSwfPlayer.mHeaderMovieSwf.frameRateIntegral << 8) | mSwfPlayer.mHeaderMovieSwf.frameRateDecimal),
	        			sampleRate, channels);
	        	break;
	        default:
	        	throw new FailAnalyzeSwfException("SoundStreamHead: unknown StreamSoundCompression=" + soundStreamHead.streamSoundCompression);
	        }
	    }
	    
	    private int getSampleRateInHzFromStreamSoundRate(int streamSoundRate) {
	        int sampleRate;
	        switch (streamSoundRate) {
	        case SoundStreamHead.SOUNDRATE_5_5:
	        	sampleRate = 5512;
	        	break;
	        case SoundStreamHead.SOUNDRATE_11:
	        	sampleRate = 11025;
	        	break;
	        case SoundStreamHead.SOUNDRATE_22:
	        	sampleRate = 22050;
	        	break;
	        case SoundStreamHead.SOUNDRATE_44:
	        	sampleRate = 44100;
	        	break;
	        default:
	        	assert false : "logic error: StreamSoundRate is invalid value=" + streamSoundRate;
	        	sampleRate = 0;
	        	break;
	        }
	        return sampleRate;
	    }
	    
	    private int getChannelsFromStreamSoundType(int streamSoundType) {
	        int channels;
	        switch (streamSoundType) {
	        case SoundStreamHead.SOUNDTYPE_MONO:
	        	channels = 1;
	        	break;
	        case SoundStreamHead.SOUNDTYPE_STEREO:
	        	channels = 2;
	        	break;
	        default:
	        	assert false : "logic error: StreamSoundType is invalid value=" + streamSoundType;
	        	channels = 0;
	        	break;
	        }
	    	return channels;
	    }
	    
//	    private int getSoundStreamBufferedSize() {
//	    	int sum = 0;
//	    	for (ByteBuffer bb : mSoundStreamBlockQueue) {
//	    		sum += bb.remaining();
//	    	}
//	    	return sum;
//	    }

	    private void destroyNativeInstance() {
			long nativeInstance = mNativeInstance;
			mNativeInstance = 0L;
			destroyNative(nativeInstance);
	    }
	    
    }
    
    private class SoundStreamListenerMediaPlayer extends AudioPlayer {
    	private static final int SPARE_TIME_MS = 500;
    	
    	private MediaPlayer mMediaPlayer;
    	private Rational mVideoPosition = new Rational();
    	private int mFrameCountSound;
    	private boolean mSyncWaiting;
    	
    	private File mAudioFile;
    	
	    @Override
	    public void onSoundStreamHead(
	    		SoundStreamHead soundStreamHead,
	    		SwfPlayer swfPlayer) {
	    	mFrameCountSound = 0;
	    	mSyncWaiting = false;
	    	
	    	// TODO 判定がわかりにくい
	    	if (!mRequestSeek) {
		    	mMediaPlayer = new MediaPlayer();
		    	try {
		    		mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
					mMediaPlayer.setDataSource(mAudioFile.getAbsolutePath());
					mMediaPlayer.prepare();
				} catch (IllegalArgumentException e) {
					Log.e(LOG_TAG, e.getMessage(), e);
				} catch (IllegalStateException e) {
					Log.e(LOG_TAG, e.getMessage(), e);
				} catch (IOException e) {
					Log.e(LOG_TAG, e.getMessage(), e);
				}
	    	}
	    }
	    @Override
	    public void onSoundStreamBlock(
	    		StreamSoundDataBase streamSoundData,
	    		SoundStreamHead soundStreamHead,
	    		SwfPlayer swfPlayer, boolean frameSkip) {
	    	if (!frameSkip) {
		    	getCurrentPositionVideoPlay(mVideoPosition);
		    	int videoPositionMs = mVideoPosition.getMs();
		    	
		    	// 描画と同期
	    		final int diffPos = mMediaPlayer.getCurrentPosition() - videoPositionMs;
	    		if (diffPos > SPARE_TIME_MS) {
	    			// 止まって待つ
	    			if (!mSyncWaiting) {
		    			mMediaPlayer.pause();
		    			mSyncWaiting = true;
	    			}
	    		} else if (diffPos < -SPARE_TIME_MS) {
	    			// 位置調整
		    		mMediaPlayer.seekTo(videoPositionMs);
		    		if (!mMediaPlayer.isPlaying()) {
						mMediaPlayer.start();
		    		}
		    		mSyncWaiting = false;
	    		} else {
	    			if (mSyncWaiting) {
	    				if (diffPos <= 0) {
							mMediaPlayer.start();
	    					mSyncWaiting = false;
	    				}
	    			} else {
	    			}
	    		}
	    	}
	    	
	    	++mFrameCountSound;
	    }

	    @Override
	    public void start(File swfFile) throws IOException, FailAnalyzeSwfException {
    		File outAudioFile = new File(VideoLoader.STREAM_TEMP_DIR, AUDIO_TEMP_FILE);
    		InputStream inSwf = null;
			mAudioFile = null;
    		try {
	    		inSwf = new FileInputStream(swfFile);
	    		inSwf = new BufferedInputStream(inSwf);
	    		if (SwfPlayer.getAudioFile(inSwf, outAudioFile)) {
	    			mAudioFile = outAudioFile;
	    		}
    		} finally {
    			if (inSwf != null) {
    				try {
    					inSwf.close();
    				} catch (IOException e) {
    					Log.d(LOG_TAG, e.getMessage(), e);
    				}
    			}
    		}
	    }
	    
	    @Override
    	public void finish() {
	    	if (mMediaPlayer != null) {
	    		mMediaPlayer.release();
	    	}
	    }
	    
		@Override
    	public boolean pause() {
	    	if (mMediaPlayer != null) {
	    		mMediaPlayer.pause();
	    		return true;
	    	} else {
	    		return false;
	    	}
		}
		
		@Override
    	public void restart() {
	    	if (mMediaPlayer != null) {
	    		mMediaPlayer.start();
	    	}
		}
	    
		@Override
		public void getCurrentPositionAudioPlay(Rational rational) {
			rational.num = mMediaPlayer.getCurrentPosition();
			rational.den = 1000;	// ms
		}
		
		@Override
		public void getCurrentPositionAudioDecode(Rational rational) {
			// playの時間を代わりに使う
			getCurrentPositionAudioPlay(rational);
		}
		
		@Override
    	public void seekBySecond(int second) {
	    	if (mMediaPlayer != null) {
	    		mMediaPlayer.seekTo(second * 1000);
	    	}
		}
		
		@Override
    	public void clearBuffer() {
	    	if (mMediaPlayer != null) {
//	    		final boolean lastIsPlaying = mMediaPlayer.isPlaying();
//	    		mMediaPlayer.stop();
//	    		if (lastIsPlaying) {
//		    		mMediaPlayer.start();
//	    		}
	    	}
		}
    }
    private AudioPlayer mAudioPlayer;
    private void createAudioPlayerIfNeeded() {
    	if (mAudioPlayer == null) {
    		if (mUseMediaPlayerForAudio) {
        		mAudioPlayer = new SoundStreamListenerMediaPlayer();
			} else {
				mAudioPlayer = new SoundStreamListenerFFmpeg();
			}
    	}
    }
    public SwfPlayer.SoundStreamListener getSoundStreamListener() {
    	createAudioPlayerIfNeeded();
    	return mAudioPlayer;
    }
    
    // Nativeメソッド
    
    private native long createNative();
    private native void destroyNative(long nativeInstance);
    private native void loadStreamADPCM(long nativeInstance, byte[] buffer, FFmpegIOCallback ioCallback,
    		int timeBaseNumerator, int timeBaseDenominator,
    		int sampleRate, int channels);
    private native void loadStreamMP3(long nativeInstance, byte[] buffer, FFmpegIOCallback ioCallback,
    		int timeBaseNumerator, int timeBaseDenominator,
    		int sampleRate, int channels);
    private native void reopenInputStream(long nativeInstance, byte[] buffer, FFmpegIOCallback ioCallback);
    /*private*/ native int readFrame(long nativeInstance,
    		byte[] readBuffer, FFmpegIOCallback ioCallback);
    /*private*/ native boolean decodeAudio(long nativeInstance,
    		FFmpegData data);
    
    static {
    	System.loadLibrary("nicoro-jni");
    }
}
