package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import static jp.sourceforge.nicoro.PlayerConstants.*;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.speech.RecognizerIntent;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import jp.gr.java_conf.shiseissi.commonlib.APILevelWrapper;
import jp.gr.java_conf.shiseissi.commonlib.ViewUtil;
import jp.sourceforge.nicoro.StaticRes.integer;

public class PlayerActivity extends FragmentActivity
implements DestroyTask.Callback, Handler.Callback,
PlaylistDialogFragment.Interface {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;

    private static final long INTERVAL_TIME_UPDATE = 100L;
    private static final long INTERVAL_NOTIFICATION_TIME_UPDATE = 3000L;

    private static final String WAKELOCK_TAG_SUFFIX = "WakeLock";

    private static final String TAG_PLAYER_INFO_DIALOG = "player_info_dialog";
    private static final String TAG_PLAYLIST_DIALOG = "playlist_dialog";

    private Context mContext;
    private SharedPreferences mSharedPreferences;

    private PlayerParentScreenLayout mParentScreen;
    private PlayerProgressViews mProgressViews = new PlayerProgressViews();
//    private PlayerInfoViews mInfoViews = new PlayerInfoViews();
    private PlayerInfoMiniViews mInfoMiniViews = new PlayerInfoMiniViews();
    private PlayerControllerViews mPlayerControllerViews = new PlayerControllerViews();
    private View mVideoMarginLeft;
    private ViewGroup mPlayerInfoArea;

    private PlayerInfoControllerManager mPlayerInfoControllerManager;

    private AlertDialog mPlayFinishDialog;
    private AlertDialog mErrorDialog;

    private Rational mRationalCurrentPlayTime = new Rational();

    private final HandlerWrapper mHandler = new HandlerWrapper(this);

    private boolean mMessageDisable;
    private boolean mPauseCommentWrite;
    private boolean mEnableHardwareAccelerated;

    private StateManager mStateManager = new StateManager();
    private boolean mDidStartPlay = false;
    /** 生放送再生か否か */
    private boolean mIsLive;
//    private boolean mMoveTaskToBackAfterPlay;

    private boolean mIsThumbInfoOk;

    private int mLastOrientation;

    private PowerManager.WakeLock mWakeLock;

    private PhoneStateListener mPhoneStateListener;

    private AbstractPlayerFragment mPlayerFragment;
    private AbstractPlayerFragment mNextPlayerFragment;

    private NotificationController mNotificationController;

    private LivePlayerFragment.InfoData mLiveInfoData;

    private boolean mResBoolHideActionBarVisible;

    private Playlist mPlaylist;

    private NewPlayerFragmentCreator mNewPlayerFragmentCreator;

    private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                int level = intent.getIntExtra("level", -1);
                int scale = intent.getIntExtra("scale", -1);

                mInfoMiniViews.updateBattery(level, scale);
            }
        }
    };

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_ID_MESSAGE_FINISHED:
                mProgressViews.setTextMessageFinished();
                break;
            case MSG_ID_MESSAGE_OCCURRED_ERROR:
                mProgressViews.setTextMessageError();
                showErrorDialog((String) msg.obj);
                break;
            case MSG_ID_THUMBINFO_FINISHED: {
                ThumbInfoInterface thumbInfo = (ThumbInfoInterface) msg.obj;
                boolean isVideoDownloadOk = (msg.arg1 != 0);
                mProgressViews.setTextThumbinfoFinished();

                mPlayerControllerViews.setSeekBarMax(thumbInfo.getLengthBySecond());
                mPlayerInfoControllerManager.setTitle(thumbInfo.getParsedTitle());
                mIsThumbInfoOk = true;

                if (isVideoDownloadOk) {
                    mPlayerControllerViews.setSeekBarSecondaryMax();
                }

                setEnableVideoInfo(true);

                FragmentManager fm = getSupportFragmentManager();
                PlayerInfoDialogFragment fragment =
                    ViewUtil.findFragmentById(fm, R.id.video_info_area);
                if (fragment != null) {
                    setThumbInfoToPlayerInfoDialogFragment(thumbInfo,
                            fragment);
                }
                fragment =
                    ViewUtil.findFragmentByTag(fm, TAG_PLAYER_INFO_DIALOG);
                if (fragment != null) {
                    setThumbInfoToPlayerInfoDialogFragment(thumbInfo,
                            fragment);
                }
            } break;
            case MSG_ID_THUMBINFO_OCCURRED_ERROR:
                mProgressViews.setTextThumbinfoError();
                showErrorDialog((String) msg.obj);
                break;
            case MSG_ID_VIDEO_OCCURRED_ERROR:
                mProgressViews.setTextVideoError();
                showErrorDialog((String) msg.obj);
                break;
            case MSG_ID_VIDEO_NOTIFY_PROGRESS:
                mProgressViews.setTextVideoNotifyProgress(
                        msg.arg1, msg.arg2);
                mPlayerControllerViews.setSeekBarSecondaryProgress(msg.arg1, msg.arg2);
                break;
            case MSG_ID_INFO_TIME_UPDATE:
                if (!mStateManager.wasDestroyed()) {
                    AbstractPlayerFragment playerFragment = mPlayerFragment;
                    if (playerFragment != null && playerFragment.didStartPlay()) {
                        mInfoMiniViews.setTime(playerFragment);

                        if (!mIsLive) {
                            mPlayerControllerViews.setSeekBarMax(
                                    playerFragment.getThumbInfo().getLengthBySecond());
                            if (mPlayerControllerViews.canUpdateSeekBarProgress()) {
                                // TODO: 二重呼び出しでちょっと無駄
                                playerFragment.getCurrentPositionVideoPlay(
                                        mRationalCurrentPlayTime);
                                mPlayerControllerViews.setSeekBarProgress(
                                        (int) (mRationalCurrentPlayTime.num / mRationalCurrentPlayTime.den));
                            }
                        }
                    }

                    // 繰り返し
                    mHandler.sendEmptyMessageDelayed(
                            MSG_ID_INFO_TIME_UPDATE, INTERVAL_TIME_UPDATE);
                }
                break;
            case MSG_ID_PLAY_ERROR:
                showErrorDialog((String) msg.obj);
                break;
            case MSG_ID_ENABLE_SEEK_BAR:
                mPlayerControllerViews.setEnabledSeekController(true);
                break;
            case MSG_ID_INFO_PLAY_DATA_UPDATE: {
                AbstractPlayerFragment playerFragment = mPlayerFragment;
                if (playerFragment != null) {
                    FragmentManager fm = getSupportFragmentManager();
                    PlayerInfoDialogFragment fragment =
                        ViewUtil.findFragmentById(fm, R.id.video_info_area);
                    if (fragment != null) {
                        StringBuilder playData = new StringBuilder();
                        Resources res = getResources();
                        PlayerInfoDialogFragment.setPlayData(playerFragment,
                                playData, res);
                        fragment.setPlayData(playData.toString());
                    }
                    fragment =
                        ViewUtil.findFragmentByTag(fm, TAG_PLAYER_INFO_DIALOG);
                    if (fragment != null) {
                        StringBuilder playData = new StringBuilder();
                        Resources res = getResources();
                        PlayerInfoDialogFragment.setPlayData(playerFragment,
                                playData, res);
                        fragment.setPlayData(playData.toString());
                    }
                }
            } break;
            case MSG_ID_PLAY_FINISHED_DIALOG: {
                DialogInterface.OnCancelListener onCancelListener =
                    new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        dialog.dismiss();
                        mPlayFinishDialog = null;
                    }
                };
                View.OnClickListener onClickListener = new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mPlayFinishDialog != null) {
                            mPlayFinishDialog.dismiss();
                            mPlayFinishDialog = null;
                        }
                        Handler handler = mHandler;
                        handler.removeMessages(MSG_ID_AUTO_CLOSE);
                        switch (v.getId()) {
                            case R.id.button_quit:
                                finish();
                                break;
                            case R.id.button_replay:
                                rewindToBegin();
                                restartPlay();
                                break;
                            case R.id.button_dialog_close:
                                break;
                            default:
                                assert false : v.getId();
                                break;
                        }
                    }
                };
                Activity activity = PlayerActivity.this;
                LayoutInflater inflater = Util.getInflaterForDialogDark(activity);
                // LayoutParams生成のためダミーのViewも使用
                View playFinish = inflater.inflate(
                        R.layout.play_finish_dialog,
                        new FrameLayout(activity), false);
                playFinish.findViewById(R.id.button_quit).setOnClickListener(onClickListener);
                playFinish.findViewById(R.id.button_replay).setOnClickListener(onClickListener);
                playFinish.findViewById(R.id.button_dialog_close).setOnClickListener(onClickListener);
                mPlayFinishDialog = new AlertDialog.Builder(activity)
                    .setTitle(R.string.dialog_title_player_finish)
                    .setView(playFinish)
                    .setCancelable(true)
                    .setOnCancelListener(onCancelListener)
                    .show();
                mHandler.removeMessages(MSG_ID_AUTO_CLOSE);
                mHandler.sendEmptyMessageDelayed(MSG_ID_AUTO_CLOSE, 30000);
            } break;
            case MSG_ID_AUTO_CLOSE:
                finish();
                break;
            case MSG_ID_VIDEO_DOWNLOAD_FINISHED:
                mProgressViews.setTextVideoDownloadFinished();
                if (mIsThumbInfoOk) {
                    mPlayerControllerViews.setSeekBarSecondaryMax();
                }
                break;
            case MSG_ID_LIVE_MESSAGE_CONNECTED:
                mProgressViews.setTextMessageLiveConnected();
                break;
            case MSG_ID_LIVE_MESSAGE_OCCURRED_ERROR:
                mProgressViews.setTextMessageLiveError();
                break;
            case MSG_ID_GET_PLAYER_STATUS_SUCCEEDED: {
                LivePlayerFragment.InfoData infoData =
                    (LivePlayerFragment.InfoData) msg.obj;
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, infoData.toString());
                }
                mProgressViews.setTextThumbinfoFinished();
                mLiveInfoData = infoData;
                setEnableVideoInfo(true);

                FragmentManager fm = getSupportFragmentManager();
                PlayerInfoDialogFragment fragment =
                    ViewUtil.findFragmentById(fm, R.id.video_info_area);
                if (fragment != null) {
                    setLivePlayerInfoDataToPlayerInfoDialogFragment(infoData,
                            fragment);
                }
                fragment =
                    ViewUtil.findFragmentByTag(fm, TAG_PLAYER_INFO_DIALOG);
                if (fragment != null) {
                    setLivePlayerInfoDataToPlayerInfoDialogFragment(infoData,
                            fragment);
                }
            } break;
            case MSG_ID_FORBID_SEEK_BY_CONTRIBUTOR:
                mPlayerControllerViews.setEnabledSeekController(false);
                mPlayerControllerViews.setContributorForbidSeekVisibility(true);
                break;
            case MSG_ID_ALLOW_SEEK_BY_CONTRIBUTOR:
                mPlayerControllerViews.setEnabledSeekController(true);
                mPlayerControllerViews.setContributorForbidSeekVisibility(false);
                break;
            case MSG_ID_FORBID_COMMENT_BY_CONTRIBUTOR:
                String disableMessage = getString(
                        R.string.forbid_comment_by_contributor);
                disableComment(disableMessage);
                break;
            case MSG_ID_ALLOW_COMMENT_BY_CONTRIBUTOR:
                enableComment();
                break;
            case MSG_ID_NOTIFICATION_TIME_UPDATE:
                if (!mStateManager.wasDestroyed()) {
                    mNotificationController.addRunningPlayer(getClass(), mPlayerFragment);
                    if (!mStateManager.isResuming() && mPlayerFragment != null) {
                        mPlayerFragment.savePlayState();
                    }

                    // 繰り返し
                    mHandler.sendEmptyMessageDelayed(
                            MSG_ID_NOTIFICATION_TIME_UPDATE,
                            INTERVAL_NOTIFICATION_TIME_UPDATE);
                }
                break;
            case MSG_ID_SET_PLAYER_FRAGMENT_PARENT_SCREEN_SIZE: {
                AbstractPlayerFragment playerFragment = mPlayerFragment;
                if (playerFragment != null) {
                    playerFragment.onParentScreenLayout(
                            mParentScreen.getWidth(), mParentScreen.getHeight());
                }
            } break;
            case MSG_ID_NEW_PLAYER_FRAGMENT: {
                Bundle result = msg.peekData();
                mHandler.removeMessages(MSG_ID_INFO_TIME_UPDATE);
                mHandler.removeMessages(MSG_ID_NOTIFICATION_TIME_UPDATE);
                setPlayerFragment(null);

                String flvUrl = result.getString(INTENT_NAME_VIDEO_URL);
                AbstractPlayerFragment newFragment =
                    createPlayerFragment(flvUrl);
                newFragment.setArguments(result);

                AbstractPlayerFragment lastPlayerFragment = mPlayerFragment;
                replacePlayerFragment(newFragment, true);

                if (lastPlayerFragment != null) {
                    lastPlayerFragment.finishFragment();
                }
            } break;
            default:
                assert false : msg.what;
                break;
        }
        return true;
    }

    /**
     * 再生状態と一時停止状態を切り替える
     * @return 成否
     */
    protected boolean switchPausePlay() {
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment == null || !playerFragment.wasCreated()) {
            return false;
        } else {
            return playerFragment.switchPausePlay();
        }
    }

    /**
     * 再生を一時停止する
     */
    protected void pausePlay() {
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment != null && playerFragment.wasCreated()) {
            playerFragment.pausePlay();
        }
    }

    /**
     * 一時停止していた再生を再開する
     */
    protected void restartPlay() {
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment != null && playerFragment.wasCreated()) {
            playerFragment.restartPlay();
        }
    }

    /**
     * 再生が一時停止中か確認する
     * @return
     */
    protected boolean isPausePlay() {
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment == null || !playerFragment.wasCreated()) {
            return false;
        } else {
            return playerFragment.isPausePlay();
        }
    }
    /**
     * 秒単位でシークする
     * @param second
     */
    protected void seekBySecond(int second) {
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment != null) {
            playerFragment.seekBySecond(second);
        }
    }

    public void startPlay(ThumbInfoInterface thumbInfo) {
        startWakeLock();

        mProgressViews.setVisibilityGone();

        mDidStartPlay = true;

//        if (mMoveTaskToBackAfterPlay) {
//            moveTaskToBack(true);
//            mMoveTaskToBackAfterPlay = false;
//        }
    }

    public void postStartPlay(int second, int maxSecond) {
        // 再開できるようにコントローラ表示
        mPlayerInfoControllerManager.showPlayerController();
        if (second > 0) {  // 0のときはシーク不要、マイナスは時間指定無し
            // いったんシークバー他無効化
            mPlayerControllerViews.setEnabledSeekController(false);
            mPlayerControllerViews.setSeekBarMax(maxSecond);
            mPlayerControllerViews.setSeekBarProgress(second);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onCreate: savedInstanceState=")
                    .append(savedInstanceState).toString());
        }
        super.onCreate(savedInstanceState);
        mStateManager.onCreate(savedInstanceState);

        mContext = getApplicationContext();

        mSharedPreferences = Util.getDefaultSharedPreferencesMultiProcess(
                mContext);

        Resources res = getResources();
        mResBoolHideActionBarVisible = res.getBoolean(
                R.bool.menu_hide_actionbar_visible);

        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                getClass().getSimpleName() + WAKELOCK_TAG_SUFFIX);
        mWakeLock.setReferenceCounted(false);

        mNotificationController = NotificationController.getInstance(mContext);

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras == null) {
            if (savedInstanceState != null) {
                extras = savedInstanceState.getBundle(Util.KEY_EXTRAS);
            }
            if (extras == null) {
                // パラメータ無し起動：プレイヤーは終了してメインActivityへ
                Intent newIntent = new Intent(mContext, LoginActivity.class)
                    .setAction(Intent.ACTION_MAIN)
                    .addCategory(Intent.CATEGORY_DEFAULT)
                    .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
                            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                startActivity(newIntent);
                // XXX エラー回避のため直接終了する：警告Toastが必ず表示されるが修正困難
                finishReally();
                return;
            } else {
                intent.putExtras(extras);
            }
        }

        // playlistは長いことがあるのでgzipで来る
        Playlist playlist;
        if (savedInstanceState == null) {
            byte[] playlistGzipBytes = extras.getByteArray(
                    PlayerConstants.INTENT_NAME_PLAYLIST);
            playlist = Util.inflateSerializable(playlistGzipBytes);

            if (playlist != null) {
                int sortOrder = extras.getInt(
                        PlayerConstants.INTENT_NAME_PLAYLIST_SORT_ORDER, 0);
                playlist.setSortOrder(sortOrder);
                playlist.setCurrentTrack(0);
            }
        } else {
            byte[] playlistGzipBytes = savedInstanceState.getByteArray(
                    PlayerConstants.INTENT_NAME_PLAYLIST);
            playlist = Util.inflateSerializable(playlistGzipBytes);
        }
        if (playlist != null) {
            if (playlist.getItemsSize() > 0) {
                // 1曲以上ある場合のみ有効
                mPlaylist = playlist;
            }
            // TODO

            if (mPlaylist == null) {
                // エラー：プレイヤーは終了してメインActivityへ
                // XXX エラー回避のため直接終了する：警告Toastが必ず表示されるが修正困難
                finishReally();
                return;
            }
        }

        mDidStartPlay = false;
        mIsThumbInfoOk = false;
        mIsLive = intent.hasExtra(INTENT_NAME_LIVE_NUMBER);

        int screenOrientation = mSharedPreferences.getInt(
                NicoroConfig.PLAYER_SCREEN_ORIENTATION,
                ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        setScreenOrientation(screenOrientation);

        mMessageDisable = mSharedPreferences.getBoolean(
                getString(R.string.pref_key_message_disable), false);
        mPauseCommentWrite = mSharedPreferences.getBoolean(
                getString(R.string.pref_key_pause_comment_write), true);

        boolean showHintToast = mSharedPreferences.getBoolean(
                getString(R.string.pref_key_show_hint_toast), true);
        if (showHintToast) {
            Util.showInfoToast(mContext,
                    R.string.toast_explain_player_ctrl);
        }

        mEnableHardwareAccelerated = mSharedPreferences.getBoolean(
                res.getString(R.string.pref_key_enable_hardware_accelerated),
                res.getBoolean(R.bool.pref_default_enable_hardware_accelerated));
        if (mEnableHardwareAccelerated) {
            getWindow().setFlags(
                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        }

        Configuration config = res.getConfiguration();
        mLastOrientation = config.orientation;

        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(
                TELEPHONY_SERVICE);
        if (telephonyManager != null) {
            mPhoneStateListener = new PhoneStateListener() {
                private boolean pauseOnCall = false;

                @Override
                public void onCallStateChanged(int state, String incomingNumber) {
                    // XXX 再生開始前に電話がかかってきた場合の状態管理が危うい
                    // TODO 通常動画と生放送の分け方
                    switch (state) {
                        case TelephonyManager.CALL_STATE_IDLE:
                            if (mIsLive) {
                            } else {
                                if (pauseOnCall) {
                                    restartPlay();
                                    setButtonPauseImage();
                                    pauseOnCall = false;
                                }
                            }
                            break;
                        case TelephonyManager.CALL_STATE_RINGING:
                        case TelephonyManager.CALL_STATE_OFFHOOK:
                            if (mIsLive) {
                                // TODO 電話かかってきたらとりあえず生放送は完全に停止
                                finish();
                            } else {
                                // 電話かかってきたら一時停止
                                if (!isPausePlay()) {
                                    pausePlay();
                                    setButtonPauseImage();
                                    pauseOnCall = true;
                                }
                            }
                            break;
                    }

                    super.onCallStateChanged(state, incomingNumber);
                }
            };
            telephonyManager.listen(mPhoneStateListener,
                    PhoneStateListener.LISTEN_CALL_STATE);
        }

        setContentView(getLayoutResID());
        initializeView();

        if (mPlaylist == null) {
            // 通常再生
            mPlayerFragment = ViewUtil.findFragmentById(getSupportFragmentManager(),
                    R.id.video_area);
            if (mPlayerFragment == null) {
                mPlayerFragment = createPlayerFragment(
                        intent.getStringExtra(INTENT_NAME_VIDEO_URL));
                mPlayerFragment.setArguments(extras);
                getSupportFragmentManager().beginTransaction().add(
                        R.id.video_area, mPlayerFragment).commit();
            }
        } else {
            // 再生リスト
            startCurrentPlaylist();
        }
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        mStateManager.onRestart();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mStateManager.onStart();

        mPlayerInfoControllerManager.setActionBar(this);

        APILevelWrapper api = APILevelWrapper.createInstance();
        api.setDisplayHomeAsUpEnabled_ActionBar(api.getActionBar(this), true);

        setEnableVideoInfo();

        IntentFilter filterBattery = new IntentFilter(
                Intent.ACTION_BATTERY_CHANGED);
        registerReceiver(mBatteryReceiver, filterBattery);
    }


    @Override
    protected void onResume() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onResume").toString());
        }
        super.onResume();
        mStateManager.onResume();

        if (mNextPlayerFragment != null) {
            replacePlayerFragmentInner(mNextPlayerFragment);
            mNextPlayerFragment = null;
            Util.showInfoToast(mContext, R.string.toast_jump_video_background);
        }
    }


    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onSaveInstanceState: outState=")
                    .append(outState).toString());
        }
        super.onSaveInstanceState(outState);
        mStateManager.onSaveInstanceState();
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            outState.putBundle(Util.KEY_EXTRAS, extras);
        }

        if (mPlaylist != null) {
            byte[] playlistGzipBytes = Util.deflateSerializable(mPlaylist);
            outState.putByteArray(INTENT_NAME_PLAYLIST, playlistGzipBytes);
        }
    }


    @Override
    protected void onPause() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onPause").toString());
        }
        super.onPause();
        mStateManager.onPause();
    }

    @Override
    protected void onStop() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onStop").toString());
        }
        super.onStop();
        mStateManager.onStop();

        unregisterReceiver(mBatteryReceiver);
    }

    private class AsyncDestroyTask extends DestroyTask {
        private ProgressDialog progressDialog;

        AsyncDestroyTask() {
            super(PlayerActivity.this);
        }

        @Override
        protected void onPreExecuteImpl() {
            if (!mStateManager.wasDestroyed()) {
                ProgressDialog pd = Util.createProgressDialogLoading(
                        PlayerActivity.this,
                        R.string.progress_finish, null);
                pd.show();
                progressDialog = pd;
            }
        }
        @Override
        protected void onPostExecuteImpl() {
            closeProgressDialog();
        }

        @Override
        protected void timeoutImpl() {
            closeProgressDialog();
        }

        public void closeProgressDialog() {
            ProgressDialog pd = progressDialog;
            progressDialog = null;
            if (pd != null) {
                pd.dismiss();
            }
        }
    }
    private AsyncDestroyTask mDestroyTask = new AsyncDestroyTask();

    @Override
    public void finish() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#finish").toString());
        }

        if (mPlayFinishDialog != null) {
            mPlayFinishDialog.dismiss();
            mPlayFinishDialog = null;
        }
        if (mErrorDialog != null) {
            mErrorDialog.dismiss();
            mErrorDialog = null;
        }

        mDestroyTask.finishActivity();
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment != null) {
            playerFragment.finishFragment();
        }
    }

    @Override
    public void finishReally() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#finishReally").toString());
        }

        if (!mStateManager.wasDestroyed()) {
            super.finish();
        }
    }

    @Override
    protected void onDestroy() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onDestroy").toString());
        }
        mStateManager.onDestroy();

        if (!mDestroyTask.onDestroy()) {
            // 警告表示
            Util.showErrorToast(mContext,
                    R.string.toast_finish_timeout);
            mDestroyTask.closeProgressDialog();
        }
        super.onDestroy();
    }

    @Override
    public void onDestroyImplPre() {
        stopWakeLock();

        mHandler.release();

        if (mPhoneStateListener != null) {
            TelephonyManager telephonyManager = (TelephonyManager) getSystemService(
                    TELEPHONY_SERVICE);
            if (telephonyManager != null) {
                telephonyManager.listen(mPhoneStateListener,
                        PhoneStateListener.LISTEN_NONE);
            }
            mPhoneStateListener = null;
        }
    }

    @Override
    public void onDestroyImpl() {
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment != null) {
            playerFragment.waitOnDestroyImpl();
        }
    }

    @Override
    public void onDestroyImplPost() {
        if (mNewPlayerFragmentCreator != null) {
            mNewPlayerFragmentCreator.stop();
        }
    }

    @Override
    public boolean wasDestroyed() {
        return mStateManager.wasDestroyed();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (mLastOrientation != newConfig.orientation) {
            onOrientationChanged(newConfig.orientation);
            mLastOrientation = newConfig.orientation;

            if (mResBoolHideActionBarVisible) {
                ActivityCompat.invalidateOptionsMenu(this);
            }
        }
    }

    private void onOrientationChanged(int orientation) {
        ViewGroup temp = ViewUtil.inflate(getLayoutInflater(),
                getLayoutResID(),
                (ViewGroup) getWindow().getDecorView(), false);

        mProgressViews.copyLayoutParamsFrom(temp);

        mInfoMiniViews.copyLayoutParamsFrom(temp);

        mPlayerControllerViews.copyLayoutParamsFrom(temp);

        mPlayerInfoControllerManager.onOrientationChanged(orientation);

        updatePlayerInfoArea(orientation);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onTouchEvent: event=").append(event)
                    .toString());
        }

        if (mPlayerInfoControllerManager.onTouchEvent(event)) {
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onKeyDown: keyCode=").append(keyCode)
                    .append(" event=").append(event)
                    .toString());
        }

        if (mPlayerInfoControllerManager.onKeyDown(keyCode, event)) {
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append(getClass().getName())
                    .append("#onKeyUp: keyCode=").append(keyCode)
                    .append(" event=").append(event)
                    .toString());
        }
        return super.onKeyUp(keyCode, event);
    }

    @Override
    public void onBackPressed() {
        if (!mPlayerInfoControllerManager.onBackPressed()) {
            super.onBackPressed();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_RECOGNIZE_SPEECH) {
            if (resultCode == RESULT_OK) {
                ArrayList<String> results = data.getStringArrayListExtra(
                        RecognizerIntent.EXTRA_RESULTS);
                if (results.size() > 0) {
                    mPlayerControllerViews.addCommentBody(results.get(0));
                }
            }
        }
    }

    private static class OptionsMenuItem {
//        MenuItem commentOnOff;
        MenuItem videoInfo;
        MenuItem playerScreenOrientationLandscape;
        MenuItem playerScreenOrientationPortrait;
        MenuItem playerScreenOrientationUser;
        MenuItem ngShareLevelNone;
        MenuItem ngShareLevelLight;
        MenuItem ngShareLevelMiddle;
        MenuItem ngShareLevelHigh;
        MenuItem playRepeatOff;
        MenuItem playRepeatOn;
        MenuItem hideActionBar;
        MenuItem videoNext;
        MenuItem videoPrevious;
        MenuItem playlist;
    }
    private OptionsMenuItem mOptionsMenuItem;

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.player, menu);
        if (mOptionsMenuItem == null) {
            mOptionsMenuItem = new OptionsMenuItem();
        }
//        mOptionsMenuItem.commentOnOff = menu.findItem(R.id.menu_comment_onoff);
//        setMenuCommentOnOffTitle(mOptionsMenuItem.commentOnOff);
        mOptionsMenuItem.videoInfo = menu.findItem(R.id.menu_video_info);

        mOptionsMenuItem.playerScreenOrientationLandscape =
            menu.findItem(R.id.menu_player_screen_orientation_landscape);
        mOptionsMenuItem.playerScreenOrientationPortrait =
            menu.findItem(R.id.menu_player_screen_orientation_portrait);
        mOptionsMenuItem.playerScreenOrientationUser =
            menu.findItem(R.id.menu_player_screen_orientation_user);

        mOptionsMenuItem.ngShareLevelNone =
            menu.findItem(R.id.menu_ng_share_level_none);
        mOptionsMenuItem.ngShareLevelLight =
            menu.findItem(R.id.menu_ng_share_level_light);
        mOptionsMenuItem.ngShareLevelMiddle =
            menu.findItem(R.id.menu_ng_share_level_middle);
        mOptionsMenuItem.ngShareLevelHigh =
            menu.findItem(R.id.menu_ng_share_level_high);

        mOptionsMenuItem.playRepeatOff =
            menu.findItem(R.id.menu_play_repeat_off);
        mOptionsMenuItem.playRepeatOn =
            menu.findItem(R.id.menu_play_repeat_on);

        mOptionsMenuItem.hideActionBar =
            menu.findItem(R.id.menu_hide_actionbar);

        mOptionsMenuItem.videoNext =
            menu.findItem(R.id.menu_video_next);
        mOptionsMenuItem.videoPrevious =
            menu.findItem(R.id.menu_video_previous);
        mOptionsMenuItem.playlist =
            menu.findItem(R.id.menu_playlist);

        if (mIsLive) {
            menu.findItem(R.id.menu_play_repeat).setVisible(false);
            // TODO NG共有レベル設定は生放送考慮していない
            menu.findItem(R.id.menu_ng_share_level).setVisible(false);
        }

        if (mResBoolHideActionBarVisible) {
            if (mLastOrientation == Configuration.ORIENTATION_PORTRAIT) {
                mOptionsMenuItem.hideActionBar.setVisible(false);
            } else {
                mOptionsMenuItem.hideActionBar.setVisible(true);
            }
        }

        if (mPlaylist == null) {
            mOptionsMenuItem.videoNext.setVisible(false);
            mOptionsMenuItem.videoPrevious.setVisible(false);
            mOptionsMenuItem.playlist.setVisible(false);
        } else {
            mOptionsMenuItem.videoNext.setVisible(true);
            mOptionsMenuItem.videoPrevious.setVisible(true);
            mOptionsMenuItem.playlist.setVisible(true);
        }
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        mOptionsMenuItem.videoInfo.setEnabled(judgeEnableVideoInfo());

        switch (getRequestedOrientation()) {
            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
                mOptionsMenuItem.playerScreenOrientationLandscape.setChecked(true);
                break;
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
                mOptionsMenuItem.playerScreenOrientationPortrait.setChecked(true);
                break;
            case ActivityInfo.SCREEN_ORIENTATION_USER:
                mOptionsMenuItem.playerScreenOrientationUser.setChecked(true);
                break;
        }

        AbstractPlayerFragment fragment = mPlayerFragment;
        if (fragment != null) {
            switch (fragment.getNgShareLevel()) {
                case MessageChatController.NG_SHARE_LEVEL_NONE:
                    mOptionsMenuItem.ngShareLevelNone.setChecked(true);
                    break;
                case MessageChatController.NG_SHARE_LEVEL_LIGHT:
                    mOptionsMenuItem.ngShareLevelLight.setChecked(true);
                    break;
                case MessageChatController.NG_SHARE_LEVEL_MIDDLE:
                    mOptionsMenuItem.ngShareLevelMiddle.setChecked(true);
                    break;
                case MessageChatController.NG_SHARE_LEVEL_HIGH:
                    mOptionsMenuItem.ngShareLevelHigh.setChecked(true);
                    break;
            }

            if (fragment.isPlayRepeatEnabled()) {
                mOptionsMenuItem.playRepeatOn.setChecked(true);
            } else {
                mOptionsMenuItem.playRepeatOff.setChecked(true);
            }
        }

        if (mPlaylist != null) {
            int size = mPlaylist.getItemsSize();
            int current = mPlaylist.getCurrentTrack();
            boolean enableNext;
            boolean enablePrevious;
            if (size == 0) {
                enableNext = false;
                enablePrevious = false;
            } else {
                if (current == 0) {
                    enablePrevious = false;
                } else {
                    enablePrevious = true;
                }
                if (current >= size - 1) {
                    enableNext = false;
                } else {
                    enableNext = true;
                }
            }
            mOptionsMenuItem.videoNext.setEnabled(enableNext);
            mOptionsMenuItem.videoPrevious.setEnabled(enablePrevious);
        }

        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            case R.id.menu_hide_actionbar:
                mPlayerInfoControllerManager.hidePlayerController();
                return true;
//            case R.id.menu_comment_onoff:
//                setMessageDisable(!mMessageDisable);
//                setMenuCommentOnOffTitle(item);
//                return true;
//            case R.id.menu_from_begin:
//                rewindToBegin();
//                return true;
            case R.id.menu_player_screen_orientation_landscape:
                setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                saveScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                item.setChecked(true);
                return true;
            case R.id.menu_player_screen_orientation_portrait:
                setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                saveScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                item.setChecked(true);
                return true;
            case R.id.menu_player_screen_orientation_user:
                setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
                saveScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
                item.setChecked(true);
                return true;
            case R.id.menu_video_info:
                showPlayerInfoDialog();
                return true;
            case R.id.menu_ng_share_level_none: {
                AbstractPlayerFragment fragment = mPlayerFragment;
                if (fragment != null) {
                    fragment.setNgShareLevel(
                            MessageChatController.NG_SHARE_LEVEL_NONE);
                    item.setChecked(true);
                }
            } return true;
            case R.id.menu_ng_share_level_light: {
                AbstractPlayerFragment fragment = mPlayerFragment;
                if (fragment != null) {
                    fragment.setNgShareLevel(
                            MessageChatController.NG_SHARE_LEVEL_LIGHT);
                    item.setChecked(true);
                }
            } return true;
            case R.id.menu_ng_share_level_middle: {
                AbstractPlayerFragment fragment = mPlayerFragment;
                if (fragment != null) {
                    fragment.setNgShareLevel(
                            MessageChatController.NG_SHARE_LEVEL_MIDDLE);
                    item.setChecked(true);
                }
            } return true;
            case R.id.menu_ng_share_level_high: {
                AbstractPlayerFragment fragment = mPlayerFragment;
                if (fragment != null) {
                    fragment.setNgShareLevel(
                            MessageChatController.NG_SHARE_LEVEL_HIGH);
                    item.setChecked(true);
                }
            } return true;
            case R.id.menu_play_repeat_off: {
                AbstractPlayerFragment fragment = mPlayerFragment;
                if (fragment != null) {
                    fragment.setPlayRepeat(false);
                    item.setChecked(true);
                }
            } return true;
            case R.id.menu_play_repeat_on: {
                AbstractPlayerFragment fragment = mPlayerFragment;
                if (fragment != null) {
                    fragment.setPlayRepeat(true);
                    item.setChecked(true);
                }
            } return true;
            case R.id.menu_video_next:
                if (!movePlaylistNextTrack()) {
                    Util.showErrorToast(mContext,
                            R.string.toast_playlist_move_track_error);
                }
                return true;
            case R.id.menu_video_previous:
                if (!movePlaylistPreviousTrack()) {
                    Util.showErrorToast(mContext,
                            R.string.toast_playlist_move_track_error);
                }
                return true;
            case R.id.menu_playlist:
                showPlaylistDialog();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    protected void initializeView() {
        mParentScreen = ViewUtil.findViewById(this, R.id.parent_screen);
        mParentScreen.setLayoutListener(new LayoutListener() {
            @Override
            public void onLayout(View view, int left, int top, int right, int bottom) {
                final AbstractPlayerFragment fragment = mPlayerFragment;
                if (fragment != null) {
                    fragment.onParentScreenLayout(right - left, bottom - top);
                }
            }
        });
        mVideoMarginLeft = ViewUtil.findViewById(this, R.id.video_margin_left);

        mProgressViews.initializeView(this);

//        mInfoViews.initializeView(this);
        mInfoMiniViews.initializeView(this);

        mPlayerControllerViews.initializeView(this);
        mPlayerControllerViews.setButtonPauseOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mDidStartPlay) {
                    switchPausePlay();
                }
            }
        });
        mPlayerControllerViews.setButtonFromBeginOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                rewindToBegin();
            }
        });
        mPlayerControllerViews.setButtonCommentOnOffOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                boolean messageDisable = !isChecked;
                if (mMessageDisable != messageDisable) {
                    setMessageDisable(messageDisable);
                }
            }
        });
        mPlayerControllerViews.setCheckedButtonCommentOnOff(!mMessageDisable);
        mPlayerControllerViews.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress,
                    boolean fromUser) {
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int second = seekBar.getProgress();
                int cacheSecond = seekBar.getSecondaryProgress();
                if (second > cacheSecond) {
                    // シーク位置はキャッシュの範囲内に抑える
                    second = cacheSecond;
                }
                // いったんシークバー他無効化
                mPlayerControllerViews.setEnabledSeekController(false);

//              // いったんポーズ
//              final boolean isPause = isPausePlay();
//              if (!isPause) {
//                  pausePlay();
//              }

//              seekBySecondCommon(second);
                seekBySecond(second);

//              if (!isPause) {
//                  restartPlay();
//              }
            }
        });
        mPlayerControllerViews.setOnSendCommentClickListener(new PlayerControllerViews.OnSendCommentClickListener() {
            @Override
            public void onClick(String mail, String body) {
                AbstractPlayerFragment playerFragment = mPlayerFragment;
                if (playerFragment == null) {
                    return;
                }
                playerFragment.sendComment(mail, body);
            }
        });
        mPlayerControllerViews.setOnCommentControllerClickListener(new PlayerControllerViews.OnCommentControllerClickListener() {
            @Override
            public void onClick() {
                if (mPauseCommentWrite) {
                    pausePlay();
                    setButtonPauseImage();
                }
            }
        });
        mPlayerControllerViews.setButtonMoreOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                openOptionsMenu();
            }
        });
        mPlayerControllerViews.setButtonHideOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPlayerInfoControllerManager.hidePlayerController();
            }
        });

        if (canUseRegonizeSpeech()) {
            mPlayerControllerViews.setSpeakVisibility(true);
            mPlayerControllerViews.setOnSpeakClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        Intent intent = new Intent(
                                RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                        intent.putExtra(
                                RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                        intent.putExtra(
                                RecognizerIntent.EXTRA_MAX_RESULTS ,
                                1);
                        intent.putExtra(
                                RecognizerIntent.EXTRA_PROMPT,
                                getString(R.string.prompt_speak_comment));
                        startActivityForResult(intent, REQUEST_RECOGNIZE_SPEECH);
                    } catch (ActivityNotFoundException e) {
                        Log.e(LOG_TAG, "queryIntentActivities return OK but ACTION_RECOGNIZE_SPEECH activity is not found",
                                e);
                    }
                }
            });
        } else {
            mPlayerControllerViews.setSpeakVisibility(false);
        }

        mPlayerInfoArea = ViewUtil.findViewById(this, R.id.video_info_area);

        // Viewが必要なためここで初期化
        mPlayerInfoControllerManager = new PlayerInfoControllerManager(
                mContext, mInfoMiniViews.getInfoMiniView(),
                mPlayerControllerViews.getControllerView(),
                findViewById(R.id.actionbar_background), mPlayerInfoArea);
        mPlayerInfoControllerManager.setEventListener(new PlayerInfoControllerManager.EventListener() {
            @Override
            public void onPlayerControllerDisplayChanged(PlayerInfoControllerManager manager, boolean shown) {
                // 諸事情でひとまず空
            }
        });
    }

    protected void setButtonPauseImage() {
        mPlayerControllerViews.setButtonPauseImage(isPausePlay());
    }

    void showErrorDialog(String errorMessage) {
        AlertDialog errorDialog = Util.createErrorDialog(this,
                errorMessage + getString(R.string.dialog_text_suffix_player_auto_close),
                true);
        if (!mStateManager.wasDestroyed()) {
            errorDialog.show();
        }
        onShowErrorDialog(errorDialog);
    }

    public void onShowErrorDialog(AlertDialog errorDialog) {
        mErrorDialog = errorDialog;
        stopWakeLock();
        Handler handler = mHandler;
        handler.removeMessages(MSG_ID_AUTO_CLOSE);
        handler.sendEmptyMessageDelayed(MSG_ID_AUTO_CLOSE, 30000);
    }

    private void startWakeLock() {
        if (!mWakeLock.isHeld()) {
            mWakeLock.acquire();
        }
        mNotificationController.addRunningPlayer(getClass(), mPlayerFragment);
    }
    public void stopWakeLock() {
        if (mWakeLock.isHeld()) {
            mWakeLock.release();
        }
        mNotificationController.cancelRunningPlayer();
    }

    public Handler getHandler() {
        return mHandler;
    }

    public boolean getMessageDisable() {
        return mMessageDisable;
    }

    private void setMessageDisable(boolean messageDisable) {
        mMessageDisable = messageDisable;
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putBoolean(
                getString(R.string.pref_key_message_disable),
                mMessageDisable);
        editor.commit();
    }

