Report current position/duration back to the UI. Refactor.
authorXavi Artigas <xartigas@fluendo.com>
Thu, 13 Sep 2012 12:36:06 +0000 (14:36 +0200)
committerXavi Artigas <xartigas@fluendo.com>
Thu, 13 Sep 2012 12:36:06 +0000 (14:36 +0200)
gst-sdk/tutorials/android-tutorial-1/AndroidManifest.xml
gst-sdk/tutorials/android-tutorial-1/jni/Android.mk
gst-sdk/tutorials/android-tutorial-1/jni/tutorial-1.c
gst-sdk/tutorials/android-tutorial-1/res/layout/main.xml
gst-sdk/tutorials/android-tutorial-1/src/com/gst_sdk_tutorials/tutorial_1/Tutorial1.java

index 88f60ce..508f2f1 100755 (executable)
@@ -4,6 +4,7 @@
       android:versionCode="1"
       android:versionName="1.0">
     <uses-sdk android:minSdkVersion="9" />
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application android:label="@string/app_name"
                  android:debuggable="true"
                  android:icon="@drawable/gst_sdk_icon">
index b940790..2e7a773 100755 (executable)
@@ -22,7 +22,7 @@ LOCAL_SHARED_LIBRARIES := gstreamer_android
 LOCAL_LDLIBS := -landroid
 include $(BUILD_SHARED_LIBRARY)
 
-GSTREAMER_PLUGINS = coreelements audiotestsrc videotestsrc ogg theora vorbis ffmpegcolorspace playback app audioconvert audiorate audioresample adder coreindexers gdp gio uridecodebin videorate videoscale typefindfunctions libvisual pango subparse eglglessink
+GSTREAMER_PLUGINS = coreelements audiotestsrc videotestsrc ogg theora vorbis ffmpegcolorspace playback app audioconvert audiorate audioresample adder coreindexers gdp gio uridecodebin videorate videoscale typefindfunctions libvisual pango subparse eglglessink soup
 GSTREAMER_STATIC_PLUGINS_PATH=/home/fluendo/cerbero/dist/android_arm/lib/gstreamer-0.10/static
 GSTREAMER_MK_PATH=/home/fluendo/cerbero/data/ndk-build/
 include $(GSTREAMER_MK_PATH)/gstreamer.mk
index 79391d2..c35141c 100755 (executable)
@@ -26,6 +26,9 @@ typedef struct _CustomData {
   GstElement *pipeline;
   GMainLoop *main_loop;
   ANativeWindow *native_window;
+  gboolean playing;
+  gint64 position;
+  gint64 duration;
 } CustomData;
 
 static pthread_t gst_app_thread;
@@ -33,11 +36,12 @@ static pthread_key_t current_jni_env;
 static JavaVM *java_vm;
 static jfieldID custom_data_field_id;
 static jmethodID set_message_method_id;
+static jmethodID set_current_position_method_id;
 
 /*
  * Private methods
  */
-static JNIEnv *gst_attach_current_thread (void) {
+static JNIEnv *attach_current_thread (void) {
   JNIEnv *env;
   JavaVMAttachArgs args;
 
@@ -54,26 +58,26 @@ static JNIEnv *gst_attach_current_thread (void) {
   return env;
 }
 
-static void gst_detach_current_thread (void *env) {
+static void detach_current_thread (void *env) {
   GST_DEBUG ("Detaching thread %p", g_thread_self ());
   (*java_vm)->DetachCurrentThread (java_vm);
 }
 
-static JNIEnv *gst_get_jni_env (void) {
+static JNIEnv *get_jni_env (void) {
   JNIEnv *env;
 
   if ((env = pthread_getspecific (current_jni_env)) == NULL) {
-    env = gst_attach_current_thread ();
+    env = attach_current_thread ();
     pthread_setspecific (current_jni_env, env);
   }
 
   return env;
 }
 
-static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
-  GST_DEBUG ("Message: %s", GST_MESSAGE_TYPE_NAME (msg));
-  JNIEnv *env = gst_get_jni_env ();
-  jstring jmessage = (*env)->NewStringUTF(env, GST_MESSAGE_TYPE_NAME (msg));
+static void set_message (const gchar *message, CustomData *data) {
+  JNIEnv *env = get_jni_env ();
+  GST_DEBUG ("Setting message to: %s", message);
+  jstring jmessage = (*env)->NewStringUTF(env, message);
   (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
   if ((*env)->ExceptionCheck (env)) {
     GST_ERROR ("Failed to call Java method");
@@ -82,7 +86,67 @@ static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
   (*env)->DeleteLocalRef (env, jmessage);
 }
 
-static void *gst_app_function (void *userdata) {
+static void set_current_position (gint64 position, gint64 duration, CustomData *data) {
+  JNIEnv *env = get_jni_env ();
+  GST_DEBUG ("Setting current position/duration to: %lld / %lld (ms)", position, duration);
+  (*env)->CallVoidMethod (env, data->app, set_current_position_method_id, position, duration);
+  if ((*env)->ExceptionCheck (env)) {
+    GST_ERROR ("Failed to call Java method");
+    (*env)->ExceptionClear (env);
+  }
+}
+
+static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
+  GError *err;
+  gchar *debug_info;
+  gchar *message_string;
+
+  gst_message_parse_error (msg, &err, &debug_info);
+  message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message);
+  g_clear_error (&err);
+  g_free (debug_info);
+  set_message (message_string, data);
+  g_free (message_string);
+  gst_element_set_state (data->pipeline, GST_STATE_NULL);
+}
+
+static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
+  set_message (GST_MESSAGE_TYPE_NAME (msg), data);
+  gst_element_set_state (data->pipeline, GST_STATE_NULL);
+}
+
+static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
+  GstState old_state, new_state, pending_state;
+  gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
+  if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
+    set_message (gst_element_state_get_name (new_state), data);
+    data->playing = (new_state == GST_STATE_PLAYING);
+  }
+}
+
+static gboolean refresh_ui (CustomData *data) {
+  GstFormat fmt = GST_FORMAT_TIME;
+  gint64 current = -1;
+
+  /* We do not want to update anything unless we are in the PLAYING state */
+  if (!data->playing)
+    return TRUE;
+   
+  /* If we didn't know it yet, query the stream duration */
+  if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
+    if (!gst_element_query_duration (data->pipeline, &fmt, &data->duration)) {
+      GST_WARNING ("Could not query current duration");
+    }
+  }
+   
+  if (gst_element_query_position (data->pipeline, &fmt, &data->position)) {
+    /* Java expects these values in milliseconds, and Gst provides nanoseconds */
+    set_current_position (data->position/1000000, data->duration/1000000, data);
+  }
+  return TRUE;
+}
+
+static void *app_function (void *userdata) {
   JavaVMAttachArgs args;
   GstBus *bus;
   GstMessage *msg;
@@ -90,15 +154,22 @@ static void *gst_app_function (void *userdata) {
 
   GST_DEBUG ("Creating pipeline in CustomData at %p", data);
 
-  data->pipeline = gst_parse_launch ("videotestsrc ! eglglessink force_rendering_slow=1 can_create_window=0", NULL);
+//  data->pipeline = gst_parse_launch ("videotestsrc ! eglglessink force_rendering_slow=1 can_create_window=0", NULL);
+//  data->pipeline = gst_parse_launch ("filesrc location=/sdcard/Movies/sintel_trailer-480p.ogv ! oggdemux ! theoradec ! fakesink", NULL);
+  data->pipeline = gst_parse_launch ("souphttpsrc location=http://docs.gstreamer.com/media/sintel_trailer-480p.ogv ! oggdemux ! theoradec ! fakesink", NULL);
+
 
   /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
   bus = gst_element_get_bus (data->pipeline);
   gst_bus_add_signal_watch (bus);
   g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, data);
-  g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)error_cb, data);
+  g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, data);
+  g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, data);
   gst_object_unref (bus);
-  
+
+  /* Register a function that GLib will call every second */
+  g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, data);
+
   /* Create a GLib Main Loop and set it to run */
   GST_DEBUG ("Entering main loop...");
   data->main_loop = g_main_loop_new (NULL, FALSE);
@@ -116,11 +187,12 @@ static void *gst_app_function (void *userdata) {
  */
 void gst_native_init (JNIEnv* env, jobject thiz) {
   CustomData *data = (CustomData *)g_malloc0 (sizeof (CustomData));
+  data->duration = GST_CLOCK_TIME_NONE;
   SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
   GST_DEBUG ("Created CustomData at %p", data);
   data->app = (*env)->NewGlobalRef (env, thiz);
   GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
-  pthread_create (&gst_app_thread, NULL, &gst_app_function, data);
+  pthread_create (&gst_app_thread, NULL, &app_function, data);
 }
 
 void gst_native_finalize (JNIEnv* env, jobject thiz) {
@@ -144,7 +216,7 @@ void gst_native_play (JNIEnv* env, jobject thiz) {
 
 void gst_native_pause (JNIEnv* env, jobject thiz) {
   CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
-  GST_DEBUG ("Setting state to READY");
+  GST_DEBUG ("Setting state to PAUSED");
   gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
 }
 
@@ -153,6 +225,8 @@ void gst_class_init (JNIEnv* env, jclass klass) {
   GST_DEBUG ("The FieldID for the native_custom_data field is %p", custom_data_field_id);
   set_message_method_id = (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
   GST_DEBUG ("The MethodID for the setMessage method is %p", set_message_method_id);
+  set_current_position_method_id = (*env)->GetMethodID (env, klass, "setCurrentPosition", "(JJ)V");
+  GST_DEBUG ("The MethodID for the setCurrentPosition method is %p", set_current_position_method_id);
 }
 
 void gst_native_surface_init (JNIEnv *env, jobject thiz, jobject surface) {
@@ -202,7 +276,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
   jclass klass = (*env)->FindClass (env, "com/gst_sdk_tutorials/tutorial_1/Tutorial1");
   ret = (*env)->RegisterNatives (env, klass, native_methods, G_N_ELEMENTS(native_methods));
 
-  pthread_key_create (&current_jni_env, gst_detach_current_thread);
+  pthread_key_create (&current_jni_env, detach_current_thread);
 
   return JNI_VERSION_1_4;
 }
index 651740a..edf04e3 100755 (executable)
             android:contentDescription="@string/button_stop"\r
             android:src="@android:drawable/ic_media_pause"\r
             android:text="@string/button_stop" />\r
+\r
+        <TextView\r
+            android:id="@+id/textview_time"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_gravity="center_vertical" />\r
     </LinearLayout>\r
 \r
     <SurfaceView\r
index 36d4e48..3da3d75 100755 (executable)
  */
 package com.gst_sdk_tutorials.tutorial_1;
 
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
 import android.app.Activity;
 import android.util.Log;
 import android.os.Bundle;
@@ -71,7 +75,18 @@ public class Tutorial1 extends Activity implements SurfaceHolder.Callback {
 
     private void setMessage(final String message) {
         final TextView tv = (TextView) this.findViewById(R.id.textview_message);
-        Log.d("GStreamer", "Received message " + message);
+        runOnUiThread (new Runnable() {
+          public void run() {
+            tv.setText(message);
+          }
+        });
+    }
+
+    private void setCurrentPosition(long position, long duration) {
+        final TextView tv = (TextView) this.findViewById(R.id.textview_time);
+        SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
+        df.setTimeZone(TimeZone.getTimeZone("UTC"));
+        final String message = df.format(new Date (position)) + " / " + df.format(new Date (duration));
         runOnUiThread (new Runnable() {
           public void run() {
             tv.setText(message);