mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +00:00
Report current position/duration back to the UI. Refactor.
This commit is contained in:
parent
0156029e06
commit
8f8258e4b9
5 changed files with 113 additions and 17 deletions
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (¤t_jni_env, gst_detach_current_thread);
|
||||
pthread_key_create (¤t_jni_env, detach_current_thread);
|
||||
|
||||
return JNI_VERSION_1_4;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,12 @@
|
|||
android:contentDescription="@string/button_stop"
|
||||
android:src="@android:drawable/ic_media_pause"
|
||||
android:text="@string/button_stop" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical" />
|
||||
</LinearLayout>
|
||||
|
||||
<SurfaceView
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
*/
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue