/*****************************************************************************
* 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.support.annotation.MainThread;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import org.videolan.libvlc.util.AndroidUtil;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
class AWindow implements IAWindowNativeHandler, 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;
protected interface SurfaceCallback {
@MainThread
void onSurfacesCreated(AWindow vout);
@MainThread
void onSurfacesDestroyed(AWindow vout);
}
private class SurfaceHelper {
private final int mId;
private final SurfaceView mSurfaceView;
private final TextureView mTextureView;
private final SurfaceHolder mSurfaceHolder;
private Surface mSurface;
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 setSurface(Surface surface) {
if (surface.isValid() && getNativeSurface(mId) == null) {
mSurface = surface;
setNativeSurface(mId, mSurface);
onSurfaceCreated();
}
}
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 releaseSurfaceTexture() {
if (mTextureView != null)
mTextureView.setSurfaceTextureListener(null);
}
public void release() {
mSurface = null;
setNativeSurface(mId, null);
if (mSurfaceHolder != null)
mSurfaceHolder.removeCallback(mSurfaceHolderCallback);
releaseSurfaceTexture();
}
public boolean isReady() {
return mSurfaceView == null || mSurface != null;
}
public Surface getSurface() {
return mSurface;
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
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();
}
};
@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) {
}
};
}
private final TextureView.SurfaceTextureListener mSurfaceTextureListener =
AndroidUtil.isICSOrLater() ? createSurfaceTextureListener() : null;
}
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 SurfaceHelper[] mSurfaceHelpers;
private final SurfaceCallback mSurfaceCallback;
private final AtomicInteger mSurfacesState = new AtomicInteger(SURFACE_STATE_INIT);
private ArrayList<IVLCVout.Callback> mIVLCVoutCallbacks = new ArrayList<IVLCVout.Callback>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Object mNativeLock = new Object();
/* synchronized Surfaces accessed by an other thread from JNI */
private final Surface[] mSurfaces;
private long mCallbackNativeHandle = 0;
private int mMouseAction = -1, mMouseButton = -1, mMouseX = -1, mMouseY = -1;
private int mWindowWidth = -1, mWindowHeight = -1;
protected 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;
}
private void setView(int id, SurfaceView view) {
if (mSurfacesState.get() != SURFACE_STATE_INIT)
throw new IllegalStateException("Can't set view when already attached");
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) {
if (!AndroidUtil.isICSOrLater())
throw new IllegalArgumentException("TextureView not implemented in this android version");
if (mSurfacesState.get() != SURFACE_STATE_INIT)
throw new IllegalStateException("Can't set view when already attached");
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) {
if (mSurfacesState.get() != SURFACE_STATE_INIT)
throw new IllegalStateException("Can't set surface when already attached");
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
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void setVideoSurface(SurfaceTexture videoSurfaceTexture) {
setSurface(ID_VIDEO, new Surface(videoSurfaceTexture), null);
}
@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
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void setSubtitlesSurface(SurfaceTexture subtitlesSurfaceTexture) {
setSurface(ID_SUBTITLES, new Surface(subtitlesSurfaceTexture), null);
}
@Override
@MainThread
public void attachViews() {
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 (mBuffersGeometryCond) {
mBuffersGeometryCond.configured = false;
mBuffersGeometryCond.abort = false;
}
for (int id = 0; id < ID_MAX; ++id) {
final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
if (surfaceHelper != null)
surfaceHelper.attach();
}
}
@Override
@MainThread
public void detachViews() {
if (mSurfacesState.get() == SURFACE_STATE_INIT)
return;
mSurfacesState.set(SURFACE_STATE_INIT);
mHandler.removeCallbacksAndMessages(null);
synchronized (mBuffersGeometryCond) {
mBuffersGeometryCond.abort = true;
mBuffersGeometryCond.notifyAll();
}
for (int id = 0; id < ID_MAX; ++id) {
final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
if (surfaceHelper != null)
surfaceHelper.release();
mSurfaceHelpers[id] = null;
}
if (mSurfaceCallback != null)
mSurfaceCallback.onSurfacesDestroyed(this);
for (IVLCVout.Callback cb : mIVLCVoutCallbacks)
cb.onSurfacesDestroyed(this);
}
@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);
if (mSurfaceCallback != null)
mSurfaceCallback.onSurfacesCreated(this);
for (IVLCVout.Callback cb : mIVLCVoutCallbacks)
cb.onSurfacesCreated(this);
}
}
@MainThread
private void onSurfaceDestroyed() {
detachViews();
}
protected 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)
nativeOnMouseEvent(mCallbackNativeHandle, action, button, x, y);
else {
mMouseAction = action;
mMouseButton = button;
mMouseX = x;
mMouseY = y;
}
}
}
@Override
public void setWindowSize(int width, int height) {
synchronized (mNativeLock) {
if (mCallbackNativeHandle != 0)
nativeOnWindowSize(mCallbackNativeHandle, width, height);
else {
mWindowWidth = width;
mWindowHeight = height;
}
}
}
@Override
public boolean setCallback(long nativeHandle) {
synchronized (mNativeLock) {
if (mCallbackNativeHandle != 0 && nativeHandle != 0)
return false;
mCallbackNativeHandle = nativeHandle;
if (mCallbackNativeHandle != 0) {
if (mMouseAction != -1)
nativeOnMouseEvent(mCallbackNativeHandle, mMouseAction, mMouseButton, mMouseX, mMouseY);
if (mWindowWidth != -1 && mWindowHeight != -1)
nativeOnWindowSize(mCallbackNativeHandle, mWindowWidth, mWindowHeight);
}
mMouseAction = mMouseButton = mMouseX = mMouseY = -1;
mWindowWidth = mWindowHeight = -1;
}
return true;
}
private void setNativeSurface(int id, Surface surface) {
synchronized (mNativeLock) {
mSurfaces[id] = surface;
}
}
private Surface getNativeSurface(int id) {
synchronized (mNativeLock) {
return mSurfaces[id];
}
}
@Override
public Surface getVideoSurface() {
return getNativeSurface(ID_VIDEO);
}
@Override
public Surface getSubtitlesSurface() {
return getNativeSurface(ID_SUBTITLES);
}
private static class BuffersGeometryCond {
private boolean configured = false;
private boolean abort = false;
}
private final BuffersGeometryCond mBuffersGeometryCond = new BuffersGeometryCond();
@Override
public boolean setBuffersGeometry(final Surface surface, final int width, final int height, final int format) {
if (AndroidUtil.isICSOrLater())
return false;
if (width * height == 0)
return false;
Log.d(TAG, "configureSurface: " + width + "x" + height);
synchronized (mBuffersGeometryCond) {
if (mBuffersGeometryCond.configured || mBuffersGeometryCond.abort)
return false;
}
mHandler.post(new Runnable() {
private SurfaceHelper getSurfaceHelper(Surface surface) {
for (int id = 0; id < ID_MAX; ++id) {
final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
if (surfaceHelper != null && surfaceHelper.getSurface() == surface)
return surfaceHelper;
}
return null;
}
@Override
public void run() {
final SurfaceHelper surfaceHelper = getSurfaceHelper(surface);
final SurfaceHolder surfaceHolder = surfaceHelper != null ? surfaceHelper.getSurfaceHolder() : null;
if (surfaceHolder != null) {
if (surfaceHolder.getSurface().isValid()) {
if (format != 0)
surfaceHolder.setFormat(format);
surfaceHolder.setFixedSize(width, height);
}
}
synchronized (mBuffersGeometryCond) {
mBuffersGeometryCond.configured = true;
mBuffersGeometryCond.notifyAll();
}
}
});
try {
synchronized (mBuffersGeometryCond) {
while (!mBuffersGeometryCond.configured && !mBuffersGeometryCond.abort)
mBuffersGeometryCond.wait();
mBuffersGeometryCond.configured = false;
}
} catch (InterruptedException e) {
return false;
}
return true;
}
@Override
public void addCallback(IVLCVout.Callback callback) {
if (!mIVLCVoutCallbacks.contains(callback))
mIVLCVoutCallbacks.add(callback);
}
@Override
public void removeCallback(IVLCVout.Callback callback) {
mIVLCVoutCallbacks.remove(callback);
}
@Override
public void setWindowLayout(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() {
for (IVLCVout.Callback cb : mIVLCVoutCallbacks)
cb.onNewLayout(AWindow.this, width, height, visibleWidth, visibleHeight, sarNum, sarDen);
}
});
}
public native void nativeOnMouseEvent(long nativeHandle, int action, int button, int x, int y);
public native void nativeOnWindowSize(long nativeHandle, int width, int height);
}