package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import static jp.sourceforge.nicoro.VideoLoader.SEEK_SET;
import static jp.sourceforge.nicoro.VideoLoader.SEEK_CUR;
import static jp.sourceforge.nicoro.VideoLoader.SEEK_END;
import static jp.sourceforge.nicoro.VideoLoader.AVSEEK_SIZE;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import jp.gr.java_conf.shiseissi.commonlib.FileUtil;

public class LiveVideoLoader extends LooperThread implements FFmpegIOCallback, LiveVideoLoaderInterface {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGD_READ = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGD_SEEK = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGD_WRITE = Release.IS_DEBUG & true;

    private static final int MSG_ID_START_CONNECT = 0;
    private static final int MSG_ID_RECONNECT_STREAM = 1;
    private static final int MSG_ID_READ_ASYNC = 2;
    private static final int MSG_ID_CLOSE = 3;

    public enum ErrorCode {
        FAIL_SETUP_URL, FAIL_CONNECT, FAIL_CONNECT_STREAM,
        FAIL_CACHE_FILE, FAIL_RECONNECT_STREAM, READ_ERROR,
        READ_COMPLETE,
    }

    // TODO ページのソース見て動的に変えた方がいいか？
    private static final String SWF_URL = "http://live.nicovideo.jp/liveplayer_v2_6.swf?rev=110331125139";
//    private static final String SWF_URL = "http://live.nicovideo.jp/liveplayer_v2_6.swf?rev=20101210.1";
    private static final String FLASH_VER = "WIN\\2010,2,153,1";
//    private static final String FLASH_VER = "WIN 10,1,51,66";

    private static final int READ_BUFFER_SIZE = 1024 * 8;
//    private static final int FILE_CACHE_HEAD_SIZE = 1024 * 512;
//    private static final int FILE_CACHE_HEAD_SIZE = 1024 * 256;
    private static final int FILE_CACHE_HEAD_SIZE = 1024 * 192;

    private static final int THREAD_PRIORITY_WRITE =
//        android.os.Process.THREAD_PRIORITY_FOREGROUND;
        android.os.Process.THREAD_PRIORITY_DEFAULT;
    private static final int THREAD_PRIORITY_DEFAULT =
        android.os.Process.THREAD_PRIORITY_BACKGROUND;

    private static class Params {
        public String url;
        public String ticket;
        public String playpath;
        public boolean live;
        public int start;
        public String liveNumber;
        public String akamaiUser;
        public String akamaiPassword;
        public String extras;
        public CallbackMessage<Void, ErrorCode> callbackMessage;

        public Params(String u, String t, String p, boolean l,
                int s, String ln,
                String au, String ap, String e,
                CallbackMessage<Void, ErrorCode> cm) {
            url = u;
            ticket = t;
            playpath = p;
            live = l;
            start = s;
            liveNumber = ln;
            akamaiUser = au;
            akamaiPassword = ap;
            extras = e;
            callbackMessage = cm;
        }
    }

    private volatile AbstractMultiRandomAccessFile mTempFile;
    private byte[] mReadBuffer = new byte[READ_BUFFER_SIZE];
    private volatile Params mParams;

    private boolean mWasNotifiedConnect;
    private boolean mWasClosed;
    private final Object mSyncRtmp = new Object();

    private Context mContext;

    private static class EmergencyAbortThread extends Thread {
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition standby = lock.newCondition();
        private final Condition look = lock.newCondition();
        private volatile boolean finish = false;

        @Override
        public void run() {
            boolean exit = false;
            lock.lock();
            try {
                while (true) {
                    standby.await();
                    if (finish) {
                        break;
                    }
                    if (!look.await(60L, TimeUnit.SECONDS)) {
                        exit = true;
                        break;
                    }
                    if (finish) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                Log.d(LOG_TAG, e.toString(), e);
            } finally {
                lock.unlock();
            }
            if (exit) {
                // 強制終了
                System.exit(-12345);
            }
        }

        public void startLook() {
            try {
                lock.lock();
                standby.signal();
            } finally {
                lock.unlock();
            }
        }
        public void endLook() {
            try {
                lock.lock();
                look.signal();
            } finally {
                lock.unlock();
            }
        }
        public void finish() {
            finish = true;
            try {
                lock.lock();
                look.signal();
                standby.signal();
            } finally {
                lock.unlock();
            }
        }
    }
    private EmergencyAbortThread mEmergencyAbortThread;

    /**
     * NativeのRTMP構造体のポインタ
     */
    private volatile long mNativeInstance = 0L;

    public LiveVideoLoader(Context context) {
        super("LiveVideoLoader", THREAD_PRIORITY_DEFAULT);
        mContext = context;
        File dir = new File(VideoLoader.getStreamTempDir(context));
        dir.mkdirs();
    }

    @Override
    public boolean isNull() {
        return false;
    }

    @Override
    public void startConnect(String url, String ticket, String playpath, boolean live,
            int start, String liveNumber,
            String extras, CallbackMessage<Void, ErrorCode> callback) {
        startConnect(url, ticket, playpath, live, start, liveNumber, null, null, extras, callback);
    }
    @Override
    public void startConnect(String url, String ticket, String playpath, boolean live,
            int start, String liveNumber,
            String akamaiUser, String akamaiPassword,
            String extras, CallbackMessage<Void, ErrorCode> callback) {
        mWasNotifiedConnect = false;
        if (mNativeInstance == 0L) {
            mNativeInstance = init();
        } else {
            // 開いていた場合に備え必ずclose
            close();
        }
        Params params = new Params(url, ticket, playpath, live,
                start, liveNumber,
                akamaiUser, akamaiPassword, extras,
                callback);
        assert mParams == null;
        mParams = params;
//        getHandler().obtainMessage(MSG_ID_START_CONNECT, params).sendToTarget();
        getHandler().sendEmptyMessage(MSG_ID_START_CONNECT);
    }
    @Override
    public void startReconnect() {
        assert mParams != null;
        getHandler().sendEmptyMessage(MSG_ID_RECONNECT_STREAM);
    }

    @Override
    public void close() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "LiveVideoLoader#close start");
        }

        mParams = null;

        Object waitClose = new Object();
        synchronized (waitClose) {
            mWasClosed = false;
            getHandler().obtainMessage(MSG_ID_CLOSE, waitClose).sendToTarget();
            try {
                // TODO ここのタイムアウトもなくすべきかも（ただlibrtmpの不具合がある）
                waitClose.wait(10000L);
            } catch (InterruptedException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
            if (!mWasClosed) {
                // 強制close
                if (mNativeInstance != 0L) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "close force");
                    }
                    close(mNativeInstance);
                }
                mWasClosed = true;
            }
        }

        AbstractMultiRandomAccessFile tempFile = mTempFile;
        if (tempFile != null) {
            mTempFile = null;
            FileUtil.closeIgnoreException(tempFile);
        }
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "LiveVideoLoader#close end");
        }
    }

    @Override
    public void closeAsync(ExecutorService executorService,
            final CountDownLatch latch) {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                close();
                latch.countDown();
            }
        });
    }

    @Override
    public void free() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "LiveVideoLoader#free");
        }
        if (mNativeInstance != 0L) {
            synchronized (mSyncRtmp) {
                free(mNativeInstance);
                mNativeInstance = 0;
            }
        }
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "LiveVideoLoader#free end");
        }
    }

    private void createTempFile() throws IOException {
        AbstractMultiRandomAccessFile tempFile = mTempFile;
        if (tempFile != null) {
            mTempFile = null;
            FileUtil.closeIgnoreException(tempFile);
        }
        mTempFile = new MultiRandomAccessFile(VideoLoader.createLiveCacheFilePath(mContext),
                true);
//        mTempFile = new MultiRandomAccessFileMmap(VideoLoader.createLiveCacheFilePath(),
//                true);
//        // とりあえず0にする
//        mTempFile.setLength(0);
        mTempFile.setLength(-1);
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, "LiveVideoLoader#finalize start");
            }
            super.finalize();
        } finally {
            if (mNativeInstance != 0L) {
                close(mNativeInstance);
                // TODO デッドロックしかねない？のでクラッシュ覚悟で通す
//                synchronized (mSyncRtmp) {
                    free(mNativeInstance);
//                }
            }
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, "LiveVideoLoader#finalize end");
            }
        }
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append("LiveVideoLoader handleMessage start: ")
                    .append(msg.toString()).toString());
        }
        switch (msg.what) {
            case MSG_ID_START_CONNECT: {
//                Params params = (Params) msg.obj;
                Params params = mParams;
                if (params == null) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "mParams is null already, cancel");
                    }
                    return true;
                }

                if (mEmergencyAbortThread != null) {
                    mEmergencyAbortThread.finish();
                }
                mEmergencyAbortThread = new EmergencyAbortThread();
                mEmergencyAbortThread.start();

                URI uri;
                try {
                    uri = new URI(params.url);
                } catch (URISyntaxException e) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, Log.buf().append("URI constructor failed: url=").append(params.url)
                                .toString());
                    }
                    params.callbackMessage.sendMessageError(ErrorCode.FAIL_SETUP_URL);
                    return true;
                }
                StringBuilder builder = new StringBuilder(256)
                    .append(params.url)
        //            .append(" tcUrl=").append(params.url)
                    .append(" swfUrl=").append(SWF_URL)
                    .append(" pageUrl=").append("http://live.nicovideo.jp/watch/").append(params.liveNumber)
