Created fork of mafw-gst-renderer with subtitles support
[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         char *autoload_sub = NULL;
1670
1671         g_assert(worker->pipeline);
1672         g_object_set(G_OBJECT(worker->pipeline),
1673                      "uri", worker->media.location, NULL);
1674
1675         if (worker->subtitles.enabled) {
1676                 autoload_sub = uri_get_subtitle_uri(worker->media.location);
1677                 if (autoload_sub) {
1678                         g_debug("SUBURI: %s", autoload_sub);
1679                         g_object_set(G_OBJECT(worker->pipeline),
1680                                      "suburi", autoload_sub,
1681                                      "subtitle-font-desc", worker->subtitles.font,
1682                                      "subtitle-encoding", worker->subtitles.encoding,
1683                                      NULL);
1684
1685                         gst_element_set_state(worker->pipeline, GST_STATE_READY);
1686                         g_free(autoload_sub);
1687                 }
1688         }
1689
1690         g_debug("URI: %s", worker->media.location);
1691         g_debug("setting pipeline to PAUSED");
1692
1693         worker->report_statechanges = TRUE;
1694         state_change_info = gst_element_set_state(worker->pipeline, 
1695                                                   GST_STATE_PAUSED);
1696         if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) {
1697                 /* FIXME:  for live sources we may have to handle
1698                    buffering and prerolling differently */
1699                 g_debug ("Source is live!");
1700                 worker->is_live = TRUE;
1701         }
1702         worker->prerolling = TRUE;
1703
1704         worker->is_stream = uri_is_stream(worker->media.location);
1705
1706         if (renderer->update_playcount_id > 0) {
1707                 g_source_remove(renderer->update_playcount_id);
1708                 renderer->update_playcount_id = 0;
1709         }
1710
1711 }
1712
1713 /*
1714  * Constructs gst pipeline
1715  *
1716  * FIXME: Could the same pipeline be used for playing all media instead of
1717  *  constantly deleting and reconstructing it again?
1718  */
1719 static void _construct_pipeline(MafwGstRendererWorker *worker)
1720 {
1721         g_debug("constructing pipeline");
1722         g_assert(worker != NULL);
1723
1724         /* Return if we have already one */
1725         if (worker->pipeline)
1726                 return;
1727
1728         _free_taglist(worker);
1729
1730         g_debug("Creating a new instance of playbin2");
1731         worker->pipeline = gst_element_factory_make("playbin2",
1732                                                     "playbin");
1733         if (worker->pipeline == NULL)
1734         {
1735                 /* Let's try with playbin */
1736                 g_warning ("playbin2 failed, falling back to playbin");
1737                 worker->pipeline = gst_element_factory_make("playbin",
1738                                                             "playbin");
1739
1740                 if (worker->pipeline) {
1741                         /* Use nwqueue only for non-rtsp and non-mms(h)
1742                            streams. */
1743                         gboolean use_nw;
1744                         use_nw = worker->media.location && 
1745                                 !g_str_has_prefix(worker->media.location, 
1746                                                   "rtsp://") &&
1747                                 !g_str_has_prefix(worker->media.location, 
1748                                                   "mms://") &&
1749                                 !g_str_has_prefix(worker->media.location, 
1750                                                   "mmsh://");
1751                         
1752                         g_debug("playbin using network queue: %d", use_nw);
1753
1754                         /* These need a modified version of playbin. */
1755                         g_object_set(G_OBJECT(worker->pipeline),
1756                                      "nw-queue", use_nw, NULL);
1757                         g_object_set(G_OBJECT(worker->pipeline),
1758                                      "no-video-transform", TRUE, NULL);
1759                 }
1760         }
1761
1762         if (!worker->pipeline) {
1763                 g_critical("failed to create playback pipeline");
1764                 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1765                                       "error",
1766                                       MAFW_RENDERER_ERROR,
1767                                       MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1768                                       "Could not create pipeline");
1769                 g_assert_not_reached();
1770         }
1771
1772
1773         worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline));
1774         gst_bus_set_sync_handler(worker->bus,
1775                                  (GstBusSyncHandler)_sync_bus_handler, worker);
1776         worker->async_bus_id = gst_bus_add_watch_full(worker->bus,G_PRIORITY_HIGH,
1777                                                  (GstBusFunc)_async_bus_handler,
1778                                                  worker, NULL);
1779
1780         /* Listen for changes in stream-info object to find out whether the
1781          * media contains video and throw error if application has not provided
1782          * video window. */
1783         g_signal_connect(worker->pipeline, "notify::stream-info",
1784                          G_CALLBACK(_stream_info_cb), worker);
1785
1786 #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1787         g_object_set(worker->pipeline, "flags", 103, NULL);
1788
1789         /* Set audio and video sinks ourselves. We create and configure
1790            them only once. */
1791         if (!worker->asink) {
1792                 worker->asink = gst_element_factory_make("pulsesink", NULL);
1793                 if (!worker->asink) {
1794                         g_critical("Failed to create pipeline audio sink");
1795                         g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1796                                               "error",
1797                                               MAFW_RENDERER_ERROR,
1798                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1799                                               "Could not create audio sink");
1800                         g_assert_not_reached();
1801                 }
1802                 gst_object_ref(worker->asink);
1803                 g_object_set(worker->asink, "buffer-time", 
1804                              (gint64) MAFW_GST_BUFFER_TIME, NULL);
1805                 g_object_set(worker->asink, "latency-time", 
1806                              (gint64) MAFW_GST_LATENCY_TIME, NULL);
1807         }
1808         g_object_set(worker->pipeline, "audio-sink", worker->asink, NULL);
1809 #endif
1810
1811         if (!worker->vsink) {
1812                 worker->vsink = gst_element_factory_make("xvimagesink", NULL);
1813                 if (!worker->vsink) {
1814                         g_critical("Failed to create pipeline video sink");
1815                         g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1816                                               "error",
1817                                               MAFW_RENDERER_ERROR,
1818                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1819                                               "Could not create video sink");
1820                         g_assert_not_reached();
1821                 }
1822                 gst_object_ref(worker->vsink);
1823                 g_object_set(G_OBJECT(worker->vsink), "handle-events",
1824                              TRUE, NULL);
1825                 g_object_set(worker->vsink, "force-aspect-ratio",
1826                              TRUE, NULL);
1827         }
1828         g_object_set(worker->pipeline, "video-sink", worker->vsink, NULL);
1829
1830         if (!worker->tsink) {
1831                 worker->tsink = gst_element_factory_make("textoverlay", NULL);
1832                 if (!worker->tsink) {
1833                         g_critical("Failed to create pipeline text sink");
1834                         g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1835                                               "error",
1836                                               MAFW_RENDERER_ERROR,
1837                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1838                                               "Could not create text sink");
1839                         g_assert_not_reached();
1840                 }
1841                 gst_object_ref(worker->tsink);
1842         }
1843         g_object_set(worker->pipeline, "text-sink", worker->tsink, NULL);
1844 }
1845
1846 /*
1847  * @seek_type: GstSeekType
1848  * @position: Time in seconds where to seek
1849  */
1850 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
1851                      gint position, GError **error)
1852 {
1853         gboolean ret;
1854         gint64 spos;
1855
1856         g_assert(worker != NULL);
1857
1858         if (worker->eos || !worker->media.seekable)
1859                 goto err;
1860
1861         /* According to the docs, relative seeking is not so easy:
1862         GST_SEEK_TYPE_CUR - change relative to currently configured segment.
1863         This can't be used to seek relative to the current playback position -
1864         do a position query, calculate the desired position and then do an
1865         absolute position seek instead if that's what you want to do. */
1866         if (seek_type == GST_SEEK_TYPE_CUR)
1867         {
1868                 gint curpos = mafw_gst_renderer_worker_get_position(worker);
1869                 position = curpos + position;
1870                 seek_type = GST_SEEK_TYPE_SET;
1871         }
1872
1873         if (position < 0) {
1874                 position = 0;
1875         }
1876
1877         worker->seek_position = position;
1878         worker->report_statechanges = FALSE;
1879         spos = (gint64)position * GST_SECOND;
1880         g_debug("seek: type = %d, offset = %lld", seek_type, spos);
1881
1882         /* If the pipeline has been set to READY by us, then wake it up by
1883            setting it to PAUSED (when we get the READY->PAUSED transition
1884            we will execute the seek). This way when we seek we disable the
1885            READY state (logical, since the player is not idle anymore)
1886            allowing the sink to render the destination frame in case of
1887            video playback */
1888         if (worker->in_ready && worker->state == GST_STATE_READY) {
1889                 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
1890         } else {
1891                 ret = gst_element_seek(worker->pipeline, 1.0, GST_FORMAT_TIME,
1892                                        GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_KEY_UNIT,
1893                                        seek_type, spos,
1894                                        GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
1895                 if (!ret) {
1896                         /* Seeking is async, so seek_position should not be
1897                            invalidated here */
1898                         goto err;
1899                 }
1900         }
1901         return;
1902
1903 err:    g_set_error(error,
1904                     MAFW_RENDERER_ERROR,
1905                     MAFW_RENDERER_ERROR_CANNOT_SET_POSITION,
1906                     "Seeking to %d failed", position);
1907 }
1908
1909 /* @vol should be between [0 .. 100], higher values (up to 1000) are allowed,
1910  * but probably cause distortion. */
1911 void mafw_gst_renderer_worker_set_volume(
1912         MafwGstRendererWorker *worker, guint volume)
1913 {
1914         _set_volume(worker, CLAMP((gdouble)volume / 100.0, 0.0, 1.0));
1915 }
1916
1917 guint mafw_gst_renderer_worker_get_volume(
1918         MafwGstRendererWorker *worker)
1919 {
1920         return (guint)
1921                 (mafw_gst_renderer_worker_volume_get(worker->wvolume) * 100);
1922 }
1923
1924 void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker,
1925                                      gboolean mute)
1926 {
1927         _set_mute(worker, mute);
1928 }
1929
1930 gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker)
1931 {
1932         return mafw_gst_renderer_worker_volume_is_muted(worker->wvolume);
1933 }
1934
1935 #ifdef HAVE_GDKPIXBUF
1936 void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker,
1937                                                                 gboolean current_frame_on_pause)
1938 {
1939         worker->current_frame_on_pause = current_frame_on_pause;
1940 }
1941
1942 gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker)
1943 {
1944         return worker->current_frame_on_pause;
1945 }
1946 #endif
1947
1948 void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker,
1949                                           GstSeekType seek_type,
1950                                           gint position, GError **error)
1951 {
1952         /* If player is paused and we have a timeout for going to ready
1953          * restart it. This is logical, since the user is seeking and
1954          * thus, the player is not idle anymore. Also this prevents that
1955          * when seeking streams we enter buffering and in the middle of
1956          * the buffering process we set the pipeline to ready (which stops
1957          * the buffering before it reaches 100%, making the client think
1958          * buffering is still going on).
1959          */
1960         if (worker->ready_timeout) {
1961                 _remove_ready_timeout(worker);
1962                 _add_ready_timeout(worker);
1963         }
1964
1965         _do_seek(worker, seek_type, position, error);
1966         if (worker->notify_seek_handler)
1967                 worker->notify_seek_handler(worker, worker->owner);
1968 }
1969
1970 /*
1971  * Gets current position, rounded down into precision of one second.  If a seek
1972  * is pending, returns the position we are going to seek.  Returns -1 on
1973  * failure.
1974  */
1975 gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker)
1976 {
1977         GstFormat format;
1978         gint64 time = 0;
1979         g_assert(worker != NULL);
1980
1981         /* If seek is ongoing, return the position where we are seeking. */
1982         if (worker->seek_position != -1)
1983         {
1984                 return worker->seek_position;
1985         }
1986         /* Otherwise query position from pipeline. */
1987         format = GST_FORMAT_TIME;
1988         if (worker->pipeline &&
1989             gst_element_query_position(worker->pipeline, &format, &time))
1990         {
1991                 return (gint)(NSECONDS_TO_SECONDS(time));
1992         }
1993         return -1;
1994 }
1995
1996 GHashTable *mafw_gst_renderer_worker_get_current_metadata(
1997         MafwGstRendererWorker *worker)
1998 {
1999         return worker->current_metadata;
2000 }
2001
2002 void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid)
2003 {
2004         /* Check for errors on the target window */
2005         XSetErrorHandler(xerror);
2006
2007         /* Store the target window id */
2008         g_debug("Setting xid: %x", (guint)xid);
2009         worker->xid = xid;
2010
2011         /* Check if we should use it right away */
2012         mafw_gst_renderer_worker_apply_xid(worker);
2013 }
2014
2015 XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker)
2016 {
2017         return worker->xid;
2018 }
2019
2020 gboolean mafw_gst_renderer_worker_get_autopaint(
2021         MafwGstRendererWorker *worker)
2022 {
2023         return worker->autopaint;
2024 }
2025 void mafw_gst_renderer_worker_set_autopaint(
2026         MafwGstRendererWorker *worker, gboolean autopaint)
2027 {
2028         worker->autopaint = autopaint;
2029         if (worker->vsink)
2030                 g_object_set(worker->vsink, "autopaint-colorkey",
2031                              autopaint, NULL);
2032 }
2033
2034 gint mafw_gst_renderer_worker_get_colorkey(
2035         MafwGstRendererWorker *worker)
2036 {
2037         return worker->colorkey;
2038 }
2039
2040 gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker)
2041 {
2042         return worker->media.seekable;
2043 }
2044
2045 static void _play_pl_next(MafwGstRendererWorker *worker) {
2046         gchar *next;
2047
2048         g_assert(worker != NULL);
2049         g_return_if_fail(worker->pl.items != NULL);
2050
2051         next = (gchar *) g_slist_nth_data(worker->pl.items,
2052                                           ++worker->pl.current);
2053         mafw_gst_renderer_worker_stop(worker);
2054         _reset_media_info(worker);
2055
2056         worker->media.location = g_strdup(next);
2057         _construct_pipeline(worker);
2058         _start_play(worker);
2059 }
2060
2061 static void _on_pl_entry_parsed(TotemPlParser *parser, gchar *uri,
2062                                 gpointer metadata, gpointer user_data)
2063 {
2064         MafwGstRendererWorker *worker = user_data;
2065
2066         if (uri != NULL) {
2067                 worker->pl.items =
2068                         g_slist_append(worker->pl.items, g_strdup(uri));
2069         }
2070 }
2071
2072 static void _do_play(MafwGstRendererWorker *worker)
2073 {
2074         g_assert(worker != NULL);
2075
2076         if (worker->pipeline == NULL) {
2077                 g_debug("play without a pipeline!");
2078                 return;
2079         }
2080         worker->report_statechanges = TRUE;
2081
2082         /* If we have to stay paused, we do and add the ready
2083          * timeout. Otherwise, we move the pipeline */
2084         if (!worker->stay_paused) {
2085                 /* If pipeline is READY, we move it to PAUSED,
2086                  * otherwise, to PLAYING */
2087                 if (worker->state == GST_STATE_READY) {
2088                         gst_element_set_state(worker->pipeline,
2089                                               GST_STATE_PAUSED);
2090                         g_debug("setting pipeline to PAUSED");
2091                 } else {
2092                         _reset_volume_and_mute_to_pipeline(worker);
2093                         gst_element_set_state(worker->pipeline,
2094                                               GST_STATE_PLAYING);
2095                         g_debug("setting pipeline to PLAYING");
2096                 }
2097         }
2098         else {
2099                 g_debug("staying in PAUSED state");
2100                 _add_ready_timeout(worker);
2101         }
2102 }
2103
2104 void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker,
2105                                   const gchar *uri)
2106 {
2107         g_assert(uri);
2108
2109         mafw_gst_renderer_worker_stop(worker);
2110         _reset_media_info(worker);
2111         _reset_pl_info(worker);
2112         /* Check if the item to play is a single item or a playlist. */
2113         if (uri_is_playlist(uri)){
2114                 /* In case of a playlist we parse it and start playing the first
2115                    item of the playlist. */
2116                 TotemPlParser *pl_parser;
2117                 gchar *item;
2118
2119                 /* Initialize the playlist parser */
2120                 pl_parser = totem_pl_parser_new ();
2121                 g_object_set(pl_parser, "recurse", TRUE, "disable-unsafe",
2122                              TRUE, NULL);
2123                 g_signal_connect(G_OBJECT(pl_parser), "entry-parsed",
2124                                  G_CALLBACK(_on_pl_entry_parsed), worker);
2125
2126                 /* Parsing */
2127                 if (totem_pl_parser_parse(pl_parser, uri, FALSE) !=
2128                     TOTEM_PL_PARSER_RESULT_SUCCESS) {
2129                         /* An error happens while parsing */
2130                         _send_error(worker,
2131                                     g_error_new(MAFW_RENDERER_ERROR,
2132                                                 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2133                                                 "Playlist parsing failed: %s",
2134                                                 uri));
2135                         return;
2136                 }
2137
2138                 if (!worker->pl.items) {
2139                         /* The playlist is empty */
2140                         _send_error(worker,
2141                                     g_error_new(MAFW_RENDERER_ERROR,
2142                                                 MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2143                                                 "The playlist %s is empty.",
2144                                                 uri));
2145                         return;
2146                 }
2147
2148                 /* Set the playback mode */
2149                 worker->mode = WORKER_MODE_PLAYLIST;
2150                 worker->pl.notify_play_pending = TRUE;
2151
2152                 /* Set the item to be played */
2153                 worker->pl.current = 0;
2154                 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2155                 worker->media.location = g_strdup(item);
2156
2157                 /* Free the playlist parser */
2158                 g_object_unref(pl_parser);
2159         } else {
2160                 /* Single item. Set the playback mode according to that */
2161                 worker->mode = WORKER_MODE_SINGLE_PLAY;
2162
2163                 /* Set the item to be played */
2164                 worker->media.location = g_strdup(uri);
2165         }
2166         _construct_pipeline(worker);
2167         _start_play(worker);
2168 }
2169
2170 void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker,
2171                                                 gchar **uris)
2172 {
2173         gint i;
2174         gchar *item;
2175
2176         g_assert(uris && uris[0]);
2177
2178         mafw_gst_renderer_worker_stop(worker);
2179         _reset_media_info(worker);
2180         _reset_pl_info(worker);
2181
2182         /* Add the uris to playlist */
2183         i = 0;
2184         while (uris[i]) {
2185                 worker->pl.items =
2186                         g_slist_append(worker->pl.items, g_strdup(uris[i]));
2187                 i++;
2188         }
2189
2190         /* Set the playback mode */
2191         worker->mode = WORKER_MODE_REDUNDANT;
2192         worker->pl.notify_play_pending = TRUE;
2193
2194         /* Set the item to be played */
2195         worker->pl.current = 0;
2196         item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2197         worker->media.location = g_strdup(item);
2198
2199         /* Start playing */
2200         _construct_pipeline(worker);
2201         _start_play(worker);
2202 }
2203
2204 /*
2205  * Currently, stop destroys the Gst pipeline and resets the worker into
2206  * default startup configuration.
2207  */
2208 void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker)
2209 {
2210         g_debug("worker stop");
2211         g_assert(worker != NULL);
2212
2213         /* If location is NULL, this is a pre-created pipeline */
2214         if (worker->async_bus_id && worker->pipeline && !worker->media.location)
2215                 return;
2216
2217         if (worker->pipeline) {
2218                 g_debug("destroying pipeline");
2219                 if (worker->async_bus_id) {
2220                         g_source_remove(worker->async_bus_id);
2221                         worker->async_bus_id = 0;
2222                 }
2223                 gst_bus_set_sync_handler(worker->bus, NULL, NULL);
2224                 gst_element_set_state(worker->pipeline, GST_STATE_NULL);
2225                 if (worker->bus) {
2226                         gst_object_unref(GST_OBJECT_CAST(worker->bus));
2227                         worker->bus = NULL;
2228                 }
2229                 gst_object_unref(GST_OBJECT(worker->pipeline));
2230                 worker->pipeline = NULL;
2231         }
2232
2233         /* Reset worker */
2234         worker->report_statechanges = TRUE;
2235         worker->state = GST_STATE_NULL;
2236         worker->prerolling = FALSE;
2237         worker->is_live = FALSE;
2238         worker->buffering = FALSE;
2239         worker->is_stream = FALSE;
2240         worker->is_error = FALSE;
2241         worker->eos = FALSE;
2242         worker->seek_position = -1;
2243         _remove_ready_timeout(worker);
2244         _free_taglist(worker);
2245         if (worker->current_metadata) {
2246                 g_hash_table_destroy(worker->current_metadata);
2247                 worker->current_metadata = NULL;
2248         }
2249
2250         if (worker->duration_seek_timeout != 0) {
2251                 g_source_remove(worker->duration_seek_timeout);
2252                 worker->duration_seek_timeout = 0;
2253         }
2254
2255         /* Reset media iformation */
2256         _reset_media_info(worker);
2257
2258         /* We are not playing, so we can let the screen blank */
2259         blanking_allow();
2260
2261         /* And now get a fresh pipeline ready */
2262         _construct_pipeline(worker);
2263 }
2264
2265 void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker)
2266 {
2267         g_assert(worker != NULL);
2268
2269         if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2270             !worker->prerolling) {
2271                 /* If we are buffering and get a pause, we have to
2272                  * signal state change and stay_paused */
2273                 g_debug("Pausing while buffering, signalling state change");
2274                 worker->stay_paused = TRUE;
2275                 if (worker->notify_pause_handler) {
2276                         worker->notify_pause_handler(
2277                                 worker,
2278                                 worker->owner);
2279                 }
2280         } else {
2281                 worker->report_statechanges = TRUE;
2282
2283                 if (gst_element_set_state(worker->pipeline, GST_STATE_PAUSED) ==
2284                     GST_STATE_CHANGE_ASYNC)
2285                 {
2286                         /* XXX this blocks at most 2 seconds. */
2287                         gst_element_get_state(worker->pipeline, NULL, NULL,
2288                                       2 * GST_SECOND);
2289                 }
2290                 blanking_allow();
2291         }
2292 }
2293
2294 void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker)
2295 {
2296         if (worker->mode == WORKER_MODE_PLAYLIST ||
2297             worker->mode == WORKER_MODE_REDUNDANT) {
2298                 /* We must notify play if the "playlist" playback
2299                    is resumed */
2300                 worker->pl.notify_play_pending = TRUE;
2301         }
2302         if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2303             !worker->prerolling) {
2304                 /* If we are buffering we cannot resume, but we know
2305                  * that the pipeline will be moved to PLAYING as
2306                  * stay_paused is FALSE, so we just activate the state
2307                  * change report, this way as soon as buffering is finished
2308                  * the pipeline will be set to PLAYING and the state
2309                  * change will be reported */
2310                 worker->report_statechanges = TRUE;
2311                 g_debug("Resumed while buffering, activating pipeline state "
2312                         "changes");
2313                 /* Notice though that we can receive the Resume before
2314                    we get any buffering information. In that case
2315                    we go with the "else" branch and set the pipeline to
2316                    to PLAYING. However, it is possible that in this case
2317                    we get the fist buffering signal before the
2318                    PAUSED -> PLAYING state change. In that case, since we
2319                    ignore state changes while buffering we never signal
2320                    the state change to PLAYING. We can only fix this by
2321                    checking, when we receive a PAUSED -> PLAYING transition
2322                    if we are buffering, and in that case signal the state
2323                    change (if we get that transition while buffering
2324                    is on, it can only mean that the client resumed playback
2325                    while buffering, and we must notify the state change) */
2326         } else {
2327                 _do_play(worker);
2328         }
2329 }
2330
2331 static void _volume_init_cb(MafwGstRendererWorkerVolume *wvolume,
2332                             gpointer data)
2333 {
2334         MafwGstRendererWorker *worker = data;
2335         gdouble volume;
2336         gboolean mute;
2337
2338         worker->wvolume = wvolume;
2339
2340         g_debug("volume manager initialized");
2341
2342         volume = mafw_gst_renderer_worker_volume_get(wvolume);
2343         mute = mafw_gst_renderer_worker_volume_is_muted(wvolume);
2344         _volume_cb(wvolume, volume, worker);
2345         _mute_cb(wvolume, mute, worker);
2346 }
2347
2348 MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner)
2349 {
2350         MafwGstRendererWorker *worker;
2351         GMainContext *main_context;
2352
2353         worker = g_new0(MafwGstRendererWorker, 1);
2354         worker->mode = WORKER_MODE_SINGLE_PLAY;
2355         worker->pl.items = NULL;
2356         worker->pl.current = 0;
2357         worker->pl.notify_play_pending = TRUE;
2358         worker->owner = owner;
2359         worker->report_statechanges = TRUE;
2360         worker->state = GST_STATE_NULL;
2361         worker->seek_position = -1;
2362         worker->ready_timeout = 0;
2363         worker->in_ready = FALSE;
2364         worker->xid = 0;
2365         worker->autopaint = TRUE;
2366         worker->colorkey = -1;
2367         worker->vsink = NULL;
2368         worker->asink = NULL;
2369         worker->tsink = NULL;
2370         worker->tag_list = NULL;
2371         worker->current_metadata = NULL;
2372         worker->subtitles.enabled = FALSE;
2373         worker->subtitles.font = NULL;
2374         worker->subtitles.encoding = NULL;
2375
2376 #ifdef HAVE_GDKPIXBUF
2377         worker->current_frame_on_pause = FALSE;
2378         _init_tmp_files_pool(worker);
2379 #endif
2380         worker->notify_seek_handler = NULL;
2381         worker->notify_pause_handler = NULL;
2382         worker->notify_play_handler = NULL;
2383         worker->notify_buffer_status_handler = NULL;
2384         worker->notify_eos_handler = NULL;
2385         worker->notify_error_handler = NULL;
2386         Global_worker = worker;
2387         main_context = g_main_context_default();
2388         worker->wvolume = NULL;
2389         mafw_gst_renderer_worker_volume_init(main_context,
2390                                              _volume_init_cb, worker,
2391                                              _volume_cb, worker,
2392                                              _mute_cb, worker);
2393         blanking_init();
2394         _construct_pipeline(worker);
2395
2396         return worker;
2397 }
2398
2399 void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker)
2400 {
2401         blanking_deinit();
2402 #ifdef HAVE_GDKPIXBUF
2403         _destroy_tmp_files_pool(worker);
2404 #endif
2405         mafw_gst_renderer_worker_volume_destroy(worker->wvolume);
2406         mafw_gst_renderer_worker_stop(worker);
2407 }
2408 /* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */