2 * This file is a part of MAFW
4 * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved.
6 * Contact: Visa Smolander <visa.smolander@nokia.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; version 2.1 of
11 * the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
30 #include <glib/gstdio.h>
33 #include <gst/interfaces/xoverlay.h>
34 #include <gst/pbutils/missing-plugins.h>
35 #include <gst/base/gstbasesink.h>
36 #include <context_provider.h>
38 #include "mafw-gst-renderer-worker.h"
39 #include "mafw-gst-renderer-utils.h"
41 #define UNUSED(x) (void)(x)
43 /* context provider DBus name must be the same as the .context file name without
44 * the .context suffix, service name in the .context file must be the same too */
45 #define CONTEXT_PROVIDER_BUS_NAME "com.nokia.mafw.context_provider.libqmafw_gst_renderer"
46 #define CONTEXT_PROVIDER_KEY_NOWPLAYING "Media.NowPlaying"
47 #define CONTEXT_PROVIDER_KEY_NOWPLAYING_TITLE "title"
48 #define CONTEXT_PROVIDER_KEY_NOWPLAYING_ALBUM "album"
49 #define CONTEXT_PROVIDER_KEY_NOWPLAYING_ARTIST "artist"
50 #define CONTEXT_PROVIDER_KEY_NOWPLAYING_GENRE "genre"
51 #define CONTEXT_PROVIDER_KEY_NOWPLAYING_RESOURCE "resource"
52 #define CONTEXT_PROVIDER_KEY_NOWPLAYING_DURATION "duration"
54 #define WORKER_ERROR g_quark_from_static_string("com.nokia.mafw.error.renderer")
56 #define MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LAZY_TIMEOUT 4000
57 #define MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_FAST_TIMEOUT 200
58 #define MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LOOP_LIMIT 10
59 #define MAFW_GST_MISSING_TYPE_DECODER "decoder"
60 #define MAFW_GST_MISSING_TYPE_ENCODER "encoder"
62 #define MAFW_TMP_URI_LEN 2048
64 #define STREAM_TYPE_MMS "mms://"
65 #define STREAM_TYPE_MMSH "mmsh://"
66 #define MAFW_GST_MMSH_CONNECTION_SPEED "2000" /* kbit/s */
67 #define MAFW_GST_MMSH_TCP_TIMEOUT "30000000" /* microseconds */
69 /* struct needed when emitting renderer art/frames as image files */
71 MafwGstRendererWorker *worker;
73 const gchar *filename;
76 /* Forward declarations. */
77 static void _do_play(MafwGstRendererWorker *worker);
79 static void _do_seek(MafwGstRendererWorker *worker,
80 GstSeekType seek_type,
82 gboolean key_frame_seek,
85 static gboolean _set_value(GValue *v, GType type, gconstpointer value);
87 static void _emit_metadatas(MafwGstRendererWorker *worker);
89 static gboolean _current_metadata_add(MafwGstRendererWorker *worker,
92 const gpointer value);
94 static gpointer _set_context_map_value(gpointer map,
99 * Is used to prevent a critical log from context fw in case of multiple initialisations.
100 * Common to all renderers in the process.
102 static gboolean _context_fw_initialised = FALSE;
105 * Sends @error to MafwGstRenderer. Only call this from the glib main thread,
106 * or face the consequences. @err is free'd.
108 static void _send_error(MafwGstRendererWorker *worker, GError *err)
110 worker->is_error = TRUE;
111 if (worker->notify_error_handler)
113 /* remap a possible gst ecode to worker ecode */
114 err->code = remap_gst_error_code(err);
115 worker->notify_error_handler(worker, worker->owner, err);
120 configuration* _create_default_configuration()
122 configuration *config = g_malloc0(sizeof(configuration));
123 config->asink = g_strdup("pulsesink");
124 config->vsink = g_strdup("omapxvsink");
126 config->buffer_time = 600000; /* microseconds */
127 config->latency_time = 100000; /* microseconds */
128 config->autoload_subtitles = TRUE;
129 config->subtitle_encoding = NULL;
130 config->subtitle_font = g_strdup("Sans Bold 18");
133 config->milliseconds_to_pause_frame = 700; /* milliseconds */
134 config->seconds_to_pause_to_ready = 3; /* seconds */
137 config->use_dhmmixer = TRUE;
139 config->mobile_surround_music.state = 0;
140 config->mobile_surround_music.room = 2;
141 config->mobile_surround_music.color = 2;
142 config->mobile_surround_video.state = 0;
143 config->mobile_surround_video.room = 2;
144 config->mobile_surround_video.color = 2;
149 void _free_configuration(configuration* config)
151 g_free(config->asink);
152 g_free(config->vsink);
158 * Posts an @error on the gst bus. _async_bus_handler will then pick it up and
159 * forward to MafwGstRenderer. @err is free'd.
161 static void _post_error(MafwGstRendererWorker *worker, GError *err)
163 gst_bus_post(worker->bus,
164 gst_message_new_error(GST_OBJECT(worker->pipeline),
170 static gboolean _set_value(GValue *v, GType type, gconstpointer value)
177 memset(v, 0, sizeof(GValue));
178 g_value_init(v, type);
180 if (type == G_TYPE_STRING) {
181 g_value_set_string(v, (const gchar*)value);
183 else if (type == G_TYPE_INT) {
184 g_value_set_int(v, *(gint*)value);
186 else if (type == G_TYPE_UINT) {
187 g_value_set_uint(v, *(uint*)value);
189 else if (type == G_TYPE_DOUBLE) {
190 g_value_set_double(v, *(gdouble*)value);
192 else if (type == G_TYPE_BOOLEAN) {
193 g_value_set_boolean(v, *(gboolean*)value);
195 else if (type == G_TYPE_INT64) {
196 g_value_set_int64(v, *(gint64*)value);
198 else if (type == G_TYPE_FLOAT) {
199 g_value_set_float(v, *(gfloat*)value);
201 else if (type == G_TYPE_VALUE_ARRAY) {
202 g_value_copy((GValue*)value,v);
205 g_warning("%s: unknown g_type", G_STRFUNC);
218 static void _emit_metadata(MafwGstRendererWorker *worker,
226 if (worker && worker->notify_metadata_handler &&
227 _set_value(&v, type, value))
229 GValueArray *array = g_value_array_new(0);
230 g_value_array_append(array, &v);
231 worker->notify_metadata_handler(worker,
236 g_value_array_free(array);
242 static void _emit_property(MafwGstRendererWorker *worker,
250 if (worker && worker->notify_property_handler &&
251 _set_value(&v, type, value))
253 worker->notify_property_handler(worker, worker->owner, property, &v);
259 static gchar *_init_tmp_file(void)
264 fd = g_file_open_tmp("mafw-gst-renderer-XXXXXX.picture", &path, NULL);
273 static void _destroy_tmp_file(MafwGstRendererWorker *worker, guint index)
275 g_unlink(worker->tmp_files_pool[index]);
276 g_free(worker->tmp_files_pool[index]);
277 worker->tmp_files_pool[index] = NULL;
280 static void _init_tmp_files_pool(MafwGstRendererWorker *worker)
284 worker->tmp_files_pool_index = 0;
286 for (i = 0; i < MAFW_GST_RENDERER_MAX_TMP_FILES; i++) {
287 worker->tmp_files_pool[i] = NULL;
291 static void _destroy_tmp_files_pool(MafwGstRendererWorker *worker)
295 for (i = 0; (i < MAFW_GST_RENDERER_MAX_TMP_FILES) &&
296 (worker->tmp_files_pool[i] != NULL); i++) {
297 g_unlink(worker->tmp_files_pool[i]);
298 g_free(worker->tmp_files_pool[i]);
302 static const gchar *_get_tmp_file_from_pool(MafwGstRendererWorker *worker)
304 gchar *path = worker->tmp_files_pool[worker->tmp_files_pool_index];
307 path = _init_tmp_file();
308 worker->tmp_files_pool[worker->tmp_files_pool_index] = path;
312 _destroy_tmp_file(worker, worker->tmp_files_pool_index);
313 path = _init_tmp_file();
314 worker->tmp_files_pool[worker->tmp_files_pool_index] = path;
317 if (++(worker->tmp_files_pool_index) >= MAFW_GST_RENDERER_MAX_TMP_FILES) {
318 worker->tmp_files_pool_index = 0;
324 static void _emit_gst_buffer_as_graphic_file_cb(GError *error,
327 SaveGraphicData *sgd = user_data;
330 /* Add the info to the current metadata. */
331 _current_metadata_add(sgd->worker,
334 (const gpointer)sgd->filename);
336 /* Emit the metadata. */
337 _emit_metadata(sgd->worker,
344 g_warning("could not emit graphic file: %s", error->message);
350 static void _emit_gst_buffer_as_graphic_file(MafwGstRendererWorker *worker,
352 const gint metadata_key)
354 GstStructure *structure;
355 const gchar *mime = NULL;
356 GError *error = NULL;
357 SaveGraphicData *sgd;
359 g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
361 structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0);
362 mime = gst_structure_get_name(structure);
364 /* video pause frame related branch */
365 if (g_str_has_prefix(mime, "video/x-raw")) {
366 const gchar *filename = _get_tmp_file_from_pool(worker);
368 if(worker->taking_screenshot)
370 worker->screenshot_handler(worker, worker->owner, NULL, NULL, TRUE);
372 worker->taking_screenshot = TRUE;
373 worker->screenshot_handler(worker, worker->owner, buffer, filename, FALSE);
375 /* gst image tag related branch */
376 } else if (g_str_has_prefix(mime, "image/")) {
378 sgd = g_new0(SaveGraphicData, 1);
379 sgd->worker = worker;
380 sgd->metadata_key = metadata_key;
381 sgd->filename = _get_tmp_file_from_pool(worker);
383 g_debug("dumping gst image %s directly to a file", mime);
384 g_file_set_contents(sgd->filename,
385 (const gchar*)GST_BUFFER_DATA(buffer),
386 GST_BUFFER_SIZE(buffer),
388 _emit_gst_buffer_as_graphic_file_cb(error, sgd);
393 g_warning("Mime type not supported, will not create a thumbnail");
394 gst_buffer_unref(buffer);
398 static gboolean _go_to_gst_ready(gpointer user_data)
400 g_debug("_go_to_gst_ready");
401 MafwGstRendererWorker *worker = user_data;
403 g_return_val_if_fail(worker->state == GST_STATE_PAUSED ||
404 worker->prerolling, FALSE);
406 worker->seek_position = mafw_gst_renderer_worker_get_position(worker);
408 g_debug("going to GST_STATE_READY");
409 gst_element_set_state(worker->pipeline, GST_STATE_READY);
410 worker->in_ready = TRUE;
414 static void _add_ready_timeout(MafwGstRendererWorker *worker)
416 if( worker->ready_timeout == 0 )
418 g_debug("Adding timeout to go to GST_STATE_READY");
419 worker->ready_timeout =
420 g_timeout_add_seconds(
421 worker->config->seconds_to_pause_to_ready,
427 static void _remove_ready_timeout(MafwGstRendererWorker *worker)
429 if( worker->ready_timeout != 0 )
431 g_debug("removing timeout for READY");
432 g_source_remove(worker->ready_timeout);
433 worker->ready_timeout = 0;
437 static gboolean _take_pause_frame(gpointer user_data)
439 MafwGstRendererWorker *worker = user_data;
441 if( worker->pause_frame_taken && worker->pause_frame_buffer )
443 gst_buffer_unref(worker->pause_frame_buffer);
444 worker->pause_frame_buffer = NULL;
448 if (worker->pause_frame_buffer != NULL) {
449 worker->pause_frame_taken = TRUE;
450 _emit_gst_buffer_as_graphic_file(
452 worker->pause_frame_buffer,
453 WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI);
454 worker->pause_frame_buffer = NULL;
459 static void _add_pause_frame_timeout(MafwGstRendererWorker *worker)
461 if (worker->media.has_visual_content && worker->current_frame_on_pause && worker->seek_position == -1)
463 if (!worker->pause_frame_timeout)
465 GstBuffer *buffer = NULL;
466 g_object_get(worker->pipeline, "frame", &buffer, NULL);
469 GstBuffer *copy = gst_buffer_copy(buffer);
470 gst_buffer_copy_metadata(copy, buffer, GST_BUFFER_COPY_ALL);
471 worker->pause_frame_buffer = copy;
472 gst_buffer_unref(buffer);
474 g_debug("Adding timeout to go to current frame capture");
475 worker->pause_frame_timeout =
478 worker->config->milliseconds_to_pause_frame,
484 g_warning("MafwGstRenderer Worker: Could not get buffer from pipeline! Maybe at EOS?");
488 g_debug("Not adding timeout to take pause frame.");
489 worker->pause_frame_timeout = 0;
493 static void _remove_pause_frame_timeout(MafwGstRendererWorker *worker)
495 if (worker->pause_frame_timeout != 0) {
496 g_debug("removing timeout for pause frame!");
497 g_source_remove(worker->pause_frame_timeout);
498 worker->pause_frame_timeout = 0;
501 if(worker->taking_screenshot)
503 worker->screenshot_handler(worker, worker->owner, NULL, NULL, TRUE);
504 worker->taking_screenshot = FALSE;
508 /* in this case the buffer has not been given to the
509 * screenshot component to be processed */
510 if(worker->pause_frame_buffer)
512 gst_buffer_unref(worker->pause_frame_buffer);
513 worker->pause_frame_buffer = NULL;
518 static gboolean _emit_video_info(MafwGstRendererWorker *worker)
521 _emit_metadata(worker,
522 WORKER_METADATA_KEY_RES_X,
524 &worker->media.video_width);
526 _emit_metadata(worker,
527 WORKER_METADATA_KEY_RES_Y,
529 &worker->media.video_height);
531 _emit_metadata(worker,
532 WORKER_METADATA_KEY_VIDEO_FRAMERATE,
541 * Checks if the video details are supported. It also extracts other useful
542 * information (such as PAR and framerate) from the caps, if available. NOTE:
543 * this will be called from a different thread than glib's mainloop (when
544 * invoked via _stream_info_cb); don't call MafwGstRenderer directly.
546 * Returns: TRUE if video details are acceptable.
548 static gboolean _handle_video_info(MafwGstRendererWorker *worker,
549 const GstStructure *structure)
555 gst_structure_get_int(structure, "width", &width);
556 gst_structure_get_int(structure, "height", &height);
557 g_debug("video size: %d x %d", width, height);
558 if (gst_structure_has_field(structure, "pixel-aspect-ratio"))
560 gst_structure_get_fraction(structure,
561 "pixel-aspect-ratio",
562 &worker->media.par_n,
563 &worker->media.par_d);
564 g_debug("video PAR: %d:%d", worker->media.par_n, worker->media.par_d);
565 width = width * worker->media.par_n / worker->media.par_d;
569 if (gst_structure_has_field(structure, "framerate"))
573 gst_structure_get_fraction(structure, "framerate", &fps_n, &fps_d);
575 fps = (gdouble)fps_n / (gdouble)fps_d;
577 g_debug("video fps: %f", fps);
580 worker->media.video_width = width;
581 worker->media.video_height = height;
582 worker->media.fps = fps;
584 _current_metadata_add(worker, WORKER_METADATA_KEY_RES_X, G_TYPE_INT,
585 (const gpointer)&width);
586 _current_metadata_add(worker, WORKER_METADATA_KEY_RES_Y, G_TYPE_INT,
587 (const gpointer)&height);
588 _current_metadata_add(worker, WORKER_METADATA_KEY_VIDEO_FRAMERATE,
589 G_TYPE_DOUBLE, (const gpointer)&fps);
591 /* Emit the metadata.*/
592 g_idle_add((GSourceFunc)_emit_video_info, worker);
596 static void _parse_stream_info_item(MafwGstRendererWorker *worker, GObject *obj)
602 g_object_get(obj, "type", &type, NULL);
603 pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), "type");
606 val = g_enum_get_value(G_PARAM_SPEC_ENUM(pspec)->enum_class, type);
609 if (!g_ascii_strcasecmp(val->value_nick, "video") ||
610 !g_ascii_strcasecmp(val->value_name, "video"))
616 g_object_get(obj, "object", &object, NULL);
619 vcaps = gst_pad_get_caps(GST_PAD_CAST(object));
621 g_object_get(obj, "caps", &vcaps, NULL);
625 if (gst_caps_is_fixed(vcaps))
627 _handle_video_info(worker, gst_caps_get_structure(vcaps, 0));
629 gst_caps_unref(vcaps);
634 /* It always returns FALSE, because it is used as an idle callback as well. */
635 static gboolean _parse_stream_info(MafwGstRendererWorker *worker)
637 GList *stream_info, *s;
640 if (g_object_class_find_property(G_OBJECT_GET_CLASS(worker->pipeline),
643 g_object_get(worker->pipeline, "stream-info", &stream_info, NULL);
645 for (s = stream_info; s; s = g_list_next(s))
646 _parse_stream_info_item(worker, G_OBJECT(s->data));
650 static void mafw_gst_renderer_worker_apply_xid(MafwGstRendererWorker *worker)
652 /* Set sink to render on the provided XID if we have do have
653 a XID a valid video sink and we are rendering video content */
656 worker->media.has_visual_content)
658 g_debug ("Setting overlay, window id: %x", (gint) worker->xid);
659 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(worker->vsink), worker->xid);
662 /* Ask the gst to redraw the frame if we are paused */
663 /* TODO: in MTG this works only in non-fs -> fs way. */
664 if (worker->state == GST_STATE_PAUSED)
666 gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink));
669 g_debug("Not setting overlay for window id: %x", (gint) worker->xid);
673 static void mafw_gst_renderer_worker_apply_render_rectangle(MafwGstRendererWorker *worker)
675 /* Set sink to render on the provided XID if we have do have
676 a XID a valid video sink and we are rendering video content */
679 worker->media.has_visual_content
681 (worker->x_overlay_rectangle.x >= 0 &&
682 worker->x_overlay_rectangle.y >= 0 &&
683 worker->x_overlay_rectangle.width >= 0 &&
684 worker->x_overlay_rectangle.height >= 0) )
686 g_debug("Applying render rectangle: X:%d,Y:%d Width:%d, Height:%d",
687 worker->x_overlay_rectangle.x,
688 worker->x_overlay_rectangle.y,
689 worker->x_overlay_rectangle.width,
690 worker->x_overlay_rectangle.height);
692 gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(worker->vsink),
693 worker->x_overlay_rectangle.x,
694 worker->x_overlay_rectangle.y,
695 worker->x_overlay_rectangle.width,
696 worker->x_overlay_rectangle.height);
697 /* Ask the gst to redraw the frame if we are paused */
698 /* TODO: in MTG this works only in non-fs -> fs way. */
699 if (worker->state == GST_STATE_PAUSED)
701 gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink));
705 g_debug("Not setting render rectangle for window id: %x", (gint) worker->xid);
710 * GstBus synchronous message handler. NOTE that this handler is NOT invoked
711 * from the glib thread, so be careful what you do here.
713 static GstBusSyncReply _sync_bus_handler(GstBus *bus,
715 MafwGstRendererWorker *worker)
720 if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
721 gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
723 g_debug("got prepare-xwindow-id");
724 worker->media.has_visual_content = TRUE;
725 set_dolby_video_property(worker, worker->config->mobile_surround_video.state);
726 set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.room, TRUE);
727 set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.color, FALSE);
728 /* The user has to preset the XID, we don't create windows by
730 if (!worker->xid || !worker->vsink) {
731 /* We must post an error message to the bus that will be picked up
732 * by _async_bus_handler. Calling the notification function
733 * directly from here (different thread) is not healthy. */
734 g_warning("No video window or video-sink set!");
736 g_error_new_literal(WORKER_ERROR,
737 WORKER_ERROR_PLAYBACK,
738 "No video window XID or video-sink set"));
739 gst_message_unref (msg);
742 g_debug ("Video window to use is: %x", (gint)worker->xid);
745 /* Instruct vsink to use the client-provided window */
746 mafw_gst_renderer_worker_apply_xid(worker);
747 /* Instruct vsink to use the required render rectangle */
748 mafw_gst_renderer_worker_apply_render_rectangle(worker);
750 /* Handle colorkey and autopaint */
751 mafw_gst_renderer_worker_set_autopaint(worker, worker->autopaint);
752 g_object_get(worker->vsink, "colorkey", &worker->colorkey, NULL);
753 /* Defer the signal emission to the thread running the mainloop. */
754 if (worker->colorkey != -1) {
755 gst_bus_post(worker->bus,
756 gst_message_new_application(
757 GST_OBJECT(worker->vsink),
758 gst_structure_empty_new("ckey")));
760 gst_message_unref (msg);
763 /* do not unref message when returning PASS */
767 static void _free_taglist_item(GstMessage *msg, gpointer data)
771 gst_message_unref(msg);
774 static void _free_taglist(MafwGstRendererWorker *worker)
776 if (worker->tag_list != NULL)
778 g_ptr_array_foreach(worker->tag_list, (GFunc)_free_taglist_item, NULL);
779 g_ptr_array_free(worker->tag_list, TRUE);
780 worker->tag_list = NULL;
784 static gboolean _seconds_duration_equal(gint64 duration1, gint64 duration2)
786 gint64 duration1_seconds, duration2_seconds;
788 duration1_seconds = duration1 / GST_SECOND;
789 duration2_seconds = duration2 / GST_SECOND;
791 return duration1_seconds == duration2_seconds;
794 static void _check_duration(MafwGstRendererWorker *worker, gint64 value)
796 gboolean right_query = TRUE;
799 GstFormat format = GST_FORMAT_TIME;
801 gst_element_query_duration(worker->pipeline, &format, &value);
804 if (right_query && value > 0
805 && !_seconds_duration_equal(worker->media.length_nanos, value))
807 gint64 duration = (value + (GST_SECOND/2)) / GST_SECOND;
809 /* Add the duration to the current metadata. */
810 if( _current_metadata_add(worker,
811 WORKER_METADATA_KEY_DURATION,
813 (const gpointer)&duration) )
815 _emit_metadata(worker,
816 WORKER_METADATA_KEY_DURATION,
821 /* Publish to context FW */
822 if( worker->context_nowplaying == NULL )
824 worker->context_nowplaying = context_provider_map_new();
826 context_provider_map_set_integer(worker->context_nowplaying,
827 CONTEXT_PROVIDER_KEY_NOWPLAYING_DURATION,
829 context_provider_set_map(CONTEXT_PROVIDER_KEY_NOWPLAYING,
830 worker->context_nowplaying, FALSE);
831 /* end of publishing to context FW */
836 worker->media.length_nanos = value;
839 g_debug("media duration: %lld", worker->media.length_nanos);
842 static void _check_seekability(MafwGstRendererWorker *worker)
844 SeekabilityType seekable = SEEKABILITY_UNKNOWN;
845 if (worker->media.length_nanos >= 0 )
847 g_debug("Quering GStreamer for seekability");
848 GstQuery *seek_query;
849 GstFormat format = GST_FORMAT_TIME;
850 /* Query the seekability of the stream */
851 seek_query = gst_query_new_seeking(format);
852 if (gst_element_query(worker->pipeline, seek_query)) {
853 gboolean renderer_seekable = FALSE;
854 gst_query_parse_seeking(seek_query,
858 g_debug("GStreamer seekability %d", renderer_seekable);
859 seekable = renderer_seekable ? SEEKABILITY_SEEKABLE : SEEKABILITY_NO_SEEKABLE;
863 g_debug("Could not query pipeline for seekability! Using old value!");
864 seekable = worker->media.seekable;
866 gst_query_unref(seek_query);
868 else if( worker->media.length_nanos == DURATION_INDEFINITE )
870 /* duration indefinite, "clearly" not seekable */
871 seekable = SEEKABILITY_NO_SEEKABLE;
875 /* otherwise we'll use last known/guessed value */
876 seekable = worker->media.seekable;
879 g_debug("media seekable: %d", seekable);
881 /* If the seekability is unknown it is set as false and sent. After that it is
882 sent only if it changes to true
884 if( (seekable == SEEKABILITY_UNKNOWN && worker->media.seekable == SEEKABILITY_UNKNOWN)
885 || seekable != worker->media.seekable )
887 if( seekable != SEEKABILITY_NO_SEEKABLE )
889 worker->media.seekable = SEEKABILITY_SEEKABLE;
893 worker->media.seekable = SEEKABILITY_NO_SEEKABLE;
896 gboolean is_seekable = (worker->media.seekable == SEEKABILITY_SEEKABLE);
897 _current_metadata_add(worker,
898 WORKER_METADATA_KEY_IS_SEEKABLE,
900 (const gpointer)&is_seekable);
901 _emit_metadata(worker,
902 WORKER_METADATA_KEY_IS_SEEKABLE,
908 static gboolean _query_duration_and_seekability_timeout(gpointer data)
910 MafwGstRendererWorker *worker = data;
912 if (!worker->in_ready)
914 _check_duration(worker, -1);
915 worker->duration_seek_timeout_loop_count += 1;
917 /* for worker's internal logic let's put the indefinite duration if loop limit has been reached */
918 /* this affects the seekability resolution */
919 if( worker->duration_seek_timeout_loop_count >= MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LOOP_LIMIT
920 && worker->media.length_nanos == DURATION_UNQUERIED )
922 worker->media.length_nanos = DURATION_INDEFINITE;
925 _check_seekability(worker);
927 if( worker->media.length_nanos >= DURATION_INDEFINITE )
929 worker->duration_seek_timeout = 0;
930 /* we've got a valid duration value no need to ask for more */
940 g_warning("_query_duration_and_seekability_timeout: We are in ready state, duration and seekability not checked.");
946 * Resets the media information.
948 static void _reset_media_info(MafwGstRendererWorker *worker)
950 if (worker->media.location) {
951 g_free(worker->media.location);
952 worker->media.location = NULL;
954 worker->media.length_nanos = DURATION_UNQUERIED;
955 worker->media.has_visual_content = FALSE;
956 worker->media.seekable = SEEKABILITY_UNKNOWN;
957 worker->media.video_width = 0;
958 worker->media.video_height = 0;
959 worker->media.fps = 0.0;
962 static void _reset_pipeline_and_worker(MafwGstRendererWorker *worker)
965 if (worker->pipeline) {
966 g_debug("destroying pipeline");
967 if (worker->async_bus_id) {
968 g_source_remove(worker->async_bus_id);
969 worker->async_bus_id = 0;
971 gst_element_set_state(worker->pipeline, GST_STATE_NULL);
973 gst_bus_set_sync_handler(worker->bus, NULL, NULL);
974 gst_object_unref(GST_OBJECT_CAST(worker->bus));
977 gst_object_unref(worker->pipeline);
978 worker->pipeline = NULL;
981 worker->report_statechanges = TRUE;
982 worker->state = GST_STATE_NULL;
983 worker->prerolling = FALSE;
984 worker->is_live = FALSE;
985 worker->buffering = FALSE;
986 worker->is_stream = FALSE;
987 worker->is_error = FALSE;
989 worker->seek_position = -1;
990 worker->stay_paused = FALSE;
991 worker->playback_speed = 1;
992 worker->in_ready = FALSE;
993 _remove_ready_timeout(worker);
994 _remove_pause_frame_timeout(worker);
995 _free_taglist(worker);
996 if (worker->current_metadata) {
997 g_hash_table_destroy(worker->current_metadata);
998 worker->current_metadata = NULL;
1001 if (worker->duration_seek_timeout != 0) {
1002 g_source_remove(worker->duration_seek_timeout);
1003 worker->duration_seek_timeout = 0;
1005 worker->duration_seek_timeout_loop_count = 0;
1007 _reset_media_info(worker);
1009 /* removes all idle timeouts with this worker as data */
1010 while(g_idle_remove_by_data(worker));
1015 * Called when the pipeline transitions into PAUSED state. It extracts more
1016 * information from Gst.
1018 static void _finalize_startup(MafwGstRendererWorker *worker)
1020 /* Check video caps */
1021 if (worker->media.has_visual_content && worker->vsink) {
1022 GstPad *pad = GST_BASE_SINK_PAD(worker->vsink);
1023 GstCaps *caps = GST_PAD_CAPS(pad);
1024 if (caps && gst_caps_is_fixed(caps)) {
1025 GstStructure *structure;
1026 structure = gst_caps_get_structure(caps, 0);
1027 if (!_handle_video_info(worker, structure))
1032 /* Something might have gone wrong at this point already. */
1033 if (worker->is_error) {
1034 g_debug("Error occured during preroll");
1038 /* Streaminfo might reveal the media to be unsupported. Therefore we
1039 * need to check the error again. */
1040 _parse_stream_info(worker);
1041 if (worker->is_error) {
1042 g_debug("Error occured. Leaving");
1046 /* Check duration and seekability */
1047 if (worker->duration_seek_timeout != 0) {
1048 g_source_remove(worker->duration_seek_timeout);
1049 worker->duration_seek_timeout = 0;
1052 _check_duration(worker, -1);
1053 _check_seekability(worker);
1056 static void _add_duration_seek_query_timeout(MafwGstRendererWorker *worker)
1058 if(worker->duration_seek_timeout == 0)
1061 if( worker->duration_seek_timeout_loop_count >= MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LOOP_LIMIT
1062 || worker->media.length_nanos >= DURATION_INDEFINITE )
1064 /* this is just for verifying the duration later on if it was received in PAUSED state early on */
1065 timeout = MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LAZY_TIMEOUT;
1069 timeout = MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_FAST_TIMEOUT;
1072 worker->duration_seek_timeout = g_timeout_add(
1074 _query_duration_and_seekability_timeout,
1079 static void _do_pause_postprocessing(MafwGstRendererWorker *worker)
1081 if (worker->notify_pause_handler) {
1082 worker->notify_pause_handler(worker, worker->owner);
1085 _add_pause_frame_timeout(worker);
1086 _add_ready_timeout(worker);
1089 static void _report_playing_state(MafwGstRendererWorker * worker)
1091 if (worker->report_statechanges && worker->notify_play_handler)
1093 worker->notify_play_handler( worker,
1098 static void _handle_state_changed(GstMessage *msg,
1099 MafwGstRendererWorker *worker)
1101 GstState newstate, oldstate;
1102 GstStateChange statetrans;
1104 gst_message_parse_state_changed(msg, &oldstate, &newstate, NULL);
1105 statetrans = GST_STATE_TRANSITION(oldstate, newstate);
1106 g_debug("State changed: %d: %d -> %d", worker->state, oldstate, newstate);
1108 /* If the state is the same we do nothing, otherwise, we keep it */
1109 if (worker->state == newstate)
1111 /* This is used for saving correct pause frame after pauseAt.
1112 * If we doing normal seek we dont want to save pause frame.
1113 * We use gst_element_get_state to check if the state change is completed.
1114 * If gst_element_get_state returns GST_STATE_CHANGE_SUCCESS we know that
1115 * it's save to do pause_postprocessing */
1116 if (newstate == GST_STATE_PAUSED && worker->stay_paused &&
1117 gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS)
1119 worker->seek_position = mafw_gst_renderer_seeker_process(worker->seeker);
1121 /* has seeking ended successfully? */
1122 if( worker->seek_position < 0 )
1124 /* we do pause_postprocessing for pauseAt */
1125 _do_pause_postprocessing(worker);
1129 /* the EOS flag should only be cleared if it has been set and seeking has been done
1130 * paused -> paused transition should only happen when seeking
1132 if( newstate == GST_STATE_PAUSED && worker->eos )
1134 worker->eos = FALSE;
1139 worker->state = newstate;
1141 switch (statetrans) {
1142 case GST_STATE_CHANGE_READY_TO_PAUSED:
1143 if (worker->in_ready) {
1144 /* Woken up from READY, resume stream position and playback */
1146 /*live sources can be sought only in PLAYING state*/
1147 if( !worker->is_live ) {
1150 worker->seek_position,
1155 /* While buffering, we have to wait in PAUSED until we reach 100% before
1157 if (worker->buffering) {
1162 } else if (worker->prerolling && worker->report_statechanges && !worker->buffering) {
1163 /* PAUSED after pipeline has been constructed. We check caps,
1164 * seek and duration and if staying in pause is needed, we
1165 * perform operations for pausing, such as current frame on
1166 * pause and signalling state change and adding the timeout to
1168 g_debug ("Prerolling done, finalizaing startup");
1169 _finalize_startup(worker);
1171 if (worker->stay_paused) {
1172 /* then we can tell we're paused */
1173 _do_pause_postprocessing(worker);
1176 if( worker->seek_position > 0 )
1178 g_debug("Immediate seek from READY state to: %d", worker->seek_position);
1179 _do_seek(worker, GST_SEEK_TYPE_SET,
1180 worker->seek_position, FALSE, NULL);
1184 g_object_set(worker->vsink, "show-preroll-frame",
1188 /* do_seek will set this to false, but we'll want to report state changes
1189 when doing immediate seek from start */
1190 worker->report_statechanges = TRUE;
1192 worker->prerolling = FALSE;
1196 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1197 /* When pausing we do the stuff, like signalling state, current
1198 * frame on pause and timeout to go to ready */
1199 if (worker->report_statechanges) {
1200 _do_pause_postprocessing(worker);
1203 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1205 /*live sources can be sought only in PLAYING state
1206 This seek should happen only after READY to PAUSED to PLAYING
1209 if( worker->report_statechanges
1210 && worker->seek_position > -1
1211 && worker->is_live )
1213 g_debug("Seeking live source in PLAYING state!");
1216 worker->seek_position,
1219 /* this has to be set as do_seek sets statechanges to FALSE
1220 but we still want to inform that we're in PLAYING state */
1221 worker->report_statechanges = TRUE;
1222 /* seek position needs to be reset here for a live stream */
1223 worker->seek_position = -1;
1226 /* Because live streams are sought in PLAYING state, we reset
1227 seek_position after all state transitions are completed. Normal
1228 streams resetting seek_position here is OK. */
1229 if(worker->report_statechanges == FALSE || !worker->is_live)
1231 /* if seek was called, at this point it is really ended */
1232 worker->seek_position = mafw_gst_renderer_seeker_process(worker->seeker);
1235 /* Signal state change if needed */
1236 _report_playing_state(worker);
1238 /* Prevent blanking if we are playing video */
1239 if (worker->media.has_visual_content &&
1240 worker->blanking__control_handler)
1242 worker->blanking__control_handler(worker, worker->owner, TRUE);
1245 /* Back to playing no longer in_ready (if ever was) */
1246 worker->in_ready = FALSE;
1248 /* context framework adaptation starts */
1249 worker->context_nowplaying =
1250 _set_context_map_value(worker->context_nowplaying,
1252 worker->media.location);
1253 context_provider_set_map(CONTEXT_PROVIDER_KEY_NOWPLAYING,
1254 worker->context_nowplaying, FALSE);
1255 /* context framework adaptation ends */
1257 /* Emit metadata. We wait until we reach the playing state because
1258 * this speeds up playback start time */
1259 _emit_metadatas(worker);
1261 /* in any case the duration is verified, it may change with VBR media */
1262 _add_duration_seek_query_timeout(worker);
1264 /* We've reached playing state, state changes are not reported
1265 * unless explicitly requested (e.g. by PAUSE request) seeking
1266 * in PLAYING does not cause state change reports
1268 worker->report_statechanges = FALSE;
1270 /* Delayed pause e.g. because of seek */
1271 if (worker->stay_paused) {
1272 mafw_gst_renderer_worker_pause(worker);
1276 case GST_STATE_CHANGE_PAUSED_TO_READY:
1277 /* If we went to READY, we free the taglist and * deassign the
1279 if (worker->in_ready) {
1280 g_debug("changed to GST_STATE_READY");
1281 worker->ready_timeout = 0;
1282 _free_taglist(worker);
1284 if( worker->notify_ready_state_handler )
1286 worker->notify_ready_state_handler(worker, worker->owner);
1290 case GST_STATE_CHANGE_NULL_TO_READY:
1291 if(g_str_has_prefix(worker->media.location, STREAM_TYPE_MMSH) ||
1292 g_str_has_prefix(worker->media.location, STREAM_TYPE_MMS))
1294 GstElement *source = NULL;
1295 g_object_get(worker->pipeline, "source", &source, NULL);
1298 gst_util_set_object_arg(G_OBJECT(source), "tcp-timeout", MAFW_GST_MMSH_TCP_TIMEOUT);
1299 gst_object_unref(source);
1302 g_warning("Failed to get source element from pipeline");
1310 static void _handle_duration(MafwGstRendererWorker *worker)
1312 /* if we've got ongoing query timeout ignore this and in any case do it only in PAUSE/PLAYING */
1313 if( worker->duration_seek_timeout == 0
1314 && ( worker->state == GST_STATE_PAUSED || worker->state == GST_STATE_PLAYING) )
1316 /* We want to check this quickly but not immediately */
1317 g_timeout_add_full(G_PRIORITY_DEFAULT,
1318 MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_FAST_TIMEOUT,
1319 _query_duration_and_seekability_timeout,
1325 static void _emit_renderer_art(MafwGstRendererWorker *worker,
1326 const GstTagList *list)
1328 GstBuffer *buffer = NULL;
1329 const GValue *value = NULL;
1331 g_return_if_fail(gst_tag_list_get_tag_size(list, GST_TAG_IMAGE) > 0);
1333 value = gst_tag_list_get_value_index(list, GST_TAG_IMAGE, 0);
1335 g_return_if_fail((value != NULL) && G_VALUE_HOLDS(value, GST_TYPE_BUFFER));
1337 buffer = g_value_peek_pointer(value);
1339 g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer));
1341 _emit_gst_buffer_as_graphic_file(worker,
1343 WORKER_METADATA_KEY_RENDERER_ART_URI);
1346 static void value_dtor(gpointer value)
1349 if (G_IS_VALUE(value)) {
1350 g_value_unset(value);
1353 g_value_array_free(value);
1358 static gboolean _current_metadata_add(MafwGstRendererWorker *worker,
1361 const gpointer value)
1364 gboolean was_updated = FALSE;
1368 g_warning("Null value for metadata was tried to be set!");
1372 if (!worker->current_metadata) {
1373 worker->current_metadata = g_hash_table_new_full(g_direct_hash,
1379 if (type == G_TYPE_VALUE_ARRAY) {
1380 GValueArray *values = (GValueArray *) value;
1382 if (values->n_values == 1) {
1383 GValue *gval = g_value_array_get_nth(values, 0);
1384 new_gval = g_new0(GValue, 1);
1385 g_value_init(new_gval, G_VALUE_TYPE(gval));
1386 g_value_copy(gval, new_gval);
1388 GValue *existing = (GValue*)g_hash_table_lookup(worker->current_metadata, (gpointer)key);
1389 if(!existing || (GST_VALUE_EQUAL != gst_value_compare(existing, new_gval)) )
1393 g_hash_table_insert(worker->current_metadata,
1397 GValueArray *new_gvalues = g_value_array_copy(values);
1399 GValueArray *existing = (GValueArray*)g_hash_table_lookup(worker->current_metadata, (gpointer)key);
1402 && new_gvalues->n_values == existing->n_values )
1404 guint size = new_gvalues->n_values;
1407 for( ; i < size; ++i )
1409 GValue *newVal = g_value_array_get_nth(new_gvalues, i);
1410 GValue *existingVal = g_value_array_get_nth(existing, i);
1411 if( GST_VALUE_EQUAL != gst_value_compare(newVal, existingVal) )
1423 g_hash_table_insert(worker->current_metadata,
1431 new_gval = g_new0(GValue, 1);
1433 if (_set_value(new_gval, type, value) == FALSE)
1435 g_warning("Metadata type: %i is not being handled", type);
1439 GValue *existing = (GValue*)g_hash_table_lookup(worker->current_metadata, (gpointer)key);
1440 if(!existing || (GST_VALUE_EQUAL != gst_value_compare(existing, new_gval)) )
1444 g_hash_table_insert(worker->current_metadata, (gpointer)key, new_gval);
1450 static GHashTable* _build_tagmap(void)
1452 GHashTable *hash_table = NULL;
1454 hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1456 g_hash_table_insert(hash_table, g_strdup(GST_TAG_TITLE),
1457 (gpointer)WORKER_METADATA_KEY_TITLE);
1458 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ARTIST),
1459 (gpointer)WORKER_METADATA_KEY_ARTIST);
1460 g_hash_table_insert(hash_table, g_strdup(GST_TAG_AUDIO_CODEC),
1461 (gpointer)WORKER_METADATA_KEY_AUDIO_CODEC);
1462 g_hash_table_insert(hash_table, g_strdup(GST_TAG_VIDEO_CODEC),
1463 (gpointer)WORKER_METADATA_KEY_VIDEO_CODEC);
1464 g_hash_table_insert(hash_table, g_strdup(GST_TAG_BITRATE),
1465 (gpointer)WORKER_METADATA_KEY_BITRATE);
1466 g_hash_table_insert(hash_table, g_strdup(GST_TAG_LANGUAGE_CODE),
1467 (gpointer)WORKER_METADATA_KEY_ENCODING);
1468 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ALBUM),
1469 (gpointer)WORKER_METADATA_KEY_ALBUM);
1470 g_hash_table_insert(hash_table, g_strdup(GST_TAG_GENRE),
1471 (gpointer)WORKER_METADATA_KEY_GENRE);
1472 g_hash_table_insert(hash_table, g_strdup(GST_TAG_TRACK_NUMBER),
1473 (gpointer)WORKER_METADATA_KEY_TRACK);
1474 g_hash_table_insert(hash_table, g_strdup(GST_TAG_ORGANIZATION),
1475 (gpointer)WORKER_METADATA_KEY_ORGANIZATION);
1476 g_hash_table_insert(hash_table, g_strdup(GST_TAG_IMAGE),
1477 (gpointer)WORKER_METADATA_KEY_RENDERER_ART_URI);
1483 * Sets values to given context framework map, allocates it when map is NULL.
1485 gpointer _set_context_map_value(gpointer map,
1492 map = context_provider_map_new();
1495 if (g_str_equal(tag, GST_TAG_LOCATION))
1497 context_provider_map_set_string(map,
1498 CONTEXT_PROVIDER_KEY_NOWPLAYING_RESOURCE,
1501 else if (g_str_equal(tag, GST_TAG_TITLE))
1503 context_provider_map_set_string(map,
1504 CONTEXT_PROVIDER_KEY_NOWPLAYING_TITLE,
1507 else if (g_str_equal(tag, GST_TAG_ARTIST))
1509 context_provider_map_set_string(map,
1510 CONTEXT_PROVIDER_KEY_NOWPLAYING_ARTIST,
1513 else if (g_str_equal(tag, GST_TAG_ALBUM))
1515 context_provider_map_set_string(map,
1516 CONTEXT_PROVIDER_KEY_NOWPLAYING_ALBUM,
1519 else if (g_str_equal(tag, GST_TAG_GENRE))
1521 context_provider_map_set_string(map,
1522 CONTEXT_PROVIDER_KEY_NOWPLAYING_GENRE,
1531 * Emits metadata-changed signals for gst tags.
1533 static void _emit_tag(const GstTagList *list,
1535 MafwGstRendererWorker *worker)
1537 /* Mapping between Gst <-> MAFW metadata tags
1538 * NOTE: This assumes that GTypes matches between GST and MAFW. */
1539 static GHashTable *tagmap = NULL;
1543 GValueArray *values;
1545 if (tagmap == NULL) {
1546 tagmap = _build_tagmap();
1549 g_debug("tag: '%s' (type: %s)", tag, g_type_name(gst_tag_get_type(tag)));
1550 /* Is there a mapping for this tag? */
1551 mafwtag = (gint)g_hash_table_lookup(tagmap, tag);
1555 if (mafwtag == WORKER_METADATA_KEY_RENDERER_ART_URI) {
1556 _emit_renderer_art(worker, list);
1560 /* Build a value array of this tag. We need to make sure that strings
1561 * are UTF-8. GstTagList API says that the value is always UTF8, but it
1562 * looks like the ID3 demuxer still might sometimes produce non-UTF-8
1564 count = gst_tag_list_get_tag_size(list, tag);
1566 type = gst_tag_get_type(tag);
1567 values = g_value_array_new(count);
1568 for (i = 0; i < count; ++i) {
1569 GValue *v = (GValue *)
1570 gst_tag_list_get_value_index(list, tag, i);
1571 if (type == G_TYPE_STRING) {
1574 gboolean tagExists = gst_tag_list_get_string_index(list, tag, i, &orig);
1575 UNUSED(tagExists); //TODO what if tag does not exists?
1576 if (convert_utf8(orig, &utf8)) {
1578 memset(&utf8gval, 0, sizeof(utf8gval));
1580 g_value_init(&utf8gval, G_TYPE_STRING);
1581 g_value_take_string(&utf8gval, utf8);
1582 g_value_array_append(values, &utf8gval);
1583 g_value_unset(&utf8gval);
1585 /* context framework adaptation starts */
1586 worker->context_nowplaying =
1587 _set_context_map_value(worker->context_nowplaying,
1590 /* context framework adaptation ends */
1592 } else if (type == G_TYPE_UINT) {
1594 memset(&intgval, 0, sizeof(intgval));
1596 g_value_init(&intgval, G_TYPE_INT);
1597 g_value_transform(v, &intgval);
1598 g_value_array_append(values, &intgval);
1599 g_value_unset(&intgval);
1601 g_value_array_append(values, v);
1605 /* context framework adaptation starts */
1606 context_provider_set_map(CONTEXT_PROVIDER_KEY_NOWPLAYING,
1607 worker->context_nowplaying, FALSE);
1608 /* context framework adaptation ends */
1610 /* Add the info to the current metadata. */
1611 gboolean changed = _current_metadata_add(worker,
1614 (const gpointer) values);
1616 /* Emit the metadata. */
1617 if (changed && worker->notify_metadata_handler)
1619 worker->notify_metadata_handler(worker,
1626 g_value_array_free(values);
1630 * Collect tag-messages, parse it later, when playing is ongoing
1632 static void _handle_tag(MafwGstRendererWorker *worker, GstMessage *msg)
1634 /* Do not emit metadata until we get to PLAYING state to speed up playback
1636 if (worker->tag_list == NULL)
1637 worker->tag_list = g_ptr_array_new();
1638 g_ptr_array_add(worker->tag_list, gst_message_ref(msg));
1640 /* Some tags come in playing state, so in this case we have to emit them
1641 * right away (example: radio stations) */
1642 if (worker->state == GST_STATE_PLAYING) {
1643 _emit_metadatas(worker);
1648 * Parses the list of tag-messages
1650 static void _parse_tagmsg(GstMessage *msg, MafwGstRendererWorker *worker)
1652 GstTagList *new_tags;
1654 gst_message_parse_tag(msg, &new_tags);
1655 gst_tag_list_foreach(new_tags, (gpointer)_emit_tag, worker);
1656 gst_tag_list_free(new_tags);
1657 gst_message_unref(msg);
1661 * Parses the collected tag messages, and emits the metadatas
1663 static void _emit_metadatas(MafwGstRendererWorker *worker)
1665 if (worker->tag_list != NULL)
1667 g_ptr_array_foreach(worker->tag_list, (GFunc)_parse_tagmsg, worker);
1668 g_ptr_array_free(worker->tag_list, TRUE);
1669 worker->tag_list = NULL;
1673 static void _handle_buffering(MafwGstRendererWorker *worker, GstMessage *msg)
1675 /* We set smaller buffer for mms streams */
1676 if((g_str_has_prefix(worker->media.location, STREAM_TYPE_MMSH) || g_str_has_prefix(worker->media.location, STREAM_TYPE_MMS))
1677 && worker->state != GST_STATE_PLAYING && !worker->buffering)
1681 g_object_set(worker->queue, "high-percent", 30, NULL);
1685 g_warning("Queue2 element doesn't exist!");
1689 /* We can ignore buffering messages when we are in READY state or when going to it */
1690 if(worker->state == GST_STATE_READY || worker->ready_timeout != 0 )
1692 worker->buffering = TRUE;
1697 gst_message_parse_buffering(msg, &percent);
1698 g_debug("buffering: %d", percent);
1700 /* No state management needed for live pipelines */
1701 if (!worker->is_live) {
1702 worker->buffering = TRUE;
1703 if (percent < 100 && worker->state == GST_STATE_PLAYING) {
1704 /* If we need to buffer more, we set larger buffer */
1705 if(g_str_has_prefix(worker->media.location, STREAM_TYPE_MMSH) || g_str_has_prefix(worker->media.location, STREAM_TYPE_MMS))
1709 g_object_set(worker->queue, "high-percent", 100, NULL);
1713 g_warning("Queue2 element doesn't exist!");
1716 g_debug("setting pipeline to PAUSED not to wolf the buffer down");
1718 //if there's no requested state transitions i.e. resume/pause let's keep this quiet
1719 if( gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS )
1721 worker->report_statechanges = FALSE;
1724 /* We can't call _pause() here, since it sets the
1725 * "report_statechanges" to TRUE. We don't want that, application
1726 * doesn't need to know that internally the state changed to PAUSED.
1728 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
1731 if (percent >= 100) {
1732 /* On buffering we go to PAUSED, so here we move back to PLAYING */
1733 worker->buffering = FALSE;
1734 if (worker->state == GST_STATE_PAUSED)
1736 /* If buffering more than once, do this only the first time we
1737 * are done with buffering */
1738 if (worker->prerolling)
1740 g_debug("buffering concluded during prerolling");
1741 _finalize_startup(worker);
1743 /* Send the paused notification */
1744 if (worker->stay_paused &&
1745 worker->notify_pause_handler)
1747 worker->notify_pause_handler(worker, worker->owner);
1749 worker->prerolling = FALSE;
1751 /* if eos has been reached no automatic playing should be done
1752 only on resume request e.g. eos reached -> seek requested => stays paused after seek&buffering completed */
1753 else if (!worker->stay_paused && !worker->eos)
1755 g_debug("buffering concluded, setting "
1756 "pipeline to PLAYING again");
1757 worker->report_statechanges = TRUE;
1758 gst_element_set_state(worker->pipeline, GST_STATE_PLAYING);
1761 /* if there's no pending state changes and we're really in PLAYING state... */
1762 else if (gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS
1763 && worker->state == GST_STATE_PLAYING)
1765 g_debug("buffering concluded, signalling state change");
1766 /* In this case we got a PLAY command while buffering, likely
1767 * because it was issued before we got the first buffering
1768 * signal. The UI should not do this, but if it does, we have
1769 * to signal that we have executed the state change, since in
1770 * _handle_state_changed we do not do anything if we are
1772 if (worker->report_statechanges &&
1773 worker->notify_play_handler) {
1774 worker->notify_play_handler(worker, worker->owner);
1776 _add_duration_seek_query_timeout(worker);
1779 /* has somebody requested pause transition? */
1780 else if( !worker->stay_paused )
1782 /* we're in PLAYING but pending for paused state change.
1783 Let's request new state change to override the pause */
1784 gst_element_set_state(worker->pipeline, GST_STATE_PLAYING);
1789 /* Send buffer percentage */
1790 if (worker->notify_buffer_status_handler)
1791 worker->notify_buffer_status_handler(worker, worker->owner, percent);
1794 static void _handle_element_msg(MafwGstRendererWorker *worker, GstMessage *msg)
1796 /* Only HelixBin sends "resolution" messages. */
1797 if (gst_structure_has_name(msg->structure, "resolution") &&
1798 _handle_video_info(worker, msg->structure))
1800 worker->media.has_visual_content = TRUE;
1801 set_dolby_video_property(worker, worker->config->mobile_surround_video.state);
1802 set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.room, TRUE);
1803 set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.color, FALSE);
1805 /* We do RTSP redirect when we try to play .sdp streams */
1806 else if(gst_structure_has_name(msg->structure, "redirect"))
1808 /* "new-location" contains the rtsp uri what we are going to play */
1809 mafw_gst_renderer_worker_play(worker, gst_structure_get_string(msg->structure, "new-location"));
1814 static GError * _get_specific_missing_plugin_error(GstMessage *msg)
1816 const GstStructure *gst_struct;
1822 desc = gst_missing_plugin_message_get_description(msg);
1824 gst_struct = gst_message_get_structure(msg);
1825 type = gst_structure_get_string(gst_struct, "type");
1827 if ((type) && ((strcmp(type, MAFW_GST_MISSING_TYPE_DECODER) == 0) ||
1828 (strcmp(type, MAFW_GST_MISSING_TYPE_ENCODER) == 0))) {
1830 /* Missing codec error. */
1832 const GstCaps *caps;
1833 GstStructure *caps_struct;
1836 val = gst_structure_get_value(gst_struct, "detail");
1837 caps = gst_value_get_caps(val);
1838 caps_struct = gst_caps_get_structure(caps, 0);
1839 mime = gst_structure_get_name(caps_struct);
1841 if (g_strrstr(mime, "video")) {
1842 error = g_error_new_literal(WORKER_ERROR,
1843 WORKER_ERROR_VIDEO_CODEC_NOT_FOUND,
1845 } else if (g_strrstr(mime, "audio")) {
1846 error = g_error_new_literal(WORKER_ERROR,
1847 WORKER_ERROR_AUDIO_CODEC_NOT_FOUND,
1850 error = g_error_new_literal(WORKER_ERROR,
1851 WORKER_ERROR_CODEC_NOT_FOUND,
1855 /* Unsupported type error. */
1856 error = g_error_new(WORKER_ERROR,
1857 WORKER_ERROR_UNSUPPORTED_TYPE,
1858 "missing plugin: %s", desc);
1867 * Asynchronous message handler. It gets removed from if it returns FALSE.
1869 static gboolean _async_bus_handler(GstBus *bus,
1871 MafwGstRendererWorker *worker)
1876 /* No need to handle message if error has already occured. */
1877 if (worker->is_error)
1880 /* Handle missing-plugin (element) messages separately, relaying more
1882 if (gst_is_missing_plugin_message(msg)) {
1883 GError *err = _get_specific_missing_plugin_error(msg);
1884 /* FIXME?: for some reason, calling the error handler directly
1885 * (_send_error) causes problems. On the other hand, turning
1886 * the error into a new GstMessage and letting the next
1887 * iteration handle it seems to work. */
1888 _post_error(worker, err);
1891 switch (GST_MESSAGE_TYPE(msg)) {
1892 case GST_MESSAGE_ERROR:
1893 if (!worker->is_error) {
1897 gst_message_parse_error(msg, &err, &debug);
1898 g_debug("gst error: domain = %s, code = %d, "
1899 "message = '%s', debug = '%s'",
1900 g_quark_to_string(err->domain), err->code, err->message, debug);
1905 //decodebin2 uses the error only to report text files
1906 if (err->code == GST_STREAM_ERROR_WRONG_TYPE && g_str_has_prefix(GST_MESSAGE_SRC_NAME(msg), "decodebin2"))
1908 err->code = WORKER_ERROR_POSSIBLY_PLAYLIST_TYPE;
1911 _send_error(worker, err);
1914 case GST_MESSAGE_EOS:
1915 if (!worker->is_error) {
1917 worker->seek_position = -1;
1918 if (worker->notify_eos_handler)
1920 worker->notify_eos_handler(worker, worker->owner);
1924 case GST_MESSAGE_TAG:
1925 _handle_tag(worker, msg);
1927 case GST_MESSAGE_BUFFERING:
1928 _handle_buffering(worker, msg);
1930 case GST_MESSAGE_DURATION:
1931 /* in ready state we might not get correct seekability info */
1932 if (!worker->in_ready)
1934 _handle_duration(worker);
1937 case GST_MESSAGE_ELEMENT:
1938 _handle_element_msg(worker, msg);
1940 case GST_MESSAGE_STATE_CHANGED:
1941 if ((GstElement *)GST_MESSAGE_SRC(msg) == worker->pipeline)
1942 _handle_state_changed(msg, worker);
1944 case GST_MESSAGE_APPLICATION:
1945 if (gst_structure_has_name(gst_message_get_structure(msg), "ckey"))
1947 _emit_property(worker,
1948 WORKER_PROPERTY_COLORKEY,
1958 /* NOTE this function will possibly be called from a different thread than the
1959 * glib main thread. */
1960 static void _stream_info_cb(GstObject *pipeline, GParamSpec *unused,
1961 MafwGstRendererWorker *worker)
1966 g_debug("stream-info changed");
1967 _parse_stream_info(worker);
1970 static void _element_added_cb(GstBin *bin, GstElement *element,
1971 MafwGstRendererWorker *worker)
1974 gchar *element_name;
1976 element_name = gst_element_get_name(element);
1977 if(g_str_has_prefix(element_name, "uridecodebin") ||
1978 g_str_has_prefix(element_name, "decodebin2"))
1980 g_signal_connect(element, "element-added",
1981 G_CALLBACK(_element_added_cb), worker);
1983 else if(g_str_has_prefix(element_name, "sdpdemux"))
1985 g_object_set(element, "redirect", FALSE, NULL);
1987 else if(g_str_has_prefix(element_name, "queue2"))
1989 worker->queue = element;
1991 g_free(element_name);
1995 * Start to play the media
1997 static void _start_play(MafwGstRendererWorker *worker)
1999 GstStateChangeReturn state_change_info;
2000 worker->stay_paused = FALSE;
2001 char *autoload_sub = NULL;
2003 g_assert(worker->pipeline);
2004 g_object_set(G_OBJECT(worker->pipeline),
2005 "uri", worker->media.location, NULL);
2007 if (worker->config->autoload_subtitles) {
2008 autoload_sub = uri_get_subtitle_uri(worker->media.location);
2010 g_debug("SUBURI: %s", autoload_sub);
2011 g_object_set(G_OBJECT(worker->pipeline),
2012 "suburi", autoload_sub,
2013 "subtitle-font-desc", worker->config->subtitle_font,
2014 "subtitle-encoding", worker->config->subtitle_encoding,
2017 gst_element_set_state(worker->pipeline, GST_STATE_READY);
2018 g_free(autoload_sub);
2021 g_object_set(G_OBJECT(worker->pipeline), "suburi", NULL, NULL);
2024 g_debug("URI: %s", worker->media.location);
2025 g_debug("setting pipeline to PAUSED");
2027 worker->report_statechanges = TRUE;
2028 state_change_info = gst_element_set_state(worker->pipeline,
2030 if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) {
2031 /* FIXME: for live sources we may have to handle buffering and
2032 * prerolling differently */
2033 g_debug ("Source is live!");
2034 worker->is_live = TRUE;
2036 worker->prerolling = TRUE;
2038 worker->is_stream = uri_is_stream(worker->media.location);
2043 * Constructs gst pipeline
2045 * FIXME: Could the same pipeline be used for playing all media instead of
2046 * constantly deleting and reconstructing it again?
2048 static void _construct_pipeline(MafwGstRendererWorker *worker, configuration *config)
2050 g_debug("constructing pipeline");
2051 g_assert(worker != NULL);
2053 /* Return if we have already one */
2054 if (worker->pipeline)
2057 _free_taglist(worker);
2059 g_debug("Creating a new instance of playbin2");
2060 worker->pipeline = gst_element_factory_make("playbin2", "playbin");
2061 if (worker->pipeline == NULL)
2063 /* Let's try with playbin */
2064 g_warning ("playbin2 failed, falling back to playbin");
2065 worker->pipeline = gst_element_factory_make("playbin", "playbin");
2067 if (worker->pipeline) {
2068 /* Use nwqueue only for non-rtsp and non-mms(h) streams. */
2070 use_nw = worker->media.location &&
2071 !g_str_has_prefix(worker->media.location, "rtsp://") &&
2072 !g_str_has_prefix(worker->media.location, "mms://") &&
2073 !g_str_has_prefix(worker->media.location, "mmsh://");
2075 g_debug("playbin using network queue: %d", use_nw);
2077 gst_object_ref_sink(worker->pipeline);
2078 /* These need a modified version of playbin. */
2079 g_object_set(G_OBJECT(worker->pipeline),
2081 "no-video-transform", TRUE,
2087 if (!worker->pipeline) {
2088 g_critical("failed to create playback pipeline");
2090 g_error_new(WORKER_ERROR,
2091 WORKER_ERROR_UNABLE_TO_PERFORM,
2092 "Could not create pipeline"));
2093 g_assert_not_reached();
2096 worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline));
2097 gst_bus_set_sync_handler(worker->bus,
2098 (GstBusSyncHandler)_sync_bus_handler,
2100 worker->async_bus_id = gst_bus_add_watch_full(
2101 worker->bus,G_PRIORITY_HIGH,
2102 (GstBusFunc)_async_bus_handler,
2105 /* Listen for changes in stream-info object to find out whether the media
2106 * contains video and throw error if application has not provided video
2108 g_signal_connect(worker->pipeline, "notify::stream-info",
2109 G_CALLBACK(_stream_info_cb), worker);
2111 g_signal_connect(worker->pipeline, "element-added",
2112 G_CALLBACK(_element_added_cb), worker);
2114 /* Set audio and video sinks ourselves. We create and configure them only
2116 if (!worker->asink) {
2117 const gchar *sink = g_getenv("AUDIO_SINK");
2118 worker->asink = gst_element_factory_make(sink?sink: worker->config->asink, NULL);
2119 if (!worker->asink){
2120 worker->asink = gst_element_factory_make("alsasink", NULL);
2122 if (!worker->asink) {
2123 g_critical("Failed to create pipeline audio sink");
2125 g_error_new(WORKER_ERROR,
2126 WORKER_ERROR_UNABLE_TO_PERFORM,
2127 "Could not create audio sink"));
2128 g_assert_not_reached();
2130 g_debug("MafwGstRendererWorker: Using following buffer-time: %lld and latency-time: %lld",
2131 config->buffer_time,
2132 config->latency_time);
2133 gst_object_ref_sink(worker->asink);
2134 g_object_set(worker->asink,
2135 "buffer-time", config->buffer_time,
2136 "latency-time", config->latency_time,
2140 if (worker->config->use_dhmmixer && !worker->amixer)
2142 worker->amixer = gst_element_factory_make("nokiadhmmix", NULL);
2143 if( !worker->amixer )
2145 g_warning("Could not create dhmmixer, falling back to basic audiosink!");
2149 if( worker->config->use_dhmmixer && worker->amixer && !worker->audiobin )
2151 worker->audiobin = gst_bin_new("audiobin");
2152 if( worker->audiobin )
2154 gst_bin_add(GST_BIN (worker->audiobin), worker->amixer);
2155 gst_bin_add(GST_BIN (worker->audiobin), worker->asink);
2156 gst_element_link(worker->amixer, worker->asink);
2158 pad = gst_element_get_static_pad (worker->amixer, "sink");
2159 gst_element_add_pad (worker->audiobin, gst_ghost_pad_new ("sink", pad));
2160 gst_object_unref (GST_OBJECT (pad));
2162 gst_object_ref(worker->audiobin);
2164 /* Use Dolby music settings by default */
2165 set_dolby_music_property(worker, worker->config->mobile_surround_music.state);
2166 set_dolby_music_sound_property(worker, worker->config->mobile_surround_music.room, TRUE);
2167 set_dolby_music_sound_property(worker, worker->config->mobile_surround_music.color, FALSE);
2171 gst_object_ref_sink(worker->asink);
2172 gst_object_sink(worker->amixer);
2173 g_warning("Could not create audiobin! Falling back to basic audio-sink!");
2178 if( worker->config->use_dhmmixer && worker->amixer && worker->audiobin )
2180 g_object_set(worker->pipeline,
2181 "audio-sink", worker->audiobin,
2182 "flags", worker->config->flags,
2187 g_object_set(worker->pipeline,
2188 "audio-sink", worker->asink,
2189 "flags", worker->config->flags,
2193 if( worker->pipeline )
2195 mafw_gst_renderer_seeker_set_pipeline(worker->seeker, worker->pipeline);
2197 if( worker->vsink && worker->xid != 0 )
2199 g_object_set(worker->pipeline,
2200 "video-sink", worker->vsink,
2205 if (!worker->tsink) {
2206 worker->tsink = gst_element_factory_make("textoverlay", NULL);
2207 if (!worker->tsink) {
2208 g_critical("Failed to create pipeline text sink");
2210 g_error_new(WORKER_ERROR,
2211 WORKER_ERROR_UNABLE_TO_PERFORM,
2212 "Could not create text sink"));
2213 g_assert_not_reached();
2215 gst_object_ref(worker->tsink);
2217 g_object_set(worker->pipeline, "text-sink", worker->tsink, NULL);
2220 guint check_dolby_audioroute(MafwGstRendererWorker *worker, guint prop) {
2221 if (g_slist_find(worker->destinations,
2222 GINT_TO_POINTER(WORKER_OUTPUT_BLUETOOTH_AUDIO)) ||
2223 g_slist_find(worker->destinations,
2224 GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK)))
2234 void set_dolby_music_property(MafwGstRendererWorker *worker, guint prop) {
2235 worker->config->mobile_surround_music.state = prop;
2236 if (worker->amixer && !worker->media.has_visual_content) {
2238 guint tempprop = check_dolby_audioroute(worker, prop);
2239 if (_set_value(&a, G_TYPE_UINT, &tempprop))
2241 g_object_set_property(G_OBJECT(worker->amixer), "mobile-surround", &a);
2247 void set_dolby_music_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty) {
2248 if (isRoomProperty) {
2249 worker->config->mobile_surround_music.room = prop;
2251 worker->config->mobile_surround_music.color = prop;
2253 if (worker->amixer && !worker->media.has_visual_content) {
2256 if (_set_value(&a, G_TYPE_UINT, &prop))
2258 if (isRoomProperty) {
2259 g_object_set_property(G_OBJECT(worker->amixer), "room-size", &a);
2261 g_object_set_property(G_OBJECT(worker->amixer), "brightness", &a);
2268 void set_dolby_video_property(MafwGstRendererWorker *worker, guint prop) {
2269 worker->config->mobile_surround_video.state = prop;
2270 if (worker->amixer && worker->media.has_visual_content) {
2272 guint tempprop = check_dolby_audioroute(worker, prop);
2273 if (_set_value(&a, G_TYPE_UINT, &tempprop))
2275 g_object_set_property(G_OBJECT(worker->amixer), "mobile-surround", &a);
2281 void set_dolby_video_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty) {
2282 if (isRoomProperty) {
2283 worker->config->mobile_surround_video.room = prop;
2285 worker->config->mobile_surround_video.color = prop;
2287 if (worker->amixer && worker->media.has_visual_content) {
2290 if (_set_value(&a, G_TYPE_UINT, &prop))
2292 if (isRoomProperty) {
2293 g_object_set_property(G_OBJECT(worker->amixer), "room-size", &a);
2295 g_object_set_property(G_OBJECT(worker->amixer), "brightness", &a);
2303 * @seek_type: GstSeekType
2304 * @position: Time in seconds where to seek
2305 * @key_frame_seek: True if this is a key frame based seek
2306 * @error: Possible error that is set and returned
2308 static void _do_seek(MafwGstRendererWorker *worker,
2309 GstSeekType seek_type,
2311 gboolean key_frame_seek,
2316 GstSeekFlags flags = GST_SEEK_FLAG_FLUSH;
2318 g_assert(worker != NULL);
2320 if (!worker->media.seekable == SEEKABILITY_SEEKABLE)
2325 /* According to the docs, relative seeking is not so easy:
2326 GST_SEEK_TYPE_CUR - change relative to currently configured segment.
2327 This can't be used to seek relative to the current playback position -
2328 do a position query, calculate the desired position and then do an
2329 absolute position seek instead if that's what you want to do. */
2330 if (seek_type == GST_SEEK_TYPE_CUR)
2332 gint curpos = mafw_gst_renderer_worker_get_position(worker);
2333 position = curpos + position;
2334 seek_type = GST_SEEK_TYPE_SET;
2341 worker->seek_position = position;
2343 if (worker->state != GST_STATE_PLAYING && worker->state != GST_STATE_PAUSED )
2345 g_debug("_do_seek: Not in playing or paused state, seeking delayed.");
2348 else if( worker->is_live && worker->state == GST_STATE_PAUSED )
2350 g_debug("_do_seek: Live source can be seeked only in playing state, seeking delayed!");
2354 worker->report_statechanges = FALSE;
2356 if (key_frame_seek == TRUE)
2358 /* tries to do key frame seeks at least with some change */
2359 ret = mafw_gst_renderer_seeker_seek_to(worker->seeker, worker->seek_position);
2363 spos = (gint64)position * GST_SECOND;
2364 g_debug("seek: type = %d, offset = %lld", seek_type, spos);
2367 ret = gst_element_seek(worker->pipeline,
2374 GST_CLOCK_TIME_NONE);
2379 /* Seeking is async, so seek_position should not be invalidated here */
2386 WORKER_ERROR_CANNOT_SET_POSITION,
2387 "Seeking to %d failed", position);
2388 worker->report_statechanges = TRUE;
2389 worker->seek_position = -1;
2390 mafw_gst_renderer_seeker_cancel(worker->seeker);
2393 void mafw_gst_renderer_worker_set_current_frame_on_pause(
2394 MafwGstRendererWorker *worker,
2395 gboolean current_frame_on_pause)
2398 worker->current_frame_on_pause = current_frame_on_pause;
2400 _emit_property(worker,
2401 WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE,
2403 &worker->current_frame_on_pause);
2406 gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(
2407 MafwGstRendererWorker *worker)
2409 return worker->current_frame_on_pause;
2412 configuration *mafw_gst_renderer_worker_create_default_configuration(MafwGstRendererWorker *worker)
2415 return _create_default_configuration();
2418 void mafw_gst_renderer_worker_set_configuration(MafwGstRendererWorker *worker,
2419 configuration *config)
2421 if( config == NULL )
2423 g_warning("NULL config was tried to be set!");
2427 if( worker->config )
2429 _free_configuration(worker->config);
2431 worker->config = config;
2433 if( (worker->pipeline == NULL)
2434 || (worker->state == GST_STATE_NULL && gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS) )
2436 _reset_pipeline_and_worker(worker);
2437 _construct_pipeline(worker, worker->config);
2442 * Sets the pipeline PAUSED-to-READY timeout to given value (in seconds). If the
2443 * pipeline is already in PAUSED state and this called with zero value the pipeline
2444 * get immediately set to READY state.
2446 void mafw_gst_renderer_worker_set_ready_timeout(MafwGstRendererWorker *worker,
2451 worker->config->seconds_to_pause_to_ready = seconds;
2453 /* zero is a special case: if we are already in PAUSED state, a pending
2454 * ready timeout has not yet elapsed and we are asked to set the timeout
2455 * value to zero --> remove the pending tmo and go immediately to READY.
2456 * This forces GStreamer to release all pipeline resources and for the
2457 * outsiders it looks like we are still in the PAUSED state. */
2458 if (seconds == 0 && worker->ready_timeout && worker->state == GST_STATE_PAUSED)
2460 _remove_ready_timeout(worker);
2461 _add_ready_timeout(worker);
2466 void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker,
2467 GstSeekType seek_type,
2468 gint position, GError **error)
2470 _do_seek(worker, seek_type, position, TRUE, error);
2471 if (worker->notify_seek_handler)
2472 worker->notify_seek_handler(worker, worker->owner);
2475 static gint64 _get_duration(MafwGstRendererWorker *worker)
2477 gint64 value = DURATION_UNQUERIED;
2478 GstFormat format = GST_FORMAT_TIME;
2480 gboolean right_query = gst_element_query_duration(worker->pipeline, &format, &value);
2483 /* just in case gstreamer messes with the value */
2484 value = DURATION_UNQUERIED;
2490 * Gets current position, rounded down into precision of one second. If a seek
2491 * is pending, returns the position we are going to seek. Returns -1 on
2494 gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker)
2498 g_assert(worker != NULL);
2500 /* If seek is ongoing, return the position where we are seeking. */
2501 if (worker->seek_position != -1)
2503 return worker->seek_position;
2506 /* Otherwise query position from pipeline. */
2507 format = GST_FORMAT_TIME;
2508 if (worker->pipeline &&
2509 gst_element_query_position(worker->pipeline, &format, &time))
2511 return (gint)(time / GST_SECOND);
2513 /* lets return the duration if we're in eos and the pipeline cannot return position */
2514 else if( worker->pipeline && worker->eos )
2516 gint64 duration = _get_duration(worker);
2519 return (gint)(duration / GST_SECOND);
2526 * Returns the duration of the current media in seconds
2528 gint64 mafw_gst_renderer_worker_get_duration(MafwGstRendererWorker *worker)
2530 gint64 duration = _get_duration(worker);
2533 gint64 second_precision = (duration + (GST_SECOND/2)) / GST_SECOND;
2535 if( !_seconds_duration_equal(duration, worker->media.length_nanos) )
2537 worker->media.length_nanos = duration;
2539 if( _current_metadata_add(worker,
2540 WORKER_METADATA_KEY_DURATION,
2542 (const gpointer)&second_precision) )
2544 _emit_metadata(worker,
2545 WORKER_METADATA_KEY_DURATION,
2550 return second_precision;
2558 gint64 mafw_gst_renderer_worker_get_last_known_duration(MafwGstRendererWorker *worker)
2560 if( worker->media.length_nanos <= 0 )
2562 return worker->media.length_nanos;
2566 return (worker->media.length_nanos + (GST_SECOND/2)) / GST_SECOND;
2570 GHashTable *mafw_gst_renderer_worker_get_current_metadata(
2571 MafwGstRendererWorker *worker)
2573 return worker->current_metadata;
2576 void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid)
2578 /* Store the target window id */
2579 g_debug("Setting xid: %x", (guint)xid);
2582 if( !worker->vsink )
2584 g_debug("Creating video-sink as XID has been set, %s", worker->config->vsink);
2585 worker->vsink = gst_element_factory_make(worker->config->vsink, NULL);
2586 if (!worker->vsink) {
2587 worker->vsink = gst_element_factory_make("xvimagesink", NULL);
2589 if (!worker->vsink) {
2590 g_critical("Failed to create pipeline video sink");
2592 g_error_new(WORKER_ERROR,
2593 WORKER_ERROR_UNABLE_TO_PERFORM,
2594 "Could not create video sink"));
2595 g_assert_not_reached();
2597 gst_object_ref_sink(worker->vsink);
2599 //special case for xvimagesink
2601 gchar* element_name = gst_element_get_name(worker->vsink);
2602 g_object_set(G_OBJECT(worker->vsink),
2603 "colorkey", 0x080810,
2605 if (g_str_has_prefix(element_name, "xvimagesink"))
2607 g_object_set(G_OBJECT(worker->vsink),
2608 "handle-events", TRUE,
2609 "force-aspect-ratio", TRUE,
2612 g_free(element_name);
2615 //do not dare to set video-sink in any other state
2616 if( worker->pipeline && worker->state == GST_STATE_NULL )
2618 g_object_set(worker->pipeline,
2619 "video-sink", worker->vsink,
2624 /* We don't want to set XID to video sink here when in READY state, because
2625 it prevents "prepare-xwindow-id" message. Setting it when we are
2626 PAUSED or PLAYING is fine, because we already got "prepare-xwindow-id". */
2627 if(worker->state == GST_STATE_PLAYING ||
2628 worker->state == GST_STATE_PAUSED)
2630 /* Check if we should use it right away */
2631 mafw_gst_renderer_worker_apply_xid(worker);
2634 _emit_property(worker, WORKER_PROPERTY_XID, G_TYPE_UINT, &worker->xid);
2638 XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker)
2643 void mafw_gst_renderer_worker_set_render_rectangle(MafwGstRendererWorker *worker, render_rectangle *rect)
2645 /* Store the target window id */
2646 g_debug("Setting render rectangle: X:%d,Y:%d Width:%d, Height:%d",
2647 rect->x, rect->y, rect->width, rect->height);
2649 worker->x_overlay_rectangle.x = rect->x;
2650 worker->x_overlay_rectangle.y = rect->y;
2651 worker->x_overlay_rectangle.width = rect->width;
2652 worker->x_overlay_rectangle.height = rect->height;
2654 /* Check if we should use it right away */
2655 mafw_gst_renderer_worker_apply_render_rectangle(worker);
2657 GValueArray *rect_array = g_value_array_new(4);
2663 _set_value(&x, G_TYPE_INT, &(rect->x));
2664 _set_value(&y, G_TYPE_INT, &(rect->y));
2665 _set_value(&width, G_TYPE_INT, &(rect->width));
2666 _set_value(&height, G_TYPE_INT, &(rect->height));
2668 g_value_array_insert(rect_array, 0, &x );
2669 g_value_array_insert(rect_array, 1, &y );
2670 g_value_array_insert(rect_array, 2, &width );
2671 g_value_array_insert(rect_array, 3, &height );
2674 memset(&value, 0, sizeof(value));
2675 g_value_init(&value, G_TYPE_VALUE_ARRAY);
2676 g_value_take_boxed(&value, rect_array);
2678 _emit_property(worker, WORKER_PROPERTY_RENDER_RECTANGLE, G_TYPE_VALUE_ARRAY, &value);
2680 g_value_unset(&value);
2683 const render_rectangle* mafw_gst_renderer_worker_get_render_rectangle(MafwGstRendererWorker *worker)
2686 return &worker->x_overlay_rectangle;
2689 gboolean mafw_gst_renderer_worker_get_autopaint(MafwGstRendererWorker *worker)
2691 return worker->autopaint;
2694 void mafw_gst_renderer_worker_set_autopaint(MafwGstRendererWorker *worker,
2697 /* TODO Is this a bug or a feature? */
2698 worker->autopaint = autopaint;
2700 g_object_set(worker->vsink, "autopaint-colorkey", autopaint, NULL);
2702 _emit_property(worker,
2703 WORKER_PROPERTY_AUTOPAINT,
2709 gboolean mafw_gst_renderer_worker_set_playback_speed(MafwGstRendererWorker* worker,
2712 gboolean retVal = FALSE;
2714 if (worker->state == GST_STATE_PLAYING)
2716 worker->playback_speed = speed;
2718 gint64 current_position;
2719 GstFormat format = GST_FORMAT_TIME;
2721 if (worker->pipeline && gst_element_query_position(worker->pipeline,
2726 retVal = gst_element_seek(worker->pipeline,
2729 GST_SEEK_FLAG_SKIP | GST_SEEK_FLAG_KEY_UNIT,
2733 GST_CLOCK_TIME_NONE);
2737 _emit_property(worker,
2738 WORKER_PROPERTY_PLAYBACK_SPEED,
2748 gfloat mafw_gst_renderer_worker_get_playback_speed(MafwGstRendererWorker* worker)
2750 return worker->playback_speed;
2753 void mafw_gst_renderer_worker_set_force_aspect_ratio(MafwGstRendererWorker *worker, gboolean force)
2756 worker->force_aspect_ratio = force;
2759 g_object_set(worker->vsink, "force-aspect-ratio", force, NULL);
2761 _emit_property(worker, WORKER_PROPERTY_FORCE_ASPECT_RATIO, G_TYPE_BOOLEAN, &force);
2765 gboolean mafw_gst_renderer_worker_get_force_aspect_ratio(MafwGstRendererWorker *worker)
2767 return worker->force_aspect_ratio;
2770 gint mafw_gst_renderer_worker_get_colorkey(MafwGstRendererWorker *worker)
2772 return worker->colorkey;
2775 gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker)
2777 return worker->media.seekable == SEEKABILITY_SEEKABLE;
2780 gboolean mafw_gst_renderer_worker_get_streaming(MafwGstRendererWorker *worker)
2782 return uri_is_stream(worker->media.location);
2785 const char* mafw_gst_renderer_worker_get_uri(MafwGstRendererWorker *worker)
2787 return worker->media.location;
2790 static void _do_play(MafwGstRendererWorker *worker)
2792 g_assert(worker != NULL);
2794 if (worker->pipeline == NULL) {
2795 g_debug("play without a pipeline!");
2798 worker->report_statechanges = TRUE;
2800 /* If we have to stay paused, we do and add the ready timeout. Otherwise, we
2801 * move the pipeline */
2802 if (!worker->stay_paused) {
2803 /* If pipeline is READY, we move it to PAUSED, otherwise, to PLAYING */
2804 if (worker->state == GST_STATE_READY) {
2805 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
2806 g_debug("setting pipeline to PAUSED");
2808 gst_element_set_state(worker->pipeline, GST_STATE_PLAYING);
2809 g_debug("setting pipeline to PLAYING");
2813 g_debug("staying in PAUSED state");
2814 _add_ready_timeout(worker);
2818 void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker,
2823 mafw_gst_renderer_worker_stop(worker);
2824 _reset_media_info(worker);
2826 /* Set the item to be played */
2827 worker->media.location = g_strdup(uri);
2829 _start_play(worker);
2833 * Currently, stop destroys the Gst pipeline and resets the worker into
2834 * default startup configuration.
2836 void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker)
2838 g_debug("worker stop");
2839 g_assert(worker != NULL);
2841 /* If location is NULL, this is a pre-created pipeline */
2842 if (worker->async_bus_id && worker->pipeline && !worker->media.location)
2845 _reset_pipeline_and_worker(worker);
2847 /* context framework adaptation starts */
2848 if (worker->context_nowplaying) {
2849 context_provider_map_free(worker->context_nowplaying);
2850 worker->context_nowplaying = NULL;
2852 context_provider_set_null(CONTEXT_PROVIDER_KEY_NOWPLAYING);
2853 /* context framework adaptation ends */
2855 /* We are not playing, so we can let the screen blank */
2856 if (worker->blanking__control_handler)
2858 worker->blanking__control_handler(worker, worker->owner, FALSE);
2861 /* And now get a fresh pipeline ready */
2862 _construct_pipeline(worker, worker->config);
2865 void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker)
2867 g_assert(worker != NULL);
2869 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2870 !worker->prerolling)
2872 /* If we are buffering and get a pause, we have to
2873 * signal state change and stay_paused */
2874 g_debug("Pausing while buffering, signalling state change");
2876 /* We need to make sure that we go into real PAUSE state */
2877 if (worker->blanking__control_handler)
2879 worker->blanking__control_handler(worker, worker->owner, FALSE);
2881 _do_pause_postprocessing(worker);
2885 worker->report_statechanges = TRUE;
2886 if (worker->seek_position == -1 && worker->state == GST_STATE_PLAYING )
2888 gst_element_set_state(worker->pipeline, GST_STATE_PAUSED);
2889 if (worker->blanking__control_handler)
2891 worker->blanking__control_handler(worker, worker->owner, FALSE);
2896 worker->stay_paused = TRUE;
2897 worker->pause_frame_taken = FALSE;
2901 * Notifier to call when audio/video routing changes
2903 void mafw_gst_renderer_worker_notify_media_destination(
2904 MafwGstRendererWorker *worker,
2905 GSList *destinations)
2907 g_assert(worker != NULL);
2908 g_assert(destinations != NULL);
2910 /* 1. update our records of current destinations */
2911 g_slist_free(worker->destinations);
2912 worker->destinations = g_slist_copy(destinations);
2914 /* 2. prevent blanking if we are playing video and outputting it on our own
2915 * display, otherwise disable it */
2916 if (worker->blanking__control_handler &&
2917 worker->media.has_visual_content &&
2918 worker->state == GST_STATE_PLAYING &&
2919 g_slist_find(worker->destinations,
2920 GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY)))
2922 worker->blanking__control_handler(worker, worker->owner, TRUE);
2926 worker->blanking__control_handler(worker, worker->owner, FALSE);
2929 /* 3. disabling Dolby Headphone effect if not outputting to audio jack or
2930 * bluetooth headphones, otherwise using the effect. Actual route check is done
2931 * in set_dolby_*****_property function*/
2932 set_dolby_music_property(worker, worker->config->mobile_surround_music.state);
2933 set_dolby_video_property(worker, worker->config->mobile_surround_video.state);
2937 void mafw_gst_renderer_worker_pause_at(MafwGstRendererWorker *worker, guint position)
2939 /* the current implementation works only from ready i.e. stopped state */
2940 g_assert( worker != NULL);
2941 worker->stay_paused = TRUE;
2942 worker->pause_frame_taken = FALSE;
2943 worker->seek_position = position;
2947 g_object_set(worker->vsink, "show-preroll-frame",
2952 void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker)
2954 worker->stay_paused = FALSE;
2955 if (worker->buffering && worker->state == GST_STATE_PAUSED &&
2956 !worker->prerolling) {
2957 /* If we are buffering we cannot resume, but we know that the pipeline
2958 * will be moved to PLAYING as stay_paused is FALSE, so we just
2959 * activate the state change report, this way as soon as buffering
2960 * is finished the pipeline will be set to PLAYING and the state
2961 * change will be reported */
2962 worker->report_statechanges = TRUE;
2963 g_debug("Resumed while buffering, activating pipeline state changes");
2964 /* Notice though that we can receive the Resume before we get any
2965 buffering information. In that case we go with the "else" branch
2966 and set the pipeline to to PLAYING. However, it is possible that
2967 in this case we get the fist buffering signal before the PAUSED ->
2968 PLAYING state change. In that case, since we ignore state changes
2969 while buffering we never signal the state change to PLAYING. We
2970 can only fix this by checking, when we receive a PAUSED -> PLAYING
2971 transition if we are buffering, and in that case signal the state
2972 change (if we get that transition while buffering is on, it can
2973 only mean that the client resumed playback while buffering, and
2974 we must notify the state change) */
2979 /* we want to resume no use for these timers anymore */
2980 _remove_pause_frame_timeout(worker);
2981 _remove_ready_timeout(worker);
2984 MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner)
2986 MafwGstRendererWorker *worker;
2988 g_debug("%s", G_STRFUNC);
2990 worker = g_new0(MafwGstRendererWorker, 1);
2991 worker->owner = owner;
2992 worker->report_statechanges = TRUE;
2993 worker->state = GST_STATE_NULL;
2994 worker->seek_position = -1;
2995 worker->ready_timeout = 0;
2996 worker->pause_frame_timeout = 0;
2997 worker->duration_seek_timeout = 0;
2998 worker->duration_seek_timeout_loop_count = 0;
2999 worker->in_ready = FALSE;
3001 worker->x_overlay_rectangle.x = -1;
3002 worker->x_overlay_rectangle.y = -1;
3003 worker->x_overlay_rectangle.width = -1;
3004 worker->x_overlay_rectangle.height = -1;
3005 worker->autopaint = TRUE;
3006 worker->playback_speed = 1;
3007 worker->colorkey = -1;
3008 worker->vsink = NULL;
3009 worker->asink = NULL;
3010 worker->tsink = NULL;
3011 worker->amixer = NULL;
3012 worker->audiobin = NULL;
3013 worker->tag_list = NULL;
3014 worker->current_metadata = NULL;
3015 worker->media.seekable = SEEKABILITY_SEEKABLE;
3017 worker->destinations = NULL;
3019 worker->current_frame_on_pause = FALSE;
3020 worker->taking_screenshot = FALSE;
3021 worker->force_aspect_ratio = TRUE;
3022 _init_tmp_files_pool(worker);
3023 worker->notify_seek_handler = NULL;
3024 worker->notify_pause_handler = NULL;
3025 worker->notify_play_handler = NULL;
3026 worker->notify_buffer_status_handler = NULL;
3027 worker->notify_eos_handler = NULL;
3028 worker->notify_metadata_handler = NULL;
3029 worker->notify_error_handler = NULL;
3030 worker->blanking__control_handler = NULL;
3031 worker->screenshot_handler = NULL;
3033 worker->config = _create_default_configuration();
3035 worker->seeker = mafw_gst_renderer_seeker_new();
3037 if (!_context_fw_initialised)
3039 /* context framework adaptation starts */
3040 if (context_provider_init(DBUS_BUS_SESSION, CONTEXT_PROVIDER_BUS_NAME)) {
3041 _context_fw_initialised = TRUE;
3042 context_provider_install_key(CONTEXT_PROVIDER_KEY_NOWPLAYING, FALSE,
3044 g_debug("Initialized context framework provider");
3047 g_warning("Could not initialize context framework provider");
3050 /* context framework adaptation ends */
3056 void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker)
3058 _destroy_tmp_files_pool(worker);
3059 _reset_pipeline_and_worker(worker);
3061 /* We are not playing, so we can let the screen blank */
3062 if (worker->blanking__control_handler)
3064 worker->blanking__control_handler(worker, worker->owner, FALSE);
3067 /* now finally sinks/bins are released */
3068 if( worker->audiobin )
3070 gst_object_unref(worker->audiobin);
3071 worker->audiobin = NULL;
3073 else if( worker->asink )
3075 gst_object_unref(worker->asink);
3076 worker->asink = NULL;
3081 gst_object_unref(worker->vsink);
3082 worker->vsink = NULL;
3085 context_provider_stop();
3086 _context_fw_initialised = FALSE;
3088 if( worker->destinations )
3090 g_slist_free(worker->destinations);
3091 worker->destinations = NULL;
3094 if( worker->config )
3096 _free_configuration(worker->config);
3097 worker->config = NULL;
3100 if( worker->seeker )
3102 mafw_gst_renderer_seeker_free(worker->seeker);
3103 worker->seeker = NULL;