package jp.sourceforge.nicoro;

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

import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;

import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LivePlayerFragment extends AbstractPlayerFragment
implements SurfaceVideoDrawer.DrawMessage {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;

    /** Surfaceの準備完了 */
    private static final int MSG_ID_SURFACE_READY = MSG_ID_SUB_OFFSET + 0;
    /** Surfaceが破棄された */
    private static final int MSG_ID_SURFACE_DESTROYED = MSG_ID_SUB_OFFSET + 1;
    /** 生放送サーバーへの接続完了 */
    private static final int MSG_ID_LIVE_CONNECT_SUCCEEDED = MSG_ID_SUB_OFFSET + 2;
    /** 生放送サーバーへの接続エラー */
    private static final int MSG_ID_LIVE_CONNECT_FAILED = MSG_ID_SUB_OFFSET + 3;

    public static class InfoData {
        public String title;
        public String description;
        public String watchCount;
        public String commentCount;
        public String sheet;

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder("InfoData: title=")
                .append(title)
                .append(" description=").append(description)
                .append(" watchCount=").append(watchCount)
                .append(" commentCount=").append(commentCount)
                .append(" sheet=").append(sheet);
            return builder.toString();
        }
    }

//    private class PrepareFFmpegTask extends AsyncTask<Void, Void, Void> {
//        @Override
//        protected Void doInBackground(Void... params) {
//            // TODO
//            try {
////                mFFmpegVideoDecoder.prepareFFmpeg(false);
//                mFFmpegVideoDecoder.prepareFFmpeg(true);
//            } catch (IllegalArgumentException e) {
//                String error = e.toString();
//                Log.e(LOG_TAG, error, e);
//                Handler handler = mHandler;
//                if (handler != null) {
//                    handler.obtainMessage(MSG_ID_PLAY_ERROR,
//                            error).sendToTarget();
//                }
//            }
//            return null;
//        }
//    }

    private SurfaceVideoDrawerInterface mSurfaceVideoDrawer =
        SurfaceVideoDrawerInterface.NullObject.getInstance();
    /*private*/ StreamAudioPlayerInterface mStreamAudioPlayer =
        StreamAudioPlayerInterface.NullObject.getInstance();
    private FFmpegVideoDecoderInterface mFFmpegVideoDecoder =
        FFmpegVideoDecoderInterface.NullObject.getInstance();
    private LiveMessageLoaderInterface mLiveMessageLoader =
        LiveMessageLoaderInterface.NullObject.getInstance();
    private LiveVideoLoaderInterface mLiveVideoLoader =
        LiveVideoLoaderInterface.NullObject.getInstance();
    private LivePlayerStatusLoader mLivePlayerStatusLoader;

//    private VariableLabelView mInfoSheet;

    private String mLiveNumber;
    private String mCookieUserSession;
    private String mTitle;
    private String mDescription;
    private long mStartTime;

    private long mDeviceStartTime;
    private long mGetPlayerStatusTime;
    private long mLiveStartTime;

    private String mResInfoLivePastTime;

    private volatile boolean mIsPlaying = false;
    private boolean mIsSurfaceOk;
    /*private*/ volatile boolean mIsFinish;

    private int mDisplayWidth = 0;
    private int mDisplayHeight = 0;

    private static class CreateDrawBufferListener implements FFmpegVideoDecoderInterface.CreateDrawBufferListener {
        private WeakReference<LivePlayerFragment> mThis;

        public CreateDrawBufferListener(LivePlayerFragment thiz) {
            mThis = new WeakReference<LivePlayerFragment>(thiz);
        }

        @Override
        public void onCreateDrawBuffer(FFmpegVideoDecoderInterface decoder) {
            LivePlayerFragment thiz = mThis.get();
            if (thiz == null) {
                return;
            }
            thiz.mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
        }
    }
    private CreateDrawBufferListener mCreateDrawBufferListener;

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

    @Override
    public boolean handleMessage(Message msg) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append("LivePlayerFragment handleMessage: ")
                    .append(msg.toString()).toString());
        }
        PlayerActivity playerActivity = getPlayerActivity();
        Handler parentHandler = (playerActivity == null) ? null
                : playerActivity.getHandler();
        switch (msg.what) {
            case MSG_ID_GET_PLAYER_STATUS_SUCCEEDED:
                if (parentHandler != null) {
                    InfoData infoData = new InfoData();

                    // TODO getplayerstatusでは文が途中で途切れる。タイトルも長ければ同様
                    infoData.title = mTitle;
                    if (infoData.title == null) {
                        infoData.title = mLivePlayerStatusLoader.getTitle();
                    }
                    infoData.description = mDescription;
                    if (infoData.description == null) {
                        infoData.description = mLivePlayerStatusLoader.getDescription();
                    }
                    infoData.watchCount = mLivePlayerStatusLoader.getWatchCount();
                    infoData.commentCount = mLivePlayerStatusLoader.getCommentCount();
                    infoData.sheet = getString(R.string.info_live_sheet,
                            mLivePlayerStatusLoader.getRoomLabel(),
                            mLivePlayerStatusLoader.getRoomSeetNo());

                    parentHandler.obtainMessage(
                            MSG_ID_GET_PLAYER_STATUS_SUCCEEDED, infoData).sendToTarget();
                }
                if (mStateManager.isResuming()) {
                    mDeviceStartTime = System.currentTimeMillis();
                    mGetPlayerStatusTime = mLivePlayerStatusLoader.getTime();
                    mLiveStartTime = mLivePlayerStatusLoader.getStartTime();

                    // TODO コメント数等の定期的な更新
                    // →http://watch.live.nicovideo.jp/api/heartbeat?v=（放送ID）
                    //   http://live.nicovideo.jp/api/heartbeat?v=（放送ID）


                    CallbackMessage<Void, LiveVideoLoader.ErrorCode> callbackLiveVideo =
                        new CallbackMessage<Void, LiveVideoLoader.ErrorCode>(
                                mHandler, MSG_ID_LIVE_CONNECT_SUCCEEDED, MSG_ID_LIVE_CONNECT_FAILED);
                    String url = mLivePlayerStatusLoader.getRtmpUrl();
                    if (url == null) {
                        url = mLivePlayerStatusLoader.getContentsUrl();
                        if (url == null) {
                            // TODO 状況によってはnullになりえる？（通常動画再生指定など）
                            String errorMessage = getString(
                                    R.string.errormessage_live_unsupported);
                            // TODO 別のIDに変えるべきかも
                            parentHandler.obtainMessage(MSG_ID_PLAY_ERROR,
                                    errorMessage).sendToTarget();
                            return true;
                        }
                    }
//                    url += "/" + mLiveNumber;
//                    // TODO アクセスできないかつ端末ごとフリーズする傾向があるのでエラーで終了
//                    if (url.indexOf("/fileorigin/") >= 0) {
//                        showErrorDialog(getString(R.string.errormessage_live_unsupported));
//                        return;
//                    }

                    String extras = null;
                    String playpath = mLivePlayerStatusLoader.getContentsPlaypath();
                    if (playpath == null) {
//                        playpath = mLivePlayerStatusLoader.getQuePlaypath();
                        playpath = mLivePlayerStatusLoader.getQueContent();
                        if (playpath == null) {
                            // TODO
                            playpath = mLiveNumber;
                        }
                    } else {
                        String contentsUrl = mLivePlayerStatusLoader.getContentsUrl();
                        if (contentsUrl != null) {
//                            extras = " subscribe=" + contentsUrl + playpath;
//                            playpath = "rtmp:" + contentsUrl + playpath;
//                            extras = " conn=S:" + contentsUrl + " conn=S:" + playpath + " conn=S:" + mLiveNumber;
//                            extras = " tcUrl=" + url;
//                            url = contentsUrl;
//                            playpath = playpath + "&" + contentsUrl + "&" + mLiveNumber;
//                            extras = " subscribe=" + "nlPlayNotice";
                            extras = " nlPlayNotice1=" + contentsUrl + " nlPlayNotice2=" + playpath;
                        }
//                        extras = " subscribe=" + playpath;
//                        playpath = mLiveNumber;
//                        extras = " subscribe=" + mLiveNumber;
//                        playpath = "rtmp:" + playpath;
//                        extras = " swfVfy=1 swfAge=0";
                    }
                    // TODO 必ずタイムシフトかどうか？
                    boolean live = (mLivePlayerStatusLoader.getArchive() == 0);
                    int start;
                    if (live) {
                        start = 0;
                    } else {
                        start = mLivePlayerStatusLoader.getQueVpos() / -100;
                    }
                    String ticket = mLivePlayerStatusLoader.getRtmpTicket();
//                    String ticket = null;
//                    String akamaiUser = mLivePlayerStatusLoader.getAkamaiUser();
//                    String akamaiPassword = mLivePlayerStatusLoader.getAkamaiPassword();
//                    String queContent = mLivePlayerStatusLoader.getQueContent();
//                    if (queContent != null) {
//                        extras = "conn=NS:sendFileRequest:" + queContent;
//                    }
//                    extras = " token=" + ticket;
                    mLiveVideoLoader.startConnect(url, ticket, playpath, live, start,
                            mLiveNumber,
//                            akamaiUser, akamaiPassword,
                            extras,
                            callbackLiveVideo);

                    mLiveMessageLoader.finish();
                    mLiveMessageLoader = new LiveMessageLoader(
                            mLivePlayerStatusLoader.getMsAddr(),
                            mLivePlayerStatusLoader.getMsPort(),
                            mLivePlayerStatusLoader.getMsThread(),
                            mCookieUserSession,
                            mLivePlayerStatusLoader.getUserId(),
                            mContext);
                    mLiveMessageLoader.setEventListener(new LiveMessageLoaderInterface.EventListener() {
                        @Override
                        public void onConnected(LiveMessageLoaderInterface loader) {
                            Handler handler = mHandler;
                            handler.sendEmptyMessage(MSG_ID_LIVE_MESSAGE_CONNECTED);
                        }
                        @Override
                        public void onOccurredError(LiveMessageLoaderInterface loader, String errorMessage) {
                            Handler handler = mHandler;
                            handler.sendEmptyMessage(MSG_ID_LIVE_MESSAGE_OCCURRED_ERROR);
                        }
                        @Override
                        public void onFinished(LiveMessageLoaderInterface loader) {
                            // nothing
                        }
                        @Override
                        public void onAddedMessage(LiveMessageLoaderInterface loader) {
                            // TODO 自動生成されたメソッド・スタブ

                        }
                    });
                    mLiveMessageLoader.startLoad();
                } else {
                    // 前面に出ていないときは無視
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "MSG_ID_GET_PLAYER_STATUS_SUCCEEDED not onResume, ignore");
                    }
                }
                break;
            case MSG_ID_GET_PLAYER_STATUS_FAILED: {
                if (mStateManager.isResuming()) {
                    // TODO ID変えた方がいいかも
                    if (parentHandler != null) {
                        parentHandler.obtainMessage(
                                MSG_ID_THUMBINFO_OCCURRED_ERROR, msg.obj).sendToTarget();
                    }
                } else {
                    // 前面に出ていないときは無視
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "MSG_ID_GET_PLAYER_STATUS_SUCCEEDED not onResume, ignore");
                    }
                }
                if (playerActivity != null) {
                    playerActivity.stopWakeLock();
                }
            } break;
            case MSG_ID_SURFACE_READY:
                mIsSurfaceOk = true;
                // TODO
                if (!mIsPlaying && canStartPlay()) {
                    startPlay();
                }
                onVideoViewSizeChanged(mSurfaceVideoDrawer.getViewWidth(),
                        mSurfaceVideoDrawer.getViewHeight());
                break;
            case MSG_ID_SURFACE_DESTROYED:
                mIsSurfaceOk = false;
                break;
            case MSG_ID_LIVE_CONNECT_SUCCEEDED:
                if (!mStateManager.wasDestroyed()) {
                    // TODO
//                    new PrepareFFmpegTask().execute();
                    new FFmpegVideoDecoderInterface.PrepareFFmpegTask(mFFmpegVideoDecoder) {
                        @Override
                        protected void onPostExecute(Boolean result) {
                            Handler handler = mHandler;
                            Exception e = getException();
                            if (e != null) {
                                handler.obtainMessage(MSG_ID_PLAY_ERROR,
                                        e.toString()).sendToTarget();
                            }
                            if (!result) {
                                // TODO しばらくしてからもう一回チャレンジ
                                handler.sendEmptyMessageDelayed(
                                        MSG_ID_LIVE_CONNECT_SUCCEEDED, 3000L);
                            }
                        }
//                    }.executeWrapper(false);
                    }.executeWrapper(true);
                }
                break;
            case MSG_ID_LIVE_CONNECT_FAILED: {
                if (parentHandler != null) {
                    LiveVideoLoader.ErrorCode errorCode =
                        (LiveVideoLoader.ErrorCode) msg.obj;
                    int stringId;
                    switch (errorCode) {
                        case FAIL_SETUP_URL:
                            stringId = R.string.errormessage_live_setup;
                            break;
                        case FAIL_CONNECT:
                        case FAIL_CONNECT_STREAM:
                            stringId = R.string.errormessage_live_connect;
                            break;
                        case FAIL_RECONNECT_STREAM:
                            stringId = R.string.errormessage_live_reconnect;
                            break;
                        case FAIL_CACHE_FILE:
                            stringId = R.string.errormessage_live_cache_file;
                            break;
                        case READ_ERROR:
                            stringId = R.string.errormessage_live_read_error;
                            break;
                        case READ_COMPLETE:
                            stringId = R.string.errormessage_live_read_complete;
                            break;
                        default:
                            assert false : errorCode;
                            stringId = R.string.errormessage_unknown;
                            break;
                    }
                    // TODO 別のIDに変えるべきかも
                    parentHandler.obtainMessage(MSG_ID_PLAY_ERROR,
                            getString(stringId)).sendToTarget();
                }
            } break;
            case MSG_ID_LIVE_MESSAGE_CONNECTED:
                if (parentHandler != null) {
                    parentHandler.sendEmptyMessage(MSG_ID_LIVE_MESSAGE_CONNECTED);
                }
                break;
            case MSG_ID_LIVE_MESSAGE_OCCURRED_ERROR:
                if (parentHandler != null) {
                    parentHandler.sendEmptyMessage(MSG_ID_LIVE_MESSAGE_OCCURRED_ERROR);
                }
                break;
            case MSG_ID_PLAY_FINISHED:
                // TODO 生放送でこれが起きるのは実質エラー？
                if (playerActivity != null) {
                    playerActivity.stopWakeLock();
                }
                break;
            default:
                super.handleMessage(msg);
                break;
        }
        return true;
    }

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

        if (savedInstanceState == null) {
        } else {
        }

        SharedPreferences sharedPreferences = Util.getDefaultSharedPreferencesMultiProcess(
                mContext);
        mSharedPreferences = sharedPreferences;

        Bundle args = getArguments();
        assert args != null;
        String liveNumber = args.getString(INTENT_NAME_LIVE_NUMBER);
        mLiveNumber = liveNumber;
        String cookieUserSession = NicoroConfig.getCookieUserSession(sharedPreferences);
        mCookieUserSession = cookieUserSession;
        mTitle = args.getString(INTENT_NAME_TITLE);
        mDescription = args.getString(INTENT_NAME_DESCRIPTION);
//        mLivePlayerStatusLoader = new LivePlayerStatusLoader(liveNumber,
//                cookieUserSession);
//        CallbackMessage<Void, String> callbackLivePlayerStatus = new CallbackMessage<Void, String>(
//                mHandler, MSG_ID_GET_PLAYER_STATUS_SUCCEEDED, MSG_ID_GET_PLAYER_STATUS_FAILED);
//        mLivePlayerStatusLoader.registerCallback(callbackLivePlayerStatus);
//        mLivePlayerStatusLoader.startLoad();

        mMessageChatController.resetMessageDataChats(null);

        Resources res = getResources();
        mResInfoLivePastTime = res.getString(R.string.info_live_time_past);

        mIsSurfaceOk = false;
        mIsPlaying = false;
    }

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

        mSurfaceVideoDrawer = new SurfaceVideoDrawer(
                (SurfaceView) view.findViewById(R.id.surface));
        mSurfaceVideoDrawer.registerEventHandler(mHandler,
                MSG_ID_SURFACE_READY, MSG_ID_SURFACE_DESTROYED,
                MSG_ID_PLAY_FINISHED);
        mSurfaceVideoDrawer.setDrawMessage(this);
        mLiveVideoLoader = new LiveVideoLoader(mContext);
        mLiveVideoLoader.start();

        mStreamAudioPlayer = new StreamAudioPlayer(mContext);
        mFFmpegVideoDecoder = new FFmpegVideoDecoder(mSurfaceVideoDrawer,
                mStreamAudioPlayer, mLiveVideoLoader,
                mContext, mLastOrientation);
