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"
50 #define G_LOG_DOMAIN "mafw-gst-renderer-worker"
52 #define MAFW_GST_RENDERER_WORKER_SECONDS_READY 60
53 #define MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY 4
55 #define MAFW_GST_MISSING_TYPE_DECODER "decoder"
56 #define MAFW_GST_MISSING_TYPE_ENCODER "encoder"
58 #define MAFW_GST_BUFFER_TIME 600000L
59 #define MAFW_GST_LATENCY_TIME (MAFW_GST_BUFFER_TIME / 2)
61 #define NSECONDS_TO_SECONDS(ns) ((ns)%1000000000 < 500000000?\
62 GST_TIME_AS_SECONDS((ns)):\
63 GST_TIME_AS_SECONDS((ns))+1)
65 /* Private variables. */
66 /* Global reference to worker instance, needed for Xerror handler */
67 static MafwGstRendererWorker *Global_worker = NULL;
69 /* Forward declarations. */
70 static void _do_play(MafwGstRendererWorker *worker);
71 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
72 gint position, GError **error);
73 static void _play_pl_next(MafwGstRendererWorker *worker);
75 static void _emit_metadatas(MafwGstRendererWorker *worker);
77 static void _current_metadata_add(MafwGstRendererWorker *worker,
78 const gchar *key, GType type,
79 const gpointer value);
82 * Sends @error to MafwGstRenderer. Only call this from the glib main thread, or
83 * face the consequences. @err is free'd.
85 static void _send_error(MafwGstRendererWorker *worker, GError *err)
87 worker->is_error = TRUE;
88 if (worker->notify_error_handler)
89 worker->notify_error_handler(worker, worker->owner, err);
94 * Posts an @error on the gst bus. _async_bus_handler will then pick it up and
95 * forward to MafwGstRenderer. @err is free'd.
97 static void _post_error(MafwGstRendererWorker *worker, GError *err)
99 gst_bus_post(worker->bus,
100 gst_message_new_error(GST_OBJECT(worker->pipeline),
105 #ifdef HAVE_GDKPIXBUF
107 MafwGstRendererWorker *worker;
112 static gchar *_init_tmp_file(void)
117 fd = g_file_open_tmp("mafw-gst-renderer-XXXXXX.jpeg", &path, NULL);
123 static void _init_tmp_files_pool(MafwGstRendererWorker *worker)
127 worker->tmp_files_pool_index = 0;
129 for (i = 0; i < MAFW_GST_RENDERER_MAX_TMP_FILES; i++) {
130 worker->tmp_files_pool[i] = NULL;
134 static void _destroy_tmp_files_pool(MafwGstRendererWorker *worker)
138 for (i = 0; (i < MAFW_GST_RENDERER_MAX_TMP_FILES) &&
139 (worker->tmp_files_pool[i] != NULL); i++) {
140 g_unlink(worker->tmp_files_pool[i]);
141 g_free(worker->tmp_files_pool[i]);
145 static const gchar *_get_tmp_file_from_pool(
146 MafwGstRendererWorker *worker)
148 gchar *path = worker->tmp_files_pool[worker->tmp_files_pool_index];
151 path = _init_tmp_file();
152 worker->tmp_files_pool[worker->tmp_files_pool_index] = path;
155 if (++(worker->tmp_files_pool_index) >=
156 MAFW_GST_RENDERER_MAX_TMP_FILES) {
157 worker->tmp_files_pool_index = 0;
163 static void _destroy_pixbuf (guchar *pixbuf, gpointer data)
165 gst_buffer_unref(GST_BUFFER(data));
168 static void _emit_gst_buffer_as_graphic_file_cb(GstBuffer *new_buffer,
171 SaveGraphicData *sgd = user_data;
172 GdkPixbuf *pixbuf = NULL;
174 if (new_buffer != NULL) {
176 GstStructure *structure;
179 gst_caps_get_structure(GST_BUFFER_CAPS(new_buffer), 0);
181 gst_structure_get_int(structure, "width", &width);
182 gst_structure_get_int(structure, "height", &height);
184 pixbuf = gdk_pixbuf_new_from_data(
185 GST_BUFFER_DATA(new_buffer), GDK_COLORSPACE_RGB,
186 FALSE, 8, width, height,
187 GST_ROUND_UP_4(3 * width), _destroy_pixbuf,
190 if (sgd->pixbuf != NULL) {
191 g_object_unref(sgd->pixbuf);
195 pixbuf = sgd->pixbuf;
198 if (pixbuf != NULL) {
200 GError *error = NULL;
201 const gchar *filename;
203 filename = _get_tmp_file_from_pool(sgd->worker);
205 save_ok = gdk_pixbuf_save (pixbuf, filename, "jpeg", &error,
208 g_object_unref (pixbuf);
211 /* Add the info to the current metadata. */
212 _current_metadata_add(sgd->worker, sgd->metadata_key,
214 (const gpointer) filename);
216 /* Emit the metadata. */
217 mafw_renderer_emit_metadata_string(sgd->worker->owner,
222 g_warning ("%s\n", error->message);
223 g_error_free (error);
225 g_critical("Unknown error when saving pixbuf "
226 "with GStreamer data");
230 g_warning("Could not create pixbuf from GstBuffer");
233 g_free(sgd->metadata_key);
237 static void _pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
238 gint width, gint height,
241 /* Be sure the image size is reasonable */
242 if (width > 512 || height > 512) {
243 g_debug ("pixbuf: image is too big: %dx%d", width, height);
245 ar = (gdouble) width / height;
246 if (width > height) {
253 g_debug ("pixbuf: scaled image to %dx%d", width, height);
254 gdk_pixbuf_loader_set_size (loader, width, height);
258 static void _emit_gst_buffer_as_graphic_file(MafwGstRendererWorker *worker,
260 const gchar *metadata_key)
262 GdkPixbufLoader *loader;
263 GstStructure *structure;
264 const gchar *mime = NULL;
265 GError *error = NULL;
267 g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
269 structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0);
270 mime = gst_structure_get_name(structure);
272 if (g_str_has_prefix(mime, "video/x-raw")) {
273 gint framerate_d, framerate_n;
275 SaveGraphicData *sgd;
277 gst_structure_get_fraction (structure, "framerate",
278 &framerate_n, &framerate_d);
280 to_caps = gst_caps_new_simple ("video/x-raw-rgb",
281 "bpp", G_TYPE_INT, 24,
282 "depth", G_TYPE_INT, 24,
283 "framerate", GST_TYPE_FRACTION,
284 framerate_n, framerate_d,
285 "pixel-aspect-ratio",
286 GST_TYPE_FRACTION, 1, 1,
288 G_TYPE_INT, G_BIG_ENDIAN,
289 "red_mask", G_TYPE_INT,
292 G_TYPE_INT, 0x00ff00,
294 G_TYPE_INT, 0x0000ff,
297 sgd = g_new0(SaveGraphicData, 1);
298 sgd->worker = worker;
299 sgd->metadata_key = g_strdup(metadata_key);
301 g_debug("pixbuf: using bvw to convert image format");
302 bvw_frame_conv_convert (buffer, to_caps,
303 _emit_gst_buffer_as_graphic_file_cb,
306 GdkPixbuf *pixbuf = NULL;
307 loader = gdk_pixbuf_loader_new_with_mime_type(mime, &error);
308 g_signal_connect (G_OBJECT (loader), "size-prepared",
309 (GCallback)_pixbuf_size_prepared_cb, NULL);
311 if (loader == NULL) {
312 g_warning ("%s\n", error->message);
313 g_error_free (error);
315 if (!gdk_pixbuf_loader_write (loader,
316 GST_BUFFER_DATA(buffer),
317 GST_BUFFER_SIZE(buffer),
319 g_warning ("%s\n", error->message);
320 g_error_free (error);
322 gdk_pixbuf_loader_close (loader, NULL);
324 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
326 if (!gdk_pixbuf_loader_close (loader, &error)) {
327 g_warning ("%s\n", error->message);
328 g_error_free (error);
330 g_object_unref(pixbuf);
332 SaveGraphicData *sgd;
334 sgd = g_new0(SaveGraphicData, 1);
336 sgd->worker = worker;
338 g_strdup(metadata_key);
339 sgd->pixbuf = pixbuf;
341 _emit_gst_buffer_as_graphic_file_cb(
350 static gboolean _go_to_gst_ready(gpointer user_data)
352 MafwGstRendererWorker *worker = user_data;
354 g_return_val_if_fail(worker->state == GST_STATE_PAUSED ||
355 worker->prerolling, FALSE);
357 worker->seek_position =
358 mafw_gst_renderer_worker_get_position(worker);
360 g_debug("going to GST_STATE_READY");
361 gst_element_set_state(worker->pipeline, GST_STATE_READY);
362 worker->in_ready = TRUE;
363 worker->ready_timeout = 0;
368 static void _add_ready_timeout(MafwGstRendererWorker *worker)
370 if (worker->media.seekable) {
371 if (!worker->ready_timeout)
373 g_debug("Adding timeout to go to GST_STATE_READY");
374 worker->ready_timeout =
375 g_timeout_add_seconds(
376 MAFW_GST_RENDERER_WORKER_SECONDS_READY,
381 g_debug("Not adding timeout to go to GST_STATE_READY as media "
383 worker->ready_timeout = 0;
387 static void _remove_ready_timeout(MafwGstRendererWorker *worker)
389 g_debug("removing timeout for READY");
390 if (worker->ready_timeout != 0) {
391 g_source_remove(worker->ready_timeout);
392 worker->ready_timeout = 0;
394 worker->in_ready = FALSE;
397 static gboolean _emit_video_info(MafwGstRendererWorker *worker)
399 mafw_renderer_emit_metadata_int(worker->owner,
400 MAFW_METADATA_KEY_RES_X,
401 worker->media.video_width);
402 mafw_renderer_emit_metadata_int(worker->owner,
403 MAFW_METADATA_KEY_RES_Y,
404 worker->media.video_height);
405 mafw_renderer_emit_metadata_double(worker->owner,
406 MAFW_METADATA_KEY_VIDEO_FRAMERATE,
412 * Checks if the video details are supported. It also extracts other useful
413 * information (such as PAR and framerate) from the caps, if available. NOTE:
414 * this will be called from a different thread than glib's mainloop (when
415 * invoked via _stream_info_cb); don't call MafwGstRenderer directly.
417 * Returns: TRUE if video details are acceptable.
419 static gboolean _handle_video_info(MafwGstRendererWorker *worker,
420 const GstStructure *structure)
426 gst_structure_get_int(structure, "width", &width);
427 gst_structure_get_int(structure, "height", &height);
428 g_debug("video size: %d x %d", width, height);
429 if (gst_structure_has_field(structure, "pixel-aspect-ratio"))
431 gst_structure_get_fraction(structure, "pixel-aspect-ratio",
432 &worker->media.par_n,
433 &worker->media.par_d);
434 g_debug("video PAR: %d:%d", worker->media.par_n,
435 worker->media.par_d);
436 width = width * worker->media.par_n / worker->media.par_d;
440 if (gst_structure_has_field(structure, "framerate"))
444 gst_structure_get_fraction(structure, "framerate",
447 fps = (gdouble)fps_n / (gdouble)fps_d;
448 g_debug("video fps: %f", fps);
451 worker->media.video_width = width;
452 worker->media.video_height = height;
453 worker->media.fps = fps;
455 /* Add the info to the current metadata. */
456 gint *p_width = g_new0(gint, 1);
457 gint *p_height = g_new0(gint, 1);
458 gdouble *p_fps = g_new0(gdouble, 1);
460 *p_width = width;* p_height = height; *p_fps = fps;
462 _current_metadata_add(worker, MAFW_METADATA_KEY_RES_X, G_TYPE_INT,
463 (const gpointer) p_width);
464 _current_metadata_add(worker, MAFW_METADATA_KEY_RES_Y, G_TYPE_INT,
465 (const gpointer) p_height);
466 _current_metadata_add(worker, MAFW_METADATA_KEY_VIDEO_FRAMERATE,
468 (const gpointer) p_fps);
470 g_free(p_width); g_free(p_height); g_free(p_fps);
472 /* Emit the metadata.*/
473 g_idle_add((GSourceFunc)_emit_video_info, worker);
478 static void _parse_stream_info_item(MafwGstRendererWorker *worker, GObject *obj)
484 g_object_get(obj, "type", &type, NULL);
485 pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), "type");
486 val = g_enum_get_value(G_PARAM_SPEC_ENUM(pspec)->enum_class, type);
489 if (!g_ascii_strcasecmp(val->value_nick, "video") ||
490 !g_ascii_strcasecmp(val->value_name, "video"))
496 g_object_get(obj, "object", &object, NULL);
499 vcaps = gst_pad_get_caps(GST_PAD_CAST(object));
501 g_object_get(obj, "caps", &vcaps, NULL);
505 if (gst_caps_is_fixed(vcaps))
509 gst_caps_get_structure(vcaps, 0));
511 gst_caps_unref(vcaps);
516 /* It always returns FALSE, because it is used as an idle callback as well. */
517 static gboolean _parse_stream_info(MafwGstRendererWorker *worker)
519 GList *stream_info, *s;
522 if (g_object_class_find_property(G_OBJECT_GET_CLASS(worker->pipeline),
525 g_object_get(worker->pipeline,
526 "stream-info", &stream_info, NULL);
528 for (s = stream_info; s; s = g_list_next(s))
529 _parse_stream_info_item(worker, G_OBJECT(s->data));
533 static void mafw_gst_renderer_worker_apply_xid(MafwGstRendererWorker *worker)
535 /* Set sink to render on the provided XID if we have do have
536 a XID a valid video sink and we are rendeing video content */
539 worker->media.has_visual_content)
541 g_debug ("Setting overlay, window id: %x",
543 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(worker->vsink),
545 /* Ask the gst to redraw the frame if we are paused */
546 /* TODO: in MTG this works only in non-fs -> fs way. */
547 if (worker->state == GST_STATE_PAUSED)
549 gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink));
552 g_debug("Not setting overlay for window id: %x",
558 * GstBus synchronous message handler. NOTE that this handler is NOT invoked
559 * from the glib thread, so be careful what you do here.
561 static GstBusSyncReply _sync_bus_handler(GstBus *bus, GstMessage *msg,
562 MafwGstRendererWorker *worker)
564 if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
565 gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
567 g_debug("got prepare-xwindow-id");
568 worker->media.has_visual_content = TRUE;
569 /* The user has to preset the XID, we don't create windows by
572 /* We must post an error message to the bus that will
573 * be picked up by _async_bus_handler. Calling the
574 * notification function directly from here (different
575 * thread) is not healthy. */
576 g_warning("No video window set!");
580 MAFW_RENDERER_ERROR_PLAYBACK,
581 "No video window XID set"));
584 g_debug ("Video window to use is: %x",
588 /* Instruct vsink to use the client-provided window */
589 mafw_gst_renderer_worker_apply_xid(worker);
591 /* Handle colorkey and autopaint */
592 mafw_gst_renderer_worker_set_autopaint(
595 g_object_get(worker->vsink,
596 "colorkey", &worker->colorkey, NULL);
597 /* Defer the signal emission to the thread running the
599 if (worker->colorkey != -1) {
600 gst_bus_post(worker->bus,
601 gst_message_new_application(
602 GST_OBJECT(worker->vsink),
603 gst_structure_empty_new("ckey")));
610 static void _free_taglist_item(GstMessage *msg, gpointer data)
612 gst_message_unref(msg);
615 static void _free_taglist(MafwGstRendererWorker *worker)
617 if (worker->tag_list != NULL)
619 g_ptr_array_foreach(worker->tag_list, (GFunc)_free_taglist_item,
621 g_ptr_array_free(worker->tag_list, TRUE);
622 worker->tag_list = NULL;
626 static gboolean _seconds_duration_equal(gint64 duration1, gint64 duration2)
628 gint64 duration1_seconds, duration2_seconds;
630 duration1_seconds = NSECONDS_TO_SECONDS(duration1);
631 duration2_seconds = NSECONDS_TO_SECONDS(duration2);
633 return duration1_seconds == duration2_seconds;
636 static void _check_duration(MafwGstRendererWorker *worker, gint64 value)
638 MafwGstRenderer *renderer = worker->owner;
639 gboolean right_query = TRUE;
642 GstFormat format = GST_FORMAT_TIME;
644 gst_element_query_duration(worker->pipeline, &format,
648 if (right_query && value > 0) {
649 gint duration_seconds = NSECONDS_TO_SECONDS(value);
651 if (!_seconds_duration_equal(worker->media.length_nanos,
653 gint64 *duration = g_new0(gint64, 1);
654 *duration = duration_seconds;
656 /* Add the duration to the current metadata. */
657 _current_metadata_add(worker,
658 MAFW_METADATA_KEY_DURATION,
660 (const gpointer) duration);
662 /* Emit the duration. */
663 mafw_renderer_emit_metadata_int64(
664 worker->owner, MAFW_METADATA_KEY_DURATION,
669 /* We compare this duration we just got with the
670 * source one and update it in the source if needed */
671 if (duration_seconds != renderer->media->duration) {
672 mafw_gst_renderer_update_source_duration(
678 worker->media.length_nanos = value;
679 g_debug("media duration: %lld", worker->media.length_nanos);
682 static void _check_seekability(MafwGstRendererWorker *worker)
684 MafwGstRenderer *renderer = worker->owner;
685 SeekabilityType seekable = SEEKABILITY_NO_SEEKABLE;
687 if (worker->media.length_nanos != -1)
689 g_debug("source seekability %d", renderer->media->seekability);
691 if (renderer->media->seekability != SEEKABILITY_NO_SEEKABLE) {
692 g_debug("Quering GStreamer for seekability");
693 GstQuery *seek_query;
694 GstFormat format = GST_FORMAT_TIME;
695 /* Query the seekability of the stream */
696 seek_query = gst_query_new_seeking(format);
697 if (gst_element_query(worker->pipeline, seek_query)) {
698 gboolean renderer_seekable = FALSE;
699 gst_query_parse_seeking(seek_query, NULL,
702 g_debug("GStreamer seekability %d",
704 seekable = renderer_seekable ?
705 SEEKABILITY_SEEKABLE :
706 SEEKABILITY_NO_SEEKABLE;
708 gst_query_unref(seek_query);
712 if (worker->media.seekable != seekable) {
713 gboolean *is_seekable = g_new0(gboolean, 1);
714 *is_seekable = (seekable == SEEKABILITY_SEEKABLE) ? TRUE : FALSE;
716 /* Add the seekability to the current metadata. */
717 _current_metadata_add(worker, MAFW_METADATA_KEY_IS_SEEKABLE,
718 G_TYPE_BOOLEAN, (const gpointer) is_seekable);
721 mafw_renderer_emit_metadata_boolean(
722 worker->owner, MAFW_METADATA_KEY_IS_SEEKABLE,
728 g_debug("media seekable: %d", seekable);
729 worker->media.seekable = seekable;
732 static gboolean _query_duration_and_seekability_timeout(gpointer data)
734 MafwGstRendererWorker *worker = data;
736 _check_duration(worker, -1);
737 _check_seekability(worker);
739 worker->duration_seek_timeout = 0;
745 * Called when the pipeline transitions into PAUSED state. It extracts more
746 * information from Gst.
748 static void _finalize_startup(MafwGstRendererWorker *worker)
750 /* Check video caps */
751 if (worker->media.has_visual_content) {
752 GstPad *pad = GST_BASE_SINK_PAD(worker->vsink);
753 GstCaps *caps = GST_PAD_CAPS(pad);
754 if (caps && gst_caps_is_fixed(caps)) {
755 GstStructure *structure;
756 structure = gst_caps_get_structure(caps, 0);
757 if (!_handle_video_info(worker, structure))
762 /* Something might have gone wrong at this point already. */
763 if (worker->is_error) {
764 g_debug("Error occured during preroll");
768 /* Streaminfo might reveal the media to be unsupported. Therefore we
769 * need to check the error again. */
770 _parse_stream_info(worker);
771 if (worker->is_error) {
772 g_debug("Error occured. Leaving");
776 /* Check duration and seekability */
777 if (worker->duration_seek_timeout != 0) {
778 g_source_remove(worker->duration_seek_timeout);
779 worker->duration_seek_timeout = 0;
781 _check_duration(worker, -1);
782 _check_seekability(worker);
785 static void _add_duration_seek_query_timeout(MafwGstRendererWorker *worker)
787 if (worker->duration_seek_timeout != 0) {
788 g_source_remove(worker->duration_seek_timeout);
790 worker->duration_seek_timeout = g_timeout_add_seconds(
791 MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY,
792 _query_duration_and_seekability_timeout,
796 static void _do_pause_postprocessing(MafwGstRendererWorker *worker)
798 if (worker->notify_pause_handler) {
799 worker->notify_pause_handler(worker, worker->owner);
802 #ifdef HAVE_GDKPIXBUF
803 if (worker->media.has_visual_content &&
804 worker->current_frame_on_pause) {
805 GstBuffer *buffer = NULL;
807 g_object_get(worker->pipeline, "frame", &buffer, NULL);
809 if (buffer != NULL) {
810 _emit_gst_buffer_as_graphic_file(
812 MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI);
817 _add_ready_timeout(worker);
820 static void _report_playing_state(MafwGstRendererWorker * worker)
822 if (worker->report_statechanges) {
823 switch (worker->mode) {
824 case WORKER_MODE_SINGLE_PLAY:
825 /* Notify play if we are playing in
827 if (worker->notify_play_handler)
828 worker->notify_play_handler(
832 case WORKER_MODE_PLAYLIST:
833 case WORKER_MODE_REDUNDANT:
834 /* Only notify play when the "playlist"
835 playback starts, don't notify play for each
836 individual element of the playlist. */
837 if (worker->pl.notify_play_pending) {
838 if (worker->notify_play_handler)
839 worker->notify_play_handler(
842 worker->pl.notify_play_pending = FALSE;
850 static void _handle_state_changed(GstMessage *msg, MafwGstRendererWorker *worker)
852 GstState newstate, oldstate;
853 GstStateChange statetrans;
854 MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
856 gst_message_parse_state_changed(msg, &oldstate, &newstate, NULL);
857 statetrans = GST_STATE_TRANSITION(oldstate, newstate);
858 g_debug ("State changed: %d: %d -> %d", worker->state, oldstate, newstate);
860 /* If the state is the same we do nothing, otherwise, we keep
862 if (worker->state == newstate) {
865 worker->state = newstate;
868 if (statetrans == GST_STATE_CHANGE_READY_TO_PAUSED &&
870 /* Woken up from READY, resume stream position and playback */
871 g_debug("State changed to pause after ready");
872 if (worker->seek_position > 0) {
873 _check_seekability(worker);
874 if (worker->media.seekable) {
875 g_debug("performing a seek");
876 _do_seek(worker, GST_SEEK_TYPE_SET,
877 worker->seek_position, NULL);
879 g_critical("media is not seekable (and should)");
883 /* If playing a stream wait for buffering to finish before
885 if (!worker->is_stream || worker->is_live) {
891 /* While buffering, we have to wait in PAUSED
892 until we reach 100% before doing anything */
893 if (worker->buffering) {
894 if (statetrans == GST_STATE_CHANGE_PAUSED_TO_PLAYING) {
895 /* Mmm... probably the client issued a seek on the
896 * stream and then a play/resume command right away,
897 * so the stream got into PLAYING state while
898 * buffering. When the next buffering signal arrives,
899 * the stream will be PAUSED silently and resumed when
900 * buffering is done (silently too), so let's signal
901 * the state change to PLAYING here. */
902 _report_playing_state(worker);
907 switch (statetrans) {
908 case GST_STATE_CHANGE_READY_TO_PAUSED:
909 if (worker->prerolling && worker->report_statechanges) {
910 /* PAUSED after pipeline has been
911 * constructed. We check caps, seek and
912 * duration and if staying in pause is needed,
913 * we perform operations for pausing, such as
914 * current frame on pause and signalling state
915 * change and adding the timeout to go to ready */
916 g_debug ("Prerolling done, finalizaing startup");
917 _finalize_startup(worker);
919 renderer->play_failed_count = 0;
921 if (worker->stay_paused) {
922 _do_pause_postprocessing(worker);
924 worker->prerolling = FALSE;
927 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
928 /* When pausing we do the stuff, like signalling
929 * state, current frame on pause and timeout to go to
931 if (worker->report_statechanges) {
932 _do_pause_postprocessing(worker);
935 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
936 /* if seek was called, at this point it is really ended */
937 worker->seek_position = -1;
940 /* Signal state change if needed */
941 _report_playing_state(worker);
943 /* Prevent blanking if we are playing video */
944 if (worker->media.has_visual_content) {
947 /* Remove the ready timeout if we are playing [again] */
948 _remove_ready_timeout(worker);
949 /* If mode is redundant we are trying to play one of several
950 * candidates, so when we get a successful playback, we notify
951 * the real URI that we are playing */
952 if (worker->mode == WORKER_MODE_REDUNDANT) {
953 mafw_renderer_emit_metadata_string(
955 MAFW_METADATA_KEY_URI,
956 worker->media.location);
959 /* Emit metadata. We wait until we reach the playing
960 state because this speeds up playback start time */
961 _emit_metadatas(worker);
962 /* Query duration and seekability. Useful for vbr
963 * clips or streams. */
964 _add_duration_seek_query_timeout(worker);
966 case GST_STATE_CHANGE_PAUSED_TO_READY:
967 /* If we went to READY, we free the taglist and
968 * deassign the timout it */
969 if (worker->in_ready) {
970 g_debug("changed to GST_STATE_READY");
971 _free_taglist(worker);
979 static void _handle_duration(MafwGstRendererWorker *worker, GstMessage *msg)
984 gst_message_parse_duration(msg, &fmt, &duration);
986 if (worker->duration_seek_timeout != 0) {
987 g_source_remove(worker->duration_seek_timeout);
988 worker->duration_seek_timeout = 0;
991 _check_duration(worker,
992 duration != GST_CLOCK_TIME_NONE ? duration : -1);
993 _check_seekability(worker);
996 #ifdef HAVE_GDKPIXBUF
997 static void _emit_renderer_art(MafwGstRendererWorker *worker,
998 const GstTagList *list)
1000 GstBuffer *buffer = NULL;
1001 const GValue *value = NULL;
1003 g_return_if_fail(gst_tag_list_get_tag_size(list, GST_TAG_IMAGE) > 0);
1005 value = gst_tag_list_get_value_index(list, GST_TAG_IMAGE, 0);
1007 g_return_if_fail((value != NULL) && G_VALUE_HOLDS(value, GST_TYPE_BUFFER));
1009 buffer = g_value_peek_pointer(value);
1011 g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
1013 _emit_gst_buffer_as_graphic_file(worker, buffer,
1014 MAFW_METADATA_KEY_RENDERER_ART_URI);
1020 static void _current_metadata_add(MafwGstRendererWorker *worker,
1021 const gchar *key, GType type,
1022 const gpointer value)
1024 g_return_if_fail(value != NULL);
1026 if (!worker->current_metadata)
1027 worker->current_metadata = mafw_metadata_new();
1029 mafw_metadata_add_something(worker->current_metadata, key, type, 1, value);
1032 static GHashTable* _build_tagmap(void)
1034 GHashTable *hash_table = NULL;
1036 hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
1039 g_hash_table_insert(hash_table, g_strdup(GST_TAG_TITLE),
1040 g_strdup(MAFW_METADATA_KEY_TITLE));
1041 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ARTIST),
1042 g_strdup(MAFW_METADATA_KEY_ARTIST));
1043 g_hash_table_insert(hash_table, g_strdup(GST_TAG_AUDIO_CODEC),
1044 g_strdup(MAFW_METADATA_KEY_AUDIO_CODEC));
1045 g_hash_table_insert(hash_table, g_strdup(GST_TAG_VIDEO_CODEC),
1046 g_strdup(MAFW_METADATA_KEY_VIDEO_CODEC));
1047 g_hash_table_insert(hash_table, g_strdup(GST_TAG_BITRATE),
1048 g_strdup(MAFW_METADATA_KEY_BITRATE));
1049 g_hash_table_insert(hash_table, g_strdup(GST_TAG_LANGUAGE_CODE),
1050 g_strdup(MAFW_METADATA_KEY_ENCODING));
1051 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ALBUM),
1052 g_strdup(MAFW_METADATA_KEY_ALBUM));
1053 g_hash_table_insert(hash_table, g_strdup(GST_TAG_GENRE),
1054 g_strdup(MAFW_METADATA_KEY_GENRE));
1055 g_hash_table_insert(hash_table, g_strdup(GST_TAG_TRACK_NUMBER),
1056 g_strdup(MAFW_METADATA_KEY_TRACK));
1057 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ORGANIZATION),
1058 g_strdup(MAFW_METADATA_KEY_ORGANIZATION));
1059 #ifdef HAVE_GDKPIXBUF
1060 g_hash_table_insert(hash_table, g_strdup(GST_TAG_IMAGE),
1061 g_strdup(MAFW_METADATA_KEY_RENDERER_ART_URI));
1068 * Emits metadata-changed signals for gst tags.
1070 static void _emit_tag(const GstTagList *list, const gchar *tag,
1071 MafwGstRendererWorker *worker)
1073 /* Mapping between Gst <-> MAFW metadata tags
1074 * NOTE: This assumes that GTypes matches between GST and MAFW. */
1075 static GHashTable *tagmap = NULL;
1077 const gchar *mafwtag;
1079 GValueArray *values;
1081 if (tagmap == NULL) {
1082 tagmap = _build_tagmap();
1085 g_debug("tag: '%s' (type: %s)", tag,
1086 g_type_name(gst_tag_get_type(tag)));
1087 /* Is there a mapping for this tag? */
1088 mafwtag = g_hash_table_lookup(tagmap, tag);
1092 #ifdef HAVE_GDKPIXBUF
1093 if (strcmp (mafwtag, MAFW_METADATA_KEY_RENDERER_ART_URI) == 0) {
1094 _emit_renderer_art(worker, list);
1099 /* Build a value array of this tag. We need to make sure that strings
1100 * are UTF-8. GstTagList API says that the value is always UTF8, but it
1101 * looks like the ID3 demuxer still might sometimes produce non-UTF-8
1103 count = gst_tag_list_get_tag_size(list, tag);
1104 type = gst_tag_get_type(tag);
1105 values = g_value_array_new(count);
1106 for (i = 0; i < count; ++i) {
1107 GValue *v = (GValue *)
1108 gst_tag_list_get_value_index(list, tag, i);
1109 if (type == G_TYPE_STRING) {
1112 gst_tag_list_get_string_index(list, tag, i, &orig);
1113 if (convert_utf8(orig, &utf8)) {
1114 GValue utf8gval = {0};
1116 g_value_init(&utf8gval, G_TYPE_STRING);
1117 g_value_take_string(&utf8gval, utf8);
1118 _current_metadata_add(worker, mafwtag, G_TYPE_VALUE,
1119 (const gpointer) &utf8gval);
1120 g_value_array_append(values, &utf8gval);
1121 g_value_unset(&utf8gval);
1124 } else if (type == G_TYPE_UINT) {
1125 GValue intgval = {0};
1127 g_value_init(&intgval, G_TYPE_INT);
1128 g_value_transform(v, &intgval);
1129 _current_metadata_add(worker, mafwtag, G_TYPE_VALUE,
1130 (const gpointer) &intgval);
1131 g_value_array_append(values, &intgval);
1132 g_value_unset(&intgval);
1134 _current_metadata_add(worker, mafwtag, G_TYPE_VALUE,
1135 (const gpointer) v);
1136 g_value_array_append(values, v);
1140 /* Emit the metadata. */
1141 g_signal_emit_by_name(worker->owner, "metadata-changed", mafwtag,
1144 g_value_array_free(values);
1148 * Collect tag-messages, parse it later, when playing is ongoing
1150 static void _handle_tag(MafwGstRendererWorker *worker, GstMessage *msg)
1152 /* Do not emit metadata until we get to PLAYING state to speed up
1154 if (worker->tag_list == NULL)
1155 worker->tag_list = g_ptr_array_new();
1156 g_ptr_array_add(worker->tag_list, gst_message_ref(msg));
1158 /* Some tags come in playing state, so in this case we have
1159 to emit them right away (example: radio stations) */
1160 if (worker->state == GST_STATE_PLAYING) {
1161 _emit_metadatas(worker);
1166 * Parses the list of tag-messages
1168 static void _parse_tagmsg(GstMessage *msg, MafwGstRendererWorker *worker)
1170 GstTagList *new_tags;
1172 gst_message_parse_tag(msg, &new_tags);
1173 gst_tag_list_foreach(new_tags, (gpointer)_emit_tag, worker);
1174 gst_tag_list_free(new_tags);
1175 gst_message_unref(msg);
1179 * Parses the collected tag messages, and emits the metadatas
1181 static void _emit_metadatas(MafwGstRendererWorker *worker)
1183 if (worker->tag_list != NULL)
1185 g_ptr_array_foreach(worker->tag_list, (GFunc)_parse_tagmsg,
1187 g_ptr_array_free(worker->tag_list, TRUE);
1188 worker->tag_list = NULL;
1192 static void _reset_volume_and_mute_to_pipeline(MafwGstRendererWorker *worker)
1194 #ifdef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1195 g_debug("resetting volume and mute to pipeline");
1197 if (worker->pipeline != NULL) {
1199 G_OBJECT(worker->pipeline), "volume",
1200 mafw_gst_renderer_worker_volume_get(worker->wvolume),
1202 mafw_gst_renderer_worker_volume_is_muted(worker->wvolume),
1208 static void _handle_buffering(MafwGstRendererWorker *worker, GstMessage *msg)
1211 MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
1213 gst_message_parse_buffering(msg, &percent);
1214 g_debug("buffering: %d", percent);
1216 /* No state management needed for live pipelines */
1217 if (!worker->is_live) {
1218 worker->buffering = TRUE;
1219 if (worker->state == GST_STATE_PLAYING) {
1220 g_debug("setting pipeline to PAUSED not to wolf the "
1222 worker->report_statechanges = FALSE;
1223 /* We can't call _pause() here, since it sets
1224 * the "report_statechanges" to TRUE. We don't
1225 * want that, application doesn't need to know
1226 * that internally the state changed to
1228 if (gst_element_set_state(worker->pipeline,
1229 GST_STATE_PAUSED) ==
1230 GST_STATE_CHANGE_ASYNC)
1232 /* XXX this blocks at most 2 seconds. */
1233 gst_element_get_state(worker->pipeline, NULL,
1239 if (percent >= 100) {
1240 /* On buffering we go to PAUSED, so here we move back to
1242 worker->buffering = FALSE;
1243 if (worker->state == GST_STATE_PAUSED) {
1244 /* If buffering more than once, do this only the
1245 first time we are done with buffering */
1246 if (worker->prerolling) {
1247 g_debug("buffering concluded during "
1249 _finalize_startup(worker);
1251 renderer->play_failed_count = 0;
1252 /* Send the paused notification */
1253 if (worker->stay_paused &&
1254 worker->notify_pause_handler) {
1255 worker->notify_pause_handler(
1259 worker->prerolling = FALSE;
1260 } else if (worker->in_ready) {
1261 /* If we had been woken up from READY
1262 and we have finish our buffering,
1263 check if we have to play or stay
1264 paused and if we have to play,
1265 signal the state change. */
1266 g_debug("buffering concluded, "
1267 "continuing playing");
1269 } else if (!worker->stay_paused) {
1270 /* This means, that we were playing but
1271 ran out of buffer, so we silently
1272 paused waited for buffering to
1273 finish and now we continue silently
1274 (silently meaning we do not expose
1276 g_debug("buffering concluded, setting "
1277 "pipeline to PLAYING again");
1278 _reset_volume_and_mute_to_pipeline(
1280 if (gst_element_set_state(
1282 GST_STATE_PLAYING) ==
1283 GST_STATE_CHANGE_ASYNC)
1285 /* XXX this blocks at most 2 seconds. */
1286 gst_element_get_state(
1287 worker->pipeline, NULL, NULL,
1291 } else if (worker->state == GST_STATE_PLAYING) {
1292 g_debug("buffering concluded, signalling "
1294 /* In this case we got a PLAY command while
1295 buffering, likely because it was issued
1296 before we got the first buffering signal.
1297 The UI should not do this, but if it does,
1298 we have to signal that we have executed
1299 the state change, since in
1300 _handle_state_changed we do not do anything
1301 if we are buffering */
1302 if (worker->report_statechanges &&
1303 worker->notify_play_handler) {
1304 worker->notify_play_handler(
1308 _add_duration_seek_query_timeout(worker);
1313 /* Send buffer percentage */
1314 if (worker->notify_buffer_status_handler)
1315 worker->notify_buffer_status_handler(worker, worker->owner,
1319 static void _handle_element_msg(MafwGstRendererWorker *worker, GstMessage *msg)
1321 /* Only HelixBin sends "resolution" messages. */
1322 if (gst_structure_has_name(msg->structure, "resolution") &&
1323 _handle_video_info(worker, msg->structure))
1325 worker->media.has_visual_content = TRUE;
1329 static void _reset_pl_info(MafwGstRendererWorker *worker)
1331 if (worker->pl.items) {
1332 g_slist_foreach(worker->pl.items, (GFunc) g_free, NULL);
1333 g_slist_free(worker->pl.items);
1334 worker->pl.items = NULL;
1337 worker->pl.current = 0;
1338 worker->pl.notify_play_pending = TRUE;
1341 static GError * _get_specific_missing_plugin_error(GstMessage *msg)
1343 const GstStructure *gst_struct;
1349 desc = gst_missing_plugin_message_get_description(msg);
1351 gst_struct = gst_message_get_structure(msg);
1352 type = gst_structure_get_string(gst_struct, "type");
1354 if ((type) && ((strcmp(type, MAFW_GST_MISSING_TYPE_DECODER) == 0) ||
1355 (strcmp(type, MAFW_GST_MISSING_TYPE_ENCODER) == 0))) {
1357 /* Missing codec error. */
1359 const GstCaps *caps;
1360 GstStructure *caps_struct;
1363 val = gst_structure_get_value(gst_struct, "detail");
1364 caps = gst_value_get_caps(val);
1365 caps_struct = gst_caps_get_structure(caps, 0);
1366 mime = gst_structure_get_name(caps_struct);
1368 if (g_strrstr(mime, "video")) {
1369 error = g_error_new_literal(
1370 MAFW_RENDERER_ERROR,
1371 MAFW_RENDERER_ERROR_VIDEO_CODEC_NOT_FOUND,
1373 } else if (g_strrstr(mime, "audio")) {
1374 error = g_error_new_literal(
1375 MAFW_RENDERER_ERROR,
1376 MAFW_RENDERER_ERROR_AUDIO_CODEC_NOT_FOUND,
1379 error = g_error_new_literal(
1380 MAFW_RENDERER_ERROR,
1381 MAFW_RENDERER_ERROR_CODEC_NOT_FOUND,
1385 /* Unsupported type error. */
1386 error = g_error_new(
1387 MAFW_RENDERER_ERROR,
1388 MAFW_RENDERER_ERROR_UNSUPPORTED_TYPE,
1389 "missing plugin: %s", desc);
1398 * Asynchronous message handler. It gets removed from if it returns FALSE.
1400 static gboolean _async_bus_handler(GstBus *bus, GstMessage *msg,
1401 MafwGstRendererWorker *worker)
1403 /* No need to handle message if error has already occured. */
1404 if (worker->is_error)
1407 /* Handle missing-plugin (element) messages separately, relaying more
1409 if (gst_is_missing_plugin_message(msg)) {
1410 GError *err = _get_specific_missing_plugin_error(msg);
1411 /* FIXME?: for some reason, calling the error handler directly
1412 * (_send_error) causes problems. On the other hand, turning
1413 * the error into a new GstMessage and letting the next
1414 * iteration handle it seems to work. */
1415 _post_error(worker, err);
1419 switch (GST_MESSAGE_TYPE(msg)) {
1420 case GST_MESSAGE_ERROR:
1421 if (!worker->is_error) {
1426 gst_message_parse_error(msg, &err, &debug);
1427 g_debug("gst error: domain = %d, code = %d, "
1428 "message = '%s', debug = '%s'",
1429 err->domain, err->code, err->message, debug);
1433 /* If we are in playlist/radio mode, we silently
1434 ignore the error and continue with the next
1435 item until we end the playlist. If no
1436 playable elements we raise the error and
1437 after finishing we go to normal mode */
1439 if (worker->mode == WORKER_MODE_PLAYLIST ||
1440 worker->mode == WORKER_MODE_REDUNDANT) {
1441 if (worker->pl.current <
1442 (g_slist_length(worker->pl.items) - 1)) {
1443 /* If the error is "no space left"
1444 notify, otherwise try to play the
1447 GST_RESOURCE_ERROR_NO_SPACE_LEFT) {
1448 _send_error(worker, err);
1451 _play_pl_next(worker);
1454 /* Playlist EOS. We cannot try another
1455 * URI, so we have to go back to normal
1456 * mode and signal the error (done
1458 worker->mode = WORKER_MODE_SINGLE_PLAY;
1459 _reset_pl_info(worker);
1463 if (worker->mode == WORKER_MODE_SINGLE_PLAY) {
1464 _send_error(worker, err);
1468 case GST_MESSAGE_EOS:
1469 if (!worker->is_error) {
1472 if (worker->mode == WORKER_MODE_PLAYLIST) {
1473 if (worker->pl.current <
1474 (g_slist_length(worker->pl.items) - 1)) {
1475 /* If the playlist EOS is not reached
1477 _play_pl_next(worker);
1479 /* Playlist EOS, go back to normal
1481 worker->mode = WORKER_MODE_SINGLE_PLAY;
1482 _reset_pl_info(worker);
1486 if (worker->mode == WORKER_MODE_SINGLE_PLAY ||
1487 worker->mode == WORKER_MODE_REDUNDANT) {
1488 if (worker->notify_eos_handler)
1489 worker->notify_eos_handler(
1493 /* We can remove the message handlers now, we
1494 are not interested in bus messages
1497 gst_bus_set_sync_handler(worker->bus,
1501 if (worker->async_bus_id) {
1502 g_source_remove(worker->async_bus_id);
1503 worker->async_bus_id = 0;
1506 if (worker->mode == WORKER_MODE_REDUNDANT) {
1507 /* Go to normal mode */
1508 worker->mode = WORKER_MODE_SINGLE_PLAY;
1509 _reset_pl_info(worker);
1514 case GST_MESSAGE_TAG:
1515 _handle_tag(worker, msg);
1517 case GST_MESSAGE_BUFFERING:
1518 _handle_buffering(worker, msg);
1520 case GST_MESSAGE_DURATION:
1521 _handle_duration(worker, msg);
1523 case GST_MESSAGE_ELEMENT:
1524 _handle_element_msg(worker, msg);
1526 case GST_MESSAGE_STATE_CHANGED:
1527 if ((GstElement *)GST_MESSAGE_SRC(msg) == worker->pipeline)
1528 _handle_state_changed(msg, worker);
1530 case GST_MESSAGE_APPLICATION:
1531 if (gst_structure_has_name(gst_message_get_structure(msg),
1535 g_value_init(&v, G_TYPE_INT);
1536 g_value_set_int(&v, worker->colorkey);
1537 mafw_extension_emit_property_changed(
1538 MAFW_EXTENSION(worker->owner),
1539 MAFW_PROPERTY_RENDERER_COLORKEY,
1547 /* NOTE this function will possibly be called from a different thread than the
1548 * glib main thread. */
1549 static void _stream_info_cb(GstObject *pipeline, GParamSpec *unused,
1550 MafwGstRendererWorker *worker)
1552 g_debug("stream-info changed");
1553 _parse_stream_info(worker);
1556 static void _volume_cb(MafwGstRendererWorkerVolume *wvolume, gdouble volume,
1559 MafwGstRendererWorker *worker = data;
1560 GValue value = {0, };
1562 _reset_volume_and_mute_to_pipeline(worker);
1564 g_value_init(&value, G_TYPE_UINT);
1565 g_value_set_uint(&value, (guint) (volume * 100.0));
1566 mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner),
1567 MAFW_PROPERTY_RENDERER_VOLUME,
1571 static void _mute_cb(MafwGstRendererWorkerVolume *wvolume, gboolean mute,
1574 MafwGstRendererWorker *worker = data;
1575 GValue value = {0, };
1577 _reset_volume_and_mute_to_pipeline(worker);
1579 g_value_init(&value, G_TYPE_BOOLEAN);
1580 g_value_set_boolean(&value, mute);
1581 mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner),
1582 MAFW_PROPERTY_RENDERER_MUTE,
1586 /* TODO: I think it's not enought to act on error, we need to handle
1587 * DestroyNotify on the given window ourselves, because for example helixbin
1588 * does it and silently stops the decoder thread. But it doesn't notify
1590 static int xerror(Display *dpy, XErrorEvent *xev)
1592 MafwGstRendererWorker *worker;
1594 if (Global_worker == NULL) {
1597 worker = Global_worker;
1600 /* Swallow BadWindow and stop pipeline when the error is about the
1601 * currently set xid. */
1603 xev->resourceid == worker->xid &&
1604 xev->error_code == BadWindow)
1606 g_warning("BadWindow received for current xid (%x).",
1607 (gint)xev->resourceid);
1609 /* We must post a message to the bus, because this function is
1610 * invoked from a different thread (xvimagerenderer's queue). */
1611 _post_error(worker, g_error_new_literal(
1612 MAFW_RENDERER_ERROR,
1613 MAFW_RENDERER_ERROR_PLAYBACK,
1614 "Video window gone"));
1620 * Resets the media information.
1622 static void _reset_media_info(MafwGstRendererWorker *worker)
1624 if (worker->media.location) {
1625 g_free(worker->media.location);
1626 worker->media.location = NULL;
1628 worker->media.length_nanos = -1;
1629 worker->media.has_visual_content = FALSE;
1630 worker->media.seekable = SEEKABILITY_UNKNOWN;
1631 worker->media.video_width = 0;
1632 worker->media.video_height = 0;
1633 worker->media.fps = 0.0;
1636 static void _set_volume_and_mute(MafwGstRendererWorker *worker, gdouble vol,
1639 g_return_if_fail(worker->wvolume != NULL);
1641 mafw_gst_renderer_worker_volume_set(worker->wvolume, vol, mute);
1644 static void _set_volume(MafwGstRendererWorker *worker, gdouble new_vol)
1646 g_return_if_fail(worker->wvolume != NULL);
1648 _set_volume_and_mute(
1650 mafw_gst_renderer_worker_volume_is_muted(worker->wvolume));
1653 static void _set_mute(MafwGstRendererWorker *worker, gboolean mute)
1655 g_return_if_fail(worker->wvolume != NULL);
1657 _set_volume_and_mute(
1658 worker, mafw_gst_renderer_worker_volume_get(worker->wvolume),
1663 * Start to play the media
1665 static void _start_play(MafwGstRendererWorker *worker)
1667 MafwGstRenderer *renderer = (MafwGstRenderer*) worker->owner;
1668 GstStateChangeReturn state_change_info;
1669 char *autoload_sub = NULL;
1671 g_assert(worker->pipeline);
1672 g_object_set(G_OBJECT(worker->pipeline),
1673 "uri", worker->media.location, NULL);
1675 if (worker->subtitles.enabled) {
1676 autoload_sub = uri_get_subtitle_uri(worker->media.location);
1678 g_debug("SUBURI: %s", autoload_sub);
1679 g_object_set(G_OBJECT(worker->pipeline),
1680 "suburi", autoload_sub,
1681 "subtitle-font-desc", worker->subtitles.font,
1682 "subtitle-encoding", worker->subtitles.encoding,
1685 gst_element_set_state(worker->pipeline, GST_STATE_READY);
1686 g_free(autoload_sub);
1689 g_object_set(G_OBJECT(worker->pipeline), "suburi", NULL, NULL);
1692 g_debug("URI: %s", worker->media.location);
1693 g_debug("setting pipeline to PAUSED");
1695 worker->report_statechanges = TRUE;
1696 state_change_info = gst_element_set_state(worker->pipeline,
1698 if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) {
1699 /* FIXME: for live sources we may have to handle
1700 buffering and prerolling differently */
1701 g_debug ("Source is live!");
1702 worker->is_live = TRUE;
1704 worker->prerolling = TRUE;
1706 worker->is_stream = uri_is_stream(worker->media.location);
1708 if (renderer->update_playcount_id > 0) {
1709 g_source_remove(renderer->update_playcount_id);
1710 renderer->update_playcount_id = 0;
1716 * Constructs gst pipeline
1718 * FIXME: Could the same pipeline be used for playing all media instead of
1719 * constantly deleting and reconstructing it again?
1721 static void _construct_pipeline(MafwGstRendererWorker *worker)
1723 g_debug("constructing pipeline");
1724 g_assert(worker != NULL);
1726 /* Return if we have already one */
1727 if (worker->pipeline)
1730 _free_taglist(worker);
1732 g_debug("Creating a new instance of playbin2");
1733 worker->pipeline = gst_element_factory_make("playbin2",
1735 if (worker->pipeline == NULL)
1737 /* Let's try with playbin */
1738 g_warning ("playbin2 failed, falling back to playbin");
1739 worker->pipeline = gst_element_factory_make("playbin",
1742 if (worker->pipeline) {
1743 /* Use nwqueue only for non-rtsp and non-mms(h)
1746 use_nw = worker->media.location &&
1747 !g_str_has_prefix(worker->media.location,
1749 !g_str_has_prefix(worker->media.location,
1751 !g_str_has_prefix(worker->media.location,
1754 g_debug("playbin using network queue: %d", use_nw);
1756 /* These need a modified version of playbin. */
1757 g_object_set(G_OBJECT(worker->pipeline),
1758 "nw-queue", use_nw, NULL);
1759 g_object_set(G_OBJECT(worker->pipeline),
1760 "no-video-transform", TRUE, NULL);
1764 if (!worker->pipeline) {
1765 g_critical("failed to create playback pipeline");
1766 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1768 MAFW_RENDERER_ERROR,
1769 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1770 "Could not create pipeline");
1771 g_assert_not_reached();
1775 worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline));
1776 gst_bus_set_sync_handler(worker->bus,
1777 (GstBusSyncHandler)_sync_bus_handler, worker);
1778 worker->async_bus_id = gst_bus_add_watch_full(worker->bus,G_PRIORITY_HIGH,
1779 (GstBusFunc)_async_bus_handler,
1782 /* Listen for changes in stream-info object to find out whether the
1783 * media contains video and throw error if application has not provided
1785 g_signal_connect(worker->pipeline, "notify::stream-info",
1786 G_CALLBACK(_stream_info_cb), worker);
1788 #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1789 g_object_set(worker->pipeline, "flags", 103, NULL);
1791 /* Set audio and video sinks ourselves. We create and configure
1793 if (!worker->asink) {
1794 worker->asink = gst_element_factory_make("pulsesink", NULL);
1795 if (!worker->asink) {
1796 g_critical("Failed to create pipeline audio sink");
1797 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1799 MAFW_RENDERER_ERROR,
1800 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1801 "Could not create audio sink");
1802 g_assert_not_reached();
1804 gst_object_ref(worker->asink);
1805 g_object_set(worker->asink, "buffer-time",
1806 (gint64) MAFW_GST_BUFFER_TIME, NULL);
1807 g_object_set(worker->asink, "latency-time",
1808 (gint64) MAFW_GST_LATENCY_TIME, NULL);
1810 g_object_set(worker->pipeline, "audio-sink", worker->asink, NULL);
1813 if (!worker->vsink) {
1814 worker->vsink = gst_element_factory_make("xvimagesink", NULL);
1815 if (!worker->vsink) {
1816 g_critical("Failed to create pipeline video sink");
1817 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1819 MAFW_RENDERER_ERROR,
1820 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1821 "Could not create video sink");
1822 g_assert_not_reached();
1824 gst_object_ref(worker->vsink);
1825 g_object_set(G_OBJECT(worker->vsink), "handle-events",
1827 g_object_set(worker->vsink, "force-aspect-ratio",
1830 g_object_set(worker->pipeline, "video-sink", worker->vsink, NULL);
1832 if (!worker->tsink) {
1833 worker->tsink = gst_element_factory_make("textoverlay", NULL);
1834 if (!worker->tsink) {
1835 g_critical("Failed to create pipeline text sink");
1836 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1838 MAFW_RENDERER_ERROR,
1839 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1840 "Could not create text sink");
1841 g_assert_not_reached();
1843 gst_object_ref(worker->tsink);
1845 g_object_set(worker->pipeline, "text-sink", worker->tsink, NULL);
1849 * @seek_type: GstSeekType
1850 * @position: Time in seconds where to seek
1852 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
1853 gint position, GError **error)
1858 g_assert(worker != NULL);
1860 if (worker->eos || !worker->media.seekable)
1863 /* According to the docs, relative seeking is not so easy:
1864 GST_SEEK_TYPE_CUR - change relative to currently configured segment.
1865 This can't be used to seek relative to the current playback position -
1866 do a position query, calculate the desired position and then do an
1867 absolute position seek instead if that's what you want to do. */
1868 if (seek_type == GST_SEEK_TYPE_CUR)
1870 gint curpos = mafw_gst_renderer_worker_get_position(worker);
1871 position = curpos + position;
1872 seek_type = GST_SEEK_TYPE_SET;
1879 worker->seek_position = position;
1880 worker->report_statechanges = FALSE;
1881 spos = (gint64)position * GST_SECOND;
1882 g_debug("seek: type = %d, offset = %lld", seek_type, spos);
1884 /* If the pipeline has been set to READY by us, then wake it up by
1885 setting it to PAUSED (when we get the READY->PAUSED transition
1886 we will execute the seek). This way when we seek we disable the
1887 READY state (logical, since the player is not idle anymore)
1888 allowing the sink to render the destination frame in case of
1890 if (worker->in_ready && worker->state == GST_STATE_READY) {
1891 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
1893 ret = gst_element_seek(worker->pipeline, 1.0, GST_FORMAT_TIME,
1894 GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_KEY_UNIT,
1896 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
1898 /* Seeking is async, so seek_position should not be
1905 err: g_set_error(error,
1906 MAFW_RENDERER_ERROR,
1907 MAFW_RENDERER_ERROR_CANNOT_SET_POSITION,
1908 "Seeking to %d failed", position);
1911 /* @vol should be between [0 .. 100], higher values (up to 1000) are allowed,
1912 * but probably cause distortion. */
1913 void mafw_gst_renderer_worker_set_volume(
1914 MafwGstRendererWorker *worker, guint volume)
1916 _set_volume(worker, CLAMP((gdouble)volume / 100.0, 0.0, 1.0));
1919 guint mafw_gst_renderer_worker_get_volume(
1920 MafwGstRendererWorker *worker)
1923 (mafw_gst_renderer_worker_volume_get(worker->wvolume) * 100);
1926 void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker,
1929 _set_mute(worker, mute);
1932 gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker)
1934 return mafw_gst_renderer_worker_volume_is_muted(worker->wvolume);
1937 #ifdef HAVE_GDKPIXBUF
1938 void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker,
1939 gboolean current_frame_on_pause)
1941 worker->current_frame_on_pause = current_frame_on_pause;
1944 gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker)
1946 return worker->current_frame_on_pause;
1950 void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker,
1951 GstSeekType seek_type,
1952 gint position, GError **error)
1954 /* If player is paused and we have a timeout for going to ready
1955 * restart it. This is logical, since the user is seeking and
1956 * thus, the player is not idle anymore. Also this prevents that
1957 * when seeking streams we enter buffering and in the middle of
1958 * the buffering process we set the pipeline to ready (which stops
1959 * the buffering before it reaches 100%, making the client think
1960 * buffering is still going on).
1962 if (worker->ready_timeout) {
1963 _remove_ready_timeout(worker);
1964 _add_ready_timeout(worker);
1967 _do_seek(worker, seek_type, position, error);
1968 if (worker->notify_seek_handler)
1969 worker->notify_seek_handler(worker, worker->owner);
1973 * Gets current position, rounded down into precision of one second. If a seek
1974 * is pending, returns the position we are going to seek. Returns -1 on
1977 gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker)
1981 g_assert(worker != NULL);
1983 /* If seek is ongoing, return the position where we are seeking. */
1984 if (worker->seek_position != -1)
1986 return worker->seek_position;
1988 /* Otherwise query position from pipeline. */
1989 format = GST_FORMAT_TIME;
1990 if (worker->pipeline &&
1991 gst_element_query_position(worker->pipeline, &format, &time))
1993 return (gint)(NSECONDS_TO_SECONDS(time));
1998 GHashTable *mafw_gst_renderer_worker_get_current_metadata(
1999 MafwGstRendererWorker *worker)
2001 return worker->current_metadata;
2004 void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid)
2006 /* Check for errors on the target window */
2007 XSetErrorHandler(xerror);
2009 /* Store the target window id */
2010 g_debug("Setting xid: %x", (guint)xid);
2013 /* Check if we should use it right away */
2014 mafw_gst_renderer_worker_apply_xid(worker);
2017 XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker)
2022 gboolean mafw_gst_renderer_worker_get_autopaint(
2023 MafwGstRendererWorker *worker)
2025 return worker->autopaint;
2027 void mafw_gst_renderer_worker_set_autopaint(
2028 MafwGstRendererWorker *worker, gboolean autopaint)
2030 worker->autopaint = autopaint;
2032 g_object_set(worker->vsink, "autopaint-colorkey",
2036 gint mafw_gst_renderer_worker_get_colorkey(
2037 MafwGstRendererWorker *worker)
2039 return worker->colorkey;
2042 gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker)
2044 return worker->media.seekable;
2047 static void _play_pl_next(MafwGstRendererWorker *worker) {
2050 g_assert(worker != NULL);
2051 g_return_if_fail(worker->pl.items != NULL);
2053 next = (gchar *) g_slist_nth_data(worker->pl.items,
2054 ++worker->pl.current);
2055 mafw_gst_renderer_worker_stop(worker);
2056 _reset_media_info(worker);
2058 worker->media.location = g_strdup(next);
2059 _construct_pipeline(worker);
2060 _start_play(worker);
2063 static void _on_pl_entry_parsed(TotemPlParser *parser, gchar *uri,
2064 gpointer metadata, gpointer user_data)
2066 MafwGstRendererWorker *worker = user_data;
2070 g_slist_append(worker->pl.items, g_strdup(uri));
2074 static void _do_play(MafwGstRendererWorker *worker)
2076 g_assert(worker != NULL);
2078 if (worker->pipeline == NULL) {
2079 g_debug("play without a pipeline!");
2082 worker->report_statechanges = TRUE;
2084 /* If we have to stay paused, we do and add the ready
2085 * timeout. Otherwise, we move the pipeline */
2086 if (!worker->stay_paused) {
2087 /* If pipeline is READY, we move it to PAUSED,
2088 * otherwise, to PLAYING */
2089 if (worker->state == GST_STATE_READY) {
2090 gst_element_set_state(worker->pipeline,
2092 g_debug("setting pipeline to PAUSED");
2094 _reset_volume_and_mute_to_pipeline(worker);
2095 gst_element_set_state(worker->pipeline,
2097 g_debug("setting pipeline to PLAYING");
2101 g_debug("staying in PAUSED state");
2102 _add_ready_timeout(worker);
2106 void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker,
2111 mafw_gst_renderer_worker_stop(worker);
2112 _reset_media_info(worker);
2113 _reset_pl_info(worker);
2114 /* Check if the item to play is a single item or a playlist. */
2115 if (uri_is_playlist(uri)){
2116 /* In case of a playlist we parse it and start playing the first
2117 item of the playlist. */
2118 TotemPlParser *pl_parser;
2121 /* Initialize the playlist parser */
2122 pl_parser = totem_pl_parser_new ();
2123 g_object_set(pl_parser, "recurse", TRUE, "disable-unsafe",
2125 g_signal_connect(G_OBJECT(pl_parser), "entry-parsed",
2126 G_CALLBACK(_on_pl_entry_parsed), worker);
2129 if (totem_pl_parser_parse(pl_parser, uri, FALSE) !=
2130 TOTEM_PL_PARSER_RESULT_SUCCESS) {
2131 /* An error happens while parsing */
2133 g_error_new(MAFW_RENDERER_ERROR,
2134 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2135 "Playlist parsing failed: %s",
2140 if (!worker->pl.items) {
2141 /* The playlist is empty */
2143 g_error_new(MAFW_RENDERER_ERROR,
2144 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2145 "The playlist %s is empty.",
2150 /* Set the playback mode */
2151 worker->mode = WORKER_MODE_PLAYLIST;
2152 worker->pl.notify_play_pending = TRUE;
2154 /* Set the item to be played */
2155 worker->pl.current = 0;
2156 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2157 worker->media.location = g_strdup(item);
2159 /* Free the playlist parser */
2160 g_object_unref(pl_parser);
2162 /* Single item. Set the playback mode according to that */
2163 worker->mode = WORKER_MODE_SINGLE_PLAY;
2165 /* Set the item to be played */
2166 worker->media.location = g_strdup(uri);
2168 _construct_pipeline(worker);
2169 _start_play(worker);
2172 void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker,
2178 g_assert(uris && uris[0]);
2180 mafw_gst_renderer_worker_stop(worker);
2181 _reset_media_info(worker);
2182 _reset_pl_info(worker);
2184 /* Add the uris to playlist */
2188 g_slist_append(worker->pl.items, g_strdup(uris[i]));
2192 /* Set the playback mode */
2193 worker->mode = WORKER_MODE_REDUNDANT;
2194 worker->pl.notify_play_pending = TRUE;
2196 /* Set the item to be played */
2197 worker->pl.current = 0;
2198 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2199 worker->media.location = g_strdup(item);
2202 _construct_pipeline(worker);
2203 _start_play(worker);
2207 * Currently, stop destroys the Gst pipeline and resets the worker into
2208 * default startup configuration.
2210 void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker)
2212 g_debug("worker stop");
2213 g_assert(worker != NULL);
2215 /* If location is NULL, this is a pre-created pipeline */
2216 if (worker->async_bus_id && worker->pipeline && !worker->media.location)
2219 if (worker->pipeline) {
2220 g_debug("destroying pipeline");
2221 if (worker->async_bus_id) {
2222 g_source_remove(worker->async_bus_id);
2223 worker->async_bus_id = 0;
2225 gst_bus_set_sync_handler(worker->bus, NULL, NULL);
2226 gst_element_set_state(worker->pipeline, GST_STATE_NULL);
2228 gst_object_unref(GST_OBJECT_CAST(worker->bus));
2231 gst_object_unref(GST_OBJECT(worker->pipeline));
2232 worker->pipeline = NULL;
2236 worker->report_statechanges = TRUE;
2237 worker->state = GST_STATE_NULL;
2238 worker->prerolling = FALSE;
2239 worker->is_live = FALSE;
2240 worker->buffering = FALSE;
2241 worker->is_stream = FALSE;
2242 worker->is_error = FALSE;
2243 worker->eos = FALSE;
2244 worker->seek_position = -1;
2245 _remove_ready_timeout(worker);
2246 _free_taglist(worker);
2247 if (worker->current_metadata) {
2248 g_hash_table_destroy(worker->current_metadata);
2249 worker->current_metadata = NULL;
2252 if (worker->duration_seek_timeout != 0) {
2253 g_source_remove(worker->duration_seek_timeout);
2254 worker->duration_seek_timeout = 0;
2257 /* Reset media iformation */
2258 _reset_media_info(worker);
2260 /* We are not playing, so we can let the screen blank */
2263 /* And now get a fresh pipeline ready */
2264 _construct_pipeline(worker);
2267 void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker)
2269 g_assert(worker != NULL);
2271 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2272 !worker->prerolling) {
2273 /* If we are buffering and get a pause, we have to
2274 * signal state change and stay_paused */
2275 g_debug("Pausing while buffering, signalling state change");
2276 worker->stay_paused = TRUE;
2277 if (worker->notify_pause_handler) {
2278 worker->notify_pause_handler(
2283 worker->report_statechanges = TRUE;
2285 if (gst_element_set_state(worker->pipeline, GST_STATE_PAUSED) ==
2286 GST_STATE_CHANGE_ASYNC)
2288 /* XXX this blocks at most 2 seconds. */
2289 gst_element_get_state(worker->pipeline, NULL, NULL,
2296 void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker)
2298 if (worker->mode == WORKER_MODE_PLAYLIST ||
2299 worker->mode == WORKER_MODE_REDUNDANT) {
2300 /* We must notify play if the "playlist" playback
2302 worker->pl.notify_play_pending = TRUE;
2304 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2305 !worker->prerolling) {
2306 /* If we are buffering we cannot resume, but we know
2307 * that the pipeline will be moved to PLAYING as
2308 * stay_paused is FALSE, so we just activate the state
2309 * change report, this way as soon as buffering is finished
2310 * the pipeline will be set to PLAYING and the state
2311 * change will be reported */
2312 worker->report_statechanges = TRUE;
2313 g_debug("Resumed while buffering, activating pipeline state "
2315 /* Notice though that we can receive the Resume before
2316 we get any buffering information. In that case
2317 we go with the "else" branch and set the pipeline to
2318 to PLAYING. However, it is possible that in this case
2319 we get the fist buffering signal before the
2320 PAUSED -> PLAYING state change. In that case, since we
2321 ignore state changes while buffering we never signal
2322 the state change to PLAYING. We can only fix this by
2323 checking, when we receive a PAUSED -> PLAYING transition
2324 if we are buffering, and in that case signal the state
2325 change (if we get that transition while buffering
2326 is on, it can only mean that the client resumed playback
2327 while buffering, and we must notify the state change) */
2333 static void _volume_init_cb(MafwGstRendererWorkerVolume *wvolume,
2336 MafwGstRendererWorker *worker = data;
2340 worker->wvolume = wvolume;
2342 g_debug("volume manager initialized");
2344 volume = mafw_gst_renderer_worker_volume_get(wvolume);
2345 mute = mafw_gst_renderer_worker_volume_is_muted(wvolume);
2346 _volume_cb(wvolume, volume, worker);
2347 _mute_cb(wvolume, mute, worker);
2350 MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner)
2352 MafwGstRendererWorker *worker;
2353 GMainContext *main_context;
2355 worker = g_new0(MafwGstRendererWorker, 1);
2356 worker->mode = WORKER_MODE_SINGLE_PLAY;
2357 worker->pl.items = NULL;
2358 worker->pl.current = 0;
2359 worker->pl.notify_play_pending = TRUE;
2360 worker->owner = owner;
2361 worker->report_statechanges = TRUE;
2362 worker->state = GST_STATE_NULL;
2363 worker->seek_position = -1;
2364 worker->ready_timeout = 0;
2365 worker->in_ready = FALSE;
2367 worker->autopaint = TRUE;
2368 worker->colorkey = -1;
2369 worker->vsink = NULL;
2370 worker->asink = NULL;
2371 worker->tsink = NULL;
2372 worker->tag_list = NULL;
2373 worker->current_metadata = NULL;
2374 worker->subtitles.enabled = FALSE;
2375 worker->subtitles.font = NULL;
2376 worker->subtitles.encoding = NULL;
2378 #ifdef HAVE_GDKPIXBUF
2379 worker->current_frame_on_pause = FALSE;
2380 _init_tmp_files_pool(worker);
2382 worker->notify_seek_handler = NULL;
2383 worker->notify_pause_handler = NULL;
2384 worker->notify_play_handler = NULL;
2385 worker->notify_buffer_status_handler = NULL;
2386 worker->notify_eos_handler = NULL;
2387 worker->notify_error_handler = NULL;
2388 Global_worker = worker;
2389 main_context = g_main_context_default();
2390 worker->wvolume = NULL;
2391 mafw_gst_renderer_worker_volume_init(main_context,
2392 _volume_init_cb, worker,
2396 _construct_pipeline(worker);
2401 void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker)
2404 #ifdef HAVE_GDKPIXBUF
2405 _destroy_tmp_files_pool(worker);
2407 mafw_gst_renderer_worker_volume_destroy(worker->wvolume);
2408 mafw_gst_renderer_worker_stop(worker);
2410 /* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */