diff --git a/playback/player/android/AndroidManifest.xml b/playback/player/android/AndroidManifest.xml
new file mode 100644
index 0000000000..8b156d49d0
--- /dev/null
+++ b/playback/player/android/AndroidManifest.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playback/player/android/jni/Android.mk b/playback/player/android/jni/Android.mk
new file mode 100644
index 0000000000..378685fd02
--- /dev/null
+++ b/playback/player/android/jni/Android.mk
@@ -0,0 +1,24 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := gstplayer
+LOCAL_SRC_FILES := player.c ../../lib/gstplayer.c
+LOCAL_SHARED_LIBRARIES := gstreamer_android
+LOCAL_LDLIBS := -llog -landroid
+include $(BUILD_SHARED_LIBRARY)
+
+ifndef GSTREAMER_ROOT
+ifndef GSTREAMER_ROOT_ANDROID
+$(error GSTREAMER_ROOT_ANDROID is not defined!)
+endif
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)
+endif
+
+GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
+
+include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
+GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED)
+GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0
+
+include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
diff --git a/playback/player/android/jni/player.c b/playback/player/android/jni/player.c
new file mode 100644
index 0000000000..b77d170ab0
--- /dev/null
+++ b/playback/player/android/jni/player.c
@@ -0,0 +1,489 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "../../lib/gstplayer.h"
+
+GST_DEBUG_CATEGORY_STATIC (debug_category);
+#define GST_CAT_DEFAULT debug_category
+
+/*
+ * These macros provide a way to store the native pointer to Player, which might be 32 or 64 bits, into
+ * a jlong, which is always 64 bits, without warnings.
+ */
+#if GLIB_SIZEOF_VOID_P == 8
+# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(*env)->GetLongField (env, thiz, fieldID)
+# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data)
+#else
+# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(jint)(*env)->GetLongField (env, thiz, fieldID)
+# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
+#endif
+
+typedef struct _Player
+{
+ jobject java_player;
+ GstPlayer *player;
+ ANativeWindow *native_window;
+} Player;
+
+static pthread_t gst_app_thread;
+static pthread_key_t current_jni_env;
+static JavaVM *java_vm;
+static jfieldID native_player_field_id;
+static jmethodID on_position_updated_method_id;
+static jmethodID on_duration_changed_method_id;
+static jmethodID on_end_of_stream_method_id;
+static jmethodID on_seek_finished_method_id;
+static jmethodID on_error_method_id;
+static jmethodID on_video_dimensions_changed_method_id;
+
+/* Register this thread with the VM */
+static JNIEnv *
+attach_current_thread (void)
+{
+ JNIEnv *env;
+ JavaVMAttachArgs args;
+
+ GST_DEBUG ("Attaching thread %p", g_thread_self ());
+ args.version = JNI_VERSION_1_4;
+ args.name = NULL;
+ args.group = NULL;
+
+ if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
+ GST_ERROR ("Failed to attach current thread");
+ return NULL;
+ }
+
+ return env;
+}
+
+/* Unregister this thread from the VM */
+static void
+detach_current_thread (void *env)
+{
+ GST_DEBUG ("Detaching thread %p", g_thread_self ());
+ (*java_vm)->DetachCurrentThread (java_vm);
+}
+
+/* Retrieve the JNI environment for this thread */
+static JNIEnv *
+get_jni_env (void)
+{
+ JNIEnv *env;
+
+ if ((env = pthread_getspecific (current_jni_env)) == NULL) {
+ env = attach_current_thread ();
+ pthread_setspecific (current_jni_env, env);
+ }
+
+ return env;
+}
+
+/*
+ * Java Bindings
+ */
+static void
+on_position_updated (GstPlayer * unused, GstClockTime position, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_position_updated_method_id, position);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_duration_changed (GstPlayer * unused, GstClockTime duration, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_duration_changed_method_id, duration);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_end_of_stream (GstPlayer * unused, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player, on_end_of_stream_method_id);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_seek_finished (GstPlayer * unused, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player, on_seek_finished_method_id);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_error (GstPlayer * unused, GError * err, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+ jstring error_msg;
+
+ // FIXME
+ error_msg = err ? (*env)->NewStringUTF (env, err->message) : NULL;
+
+ (*env)->CallVoidMethod (env, player->java_player, on_error_method_id,
+ error_msg);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+
+ (*env)->DeleteLocalRef (env, error_msg);
+}
+
+static void
+on_video_dimensions_changed (GstPlayer * unused, gint width, gint height,
+ Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_video_dimensions_changed_method_id, width, height);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+native_new (JNIEnv * env, jobject thiz)
+{
+ Player *player = g_slice_new0 (Player);
+
+ player->player = gst_player_new (FALSE);
+ SET_CUSTOM_DATA (env, thiz, native_player_field_id, player);
+ player->java_player = (*env)->NewGlobalRef (env, thiz);
+
+ g_signal_connect (player->player, "position-updated",
+ G_CALLBACK (on_position_updated), player);
+ g_signal_connect (player->player, "duration-changed",
+ G_CALLBACK (on_duration_changed), player);
+ g_signal_connect (player->player, "end-of-stream",
+ G_CALLBACK (on_end_of_stream), player);
+ g_signal_connect (player->player, "seek-finished",
+ G_CALLBACK (on_seek_finished), player);
+ g_signal_connect (player->player, "error", G_CALLBACK (on_error), player);
+ g_signal_connect (player->player, "video-dimensions-changed",
+ G_CALLBACK (on_video_dimensions_changed), player);
+}
+
+static void
+native_free (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ (*env)->DeleteGlobalRef (env, player->java_player);
+ g_slice_free (Player, player);
+ SET_CUSTOM_DATA (env, thiz, native_player_field_id, NULL);
+}
+
+static void
+native_play (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_play (player->player);
+}
+
+static void
+native_pause (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_pause (player->player);
+}
+
+static void
+native_stop (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_stop (player->player);
+}
+
+static void
+native_seek (JNIEnv * env, jobject thiz, jlong position)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_seek (player->player, position);
+}
+
+static void
+native_set_uri (JNIEnv * env, jobject thiz, jobject uri)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ const gchar *uri_str;
+
+ if (!player)
+ return;
+
+ uri_str = (*env)->GetStringUTFChars (env, uri, NULL);
+ g_object_set (player->player, "uri", uri_str, NULL);
+ (*env)->ReleaseStringUTFChars (env, uri, uri_str);
+}
+
+static jobject
+native_get_uri (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jobject uri;
+ gchar *uri_str;
+
+ if (!player)
+ return NULL;
+
+ g_object_get (player->player, "uri", &uri_str, NULL);
+
+ uri = (*env)->NewStringUTF (env, uri_str);
+ g_free (uri_str);
+
+ return uri;
+}
+
+static jboolean
+native_is_playing (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jboolean is_playing;
+
+ if (!player)
+ return FALSE;
+
+ g_object_get (player->player, "is-playing", &is_playing, NULL);
+
+ return is_playing;
+}
+
+static jlong
+native_get_position (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jdouble position;
+
+ if (!player)
+ return -1;
+
+ g_object_get (player->player, "position", &position, NULL);
+
+ return position;
+}
+
+static jlong
+native_get_duration (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jlong duration;
+
+ if (!player)
+ return -1;
+
+ g_object_get (player->player, "duration", &duration, NULL);
+
+ return duration;
+}
+
+static jdouble
+native_get_volume (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jdouble volume;
+
+ if (!player)
+ return 1.0;
+
+ g_object_get (player->player, "volume", &volume, NULL);
+
+ return volume;
+}
+
+static void
+native_set_volume (JNIEnv * env, jobject thiz, jdouble volume)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ g_object_set (player->player, "volume", volume, NULL);
+}
+
+static jboolean
+native_get_mute (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jboolean mute;
+
+ if (!player)
+ return FALSE;
+
+ g_object_get (player->player, "mute", &mute, NULL);
+
+ return mute;
+}
+
+static void
+native_set_mute (JNIEnv * env, jobject thiz, jboolean mute)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ g_object_set (player->player, "mute", mute, NULL);
+}
+
+static void
+native_set_surface (JNIEnv * env, jobject thiz, jobject surface)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ ANativeWindow *new_native_window;
+
+ if (!player)
+ return;
+
+ new_native_window = surface ? ANativeWindow_fromSurface (env, surface) : NULL;
+ GST_DEBUG ("Received surface %p (native window %p)", surface,
+ new_native_window);
+
+ if (player->native_window) {
+ ANativeWindow_release (player->native_window);
+ }
+
+ player->native_window = new_native_window;
+ g_object_set (player->player, "window-handle", (gpointer) new_native_window,
+ NULL);
+}
+
+static void
+native_class_init (JNIEnv * env, jclass klass)
+{
+ native_player_field_id =
+ (*env)->GetFieldID (env, klass, "native_player", "J");
+ on_position_updated_method_id =
+ (*env)->GetMethodID (env, klass, "onPositionUpdated", "(J)V");
+ on_duration_changed_method_id =
+ (*env)->GetMethodID (env, klass, "onDurationChanged", "(J)V");
+ on_end_of_stream_method_id =
+ (*env)->GetMethodID (env, klass, "onEndOfStream", "()V");
+ on_seek_finished_method_id =
+ (*env)->GetMethodID (env, klass, "onSeekFinished", "()V");
+ on_error_method_id =
+ (*env)->GetMethodID (env, klass, "onError", "(Ljava/lang/String;)V");
+ on_video_dimensions_changed_method_id =
+ (*env)->GetMethodID (env, klass, "onVideoDimensionsChanged", "(II)V");
+
+ if (!native_player_field_id ||
+ !on_position_updated_method_id || !on_duration_changed_method_id ||
+ !on_end_of_stream_method_id || !on_seek_finished_method_id ||
+ !on_error_method_id || !on_video_dimensions_changed_method_id) {
+ static const gchar *message =
+ "The calling class does not implement all necessary interface methods";
+ jclass exception_class = (*env)->FindClass (env, "java/lang/Exception");
+ __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", "%s", message);
+ (*env)->ThrowNew (env, exception_class, message);
+ }
+
+ gst_debug_set_threshold_for_name ("gst-player", GST_LEVEL_TRACE);
+}
+
+/* List of implemented native methods */
+static JNINativeMethod native_methods[] = {
+ {"nativeClassInit", "()V", (void *) native_class_init},
+ {"nativeNew", "()V", (void *) native_new},
+ {"nativePlay", "()V", (void *) native_play},
+ {"nativePause", "()V", (void *) native_pause},
+ {"nativeSeek", "(J)V", (void *) native_seek},
+ {"nativeFree", "()V", (void *) native_free},
+ {"nativeGetUri", "()Ljava/lang/String;", (void *) native_get_uri},
+ {"nativeSetUri", "(Ljava/lang/String;)V", (void *) native_set_uri},
+ {"nativeIsPlaying", "()Z", (void *) native_is_playing},
+ {"nativeGetPosition", "()J", (void *) native_get_position},
+ {"nativeGetDuration", "()J", (void *) native_get_duration},
+ {"nativeGetVolume", "()D", (void *) native_get_volume},
+ {"nativeSetVolume", "(D)V", (void *) native_set_volume},
+ {"nativeGetMute", "()Z", (void *) native_get_mute},
+ {"nativeSetMute", "(Z)V", (void *) native_set_mute},
+ {"nativeSetSurface", "(Landroid/view/Surface;)V",
+ (void *) native_set_surface}
+};
+
+/* Library initializer */
+jint
+JNI_OnLoad (JavaVM * vm, void *reserved)
+{
+ JNIEnv *env = NULL;
+
+ java_vm = vm;
+
+ if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
+ "Could not retrieve JNIEnv");
+ return 0;
+ }
+ jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/Player");
+ (*env)->RegisterNatives (env, klass, native_methods,
+ G_N_ELEMENTS (native_methods));
+
+ pthread_key_create (¤t_jni_env, detach_current_thread);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/playback/player/android/src/org/freedesktop/gstreamer/Player.java b/playback/player/android/src/org/freedesktop/gstreamer/Player.java
new file mode 100644
index 0000000000..862444ba03
--- /dev/null
+++ b/playback/player/android/src/org/freedesktop/gstreamer/Player.java
@@ -0,0 +1,216 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer;
+
+import java.io.Closeable;
+import android.view.Surface;
+import android.content.Context;
+import org.freedesktop.gstreamer.GStreamer;
+
+public class Player implements Closeable {
+ private static native void nativeClassInit();
+ public static void init(Context context) throws Exception {
+ System.loadLibrary("gstreamer_android");
+ GStreamer.init(context);
+
+ System.loadLibrary("gstplayer");
+ nativeClassInit();
+ }
+
+ private long native_player;
+ private native void nativeNew();
+ public Player() {
+ nativeNew();
+ }
+
+ private native void nativeFree();
+ @Override
+ public void close() {
+ nativeFree();
+ }
+
+ private native void nativePlay();
+ public void play() {
+ nativePlay();
+ }
+
+ private native void nativePause();
+ public void pause() {
+ nativePause();
+ }
+
+ private native void nativeStop();
+ public void stop() {
+ nativeStop();
+ }
+
+ private native void nativeSeek(long position);
+ public void seek(long position) {
+ nativeSeek(position);
+ }
+
+ private native String nativeGetUri();
+ public String getUri() {
+ return nativeGetUri();
+ }
+
+ private native void nativeSetUri(String uri);
+ public void setUri(String uri) {
+ nativeSetUri(uri);
+ }
+
+ private native boolean nativeIsPlaying();
+ public boolean isPlaying() {
+ return nativeIsPlaying();
+ }
+
+ private native long nativeGetPosition();
+ public long getPosition() {
+ return nativeGetPosition();
+ }
+
+ private native long nativeGetDuration();
+ public long getDuration() {
+ return nativeGetDuration();
+ }
+
+ private native double nativeGetVolume();
+ public double getVolume() {
+ return nativeGetVolume();
+ }
+
+ private native void nativeSetVolume(double volume);
+ public void setVolume(double volume) {
+ nativeSetVolume(volume);
+ }
+
+ private native boolean nativeGetMute();
+ public boolean getMute() {
+ return nativeGetMute();
+ }
+
+ private native void nativeSetMute(boolean mute);
+ public void setMute(boolean mute) {
+ nativeSetMute(mute);
+ }
+
+ private Surface surface;
+ private native void nativeSetSurface(Surface surface);
+ public void setSurface(Surface surface) {
+ this.surface = surface;
+ nativeSetSurface(surface);
+ }
+
+ public Surface getSurface() {
+ return surface;
+ }
+
+ public static interface PositionUpdatedListener {
+ abstract void positionUpdated(Player player, long position);
+ }
+
+ private PositionUpdatedListener positionUpdatedListener;
+ public void setPositionUpdatedListener(PositionUpdatedListener listener) {
+ positionUpdatedListener = listener;
+ }
+
+ private void onPositionUpdated(long position) {
+ if (positionUpdatedListener != null) {
+ positionUpdatedListener.positionUpdated(this, position);
+ }
+ }
+
+ public static interface DurationChangedListener {
+ abstract void durationChanged(Player player, long duration);
+ }
+
+ private DurationChangedListener durationChangedListener;
+ public void setDurationChangedListener(DurationChangedListener listener) {
+ durationChangedListener = listener;
+ }
+
+ private void onDurationChanged(long duration) {
+ if (durationChangedListener != null) {
+ durationChangedListener.durationChanged(this, duration);
+ }
+ }
+
+ public static interface EndOfStreamListener {
+ abstract void endOfStream(Player player);
+ }
+
+ private EndOfStreamListener endOfStreamListener;
+ public void setEndOfStreamListener(EndOfStreamListener listener) {
+ endOfStreamListener = listener;
+ }
+
+ private void onEndOfStream() {
+ if (endOfStreamListener != null) {
+ endOfStreamListener.endOfStream(this);
+ }
+ }
+
+ public static interface SeekFinishedListener {
+ abstract void seekFinished(Player player);
+ }
+
+ private SeekFinishedListener seekFinishedListener;
+ public void setSeekFinishedListener(SeekFinishedListener listener) {
+ seekFinishedListener = listener;
+ }
+
+ private void onSeekFinished() {
+ if (seekFinishedListener != null) {
+ seekFinishedListener.seekFinished(this);
+ }
+ }
+
+ public static interface ErrorListener {
+ // FIXME: enums
+ abstract void error(Player player, String errorMessage);
+ }
+
+ private ErrorListener errorListener;
+ public void setErrorListener(ErrorListener listener) {
+ errorListener = listener;
+ }
+
+ private void onError(String errorMessage) {
+ if (errorListener != null) {
+ errorListener.error(this, errorMessage);
+ }
+ }
+
+ public static interface VideoDimensionsChangedListener {
+ abstract void videoDimensionsChanged(Player player, int width, int height);
+ }
+
+ private VideoDimensionsChangedListener videoDimensionsChangedListener;
+ public void setVideoDimensionsChangedListener(VideoDimensionsChangedListener listener) {
+ videoDimensionsChangedListener = listener;
+ }
+
+ private void onVideoDimensionsChanged(int width, int height) {
+ if (videoDimensionsChangedListener != null) {
+ videoDimensionsChangedListener.videoDimensionsChanged(this, width, height);
+ }
+ }
+}
diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java b/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java
new file mode 100644
index 0000000000..f2dd8a9bf0
--- /dev/null
+++ b/playback/player/android/src/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java
@@ -0,0 +1,105 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.play;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View;
+
+// A simple SurfaceView whose width and height can be set from the outside
+public class GStreamerSurfaceView extends SurfaceView {
+ public int media_width = 320;
+ public int media_height = 240;
+
+ // Mandatory constructors, they do not do much
+ public GStreamerSurfaceView(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public GStreamerSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GStreamerSurfaceView (Context context) {
+ super(context);
+ }
+
+ // Called by the layout manager to find out our size and give us some rules.
+ // We will try to maximize our size, and preserve the media's aspect ratio if
+ // we are given the freedom to do so.
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = 0, height = 0;
+ int wmode = View.MeasureSpec.getMode(widthMeasureSpec);
+ int hmode = View.MeasureSpec.getMode(heightMeasureSpec);
+ int wsize = View.MeasureSpec.getSize(widthMeasureSpec);
+ int hsize = View.MeasureSpec.getSize(heightMeasureSpec);
+
+ Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height);
+ // Obey width rules
+ switch (wmode) {
+ case View.MeasureSpec.AT_MOST:
+ if (hmode == View.MeasureSpec.EXACTLY) {
+ width = Math.min(hsize * media_width / media_height, wsize);
+ break;
+ }
+ case View.MeasureSpec.EXACTLY:
+ width = wsize;
+ break;
+ case View.MeasureSpec.UNSPECIFIED:
+ width = media_width;
+ }
+
+ // Obey height rules
+ switch (hmode) {
+ case View.MeasureSpec.AT_MOST:
+ if (wmode == View.MeasureSpec.EXACTLY) {
+ height = Math.min(wsize * media_height / media_width, hsize);
+ break;
+ }
+ case View.MeasureSpec.EXACTLY:
+ height = hsize;
+ break;
+ case View.MeasureSpec.UNSPECIFIED:
+ height = media_height;
+ }
+
+ // Finally, calculate best size when both axis are free
+ if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) {
+ int correct_height = width * media_height / media_width;
+ int correct_width = height * media_width / media_height;
+
+ if (correct_height < height)
+ height = correct_height;
+ else
+ width = correct_width;
+ }
+
+ // Obey minimum size
+ width = Math.max (getSuggestedMinimumWidth(), width);
+ height = Math.max (getSuggestedMinimumHeight(), height);
+ setMeasuredDimension(width, height);
+ }
+
+}
diff --git a/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java b/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java
new file mode 100644
index 0000000000..2874f05de5
--- /dev/null
+++ b/playback/player/android/src/org/freedesktop/gstreamer/player/Play.java
@@ -0,0 +1,196 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.play;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.freedesktop.gstreamer.Player;
+
+public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarChangeListener {
+ private PowerManager.WakeLock wake_lock;
+ private Player player;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ try {
+ Player.init(this);
+ } catch (Exception e) {
+ Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.main);
+
+ player = new Player();
+
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ wake_lock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GStreamer Play");
+ wake_lock.setReferenceCounted(false);
+
+ ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
+ play.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ player.play();
+ wake_lock.acquire();
+ }
+ });
+
+ ImageButton pause = (ImageButton) this.findViewById(R.id.button_pause);
+ pause.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ player.pause();
+ wake_lock.release();
+ }
+ });
+
+ final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar);
+ sb.setOnSeekBarChangeListener(this);
+
+ player.setPositionUpdatedListener(new Player.PositionUpdatedListener() {
+ public void positionUpdated(Player player, final long position) {
+ runOnUiThread (new Runnable() {
+ public void run() {
+ sb.setProgress((int) (position / 1000000));
+ updateTimeWidget();
+ }
+ });
+ }
+ });
+
+ player.setDurationChangedListener(new Player.DurationChangedListener() {
+ public void durationChanged(Player player, final long duration) {
+ runOnUiThread (new Runnable() {
+ public void run() {
+ sb.setMax((int) (duration / 1000000));
+ updateTimeWidget();
+ }
+ });
+ }
+ });
+
+ final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video);
+ player.setVideoDimensionsChangedListener(new Player.VideoDimensionsChangedListener() {
+ public void videoDimensionsChanged(Player player, final int width, final int height) {
+ runOnUiThread (new Runnable() {
+ public void run() {
+ Log.i ("GStreamer", "Media size changed to " + width + "x" + height);
+ gsv.media_width = width;
+ gsv.media_height = height;
+ runOnUiThread(new Runnable() {
+ public void run() {
+ gsv.requestLayout();
+ }
+ });
+ }
+ });
+ }
+ });
+
+ SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video);
+ SurfaceHolder sh = sv.getHolder();
+ sh.addCallback(this);
+
+ String mediaUri = null;
+ Intent intent = getIntent();
+ android.net.Uri uri = intent.getData();
+ Log.i ("GStreamer", "Received URI: " + uri);
+ if (uri.getScheme().equals("content")) {
+ android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+ cursor.moveToFirst();
+ mediaUri = "file://" + cursor.getString(cursor.getColumnIndex(android.provider.MediaStore.Video.Media.DATA));
+ cursor.close();
+ } else {
+ mediaUri = uri.toString();
+ }
+ player.setUri(mediaUri);
+
+ updateTimeWidget();
+ }
+
+ protected void onDestroy() {
+ player.close();
+ super.onDestroy();
+ }
+
+ private void updateTimeWidget () {
+ final TextView tv = (TextView) this.findViewById(R.id.textview_time);
+ final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar);
+ final int pos = sb.getProgress();
+ final int max = sb.getMax();
+
+ SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ final String message = df.format(new Date (pos)) + " / " + df.format(new Date (max));
+ tv.setText(message);
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ Log.d("GStreamer", "Surface changed to format " + format + " width "
+ + width + " height " + height);
+ player.setSurface(holder.getSurface());
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.d("GStreamer", "Surface created: " + holder.getSurface());
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.d("GStreamer", "Surface destroyed");
+ player.setSurface(null);
+ }
+
+ public void onProgressChanged(SeekBar sb, int progress, boolean fromUser) {
+ if (!fromUser) return;
+
+ updateTimeWidget();
+ }
+
+ public void onStartTrackingTouch(SeekBar sb) {
+ }
+
+ public void onStopTrackingTouch(SeekBar sb) {
+ Log.d("GStreamer", "Seek to " + sb.getProgress());
+ player.seek(((long) sb.getProgress()) * 1000000);
+ }
+}