//        mFFmpegVideoDecoder.setCacheDecodeFirst(false);
        // TODO AudioTrackが起きるのに異常に時間がかかるので、生放送でもとりあえずキャッシュ
        mFFmpegVideoDecoder.setCacheDecodeFirst(true);
        mCreateDrawBufferListener = new CreateDrawBufferListener(this);
        mFFmpegVideoDecoder.setCreateDrawBufferListener(mCreateDrawBufferListener);

        return view;
    }

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

        mStartTime = SystemClock.elapsedRealtime();
    }

    @Override
    public void onStart() {
        super.onStart();
        if (!mStateManager.wasRestarted()) {
            assert mLivePlayerStatusLoader == null;
            mLivePlayerStatusLoader = new LivePlayerStatusLoader(mLiveNumber,
                    mCookieUserSession, mContext);
            CallbackMessage<Void, String> callbackLivePlayerStatus = new CallbackMessage<Void, String>(
                    mHandler, MSG_ID_GET_PLAYER_STATUS_SUCCEEDED, MSG_ID_GET_PLAYER_STATUS_FAILED);
            mLivePlayerStatusLoader.registerCallback(callbackLivePlayerStatus);
            mLivePlayerStatusLoader.startLoad();
        }
        mFFmpegVideoDecoder.setIsVisible(true);
    }

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

        super.onPause();
