diff --git a/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json index efddbb81f4..c3d8b03ad1 100644 --- a/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json @@ -10364,6 +10364,18 @@ "type": "gboolean", "writable": true }, + "instant-uri": { + "blurb": "When enabled, URI changes are applied immediately", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, "mute": { "blurb": "Mute the audio channel without changing the volume", "conditionally-available": false, @@ -11415,6 +11427,18 @@ "type": "gboolean", "writable": true }, + "instant-uri": { + "blurb": "When enabled, URI changes are applied immediately", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, "ring-buffer-max-size": { "blurb": "Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled)", "conditionally-available": false, diff --git a/subprojects/gst-plugins-base/gst/playback/gstplaybin3.c b/subprojects/gst-plugins-base/gst/playback/gstplaybin3.c index 5618d04057..14b160e318 100644 --- a/subprojects/gst-plugins-base/gst/playback/gstplaybin3.c +++ b/subprojects/gst-plugins-base/gst/playback/gstplaybin3.c @@ -249,7 +249,6 @@ GST_DEBUG_CATEGORY_STATIC (gst_play_bin3_debug); typedef struct _GstPlayBin3 GstPlayBin3; typedef struct _GstPlayBin3Class GstPlayBin3Class; -typedef struct _GstSourceGroup GstSourceGroup; typedef struct _GstSourceCombine GstSourceCombine; typedef struct _SourcePad SourcePad; @@ -272,14 +271,8 @@ struct _GstSourceCombine GPtrArray *streams; /* Sorted array of GstStream for the given type */ gboolean has_active_pad; /* stream combiner has the "active-pad" property */ - - gboolean is_concat; /* The stream combiner is the 'concat' element */ }; -#define GST_SOURCE_GROUP_GET_LOCK(group) (&((GstSourceGroup*)(group))->lock) -#define GST_SOURCE_GROUP_LOCK(group) (g_mutex_lock (GST_SOURCE_GROUP_GET_LOCK(group))) -#define GST_SOURCE_GROUP_UNLOCK(group) (g_mutex_unlock (GST_SOURCE_GROUP_GET_LOCK(group))) - enum { PLAYBIN_STREAM_AUDIO = 0, @@ -309,94 +302,10 @@ struct _SourcePad gulong event_probe_id; }; -/* a structure to hold the objects for decoding a uri and the subtitle uri - */ -struct _GstSourceGroup -{ - GstPlayBin3 *playbin; - - GMutex lock; - - gboolean valid; /* the group has valid info to start playback */ - gboolean active; /* the group is active */ - - gboolean playing; /* the group is currently playing - * (outputted on the sinks) */ - - /* properties */ - gchar *uri; - gchar *suburi; - - /* The currently outputted group_id */ - guint group_id; - - /* Bit-wise set of stream types we have requested from uridecodebin3 */ - GstStreamType selected_stream_types; - - /* Bit-wise set of stream types for which pads are present */ - GstStreamType present_stream_types; - - /* TRUE if a 'about-to-finish' needs to be posted once we have - * got source pads for all requested stream types - * - * FIXME : Move this logic to uridecodebin3 later */ - gboolean pending_about_to_finish; - - /* uridecodebin to handle uri and suburi */ - GstElement *uridecodebin; - - /* Active sinks for each media type. These are initialized with - * the configured or currently used sink, otherwise - * left as NULL and playbin tries to automatically - * select a good sink */ - GstElement *audio_sink; - GstElement *video_sink; - GstElement *text_sink; - - /* List of source pads */ - GList *source_pads; - - /* uridecodebin signals */ - gulong pad_added_id; - gulong pad_removed_id; - gulong select_stream_id; - gulong source_setup_id; - gulong about_to_finish_id; - - gboolean stream_changed_pending; - - /* Active stream collection */ - GstStreamCollection *collection; - - - /* buffering message stored for after switching */ - GstMessage *pending_buffering_msg; -}; - #define GST_PLAY_BIN3_GET_LOCK(bin) (&((GstPlayBin3*)(bin))->lock) #define GST_PLAY_BIN3_LOCK(bin) (g_rec_mutex_lock (GST_PLAY_BIN3_GET_LOCK(bin))) #define GST_PLAY_BIN3_UNLOCK(bin) (g_rec_mutex_unlock (GST_PLAY_BIN3_GET_LOCK(bin))) -/* lock to protect dynamic callbacks, like no-more-pads */ -#define GST_PLAY_BIN3_DYN_LOCK(bin) g_mutex_lock (&(bin)->dyn_lock) -#define GST_PLAY_BIN3_DYN_UNLOCK(bin) g_mutex_unlock (&(bin)->dyn_lock) - -/* lock for shutdown */ -#define GST_PLAY_BIN3_SHUTDOWN_LOCK(bin,label) \ - G_STMT_START { \ - if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) \ - goto label; \ - GST_PLAY_BIN3_DYN_LOCK (bin); \ - if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) { \ - GST_PLAY_BIN3_DYN_UNLOCK (bin); \ - goto label; \ - } \ - } G_STMT_END - -/* unlock for shutdown */ -#define GST_PLAY_BIN3_SHUTDOWN_UNLOCK(bin) \ - GST_PLAY_BIN3_DYN_UNLOCK (bin); \ - /** * GstPlayBin3: * @@ -406,19 +315,21 @@ struct _GstPlayBin3 { GstPipeline parent; - GRecMutex lock; /* to protect group switching */ + GRecMutex lock; /* to protect various properties */ - /* the input groups, we use a double buffer to switch between current and next */ - GstSourceGroup groups[2]; /* array with group info */ - GstSourceGroup *curr_group; /* pointer to the currently playing group */ - GstSourceGroup *next_group; /* pointer to the next group */ + /* uridecodebin to handle uri and suburi */ + GstElement *uridecodebin; + + /* List of SourcePad structures */ + GList *source_pads; + + /* Active stream collection */ + GstStreamCollection *collection; /* combiners for different streams */ GstSourceCombine combiner[PLAYBIN_STREAM_LAST]; - /* Bit-wise set of stream types we have requested from uridecodebin3. - * Calculated as the combination of the 'selected_stream_types' of - * each sourcegroup */ + /* Bit-wise set of stream types we have requested from uridecodebin3. */ GstStreamType selected_stream_types; /* Bit-wise set of configured output stream types (i.e. active @@ -426,7 +337,6 @@ struct _GstPlayBin3 GstStreamType active_stream_types; /* properties */ - guint64 connection_speed; /* connection speed in bits/sec (0 = unknown) */ gint current_video; /* the currently selected stream */ gint current_audio; /* the currently selected stream */ gint current_text; /* the currently selected stream */ @@ -434,8 +344,7 @@ struct _GstPlayBin3 gboolean do_stream_selections; /* Set to TRUE when any of current-{video|audio|text} are set to say playbin should do backwards-compatibility behaviours */ - guint64 buffer_duration; /* When buffering, the max buffer duration (ns) */ - guint buffer_size; /* When buffering, the max buffer size (bytes) */ + GstObject *collection_source; /* The element that provided the latest stream collection */ gboolean force_aspect_ratio; /* Multiview/stereoscopic overrides */ @@ -445,14 +354,6 @@ struct _GstPlayBin3 /* our play sink */ GstPlaySink *playsink; - /* Task for (de)activating groups, protected by the activation lock */ - GstTask *activation_task; - GRecMutex activation_lock; - - /* lock protecting dynamic adding/removing */ - GMutex dyn_lock; - /* if we are shutting down or not */ - gint shutdown; gboolean async_pending; /* async-start has been emitted */ gboolean have_selector; /* set to FALSE when we fail to create an @@ -477,11 +378,7 @@ struct _GstPlayBin3 GstElement *video_stream_combiner; /* configured video stream combiner, or NULL */ GstElement *text_stream_combiner; /* configured text stream combiner, or NULL */ - guint64 ring_buffer_max_size; /* 0 means disabled */ - - gboolean is_live; /* Whether our current group is live */ - - GMutex buffering_post_lock; /* Protect serialisation of buffering messages. Must not acquire this while holding any SOURCE_GROUP lock */ + gboolean is_live; /* Whether we are live */ }; struct _GstPlayBin3Class @@ -549,7 +446,8 @@ enum PROP_AUDIO_FILTER, PROP_VIDEO_FILTER, PROP_MULTIVIEW_MODE, - PROP_MULTIVIEW_FLAGS + PROP_MULTIVIEW_FLAGS, + PROP_INSTANT_URI }; /* signals */ @@ -581,19 +479,22 @@ static gboolean gst_play_bin3_send_event (GstElement * element, static GstSample *gst_play_bin3_convert_sample (GstPlayBin3 * playbin, GstCaps * caps); -static GstStateChangeReturn setup_next_source (GstPlayBin3 * playbin); - -static void gst_play_bin3_check_group_status (GstPlayBin3 * playbin); -static void emit_about_to_finish (GstPlayBin3 * playbin); static void reconfigure_output (GstPlayBin3 * playbin); -static void pad_removed_cb (GstElement * decodebin, GstPad * pad, - GstSourceGroup * group); +/* uridecodebin3 signal callbacks */ +static void pad_added_cb (GstElement * uridecodebin, GstPad * pad, + GstPlayBin3 * playbin); +static void pad_removed_cb (GstElement * decodebin, GstPad * pad, + GstPlayBin3 * playbin); static gint select_stream_cb (GstElement * decodebin, GstStreamCollection * collection, GstStream * stream, - GstSourceGroup * group); + GstPlayBin3 * playbin); +static void source_setup_cb (GstElement * element, GstElement * source, + GstPlayBin3 * playbin); +static void about_to_finish_cb (GstElement * uridecodebin, + GstPlayBin3 * playbin); -static void do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group); +static void do_stream_selection (GstPlayBin3 * playbin); static GstElementClass *parent_class; @@ -839,6 +740,7 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass) "Buffer size when buffering network streams", -1, G_MAXINT, DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_klass, PROP_BUFFER_DURATION, g_param_spec_int64 ("buffer-duration", "Buffer duration (ns)", "Buffer duration when buffering network streams", @@ -927,6 +829,19 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass) GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstPlayBin3:instant-uri: + * + * Changes to uri are applied immediately, instead of on EOS or when the + * element is set back to PLAYING. + * + * Since: 1.22 + */ + g_object_class_install_property (gobject_klass, PROP_INSTANT_URI, + g_param_spec_boolean ("instant-uri", "Instantaneous URI change", + "When enabled, URI changes are applied immediately", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** * GstPlayBin3::about-to-finish * @playbin: a #GstPlayBin3 @@ -1026,6 +941,8 @@ do_async_start (GstPlayBin3 * playbin) playbin->async_pending = TRUE; + GST_DEBUG_OBJECT (playbin, "posting ASYNC_START"); + message = gst_message_new_async_start (GST_OBJECT_CAST (playbin)); GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (playbin), message); @@ -1072,10 +989,7 @@ init_combiners (GstPlayBin3 * playbin) g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); } -/* Update the combiner information to be in sync with the current collection - * - * FIXME : "current" collection doesn't mean anything until we have a "combined" - * collection of all groups */ +/* Update the combiner information to be in sync with the current collection */ static void update_combiner_info (GstPlayBin3 * playbin, GstStreamCollection * collection) { @@ -1122,57 +1036,6 @@ update_combiner_info (GstPlayBin3 * playbin, GstStreamCollection * collection) playbin->combiner[PLAYBIN_STREAM_TEXT].streams->len); } -#ifndef GST_DISABLE_GST_DEBUG -#define debug_groups(playbin) G_STMT_START { \ - guint i; \ - \ - for (i = 0; i < 2; i++) { \ - GstSourceGroup *group = &playbin->groups[i]; \ - \ - GST_DEBUG ("GstSourceGroup #%d (%s) : %s", i, (group == playbin->curr_group) ? "current" : (group == playbin->next_group) ? "next" : "unused", \ - group->uridecodebin ? GST_ELEMENT_NAME (group->uridecodebin) : "NULL" ); \ - GST_DEBUG (" valid:%d , active:%d , playing:%d", group->valid, group->active, group->playing); \ - GST_DEBUG (" uri:%s", group->uri); \ - GST_DEBUG (" suburi:%s", group->suburi); \ - GST_DEBUG (" group_id:%d", group->group_id); \ - GST_DEBUG (" pending_about_to_finish:%d", group->pending_about_to_finish); \ - } \ - } G_STMT_END -#else -#define debug_groups(p) {} -#endif - -static void -init_group (GstPlayBin3 * playbin, GstSourceGroup * group) -{ - g_mutex_init (&group->lock); - - group->stream_changed_pending = FALSE; - group->group_id = GST_GROUP_ID_INVALID; - - group->playbin = playbin; -} - -static void -free_group (GstPlayBin3 * playbin, GstSourceGroup * group) -{ - g_free (group->uri); - g_free (group->suburi); - - g_mutex_clear (&group->lock); - group->stream_changed_pending = FALSE; - - if (group->pending_buffering_msg) - gst_message_unref (group->pending_buffering_msg); - group->pending_buffering_msg = NULL; - - gst_object_replace ((GstObject **) & group->collection, NULL); - - gst_object_replace ((GstObject **) & group->audio_sink, NULL); - gst_object_replace ((GstObject **) & group->video_sink, NULL); - gst_object_replace ((GstObject **) & group->text_sink, NULL); -} - static void notify_volume_cb (GObject * combiner, GParamSpec * pspec, GstPlayBin3 * playbin) { @@ -1196,22 +1059,30 @@ static void gst_play_bin3_init (GstPlayBin3 * playbin) { g_rec_mutex_init (&playbin->lock); - g_mutex_init (&playbin->dyn_lock); - - g_mutex_init (&playbin->buffering_post_lock); /* assume we can create an input-selector */ playbin->have_selector = TRUE; init_combiners (playbin); - /* init groups */ - playbin->curr_group = &playbin->groups[0]; - playbin->next_group = &playbin->groups[1]; - init_group (playbin, &playbin->groups[0]); - init_group (playbin, &playbin->groups[1]); + /* Create uridecodebin3 */ + playbin->uridecodebin = + gst_element_factory_make ("uridecodebin3", "uridecodebin3"); + /* Set default property (based on default flags value) */ + g_object_set (playbin->uridecodebin, "use-buffering", TRUE, NULL); + gst_bin_add (GST_BIN_CAST (playbin), + GST_ELEMENT_CAST (playbin->uridecodebin)); - g_rec_mutex_init (&playbin->activation_lock); + g_signal_connect (playbin->uridecodebin, "pad-added", + G_CALLBACK (pad_added_cb), playbin); + g_signal_connect (playbin->uridecodebin, "pad-removed", + G_CALLBACK (pad_removed_cb), playbin); + g_signal_connect (playbin->uridecodebin, "select-stream", + G_CALLBACK (select_stream_cb), playbin); + g_signal_connect (playbin->uridecodebin, "source-setup", + G_CALLBACK (source_setup_cb), playbin); + g_signal_connect (playbin->uridecodebin, "about-to-finish", + G_CALLBACK (about_to_finish_cb), playbin); /* add sink */ playbin->playsink = @@ -1231,10 +1102,6 @@ gst_play_bin3_init (GstPlayBin3 * playbin) playbin->current_audio = DEFAULT_CURRENT_AUDIO; playbin->current_text = DEFAULT_CURRENT_TEXT; - playbin->buffer_duration = DEFAULT_BUFFER_DURATION; - playbin->buffer_size = DEFAULT_BUFFER_SIZE; - playbin->ring_buffer_max_size = DEFAULT_RING_BUFFER_MAX_SIZE; - playbin->force_aspect_ratio = TRUE; playbin->multiview_mode = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE; @@ -1250,9 +1117,6 @@ gst_play_bin3_finalize (GObject * object) playbin = GST_PLAY_BIN3 (object); - free_group (playbin, &playbin->groups[0]); - free_group (playbin, &playbin->groups[1]); - /* Setting states to NULL is safe here because playsink * will already be gone and none of these sinks will be * a child of playsink @@ -1290,12 +1154,8 @@ gst_play_bin3_finalize (GObject * object) g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].streams, TRUE); g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].inputpads, TRUE); - g_rec_mutex_clear (&playbin->activation_lock); g_rec_mutex_clear (&playbin->lock); - g_mutex_clear (&playbin->buffering_post_lock); - g_mutex_clear (&playbin->dyn_lock); - G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -1330,9 +1190,12 @@ invalid: static void gst_play_bin3_set_uri (GstPlayBin3 * playbin, const gchar * uri) { - GstSourceGroup *group; + if (uri == NULL) { + g_warning ("cannot set NULL uri"); + return; + } - if (uri && !gst_playbin_uri_is_valid (playbin, uri)) { + if (!gst_playbin_uri_is_valid (playbin, uri)) { if (g_str_has_prefix (uri, "file:")) { GST_WARNING_OBJECT (playbin, "not entirely correct file URI '%s' - make " "sure to escape spaces and non-ASCII characters properly and specify " @@ -1343,41 +1206,17 @@ gst_play_bin3_set_uri (GstPlayBin3 * playbin, const gchar * uri) } } - GST_PLAY_BIN3_LOCK (playbin); - group = playbin->next_group; - - GST_SOURCE_GROUP_LOCK (group); - /* store the uri in the next group we will play */ - g_free (group->uri); - if (uri) { - group->uri = g_strdup (uri); - group->valid = TRUE; - } else { - group->uri = NULL; - group->valid = FALSE; - } - GST_SOURCE_GROUP_UNLOCK (group); + g_object_set (playbin->uridecodebin, "uri", uri, NULL); GST_DEBUG ("set new uri to %s", GST_STR_NULL (uri)); - GST_PLAY_BIN3_UNLOCK (playbin); } static void gst_play_bin3_set_suburi (GstPlayBin3 * playbin, const gchar * suburi) { - GstSourceGroup *group; - - GST_PLAY_BIN3_LOCK (playbin); - group = playbin->next_group; - - GST_SOURCE_GROUP_LOCK (group); - g_free (group->suburi); - group->suburi = g_strdup (suburi); - GST_SOURCE_GROUP_UNLOCK (group); + g_object_set (playbin->uridecodebin, "suburi", suburi, NULL); GST_DEBUG ("setting new .sub uri to %s", suburi); - - GST_PLAY_BIN3_UNLOCK (playbin); } static void @@ -1390,32 +1229,12 @@ gst_play_bin3_set_flags (GstPlayBin3 * playbin, GstPlayFlags flags) gst_play_sink_set_flags (playbin->playsink, flags); gst_play_sink_reconfigure (playbin->playsink); } + g_object_set (playbin->uridecodebin, "download", + ((flags & GST_PLAY_FLAG_DOWNLOAD) != 0), + /* configure buffering of demuxed/parsed data */ + "use-buffering", ((flags & GST_PLAY_FLAG_BUFFERING) != 0), NULL); } -static GstPlayFlags -gst_play_bin3_get_flags (GstPlayBin3 * playbin) -{ - GstPlayFlags flags; - - flags = gst_play_sink_get_flags (playbin->playsink); - - return flags; -} - -/* get the currently playing group or if nothing is playing, the next - * group. Must be called with the PLAY_BIN_LOCK. */ -static GstSourceGroup * -get_group (GstPlayBin3 * playbin) -{ - GstSourceGroup *result; - - if (!(result = playbin->curr_group)) - result = playbin->next_group; - - return result; -} - - static GstSample * gst_play_bin3_convert_sample (GstPlayBin3 * playbin, GstCaps * caps) { @@ -1463,10 +1282,10 @@ gst_play_bin3_set_current_stream (GstPlayBin3 * playbin, GST_DEBUG_OBJECT (playbin, "Changing current %s stream %d -> %d", stream_type_names[stream_type], *current_value, stream); - if (combine->combiner == NULL || combine->is_concat) { + if (combine->combiner == NULL) { /* FIXME: Check that the current_value is within range */ *current_value = stream; - do_stream_selection (playbin, playbin->curr_group); + do_stream_selection (playbin); GST_PLAY_BIN3_UNLOCK (playbin); return TRUE; } @@ -1618,14 +1437,6 @@ gst_play_bin3_set_property (GObject * object, guint prop_id, break; case PROP_FLAGS: gst_play_bin3_set_flags (playbin, g_value_get_flags (value)); - if (playbin->curr_group) { - GST_SOURCE_GROUP_LOCK (playbin->curr_group); - if (playbin->curr_group->uridecodebin) { - g_object_set (playbin->curr_group->uridecodebin, "download", - (g_value_get_flags (value) & GST_PLAY_FLAG_DOWNLOAD) != 0, NULL); - } - GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); - } break; case PROP_SUBTITLE_ENCODING: gst_play_bin3_set_encoding (playbin, g_value_get_string (value)); @@ -1678,14 +1489,17 @@ gst_play_bin3_set_property (GObject * object, guint prop_id, break; case PROP_CONNECTION_SPEED: GST_PLAY_BIN3_LOCK (playbin); - playbin->connection_speed = g_value_get_uint64 (value) * 1000; + g_object_set_property ((GObject *) playbin->uridecodebin, + "connection-speed", value); GST_PLAY_BIN3_UNLOCK (playbin); break; case PROP_BUFFER_SIZE: - playbin->buffer_size = g_value_get_int (value); + g_object_set_property ((GObject *) playbin->uridecodebin, "buffer-size", + value); break; case PROP_BUFFER_DURATION: - playbin->buffer_duration = g_value_get_int64 (value); + g_object_set_property ((GObject *) playbin->uridecodebin, + "buffer-duration", value); break; case PROP_AV_OFFSET: gst_play_sink_set_av_offset (playbin->playsink, @@ -1696,15 +1510,8 @@ gst_play_bin3_set_property (GObject * object, guint prop_id, g_value_get_int64 (value)); break; case PROP_RING_BUFFER_MAX_SIZE: - playbin->ring_buffer_max_size = g_value_get_uint64 (value); - if (playbin->curr_group) { - GST_SOURCE_GROUP_LOCK (playbin->curr_group); - if (playbin->curr_group->uridecodebin) { - g_object_set (playbin->curr_group->uridecodebin, - "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL); - } - GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); - } + g_object_set_property ((GObject *) playbin->uridecodebin, + "ring-buffer-max-size", value); break; case PROP_FORCE_ASPECT_RATIO: g_object_set (playbin->playsink, "force-aspect-ratio", @@ -1720,6 +1527,10 @@ gst_play_bin3_set_property (GObject * object, guint prop_id, playbin->multiview_flags = g_value_get_flags (value); GST_PLAY_BIN3_UNLOCK (playbin); break; + case PROP_INSTANT_URI: + g_object_set_property ((GObject *) playbin->uridecodebin, + "instant-uri", value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1746,25 +1557,6 @@ gst_play_bin3_get_current_sink (GstPlayBin3 * playbin, GstElement ** elem, return sink; } -static GstElement * -gst_play_bin3_get_current_stream_combiner (GstPlayBin3 * playbin, - GstElement ** elem, const gchar * dbg, int stream_type) -{ - GstElement *combiner; - - GST_PLAY_BIN3_LOCK (playbin); - /* The special concat element should never be returned */ - if (playbin->combiner[stream_type].is_concat) - combiner = NULL; - else if ((combiner = playbin->combiner[stream_type].combiner)) - gst_object_ref (combiner); - else if ((combiner = *elem)) - gst_object_ref (combiner); - GST_PLAY_BIN3_UNLOCK (playbin); - - return combiner; -} - static void gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) @@ -1774,46 +1566,37 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, switch (prop_id) { case PROP_URI: { - GstSourceGroup *group; - GST_PLAY_BIN3_LOCK (playbin); - group = playbin->next_group; - g_value_set_string (value, group->uri); + g_object_get_property ((GObject *) playbin->uridecodebin, "uri", value); GST_PLAY_BIN3_UNLOCK (playbin); break; } case PROP_CURRENT_URI: { - GstSourceGroup *group; - GST_PLAY_BIN3_LOCK (playbin); - group = get_group (playbin); - g_value_set_string (value, group->uri); + g_object_get_property ((GObject *) playbin->uridecodebin, "current-uri", + value); GST_PLAY_BIN3_UNLOCK (playbin); break; } case PROP_SUBURI: { - GstSourceGroup *group; - GST_PLAY_BIN3_LOCK (playbin); - group = playbin->next_group; - g_value_set_string (value, group->suburi); + g_object_get_property ((GObject *) playbin->uridecodebin, "suburi", + value); GST_PLAY_BIN3_UNLOCK (playbin); break; } case PROP_CURRENT_SUBURI: { - GstSourceGroup *group; - GST_PLAY_BIN3_LOCK (playbin); - group = get_group (playbin); - g_value_set_string (value, group->suburi); + g_object_get_property ((GObject *) playbin->uridecodebin, + "current-suburi", value); GST_PLAY_BIN3_UNLOCK (playbin); break; } case PROP_FLAGS: - g_value_set_flags (value, gst_play_bin3_get_flags (playbin)); + g_value_set_flags (value, gst_play_sink_get_flags (playbin->playsink)); break; case PROP_SUBTITLE_ENCODING: GST_PLAY_BIN3_LOCK (playbin); @@ -1851,19 +1634,13 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, "text", GST_PLAY_SINK_TYPE_TEXT)); break; case PROP_VIDEO_STREAM_COMBINER: - g_value_take_object (value, - gst_play_bin3_get_current_stream_combiner (playbin, - &playbin->video_stream_combiner, "video", PLAYBIN_STREAM_VIDEO)); + g_value_set_object (value, playbin->video_stream_combiner); break; case PROP_AUDIO_STREAM_COMBINER: - g_value_take_object (value, - gst_play_bin3_get_current_stream_combiner (playbin, - &playbin->audio_stream_combiner, "audio", PLAYBIN_STREAM_AUDIO)); + g_value_set_object (value, playbin->audio_stream_combiner); break; case PROP_TEXT_STREAM_COMBINER: - g_value_take_object (value, - gst_play_bin3_get_current_stream_combiner (playbin, - &playbin->text_stream_combiner, "text", PLAYBIN_STREAM_TEXT)); + g_value_set_object (value, playbin->text_stream_combiner); break; case PROP_VOLUME: g_value_set_double (value, gst_play_sink_get_volume (playbin->playsink)); @@ -1881,17 +1658,20 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, break; case PROP_CONNECTION_SPEED: GST_PLAY_BIN3_LOCK (playbin); - g_value_set_uint64 (value, playbin->connection_speed / 1000); + g_object_get_property ((GObject *) playbin->uridecodebin, + "connection-speed", value); GST_PLAY_BIN3_UNLOCK (playbin); break; case PROP_BUFFER_SIZE: GST_OBJECT_LOCK (playbin); - g_value_set_int (value, playbin->buffer_size); + g_object_get_property ((GObject *) playbin->uridecodebin, "buffer-size", + value); GST_OBJECT_UNLOCK (playbin); break; case PROP_BUFFER_DURATION: GST_OBJECT_LOCK (playbin); - g_value_set_int64 (value, playbin->buffer_duration); + g_object_get_property ((GObject *) playbin->uridecodebin, + "buffer-duration", value); GST_OBJECT_UNLOCK (playbin); break; case PROP_AV_OFFSET: @@ -1903,7 +1683,8 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, gst_play_sink_get_text_offset (playbin->playsink)); break; case PROP_RING_BUFFER_MAX_SIZE: - g_value_set_uint64 (value, playbin->ring_buffer_max_size); + g_object_get_property ((GObject *) playbin->uridecodebin, + "ring-buffer-max-size", value); break; case PROP_FORCE_ASPECT_RATIO:{ gboolean v; @@ -1922,6 +1703,10 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value, g_value_set_flags (value, playbin->multiview_flags); GST_OBJECT_UNLOCK (playbin); break; + case PROP_INSTANT_URI: + g_object_get_property ((GObject *) playbin->uridecodebin, + "instant-uri", value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1983,8 +1768,7 @@ extend_list_of_streams (GstPlayBin3 * playbin, GstStreamType stype, } static GstEvent * -update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event, - GstSourceGroup * group) +update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event) { GList *streams = NULL; GList *to_use; @@ -1998,9 +1782,9 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event, return event; } - if (!group->collection) { + if (!playbin->collection) { GST_DEBUG_OBJECT (playbin, - "No stream collection for group, no need to modify SELECT_STREAMS event"); + "No stream collection, no need to modify SELECT_STREAMS event"); return event; } @@ -2012,7 +1796,7 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event, if (playbin->audio_stream_combiner) { to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_AUDIO, to_use, - group->collection); + playbin->collection); combine_id = get_combiner_stream_id (playbin, &playbin->combiner[PLAYBIN_STREAM_AUDIO], streams); @@ -2022,7 +1806,7 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event, if (playbin->video_stream_combiner) { to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_VIDEO, to_use, - group->collection); + playbin->collection); combine_id = get_combiner_stream_id (playbin, &playbin->combiner[PLAYBIN_STREAM_VIDEO], streams); @@ -2032,7 +1816,7 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event, if (playbin->text_stream_combiner) { to_use = extend_list_of_streams (playbin, GST_STREAM_TYPE_TEXT, to_use, - group->collection); + playbin->collection); combine_id = get_combiner_stream_id (playbin, &playbin->combiner[PLAYBIN_STREAM_TEXT], streams); @@ -2051,57 +1835,6 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event, return event; } -/* Returns TRUE if the given list of streams belongs to the stream collection */ -static gboolean -gst_streams_belong_to_collection (GList * streams, - GstStreamCollection * collection) -{ - GList *tmp; - guint i, nb; - - if (streams == NULL || collection == NULL) - return FALSE; - nb = gst_stream_collection_get_size (collection); - if (nb == 0) - return FALSE; - - for (tmp = streams; tmp; tmp = tmp->next) { - const gchar *cand = (const gchar *) tmp->data; - gboolean found = FALSE; - - for (i = 0; i < nb; i++) { - GstStream *stream = gst_stream_collection_get_stream (collection, i); - if (!g_strcmp0 (cand, gst_stream_get_stream_id (stream))) { - found = TRUE; - break; - } - } - if (!found) - return FALSE; - } - return TRUE; -} - -static GstSourceGroup * -get_source_group_for_streams (GstPlayBin3 * playbin, GstEvent * event) -{ - GList *streams; - GstSourceGroup *res = NULL; - - gst_event_parse_select_streams (event, &streams); - if (playbin->curr_group->collection && - gst_streams_belong_to_collection (streams, - playbin->curr_group->collection)) - res = playbin->curr_group; - else if (playbin->next_group->collection && - gst_streams_belong_to_collection (streams, - playbin->next_group->collection)) - res = playbin->next_group; - g_list_free_full (streams, g_free); - - return res; -} - static gboolean gst_play_bin3_send_event (GstElement * element, GstEvent * event) { @@ -2109,7 +1842,6 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event) if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) { gboolean res; - GstSourceGroup *group; GST_PLAY_BIN3_LOCK (playbin); GST_LOG_OBJECT (playbin, @@ -2117,25 +1849,17 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event) /* This is probably already false, but it doesn't hurt to be sure */ playbin->do_stream_selections = FALSE; - group = get_source_group_for_streams (playbin, event); - if (group == NULL) { - GST_WARNING_OBJECT (playbin, - "Can't figure out to which uridecodebin the select-streams event should be sent to"); - GST_PLAY_BIN3_UNLOCK (playbin); - return FALSE; - } - /* If we have custom combiners, we need to extend the selection with * the list of all streams for that given type since we will be handling * the selection with that combiner */ - event = update_select_streams_event (playbin, event, group); + event = update_select_streams_event (playbin, event); /* Don't reconfigure playsink just yet, until the streams-selected * message(s) tell us as streams become active / available */ /* Send this event directly to uridecodebin, so it works even * if uridecodebin didn't add any pads yet */ - res = gst_element_send_event (group->uridecodebin, event); + res = gst_element_send_event (playbin->uridecodebin, event); GST_PLAY_BIN3_UNLOCK (playbin); return res; @@ -2159,7 +1883,7 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event) /* Called with playbin lock held */ static void -do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group) +do_stream_selection (GstPlayBin3 * playbin) { GstStreamCollection *collection; guint i, nb_streams; @@ -2167,10 +1891,7 @@ do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group) gint nb_video = 0, nb_audio = 0, nb_text = 0; GstStreamType chosen_stream_types = 0; - if (group == NULL) - return; - - collection = group->collection; + collection = playbin->collection; if (collection == NULL) { GST_LOG_OBJECT (playbin, "No stream collection. Not doing stream-select"); return; @@ -2236,168 +1957,43 @@ do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group) } if (streams) { - if (group->uridecodebin) { - GstEvent *ev = gst_event_new_select_streams (streams); - gst_element_send_event (group->uridecodebin, ev); - } + GstEvent *ev = gst_event_new_select_streams (streams); + gst_element_send_event ((GstElement *) playbin->collection_source, ev); g_list_free (streams); } - group->selected_stream_types = chosen_stream_types; - /* Update global selected_stream_types */ - playbin->selected_stream_types = - playbin->groups[0].selected_stream_types | playbin->groups[1]. - selected_stream_types; + playbin->selected_stream_types = chosen_stream_types; if (playbin->active_stream_types != playbin->selected_stream_types) reconfigure_output (playbin); } -/* Return the GstSourceGroup to which this element belongs - * Can be NULL (if it belongs to playsink for example) */ -static GstSourceGroup * -find_source_group_owner (GstPlayBin3 * playbin, GstObject * element) -{ - if (playbin->curr_group->uridecodebin - && gst_object_has_as_ancestor (element, - GST_OBJECT_CAST (playbin->curr_group->uridecodebin))) - return playbin->curr_group; - if (playbin->next_group->uridecodebin - && gst_object_has_as_ancestor (element, - GST_OBJECT_CAST (playbin->next_group->uridecodebin))) - return playbin->next_group; - return NULL; -} - static void gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg) { GstPlayBin3 *playbin = GST_PLAY_BIN3 (bin); gboolean do_reset_time = FALSE; - if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_START) { - GstSourceGroup *group = NULL, *other_group = NULL; - gboolean changed = FALSE; - guint group_id; - GstMessage *buffering_msg; - - if (!gst_message_parse_group_id (msg, &group_id)) { - GST_ERROR_OBJECT (bin, - "Could not get group_id from STREAM_START message !"); - goto beach; - } - GST_DEBUG_OBJECT (bin, "STREAM_START group_id:%u", group_id); - - /* Figure out to which group this group_id corresponds */ - GST_PLAY_BIN3_LOCK (playbin); - if (playbin->groups[0].group_id == group_id) { - group = &playbin->groups[0]; - other_group = &playbin->groups[1]; - } else if (playbin->groups[1].group_id == group_id) { - group = &playbin->groups[1]; - other_group = &playbin->groups[0]; - } - if (group == NULL) { - GST_ERROR_OBJECT (bin, "group_id %u is not provided by any group !", - group_id); - GST_PLAY_BIN3_UNLOCK (playbin); - goto beach; - } - - debug_groups (playbin); - - /* Do the switch now ! */ - playbin->curr_group = group; - playbin->next_group = other_group; - - /* we may need to serialise a buffering - * message, and need to take that lock - * before any source group lock, so - * do that now */ - g_mutex_lock (&playbin->buffering_post_lock); - - GST_SOURCE_GROUP_LOCK (group); - if (group->playing == FALSE) - changed = TRUE; - group->playing = TRUE; - - buffering_msg = group->pending_buffering_msg; - group->pending_buffering_msg = NULL; - - GST_SOURCE_GROUP_UNLOCK (group); - - GST_SOURCE_GROUP_LOCK (other_group); - other_group->playing = FALSE; - GST_SOURCE_GROUP_UNLOCK (other_group); - - debug_groups (playbin); - GST_PLAY_BIN3_UNLOCK (playbin); - if (changed) - gst_play_bin3_check_group_status (playbin); - else - GST_DEBUG_OBJECT (bin, "Groups didn't changed"); - - /* If there was a pending buffering message to send, do it now */ - if (buffering_msg) - GST_BIN_CLASS (parent_class)->handle_message (bin, buffering_msg); - - g_mutex_unlock (&playbin->buffering_post_lock); - - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_BUFFERING) { - GstSourceGroup *group; - - /* Only post buffering messages for group which is currently playing */ - GST_PLAY_BIN3_LOCK (playbin); - group = find_source_group_owner (playbin, msg->src); - if (group->active) { - g_mutex_lock (&playbin->buffering_post_lock); - - GST_SOURCE_GROUP_LOCK (group); - GST_PLAY_BIN3_UNLOCK (playbin); - - if (!group->playing) { - GST_DEBUG_OBJECT (playbin, - "Storing buffering message from pending group " "%p %" - GST_PTR_FORMAT, group, msg); - gst_message_replace (&group->pending_buffering_msg, msg); - gst_message_unref (msg); - msg = NULL; - } else { - /* Ensure there's no cached buffering message for this group */ - gst_message_replace (&group->pending_buffering_msg, NULL); - } - GST_SOURCE_GROUP_UNLOCK (group); - - if (msg != NULL) { - GST_BIN_CLASS (parent_class)->handle_message (bin, msg); - msg = NULL; - } - g_mutex_unlock (&playbin->buffering_post_lock); - } else { - GST_PLAY_BIN3_UNLOCK (playbin); - } - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_COLLECTION) { + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_COLLECTION) { GstStreamCollection *collection = NULL; gst_message_parse_stream_collection (msg, &collection); if (collection) { gboolean pstate = playbin->do_stream_selections; - GstSourceGroup *target_group = NULL; GST_PLAY_BIN3_LOCK (playbin); GST_DEBUG_OBJECT (playbin, "STREAM_COLLECTION: Got a collection from %" GST_PTR_FORMAT, msg->src); - target_group = find_source_group_owner (playbin, msg->src); - if (target_group) - gst_object_replace ((GstObject **) & target_group->collection, - (GstObject *) collection); - /* FIXME: Only do the following if it's the current group? */ - if (target_group == playbin->curr_group) - update_combiner_info (playbin, target_group->collection); + gst_object_replace ((GstObject **) & playbin->collection, + (GstObject *) collection); + gst_object_replace ((GstObject **) & playbin->collection_source, + (GstObject *) GST_MESSAGE_SRC (msg)); + + update_combiner_info (playbin, playbin->collection); if (pstate) playbin->do_stream_selections = FALSE; - do_stream_selection (playbin, target_group); + do_stream_selection (playbin); if (pstate) playbin->do_stream_selections = TRUE; GST_PLAY_BIN3_UNLOCK (playbin); @@ -2409,34 +2005,26 @@ gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg) do_reset_time = TRUE; } } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAMS_SELECTED) { - GstSourceGroup *target_group; + GstStreamType selected_types = 0; + guint i, nb; GST_PLAY_BIN3_LOCK (playbin); - target_group = find_source_group_owner (playbin, msg->src); - if (target_group) { - GstStreamType selected_types = 0; - guint i, nb; - nb = gst_message_streams_selected_get_size (msg); - for (i = 0; i < nb; i++) { - GstStream *stream = gst_message_streams_selected_get_stream (msg, i); - selected_types |= gst_stream_get_stream_type (stream); - gst_object_unref (stream); - } - target_group->selected_stream_types = selected_types; - playbin->selected_stream_types = - playbin->groups[0].selected_stream_types | playbin->groups[1]. - selected_stream_types; - if (playbin->active_stream_types != playbin->selected_stream_types) { - GST_DEBUG_OBJECT (playbin, - "selected stream types changed, reconfiguring output"); - reconfigure_output (playbin); - } + nb = gst_message_streams_selected_get_size (msg); + for (i = 0; i < nb; i++) { + GstStream *stream = gst_message_streams_selected_get_stream (msg, i); + selected_types |= gst_stream_get_stream_type (stream); + gst_object_unref (stream); + } + playbin->selected_stream_types = selected_types; + if (playbin->active_stream_types != playbin->selected_stream_types) { + GST_DEBUG_OBJECT (playbin, + "selected stream types changed, reconfiguring output"); + reconfigure_output (playbin); } GST_PLAY_BIN3_UNLOCK (playbin); } -beach: if (msg) GST_BIN_CLASS (parent_class)->handle_message (bin, msg); @@ -2600,28 +2188,12 @@ update_video_multiview_caps (GstPlayBin3 * playbin, GstCaps * caps) return out_caps; } -static void -emit_about_to_finish (GstPlayBin3 * playbin) -{ - GST_DEBUG_OBJECT (playbin, "Emitting about-to-finish"); - - /* after this call, we should have a next group to activate or we EOS */ - g_signal_emit (G_OBJECT (playbin), - gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); - - debug_groups (playbin); - - /* now activate the next group. If the app did not set a uri, this will - * fail and we can do EOS */ - setup_next_source (playbin); -} - static SourcePad * -find_source_pad (GstSourceGroup * group, GstPad * target) +find_source_pad (GstPlayBin3 * playbin, GstPad * target) { GList *tmp; - for (tmp = group->source_pads; tmp; tmp = tmp->next) { + for (tmp = playbin->source_pads; tmp; tmp = tmp->next) { SourcePad *res = (SourcePad *) tmp->data; if (res->pad == target) return res; @@ -2630,11 +2202,10 @@ find_source_pad (GstSourceGroup * group, GstPad * target) } static GstPadProbeReturn -_decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata) +_decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, + GstPlayBin3 * playbin) { GstPadProbeReturn ret = GST_PAD_PROBE_OK; - GstSourceGroup *group = (GstSourceGroup *) udata; - GstPlayBin3 *playbin = group->playbin; GstEvent *event = GST_PAD_PROBE_INFO_DATA (info); switch (GST_EVENT_TYPE (event)) { @@ -2658,21 +2229,6 @@ _decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata) } break; } - case GST_EVENT_STREAM_START: - { - guint group_id; - if (gst_event_parse_group_id (event, &group_id)) { - GST_LOG_OBJECT (pad, "STREAM_START group_id:%u", group_id); - if (group->group_id == GST_GROUP_ID_INVALID) - group->group_id = group_id; - else if (group->group_id != group_id) { - GST_DEBUG_OBJECT (pad, "group_id changing from %u to %u", - group->group_id, group_id); - group->group_id = group_id; - } - } - break; - } default: break; } @@ -2681,7 +2237,7 @@ _decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata) } static void -control_source_pad (GstSourceGroup * group, GstPad * pad, +control_source_pad (GstPlayBin3 * playbin, GstPad * pad, GstPad * combine_pad, GstStreamType stream_type) { SourcePad *sourcepad = g_slice_new0 (SourcePad); @@ -2689,10 +2245,10 @@ control_source_pad (GstSourceGroup * group, GstPad * pad, sourcepad->pad = pad; sourcepad->event_probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - _decodebin_event_probe, group, NULL); + (GstPadProbeCallback) _decodebin_event_probe, playbin, NULL); sourcepad->stream_type = stream_type; sourcepad->combine_sinkpad = combine_pad; - group->source_pads = g_list_append (group->source_pads, sourcepad); + playbin->source_pads = g_list_append (playbin->source_pads, sourcepad); } static void @@ -2740,32 +2296,21 @@ create_combiner (GstPlayBin3 * playbin, GstSourceCombine * combine) combine->combiner = custom_combiner; if (!combine->combiner) { - gchar *concat_name; - GST_DEBUG_OBJECT (playbin, - "No custom combiner requested, using 'concat' element"); - concat_name = - g_strdup_printf ("%s-concat", - gst_stream_type_get_name (combine->stream_type)); - combine->combiner = gst_element_factory_make ("concat", concat_name); - g_object_set (combine->combiner, "adjust-base", FALSE, NULL); - g_free (concat_name); - combine->is_concat = TRUE; + GST_DEBUG_OBJECT (playbin, "No custom combiner requested"); + return; } combine->srcpad = gst_element_get_static_pad (combine->combiner, "src"); /* We only want to use 'active-pad' if it's a regular combiner that - * will consume all streams, and not concat (which is just used for - * gapless) */ - if (!combine->is_concat) { - combine->has_active_pad = - g_object_class_find_property (G_OBJECT_GET_CLASS (combine->combiner), - "active-pad") != NULL; + * will consume all streams */ + combine->has_active_pad = + g_object_class_find_property (G_OBJECT_GET_CLASS (combine->combiner), + "active-pad") != NULL; - if (combine->has_active_pad) - g_signal_connect (combine->combiner, "notify::active-pad", - G_CALLBACK (combiner_active_pad_changed), playbin); - } + if (combine->has_active_pad) + g_signal_connect (combine->combiner, "notify::active-pad", + G_CALLBACK (combiner_active_pad_changed), playbin); GST_DEBUG_OBJECT (playbin, "adding new stream combiner %" GST_PTR_FORMAT, combine->combiner); @@ -2848,17 +2393,13 @@ failed_combiner_link: return NULL; } - -/* Call after pad was unlinked from (potential) combiner */ static void -release_source_pad (GstPlayBin3 * playbin, GstSourceGroup * group, +release_source_pad (GstPlayBin3 * playbin, GstSourceCombine * combine, GstPad * pad) { SourcePad *sourcepad; - GList *tmp; - GstStreamType alltype = 0; - sourcepad = find_source_pad (group, pad); + sourcepad = find_source_pad (playbin, pad); if (!sourcepad) { GST_DEBUG_OBJECT (playbin, "Not a pad controlled by us ?"); return; @@ -2876,32 +2417,22 @@ release_source_pad (GstPlayBin3 * playbin, GstSourceGroup * group, } /* Remove from list of controlled pads and check again for EOS status */ - group->source_pads = g_list_remove (group->source_pads, sourcepad); + playbin->source_pads = g_list_remove (playbin->source_pads, sourcepad); g_slice_free (SourcePad, sourcepad); - - /* Update present stream types */ - for (tmp = group->source_pads; tmp; tmp = tmp->next) { - SourcePad *cand = (SourcePad *) tmp->data; - alltype |= cand->stream_type; - } - group->present_stream_types = alltype; } /* this function is called when a new pad is added to decodebin. We check the * type of the pad and add it to the combiner element */ static void -pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstSourceGroup * group) +pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstPlayBin3 * playbin) { GstSourceCombine *combine = NULL; gint pb_stream_type = -1; gchar *pad_name; - GstPlayBin3 *playbin = group->playbin; GstPad *combine_pad; GstStreamType selected, active, cur; - GST_PLAY_BIN3_SHUTDOWN_LOCK (playbin, shutdown); - pad_name = gst_object_get_name (GST_OBJECT (pad)); GST_DEBUG_OBJECT (playbin, "decoded pad %s:%s added", @@ -2925,7 +2456,6 @@ pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstSourceGroup * group) /* no stream type found for the media type, don't bother linking it to a * combiner. This will leave the pad unlinked and thus ignored. */ if (pb_stream_type < 0) { - GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); goto unknown_type; } @@ -2946,54 +2476,27 @@ pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstSourceGroup * group) GST_DEBUG_PAD_NAME (pad)); playbin->selected_stream_types = selected; reconfigure_output (playbin); - - /* shutdown state can be changed meantime then combiner will not be - * configured */ - if (g_atomic_int_get (&playbin->shutdown)) { - GST_PLAY_BIN3_UNLOCK (playbin); - GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); - return; - } } combine_pad = combiner_control_pad (playbin, combine, pad); + control_source_pad (playbin, pad, combine_pad, combine->stream_type); - control_source_pad (group, pad, combine_pad, combine->stream_type); - - /* Update present stream_types and check whether we should post a pending about-to-finish */ - group->present_stream_types |= combine->stream_type; - - if (group->playing && group->pending_about_to_finish - && group->present_stream_types == group->selected_stream_types) { - group->pending_about_to_finish = FALSE; - emit_about_to_finish (playbin); - } GST_PLAY_BIN3_UNLOCK (playbin); - GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin); - return; /* ERRORS */ unknown_type: GST_DEBUG_OBJECT (playbin, "Ignoring pad with unknown type"); return; - -shutdown: - { - GST_DEBUG ("ignoring, we are shutting down. Pad will be left unlinked"); - /* not going to done as we didn't request the caps */ - return; - } } /* called when a pad is removed from the decodebin. We unlink the pad from * the combiner. */ static void -pad_removed_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group) +pad_removed_cb (GstElement * decodebin, GstPad * pad, GstPlayBin3 * playbin) { GstSourceCombine *combine; - GstPlayBin3 *playbin = group->playbin; GST_DEBUG_OBJECT (playbin, "decoded pad %s:%s removed", GST_DEBUG_PAD_NAME (pad)); @@ -3010,7 +2513,7 @@ pad_removed_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group) else goto done; - release_source_pad (playbin, group, combine, pad); + release_source_pad (playbin, combine, pad); done: GST_PLAY_BIN3_UNLOCK (playbin); @@ -3019,11 +2522,10 @@ done: static gint select_stream_cb (GstElement * decodebin, GstStreamCollection * collection, - GstStream * stream, GstSourceGroup * group) + GstStream * stream, GstPlayBin3 * playbin) { GstStreamType stype = gst_stream_get_stream_type (stream); GstElement *combiner = NULL; - GstPlayBin3 *playbin = group->playbin; if (stype & GST_STREAM_TYPE_AUDIO) combiner = playbin->audio_stream_combiner; @@ -3101,16 +2603,11 @@ reconfigure_output (GstPlayBin3 * playbin) } /* Release combiner */ - GST_FIXME_OBJECT (playbin, "Release combiner"); remove_combiner (playbin, combine); } else if (!is_active && is_selected) { GST_DEBUG_OBJECT (playbin, "Stream type '%s' is now requested", gst_stream_type_get_name (combine->stream_type)); - /* If we are shutting down, do *not* add more combiners */ - if (g_atomic_int_get (&playbin->shutdown)) - continue; - g_assert (combine->sinkpad == NULL); /* Request playsink sink pad */ @@ -3151,601 +2648,40 @@ reconfigure_output (GstPlayBin3 * playbin) } static void -about_to_finish_cb (GstElement * uridecodebin, GstSourceGroup * group) +about_to_finish_cb (GstElement * uridecodebin, GstPlayBin3 * playbin) { - GstPlayBin3 *playbin = group->playbin; - GST_DEBUG_OBJECT (playbin, "about to finish in group %p", group); + GST_DEBUG_OBJECT (playbin, "about to finish"); GST_LOG_OBJECT (playbin, "selected_stream_types:%" STREAM_TYPES_FORMAT, - STREAM_TYPES_ARGS (group->selected_stream_types)); - GST_LOG_OBJECT (playbin, "present_stream_types:%" STREAM_TYPES_FORMAT, - STREAM_TYPES_ARGS (group->present_stream_types)); + STREAM_TYPES_ARGS (playbin->selected_stream_types)); - if (group->selected_stream_types == 0 - || (group->selected_stream_types != group->present_stream_types)) { - GST_LOG_OBJECT (playbin, - "Delaying emission of signal until this group is ready"); - group->pending_about_to_finish = TRUE; - } else - emit_about_to_finish (playbin); + GST_DEBUG_OBJECT (playbin, "Emitting about-to-finish"); + + /* after this call, we should have a next group to activate or we EOS */ + g_signal_emit (G_OBJECT (playbin), + gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); } -static GstBusSyncReply -activate_sink_bus_handler (GstBus * bus, GstMessage * msg, - GstPlayBin3 * playbin) -{ - if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { - /* Only proxy errors from a fixed sink. If that fails we can just error out - * early as stuff will fail later anyway */ - if (playbin->audio_sink - && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg), - GST_OBJECT_CAST (playbin->audio_sink))) - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - else if (playbin->video_sink - && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg), - GST_OBJECT_CAST (playbin->video_sink))) - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - else if (playbin->text_sink - && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg), - GST_OBJECT_CAST (playbin->text_sink))) - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - else - gst_message_unref (msg); - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_HAVE_CONTEXT) { - GstContext *context; - - gst_message_parse_have_context (msg, &context); - gst_element_set_context (GST_ELEMENT_CAST (playbin), context); - gst_context_unref (context); - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - } else { - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - } - - /* Doesn't really matter, nothing is using this bus */ - return GST_BUS_DROP; -} - -static gboolean -activate_sink (GstPlayBin3 * playbin, GstElement * sink, gboolean * activated) -{ - GstState state; - GstBus *bus = NULL; - GstStateChangeReturn sret; - gboolean ret = FALSE; - - if (activated) - *activated = FALSE; - - GST_OBJECT_LOCK (sink); - state = GST_STATE (sink); - GST_OBJECT_UNLOCK (sink); - if (state >= GST_STATE_READY) { - ret = TRUE; - goto done; - } - - if (!GST_OBJECT_PARENT (sink)) { - bus = gst_bus_new (); - gst_bus_set_sync_handler (bus, - (GstBusSyncHandler) activate_sink_bus_handler, playbin, NULL); - gst_element_set_bus (sink, bus); - } - - sret = gst_element_set_state (sink, GST_STATE_READY); - if (sret == GST_STATE_CHANGE_FAILURE) - goto done; - - if (activated) - *activated = TRUE; - ret = TRUE; - -done: - if (bus) { - gst_element_set_bus (sink, NULL); - gst_object_unref (bus); - } - - return ret; -} - -/* must be called with the group lock */ -static gboolean -group_set_locked_state_unlocked (GstPlayBin3 * playbin, GstSourceGroup * group, - gboolean locked) -{ - GST_DEBUG_OBJECT (playbin, "locked_state %d on group %p", locked, group); - - if (group->uridecodebin) - gst_element_set_locked_state (group->uridecodebin, locked); - - return TRUE; -} - -static gboolean -make_or_reuse_element (GstPlayBin3 * playbin, const gchar * name, - GstElement ** elem) -{ - if (*elem) { - GST_DEBUG_OBJECT (playbin, "reusing existing %s", name); - gst_element_set_state (*elem, GST_STATE_READY); - /* no need to take extra ref, we already have one - * and the bin will add one since it is no longer floating, - * as we added a non-floating ref when removing it from the - * bin earlier */ - } else { - GstElement *new_elem; - GST_DEBUG_OBJECT (playbin, "making new %s", name); - new_elem = gst_element_factory_make (name, NULL); - if (!new_elem) - return FALSE; - *elem = gst_object_ref (new_elem); - } - - if (GST_OBJECT_PARENT (*elem) != GST_OBJECT_CAST (playbin)) - gst_bin_add (GST_BIN_CAST (playbin), *elem); - return TRUE; -} - - static void source_setup_cb (GstElement * element, GstElement * source, - GstSourceGroup * group) + GstPlayBin3 * playbin) { - g_signal_emit (group->playbin, gst_play_bin3_signals[SIGNAL_SOURCE_SETUP], 0, + g_signal_emit (playbin, gst_play_bin3_signals[SIGNAL_SOURCE_SETUP], 0, source); } -/* must be called with PLAY_BIN_LOCK */ -static GstStateChangeReturn -activate_group (GstPlayBin3 * playbin, GstSourceGroup * group) -{ - GstElement *uridecodebin = NULL; - GstPlayFlags flags; - gboolean audio_sink_activated = FALSE; - gboolean video_sink_activated = FALSE; - gboolean text_sink_activated = FALSE; - GstStateChangeReturn state_ret; - - g_return_val_if_fail (group->valid, GST_STATE_CHANGE_FAILURE); - g_return_val_if_fail (!group->active, GST_STATE_CHANGE_FAILURE); - - GST_DEBUG_OBJECT (playbin, "activating group %p", group); - - GST_SOURCE_GROUP_LOCK (group); - - /* First set up the custom sinks */ - if (playbin->audio_sink) - group->audio_sink = gst_object_ref (playbin->audio_sink); - else - group->audio_sink = - gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO); - - if (group->audio_sink) { - if (!activate_sink (playbin, group->audio_sink, &audio_sink_activated)) { - if (group->audio_sink == playbin->audio_sink) { - goto sink_failure; - } else { - gst_object_unref (group->audio_sink); - group->audio_sink = NULL; - } - } - } - - if (playbin->video_sink) - group->video_sink = gst_object_ref (playbin->video_sink); - else - group->video_sink = - gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO); - - if (group->video_sink) { - if (!activate_sink (playbin, group->video_sink, &video_sink_activated)) { - if (group->video_sink == playbin->video_sink) { - goto sink_failure; - } else { - gst_object_unref (group->video_sink); - group->video_sink = NULL; - } - } - } - - if (playbin->text_sink) - group->text_sink = gst_object_ref (playbin->text_sink); - else - group->text_sink = - gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_TEXT); - - if (group->text_sink) { - if (!activate_sink (playbin, group->text_sink, &text_sink_activated)) { - if (group->text_sink == playbin->text_sink) { - goto sink_failure; - } else { - gst_object_unref (group->text_sink); - group->text_sink = NULL; - } - } - } - - - if (!make_or_reuse_element (playbin, "uridecodebin3", &group->uridecodebin)) - goto no_uridecodebin; - uridecodebin = group->uridecodebin; - - flags = gst_play_sink_get_flags (playbin->playsink); - - g_object_set (uridecodebin, - /* configure connection speed */ - "connection-speed", playbin->connection_speed / 1000, - /* configure uri */ - "uri", group->uri, - /* configure download buffering */ - "download", ((flags & GST_PLAY_FLAG_DOWNLOAD) != 0), - /* configure buffering of demuxed/parsed data */ - "use-buffering", ((flags & GST_PLAY_FLAG_BUFFERING) != 0), - /* configure buffering parameters */ - "buffer-duration", playbin->buffer_duration, - "buffer-size", playbin->buffer_size, - "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL); - - group->pad_added_id = g_signal_connect (uridecodebin, "pad-added", - G_CALLBACK (pad_added_cb), group); - group->pad_removed_id = g_signal_connect (uridecodebin, - "pad-removed", G_CALLBACK (pad_removed_cb), group); - group->select_stream_id = g_signal_connect (uridecodebin, "select-stream", - G_CALLBACK (select_stream_cb), group); - group->source_setup_id = g_signal_connect (uridecodebin, "source-setup", - G_CALLBACK (source_setup_cb), group); - group->about_to_finish_id = - g_signal_connect (uridecodebin, "about-to-finish", - G_CALLBACK (about_to_finish_cb), group); - - if (group->suburi) - g_object_set (group->uridecodebin, "suburi", group->suburi, NULL); - - /* release the group lock before setting the state of the source bins, they - * might fire signals in this thread that we need to handle with the - * group_lock taken. */ - GST_SOURCE_GROUP_UNLOCK (group); - - if ((state_ret = - gst_element_set_state (uridecodebin, - GST_STATE_PAUSED)) == GST_STATE_CHANGE_FAILURE) - goto uridecodebin_failure; - - GST_SOURCE_GROUP_LOCK (group); - /* allow state changes of the playbin affect the group elements now */ - group_set_locked_state_unlocked (playbin, group, FALSE); - group->active = TRUE; - GST_SOURCE_GROUP_UNLOCK (group); - - return state_ret; - - /* ERRORS */ -no_uridecodebin: - { - GstMessage *msg; - - GST_SOURCE_GROUP_UNLOCK (group); - msg = - gst_missing_element_message_new (GST_ELEMENT_CAST (playbin), - "uridecodebin3"); - gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); - - GST_ELEMENT_ERROR (playbin, CORE, MISSING_PLUGIN, - (_("Could not create \"uridecodebin3\" element.")), (NULL)); - - GST_SOURCE_GROUP_LOCK (group); - - goto error_cleanup; - } -uridecodebin_failure: - { - GST_DEBUG_OBJECT (playbin, "failed state change of uridecodebin"); - GST_SOURCE_GROUP_LOCK (group); - goto error_cleanup; - } -sink_failure: - { - GST_ERROR_OBJECT (playbin, "failed to activate sinks"); - goto error_cleanup; - } - -error_cleanup: - { - group->selected_stream_types = 0; - - /* delete any custom sinks we might have */ - if (group->audio_sink) { - /* If this is a automatically created sink set it to NULL */ - if (audio_sink_activated) - gst_element_set_state (group->audio_sink, GST_STATE_NULL); - gst_object_unref (group->audio_sink); - } - group->audio_sink = NULL; - - if (group->video_sink) { - /* If this is a automatically created sink set it to NULL */ - if (video_sink_activated) - gst_element_set_state (group->video_sink, GST_STATE_NULL); - gst_object_unref (group->video_sink); - } - group->video_sink = NULL; - - if (group->text_sink) { - /* If this is a automatically created sink set it to NULL */ - if (text_sink_activated) - gst_element_set_state (group->text_sink, GST_STATE_NULL); - gst_object_unref (group->text_sink); - } - group->text_sink = NULL; - - if (uridecodebin) { - REMOVE_SIGNAL (group->uridecodebin, group->pad_added_id); - REMOVE_SIGNAL (group->uridecodebin, group->pad_removed_id); - REMOVE_SIGNAL (group->uridecodebin, group->select_stream_id); - REMOVE_SIGNAL (group->uridecodebin, group->source_setup_id); - REMOVE_SIGNAL (group->uridecodebin, group->about_to_finish_id); - - gst_element_set_state (uridecodebin, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (playbin), uridecodebin); - } - - GST_SOURCE_GROUP_UNLOCK (group); - - return GST_STATE_CHANGE_FAILURE; - } -} - -/* must be called with PLAY_BIN_LOCK, which is dropped temporarily - * if changing states */ -static gboolean -deactivate_group (GstPlayBin3 * playbin, GstSourceGroup * group) -{ - g_return_val_if_fail (group->active, FALSE); - g_return_val_if_fail (group->valid, FALSE); - - GST_DEBUG_OBJECT (playbin, "unlinking group %p", group); - - GST_SOURCE_GROUP_LOCK (group); - group->active = FALSE; - group->playing = FALSE; - group->group_id = GST_GROUP_ID_INVALID; - - group->selected_stream_types = 0; - /* Update global selected_stream_types */ - playbin->selected_stream_types = - playbin->groups[0].selected_stream_types | playbin->groups[1]. - selected_stream_types; - if (playbin->active_stream_types != playbin->selected_stream_types) - reconfigure_output (playbin); - - if (group->uridecodebin) { - REMOVE_SIGNAL (group->uridecodebin, group->select_stream_id); - REMOVE_SIGNAL (group->uridecodebin, group->source_setup_id); - REMOVE_SIGNAL (group->uridecodebin, group->about_to_finish_id); - - GST_PLAY_BIN3_UNLOCK (playbin); - gst_element_set_state (group->uridecodebin, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (playbin), group->uridecodebin); - GST_PLAY_BIN3_LOCK (playbin); - - REMOVE_SIGNAL (group->uridecodebin, group->pad_added_id); - REMOVE_SIGNAL (group->uridecodebin, group->pad_removed_id); - } - - GST_SOURCE_GROUP_UNLOCK (group); - - GST_DEBUG_OBJECT (playbin, "Done"); - - return TRUE; -} - -/* setup the next group to play, this assumes the next_group is valid and - * configured. It swaps out the current_group and activates the valid - * next_group. */ -static GstStateChangeReturn -setup_next_source (GstPlayBin3 * playbin) -{ - GstSourceGroup *new_group; - GstStateChangeReturn state_ret; - - GST_DEBUG_OBJECT (playbin, "setup next source"); - - debug_groups (playbin); - - /* see if there is a next group */ - GST_PLAY_BIN3_LOCK (playbin); - new_group = playbin->next_group; - if (!new_group || !new_group->valid || new_group->active) - goto no_next_group; - - /* activate the new group */ - state_ret = activate_group (playbin, new_group); - if (state_ret == GST_STATE_CHANGE_FAILURE) - goto activate_failed; - - GST_PLAY_BIN3_UNLOCK (playbin); - - debug_groups (playbin); - - return state_ret; - - /* ERRORS */ -no_next_group: - { - GST_DEBUG_OBJECT (playbin, "no next group"); - GST_PLAY_BIN3_UNLOCK (playbin); - return GST_STATE_CHANGE_FAILURE; - } -activate_failed: - { - new_group->stream_changed_pending = FALSE; - GST_DEBUG_OBJECT (playbin, "activate failed"); - new_group->valid = FALSE; - GST_PLAY_BIN3_UNLOCK (playbin); - return GST_STATE_CHANGE_FAILURE; - } -} - -/* The group that is currently playing is copied again to the - * next_group so that it will start playing the next time. - */ -static gboolean -save_current_group (GstPlayBin3 * playbin) -{ - GstSourceGroup *curr_group; - gboolean swapped = FALSE; - - GST_DEBUG_OBJECT (playbin, "save current group"); - - /* see if there is a current group */ - GST_PLAY_BIN3_LOCK (playbin); - curr_group = playbin->curr_group; - if (curr_group && curr_group->valid && curr_group->active) { - swapped = TRUE; - } - /* swap old and new */ - playbin->curr_group = playbin->next_group; - playbin->next_group = curr_group; - - if (swapped) { - /* unlink our pads with the sink */ - deactivate_group (playbin, curr_group); - } - GST_PLAY_BIN3_UNLOCK (playbin); - - return TRUE; -} - -/* clear the locked state from all groups. This function is called before a - * state change to NULL is performed on them. */ -static gboolean -groups_set_locked_state (GstPlayBin3 * playbin, gboolean locked) -{ - GST_DEBUG_OBJECT (playbin, "setting locked state to %d on all groups", - locked); - - GST_PLAY_BIN3_LOCK (playbin); - GST_SOURCE_GROUP_LOCK (playbin->curr_group); - group_set_locked_state_unlocked (playbin, playbin->curr_group, locked); - GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); - GST_SOURCE_GROUP_LOCK (playbin->next_group); - group_set_locked_state_unlocked (playbin, playbin->next_group, locked); - GST_SOURCE_GROUP_UNLOCK (playbin->next_group); - GST_PLAY_BIN3_UNLOCK (playbin); - - return TRUE; -} - -static void -gst_play_bin3_check_group_status (GstPlayBin3 * playbin) -{ - if (playbin->activation_task) - gst_task_start (playbin->activation_task); -} - -static void -gst_play_bin3_activation_thread (GstPlayBin3 * playbin) -{ - GST_DEBUG_OBJECT (playbin, "starting"); - - debug_groups (playbin); - - /* Check if next_group needs to be deactivated */ - GST_PLAY_BIN3_LOCK (playbin); - if (playbin->next_group->active) { - deactivate_group (playbin, playbin->next_group); - playbin->next_group->valid = FALSE; - } - - /* Is there a pending about-to-finish to be emitted ? */ - GST_SOURCE_GROUP_LOCK (playbin->curr_group); - if (playbin->curr_group->pending_about_to_finish) { - GST_LOG_OBJECT (playbin, "Propagating about-to-finish"); - playbin->curr_group->pending_about_to_finish = FALSE; - GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); - /* This will activate the next source afterwards */ - emit_about_to_finish (playbin); - } else - GST_SOURCE_GROUP_UNLOCK (playbin->curr_group); - - GST_LOG_OBJECT (playbin, "Pausing task"); - if (playbin->activation_task) - gst_task_pause (playbin->activation_task); - GST_PLAY_BIN3_UNLOCK (playbin); - - GST_DEBUG_OBJECT (playbin, "done"); - return; -} - static gboolean gst_play_bin3_start (GstPlayBin3 * playbin) { GST_DEBUG_OBJECT (playbin, "starting"); GST_PLAY_BIN3_LOCK (playbin); - - if (playbin->activation_task == NULL) { - playbin->activation_task = - gst_task_new ((GstTaskFunction) gst_play_bin3_activation_thread, - playbin, NULL); - if (playbin->activation_task == NULL) - goto task_error; - gst_task_set_lock (playbin->activation_task, &playbin->activation_lock); - } - GST_LOG_OBJECT (playbin, "clearing shutdown flag"); - g_atomic_int_set (&playbin->shutdown, 0); + playbin->active_stream_types = 0; + playbin->selected_stream_types = 0; do_async_start (playbin); - GST_PLAY_BIN3_UNLOCK (playbin); return TRUE; - -task_error: - { - GST_PLAY_BIN3_UNLOCK (playbin); - GST_ERROR_OBJECT (playbin, "Failed to create task"); - return FALSE; - } -} - -static void -gst_play_bin3_stop (GstPlayBin3 * playbin) -{ - GstTask *task; - - GST_DEBUG_OBJECT (playbin, "stopping"); - - /* FIXME unlock our waiting groups */ - GST_LOG_OBJECT (playbin, "setting shutdown flag"); - g_atomic_int_set (&playbin->shutdown, 1); - - /* wait for all callbacks to end by taking the lock. - * No dynamic (critical) new callbacks will - * be able to happen as we set the shutdown flag. */ - GST_PLAY_BIN3_DYN_LOCK (playbin); - GST_LOG_OBJECT (playbin, "dynamic lock taken, we can continue shutdown"); - GST_PLAY_BIN3_DYN_UNLOCK (playbin); - - /* Stop the activation task */ - GST_PLAY_BIN3_LOCK (playbin); - if ((task = playbin->activation_task)) { - playbin->activation_task = NULL; - GST_PLAY_BIN3_UNLOCK (playbin); - - gst_task_stop (task); - - /* Make sure task is not running */ - g_rec_mutex_lock (&playbin->activation_lock); - g_rec_mutex_unlock (&playbin->activation_lock); - - /* Wait for task to finish and unref it */ - gst_task_join (task); - gst_object_unref (task); - - GST_PLAY_BIN3_LOCK (playbin); - } - GST_PLAY_BIN3_UNLOCK (playbin); } static GstStateChangeReturn @@ -3753,7 +2689,6 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstPlayBin3 *playbin; - gboolean do_save = FALSE; playbin = GST_PLAY_BIN3 (element); @@ -3762,24 +2697,6 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) if (!gst_play_bin3_start (playbin)) return GST_STATE_CHANGE_FAILURE; break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - async_down: - gst_play_bin3_stop (playbin); - if (!do_save) - break; - case GST_STATE_CHANGE_READY_TO_NULL: - /* we go async to PAUSED, so if that fails, we never make it to PAUSED - * and we will never be called with the GST_STATE_CHANGE_PAUSED_TO_READY. - * Make sure we do go through the same steps (see above) for - * proper cleanup */ - if (!g_atomic_int_get (&playbin->shutdown)) { - do_save = TRUE; - goto async_down; - } - - /* unlock so that all groups go to NULL */ - groups_set_locked_state (playbin, FALSE); - break; default: break; } @@ -3790,45 +2707,22 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: - if ((ret = setup_next_source (playbin)) == GST_STATE_CHANGE_FAILURE) - goto failure; if (ret == GST_STATE_CHANGE_SUCCESS) ret = GST_STATE_CHANGE_ASYNC; - break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: do_async_done (playbin); - /* FIXME Release audio device when we implement that */ break; case GST_STATE_CHANGE_PAUSED_TO_READY: playbin->is_live = FALSE; - save_current_group (playbin); + /* Make sure we reset our state */ + if (playbin->selected_stream_types) { + playbin->selected_stream_types = 0; + reconfigure_output (playbin); + } break; case GST_STATE_CHANGE_READY_TO_NULL: { - guint i; - - /* also do missed state change down to READY */ - if (do_save) - save_current_group (playbin); - /* Deactivate the groups, set uridecodebin to NULL and unref it */ - GST_PLAY_BIN3_LOCK (playbin); - for (i = 0; i < 2; i++) { - if (playbin->groups[i].active && playbin->groups[i].valid) { - deactivate_group (playbin, &playbin->groups[i]); - playbin->groups[i].valid = FALSE; - } - - if (playbin->groups[i].uridecodebin) { - gst_element_set_state (playbin->groups[i].uridecodebin, - GST_STATE_NULL); - gst_object_unref (playbin->groups[i].uridecodebin); - playbin->groups[i].uridecodebin = NULL; - } - - } - GST_PLAY_BIN3_UNLOCK (playbin); - /* Set our sinks back to NULL, they might not be child of playbin */ if (playbin->audio_sink) gst_element_set_state (playbin->audio_sink, GST_STATE_NULL); @@ -3844,9 +2738,9 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) if (playbin->text_stream_combiner) gst_element_set_state (playbin->text_stream_combiner, GST_STATE_NULL); - /* make sure the groups don't perform a state change anymore until we - * enable them again */ - groups_set_locked_state (playbin, TRUE); + gst_object_replace ((GstObject **) & playbin->collection, NULL); + gst_object_replace ((GstObject **) & playbin->collection_source, NULL); + break; } default: @@ -3865,27 +2759,6 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition) failure: { do_async_done (playbin); - - if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) { - GstSourceGroup *curr_group; - - GST_PLAY_BIN3_LOCK (playbin); - - curr_group = playbin->curr_group; - if (curr_group) { - if (curr_group->active && curr_group->valid) { - /* unlink our pads with the sink */ - deactivate_group (playbin, curr_group); - } - curr_group->valid = FALSE; - } - - /* Swap current and next group back */ - playbin->curr_group = playbin->next_group; - playbin->next_group = curr_group; - - GST_PLAY_BIN3_UNLOCK (playbin); - } return ret; } } diff --git a/subprojects/gst-plugins-base/gst/playback/gsturidecodebin3.c b/subprojects/gst-plugins-base/gst/playback/gsturidecodebin3.c index d6fce6fa51..390b51b48e 100644 --- a/subprojects/gst-plugins-base/gst/playback/gsturidecodebin3.c +++ b/subprojects/gst-plugins-base/gst/playback/gsturidecodebin3.c @@ -27,12 +27,6 @@ * post the (last) buffering messages. * If no group_id is being outputted (still prerolling), then output * the messages directly - * - * * ASYNC HANDLING - * ** URIDECODEBIN3 is not async-aware. - * - * * GAPLESS HANDLING - * ** Correlate group_id and URI to know when/which stream is being outputted/started */ /** @@ -74,16 +68,19 @@ typedef struct _GstSourceGroup GstSourceGroup; typedef struct _GstURIDecodeBin3 GstURIDecodeBin3; typedef struct _GstURIDecodeBin3Class GstURIDecodeBin3Class; -#define GST_URI_DECODE_BIN3_LOCK(dec) (g_mutex_lock(&((GstURIDecodeBin3*)(dec))->lock)) -#define GST_URI_DECODE_BIN3_UNLOCK(dec) (g_mutex_unlock(&((GstURIDecodeBin3*)(dec))->lock)) - typedef struct _GstPlayItem GstPlayItem; typedef struct _GstSourceItem GstSourceItem; typedef struct _GstSourceHandler GstSourceHandler; +typedef struct _GstSourcePad GstSourcePad; typedef struct _OutputPad OutputPad; /* A structure describing a play item, which travels through the elements - * over time. */ + * over time. + * + * All source items in this play item will be played together. Corresponds to an + * end-user "play item" (ex: one item from a playlist, even though it might be + * using a main content and subtitle content). + */ struct _GstPlayItem { GstURIDecodeBin3 *uridecodebin; @@ -101,11 +98,25 @@ struct _GstPlayItem * The urisourcebin-specific group_id is located in GstSourceItem */ guint group_id; - /* Is this play item the one being currently outputted by decodebin3 - * and on our source ghostpads */ - gboolean currently_outputted; + /* The two following variables are required for gapless, since there could be + * a play item which is started which is different from the one currently + * being outputted */ + + /* active: TRUE if the backing urisourcebin were created */ + gboolean active; + + /* Whether about-to-finish was already posted for this play item */ + gboolean posted_about_to_finish; + + /* Whether about-to-finish should be posted once this play item becomes the + * current input item */ + gboolean pending_about_to_finish; }; +/* The actual "source" component of a "play item" + * + * This is defined by having a URI, is backed by a `GstSourceHandler`. + */ struct _GstSourceItem { /* The GstPlayItem to which this GstSourceItem belongs to */ @@ -116,17 +127,13 @@ struct _GstSourceItem /* The urisourcebin controlling this uri * Can be NULL */ GstSourceHandler *handler; - - /* The groupid created by urisourcebin for this uri */ - guint internal_groupid; - - /* FIXME : Add tag lists and other uri-specific items here ? */ }; /* Structure wrapping everything related to a urisourcebin */ struct _GstSourceHandler { GstURIDecodeBin3 *uridecodebin; + GstPlayItem *play_item; GstElement *urisourcebin; @@ -139,19 +146,46 @@ struct _GstSourceHandler /* TRUE if the controlled urisourcebin was added to uridecodebin */ gboolean active; - /* whether urisourcebin is drained or not. - * Reset if/when setting a new URI */ - gboolean drained; - - /* Whether urisourcebin posted EOS on all pads and - * there is no pending entry */ - gboolean is_eos; - /* TRUE if the urisourcebin handles main item */ gboolean is_main_source; /* buffering message stored for after switching */ GstMessage *pending_buffering_msg; + + /* Number of expected sourcepads. Default 1, else it's the number of streams + * specified by GST_MESSAGE_SELECTED_STREAMS from the source */ + guint expected_pads; + + /* List of GstSourcePad */ + GList *sourcepads; +}; + +/* Structure wrapping everything related to a urisourcebin pad */ +struct _GstSourcePad +{ + GstSourceHandler *handler; + + GstPad *src_pad; + + /* GstStream (if present) */ + GstStream *stream; + + /* Decodebin3 pad to which src_pad is linked to */ + GstPad *db3_sink_pad; + + /* TRUE if db3_sink_pad is a request pad */ + gboolean db3_pad_is_request; + + /* TRUE if EOS went through the source pad. Marked as TRUE if decodebin3 + * notified `about-to-finish` for pull mode */ + gboolean saw_eos; + + /* Downstream blocking probe id. Only set/valid if we need to block this + * pad */ + gulong block_probe_id; + + /* Downstream event probe id */ + gulong event_probe_id; }; /* Controls an output source pad */ @@ -165,14 +199,23 @@ struct _OutputPad /* Downstream event probe id */ gulong probe_id; - /* TRUE if the pad saw EOS. Reset to FALSE on STREAM_START */ - gboolean is_eos; - /* The last seen (i.e. current) group_id * Can be (guint)-1 if no group_id was seen yet */ guint current_group_id; }; +#define PLAY_ITEMS_GET_LOCK(d) (&(GST_URI_DECODE_BIN3_CAST(d)->play_items_lock)) +#define PLAY_ITEMS_LOCK(d) G_STMT_START { \ + GST_TRACE("Locking play_items from thread %p", g_thread_self()); \ + g_mutex_lock (PLAY_ITEMS_GET_LOCK (d)); \ + GST_TRACE("Locked play_items from thread %p", g_thread_self()); \ + } G_STMT_END + +#define PLAY_ITEMS_UNLOCK(d) G_STMT_START { \ + GST_TRACE("Unlocking play_items from thread %p", g_thread_self()); \ + g_mutex_unlock (PLAY_ITEMS_GET_LOCK (d)); \ + } G_STMT_END + /** * GstURIDecodeBin3 * @@ -182,8 +225,6 @@ struct _GstURIDecodeBin3 { GstBin parent_instance; - GMutex lock; /* lock for constructing */ - /* Properties */ GstElement *source; guint64 connection_speed; /* In bits/sec (0 = unknown) */ @@ -193,26 +234,23 @@ struct _GstURIDecodeBin3 gboolean download; gboolean use_buffering; guint64 ring_buffer_max_size; + gboolean instant_uri; /* Whether URI changes should be applied immediately or not */ - GList *play_items; /* List of GstPlayItem ordered by time of - * creation. Head of list is therefore the - * current (or pending if initial) one being - * outputted */ - GstPlayItem *current; /* Currently active GstPlayItem. Can be NULL - * if no entry is active yet (i.e. no source - * pads) */ + /* Mutex to protect play_items/input_item/output_item */ + GMutex play_items_lock; - /* sources. - * FIXME : Replace by a more modular system later on */ - GstSourceHandler *main_handler; - GstSourceHandler *sub_handler; + /* Notify that the input_item sources have all drained */ + GCond input_source_drained; - /* URI handling - * FIXME : Switch to a playlist-based API */ - gchar *uri; - gboolean uri_changed; /* TRUE if uri changed */ - gchar *suburi; - gboolean suburi_changed; /* TRUE if suburi changed */ + /* List of GstPlayItem ordered by time of creation (first is oldest, new ones + * are appended) */ + GList *play_items; + + /* Play item currently feeding decodebin3. */ + GstPlayItem *input_item; + + /* Play item currently outputted by decodebin3 */ + GstPlayItem *output_item; /* A global decodebin3 that's used to actually do decoding */ GstElement *decodebin; @@ -223,15 +261,14 @@ struct _GstURIDecodeBin3 gulong db_select_stream_id; gulong db_about_to_finish_id; + /* 1 if shutting down */ + gint shutdown; + GList *output_pads; /* List of OutputPad */ - - GList *source_handlers; /* List of SourceHandler */ - - /* Whether we already signalled about-to-finish or not - * FIXME: Track this by group-id ! */ - gboolean posted_about_to_finish; }; +static GstStateChangeReturn activate_play_item (GstPlayItem * item); + static gint gst_uridecodebin3_select_stream (GstURIDecodeBin3 * dbin, GstStreamCollection * collection, GstStream * stream) @@ -261,11 +298,6 @@ enum LAST_SIGNAL }; -#if 0 -static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw(ANY)"); -static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw(ANY)"); -#endif - /* properties */ #define DEFAULT_PROP_URI NULL #define DEFAULT_PROP_SUBURI NULL @@ -276,6 +308,7 @@ static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw(ANY)"); #define DEFAULT_DOWNLOAD FALSE #define DEFAULT_USE_BUFFERING FALSE #define DEFAULT_RING_BUFFER_MAX_SIZE 0 +#define DEFAULT_INSTANT_URI FALSE enum { @@ -291,7 +324,8 @@ enum PROP_DOWNLOAD, PROP_USE_BUFFERING, PROP_RING_BUFFER_MAX_SIZE, - PROP_CAPS + PROP_CAPS, + PROP_INSTANT_URI }; static guint gst_uri_decode_bin3_signals[LAST_SIGNAL] = { 0 }; @@ -341,14 +375,28 @@ static void gst_uri_decode_bin3_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_uri_decode_bin3_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); -static void gst_uri_decode_bin3_finalize (GObject * obj); +static void gst_uri_decode_bin3_dispose (GObject * obj); static GstSourceHandler *new_source_handler (GstURIDecodeBin3 * uridecodebin, - gboolean is_main); + GstPlayItem * item, gboolean is_main); +static void free_source_item (GstURIDecodeBin3 * uridecodebin, + GstSourceItem * item); + +static GstPlayItem *new_play_item (GstURIDecodeBin3 * dec); +static void free_play_item (GstURIDecodeBin3 * dec, GstPlayItem * item); +static gboolean play_item_is_eos (GstPlayItem * item); +static void play_item_set_eos (GstPlayItem * item); +static gboolean play_item_has_all_pads (GstPlayItem * item); + +static void gst_uri_decode_bin3_set_uri (GstURIDecodeBin3 * dec, + const gchar * uri); +static void gst_uri_decode_bin3_set_suburi (GstURIDecodeBin3 * dec, + const gchar * uri); static GstStateChangeReturn gst_uri_decode_bin3_change_state (GstElement * element, GstStateChange transition); static gboolean gst_uri_decodebin3_send_event (GstElement * element, GstEvent * event); +static void gst_uri_decode_bin3_handle_message (GstBin * bin, GstMessage * msg); static gboolean _gst_int_accumulator (GSignalInvocationHint * ihint, @@ -370,13 +418,15 @@ gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; + GstBinClass *gstbin_class; gobject_class = G_OBJECT_CLASS (klass); gstelement_class = GST_ELEMENT_CLASS (klass); + gstbin_class = GST_BIN_CLASS (klass); gobject_class->set_property = gst_uri_decode_bin3_set_property; gobject_class->get_property = gst_uri_decode_bin3_get_property; - gobject_class->finalize = gst_uri_decode_bin3_finalize; + gobject_class->dispose = gst_uri_decode_bin3_dispose; g_object_class_install_property (gobject_class, PROP_URI, g_param_spec_string ("uri", "URI", "URI to decode", @@ -459,6 +509,19 @@ gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass) "The caps on which to stop decoding. (NULL = default)", GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstURIDecodeBin3:instant-uri: + * + * Changes to uri are applied immediately (instead of on EOS or when the + * element is set back to PLAYING. + * + * Since: 1.22 + */ + g_object_class_install_property (gobject_class, PROP_INSTANT_URI, + g_param_spec_boolean ("instant-uri", "Instantaneous URI change", + "When enabled, URI changes are applied immediately", + DEFAULT_INSTANT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** * GstURIDecodebin3::select-stream * @decodebin: a #GstURIDecodebin3 @@ -521,18 +584,93 @@ gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass) gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_uri_decodebin3_send_event); + gstbin_class->handle_message = gst_uri_decode_bin3_handle_message; + klass->select_stream = gst_uridecodebin3_select_stream; } +static void +check_output_group_id (GstURIDecodeBin3 * dec) +{ + GList *iter; + guint common_group_id = GST_GROUP_ID_INVALID; + + PLAY_ITEMS_LOCK (dec); + + for (iter = dec->output_pads; iter; iter = iter->next) { + OutputPad *pad = iter->data; + + if (common_group_id == GST_GROUP_ID_INVALID) + common_group_id = pad->current_group_id; + else if (common_group_id != pad->current_group_id) { + GST_DEBUG_OBJECT (dec, "transitioning output play item"); + PLAY_ITEMS_UNLOCK (dec); + return; + } + } + + if (dec->output_item->group_id == common_group_id) { + GST_DEBUG_OBJECT (dec, "Output play item %d fully active", common_group_id); + } else if (dec->output_item->group_id == GST_GROUP_ID_INVALID) { + /* This can happen for pull-based situations */ + GST_DEBUG_OBJECT (dec, "Assigning group id %u to current output play item", + common_group_id); + dec->output_item->group_id = common_group_id; + } else if (common_group_id != GST_GROUP_ID_INVALID && + dec->output_item->group_id != common_group_id) { + GstPlayItem *previous_item = dec->output_item; + GST_DEBUG_OBJECT (dec, "Output play item %d fully active", common_group_id); + if (g_list_length (dec->play_items) > 1) { + dec->play_items = g_list_remove (dec->play_items, previous_item); + dec->output_item = dec->play_items->data; + dec->output_item->group_id = common_group_id; + free_play_item (dec, previous_item); + } + } + + PLAY_ITEMS_UNLOCK (dec); +} + static GstPadProbeReturn db_src_probe (GstPad * pad, GstPadProbeInfo * info, OutputPad * output) { - /* FIXME : IMPLEMENT */ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GstURIDecodeBin3 *uridecodebin = output->uridecodebin; + + GST_DEBUG_OBJECT (pad, "event %" GST_PTR_FORMAT, event); /* EOS : Mark pad as EOS */ - /* STREAM_START : Store group_id and check if currently active - * PlayEntry changed */ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + { + /* If there is a next input, drop the EOS event */ + if (uridecodebin->input_item != uridecodebin->output_item || + uridecodebin->input_item != + g_list_last (uridecodebin->play_items)->data) { + GST_DEBUG_OBJECT (uridecodebin, + "Dropping EOS event because in gapless mode"); + return GST_PAD_PROBE_DROP; + } + break; + } + case GST_EVENT_STREAM_START: + { + /* STREAM_START : Store group_id and check if currently active + * PlayEntry changed */ + if (gst_event_parse_group_id (event, &output->current_group_id)) { + GST_DEBUG_OBJECT (pad, "current group id %" G_GUINT32_FORMAT, + output->current_group_id); + /* Check if we switched over to a new output */ + check_output_group_id (uridecodebin); + } + + break; + } + default: + break; + } + return GST_PAD_PROBE_OK; } @@ -617,12 +755,12 @@ db_pad_removed_cb (GstElement * element, GstPad * pad, GstURIDecodeBin3 * dec) gst_ghost_pad_set_target ((GstGhostPad *) output->ghost_pad, NULL); gst_element_remove_pad ((GstElement *) dec, output->ghost_pad); - /* FIXME : Update global/current PlayEntry group_id (did we switch ?) */ - /* Remove event probe */ gst_pad_remove_probe (output->target_pad, output->probe_id); g_slice_free (OutputPad, output); + + check_output_group_id (dec); } } @@ -639,23 +777,88 @@ db_select_stream_cb (GstElement * decodebin, return response; } +static gboolean +check_pad_mode (GstElement * src, GstPad * pad, gpointer udata) +{ + GstPadMode curmode = GST_PAD_MODE (pad); + GstPadMode *retmode = (GstPadMode *) udata; + + /* We don't care if pads aren't activated */ + if (curmode == GST_PAD_MODE_NONE) + return TRUE; + + if (*retmode == GST_PAD_MODE_NONE) { + *retmode = curmode; + } else if (*retmode != curmode) { + GST_ERROR_OBJECT (src, "source has different scheduling mode ?"); + } + + return TRUE; +} + +static gboolean +play_item_is_pull_based (GstPlayItem * item) +{ + GstElement *src; + GstPadMode mode = GST_PAD_MODE_NONE; + + g_assert (item->main_item && item->main_item->handler + && item->main_item->handler->urisourcebin); + + src = item->main_item->handler->urisourcebin; + gst_element_foreach_src_pad (src, check_pad_mode, &mode); + + return (mode == GST_PAD_MODE_PULL); +} + +static void +emit_and_handle_about_to_finish (GstURIDecodeBin3 * uridecodebin, + GstPlayItem * item) +{ + GST_DEBUG_OBJECT (uridecodebin, "output %d , posted_about_to_finish:%d", + item->group_id, item->posted_about_to_finish); + + if (item->posted_about_to_finish) { + GST_DEBUG_OBJECT (uridecodebin, + "already handling about-to-finish for this play item"); + return; + } + + if (item != uridecodebin->input_item) { + GST_DEBUG_OBJECT (uridecodebin, "Postponing about-to-finish propagation"); + item->pending_about_to_finish = TRUE; + return; + } + + /* If the input entry is pull-based, mark all the source pads as EOS */ + if (play_item_is_pull_based (item)) { + GST_DEBUG_OBJECT (uridecodebin, "Marking play item as EOS"); + play_item_set_eos (item); + } + + item->posted_about_to_finish = TRUE; + GST_DEBUG_OBJECT (uridecodebin, "Posting about-to-finish"); + g_signal_emit (uridecodebin, + gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + + /* Note : Activation of the (potential) next entry is handled in + * gst_uri_decode_bin3_set_uri */ +} + static void db_about_to_finish_cb (GstElement * decodebin, GstURIDecodeBin3 * uridecodebin) { - if (!uridecodebin->posted_about_to_finish) { - uridecodebin->posted_about_to_finish = TRUE; - g_signal_emit (uridecodebin, - gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); - } + GST_LOG_OBJECT (uridecodebin, "about to finish from %s", + GST_OBJECT_NAME (decodebin)); + + emit_and_handle_about_to_finish (uridecodebin, uridecodebin->output_item); } static void gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec) { - g_mutex_init (&dec->lock); + GstPlayItem *item; - dec->uri = DEFAULT_PROP_URI; - dec->suburi = DEFAULT_PROP_SUBURI; dec->connection_speed = DEFAULT_CONNECTION_SPEED; dec->caps = DEFAULT_CAPS; dec->buffer_duration = DEFAULT_BUFFER_DURATION; @@ -664,6 +867,9 @@ gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec) dec->use_buffering = DEFAULT_USE_BUFFERING; dec->ring_buffer_max_size = DEFAULT_RING_BUFFER_MAX_SIZE; + g_mutex_init (&dec->play_items_lock); + g_cond_init (&dec->input_source_drained); + dec->decodebin = gst_element_factory_make ("decodebin3", NULL); gst_bin_add (GST_BIN_CAST (dec), dec->decodebin); dec->db_pad_added_id = @@ -682,18 +888,32 @@ gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec) GST_OBJECT_FLAG_SET (dec, GST_ELEMENT_FLAG_SOURCE); gst_bin_set_suppressed_flags (GST_BIN (dec), GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK); + + item = new_play_item (dec); + dec->play_items = g_list_append (dec->play_items, item); + /* The initial play item is automatically the input and output one */ + dec->input_item = dec->output_item = item; } static void -gst_uri_decode_bin3_finalize (GObject * obj) +gst_uri_decode_bin3_dispose (GObject * obj) { GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (obj); + GList *iter; - g_mutex_clear (&dec->lock); - g_free (dec->uri); - g_free (dec->suburi); + GST_DEBUG_OBJECT (obj, "Disposing"); - G_OBJECT_CLASS (parent_class)->finalize (obj); + /* Free all play items */ + for (iter = dec->play_items; iter; iter = iter->next) { + GstPlayItem *item = iter->data; + free_play_item (dec, item); + } + g_list_free (dec->play_items); + dec->play_items = NULL; + + g_mutex_clear (&dec->play_items_lock); + + G_OBJECT_CLASS (parent_class)->dispose (obj); } static GstStateChangeReturn @@ -709,31 +929,19 @@ activate_source_item (GstSourceItem * item) g_object_set (handler->urisourcebin, "uri", item->uri, NULL); if (!handler->active) { gst_bin_add ((GstBin *) handler->uridecodebin, handler->urisourcebin); - /* if (!gst_element_sync_state_with_parent (handler->urisourcebin)) */ - /* return GST_STATE_CHANGE_FAILURE; */ handler->active = TRUE; } + gst_element_sync_state_with_parent (handler->urisourcebin); + return GST_STATE_CHANGE_SUCCESS; } static void -src_pad_added_cb (GstElement * element, GstPad * pad, - GstSourceHandler * handler) +link_src_pad_to_db3 (GstURIDecodeBin3 * uridecodebin, GstSourcePad * spad) { - GstURIDecodeBin3 *uridecodebin; + GstSourceHandler *handler = spad->handler; GstPad *sinkpad = NULL; - GstPadLinkReturn res; - GstPlayItem *current_play_item; - GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - - uridecodebin = handler->uridecodebin; - current_play_item = uridecodebin->current; - - GST_DEBUG_OBJECT (uridecodebin, - "New pad %" GST_PTR_FORMAT " from source %" GST_PTR_FORMAT, pad, element); - - /* FIXME: Add probe to unify group_id and detect EOS */ /* Try to link to main sink pad only if it's from a main handler */ if (handler->is_main_source) { @@ -744,38 +952,45 @@ src_pad_added_cb (GstElement * element, GstPad * pad, } } - if (sinkpad == NULL) + if (sinkpad == NULL) { sinkpad = gst_element_request_pad_simple (uridecodebin->decodebin, "sink_%u"); - - if (sinkpad) { - GST_DEBUG_OBJECT (uridecodebin, - "Linking %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, pad, sinkpad); - res = gst_pad_link (pad, sinkpad); - gst_object_unref (sinkpad); - if (GST_PAD_LINK_FAILED (res)) - goto link_failed; + spad->db3_pad_is_request = TRUE; } + if (sinkpad) { + GstPadLinkReturn res; + GST_DEBUG_OBJECT (uridecodebin, + "Linking %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, spad->src_pad, + sinkpad); + res = gst_pad_link (spad->src_pad, sinkpad); + gst_object_unref (sinkpad); + if (GST_PAD_LINK_FAILED (res)) { + GST_ERROR_OBJECT (uridecodebin, + "failed to link pad %s:%s to decodebin, reason %s (%d)", + GST_DEBUG_PAD_NAME (spad->src_pad), gst_pad_link_get_name (res), res); + return; + } + } else { + GST_ERROR_OBJECT (uridecodebin, "Could not get a sinkpad from decodebin3"); + return; + } + + spad->db3_sink_pad = sinkpad; + /* Activate sub_item after the main source activation was finished */ - if (handler->is_main_source && current_play_item->sub_item - && !current_play_item->sub_item->handler) { - current_play_item->sub_item->handler = - new_source_handler (uridecodebin, FALSE); - ret = activate_source_item (current_play_item->sub_item); + if (handler->is_main_source && handler->play_item->sub_item + && !handler->play_item->sub_item->handler) { + GstStateChangeReturn ret; + handler->play_item->sub_item->handler = + new_source_handler (uridecodebin, handler->play_item, FALSE); + ret = activate_source_item (handler->play_item->sub_item); if (ret == GST_STATE_CHANGE_FAILURE) goto sub_item_activation_failed; } return; -link_failed: - { - GST_ERROR_OBJECT (uridecodebin, - "failed to link pad %s:%s to decodebin, reason %s (%d)", - GST_DEBUG_PAD_NAME (pad), gst_pad_link_get_name (res), res); - return; - } sub_item_activation_failed: { GST_ERROR_OBJECT (uridecodebin, @@ -784,31 +999,346 @@ sub_item_activation_failed: } } +static GList * +get_all_play_item_source_pads (GstPlayItem * item) +{ + GList *ret = NULL; + + if (item->main_item && item->main_item->handler) { + ret = g_list_copy (item->main_item->handler->sourcepads); + } + + if (item->sub_item && item->sub_item->handler) { + ret = + g_list_concat (ret, g_list_copy (item->sub_item->handler->sourcepads)); + } + + return ret; +} + +static GstSourcePad * +find_matching_source_pad (GList * candidates, GstSourcePad * target) +{ + GList *iter; + GstStream *stream = target->stream; + + GST_DEBUG_OBJECT (target->src_pad, "Find match for stream %" GST_PTR_FORMAT, + stream); + + for (iter = candidates; iter; iter = iter->next) { + GstSourcePad *cand = iter->data; + + if (!cand->db3_sink_pad) + continue; + + /* Target doesn't have a specific GstStream, return the first result */ + if (!stream) + return cand; + + if (gst_stream_get_stream_type (cand->stream) == + gst_stream_get_stream_type (stream)) + return cand; + } + + return NULL; +} + +/* PLAY_ITEMS_LOCK held + * + * Switch the input play item to the next one + */ +static void +switch_and_activate_input_locked (GstURIDecodeBin3 * uridecodebin, + GstPlayItem * new_item) +{ + GList *new_pads = get_all_play_item_source_pads (new_item); + GList *old_pads = get_all_play_item_source_pads (uridecodebin->input_item); + GList *to_activate = NULL; + GList *iternew, *iterold; + + /* Deactivate old urisourcebins first ? Problem is they might remove the pads */ + + /* Go over new item source pads and figure out a candidate replacement in */ + /* Figure out source pad matches */ + for (iternew = new_pads; iternew; iternew = iternew->next) { + GstSourcePad *new_spad = iternew->data; + GstSourcePad *old_spad = find_matching_source_pad (old_pads, new_spad); + + if (old_spad) { + GST_DEBUG_OBJECT (uridecodebin, "Relinking %s:%s from %s:%s to %s:%s", + GST_DEBUG_PAD_NAME (old_spad->db3_sink_pad), + GST_DEBUG_PAD_NAME (old_spad->src_pad), + GST_DEBUG_PAD_NAME (new_spad->src_pad)); + gst_pad_unlink (old_spad->src_pad, old_spad->db3_sink_pad); + new_spad->db3_sink_pad = old_spad->db3_sink_pad; + new_spad->db3_pad_is_request = old_spad->db3_pad_is_request; + old_spad->db3_sink_pad = NULL; + + gst_pad_link (new_spad->src_pad, new_spad->db3_sink_pad); + old_pads = g_list_remove (old_pads, old_spad); + } else { + GST_DEBUG_OBJECT (new_spad->src_pad, "Needs a new pad"); + to_activate = g_list_append (to_activate, new_spad); + } + } + + /* Remove unmatched old source pads */ + for (iterold = old_pads; iterold; iterold = iterold->next) { + GstSourcePad *old_spad = iterold->data; + if (old_spad->db3_sink_pad && old_spad->db3_pad_is_request) { + GST_DEBUG_OBJECT (uridecodebin, "Releasing no longer used db3 pad"); + gst_element_release_request_pad (uridecodebin->decodebin, + old_spad->db3_sink_pad); + old_spad->db3_sink_pad = NULL; + } + } + + /* Link new source pads */ + for (iternew = to_activate; iternew; iternew = iternew->next) { + GstSourcePad *new_spad = iternew->data; + link_src_pad_to_db3 (uridecodebin, new_spad); + } + + /* Unblock all new item source pads */ + for (iternew = new_pads; iternew; iternew = iternew->next) { + GstSourcePad *new_spad = iternew->data; + if (new_spad->block_probe_id) { + gst_pad_remove_probe (new_spad->src_pad, new_spad->block_probe_id); + new_spad->block_probe_id = 0; + } + } + g_list_free (new_pads); + g_list_free (old_pads); + + /* Deactivate old input item (by removing the source components). The final + * removal of this play item will be done once decodebin3 starts output the + * content of the new play item. */ + if (uridecodebin->input_item->main_item) { + free_source_item (uridecodebin, uridecodebin->input_item->main_item); + uridecodebin->input_item->main_item = NULL; + } + if (uridecodebin->input_item->sub_item) { + free_source_item (uridecodebin, uridecodebin->input_item->sub_item); + uridecodebin->input_item->sub_item = NULL; + } + + /* and set new one as input item */ + uridecodebin->input_item = new_item; + + if (new_item->main_item->handler->pending_buffering_msg) { + GstMessage *msg = new_item->main_item->handler->pending_buffering_msg; + new_item->main_item->handler->pending_buffering_msg = NULL; + PLAY_ITEMS_UNLOCK (uridecodebin); + GST_BIN_CLASS (parent_class)->handle_message ((GstBin *) uridecodebin, msg); + PLAY_ITEMS_LOCK (uridecodebin); + } +} + +static GstPadProbeReturn +uri_src_probe (GstPad * pad, GstPadProbeInfo * info, GstSourcePad * srcpad) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GstSourceHandler *handler = srcpad->handler; + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + GST_DEBUG_OBJECT (pad, "event %" GST_PTR_FORMAT, event); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + { + GstPad *peer; + /* Propagate the EOS *before* triggering any potential switch */ + peer = gst_pad_get_peer (pad); + if (peer) { + gst_pad_send_event (peer, event); + gst_object_unref (peer); + } + + PLAY_ITEMS_LOCK (handler->uridecodebin); + /* EOS : Mark pad as EOS */ + srcpad->saw_eos = TRUE; + /* Check if the input play item is fully EOS. If yes and there is a + * pending play item, switch to it */ + if (handler->play_item == handler->uridecodebin->input_item && + play_item_is_eos (handler->play_item)) { + g_cond_signal (&handler->uridecodebin->input_source_drained); + } + PLAY_ITEMS_UNLOCK (handler->uridecodebin); + ret = GST_PAD_PROBE_HANDLED; + break; + } + case GST_EVENT_STREAM_START: + { + GstStream *stream = NULL; + srcpad->saw_eos = FALSE; + gst_event_parse_stream (event, &stream); + if (stream) { + GST_DEBUG_OBJECT (srcpad->src_pad, "Got GstStream %" GST_PTR_FORMAT, + stream); + if (srcpad->stream) + gst_object_unref (srcpad->stream); + srcpad->stream = stream; + } + break; + } + case GST_EVENT_SEGMENT: + { + srcpad->saw_eos = FALSE; + break; + } + default: + break; + } + + return ret; +} + +static GstPadProbeReturn +uri_src_block_probe (GstPad * pad, GstPadProbeInfo * info, + GstSourcePad * srcpad) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + GstSourceHandler *handler = srcpad->handler; + GST_DEBUG_OBJECT (pad, "blocked"); + + /* We only block on buffers, buffer list and gap events. Everything else is + * dropped (sticky events will be propagated later) */ + if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info)) && + GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_GAP) { + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START) { + GstStream *stream = NULL; + gst_event_parse_stream (event, &stream); + if (stream) { + GST_DEBUG_OBJECT (srcpad->src_pad, "Got GstStream %" GST_PTR_FORMAT, + stream); + if (srcpad->stream) + gst_object_unref (srcpad->stream); + srcpad->stream = stream; + } + } + GST_LOG_OBJECT (pad, "Skiping %" GST_PTR_FORMAT, event); + return GST_PAD_PROBE_DROP; + } + + PLAY_ITEMS_LOCK (handler->uridecodebin); + if (play_item_is_eos (handler->uridecodebin->input_item)) { + GST_DEBUG_OBJECT (handler->uridecodebin, + "We can switch over to the next input item"); + switch_and_activate_input_locked (handler->uridecodebin, + handler->play_item); + ret = GST_PAD_PROBE_REMOVE; + } else if (play_item_has_all_pads (handler->play_item)) { + /* We have all expected pads for this play item but the current input + * play item isn't done yet, wait for it */ + g_cond_wait (&handler->uridecodebin->input_source_drained, + &handler->uridecodebin->play_items_lock); + if (g_atomic_int_get (&handler->uridecodebin->shutdown)) + goto shutdown; + if (play_item_is_eos (handler->uridecodebin->input_item)) { + GST_DEBUG_OBJECT (handler->uridecodebin, + "We can switch over to the next input item"); + switch_and_activate_input_locked (handler->uridecodebin, + handler->play_item); + ret = GST_PAD_PROBE_REMOVE; + } + } + + PLAY_ITEMS_UNLOCK (handler->uridecodebin); + + return ret; + + /* ERRORS */ +shutdown: + { + GST_LOG_OBJECT (pad, "Shutting down"); + PLAY_ITEMS_UNLOCK (handler->uridecodebin); + return GST_PAD_PROBE_REMOVE; + } +} + +static void +src_pad_added_cb (GstElement * element, GstPad * pad, + GstSourceHandler * handler) +{ + GstSourcePad *spad = g_slice_new0 (GstSourcePad); + GstURIDecodeBin3 *uridecodebin; + + uridecodebin = handler->uridecodebin; + + PLAY_ITEMS_LOCK (uridecodebin); + + GST_DEBUG_OBJECT (uridecodebin, + "New pad %" GST_PTR_FORMAT " from source %" GST_PTR_FORMAT, pad, element); + + /* Register the new pad information with the source handler */ + spad->handler = handler; + spad->src_pad = pad; + spad->event_probe_id = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, (GstPadProbeCallback) uri_src_probe, + spad, NULL); + + handler->sourcepads = g_list_append (handler->sourcepads, spad); + + /* Can the pad be linked straight away to db3 ? + * This can happen if: + * * It is the initial play item + * * It is part of the current input item + */ + if (handler->play_item == uridecodebin->input_item) { + GST_DEBUG_OBJECT (uridecodebin, + "Pad is part of current input item, linking"); + + link_src_pad_to_db3 (uridecodebin, spad); + PLAY_ITEMS_UNLOCK (uridecodebin); + return; + } + + /* This pad is not from the current input item. We add a blocking probe to + * wait until we block on the new urisourcebin streaming thread and can + * switch */ + GST_DEBUG_OBJECT (uridecodebin, "Blocking input pad"); + spad->block_probe_id = + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) uri_src_block_probe, spad, NULL); + PLAY_ITEMS_UNLOCK (uridecodebin); +} + +static GstSourcePad * +handler_get_source_pad (GstSourceHandler * handler, GstPad * srcpad) +{ + GList *iter; + + for (iter = handler->sourcepads; iter; iter = iter->next) { + GstSourcePad *spad = iter->data; + if (spad->src_pad == srcpad) + return spad; + } + + return NULL; +} + static void src_pad_removed_cb (GstElement * element, GstPad * pad, GstSourceHandler * handler) { GstURIDecodeBin3 *uridecodebin = handler->uridecodebin; - GstPad *peer_pad = gst_pad_get_peer (pad); + GstSourcePad *spad = handler_get_source_pad (handler, pad); - if (peer_pad) { - GstPadTemplate *templ = gst_pad_get_pad_template (peer_pad); + if (!spad) + return; - GST_DEBUG_OBJECT (uridecodebin, - "Source %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT " peer %" - GST_PTR_FORMAT, element, pad, peer_pad); + GST_DEBUG_OBJECT (uridecodebin, + "Source %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT " peer %" + GST_PTR_FORMAT, element, pad, spad->db3_sink_pad); - if (templ) { - if (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST) { - GST_DEBUG_OBJECT (uridecodebin, - "Releasing decodebin pad %" GST_PTR_FORMAT, peer_pad); - gst_element_release_request_pad (uridecodebin->decodebin, peer_pad); - } - gst_object_unref (templ); - } + if (spad->db3_sink_pad && spad->db3_pad_is_request) + gst_element_release_request_pad (uridecodebin->decodebin, + spad->db3_sink_pad); - gst_object_unref (peer_pad); - } + handler->sourcepads = g_list_remove (handler->sourcepads, spad); + g_slice_free (GstSourcePad, spad); } static void @@ -822,22 +1352,22 @@ src_source_setup_cb (GstElement * element, GstElement * source, static void src_about_to_finish_cb (GstElement * element, GstSourceHandler * handler) { - /* FIXME : check if all sources are done */ - if (!handler->uridecodebin->posted_about_to_finish) { - handler->uridecodebin->posted_about_to_finish = TRUE; - g_signal_emit (handler->uridecodebin, - gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); - } + GST_LOG_OBJECT (handler->uridecodebin, "about to finish from %s", + GST_OBJECT_NAME (element)); + + emit_and_handle_about_to_finish (handler->uridecodebin, handler->play_item); } static GstSourceHandler * -new_source_handler (GstURIDecodeBin3 * uridecodebin, gboolean is_main) +new_source_handler (GstURIDecodeBin3 * uridecodebin, GstPlayItem * item, + gboolean is_main) { GstSourceHandler *handler; handler = g_slice_new0 (GstSourceHandler); handler->uridecodebin = uridecodebin; + handler->play_item = item; handler->is_main_source = is_main; handler->urisourcebin = gst_element_factory_make ("urisourcebin", NULL); /* Set pending properties */ @@ -863,12 +1393,38 @@ new_source_handler (GstURIDecodeBin3 * uridecodebin, gboolean is_main) g_signal_connect (handler->urisourcebin, "about-to-finish", (GCallback) src_about_to_finish_cb, handler); - uridecodebin->source_handlers = - g_list_append (uridecodebin->source_handlers, handler); + handler->expected_pads = 1; return handler; } +static gboolean +source_handler_is_eos (GstSourceHandler * handler) +{ + GList *iter; + + for (iter = handler->sourcepads; iter; iter = iter->next) { + GstSourcePad *spad = iter->data; + + if (!spad->saw_eos) + return FALSE; + } + + return TRUE; +} + +static void +source_handler_set_eos (GstSourceHandler * handler) +{ + GList *iter; + + for (iter = handler->sourcepads; iter; iter = iter->next) { + GstSourcePad *spad = iter->data; + + spad->saw_eos = TRUE; + } +} + static void gst_uri_decode_bin3_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) @@ -877,19 +1433,15 @@ gst_uri_decode_bin3_set_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_URI: - if (dec->uri) - g_free (dec->uri); - dec->uri = g_value_dup_string (value); + gst_uri_decode_bin3_set_uri (dec, g_value_get_string (value)); break; case PROP_SUBURI: - if (dec->suburi) - g_free (dec->suburi); - dec->suburi = g_value_dup_string (value); + gst_uri_decode_bin3_set_suburi (dec, g_value_get_string (value)); break; case PROP_CONNECTION_SPEED: - GST_URI_DECODE_BIN3_LOCK (dec); + GST_OBJECT_LOCK (dec); dec->connection_speed = g_value_get_uint64 (value) * 1000; - GST_URI_DECODE_BIN3_UNLOCK (dec); + GST_OBJECT_UNLOCK (dec); break; case PROP_BUFFER_SIZE: dec->buffer_size = g_value_get_int (value); @@ -913,6 +1465,11 @@ gst_uri_decode_bin3_set_property (GObject * object, guint prop_id, dec->caps = g_value_dup_boxed (value); GST_OBJECT_UNLOCK (dec); break; + case PROP_INSTANT_URI: + GST_OBJECT_LOCK (dec); + dec->instant_uri = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (dec); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -928,13 +1485,18 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_URI: { - g_value_set_string (value, dec->uri); + GstPlayItem *item = dec->play_items->data; + /* Return from the head */ + if (item->main_item) + g_value_set_string (value, item->main_item->uri); + else + g_value_set_string (value, NULL); break; } case PROP_CURRENT_URI: { - if (dec->current && dec->current->main_item) { - g_value_set_string (value, dec->current->main_item->uri); + if (dec->output_item && dec->output_item->main_item) { + g_value_set_string (value, dec->output_item->main_item->uri); } else { g_value_set_string (value, NULL); } @@ -942,13 +1504,18 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id, } case PROP_SUBURI: { - g_value_set_string (value, dec->suburi); + GstPlayItem *item = dec->play_items->data; + /* Return from the head */ + if (item->sub_item) + g_value_set_string (value, item->sub_item->uri); + else + g_value_set_string (value, NULL); break; } case PROP_CURRENT_SUBURI: { - if (dec->current && dec->current->sub_item) { - g_value_set_string (value, dec->current->sub_item->uri); + if (dec->output_item && dec->output_item->sub_item) { + g_value_set_string (value, dec->output_item->sub_item->uri); } else { g_value_set_string (value, NULL); } @@ -962,9 +1529,9 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id, break; } case PROP_CONNECTION_SPEED: - GST_URI_DECODE_BIN3_LOCK (dec); + GST_OBJECT_LOCK (dec); g_value_set_uint64 (value, dec->connection_speed / 1000); - GST_URI_DECODE_BIN3_UNLOCK (dec); + GST_OBJECT_UNLOCK (dec); break; case PROP_BUFFER_SIZE: GST_OBJECT_LOCK (dec); @@ -990,6 +1557,11 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id, g_value_set_boxed (value, dec->caps); GST_OBJECT_UNLOCK (dec); break; + case PROP_INSTANT_URI: + GST_OBJECT_LOCK (dec); + g_value_set_boolean (value, dec->instant_uri); + GST_OBJECT_UNLOCK (dec); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1002,13 +1574,22 @@ free_source_handler (GstURIDecodeBin3 * uridecodebin, { GST_LOG_OBJECT (uridecodebin, "source handler %p", handler); if (handler->active) { + GList *iter; + GST_STATE_LOCK (uridecodebin); GST_LOG_OBJECT (uridecodebin, "Removing %" GST_PTR_FORMAT, handler->urisourcebin); + for (iter = handler->sourcepads; iter; iter = iter->next) { + GstSourcePad *spad = iter->data; + if (spad->block_probe_id) + gst_pad_remove_probe (spad->src_pad, spad->block_probe_id); + } gst_element_set_state (handler->urisourcebin, GST_STATE_NULL); gst_bin_remove ((GstBin *) uridecodebin, handler->urisourcebin); + GST_STATE_UNLOCK (uridecodebin); + g_list_free (handler->sourcepads); } - uridecodebin->source_handlers = - g_list_remove (uridecodebin->source_handlers, handler); + if (handler->pending_buffering_msg) + gst_message_unref (handler->pending_buffering_msg); g_slice_free (GstSourceHandler, handler); } @@ -1029,18 +1610,28 @@ free_source_item (GstURIDecodeBin3 * uridecodebin, GstSourceItem * item) GST_LOG_OBJECT (uridecodebin, "source item %p", item); if (item->handler) free_source_handler (uridecodebin, item->handler); + g_free (item->uri); g_slice_free (GstSourceItem, item); } +static void +source_item_set_uri (GstSourceItem * item, const gchar * uri) +{ + if (item->uri) + g_free (item->uri); + item->uri = g_strdup (uri); + if (item->handler) { + g_object_set (item->handler->urisourcebin, "uri", uri, NULL); + } +} + static GstPlayItem * -new_play_item (GstURIDecodeBin3 * dec, gchar * uri, gchar * suburi) +new_play_item (GstURIDecodeBin3 * dec) { GstPlayItem *item = g_slice_new0 (GstPlayItem); item->uridecodebin = dec; - item->main_item = new_source_item (dec, item, uri); - if (suburi) - item->sub_item = new_source_item (dec, item, suburi); + item->group_id = GST_GROUP_ID_INVALID; return item; } @@ -1057,6 +1648,170 @@ free_play_item (GstURIDecodeBin3 * dec, GstPlayItem * item) g_slice_free (GstPlayItem, item); } +static void +play_item_set_uri (GstPlayItem * item, const gchar * uri) +{ + if (!item->main_item) { + item->main_item = + new_source_item (item->uridecodebin, item, g_strdup (uri)); + } else { + source_item_set_uri (item->main_item, uri); + } +} + +static void +play_item_set_suburi (GstPlayItem * item, const gchar * uri) +{ + if (!item->sub_item) { + item->sub_item = new_source_item (item->uridecodebin, item, g_strdup (uri)); + } else { + source_item_set_uri (item->sub_item, uri); + } +} + +static gboolean +play_item_is_eos (GstPlayItem * item) +{ + if (item->main_item && item->main_item->handler) { + if (!source_handler_is_eos (item->main_item->handler)) + return FALSE; + } + if (item->sub_item && item->sub_item->handler) { + if (!source_handler_is_eos (item->sub_item->handler)) + return FALSE; + } + + return TRUE; +} + +/* Mark all sourcepads of a play item as EOS. Used in pull-mode */ +static void +play_item_set_eos (GstPlayItem * item) +{ + if (item->main_item && item->main_item->handler) + source_handler_set_eos (item->main_item->handler); + + if (item->sub_item && item->sub_item->handler) + source_handler_set_eos (item->sub_item->handler); +} + +static gboolean +play_item_has_all_pads (GstPlayItem * item) +{ + GstSourceHandler *handler; + + if (item->main_item && item->main_item->handler) { + handler = item->main_item->handler; + if (handler->expected_pads != g_list_length (handler->sourcepads)) + return FALSE; + } + + if (item->sub_item && item->sub_item->handler) { + handler = item->sub_item->handler; + if (handler->expected_pads != g_list_length (handler->sourcepads)) + return FALSE; + } + + return TRUE; +} + +/* Returns the next inactive play item. If none available, it will create one + * and add it to the list of play items */ +static GstPlayItem * +next_inactive_play_item (GstURIDecodeBin3 * dec) +{ + GstPlayItem *res; + GList *iter; + + for (iter = dec->play_items; iter; iter = iter->next) { + res = iter->data; + if (!res->active) + return res; + } + + GST_DEBUG_OBJECT (dec, "No inactive play items, creating a new one"); + res = new_play_item (dec); + dec->play_items = g_list_append (dec->play_items, res); + + return res; +} + +static GstPadProbeReturn +uri_src_ignore_block_probe (GstPad * pad, GstPadProbeInfo * info, + GstSourcePad * srcpad) +{ + GST_DEBUG_OBJECT (pad, "blocked"); + return GST_PAD_PROBE_OK; +} + +static void +gst_uri_decode_bin3_set_uri (GstURIDecodeBin3 * dec, const gchar * uri) +{ + GstPlayItem *item; + gboolean start_item = FALSE; + + GST_DEBUG_OBJECT (dec, "uri: %s", uri); + + item = next_inactive_play_item (dec); + play_item_set_uri (item, uri); + if (dec->instant_uri && item != dec->input_item) { + GList *old_pads = get_all_play_item_source_pads (dec->input_item); + GList *iter; + + /* Switch immediately if not the current input item */ + GST_DEBUG_OBJECT (dec, "Switching immediately"); + + /* FLUSH START all input pads */ + for (iter = old_pads; iter; iter = iter->next) { + GstSourcePad *spad = iter->data; + if (spad->db3_sink_pad) { + /* Mark all input pads as EOS */ + gst_pad_send_event (spad->db3_sink_pad, gst_event_new_flush_start ()); + } + /* Block all input source pads */ + spad->block_probe_id = + gst_pad_add_probe (spad->src_pad, GST_PAD_PROBE_TYPE_IDLE, + (GstPadProbeCallback) uri_src_ignore_block_probe, spad, NULL); + spad->saw_eos = TRUE; + } + for (iter = old_pads; iter; iter = iter->next) { + /* FLUSH_STOP all current input pads */ + GstSourcePad *spad = iter->data; + if (spad->db3_sink_pad) { + gst_pad_send_event (spad->db3_sink_pad, + gst_event_new_flush_stop (TRUE)); + } + } + start_item = TRUE; + } else if (dec->input_item->posted_about_to_finish) { + GList *iter = g_list_find (dec->play_items, dec->input_item); + /* If the current item is finishing and the new item is the one just after, + * we need to activate it */ + if (iter && iter->next && iter->next->data == item) { + GST_DEBUG_OBJECT (dec, "Starting new entry (gapless mode)"); + start_item = TRUE; + } + } + + if (start_item) { + /* Start new item */ + activate_play_item (item); + } +} + +static void +gst_uri_decode_bin3_set_suburi (GstURIDecodeBin3 * dec, const gchar * uri) +{ + GstPlayItem *item; + GST_DEBUG_OBJECT (dec, "suburi: %s", uri); + + /* FIXME : Handle instant-uri-change. Should we just apply it automatically to + * the current input item ? */ + + item = next_inactive_play_item (dec); + play_item_set_suburi (item, uri); +} + /* Sync source handlers for the given play item. Might require creating/removing some * and/or configure the handlers accordingly */ static GstStateChangeReturn @@ -1064,12 +1819,12 @@ assign_handlers_to_item (GstURIDecodeBin3 * dec, GstPlayItem * item) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - /* FIXME : Go over existing handlers to see if we can assign some to the - * given item */ + if (item->main_item == NULL) + return GST_STATE_CHANGE_FAILURE; /* Create missing handlers */ if (item->main_item->handler == NULL) { - item->main_item->handler = new_source_handler (dec, TRUE); + item->main_item->handler = new_source_handler (dec, item, TRUE); ret = activate_source_item (item->main_item); if (ret == GST_STATE_CHANGE_FAILURE) return ret; @@ -1080,40 +1835,37 @@ assign_handlers_to_item (GstURIDecodeBin3 * dec, GstPlayItem * item) /* Called to activate the next play item */ static GstStateChangeReturn -activate_next_play_item (GstURIDecodeBin3 * dec) +activate_play_item (GstPlayItem * item) { - GstPlayItem *item; GstStateChangeReturn ret; - /* If there is no current play entry, create one from the uri/suburi - * FIXME : Use a playlist API in the future */ - item = new_play_item (dec, dec->uri, dec->suburi); + GST_DEBUG_OBJECT (item->uridecodebin, "Activating play item"); - ret = assign_handlers_to_item (dec, item); - if (ret == GST_STATE_CHANGE_FAILURE) { - free_play_item (dec, item); - return ret; + ret = assign_handlers_to_item (item->uridecodebin, item); + if (ret != GST_STATE_CHANGE_FAILURE) { + item->active = TRUE; } - dec->play_items = g_list_append (dec->play_items, item); - dec->current = dec->play_items->data; - return ret; } +/* Remove all but the last play item */ static void -free_play_items (GstURIDecodeBin3 * dec) +purge_play_items (GstURIDecodeBin3 * dec) { - GList *tmp; + GST_DEBUG_OBJECT (dec, "Purging play items"); - for (tmp = dec->play_items; tmp; tmp = tmp->next) { - GstPlayItem *item = (GstPlayItem *) tmp->data; + PLAY_ITEMS_LOCK (dec); + g_cond_signal (&dec->input_source_drained); + while (dec->play_items && dec->play_items->next) { + GstPlayItem *item = dec->play_items->data; + dec->play_items = g_list_remove (dec->play_items, item); free_play_item (dec, item); } - g_list_free (dec->play_items); - dec->play_items = NULL; - dec->current = NULL; + dec->output_item = dec->input_item = dec->play_items->data; + dec->output_item->posted_about_to_finish = FALSE; + PLAY_ITEMS_UNLOCK (dec); } static GstStateChangeReturn @@ -1128,9 +1880,17 @@ gst_uri_decode_bin3_change_state (GstElement * element, g_object_set (uridecodebin->decodebin, "caps", uridecodebin->caps, NULL); break; case GST_STATE_CHANGE_READY_TO_PAUSED: - ret = activate_next_play_item (uridecodebin); + g_atomic_int_set (&uridecodebin->shutdown, 0); + ret = activate_play_item (uridecodebin->input_item); if (ret == GST_STATE_CHANGE_FAILURE) goto failure; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + PLAY_ITEMS_LOCK (uridecodebin); + g_atomic_int_set (&uridecodebin->shutdown, 1); + g_cond_signal (&uridecodebin->input_source_drained); + PLAY_ITEMS_UNLOCK (uridecodebin); + break; default: break; } @@ -1141,10 +1901,9 @@ gst_uri_decode_bin3_change_state (GstElement * element, switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: - /* FIXME: Cleanup everything */ - free_play_items (uridecodebin); - /* Free play item */ - uridecodebin->posted_about_to_finish = FALSE; + /* Remove all play items *but* the last one, which becomes the current entry */ + purge_play_items (uridecodebin); + uridecodebin->input_item->active = FALSE; break; default: break; @@ -1156,11 +1915,93 @@ gst_uri_decode_bin3_change_state (GstElement * element, failure: { if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) - free_play_items (uridecodebin); + purge_play_items (uridecodebin); return ret; } } +static GstSourceHandler * +find_source_handler_for_element (GstURIDecodeBin3 * uridecodebin, + GstObject * element) +{ + GList *iter; + + for (iter = uridecodebin->play_items; iter; iter = iter->next) { + GstPlayItem *item = iter->data; + + if (item->main_item && item->main_item->handler) { + GstSourceHandler *handler = item->main_item->handler; + + if (gst_object_has_as_ancestor (element, + (GstObject *) handler->urisourcebin)) + return handler; + } + if (item->sub_item && item->sub_item->handler) { + GstSourceHandler *handler = item->sub_item->handler; + + if (gst_object_has_as_ancestor (element, + (GstObject *) handler->urisourcebin)) + return handler; + } + } + + return NULL; +} + +static void +gst_uri_decode_bin3_handle_message (GstBin * bin, GstMessage * msg) +{ + GstURIDecodeBin3 *uridecodebin = (GstURIDecodeBin3 *) bin; + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_STREAMS_SELECTED: + { + GstSourceHandler *handler; + GST_DEBUG_OBJECT (uridecodebin, "Handle streams selected"); + PLAY_ITEMS_LOCK (uridecodebin); + /* Find the matching handler (if any) */ + if ((handler = find_source_handler_for_element (uridecodebin, msg->src))) { + handler->expected_pads = gst_message_streams_selected_get_size (msg); + GST_DEBUG_OBJECT (uridecodebin, + "Got streams-selected for %s with %d streams selected", + GST_ELEMENT_NAME (handler->urisourcebin), handler->expected_pads); + } + PLAY_ITEMS_UNLOCK (uridecodebin); + break; + } + case GST_MESSAGE_BUFFERING: + { + GstSourceHandler *handler; + GST_DEBUG_OBJECT (uridecodebin, "Handle buffering message"); + PLAY_ITEMS_LOCK (uridecodebin); + /* Find the matching handler (if any) */ + handler = find_source_handler_for_element (uridecodebin, msg->src); + if (!handler || !uridecodebin->input_item->main_item) { + gst_message_unref (msg); + msg = NULL; + } else if (handler != uridecodebin->input_item->main_item->handler) { + /* Store the message for a later time */ + if (handler->pending_buffering_msg) + gst_message_unref (handler->pending_buffering_msg); + handler->pending_buffering_msg = msg; + msg = NULL; + } else { + /* This is the active main input item, we can forward directly */ + GST_DEBUG_OBJECT (uridecodebin, + "Forwarding message for active input item"); + } + PLAY_ITEMS_UNLOCK (uridecodebin); + break; + + } + default: + break; + } + + if (msg) + GST_BIN_CLASS (parent_class)->handle_message (bin, msg); +} + static gboolean gst_uri_decodebin3_send_event (GstElement * element, GstEvent * event) { diff --git a/subprojects/gst-plugins-base/tools/gst-play.c b/subprojects/gst-plugins-base/tools/gst-play.c index c66c77adee..11378bada5 100644 --- a/subprojects/gst-plugins-base/tools/gst-play.c +++ b/subprojects/gst-plugins-base/tools/gst-play.c @@ -104,6 +104,7 @@ typedef struct gboolean buffering; gboolean is_live; + gboolean initial_file; GstState desired_state; /* as per user interaction, PAUSED or PLAYING */ @@ -111,6 +112,7 @@ typedef struct /* configuration */ gboolean gapless; + gboolean instant_uri; GstPlayTrickMode trick_mode; gdouble rate; @@ -166,8 +168,9 @@ gst_play_printf (const gchar * format, ...) static GstPlay * play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink, - gboolean gapless, gdouble initial_volume, gboolean verbose, - const gchar * flags_string, gboolean use_playbin3, gdouble start_position) + gboolean gapless, gboolean instant_uri, gdouble initial_volume, + gboolean verbose, const gchar * flags_string, gboolean use_playbin3, + gdouble start_position) { GstElement *sink, *playbin; GstPlay *play; @@ -262,6 +265,11 @@ play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink, G_CALLBACK (play_about_to_finish), play); } + play->initial_file = TRUE; + if (use_playbin3) { + play->instant_uri = instant_uri; + g_object_set (G_OBJECT (play->playbin), "instant-uri", instant_uri, NULL); + } if (initial_volume != -1) play_set_relative_volume (play, initial_volume - 1.0); @@ -745,7 +753,8 @@ play_uri (GstPlay * play, const gchar * next_uri) { gchar *loc; - gst_element_set_state (play->playbin, GST_STATE_READY); + if (!play->instant_uri || play->initial_file) + gst_element_set_state (play->playbin, GST_STATE_READY); play_reset (play); loc = play_uri_get_display_name (play, next_uri); @@ -754,23 +763,26 @@ play_uri (GstPlay * play, const gchar * next_uri) g_object_set (play->playbin, "uri", next_uri, NULL); - switch (gst_element_set_state (play->playbin, GST_STATE_PAUSED)) { - case GST_STATE_CHANGE_FAILURE: - /* ignore, we should get an error message posted on the bus */ - break; - case GST_STATE_CHANGE_NO_PREROLL: - gst_print ("Pipeline is live.\n"); - play->is_live = TRUE; - break; - case GST_STATE_CHANGE_ASYNC: - gst_print ("Prerolling...\r"); - break; - default: - break; - } + if (!play->instant_uri || play->initial_file) { + switch (gst_element_set_state (play->playbin, GST_STATE_PAUSED)) { + case GST_STATE_CHANGE_FAILURE: + /* ignore, we should get an error message posted on the bus */ + break; + case GST_STATE_CHANGE_NO_PREROLL: + gst_print ("Pipeline is live.\n"); + play->is_live = TRUE; + break; + case GST_STATE_CHANGE_ASYNC: + gst_print ("Prerolling...\r"); + break; + default: + break; + } - if (play->desired_state != GST_STATE_PAUSED) - gst_element_set_state (play->playbin, play->desired_state); + if (play->desired_state != GST_STATE_PAUSED) + gst_element_set_state (play->playbin, play->desired_state); + } + play->initial_file = FALSE; } /* returns FALSE if we have reached the end of the playlist */ @@ -1589,6 +1601,7 @@ main (int argc, char **argv) gboolean print_version = FALSE; gboolean interactive = TRUE; gboolean gapless = FALSE; + gboolean instant_uri = FALSE; gboolean shuffle = FALSE; gdouble volume = -1; gdouble start_position = 0; @@ -1619,6 +1632,8 @@ main (int argc, char **argv) N_("Audio sink to use (default is autoaudiosink)"), NULL}, {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless, N_("Enable gapless playback"), NULL}, + {"instant-uri", 0, 0, G_OPTION_ARG_NONE, &instant_uri, + N_("Enable instantaneous uri changes (only with playbin3)"), NULL}, {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle, N_("Shuffle playlist"), NULL}, {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, @@ -1754,8 +1769,9 @@ main (int argc, char **argv) shuffle_uris (uris, num); /* prepare */ - play = play_new (uris, audio_sink, video_sink, gapless, volume, verbose, - flags, use_playbin3, start_position); + play = + play_new (uris, audio_sink, video_sink, gapless, instant_uri, volume, + verbose, flags, use_playbin3, start_position); if (play == NULL) { gst_printerr