//    private void setMenuCommentOnOffTitle(MenuItem item) {
//        int titleId;
//        if (mMessageDisable) {
//            titleId = R.string.button_comment_off;
//        } else {
//            titleId = R.string.button_comment_on;
//        }
//        item.setTitle(titleId);
//    }

    public boolean canShowErrorDialog() {
        return (!isFinishing() && !mStateManager.wasDestroyed() &&
                (mErrorDialog == null || !mErrorDialog.isShowing()));
    }

    public RelativeLayout getParentScreen() {
        return mParentScreen;
    }

    public AbstractPlayerFragment createPlayerFragment(String getflvUrl) {
        if (mIsLive) {
            return new LivePlayerFragment();
        }

        if (getflvUrl == null) {
            // 想定外
            Log.w(LOG_TAG, "Video URL is null");
            // とりあえずデフォルトで起動を試みる
            return new FFmpegPlayerFragment();
        }

        if (NicoroAPIManager.isGetflvUrlFlv(getflvUrl)) {
            // flv
            return new FFmpegPlayerFragment();
        } else if (NicoroAPIManager.isGetflvUrlMp4(getflvUrl)) {
            // mp4
            if (mSharedPreferences.getBoolean(
                    mContext.getString(R.string.pref_key_mp4_mediaplayer), true)) {
                return new MediaPlayerFragment();
            } else {
                return new FFmpegPlayerFragment();
            }
        } else if (NicoroAPIManager.isGetflvUrlSwf(getflvUrl)) {
            return new SwfPlayerFragment();
        } else {
            // 想定外
            Log.w(LOG_TAG, Log.buf().append("Unrecognized video URL:")
                    .append(getflvUrl).toString());
            // とりあえずデフォルトで起動を試みる
            return new FFmpegPlayerFragment();
        }
    }

    public void setProgressTextInnerSwfPrepareWait() {
        mProgressViews.setTextInnerSwfPrepareWait();
    }
    public void setProgressTextInnerSwfPrepareFinished() {
        mProgressViews.setTextInnerSwfPrepareFinished();
    }

    private int getLayoutResID() {
        if (mIsLive) {
            return R.layout.liveplayer;
        } else {
            return R.layout.player;
        }
    }

    public void setPlayerFragment(AbstractPlayerFragment playerFragment) {
        mPlayerFragment = playerFragment;

        if (playerFragment != null) {
            Bundle args = playerFragment.getArguments();
            WatchVideo currentVideo = getIntent().getParcelableExtra(
                    INTENT_NAME_WATCH_VIDEO);
            WatchVideo newVideo = args.getParcelable(INTENT_NAME_WATCH_VIDEO);
            if (currentVideo != null && newVideo != null
                    && !newVideo.hasVideoNumber(currentVideo.v())) {
                Intent result = new Intent();
                result.putExtra(INTENT_NAME_WATCH_VIDEO, newVideo);
                setResult(Activity.RESULT_OK, result);
            }
        }
    }

    public void forbidSeekByContributor() {
        Handler handler = mHandler;
        handler.sendEmptyMessage(MSG_ID_FORBID_SEEK_BY_CONTRIBUTOR);
    }

    public void allowSeekByContributor() {
        Handler handler = mHandler;
        handler.sendEmptyMessage(MSG_ID_ALLOW_SEEK_BY_CONTRIBUTOR);
    }

    public void resetProgress() {
        mProgressViews.resetViewData();
        mProgressViews.setVisibilityVisible();
        mPlayerControllerViews.setSeekBarProgress(0);
    }

    public boolean isStarting() {
        return mStateManager.isStarting();
    }

    public void replacePlayerFragment(AbstractPlayerFragment playerFragment,
            boolean showProgress) {
        if (mStateManager.wasDestroyed()) {
            return;
        }

        if (showProgress) {
            resetProgress();
        }

        if (mStateManager.isStarting()) {
            replacePlayerFragmentInner(playerFragment);
        } else {
            // commitAllowingStateLossならバックグラウンドでも直接置換可能だが
            // 他にも問題があるので、Activityを表に戻してから差し替え

            mNextPlayerFragment = playerFragment;
//            mMoveTaskToBackAfterPlay = true;

            Intent intent = new Intent(mContext, PlayerActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
//                    | Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                    );
            // バックグラウンド状態ではActivity#startActivityでは起動しない
            mContext.startActivity(intent);
        }
    }

    private void replacePlayerFragmentInner(AbstractPlayerFragment playerFragment) {
        FragmentManager fm = getSupportFragmentManager();
        if (fm.findFragmentById(R.id.video_area) == null) {
            fm.beginTransaction().add(
                    R.id.video_area, playerFragment).commit();
        } else {
            fm.beginTransaction().replace(
                    R.id.video_area, playerFragment).commit();
        }
        setPlayerFragment(playerFragment);

        // onLayoutは既に完了後かもしれないのでここで手動で設定
        mHandler.sendEmptyMessage(MSG_ID_SET_PLAYER_FRAGMENT_PARENT_SCREEN_SIZE);
    }

    public void enableComment() {
        mPlayerControllerViews.enableComment();
    }

    public void disableComment(String disableMessage) {
        mPlayerControllerViews.disableComment(disableMessage);
    }

    public void forbidCommentByContributor() {
        Handler handler = mHandler;
        handler.sendEmptyMessage(MSG_ID_FORBID_COMMENT_BY_CONTRIBUTOR);
    }

    public void allowCommentByContributor() {
        Handler handler = mHandler;
        handler.sendEmptyMessage(MSG_ID_ALLOW_COMMENT_BY_CONTRIBUTOR);
    }

    private boolean canUseRegonizeSpeech() {
        PackageManager packageManager = getPackageManager();
        List<ResolveInfo> activities = packageManager.queryIntentActivities(
                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
        return !activities.isEmpty();
    }

    public void rewindToBegin() {
        // いったんシークバー他無効化
        mPlayerControllerViews.setEnabledSeekController(false);
        mPlayerControllerViews.setSeekBarProgress(0);
        seekBySecond(0);

        // TODO 最初に戻った後の再度再生するのはどうするか
    }

    private void showPlayerInfoDialog() {
        PlayerInfoDialogFragment fragment = createPlayerInfoDialogFragment(true);
        if (fragment == null) {
            return;
        }
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        Fragment prev = manager.findFragmentByTag(TAG_PLAYER_INFO_DIALOG);
        if (prev != null) {
            transaction.remove(prev);
        }
        transaction.addToBackStack(null);

        fragment.show(transaction, TAG_PLAYER_INFO_DIALOG);
    }

    private PlayerInfoDialogFragment createPlayerInfoDialogFragment(boolean isDialog) {
        AbstractPlayerFragment playerFragment = mPlayerFragment;
        if (playerFragment == null) {
            return null;
        }
        PlayerInfoDialogFragment fragment;
        if (mIsLive) {
            LivePlayerFragment.InfoData infoData = mLiveInfoData;
            if (infoData == null) {
                return null;
            }

            if (isDialog) {
                fragment = PlayerInfoDialogFragment.createInstanceForLiveDialog();
            } else {
                fragment = PlayerInfoDialogFragment.createInstanceForLiveLayout();
            }
            setLivePlayerInfoDataToPlayerInfoDialogFragment(infoData, fragment);
        } else {
            ThumbInfoInterface thumbInfo = playerFragment.getThumbInfo();
            if (thumbInfo.isNull()) {
                return null;
            }

            if (isDialog) {
                fragment = PlayerInfoDialogFragment.createInstanceForVideoDialog();
            } else {
                fragment = PlayerInfoDialogFragment.createInstanceForVideoLayout();
            }
            setThumbInfoToPlayerInfoDialogFragment(thumbInfo, fragment);
        }
        StringBuilder playData = new StringBuilder();
        Resources res = getResources();
        PlayerInfoDialogFragment.setPlayData(playerFragment, playData, res);
        fragment.setPlayData(playData.toString());

        return fragment;
    }

    public void onVideoViewSizeChanged(int width, int height) {
        ViewGroup.LayoutParams params = mVideoMarginLeft.getLayoutParams();
        if (width == 0) {
            // Viewのサイズ取れていないときは、いったん0に戻す
            if (params.width != 0) {
                params.width = 0;
                mVideoMarginLeft.requestLayout();
            }
        } else {
            int newMarginLeftWidth = (mParentScreen.getWidth() - width) / 2;
            if (params.width != newMarginLeftWidth) {
                params.width = newMarginLeftWidth;
                mVideoMarginLeft.requestLayout();
            }
        }
    }

    private boolean judgeEnableVideoInfo() {
        if (mIsLive) {
            return (mLiveInfoData != null);
        } else {
            return mIsThumbInfoOk;
        }
    }
    private void setEnableVideoInfo() {
        setEnableVideoInfo(judgeEnableVideoInfo());
    }

    private void setEnableVideoInfo(boolean enable) {
        if (mOptionsMenuItem != null) {
            if (mOptionsMenuItem.videoInfo.isEnabled() != enable) {
                ActivityCompat.invalidateOptionsMenu(this);
            }
        }
        updatePlayerInfoArea(mLastOrientation);
    }

    private void updatePlayerInfoArea(int orientation) {
        FragmentManager fm = getSupportFragmentManager();
        PlayerInfoDialogFragment fragment = ViewUtil.findFragmentById(fm,
                R.id.video_info_area);
        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            // 縦画面ではvideo_info_areaに埋め込む
            if (fragment == null) {
                fragment = createPlayerInfoDialogFragment(false);
                if (fragment != null) {
                    fm.beginTransaction().add(R.id.video_info_area,
                            fragment).commitAllowingStateLoss();
                }
            }
            mPlayerInfoArea.setVisibility(View.VISIBLE);
        } else {
            // video_info_areaを外す
            if (fragment != null) {
                fm.beginTransaction().remove(fragment).commitAllowingStateLoss();
            }
            mPlayerInfoArea.setVisibility(View.GONE);
        }
    }

    private void setThumbInfoToPlayerInfoDialogFragment(
            ThumbInfoInterface thumbInfo,
            PlayerInfoDialogFragment playerInfo) {
        Resources res = getResources();
        Date firstRetrieveDate = thumbInfo.getFirstRetrieveAsDate();
        String firstRetrieve;
        if (firstRetrieveDate == null) {
            firstRetrieve = null;
        } else {
            DateFormat dateFormat = DateFormat.getDateTimeInstance(
                    DateFormat.LONG, DateFormat.DEFAULT);
            firstRetrieve = dateFormat.format(firstRetrieveDate)
                + " " + res.getString(R.string.upload);
        }

        playerInfo.setTitle(thumbInfo.getTitle());
        playerInfo.setDescription(thumbInfo.getDescription());
        playerInfo.setFirstRetrieve(firstRetrieve);
        playerInfo.setThumbnailUrl(thumbInfo.getThumbnailUrl());
        playerInfo.setCountPlay(thumbInfo.getViewCounter());
        playerInfo.setCountComment(thumbInfo.getCommentNum());
        playerInfo.setCountMylist(thumbInfo.getMylistCounter());
        playerInfo.setTagList(thumbInfo.getTags());
    }

    private void setLivePlayerInfoDataToPlayerInfoDialogFragment(
            LivePlayerFragment.InfoData infoData,
            PlayerInfoDialogFragment playerInfo) {
        playerInfo.setTitle(infoData.title);
        playerInfo.setDescription(infoData.description);
        playerInfo.setCountPlay(Util.parseInt(infoData.watchCount, 0));
        playerInfo.setCountComment(Util.parseInt(infoData.commentCount, 0));
        playerInfo.setSheet(infoData.sheet);
    }

    private void setScreenOrientation(int screenOrientation) {
        setScreenOrientation(this, screenOrientation);
    }
    static void setScreenOrientation(Activity activity, int screenOrientation) {
        switch (screenOrientation) {
            case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
            case ActivityInfo.SCREEN_ORIENTATION_USER:
                activity.setRequestedOrientation(screenOrientation);
                break;
        }
    }
    private void saveScreenOrientation(int screenOrientation) {
        saveScreenOrientation(mSharedPreferences, screenOrientation);
    }
    static void saveScreenOrientation(SharedPreferences sharedPreferences,
            int screenOrientation) {
        switch (screenOrientation) {
            case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
            case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
            case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
            case ActivityInfo.SCREEN_ORIENTATION_USER:
                sharedPreferences.edit().putInt(NicoroConfig.PLAYER_SCREEN_ORIENTATION,
                        screenOrientation).commit();
                break;
            default:
                Log.w(LOG_TAG, Log.buf().append("Not supported screen orientation: ")
                        .append(screenOrientation).toString());
                break;
        }
    }

    public boolean getEnableHardwareAccelerated() {
        return mEnableHardwareAccelerated;
    }

    public boolean hasPlaylist() {
        return mPlaylist != null;
    }

    public boolean movePlaylistNextTrack() {
        Playlist playlist = mPlaylist;
        if (playlist == null) {
            return false;
        }
        int ret = playlist.moveNextTrack();
        if (ret == Playlist.MOVE_TRACK_SUCCEEDED) {
            updatePlaylistCurrentTrack(playlist);
            return true;
        } else if (ret == Playlist.MOVE_TRACK_SKIP_DELETED) {
            updatePlaylistCurrentTrack(playlist);
            Util.showInfoToast(mContext, R.string.toast_playlist_skip_deleted);
            return true;
        } else {
            return false;
        }
    }

    public boolean movePlaylistPreviousTrack() {
        Playlist playlist = mPlaylist;
        if (playlist == null) {
            return false;
        }
        int ret = playlist.movePreviousTrack();
        if (ret == Playlist.MOVE_TRACK_SUCCEEDED) {
            updatePlaylistCurrentTrack(playlist);
            return true;
        } else if (ret == Playlist.MOVE_TRACK_SKIP_DELETED) {
            updatePlaylistCurrentTrack(playlist);
            Util.showInfoToast(mContext, R.string.toast_playlist_skip_deleted);
            return true;
        } else {
            return false;
        }
    }

    private void updatePlaylistCurrentTrack(Playlist playlist) {
        startCurrentPlaylist();
        ActivityCompat.invalidateOptionsMenu(this);

        PlaylistDialogFragment playlistFragment = ViewUtil.findFragmentByTag(
                getSupportFragmentManager(), TAG_PLAYLIST_DIALOG);
        if (playlistFragment != null) {
            playlistFragment.setCurrentTrack(playlist.getCurrentTrack());
        }
    }

    private void startCurrentPlaylist() {
        pausePlay();

        Bundle extras = getIntent().getExtras();
        Bundle newArgs;
        if (extras == null) {
            newArgs = new Bundle();
        } else {
            newArgs = new Bundle(extras);
        }
        String videoNumber = mPlaylist.getCurrentItem().videoId;
        newArgs.putString(INTENT_NAME_VIDEO_NUMBER, videoNumber);
        if (mNewPlayerFragmentCreator == null) {
            mNewPlayerFragmentCreator = new NewPlayerFragmentCreator(
                    mContext, mHandler,
                    MSG_ID_NEW_PLAYER_FRAGMENT, MSG_ID_PLAY_ERROR);
        }
        mNewPlayerFragmentCreator.execute(newArgs);
    }

    private void showPlaylistDialog() {
        PlaylistDialogFragment fragment =
            PlaylistDialogFragment.createInstance();
        if (fragment == null) {
            return;
        }
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        Fragment prev = manager.findFragmentByTag(TAG_PLAYLIST_DIALOG);
        if (prev != null) {
            transaction.remove(prev);
        }
        transaction.addToBackStack(null);

        fragment.show(transaction, TAG_PLAYLIST_DIALOG);
    }

    @Override
    public void onPlaylistSortItemClick(PlaylistDialogFragment fragment, int position) {
        Playlist playlist = mPlaylist;
        if (playlist == null) {
            Util.showErrorToast(mContext, R.string.errormessage_unknown);
            return;
        }

        if (playlist.getSortOrder() == position) {
            // 変化がない場合は何もしない
            return;
        }

        playlist.setSortOrder(position);

        if (position == integer.mylist_sort_dummy) {
            // 何もしない
        } else {
            ActivityCompat.invalidateOptionsMenu(this);

            fragment.notifyPlaylistChanged();
        }
    }

    @Override
    public void onPlaylistItemClick(PlaylistDialogFragment fragment, int position) {
        Playlist playlist = mPlaylist;
        if (playlist == null) {
            Util.showErrorToast(mContext, R.string.errormessage_unknown);
            return;
        }
        if (playlist.getCurrentTrack() == position) {
            // 変化がない場合は何もしない
            return;
        }
        int ret = playlist.setCurrentTrack(position);
        if (ret == Playlist.MOVE_TRACK_SUCCEEDED) {
            startCurrentPlaylist();
            ActivityCompat.invalidateOptionsMenu(this);
        } else if (ret == Playlist.MOVE_TRACK_SKIP_DELETED) {
            startCurrentPlaylist();
            ActivityCompat.invalidateOptionsMenu(this);

            Util.showInfoToast(mContext, R.string.toast_playlist_skip_deleted);
        } else {
            Util.showErrorToast(mContext, R.string.toast_playlist_move_track_error);
        }
    }

    @Override
    public void onPlaylistShuffleItemClick(PlaylistDialogFragment fragment) {
        Playlist playlist = mPlaylist;
        if (playlist == null) {
            Util.showErrorToast(mContext, R.string.errormessage_unknown);
            return;
        }

        playlist.shuffle();

        ActivityCompat.invalidateOptionsMenu(this);

        fragment.notifyPlaylistChanged();
    }

    @Override
    public Playlist getPlaylist() {
        return mPlaylist;
    }
}