//        // TODO pauseがどうにもうまくいかないので、とりあえず落とす
//        if (true) {
//            finish();
//            return;
//        }
//
//        assert mLivePlayerStatusLoader != null;
//        mLivePlayerStatusLoader.finish();
//        mLivePlayerStatusLoader = null;
    }

    @Override
    public void onStop() {
        super.onStop();
        mFFmpegVideoDecoder.setIsVisible(false);
    }

    @Override
    public void onDestroyImplPre() {
        super.onDestroyImplPre();
        mIsFinish = true;
    }

    @Override
    public void onDestroyImpl() {
        super.onDestroyImpl();
        ExecutorService executorService = Executors.newCachedThreadPool();
        CountDownLatch latch = new CountDownLatch(4);
        mLiveVideoLoader.closeAsync(executorService, latch);
        mSurfaceVideoDrawer.quitAsync(executorService, latch);
        mFFmpegVideoDecoder.quitAsync(executorService, latch);
        mLiveMessageLoader.finishAsync(executorService, latch);

        mStreamAudioPlayer.finish();
//        mLivePlayerStatusLoader.finish();
//        mLivePlayerStatusLoader = null;

        while (true) {
            try {
                latch.await();
                break;
            } catch (InterruptedException e) {
                Log.d(LOG_TAG, e.toString(), e);
            }
        }
        executorService.shutdown();

        mLiveVideoLoader.quit();
        mLiveVideoLoader.free();
    }

    @Override
    public void onDestroyImplPost() {
        super.onDestroyImplPost();
        mStreamAudioPlayer = StreamAudioPlayerInterface.NullObject.getInstance();
        mSurfaceVideoDrawer = SurfaceVideoDrawerInterface.NullObject.getInstance();
        mFFmpegVideoDecoder = FFmpegVideoDecoderInterface.NullObject.getInstance();
        mLiveMessageLoader = LiveMessageLoaderInterface.NullObject.getInstance();
        mLiveVideoLoader = LiveVideoLoaderInterface.NullObject.getInstance();
    }

    @Override
    protected void onOrientationChanged(int orientation) {
        // layout整ってから設定
//        mFFmpegVideoDecoder.updateDisplayMetrics(this);
        mFFmpegVideoDecoder.setOrientation(orientation);
//        mFFmpegVideoDecoder.updateSurfaceSize();

//        Util.copyLayoutParamsById(mInfoSheet, temp);
    }

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

    // SurfaceVideoDrawer.DrawMessage

    @Override
    public void drawMessage(Canvas canvas, int vpos) {
        // TODO vposは現在時間基準
        vpos = (int) (SystemClock.elapsedRealtime() - mStartTime) / 10;

        mMessageChatController.drawMessageForLive(
                canvas, vpos,
                canvas.getWidth(), canvas.getHeight(),
                getMessageDisable(), mLiveMessageLoader);
    }

    @Override
    protected void startPlay() {
        super.startPlay(ThumbInfoInterface.NullObject.getInstance());

        mIsPlaying = true;

        mStreamAudioPlayer.prepareStart();
//        mFFmpegVideoDecoder.prepareFFmpeg();
        mFFmpegVideoDecoder.prepareDecode();
        mFFmpegVideoDecoder.start();
    }

    @Override
    protected boolean canStartPlay() {
        // 生放送はバックグラウンド中に再生開始しない
        return mStateManager.isStarting() && mIsSurfaceOk;
    }

    @Override
    public StringBuilder appendVideoResolution(StringBuilder builder) {
        if (mIsFinish) {
            return builder;
        }
        return builder.append(mFFmpegVideoDecoder.getVideoOriginalWidth())
            .append('×')
            .append(mFFmpegVideoDecoder.getVideoOriginalHeight());
    }

    @Override
    public StringBuilder appendPlayerInfo(StringBuilder builder) {
        return builder;
    }

    @Override
    public StringBuilder appendCurrentPlayTime(StringBuilder builder) {
        if (mLivePlayerStatusLoader.getArchive() == 0) {
            // 生放送
            builder.append(mResInfoLivePastTime);
            int timeS = (int) ((System.currentTimeMillis() - mDeviceStartTime) / 1000);
            if (mGetPlayerStatusTime > 0 && mLiveStartTime > 0) {
                timeS += (mGetPlayerStatusTime - mLiveStartTime);
            }
            int s = timeS % 60;
            int m = timeS / 60 % 60;
            int h = timeS / (60 * 60);
            Util.appendPlayTime(builder, h, m, s);
        } else {
            // タイムシフト
            mSurfaceVideoDrawer.getCurrentPosition(mRationalCurrentPlayTime);
            final long posNum = mRationalCurrentPlayTime.num;
            final int posDen = mRationalCurrentPlayTime.den;
            if (posDen != 0) {
                int minutes = (int) (posNum / (posDen * 60));
                int seconds = (int) (posNum / posDen % 60);
                Util.appendPlayTime(builder, minutes, seconds);
            } else {
                builder.append(INFO_TIME_DEFAULT);
            }
        }


        if (DEBUG_LOGV) {
            Rational r = mRationalDebugLog;
            mSurfaceVideoDrawer.getCurrentPosition(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());
            mStreamAudioPlayer.getCurrentPosition(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());
        }

        return builder;
    }

    @Override
    public boolean hasTotalPlayTime() {
        // TODO タイムシフトならtrue返すべき
        return false;
    }

    @Override
    public void getCurrentPositionVideoPlay(Rational rational) {
        if (mIsFinish) {
            return;
        }
        mSurfaceVideoDrawer.getCurrentPosition(rational);
    }

    @Override
    protected void getCurrentPositionAudioPlay(Rational rational) {
        if (mIsFinish) {
            return;
        }
        // シークすると若干誤差が出る
        mStreamAudioPlayer.getCurrentPosition(rational);
    }

    @Override
    protected void getCurrentPositionVideoDecode(Rational rational) {
        if (mIsFinish) {
            return;
        }
        mFFmpegVideoDecoder.getCurrentPositionVideoDecode(rational);
    }

    @Override
    protected void getCurrentPositionAudioDecode(Rational rational) {
        if (mIsFinish) {
            return;
        }
//      // TODO シークすると破綻
        mFFmpegVideoDecoder.getCurrentPositionAudioDecode(rational);
    }

    @Override
    protected boolean switchPausePlay() {
        return false;
    }

    @Override
    protected void pausePlay() {
    }

    @Override
    protected void restartPlay() {
    }

    @Override
    protected boolean isPausePlay() {
        return false;
    }

    @Override
    protected void seekBySecond(int second) {
    }

    @Override
    protected VideoLoaderInterface.EventListener createVideoLoaderEventListener() {
        return null;
    }

    @Override
    protected int getVpos() {
        return 0;
    }

    @Override
    protected void loadConfigureNgClient(String userSession) {
        // TODO 生放送のNG設定はまだ未対応
    }

    @Override
    public String getTitle() {
        return mTitle;
    }

    @Override
    protected void loadThumbInfo(Bundle args) {
        // 生放送は何もしない
    }
}
