| 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: */ |