Small clean of subtitles applet code.
[mafwsubrenderer] / mafw-gst-subtitles-renderer / 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 #include "keypad.h"
49
50 #undef  G_LOG_DOMAIN
51 #define G_LOG_DOMAIN "mafw-gst-renderer-worker"
52
53 #define MAFW_GST_RENDERER_WORKER_SECONDS_READY 60
54 #define MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY 4
55
56 #define MAFW_GST_MISSING_TYPE_DECODER "decoder"
57 #define MAFW_GST_MISSING_TYPE_ENCODER "encoder"
58
59 #define MAFW_GST_BUFFER_TIME  600000L
60 #define MAFW_GST_LATENCY_TIME (MAFW_GST_BUFFER_TIME / 2)
61
62 #define NSECONDS_TO_SECONDS(ns) ((ns)%1000000000 < 500000000?\
63                                  GST_TIME_AS_SECONDS((ns)):\
64                                  GST_TIME_AS_SECONDS((ns))+1)
65
66 #define _current_metadata_add(worker, key, type, value) \
67                 do { \
68                         if (!worker->current_metadata) \
69                                 worker->current_metadata = mafw_metadata_new(); \
70                         /* At first remove old value */ \
71                         g_hash_table_remove(worker->current_metadata, key); \
72                         mafw_metadata_add_something(worker->current_metadata, \
73                                         key, type, 1, value); \
74                 } while (0)
75
76 /* Private variables. */
77 /* Global reference to worker instance, needed for Xerror handler */
78 static MafwGstRendererWorker *Global_worker = NULL;
79
80 /* Forward declarations. */
81 static void _do_play(MafwGstRendererWorker *worker);
82 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
83                      gint position, GError **error);
84 static void _play_pl_next(MafwGstRendererWorker *worker);
85
86 static void _emit_metadatas(MafwGstRendererWorker *worker);
87
88 /* Playlist parsing */
89 static void _on_pl_entry_parsed(TotemPlParser *parser, gchar *uri,
90                                 gpointer metadata, GSList **plitems)
91 {
92         if (uri != NULL) {
93                 *plitems = g_slist_append(*plitems, g_strdup(uri));
94         }
95 }
96 static GSList *_parse_playlist(const gchar *uri)
97 {
98         static TotemPlParser *pl_parser = NULL;
99         GSList *plitems = NULL;
100         gulong handler_id;
101
102         /* Initialize the playlist parser */
103         if (!pl_parser)
104         {
105                 pl_parser = totem_pl_parser_new ();
106                 g_object_set(pl_parser, "recurse", TRUE, "disable-unsafe",
107                      TRUE, NULL);
108         }
109         handler_id = g_signal_connect(G_OBJECT(pl_parser), "entry-parsed",
110                          G_CALLBACK(_on_pl_entry_parsed), &plitems);
111         /* Parsing */
112         if (totem_pl_parser_parse(pl_parser, uri, FALSE) !=
113             TOTEM_PL_PARSER_RESULT_SUCCESS) {
114                 /* An error happens while parsing */
115                 
116         }
117         g_signal_handler_disconnect(pl_parser, handler_id);
118         return plitems;
119 }
120                 
121 /*
122  * Sends @error to MafwGstRenderer.  Only call this from the glib main thread, or
123  * face the consequences.  @err is free'd.
124  */
125 static void _send_error(MafwGstRendererWorker *worker, GError *err)
126 {
127         worker->is_error = TRUE;
128         if (worker->notify_error_handler)
129                 worker->notify_error_handler(worker, worker->owner, err);
130         g_error_free(err);
131 }
132
133 /*
134  * Posts an @error on the gst bus.  _async_bus_handler will then pick it up and
135  * forward to MafwGstRenderer.  @err is free'd.
136  */
137 static void _post_error(MafwGstRendererWorker *worker, GError *err)
138 {
139         gst_bus_post(worker->bus,
140                      gst_message_new_error(GST_OBJECT(worker->pipeline),
141                                            err, NULL));
142         g_error_free(err);
143 }
144
145 #ifdef HAVE_GDKPIXBUF
146 typedef struct {
147         MafwGstRendererWorker *worker;
148         gchar *metadata_key;
149         GdkPixbuf *pixbuf;
150 } SaveGraphicData;
151
152 static gchar *_init_tmp_file(void)
153 {
154         gint fd;
155         gchar *path = NULL;
156
157         fd = g_file_open_tmp("mafw-gst-renderer-XXXXXX.jpeg", &path, NULL);
158         close(fd);
159
160         return path;
161 }
162
163 static void _init_tmp_files_pool(MafwGstRendererWorker *worker)
164 {
165         guint8 i;
166
167         worker->tmp_files_pool_index = 0;
168
169         for (i = 0; i < MAFW_GST_RENDERER_MAX_TMP_FILES; i++) {
170                 worker->tmp_files_pool[i] = NULL;
171         }
172 }
173
174 static void _destroy_tmp_files_pool(MafwGstRendererWorker *worker)
175 {
176         guint8 i;
177
178         for (i = 0; (i < MAFW_GST_RENDERER_MAX_TMP_FILES) &&
179                      (worker->tmp_files_pool[i] != NULL); i++) {
180                 g_unlink(worker->tmp_files_pool[i]);
181                 g_free(worker->tmp_files_pool[i]);
182         }
183 }
184
185 static const gchar *_get_tmp_file_from_pool(
186                         MafwGstRendererWorker *worker)
187 {
188         gchar *path = worker->tmp_files_pool[worker->tmp_files_pool_index];
189
190         if (path == NULL) {
191                 path = _init_tmp_file();
192                 worker->tmp_files_pool[worker->tmp_files_pool_index] = path;
193         }
194
195         if (++(worker->tmp_files_pool_index) >=
196             MAFW_GST_RENDERER_MAX_TMP_FILES) {
197                 worker->tmp_files_pool_index = 0;
198         }
199
200         return path;
201 }
202
203 static void _destroy_pixbuf (guchar *pixbuf, gpointer data)
204 {
205         gst_buffer_unref(GST_BUFFER(data));
206 }
207
208 static void _emit_gst_buffer_as_graphic_file_cb(GstBuffer *new_buffer,
209                                                 gpointer user_data)
210 {
211         SaveGraphicData *sgd = user_data;
212         GdkPixbuf *pixbuf = NULL;
213
214         if (new_buffer != NULL) {
215                 gint width, height;
216                 GstStructure *structure;
217
218                 structure =
219                         gst_caps_get_structure(GST_BUFFER_CAPS(new_buffer), 0);
220
221                 gst_structure_get_int(structure, "width", &width);
222                 gst_structure_get_int(structure, "height", &height);
223
224                 pixbuf = gdk_pixbuf_new_from_data(
225                         GST_BUFFER_DATA(new_buffer), GDK_COLORSPACE_RGB,
226                         FALSE, 8, width, height,
227                         GST_ROUND_UP_4(3 * width), _destroy_pixbuf,
228                         new_buffer);
229
230                 if (sgd->pixbuf != NULL) {
231                         g_object_unref(sgd->pixbuf);
232                         sgd->pixbuf = NULL;
233                 }
234         } else {
235                 pixbuf = sgd->pixbuf;
236         }
237
238         if (pixbuf != NULL) {
239                 gboolean save_ok;
240                 GError *error = NULL;
241                 const gchar *filename;
242
243                 filename = _get_tmp_file_from_pool(sgd->worker);
244
245                 save_ok = gdk_pixbuf_save (pixbuf, filename, "jpeg", &error,
246                                            NULL);
247
248                 g_object_unref (pixbuf);
249
250                 if (save_ok) {
251                         /* Add the info to the current metadata. */
252                         _current_metadata_add(sgd->worker, sgd->metadata_key,
253                                               G_TYPE_STRING,
254                                               (gchar*)filename);
255
256                         /* Emit the metadata. */
257                         mafw_renderer_emit_metadata_string(sgd->worker->owner,
258                                                            sgd->metadata_key,
259                                                            (gchar *) filename);
260                 } else {
261                         if (error != NULL) {
262                                 g_warning ("%s\n", error->message);
263                                 g_error_free (error);
264                         } else {
265                                 g_critical("Unknown error when saving pixbuf "
266                                            "with GStreamer data");
267                         }
268                 }
269         } else {
270                 g_warning("Could not create pixbuf from GstBuffer");
271         }
272
273         g_free(sgd->metadata_key);
274         g_free(sgd);
275 }
276
277 static void _pixbuf_size_prepared_cb (GdkPixbufLoader *loader, 
278                                       gint width, gint height,
279                                       gpointer user_data)
280 {
281         /* Be sure the image size is reasonable */
282         if (width > 512 || height > 512) {
283                 g_debug ("pixbuf: image is too big: %dx%d", width, height);
284                 gdouble ar;
285                 ar = (gdouble) width / height;
286                 if (width > height) {
287                         width = 512;
288                         height = width / ar;
289                 } else {
290                         height = 512;
291                         width = height * ar;
292                 }
293                 g_debug ("pixbuf: scaled image to %dx%d", width, height);
294                 gdk_pixbuf_loader_set_size (loader, width, height);
295         }
296 }
297
298 static void _emit_gst_buffer_as_graphic_file(MafwGstRendererWorker *worker,
299                                              GstBuffer *buffer,
300                                              const gchar *metadata_key)
301 {
302         GdkPixbufLoader *loader;
303         GstStructure *structure;
304         const gchar *mime = NULL;
305         GError *error = NULL;
306
307         g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
308
309         structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0);
310         mime = gst_structure_get_name(structure);
311
312         if (g_str_has_prefix(mime, "video/x-raw")) {
313                 gint framerate_d, framerate_n;
314                 GstCaps *to_caps;
315                 SaveGraphicData *sgd;
316
317                 gst_structure_get_fraction (structure, "framerate",
318                                             &framerate_n, &framerate_d);
319
320                 to_caps = gst_caps_new_simple ("video/x-raw-rgb",
321                                                "bpp", G_TYPE_INT, 24,
322                                                "depth", G_TYPE_INT, 24,
323                                                "framerate", GST_TYPE_FRACTION,
324                                                framerate_n, framerate_d,
325                                                "pixel-aspect-ratio",
326                                                GST_TYPE_FRACTION, 1, 1,
327                                                "endianness",
328                                                G_TYPE_INT, G_BIG_ENDIAN,
329                                                "red_mask", G_TYPE_INT,
330                                                0xff0000,
331                                                "green_mask",
332                                                G_TYPE_INT, 0x00ff00,
333                                                "blue_mask",
334                                                G_TYPE_INT, 0x0000ff,
335                                                NULL);
336
337                 sgd = g_new0(SaveGraphicData, 1);
338                 sgd->worker = worker;
339                 sgd->metadata_key = g_strdup(metadata_key);
340
341                 g_debug("pixbuf: using bvw to convert image format");
342                 bvw_frame_conv_convert (buffer, to_caps,
343                                         _emit_gst_buffer_as_graphic_file_cb,
344                                         sgd);
345         } else {
346                 GdkPixbuf *pixbuf = NULL;
347                 loader = gdk_pixbuf_loader_new_with_mime_type(mime, &error);
348                 g_signal_connect (G_OBJECT (loader), "size-prepared", 
349                                  (GCallback)_pixbuf_size_prepared_cb, NULL);
350
351                 if (loader == NULL) {
352                         g_warning ("%s\n", error->message);
353                         g_error_free (error);
354                 } else {
355                         if (!gdk_pixbuf_loader_write (loader,
356                                                       GST_BUFFER_DATA(buffer),
357                                                       GST_BUFFER_SIZE(buffer),
358                                                       &error)) {
359                                 g_warning ("%s\n", error->message);
360                                 g_error_free (error);
361
362                                 gdk_pixbuf_loader_close (loader, NULL);
363                         } else {
364                                 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
365
366                                 if (!gdk_pixbuf_loader_close (loader, &error)) {
367                                         g_warning ("%s\n", error->message);
368                                         g_error_free (error);
369
370                                         g_object_unref(pixbuf);
371                                 } else {
372                                         SaveGraphicData *sgd;
373
374                                         sgd = g_new0(SaveGraphicData, 1);
375
376                                         sgd->worker = worker;
377                                         sgd->metadata_key =
378                                                 g_strdup(metadata_key);
379                                         sgd->pixbuf = pixbuf;
380
381                                         _emit_gst_buffer_as_graphic_file_cb(
382                                                 NULL, sgd);
383                                 }
384                         }
385                         g_object_unref(loader);
386                 }
387         }
388 }
389 #endif
390
391 static gboolean _go_to_gst_ready(gpointer user_data)
392 {
393         MafwGstRendererWorker *worker = user_data;
394
395         g_return_val_if_fail(worker->state == GST_STATE_PAUSED ||
396                              worker->prerolling, FALSE);
397
398         worker->seek_position =
399                 mafw_gst_renderer_worker_get_position(worker);
400
401         g_debug("going to GST_STATE_READY");
402         gst_element_set_state(worker->pipeline, GST_STATE_READY);
403         worker->in_ready = TRUE;
404         worker->ready_timeout = 0;
405
406         return FALSE;
407 }
408
409 static void _add_ready_timeout(MafwGstRendererWorker *worker)
410 {
411         if (worker->media.seekable) {
412                 if (!worker->ready_timeout)
413                 {
414                         g_debug("Adding timeout to go to GST_STATE_READY");
415                         worker->ready_timeout =
416                                 g_timeout_add_seconds(
417                                         MAFW_GST_RENDERER_WORKER_SECONDS_READY,
418                                         _go_to_gst_ready,
419                                         worker);
420                 }
421         } else {
422                 g_debug("Not adding timeout to go to GST_STATE_READY as media "
423                         "is not seekable");
424                 worker->ready_timeout = 0;
425         }
426 }
427
428 static void _remove_ready_timeout(MafwGstRendererWorker *worker)
429 {
430         if (worker->ready_timeout != 0) {
431                 g_debug("removing timeout for READY");
432                 g_source_remove(worker->ready_timeout);
433                 worker->ready_timeout = 0;
434         }
435         worker->in_ready = FALSE;
436 }
437
438 static gboolean _emit_video_info(MafwGstRendererWorker *worker)
439 {
440         mafw_renderer_emit_metadata_int(worker->owner,
441                                     MAFW_METADATA_KEY_RES_X,
442                                     worker->media.video_width);
443         mafw_renderer_emit_metadata_int(worker->owner,
444                                     MAFW_METADATA_KEY_RES_Y,
445                                     worker->media.video_height);
446         mafw_renderer_emit_metadata_double(worker->owner,
447                                        MAFW_METADATA_KEY_VIDEO_FRAMERATE,
448                                        worker->media.fps);
449         return FALSE;
450 }
451
452 /*
453  * Checks if the video details are supported.  It also extracts other useful
454  * information (such as PAR and framerate) from the caps, if available.  NOTE:
455  * this will be called from a different thread than glib's mainloop (when
456  * invoked via _stream_info_cb);  don't call MafwGstRenderer directly.
457  *
458  * Returns: TRUE if video details are acceptable.
459  */
460 static gboolean _handle_video_info(MafwGstRendererWorker *worker,
461                                    const GstStructure *structure)
462 {
463         gint width, height;
464         gdouble fps;
465
466         width = height = 0;
467         gst_structure_get_int(structure, "width", &width);
468         gst_structure_get_int(structure, "height", &height);
469         g_debug("video size: %d x %d", width, height);
470         if (gst_structure_has_field(structure, "pixel-aspect-ratio"))
471         {
472                 gst_structure_get_fraction(structure, "pixel-aspect-ratio",
473                                            &worker->media.par_n,
474                                            &worker->media.par_d);
475                 g_debug("video PAR: %d:%d", worker->media.par_n,
476                         worker->media.par_d);
477                 width = width * worker->media.par_n / worker->media.par_d;
478         }
479
480         fps = 1.0;
481         if (gst_structure_has_field(structure, "framerate"))
482         {
483                 gint fps_n, fps_d;
484
485                 gst_structure_get_fraction(structure, "framerate",
486                                            &fps_n, &fps_d);
487                 if (fps_d > 0)
488                         fps = (gdouble)fps_n / (gdouble)fps_d;
489                 g_debug("video fps: %f", fps);
490         }
491
492         worker->media.video_width = width;
493         worker->media.video_height = height;
494         worker->media.fps = fps;
495
496         /* Add the info to the current metadata. */
497         gint p_width, p_height, p_fps;
498
499         p_width = width;
500         p_height = height;
501         p_fps = fps;
502
503         _current_metadata_add(worker, MAFW_METADATA_KEY_RES_X, G_TYPE_INT,
504                               p_width);
505         _current_metadata_add(worker, MAFW_METADATA_KEY_RES_Y, G_TYPE_INT,
506                               p_height);
507         _current_metadata_add(worker, MAFW_METADATA_KEY_VIDEO_FRAMERATE,
508                               G_TYPE_DOUBLE,
509                               p_fps);
510
511         /* Emit the metadata.*/
512         g_idle_add((GSourceFunc)_emit_video_info, worker);
513
514         return TRUE;
515 }
516
517 static void _parse_stream_info_item(MafwGstRendererWorker *worker, GObject *obj)
518 {
519         GParamSpec *pspec;
520         GEnumValue *val;
521         gint type;
522
523         g_object_get(obj, "type", &type, NULL);
524         pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), "type");
525         val = g_enum_get_value(G_PARAM_SPEC_ENUM(pspec)->enum_class, type);
526         if (!val)
527                 return;
528         if (!g_ascii_strcasecmp(val->value_nick, "video") ||
529             !g_ascii_strcasecmp(val->value_name, "video"))
530         {
531                 GstCaps *vcaps;
532                 GstObject *object;
533
534                 object = NULL;
535                 g_object_get(obj, "object", &object, NULL);
536                 vcaps = NULL;
537                 if (object) {
538                         vcaps = gst_pad_get_caps(GST_PAD_CAST(object));
539                 } else {
540                         g_object_get(obj, "caps", &vcaps, NULL);
541                         gst_caps_ref(vcaps);
542                 }
543                 if (vcaps) {
544                         if (gst_caps_is_fixed(vcaps))
545                         {
546                                 _handle_video_info(
547                                         worker,
548                                         gst_caps_get_structure(vcaps, 0));
549                         }
550                         gst_caps_unref(vcaps);
551                 }
552         }
553 }
554
555 /* It always returns FALSE, because it is used as an idle callback as well. */
556 static gboolean _parse_stream_info(MafwGstRendererWorker *worker)
557 {
558         GList *stream_info, *s;
559
560         stream_info = NULL;
561         if (g_object_class_find_property(G_OBJECT_GET_CLASS(worker->pipeline),
562                                          "stream-info"))
563         {
564                 g_object_get(worker->pipeline,
565                              "stream-info", &stream_info, NULL);
566         }
567         for (s = stream_info; s; s = g_list_next(s))
568                 _parse_stream_info_item(worker, G_OBJECT(s->data));
569         return FALSE;
570 }
571
572 static void mafw_gst_renderer_worker_apply_xid(MafwGstRendererWorker *worker)
573 {
574         /* Set sink to render on the provided XID if we have do have
575            a XID a valid video sink and we are rendeing video content */
576         if (worker->xid && 
577             worker->vsink && 
578             worker->media.has_visual_content)
579         {
580                 g_debug ("Setting overlay, window id: %x", 
581                          (gint) worker->xid);
582                 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(worker->vsink), 
583                                              worker->xid);
584                 /* Ask the gst to redraw the frame if we are paused */
585                 /* TODO: in MTG this works only in non-fs -> fs way. */
586                 if (worker->state == GST_STATE_PAUSED)
587                 {
588                         gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink));
589                 }
590         } else {
591                 g_debug("Not setting overlay for window id: %x", 
592                         (gint) worker->xid);
593         }
594 }
595
596 /*
597  * GstBus synchronous message handler.  NOTE that this handler is NOT invoked
598  * from the glib thread, so be careful what you do here.
599  */
600 static GstBusSyncReply _sync_bus_handler(GstBus *bus, GstMessage *msg,
601                                          MafwGstRendererWorker *worker)
602 {
603         if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
604             gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
605         {
606                 g_debug("got prepare-xwindow-id");
607                 worker->media.has_visual_content = TRUE;
608                 /* The user has to preset the XID, we don't create windows by
609                  * ourselves. */
610                 if (!worker->xid) {
611                         /* We must post an error message to the bus that will
612                          * be picked up by _async_bus_handler.  Calling the
613                          * notification function directly from here (different
614                          * thread) is not healthy. */
615                         g_warning("No video window set!");
616                         _post_error(worker,
617                                     g_error_new_literal(
618                                             MAFW_RENDERER_ERROR,
619                                             MAFW_RENDERER_ERROR_PLAYBACK,
620                                             "No video window XID set"));
621                         gst_message_unref (msg);
622                         return GST_BUS_DROP;
623                 } else {
624                         g_debug ("Video window to use is: %x", 
625                                  (gint) worker->xid);
626                 }
627
628                 /* Instruct vsink to use the client-provided window */
629                 mafw_gst_renderer_worker_apply_xid(worker);
630
631                 /* Handle colorkey and autopaint */
632                 mafw_gst_renderer_worker_set_autopaint(
633                         worker,
634                         worker->autopaint);
635                 if (worker->colorkey == -1)
636                         g_object_get(worker->vsink,
637                                 "colorkey", &worker->colorkey, NULL);
638                 else
639                         mafw_gst_renderer_worker_set_colorkey(
640                         worker,
641                         worker->colorkey);
642                 /* Defer the signal emission to the thread running the
643                  * mainloop. */
644                 if (worker->colorkey != -1) {
645                         gst_bus_post(worker->bus,
646                                      gst_message_new_application(
647                                              GST_OBJECT(worker->vsink),
648                                              gst_structure_empty_new("ckey")));
649                 }
650                 gst_message_unref (msg);
651                 return GST_BUS_DROP;
652         }
653         /* do not unref message when returning PASS */
654         return GST_BUS_PASS;
655 }
656
657 static void _free_taglist_item(GstMessage *msg, gpointer data)
658 {
659         gst_message_unref(msg);
660 }
661
662 static void _free_taglist(MafwGstRendererWorker *worker)
663 {
664         if (worker->tag_list != NULL)
665         {
666                 g_ptr_array_foreach(worker->tag_list, (GFunc)_free_taglist_item,
667                                     NULL);
668                 g_ptr_array_free(worker->tag_list, TRUE);
669                 worker->tag_list = NULL;
670         }
671 }
672
673 static gboolean _seconds_duration_equal(gint64 duration1, gint64 duration2)
674 {
675         gint64 duration1_seconds, duration2_seconds;
676
677         duration1_seconds = NSECONDS_TO_SECONDS(duration1);
678         duration2_seconds = NSECONDS_TO_SECONDS(duration2);
679
680         return duration1_seconds == duration2_seconds;
681 }
682
683 static void _check_duration(MafwGstRendererWorker *worker, gint64 value)
684 {
685         MafwGstRenderer *renderer = worker->owner;
686         gboolean right_query = TRUE;
687
688         if (value == -1) {
689                 GstFormat format = GST_FORMAT_TIME;
690                 right_query =
691                         gst_element_query_duration(worker->pipeline, &format,
692                                                    &value);
693         }
694
695         if (right_query && value > 0) {
696                 gint duration_seconds = NSECONDS_TO_SECONDS(value);
697
698                 if (!_seconds_duration_equal(worker->media.length_nanos,
699                                              value)) {                  
700                         /* Add the duration to the current metadata. */
701                         _current_metadata_add(worker, MAFW_METADATA_KEY_DURATION,
702                                                 G_TYPE_INT64,
703                                                 (gint64)duration_seconds);
704                         /* Emit the duration. */
705                         mafw_renderer_emit_metadata_int64(
706                                 worker->owner, MAFW_METADATA_KEY_DURATION,
707                                 (gint64)duration_seconds);
708                 }
709
710                 /* We compare this duration we just got with the
711                  * source one and update it in the source if needed */
712                 if (duration_seconds > 0 &&
713                         duration_seconds != renderer->media->duration) {
714                         mafw_gst_renderer_update_source_duration(
715                                 renderer,
716                                 duration_seconds);
717                 }
718         }
719
720         worker->media.length_nanos = value;
721         g_debug("media duration: %lld", worker->media.length_nanos);
722 }
723
724 static void _check_seekability(MafwGstRendererWorker *worker)
725 {
726         MafwGstRenderer *renderer = worker->owner;
727         SeekabilityType seekable = SEEKABILITY_NO_SEEKABLE;
728
729         if (worker->media.length_nanos != -1)
730         {
731                 g_debug("source seekability %d", renderer->media->seekability);
732
733                 if (renderer->media->seekability != SEEKABILITY_NO_SEEKABLE) {
734                         g_debug("Quering GStreamer for seekability");
735                         GstQuery *seek_query;
736                         GstFormat format = GST_FORMAT_TIME;
737                         /* Query the seekability of the stream */
738                         seek_query = gst_query_new_seeking(format);
739                         if (gst_element_query(worker->pipeline, seek_query)) {
740                                 gboolean renderer_seekable = FALSE;
741                                 gst_query_parse_seeking(seek_query, NULL,
742                                                         &renderer_seekable,
743                                                         NULL, NULL);
744                                 g_debug("GStreamer seekability %d",
745                                         renderer_seekable);
746                                 seekable = renderer_seekable ?
747                                         SEEKABILITY_SEEKABLE :
748                                         SEEKABILITY_NO_SEEKABLE;
749                         }
750                         gst_query_unref(seek_query);
751                 }
752         }
753
754         if (worker->media.seekable != seekable) {
755                 gboolean is_seekable = (seekable == SEEKABILITY_SEEKABLE);
756
757                 /* Add the seekability to the current metadata. */
758                 _current_metadata_add(worker, MAFW_METADATA_KEY_IS_SEEKABLE,
759                         G_TYPE_BOOLEAN, is_seekable);
760
761                 /* Emit. */
762                 mafw_renderer_emit_metadata_boolean(
763                         worker->owner, MAFW_METADATA_KEY_IS_SEEKABLE,
764                         is_seekable);
765         }
766
767         g_debug("media seekable: %d", seekable);
768         worker->media.seekable = seekable;
769 }
770
771 static gboolean _query_duration_and_seekability_timeout(gpointer data)
772 {
773         MafwGstRendererWorker *worker = data;
774
775         _check_duration(worker, -1);
776         _check_seekability(worker);
777
778         worker->duration_seek_timeout = 0;
779
780         return FALSE;
781 }
782
783 /*
784  * Called when the pipeline transitions into PAUSED state.  It extracts more
785  * information from Gst.
786  */
787 static void _finalize_startup(MafwGstRendererWorker *worker)
788 {
789         /* Check video caps */
790         if (worker->media.has_visual_content) {
791                 GstPad *pad = GST_BASE_SINK_PAD(worker->vsink);
792                 GstCaps *caps = GST_PAD_CAPS(pad);
793                 if (caps && gst_caps_is_fixed(caps)) {
794                         GstStructure *structure;
795                         structure = gst_caps_get_structure(caps, 0);
796                         if (!_handle_video_info(worker, structure))
797                                 return;
798                 }
799         }
800
801         /* Something might have gone wrong at this point already. */
802         if (worker->is_error) {
803                 g_debug("Error occured during preroll");
804                 return;
805         }
806
807         /* Streaminfo might reveal the media to be unsupported.  Therefore we
808          * need to check the error again. */
809         _parse_stream_info(worker);
810         if (worker->is_error) {
811                 g_debug("Error occured. Leaving");
812                 return;
813         }
814
815         /* Check duration and seekability */
816         if (worker->duration_seek_timeout != 0) {
817                 g_source_remove(worker->duration_seek_timeout);
818                 worker->duration_seek_timeout = 0;
819         }
820         _check_duration(worker, -1);
821         _check_seekability(worker);
822 }
823
824 static void _add_duration_seek_query_timeout(MafwGstRendererWorker *worker)
825 {
826         if (worker->duration_seek_timeout != 0) {
827                 g_source_remove(worker->duration_seek_timeout);
828         }
829         worker->duration_seek_timeout = g_timeout_add_seconds(
830                 MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY,
831                 _query_duration_and_seekability_timeout,
832                 worker);
833 }
834
835 static void _do_pause_postprocessing(MafwGstRendererWorker *worker)
836 {
837         if (worker->notify_pause_handler) {
838                 worker->notify_pause_handler(worker, worker->owner);
839         }
840
841 #ifdef HAVE_GDKPIXBUF
842         if (worker->media.has_visual_content &&
843             worker->current_frame_on_pause) {
844                 GstBuffer *buffer = NULL;
845
846                 g_object_get(worker->pipeline, "frame", &buffer, NULL);
847
848                 if (buffer != NULL) {
849                         _emit_gst_buffer_as_graphic_file(
850                                 worker, buffer,
851                                 MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI);
852                 }
853         }
854 #endif
855
856         _add_ready_timeout(worker);
857 }
858
859 static void _report_playing_state(MafwGstRendererWorker * worker)
860 {
861         if (worker->report_statechanges) {
862                 switch (worker->mode) {
863                 case WORKER_MODE_SINGLE_PLAY:
864                         /* Notify play if we are playing in
865                          * single mode */
866                         if (worker->notify_play_handler)
867                                 worker->notify_play_handler(
868                                         worker,
869                                         worker->owner);
870                         break;
871                 case WORKER_MODE_PLAYLIST:
872                 case WORKER_MODE_REDUNDANT:
873                         /* Only notify play when the "playlist"
874                            playback starts, don't notify play for each
875                            individual element of the playlist. */
876                         if (worker->pl.notify_play_pending) {
877                                 if (worker->notify_play_handler)
878                                         worker->notify_play_handler(
879                                                 worker,
880                                                 worker->owner);
881                                 worker->pl.notify_play_pending = FALSE;
882                         }
883                         break;
884                 default: break;
885                 }
886         }
887 }
888
889 static void _handle_state_changed(GstMessage *msg, MafwGstRendererWorker *worker)
890 {
891         GstState newstate, oldstate;
892         GstStateChange statetrans;
893         MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
894
895         gst_message_parse_state_changed(msg, &oldstate, &newstate, NULL);
896         statetrans = GST_STATE_TRANSITION(oldstate, newstate);
897         g_debug ("State changed: %d: %d -> %d", worker->state, oldstate, newstate);
898
899         /* If the state is the same we do nothing, otherwise, we keep
900          * it */
901         if (worker->state == newstate) {
902                 return;
903         } else {
904                 worker->state = newstate;
905         }
906
907         if (statetrans == GST_STATE_CHANGE_READY_TO_PAUSED &&
908             worker->in_ready) {
909                 /* Woken up from READY, resume stream position and playback */
910                 g_debug("State changed to pause after ready");
911                 if (worker->seek_position > 0) {
912                         _check_seekability(worker);
913                         if (worker->media.seekable) {
914                                 g_debug("performing a seek");
915                                 _do_seek(worker, GST_SEEK_TYPE_SET,
916                                          worker->seek_position, NULL);
917                         } else {
918                                 g_critical("media is not seekable (and should)");
919                         }
920                 }
921
922                 /* If playing a stream wait for buffering to finish before
923                    starting to play */
924                 if (!worker->is_stream || worker->is_live) {
925                         _do_play(worker);
926                 }
927                 return;
928         }
929
930         /* While buffering, we have to wait in PAUSED 
931            until we reach 100% before doing anything */
932         if (worker->buffering) {
933                 if (statetrans == GST_STATE_CHANGE_PAUSED_TO_PLAYING) {
934                         /* Mmm... probably the client issued a seek on the
935                          * stream and then a play/resume command right away,
936                          * so the stream got into PLAYING state while
937                          * buffering. When the next buffering signal arrives,
938                          * the stream will be PAUSED silently and resumed when
939                          * buffering is done (silently too), so let's signal
940                          * the state change to PLAYING here. */
941                         _report_playing_state(worker);                  
942                 }
943                 return;
944         }
945
946         switch (statetrans) {
947         case GST_STATE_CHANGE_READY_TO_PAUSED:
948                 if (worker->prerolling && worker->report_statechanges) {
949                         /* PAUSED after pipeline has been
950                          * constructed. We check caps, seek and
951                          * duration and if staying in pause is needed,
952                          * we perform operations for pausing, such as
953                          * current frame on pause and signalling state
954                          * change and adding the timeout to go to ready */
955                         g_debug ("Prerolling done, finalizaing startup");
956                         _finalize_startup(worker);
957                         _do_play(worker);
958                         renderer->play_failed_count = 0;
959
960                         if (worker->stay_paused) {
961                                 _do_pause_postprocessing(worker);
962                         }
963                         worker->prerolling = FALSE;
964                 }
965                 break;
966         case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
967                 /* When pausing we do the stuff, like signalling
968                  * state, current frame on pause and timeout to go to
969                  * ready */
970                 if (worker->report_statechanges) {
971                         _do_pause_postprocessing(worker);
972                 }
973                 break;
974         case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
975                 /* if seek was called, at this point it is really ended */
976                 worker->seek_position = -1;
977                 worker->eos = FALSE;
978
979                 /* Signal state change if needed */
980                 _report_playing_state(worker);
981
982                 /* Prevent blanking if we are playing video */
983                 if (worker->media.has_visual_content) {
984                         blanking_prohibit();
985                 }
986                 keypadlocking_prohibit();
987                 /* Remove the ready timeout if we are playing [again] */
988                 _remove_ready_timeout(worker);
989                 /* If mode is redundant we are trying to play one of several
990                  * candidates, so when we get a successful playback, we notify
991                  * the real URI that we are playing */
992                 if (worker->mode == WORKER_MODE_REDUNDANT) {
993                         mafw_renderer_emit_metadata_string(
994                                 worker->owner,
995                                 MAFW_METADATA_KEY_URI,
996                                 worker->media.location);
997                 }
998
999                 /* Emit metadata. We wait until we reach the playing
1000                    state because this speeds up playback start time */
1001                 _emit_metadatas(worker);
1002                 /* Query duration and seekability. Useful for vbr
1003                  * clips or streams. */
1004                 _add_duration_seek_query_timeout(worker);
1005                 break;
1006         case GST_STATE_CHANGE_PAUSED_TO_READY:
1007                 /* If we went to READY, we free the taglist and
1008                  * deassign the timout it */
1009                 if (worker->in_ready) {
1010                         g_debug("changed to GST_STATE_READY");
1011                         _free_taglist(worker);
1012                 }
1013                 break;
1014         default:
1015                 break;
1016         }
1017 }
1018
1019 static void _handle_duration(MafwGstRendererWorker *worker, GstMessage *msg)
1020 {
1021         GstFormat fmt;
1022         gint64 duration;
1023
1024         gst_message_parse_duration(msg, &fmt, &duration);
1025
1026         if (worker->duration_seek_timeout != 0) {
1027                 g_source_remove(worker->duration_seek_timeout);
1028                 worker->duration_seek_timeout = 0;
1029         }
1030
1031         _check_duration(worker,
1032                         duration != GST_CLOCK_TIME_NONE ? duration : -1);
1033         _check_seekability(worker);
1034 }
1035
1036 #ifdef HAVE_GDKPIXBUF
1037 static void _emit_renderer_art(MafwGstRendererWorker *worker,
1038                                const GstTagList *list)
1039 {
1040         GstBuffer *buffer = NULL;
1041         const GValue *value = NULL;
1042
1043         g_return_if_fail(gst_tag_list_get_tag_size(list, GST_TAG_IMAGE) > 0);
1044
1045         value = gst_tag_list_get_value_index(list, GST_TAG_IMAGE, 0);
1046
1047         g_return_if_fail((value != NULL) && G_VALUE_HOLDS(value, GST_TYPE_BUFFER));
1048
1049         buffer = g_value_peek_pointer(value);
1050
1051         g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
1052
1053         _emit_gst_buffer_as_graphic_file(worker, buffer,
1054                                          MAFW_METADATA_KEY_RENDERER_ART_URI);
1055 }
1056 #endif
1057
1058 static GHashTable* _build_tagmap(void)
1059 {
1060         GHashTable *hash_table = NULL;
1061
1062         hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
1063                                            g_free);
1064
1065         g_hash_table_insert(hash_table, g_strdup(GST_TAG_TITLE),
1066                             g_strdup(MAFW_METADATA_KEY_TITLE));
1067         g_hash_table_insert(hash_table, g_strdup(GST_TAG_ARTIST),
1068                             g_strdup(MAFW_METADATA_KEY_ARTIST));
1069         g_hash_table_insert(hash_table, g_strdup(GST_TAG_AUDIO_CODEC),
1070                             g_strdup(MAFW_METADATA_KEY_AUDIO_CODEC));
1071         g_hash_table_insert(hash_table, g_strdup(GST_TAG_VIDEO_CODEC),
1072                             g_strdup(MAFW_METADATA_KEY_VIDEO_CODEC));
1073         g_hash_table_insert(hash_table, g_strdup(GST_TAG_BITRATE),
1074                             g_strdup(MAFW_METADATA_KEY_BITRATE));
1075         g_hash_table_insert(hash_table, g_strdup(GST_TAG_LANGUAGE_CODE),
1076                             g_strdup(MAFW_METADATA_KEY_ENCODING));
1077         g_hash_table_insert(hash_table, g_strdup(GST_TAG_ALBUM),
1078                             g_strdup(MAFW_METADATA_KEY_ALBUM));
1079         g_hash_table_insert(hash_table, g_strdup(GST_TAG_GENRE),
1080                             g_strdup(MAFW_METADATA_KEY_GENRE));
1081         g_hash_table_insert(hash_table, g_strdup(GST_TAG_TRACK_NUMBER),
1082                             g_strdup(MAFW_METADATA_KEY_TRACK));
1083         g_hash_table_insert(hash_table, g_strdup(GST_TAG_ORGANIZATION),
1084                             g_strdup(MAFW_METADATA_KEY_ORGANIZATION));
1085 #ifdef HAVE_GDKPIXBUF
1086         g_hash_table_insert(hash_table, g_strdup(GST_TAG_IMAGE),
1087                             g_strdup(MAFW_METADATA_KEY_RENDERER_ART_URI));
1088 #endif
1089
1090         return hash_table;
1091 }
1092
1093 /*
1094  * Emits metadata-changed signals for gst tags.
1095  */
1096 static void _emit_tag(const GstTagList *list, const gchar *tag,
1097                       MafwGstRendererWorker *worker)
1098 {
1099         /* Mapping between Gst <-> MAFW metadata tags
1100          * NOTE: This assumes that GTypes matches between GST and MAFW. */
1101         static GHashTable *tagmap = NULL;
1102         gint i, count;
1103         const gchar *mafwtag;
1104         GType type;
1105         GValueArray *values;
1106
1107         if (tagmap == NULL) {
1108                 tagmap = _build_tagmap();
1109         }
1110
1111         g_debug("tag: '%s' (type: %s)", tag,
1112                 g_type_name(gst_tag_get_type(tag)));
1113         /* Is there a mapping for this tag? */
1114         mafwtag = g_hash_table_lookup(tagmap, tag);
1115         if (!mafwtag)
1116                 return;
1117
1118 #ifdef HAVE_GDKPIXBUF
1119         if (strcmp (mafwtag, MAFW_METADATA_KEY_RENDERER_ART_URI) == 0) {
1120                 _emit_renderer_art(worker, list);
1121                 return;
1122         }
1123 #endif
1124
1125         /* Build a value array of this tag.  We need to make sure that strings
1126          * are UTF-8.  GstTagList API says that the value is always UTF8, but it
1127          * looks like the ID3 demuxer still might sometimes produce non-UTF-8
1128          * strings. */
1129         count = gst_tag_list_get_tag_size(list, tag);
1130         type = gst_tag_get_type(tag);
1131         values = g_value_array_new(count);
1132         for (i = 0; i < count; ++i) {
1133                 GValue *v = (GValue *)
1134                         gst_tag_list_get_value_index(list, tag, i);
1135                 if (type == G_TYPE_STRING) {
1136                         gchar *orig, *utf8;
1137
1138                         gst_tag_list_get_string_index(list, tag, i, &orig);
1139                         if (convert_utf8(orig, &utf8)) {
1140                                 GValue utf8gval = {0};
1141
1142                                 g_value_init(&utf8gval, G_TYPE_STRING);
1143                                 g_value_take_string(&utf8gval, utf8);
1144                                 _current_metadata_add(worker, mafwtag, G_TYPE_STRING,
1145                                                         utf8);
1146                                 g_value_array_append(values, &utf8gval);
1147                                 g_value_unset(&utf8gval);
1148                         }
1149                         g_free(orig);
1150                 } else if (type == G_TYPE_UINT) {
1151                         GValue intgval = {0};
1152                         gint intval;
1153
1154                         g_value_init(&intgval, G_TYPE_INT);
1155                         g_value_transform(v, &intgval);
1156                         intval = g_value_get_int(&intgval);
1157                         _current_metadata_add(worker, mafwtag, G_TYPE_INT,
1158                                                 intval);
1159                         g_value_array_append(values, &intgval);
1160                         g_value_unset(&intgval);
1161                 } else {
1162                         _current_metadata_add(worker, mafwtag, G_TYPE_VALUE,
1163                                                 v);
1164                         g_value_array_append(values, v);
1165                 }
1166         }
1167
1168         /* Emit the metadata. */
1169         g_signal_emit_by_name(worker->owner, "metadata-changed", mafwtag,
1170                               values);
1171
1172         g_value_array_free(values);
1173 }
1174
1175 /**
1176  * Collect tag-messages, parse it later, when playing is ongoing
1177  */
1178 static void _handle_tag(MafwGstRendererWorker *worker, GstMessage *msg)
1179 {
1180         /* Do not emit metadata until we get to PLAYING state to speed up
1181            playback start */
1182         if (worker->tag_list == NULL)
1183                 worker->tag_list = g_ptr_array_new();
1184         g_ptr_array_add(worker->tag_list, gst_message_ref(msg));
1185
1186         /* Some tags come in playing state, so in this case we have
1187            to emit them right away (example: radio stations) */
1188         if (worker->state == GST_STATE_PLAYING) {
1189                 _emit_metadatas(worker);
1190         }
1191 }
1192
1193 /**
1194  * Parses the list of tag-messages
1195  */
1196 static void _parse_tagmsg(GstMessage *msg, MafwGstRendererWorker *worker)
1197 {
1198         GstTagList *new_tags;
1199
1200         gst_message_parse_tag(msg, &new_tags);
1201         gst_tag_list_foreach(new_tags, (gpointer)_emit_tag, worker);
1202         gst_tag_list_free(new_tags);
1203         gst_message_unref(msg);
1204 }
1205
1206 /**
1207  * Parses the collected tag messages, and emits the metadatas
1208  */
1209 static void _emit_metadatas(MafwGstRendererWorker *worker)
1210 {
1211         if (worker->tag_list != NULL)
1212         {
1213                 g_ptr_array_foreach(worker->tag_list, (GFunc)_parse_tagmsg,
1214                                     worker);
1215                 g_ptr_array_free(worker->tag_list, TRUE);
1216                 worker->tag_list = NULL;
1217         }
1218 }
1219
1220 static void _reset_volume_and_mute_to_pipeline(MafwGstRendererWorker *worker)
1221 {
1222 #ifdef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1223         g_debug("resetting volume and mute to pipeline");
1224
1225         if (worker->pipeline != NULL) {
1226                 g_object_set(
1227                         G_OBJECT(worker->pipeline), "volume",
1228                         mafw_gst_renderer_worker_volume_get(worker->wvolume),
1229                         "mute",
1230                         mafw_gst_renderer_worker_volume_is_muted(worker->wvolume),
1231                         NULL);
1232         }
1233 #endif
1234 }
1235
1236 static void _handle_buffering(MafwGstRendererWorker *worker, GstMessage *msg)
1237 {
1238         gint percent;
1239         MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner;
1240
1241         gst_message_parse_buffering(msg, &percent);
1242         g_debug("buffering: %d", percent);
1243
1244         /* No state management needed for live pipelines */
1245         if (!worker->is_live) {
1246                 worker->buffering = TRUE;
1247                 if (percent < 100 && worker->state == GST_STATE_PLAYING) {
1248                         g_debug("setting pipeline to PAUSED not to wolf the "
1249                                 "buffer down");
1250                         worker->report_statechanges = FALSE;
1251                         /* We can't call _pause() here, since it sets
1252                          * the "report_statechanges" to TRUE.  We don't
1253                          * want that, application doesn't need to know
1254                          * that internally the state changed to
1255                          * PAUSED. */
1256                         if (gst_element_set_state(worker->pipeline,
1257                                               GST_STATE_PAUSED) ==
1258                                         GST_STATE_CHANGE_ASYNC)
1259                         {
1260                                 /* XXX this blocks at most 2 seconds. */
1261                                 gst_element_get_state(worker->pipeline, NULL,
1262                                               NULL,
1263                                               2 * GST_SECOND);
1264                         }
1265                 }
1266
1267                 if (percent >= 100) {
1268                         /* On buffering we go to PAUSED, so here we move back to
1269                            PLAYING */
1270                         worker->buffering = FALSE;
1271                         if (worker->state == GST_STATE_PAUSED) {
1272                                 /* If buffering more than once, do this only the
1273                                    first time we are done with buffering */
1274                                 if (worker->prerolling) {
1275                                         g_debug("buffering concluded during "
1276                                                 "prerolling");
1277                                         _finalize_startup(worker);
1278                                         _do_play(worker);
1279                                         renderer->play_failed_count = 0;
1280                                         /* Send the paused notification */
1281                                         if (worker->stay_paused &&
1282                                             worker->notify_pause_handler) {
1283                                                 worker->notify_pause_handler(
1284                                                         worker,
1285                                                         worker->owner);
1286                                         }
1287                                         worker->prerolling = FALSE;
1288                                 } else if (worker->in_ready) {
1289                                         /* If we had been woken up from READY
1290                                            and we have finish our buffering,
1291                                            check if we have to play or stay
1292                                            paused and if we have to play,
1293                                            signal the state change. */
1294                                         g_debug("buffering concluded, "
1295                                                 "continuing playing");
1296                                         _do_play(worker);
1297                                 } else if (!worker->stay_paused) {
1298                                         /* This means, that we were playing but
1299                                            ran out of buffer, so we silently
1300                                            paused waited for buffering to
1301                                            finish and now we continue silently
1302                                            (silently meaning we do not expose
1303                                            state changes) */
1304                                         g_debug("buffering concluded, setting "
1305                                                 "pipeline to PLAYING again");
1306                                         _reset_volume_and_mute_to_pipeline(
1307                                                 worker);
1308                                         if (gst_element_set_state(
1309                                                 worker->pipeline,
1310                                                 GST_STATE_PLAYING) ==
1311                                                         GST_STATE_CHANGE_ASYNC)
1312                                         {
1313                                                 /* XXX this blocks at most 2 seconds. */
1314                                                 gst_element_get_state(
1315                                                         worker->pipeline, NULL, NULL,
1316                                                         2 * GST_SECOND);
1317                                         }
1318                                 }
1319                         } else if (worker->state == GST_STATE_PLAYING) {
1320                                 g_debug("buffering concluded, signalling "
1321                                         "state change");
1322                                 /* In this case we got a PLAY command while 
1323                                    buffering, likely because it was issued
1324                                    before we got the first buffering signal.
1325                                    The UI should not do this, but if it does,
1326                                    we have to signal that we have executed
1327                                    the state change, since in 
1328                                    _handle_state_changed we do not do anything 
1329                                    if we are buffering  */
1330
1331                                 /* Set the pipeline to playing. This is an async
1332                                    handler, it could be, that the reported state
1333                                    is not the real-current state */
1334                                 if (gst_element_set_state(
1335                                                 worker->pipeline,
1336                                                 GST_STATE_PLAYING) ==
1337                                                         GST_STATE_CHANGE_ASYNC)
1338                                 {
1339                                         /* XXX this blocks at most 2 seconds. */
1340                                         gst_element_get_state(
1341                                                 worker->pipeline, NULL, NULL,
1342                                                 2 * GST_SECOND);
1343                                 }
1344                                 if (worker->report_statechanges &&
1345                                     worker->notify_play_handler) {
1346                                         worker->notify_play_handler(
1347                                                         worker,
1348                                                         worker->owner);
1349                                 }
1350                                 _add_duration_seek_query_timeout(worker);
1351                         }
1352                 }
1353         }
1354
1355         /* Send buffer percentage */
1356         if (worker->notify_buffer_status_handler)
1357                 worker->notify_buffer_status_handler(worker, worker->owner,
1358                                                      percent);
1359 }
1360
1361 static void _handle_element_msg(MafwGstRendererWorker *worker, GstMessage *msg)
1362 {
1363         /* Only HelixBin sends "resolution" messages. */
1364         if (gst_structure_has_name(msg->structure, "resolution") &&
1365             _handle_video_info(worker, msg->structure))
1366         {
1367                 worker->media.has_visual_content = TRUE;
1368         }
1369 }
1370
1371 static void _reset_pl_info(MafwGstRendererWorker *worker)
1372 {
1373         if (worker->pl.items) {
1374                 g_slist_foreach(worker->pl.items, (GFunc) g_free, NULL);
1375                 g_slist_free(worker->pl.items);
1376                 worker->pl.items = NULL;
1377         }
1378
1379         worker->pl.current = 0;
1380         worker->pl.notify_play_pending = TRUE;
1381 }
1382
1383 static GError * _get_specific_missing_plugin_error(GstMessage *msg)
1384 {
1385         const GstStructure *gst_struct;
1386         const gchar *type;
1387
1388         GError *error;
1389         gchar *desc;
1390
1391         desc = gst_missing_plugin_message_get_description(msg);
1392
1393         gst_struct = gst_message_get_structure(msg);
1394         type = gst_structure_get_string(gst_struct, "type");
1395
1396         if ((type) && ((strcmp(type, MAFW_GST_MISSING_TYPE_DECODER) == 0) ||
1397                        (strcmp(type, MAFW_GST_MISSING_TYPE_ENCODER) == 0))) {
1398
1399                 /* Missing codec error. */
1400                 const GValue *val;
1401                 const GstCaps *caps;
1402                 GstStructure *caps_struct;
1403                 const gchar *mime;
1404
1405                 val = gst_structure_get_value(gst_struct, "detail");
1406                 caps = gst_value_get_caps(val);
1407                 caps_struct = gst_caps_get_structure(caps, 0);
1408                 mime = gst_structure_get_name(caps_struct);
1409
1410                 if (g_strrstr(mime, "video")) {
1411                         error = g_error_new_literal(
1412                                 MAFW_RENDERER_ERROR,
1413                                 MAFW_RENDERER_ERROR_VIDEO_CODEC_NOT_FOUND,
1414                                 desc);
1415                 } else if (g_strrstr(mime, "audio")) {
1416                         error = g_error_new_literal(
1417                                 MAFW_RENDERER_ERROR,
1418                                 MAFW_RENDERER_ERROR_AUDIO_CODEC_NOT_FOUND,
1419                                 desc);
1420                 } else {
1421                         error = g_error_new_literal(
1422                                 MAFW_RENDERER_ERROR,
1423                                 MAFW_RENDERER_ERROR_CODEC_NOT_FOUND,
1424                                 desc);
1425                 }
1426         } else {
1427                 /* Unsupported type error. */
1428                 error = g_error_new(
1429                         MAFW_RENDERER_ERROR,
1430                         MAFW_RENDERER_ERROR_UNSUPPORTED_TYPE,
1431                         "missing plugin: %s", desc);
1432         }
1433
1434         g_free(desc);
1435
1436         return error;
1437 }
1438
1439 /*
1440  * Asynchronous message handler.  It gets removed from if it returns FALSE.
1441  */
1442 static gboolean _async_bus_handler(GstBus *bus, GstMessage *msg,
1443                                    MafwGstRendererWorker *worker)
1444 {
1445         /* No need to handle message if error has already occured. */
1446         if (worker->is_error)
1447                 return TRUE;
1448
1449         /* Handle missing-plugin (element) messages separately, relaying more
1450          * details. */
1451         if (gst_is_missing_plugin_message(msg)) {
1452                 GError *err = _get_specific_missing_plugin_error(msg);
1453                 /* FIXME?: for some reason, calling the error handler directly
1454                  * (_send_error) causes problems.  On the other hand, turning
1455                  * the error into a new GstMessage and letting the next
1456                  * iteration handle it seems to work. */
1457                 _post_error(worker, err);
1458                 return TRUE;
1459         }
1460
1461         switch (GST_MESSAGE_TYPE(msg)) {
1462         case GST_MESSAGE_ERROR:
1463                 if (!worker->is_error) {
1464                         gchar *debug;
1465                         GError *err;
1466
1467                         debug = NULL;
1468                         gst_message_parse_error(msg, &err, &debug);
1469                         g_debug("gst error: domain = %d, code = %d, "
1470                                 "message = '%s', debug = '%s'",
1471                                 err->domain, err->code, err->message, debug);
1472                         if (debug)
1473                                 g_free(debug);
1474
1475                         /* If we are in playlist/radio mode, we silently
1476                            ignore the error and continue with the next
1477                            item until we end the playlist. If no
1478                            playable elements we raise the error and
1479                            after finishing we go to normal mode */
1480
1481                         if (worker->mode == WORKER_MODE_PLAYLIST ||
1482                             worker->mode == WORKER_MODE_REDUNDANT) {
1483                                 if (worker->pl.current <
1484                                     (g_slist_length(worker->pl.items) - 1)) {
1485                                         /* If the error is "no space left"
1486                                            notify, otherwise try to play the
1487                                            next item */
1488                                         if (err->code ==
1489                                             GST_RESOURCE_ERROR_NO_SPACE_LEFT) {
1490                                                 _send_error(worker, err);
1491
1492                                         } else {
1493                                                 _play_pl_next(worker);
1494                                         }
1495                                 } else {
1496                                         /* Playlist EOS. We cannot try another
1497                                          * URI, so we have to go back to normal
1498                                          * mode and signal the error (done
1499                                          * below) */
1500                                         worker->mode = WORKER_MODE_SINGLE_PLAY;
1501                                         _reset_pl_info(worker);
1502                                 }
1503                         }
1504
1505                         if (worker->mode == WORKER_MODE_SINGLE_PLAY) {
1506                                 if (err->domain == GST_STREAM_ERROR &&
1507                                         err->code == GST_STREAM_ERROR_WRONG_TYPE)
1508                                 {/* Maybe it is a playlist? */
1509                                         GSList *plitems = _parse_playlist(worker->media.location);
1510                                         
1511                                         if (plitems)
1512                                         {/* Yes, it is a plitem */
1513                                                 g_error_free(err);
1514                                                 mafw_gst_renderer_worker_play(worker, NULL, plitems);
1515                                                 break;
1516                                         }
1517                                         
1518                                         
1519                                 }
1520                                 _send_error(worker, err);
1521                         }
1522                 }
1523                 break;
1524         case GST_MESSAGE_EOS:
1525                 if (!worker->is_error) {
1526                         worker->eos = TRUE;
1527
1528                         if (worker->mode == WORKER_MODE_PLAYLIST) {
1529                                 if (worker->pl.current <
1530                                     (g_slist_length(worker->pl.items) - 1)) {
1531                                         /* If the playlist EOS is not reached
1532                                            continue playing */
1533                                         _play_pl_next(worker);
1534                                 } else {
1535                                         /* Playlist EOS, go back to normal
1536                                            mode */
1537                                         worker->mode = WORKER_MODE_SINGLE_PLAY;
1538                                         _reset_pl_info(worker);
1539                                 }
1540                         }
1541
1542                         if (worker->mode == WORKER_MODE_SINGLE_PLAY ||
1543                             worker->mode == WORKER_MODE_REDUNDANT) {
1544                                 if (worker->notify_eos_handler)
1545                                         worker->notify_eos_handler(
1546                                                 worker,
1547                                                 worker->owner);
1548
1549                                 /* We can remove the message handlers now, we
1550                                    are not interested in bus messages
1551                                    anymore. */
1552                                 if (worker->bus) {
1553                                         gst_bus_set_sync_handler(worker->bus,
1554                                                                  NULL,
1555                                                                  NULL);
1556                                 }
1557                                 if (worker->async_bus_id) {
1558                                         g_source_remove(worker->async_bus_id);
1559                                         worker->async_bus_id = 0;
1560                                 }
1561
1562                                 if (worker->mode == WORKER_MODE_REDUNDANT) {
1563                                         /* Go to normal mode */
1564                                         worker->mode = WORKER_MODE_SINGLE_PLAY;
1565                                         _reset_pl_info(worker);
1566                                 }
1567                         }
1568                 }
1569                 break;
1570         case GST_MESSAGE_TAG:
1571                 _handle_tag(worker, msg);
1572                 break;
1573         case GST_MESSAGE_BUFFERING:
1574                 _handle_buffering(worker, msg);
1575                 break;
1576         case GST_MESSAGE_DURATION:
1577                 _handle_duration(worker, msg);
1578                 break;
1579         case GST_MESSAGE_ELEMENT:
1580                 _handle_element_msg(worker, msg);
1581                 break;
1582         case GST_MESSAGE_STATE_CHANGED:
1583                 if ((GstElement *)GST_MESSAGE_SRC(msg) == worker->pipeline)
1584                         _handle_state_changed(msg, worker);
1585                 break;
1586         case GST_MESSAGE_APPLICATION:
1587                 if (gst_structure_has_name(gst_message_get_structure(msg),
1588                                            "ckey"))
1589                 {
1590                         GValue v = {0};
1591                         g_value_init(&v, G_TYPE_INT);
1592                         g_value_set_int(&v, worker->colorkey);
1593                         mafw_extension_emit_property_changed(
1594                                 MAFW_EXTENSION(worker->owner),
1595                                 MAFW_PROPERTY_RENDERER_COLORKEY,
1596                                 &v);
1597                 }
1598         default: break;
1599         }
1600         return TRUE;
1601 }
1602
1603 /* NOTE this function will possibly be called from a different thread than the
1604  * glib main thread. */
1605 static void _stream_info_cb(GstObject *pipeline, GParamSpec *unused,
1606                             MafwGstRendererWorker *worker)
1607 {
1608         g_debug("stream-info changed");
1609         _parse_stream_info(worker);
1610 }
1611
1612 static void _volume_cb(MafwGstRendererWorkerVolume *wvolume, gdouble volume,
1613                        gpointer data)
1614 {
1615         MafwGstRendererWorker *worker = data;
1616         GValue value = {0, };
1617
1618         _reset_volume_and_mute_to_pipeline(worker);
1619
1620         g_value_init(&value, G_TYPE_UINT);
1621         g_value_set_uint(&value, (guint) (volume * 100.0));
1622         mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner),
1623                                              MAFW_PROPERTY_RENDERER_VOLUME,
1624                                              &value);
1625 }
1626
1627 #ifdef MAFW_GST_RENDERER_ENABLE_MUTE
1628
1629 static void _mute_cb(MafwGstRendererWorkerVolume *wvolume, gboolean mute,
1630                      gpointer data)
1631 {
1632         MafwGstRendererWorker *worker = data;
1633         GValue value = {0, };
1634
1635         _reset_volume_and_mute_to_pipeline(worker);
1636
1637         g_value_init(&value, G_TYPE_BOOLEAN);
1638         g_value_set_boolean(&value, mute);
1639         mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner),
1640                                              MAFW_PROPERTY_RENDERER_MUTE,
1641                                              &value);
1642 }
1643
1644 #endif
1645
1646 /* TODO: I think it's not enought to act on error, we need to handle
1647  * DestroyNotify on the given window ourselves, because for example helixbin
1648  * does it and silently stops the decoder thread.  But it doesn't notify
1649  * us... */
1650 static int xerror(Display *dpy, XErrorEvent *xev)
1651 {
1652         MafwGstRendererWorker *worker;
1653
1654         if (Global_worker == NULL) {
1655                 return -1;
1656         } else {
1657                 worker = Global_worker;
1658         }
1659
1660         /* Swallow BadWindow and stop pipeline when the error is about the
1661          * currently set xid. */
1662         if (worker->xid &&
1663             xev->resourceid == worker->xid &&
1664             xev->error_code == BadWindow)
1665         {
1666                 g_warning("BadWindow received for current xid (%x).",
1667                         (gint)xev->resourceid);
1668                 worker->xid = 0;
1669                 /* We must post a message to the bus, because this function is
1670                  * invoked from a different thread (xvimagerenderer's queue). */
1671                 _post_error(worker, g_error_new_literal(
1672                                     MAFW_RENDERER_ERROR,
1673                                     MAFW_RENDERER_ERROR_PLAYBACK,
1674                                     "Video window gone"));
1675         }
1676         return 0;
1677 }
1678
1679 /*
1680  * Resets the media information.
1681  */
1682 static void _reset_media_info(MafwGstRendererWorker *worker)
1683 {
1684         if (worker->media.location) {
1685                 g_free(worker->media.location);
1686                 worker->media.location = NULL;
1687         }
1688         worker->media.length_nanos = -1;
1689         worker->media.has_visual_content = FALSE;
1690         worker->media.seekable = SEEKABILITY_UNKNOWN;
1691         worker->media.video_width = 0;
1692         worker->media.video_height = 0;
1693         worker->media.fps = 0.0;
1694 }
1695
1696 static void _set_volume_and_mute(MafwGstRendererWorker *worker, gdouble vol,
1697                                  gboolean mute)
1698 {
1699         g_return_if_fail(worker->wvolume != NULL);
1700
1701         mafw_gst_renderer_worker_volume_set(worker->wvolume, vol, mute);
1702 }
1703
1704 static void _set_volume(MafwGstRendererWorker *worker, gdouble new_vol)
1705 {
1706         g_return_if_fail(worker->wvolume != NULL);
1707
1708         _set_volume_and_mute(
1709                 worker, new_vol,
1710                 mafw_gst_renderer_worker_volume_is_muted(worker->wvolume));
1711 }
1712
1713 static void _set_mute(MafwGstRendererWorker *worker, gboolean mute)
1714 {
1715         g_return_if_fail(worker->wvolume != NULL);
1716
1717         _set_volume_and_mute(
1718                 worker, mafw_gst_renderer_worker_volume_get(worker->wvolume),
1719                 mute);
1720 }
1721
1722 /*
1723  * Start to play the media
1724  */
1725 static void _start_play(MafwGstRendererWorker *worker)
1726 {
1727         MafwGstRenderer *renderer = (MafwGstRenderer*) worker->owner;
1728         GstStateChangeReturn state_change_info;
1729         char *autoload_sub = NULL;
1730
1731         g_assert(worker->pipeline);
1732         g_object_set(G_OBJECT(worker->pipeline),
1733                      "uri", worker->media.location, NULL);
1734
1735         if (worker->subtitles.enabled) {
1736                 autoload_sub = uri_get_subtitle_uri(worker->media.location);
1737                 if (autoload_sub) {
1738                         g_debug("SUBURI: %s", autoload_sub);
1739                         g_object_set(G_OBJECT(worker->pipeline),
1740                                      "suburi", autoload_sub,
1741                                      "subtitle-font-desc", worker->subtitles.font,
1742                                      "subtitle-encoding", worker->subtitles.encoding,
1743                                      NULL);
1744
1745                         gst_element_set_state(worker->pipeline, GST_STATE_READY);
1746                         g_free(autoload_sub);
1747                 }
1748         } else {
1749                 g_object_set(G_OBJECT(worker->pipeline), "suburi", NULL, NULL);
1750         }
1751
1752         g_debug("URI: %s", worker->media.location);
1753         g_debug("setting pipeline to PAUSED");
1754
1755         worker->report_statechanges = TRUE;
1756         state_change_info = gst_element_set_state(worker->pipeline, 
1757                                                   GST_STATE_PAUSED);
1758         if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) {
1759                 /* FIXME:  for live sources we may have to handle
1760                    buffering and prerolling differently */
1761                 g_debug ("Source is live!");
1762                 worker->is_live = TRUE;
1763         }
1764         worker->prerolling = TRUE;
1765
1766         worker->is_stream = uri_is_stream(worker->media.location);
1767
1768         if (renderer->update_playcount_id > 0) {
1769                 g_source_remove(renderer->update_playcount_id);
1770                 renderer->update_playcount_id = 0;
1771         }
1772
1773 }
1774
1775 /*
1776  * Constructs gst pipeline
1777  *
1778  * FIXME: Could the same pipeline be used for playing all media instead of
1779  *  constantly deleting and reconstructing it again?
1780  */
1781 static void _construct_pipeline(MafwGstRendererWorker *worker)
1782 {
1783         g_debug("constructing pipeline");
1784         g_assert(worker != NULL);
1785
1786         /* Return if we have already one */
1787         if (worker->pipeline)
1788                 return;
1789
1790         _free_taglist(worker);
1791
1792         g_debug("Creating a new instance of playbin2");
1793         worker->pipeline = gst_element_factory_make("playbin2",
1794                                                     "playbin");
1795         if (worker->pipeline == NULL)
1796         {
1797                 /* Let's try with playbin */
1798                 g_warning ("playbin2 failed, falling back to playbin");
1799                 worker->pipeline = gst_element_factory_make("playbin",
1800                                                             "playbin");
1801
1802                 if (worker->pipeline) {
1803                         /* Use nwqueue only for non-rtsp and non-mms(h)
1804                            streams. */
1805                         gboolean use_nw;
1806                         use_nw = worker->media.location && 
1807                                 !g_str_has_prefix(worker->media.location, 
1808                                                   "rtsp://") &&
1809                                 !g_str_has_prefix(worker->media.location, 
1810                                                   "mms://") &&
1811                                 !g_str_has_prefix(worker->media.location, 
1812                                                   "mmsh://");
1813                         
1814                         g_debug("playbin using network queue: %d", use_nw);
1815
1816                         /* These need a modified version of playbin. */
1817                         g_object_set(G_OBJECT(worker->pipeline),
1818                                      "nw-queue", use_nw,
1819                                      "no-video-transform", TRUE,
1820                                      NULL);
1821                 }
1822         }
1823
1824         if (!worker->pipeline) {
1825                 g_critical("failed to create playback pipeline");
1826                 g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1827                                       "error",
1828                                       MAFW_RENDERER_ERROR,
1829                                       MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1830                                       "Could not create pipeline");
1831                 g_assert_not_reached();
1832         }
1833
1834
1835         worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline));
1836         gst_bus_set_sync_handler(worker->bus,
1837                                  (GstBusSyncHandler)_sync_bus_handler, worker);
1838         worker->async_bus_id = gst_bus_add_watch_full(worker->bus,G_PRIORITY_HIGH,
1839                                                  (GstBusFunc)_async_bus_handler,
1840                                                  worker, NULL);
1841
1842         /* Listen for changes in stream-info object to find out whether the
1843          * media contains video and throw error if application has not provided
1844          * video window. */
1845         g_signal_connect(worker->pipeline, "notify::stream-info",
1846                          G_CALLBACK(_stream_info_cb), worker);
1847
1848 #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
1849         
1850
1851         /* Set audio and video sinks ourselves. We create and configure
1852            them only once. */
1853         if (!worker->asink) {
1854                 worker->asink = gst_element_factory_make("pulsesink", NULL);
1855                 if (!worker->asink) {
1856                         g_critical("Failed to create pipeline audio sink");
1857                         g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1858                                               "error",
1859                                               MAFW_RENDERER_ERROR,
1860                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1861                                               "Could not create audio sink");
1862                         g_assert_not_reached();
1863                 }
1864                 gst_object_ref(worker->asink);
1865                 g_object_set(worker->asink,
1866                                 "buffer-time", (gint64) MAFW_GST_BUFFER_TIME,
1867                                 "latency-time", (gint64) MAFW_GST_LATENCY_TIME,
1868                                 NULL);
1869         }
1870         g_object_set(worker->pipeline, "audio-sink", worker->asink, NULL);
1871 #endif
1872
1873         if (!worker->vsink) {
1874                 worker->vsink = gst_element_factory_make("xvimagesink", NULL);
1875                 if (!worker->vsink) {
1876                         g_critical("Failed to create pipeline video sink");
1877                         g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1878                                               "error",
1879                                               MAFW_RENDERER_ERROR,
1880                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1881                                               "Could not create video sink");
1882                         g_assert_not_reached();
1883                 }
1884                 gst_object_ref(worker->vsink);
1885                 g_object_set(G_OBJECT(worker->vsink),
1886                                 "handle-events", TRUE,
1887                                 "force-aspect-ratio", TRUE,
1888                                 NULL);
1889         }
1890         g_object_set(worker->pipeline,
1891                         "video-sink", worker->vsink,
1892                         "flags", 103,
1893                         NULL);
1894
1895         if (!worker->tsink) {
1896                 worker->tsink = gst_element_factory_make("textoverlay", NULL);
1897                 if (!worker->tsink) {
1898                         g_critical("Failed to create pipeline text sink");
1899                         g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), 
1900                                               "error",
1901                                               MAFW_RENDERER_ERROR,
1902                                               MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM,
1903                                               "Could not create text sink");
1904                         g_assert_not_reached();
1905                 }
1906                 gst_object_ref(worker->tsink);
1907         }
1908         g_object_set(worker->pipeline, "text-sink", worker->tsink, NULL);
1909 }
1910
1911 /*
1912  * @seek_type: GstSeekType
1913  * @position: Time in seconds where to seek
1914  */
1915 static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type,
1916                      gint position, GError **error)
1917 {
1918         gboolean ret;
1919         gint64 spos;
1920
1921         g_assert(worker != NULL);
1922
1923         if (worker->eos || !worker->media.seekable)
1924                 goto err;
1925
1926         /* According to the docs, relative seeking is not so easy:
1927         GST_SEEK_TYPE_CUR - change relative to currently configured segment.
1928         This can't be used to seek relative to the current playback position -
1929         do a position query, calculate the desired position and then do an
1930         absolute position seek instead if that's what you want to do. */
1931         if (seek_type == GST_SEEK_TYPE_CUR)
1932         {
1933                 gint curpos = mafw_gst_renderer_worker_get_position(worker);
1934                 position = curpos + position;
1935                 seek_type = GST_SEEK_TYPE_SET;
1936         }
1937
1938         if (position < 0) {
1939                 position = 0;
1940         }
1941
1942         worker->seek_position = position;
1943         worker->report_statechanges = FALSE;
1944         spos = (gint64)position * GST_SECOND;
1945         g_debug("seek: type = %d, offset = %lld", seek_type, spos);
1946
1947         /* If the pipeline has been set to READY by us, then wake it up by
1948            setting it to PAUSED (when we get the READY->PAUSED transition
1949            we will execute the seek). This way when we seek we disable the
1950            READY state (logical, since the player is not idle anymore)
1951            allowing the sink to render the destination frame in case of
1952            video playback */
1953         if (worker->in_ready && worker->state == GST_STATE_READY) {
1954                 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
1955         } else {
1956                 ret = gst_element_seek(worker->pipeline, 1.0, GST_FORMAT_TIME,
1957                                        GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_KEY_UNIT,
1958                                        seek_type, spos,
1959                                        GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
1960                 if (!ret) {
1961                         /* Seeking is async, so seek_position should not be
1962                            invalidated here */
1963                         goto err;
1964                 }
1965         }
1966         return;
1967
1968 err:    g_set_error(error,
1969                     MAFW_RENDERER_ERROR,
1970                     MAFW_RENDERER_ERROR_CANNOT_SET_POSITION,
1971                     "Seeking to %d failed", position);
1972 }
1973
1974 /* @vol should be between [0 .. 100], higher values (up to 1000) are allowed,
1975  * but probably cause distortion. */
1976 void mafw_gst_renderer_worker_set_volume(
1977         MafwGstRendererWorker *worker, guint volume)
1978 {
1979         _set_volume(worker, CLAMP((gdouble)volume / 100.0, 0.0, 1.0));
1980 }
1981
1982 guint mafw_gst_renderer_worker_get_volume(
1983         MafwGstRendererWorker *worker)
1984 {
1985         return (guint)
1986                 (mafw_gst_renderer_worker_volume_get(worker->wvolume) * 100);
1987 }
1988
1989 void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker,
1990                                      gboolean mute)
1991 {
1992         _set_mute(worker, mute);
1993 }
1994
1995 gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker)
1996 {
1997         return mafw_gst_renderer_worker_volume_is_muted(worker->wvolume);
1998 }
1999
2000 #ifdef HAVE_GDKPIXBUF
2001 void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker,
2002                                                                 gboolean current_frame_on_pause)
2003 {
2004         worker->current_frame_on_pause = current_frame_on_pause;
2005 }
2006
2007 gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker)
2008 {
2009         return worker->current_frame_on_pause;
2010 }
2011 #endif
2012
2013 void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker,
2014                                           GstSeekType seek_type,
2015                                           gint position, GError **error)
2016 {
2017         /* If player is paused and we have a timeout for going to ready
2018          * restart it. This is logical, since the user is seeking and
2019          * thus, the player is not idle anymore. Also this prevents that
2020          * when seeking streams we enter buffering and in the middle of
2021          * the buffering process we set the pipeline to ready (which stops
2022          * the buffering before it reaches 100%, making the client think
2023          * buffering is still going on).
2024          */
2025         if (worker->ready_timeout) {
2026                 _remove_ready_timeout(worker);
2027                 _add_ready_timeout(worker);
2028         }
2029
2030         _do_seek(worker, seek_type, position, error);
2031         if (worker->notify_seek_handler)
2032                 worker->notify_seek_handler(worker, worker->owner);
2033 }
2034
2035 /*
2036  * Gets current position, rounded down into precision of one second.  If a seek
2037  * is pending, returns the position we are going to seek.  Returns -1 on
2038  * failure.
2039  */
2040 gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker)
2041 {
2042         GstFormat format;
2043         gint64 time = 0;
2044         g_assert(worker != NULL);
2045
2046         /* If seek is ongoing, return the position where we are seeking. */
2047         if (worker->seek_position != -1)
2048         {
2049                 return worker->seek_position;
2050         }
2051         /* Otherwise query position from pipeline. */
2052         format = GST_FORMAT_TIME;
2053         if (worker->pipeline &&
2054             gst_element_query_position(worker->pipeline, &format, &time))
2055         {
2056                 return (gint)(NSECONDS_TO_SECONDS(time));
2057         }
2058         return -1;
2059 }
2060
2061 GHashTable *mafw_gst_renderer_worker_get_current_metadata(
2062         MafwGstRendererWorker *worker)
2063 {
2064         return worker->current_metadata;
2065 }
2066
2067 void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid)
2068 {
2069         /* Check for errors on the target window */
2070         XSetErrorHandler(xerror);
2071
2072         /* Store the target window id */
2073         g_debug("Setting xid: %x", (guint)xid);
2074         worker->xid = xid;
2075
2076         /* Check if we should use it right away */
2077         mafw_gst_renderer_worker_apply_xid(worker);
2078 }
2079
2080 XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker)
2081 {
2082         return worker->xid;
2083 }
2084
2085 gboolean mafw_gst_renderer_worker_get_autopaint(
2086         MafwGstRendererWorker *worker)
2087 {
2088         return worker->autopaint;
2089 }
2090 void mafw_gst_renderer_worker_set_autopaint(
2091         MafwGstRendererWorker *worker, gboolean autopaint)
2092 {
2093         worker->autopaint = autopaint;
2094         if (worker->vsink)
2095                 g_object_set(worker->vsink, "autopaint-colorkey",
2096                              worker->autopaint, NULL);
2097 }
2098
2099 gint mafw_gst_renderer_worker_get_colorkey(
2100         MafwGstRendererWorker *worker)
2101 {
2102         return worker->colorkey;
2103 }
2104
2105 void mafw_gst_renderer_worker_set_colorkey(
2106         MafwGstRendererWorker *worker, gint colorkey)
2107 {
2108         worker->colorkey = colorkey;
2109         if (worker->vsink)
2110                 g_object_set(worker->vsink, "colorkey",
2111                              worker->colorkey, NULL);
2112 }
2113
2114 gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker)
2115 {
2116         return worker->media.seekable;
2117 }
2118
2119 static void _play_pl_next(MafwGstRendererWorker *worker) {
2120         gchar *next;
2121
2122         g_assert(worker != NULL);
2123         g_return_if_fail(worker->pl.items != NULL);
2124
2125         next = (gchar *) g_slist_nth_data(worker->pl.items,
2126                                           ++worker->pl.current);
2127         mafw_gst_renderer_worker_stop(worker);
2128         _reset_media_info(worker);
2129
2130         worker->media.location = g_strdup(next);
2131         _construct_pipeline(worker);
2132         _start_play(worker);
2133 }
2134
2135 static void _do_play(MafwGstRendererWorker *worker)
2136 {
2137         g_assert(worker != NULL);
2138
2139         if (worker->pipeline == NULL) {
2140                 g_debug("play without a pipeline!");
2141                 return;
2142         }
2143         worker->report_statechanges = TRUE;
2144
2145         /* If we have to stay paused, we do and add the ready
2146          * timeout. Otherwise, we move the pipeline */
2147         if (!worker->stay_paused) {
2148                 /* If pipeline is READY, we move it to PAUSED,
2149                  * otherwise, to PLAYING */
2150                 if (worker->state == GST_STATE_READY) {
2151                         gst_element_set_state(worker->pipeline,
2152                                               GST_STATE_PAUSED);
2153                         g_debug("setting pipeline to PAUSED");
2154                 } else {
2155                         _reset_volume_and_mute_to_pipeline(worker);
2156                         gst_element_set_state(worker->pipeline,
2157                                               GST_STATE_PLAYING);
2158                         g_debug("setting pipeline to PLAYING");
2159                 }
2160         }
2161         else {
2162                 g_debug("staying in PAUSED state");
2163                 _add_ready_timeout(worker);
2164         }
2165 }
2166
2167 void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker,
2168                                   const gchar *uri, GSList *plitems)
2169 {
2170         g_assert(uri || plitems);
2171
2172         mafw_gst_renderer_worker_stop(worker);
2173         _reset_media_info(worker);
2174         _reset_pl_info(worker);
2175         /* Check if the item to play is a single item or a playlist. */
2176         if (plitems || uri_is_playlist(uri)){
2177                 gchar *item;
2178                 /* In case of a playlist we parse it and start playing the first
2179                    item of the playlist. */
2180                 if (plitems)
2181                 {
2182                         worker->pl.items = plitems;
2183                 }
2184                 else
2185                 {
2186                         worker->pl.items = _parse_playlist(uri);
2187                 }
2188                 if (!worker->pl.items)
2189                 {
2190                         _send_error(worker,
2191                             g_error_new(MAFW_RENDERER_ERROR,
2192                                         MAFW_RENDERER_ERROR_PLAYLIST_PARSING,
2193                                         "Playlist parsing failed: %s",
2194                                         uri));
2195                         return;
2196                 }
2197
2198                 /* Set the playback mode */
2199                 worker->mode = WORKER_MODE_PLAYLIST;
2200                 worker->pl.notify_play_pending = TRUE;
2201
2202                 /* Set the item to be played */
2203                 worker->pl.current = 0;
2204                 item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2205                 worker->media.location = g_strdup(item);
2206         } else {
2207                 /* Single item. Set the playback mode according to that */
2208                 worker->mode = WORKER_MODE_SINGLE_PLAY;
2209
2210                 /* Set the item to be played */
2211                 worker->media.location = g_strdup(uri);
2212         }
2213         _construct_pipeline(worker);
2214         _start_play(worker);
2215 }
2216
2217 void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker,
2218                                                 gchar **uris)
2219 {
2220         gint i;
2221         gchar *item;
2222
2223         g_assert(uris && uris[0]);
2224
2225         mafw_gst_renderer_worker_stop(worker);
2226         _reset_media_info(worker);
2227         _reset_pl_info(worker);
2228
2229         /* Add the uris to playlist */
2230         i = 0;
2231         while (uris[i]) {
2232                 worker->pl.items =
2233                         g_slist_append(worker->pl.items, g_strdup(uris[i]));
2234                 i++;
2235         }
2236
2237         /* Set the playback mode */
2238         worker->mode = WORKER_MODE_REDUNDANT;
2239         worker->pl.notify_play_pending = TRUE;
2240
2241         /* Set the item to be played */
2242         worker->pl.current = 0;
2243         item = (gchar *) g_slist_nth_data(worker->pl.items, 0);
2244         worker->media.location = g_strdup(item);
2245
2246         /* Start playing */
2247         _construct_pipeline(worker);
2248         _start_play(worker);
2249 }
2250
2251 /*
2252  * Currently, stop destroys the Gst pipeline and resets the worker into
2253  * default startup configuration.
2254  */
2255 void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker)
2256 {
2257         g_debug("worker stop");
2258         g_assert(worker != NULL);
2259
2260         /* If location is NULL, this is a pre-created pipeline */
2261         if (worker->async_bus_id && worker->pipeline && !worker->media.location)
2262                 return;
2263
2264         if (worker->pipeline) {
2265                 g_debug("destroying pipeline");
2266                 if (worker->async_bus_id) {
2267                         g_source_remove(worker->async_bus_id);
2268                         worker->async_bus_id = 0;
2269                 }
2270                 gst_bus_set_sync_handler(worker->bus, NULL, NULL);
2271                 gst_element_set_state(worker->pipeline, GST_STATE_NULL);
2272                 if (worker->bus) {
2273                         gst_object_unref(GST_OBJECT_CAST(worker->bus));
2274                         worker->bus = NULL;
2275                 }
2276                 gst_object_unref(GST_OBJECT(worker->pipeline));
2277                 worker->pipeline = NULL;
2278         }
2279
2280         /* Reset worker */
2281         worker->report_statechanges = TRUE;
2282         worker->state = GST_STATE_NULL;
2283         worker->prerolling = FALSE;
2284         worker->is_live = FALSE;
2285         worker->buffering = FALSE;
2286         worker->is_stream = FALSE;
2287         worker->is_error = FALSE;
2288         worker->eos = FALSE;
2289         worker->seek_position = -1;
2290         _remove_ready_timeout(worker);
2291         _free_taglist(worker);
2292         if (worker->current_metadata) {
2293                 g_hash_table_destroy(worker->current_metadata);
2294                 worker->current_metadata = NULL;
2295         }
2296
2297         if (worker->duration_seek_timeout != 0) {
2298                 g_source_remove(worker->duration_seek_timeout);
2299                 worker->duration_seek_timeout = 0;
2300         }
2301
2302         /* Reset media iformation */
2303         _reset_media_info(worker);
2304
2305         /* We are not playing, so we can let the screen blank */
2306         blanking_allow();
2307         keypadlocking_allow();
2308
2309         /* And now get a fresh pipeline ready */
2310         _construct_pipeline(worker);
2311 }
2312
2313 void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker)
2314 {
2315         g_assert(worker != NULL);
2316
2317         if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2318             !worker->prerolling) {
2319                 /* If we are buffering and get a pause, we have to
2320                  * signal state change and stay_paused */
2321                 g_debug("Pausing while buffering, signalling state change");
2322                 worker->stay_paused = TRUE;
2323                 if (worker->notify_pause_handler) {
2324                         worker->notify_pause_handler(
2325                                 worker,
2326                                 worker->owner);
2327                 }
2328         } else {
2329                 worker->report_statechanges = TRUE;
2330
2331                 if (gst_element_set_state(worker->pipeline, GST_STATE_PAUSED) ==
2332                     GST_STATE_CHANGE_ASYNC)
2333                 {
2334                         /* XXX this blocks at most 2 seconds. */
2335                         gst_element_get_state(worker->pipeline, NULL, NULL,
2336                                       2 * GST_SECOND);
2337                 }
2338                 blanking_allow();
2339                 keypadlocking_allow();
2340         }
2341 }
2342
2343 void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker)
2344 {
2345         if (worker->mode == WORKER_MODE_PLAYLIST ||
2346             worker->mode == WORKER_MODE_REDUNDANT) {
2347                 /* We must notify play if the "playlist" playback
2348                    is resumed */
2349                 worker->pl.notify_play_pending = TRUE;
2350         }
2351         if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2352             !worker->prerolling) {
2353                 /* If we are buffering we cannot resume, but we know
2354                  * that the pipeline will be moved to PLAYING as
2355                  * stay_paused is FALSE, so we just activate the state
2356                  * change report, this way as soon as buffering is finished
2357                  * the pipeline will be set to PLAYING and the state
2358                  * change will be reported */
2359                 worker->report_statechanges = TRUE;
2360                 g_debug("Resumed while buffering, activating pipeline state "
2361                         "changes");
2362                 /* Notice though that we can receive the Resume before
2363                    we get any buffering information. In that case
2364                    we go with the "else" branch and set the pipeline to
2365                    to PLAYING. However, it is possible that in this case
2366                    we get the fist buffering signal before the
2367                    PAUSED -> PLAYING state change. In that case, since we
2368                    ignore state changes while buffering we never signal
2369                    the state change to PLAYING. We can only fix this by
2370                    checking, when we receive a PAUSED -> PLAYING transition
2371                    if we are buffering, and in that case signal the state
2372                    change (if we get that transition while buffering
2373                    is on, it can only mean that the client resumed playback
2374                    while buffering, and we must notify the state change) */
2375         } else {
2376                 _do_play(worker);
2377         }
2378 }
2379
2380 static void _volume_init_cb(MafwGstRendererWorkerVolume *wvolume,
2381                             gpointer data)
2382 {
2383         MafwGstRendererWorker *worker = data;
2384         gdouble volume;
2385         gboolean mute;
2386
2387         worker->wvolume = wvolume;
2388
2389         g_debug("volume manager initialized");
2390
2391         volume = mafw_gst_renderer_worker_volume_get(wvolume);
2392         mute = mafw_gst_renderer_worker_volume_is_muted(wvolume);
2393         _volume_cb(wvolume, volume, worker);
2394 #ifdef MAFW_GST_RENDERER_ENABLE_MUTE
2395         _mute_cb(wvolume, mute, worker);
2396 #endif
2397 }
2398
2399 MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner)
2400 {
2401         MafwGstRendererWorker *worker;
2402         GMainContext *main_context;
2403
2404         worker = g_new0(MafwGstRendererWorker, 1);
2405         worker->mode = WORKER_MODE_SINGLE_PLAY;
2406         worker->pl.items = NULL;
2407         worker->pl.current = 0;
2408         worker->pl.notify_play_pending = TRUE;
2409         worker->owner = owner;
2410         worker->report_statechanges = TRUE;
2411         worker->state = GST_STATE_NULL;
2412         worker->seek_position = -1;
2413         worker->ready_timeout = 0;
2414         worker->in_ready = FALSE;
2415         worker->xid = 0;
2416         worker->autopaint = TRUE;
2417         worker->colorkey = -1;
2418         worker->vsink = NULL;
2419         worker->asink = NULL;
2420         worker->tsink = NULL;
2421         worker->tag_list = NULL;
2422         worker->current_metadata = NULL;
2423         worker->subtitles.enabled = FALSE;
2424         worker->subtitles.font = NULL;
2425         worker->subtitles.encoding = NULL;
2426
2427 #ifdef HAVE_GDKPIXBUF
2428         worker->current_frame_on_pause = FALSE;
2429         _init_tmp_files_pool(worker);
2430 #endif
2431         worker->notify_seek_handler = NULL;
2432         worker->notify_pause_handler = NULL;
2433         worker->notify_play_handler = NULL;
2434         worker->notify_buffer_status_handler = NULL;
2435         worker->notify_eos_handler = NULL;
2436         worker->notify_error_handler = NULL;
2437         Global_worker = worker;
2438         main_context = g_main_context_default();
2439         worker->wvolume = NULL;
2440         mafw_gst_renderer_worker_volume_init(main_context,
2441                                              _volume_init_cb, worker,
2442                                              _volume_cb, worker,
2443 #ifdef MAFW_GST_RENDERER_ENABLE_MUTE
2444                                              _mute_cb,
2445 #else
2446                                              NULL,
2447 #endif
2448                                              worker);
2449         blanking_init();
2450         _construct_pipeline(worker);
2451
2452         return worker;
2453 }
2454
2455 void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker)
2456 {
2457         blanking_deinit();
2458 #ifdef HAVE_GDKPIXBUF
2459         _destroy_tmp_files_pool(worker);
2460 #endif
2461         mafw_gst_renderer_worker_volume_destroy(worker->wvolume);
2462         mafw_gst_renderer_worker_stop(worker);
2463 }
2464 /* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */