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