package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.FFmpegVideoDecoder.CODE_DECODE_FRAME_AUDIO;
import static jp.sourceforge.nicoro.FFmpegVideoDecoder.CODE_DECODE_FRAME_ERROR_OR_END;
import static jp.sourceforge.nicoro.Log.LOG_TAG;
import static jp.sourceforge.nicoro.PlayerConstants.*;

import android.content.Context;
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.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

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.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;

import jp.gr.java_conf.shiseissi.commonlib.ViewUtil;
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.SWFFileHeaderMovie;
import jp.sourceforge.nicoro.swf.SoundStreamHead;
import jp.sourceforge.nicoro.swf.StreamSoundDataBase;
import jp.sourceforge.nicoro.swf.SwfPlayer;

public class SwfPlayerFragment extends AbstractPlayerFragment
implements SwfPlayer.PlayerParent {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & false;
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;

    /** Surfaceの準備完了 */
    private static final int MSG_ID_SURFACE_CREATED = MSG_ID_SUB_OFFSET + 1;
    /** Surfaceが破棄された */
    private static final int MSG_ID_SURFACE_DESTROYED = MSG_ID_SUB_OFFSET + 2;
    /** swfファイルを再生前に事前展開して準備完了 */
    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 SurfaceHolder.Callback mSurfaceCallback;
    /*private*/ volatile boolean mIsFinish;

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

    private int mDrawOffsetY = 0;

    private boolean mIsSurfaceOk;
    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 int mDisplayWidth = 0;
    private int mDisplayHeight = 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();

    @SuppressWarnings("unused")
    private Rational mRationalDebugLog = (DEBUG_LOGV | DEBUG_LOGD)
        ? new Rational() : null;

    private boolean mUseMediaPlayerForAudio;

    private volatile int mRequestSeekSecond = -1;
    private volatile boolean mRequestSeek = false;

    private DisplayMetrics mMetrics = new DisplayMetrics();
    private ComputeVideoDisplaySize mComputeVideoDisplaySize =
        new ComputeVideoDisplaySize();

    private class SwfThread implements Runnable {
        @Override
        public void run() {
            Handler handler = mHandler;
            try {
                mSwfPlayer.runMainLoop(mInSwf);
                if (!isFinish()) {
                    handler.sendEmptyMessage(MSG_ID_PLAY_FINISHED);
                }
            } catch (IOException e) {
                Log.e(LOG_TAG, e.toString(), e);
                handler.obtainMessage(
                        MSG_ID_PLAY_ERROR,
                        getString(R.string.errormessage_player_io)
                        ).sendToTarget();
            } catch (OutOfMemoryError e) {
                Log.e(LOG_TAG, e.toString(), e);
                handler.obtainMessage(
                        MSG_ID_PLAY_ERROR,
                        getString(R.string.errormessage_player_outofmemory)
                        ).sendToTarget();
            } catch (FailAnalyzeSwfException e) {
                Log.e(LOG_TAG, e.toString(), e);
                handler.obtainMessage(
                        MSG_ID_PLAY_ERROR,
                        getString(R.string.errormessage_player_analyzeswf)
                        ).sendToTarget();
            }
        }
    }

    private class PrepareSwfThread implements Runnable {
        @Override
        public void run() {
            Handler handler = mHandler;
            if (!VideoLoader.isStreamTempDirWritable(mContext)) {
                Message message = handler.obtainMessage(
                        MSG_ID_PLAY_ERROR,
                        getString(R.string.errormessage_player_strage_unmount));
                handler.sendMessage(message);
                return;
            }

            try {
                createAudioPlayerIfNeeded();
                String file = mVideoLoader.getFilePath();

                File outTempFile = new File(VideoLoader.getStreamTempDir(mContext), FWS_TEMP_FILE);
                File inFile = new File(file);
                // TODO ファイル取得中にスレッドを途中終了させる方法がない
                if (SwfPlayer.getFWSFile(inFile, outTempFile)) {
                    inFile = outTempFile;
                }
                mSwfFile = inFile;
                mAudioPlayer.start(inFile);

                handler.sendEmptyMessage(MSG_ID_SWF_PREPARED);
            } catch (IOException e) {
                Log.e(LOG_TAG, e.toString(), e);
                Message message = handler.obtainMessage(
                        MSG_ID_PLAY_ERROR,
                        getString(R.string.errormessage_player_io));
                handler.sendMessage(message);
            } catch (FailAnalyzeSwfException e) {
                Log.e(LOG_TAG, e.toString(), e);
                Message message = handler.obtainMessage(
                        MSG_ID_PLAY_ERROR,
                        getString(R.string.errormessage_player_analyzeswf));
                handler.sendMessage(message);
            }
        }
    }

    @Override
    public boolean handleMessage(Message msg) {
        PlayerActivity playerActivity = getPlayerActivity();
        switch (msg.what) {
        case MSG_ID_VIDEO_DOWNLOAD_FINISHED:
            super.handleMessage(msg);
            if (playerActivity != null) {
                playerActivity.setProgressTextInnerSwfPrepareWait();
            }
            mThreadPrepareSwf = new Thread(new PrepareSwfThread());
            mThreadPrepareSwf.start();
            if (canStartPlay()) {
                startPlay();
            }
            break;
        case MSG_ID_SURFACE_CREATED:
            mIsSurfaceOk = true;
            if (!mIsPlaying && canStartPlay()) {
                startPlay();
            }
            break;
        case MSG_ID_SURFACE_DESTROYED:
            mIsSurfaceOk = false;
            break;
        case MSG_ID_SWF_PREPARED:
            if (playerActivity != null) {
                playerActivity.setProgressTextInnerSwfPrepareFinished();
            }
            mIsPrepareSwfOk = true;
            if (canStartPlay()) {
                startPlay();
            }
            break;
        default:
            super.handleMessage(msg);
            break;
        }
        return true;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        initializeVideoLoader();

        mIsSurfaceOk = false;
        mIsPrepareSwfOk = false;

        SharedPreferences sharedPreferences = Util.getDefaultSharedPreferencesMultiProcess(mContext);
        mUseMediaPlayerForAudio = sharedPreferences.getBoolean(
                getString(R.string.pref_key_swf_mediaplayer), true);

        mSwfPlayer.create(this);

        mIsPlaying = false;
        mIsPause = false;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_swfplayer, container, false);

        mSurfaceView = ViewUtil.findViewById(view, R.id.surface);
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceCallback = 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 (DEBUG_LOGD) {
                    Log.d(LOG_TAG, "surfaceCreated");
                }
                Handler handler = mHandler;
                handler.sendEmptyMessage(MSG_ID_SURFACE_CREATED);
            }
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, "surfaceDestroyed");
                }
                Handler handler = mHandler;
                handler.sendEmptyMessage(MSG_ID_SURFACE_DESTROYED);
//                mIsFinish = true;
//                mStreamAudioPlayer.finish();
            }
        };
        mSurfaceHolder.addCallback(mSurfaceCallback);
        updateSurfaceSize(getResources().getConfiguration().orientation);

        return view;
    }

    @Override
    public void onDestroyImpl() {
        mIsFinish = true;
        // TODO 終了処理の高速化
        if (mThreadPrepareSwf != null) {
            try {
                // 完全に止めるためタイムアウト無しで
                mThreadPrepareSwf.join();
            } catch (InterruptedException e) {
                Log.d(LOG_TAG, e.toString(), e);
            }
            mThreadPrepareSwf = null;
        }
        if (mThreadSwf != null) {
            try {
                // 完全に止めるためタイムアウト無しで
                mThreadSwf.join();
            } catch (InterruptedException e) {
                Log.d(LOG_TAG, e.toString(), e);
            }
            mThreadSwf = null;
        }
        mAudioPlayer.finish();
        super.onDestroyImpl();
        if (mInSwf != null) {
            try {
                mInSwf.close();
            } catch (IOException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
            mInSwf = null;
        }

        if (mSurfaceHolder != null) {
            assert mSurfaceCallback != null;
            mSurfaceHolder.removeCallback(mSurfaceCallback);
            mSurfaceHolder = null;
        }
        mSurfaceCallback = null;

//      // test
//      if (mTestOutMP3 != null) {
//          try {
//              mTestOutMP3.close();
//          } catch (IOException e) {
//              // TODO 自動生成された catch ブロック
//              e.printStackTrace();
//          }
//          mTestOutMP3 = null;
//      }
    }

//    @Override
//    protected void finalize() throws Throwable {
//      try {
//            if (DEBUG_LOGV) {
//                Log.v(LOG_TAG, "NicoroSwfPlayer#finalize start");
//            }
//          super.finalize();
//      } finally {
//          mIsFinish = true;
//          try {
//              if (mThreadPrepareSwf != null) {
//                  // 完全に止めるためタイムアウト無しで
//                  mThreadPrepareSwf.join();
//              }
//          } catch (InterruptedException e) {
//              Log.d(LOG_TAG, e.toString(), e);
//          }
//          try {
//              if (mThreadSwf != null) {
//                    // 完全に止めるためタイムアウト無しで
//                  mThreadSwf.join();
//              }
//          } catch (InterruptedException e) {
//              Log.d(LOG_TAG, e.toString(), e);
//          }
//            if (DEBUG_LOGV) {
//                Log.v(LOG_TAG, "NicoroSwfPlayer#finalize start");
//            }
//      }
//    }

    @Override
    protected void onOrientationChanged(int orientation) {
        // layout整ってから設定
//        updateSurfaceSize(orientation);
    }

    @Override
    public void onParentScreenLayout(int width, int height) {
        if (width != mDisplayWidth || height != mDisplayHeight) {
            updateSurfaceSize(getResources().getConfiguration().orientation);
        }
    }

    private void updateSurfaceSize(int orientation) {
        PlayerActivity playerActivity = getPlayerActivity();
        if (playerActivity == null) {
            return;
        }
        RelativeLayout parentScreen = playerActivity.getParentScreen();
        int displayWidth = parentScreen.getWidth();
        int displayHeight = parentScreen.getHeight();
        if (displayWidth <= 0 || displayHeight <= 0) {
            return;
        }
        mDisplayWidth = displayWidth;
        mDisplayHeight = displayHeight;
        mComputeVideoDisplaySize.compute(
                displayWidth, displayHeight,
                displayWidth, displayHeight,
                orientation, false);
        ViewGroup.LayoutParams params = mSurfaceView.getLayoutParams();
        params.width = mComputeVideoDisplaySize.getWidth();
        params.height = mComputeVideoDisplaySize.getHeight();
//        mSurfaceHolder.setFixedSize(params.width, params.height);
        mSurfaceView.requestLayout();
        onVideoViewSizeChanged(params.width, params.height);
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("NicoroSwfPlayer: SurfaceView width=").append(params.width)
                    .append(" height=").append(params.height).toString());
        }
    }

    @Override
    public 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(mRationalCurrentPlayTime);
            final long posNum = mRationalCurrentPlayTime.num;
            final int posDen = mRationalCurrentPlayTime.den;
            return appendCurrentPlayTimeCommon(builder, posNum, posDen);
        }
    }

    @Override
    public boolean hasTotalPlayTime() {
        return true;
    }

    @Override
    protected boolean canStartPlay() {
        if (mStateManager.isStarting()) {
            return (mIsSurfaceOk && mIsVideoDownloadOk
                    && mMessageChatController.isMessageDataOk()
                    && mIsPrepareSwfOk);
        } else if (mStateManager.wasCreated() && !mStateManager.wasDestroyed()) {
            // バックグラウンド中
            return (mIsVideoDownloadOk
                    && mMessageChatController.isMessageDataOk()
                    && mIsPrepareSwfOk);
        } else {
            return false;
        }
    }

    @Override
    protected void startPlay() {
        super.startPlay(mThumbInfo);
        Util.gcStrong();

        mSwfPlayer.preparePlay();
        mIsFirstShowFrame = true;
        mStartFrameTime = 0L;

        mIsPlaying = true;
        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.toString(), e);
        } catch (FailAnalyzeSwfException e) {
            Log.e(LOG_TAG, e.toString(), e);
        }

        mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
        postStartPlayIfIsRestored();
    }

    @Override
    public 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;

        if (!isFinish() && mThreadSwf != null && !mThreadSwf.isAlive()) {
            assert mInSwf != null;
            assert mSwfPlayer != null;
            try {
                mInSwf.seek(0);
                mSwfPlayer.preparePlay();
                mSwfPlayer.readSWFFileHeaderWhole(mInSwf);
                mThreadSwf = new Thread(new SwfThread());
                mThreadSwf.start();
            } catch (IOException e) {
                Log.e(LOG_TAG, e.toString(), e);
            } catch (FailAnalyzeSwfException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
        }
    }

    @Override
    protected VideoLoaderInterface.EventListener createVideoLoaderEventListener() {
        return new VideoLoaderInterface.EventListener() {
            @Override
            public void onStarted(VideoLoaderInterface streamLoader) {
                // 何もしない
            }
            @Override
            public void onCached(VideoLoaderInterface videoLoader) {
                // nothing
            }
            @Override
            public void onFinished(VideoLoaderInterface videoLoader) {
                Handler handler = mHandler;
                handler.sendEmptyMessage(MSG_ID_VIDEO_DOWNLOAD_FINISHED);
            }
            @Override
            public void onOccurredError(VideoLoaderInterface videoLoader, String errorMessage) {
                Handler handler = mHandler;
                handler.obtainMessage(MSG_ID_VIDEO_OCCURRED_ERROR,
                        errorMessage).sendToTarget();
            }
            @Override
            public void onNotifyProgress(int num, int den) {
                Handler handler = mHandler;
//                if (!mIsPlaying) {
                    handler.removeMessages(MSG_ID_VIDEO_NOTIFY_PROGRESS);
                    handler.obtainMessage(MSG_ID_VIDEO_NOTIFY_PROGRESS,
                            num, den).sendToTarget();
//                }
            }
            @Override
            public void onRestarted(VideoLoaderInterface streamLoader) {
                // 何もしない
            }
        };
    }

    @Override
    protected int getVpos() {
        SWFFileHeaderMovie headerMovie = mSwfPlayer.mHeaderMovieSwf;
        if (headerMovie == null) {
            // XXX 再生可能な状態以前の問題なのでとりあえず0返す
            return 0;
        }
        return (int) (100 * mSwfPlayer.getFrameCount()
                / ((float) ((headerMovie.frameRateIntegral << 8)
                        | headerMovie.frameRateDecimal) / 256.0f));
    }

    @Override
    public StringBuilder appendVideoResolution(StringBuilder builder) {
        // swfは情報無しで
        return builder;
    }

    @Override
    public 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);
    }

    @Override
    public boolean isFinish() {
        return mIsFinish;
    }

    @Override
    public boolean isPause() {
        return mIsPause;
    }

    @Override
    public boolean isSeeking() {
        return mRequestSeek;
    }

    @Override
    public int getSeekTimeBySecond() {
        return mRequestSeekSecond;
    }

    @Override
    public void notifySeekCompleted() {
        seekBySecondCommon(mRequestSeekSecond);
        mAudioPlayer.seekBySecond(mRequestSeekSecond);
        updateStartFrameTime();
        mRequestSeekSecond = -1;
        mRequestSeek = false;

        // シークバー再有効化
        Handler handler = mHandler;
        handler.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;
//    }

    @Override
    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) {
                }
            }
        }

        SurfaceHolder surfaceHolder = mSurfaceHolder;
        if (frameSkip) {
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, "frame skip");
            }
        } else if (surfaceHolder != null) {
            Canvas canvas = surfaceHolder.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);
                                    // TODO Android3.0で画面が大きすぎると余分にclipしてしまう？
                                    // →そもそも実装が間違っている可能性もあるため一時無効
//                                  canvas.clipRect(
//                                          defineShape3.shapeBounds.xmin,
//                                          defineShape3.shapeBounds.ymin,
//                                          defineShape3.shapeBounds.xmax,
//                                          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
                                    String error;
                                    if (defineShapeBase == null) {
                                        error = "TODO: defineShapeBase is null.";
                                    } else {
                                        error = "TODO: " + defineShapeBase.toString() + " is umimplemented.";
                                    }
                                    throw new FailAnalyzeSwfException(error);
                                }
                            }
                            if (placeObject2.placeFlagMove) {
                                // TODO
                                throw new FailAnalyzeSwfException("TODO: PlaceObject2 PlaceFlagMove is umimplemented.");
                            }


                        } else {
                            // TODO
                            throw new FailAnalyzeSwfException("TODO: " + placeObject.toString() + " is umimplemented.");
                        }
                    }

                    // とりあえずコメント内部表示
                    mMessageChatController.drawMessage(canvas,
                            getVpos(),
    //                      mWidth, mHeight);
                            canvas.getWidth(), canvas.getHeight(),
                            getMessageDisable());
                } finally {
                    surfaceHolder.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.concat(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*/ final StreamAudioPlayer mStreamAudioPlayer;

        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.toString(), e);
                } finally {
                    synchronized (this) {
                        destroyNativeInstance();
                    }
                }
            }
        }

        SoundStreamListenerFFmpeg(Context context) {
            mStreamAudioPlayer = new StreamAudioPlayer(context);
        }

        @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.waitRealStartAtStart();
            }
        }

        @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();
                } catch (InterruptedException e) {
                    Log.d(LOG_TAG, e.toString(), e);
                }
                mSoundStreamThread = null;
            }
            synchronized (this) {
                destroyNativeInstance();
            }
        }

        @Override
        public boolean pause() {
            if (mStreamAudioPlayer.isInDummyDataAtStart()) {
                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 {
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, "NicoroJikkyoPlayer$SoundStreamListenerFFmpeg#finalize start");
                }
                super.finalize();
            } finally {
                mStreamAudioPlayer.finish();
                if (mSoundStreamThread != null) {
                    try {
                        synchronized (mSoundStreamThread) {
//                          mSoundStreamThread.notify();
                            mSoundStreamThread.notifyAll();
                        }
                        // 完全に止めるためタイムアウト無しで
                        mSoundStreamThread.join();
                    } catch (InterruptedException e) {
                        Log.d(LOG_TAG, e.toString(), e);
                    }
                }
                synchronized (this) {
                    destroyNativeInstance();
                }
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, "NicoroJikkyoPlayer$SoundStreamListenerFFmpeg#finalize end");
                }
            }
        }

        private void startAudio(SoundStreamHead soundStreamHead) throws FailAnalyzeSwfException {
            // FFmpeg生成
            destroyNativeInstance();
            mNativeInstance = createNative();

            int sampleRate = getSampleRateInHzFromStreamSoundRate(soundStreamHead.streamSoundRate);
            int channels = getChannelsFromStreamSoundType(soundStreamHead.streamSoundType);

            // Audio開始
            try {
                mStreamAudioPlayer.startBySwf(sampleRate, channels);
            } catch (IllegalArgumentException e) {
                // TODO AudioTrackのパラメータエラーはそのまま通知
                // 本来は非対応サンプルレートの音声の場合は変換して再生するべき
                String error = e.toString();
                Log.e(LOG_TAG, error, e);
                throw new FailAnalyzeSwfException(error);
            }

            // AudioBuffer準備
            int audioBufferByteSize = mStreamAudioPlayer.getAudioBufferByteSize();
//            mFFmpegData.mAudioBuffer = new short[audioBufferByteSize/2];
            mFFmpegData.mAudioBuffer = ByteBuffer.allocateDirect(audioBufferByteSize);
            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
            implements MediaPlayer.OnSeekCompleteListener {
        private static final int SPARE_TIME_MS = 500;

        private MediaPlayer mMediaPlayer;
        private Rational mVideoPosition = new Rational();
        private int mFrameCountSound;
        private boolean mSyncWaiting;
//      private boolean mReservePause;
        private boolean mIsSeeking;

        private File mAudioFile;

        @Override
        public void onSoundStreamHead(
                SoundStreamHead soundStreamHead,
                SwfPlayer swfPlayer) {
            mFrameCountSound = 0;
            mSyncWaiting = false;

            // TODO 判定がわかりにくい
            MediaPlayer mediaPlayer = mMediaPlayer;
            if (!mRequestSeek || mediaPlayer == null) {
//          if (true) {
                mediaPlayer = new MediaPlayer();
                mMediaPlayer = mediaPlayer;
                try {
                    mediaPlayer.setOnSeekCompleteListener(this);
                    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                    mediaPlayer.setDataSource(mAudioFile.getAbsolutePath());
                    mediaPlayer.prepare();
                    mediaPlayer.start();    // いったん開始しておく
//                  if (mReservePause) {
                    mediaPlayer.pause();
//                      mReservePause = false;
//                  }
                } catch (IllegalArgumentException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                } catch (IllegalStateException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                } catch (IOException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                }
                mIsSeeking = false;
            }
        }
        @Override
        public void onSoundStreamBlock(
                StreamSoundDataBase streamSoundData,
                SoundStreamHead soundStreamHead,
                SwfPlayer swfPlayer, boolean frameSkip) {
            if (!frameSkip) {
                getCurrentPositionVideoPlay(mVideoPosition);
                int videoPositionMs = mVideoPosition.getMs();

                // 描画と同期
                MediaPlayer mediaPlayer = mMediaPlayer;
                if (mediaPlayer != null) {
                    final int diffPos = mediaPlayer.getCurrentPosition() - videoPositionMs;
                    if (diffPos > SPARE_TIME_MS) {
                        // 止まって待つ
                        if (!mSyncWaiting) {
                            mediaPlayer.pause();
                            mSyncWaiting = true;
                        }
                    } else if (diffPos < -SPARE_TIME_MS) {
                        if (!mIsSeeking) {
                            // 位置調整
                            mIsSeeking = true;
                            mediaPlayer.seekTo(videoPositionMs);
                        }
                        if (!mediaPlayer.isPlaying()) {
                            mediaPlayer.start();
                        }
                        mSyncWaiting = false;
                    } else {
                        if (mSyncWaiting) {
                            if (diffPos <= 0) {
                                mediaPlayer.start();
                                mSyncWaiting = false;
                            }
                        } else {
                        }
                    }
                }
            }

            ++mFrameCountSound;
        }

        @Override
        public void start(File swfFile) throws IOException, FailAnalyzeSwfException {
            File outAudioFile = new File(VideoLoader.getStreamTempDir(mContext), 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.toString(), e);
                    }
                }
            }
        }

        @Override
        public void finish() {
            MediaPlayer mediaPlayer = mMediaPlayer;
            if (mediaPlayer != null) {
                mediaPlayer.release();
                mMediaPlayer = null;
            }
        }

        @Override
        public boolean pause() {
            MediaPlayer mediaPlayer = mMediaPlayer;
            if (mediaPlayer == null) {
//              mReservePause = true;
            } else {
                mediaPlayer.pause();
            }
            return true;
        }

        @Override
        public void restart() {
            MediaPlayer mediaPlayer = mMediaPlayer;
            if (mediaPlayer != null) {
                mediaPlayer.start();
            }
//          mReservePause = false;
        }

        @Override
        public void getCurrentPositionAudioPlay(Rational rational) {
            MediaPlayer mediaPlayer = mMediaPlayer;
            if (mediaPlayer == null) {
                rational.num = 0;
                rational.den = 1;
            } else {
                rational.num = mediaPlayer.getCurrentPosition();
                rational.den = 1000;    // ms
            }
        }

        @Override
        public void getCurrentPositionAudioDecode(Rational rational) {
            // playの時間を代わりに使う
            getCurrentPositionAudioPlay(rational);
        }

        @Override
        public void seekBySecond(int second) {
            MediaPlayer mediaPlayer = mMediaPlayer;
            if (mediaPlayer != null) {
                mIsSeeking = true;
                mediaPlayer.seekTo(second * 1000);
            }
        }

        @Override
        public void clearBuffer() {
            MediaPlayer mediaPlayer = mMediaPlayer;
            if (mediaPlayer != null) {
//              final boolean lastIsPlaying = mMediaPlayer.isPlaying();
//              mMediaPlayer.stop();
//              if (lastIsPlaying) {
//                  mMediaPlayer.start();
//              }
            }
        }

        // MediaPlayer.OnSeekCompleteListener

        @Override
        public void onSeekComplete(MediaPlayer mp) {
            mIsSeeking = false;
        }
    }
    private AudioPlayer mAudioPlayer;
    private void createAudioPlayerIfNeeded() {
        if (mAudioPlayer == null) {
            if (mUseMediaPlayerForAudio) {
                mAudioPlayer = new SoundStreamListenerMediaPlayer();
            } else {
                mAudioPlayer = new SoundStreamListenerFFmpeg(mContext);
            }
        }
    }

    @Override
    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 {
        FFmpegVideoDecoder.loadNativeLibrary();
    }
}
