/*****************************************************************************
 * class AWindow.java
 *****************************************************************************
 * Copyright © 2015 VLC authors, VideoLAN and VideoLabs
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

package org.videolan.libvlc;

import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;

import androidx.annotation.MainThread;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

@SuppressWarnings("WeakerAccess")
public class AWindow implements IVLCVout {
    private static final String TAG = "AWindow";

    private static final int ID_VIDEO = 0;
    private static final int ID_SUBTITLES = 1;
    private static final int ID_MAX = 2;
    private final static int SURFACE_STATE_INIT = 0;
    private final static int SURFACE_STATE_ATTACHED = 1;
    private final static int SURFACE_STATE_READY = 2;
    private final static int AWINDOW_REGISTER_ERROR = 0;
    private final static int AWINDOW_REGISTER_FLAGS_SUCCESS = 0x1;
    private final static int AWINDOW_REGISTER_FLAGS_HAS_VIDEO_LAYOUT_LISTENER = 0x2;
    private final SurfaceHelper[] mSurfaceHelpers;
    private final SurfaceCallback mSurfaceCallback;
    private final AtomicInteger mSurfacesState = new AtomicInteger(SURFACE_STATE_INIT);
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    /* synchronized Surfaces accessed by an other thread from JNI */
    private final Surface[] mSurfaces;
    private final NativeLock mNativeLock = new NativeLock();
    private OnNewVideoLayoutListener mOnNewVideoLayoutListener = null;
    private ArrayList<IVLCVout.Callback> mIVLCVoutCallbacks = new ArrayList<IVLCVout.Callback>();
    private long mCallbackNativeHandle = 0;
    private int mMouseAction = -1, mMouseButton = -1, mMouseX = -1, mMouseY = -1;
    private int mWindowWidth = -1, mWindowHeight = -1;
    private SurfaceTextureThread mSurfaceTextureThread = new SurfaceTextureThread();

    /**
     * Create an AWindow
     * <p>
     * You call this directly only if you use the libvlc_media_player native API (and not the Java
     * MediaPlayer class).
     *
     * @param surfaceCallback
     */
    public AWindow(SurfaceCallback surfaceCallback) {
        mSurfaceCallback = surfaceCallback;
        mSurfaceHelpers = new SurfaceHelper[ID_MAX];
        mSurfaceHelpers[ID_VIDEO] = null;
        mSurfaceHelpers[ID_SUBTITLES] = null;
        mSurfaces = new Surface[ID_MAX];
        mSurfaces[ID_VIDEO] = null;
        mSurfaces[ID_SUBTITLES] = null;
    }

    /**
     * Callback called from {@link IVLCVout#sendMouseEvent}.
     *
     * @param nativeHandle handle passed by {@link #registerNative(long)}.
     * @param action       see ACTION_* in {@link android.view.MotionEvent}.
     * @param button       see BUTTON_* in {@link android.view.MotionEvent}.
     * @param x            x coordinate.
     * @param y            y coordinate.
     */
    @SuppressWarnings("JniMissingFunction")
    private static native void nativeOnMouseEvent(long nativeHandle, int action, int button, int x, int y);

    /**
     * Callback called from {@link IVLCVout#setWindowSize}.
     *
     * @param nativeHandle handle passed by {@link #registerNative(long)}.
     * @param width        width of the window.
     * @param height       height of the window.
     */
    @SuppressWarnings("JniMissingFunction")
    private static native void nativeOnWindowSize(long nativeHandle, int width, int height);

    private void ensureInitState() throws IllegalStateException {
        if (mSurfacesState.get() != SURFACE_STATE_INIT)
            throw new IllegalStateException("Can't set view when already attached. " +
                    "Current state: " + mSurfacesState.get() + ", " +
                    "mSurfaces[ID_VIDEO]: " + mSurfaceHelpers[ID_VIDEO] + " / " + mSurfaces[ID_VIDEO] + ", " +
                    "mSurfaces[ID_SUBTITLES]: " + mSurfaceHelpers[ID_SUBTITLES] + " / " + mSurfaces[ID_SUBTITLES]);
    }

    private void setView(int id, SurfaceView view) {
        ensureInitState();
        if (view == null)
            throw new NullPointerException("view is null");
        final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
        if (surfaceHelper != null)
            surfaceHelper.release();

        mSurfaceHelpers[id] = new SurfaceHelper(id, view);
    }

    private void setView(int id, TextureView view) {
        ensureInitState();
        if (view == null)
            throw new NullPointerException("view is null");
        final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
        if (surfaceHelper != null)
            surfaceHelper.release();

        mSurfaceHelpers[id] = new SurfaceHelper(id, view);
    }

    private void setSurface(int id, Surface surface, SurfaceHolder surfaceHolder) {
        ensureInitState();
        if (!surface.isValid() && surfaceHolder == null)
            throw new IllegalStateException("surface is not attached and holder is null");
        final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
        if (surfaceHelper != null)
            surfaceHelper.release();

        mSurfaceHelpers[id] = new SurfaceHelper(id, surface, surfaceHolder);
    }

    @Override
    @MainThread
    public void setVideoView(SurfaceView videoSurfaceView) {
        setView(ID_VIDEO, videoSurfaceView);
    }

    @Override
    @MainThread
    public void setVideoView(TextureView videoTextureView) {
        setView(ID_VIDEO, videoTextureView);
    }

    @Override
    public void setVideoSurface(Surface videoSurface, SurfaceHolder surfaceHolder) {
        setSurface(ID_VIDEO, videoSurface, surfaceHolder);
    }

    @Override
    @MainThread
    public void setSubtitlesView(SurfaceView subtitlesSurfaceView) {
        setView(ID_SUBTITLES, subtitlesSurfaceView);
    }

    @Override
    @MainThread
    public void setSubtitlesView(TextureView subtitlesTextureView) {
        setView(ID_SUBTITLES, subtitlesTextureView);
    }

    @Override
    public void setSubtitlesSurface(Surface subtitlesSurface, SurfaceHolder surfaceHolder) {
        setSurface(ID_SUBTITLES, subtitlesSurface, surfaceHolder);
    }

    @Override
    @MainThread
    public void attachViews(OnNewVideoLayoutListener onNewVideoLayoutListener) {
        if (mSurfacesState.get() != SURFACE_STATE_INIT || mSurfaceHelpers[ID_VIDEO] == null)
            throw new IllegalStateException("already attached or video view not configured");
        mSurfacesState.set(SURFACE_STATE_ATTACHED);
        synchronized (mNativeLock) {
            mOnNewVideoLayoutListener = onNewVideoLayoutListener;
            mNativeLock.buffersGeometryConfigured = false;
            mNativeLock.buffersGeometryAbort = false;
        }
        for (int id = 0; id < ID_MAX; ++id) {
            final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
            if (surfaceHelper != null)
                surfaceHelper.attach();
        }
    }

    @Override
    @MainThread
    public void attachViews() {
        attachViews(null);
    }

    @Override
    @MainThread
    public void detachViews() {
        if (mSurfacesState.get() == SURFACE_STATE_INIT)
            return;

        mSurfacesState.set(SURFACE_STATE_INIT);
        mHandler.removeCallbacksAndMessages(null);
        synchronized (mNativeLock) {
            mOnNewVideoLayoutListener = null;
            mNativeLock.buffersGeometryAbort = true;
            mNativeLock.notifyAll();
        }
        for (int id = 0; id < ID_MAX; ++id) {
            final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
            if (surfaceHelper != null)
                surfaceHelper.release();
            mSurfaceHelpers[id] = null;
        }
        for (IVLCVout.Callback cb : mIVLCVoutCallbacks)
            cb.onSurfacesDestroyed(this);
        if (mSurfaceCallback != null)
            mSurfaceCallback.onSurfacesDestroyed(this);
        mSurfaceTextureThread.release();
    }

    @Override
    @MainThread
    public boolean areViewsAttached() {
        return mSurfacesState.get() != SURFACE_STATE_INIT;
    }

    @MainThread
    private void onSurfaceCreated() {
        if (mSurfacesState.get() != SURFACE_STATE_ATTACHED)
            throw new IllegalArgumentException("invalid state");

        final SurfaceHelper videoHelper = mSurfaceHelpers[ID_VIDEO];
        final SurfaceHelper subtitlesHelper = mSurfaceHelpers[ID_SUBTITLES];
        if (videoHelper == null)
            throw new NullPointerException("videoHelper shouldn't be null here");

        if (videoHelper.isReady() && (subtitlesHelper == null || subtitlesHelper.isReady())) {
            mSurfacesState.set(SURFACE_STATE_READY);
            for (IVLCVout.Callback cb : mIVLCVoutCallbacks)
                cb.onSurfacesCreated(this);
            if (mSurfaceCallback != null)
                mSurfaceCallback.onSurfacesCreated(this);
        }
    }

    @MainThread
    private void onSurfaceDestroyed() {
        detachViews();
    }

    boolean areSurfacesWaiting() {
        return mSurfacesState.get() == SURFACE_STATE_ATTACHED;
    }

    @Override
    public void sendMouseEvent(int action, int button, int x, int y) {
        synchronized (mNativeLock) {
            if (mCallbackNativeHandle != 0 && (mMouseAction != action || mMouseButton != button
                    || mMouseX != x || mMouseY != y))
                nativeOnMouseEvent(mCallbackNativeHandle, action, button, x, y);
            mMouseAction = action;
            mMouseButton = button;
            mMouseX = x;
            mMouseY = y;
        }
    }

    @Override
    public void setWindowSize(int width, int height) {
        synchronized (mNativeLock) {
            if (mCallbackNativeHandle != 0 && (mWindowWidth != width || mWindowHeight != height))
                nativeOnWindowSize(mCallbackNativeHandle, width, height);
            mWindowWidth = width;
            mWindowHeight = height;
        }
    }

    private void setNativeSurface(int id, Surface surface) {
        synchronized (mNativeLock) {
            mSurfaces[id] = surface;
        }
    }

    private Surface getNativeSurface(int id) {
        synchronized (mNativeLock) {
            return mSurfaces[id];
        }
    }

    @Override
    public void addCallback(IVLCVout.Callback callback) {
        if (!mIVLCVoutCallbacks.contains(callback))
            mIVLCVoutCallbacks.add(callback);
    }

    @Override
    public void removeCallback(IVLCVout.Callback callback) {
        mIVLCVoutCallbacks.remove(callback);
    }

    /**
     * Get the valid Video surface.
     *
     * @return can be null if the surface was destroyed.
     */
    @SuppressWarnings("unused") /* used by JNI */
    private Surface getVideoSurface() {
        return getNativeSurface(ID_VIDEO);
    }

    @Override
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void setVideoSurface(SurfaceTexture videoSurfaceTexture) {
        setSurface(ID_VIDEO, new Surface(videoSurfaceTexture), null);
    }

    /**
     * Get the valid Subtitles surface.
     *
     * @return can be null if the surface was destroyed.
     */
    @SuppressWarnings("unused") /* used by JNI */
    private Surface getSubtitlesSurface() {
        return getNativeSurface(ID_SUBTITLES);

    }

    @Override
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void setSubtitlesSurface(SurfaceTexture subtitlesSurfaceTexture) {
        setSurface(ID_SUBTITLES, new Surface(subtitlesSurfaceTexture), null);
    }

    /**
     * Set a callback in order to receive {@link #nativeOnMouseEvent} and {@link #nativeOnWindowSize} events.
     *
     * @param nativeHandle native Handle passed by {@link #nativeOnMouseEvent} and {@link #nativeOnWindowSize}, cannot be NULL
     * @return true if callback was successfully registered
     */
    @SuppressWarnings("unused") /* used by JNI */
    private int registerNative(long nativeHandle) {
        if (nativeHandle == 0)
            throw new IllegalArgumentException("nativeHandle is null");
        synchronized (mNativeLock) {
            if (mCallbackNativeHandle != 0)
                return AWINDOW_REGISTER_ERROR;
            mCallbackNativeHandle = nativeHandle;
            if (mMouseAction != -1)
                nativeOnMouseEvent(mCallbackNativeHandle, mMouseAction, mMouseButton, mMouseX, mMouseY);
            if (mWindowWidth != -1 && mWindowHeight != -1)
                nativeOnWindowSize(mCallbackNativeHandle, mWindowWidth, mWindowHeight);
            int flags = AWINDOW_REGISTER_FLAGS_SUCCESS;

            if (mOnNewVideoLayoutListener != null)
                flags |= AWINDOW_REGISTER_FLAGS_HAS_VIDEO_LAYOUT_LISTENER;
            return flags;
        }
    }

    @SuppressWarnings("unused") /* used by JNI */
    private void unregisterNative() {
        synchronized (mNativeLock) {
            if (mCallbackNativeHandle == 0)
                throw new IllegalArgumentException("unregister called when not registered");
            mCallbackNativeHandle = 0;
        }
    }

    /**
     * This method is only used for HoneyComb and before since ANativeWindow_setBuffersGeometry doesn't work before.
     * It is synchronous.
     *
     * @param surface surface returned by getVideoSurface or getSubtitlesSurface
     * @param width   surface width
     * @param height  surface height
     * @param format  color format (or PixelFormat)
     * @return true if buffersGeometry were set (only before ICS)
     */
    @SuppressWarnings("unused") /* used by JNI */
    private boolean setBuffersGeometry(final Surface surface, final int width, final int height, final int format) {
        return false;
    }

    /**
     * Set the video Layout.
     * This call will result of{@link IVLCVout.OnNewVideoLayoutListener#onNewVideoLayout(IVLCVout, int, int, int, int, int, int)}
     * being called from the main thread.
     *
     * @param width         Frame width
     * @param height        Frame height
     * @param visibleWidth  Visible frame width
     * @param visibleHeight Visible frame height
     * @param sarNum        Surface aspect ratio numerator
     * @param sarDen        Surface aspect ratio denominator
     */
    @SuppressWarnings("unused") /* used by JNI */
    private void setVideoLayout(final int width, final int height, final int visibleWidth,
                                final int visibleHeight, final int sarNum, final int sarDen) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                /* No need to synchronize here, mOnNewVideoLayoutListener is only set from MainThread */
                if (mOnNewVideoLayoutListener != null)
                    mOnNewVideoLayoutListener.onNewVideoLayout(AWindow.this, width, height,
                            visibleWidth, visibleHeight, sarNum, sarDen);
            }
        });
    }

    /**
     * Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread.
     *
     * @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
     * @return true in case of success
     */
    @SuppressWarnings("unused") /* used by JNI */
    boolean SurfaceTexture_attachToGLContext(int texName) {
        return mSurfaceTextureThread.attachToGLContext(texName);
    }

    /**
     * Detach the SurfaceTexture from the OpenGL ES context that owns the OpenGL ES texture object.
     */
    @SuppressWarnings("unused") /* used by JNI */
    private void SurfaceTexture_detachFromGLContext() {
        mSurfaceTextureThread.detachFromGLContext();
    }

    /**
     * Wait for a frame and update the TexImage
     *
     * @return true on success, false on error or timeout
     */
    @SuppressWarnings("unused") /* used by JNI */
    private boolean SurfaceTexture_waitAndUpdateTexImage(float[] transformMatrix) {
        return mSurfaceTextureThread.waitAndUpdateTexImage(transformMatrix);
    }

    /**
     * Get a Surface from the SurfaceTexture
     */
    @SuppressWarnings("unused") /* used by JNI */
    private Surface SurfaceTexture_getSurface() {
        return mSurfaceTextureThread.getSurface();
    }

    public interface SurfaceCallback {
        @MainThread
        void onSurfacesCreated(AWindow vout);

        @MainThread
        void onSurfacesDestroyed(AWindow vout);
    }

    private static class NativeLock {
        private boolean buffersGeometryConfigured = false;
        private boolean buffersGeometryAbort = false;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private static class SurfaceTextureThread
            implements Runnable, SurfaceTexture.OnFrameAvailableListener {
        private SurfaceTexture mSurfaceTexture = null;
        private Surface mSurface = null;

        private boolean mFrameAvailable = false;
        private Looper mLooper = null;
        private Thread mThread = null;
        private boolean mIsAttached = false;
        private boolean mDoRelease = false;

        private SurfaceTextureThread() {
        }

        private synchronized boolean attachToGLContext(int texName) {
            /* Try to re-use the same SurfaceTexture until views are detached. By reusing the same
             * SurfaceTexture, we don't have to reconfigure MediaCodec when it signals a video size
             * change (and when a new VLC vout is created) */
            if (mSurfaceTexture == null) {
                /* Yes, a new Thread, see comments in the run method */
                mThread = new Thread(this);
                mThread.start();
                while (mSurfaceTexture == null) {
                    try {
                        wait();
                    } catch (InterruptedException ignored) {
                        return false;
                    }
                }
                mSurface = new Surface(mSurfaceTexture);
            }
            mSurfaceTexture.attachToGLContext(texName);
            mFrameAvailable = false;
            mIsAttached = true;
            return true;
        }

        @Override
        public synchronized void onFrameAvailable(SurfaceTexture surfaceTexture) {
            if (surfaceTexture == mSurfaceTexture) {
                if (mFrameAvailable)
                    throw new IllegalStateException("An available frame was not updated");
                mFrameAvailable = true;
                notify();
            }
        }

        @Override
        public void run() {
            Looper.prepare();

            synchronized (this) {
                /* Ideally, all devices are running Android O, and we can create a SurfaceTexture
                 * without an OpenGL context and we can specify the thread (Handler) where to run
                 * SurfaceTexture callbacks. But this is not the case. The SurfaceTexture has to be
                 * created from a new thread with a prepared looper in order to don't use the
                 * MainLooper one (and have deadlock when we stop VLC from the mainloop).
                 */
                mLooper = Looper.myLooper();
                mSurfaceTexture = new SurfaceTexture(0);
                /* The OpenGL texture will be attached from the OpenGL Thread */
                mSurfaceTexture.detachFromGLContext();
                mSurfaceTexture.setOnFrameAvailableListener(this);
                notify();
            }

            Looper.loop();
        }

        private synchronized void detachFromGLContext() {
            if (mDoRelease) {
                mLooper.quit();
                mLooper = null;

                try {
                    mThread.join();
                } catch (InterruptedException ignored) {
                }
                mThread = null;

                mSurface.release();
                mSurface = null;
                mSurfaceTexture.release();
                mSurfaceTexture = null;
                mDoRelease = false;
            } else {
                mSurfaceTexture.detachFromGLContext();
            }
            mIsAttached = false;
        }

        private boolean waitAndUpdateTexImage(float[] transformMatrix) {
            synchronized (this) {
                while (!mFrameAvailable) {
                    try {
                        wait(500);
                        if (!mFrameAvailable)
                            return false;
                    } catch (InterruptedException ignored) {
                    }
                }
                mFrameAvailable = false;
            }
            mSurfaceTexture.updateTexImage();
            mSurfaceTexture.getTransformMatrix(transformMatrix);
            return true;
        }

        private synchronized Surface getSurface() {
            return mSurface;
        }

        private synchronized void release() {
            if (mSurfaceTexture != null) {
                if (mIsAttached) {
                    /* Release from detachFromGLContext */
                    mDoRelease = true;
                } else {
                    mSurface.release();
                    mSurface = null;
                    mSurfaceTexture.release();
                    mSurfaceTexture = null;
                }
            }
        }
    }

    private class SurfaceHelper {
        private final int mId;
        private final SurfaceView mSurfaceView;
        private final TextureView mTextureView;
        private final SurfaceHolder mSurfaceHolder;
        private Surface mSurface;
        private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (holder != mSurfaceHolder)
                    throw new IllegalStateException("holders are different");
                setSurface(holder.getSurface());
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                onSurfaceDestroyed();
            }
        };
        private final TextureView.SurfaceTextureListener mSurfaceTextureListener = createSurfaceTextureListener();

        private SurfaceHelper(int id, SurfaceView surfaceView) {
            mId = id;
            mTextureView = null;
            mSurfaceView = surfaceView;
            mSurfaceHolder = mSurfaceView.getHolder();
        }

        private SurfaceHelper(int id, TextureView textureView) {
            mId = id;
            mSurfaceView = null;
            mSurfaceHolder = null;
            mTextureView = textureView;
        }

        private SurfaceHelper(int id, Surface surface, SurfaceHolder surfaceHolder) {
            mId = id;
            mSurfaceView = null;
            mTextureView = null;
            mSurfaceHolder = surfaceHolder;
            mSurface = surface;
        }

        private void attachSurfaceView() {
            mSurfaceHolder.addCallback(mSurfaceHolderCallback);
            setSurface(mSurfaceHolder.getSurface());
        }

        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        private void attachTextureView() {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
            setSurface(new Surface(mTextureView.getSurfaceTexture()));
        }

        private void attachSurface() {
            if (mSurfaceHolder != null)
                mSurfaceHolder.addCallback(mSurfaceHolderCallback);
            setSurface(mSurface);
        }

        public void attach() {
            if (mSurfaceView != null) {
                attachSurfaceView();
            } else if (mTextureView != null) {
                attachTextureView();
            } else if (mSurface != null) {
                attachSurface();
            } else
                throw new IllegalStateException();
        }

        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        private void releaseTextureView() {
            if (mTextureView != null)
                mTextureView.setSurfaceTextureListener(null);
        }

        public void release() {
            mSurface = null;
            setNativeSurface(mId, null);
            if (mSurfaceHolder != null)
                mSurfaceHolder.removeCallback(mSurfaceHolderCallback);
            releaseTextureView();
        }

        public boolean isReady() {
            return mSurfaceView == null || mSurface != null;
        }

        public Surface getSurface() {
            return mSurface;
        }

        private void setSurface(Surface surface) {
            if (surface.isValid() && getNativeSurface(mId) == null) {
                mSurface = surface;
                setNativeSurface(mId, mSurface);
                onSurfaceCreated();
            }
        }

        SurfaceHolder getSurfaceHolder() {
            return mSurfaceHolder;
        }

        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        private TextureView.SurfaceTextureListener createSurfaceTextureListener() {
            return new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
                    setSurface(new Surface(surfaceTexture));
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

                }

                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    onSurfaceDestroyed();
                    return true;
                }

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {
                }
            };
        }
    }
}