2 * This file is a part of MAFW
4 * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved.
6 * Contact: Visa Smolander <visa.smolander@nokia.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; version 2.1 of
11 * the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31 #include <gst/interfaces/xoverlay.h>
32 #include <gst/pbutils/missing-plugins.h>
33 #include <gst/base/gstbasesink.h>
34 #include <libmafw/mafw.h>
37 #include <gdk-pixbuf/gdk-pixbuf.h>
38 #include <glib/gstdio.h>
40 #include "gstscreenshot.h"
43 #include <totem-pl-parser.h>
44 #include "mafw-gst-renderer.h"
45 #include "mafw-gst-renderer-worker.h"
46 #include "mafw-gst-renderer-utils.h"
51 #define G_LOG_DOMAIN "mafw-gst-renderer-worker"
53 #define MAFW_GST_RENDERER_WORKER_SECONDS_READY 60
54 #define MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY 4
56 #define MAFW_GST_MISSING_TYPE_DECODER "decoder"
57 #define MAFW_GST_MISSING_TYPE_ENCODER "encoder"
59 #define MAFW_GST_BUFFER_TIME 600000L
60 #define MAFW_GST_LATENCY_TIME (MAFW_GST_BUFFER_TIME / 2)
62 #define NSECONDS_TO_SECONDS(ns) ((ns)%1000000000 < 500000000?\
63 GST_TIME_AS_SECONDS((ns)):\
64 GST_TIME_AS_SECONDS((ns))+1)
66 #define _current_metadata_add(worker, key, type, value) \
68 if (!worker->current_metadata) \
69 worker->current_metadata = mafw_metadata_new(); \
70 /* At first remove old value */ \
71 g_hash_table_remove(worker->current_metadata, key); \
72 mafw_metadata_add_something(worker->current_metadata, \
73 key, type, 1, value); \
76 /* Private variables. */
77 /* Global reference to worker instance, needed for Xerror handler */
78 static MafwGstRendererWorker *Global_worker = NULL;
80 /* Forward declarations. */
81 static void _do_play(MafwGstRendererWorker *worker);
82 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
83 gint position, GError **error);
84 static void _play_pl_next(MafwGstRendererWorker *worker);
86 static void _emit_metadatas(MafwGstRendererWorker *worker);
88 /* Playlist parsing */
89 static void _on_pl_entry_parsed(TotemPlParser *parser, gchar *uri,
90 gpointer metadata, GSList **plitems)
93 *plitems = g_slist_append(*plitems, g_strdup(uri));
96 static GSList *_parse_playlist(const gchar *uri)
98 static TotemPlParser *pl_parser = NULL;
99 GSList *plitems = NULL;
102 /* Initialize the playlist parser */
105 pl_parser = totem_pl_parser_new ();
106 g_object_set(pl_parser, "recurse", TRUE, "disable-unsafe",
109 handler_id = g_signal_connect(G_OBJECT(pl_parser), "entry-parsed",
110 G_CALLBACK(_on_pl_entry_parsed), &plitems);
112 if (totem_pl_parser_parse(pl_parser, uri, FALSE) !=
113 TOTEM_PL_PARSER_RESULT_SUCCESS) {
114 /* An error happens while parsing */
117 g_signal_handler_disconnect(pl_parser, handler_id);
122 * Sends @error to MafwGstRenderer. Only call this from the glib main thread, or
123 * face the consequences. @err is free'd.
125 static void _send_error(MafwGstRendererWorker *worker, GError *err)
127 worker->is_error = TRUE;
128 if (worker->notify_error_handler)
129 worker->notify_error_handler(worker, worker->owner, err);
134 * Posts an @error on the gst bus. _async_bus_handler will then pick it up and
135 * forward to MafwGstRenderer. @err is free'd.
137 static void _post_error(MafwGstRendererWorker *worker, GError *err)
139 gst_bus_post(worker->bus,
140 gst_message_new_error(GST_OBJECT(worker->pipeline),
145 #ifdef HAVE_GDKPIXBUF
147 MafwGstRendererWorker *worker;
152 static gchar *_init_tmp_file(void)
157 fd = g_file_open_tmp("mafw-gst-renderer-XXXXXX.jpeg", &path, NULL);
163 static void _init_tmp_files_pool(MafwGstRendererWorker *worker)
167 worker->tmp_files_pool_index = 0;
169 for (i = 0; i < MAFW_GST_RENDERER_MAX_TMP_FILES; i++) {
170 worker->tmp_files_pool[i] = NULL;
174 static void _destroy_tmp_files_pool(MafwGstRendererWorker *worker)
178 for (i = 0; (i < MAFW_GST_RENDERER_MAX_TMP_FILES) &&
179 (worker->tmp_files_pool[i] != NULL); i++) {
180 g_unlink(worker->tmp_files_pool[i]);
181 g_free(worker->tmp_files_pool[i]);
185 static const gchar *_get_tmp_file_from_pool(
186 MafwGstRendererWorker *worker)
188 gchar *path = worker->tmp_files_pool[worker->tmp_files_pool_index];
191 path = _init_tmp_file();
192 worker->tmp_files_pool[worker->tmp_files_pool_index] = path;
195 if (++(worker->tmp_files_pool_index) >=
196 MAFW_GST_RENDERER_MAX_TMP_FILES) {
197 worker->tmp_files_pool_index = 0;
203 static void _destroy_pixbuf (guchar *pixbuf, gpointer data)
205 gst_buffer_unref(GST_BUFFER(data));
208 static void _emit_gst_buffer_as_graphic_file_cb(GstBuffer *new_buffer,
211 SaveGraphicData *sgd = user_data;
212 GdkPixbuf *pixbuf = NULL;
214 if (new_buffer != NULL) {
216 GstStructure *structure;
219 gst_caps_get_structure(GST_BUFFER_CAPS(new_buffer), 0);
221 gst_structure_get_int(structure, "width", &width);
222 gst_structure_get_int(structure, "height", &height);
224 pixbuf = gdk_pixbuf_new_from_data(
225 GST_BUFFER_DATA(new_buffer), GDK_COLORSPACE_RGB,
226 FALSE, 8, width, height,
227 GST_ROUND_UP_4(3 * width), _destroy_pixbuf,
230 if (sgd->pixbuf != NULL) {
231 g_object_unref(sgd->pixbuf);
235 pixbuf = sgd->pixbuf;
238 if (pixbuf != NULL) {
240 GError *error = NULL;
241 const gchar *filename;
243 filename = _get_tmp_file_from_pool(sgd->worker);
245 save_ok = gdk_pixbuf_save (pixbuf, filename, "jpeg", &error,
248 g_object_unref (pixbuf);
251 /* Add the info to the current metadata. */
252 _current_metadata_add(sgd->worker, sgd->metadata_key,
256 /* Emit the metadata. */
257 mafw_renderer_emit_metadata_string(sgd->worker->owner,
262 g_warning ("%s\n", error->message);
263 g_error_free (error);
265 g_critical("Unknown error when saving pixbuf "
266 "with GStreamer data");
270 g_warning("Could not create pixbuf from GstBuffer");
273 g_free(sgd->metadata_key);
277 static void _pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
278 gint width, gint height,
281 /* Be sure the image size is reasonable */
282 if (width > 512 || height > 512) {
283 g_debug ("pixbuf: image is too big: %dx%d", width, height);
285 ar = (gdouble) width / height;
286 if (width > height) {
293 g_debug ("pixbuf: scaled image to %dx%d", width, height);
294 gdk_pixbuf_loader_set_size (loader, width, height);
298 static void _emit_gst_buffer_as_graphic_file(MafwGstRendererWorker *worker,
300 const gchar *metadata_key)
302 GdkPixbufLoader *loader;
303 GstStructure *structure;
304 const gchar *mime = NULL;
305 GError *error = NULL;
307 g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
309 structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0);
310 mime = gst_structure_get_name(structure);
312 if (g_str_has_prefix(mime, "video/x-raw")) {
313 gint framerate_d, framerate_n;
315 SaveGraphicData *sgd;
317 gst_structure_get_fraction (structure, "framerate",
318 &framerate_n, &framerate_d);
320 to_caps = gst_caps_new_simple ("video/x-raw-rgb",
321 "bpp", G_TYPE_INT, 24,
322 "depth", G_TYPE_INT, 24,
323 "framerate", GST_TYPE_FRACTION,
324 framerate_n, framerate_d,
325 "pixel-aspect-ratio",
326 GST_TYPE_FRACTION, 1, 1,
328 G_TYPE_INT, G_BIG_ENDIAN,
329 "red_mask", G_TYPE_INT,
332 G_TYPE_INT, 0x00ff00,
334 G_TYPE_INT, 0x0000ff,
337 sgd = g_new0(SaveGraphicData, 1);
338 sgd->worker = worker;
339 sgd->metadata_key = g_strdup(metadata_key);
341 g_debug("pixbuf: using bvw to convert image format");
342 bvw_frame_conv_convert (buffer, to_caps,
343 _emit_gst_buffer_as_graphic_file_cb,
346 GdkPixbuf *pixbuf = NULL;
347 loader = gdk_pixbuf_loader_new_with_mime_type(mime, &error);
348 g_signal_connect (G_OBJECT (loader), "size-prepared",
349 (GCallback)_pixbuf_size_prepared_cb, NULL);
351 if (loader == NULL) {
352 g_warning ("%s\n", error->message);
353 g_error_free (error);
355 if (!gdk_pixbuf_loader_write (loader,
356 GST_BUFFER_DATA(buffer),
357 GST_BUFFER_SIZE(buffer),
359 g_warning ("%s\n", error->message);
360 g_error_free (error);
362 gdk_pixbuf_loader_close (loader, NULL);
364 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
366 if (!gdk_pixbuf_loader_close (loader, &error)) {
367 g_warning ("%s\n", error->message);
368 g_error_free (error);
370 g_object_unref(pixbuf);
372 SaveGraphicData *sgd;
374 sgd = g_new0(SaveGraphicData, 1);
376 sgd->worker = worker;
378 g_strdup(metadata_key);
379 sgd->pixbuf = pixbuf;
381 _emit_gst_buffer_as_graphic_file_cb(
385 g_object_unref(loader);
391 static gboolean _go_to_gst_ready(gpointer user_data)
393 MafwGstRendererWorker *worker = user_data;
395 g_return_val_if_fail(worker->state == GST_STATE_PAUSED ||
396 worker->prerolling, FALSE);
398 worker->seek_position =
399 mafw_gst_renderer_worker_get_position(worker);
401 g_debug("going to GST_STATE_READY");
402 gst_element_set_state(worker->pipeline, GST_STATE_READY);
403 worker->in_ready = TRUE;
404 worker->ready_timeout = 0;
409 static void _add_ready_timeout(MafwGstRendererWorker *worker)
411 if (worker->media.seekable) {
412 if (!worker->ready_timeout)
414 g_debug("Adding timeout to go to GST_STATE_READY");
415 worker->ready_timeout =
416 g_timeout_add_seconds(
417 MAFW_GST_RENDERER_WORKER_SECONDS_READY,
422 g_debug("Not adding timeout to go to GST_STATE_READY as media "
424 worker->ready_timeout = 0;
428 static void _remove_ready_timeout(MafwGstRendererWorker *worker)
430 if (worker->ready_timeout != 0) {
431 g_debug("removing timeout for READY");
432 g_source_remove(worker->ready_timeout);
433 worker->ready_timeout = 0;
435 worker->in_ready = FALSE;
438 static gboolean _emit_video_info(MafwGstRendererWorker *worker)
440 mafw_renderer_emit_metadata_int(worker->owner,
441 MAFW_METADATA_KEY_RES_X,
442 worker->media.video_width);
443 mafw_renderer_emit_metadata_int(worker->owner,
444 MAFW_METADATA_KEY_RES_Y,
445 worker->media.video_height);
446 mafw_renderer_emit_metadata_double(worker->owner,
447 MAFW_METADATA_KEY_VIDEO_FRAMERATE,
453 * Checks if the video details are supported. It also extracts other useful
454 * information (such as PAR and framerate) from the caps, if available. NOTE:
455 * this will be called from a different thread than glib's mainloop (when
456 * invoked via _stream_info_cb); don't call MafwGstRenderer directly.
458 * Returns: TRUE if video details are acceptable.
460 static gboolean _handle_video_info(MafwGstRendererWorker *worker,
461 const GstStructure *structure)
467 gst_structure_get_int(structure, "width", &width);
468 gst_structure_get_int(structure, "height", &height);
469 g_debug("video size: %d x %d", width, height);
470 if (gst_structure_has_field(structure, "pixel-aspect-ratio"))
472 gst_structure_get_fraction(structure, "pixel-aspect-ratio",
473 &worker->media.par_n,
474 &worker->media.par_d);
475 g_debug("video PAR: %d:%d", worker->media.par_n,
476 worker->media.par_d);
477 width = width * worker->media.par_n / worker->media.par_d;
481 if (gst_structure_has_field(structure, "framerate"))
485 gst_structure_get_fraction(structure, "framerate",
488 fps = (gdouble)fps_n / (gdouble)fps_d;
489 g_debug("video fps: %f", fps);
492 worker->media.video_width = width;
493 worker->media.video_height = height;
494 worker->media.fps = fps;
496 /* Add the info to the current metadata. */
497 gint p_width, p_height, p_fps;
503 _current_metadata_add(worker, MAFW_METADATA_KEY_RES_X, G_TYPE_INT,
505 _current_metadata_add(worker, MAFW_METADATA_KEY_RES_Y, G_TYPE_INT,
507 _current_metadata_add(worker, MAFW_METADATA_KEY_VIDEO_FRAMERATE,
511 /* Emit the metadata.*/
512 g_idle_add((GSourceFunc)_emit_video_info, worker);
517 static void _parse_stream_info_item(MafwGstRendererWorker *worker, GObject *obj)
523 g_object_get(obj, "type", &type, NULL);
524 pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), "type");
525 val = g_enum_get_value(G_PARAM_SPEC_ENUM(pspec)->enum_class, type);
528 if (!g_ascii_strcasecmp(val->value_nick, "video") ||
529 !g_ascii_strcasecmp(val->value_name, "video"))
535 g_object_get(obj, "object", &object, NULL);
538 vcaps = gst_pad_get_caps(GST_PAD_CAST(object));
540 g_object_get(obj, "caps", &vcaps, NULL);
544 if (gst_caps_is_fixed(vcaps))
548 gst_caps_get_structure(vcaps, 0));
550 gst_caps_unref(vcaps);
555 /* It always returns FALSE, because it is used as an idle callback as well. */
556 static gboolean _parse_stream_info(MafwGstRendererWorker *worker)
558 GList *stream_info, *s;
561 if (g_object_class_find_property(G_OBJECT_GET_CLASS(worker->pipeline),
564 g_object_get(worker->pipeline,
565 "stream-info", &stream_info, NULL);
567 for (s = stream_info; s; s = g_list_next(s))
568 _parse_stream_info_item(worker, G_OBJECT(s->data));
572 static void mafw_gst_renderer_worker_apply_xid(MafwGstRendererWorker *worker)
574 /* Set sink to render on the provided XID if we have do have
575 a XID a valid video sink and we are rendeing video content */
578 worker->media.has_visual_content)
580 g_debug ("Setting overlay, window id: %x",
582 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(worker->vsink),
584 /* Ask the gst to redraw the frame if we are paused */
585 /* TODO: in MTG this works only in non-fs -> fs way. */
586 if (worker->state == GST_STATE_PAUSED)
588 gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink));
591 g_debug("Not setting overlay for window id: %x",
597 * GstBus synchronous message handler. NOTE that this handler is NOT invoked
598 * from the glib thread, so be careful what you do here.
600 static GstBusSyncReply _sync_bus_handler(GstBus *bus, GstMessage *msg,
601 MafwGstRendererWorker *worker)
603 if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
604 gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
606 g_debug("got prepare-xwindow-id");
607 worker->media.has_visual_content = TRUE;
608 /* The user has to preset the XID, we don't create windows by
611 /* We must post an error message to the bus that will
612 * be picked up by _async_bus_handler. Calling the
613 * notification function directly from here (different
614 * thread) is not healthy. */
615 g_warning("No video window set!");
619 MAFW_RENDERER_ERROR_PLAYBACK,
620 "No video window XID set"));
621 gst_message_unref (msg);
624 g_debug ("Video window to use is: %x",
628 /* Instruct vsink to use the client-provided window */
629 mafw_gst_renderer_worker_apply_xid(worker);
631 /* Handle colorkey and autopaint */
632 mafw_gst_renderer_worker_set_autopaint(
635 if (worker->colorkey == -1)
636 g_object_get(worker->vsink,
637 "colorkey", &worker->colorkey, NULL);
639 mafw_gst_renderer_worker_set_colorkey(
642 /* Defer the signal emission to the thread running the
644 if (worker->colorkey != -1) {
645 gst_bus_post(worker->bus,
646 gst_message_new_application(
647 GST_OBJECT(worker->vsink),
648 gst_structure_empty_new("ckey")));
650 gst_message_unref (msg);
653 /* do not unref message when returning PASS */
657 static void _free_taglist_item(GstMessage *msg, gpointer data)
659 gst_message_unref(msg);
662 static void _free_taglist(MafwGstRendererWorker *worker)
664 if (worker->tag_list != NULL)
666 g_ptr_array_foreach(worker->tag_list, (GFunc)_free_taglist_item,
668 g_ptr_array_free(worker->tag_list, TRUE);
669 worker->tag_list = NULL;
673 static gboolean _seconds_duration_equal(gint64 duration1, gint64 duration2)
675 gint64 duration1_seconds, duration2_seconds;
677 duration1_seconds = NSECONDS_TO_SECONDS(duration1);
678 duration2_seconds = NSECONDS_TO_SECONDS(duration2);
680 return duration1_seconds == duration2_seconds;
683 static void _check_duration(MafwGstRendererWorker *worker, gint64 value)
685 MafwGstRenderer *renderer = worker->owner;
686 gboolean right_query = TRUE;
689 GstFormat format = GST_FORMAT_TIME;
691 gst_element_query_duration(worker->pipeline, &format,
695 if (right_query && value > 0) {
696 gint duration_seconds = NSECONDS_TO_SECONDS(value);
698 if (!_seconds_duration_equal(worker->media.length_nanos,
700 /* Add the duration to the current metadata. */
701 _current_metadata_add(worker, MAFW_METADATA_KEY_DURATION,
703 (gint64)duration_seconds);
704 /* Emit the duration. */
705 mafw_renderer_emit_metadata_int64(
706 worker->owner, MAFW_METADATA_KEY_DURATION,
707 (gint64)duration_seconds);
710 /* We compare this duration we just got with the
711 * source one and update it in the source if needed */
712 if (duration_seconds > 0 &&
713 duration_seconds != renderer->media->duration) {
714 mafw_gst_renderer_update_source_duration(
720 worker->media.length_nanos = value;
721 g_debug("media duration: %lld", worker->media.length_nanos);
724 static void _check_seekability(MafwGstRendererWorker *worker)
726 MafwGstRenderer *renderer = worker->owner;
727 SeekabilityType seekable = SEEKABILITY_NO_SEEKABLE;
729 if (worker->media.length_nanos != -1)
731 g_debug("source seekability %d", renderer->media->seekability);
733 if (renderer->media->seekability != SEEKABILITY_NO_SEEKABLE) {
734 g_debug("Quering GStreamer for seekability");
735 GstQuery *seek_query;
736 GstFormat format = GST_FORMAT_TIME;
737 /* Query the seekability of the stream */
738 seek_query = gst_query_new_seeking(format);
739 if (gst_element_query(worker->pipeline, seek_query)) {
740 gboolean renderer_seekable = FALSE;
741 gst_query_parse_seeking(seek_query, NULL,
744 g_debug("GStreamer seekability %d",
746 seekable = renderer_seekable ?
747 SEEKABILITY_SEEKABLE :
748 SEEKABILITY_NO_SEEKABLE;
750 gst_query_unref(seek_query);
754 if (worker->media.seekable != seekable) {
755 gboolean is_seekable = (seekable == SEEKABILITY_SEEKABLE);
757 /* Add the seekability to the current metadata. */
758 _current_metadata_add(worker, MAFW_METADATA_KEY_IS_SEEKABLE,
759 G_TYPE_BOOLEAN, is_seekable);
762 mafw_renderer_emit_metadata_boolean(
763 worker->owner, MAFW_METADATA_KEY_IS_SEEKABLE,
767 g_debug("media seekable: %d", seekable);
768 worker->media.seekable = seekable;
771 static gboolean _query_duration_and_seekability_timeout(gpointer data)
773 MafwGstRendererWorker *worker = data;
775 _check_duration(worker, -1);
776 _check_seekability(worker);
778 worker->duration_seek_timeout = 0;
784 * Called when the pipeline transitions into PAUSED state. It extracts more
785 * information from Gst.
787 static void _finalize_startup(MafwGstRendererWorker *worker)
789 /* Check video caps */
790 if (worker->media.has_visual_content) {
791 GstPad *pad = GST_BASE_SINK_PAD(worker->vsink);
792 GstCaps *caps = GST_PAD_CAPS(pad);
793 if (caps && gst_caps_is_fixed(caps)) {
794 GstStructure *structure;
795 structure = gst_caps_get_structure(caps, 0);
796 if (!_handle_video_info(worker, structure))
801 /* Something might have gone wrong at this point already. */
802 if (worker->is_error) {
803 g_debug("Error occured during preroll");
807 /* Streaminfo might reveal the media to be unsupported. Therefore we
808 * need to check the error again. */
809 _parse_stream_info(worker);
810 if (worker->is_error) {
811 g_debug("Error occured. Leaving");
815 /* Check duration and seekability */
816 if (worker->duration_seek_timeout != 0) {
817 g_source_remove(worker->duration_seek_timeout);
818 worker->duration_seek_timeout = 0;
820 _check_duration(worker, -1);
821 _check_seekability(worker);
824 static void _add_duration_seek_query_timeout(MafwGstRendererWorker *worker)
826 if (worker->duration_seek_timeout != 0) {
827 g_source_remove(worker->duration_seek_timeout);
829 worker->duration_seek_timeout = g_timeout_add_seconds(
830 MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY,
831 _query_duration_and_seekability_timeout,
835 static void _do_pause_postprocessing(MafwGstRendererWorker *worker)
837 if (worker->notify_pause_handler) {
838 worker->notify_pause_handler(worker, worker->owner);
841 #ifdef HAVE_GDKPIXBUF
842 if (worker->media.has_visual_content &&
843 worker->current_frame_on_pause) {
844 GstBuffer *buffer = NULL;
846 g_object_get(worker->pipeline, "frame", &buffer, NULL);
848 if (buffer != NULL) {
849 _emit_gst_buffer_as_graphic_file(
851 MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI);
856 _add_ready_timeout(worker);
859 static void _report_playing_state(MafwGstRendererWorker * worker)
861 if (worker->report_statechanges) {
862 switch (worker->mode) {
863 case WORKER_MODE_SINGLE_PLAY:
864 /* Notify play if we are playing in
866 if (worker->notify_play_handler)
867 worker->notify_play_handler(
871 case WORKER_MODE_PLAYLIST:
872 case WORKER_MODE_REDUNDANT:
873 /* Only notify play when the "playlist"
874 playback starts, don't notify play for each
875 individual element of the playlist. */
876 if (worker->pl.notify_play_pending) {
877 if (worker->notify_play_handler)
878 worker->notify_play_handler(
881 worker->pl.notify_play_pending = FALSE;
889 static void _handle_state_changed(GstMessage *msg, MafwGstRendererWorker *worker)
891 GstState newstate, oldstate;
892 GstStateChange statetrans;
893 MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
895 gst_message_parse_state_changed(msg, &oldstate, &newstate, NULL);
896 statetrans = GST_STATE_TRANSITION(oldstate, newstate);
897 g_debug ("State changed: %d: %d -> %d", worker->state, oldstate, newstate);
899 /* If the state is the same we do nothing, otherwise, we keep
901 if (worker->state == newstate) {
904 worker->state = newstate;
907 if (statetrans == GST_STATE_CHANGE_READY_TO_PAUSED &&
909 /* Woken up from READY, resume stream position and playback */
910 g_debug("State changed to pause after ready");
911 if (worker->seek_position > 0) {
912 _check_seekability(worker);
913 if (worker->media.seekable) {
914 g_debug("performing a seek");
915 _do_seek(worker, GST_SEEK_TYPE_SET,
916 worker->seek_position, NULL);
918 g_critical("media is not seekable (and should)");
922 /* If playing a stream wait for buffering to finish before
924 if (!worker->is_stream || worker->is_live) {
930 /* While buffering, we have to wait in PAUSED
931 until we reach 100% before doing anything */
932 if (worker->buffering) {
933 if (statetrans == GST_STATE_CHANGE_PAUSED_TO_PLAYING) {
934 /* Mmm... probably the client issued a seek on the
935 * stream and then a play/resume command right away,
936 * so the stream got into PLAYING state while
937 * buffering. When the next buffering signal arrives,
938 * the stream will be PAUSED silently and resumed when
939 * buffering is done (silently too), so let's signal
940 * the state change to PLAYING here. */
941 _report_playing_state(worker);
946 switch (statetrans) {
947 case GST_STATE_CHANGE_READY_TO_PAUSED:
948 if (worker->prerolling && worker->report_statechanges) {
949 /* PAUSED after pipeline has been
950 * constructed. We check caps, seek and
951 * duration and if staying in pause is needed,
952 * we perform operations for pausing, such as
953 * current frame on pause and signalling state
954 * change and adding the timeout to go to ready */
955 g_debug ("Prerolling done, finalizaing startup");
956 _finalize_startup(worker);
958 renderer->play_failed_count = 0;
960 if (worker->stay_paused) {
961 _do_pause_postprocessing(worker);
963 worker->prerolling = FALSE;
966 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
967 /* When pausing we do the stuff, like signalling
968 * state, current frame on pause and timeout to go to
970 if (worker->report_statechanges) {
971 _do_pause_postprocessing(worker);
974 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
975 /* if seek was called, at this point it is really ended */
976 worker->seek_position = -1;
979 /* Signal state change if needed */
980 _report_playing_state(worker);
982 /* Prevent blanking if we are playing video */
983 if (worker->media.has_visual_content) {
986 keypadlocking_prohibit();
987 /* Remove the ready timeout if we are playing [again] */
988 _remove_ready_timeout(worker);
989 /* If mode is redundant we are trying to play one of several
990 * candidates, so when we get a successful playback, we notify
991 * the real URI that we are playing */
992 if (worker->mode == WORKER_MODE_REDUNDANT) {
993 mafw_renderer_emit_metadata_string(
995 MAFW_METADATA_KEY_URI,
996 worker->media.location);
999 /* Emit metadata. We wait until we reach the playing
1000 state because this speeds up playback start time */
1001 _emit_metadatas(worker);
1002 /* Query duration and seekability. Useful for vbr
1003 * clips or streams. */
1004 _add_duration_seek_query_timeout(worker);
1006 case GST_STATE_CHANGE_PAUSED_TO_READY:
1007 /* If we went to READY, we free the taglist and
1008 * deassign the timout it */
1009 if (worker->in_ready) {
1010 g_debug("changed to GST_STATE_READY");
1011 _free_taglist(worker);
1019 static void _handle_duration(MafwGstRendererWorker *worker, GstMessage *msg)
1024 gst_message_parse_duration(msg, &fmt, &duration);
1026 if (worker->duration_seek_timeout != 0) {
1027 g_source_remove(worker->duration_seek_timeout);
1028 worker->duration_seek_timeout = 0;
1031 _check_duration(worker,
1032 duration != GST_CLOCK_TIME_NONE ? duration : -1);
1033 _check_seekability(worker);
1036 #ifdef HAVE_GDKPIXBUF
1037 static void _emit_renderer_art(MafwGstRendererWorker *worker,
1038 const GstTagList *list)
1040 GstBuffer *buffer = NULL;
1041 const GValue *value = NULL;
1043 g_return_if_fail(gst_tag_list_get_tag_size(list, GST_TAG_IMAGE) > 0);
1045 value = gst_tag_list_get_value_index(list, GST_TAG_IMAGE, 0);
1047 g_return_if_fail((value != NULL) && G_VALUE_HOLDS(value, GST_TYPE_BUFFER));
1049 buffer = g_value_peek_pointer(value);
1051 g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
1053 _emit_gst_buffer_as_graphic_file(worker, buffer,
1054 MAFW_METADATA_KEY_RENDERER_ART_URI);
1058 static GHashTable* _build_tagmap(void)
1060 GHashTable *hash_table = NULL;
1062 hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
1065 g_hash_table_insert(hash_table, g_strdup(GST_TAG_TITLE),
1066 g_strdup(MAFW_METADATA_KEY_TITLE));
1067 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ARTIST),
1068 g_strdup(MAFW_METADATA_KEY_ARTIST));
1069 g_hash_table_insert(hash_table, g_strdup(GST_TAG_AUDIO_CODEC),
1070 g_strdup(MAFW_METADATA_KEY_AUDIO_CODEC));
1071 g_hash_table_insert(hash_table, g_strdup(GST_TAG_VIDEO_CODEC),
1072 g_strdup(MAFW_METADATA_KEY_VIDEO_CODEC));
1073 g_hash_table_insert(hash_table, g_strdup(GST_TAG_BITRATE),
1074 g_strdup(MAFW_METADATA_KEY_BITRATE));
1075 g_hash_table_insert(hash_table, g_strdup(GST_TAG_LANGUAGE_CODE),
1076 g_strdup(MAFW_METADATA_KEY_ENCODING));
1077 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ALBUM),
1078 g_strdup(MAFW_METADATA_KEY_ALBUM));
1079 g_hash_table_insert(hash_table, g_strdup(GST_TAG_GENRE),
1080 g_strdup(MAFW_METADATA_KEY_GENRE));
1081 g_hash_table_insert(hash_table, g_strdup(GST_TAG_TRACK_NUMBER),
1082 g_strdup(MAFW_METADATA_KEY_TRACK));
1083 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ORGANIZATION),
1084 g_strdup(MAFW_METADATA_KEY_ORGANIZATION));
1085 #ifdef HAVE_GDKPIXBUF
1086 g_hash_table_insert(hash_table, g_strdup(GST_TAG_IMAGE),
1087 g_strdup(MAFW_METADATA_KEY_RENDERER_ART_URI));
1094 * Emits metadata-changed signals for gst tags.
1096 static void _emit_tag(const GstTagList *list, const gchar *tag,
1097 MafwGstRendererWorker *worker)
1099 /* Mapping between Gst <-> MAFW metadata tags
1100 * NOTE: This assumes that GTypes matches between GST and MAFW. */
1101 static GHashTable *tagmap = NULL;
1103 const gchar *mafwtag;
1105 GValueArray *values;
1107 if (tagmap == NULL) {
1108 tagmap = _build_tagmap();
1111 g_debug("tag: '%s' (type: %s)", tag,
1112 g_type_name(gst_tag_get_type(tag)));
1113 /* Is there a mapping for this tag? */
1114 mafwtag = g_hash_table_lookup(tagmap, tag);
1118 #ifdef HAVE_GDKPIXBUF
1119 if (strcmp (mafwtag, MAFW_METADATA_KEY_RENDERER_ART_URI) == 0) {
1120 _emit_renderer_art(worker, list);
1125 /* Build a value array of this tag. We need to make sure that strings
1126 * are UTF-8. GstTagList API says that the value is always UTF8, but it
1127 * looks like the ID3 demuxer still might sometimes produce non-UTF-8
1129 count = gst_tag_list_get_tag_size(list, tag);
1130 type = gst_tag_get_type(tag);
1131 values = g_value_array_new(count);
1132 for (i = 0; i < count; ++i) {
1133 GValue *v = (GValue *)
1134 gst_tag_list_get_value_index(list, tag, i);
1135 if (type == G_TYPE_STRING) {
1138 gst_tag_list_get_string_index(list, tag, i, &orig);
1139 if (convert_utf8(orig, &utf8)) {
1140 GValue utf8gval = {0};
1142 g_value_init(&utf8gval, G_TYPE_STRING);
1143 g_value_take_string(&utf8gval, utf8);
1144 _current_metadata_add(worker, mafwtag, G_TYPE_STRING,
1146 g_value_array_append(values, &utf8gval);
1147 g_value_unset(&utf8gval);
1150 } else if (type == G_TYPE_UINT) {
1151 GValue intgval = {0};
1154 g_value_init(&intgval, G_TYPE_INT);
1155 g_value_transform(v, &intgval);
1156 intval = g_value_get_int(&intgval);
1157 _current_metadata_add(worker, mafwtag, G_TYPE_INT,
1159 g_value_array_append(values, &intgval);
1160 g_value_unset(&intgval);
1162 _current_metadata_add(worker, mafwtag, G_TYPE_VALUE,
1164 g_value_array_append(values, v);
1168 /* Emit the metadata. */
1169 g_signal_emit_by_name(worker->owner, "metadata-changed", mafwtag,
1172 g_value_array_free(values);
1176 * Collect tag-messages, parse it later, when playing is ongoing
1178 static void _handle_tag(MafwGstRendererWorker *worker, GstMessage *msg)
1180 /* Do not emit metadata until we get to PLAYING state to speed up
1182 if (worker->tag_list == NULL)
1183 worker->tag_list = g_ptr_array_new();
1184 g_ptr_array_add(worker->tag_list, gst_message_ref(msg));
1186 /* Some tags come in playing state, so in this case we have
1187 to emit them right away (example: radio stations) */
1188 if (worker->state == GST_STATE_PLAYING) {
1189 _emit_metadatas(worker);
1194 * Parses the list of tag-messages
1196 static void _parse_tagmsg(GstMessage *msg, MafwGstRendererWorker *worker)
1198 GstTagList *new_tags;
1200 gst_message_parse_tag(msg, &new_tags);
1201 gst_tag_list_foreach(new_tags, (gpointer)_emit_tag, worker);
1202 gst_tag_list_free(new_tags);
1203 gst_message_unref(msg);
1207 * Parses the collected tag messages, and emits the metadatas
1209 static void _emit_metadatas(MafwGstRendererWorker *worker)
1211 if (worker->tag_list != NULL)
1213 g_ptr_array_foreach(worker->tag_list, (GFunc)_parse_tagmsg,
1215 g_ptr_array_free(worker->tag_list, TRUE);
1216 worker->tag_list = NULL;
1220 static void _reset_volume_and_mute_to_pipeline(MafwGstRendererWorker *worker)
1222 #ifdef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1223 g_debug("resetting volume and mute to pipeline");
1225 if (worker->pipeline != NULL) {
1227 G_OBJECT(worker->pipeline), "volume",
1228 mafw_gst_renderer_worker_volume_get(worker->wvolume),
1230 mafw_gst_renderer_worker_volume_is_muted(worker->wvolume),
1236 static void _handle_buffering(MafwGstRendererWorker *worker, GstMessage *msg)
1239 MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
1241 gst_message_parse_buffering(msg, &percent);
1242 g_debug("buffering: %d", percent);
1244 /* No state management needed for live pipelines */
1245 if (!worker->is_live) {
1246 worker->buffering = TRUE;
1247 if (percent < 100 && worker->state == GST_STATE_PLAYING) {
1248 g_debug("setting pipeline to PAUSED not to wolf the "
1250 worker->report_statechanges = FALSE;
1251 /* We can't call _pause() here, since it sets
1252 * the "report_statechanges" to TRUE. We don't
1253 * want that, application doesn't need to know
1254 * that internally the state changed to
1256 if (gst_element_set_state(worker->pipeline,
1257 GST_STATE_PAUSED) ==
1258 GST_STATE_CHANGE_ASYNC)
1260 /* XXX this blocks at most 2 seconds. */
1261 gst_element_get_state(worker->pipeline, NULL,
1267 if (percent >= 100) {
1268 /* On buffering we go to PAUSED, so here we move back to
1270 worker->buffering = FALSE;
1271 if (worker->state == GST_STATE_PAUSED) {
1272 /* If buffering more than once, do this only the
1273 first time we are done with buffering */
1274 if (worker->prerolling) {
1275 g_debug("buffering concluded during "
1277 _finalize_startup(worker);
1279 renderer->play_failed_count = 0;
1280 /* Send the paused notification */
1281 if (worker->stay_paused &&
1282 worker->notify_pause_handler) {
1283 worker->notify_pause_handler(
1287 worker->prerolling = FALSE;
1288 } else if (worker->in_ready) {
1289 /* If we had been woken up from READY
1290 and we have finish our buffering,
1291 check if we have to play or stay
1292 paused and if we have to play,
1293 signal the state change. */
1294 g_debug("buffering concluded, "
1295 "continuing playing");
1297 } else if (!worker->stay_paused) {
1298 /* This means, that we were playing but
1299 ran out of buffer, so we silently
1300 paused waited for buffering to
1301 finish and now we continue silently
1302 (silently meaning we do not expose
1304 g_debug("buffering concluded, setting "
1305 "pipeline to PLAYING again");
1306 _reset_volume_and_mute_to_pipeline(
1308 if (gst_element_set_state(
1310 GST_STATE_PLAYING) ==
1311 GST_STATE_CHANGE_ASYNC)
1313 /* XXX this blocks at most 2 seconds. */
1314 gst_element_get_state(
1315 worker->pipeline, NULL, NULL,
1319 } else if (worker->state == GST_STATE_PLAYING) {
1320 g_debug("buffering concluded, signalling "
1322 /* In this case we got a PLAY command while
1323 buffering, likely because it was issued
1324 before we got the first buffering signal.
1325 The UI should not do this, but if it does,
1326 we have to signal that we have executed
1327 the state change, since in
1328 _handle_state_changed we do not do anything
1329 if we are buffering */
1331 /* Set the pipeline to playing. This is an async
1332 handler, it could be, that the reported state
1333 is not the real-current state */
1334 if (gst_element_set_state(
1336 GST_STATE_PLAYING) ==
1337 GST_STATE_CHANGE_ASYNC)
1339 /* XXX this blocks at most 2 seconds. */
1340 gst_element_get_state(
1341 worker->pipeline, NULL, NULL,
1344 if (worker->report_statechanges &&
1345 worker->notify_play_handler) {
1346 worker->notify_play_handler(
1350 _add_duration_seek_query_timeout(worker);
1355 /* Send buffer percentage */
1356 if (worker->notify_buffer_status_handler)
1357 worker->notify_buffer_status_handler(worker, worker->owner,
1361 static void _handle_element_msg(MafwGstRendererWorker *worker, GstMessage *msg)
1363 /* Only HelixBin sends "resolution" messages. */
1364 if (gst_structure_has_name(msg->structure, "resolution") &&
1365 _handle_video_info(worker, msg->structure))
1367 worker->media.has_visual_content = TRUE;
1371 static void _reset_pl_info(MafwGstRendererWorker *worker)
1373 if (worker->pl.items) {
1374 g_slist_foreach(worker->pl.items, (GFunc) g_free, NULL);
1375 g_slist_free(worker->pl.items);
1376 worker->pl.items = NULL;
1379 worker->pl.current = 0;
1380 worker->pl.notify_play_pending = TRUE;
1383 static GError * _get_specific_missing_plugin_error(GstMessage *msg)
1385 const GstStructure *gst_struct;
1391 desc = gst_missing_plugin_message_get_description(msg);
1393 gst_struct = gst_message_get_structure(msg);
1394 type = gst_structure_get_string(gst_struct, "type");
1396 if ((type) && ((strcmp(type, MAFW_GST_MISSING_TYPE_DECODER) == 0) ||
1397 (strcmp(type, MAFW_GST_MISSING_TYPE_ENCODER) == 0))) {
1399 /* Missing codec error. */
1401 const GstCaps *caps;
1402 GstStructure *caps_struct;
1405 val = gst_structure_get_value(gst_struct, "detail");
1406 caps = gst_value_get_caps(val);
1407 caps_struct = gst_caps_get_structure(caps, 0);
1408 mime = gst_structure_get_name(caps_struct);
1410 if (g_strrstr(mime, "video")) {
1411 error = g_error_new_literal(
1412 MAFW_RENDERER_ERROR,
1413 MAFW_RENDERER_ERROR_VIDEO_CODEC_NOT_FOUND,
1415 } else if (g_strrstr(mime, "audio")) {
1416 error = g_error_new_literal(
1417 MAFW_RENDERER_ERROR,
1418 MAFW_RENDERER_ERROR_AUDIO_CODEC_NOT_FOUND,
1421 error = g_error_new_literal(
1422 MAFW_RENDERER_ERROR,
1423 MAFW_RENDERER_ERROR_CODEC_NOT_FOUND,
1427 /* Unsupported type error. */
1428 error = g_error_new(
1429 MAFW_RENDERER_ERROR,
1430 MAFW_RENDERER_ERROR_UNSUPPORTED_TYPE,
1431 "missing plugin: %s", desc);
1440 * Asynchronous message handler. It gets removed from if it returns FALSE.
1442 static gboolean _async_bus_handler(GstBus *bus, GstMessage *msg,
1443 MafwGstRendererWorker *worker)
1445 /* No need to handle message if error has already occured. */
1446 if (worker->is_error)
1449 /* Handle missing-plugin (element) messages separately, relaying more
1451 if (gst_is_missing_plugin_message(msg)) {
1452 GError *err = _get_specific_missing_plugin_error(msg);
1453 /* FIXME?: for some reason, calling the error handler directly
1454 * (_send_error) causes problems. On the other hand, turning
1455 * the error into a new GstMessage and letting the next
1456 * iteration handle it seems to work. */
1457 _post_error(worker, err);
1461 switch (GST_MESSAGE_TYPE(msg)) {
1462 case GST_MESSAGE_ERROR:
1463 if (!worker->is_error) {
1468 gst_message_parse_error(msg, &err, &debug);
1469 g_debug("gst error: domain = %d, code = %d, "
1470 "message = '%s', debug = '%s'",
1471 err->domain, err->code, err->message, debug);
1475 /* If we are in playlist/radio mode, we silently
1476 ignore the error and continue with the next
1477 item until we end the playlist. If no
1478 playable elements we raise the error and
1479 after finishing we go to normal mode */
1481 if (worker->mode == WORKER_MODE_PLAYLIST ||
1482 worker->mode == WORKER_MODE_REDUNDANT) {
1483 if (worker->pl.current <
1484 (g_slist_length(worker->pl.items) - 1)) {
1485 /* If the error is "no space left"
1486 notify, otherwise try to play the
1489 GST_RESOURCE_ERROR_NO_SPACE_LEFT) {
1490 _send_error(worker, err);
1493 _play_pl_next(worker);
1496 /* Playlist EOS. We cannot try another
1497 * URI, so we have to go back to normal
1498 * mode and signal the error (done
1500 worker->mode = WORKER_MODE_SINGLE_PLAY;
1501 _reset_pl_info(worker);
1505 if (worker->mode == WORKER_MODE_SINGLE_PLAY) {
1506 if (err->domain == GST_STREAM_ERROR &&
1507 err->code == GST_STREAM_ERROR_WRONG_TYPE)
1508 {/* Maybe it is a playlist? */
1509 GSList *plitems = _parse_playlist(worker->media.location);
1512 {/* Yes, it is a plitem */
1514 mafw_gst_renderer_worker_play(worker, NULL, plitems);
1520 _send_error(worker, err);
1524 case GST_MESSAGE_EOS:
1525 if (!worker->is_error) {
1528 if (worker->mode == WORKER_MODE_PLAYLIST) {
1529 if (worker->pl.current <
1530 (g_slist_length(worker->pl.items) - 1)) {
1531 /* If the playlist EOS is not reached
1533 _play_pl_next(worker);
1535 /* Playlist EOS, go back to normal
1537 worker->mode = WORKER_MODE_SINGLE_PLAY;
1538 _reset_pl_info(worker);
1542 if (worker->mode == WORKER_MODE_SINGLE_PLAY ||
1543 worker->mode == WORKER_MODE_REDUNDANT) {
1544 if (worker->notify_eos_handler)
1545 worker->notify_eos_handler(
1549 /* We can remove the message handlers now, we
1550 are not interested in bus messages
1553 gst_bus_set_sync_handler(worker->bus,
1557 if (worker->async_bus_id) {
1558 g_source_remove(worker->async_bus_id);
1559 worker->async_bus_id = 0;
1562 if (worker->mode == WORKER_MODE_REDUNDANT) {
1563 /* Go to normal mode */
1564 worker->mode = WORKER_MODE_SINGLE_PLAY;
1565 _reset_pl_info(worker);
1570 case GST_MESSAGE_TAG:
1571 _handle_tag(worker, msg);
1573 case GST_MESSAGE_BUFFERING:
1574 _handle_buffering(worker, msg);
1576 case GST_MESSAGE_DURATION:
1577 _handle_duration(worker, msg);
1579 case GST_MESSAGE_ELEMENT:
1580 _handle_element_msg(worker, msg);
1582 case GST_MESSAGE_STATE_CHANGED:
1583 if ((GstElement *)GST_MESSAGE_SRC(msg) == worker->pipeline)
1584 _handle_state_changed(msg, worker);
1586 case GST_MESSAGE_APPLICATION:
1587 if (gst_structure_has_name(gst_message_get_structure(msg),
1591 g_value_init(&v, G_TYPE_INT);
1592 g_value_set_int(&v, worker->colorkey);
1593 mafw_extension_emit_property_changed(
1594 MAFW_EXTENSION(worker->owner),
1595 MAFW_PROPERTY_RENDERER_COLORKEY,
1603 /* NOTE this function will possibly be called from a different thread than the
1604 * glib main thread. */
1605 static void _stream_info_cb(GstObject *pipeline, GParamSpec *unused,
1606 MafwGstRendererWorker *worker)
1608 g_debug("stream-info changed");
1609 _parse_stream_info(worker);
1612 static void _volume_cb(MafwGstRendererWorkerVolume *wvolume, gdouble volume,
1615 MafwGstRendererWorker *worker = data;
1616 GValue value = {0, };
1618 _reset_volume_and_mute_to_pipeline(worker);
1620 g_value_init(&value, G_TYPE_UINT);
1621 g_value_set_uint(&value, (guint) (volume * 100.0));
1622 mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner),
1623 MAFW_PROPERTY_RENDERER_VOLUME,
1627 #ifdef MAFW_GST_RENDERER_ENABLE_MUTE
1629 static void _mute_cb(MafwGstRendererWorkerVolume *wvolume, gboolean mute,
1632 MafwGstRendererWorker *worker = data;
1633 GValue value = {0, };
1635 _reset_volume_and_mute_to_pipeline(worker);
1637 g_value_init(&value, G_TYPE_BOOLEAN);
1638 g_value_set_boolean(&value, mute);
1639 mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner),
1640 MAFW_PROPERTY_RENDERER_MUTE,
1646 /* TODO: I think it's not enought to act on error, we need to handle
1647 * DestroyNotify on the given window ourselves, because for example helixbin
1648 * does it and silently stops the decoder thread. But it doesn't notify
1650 static int xerror(Display *dpy, XErrorEvent *xev)
1652 MafwGstRendererWorker *worker;
1654 if (Global_worker == NULL) {
1657 worker = Global_worker;
1660 /* Swallow BadWindow and stop pipeline when the error is about the
1661 * currently set xid. */
1663 xev->resourceid == worker->xid &&
1664 xev->error_code == BadWindow)
1666 g_warning("BadWindow received for current xid (%x).",
1667 (gint)xev->resourceid);
1669 /* We must post a message to the bus, because this function is
1670 * invoked from a different thread (xvimagerenderer's queue). */
1671 _post_error(worker, g_error_new_literal(
1672 MAFW_RENDERER_ERROR,
1673 MAFW_RENDERER_ERROR_PLAYBACK,
1674 "Video window gone"));
1680 * Resets the media information.
1682 static void _reset_media_info(MafwGstRendererWorker *worker)
1684 if (worker->media.location) {
1685 g_free(worker->media.location);
1686 worker->media.location = NULL;
1688 worker->media.length_nanos = -1;
1689 worker->media.has_visual_content = FALSE;
1690 worker->media.seekable = SEEKABILITY_UNKNOWN;
1691 worker->media.video_width = 0;
1692 worker->media.video_height = 0;
1693 worker->media.fps = 0.0;
1696 static void _set_volume_and_mute(MafwGstRendererWorker *worker, gdouble vol,
1699 g_return_if_fail(worker->wvolume != NULL);
1701 mafw_gst_renderer_worker_volume_set(worker->wvolume, vol, mute);
1704 static void _set_volume(MafwGstRendererWorker *worker, gdouble new_vol)
1706 g_return_if_fail(worker->wvolume != NULL);
1708 _set_volume_and_mute(
1710 mafw_gst_renderer_worker_volume_is_muted(worker->wvolume));
1713 static void _set_mute(MafwGstRendererWorker *worker, gboolean mute)
1715 g_return_if_fail(worker->wvolume != NULL);
1717 _set_volume_and_mute(
1718 worker, mafw_gst_renderer_worker_volume_get(worker->wvolume),
1723 * Start to play the media
1725 static void _start_play(MafwGstRendererWorker *worker)
1727 MafwGstRenderer *renderer = (MafwGstRenderer*) worker->owner;
1728 GstStateChangeReturn state_change_info;
1729 char *autoload_sub = NULL;
1731 g_assert(worker->pipeline);
1732 g_object_set(G_OBJECT(worker->pipeline),
1733 "uri", worker->media.location, NULL);
1735 if (worker->subtitles.enabled) {
1736 autoload_sub = uri_get_subtitle_uri(worker->media.location);
1738 g_debug("SUBURI: %s", autoload_sub);
1739 g_object_set(G_OBJECT(worker->pipeline),
1740 "suburi", autoload_sub,
1741 "subtitle-font-desc", worker->subtitles.font,
1742 "subtitle-encoding", worker->subtitles.encoding,
1745 gst_element_set_state(worker->pipeline, GST_STATE_READY);
1746 g_free(autoload_sub);
1749 g_object_set(G_OBJECT(worker->pipeline), "suburi", NULL, NULL);
1752 g_debug("URI: %s", worker->media.location);
1753 g_debug("setting pipeline to PAUSED");
1755 worker->report_statechanges = TRUE;
1756 state_change_info = gst_element_set_state(worker->pipeline,
1758 if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) {
1759 /* FIXME: for live sources we may have to handle
1760 buffering and prerolling differently */
1761 g_debug ("Source is live!");
1762 worker->is_live = TRUE;
1764 worker->prerolling = TRUE;
1766 worker->is_stream = uri_is_stream(worker->media.location);
1768 if (renderer->update_playcount_id > 0) {
1769 g_source_remove(renderer->update_playcount_id);
1770 renderer->update_playcount_id = 0;
1776 * Constructs gst pipeline
1778 * FIXME: Could the same pipeline be used for playing all media instead of
1779 * constantly deleting and reconstructing it again?
1781 static void _construct_pipeline(MafwGstRendererWorker *worker)
1783 g_debug("constructing pipeline");
1784 g_assert(worker != NULL);
1786 /* Return if we have already one */
1787 if (worker->pipeline)
1790 _free_taglist(worker);
1792 g_debug("Creating a new instance of playbin2");
1793 worker->pipeline = gst_element_factory_make("playbin2",
1795 if (worker->pipeline == NULL)
1797 /* Let's try with playbin */
1798 g_warning ("playbin2 failed, falling back to playbin");
1799 worker->pipeline = gst_element_factory_make("playbin",
1802 if (worker->pipeline) {
1803 /* Use nwqueue only for non-rtsp and non-mms(h)
1806 use_nw = worker->media.location &&
1807 !g_str_has_prefix(worker->media.location,
1809 !g_str_has_prefix(worker->media.location,
1811 !g_str_has_prefix(worker->media.location,
1814 g_debug("playbin using network queue: %d", use_nw);
1816 /* These need a modified version of playbin. */
1817 g_object_set(G_OBJECT(worker->pipeline),
1819 "no-video-transform", TRUE,
1824 if (!worker->pipeline) {
1825 g_critical("failed to create playback pipeline");
1826 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1828 MAFW_RENDERER_ERROR,
1829 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1830 "Could not create pipeline");
1831 g_assert_not_reached();
1835 worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline));
1836 gst_bus_set_sync_handler(worker->bus,
1837 (GstBusSyncHandler)_sync_bus_handler, worker);
1838 worker->async_bus_id = gst_bus_add_watch_full(worker->bus,G_PRIORITY_HIGH,
1839 (GstBusFunc)_async_bus_handler,
1842 /* Listen for changes in stream-info object to find out whether the
1843 * media contains video and throw error if application has not provided
1845 g_signal_connect(worker->pipeline, "notify::stream-info",
1846 G_CALLBACK(_stream_info_cb), worker);
1848 #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1851 /* Set audio and video sinks ourselves. We create and configure
1853 if (!worker->asink) {
1854 worker->asink = gst_element_factory_make("pulsesink", NULL);
1855 if (!worker->asink) {
1856 g_critical("Failed to create pipeline audio sink");
1857 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1859 MAFW_RENDERER_ERROR,
1860 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1861 "Could not create audio sink");
1862 g_assert_not_reached();
1864 gst_object_ref(worker->asink);
1865 g_object_set(worker->asink,
1866 "buffer-time", (gint64) MAFW_GST_BUFFER_TIME,
1867 "latency-time", (gint64) MAFW_GST_LATENCY_TIME,
1870 g_object_set(worker->pipeline, "audio-sink", worker->asink, NULL);
1873 if (!worker->vsink) {
1874 worker->vsink = gst_element_factory_make("xvimagesink", NULL);
1875 if (!worker->vsink) {
1876 g_critical("Failed to create pipeline video sink");
1877 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1879 MAFW_RENDERER_ERROR,
1880 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1881 "Could not create video sink");
1882 g_assert_not_reached();
1884 gst_object_ref(worker->vsink);
1885 g_object_set(G_OBJECT(worker->vsink),
1886 "handle-events", TRUE,
1887 "force-aspect-ratio", TRUE,
1890 g_object_set(worker->pipeline,
1891 "video-sink", worker->vsink,
1895 if (!worker->tsink) {
1896 worker->tsink = gst_element_factory_make("textoverlay", NULL);
1897 if (!worker->tsink) {
1898 g_critical("Failed to create pipeline text sink");
1899 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1901 MAFW_RENDERER_ERROR,
1902 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1903 "Could not create text sink");
1904 g_assert_not_reached();
1906 gst_object_ref(worker->tsink);
1908 g_object_set(worker->pipeline, "text-sink", worker->tsink, NULL);
1912 * @seek_type: GstSeekType
1913 * @position: Time in seconds where to seek
1915 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
1916 gint position, GError **error)
1921 g_assert(worker != NULL);
1923 if (worker->eos || !worker->media.seekable)
1926 /* According to the docs, relative seeking is not so easy:
1927 GST_SEEK_TYPE_CUR - change relative to currently configured segment.
1928 This can't be used to seek relative to the current playback position -
1929 do a position query, calculate the desired position and then do an
1930 absolute position seek instead if that's what you want to do. */
1931 if (seek_type == GST_SEEK_TYPE_CUR)
1933 gint curpos = mafw_gst_renderer_worker_get_position(worker);
1934 position = curpos + position;
1935 seek_type = GST_SEEK_TYPE_SET;
1942 worker->seek_position = position;
1943 worker->report_statechanges = FALSE;
1944 spos = (gint64)position * GST_SECOND;
1945 g_debug("seek: type = %d, offset = %lld", seek_type, spos);
1947 /* If the pipeline has been set to READY by us, then wake it up by
1948 setting it to PAUSED (when we get the READY->PAUSED transition
1949 we will execute the seek). This way when we seek we disable the
1950 READY state (logical, since the player is not idle anymore)
1951 allowing the sink to render the destination frame in case of
1953 if (worker->in_ready && worker->state == GST_STATE_READY) {
1954 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
1956 ret = gst_element_seek(worker->pipeline, 1.0, GST_FORMAT_TIME,
1957 GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_KEY_UNIT,
1959 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
1961 /* Seeking is async, so seek_position should not be
1968 err: g_set_error(error,
1969 MAFW_RENDERER_ERROR,
1970 MAFW_RENDERER_ERROR_CANNOT_SET_POSITION,
1971 "Seeking to %d failed", position);
1974 /* @vol should be between [0 .. 100], higher values (up to 1000) are allowed,
1975 * but probably cause distortion. */
1976 void mafw_gst_renderer_worker_set_volume(
1977 MafwGstRendererWorker *worker, guint volume)
1979 _set_volume(worker, CLAMP((gdouble)volume / 100.0, 0.0, 1.0));
1982 guint mafw_gst_renderer_worker_get_volume(
1983 MafwGstRendererWorker *worker)
1986 (mafw_gst_renderer_worker_volume_get(worker->wvolume) * 100);
1989 void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker,
1992 _set_mute(worker, mute);
1995 gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker)
1997 return mafw_gst_renderer_worker_volume_is_muted(worker->wvolume);
2000 #ifdef HAVE_GDKPIXBUF
2001 void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker,
2002 gboolean current_frame_on_pause)
2004 worker->current_frame_on_pause = current_frame_on_pause;
2007 gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker)
2009 return worker->current_frame_on_pause;
2013 void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker,
2014 GstSeekType seek_type,
2015 gint position, GError **error)
2017 /* If player is paused and we have a timeout for going to ready
2018 * restart it. This is logical, since the user is seeking and
2019 * thus, the player is not idle anymore. Also this prevents that
2020 * when seeking streams we enter buffering and in the middle of
2021 * the buffering process we set the pipeline to ready (which stops
2022 * the buffering before it reaches 100%, making the client think
2023 * buffering is still going on).
2025 if (worker->ready_timeout) {
2026 _remove_ready_timeout(worker);
2027 _add_ready_timeout(worker);
2030 _do_seek(worker, seek_type, position, error);
2031 if (worker->notify_seek_handler)
2032 worker->notify_seek_handler(worker, worker->owner);
2036 * Gets current position, rounded down into precision of one second. If a seek
2037 * is pending, returns the position we are going to seek. Returns -1 on
2040 gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker)
2044 g_assert(worker != NULL);
2046 /* If seek is ongoing, return the position where we are seeking. */
2047 if (worker->seek_position != -1)
2049 return worker->seek_position;
2051 /* Otherwise query position from pipeline. */
2052 format = GST_FORMAT_TIME;
2053 if (worker->pipeline &&
2054 gst_element_query_position(worker->pipeline, &format, &time))
2056 return (gint)(NSECONDS_TO_SECONDS(time));
2061 GHashTable *mafw_gst_renderer_worker_get_current_metadata(
2062 MafwGstRendererWorker *worker)
2064 return worker->current_metadata;
2067 void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid)
2069 /* Check for errors on the target window */
2070 XSetErrorHandler(xerror);
2072 /* Store the target window id */
2073 g_debug("Setting xid: %x", (guint)xid);
2076 /* Check if we should use it right away */
2077 mafw_gst_renderer_worker_apply_xid(worker);
2080 XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker)
2085 gboolean mafw_gst_renderer_worker_get_autopaint(
2086 MafwGstRendererWorker *worker)
2088 return worker->autopaint;
2090 void mafw_gst_renderer_worker_set_autopaint(
2091 MafwGstRendererWorker *worker, gboolean autopaint)
2093 worker->autopaint = autopaint;
2095 g_object_set(worker->vsink, "autopaint-colorkey",
2096 worker->autopaint, NULL);
2099 gint mafw_gst_renderer_worker_get_colorkey(
2100 MafwGstRendererWorker *worker)
2102 return worker->colorkey;
2105 void mafw_gst_renderer_worker_set_colorkey(
2106 MafwGstRendererWorker *worker, gint colorkey)
2108 worker->colorkey = colorkey;
2110 g_object_set(worker->vsink, "colorkey",
2111 worker->colorkey, NULL);
2114 gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker)
2116 return worker->media.seekable;
2119 static void _play_pl_next(MafwGstRendererWorker *worker) {
2122 g_assert(worker != NULL);
2123 g_return_if_fail(worker->pl.items != NULL);
2125 next = (gchar *) g_slist_nth_data(worker->pl.items,
2126 ++worker->pl.current);
2127 mafw_gst_renderer_worker_stop(worker);
2128 _reset_media_info(worker);
2130 worker->media.location = g_strdup(next);
2131 _construct_pipeline(worker);
2132 _start_play(worker);
2135 static void _do_play(MafwGstRendererWorker *worker)
2137 g_assert(worker != NULL);
2139 if (worker->pipeline == NULL) {
2140 g_debug("play without a pipeline!");
2143 worker->report_statechanges = TRUE;
2145 /* If we have to stay paused, we do and add the ready
2146 * timeout. Otherwise, we move the pipeline */
2147 if (!worker->stay_paused) {
2148 /* If pipeline is READY, we move it to PAUSED,
2149 * otherwise, to PLAYING */
2150 if (worker->state == GST_STATE_READY) {
2151 gst_element_set_state(worker->pipeline,
2153 g_debug("setting pipeline to PAUSED");
2155 _reset_volume_and_mute_to_pipeline(worker);
2156 gst_element_set_state(worker->pipeline,
2158 g_debug("setting pipeline to PLAYING");
2162 g_debug("staying in PAUSED state");
2163 _add_ready_timeout(worker);
2167 void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker,
2168 const gchar *uri, GSList *plitems)
2170 g_assert(uri || plitems);
2172 mafw_gst_renderer_worker_stop(worker);
2173 _reset_media_info(worker);
2174 _reset_pl_info(worker);
2175 /* Check if the item to play is a single item or a playlist. */
2176 if (plitems || uri_is_playlist(uri)){
2178 /* In case of a playlist we parse it and start playing the first
2179 item of the playlist. */
2182 worker->pl.items = plitems;
2186 worker->pl.items = _parse_playlist(uri);
2188 if (!worker->pl.items)
2191 g_error_new(MAFW_RENDERER_ERROR,
2192 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2193 "Playlist parsing failed: %s",
2198 /* Set the playback mode */
2199 worker->mode = WORKER_MODE_PLAYLIST;
2200 worker->pl.notify_play_pending = TRUE;
2202 /* Set the item to be played */
2203 worker->pl.current = 0;
2204 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2205 worker->media.location = g_strdup(item);
2207 /* Single item. Set the playback mode according to that */
2208 worker->mode = WORKER_MODE_SINGLE_PLAY;
2210 /* Set the item to be played */
2211 worker->media.location = g_strdup(uri);
2213 _construct_pipeline(worker);
2214 _start_play(worker);
2217 void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker,
2223 g_assert(uris && uris[0]);
2225 mafw_gst_renderer_worker_stop(worker);
2226 _reset_media_info(worker);
2227 _reset_pl_info(worker);
2229 /* Add the uris to playlist */
2233 g_slist_append(worker->pl.items, g_strdup(uris[i]));
2237 /* Set the playback mode */
2238 worker->mode = WORKER_MODE_REDUNDANT;
2239 worker->pl.notify_play_pending = TRUE;
2241 /* Set the item to be played */
2242 worker->pl.current = 0;
2243 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2244 worker->media.location = g_strdup(item);
2247 _construct_pipeline(worker);
2248 _start_play(worker);
2252 * Currently, stop destroys the Gst pipeline and resets the worker into
2253 * default startup configuration.
2255 void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker)
2257 g_debug("worker stop");
2258 g_assert(worker != NULL);
2260 /* If location is NULL, this is a pre-created pipeline */
2261 if (worker->async_bus_id && worker->pipeline && !worker->media.location)
2264 if (worker->pipeline) {
2265 g_debug("destroying pipeline");
2266 if (worker->async_bus_id) {
2267 g_source_remove(worker->async_bus_id);
2268 worker->async_bus_id = 0;
2270 gst_bus_set_sync_handler(worker->bus, NULL, NULL);
2271 gst_element_set_state(worker->pipeline, GST_STATE_NULL);
2273 gst_object_unref(GST_OBJECT_CAST(worker->bus));
2276 gst_object_unref(GST_OBJECT(worker->pipeline));
2277 worker->pipeline = NULL;
2281 worker->report_statechanges = TRUE;
2282 worker->state = GST_STATE_NULL;
2283 worker->prerolling = FALSE;
2284 worker->is_live = FALSE;
2285 worker->buffering = FALSE;
2286 worker->is_stream = FALSE;
2287 worker->is_error = FALSE;
2288 worker->eos = FALSE;
2289 worker->seek_position = -1;
2290 _remove_ready_timeout(worker);
2291 _free_taglist(worker);
2292 if (worker->current_metadata) {
2293 g_hash_table_destroy(worker->current_metadata);
2294 worker->current_metadata = NULL;
2297 if (worker->duration_seek_timeout != 0) {
2298 g_source_remove(worker->duration_seek_timeout);
2299 worker->duration_seek_timeout = 0;
2302 /* Reset media iformation */
2303 _reset_media_info(worker);
2305 /* We are not playing, so we can let the screen blank */
2307 keypadlocking_allow();
2309 /* And now get a fresh pipeline ready */
2310 _construct_pipeline(worker);
2313 void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker)
2315 g_assert(worker != NULL);
2317 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2318 !worker->prerolling) {
2319 /* If we are buffering and get a pause, we have to
2320 * signal state change and stay_paused */
2321 g_debug("Pausing while buffering, signalling state change");
2322 worker->stay_paused = TRUE;
2323 if (worker->notify_pause_handler) {
2324 worker->notify_pause_handler(
2329 worker->report_statechanges = TRUE;
2331 if (gst_element_set_state(worker->pipeline, GST_STATE_PAUSED) ==
2332 GST_STATE_CHANGE_ASYNC)
2334 /* XXX this blocks at most 2 seconds. */
2335 gst_element_get_state(worker->pipeline, NULL, NULL,
2339 keypadlocking_allow();
2343 void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker)
2345 if (worker->mode == WORKER_MODE_PLAYLIST ||
2346 worker->mode == WORKER_MODE_REDUNDANT) {
2347 /* We must notify play if the "playlist" playback
2349 worker->pl.notify_play_pending = TRUE;
2351 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2352 !worker->prerolling) {
2353 /* If we are buffering we cannot resume, but we know
2354 * that the pipeline will be moved to PLAYING as
2355 * stay_paused is FALSE, so we just activate the state
2356 * change report, this way as soon as buffering is finished
2357 * the pipeline will be set to PLAYING and the state
2358 * change will be reported */
2359 worker->report_statechanges = TRUE;
2360 g_debug("Resumed while buffering, activating pipeline state "
2362 /* Notice though that we can receive the Resume before
2363 we get any buffering information. In that case
2364 we go with the "else" branch and set the pipeline to
2365 to PLAYING. However, it is possible that in this case
2366 we get the fist buffering signal before the
2367 PAUSED -> PLAYING state change. In that case, since we
2368 ignore state changes while buffering we never signal
2369 the state change to PLAYING. We can only fix this by
2370 checking, when we receive a PAUSED -> PLAYING transition
2371 if we are buffering, and in that case signal the state
2372 change (if we get that transition while buffering
2373 is on, it can only mean that the client resumed playback
2374 while buffering, and we must notify the state change) */
2380 static void _volume_init_cb(MafwGstRendererWorkerVolume *wvolume,
2383 MafwGstRendererWorker *worker = data;
2387 worker->wvolume = wvolume;
2389 g_debug("volume manager initialized");
2391 volume = mafw_gst_renderer_worker_volume_get(wvolume);
2392 mute = mafw_gst_renderer_worker_volume_is_muted(wvolume);
2393 _volume_cb(wvolume, volume, worker);
2394 #ifdef MAFW_GST_RENDERER_ENABLE_MUTE
2395 _mute_cb(wvolume, mute, worker);
2399 MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner)
2401 MafwGstRendererWorker *worker;
2402 GMainContext *main_context;
2404 worker = g_new0(MafwGstRendererWorker, 1);
2405 worker->mode = WORKER_MODE_SINGLE_PLAY;
2406 worker->pl.items = NULL;
2407 worker->pl.current = 0;
2408 worker->pl.notify_play_pending = TRUE;
2409 worker->owner = owner;
2410 worker->report_statechanges = TRUE;
2411 worker->state = GST_STATE_NULL;
2412 worker->seek_position = -1;
2413 worker->ready_timeout = 0;
2414 worker->in_ready = FALSE;
2416 worker->autopaint = TRUE;
2417 worker->colorkey = -1;
2418 worker->vsink = NULL;
2419 worker->asink = NULL;
2420 worker->tsink = NULL;
2421 worker->tag_list = NULL;
2422 worker->current_metadata = NULL;
2423 worker->subtitles.enabled = FALSE;
2424 worker->subtitles.font = NULL;
2425 worker->subtitles.encoding = NULL;
2427 #ifdef HAVE_GDKPIXBUF
2428 worker->current_frame_on_pause = FALSE;
2429 _init_tmp_files_pool(worker);
2431 worker->notify_seek_handler = NULL;
2432 worker->notify_pause_handler = NULL;
2433 worker->notify_play_handler = NULL;
2434 worker->notify_buffer_status_handler = NULL;
2435 worker->notify_eos_handler = NULL;
2436 worker->notify_error_handler = NULL;
2437 Global_worker = worker;
2438 main_context = g_main_context_default();
2439 worker->wvolume = NULL;
2440 mafw_gst_renderer_worker_volume_init(main_context,
2441 _volume_init_cb, worker,
2443 #ifdef MAFW_GST_RENDERER_ENABLE_MUTE
2450 _construct_pipeline(worker);
2455 void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker)
2458 #ifdef HAVE_GDKPIXBUF
2459 _destroy_tmp_files_pool(worker);
2461 mafw_gst_renderer_worker_volume_destroy(worker->wvolume);
2462 mafw_gst_renderer_worker_stop(worker);
2464 /* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */