[Android] [PATCH] libvlc: replace static jni vout utils with java helpers
Thomas Guillem
thomas at gllm.fr
Thu Jun 25 14:59:27 CEST 2015
IVLCVout: setup MediaPlayer Vout (Video, Subtitles surfaces).
IAWindowNativeHandler: used by native vlc.
AWindow: implements IAWindowNativeHandler and IVLCVout and owned by a
MediaPlayer.
---
libvlc/jni/Android.mk | 2 +-
libvlc/jni/libvlcjni-mediaplayer.c | 37 +-
libvlc/jni/libvlcjni.c | 70 ++--
libvlc/jni/vout.c | 224 ----------
libvlc/jni/vout.h | 28 --
libvlc/src/org/videolan/libvlc/AWindow.java | 459 +++++++++++++++++++++
.../org/videolan/libvlc/IAWindowNativeHandler.java | 97 +++++
libvlc/src/org/videolan/libvlc/IVLCVout.java | 123 ++++++
libvlc/src/org/videolan/libvlc/IVideoPlayer.java | 54 ---
libvlc/src/org/videolan/libvlc/LibVLC.java | 23 +-
libvlc/src/org/videolan/libvlc/MediaPlayer.java | 43 +-
.../vlc/gui/video/VideoPlayerActivity.java | 261 +++---------
12 files changed, 863 insertions(+), 558 deletions(-)
delete mode 100644 libvlc/jni/vout.c
delete mode 100644 libvlc/jni/vout.h
create mode 100644 libvlc/src/org/videolan/libvlc/AWindow.java
create mode 100644 libvlc/src/org/videolan/libvlc/IAWindowNativeHandler.java
create mode 100644 libvlc/src/org/videolan/libvlc/IVLCVout.java
delete mode 100644 libvlc/src/org/videolan/libvlc/IVideoPlayer.java
diff --git a/libvlc/jni/Android.mk b/libvlc/jni/Android.mk
index 4337bad..a513010 100644
--- a/libvlc/jni/Android.mk
+++ b/libvlc/jni/Android.mk
@@ -9,7 +9,7 @@ LOCAL_SRC_FILES += libvlcjni-mediaplayer.c
LOCAL_SRC_FILES += libvlcjni-equalizer.c
LOCAL_SRC_FILES += libvlcjni-vlcobject.c
LOCAL_SRC_FILES += libvlcjni-media.c libvlcjni-medialist.c libvlcjni-mediadiscoverer.c
-LOCAL_SRC_FILES += vout.c native_crash_handler.c thumbnailer.c
+LOCAL_SRC_FILES += native_crash_handler.c thumbnailer.c
LOCAL_SRC_FILES += std_logger.c
ifneq ($(APP_PLATFORM),android-21)
diff --git a/libvlc/jni/libvlcjni-mediaplayer.c b/libvlc/jni/libvlcjni-mediaplayer.c
index e3d7caf..f72a9d2 100644
--- a/libvlc/jni/libvlcjni-mediaplayer.c
+++ b/libvlc/jni/libvlcjni-mediaplayer.c
@@ -26,9 +26,15 @@
#define THREAD_NAME "libvlcjni"
JNIEnv *jni_get_env(const char *name);
+extern JavaVM *libvlc_get_jvm();
extern jobject eventHandlerInstance;
+struct vlcjni_object_sys
+{
+ jobject jwindow;
+};
+
/* TODO REMOVE */
static void vlc_event_callback(const libvlc_event_t *ev, void *data)
{
@@ -119,14 +125,26 @@ end:
}
static void
-MediaPlayer_newCommon(JNIEnv *env, jobject thiz, vlcjni_object *p_obj)
+MediaPlayer_newCommon(JNIEnv *env, jobject thiz, vlcjni_object *p_obj,
+ jobject jwindow)
{
- if (!p_obj->u.p_mp)
+ p_obj->p_sys = calloc(1, sizeof(vlcjni_object_sys));
+
+ if (!p_obj->u.p_mp || !p_obj->p_sys)
+ {
+ VLCJniObject_release(env, thiz, p_obj);
+ throw_IllegalStateException(env, "can't create MediaPlayer instance");
+ return;
+ }
+ p_obj->p_sys->jwindow = (*env)->NewGlobalRef(env, jwindow);
+ if (!p_obj->p_sys->jwindow)
{
VLCJniObject_release(env, thiz, p_obj);
throw_IllegalStateException(env, "can't create MediaPlayer instance");
return;
}
+ libvlc_media_player_set_android_context(p_obj->u.p_mp, libvlc_get_jvm(),
+ p_obj->p_sys->jwindow);
/*
VLCJniObject_attachEvents(p_obj, MediaPlayer_event_cb,
libvlc_media_event_manager(p_obj->u.p_mp),
@@ -156,7 +174,8 @@ MediaPlayer_newCommon(JNIEnv *env, jobject thiz, vlcjni_object *p_obj)
void
Java_org_videolan_libvlc_MediaPlayer_nativeNewFromLibVlc(JNIEnv *env,
jobject thiz,
- jobject libvlc)
+ jobject libvlc,
+ jobject jwindow)
{
vlcjni_object *p_obj = VLCJniObject_newFromJavaLibVlc(env, thiz, libvlc);
if (!p_obj)
@@ -164,13 +183,14 @@ Java_org_videolan_libvlc_MediaPlayer_nativeNewFromLibVlc(JNIEnv *env,
/* Create a media player playing environment */
p_obj->u.p_mp = libvlc_media_player_new(p_obj->p_libvlc);
- MediaPlayer_newCommon(env, thiz, p_obj);
+ MediaPlayer_newCommon(env, thiz, p_obj, jwindow);
}
void
Java_org_videolan_libvlc_MediaPlayer_nativeNewFromMedia(JNIEnv *env,
jobject thiz,
- jobject jmedia)
+ jobject jmedia,
+ jobject jwindow)
{
vlcjni_object *p_obj;
vlcjni_object *p_m_obj = VLCJniObject_getInstance(env, jmedia);
@@ -182,7 +202,7 @@ Java_org_videolan_libvlc_MediaPlayer_nativeNewFromMedia(JNIEnv *env,
if (!p_obj)
return;
p_obj->u.p_mp = libvlc_media_player_new_from_media(p_m_obj->u.p_m);
- MediaPlayer_newCommon(env, thiz, p_obj);
+ MediaPlayer_newCommon(env, thiz, p_obj, jwindow);
}
void
@@ -212,6 +232,11 @@ Java_org_videolan_libvlc_MediaPlayer_nativeRelease(JNIEnv *env, jobject thiz)
libvlc_event_detach(ev, mp_events[i], vlc_event_callback, NULL);
libvlc_media_player_release(p_obj->u.p_mp);
+ if (p_obj->p_sys && p_obj->p_sys->jwindow)
+ (*env)->DeleteGlobalRef(env, p_obj->p_sys->jwindow);
+
+ free(p_obj->p_sys);
+
VLCJniObject_release(env, thiz, p_obj);
}
diff --git a/libvlc/jni/libvlcjni.c b/libvlc/jni/libvlcjni.c
index 7970399..0e10204 100644
--- a/libvlc/jni/libvlcjni.c
+++ b/libvlc/jni/libvlcjni.c
@@ -36,7 +36,6 @@
#include "libvlcjni-modules.h"
#include "libvlcjni-vlcobject.h"
-#include "vout.h"
#include "utils.h"
#include "native_crash_handler.h"
#include "std_logger.h"
@@ -55,6 +54,13 @@ jobject eventHandlerInstance = NULL;
* can only be one instance of this shared library in a single VM
*/
static JavaVM *myVm;
+
+JavaVM *
+libvlc_get_jvm()
+{
+ return myVm;
+}
+
static pthread_key_t jni_env_key;
/* This function is called when a thread attached to the Java VM is canceled or
@@ -117,9 +123,6 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved)
if (pthread_key_create(&jni_env_key, jni_detach_thread) != 0)
return -1;
- pthread_mutex_init(&vout_android_lock, NULL);
- pthread_cond_init(&vout_android_surf_attached, NULL);
-
#ifndef NDEBUG
p_std_logger = std_logger_Open("VLC-std");
#endif
@@ -242,9 +245,6 @@ void JNI_OnUnload(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
- pthread_mutex_destroy(&vout_android_lock);
- pthread_cond_destroy(&vout_android_surf_attached);
-
destroy_native_crash_handler();
if ((*vm)->GetEnv(vm, (void**) &env, VLC_JNI_VERSION) != JNI_OK)
@@ -371,27 +371,6 @@ jstring Java_org_videolan_libvlc_LibVLC_changeset(JNIEnv* env, jobject thiz)
return (*env)->NewStringUTF(env, libvlc_get_changeset());
}
-// TODO: remove static variables
-static int i_window_width = 0;
-static int i_window_height = 0;
-
-void Java_org_videolan_libvlc_LibVLC_setWindowSize(JNIEnv *env, jobject thiz, jint width, jint height)
-{
- pthread_mutex_lock(&vout_android_lock);
- i_window_width = width;
- i_window_height = height;
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-int jni_GetWindowSize(int *width, int *height)
-{
- pthread_mutex_lock(&vout_android_lock);
- *width = i_window_width;
- *height = i_window_height;
- pthread_mutex_unlock(&vout_android_lock);
- return 0;
-}
-
/* used by opensles module */
int aout_get_native_sample_rate(void)
{
@@ -403,3 +382,38 @@ int aout_get_native_sample_rate(void)
int sample_rate = (*p_env)->CallStaticIntMethod (p_env, cls, method, 3); // AudioManager.STREAM_MUSIC
return sample_rate;
}
+
+/* TODO REMOVE */
+static jobject error_obj = NULL;
+pthread_mutex_t error_obj_lock;
+
+void Java_org_videolan_libvlc_LibVLC_nativeSetOnHardwareAccelerationError(JNIEnv *env, jobject thiz, jobject error_obj_)
+{
+ pthread_mutex_lock(&error_obj_lock);
+
+ if (error_obj != NULL)
+ (*env)->DeleteGlobalRef(env, error_obj);
+ error_obj = error_obj_ ? (*env)->NewGlobalRef(env, error_obj_) : NULL;
+ pthread_mutex_unlock(&error_obj_lock);
+}
+
+void jni_EventHardwareAccelerationError()
+{
+ JNIEnv *env;
+
+ if (!(env = jni_get_env(THREAD_NAME)))
+ return;
+
+ pthread_mutex_lock(&error_obj_lock);
+ if (error_obj == NULL) {
+ pthread_mutex_unlock(&error_obj_lock);
+ return;
+ }
+
+ jclass cls = (*env)->GetObjectClass(env, error_obj);
+ jmethodID methodId = (*env)->GetMethodID(env, cls, "eventHardwareAccelerationError", "()V");
+ (*env)->CallVoidMethod(env, error_obj, methodId);
+
+ (*env)->DeleteLocalRef(env, cls);
+ pthread_mutex_unlock(&error_obj_lock);
+}
diff --git a/libvlc/jni/vout.c b/libvlc/jni/vout.c
deleted file mode 100644
index ee50463..0000000
--- a/libvlc/jni/vout.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/*****************************************************************************
- * vout.c
- *****************************************************************************
- * Copyright © 2010-2013 VLC authors and VideoLAN
- *
- * 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.
- *****************************************************************************/
-
-#include <vlc/vlc.h>
-#include <vlc_common.h>
-
-#include <jni.h>
-
-#define THREAD_NAME "jni_vout"
-extern JNIEnv *jni_get_env(const char *name);
-
-pthread_mutex_t vout_android_lock;
-static void *vout_android_gui = NULL;
-static jobject vout_android_java_surf = NULL;
-static jobject vout_android_subtitles_surf = NULL;
-
-void *jni_LockAndGetSubtitlesSurface() {
- pthread_mutex_lock(&vout_android_lock);
- if (!vout_android_subtitles_surf) {
- pthread_mutex_unlock(&vout_android_lock);
- return NULL;
- }
- return vout_android_subtitles_surf;
-}
-
-jobject jni_LockAndGetAndroidJavaSurface() {
- pthread_mutex_lock(&vout_android_lock);
- if (!vout_android_java_surf) {
- pthread_mutex_unlock(&vout_android_lock);
- return NULL;
- }
- return vout_android_java_surf;
-}
-
-void jni_UnlockAndroidSurface() {
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-void jni_EventHardwareAccelerationError()
-{
- JNIEnv *env;
-
- if (!(env = jni_get_env(THREAD_NAME)))
- return;
-
- pthread_mutex_lock(&vout_android_lock);
- if (vout_android_gui == NULL) {
- pthread_mutex_unlock(&vout_android_lock);
- return;
- }
-
- jclass cls = (*env)->GetObjectClass(env, vout_android_gui);
- jmethodID methodId = (*env)->GetMethodID(env, cls, "eventHardwareAccelerationError", "()V");
- (*env)->CallVoidMethod(env, vout_android_gui, methodId);
-
- (*env)->DeleteLocalRef(env, cls);
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-static void jni_SetSurfaceLayoutEnv(JNIEnv *p_env, int width, int height, int visible_width, int visible_height, int sar_num, int sar_den)
-{
- pthread_mutex_lock(&vout_android_lock);
- if (vout_android_gui == NULL) {
- pthread_mutex_unlock(&vout_android_lock);
- return;
- }
-
- jclass cls = (*p_env)->GetObjectClass (p_env, vout_android_gui);
- jmethodID methodId = (*p_env)->GetMethodID (p_env, cls, "setSurfaceLayout", "(IIIIII)V");
-
- (*p_env)->CallVoidMethod (p_env, vout_android_gui, methodId, width, height, visible_width, visible_height, sar_num, sar_den);
-
- (*p_env)->DeleteLocalRef(p_env, cls);
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-void jni_SetSurfaceLayout(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den)
-{
- JNIEnv *p_env;
-
- if (!(p_env = jni_get_env(THREAD_NAME)))
- return;
-
- jni_SetSurfaceLayoutEnv(p_env, width, height, visible_width, visible_height, sar_num, sar_den);
-}
-
-void *jni_AndroidJavaSurfaceToNativeSurface(jobject surf)
-{
- JNIEnv *p_env;
- jclass clz;
- jfieldID fid;
- void *native_surface = NULL;
-
- if (!(p_env = jni_get_env(THREAD_NAME)))
- return NULL;
-
- clz = (*p_env)->GetObjectClass(p_env, surf);
- fid = (*p_env)->GetFieldID(p_env, clz, "mSurface", "I");
- if (fid == NULL) {
- jthrowable exp = (*p_env)->ExceptionOccurred(p_env);
- if (exp) {
- (*p_env)->DeleteLocalRef(p_env, exp);
- (*p_env)->ExceptionClear(p_env);
- }
- fid = (*p_env)->GetFieldID(p_env, clz, "mNativeSurface", "I");
- if (fid == NULL) {
- jthrowable exp = (*p_env)->ExceptionOccurred(p_env);
- if (exp) {
- (*p_env)->DeleteLocalRef(p_env, exp);
- (*p_env)->ExceptionClear(p_env);
- }
- }
- }
- if (fid != NULL)
- native_surface = (void*)(*p_env)->GetIntField(p_env, surf, fid);
- (*p_env)->DeleteLocalRef(p_env, clz);
-
- return native_surface;
-}
-
-int jni_ConfigureSurface(jobject jsurf, int width, int height, int hal, bool *configured)
-{
- JNIEnv *p_env;
- int ret;
-
- if (!(p_env = jni_get_env(THREAD_NAME)))
- return -1;
-
- pthread_mutex_lock(&vout_android_lock);
- if (vout_android_gui == NULL) {
- pthread_mutex_unlock(&vout_android_lock);
- return -1;
- }
-
- jclass clz = (*p_env)->GetObjectClass (p_env, vout_android_gui);
- jmethodID methodId = (*p_env)->GetMethodID (p_env, clz, "configureSurface", "(Landroid/view/Surface;III)I");
- ret = (*p_env)->CallIntMethod (p_env, vout_android_gui, methodId, jsurf, width, height, hal);
- if (ret >= 0 && configured)
- *configured = ret == 1;
-
- (*p_env)->DeleteLocalRef(p_env, clz);
-
- pthread_mutex_unlock(&vout_android_lock);
- return ret == -1 ? -1 : 0;
-}
-
-void Java_org_videolan_libvlc_LibVLC_attachSurface(JNIEnv *env, jobject thiz, jobject surf, jobject gui) {
- pthread_mutex_lock(&vout_android_lock);
-
- if (vout_android_gui != NULL)
- (*env)->DeleteGlobalRef(env, vout_android_gui);
- if (vout_android_java_surf != NULL)
- (*env)->DeleteGlobalRef(env, vout_android_java_surf);
- vout_android_gui = (*env)->NewGlobalRef(env, gui);
- vout_android_java_surf = (*env)->NewGlobalRef(env, surf);
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-void Java_org_videolan_libvlc_LibVLC_detachSurface(JNIEnv *env, jobject thiz) {
- pthread_mutex_lock(&vout_android_lock);
- if (vout_android_gui != NULL)
- (*env)->DeleteGlobalRef(env, vout_android_gui);
- if (vout_android_java_surf != NULL)
- (*env)->DeleteGlobalRef(env, vout_android_java_surf);
- vout_android_gui = NULL;
- vout_android_java_surf = NULL;
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-void Java_org_videolan_libvlc_LibVLC_attachSubtitlesSurface(JNIEnv *env, jobject thiz, jobject surf) {
- pthread_mutex_lock(&vout_android_lock);
- if (vout_android_subtitles_surf != NULL)
- (*env)->DeleteGlobalRef(env, vout_android_subtitles_surf);
- vout_android_subtitles_surf = (*env)->NewGlobalRef(env, surf);
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-void Java_org_videolan_libvlc_LibVLC_detachSubtitlesSurface(JNIEnv *env, jobject thiz) {
- pthread_mutex_lock(&vout_android_lock);
- if (vout_android_subtitles_surf != NULL)
- (*env)->DeleteGlobalRef(env, vout_android_subtitles_surf);
- vout_android_subtitles_surf = NULL;
- pthread_mutex_unlock(&vout_android_lock);
-}
-
-static int mouse_x = -1;
-static int mouse_y = -1;
-static int mouse_button = -1;
-static int mouse_action = -1;
-
-void Java_org_videolan_libvlc_LibVLC_sendMouseEvent(JNIEnv* env, jobject thiz, jint action, jint button, jint x, jint y)
-{
- mouse_x = x;
- mouse_y = y;
- mouse_button = button;
- mouse_action = action;
-}
-
-void jni_getMouseCoordinates(int *action, int *button, int *x, int *y)
-{
- *x = mouse_x;
- *y = mouse_y;
- *button = mouse_button;
- *action = mouse_action;
-
- mouse_button = mouse_action = mouse_x = mouse_y = -1;
-}
diff --git a/libvlc/jni/vout.h b/libvlc/jni/vout.h
deleted file mode 100644
index c3d4fd7..0000000
--- a/libvlc/jni/vout.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*****************************************************************************
- * vout.h
- *****************************************************************************
- * Copyright © 2011-2013 VLC authors and VideoLAN
- *
- * 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.
- *****************************************************************************/
-
-#ifndef LIBVLCJNI_VOUT_H
-#define LIBVLCJNI_VOUT_H
-
-/* vout lock initialized in vout.c */
-pthread_mutex_t vout_android_lock;
-pthread_cond_t vout_android_surf_attached;
-
-#endif // LIBVLCJNI_VOUT_H
diff --git a/libvlc/src/org/videolan/libvlc/AWindow.java b/libvlc/src/org/videolan/libvlc/AWindow.java
new file mode 100644
index 0000000..b041e76
--- /dev/null
+++ b/libvlc/src/org/videolan/libvlc/AWindow.java
@@ -0,0 +1,459 @@
+/*****************************************************************************
+ * 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.PixelFormat;
+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.concurrent.atomic.AtomicBoolean;
+
+class AWindow implements IAWindowNativeHandler, IVLCVout {
+ private static final int ID_VIDEO = 0;
+ private static final int ID_SUBTITLES = 1;
+ private static final int ID_MAX = 2;
+ private final String TAG = "AWindow";
+
+ 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 != null ? mSurfaceView.getHolder() : null;
+ if (mId == ID_SUBTITLES) {
+ mSurfaceView.setZOrderMediaOverlay(true);
+ mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
+ }
+ }
+
+ private SurfaceHelper(int id, TextureView textureView) {
+ mId = id;
+ mSurfaceView = null;
+ mSurfaceHolder = null;
+ mTextureView = textureView;
+ if (mId == ID_SUBTITLES) {
+ //TODO
+ }
+ }
+
+ private void setSurface(Surface surface) {
+ boolean surfaceValid = surface.isValid();
+ if (surfaceValid && mSurface == 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()));
+ }
+
+ public void attach() {
+ if (mSurfaceView != null) {
+ attachSurfaceView();
+ } else {
+ attachTextureView();
+ }
+ }
+
+ @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 SurfaceHelper[] mSurfaceHelpers;
+
+ private final SurfaceCallback mSurfaceCallback;
+ private final AtomicBoolean mSurfacesReady = new AtomicBoolean(false);
+ private boolean mViewsAttached = false;
+ private IVLCVout.Callback mIAndroidWindowCallback = null;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private 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 (mViewsAttached)
+ 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 (mViewsAttached)
+ 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);
+ }
+
+ @Override
+ @MainThread
+ public void setVideoView(SurfaceView videoSurface) {
+ setView(ID_VIDEO, videoSurface);
+ }
+
+ @Override
+ @MainThread
+ public void setVideoView(TextureView videoTexture) {
+ setView(ID_VIDEO, videoTexture);
+ }
+
+ @Override
+ @MainThread
+ public void setSubtitlesView(SurfaceView videoSurface) {
+ setView(ID_SUBTITLES, videoSurface);
+ }
+
+ @Override
+ @MainThread
+ public void setSubtitlesView(TextureView videoTexture) {
+ setView(ID_SUBTITLES, videoTexture);
+ }
+
+ @Override
+ @MainThread
+ public void attachViews() {
+ if (mViewsAttached || mSurfaceHelpers[ID_VIDEO] == null)
+ throw new IllegalStateException("already attached or video view not configured");
+ for (int id = 0; id < ID_MAX; ++id) {
+ final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
+ if (surfaceHelper != null)
+ surfaceHelper.attach();
+ }
+ mViewsAttached = true;
+ }
+
+ @Override
+ @MainThread
+ public void detachViews() {
+ if (!mViewsAttached)
+ return;
+ mSurfacesReady.set(false);
+ for (int id = 0; id < ID_MAX; ++id) {
+ final SurfaceHelper surfaceHelper = mSurfaceHelpers[id];
+ if (surfaceHelper != null)
+ surfaceHelper.release();
+ mSurfaceHelpers[id] = null;
+ }
+ mViewsAttached = false;
+ if (mSurfaceCallback != null)
+ mSurfaceCallback.onSurfacesDestroyed(this);
+ }
+
+ @Override
+ @MainThread
+ public boolean areViewsAttached() {
+ return mViewsAttached;
+ }
+
+ @MainThread
+ private void onSurfaceCreated() {
+ if (mSurfacesReady.get())
+ throw new IllegalArgumentException("callback already called");
+
+ final SurfaceHelper videoHelper = mSurfaceHelpers[ID_VIDEO];
+ final SurfaceHelper subtitlesHelper = mSurfaceHelpers[ID_SUBTITLES];
+ if (videoHelper == null)
+ throw new NullPointerException("videoHelper shouldn't be null here");
+
+ boolean ready = false;
+ if (videoHelper.isReady() && (subtitlesHelper == null || subtitlesHelper.isReady()))
+ ready = true;
+ mSurfacesReady.set(ready);
+ if (mSurfaceCallback != null && ready)
+ mSurfaceCallback.onSurfacesCreated(this);
+ }
+
+ @MainThread
+ private void onSurfaceDestroyed() {
+ detachViews();
+ }
+
+ protected boolean areSurfacesWaiting() {
+ return !mSurfacesReady.get();
+ }
+
+ @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);
+ }
+
+ @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);
+
+ class Cond {
+ private boolean configured;
+ }
+ final Cond cond = new Cond();
+
+ final Handler handler = new Handler(Looper.getMainLooper());
+ handler.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 (cond) {
+ cond.configured = true;
+ cond.notifyAll();
+ }
+ }
+ });
+
+ try {
+ synchronized (cond) {
+ while (!cond.configured)
+ cond.wait();
+ }
+ } catch (InterruptedException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void setCallback(IVLCVout.Callback callback) {
+ mIAndroidWindowCallback = 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() {
+ if (mIAndroidWindowCallback != null)
+ mIAndroidWindowCallback.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);
+ public native void nativeOnSurfacesReleased(long nativeHandle);
+}
\ No newline at end of file
diff --git a/libvlc/src/org/videolan/libvlc/IAWindowNativeHandler.java b/libvlc/src/org/videolan/libvlc/IAWindowNativeHandler.java
new file mode 100644
index 0000000..51c25ee
--- /dev/null
+++ b/libvlc/src/org/videolan/libvlc/IAWindowNativeHandler.java
@@ -0,0 +1,97 @@
+/*****************************************************************************
+ * public class IAWindowNativeHandler.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.view.Surface;
+
+interface IAWindowNativeHandler {
+ /**
+ * Callback called from {@link IVLCVout#sendMouseEvent}.
+ *
+ * @param nativeHandle handle passed by {@link #setCallback}.
+ * @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.
+ */
+ void nativeOnMouseEvent(long nativeHandle, int action, int button, int x, int y);
+
+ /**
+ * Callback called from {@link IVLCVout#setWindowSize}.
+ *
+ * @param nativeHandle handle passed by {@link #setCallback}.
+ * @param width width of the window.
+ * @param height height of the window.
+ */
+ void nativeOnWindowSize(long nativeHandle, int width, int height);
+
+ /**
+ * Get the valid Video surface.
+ *
+ * @return can be null if the surface was destroyed.
+ */
+ @SuppressWarnings("unused") /* Used by JNI */
+ Surface getVideoSurface();
+
+ /**
+ * Get the valid Subtitles surface.
+ *
+ * @return can be null if the surface was destroyed.
+ */
+ @SuppressWarnings("unused") /* Used by JNI */
+ Surface getSubtitlesSurface();
+
+ /**
+ * Set a callback in order to receive {@link #nativeOnMouseEvent} and {@link #nativeOnWindowSize} events.
+ *
+ * @param nativeHandle native Handle passed by {@link #nativeOnMouseEvent} and {@link #nativeOnWindowSize}
+ * @return true if callback was successfully registered
+ */
+ @SuppressWarnings("unused") /* Used by JNI */
+ boolean setCallback(long nativeHandle);
+
+ /**
+ * This method is only used for ICS 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 */
+ boolean setBuffersGeometry(Surface surface, int width, int height, int format);
+
+ /**
+ * Set the window Layout.
+ * This call will result of {@link IVLCVout.Callback#onNewLayout} 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 */
+ void setWindowLayout(int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen);
+}
\ No newline at end of file
diff --git a/libvlc/src/org/videolan/libvlc/IVLCVout.java b/libvlc/src/org/videolan/libvlc/IVLCVout.java
new file mode 100644
index 0000000..61a475a
--- /dev/null
+++ b/libvlc/src/org/videolan/libvlc/IVLCVout.java
@@ -0,0 +1,123 @@
+/*****************************************************************************
+ * public class IVLCVout.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.os.Build;
+import android.support.annotation.MainThread;
+import android.view.SurfaceView;
+import android.view.TextureView;
+
+ at SuppressWarnings("unused")
+public interface IVLCVout {
+ interface Callback {
+ /**
+ * This callback is called when the native vout call request a new Layout.
+ *
+ * @param vlcVout vlcVout
+ * @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
+ */
+ @MainThread
+ void onNewLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen);
+ }
+
+ /**
+ * Set a surfaceView used for video out.
+ * @see #attachViews()
+ */
+ @MainThread
+ void setVideoView(SurfaceView videoSurface);
+
+ /**
+ * Set a TextureView used for video out.
+ * @see #attachViews()
+ */
+ @MainThread
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ void setVideoView(TextureView videoTexture);
+
+ /**
+ * Set a surfaceView used for subtitles out.
+ * @see #attachViews()
+ */
+ @MainThread
+ void setSubtitlesView(SurfaceView subtitlesSurface);
+
+ /**
+ * Set a TextureView used for subtitles out.
+ * @see #attachViews()
+ */
+ @MainThread
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ void setSubtitlesView(TextureView subtitlesSurface);
+
+ /**
+ * Attach views previously set by setVideoView and setSubtitlesView.
+ * @see #setVideoView(SurfaceView)
+ * @see #setVideoView(TextureView)
+ * @see #setSubtitlesView(SurfaceView)
+ * @see #setSubtitlesView(TextureView)
+ */
+ @MainThread
+ void attachViews();
+
+ /**
+ * Detach views previously attached.
+ * This will be called automatically when surfaces are destroyed.
+ */
+ @MainThread
+ void detachViews();
+
+ /**
+ * Return true if views are attached. If surfaces were destroyed, this will return false.
+ */
+ @MainThread
+ boolean areViewsAttached();
+
+ /**
+ * Set a callback to receive {@link Callback#onNewLayout} events.
+ */
+ @MainThread
+ void setCallback(Callback callback);
+
+ /**
+ * Send a mouse event to the native vout.
+ * @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.
+ */
+ @MainThread
+ void sendMouseEvent(int action, int button, int x, int y);
+
+ /**
+ * Send the the window size to the native vout.
+ * @param width width of the window.
+ * @param height height of the window.
+ */
+ @MainThread
+ void setWindowSize(int width, int height);
+}
diff --git a/libvlc/src/org/videolan/libvlc/IVideoPlayer.java b/libvlc/src/org/videolan/libvlc/IVideoPlayer.java
deleted file mode 100644
index 08ac9ed..0000000
--- a/libvlc/src/org/videolan/libvlc/IVideoPlayer.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*****************************************************************************
- * IVideoPlayer.java
- *****************************************************************************
- * Copyright © 2010-2013 VLC authors and VideoLAN
- *
- * 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.view.Surface;
-
-public interface IVideoPlayer {
- /**
- * This method is called by native vout to request a new layout.
- * @param width Frame width
- * @param height Frame height
- * @param visible_width Visible frame width
- * @param visible_height Visible frame height
- * @param sar_num Surface aspect ratio numerator
- * @param sar_den Surface aspect ratio denominator
- */
- void setSurfaceLayout(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den);
-
- /**
- * This method is only used for Gingerbread and before.
- * It is called by native vout to request a surface size and hal.
- * It is synchronous.
- * @param surface
- * @param width surface width
- * @param height surface height
- * @param hal color format (or PixelFormat)
- * @return -1 if you should'nt not use this call, 1 if surface size is changed, 0 otherwise
- */
- int configureSurface(Surface surface, int width, int height, int hal);
-
-
- /**
- * Called in case of hardware acceleration error
- */
- public void eventHardwareAccelerationError();
-}
diff --git a/libvlc/src/org/videolan/libvlc/LibVLC.java b/libvlc/src/org/videolan/libvlc/LibVLC.java
index db64267..1f6d85d 100644
--- a/libvlc/src/org/videolan/libvlc/LibVLC.java
+++ b/libvlc/src/org/videolan/libvlc/LibVLC.java
@@ -40,13 +40,9 @@ public class LibVLC extends VLCObject<LibVLC.Event> {
/** Native crash handler */
private static OnNativeCrashListener sOnNativeCrashListener;
- /** Check in libVLC already initialized otherwise crash */
- public native void attachSurface(Surface surface, IVideoPlayer player);
-
- public native void detachSurface();
-
- public native void attachSubtitlesSurface(Surface surface);
- public native void detachSubtitlesSurface();
+ public interface HardwareAccelerationError {
+ void eventHardwareAccelerationError(); // TODO REMOVE
+ }
/**
* Create a LibVLC withs options
@@ -95,11 +91,10 @@ public class LibVLC extends VLCObject<LibVLC.Event> {
this(null);
}
- /**
- * Give to LibVLC the surface to draw the video.
- * @param f the surface to draw
- */
- public native void setSurface(Surface f);
+ public void setOnHardwareAccelerationError(HardwareAccelerationError error) {
+ nativeSetOnHardwareAccelerationError(error);
+ }
+ private native void nativeSetOnHardwareAccelerationError(HardwareAccelerationError error);
/**
* Get the libVLC version
@@ -119,8 +114,6 @@ public class LibVLC extends VLCObject<LibVLC.Event> {
*/
public native String changeset();
- public native static void sendMouseEvent( int action, int button, int x, int y);
-
private native void setEventHandler(EventHandler eventHandler);
private native void detachEventHandler();
@@ -149,8 +142,6 @@ public class LibVLC extends VLCObject<LibVLC.Event> {
sOnNativeCrashListener.onNativeCrash();
}
- public native int setWindowSize(int width, int height);
-
/* JNI */
private native void nativeNew(String[] options);
private native void nativeRelease();
diff --git a/libvlc/src/org/videolan/libvlc/MediaPlayer.java b/libvlc/src/org/videolan/libvlc/MediaPlayer.java
index c4c40f5..92478d7 100644
--- a/libvlc/src/org/videolan/libvlc/MediaPlayer.java
+++ b/libvlc/src/org/videolan/libvlc/MediaPlayer.java
@@ -24,8 +24,7 @@ package org.videolan.libvlc;
import java.util.Map;
-
-public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
+public class MediaPlayer extends VLCObject<MediaPlayer.Event> implements AWindow.SurfaceCallback {
public static class Event extends VLCEvent {
//public static final int MediaChanged = 0x100;
@@ -77,6 +76,9 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
}
private Media mMedia = null;
+ private boolean mPlaying = false;
+ private boolean mPlayRequested = false;
+ private final AWindow mWindow = new AWindow(this);
/**
* Create an empty MediaPlayer
@@ -84,7 +86,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
* @param libVLC
*/
public MediaPlayer(LibVLC libVLC) {
- nativeNewFromLibVlc(libVLC);
+ nativeNewFromLibVlc(libVLC, mWindow);
}
/**
@@ -97,7 +99,15 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
throw new IllegalArgumentException("Media is null or released");
mMedia = media;
mMedia.retain();
- nativeNewFromMedia(mMedia);
+ nativeNewFromMedia(mMedia, mWindow);
+ }
+
+ /**
+ *
+ * @return
+ */
+ public IVLCVout getVLCVout() {
+ return mWindow;
}
/**
@@ -134,6 +144,12 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
*
*/
public synchronized void play() {
+ if (!mPlaying) {
+ mPlayRequested = true;
+ if (mWindow.areSurfacesWaiting())
+ return;
+ }
+ mPlaying = true;
nativePlay();
}
@@ -142,9 +158,24 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
*
*/
public synchronized void stop() {
+ mPlayRequested = false;
+ mPlaying = false;
nativeStop();
}
+
+ @Override
+ public synchronized void onSurfacesCreated(AWindow vout) {
+ if (!mPlaying && mPlayRequested)
+ play();
+ }
+
+ @Override
+ public synchronized void onSurfacesDestroyed(AWindow vout) {
+ if (mPlaying)
+ setVideoTrackEnabled(false);
+ }
+
/**
* Set if, and how, the video title will be shown when media is played
*
@@ -305,8 +336,8 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
}
/* JNI */
- private native void nativeNewFromLibVlc(LibVLC libVLC);
- private native void nativeNewFromMedia(Media media);
+ private native void nativeNewFromLibVlc(LibVLC libVLC, IAWindowNativeHandler window);
+ private native void nativeNewFromMedia(Media media, IAWindowNativeHandler window);
private native void nativeRelease();
private native void nativeSetMedia(Media media);
private native void nativePlay();
diff --git a/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java b/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java
index 9f558c3..abedfe0 100644
--- a/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.java
@@ -36,7 +36,6 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Color;
-import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaRouter;
@@ -44,7 +43,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
@@ -71,8 +69,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
@@ -96,12 +92,11 @@ import android.widget.TextView;
import android.widget.Toast;
import org.videolan.libvlc.EventHandler;
-import org.videolan.libvlc.IVideoPlayer;
+import org.videolan.libvlc.IVLCVout;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.util.AndroidUtil;
-import org.videolan.libvlc.util.HWDecoderUtil;
import org.videolan.vlc.BuildConfig;
import org.videolan.vlc.MediaDatabase;
import org.videolan.vlc.MediaWrapper;
@@ -138,7 +133,8 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
-public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlayer, GestureDetector.OnDoubleTapListener, IDelayController {
+public class VideoPlayerActivity extends AppCompatActivity implements IVLCVout.Callback,
+ GestureDetector.OnDoubleTapListener, IDelayController, LibVLC.HardwareAccelerationError {
public final static String TAG = "VLC/VideoPlayerActivity";
@@ -161,12 +157,8 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
public final static int RESULT_VIDEO_TRACK_LOST = RESULT_FIRST_USER + 4;
private PlaybackServiceClient mClient;
- private SurfaceView mSurfaceView;
- private SurfaceView mSubtitlesSurfaceView;
- private SurfaceHolder mSurfaceHolder;
- private SurfaceHolder mSubtitlesSurfaceHolder;
- private Surface mSurface = null;
- private Surface mSubtitleSurface = null;
+ private SurfaceView mSurfaceView = null;
+ private SurfaceView mSubtitlesSurfaceView = null;
private FrameLayout mSurfaceFrame;
private MediaRouter mMediaRouter;
private MediaRouter.SimpleCallback mMediaRouterCallback;
@@ -197,12 +189,11 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
private static final int OVERLAY_INFINITE = -1;
private static final int FADE_OUT = 1;
private static final int SHOW_PROGRESS = 2;
- private static final int SURFACE_LAYOUT = 3;
- private static final int FADE_OUT_INFO = 4;
- private static final int START_PLAYBACK = 5;
- private static final int AUDIO_SERVICE_CONNECTION_FAILED = 6;
- private static final int RESET_BACK_LOCK = 7;
- private static final int CHECK_VIDEO_TRACKS = 8;
+ private static final int FADE_OUT_INFO = 3;
+ private static final int START_PLAYBACK = 4;
+ private static final int AUDIO_SERVICE_CONNECTION_FAILED = 5;
+ private static final int RESET_BACK_LOCK = 6;
+ private static final int CHECK_VIDEO_TRACKS = 7;
private boolean mDragging;
private boolean mShowing;
private DelayState mDelay = DelayState.OFF;
@@ -319,10 +310,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
private OnLayoutChangeListener mOnLayoutChangeListener;
private AlertDialog mAlertDialog;
- private boolean mPlaybackServiceReady = false;
- private boolean mSurfaceReady = false;
- private boolean mSubtitleSurfaceReady = false;
-
private boolean mHasHdmiAudio = false;
private static LibVLC LibVLC() {
@@ -445,22 +432,9 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
mMediaListPlayer = MediaWrapperListPlayer.getInstance();
mSurfaceView = (SurfaceView) findViewById(R.id.player_surface);
- mSurfaceHolder = mSurfaceView.getHolder();
- mSurfaceFrame = (FrameLayout) findViewById(R.id.player_surface_frame);
-
mSubtitlesSurfaceView = (SurfaceView) findViewById(R.id.subtitles_surface);
- mSubtitlesSurfaceHolder = mSubtitlesSurfaceView.getHolder();
- mSubtitlesSurfaceView.setZOrderMediaOverlay(true);
- mSubtitlesSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
- if (!HWDecoderUtil.HAS_SUBTITLES_SURFACE) {
- mSubtitlesSurfaceView.setVisibility(View.GONE);
- mSubtitleSurfaceReady = true;
- }
- if (mPresentation == null) {
- mSurfaceHolder.addCallback(mSurfaceCallback);
- mSubtitlesSurfaceHolder.addCallback(mSubtitlesSurfaceCallback);
- }
+ mSurfaceFrame = (FrameLayout) findViewById(R.id.player_surface_frame);
mSeekbar = (SeekBar) findViewById(R.id.player_overlay_seekbar);
@@ -583,7 +557,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (!AndroidUtil.isHoneycombOrLater())
- setSurfaceLayout(mVideoWidth, mVideoHeight, mVideoVisibleWidth, mVideoVisibleHeight, mSarNum, mSarDen);
+ changeSurfaceLayout();
super.onConfigurationChanged(newConfig);
resetHudLayout();
}
@@ -674,9 +648,21 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void startPlayback() {
/* start playback only when audio service and both surfaces are ready */
- if (mPlaybackStarted || !mPlaybackServiceReady || !mSurfaceReady || !mSubtitleSurfaceReady)
+ if (mPlaybackStarted || !mClient.isConnected())
return;
+ LibVLC().setOnHardwareAccelerationError(this);
+ final IVLCVout vlcVout = MediaPlayer().getVLCVout();
+ if (mPresentation == null) {
+ vlcVout.setVideoView(mSurfaceView);
+ vlcVout.setSubtitlesView(mSubtitlesSurfaceView);
+ } else {
+ vlcVout.setVideoView(mPresentation.mSurfaceView);
+ vlcVout.setSubtitlesView(mPresentation.mSubtitlesSurfaceView);
+ }
+ vlcVout.setCallback(this);
+ vlcVout.attachViews();
+
mPlaybackStarted = true;
if (AndroidUtil.isHoneycombOrLater()) {
@@ -686,13 +672,13 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom)
- setSurfaceLayout(mVideoWidth, mVideoHeight, mVideoVisibleWidth, mVideoVisibleHeight, mSarNum, mSarDen);
+ changeSurfaceLayout();
}
};
}
mSurfaceFrame.addOnLayoutChangeListener(mOnLayoutChangeListener);
}
- setSurfaceLayout(mVideoWidth, mVideoHeight, mVideoVisibleWidth, mVideoVisibleHeight, mSarNum, mSarDen);
+ changeSurfaceLayout();
if (mMediaRouter != null) {
// Listen for changes to media routes.
@@ -724,6 +710,8 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
if (!mPlaybackStarted)
return;
+ LibVLC().setOnHardwareAccelerationError(null);
+
mPlaybackStarted = false;
if(mSwitchingView) {
@@ -757,6 +745,10 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
time -= 5000; // go back 5 seconds, to compensate loading time
MediaPlayer().stop();
+ final IVLCVout vlcVout = MediaPlayer().getVLCVout();
+ vlcVout.detachViews();
+ vlcVout.setCallback(null);
+
SharedPreferences.Editor editor = mSettings.edit();
// Save position
if (time >= 0 && mCanSeek) {
@@ -1081,22 +1073,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
}
@Override
- public void setSurfaceLayout(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den) {
- if (width * height == 0)
- return;
-
- // store video size
- mVideoHeight = height;
- mVideoWidth = width;
- mVideoVisibleHeight = visible_height;
- mVideoVisibleWidth = visible_width;
- mSarNum = sar_num;
- mSarDen = sar_den;
- Message msg = mHandler.obtainMessage(SURFACE_LAYOUT);
- mHandler.sendMessage(msg);
- }
-
- @Override
public void showAudioDelaySetting() {
mDelay = DelayState.AUDIO;
showDelayControls();
@@ -1190,57 +1166,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
}
}
- private static class ConfigureSurfaceHolder {
- private final Surface surface;
- private boolean configured;
-
- private ConfigureSurfaceHolder(Surface surface) {
- this.surface = surface;
- }
- }
-
- @Override
- public int configureSurface(Surface surface, final int width, final int height, final int hal) {
- if (AndroidUtil.isICSOrLater() || surface == null)
- return -1;
- if (width * height == 0)
- return 0;
- Log.d(TAG, "configureSurface: " + width +"x"+height);
-
- final ConfigureSurfaceHolder holder = new ConfigureSurfaceHolder(surface);
-
- final Handler handler = new Handler(Looper.getMainLooper());
- handler.post(new Runnable() {
- @Override
- public void run() {
- if (mSurface == holder.surface && mSurfaceHolder != null) {
- if (hal != 0)
- mSurfaceHolder.setFormat(hal);
- mSurfaceHolder.setFixedSize(width, height);
- } else if (mSubtitleSurface == holder.surface && mSubtitlesSurfaceHolder != null) {
- if (hal != 0)
- mSubtitlesSurfaceHolder.setFormat(hal);
- mSubtitlesSurfaceHolder.setFixedSize(width, height);
- }
-
- synchronized (holder) {
- holder.configured = true;
- holder.notifyAll();
- }
- }
- });
-
- try {
- synchronized (holder) {
- while (!holder.configured)
- holder.wait();
- }
- } catch (InterruptedException e) {
- return 0;
- }
- return 1;
- }
-
/**
* Lock screen rotation
*/
@@ -1536,9 +1461,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
sendMessageDelayed(msg, 1000 - (pos % 1000));
}
break;
- case SURFACE_LAYOUT:
- activity.changeSurfaceLayout();
- break;
case FADE_OUT_INFO:
activity.fadeOutInfo();
break;
@@ -1607,6 +1529,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
mAlertDialog.show();
}
+ @Override
public void eventHardwareAccelerationError() {
EventHandler em = EventHandler.getInstance();
em.callback(EventHandler.HardwareAccelerationError, new Bundle());
@@ -1645,7 +1568,8 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
}
private void handleVout(Message msg) {
- if (msg.getData().getInt("data") == 0 && !mEndReached) {
+ final IVLCVout vlcVout = MediaPlayer().getVLCVout();
+ if (vlcVout.areViewsAttached() && msg.getData().getInt("data") == 0 && !mEndReached) {
/* Video track lost, open in audio mode */
Log.i(TAG, "Video track lost, switching to audio");
mSwitchingView = true;
@@ -1687,8 +1611,8 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
sw = mPresentation.getWindow().getDecorView().getWidth();
sh = mPresentation.getWindow().getDecorView().getHeight();
}
- if (LibVLC() != null)
- LibVLC().setWindowSize(sw, sh);
+ final IVLCVout vlcVout = MediaPlayer().getVLCVout();
+ vlcVout.setWindowSize(sw, sh);
double dw = sw, dh = sh;
boolean isPortrait;
@@ -1780,7 +1704,8 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
lp.width = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth);
lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight);
surface.setLayoutParams(lp);
- subtitlesSurface.setLayoutParams(lp);
+ if (subtitlesSurface != null)
+ subtitlesSurface.setLayoutParams(lp);
// set frame size (crop if necessary)
lp = surfaceFrame.getLayoutParams();
@@ -1789,7 +1714,13 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
surfaceFrame.setLayoutParams(lp);
surface.invalidate();
- subtitlesSurface.invalidate();
+ if (subtitlesSurface != null)
+ subtitlesSurface.invalidate();
+ }
+
+ private void sendMouseEvent(int action, int button, int x, int y) {
+ final IVLCVout vlcVout = MediaPlayer().getVLCVout();
+ vlcVout.sendMouseEvent(action, button, x, y);
}
/**
@@ -1853,12 +1784,12 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
// Seek
mTouchX = event.getRawX();
// Mouse events for the core
- LibVLC.sendMouseEvent(MotionEvent.ACTION_DOWN, 0, xTouch, yTouch);
+ sendMouseEvent(MotionEvent.ACTION_DOWN, 0, xTouch, yTouch);
break;
case MotionEvent.ACTION_MOVE:
// Mouse events for the core
- LibVLC.sendMouseEvent(MotionEvent.ACTION_MOVE, 0, xTouch, yTouch);
+ sendMouseEvent(MotionEvent.ACTION_MOVE, 0, xTouch, yTouch);
// No volume/brightness action if coef < 2 or a secondary display is connected
//TODO : Volume action when a secondary display is connected
@@ -1885,7 +1816,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
case MotionEvent.ACTION_UP:
// Mouse events for the core
- LibVLC.sendMouseEvent(MotionEvent.ACTION_UP, 0, xTouch, yTouch);
+ sendMouseEvent(MotionEvent.ACTION_UP, 0, xTouch, yTouch);
if (mTouchAction == TOUCH_NONE) {
if (!mShowing) {
@@ -2325,67 +2256,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
};
/**
- * attach and disattach surface to the lib
- */
- private final SurfaceHolder.Callback mSurfaceCallback = new Callback() {
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- if(MediaPlayer() != null) {
- final Surface newSurface = holder.getSurface();
- if (mSurface != newSurface) {
- mSurface = newSurface;
- Log.d(TAG, "surfaceChanged: " + mSurface);
- LibVLC().attachSurface(mSurface, VideoPlayerActivity.this);
- mSurfaceReady = true;
- mHandler.sendEmptyMessage(START_PLAYBACK);
- }
- }
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- Log.d(TAG, "surfaceDestroyed");
- if(MediaPlayer() != null) {
- mSurface = null;
- LibVLC().detachSurface();
- mSurfaceReady = false;
- }
- }
- };
-
- private final SurfaceHolder.Callback mSubtitlesSurfaceCallback = new Callback() {
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- if(MediaPlayer() != null) {
- final Surface newSurface = holder.getSurface();
- if (mSubtitleSurface != newSurface) {
- mSubtitleSurface = newSurface;
- LibVLC().attachSubtitlesSurface(mSubtitleSurface);
- mSubtitleSurfaceReady = true;
- mHandler.sendEmptyMessage(START_PLAYBACK);
- }
- }
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- if(MediaPlayer() != null) {
- mSubtitleSurface = null;
- LibVLC().detachSubtitlesSurface();
- mSubtitleSurfaceReady = false;
- }
- }
- };
-
- /**
* show overlay
* @param forceCheck: adjust the timeout in function of playing state
*/
@@ -3045,8 +2915,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
private SurfaceView mSurfaceView;
private SurfaceView mSubtitlesSurfaceView;
- private SurfaceHolder mSurfaceHolder;
- private SurfaceHolder mSubtitlesSurfaceHolder;
private FrameLayout mSurfaceFrame;
public SecondaryDisplay(Context context, LibVLC libVLC, Display display) {
@@ -3062,7 +2930,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
setContentView(R.layout.player_remote);
mSurfaceView = (SurfaceView) findViewById(R.id.remote_player_surface);
- mSurfaceHolder = mSurfaceView.getHolder();
+ mSubtitlesSurfaceView = (SurfaceView) findViewById(R.id.remote_subtitles_surface);
mSurfaceFrame = (FrameLayout) findViewById(R.id.remote_player_surface_frame);
VideoPlayerActivity activity = (VideoPlayerActivity)getOwnerActivity();
@@ -3071,16 +2939,6 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
return;
}
- mSurfaceHolder.addCallback(activity.mSurfaceCallback);
-
- mSubtitlesSurfaceView = (SurfaceView) findViewById(R.id.remote_subtitles_surface);
- mSubtitlesSurfaceHolder = mSubtitlesSurfaceView.getHolder();
- mSubtitlesSurfaceView.setZOrderMediaOverlay(true);
- mSubtitlesSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
- mSubtitlesSurfaceHolder.addCallback(activity.mSubtitlesSurfaceCallback);
-
- if (!HWDecoderUtil.HAS_SUBTITLES_SURFACE)
- mSubtitlesSurfaceView.setVisibility(View.GONE);
Log.i(TAG, "Secondary display created");
}
}
@@ -3176,13 +3034,11 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
@Override
public void onConnected() {
- mPlaybackServiceReady = true;
mHandler.sendEmptyMessage(START_PLAYBACK);
}
@Override
public void onDisconnected() {
- mPlaybackServiceReady = false;
mHandler.sendEmptyMessage(AUDIO_SERVICE_CONNECTION_FAILED);
}
@@ -3202,4 +3058,19 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVideoPlay
public void onMediaPlayedRemoved(int index) {
}
};
+
+ @Override
+ public void onNewLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) {
+ if (width * height == 0)
+ return;
+
+ // store video size
+ mVideoWidth = width;
+ mVideoHeight = height;
+ mVideoVisibleWidth = visibleWidth;
+ mVideoVisibleHeight = visibleHeight;
+ mSarNum = sarNum;
+ mSarDen = sarDen;
+ changeSurfaceLayout();
+ }
}
--
2.1.4
More information about the Android
mailing list