//                    .append(" buffer=30000")
                    .append(" buffer=10000")
        //            .append(" timeout=120")
                    .append(" timeout=11")
                    .append(" live=");
                if (params.live) {
                    builder.append('1');
                } else {
                    builder.append('0');
                    // TODO タイムシフトでstart効果ない？
                    builder.append(" start=").append(params.start);
                }
                String app = uri.getPath();
                if (app.startsWith("/")) {
                    app = app.substring(1);
                }
                builder.append(" app=").append(app);
                if (params.ticket != null) {
                    builder.append(" conn=S:").append(params.ticket);
                }
                if (params.playpath != null) {
                    builder.append(" playpath=").append(params.playpath);
                }
                if (params.akamaiUser != null) {
                    builder.append(" akUserid=").append(params.akamaiUser);
                }
                if (params.akamaiPassword != null) {
                    builder.append(" akPass=").append(params.akamaiPassword);
                }
                if (params.extras != null) {
                    builder.append(" ").append(params.extras);
                }
                builder.append(" flashVer=").append(FLASH_VER).append(""); // 何故か最後に設定しないと駄目？
                String setupUrl = builder.toString();
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append("setupUrl=").append(setupUrl)
                            .toString());
                }

                boolean ret;
                synchronized (mSyncRtmp) {
                    if (mNativeInstance == 0) {
                        if (DEBUG_LOGD) {
                            Log.d(LOG_TAG, "setupURL: mNativeInstance free already");
                        }
                        return true;
                    }
                    ret = setupURL(mNativeInstance, setupUrl);
                }
                if (!ret) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, Log.buf().append("setupURL failed: url=").append(setupUrl)
                                .toString());
                    }
                    params.callbackMessage.sendMessageError(ErrorCode.FAIL_SETUP_URL);
                    return true;
                }
                try {
                    if (DEBUG_LOGV) {
                        Log.v(LOG_TAG, "connect start");
                    }
                    mEmergencyAbortThread.startLook();
                    synchronized (mSyncRtmp) {
                        if (mNativeInstance == 0) {
                            if (DEBUG_LOGD) {
                                Log.d(LOG_TAG, "connect: mNativeInstance free already");
                            }
                            return true;
                        }
                        ret = connect(mNativeInstance);
                    }
                } finally {
                    mEmergencyAbortThread.endLook();
                    if (DEBUG_LOGV) {
                        Log.v(LOG_TAG, "connect end");
                    }
                }
                if (!ret) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "connect failed");
                    }
                    params.callbackMessage.sendMessageError(ErrorCode.FAIL_CONNECT);
                    return true;
                }
                try {
                    if (DEBUG_LOGV) {
                        Log.v(LOG_TAG, "connectStream start");
                    }
                    mEmergencyAbortThread.startLook();
                    synchronized (mSyncRtmp) {
                        if (mNativeInstance == 0) {
                            if (DEBUG_LOGD) {
                                Log.d(LOG_TAG, "connectStream: mNativeInstance free already");
                            }
                            return true;
                        }
                        ret = connectStream(mNativeInstance, 0);
//                        ret = connectStream(mNativeInstance, params.start);
                    }
                } finally {
                    mEmergencyAbortThread.endLook();
                    if (DEBUG_LOGV) {
                        Log.v(LOG_TAG, "connectStream end");
                    }
                }
                if (!ret) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "connectStream failed");
                    }
                    params.callbackMessage.sendMessageError(ErrorCode.FAIL_CONNECT_STREAM);
                    return true;
                }

                try {
                    createTempFile();
                } catch (IOException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    params.callbackMessage.sendMessageError(ErrorCode.FAIL_CACHE_FILE);
                    return true;
                }

//                params.callbackMessage.sendMessageSuccess(null);
//                getHandler().obtainMessage(MSG_ID_READ_ASYNC, params).sendToTarget();
                getHandler().sendEmptyMessage(MSG_ID_READ_ASYNC);
            } break;
            case MSG_ID_RECONNECT_STREAM: {
//                Params params = (Params) msg.obj;
                Params params = mParams;
                if (params == null) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "mParams is null already, cancel");
                    }
                    return true;
                }

                boolean ret;
                try {
                    if (DEBUG_LOGV) {
                        Log.v(LOG_TAG, "reconnectStream start");
                    }
                    mEmergencyAbortThread.startLook();
                    synchronized (mSyncRtmp) {
                        if (mNativeInstance == 0) {
                            if (DEBUG_LOGD) {
                                Log.d(LOG_TAG, "reconnectStream: mNativeInstance free already");
                            }
                            return true;
                        }
                        ret = reconnectStream(mNativeInstance, 0);
                    }
                } finally {
                    mEmergencyAbortThread.endLook();
                    if (DEBUG_LOGV) {
                        Log.v(LOG_TAG, "reconnectStream end");
                    }
                }
                if (!ret) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "reconnectStream failed");
                    }
                    params.callbackMessage.sendMessageError(ErrorCode.FAIL_RECONNECT_STREAM);
                    return true;
                }

                try {
                    createTempFile();
                } catch (IOException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    params.callbackMessage.sendMessageError(ErrorCode.FAIL_CACHE_FILE);
                    return true;
                }

//                params.callbackMessage.sendMessageSuccess(null);
//                getHandler().obtainMessage(MSG_ID_READ_ASYNC, params).sendToTarget();
                getHandler().sendEmptyMessage(MSG_ID_READ_ASYNC);
            } break;
            case MSG_ID_READ_ASYNC: {
//                Params params = (Params) msg.obj;
                Params params = mParams;
                if (params == null) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "mParams is null already, cancel");
                    }
                    return true;
                }

                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, "RTMP read native start");
                }
                int read;
                try {
                    mEmergencyAbortThread.startLook();
                    synchronized (mSyncRtmp) {
                        if (mNativeInstance == 0) {
                            if (DEBUG_LOGD) {
                                Log.d(LOG_TAG, "read: mNativeInstance free already");
                            }
                            return true;
                        }
                        // TODO 接続できないときにまれにCPU100%でHeapWorkerが暴走する
                        // →メモリ破壊？
                        read = read(mNativeInstance, mReadBuffer, 0, mReadBuffer.length);
                    }
                } finally {
                    mEmergencyAbortThread.endLook();
                }
                if (DEBUG_LOGV) {
                    Log.v(LOG_TAG, Log.buf().append("RTMP read native end: read=")
                            .append(read).toString());
                }
                if (read < 0) {
                    Log.e(LOG_TAG, "MSG_ID_READ_ASYNC: read failed");
//                    // 再接続
//                    getHandler().obtainMessage(MSG_ID_RECONNECT_STREAM, params).sendToTarget();
                    params.callbackMessage.sendMessageError(ErrorCode.READ_ERROR);
                } else if (read == 0) {
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, "read 0: the stream is complete?");
                    }
//                    // 再接続
//                    getHandler().obtainMessage(MSG_ID_RECONNECT_STREAM, params).sendToTarget();
                    params.callbackMessage.sendMessageError(ErrorCode.READ_COMPLETE);
                } else {
                    AbstractMultiRandomAccessFile tempFile = mTempFile;
                    if (tempFile == null) {
                        // タイムアウトで外部スレッドから強制的に閉じられた
                        params.callbackMessage.sendMessageError(ErrorCode.FAIL_CACHE_FILE);
                    } else {
                        try {
                            if (DEBUG_LOGD_WRITE) {
                                Log.d(LOG_TAG, Log.buf().append("RTMP cache write start: ")
                                        .append(read).append(" bytes").toString());
                            }
                            android.os.Process.setThreadPriority(THREAD_PRIORITY_WRITE);
                            tempFile.write(mReadBuffer, 0, read);
                            android.os.Process.setThreadPriority(THREAD_PRIORITY_DEFAULT);
                            if (DEBUG_LOGD_WRITE) {
                                Log.d(LOG_TAG, "RTMP cache write end");
                            }
                            if (!mWasNotifiedConnect) {
                                // TODO
                                if (tempFile.tellWrite() >= FILE_CACHE_HEAD_SIZE) {
                                    params.callbackMessage.sendMessageSuccess(null);
                                    mWasNotifiedConnect = true;
                                }
                            }
                        } catch (IOException e) {
                            Log.e(LOG_TAG, e.toString(), e);
                            params.callbackMessage.sendMessageError(ErrorCode.FAIL_CACHE_FILE);
                        }
//                        getHandler().obtainMessage(MSG_ID_READ_ASYNC, params).sendToTarget();
                        getHandler().sendEmptyMessage(MSG_ID_READ_ASYNC);
                    }
                }
            } break;
            case MSG_ID_CLOSE: {
                if (mEmergencyAbortThread != null) {
                    mEmergencyAbortThread.finish();
                    mEmergencyAbortThread = null;
                }
                Handler handler = getHandler();
                handler.removeMessages(MSG_ID_START_CONNECT);
                handler.removeMessages(MSG_ID_RECONNECT_STREAM);
                handler.removeMessages(MSG_ID_READ_ASYNC);
                final Object waitClose = msg.obj;
                synchronized (waitClose) {
                    if (!mWasClosed) {
                        if (mNativeInstance != 0L) {
                            close(mNativeInstance);
                        }
                        mWasClosed = true;
                    }
                    waitClose.notifyAll();
                }
            } break;
        }
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "LiveVideoLoader handleMessage end ");
        }
        return true;
    }

    @Override
    public int readFromNativeCallback(int bufSize, byte[] buffer) {
        try {
            long startTime = 0;
            if (DEBUG_LOGD_READ) {
                startTime = SystemClock.elapsedRealtime();
            }
            int ret;
            AbstractMultiRandomAccessFile tempFile = mTempFile;
            if (tempFile == null) {
                ret = -1;
            } else {
                try {
                    long tellRead = tempFile.tellRead();
                    long tellWrite = tempFile.tellWrite();
                    if (DEBUG_LOGD_READ) {
                        Log.d(LOG_TAG, Log.buf()
                                .append("readFromNativeCallback: bufSize=").append(bufSize)
                                .append(" buffer=[").append(buffer.length)
                                .append("] mTempFile.tellRead()=").append(tellRead)
                                .append(" mTempFile.tellWrite()=").append(tellWrite)
                                .toString());
                    }
                    if (bufSize > buffer.length) {
                        bufSize = buffer.length;
                    }
                    try {
                        tempFile.readFully(buffer, 0, bufSize);
                        ret = bufSize;
                    } catch (EOFException e) {
                        int read = (int) Math.min(tellWrite - tellRead, (long) bufSize);
                        if (read > 0) {
                            ret = tempFile.read(buffer, 0, read);
                        } else {
                            ret = 0;
                        }
                    }
                } catch (IOException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    ret = -1;
                }
            }
            if (DEBUG_LOGD_READ) {
                Log.d(LOG_TAG, Log.buf().append("readFromNativeCallback: return=")
                        .append(ret).append(" time=")
                        .append(SystemClock.elapsedRealtime() - startTime)
                        .append("ms")
                        .toString());
            }
            return ret;
        } catch (Throwable e) {
            Log.e(LOG_TAG, e.toString(), e);
            return -1;
        }
    }

    @Override
    public long seekFromNativeCallback(long offset, int whence) {
        try {
            long startTime = 0;
            AbstractMultiRandomAccessFile tempFile = mTempFile;
            if (DEBUG_LOGD_SEEK) {
                startTime = SystemClock.elapsedRealtime();
                StringBuilder buf = Log.buf()
                    .append("seekFromNativeCallback: offset=").append(offset)
                    .append(" whence=").append(whence);
                if (tempFile == null) {
                    buf.append(" mTempFile is null!");
                } else {
                    buf.append(" mTempFile.tellRead()=").append(tempFile.tellRead())
                        .append(" mTempFile.tellWrite()=").append(tempFile.tellWrite());
                }
                Log.d(LOG_TAG, buf.toString());
            }
            long ret = -1L;
            try {
                final long MARGIN = 8 * 1024;
                switch (whence) {
                case SEEK_SET:
                    if (tempFile != null) {
                        long tellWrite = tempFile.tellWrite();
                        if (offset <= tellWrite + MARGIN) {
                            ret = tempFile.seekRead(offset, MultiRandomAccessFile.SEEK_SET);
                        }
                    }
                    break;
                case SEEK_CUR:
                    if (tempFile != null) {
                        long tellWrite = tempFile.tellWrite();
                        long tellRead = tempFile.tellRead();
                        if (tellRead + offset <= tellWrite + MARGIN) {
                            ret = tempFile.seekRead(offset, MultiRandomAccessFile.SEEK_CUR);
                        }
                    }
                    break;
                case SEEK_END:
                    if (tempFile != null) {
//                        ret = tempFile.seekRead(offset, MultiRandomAccessFile.SEEK_END);
                        long tellWrite = tempFile.tellWrite();
                        ret = tempFile.seekRead(tellWrite + offset, MultiRandomAccessFile.SEEK_SET);
                    }
//                    ret = -1;
                    break;
                case AVSEEK_SIZE:
                    ret = -1;
                    break;
                }
            } catch (IOException e) {
                Log.e(LOG_TAG, e.toString(), e);
                assert ret == -1;
            }
            if (DEBUG_LOGD_SEEK) {
                Log.d(LOG_TAG, Log.buf().append("seekFromNativeCallback: return=")
                        .append(ret).append(" time=")
                        .append(SystemClock.elapsedRealtime() - startTime)
                        .append("ms")
                        .toString());
            }
            return ret;
        } catch (Throwable e) {
            Log.e(LOG_TAG, e.toString(), e);
            return -1;
        }
    }

    static native long init();
    static native void close(long nativeInstance);
    static native void free(long nativeInstance);
    static native boolean setupURL(long nativeInstance, String url);
    static native boolean connect(long nativeInstance);
    static native boolean connectStream(long nativeInstance, int seekTime);
    static native boolean reconnectStream(long nativeInstance, int seekTime);
    static native int read(long nativeInstance, byte[] buf, int offset, int length);
    static native boolean pause(long nativeInstance, boolean doPause);
    static native boolean sendSeek(long nativeInstance, int dTime);

    static {
        FFmpegVideoDecoder.loadNativeLibrary();
    }
}
