gstreamer/playback/player/android/jni/player.c
2014-08-07 10:59:37 +02:00

471 lines
12 KiB
C

/* GStreamer
*
* Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
*
* 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 <string.h>
#include <stdint.h>
#include <jni.h>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "../../lib/gst/player/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_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_error (GstPlayer * unused, GError * err, Player * player)
{
JNIEnv *env = get_jni_env ();
jstring error_msg;
error_msg = (*env)->NewStringUTF (env, err->message);
(*env)->CallVoidMethod (env, player->java_player, on_error_method_id,
err->code, 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 ();
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, "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_error_method_id =
(*env)->GetMethodID (env, klass, "onError", "(ILjava/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_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 (&current_jni_env, detach_current_thread);
return JNI_VERSION_1_4;
}