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;
1670 g_assert(worker->pipeline);
1671 g_object_set(G_OBJECT(worker->pipeline),
1672 "uri", worker->media.location, NULL);
1674 g_debug("URI: %s", worker->media.location);
1675 g_debug("setting pipeline to PAUSED");
1677 worker->report_statechanges = TRUE;
1678 state_change_info = gst_element_set_state(worker->pipeline,
1680 if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) {
1681 /* FIXME: for live sources we may have to handle
1682 buffering and prerolling differently */
1683 g_debug ("Source is live!");
1684 worker->is_live = TRUE;
1686 worker->prerolling = TRUE;
1688 worker->is_stream = uri_is_stream(worker->media.location);
1690 if (renderer->update_playcount_id > 0) {
1691 g_source_remove(renderer->update_playcount_id);
1692 renderer->update_playcount_id = 0;
1698 * Constructs gst pipeline
1700 * FIXME: Could the same pipeline be used for playing all media instead of
1701 * constantly deleting and reconstructing it again?
1703 static void _construct_pipeline(MafwGstRendererWorker *worker)
1705 g_debug("constructing pipeline");
1706 g_assert(worker != NULL);
1708 /* Return if we have already one */
1709 if (worker->pipeline)
1712 _free_taglist(worker);
1714 g_debug("Creating a new instance of playbin2");
1715 worker->pipeline = gst_element_factory_make("playbin2",
1717 if (worker->pipeline == NULL)
1719 /* Let's try with playbin */
1720 g_warning ("playbin2 failed, falling back to playbin");
1721 worker->pipeline = gst_element_factory_make("playbin",
1724 if (worker->pipeline) {
1725 /* Use nwqueue only for non-rtsp and non-mms(h)
1728 use_nw = worker->media.location &&
1729 !g_str_has_prefix(worker->media.location,
1731 !g_str_has_prefix(worker->media.location,
1733 !g_str_has_prefix(worker->media.location,
1736 g_debug("playbin using network queue: %d", use_nw);
1738 /* These need a modified version of playbin. */
1739 g_object_set(G_OBJECT(worker->pipeline),
1740 "nw-queue", use_nw, NULL);
1741 g_object_set(G_OBJECT(worker->pipeline),
1742 "no-video-transform", TRUE, NULL);
1746 if (!worker->pipeline) {
1747 g_critical("failed to create playback pipeline");
1748 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1750 MAFW_RENDERER_ERROR,
1751 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1752 "Could not create pipeline");
1753 g_assert_not_reached();
1757 worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline));
1758 gst_bus_set_sync_handler(worker->bus,
1759 (GstBusSyncHandler)_sync_bus_handler, worker);
1760 worker->async_bus_id = gst_bus_add_watch_full(worker->bus,G_PRIORITY_HIGH,
1761 (GstBusFunc)_async_bus_handler,
1764 /* Listen for changes in stream-info object to find out whether the
1765 * media contains video and throw error if application has not provided
1767 g_signal_connect(worker->pipeline, "notify::stream-info",
1768 G_CALLBACK(_stream_info_cb), worker);
1770 #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1771 g_object_set(worker->pipeline, "flags", 99, NULL);
1773 /* Set audio and video sinks ourselves. We create and configure
1775 if (!worker->asink) {
1776 worker->asink = gst_element_factory_make("pulsesink", NULL);
1777 if (!worker->asink) {
1778 g_critical("Failed to create pipeline audio sink");
1779 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1781 MAFW_RENDERER_ERROR,
1782 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1783 "Could not create audio sink");
1784 g_assert_not_reached();
1786 gst_object_ref(worker->asink);
1787 g_object_set(worker->asink, "buffer-time",
1788 (gint64) MAFW_GST_BUFFER_TIME, NULL);
1789 g_object_set(worker->asink, "latency-time",
1790 (gint64) MAFW_GST_LATENCY_TIME, NULL);
1792 g_object_set(worker->pipeline, "audio-sink", worker->asink, NULL);
1795 if (!worker->vsink) {
1796 worker->vsink = gst_element_factory_make("xvimagesink", NULL);
1797 if (!worker->vsink) {
1798 g_critical("Failed to create pipeline video sink");
1799 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner),
1801 MAFW_RENDERER_ERROR,
1802 MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1803 "Could not create video sink");
1804 g_assert_not_reached();
1806 gst_object_ref(worker->vsink);
1807 g_object_set(G_OBJECT(worker->vsink), "handle-events",
1809 g_object_set(worker->vsink, "force-aspect-ratio",
1812 g_object_set(worker->pipeline, "video-sink", worker->vsink, NULL);
1816 * @seek_type: GstSeekType
1817 * @position: Time in seconds where to seek
1819 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
1820 gint position, GError **error)
1825 g_assert(worker != NULL);
1827 if (worker->eos || !worker->media.seekable)
1830 /* According to the docs, relative seeking is not so easy:
1831 GST_SEEK_TYPE_CUR - change relative to currently configured segment.
1832 This can't be used to seek relative to the current playback position -
1833 do a position query, calculate the desired position and then do an
1834 absolute position seek instead if that's what you want to do. */
1835 if (seek_type == GST_SEEK_TYPE_CUR)
1837 gint curpos = mafw_gst_renderer_worker_get_position(worker);
1838 position = curpos + position;
1839 seek_type = GST_SEEK_TYPE_SET;
1846 worker->seek_position = position;
1847 worker->report_statechanges = FALSE;
1848 spos = (gint64)position * GST_SECOND;
1849 g_debug("seek: type = %d, offset = %lld", seek_type, spos);
1851 /* If the pipeline has been set to READY by us, then wake it up by
1852 setting it to PAUSED (when we get the READY->PAUSED transition
1853 we will execute the seek). This way when we seek we disable the
1854 READY state (logical, since the player is not idle anymore)
1855 allowing the sink to render the destination frame in case of
1857 if (worker->in_ready && worker->state == GST_STATE_READY) {
1858 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
1860 ret = gst_element_seek(worker->pipeline, 1.0, GST_FORMAT_TIME,
1861 GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_KEY_UNIT,
1863 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
1865 /* Seeking is async, so seek_position should not be
1872 err: g_set_error(error,
1873 MAFW_RENDERER_ERROR,
1874 MAFW_RENDERER_ERROR_CANNOT_SET_POSITION,
1875 "Seeking to %d failed", position);
1878 /* @vol should be between [0 .. 100], higher values (up to 1000) are allowed,
1879 * but probably cause distortion. */
1880 void mafw_gst_renderer_worker_set_volume(
1881 MafwGstRendererWorker *worker, guint volume)
1883 _set_volume(worker, CLAMP((gdouble)volume / 100.0, 0.0, 1.0));
1886 guint mafw_gst_renderer_worker_get_volume(
1887 MafwGstRendererWorker *worker)
1890 (mafw_gst_renderer_worker_volume_get(worker->wvolume) * 100);
1893 void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker,
1896 _set_mute(worker, mute);
1899 gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker)
1901 return mafw_gst_renderer_worker_volume_is_muted(worker->wvolume);
1904 #ifdef HAVE_GDKPIXBUF
1905 void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker,
1906 gboolean current_frame_on_pause)
1908 worker->current_frame_on_pause = current_frame_on_pause;
1911 gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker)
1913 return worker->current_frame_on_pause;
1917 void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker,
1918 GstSeekType seek_type,
1919 gint position, GError **error)
1921 /* If player is paused and we have a timeout for going to ready
1922 * restart it. This is logical, since the user is seeking and
1923 * thus, the player is not idle anymore. Also this prevents that
1924 * when seeking streams we enter buffering and in the middle of
1925 * the buffering process we set the pipeline to ready (which stops
1926 * the buffering before it reaches 100%, making the client think
1927 * buffering is still going on).
1929 if (worker->ready_timeout) {
1930 _remove_ready_timeout(worker);
1931 _add_ready_timeout(worker);
1934 _do_seek(worker, seek_type, position, error);
1935 if (worker->notify_seek_handler)
1936 worker->notify_seek_handler(worker, worker->owner);
1940 * Gets current position, rounded down into precision of one second. If a seek
1941 * is pending, returns the position we are going to seek. Returns -1 on
1944 gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker)
1948 g_assert(worker != NULL);
1950 /* If seek is ongoing, return the position where we are seeking. */
1951 if (worker->seek_position != -1)
1953 return worker->seek_position;
1955 /* Otherwise query position from pipeline. */
1956 format = GST_FORMAT_TIME;
1957 if (worker->pipeline &&
1958 gst_element_query_position(worker->pipeline, &format, &time))
1960 return (gint)(NSECONDS_TO_SECONDS(time));
1965 GHashTable *mafw_gst_renderer_worker_get_current_metadata(
1966 MafwGstRendererWorker *worker)
1968 return worker->current_metadata;
1971 void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid)
1973 /* Check for errors on the target window */
1974 XSetErrorHandler(xerror);
1976 /* Store the target window id */
1977 g_debug("Setting xid: %x", (guint)xid);
1980 /* Check if we should use it right away */
1981 mafw_gst_renderer_worker_apply_xid(worker);
1984 XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker)
1989 gboolean mafw_gst_renderer_worker_get_autopaint(
1990 MafwGstRendererWorker *worker)
1992 return worker->autopaint;
1994 void mafw_gst_renderer_worker_set_autopaint(
1995 MafwGstRendererWorker *worker, gboolean autopaint)
1997 worker->autopaint = autopaint;
1999 g_object_set(worker->vsink, "autopaint-colorkey",
2003 gint mafw_gst_renderer_worker_get_colorkey(
2004 MafwGstRendererWorker *worker)
2006 return worker->colorkey;
2009 gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker)
2011 return worker->media.seekable;
2014 static void _play_pl_next(MafwGstRendererWorker *worker) {
2017 g_assert(worker != NULL);
2018 g_return_if_fail(worker->pl.items != NULL);
2020 next = (gchar *) g_slist_nth_data(worker->pl.items,
2021 ++worker->pl.current);
2022 mafw_gst_renderer_worker_stop(worker);
2023 _reset_media_info(worker);
2025 worker->media.location = g_strdup(next);
2026 _construct_pipeline(worker);
2027 _start_play(worker);
2030 static void _on_pl_entry_parsed(TotemPlParser *parser, gchar *uri,
2031 gpointer metadata, gpointer user_data)
2033 MafwGstRendererWorker *worker = user_data;
2037 g_slist_append(worker->pl.items, g_strdup(uri));
2041 static void _do_play(MafwGstRendererWorker *worker)
2043 g_assert(worker != NULL);
2045 if (worker->pipeline == NULL) {
2046 g_debug("play without a pipeline!");
2049 worker->report_statechanges = TRUE;
2051 /* If we have to stay paused, we do and add the ready
2052 * timeout. Otherwise, we move the pipeline */
2053 if (!worker->stay_paused) {
2054 /* If pipeline is READY, we move it to PAUSED,
2055 * otherwise, to PLAYING */
2056 if (worker->state == GST_STATE_READY) {
2057 gst_element_set_state(worker->pipeline,
2059 g_debug("setting pipeline to PAUSED");
2061 _reset_volume_and_mute_to_pipeline(worker);
2062 gst_element_set_state(worker->pipeline,
2064 g_debug("setting pipeline to PLAYING");
2068 g_debug("staying in PAUSED state");
2069 _add_ready_timeout(worker);
2073 void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker,
2078 mafw_gst_renderer_worker_stop(worker);
2079 _reset_media_info(worker);
2080 _reset_pl_info(worker);
2081 /* Check if the item to play is a single item or a playlist. */
2082 if (uri_is_playlist(uri)){
2083 /* In case of a playlist we parse it and start playing the first
2084 item of the playlist. */
2085 TotemPlParser *pl_parser;
2088 /* Initialize the playlist parser */
2089 pl_parser = totem_pl_parser_new ();
2090 g_object_set(pl_parser, "recurse", TRUE, "disable-unsafe",
2092 g_signal_connect(G_OBJECT(pl_parser), "entry-parsed",
2093 G_CALLBACK(_on_pl_entry_parsed), worker);
2096 if (totem_pl_parser_parse(pl_parser, uri, FALSE) !=
2097 TOTEM_PL_PARSER_RESULT_SUCCESS) {
2098 /* An error happens while parsing */
2100 g_error_new(MAFW_RENDERER_ERROR,
2101 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2102 "Playlist parsing failed: %s",
2107 if (!worker->pl.items) {
2108 /* The playlist is empty */
2110 g_error_new(MAFW_RENDERER_ERROR,
2111 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2112 "The playlist %s is empty.",
2117 /* Set the playback mode */
2118 worker->mode = WORKER_MODE_PLAYLIST;
2119 worker->pl.notify_play_pending = TRUE;
2121 /* Set the item to be played */
2122 worker->pl.current = 0;
2123 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2124 worker->media.location = g_strdup(item);
2126 /* Free the playlist parser */
2127 g_object_unref(pl_parser);
2129 /* Single item. Set the playback mode according to that */
2130 worker->mode = WORKER_MODE_SINGLE_PLAY;
2132 /* Set the item to be played */
2133 worker->media.location = g_strdup(uri);
2135 _construct_pipeline(worker);
2136 _start_play(worker);
2139 void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker,
2145 g_assert(uris && uris[0]);
2147 mafw_gst_renderer_worker_stop(worker);
2148 _reset_media_info(worker);
2149 _reset_pl_info(worker);
2151 /* Add the uris to playlist */
2155 g_slist_append(worker->pl.items, g_strdup(uris[i]));
2159 /* Set the playback mode */
2160 worker->mode = WORKER_MODE_REDUNDANT;
2161 worker->pl.notify_play_pending = TRUE;
2163 /* Set the item to be played */
2164 worker->pl.current = 0;
2165 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2166 worker->media.location = g_strdup(item);
2169 _construct_pipeline(worker);
2170 _start_play(worker);
2174 * Currently, stop destroys the Gst pipeline and resets the worker into
2175 * default startup configuration.
2177 void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker)
2179 g_debug("worker stop");
2180 g_assert(worker != NULL);
2182 /* If location is NULL, this is a pre-created pipeline */
2183 if (worker->async_bus_id && worker->pipeline && !worker->media.location)
2186 if (worker->pipeline) {
2187 g_debug("destroying pipeline");
2188 if (worker->async_bus_id) {
2189 g_source_remove(worker->async_bus_id);
2190 worker->async_bus_id = 0;
2192 gst_bus_set_sync_handler(worker->bus, NULL, NULL);
2193 gst_element_set_state(worker->pipeline, GST_STATE_NULL);
2195 gst_object_unref(GST_OBJECT_CAST(worker->bus));
2198 gst_object_unref(GST_OBJECT(worker->pipeline));
2199 worker->pipeline = NULL;
2203 worker->report_statechanges = TRUE;
2204 worker->state = GST_STATE_NULL;
2205 worker->prerolling = FALSE;
2206 worker->is_live = FALSE;
2207 worker->buffering = FALSE;
2208 worker->is_stream = FALSE;
2209 worker->is_error = FALSE;
2210 worker->eos = FALSE;
2211 worker->seek_position = -1;
2212 _remove_ready_timeout(worker);
2213 _free_taglist(worker);
2214 if (worker->current_metadata) {
2215 g_hash_table_destroy(worker->current_metadata);
2216 worker->current_metadata = NULL;
2219 if (worker->duration_seek_timeout != 0) {
2220 g_source_remove(worker->duration_seek_timeout);
2221 worker->duration_seek_timeout = 0;
2224 /* Reset media iformation */
2225 _reset_media_info(worker);
2227 /* We are not playing, so we can let the screen blank */
2230 /* And now get a fresh pipeline ready */
2231 _construct_pipeline(worker);
2234 void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker)
2236 g_assert(worker != NULL);
2238 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2239 !worker->prerolling) {
2240 /* If we are buffering and get a pause, we have to
2241 * signal state change and stay_paused */
2242 g_debug("Pausing while buffering, signalling state change");
2243 worker->stay_paused = TRUE;
2244 if (worker->notify_pause_handler) {
2245 worker->notify_pause_handler(
2250 worker->report_statechanges = TRUE;
2252 if (gst_element_set_state(worker->pipeline, GST_STATE_PAUSED) ==
2253 GST_STATE_CHANGE_ASYNC)
2255 /* XXX this blocks at most 2 seconds. */
2256 gst_element_get_state(worker->pipeline, NULL, NULL,
2263 void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker)
2265 if (worker->mode == WORKER_MODE_PLAYLIST ||
2266 worker->mode == WORKER_MODE_REDUNDANT) {
2267 /* We must notify play if the "playlist" playback
2269 worker->pl.notify_play_pending = TRUE;
2271 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2272 !worker->prerolling) {
2273 /* If we are buffering we cannot resume, but we know
2274 * that the pipeline will be moved to PLAYING as
2275 * stay_paused is FALSE, so we just activate the state
2276 * change report, this way as soon as buffering is finished
2277 * the pipeline will be set to PLAYING and the state
2278 * change will be reported */
2279 worker->report_statechanges = TRUE;
2280 g_debug("Resumed while buffering, activating pipeline state "
2282 /* Notice though that we can receive the Resume before
2283 we get any buffering information. In that case
2284 we go with the "else" branch and set the pipeline to
2285 to PLAYING. However, it is possible that in this case
2286 we get the fist buffering signal before the
2287 PAUSED -> PLAYING state change. In that case, since we
2288 ignore state changes while buffering we never signal
2289 the state change to PLAYING. We can only fix this by
2290 checking, when we receive a PAUSED -> PLAYING transition
2291 if we are buffering, and in that case signal the state
2292 change (if we get that transition while buffering
2293 is on, it can only mean that the client resumed playback
2294 while buffering, and we must notify the state change) */
2300 static void _volume_init_cb(MafwGstRendererWorkerVolume *wvolume,
2303 MafwGstRendererWorker *worker = data;
2307 worker->wvolume = wvolume;
2309 g_debug("volume manager initialized");
2311 volume = mafw_gst_renderer_worker_volume_get(wvolume);
2312 mute = mafw_gst_renderer_worker_volume_is_muted(wvolume);
2313 _volume_cb(wvolume, volume, worker);
2314 _mute_cb(wvolume, mute, worker);
2317 MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner)
2319 MafwGstRendererWorker *worker;
2320 GMainContext *main_context;
2322 worker = g_new0(MafwGstRendererWorker, 1);
2323 worker->mode = WORKER_MODE_SINGLE_PLAY;
2324 worker->pl.items = NULL;
2325 worker->pl.current = 0;
2326 worker->pl.notify_play_pending = TRUE;
2327 worker->owner = owner;
2328 worker->report_statechanges = TRUE;
2329 worker->state = GST_STATE_NULL;
2330 worker->seek_position = -1;
2331 worker->ready_timeout = 0;
2332 worker->in_ready = FALSE;
2334 worker->autopaint = TRUE;
2335 worker->colorkey = -1;
2336 worker->vsink = NULL;
2337 worker->asink = NULL;
2338 worker->tag_list = NULL;
2339 worker->current_metadata = NULL;
2341 #ifdef HAVE_GDKPIXBUF
2342 worker->current_frame_on_pause = FALSE;
2343 _init_tmp_files_pool(worker);
2345 worker->notify_seek_handler = NULL;
2346 worker->notify_pause_handler = NULL;
2347 worker->notify_play_handler = NULL;
2348 worker->notify_buffer_status_handler = NULL;
2349 worker->notify_eos_handler = NULL;
2350 worker->notify_error_handler = NULL;
2351 Global_worker = worker;
2352 main_context = g_main_context_default();
2353 worker->wvolume = NULL;
2354 mafw_gst_renderer_worker_volume_init(main_context,
2355 _volume_init_cb, worker,
2359 _construct_pipeline(worker);
2364 void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker)
2367 #ifdef HAVE_GDKPIXBUF
2368 _destroy_tmp_files_pool(worker);
2370 mafw_gst_renderer_worker_volume_destroy(worker->wvolume);
2371 mafw_gst_renderer_worker_stop(worker);
2373 /* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */