#include "cltr-video.h" #include "cltr-private.h" /* This is all very much based on the totem gst bacon video widget */ struct CltrVideo { CltrWidget widget; GstElement *play, *data_src, *video_sink, *audio_sink, *vis_element; GAsyncQueue *queue; gint video_width, video_height; gdouble video_fps; CltrTexture *frame_texture; gboolean has_video, has_audio; gint64 stream_length; gint64 current_time_nanos; gint64 current_time; float current_position; guint update_id; gchar *last_error_message; gchar *mrl; }; static void cltr_video_show(CltrWidget *widget); static gboolean cltr_video_handle_xevent (CltrWidget *widget, XEvent *xev); static void cltr_video_paint(CltrWidget *widget); static void parse_stream_info (CltrVideo *video); static gboolean cb_iterate (CltrVideo *video); static void reset_error_msg (CltrVideo *video); static gboolean cltr_video_idler (CltrVideo *video); static gint64 length = 0; /* to go */ static void cltr_video_print_tag (const GstTagList *list, const gchar *tag, gpointer unused) { gint i, count; count = gst_tag_list_get_tag_size (list, tag); for (i = 0; i < count; i++) { gchar *str; if (gst_tag_get_type (tag) == G_TYPE_STRING) { if (!gst_tag_list_get_string_index (list, tag, i, &str)) g_assert_not_reached (); } else { str = g_strdup_value_contents (gst_tag_list_get_value_index (list, tag, i)); } if (i == 0) { g_print ("%15s: %s\n", gst_tag_get_nick (tag), str); } else { g_print (" : %s\n", str); } g_free (str); } } static void got_eos (GstPlay* play, CltrVideo *video) { CLTR_DBG ("End Of Stream\n"); CltrVideoSignal *signal; signal = g_new0 (CltrVideoSignal, 1); signal->signal_id = CLTR_VIDEO_ASYNC_EOS; g_async_queue_push (video->queue, signal); gst_element_set_state (GST_ELEMENT (play), GST_STATE_READY); } static void got_stream_length (GstElement *play, gint64 length_nanos, CltrVideo *video) { video->stream_length = (gint64) length_nanos / GST_MSECOND; CLTR_MARK(); /* fire off some callback here ? */ CLTR_DBG("length: %li", video->stream_length); } static void got_time_tick (GstElement *play, gint64 time_nanos, CltrVideo *video) { CLTR_MARK(); video->current_time_nanos = time_nanos; video->current_time = (gint64) time_nanos / GST_MSECOND; if (video->stream_length == 0) video->current_position = 0; else { video->current_position = (float) video->current_time / video->stream_length; } CLTR_DBG("current pos is %f\n", video->current_position); /* fire off callback here */ } static void got_found_tag (GstPlay *play, GstElement *source, GstTagList *tag_list, CltrVideo *video) { CltrVideoSignal *signal; CLTR_MARK(); signal = g_new0 (CltrVideoSignal, 1); signal->signal_id = CLTR_VIDEO_ASYNC_FOUND_TAG; signal->signal_data.found_tag.source = source; signal->signal_data.found_tag.tag_list = gst_tag_list_copy (tag_list); g_async_queue_push (video->queue, signal); /* gst_tag_list_foreach (tag_list, cltr_video_print_tag, NULL); */ } static void got_state_change (GstElement *play, GstElementState old_state, GstElementState new_state, CltrVideo *video) { if (old_state == GST_STATE_PLAYING) { if (video->update_id != 0) { g_source_remove (video->update_id); video->update_id = 0; } g_idle_remove_by_data (video); } else if (new_state == GST_STATE_PLAYING) { if (video->update_id != 0) g_source_remove (video->update_id); video->update_id = g_timeout_add (200, (GSourceFunc) cb_iterate, video); g_idle_add((GSourceFunc) cltr_video_idler, video); } if (old_state <= GST_STATE_READY && new_state >= GST_STATE_PAUSED) { parse_stream_info (video); } else if (new_state <= GST_STATE_READY && old_state >= GST_STATE_PAUSED) { video->has_video = FALSE; video->has_audio = FALSE; /* blank the texture */ /* while (g_async_queue_try_pop (video->queue)) ; */ /* if (bvw->priv->tagcache) { gst_tag_list_free (bvw->priv->tagcache); bvw->priv->tagcache = NULL; } */ video->video_width = 0; video->video_height = 0; } } static void got_redirect (GstElement *play, const gchar *new_location, CltrVideo *bvw) { CLTR_MARK(); /* bvw->priv->got_redirect = TRUE; signal = g_new0 (BVWSignal, 1); signal->signal_id = ASYNC_REDIRECT; signal->signal_data.redirect.new_location = g_strdup (new_location); g_async_queue_push (bvw->priv->queue, signal); g_idle_add ((GSourceFunc) bacon_video_widget_signal_idler, bvw); */ } static void stream_info_set (GObject *obj, GParamSpec *pspec, CltrVideo *video) { parse_stream_info (video); /* signal = g_new0 (BVWSignal, 1); signal->signal_id = ASYNC_NOTIFY_STREAMINFO; g_async_queue_push (bvw->priv->queue, signal); g_idle_add ((GSourceFunc) bacon_video_widget_signal_idler, bvw); */ } static void got_source (GObject *play, GParamSpec *pspec, CltrVideo *video) { GObject *source = NULL; GObjectClass *klass; CLTR_MARK(); /* if (bvw->priv->tagcache) { gst_tag_list_free (bvw->priv->tagcache); bvw->priv->tagcache = NULL; } if (!bvw->priv->media_device) return; g_object_get (play, "source", &source, NULL); if (!source) return; klass = G_OBJECT_GET_CLASS (source); if (!g_object_class_find_property (klass, "device")) return; g_object_set (source, "device", bvw->priv->media_device, NULL); */ } static void got_buffering (GstElement *play, gint percentage, CltrVideo *video) { CLTR_DBG("Buffering with %i", percentage); #if 0 BVWSignal *signal; g_return_if_fail (bvw != NULL); g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); signal = g_new0 (BVWSignal, 1); signal->signal_id = ASYNC_BUFFERING; signal->signal_data.buffering.percent = percentage; g_async_queue_push (bvw->priv->queue, signal); g_idle_add ((GSourceFunc) bacon_video_widget_signal_idler, bvw); #endif } static void reset_error_msg (CltrVideo *video) { if (video->last_error_message) { g_free (video->last_error_message); video->last_error_message = NULL; } } static void got_error (GstElement *play, GstElement *orig, GError *error, gchar *debug, CltrVideo *video) { /* XXX TODO cpy the error message to asyc queueu */ CLTR_MARK(); #if 0 /* since we're opening, we will never enter the mainloop * until we return, so setting an idle handler doesn't * help... Anyway, let's prepare a message. */ if (GST_STATE (play) != GST_STATE_PLAYING) { g_free (bvw->priv->last_error_message); bvw->priv->last_error_message = g_strdup (error->message); return; } signal = g_new0 (BVWSignal, 1); signal->signal_id = ASYNC_ERROR; signal->signal_data.error.element = orig; signal->signal_data.error.error = g_error_copy (error); if (debug) signal->signal_data.error.debug_message = g_strdup (debug); g_async_queue_push (bvw->priv->queue, signal); g_idle_add ((GSourceFunc) bacon_video_widget_signal_idler, bvw); #endif } static void caps_set (GObject *obj, GParamSpec *pspec, CltrVideo *video) { GstPad *pad = GST_PAD (obj); GstStructure *s; if (!GST_PAD_CAPS (pad)) return; s = gst_caps_get_structure (GST_PAD_CAPS (pad), 0); if (s) { /* const GValue *par; */ if (!(gst_structure_get_double (s, "framerate", &video->video_fps) && gst_structure_get_int (s, "width", &video->video_width) && gst_structure_get_int (s, "height", &video->video_height))) return; /* if ((par = gst_structure_get_value (s, "pixel-aspect-ratio"))) { gint num = gst_value_get_fraction_numerator (par), den = gst_value_get_fraction_denominator (par); if (num > den) bvw->priv->video_width *= (gfloat) num / den; else bvw->priv->video_height *= (gfloat) den / num; } got_video_size (bvw->priv->play, bvw->priv->video_width, bvw->priv->video_height, bvw); */ } } static void parse_stream_info (CltrVideo *video) { GList *streaminfo = NULL; GstPad *videopad = NULL; g_object_get (G_OBJECT (video->play), "stream-info", &streaminfo, NULL); streaminfo = g_list_copy (streaminfo); g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL); for ( ; streaminfo != NULL; streaminfo = streaminfo->next) { GObject *info = streaminfo->data; gint type; GParamSpec *pspec; GEnumValue *val; if (!info) continue; g_object_get (info, "type", &type, NULL); pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type"); val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type); if (strstr (val->value_name, "AUDIO")) { if (!video->has_audio) { video->has_audio = TRUE; /*if (!bvw->priv->media_has_video && bvw->priv->show_vfx && bvw->priv->vis_element) { videopad = gst_element_get_pad (bvw->priv->vis_element, "src"); }*/ } } else if (strstr (val->value_name, "VIDEO")) { video->has_video = TRUE; if (!videopad) g_object_get (info, "object", &videopad, NULL); } } if (videopad) { GstPad *real = (GstPad *) GST_PAD_REALIZE (videopad); /* handle explicit caps as well - they're set later */ if (((GstRealPad *) real)->link != NULL && GST_PAD_CAPS (real)) caps_set (G_OBJECT (real), NULL, video); g_signal_connect (real, "notify::caps", G_CALLBACK (caps_set), video); } /* else if (bvw->priv->show_vfx && bvw->priv->vis_element) { fixate_visualization (NULL, NULL, bvw); } */ g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL); g_list_free (streaminfo); } static gboolean cb_iterate (CltrVideo *video) { GstFormat fmt = GST_FORMAT_TIME; gint64 value; CLTR_MARK(); /* check length/pos of stream */ if (gst_element_query (GST_ELEMENT (video->play), GST_QUERY_TOTAL, &fmt, &value) && GST_CLOCK_TIME_IS_VALID (value) && value / GST_MSECOND != video->stream_length) { got_stream_length (GST_ELEMENT (video->play), value, video); } if (gst_element_query (GST_ELEMENT (video->play), GST_QUERY_POSITION, &fmt, &value)) { got_time_tick (GST_ELEMENT (video->play), value, video); } return TRUE; } CltrWidget* cltr_video_new(int width, int height) { CltrVideo *video; GError *error = NULL; video = g_malloc0(sizeof(CltrVideo)); video->widget.width = width; video->widget.height = height; video->widget.show = cltr_video_show; video->widget.paint = cltr_video_paint; video->widget.xevent_handler = cltr_video_handle_xevent; /* Creating the GstPlay object */ video->play = gst_element_factory_make ("playbin", "play"); if (!video->play) { g_error ("Could not make playbin element"); /* XXX Error */ return NULL; } video->audio_sink = gst_gconf_get_default_audio_sink (); if (!GST_IS_ELEMENT (video->audio_sink)) g_error ("Could not get default audio sink from GConf"); video->video_sink = gst_element_factory_make ("cltrimagesink", "cltr-output"); if (!GST_IS_ELEMENT (video->video_sink)) g_error ("Could not get clutter video sink"); #if 0 sig1 = g_signal_connect (video_sink, "error", G_CALLBACK (out_error), err); sig2 = g_signal_connect (audio_sink, "error", G_CALLBACK (out_error), err); if (gst_element_set_state (video_sink, GST_STATE_READY) != GST_STATE_SUCCESS || gst_element_set_state (audio_sink, GST_STATE_READY) != GST_STATE_SUCCESS) { if (err && !*err) { g_set_error (err, 0, 0, "Failed to intialize %s output; check your configuration", GST_STATE (video_sink) == GST_STATE_NULL ? "video" : "audio"); } gst_object_unref (GST_OBJECT (video_sink)); gst_object_unref (GST_OBJECT (audio_sink)); g_object_unref (G_OBJECT (bvw)); return NULL; } /* somehow, alsa hangs? */ gst_element_set_state (video->audio_sink, GST_STATE_NULL); g_signal_handler_disconnect (video->video_sink, sig1); g_signal_handler_disconnect (video->audio_sink, sig2); #endif g_object_set (G_OBJECT (video->play), "video-sink", video->video_sink, NULL); g_object_set (G_OBJECT (video->play), "audio-sink", video->audio_sink, NULL); /* Needed ? */ #if 0 g_signal_connect (GST_PAD_REALIZE (gst_element_get_pad (audio_sink, "sink")), "fixate", G_CALLBACK (cb_audio_fixate), (gpointer) bvw); #endif g_signal_connect (G_OBJECT (video->play), "eos", G_CALLBACK (got_eos), (gpointer) video); g_signal_connect (G_OBJECT (video->play), "state-change", G_CALLBACK (got_state_change), (gpointer) video); g_signal_connect (G_OBJECT (video->play), "found_tag", G_CALLBACK (got_found_tag), (gpointer) video); g_signal_connect (G_OBJECT (video->play), "error", G_CALLBACK (got_error), (gpointer) video); g_signal_connect (G_OBJECT (video->play), "buffering", G_CALLBACK (got_buffering), (gpointer) video); g_signal_connect (G_OBJECT (video->play), "notify::source", G_CALLBACK (got_source), (gpointer) video); g_signal_connect (G_OBJECT (video->play), "notify::stream-info", G_CALLBACK (stream_info_set), (gpointer) video); /* what does this do ? g_signal_connect (G_OBJECT (video->play), "group-switch", G_CALLBACK (group_switch), (gpointer) video); */ g_signal_connect (G_OBJECT (video->play), "got-redirect", G_CALLBACK (got_redirect), (gpointer) video); video->queue = g_async_queue_new (); gst_element_set(video->video_sink, "queue", video->queue, NULL); return CLTR_WIDGET(video); } gboolean cltr_video_play ( CltrVideo *video, GError ** error) { gboolean ret; reset_error_msg (video); ret = (gst_element_set_state (GST_ELEMENT (video->play), GST_STATE_PLAYING) == GST_STATE_SUCCESS); if (!ret) { g_set_error (error, 0, 0, "%s", video->last_error_message ? video->last_error_message : "Failed to play; reason unknown"); } return ret; } gint64 cltr_video_get_time (CltrVideo *video) { CLTR_DBG("current pos is %f\n", video->current_position); return video->current_time; } gboolean cltr_video_seek (CltrVideo *video, float position, GError **gerror) { gint64 seek_time, length_nanos; /* Resetting last_error_message to NULL */ if (video->last_error_message) { g_free (video->last_error_message); video->last_error_message = NULL; } length_nanos = (gint64) (video->stream_length * GST_MSECOND); seek_time = (gint64) (length_nanos * position); gst_element_seek (video->play, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH | GST_FORMAT_TIME, seek_time); return TRUE; } gboolean cltr_video_seek_time (CltrVideo *video, gint64 time, GError **gerror) { if (video->last_error_message) { g_free (video->last_error_message); video->last_error_message = NULL; } gst_element_seek (video->play, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH | GST_FORMAT_TIME, time * GST_MSECOND); return TRUE; } void cltr_video_stop ( CltrVideo *video) { gst_element_set_state (GST_ELEMENT (video->play), GST_STATE_READY); } void cltr_video_close ( CltrVideo *video) { gst_element_set_state (GST_ELEMENT (video->play), GST_STATE_READY); /* XX close callback here */ } void cltr_video_pause ( CltrVideo *video) { gst_element_set_state (GST_ELEMENT (video->play), GST_STATE_PAUSED); } gboolean cltr_video_can_set_volume ( CltrVideo *video ) { return TRUE; } void cltr_video_set_volume ( CltrVideo *video, int volume) { if (cltr_video_can_set_volume (video) != FALSE) { volume = CLAMP (volume, 0, 100); g_object_set (G_OBJECT (video->play), "volume", (gdouble) (1. * volume / 100), NULL); } } int cltr_video_get_volume ( CltrVideo *video) { gdouble vol; g_object_get (G_OBJECT (video->play), "volume", &vol, NULL); return (gint) (vol * 100 + 0.5); } Pixbuf* cltr_video_get_pixbuf (CltrVideo *video) { Pixbuf *pixb = NULL; /* if (video->frame_texture) */ { cltr_texture_lock(video->frame_texture); pixb = pixbuf_clone(cltr_texture_get_pixbuf(video->frame_texture)); cltr_texture_unlock(video->frame_texture); } return pixb; } static gboolean cltr_video_idler (CltrVideo *video) { gint queue_length; CltrVideoSignal *signal; signal = g_async_queue_try_pop (video->queue); if (!signal) return TRUE; switch (signal->signal_id) { case CLTR_VIDEO_ASYNC_TEXTURE: { Pixbuf *pixb = NULL; video->frame_texture = signal->signal_data.texture.ref; cltr_texture_lock(video->frame_texture); if (cltr_texture_get_pixbuf(video->frame_texture)) cltr_texture_sync_pixbuf(video->frame_texture); cltr_texture_unlock(video->frame_texture); cltr_widget_queue_paint(CLTR_WIDGET(video)); } break; case CLTR_VIDEO_ASYNC_VIDEO_SIZE: video->video_width = signal->signal_data.video_size.width; video->video_height = signal->signal_data.video_size.height; break; case CLTR_VIDEO_ASYNC_ERROR: break; case CLTR_VIDEO_ASYNC_FOUND_TAG: break; case CLTR_VIDEO_ASYNC_NOTIFY_STREAMINFO: break; case CLTR_VIDEO_ASYNC_EOS: break; case CLTR_VIDEO_ASYNC_BUFFERING: break; case CLTR_VIDEO_ASYNC_REDIRECT: break; } g_free (signal); return TRUE; } gboolean cltr_video_set_source(CltrVideo *video, char *mrl) { gboolean ret; if (video->mrl && !strcmp (video->mrl, mrl)) return TRUE; if (video->mrl) g_free (video->mrl); video->mrl = g_strdup (mrl); gst_element_set_state (GST_ELEMENT (video->play), GST_STATE_READY); reset_error_msg (video); /* video->got_redirect = FALSE; */ video->has_video = FALSE; video->stream_length = 0; /* Dont handle subtitles as yet if (g_strrstr (video->mrl, "#subtitle:")) { gchar **uris; uris = g_strsplit (video->mrl, "#subtitle:", 2); g_object_set (G_OBJECT (video->play), "uri", uris[0], "suburi", uris[1], NULL); g_strfreev (uris); } else { g_object_set (G_OBJECT (video->play), "uri", video->mrl, "suburi", subtitle_uri, NULL); } */ g_object_set (G_OBJECT (video->play), "uri", video->mrl, "suburi", NULL, NULL); ret = (gst_element_set_state (video->play, GST_STATE_PAUSED) == GST_STATE_SUCCESS); if (!ret /* && !video->got_redirect */) { /* g_set_error (error, 0, 0, "%s", video->last_error_message ? video->last_error_message : "Failed to open; reason unknown"); */ g_free (video->mrl); video->mrl = NULL; return FALSE; } /* if (ret) g_signal_emit (bvw, bvw_table_signals[SIGNAL_CHANNELS_CHANGE], 0); */ return ret; } static void cltr_video_show(CltrWidget *widget) { return; } static void cltr_video_hide(CltrWidget *widget) { return; } static gboolean cltr_video_handle_xevent (CltrWidget *widget, XEvent *xev) { CLTR_DBG("X Event"); return False; } static void cltr_video_paint(CltrWidget *widget) { CltrVideo *video = CLTR_VIDEO(widget); glPushMatrix(); if (video->frame_texture && video->video_height && video->video_width) { int dis_x = 0, dis_y = 0, dis_height = 0, dis_width = 0; if (video->video_width > video->video_height) { dis_width = widget->width; dis_height = ( video->video_height * widget->width ) / video->video_width; dis_y = (widget->height - dis_height)/2; dis_x = 0; } glEnable(GL_TEXTURE_2D); /* glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */ glColor4f(1.0, 1.0, 1.0, 1.0); cltr_texture_lock(video->frame_texture); cltr_texture_render_to_gl_quad(video->frame_texture, widget->x + dis_x, widget->y + dis_y, widget->x + dis_x + dis_width, widget->y + dis_y + dis_height); cltr_texture_unlock(video->frame_texture); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); // glColor4f(1.0, 1.0, 1.0, 0.5); // glRecti(100, 100, 600, 600); } glPopMatrix(); }