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