4 #include <android/log.h>
\r
5 #include <android/native_window.h>
\r
6 #include <android/native_window_jni.h>
\r
8 #include <gst/video/video.h>
\r
11 GST_DEBUG_CATEGORY_STATIC (debug_category);
\r
12 #define GST_CAT_DEFAULT debug_category
\r
15 * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into
\r
16 * a jlong, which is always 64 bits, without warnings.
\r
18 #if GLIB_SIZEOF_VOID_P == 8
\r
19 # define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID)
\r
20 # define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data)
\r
22 # define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID)
\r
23 # define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
\r
26 /* Do not allow seeks to be performed closer than this distance. It is visually useless, and will probably
\r
27 * confuse some demuxers. */
\r
28 #define SEEK_MIN_DELAY (500 * GST_MSECOND)
\r
30 /* Structure to contain all our information, so we can pass it to callbacks */
\r
31 typedef struct _CustomData {
\r
32 jobject app; /* Application instance, used to call its methods. A global reference is kept. */
\r
33 GstElement *pipeline; /* The running pipeline */
\r
34 GMainContext *context; /* GLib context used to run the main loop */
\r
35 GMainLoop *main_loop; /* GLib main loop */
\r
36 gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
\r
37 ANativeWindow *native_window; /* The Android native window where video will be rendered */
\r
38 GstState state; /* Current pipeline state */
\r
39 GstState target_state; /* Desired pipeline state, to be set once buffering is complete */
\r
40 gint64 duration; /* Cached clip duration */
\r
41 gint64 desired_position; /* Position to seek to, once the pipeline is running */
\r
42 GstClockTime last_seek_time; /* For seeking overflow prevention (throttling) */
\r
43 gboolean is_live; /* Live streams do not use buffering */
\r
48 GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */
\r
51 /* These global variables cache values which are not changing during execution */
\r
52 static pthread_t gst_app_thread;
\r
53 static pthread_key_t current_jni_env;
\r
54 static JavaVM *java_vm;
\r
55 static jfieldID custom_data_field_id;
\r
56 static jmethodID set_message_method_id;
\r
57 static jmethodID set_current_position_method_id;
\r
58 static jmethodID on_gstreamer_initialized_method_id;
\r
59 static jmethodID on_media_size_changed_method_id;
\r
65 /* Register this thread with the VM */
\r
66 static JNIEnv *attach_current_thread (void) {
\r
68 JavaVMAttachArgs args;
\r
70 GST_DEBUG ("Attaching thread %p", g_thread_self ());
\r
71 args.version = JNI_VERSION_1_4;
\r
75 if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
\r
76 GST_ERROR ("Failed to attach current thread");
\r
83 /* Unregister this thread from the VM */
\r
84 static void detach_current_thread (void *env) {
\r
85 GST_DEBUG ("Detaching thread %p", g_thread_self ());
\r
86 (*java_vm)->DetachCurrentThread (java_vm);
\r
89 /* Retrieve the JNI environment for this thread */
\r
90 static JNIEnv *get_jni_env (void) {
\r
93 if ((env = pthread_getspecific (current_jni_env)) == NULL) {
\r
94 env = attach_current_thread ();
\r
95 pthread_setspecific (current_jni_env, env);
\r
101 /* Change the content of the UI's TextView */
\r
102 static void set_ui_message (const gchar *message, CustomData *data) {
\r
103 JNIEnv *env = get_jni_env ();
\r
104 GST_DEBUG ("Setting message to: %s", message);
\r
105 jstring jmessage = (*env)->NewStringUTF(env, message);
\r
106 (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
\r
107 if ((*env)->ExceptionCheck (env)) {
\r
108 GST_ERROR ("Failed to call Java method");
\r
109 (*env)->ExceptionClear (env);
\r
111 (*env)->DeleteLocalRef (env, jmessage);
\r
114 /* Tell the application what is the current position and clip duration */
\r
115 static void set_current_ui_position (gint position, gint duration, CustomData *data) {
\r
116 JNIEnv *env = get_jni_env ();
\r
117 (*env)->CallVoidMethod (env, data->app, set_current_position_method_id, position, duration);
\r
118 if ((*env)->ExceptionCheck (env)) {
\r
119 GST_ERROR ("Failed to call Java method");
\r
120 (*env)->ExceptionClear (env);
\r
124 /* If we have pipeline and it is running, query the current position and clip duration and inform
\r
125 * the application */
\r
126 static gboolean refresh_ui (CustomData *data) {
\r
127 gint64 current = -1;
\r
130 /* We do not want to update anything unless we have a working pipeline in the PAUSED or PLAYING state */
\r
131 if (!data || !data->pipeline || data->state < GST_STATE_PAUSED)
\r
134 /* If we didn't know it yet, query the stream duration */
\r
135 if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
\r
136 if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME, &data->duration)) {
\r
137 GST_WARNING ("Could not query current duration");
\r
141 if (gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
\r
142 /* Java expects these values in milliseconds, and GStreamer provides nanoseconds */
\r
143 set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND, data);
\r
148 /* Forward declaration for the delayed seek callback */
\r
149 static gboolean delayed_seek_cb (CustomData *data);
\r
151 /* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for
\r
152 * some time in the future. */
\r
153 static void execute_seek (gint64 desired_position, CustomData *data) {
\r
156 if (desired_position == GST_CLOCK_TIME_NONE)
\r
159 diff = gst_util_get_timestamp () - data->last_seek_time;
\r
161 if (GST_CLOCK_TIME_IS_VALID (data->last_seek_time) && diff < SEEK_MIN_DELAY) {
\r
162 /* The previous seek was too close, delay this one */
\r
163 GSource *timeout_source;
\r
165 if (data->desired_position == GST_CLOCK_TIME_NONE) {
\r
166 /* There was no previous seek scheduled. Setup a timer for some time in the future */
\r
167 timeout_source = g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND);
\r
168 g_source_set_callback (timeout_source, (GSourceFunc)delayed_seek_cb, data, NULL);
\r
169 g_source_attach (timeout_source, data->context);
\r
170 g_source_unref (timeout_source);
\r
172 /* Update the desired seek position. If multiple requests are received before it is time
\r
173 * to perform a seek, only the last one is remembered. */
\r
174 data->desired_position = desired_position;
\r
175 GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %" GST_TIME_FORMAT,
\r
176 GST_TIME_ARGS (desired_position), GST_TIME_ARGS (SEEK_MIN_DELAY - diff));
\r
178 /* Perform the seek now */
\r
179 GST_DEBUG ("Seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (desired_position));
\r
180 data->last_seek_time = gst_util_get_timestamp ();
\r
181 gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position);
\r
182 data->desired_position = GST_CLOCK_TIME_NONE;
\r
186 /* Delayed seek callback. This gets called by the timer setup in the above function. */
\r
187 static gboolean delayed_seek_cb (CustomData *data) {
\r
188 GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (data->desired_position));
\r
189 execute_seek (data->desired_position, data);
\r
193 /* Retrieve errors from the bus and show them on the UI */
\r
194 static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
\r
197 gchar *message_string;
\r
199 gst_message_parse_error (msg, &err, &debug_info);
\r
200 message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message);
\r
201 g_clear_error (&err);
\r
202 g_free (debug_info);
\r
203 set_ui_message (message_string, data);
\r
204 g_free (message_string);
\r
205 data->target_state = GST_STATE_NULL;
\r
206 gst_element_set_state (data->pipeline, GST_STATE_NULL);
\r
209 /* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */
\r
210 static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
\r
211 data->target_state = GST_STATE_PAUSED;
\r
212 data->is_live = (gst_element_set_state (data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
\r
213 execute_seek (0, data);
\r
216 /* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */
\r
217 static void duration_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
\r
218 data->duration = GST_CLOCK_TIME_NONE;
\r
221 /* Called when buffering messages are received. We inform the UI about the current buffering level and
\r
222 * keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */
\r
223 static void buffering_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
\r
229 gst_message_parse_buffering (msg, &percent);
\r
230 if (percent < 100 && data->target_state >= GST_STATE_PAUSED) {
\r
231 gchar * message_string = g_strdup_printf ("Buffering %d%%", percent);
\r
232 gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
\r
233 set_ui_message (message_string, data);
\r
234 g_free (message_string);
\r
235 } else if (data->target_state >= GST_STATE_PLAYING) {
\r
236 gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
\r
237 } else if (data->target_state >= GST_STATE_PAUSED) {
\r
238 set_ui_message ("Buffering complete", data);
\r
242 /* Called when the clock is lost */
\r
243 static void clock_lost_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
\r
244 if (data->target_state >= GST_STATE_PLAYING) {
\r
245 gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
\r
246 gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
\r
250 /* Retrieve the video sink's Caps and tell the application about the media size */
\r
251 static void check_media_size (CustomData *data) {
\r
252 JNIEnv *env = get_jni_env ();
\r
253 GstElement *video_sink;
\r
254 GstPad *video_sink_pad;
\r
258 /* Retrieve the Caps at the entrance of the video sink */
\r
259 g_object_get (data->pipeline, "video-sink", &video_sink, NULL);
\r
260 video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
\r
261 caps = gst_pad_get_current_caps (video_sink_pad);
\r
263 if (gst_video_info_from_caps (&info, caps)) {
\r
264 info.width = info.width * info.par_n / info.par_d;
\r
265 GST_DEBUG ("Media size is %dx%d, notifying application", info.width, info.height);
\r
267 (*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id, (jint)info.width, (jint)info.height);
\r
268 if ((*env)->ExceptionCheck (env)) {
\r
269 GST_ERROR ("Failed to call Java method");
\r
270 (*env)->ExceptionClear (env);
\r
274 gst_caps_unref(caps);
\r
275 gst_object_unref (video_sink_pad);
\r
276 gst_object_unref(video_sink);
\r
279 /* Notify UI about pipeline state changes */
\r
280 static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
\r
281 GstState old_state, new_state, pending_state;
\r
282 gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
\r
283 /* Only pay attention to messages coming from the pipeline, not its children */
\r
284 if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
\r
285 data->state = new_state;
\r
286 gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));
\r
287 set_ui_message(message, data);
\r
290 /* The Ready to Paused state change is particularly interesting: */
\r
291 if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
\r
292 /* By now the sink already knows the media size */
\r
293 check_media_size(data);
\r
295 /* If there was a scheduled seek, perform it now that we have moved to the Paused state */
\r
296 if (GST_CLOCK_TIME_IS_VALID (data->desired_position))
\r
297 execute_seek (data->desired_position, data);
\r
302 /* Check if all conditions are met to report GStreamer as initialized.
\r
303 * These conditions will change depending on the application */
\r
304 static void check_initialization_complete (CustomData *data) {
\r
305 JNIEnv *env = get_jni_env ();
\r
306 if (!data->initialized && data->native_window && data->main_loop) {
\r
307 GST_DEBUG ("Initialization complete, notifying application. native_window:%p main_loop:%p", data->native_window, data->main_loop);
\r
309 /* The main loop is running and we received a native window, inform the sink about it */
\r
310 gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), (guintptr)data->native_window);
\r
312 (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
\r
313 if ((*env)->ExceptionCheck (env)) {
\r
314 GST_ERROR ("Failed to call Java method");
\r
315 (*env)->ExceptionClear (env);
\r
317 data->initialized = TRUE;
\r
321 /* Main method for the native code. This is executed on its own thread. */
\r
322 static void *app_function (void *userdata) {
\r
323 JavaVMAttachArgs args;
\r
325 CustomData *data = (CustomData *)userdata;
\r
326 GSource *timeout_source;
\r
327 GSource *bus_source;
\r
328 GError *error = NULL;
\r
331 GST_DEBUG ("Creating pipeline in CustomData at %p", data);
\r
333 /* Create our own GLib Main Context and make it the default one */
\r
334 data->context = g_main_context_new ();
\r
335 g_main_context_push_thread_default(data->context);
\r
337 /* Build pipeline */
\r
338 data->pipeline = gst_parse_launch("playbin", &error);
\r
340 gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);
\r
341 g_clear_error (&error);
\r
342 set_ui_message(message, data);
\r
347 /* Disable subtitles */
\r
348 g_object_get (data->pipeline, "flags", &flags, NULL);
\r
349 flags &= ~GST_PLAY_FLAG_TEXT;
\r
350 g_object_set (data->pipeline, "flags", flags, NULL);
\r
352 /* Set the pipeline to READY, so it can already accept a window handle, if we have one */
\r
353 data->target_state = GST_STATE_READY;
\r
354 gst_element_set_state(data->pipeline, GST_STATE_READY);
\r
356 /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
\r
357 bus = gst_element_get_bus (data->pipeline);
\r
358 bus_source = gst_bus_create_watch (bus);
\r
359 g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL);
\r
360 g_source_attach (bus_source, data->context);
\r
361 g_source_unref (bus_source);
\r
362 g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data);
\r
363 g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, data);
\r
364 g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data);
\r
365 g_signal_connect (G_OBJECT (bus), "message::duration", (GCallback)duration_cb, data);
\r
366 g_signal_connect (G_OBJECT (bus), "message::buffering", (GCallback)buffering_cb, data);
\r
367 g_signal_connect (G_OBJECT (bus), "message::clock-lost", (GCallback)clock_lost_cb, data);
\r
368 gst_object_unref (bus);
\r
370 /* Register a function that GLib will call 4 times per second */
\r
371 timeout_source = g_timeout_source_new (250);
\r
372 g_source_set_callback (timeout_source, (GSourceFunc)refresh_ui, data, NULL);
\r
373 g_source_attach (timeout_source, data->context);
\r
374 g_source_unref (timeout_source);
\r
376 /* Create a GLib Main Loop and set it to run */
\r
377 GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
\r
378 data->main_loop = g_main_loop_new (data->context, FALSE);
\r
379 check_initialization_complete (data);
\r
380 g_main_loop_run (data->main_loop);
\r
381 GST_DEBUG ("Exited main loop");
\r
382 g_main_loop_unref (data->main_loop);
\r
383 data->main_loop = NULL;
\r
385 /* Free resources */
\r
386 g_main_context_pop_thread_default(data->context);
\r
387 g_main_context_unref (data->context);
\r
388 data->target_state = GST_STATE_NULL;
\r
389 gst_element_set_state (data->pipeline, GST_STATE_NULL);
\r
390 gst_object_unref (data->pipeline);
\r
399 /* Instruct the native code to create its internal data structure, pipeline and thread */
\r
400 static void gst_native_init (JNIEnv* env, jobject thiz) {
\r
401 CustomData *data = g_new0 (CustomData, 1);
\r
402 data->desired_position = GST_CLOCK_TIME_NONE;
\r
403 data->last_seek_time = GST_CLOCK_TIME_NONE;
\r
404 SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
\r
405 GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-4", 0, "Android tutorial 4");
\r
406 gst_debug_set_threshold_for_name("tutorial-4", GST_LEVEL_DEBUG);
\r
407 GST_DEBUG ("Created CustomData at %p", data);
\r
408 data->app = (*env)->NewGlobalRef (env, thiz);
\r
409 GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
\r
410 pthread_create (&gst_app_thread, NULL, &app_function, data);
\r
413 /* Quit the main loop, remove the native thread and free resources */
\r
414 static void gst_native_finalize (JNIEnv* env, jobject thiz) {
\r
415 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
\r
417 GST_DEBUG ("Quitting main loop...");
\r
418 g_main_loop_quit (data->main_loop);
\r
419 GST_DEBUG ("Waiting for thread to finish...");
\r
420 pthread_join (gst_app_thread, NULL);
\r
421 GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
\r
422 (*env)->DeleteGlobalRef (env, data->app);
\r
423 GST_DEBUG ("Freeing CustomData at %p", data);
\r
425 SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
\r
426 GST_DEBUG ("Done finalizing");
\r
429 /* Set playbin's URI */
\r
430 void gst_native_set_uri (JNIEnv* env, jobject thiz, jstring uri) {
\r
431 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
\r
432 if (!data || !data->pipeline) return;
\r
433 const jbyte *char_uri = (*env)->GetStringUTFChars (env, uri, NULL);
\r
434 GST_DEBUG ("Setting URI to %s", char_uri);
\r
435 if (data->target_state >= GST_STATE_READY)
\r
436 gst_element_set_state (data->pipeline, GST_STATE_READY);
\r
437 g_object_set(data->pipeline, "uri", char_uri, NULL);
\r
438 (*env)->ReleaseStringUTFChars (env, uri, char_uri);
\r
439 data->duration = GST_CLOCK_TIME_NONE;
\r
440 data->is_live = (gst_element_set_state (data->pipeline, data->target_state) == GST_STATE_CHANGE_NO_PREROLL);
\r
443 /* Set pipeline to PLAYING state */
\r
444 static void gst_native_play (JNIEnv* env, jobject thiz) {
\r
445 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
\r
447 GST_DEBUG ("Setting state to PLAYING");
\r
448 data->target_state = GST_STATE_PLAYING;
\r
449 data->is_live = (gst_element_set_state (data->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL);
\r
452 /* Set pipeline to PAUSED state */
\r
453 static void gst_native_pause (JNIEnv* env, jobject thiz) {
\r
454 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
\r
456 GST_DEBUG ("Setting state to PAUSED");
\r
457 data->target_state = GST_STATE_PAUSED;
\r
458 data->is_live = (gst_element_set_state (data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
\r
461 /* Instruct the pipeline to seek to a different position */
\r
462 void gst_native_set_position (JNIEnv* env, jobject thiz, int milliseconds) {
\r
463 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
\r
465 gint64 desired_position = (gint64)(milliseconds * GST_MSECOND);
\r
466 if (data->state >= GST_STATE_PAUSED) {
\r
467 execute_seek(desired_position, data);
\r
469 GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later", GST_TIME_ARGS (desired_position));
\r
470 data->desired_position = desired_position;
\r
474 /* Static class initializer: retrieve method and field IDs */
\r
475 static jboolean gst_native_class_init (JNIEnv* env, jclass klass) {
\r
476 custom_data_field_id = (*env)->GetFieldID (env, klass, "native_custom_data", "J");
\r
477 set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
\r
478 set_current_position_method_id = (*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V");
\r
479 on_gstreamer_initialized_method_id = (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
\r
480 on_media_size_changed_method_id = (*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V");
\r
482 if (!custom_data_field_id || !set_message_method_id || !on_gstreamer_initialized_method_id ||
\r
483 !on_media_size_changed_method_id || !set_current_position_method_id) {
\r
484 /* We emit this message through the Android log instead of the GStreamer log because the later
\r
485 * has not been initialized yet.
\r
487 __android_log_print (ANDROID_LOG_ERROR, "tutorial-4", "The calling class does not implement all necessary interface methods");
\r
493 static void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface) {
\r
494 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
\r
496 ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface);
\r
497 GST_DEBUG ("Received surface %p (native window %p)", surface, new_native_window);
\r
499 if (data->native_window) {
\r
500 ANativeWindow_release (data->native_window);
\r
501 if (data->native_window == new_native_window) {
\r
502 GST_DEBUG ("New native window is the same as the previous one %p", data->native_window);
\r
503 if (data->pipeline) {
\r
504 gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
\r
505 gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
\r
509 GST_DEBUG ("Released previous native window %p", data->native_window);
\r
510 data->initialized = FALSE;
\r
513 data->native_window = new_native_window;
\r
515 check_initialization_complete (data);
\r
518 static void gst_native_surface_finalize (JNIEnv *env, jobject thiz) {
\r
519 CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
\r
521 GST_DEBUG ("Releasing Native Window %p", data->native_window);
\r
523 if (data->pipeline) {
\r
524 gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline), (guintptr)NULL);
\r
525 gst_element_set_state (data->pipeline, GST_STATE_READY);
\r
528 ANativeWindow_release (data->native_window);
\r
529 data->native_window = NULL;
\r
530 data->initialized = FALSE;
\r
533 /* List of implemented native methods */
\r
534 static JNINativeMethod native_methods[] = {
\r
535 { "nativeInit", "()V", (void *) gst_native_init},
\r
536 { "nativeFinalize", "()V", (void *) gst_native_finalize},
\r
537 { "nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
\r
538 { "nativePlay", "()V", (void *) gst_native_play},
\r
539 { "nativePause", "()V", (void *) gst_native_pause},
\r
540 { "nativeSetPosition", "(I)V", (void*) gst_native_set_position},
\r
541 { "nativeSurfaceInit", "(Ljava/lang/Object;)V", (void *) gst_native_surface_init},
\r
542 { "nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
\r
543 { "nativeClassInit", "()Z", (void *) gst_native_class_init}
\r
546 /* Library initializer */
\r
547 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
\r
548 JNIEnv *env = NULL;
\r
552 if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
\r
553 __android_log_print (ANDROID_LOG_ERROR, "tutorial-4", "Could not retrieve JNIEnv");
\r
556 jclass klass = (*env)->FindClass (env, "com/gst_sdk_tutorials/tutorial_4/Tutorial4");
\r
557 (*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods));
\r
559 pthread_key_create (¤t_jni_env, detach_current_thread);
\r
561 return JNI_VERSION_1_4;
\r