/* 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/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_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; 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, "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", "(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_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; }