Move files from gst-examples into the "subprojects/gst-examples/" subdir
[platform/upstream/gstreamer.git] / subprojects / gst-examples / playback / player / android / app / src / main / jni / player.c
1 /* GStreamer
2  *
3  * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #include <string.h>
22 #include <stdint.h>
23 #include <jni.h>
24 #include <android/log.h>
25 #include <android/native_window.h>
26 #include <android/native_window_jni.h>
27
28 #include <gst/player/gstplayer.h>
29 #include <gst/player/gstplayer-video-overlay-video-renderer.h>
30
31 GST_DEBUG_CATEGORY_STATIC (debug_category);
32 #define GST_CAT_DEFAULT debug_category
33
34 #define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(gintptr)(*env)->GetLongField (env, thiz, fieldID)
35 #define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(gintptr)data)
36
37 typedef struct _Player
38 {
39   jobject java_player;
40   GstPlayer *player;
41   GstPlayerVideoRenderer *renderer;
42   ANativeWindow *native_window;
43 } Player;
44
45 static pthread_key_t current_jni_env;
46 static JavaVM *java_vm;
47 static jfieldID native_player_field_id;
48 static jmethodID on_position_updated_method_id;
49 static jmethodID on_duration_changed_method_id;
50 static jmethodID on_state_changed_method_id;
51 static jmethodID on_buffering_method_id;
52 static jmethodID on_end_of_stream_method_id;
53 static jmethodID on_error_method_id;
54 static jmethodID on_video_dimensions_changed_method_id;
55
56 /* Register this thread with the VM */
57 static JNIEnv *
58 attach_current_thread (void)
59 {
60   JNIEnv *env;
61   JavaVMAttachArgs args;
62
63   GST_DEBUG ("Attaching thread %p", g_thread_self ());
64   args.version = JNI_VERSION_1_4;
65   args.name = NULL;
66   args.group = NULL;
67
68   if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
69     GST_ERROR ("Failed to attach current thread");
70     return NULL;
71   }
72
73   return env;
74 }
75
76 /* Unregister this thread from the VM */
77 static void
78 detach_current_thread (void *env)
79 {
80   GST_DEBUG ("Detaching thread %p", g_thread_self ());
81   (*java_vm)->DetachCurrentThread (java_vm);
82 }
83
84 /* Retrieve the JNI environment for this thread */
85 static JNIEnv *
86 get_jni_env (void)
87 {
88   JNIEnv *env;
89
90   if ((env = pthread_getspecific (current_jni_env)) == NULL) {
91     env = attach_current_thread ();
92     pthread_setspecific (current_jni_env, env);
93   }
94
95   return env;
96 }
97
98 /*
99  * Java Bindings
100  */
101 static void
102 on_position_updated (GstPlayer * unused, GstClockTime position, Player * player)
103 {
104   JNIEnv *env = get_jni_env ();
105
106   (*env)->CallVoidMethod (env, player->java_player,
107       on_position_updated_method_id, position);
108   if ((*env)->ExceptionCheck (env)) {
109     (*env)->ExceptionDescribe (env);
110     (*env)->ExceptionClear (env);
111   }
112 }
113
114 static void
115 on_duration_changed (GstPlayer * unused, GstClockTime duration, Player * player)
116 {
117   JNIEnv *env = get_jni_env ();
118
119   (*env)->CallVoidMethod (env, player->java_player,
120       on_duration_changed_method_id, duration);
121   if ((*env)->ExceptionCheck (env)) {
122     (*env)->ExceptionDescribe (env);
123     (*env)->ExceptionClear (env);
124   }
125 }
126
127 static void
128 on_state_changed (GstPlayer * unused, GstPlayerState state, Player * player)
129 {
130   JNIEnv *env = get_jni_env ();
131
132   (*env)->CallVoidMethod (env, player->java_player,
133       on_state_changed_method_id, state);
134   if ((*env)->ExceptionCheck (env)) {
135     (*env)->ExceptionDescribe (env);
136     (*env)->ExceptionClear (env);
137   }
138 }
139
140 static void
141 on_buffering (GstPlayer * unused, gint percent, Player * player)
142 {
143   JNIEnv *env = get_jni_env ();
144
145   (*env)->CallVoidMethod (env, player->java_player,
146       on_buffering_method_id, percent);
147   if ((*env)->ExceptionCheck (env)) {
148     (*env)->ExceptionDescribe (env);
149     (*env)->ExceptionClear (env);
150   }
151 }
152
153 static void
154 on_end_of_stream (GstPlayer * unused, Player * player)
155 {
156   JNIEnv *env = get_jni_env ();
157
158   (*env)->CallVoidMethod (env, player->java_player, on_end_of_stream_method_id);
159   if ((*env)->ExceptionCheck (env)) {
160     (*env)->ExceptionDescribe (env);
161     (*env)->ExceptionClear (env);
162   }
163 }
164
165 static void
166 on_error (GstPlayer * unused, GError * err, Player * player)
167 {
168   JNIEnv *env = get_jni_env ();
169   jstring error_msg;
170
171   error_msg = (*env)->NewStringUTF (env, err->message);
172
173   (*env)->CallVoidMethod (env, player->java_player, on_error_method_id,
174       err->code, error_msg);
175   if ((*env)->ExceptionCheck (env)) {
176     (*env)->ExceptionDescribe (env);
177     (*env)->ExceptionClear (env);
178   }
179
180   (*env)->DeleteLocalRef (env, error_msg);
181 }
182
183 static void
184 on_video_dimensions_changed (GstPlayer * unused, gint width, gint height,
185     Player * player)
186 {
187   JNIEnv *env = get_jni_env ();
188
189   (*env)->CallVoidMethod (env, player->java_player,
190       on_video_dimensions_changed_method_id, width, height);
191   if ((*env)->ExceptionCheck (env)) {
192     (*env)->ExceptionDescribe (env);
193     (*env)->ExceptionClear (env);
194   }
195 }
196
197 static void
198 native_new (JNIEnv * env, jobject thiz)
199 {
200   Player *player = g_new0 (Player, 1);
201
202   player->renderer = gst_player_video_overlay_video_renderer_new (NULL);
203   player->player = gst_player_new (player->renderer, NULL);
204   SET_CUSTOM_DATA (env, thiz, native_player_field_id, player);
205   player->java_player = (*env)->NewGlobalRef (env, thiz);
206
207   g_signal_connect (player->player, "position-updated",
208       G_CALLBACK (on_position_updated), player);
209   g_signal_connect (player->player, "duration-changed",
210       G_CALLBACK (on_duration_changed), player);
211   g_signal_connect (player->player, "state-changed",
212       G_CALLBACK (on_state_changed), player);
213   g_signal_connect (player->player, "buffering",
214       G_CALLBACK (on_buffering), player);
215   g_signal_connect (player->player, "end-of-stream",
216       G_CALLBACK (on_end_of_stream), player);
217   g_signal_connect (player->player, "error", G_CALLBACK (on_error), player);
218   g_signal_connect (player->player, "video-dimensions-changed",
219       G_CALLBACK (on_video_dimensions_changed), player);
220 }
221
222 static void
223 native_free (JNIEnv * env, jobject thiz)
224 {
225   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
226
227   if (!player)
228     return;
229
230   g_object_unref (player->player);
231   (*env)->DeleteGlobalRef (env, player->java_player);
232   g_free (player);
233   SET_CUSTOM_DATA (env, thiz, native_player_field_id, NULL);
234 }
235
236 static void
237 native_play (JNIEnv * env, jobject thiz)
238 {
239   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
240
241   if (!player)
242     return;
243
244   gst_player_play (player->player);
245 }
246
247 static void
248 native_pause (JNIEnv * env, jobject thiz)
249 {
250   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
251
252   if (!player)
253     return;
254
255   gst_player_pause (player->player);
256 }
257
258 static void
259 native_stop (JNIEnv * env, jobject thiz)
260 {
261   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
262
263   if (!player)
264     return;
265
266   gst_player_stop (player->player);
267 }
268
269 static void
270 native_seek (JNIEnv * env, jobject thiz, jlong position)
271 {
272   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
273
274   if (!player)
275     return;
276
277   gst_player_seek (player->player, position);
278 }
279
280 static void
281 native_set_uri (JNIEnv * env, jobject thiz, jobject uri)
282 {
283   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
284   const gchar *uri_str;
285
286   if (!player)
287     return;
288
289   uri_str = (*env)->GetStringUTFChars (env, uri, NULL);
290   g_object_set (player->player, "uri", uri_str, NULL);
291   (*env)->ReleaseStringUTFChars (env, uri, uri_str);
292 }
293
294 static jobject
295 native_get_uri (JNIEnv * env, jobject thiz)
296 {
297   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
298   jobject uri;
299   gchar *uri_str;
300
301   if (!player)
302     return NULL;
303
304   g_object_get (player->player, "uri", &uri_str, NULL);
305
306   uri = (*env)->NewStringUTF (env, uri_str);
307   g_free (uri_str);
308
309   return uri;
310 }
311
312 static jlong
313 native_get_position (JNIEnv * env, jobject thiz)
314 {
315   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
316   jdouble position;
317
318   if (!player)
319     return -1;
320
321   g_object_get (player->player, "position", &position, NULL);
322
323   return position;
324 }
325
326 static jlong
327 native_get_duration (JNIEnv * env, jobject thiz)
328 {
329   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
330   jlong duration;
331
332   if (!player)
333     return -1;
334
335   g_object_get (player->player, "duration", &duration, NULL);
336
337   return duration;
338 }
339
340 static jdouble
341 native_get_volume (JNIEnv * env, jobject thiz)
342 {
343   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
344   jdouble volume;
345
346   if (!player)
347     return 1.0;
348
349   g_object_get (player->player, "volume", &volume, NULL);
350
351   return volume;
352 }
353
354 static void
355 native_set_volume (JNIEnv * env, jobject thiz, jdouble volume)
356 {
357   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
358
359   if (!player)
360     return;
361
362   g_object_set (player->player, "volume", volume, NULL);
363 }
364
365 static jboolean
366 native_get_mute (JNIEnv * env, jobject thiz)
367 {
368   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
369   jboolean mute;
370
371   if (!player)
372     return FALSE;
373
374   g_object_get (player->player, "mute", &mute, NULL);
375
376   return mute;
377 }
378
379 static void
380 native_set_mute (JNIEnv * env, jobject thiz, jboolean mute)
381 {
382   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
383
384   if (!player)
385     return;
386
387   g_object_set (player->player, "mute", mute, NULL);
388 }
389
390 static void
391 native_set_surface (JNIEnv * env, jobject thiz, jobject surface)
392 {
393   Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
394   ANativeWindow *new_native_window;
395
396   if (!player)
397     return;
398
399   new_native_window = surface ? ANativeWindow_fromSurface (env, surface) : NULL;
400   GST_DEBUG ("Received surface %p (native window %p)", surface,
401       new_native_window);
402
403   if (player->native_window) {
404     ANativeWindow_release (player->native_window);
405   }
406
407   player->native_window = new_native_window;
408   gst_player_video_overlay_video_renderer_set_window_handle
409       (GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (player->renderer),
410       (gpointer) new_native_window);
411   gst_player_video_overlay_video_renderer_expose
412       (GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (player->renderer));
413 }
414
415 static void
416 native_class_init (JNIEnv * env, jclass klass)
417 {
418   native_player_field_id =
419       (*env)->GetFieldID (env, klass, "native_player", "J");
420   on_position_updated_method_id =
421       (*env)->GetMethodID (env, klass, "onPositionUpdated", "(J)V");
422   on_duration_changed_method_id =
423       (*env)->GetMethodID (env, klass, "onDurationChanged", "(J)V");
424   on_state_changed_method_id =
425       (*env)->GetMethodID (env, klass, "onStateChanged", "(I)V");
426   on_buffering_method_id =
427       (*env)->GetMethodID (env, klass, "onBuffering", "(I)V");
428   on_end_of_stream_method_id =
429       (*env)->GetMethodID (env, klass, "onEndOfStream", "()V");
430   on_error_method_id =
431       (*env)->GetMethodID (env, klass, "onError", "(ILjava/lang/String;)V");
432   on_video_dimensions_changed_method_id =
433       (*env)->GetMethodID (env, klass, "onVideoDimensionsChanged", "(II)V");
434
435   if (!native_player_field_id ||
436       !on_position_updated_method_id || !on_duration_changed_method_id ||
437       !on_state_changed_method_id || !on_buffering_method_id ||
438       !on_end_of_stream_method_id ||
439       !on_error_method_id || !on_video_dimensions_changed_method_id) {
440     static const gchar *message =
441         "The calling class does not implement all necessary interface methods";
442     jclass exception_class = (*env)->FindClass (env, "java/lang/Exception");
443     __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", "%s", message);
444     (*env)->ThrowNew (env, exception_class, message);
445   }
446
447   gst_debug_set_threshold_for_name ("gst-player", GST_LEVEL_TRACE);
448 }
449
450 /* List of implemented native methods */
451 static JNINativeMethod native_methods[] = {
452   {"nativeClassInit", "()V", (void *) native_class_init},
453   {"nativeNew", "()V", (void *) native_new},
454   {"nativePlay", "()V", (void *) native_play},
455   {"nativePause", "()V", (void *) native_pause},
456   {"nativeStop", "()V", (void *) native_stop},
457   {"nativeSeek", "(J)V", (void *) native_seek},
458   {"nativeFree", "()V", (void *) native_free},
459   {"nativeGetUri", "()Ljava/lang/String;", (void *) native_get_uri},
460   {"nativeSetUri", "(Ljava/lang/String;)V", (void *) native_set_uri},
461   {"nativeGetPosition", "()J", (void *) native_get_position},
462   {"nativeGetDuration", "()J", (void *) native_get_duration},
463   {"nativeGetVolume", "()D", (void *) native_get_volume},
464   {"nativeSetVolume", "(D)V", (void *) native_set_volume},
465   {"nativeGetMute", "()Z", (void *) native_get_mute},
466   {"nativeSetMute", "(Z)V", (void *) native_set_mute},
467   {"nativeSetSurface", "(Landroid/view/Surface;)V",
468       (void *) native_set_surface}
469 };
470
471 /* Library initializer */
472 jint
473 JNI_OnLoad (JavaVM * vm, void *reserved)
474 {
475   JNIEnv *env = NULL;
476
477   java_vm = vm;
478
479   if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
480     __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
481         "Could not retrieve JNIEnv");
482     return 0;
483   }
484   jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/Player");
485   if (!klass) {
486     __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
487         "Could not retrieve class org.freedesktop.gstreamer.Player");
488     return 0;
489   }
490   if ((*env)->RegisterNatives (env, klass, native_methods,
491           G_N_ELEMENTS (native_methods))) {
492     __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
493         "Could not register native methods for org.freedesktop.gstreamer.Player");
494     return 0;
495   }
496
497   pthread_key_create (&current_jni_env, detach_current_thread);
498
499   return JNI_VERSION_1_4;
500 }