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); + } +}