0cd1ec442dc4dec61a189ce1ae78a5d64469065e
[mafwsubrenderer] / libmafw-gst-renderer / mafw-gst-renderer-worker.c
1 /*
2  * This file is a part of MAFW
3  *
4  * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Visa Smolander <visa.smolander@nokia.com>
7  *
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.
12  *
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.
17  *
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
21  * 02110-1301 USA
22  *
23  */
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <string.h>
29 #include <glib.h>
30 #include <X11/Xlib.h>
31 #include <gst/interfaces/xoverlay.h>
32 #include <gst/pbutils/missing-plugins.h>
33 #include <gst/base/gstbasesink.h>
34 #include <libmafw/mafw.h>
35
36 #ifdef HAVE_GDKPIXBUF
37 #include <gdk-pixbuf/gdk-pixbuf.h>
38 #include <glib/gstdio.h>
39 #include <unistd.h>
40 #include "gstscreenshot.h"
41 #endif
42
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"
47 #include "blanking.h"
48
49 #undef  G_LOG_DOMAIN
50 #define G_LOG_DOMAIN "mafw-gst-renderer-worker"
51
52 #define MAFW_GST_RENDERER_WORKER_SECONDS_READY 60
53 #define MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY 4
54
55 #define MAFW_GST_MISSING_TYPE_DECODER "decoder"
56 #define MAFW_GST_MISSING_TYPE_ENCODER "encoder"
57
58 #define MAFW_GST_BUFFER_TIME  600000L
59 #define MAFW_GST_LATENCY_TIME (MAFW_GST_BUFFER_TIME / 2)
60
61 #define NSECONDS_TO_SECONDS(ns) ((ns)%1000000000 < 500000000?\
62                                  GST_TIME_AS_SECONDS((ns)):\
63                                  GST_TIME_AS_SECONDS((ns))+1)
64
65 /* Private variables. */
66 /* Global reference to worker instance, needed for Xerror handler */
67 static MafwGstRendererWorker *Global_worker = NULL;
68
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);
74
75 static void _emit_metadatas(MafwGstRendererWorker *worker);
76
77 static void _current_metadata_add(MafwGstRendererWorker *worker,
78                                   const gchar *key, GType type,
79                                   const gpointer value);
80
81 /*
82  * Sends @error to MafwGstRenderer.  Only call this from the glib main thread, or
83  * face the consequences.  @err is free'd.
84  */
85 static void _send_error(MafwGstRendererWorker *worker, GError *err)
86 {
87         worker->is_error = TRUE;
88         if (worker->notify_error_handler)
89                 worker->notify_error_handler(worker, worker->owner, err);
90         g_error_free(err);
91 }
92
93 /*
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.
96  */
97 static void _post_error(MafwGstRendererWorker *worker, GError *err)
98 {
99         gst_bus_post(worker->bus,
100                      gst_message_new_error(GST_OBJECT(worker->pipeline),
101                                            err, NULL));
102         g_error_free(err);
103 }
104
105 #ifdef HAVE_GDKPIXBUF
106 typedef struct {
107         MafwGstRendererWorker *worker;
108         gchar *metadata_key;
109         GdkPixbuf *pixbuf;
110 } SaveGraphicData;
111
112 static gchar *_init_tmp_file(void)
113 {
114         gint fd;
115         gchar *path = NULL;
116
117         fd = g_file_open_tmp("mafw-gst-renderer-XXXXXX.jpeg", &path, NULL);
118         close(fd);
119
120         return path;
121 }
122
123 static void _init_tmp_files_pool(MafwGstRendererWorker *worker)
124 {
125         guint8 i;
126
127         worker->tmp_files_pool_index = 0;
128
129         for (i = 0; i < MAFW_GST_RENDERER_MAX_TMP_FILES; i++) {
130                 worker->tmp_files_pool[i] = NULL;
131         }
132 }
133
134 static void _destroy_tmp_files_pool(MafwGstRendererWorker *worker)
135 {
136         guint8 i;
137
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]);
142         }
143 }
144
145 static const gchar *_get_tmp_file_from_pool(
146                         MafwGstRendererWorker *worker)
147 {
148         gchar *path = worker->tmp_files_pool[worker->tmp_files_pool_index];
149
150         if (path == NULL) {
151                 path = _init_tmp_file();
152                 worker->tmp_files_pool[worker->tmp_files_pool_index] = path;
153         }
154
155         if (++(worker->tmp_files_pool_index) >=
156             MAFW_GST_RENDERER_MAX_TMP_FILES) {
157                 worker->tmp_files_pool_index = 0;
158         }
159
160         return path;
161 }
162
163 static void _destroy_pixbuf (guchar *pixbuf, gpointer data)
164 {
165         gst_buffer_unref(GST_BUFFER(data));
166 }
167
168 static void _emit_gst_buffer_as_graphic_file_cb(GstBuffer *new_buffer,
169                                                 gpointer user_data)
170 {
171         SaveGraphicData *sgd = user_data;
172         GdkPixbuf *pixbuf = NULL;
173
174         if (new_buffer != NULL) {
175                 gint width, height;
176                 GstStructure *structure;
177
178                 structure =
179                         gst_caps_get_structure(GST_BUFFER_CAPS(new_buffer), 0);
180
181                 gst_structure_get_int(structure, "width", &width);
182                 gst_structure_get_int(structure, "height", &height);
183
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,
188                         new_buffer);
189
190                 if (sgd->pixbuf != NULL) {
191                         g_object_unref(sgd->pixbuf);
192                         sgd->pixbuf = NULL;
193                 }
194         } else {
195                 pixbuf = sgd->pixbuf;
196         }
197
198         if (pixbuf != NULL) {
199                 gboolean save_ok;
200                 GError *error = NULL;
201                 const gchar *filename;
202
203                 filename = _get_tmp_file_from_pool(sgd->worker);
204
205                 save_ok = gdk_pixbuf_save (pixbuf, filename, "jpeg", &error,
206                                            NULL);
207
208                 g_object_unref (pixbuf);
209
210                 if (save_ok) {
211                         /* Add the info to the current metadata. */
212                         _current_metadata_add(sgd->worker, sgd->metadata_key,
213                                               G_TYPE_STRING,
214                                               (const gpointer) filename);
215
216                         /* Emit the metadata. */
217                         mafw_renderer_emit_metadata_string(sgd->worker->owner,
218                                                            sgd->metadata_key,
219                                                            (gchar *) filename);
220                 } else {
221                         if (error != NULL) {
222                                 g_warning ("%s\n", error->message);
223                                 g_error_free (error);
224                         } else {
225                                 g_critical("Unknown error when saving pixbuf "
226                                            "with GStreamer data");
227                         }
228                 }
229         } else {
230                 g_warning("Could not create pixbuf from GstBuffer");
231         }
232
233         g_free(sgd->metadata_key);
234         g_free(sgd);
235 }
236
237 static void _pixbuf_size_prepared_cb (GdkPixbufLoader *loader, 
238                                       gint width, gint height,
239                                       gpointer user_data)
240 {
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);
244                 gdouble ar;
245                 ar = (gdouble) width / height;
246                 if (width > height) {
247                         width = 512;
248                         height = width / ar;
249                 } else {
250                         height = 512;
251                         width = height * ar;
252                 }
253                 g_debug ("pixbuf: scaled image to %dx%d", width, height);
254                 gdk_pixbuf_loader_set_size (loader, width, height);
255         }
256 }
257
258 static void _emit_gst_buffer_as_graphic_file(MafwGstRendererWorker *worker,
259                                              GstBuffer *buffer,
260                                              const gchar *metadata_key)
261 {
262         GdkPixbufLoader *loader;
263         GstStructure *structure;
264         const gchar *mime = NULL;
265         GError *error = NULL;
266
267         g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
268
269         structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0);
270         mime = gst_structure_get_name(structure);
271
272         if (g_str_has_prefix(mime, "video/x-raw")) {
273                 gint framerate_d, framerate_n;
274                 GstCaps *to_caps;
275                 SaveGraphicData *sgd;
276
277                 gst_structure_get_fraction (structure, "framerate",
278                                             &framerate_n, &framerate_d);
279
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,
287                                                "endianness",
288                                                G_TYPE_INT, G_BIG_ENDIAN,
289                                                "red_mask", G_TYPE_INT,
290                                                0xff0000,
291                                                "green_mask",
292                                                G_TYPE_INT, 0x00ff00,
293                                                "blue_mask",
294                                                G_TYPE_INT, 0x0000ff,
295                                                NULL);
296
297                 sgd = g_new0(SaveGraphicData, 1);
298                 sgd->worker = worker;
299                 sgd->metadata_key = g_strdup(metadata_key);
300
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,
304                                         sgd);
305         } else {
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);
310
311                 if (loader == NULL) {
312                         g_warning ("%s\n", error->message);
313                         g_error_free (error);
314                 } else {
315                         if (!gdk_pixbuf_loader_write (loader,
316                                                       GST_BUFFER_DATA(buffer),
317                                                       GST_BUFFER_SIZE(buffer),
318                                                       &error)) {
319                                 g_warning ("%s\n", error->message);
320                                 g_error_free (error);
321
322                                 gdk_pixbuf_loader_close (loader, NULL);
323                         } else {
324                                 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
325
326                                 if (!gdk_pixbuf_loader_close (loader, &error)) {
327                                         g_warning ("%s\n", error->message);
328                                         g_error_free (error);
329
330                                         g_object_unref(pixbuf);
331                                 } else {
332                                         SaveGraphicData *sgd;
333
334                                         sgd = g_new0(SaveGraphicData, 1);
335
336                                         sgd->worker = worker;
337                                         sgd->metadata_key =
338                                                 g_strdup(metadata_key);
339                                         sgd->pixbuf = pixbuf;
340
341                                         _emit_gst_buffer_as_graphic_file_cb(
342                                                 NULL, sgd);
343                                 }
344                         }
345                 }
346         }
347 }
348 #endif
349
350 static gboolean _go_to_gst_ready(gpointer user_data)
351 {
352         MafwGstRendererWorker *worker = user_data;
353
354         g_return_val_if_fail(worker->state == GST_STATE_PAUSED ||
355                              worker->prerolling, FALSE);
356
357         worker->seek_position =
358                 mafw_gst_renderer_worker_get_position(worker);
359
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;
364
365         return FALSE;
366 }
367
368 static void _add_ready_timeout(MafwGstRendererWorker *worker)
369 {
370         if (worker->media.seekable) {
371                 if (!worker->ready_timeout)
372                 {
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,
377                                         _go_to_gst_ready,
378                                         worker);
379                 }
380         } else {
381                 g_debug("Not adding timeout to go to GST_STATE_READY as media "
382                         "is not seekable");
383                 worker->ready_timeout = 0;
384         }
385 }
386
387 static void _remove_ready_timeout(MafwGstRendererWorker *worker)
388 {
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;
393         }
394         worker->in_ready = FALSE;
395 }
396
397 static gboolean _emit_video_info(MafwGstRendererWorker *worker)
398 {
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,
407                                        worker->media.fps);
408         return FALSE;
409 }
410
411 /*
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.
416  *
417  * Returns: TRUE if video details are acceptable.
418  */
419 static gboolean _handle_video_info(MafwGstRendererWorker *worker,
420                                    const GstStructure *structure)
421 {
422         gint width, height;
423         gdouble fps;
424
425         width = height = 0;
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"))
430         {
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;
437         }
438
439         fps = 1.0;
440         if (gst_structure_has_field(structure, "framerate"))
441         {
442                 gint fps_n, fps_d;
443
444                 gst_structure_get_fraction(structure, "framerate",
445                                            &fps_n, &fps_d);
446                 if (fps_d > 0)
447                         fps = (gdouble)fps_n / (gdouble)fps_d;
448                 g_debug("video fps: %f", fps);
449         }
450
451         worker->media.video_width = width;
452         worker->media.video_height = height;
453         worker->media.fps = fps;
454
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);
459
460         *p_width = width;* p_height = height; *p_fps = fps;
461
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,
467                               G_TYPE_DOUBLE,
468                               (const gpointer) p_fps);
469
470         g_free(p_width); g_free(p_height); g_free(p_fps);
471
472         /* Emit the metadata.*/
473         g_idle_add((GSourceFunc)_emit_video_info, worker);
474
475         return TRUE;
476 }
477
478 static void _parse_stream_info_item(MafwGstRendererWorker *worker, GObject *obj)
479 {
480         GParamSpec *pspec;
481         GEnumValue *val;
482         gint type;
483
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);
487         if (!val)
488                 return;
489         if (!g_ascii_strcasecmp(val->value_nick, "video") ||
490             !g_ascii_strcasecmp(val->value_name, "video"))
491         {
492                 GstCaps *vcaps;
493                 GstObject *object;
494
495                 object = NULL;
496                 g_object_get(obj, "object", &object, NULL);
497                 vcaps = NULL;
498                 if (object) {
499                         vcaps = gst_pad_get_caps(GST_PAD_CAST(object));
500                 } else {
501                         g_object_get(obj, "caps", &vcaps, NULL);
502                         gst_caps_ref(vcaps);
503                 }
504                 if (vcaps) {
505                         if (gst_caps_is_fixed(vcaps))
506                         {
507                                 _handle_video_info(
508                                         worker,
509                                         gst_caps_get_structure(vcaps, 0));
510                         }
511                         gst_caps_unref(vcaps);
512                 }
513         }
514 }
515
516 /* It always returns FALSE, because it is used as an idle callback as well. */
517 static gboolean _parse_stream_info(MafwGstRendererWorker *worker)
518 {
519         GList *stream_info, *s;
520
521         stream_info = NULL;
522         if (g_object_class_find_property(G_OBJECT_GET_CLASS(worker->pipeline),
523                                          "stream-info"))
524         {
525                 g_object_get(worker->pipeline,
526                              "stream-info", &stream_info, NULL);
527         }
528         for (s = stream_info; s; s = g_list_next(s))
529                 _parse_stream_info_item(worker, G_OBJECT(s->data));
530         return FALSE;
531 }
532
533 static void mafw_gst_renderer_worker_apply_xid(MafwGstRendererWorker *worker)
534 {
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 */
537         if (worker->xid && 
538             worker->vsink && 
539             worker->media.has_visual_content)
540         {
541                 g_debug ("Setting overlay, window id: %x", 
542                          (gint) worker->xid);
543                 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(worker->vsink), 
544                                              worker->xid);
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)
548                 {
549                         gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink));
550                 }
551         } else {
552                 g_debug("Not setting overlay for window id: %x", 
553                         (gint) worker->xid);
554         }
555 }
556
557 /*
558  * GstBus synchronous message handler.  NOTE that this handler is NOT invoked
559  * from the glib thread, so be careful what you do here.
560  */
561 static GstBusSyncReply _sync_bus_handler(GstBus *bus, GstMessage *msg,
562                                          MafwGstRendererWorker *worker)
563 {
564         if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
565             gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
566         {
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
570                  * ourselves. */
571                 if (!worker->xid) {
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!");
577                         _post_error(worker,
578                                     g_error_new_literal(
579                                             MAFW_RENDERER_ERROR,
580                                             MAFW_RENDERER_ERROR_PLAYBACK,
581                                             "No video window XID set"));
582                         return GST_BUS_DROP;
583                 } else {
584                         g_debug ("Video window to use is: %x", 
585                                  (gint) worker->xid);
586                 }
587
588                 /* Instruct vsink to use the client-provided window */
589                 mafw_gst_renderer_worker_apply_xid(worker);
590
591                 /* Handle colorkey and autopaint */
592                 mafw_gst_renderer_worker_set_autopaint(
593                         worker,
594                         worker->autopaint);
595                 g_object_get(worker->vsink,
596                              "colorkey", &worker->colorkey, NULL);
597                 /* Defer the signal emission to the thread running the
598                  * mainloop. */
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")));
604                 }
605                 return GST_BUS_DROP;
606         }
607         return GST_BUS_PASS;
608 }
609
610 static void _free_taglist_item(GstMessage *msg, gpointer data)
611 {
612         gst_message_unref(msg);
613 }
614
615 static void _free_taglist(MafwGstRendererWorker *worker)
616 {
617         if (worker->tag_list != NULL)
618         {
619                 g_ptr_array_foreach(worker->tag_list, (GFunc)_free_taglist_item,
620                                     NULL);
621                 g_ptr_array_free(worker->tag_list, TRUE);
622                 worker->tag_list = NULL;
623         }
624 }
625
626 static gboolean _seconds_duration_equal(gint64 duration1, gint64 duration2)
627 {
628         gint64 duration1_seconds, duration2_seconds;
629
630         duration1_seconds = NSECONDS_TO_SECONDS(duration1);
631         duration2_seconds = NSECONDS_TO_SECONDS(duration2);
632
633         return duration1_seconds == duration2_seconds;
634 }
635
636 static void _check_duration(MafwGstRendererWorker *worker, gint64 value)
637 {
638         MafwGstRenderer *renderer = worker->owner;
639         gboolean right_query = TRUE;
640
641         if (value == -1) {
642                 GstFormat format = GST_FORMAT_TIME;
643                 right_query =
644                         gst_element_query_duration(worker->pipeline, &format,
645                                                    &value);
646         }
647
648         if (right_query && value > 0) {
649                 gint duration_seconds = NSECONDS_TO_SECONDS(value);
650
651                 if (!_seconds_duration_equal(worker->media.length_nanos,
652                                              value)) {
653                         gint64 *duration = g_new0(gint64, 1);
654                         *duration = duration_seconds;
655
656                         /* Add the duration to the current metadata. */
657                         _current_metadata_add(worker,
658                                               MAFW_METADATA_KEY_DURATION,
659                                               G_TYPE_INT64,
660                                               (const gpointer) duration);
661
662                         /* Emit the duration. */
663                         mafw_renderer_emit_metadata_int64(
664                                 worker->owner, MAFW_METADATA_KEY_DURATION,
665                                 *duration);
666                         g_free(duration);
667                 }
668
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(
673                                 renderer,
674                                 duration_seconds);
675                 }
676         }
677
678         worker->media.length_nanos = value;
679         g_debug("media duration: %lld", worker->media.length_nanos);
680 }
681
682 static void _check_seekability(MafwGstRendererWorker *worker)
683 {
684         MafwGstRenderer *renderer = worker->owner;
685         SeekabilityType seekable = SEEKABILITY_NO_SEEKABLE;
686
687         if (worker->media.length_nanos != -1)
688         {
689                 g_debug("source seekability %d", renderer->media->seekability);
690
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,
700                                                         &renderer_seekable,
701                                                         NULL, NULL);
702                                 g_debug("GStreamer seekability %d",
703                                         renderer_seekable);
704                                 seekable = renderer_seekable ?
705                                         SEEKABILITY_SEEKABLE :
706                                         SEEKABILITY_NO_SEEKABLE;
707                         }
708                         gst_query_unref(seek_query);
709                 }
710         }
711
712         if (worker->media.seekable != seekable) {
713                 gboolean *is_seekable = g_new0(gboolean, 1);
714                 *is_seekable = (seekable == SEEKABILITY_SEEKABLE) ? TRUE : FALSE;
715
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);
719
720                 /* Emit. */
721                 mafw_renderer_emit_metadata_boolean(
722                         worker->owner, MAFW_METADATA_KEY_IS_SEEKABLE,
723                         *is_seekable);
724
725                 g_free(is_seekable);
726         }
727
728         g_debug("media seekable: %d", seekable);
729         worker->media.seekable = seekable;
730 }
731
732 static gboolean _query_duration_and_seekability_timeout(gpointer data)
733 {
734         MafwGstRendererWorker *worker = data;
735
736         _check_duration(worker, -1);
737         _check_seekability(worker);
738
739         worker->duration_seek_timeout = 0;
740
741         return FALSE;
742 }
743
744 /*
745  * Called when the pipeline transitions into PAUSED state.  It extracts more
746  * information from Gst.
747  */
748 static void _finalize_startup(MafwGstRendererWorker *worker)
749 {
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))
758                                 return;
759                 }
760         }
761
762         /* Something might have gone wrong at this point already. */
763         if (worker->is_error) {
764                 g_debug("Error occured during preroll");
765                 return;
766         }
767
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");
773                 return;
774         }
775
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;
780         }
781         _check_duration(worker, -1);
782         _check_seekability(worker);
783 }
784
785 static void _add_duration_seek_query_timeout(MafwGstRendererWorker *worker)
786 {
787         if (worker->duration_seek_timeout != 0) {
788                 g_source_remove(worker->duration_seek_timeout);
789         }
790         worker->duration_seek_timeout = g_timeout_add_seconds(
791                 MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY,
792                 _query_duration_and_seekability_timeout,
793                 worker);
794 }
795
796 static void _do_pause_postprocessing(MafwGstRendererWorker *worker)
797 {
798         if (worker->notify_pause_handler) {
799                 worker->notify_pause_handler(worker, worker->owner);
800         }
801
802 #ifdef HAVE_GDKPIXBUF
803         if (worker->media.has_visual_content &&
804             worker->current_frame_on_pause) {
805                 GstBuffer *buffer = NULL;
806
807                 g_object_get(worker->pipeline, "frame", &buffer, NULL);
808
809                 if (buffer != NULL) {
810                         _emit_gst_buffer_as_graphic_file(
811                                 worker, buffer,
812                                 MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI);
813                 }
814         }
815 #endif
816
817         _add_ready_timeout(worker);
818 }
819
820 static void _report_playing_state(MafwGstRendererWorker * worker)
821 {
822         if (worker->report_statechanges) {
823                 switch (worker->mode) {
824                 case WORKER_MODE_SINGLE_PLAY:
825                         /* Notify play if we are playing in
826                          * single mode */
827                         if (worker->notify_play_handler)
828                                 worker->notify_play_handler(
829                                         worker,
830                                         worker->owner);
831                         break;
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(
840                                                 worker,
841                                                 worker->owner);
842                                 worker->pl.notify_play_pending = FALSE;
843                         }
844                         break;
845                 default: break;
846                 }
847         }
848 }
849
850 static void _handle_state_changed(GstMessage *msg, MafwGstRendererWorker *worker)
851 {
852         GstState newstate, oldstate;
853         GstStateChange statetrans;
854         MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
855
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);
859
860         /* If the state is the same we do nothing, otherwise, we keep
861          * it */
862         if (worker->state == newstate) {
863                 return;
864         } else {
865                 worker->state = newstate;
866         }
867
868         if (statetrans == GST_STATE_CHANGE_READY_TO_PAUSED &&
869             worker->in_ready) {
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);
878                         } else {
879                                 g_critical("media is not seekable (and should)");
880                         }
881                 }
882
883                 /* If playing a stream wait for buffering to finish before
884                    starting to play */
885                 if (!worker->is_stream || worker->is_live) {
886                         _do_play(worker);
887                 }
888                 return;
889         }
890
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);                  
903                 }
904                 return;
905         }
906
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);
918                         _do_play(worker);
919                         renderer->play_failed_count = 0;
920
921                         if (worker->stay_paused) {
922                                 _do_pause_postprocessing(worker);
923                         }
924                         worker->prerolling = FALSE;
925                 }
926                 break;
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
930                  * ready */
931                 if (worker->report_statechanges) {
932                         _do_pause_postprocessing(worker);
933                 }
934                 break;
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;
938                 worker->eos = FALSE;
939
940                 /* Signal state change if needed */
941                 _report_playing_state(worker);
942
943                 /* Prevent blanking if we are playing video */
944                 if (worker->media.has_visual_content) {
945                         blanking_prohibit();
946                 }
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(
954                                 worker->owner,
955                                 MAFW_METADATA_KEY_URI,
956                                 worker->media.location);
957                 }
958
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);
965                 break;
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);
972                 }
973                 break;
974         default:
975                 break;
976         }
977 }
978
979 static void _handle_duration(MafwGstRendererWorker *worker, GstMessage *msg)
980 {
981         GstFormat fmt;
982         gint64 duration;
983
984         gst_message_parse_duration(msg, &fmt, &duration);
985
986         if (worker->duration_seek_timeout != 0) {
987                 g_source_remove(worker->duration_seek_timeout);
988                 worker->duration_seek_timeout = 0;
989         }
990
991         _check_duration(worker,
992                         duration != GST_CLOCK_TIME_NONE ? duration : -1);
993         _check_seekability(worker);
994 }
995
996 #ifdef HAVE_GDKPIXBUF
997 static void _emit_renderer_art(MafwGstRendererWorker *worker,
998                                const GstTagList *list)
999 {
1000         GstBuffer *buffer = NULL;
1001         const GValue *value = NULL;
1002
1003         g_return_if_fail(gst_tag_list_get_tag_size(list, GST_TAG_IMAGE) > 0);
1004
1005         value = gst_tag_list_get_value_index(list, GST_TAG_IMAGE, 0);
1006
1007         g_return_if_fail((value != NULL) && G_VALUE_HOLDS(value, GST_TYPE_BUFFER));
1008
1009         buffer = g_value_peek_pointer(value);
1010
1011         g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
1012
1013         _emit_gst_buffer_as_graphic_file(worker, buffer,
1014                                          MAFW_METADATA_KEY_RENDERER_ART_URI);
1015 }
1016 #endif
1017
1018
1019
1020 static void _current_metadata_add(MafwGstRendererWorker *worker,
1021                                   const gchar *key, GType type,
1022                                   const gpointer value)
1023 {
1024         g_return_if_fail(value != NULL);
1025
1026         if (!worker->current_metadata)
1027                 worker->current_metadata = mafw_metadata_new();
1028
1029         mafw_metadata_add_something(worker->current_metadata, key, type, 1, value);
1030 }
1031
1032 static GHashTable* _build_tagmap(void)
1033 {
1034         GHashTable *hash_table = NULL;
1035
1036         hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
1037                                            g_free);
1038
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));
1062 #endif
1063
1064         return hash_table;
1065 }
1066
1067 /*
1068  * Emits metadata-changed signals for gst tags.
1069  */
1070 static void _emit_tag(const GstTagList *list, const gchar *tag,
1071                       MafwGstRendererWorker *worker)
1072 {
1073         /* Mapping between Gst <-> MAFW metadata tags
1074          * NOTE: This assumes that GTypes matches between GST and MAFW. */
1075         static GHashTable *tagmap = NULL;
1076         gint i, count;
1077         const gchar *mafwtag;
1078         GType type;
1079         GValueArray *values;
1080
1081         if (tagmap == NULL) {
1082                 tagmap = _build_tagmap();
1083         }
1084
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);
1089         if (!mafwtag)
1090                 return;
1091
1092 #ifdef HAVE_GDKPIXBUF
1093         if (strcmp (mafwtag, MAFW_METADATA_KEY_RENDERER_ART_URI) == 0) {
1094                 _emit_renderer_art(worker, list);
1095                 return;
1096         }
1097 #endif
1098
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
1102          * strings. */
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) {
1110                         gchar *orig, *utf8;
1111
1112                         gst_tag_list_get_string_index(list, tag, i, &orig);
1113                         if (convert_utf8(orig, &utf8)) {
1114                                 GValue utf8gval = {0};
1115
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);
1122                         }
1123                         g_free(orig);
1124                 } else if (type == G_TYPE_UINT) {
1125                         GValue intgval = {0};
1126
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);
1133                 } else {
1134                         _current_metadata_add(worker, mafwtag, G_TYPE_VALUE,
1135                                         (const gpointer) v);
1136                         g_value_array_append(values, v);
1137                 }
1138         }
1139
1140         /* Emit the metadata. */
1141         g_signal_emit_by_name(worker->owner, "metadata-changed", mafwtag,
1142                               values);
1143
1144         g_value_array_free(values);
1145 }
1146
1147 /**
1148  * Collect tag-messages, parse it later, when playing is ongoing
1149  */
1150 static void _handle_tag(MafwGstRendererWorker *worker, GstMessage *msg)
1151 {
1152         /* Do not emit metadata until we get to PLAYING state to speed up
1153            playback start */
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));
1157
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);
1162         }
1163 }
1164
1165 /**
1166  * Parses the list of tag-messages
1167  */
1168 static void _parse_tagmsg(GstMessage *msg, MafwGstRendererWorker *worker)
1169 {
1170         GstTagList *new_tags;
1171
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);
1176 }
1177
1178 /**
1179  * Parses the collected tag messages, and emits the metadatas
1180  */
1181 static void _emit_metadatas(MafwGstRendererWorker *worker)
1182 {
1183         if (worker->tag_list != NULL)
1184         {
1185                 g_ptr_array_foreach(worker->tag_list, (GFunc)_parse_tagmsg,
1186                                     worker);
1187                 g_ptr_array_free(worker->tag_list, TRUE);
1188                 worker->tag_list = NULL;
1189         }
1190 }
1191
1192 static void _reset_volume_and_mute_to_pipeline(MafwGstRendererWorker *worker)
1193 {
1194 #ifdef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1195         g_debug("resetting volume and mute to pipeline");
1196
1197         if (worker->pipeline != NULL) {
1198                 g_object_set(
1199                         G_OBJECT(worker->pipeline), "volume",
1200                         mafw_gst_renderer_worker_volume_get(worker->wvolume),
1201                         "mute",
1202                         mafw_gst_renderer_worker_volume_is_muted(worker->wvolume),
1203                         NULL);
1204         }
1205 #endif
1206 }
1207
1208 static void _handle_buffering(MafwGstRendererWorker *worker, GstMessage *msg)
1209 {
1210         gint percent;
1211         MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
1212
1213         gst_message_parse_buffering(msg, &percent);
1214         g_debug("buffering: %d", percent);
1215
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 "
1221                                 "buffer down");
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
1227                          * PAUSED. */
1228                         if (gst_element_set_state(worker->pipeline,
1229                                               GST_STATE_PAUSED) ==
1230                                         GST_STATE_CHANGE_ASYNC)
1231                         {
1232                                 /* XXX this blocks at most 2 seconds. */
1233                                 gst_element_get_state(worker->pipeline, NULL,
1234                                               NULL,
1235                                               2 * GST_SECOND);
1236                         }
1237                 }
1238
1239                 if (percent >= 100) {
1240                         /* On buffering we go to PAUSED, so here we move back to
1241                            PLAYING */
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 "
1248                                                 "prerolling");
1249                                         _finalize_startup(worker);
1250                                         _do_play(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(
1256                                                         worker,
1257                                                         worker->owner);
1258                                         }
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");
1268                                         _do_play(worker);
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
1275                                            state changes) */
1276                                         g_debug("buffering concluded, setting "
1277                                                 "pipeline to PLAYING again");
1278                                         _reset_volume_and_mute_to_pipeline(
1279                                                 worker);
1280                                         if (gst_element_set_state(
1281                                                 worker->pipeline,
1282                                                 GST_STATE_PLAYING) ==
1283                                                         GST_STATE_CHANGE_ASYNC)
1284                                         {
1285                                                 /* XXX this blocks at most 2 seconds. */
1286                                                 gst_element_get_state(
1287                                                         worker->pipeline, NULL, NULL,
1288                                                         2 * GST_SECOND);
1289                                         }
1290                                 }
1291                         } else if (worker->state == GST_STATE_PLAYING) {
1292                                 g_debug("buffering concluded, signalling "
1293                                         "state change");
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(
1305                                                         worker,
1306                                                         worker->owner);
1307                                 }
1308                                 _add_duration_seek_query_timeout(worker);
1309                         }
1310                 }
1311         }
1312
1313         /* Send buffer percentage */
1314         if (worker->notify_buffer_status_handler)
1315                 worker->notify_buffer_status_handler(worker, worker->owner,
1316                                                      percent);
1317 }
1318
1319 static void _handle_element_msg(MafwGstRendererWorker *worker, GstMessage *msg)
1320 {
1321         /* Only HelixBin sends "resolution" messages. */
1322         if (gst_structure_has_name(msg->structure, "resolution") &&
1323             _handle_video_info(worker, msg->structure))
1324         {
1325                 worker->media.has_visual_content = TRUE;
1326         }
1327 }
1328
1329 static void _reset_pl_info(MafwGstRendererWorker *worker)
1330 {
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;
1335         }
1336
1337         worker->pl.current = 0;
1338         worker->pl.notify_play_pending = TRUE;
1339 }
1340
1341 static GError * _get_specific_missing_plugin_error(GstMessage *msg)
1342 {
1343         const GstStructure *gst_struct;
1344         const gchar *type;
1345
1346         GError *error;
1347         gchar *desc;
1348
1349         desc = gst_missing_plugin_message_get_description(msg);
1350
1351         gst_struct = gst_message_get_structure(msg);
1352         type = gst_structure_get_string(gst_struct, "type");
1353
1354         if ((type) && ((strcmp(type, MAFW_GST_MISSING_TYPE_DECODER) == 0) ||
1355                        (strcmp(type, MAFW_GST_MISSING_TYPE_ENCODER) == 0))) {
1356
1357                 /* Missing codec error. */
1358                 const GValue *val;
1359                 const GstCaps *caps;
1360                 GstStructure *caps_struct;
1361                 const gchar *mime;
1362
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);
1367
1368                 if (g_strrstr(mime, "video")) {
1369                         error = g_error_new_literal(
1370                                 MAFW_RENDERER_ERROR,
1371                                 MAFW_RENDERER_ERROR_VIDEO_CODEC_NOT_FOUND,
1372                                 desc);
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,
1377                                 desc);
1378                 } else {
1379                         error = g_error_new_literal(
1380                                 MAFW_RENDERER_ERROR,
1381                                 MAFW_RENDERER_ERROR_CODEC_NOT_FOUND,
1382                                 desc);
1383                 }
1384         } else {
1385                 /* Unsupported type error. */
1386                 error = g_error_new(
1387                         MAFW_RENDERER_ERROR,
1388                         MAFW_RENDERER_ERROR_UNSUPPORTED_TYPE,
1389                         "missing plugin: %s", desc);
1390         }
1391
1392         g_free(desc);
1393
1394         return error;
1395 }
1396
1397 /*
1398  * Asynchronous message handler.  It gets removed from if it returns FALSE.
1399  */
1400 static gboolean _async_bus_handler(GstBus *bus, GstMessage *msg,
1401                                    MafwGstRendererWorker *worker)
1402 {
1403         /* No need to handle message if error has already occured. */
1404         if (worker->is_error)
1405                 return TRUE;
1406
1407         /* Handle missing-plugin (element) messages separately, relaying more
1408          * details. */
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);
1416                 return TRUE;
1417         }
1418
1419         switch (GST_MESSAGE_TYPE(msg)) {
1420         case GST_MESSAGE_ERROR:
1421                 if (!worker->is_error) {
1422                         gchar *debug;
1423                         GError *err;
1424
1425                         debug = NULL;
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);
1430                         if (debug)
1431                                 g_free(debug);
1432
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 */
1438
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
1445                                            next item */
1446                                         if (err->code ==
1447                                             GST_RESOURCE_ERROR_NO_SPACE_LEFT) {
1448                                                 _send_error(worker, err);
1449
1450                                         } else {
1451                                                 _play_pl_next(worker);
1452                                         }
1453                                 } else {
1454                                         /* Playlist EOS. We cannot try another
1455                                          * URI, so we have to go back to normal
1456                                          * mode and signal the error (done
1457                                          * below) */
1458                                         worker->mode = WORKER_MODE_SINGLE_PLAY;
1459                                         _reset_pl_info(worker);
1460                                 }
1461                         }
1462
1463                         if (worker->mode == WORKER_MODE_SINGLE_PLAY) {
1464                                 _send_error(worker, err);
1465                         }
1466                 }
1467                 break;
1468         case GST_MESSAGE_EOS:
1469                 if (!worker->is_error) {
1470                         worker->eos = TRUE;
1471
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
1476                                            continue playing */
1477                                         _play_pl_next(worker);
1478                                 } else {
1479                                         /* Playlist EOS, go back to normal
1480                                            mode */
1481                                         worker->mode = WORKER_MODE_SINGLE_PLAY;
1482                                         _reset_pl_info(worker);
1483                                 }
1484                         }
1485
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(
1490                                                 worker,
1491                                                 worker->owner);
1492
1493                                 /* We can remove the message handlers now, we
1494                                    are not interested in bus messages
1495                                    anymore. */
1496                                 if (worker->bus) {
1497                                         gst_bus_set_sync_handler(worker->bus,
1498                                                                  NULL,
1499                                                                  NULL);
1500                                 }
1501                                 if (worker->async_bus_id) {
1502                                         g_source_remove(worker->async_bus_id);
1503                                         worker->async_bus_id = 0;
1504                                 }
1505
1506                                 if (worker->mode == WORKER_MODE_REDUNDANT) {
1507                                         /* Go to normal mode */
1508                                         worker->mode = WORKER_MODE_SINGLE_PLAY;
1509                                         _reset_pl_info(worker);
1510                                 }
1511                         }
1512                 }
1513                 break;
1514         case GST_MESSAGE_TAG:
1515                 _handle_tag(worker, msg);
1516                 break;
1517         case GST_MESSAGE_BUFFERING:
1518                 _handle_buffering(worker, msg);
1519                 break;
1520         case GST_MESSAGE_DURATION:
1521                 _handle_duration(worker, msg);
1522                 break;
1523         case GST_MESSAGE_ELEMENT:
1524                 _handle_element_msg(worker, msg);
1525                 break;
1526         case GST_MESSAGE_STATE_CHANGED:
1527                 if ((GstElement *)GST_MESSAGE_SRC(msg) == worker->pipeline)
1528                         _handle_state_changed(msg, worker);
1529                 break;
1530         case GST_MESSAGE_APPLICATION:
1531                 if (gst_structure_has_name(gst_message_get_structure(msg),
1532                                            "ckey"))
1533                 {
1534                         GValue v = {0};
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,
1540                                 &v);
1541                 }
1542         default: break;
1543         }
1544         return TRUE;
1545 }
1546
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)
1551 {
1552         g_debug("stream-info changed");
1553         _parse_stream_info(worker);
1554 }
1555
1556 static void _volume_cb(MafwGstRendererWorkerVolume *wvolume, gdouble volume,
1557                        gpointer data)
1558 {
1559         MafwGstRendererWorker *worker = data;
1560         GValue value = {0, };
1561
1562         _reset_volume_and_mute_to_pipeline(worker);
1563
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,
1568                                              &value);
1569 }
1570
1571 static void _mute_cb(MafwGstRendererWorkerVolume *wvolume, gboolean mute,
1572                      gpointer data)
1573 {
1574         MafwGstRendererWorker *worker = data;
1575         GValue value = {0, };
1576
1577         _reset_volume_and_mute_to_pipeline(worker);
1578
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,
1583                                              &value);
1584 }
1585
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
1589  * us... */
1590 static int xerror(Display *dpy, XErrorEvent *xev)
1591 {
1592         MafwGstRendererWorker *worker;
1593
1594         if (Global_worker == NULL) {
1595                 return -1;
1596         } else {
1597                 worker = Global_worker;
1598         }
1599
1600         /* Swallow BadWindow and stop pipeline when the error is about the
1601          * currently set xid. */
1602         if (worker->xid &&
1603             xev->resourceid == worker->xid &&
1604             xev->error_code == BadWindow)
1605         {
1606                 g_warning("BadWindow received for current xid (%x).",
1607                         (gint)xev->resourceid);
1608                 worker->xid = 0;
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"));
1615         }
1616         return 0;
1617 }
1618
1619 /*
1620  * Resets the media information.
1621  */
1622 static void _reset_media_info(MafwGstRendererWorker *worker)
1623 {
1624         if (worker->media.location) {
1625                 g_free(worker->media.location);
1626                 worker->media.location = NULL;
1627         }
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;
1634 }
1635
1636 static void _set_volume_and_mute(MafwGstRendererWorker *worker, gdouble vol,
1637                                  gboolean mute)
1638 {
1639         g_return_if_fail(worker->wvolume != NULL);
1640
1641         mafw_gst_renderer_worker_volume_set(worker->wvolume, vol, mute);
1642 }
1643
1644 static void _set_volume(MafwGstRendererWorker *worker, gdouble new_vol)
1645 {
1646         g_return_if_fail(worker->wvolume != NULL);
1647
1648         _set_volume_and_mute(
1649                 worker, new_vol,
1650                 mafw_gst_renderer_worker_volume_is_muted(worker->wvolume));
1651 }
1652
1653 static void _set_mute(MafwGstRendererWorker *worker, gboolean mute)
1654 {
1655         g_return_if_fail(worker->wvolume != NULL);
1656
1657         _set_volume_and_mute(
1658                 worker, mafw_gst_renderer_worker_volume_get(worker->wvolume),
1659                 mute);
1660 }
1661
1662 /*
1663  * Start to play the media
1664  */
1665 static void _start_play(MafwGstRendererWorker *worker)
1666 {
1667         MafwGstRenderer *renderer = (MafwGstRenderer*) worker->owner;
1668         GstStateChangeReturn state_change_info;
1669
1670         g_assert(worker->pipeline);
1671         g_object_set(G_OBJECT(worker->pipeline),
1672                      "uri", worker->media.location, NULL);
1673
1674         g_debug("URI: %s", worker->media.location);
1675         g_debug("setting pipeline to PAUSED");
1676
1677         worker->report_statechanges = TRUE;
1678         state_change_info = gst_element_set_state(worker->pipeline, 
1679                                                   GST_STATE_PAUSED);
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;
1685         }
1686         worker->prerolling = TRUE;
1687
1688         worker->is_stream = uri_is_stream(worker->media.location);
1689
1690         if (renderer->update_playcount_id > 0) {
1691                 g_source_remove(renderer->update_playcount_id);
1692                 renderer->update_playcount_id = 0;
1693         }
1694
1695 }
1696
1697 /*
1698  * Constructs gst pipeline
1699  *
1700  * FIXME: Could the same pipeline be used for playing all media instead of
1701  *  constantly deleting and reconstructing it again?
1702  */
1703 static void _construct_pipeline(MafwGstRendererWorker *worker)
1704 {
1705         g_debug("constructing pipeline");
1706         g_assert(worker != NULL);
1707
1708         /* Return if we have already one */
1709         if (worker->pipeline)
1710                 return;
1711
1712         _free_taglist(worker);
1713
1714         g_debug("Creating a new instance of playbin2");
1715         worker->pipeline = gst_element_factory_make("playbin2",
1716                                                     "playbin");
1717         if (worker->pipeline == NULL)
1718         {
1719                 /* Let's try with playbin */
1720                 g_warning ("playbin2 failed, falling back to playbin");
1721                 worker->pipeline = gst_element_factory_make("playbin",
1722                                                             "playbin");
1723
1724                 if (worker->pipeline) {
1725                         /* Use nwqueue only for non-rtsp and non-mms(h)
1726                            streams. */
1727                         gboolean use_nw;
1728                         use_nw = worker->media.location && 
1729                                 !g_str_has_prefix(worker->media.location, 
1730                                                   "rtsp://") &&
1731                                 !g_str_has_prefix(worker->media.location, 
1732                                                   "mms://") &&
1733                                 !g_str_has_prefix(worker->media.location, 
1734                                                   "mmsh://");
1735                         
1736                         g_debug("playbin using network queue: %d", use_nw);
1737
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);
1743                 }
1744         }
1745
1746         if (!worker->pipeline) {
1747                 g_critical("failed to create playback pipeline");
1748                 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1749                                       "error",
1750                                       MAFW_RENDERER_ERROR,
1751                                       MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1752                                       "Could not create pipeline");
1753                 g_assert_not_reached();
1754         }
1755
1756
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,
1762                                                  worker, NULL);
1763
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
1766          * video window. */
1767         g_signal_connect(worker->pipeline, "notify::stream-info",
1768                          G_CALLBACK(_stream_info_cb), worker);
1769
1770 #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1771         g_object_set(worker->pipeline, "flags", 99, NULL);
1772
1773         /* Set audio and video sinks ourselves. We create and configure
1774            them only once. */
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), 
1780                                               "error",
1781                                               MAFW_RENDERER_ERROR,
1782                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1783                                               "Could not create audio sink");
1784                         g_assert_not_reached();
1785                 }
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);
1791         }
1792         g_object_set(worker->pipeline, "audio-sink", worker->asink, NULL);
1793 #endif
1794
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), 
1800                                               "error",
1801                                               MAFW_RENDERER_ERROR,
1802                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1803                                               "Could not create video sink");
1804                         g_assert_not_reached();
1805                 }
1806                 gst_object_ref(worker->vsink);
1807                 g_object_set(G_OBJECT(worker->vsink), "handle-events",
1808                              TRUE, NULL);
1809                 g_object_set(worker->vsink, "force-aspect-ratio",
1810                              TRUE, NULL);
1811         }
1812         g_object_set(worker->pipeline, "video-sink", worker->vsink, NULL);
1813 }
1814
1815 /*
1816  * @seek_type: GstSeekType
1817  * @position: Time in seconds where to seek
1818  */
1819 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
1820                      gint position, GError **error)
1821 {
1822         gboolean ret;
1823         gint64 spos;
1824
1825         g_assert(worker != NULL);
1826
1827         if (worker->eos || !worker->media.seekable)
1828                 goto err;
1829
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)
1836         {
1837                 gint curpos = mafw_gst_renderer_worker_get_position(worker);
1838                 position = curpos + position;
1839                 seek_type = GST_SEEK_TYPE_SET;
1840         }
1841
1842         if (position < 0) {
1843                 position = 0;
1844         }
1845
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);
1850
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
1856            video playback */
1857         if (worker->in_ready && worker->state == GST_STATE_READY) {
1858                 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
1859         } else {
1860                 ret = gst_element_seek(worker->pipeline, 1.0, GST_FORMAT_TIME,
1861                                        GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_KEY_UNIT,
1862                                        seek_type, spos,
1863                                        GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
1864                 if (!ret) {
1865                         /* Seeking is async, so seek_position should not be
1866                            invalidated here */
1867                         goto err;
1868                 }
1869         }
1870         return;
1871
1872 err:    g_set_error(error,
1873                     MAFW_RENDERER_ERROR,
1874                     MAFW_RENDERER_ERROR_CANNOT_SET_POSITION,
1875                     "Seeking to %d failed", position);
1876 }
1877
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)
1882 {
1883         _set_volume(worker, CLAMP((gdouble)volume / 100.0, 0.0, 1.0));
1884 }
1885
1886 guint mafw_gst_renderer_worker_get_volume(
1887         MafwGstRendererWorker *worker)
1888 {
1889         return (guint)
1890                 (mafw_gst_renderer_worker_volume_get(worker->wvolume) * 100);
1891 }
1892
1893 void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker,
1894                                      gboolean mute)
1895 {
1896         _set_mute(worker, mute);
1897 }
1898
1899 gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker)
1900 {
1901         return mafw_gst_renderer_worker_volume_is_muted(worker->wvolume);
1902 }
1903
1904 #ifdef HAVE_GDKPIXBUF
1905 void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker,
1906                                                                 gboolean current_frame_on_pause)
1907 {
1908         worker->current_frame_on_pause = current_frame_on_pause;
1909 }
1910
1911 gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker)
1912 {
1913         return worker->current_frame_on_pause;
1914 }
1915 #endif
1916
1917 void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker,
1918                                           GstSeekType seek_type,
1919                                           gint position, GError **error)
1920 {
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).
1928          */
1929         if (worker->ready_timeout) {
1930                 _remove_ready_timeout(worker);
1931                 _add_ready_timeout(worker);
1932         }
1933
1934         _do_seek(worker, seek_type, position, error);
1935         if (worker->notify_seek_handler)
1936                 worker->notify_seek_handler(worker, worker->owner);
1937 }
1938
1939 /*
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
1942  * failure.
1943  */
1944 gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker)
1945 {
1946         GstFormat format;
1947         gint64 time = 0;
1948         g_assert(worker != NULL);
1949
1950         /* If seek is ongoing, return the position where we are seeking. */
1951         if (worker->seek_position != -1)
1952         {
1953                 return worker->seek_position;
1954         }
1955         /* Otherwise query position from pipeline. */
1956         format = GST_FORMAT_TIME;
1957         if (worker->pipeline &&
1958             gst_element_query_position(worker->pipeline, &format, &time))
1959         {
1960                 return (gint)(NSECONDS_TO_SECONDS(time));
1961         }
1962         return -1;
1963 }
1964
1965 GHashTable *mafw_gst_renderer_worker_get_current_metadata(
1966         MafwGstRendererWorker *worker)
1967 {
1968         return worker->current_metadata;
1969 }
1970
1971 void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid)
1972 {
1973         /* Check for errors on the target window */
1974         XSetErrorHandler(xerror);
1975
1976         /* Store the target window id */
1977         g_debug("Setting xid: %x", (guint)xid);
1978         worker->xid = xid;
1979
1980         /* Check if we should use it right away */
1981         mafw_gst_renderer_worker_apply_xid(worker);
1982 }
1983
1984 XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker)
1985 {
1986         return worker->xid;
1987 }
1988
1989 gboolean mafw_gst_renderer_worker_get_autopaint(
1990         MafwGstRendererWorker *worker)
1991 {
1992         return worker->autopaint;
1993 }
1994 void mafw_gst_renderer_worker_set_autopaint(
1995         MafwGstRendererWorker *worker, gboolean autopaint)
1996 {
1997         worker->autopaint = autopaint;
1998         if (worker->vsink)
1999                 g_object_set(worker->vsink, "autopaint-colorkey",
2000                              autopaint, NULL);
2001 }
2002
2003 gint mafw_gst_renderer_worker_get_colorkey(
2004         MafwGstRendererWorker *worker)
2005 {
2006         return worker->colorkey;
2007 }
2008
2009 gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker)
2010 {
2011         return worker->media.seekable;
2012 }
2013
2014 static void _play_pl_next(MafwGstRendererWorker *worker) {
2015         gchar *next;
2016
2017         g_assert(worker != NULL);
2018         g_return_if_fail(worker->pl.items != NULL);
2019
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);
2024
2025         worker->media.location = g_strdup(next);
2026         _construct_pipeline(worker);
2027         _start_play(worker);
2028 }
2029
2030 static void _on_pl_entry_parsed(TotemPlParser *parser, gchar *uri,
2031                                 gpointer metadata, gpointer user_data)
2032 {
2033         MafwGstRendererWorker *worker = user_data;
2034
2035         if (uri != NULL) {
2036                 worker->pl.items =
2037                         g_slist_append(worker->pl.items, g_strdup(uri));
2038         }
2039 }
2040
2041 static void _do_play(MafwGstRendererWorker *worker)
2042 {
2043         g_assert(worker != NULL);
2044
2045         if (worker->pipeline == NULL) {
2046                 g_debug("play without a pipeline!");
2047                 return;
2048         }
2049         worker->report_statechanges = TRUE;
2050
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,
2058                                               GST_STATE_PAUSED);
2059                         g_debug("setting pipeline to PAUSED");
2060                 } else {
2061                         _reset_volume_and_mute_to_pipeline(worker);
2062                         gst_element_set_state(worker->pipeline,
2063                                               GST_STATE_PLAYING);
2064                         g_debug("setting pipeline to PLAYING");
2065                 }
2066         }
2067         else {
2068                 g_debug("staying in PAUSED state");
2069                 _add_ready_timeout(worker);
2070         }
2071 }
2072
2073 void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker,
2074                                   const gchar *uri)
2075 {
2076         g_assert(uri);
2077
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;
2086                 gchar *item;
2087
2088                 /* Initialize the playlist parser */
2089                 pl_parser = totem_pl_parser_new ();
2090                 g_object_set(pl_parser, "recurse", TRUE, "disable-unsafe",
2091                              TRUE, NULL);
2092                 g_signal_connect(G_OBJECT(pl_parser), "entry-parsed",
2093                                  G_CALLBACK(_on_pl_entry_parsed), worker);
2094
2095                 /* Parsing */
2096                 if (totem_pl_parser_parse(pl_parser, uri, FALSE) !=
2097                     TOTEM_PL_PARSER_RESULT_SUCCESS) {
2098                         /* An error happens while parsing */
2099                         _send_error(worker,
2100                                     g_error_new(MAFW_RENDERER_ERROR,
2101                                                 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2102                                                 "Playlist parsing failed: %s",
2103                                                 uri));
2104                         return;
2105                 }
2106
2107                 if (!worker->pl.items) {
2108                         /* The playlist is empty */
2109                         _send_error(worker,
2110                                     g_error_new(MAFW_RENDERER_ERROR,
2111                                                 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2112                                                 "The playlist %s is empty.",
2113                                                 uri));
2114                         return;
2115                 }
2116
2117                 /* Set the playback mode */
2118                 worker->mode = WORKER_MODE_PLAYLIST;
2119                 worker->pl.notify_play_pending = TRUE;
2120
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);
2125
2126                 /* Free the playlist parser */
2127                 g_object_unref(pl_parser);
2128         } else {
2129                 /* Single item. Set the playback mode according to that */
2130                 worker->mode = WORKER_MODE_SINGLE_PLAY;
2131
2132                 /* Set the item to be played */
2133                 worker->media.location = g_strdup(uri);
2134         }
2135         _construct_pipeline(worker);
2136         _start_play(worker);
2137 }
2138
2139 void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker,
2140                                                 gchar **uris)
2141 {
2142         gint i;
2143         gchar *item;
2144
2145         g_assert(uris && uris[0]);
2146
2147         mafw_gst_renderer_worker_stop(worker);
2148         _reset_media_info(worker);
2149         _reset_pl_info(worker);
2150
2151         /* Add the uris to playlist */
2152         i = 0;
2153         while (uris[i]) {
2154                 worker->pl.items =
2155                         g_slist_append(worker->pl.items, g_strdup(uris[i]));
2156                 i++;
2157         }
2158
2159         /* Set the playback mode */
2160         worker->mode = WORKER_MODE_REDUNDANT;
2161         worker->pl.notify_play_pending = TRUE;
2162
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);
2167
2168         /* Start playing */
2169         _construct_pipeline(worker);
2170         _start_play(worker);
2171 }
2172
2173 /*
2174  * Currently, stop destroys the Gst pipeline and resets the worker into
2175  * default startup configuration.
2176  */
2177 void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker)
2178 {
2179         g_debug("worker stop");
2180         g_assert(worker != NULL);
2181
2182         /* If location is NULL, this is a pre-created pipeline */
2183         if (worker->async_bus_id && worker->pipeline && !worker->media.location)
2184                 return;
2185
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;
2191                 }
2192                 gst_bus_set_sync_handler(worker->bus, NULL, NULL);
2193                 gst_element_set_state(worker->pipeline, GST_STATE_NULL);
2194                 if (worker->bus) {
2195                         gst_object_unref(GST_OBJECT_CAST(worker->bus));
2196                         worker->bus = NULL;
2197                 }
2198                 gst_object_unref(GST_OBJECT(worker->pipeline));
2199                 worker->pipeline = NULL;
2200         }
2201
2202         /* Reset worker */
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;
2217         }
2218
2219         if (worker->duration_seek_timeout != 0) {
2220                 g_source_remove(worker->duration_seek_timeout);
2221                 worker->duration_seek_timeout = 0;
2222         }
2223
2224         /* Reset media iformation */
2225         _reset_media_info(worker);
2226
2227         /* We are not playing, so we can let the screen blank */
2228         blanking_allow();
2229
2230         /* And now get a fresh pipeline ready */
2231         _construct_pipeline(worker);
2232 }
2233
2234 void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker)
2235 {
2236         g_assert(worker != NULL);
2237
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(
2246                                 worker,
2247                                 worker->owner);
2248                 }
2249         } else {
2250                 worker->report_statechanges = TRUE;
2251
2252                 if (gst_element_set_state(worker->pipeline, GST_STATE_PAUSED) ==
2253                     GST_STATE_CHANGE_ASYNC)
2254                 {
2255                         /* XXX this blocks at most 2 seconds. */
2256                         gst_element_get_state(worker->pipeline, NULL, NULL,
2257                                       2 * GST_SECOND);
2258                 }
2259                 blanking_allow();
2260         }
2261 }
2262
2263 void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker)
2264 {
2265         if (worker->mode == WORKER_MODE_PLAYLIST ||
2266             worker->mode == WORKER_MODE_REDUNDANT) {
2267                 /* We must notify play if the "playlist" playback
2268                    is resumed */
2269                 worker->pl.notify_play_pending = TRUE;
2270         }
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 "
2281                         "changes");
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) */
2295         } else {
2296                 _do_play(worker);
2297         }
2298 }
2299
2300 static void _volume_init_cb(MafwGstRendererWorkerVolume *wvolume,
2301                             gpointer data)
2302 {
2303         MafwGstRendererWorker *worker = data;
2304         gdouble volume;
2305         gboolean mute;
2306
2307         worker->wvolume = wvolume;
2308
2309         g_debug("volume manager initialized");
2310
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);
2315 }
2316
2317 MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner)
2318 {
2319         MafwGstRendererWorker *worker;
2320         GMainContext *main_context;
2321
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;
2333         worker->xid = 0;
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;
2340
2341 #ifdef HAVE_GDKPIXBUF
2342         worker->current_frame_on_pause = FALSE;
2343         _init_tmp_files_pool(worker);
2344 #endif
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,
2356                                              _volume_cb, worker,
2357                                              _mute_cb, worker);
2358         blanking_init();
2359         _construct_pipeline(worker);
2360
2361         return worker;
2362 }
2363
2364 void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker)
2365 {
2366         blanking_deinit();
2367 #ifdef HAVE_GDKPIXBUF
2368         _destroy_tmp_files_pool(worker);
2369 #endif
2370         mafw_gst_renderer_worker_volume_destroy(worker->wvolume);
2371         mafw_gst_renderer_worker_stop(worker);
2372 }
2373 /* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */