2014-07-28 18:09:55 +00:00
|
|
|
/* 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>
|
|
|
|
|
2014-08-02 18:33:38 +00:00
|
|
|
#include "../../lib/gst/player/gstplayer.h"
|
2014-07-28 18:09:55 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|