gstreamer/subprojects/gst-plugins-base/gst/playback/gsturidecodebin3.c
Edward Hervey 18d83192be uridecodebin3: Don't use decodebin3 static sink pad
Makes the code leaner, and fixes the issue which would happen when the
urisourcebin stream that was linked to the sink pad goes away (without any
replacement).

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7949>
2024-12-10 08:47:43 +00:00

2307 lines
70 KiB
C

/* GStreamer
* Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-uridecodebin3
* @title: uridecodebin3
*
* Decodes data from a URI into raw media. It selects a source element that can
* handle the given #GstURIDecodeBin3:uri scheme and connects it to a decodebin3.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include <gst/gst.h>
#include <glib/gi18n-lib.h>
#include <gst/pbutils/missing-plugins.h>
#include "gstplay-enum.h"
#include "gstrawcaps.h"
#include "gstplaybackelements.h"
#include "gstplaybackutils.h"
#define GST_TYPE_URI_DECODE_BIN3 \
(gst_uri_decode_bin3_get_type())
#define GST_URI_DECODE_BIN3(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_URI_DECODE_BIN3,GstURIDecodeBin3))
#define GST_URI_DECODE_BIN3_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_URI_DECODE_BIN3,GstURIDecodeBin3Class))
#define GST_IS_URI_DECODE_BIN3(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_URI_DECODE_BIN3))
#define GST_IS_URI_DECODE_BIN3_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_URI_DECODE_BIN3))
#define GST_URI_DECODE_BIN3_CAST(obj) ((GstURIDecodeBin3 *) (obj))
typedef struct _GstSourceGroup GstSourceGroup;
typedef struct _GstURIDecodeBin3 GstURIDecodeBin3;
typedef struct _GstURIDecodeBin3Class GstURIDecodeBin3Class;
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.
*
* 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;
/* Main URI */
GstSourceItem *main_item;
/* Auxiliary URI */
/* FIXME : Replace by a list later */
GstSourceItem *sub_item;
/* The group_id used to identify this play item via STREAM_START events
* This is the group_id which will be used externally (i.e. rewritten
* to outgoing STREAM_START events and in emitted signals).
* The urisourcebin-specific group_id is located in GstSourceItem */
guint group_id;
/* 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 */
GstPlayItem *play_item;
gchar *uri;
/* The urisourcebin controlling this uri
* Can be NULL */
GstSourceHandler *handler;
};
/* Structure wrapping everything related to a urisourcebin */
struct _GstSourceHandler
{
GstURIDecodeBin3 *uridecodebin;
GstPlayItem *play_item;
GstElement *urisourcebin;
/* Signal handlers */
gulong pad_added_id;
gulong pad_removed_id;
gulong source_setup_id;
gulong about_to_finish_id;
/* TRUE if the controlled urisourcebin was added to uridecodebin */
gboolean active;
/* TRUE if the urisourcebin handles main item */
gboolean is_main_source;
/* buffering message stored for after switching */
GstMessage *pending_buffering_msg;
/* TRUE if urisourcebin handles stream-selection */
gboolean upstream_selected;
/* 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;
/* If we saw a redirection message from this source. */
gboolean saw_redirection;
};
/* 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 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 */
struct _OutputPad
{
GstURIDecodeBin3 *uridecodebin;
GstPad *target_pad;
GstPad *ghost_pad;
/* Downstream event probe id */
gulong probe_id;
/* 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
*
* uridecodebin3 element struct
*/
struct _GstURIDecodeBin3
{
GstBin parent_instance;
/* Properties */
guint64 connection_speed; /* In bits/sec (0 = unknown) */
GstCaps *caps;
guint64 buffer_duration; /* When buffering, buffer duration (ns) */
guint buffer_size; /* When buffering, buffer size (bytes) */
gboolean download;
gchar *download_dir;
gboolean use_buffering;
guint64 ring_buffer_max_size;
gboolean instant_uri; /* Whether URI changes should be applied immediately or not */
/* Mutex to protect play_items/input_item/output_item */
GMutex play_items_lock;
/* Notify that the input_item sources have all drained */
GCond input_source_drained;
/* 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;
/* db3 signals */
gulong db_pad_added_id;
gulong db_pad_removed_id;
gulong db_select_stream_id;
gulong db_about_to_finish_id;
/* 1 if shutting down */
gint shutdown;
GList *output_pads; /* List of OutputPad */
};
static GstStateChangeReturn activate_play_item (GstPlayItem * item);
static gint
gst_uridecodebin3_select_stream (GstURIDecodeBin3 * dbin,
GstStreamCollection * collection, GstStream * stream)
{
GST_LOG_OBJECT (dbin, "default select-stream, returning -1");
return -1;
}
struct _GstURIDecodeBin3Class
{
GstBinClass parent_class;
gint (*select_stream) (GstURIDecodeBin3 * dbin,
GstStreamCollection * collection, GstStream * stream);
};
GST_DEBUG_CATEGORY_STATIC (gst_uri_decode_bin3_debug);
#define GST_CAT_DEFAULT gst_uri_decode_bin3_debug
/* signals */
enum
{
SIGNAL_SELECT_STREAM,
SIGNAL_SOURCE_SETUP,
SIGNAL_ABOUT_TO_FINISH,
LAST_SIGNAL
};
/* properties */
#define DEFAULT_PROP_URI NULL
#define DEFAULT_PROP_SUBURI NULL
#define DEFAULT_CONNECTION_SPEED 0
#define DEFAULT_CAPS (gst_static_caps_get (&default_raw_caps))
#define DEFAULT_BUFFER_DURATION -1
#define DEFAULT_BUFFER_SIZE -1
#define DEFAULT_DOWNLOAD FALSE
#define DEFAULT_USE_BUFFERING FALSE
#define DEFAULT_RING_BUFFER_MAX_SIZE 0
#define DEFAULT_INSTANT_URI FALSE
enum
{
PROP_0,
PROP_URI,
PROP_CURRENT_URI,
PROP_SUBURI,
PROP_CURRENT_SUBURI,
PROP_CONNECTION_SPEED,
PROP_BUFFER_SIZE,
PROP_BUFFER_DURATION,
PROP_DOWNLOAD,
PROP_DOWNLOAD_DIR,
PROP_USE_BUFFERING,
PROP_RING_BUFFER_MAX_SIZE,
PROP_CAPS,
PROP_INSTANT_URI
};
static guint gst_uri_decode_bin3_signals[LAST_SIGNAL] = { 0 };
static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS);
static GstStaticPadTemplate video_src_template =
GST_STATIC_PAD_TEMPLATE ("video_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate audio_src_template =
GST_STATIC_PAD_TEMPLATE ("audio_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate text_src_template =
GST_STATIC_PAD_TEMPLATE ("text_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
GType gst_uri_decode_bin3_get_type (void);
#define gst_uri_decode_bin3_parent_class parent_class
G_DEFINE_TYPE (GstURIDecodeBin3, gst_uri_decode_bin3, GST_TYPE_BIN);
#define _do_init \
GST_DEBUG_CATEGORY_INIT (gst_uri_decode_bin3_debug, "uridecodebin3", 0, "URI decoder element 3"); \
playback_element_init (plugin);
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (uridecodebin3, "uridecodebin3",
GST_RANK_NONE, GST_TYPE_URI_DECODE_BIN3, _do_init);
#define REMOVE_SIGNAL(obj,id) \
if (id) { \
g_signal_handler_disconnect (obj, id); \
id = 0; \
}
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_dispose (GObject * obj);
static GstSourceHandler *new_source_handler (GstURIDecodeBin3 * uridecodebin,
GstPlayItem * item, gboolean is_main);
static void free_source_handler (GstURIDecodeBin3 * uridecodebin,
GstSourceHandler * item, gboolean lock_state);
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,
GValue * return_accu, const GValue * handler_return, gpointer dummy)
{
gint res = g_value_get_int (handler_return);
g_value_set_int (return_accu, res);
if (res == -1)
return TRUE;
return FALSE;
}
static void
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->dispose = gst_uri_decode_bin3_dispose;
g_object_class_install_property (gobject_class, PROP_URI,
g_param_spec_string ("uri", "URI", "URI to decode",
DEFAULT_PROP_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_CURRENT_URI,
g_param_spec_string ("current-uri", "Current URI",
"The currently playing URI", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SUBURI,
g_param_spec_string ("suburi", ".sub-URI", "Optional URI of a subtitle",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_CURRENT_SUBURI,
g_param_spec_string ("current-suburi", "Current .sub-URI",
"The currently playing URI of a subtitle",
NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
g_param_spec_uint64 ("connection-speed", "Connection Speed",
"Network connection speed in kbps (0 = unknown)",
0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
g_param_spec_int ("buffer-size", "Buffer size (bytes)",
"Buffer size when buffering streams (-1 default value)",
-1, G_MAXINT, DEFAULT_BUFFER_SIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_BUFFER_DURATION,
g_param_spec_int64 ("buffer-duration", "Buffer duration (ns)",
"Buffer duration when buffering streams (-1 default value)",
-1, G_MAXINT64, DEFAULT_BUFFER_DURATION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstURIDecodeBin3::download:
*
* For certain media type, enable download buffering.
*/
g_object_class_install_property (gobject_class, PROP_DOWNLOAD,
g_param_spec_boolean ("download", "Download",
"Attempt download buffering when buffering network streams",
DEFAULT_DOWNLOAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstURIDecodeBin3:download-dir:
*
* The directory where buffers are downloaded to, if 'download' is enabled.
* If not set (default), the XDG cache directory is used.
* Will be applied to the next 'uri' played or until the element go back to
* the PAUSED state.
*
* Since: 1.24
*/
g_object_class_install_property (gobject_class, PROP_DOWNLOAD_DIR,
g_param_spec_string ("download-dir", "Download Directory",
"The directory where buffers are downloaded to, if 'download' is enabled. "
"If not set (default), the XDG cache directory is used.",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstURIDecodeBin3::use-buffering:
*
* Emit BUFFERING messages based on low-/high-percent thresholds of the
* demuxed or parsed data.
* When download buffering is activated and used for the current media
* type, this property does nothing. Otherwise perform buffering on the
* demuxed or parsed media.
*/
g_object_class_install_property (gobject_class, PROP_USE_BUFFERING,
g_param_spec_boolean ("use-buffering", "Use Buffering",
"Perform buffering on demuxed/parsed media",
DEFAULT_USE_BUFFERING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstURIDecodeBin3::ring-buffer-max-size
*
* The maximum size of the ring buffer in kilobytes. If set to 0, the ring
* buffer is disabled. Default is 0.
*/
g_object_class_install_property (gobject_class, PROP_RING_BUFFER_MAX_SIZE,
g_param_spec_uint64 ("ring-buffer-max-size",
"Max. ring buffer size (bytes)",
"Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled)",
0, G_MAXUINT, DEFAULT_RING_BUFFER_MAX_SIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_CAPS,
g_param_spec_boxed ("caps", "Caps",
"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
* @collection: a #GstStreamCollection
* @stream: a #GstStream
*
* This signal is emitted whenever @decodebin needs to decide whether
* to expose a @stream of a given @collection.
*
* Note that the prefered way to select streams is to listen to
* GST_MESSAGE_STREAM_COLLECTION on the bus and send a
* GST_EVENT_SELECT_STREAMS with the streams the user wants.
*
* Returns: 1 if the stream should be selected, 0 if it shouldn't be selected.
* A value of -1 (default) lets @decodebin decide what to do with the stream.
* */
gst_uri_decode_bin3_signals[SIGNAL_SELECT_STREAM] =
g_signal_new ("select-stream", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstURIDecodeBin3Class, select_stream),
_gst_int_accumulator, NULL, NULL, G_TYPE_INT, 2,
GST_TYPE_STREAM_COLLECTION, GST_TYPE_STREAM);
/**
* GstURIDecodeBin3::source-setup:
* @bin: the uridecodebin.
* @source: source element
*
* This signal is emitted after a source element has been created, so
* it can be configured by setting additional properties (e.g. set a
* proxy server for an http source, or set the device and read speed for
* an audio cd source).
*/
gst_uri_decode_bin3_signals[SIGNAL_SOURCE_SETUP] =
g_signal_new ("source-setup", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
/**
* GstURIDecodeBin3::about-to-finish:
*
* This signal is emitted when the data for the selected URI is
* entirely buffered and it is safe to specify another URI.
*/
gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH] =
g_signal_new ("about-to-finish", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
gst_element_class_add_static_pad_template (gstelement_class,
&video_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&audio_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&text_src_template);
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
gst_element_class_set_static_metadata (gstelement_class,
"URI Decoder", "Generic/Bin/Decoder",
"Autoplug and decode an URI to raw media",
"Edward Hervey <edward@centricular.com>, Jan Schmidt <jan@centricular.com>");
gstelement_class->change_state = gst_uri_decode_bin3_change_state;
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
current_updated (GstURIDecodeBin3 * dec)
{
GObject *object = G_OBJECT (dec);
g_object_notify (object, "current-uri");
g_object_notify (object, "current-suburi");
}
static void
check_output_group_id (GstURIDecodeBin3 * dec)
{
GList *iter;
guint common_group_id = GST_GROUP_ID_INVALID;
gboolean notify_current = FALSE;
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);
}
notify_current = TRUE;
}
PLAY_ITEMS_UNLOCK (dec);
if (notify_current) {
/* don't hold the object lock as application could fetch some properties whose getters require this lock as well */
current_updated (dec);
}
}
static GstPadProbeReturn
db_src_probe (GstPad * pad, GstPadProbeInfo * info, OutputPad * output)
{
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 */
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
{
gboolean drop_event = FALSE;
GstPlayItem *last;
PLAY_ITEMS_LOCK (uridecodebin);
last = g_list_last (uridecodebin->play_items)->data;
/* If there is a next input, drop the EOS event */
if (uridecodebin->input_item != uridecodebin->output_item ||
uridecodebin->input_item != last) {
drop_event = TRUE;
}
PLAY_ITEMS_UNLOCK (uridecodebin);
if (drop_event) {
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;
}
static OutputPad *
add_output_pad (GstURIDecodeBin3 * dec, GstPad * target_pad)
{
OutputPad *output;
gchar *pad_name;
GstEvent *stream_start;
output = g_new0 (OutputPad, 1);
GST_LOG_OBJECT (dec, "Created output %p", output);
output->uridecodebin = dec;
output->target_pad = target_pad;
output->current_group_id = GST_GROUP_ID_INVALID;
pad_name = gst_pad_get_name (target_pad);
output->ghost_pad = gst_ghost_pad_new (pad_name, target_pad);
g_free (pad_name);
gst_pad_set_active (output->ghost_pad, TRUE);
stream_start = gst_pad_get_sticky_event (target_pad,
GST_EVENT_STREAM_START, 0);
if (stream_start) {
gst_pad_store_sticky_event (output->ghost_pad, stream_start);
gst_event_unref (stream_start);
} else {
GST_WARNING_OBJECT (target_pad,
"Exposing pad without stored stream-start event");
}
gst_element_add_pad (GST_ELEMENT (dec), output->ghost_pad);
output->probe_id =
gst_pad_add_probe (output->target_pad,
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, (GstPadProbeCallback) db_src_probe,
output, NULL);
/* FIXME: LOCK TO PROTECT PAD LIST */
dec->output_pads = g_list_append (dec->output_pads, output);
return output;
}
static void
db_pad_added_cb (GstElement * element, GstPad * pad, GstURIDecodeBin3 * dec)
{
GST_DEBUG_OBJECT (dec, "Wrapping new pad %s:%s", GST_DEBUG_PAD_NAME (pad));
if (GST_PAD_IS_SRC (pad))
add_output_pad (dec, pad);
}
static void
db_pad_removed_cb (GstElement * element, GstPad * pad, GstURIDecodeBin3 * dec)
{
GList *tmp;
OutputPad *output = NULL;
if (!GST_PAD_IS_SRC (pad))
return;
GST_DEBUG_OBJECT (dec, "pad %s:%s", GST_DEBUG_PAD_NAME (pad));
/* FIXME: LOCK for list access */
for (tmp = dec->output_pads; tmp; tmp = tmp->next) {
OutputPad *cand = (OutputPad *) tmp->data;
if (cand->target_pad == pad) {
output = cand;
dec->output_pads = g_list_delete_link (dec->output_pads, tmp);
break;
}
}
if (output) {
GST_LOG_OBJECT (element, "Removing output %p", output);
/* Remove source ghost pad */
gst_ghost_pad_set_target ((GstGhostPad *) output->ghost_pad, NULL);
gst_element_remove_pad ((GstElement *) dec, output->ghost_pad);
/* Remove event probe */
gst_pad_remove_probe (output->target_pad, output->probe_id);
g_free (output);
check_output_group_id (dec);
}
}
static gint
db_select_stream_cb (GstElement * decodebin,
GstStreamCollection * collection, GstStream * stream,
GstURIDecodeBin3 * uridecodebin)
{
gint response = -1;
g_signal_emit (uridecodebin,
gst_uri_decode_bin3_signals[SIGNAL_SELECT_STREAM], 0, collection, stream,
&response);
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)
{
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)
{
GstPlayItem *item;
dec->connection_speed = DEFAULT_CONNECTION_SPEED;
dec->caps = DEFAULT_CAPS;
dec->buffer_duration = DEFAULT_BUFFER_DURATION;
dec->buffer_size = DEFAULT_BUFFER_SIZE;
dec->download = DEFAULT_DOWNLOAD;
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 =
g_signal_connect (dec->decodebin, "pad-added",
G_CALLBACK (db_pad_added_cb), dec);
dec->db_pad_removed_id =
g_signal_connect (dec->decodebin, "pad-removed",
G_CALLBACK (db_pad_removed_cb), dec);
dec->db_select_stream_id =
g_signal_connect (dec->decodebin, "select-stream",
G_CALLBACK (db_select_stream_cb), dec);
dec->db_about_to_finish_id =
g_signal_connect (dec->decodebin, "about-to-finish",
G_CALLBACK (db_about_to_finish_cb), 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_dispose (GObject * obj)
{
GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (obj);
GList *iter;
GST_DEBUG_OBJECT (obj, "Disposing");
/* 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_clear_pointer (&dec->download_dir, g_free);
g_mutex_clear (&dec->play_items_lock);
gst_clear_caps (&dec->caps);
G_OBJECT_CLASS (parent_class)->dispose (obj);
}
static GstStateChangeReturn
activate_source_item (GstSourceItem * item)
{
GstSourceHandler *handler = item->handler;
if (handler == NULL) {
GST_WARNING ("Can't activate item without a handler");
return GST_STATE_CHANGE_FAILURE;
}
g_object_set (handler->urisourcebin, "uri", item->uri, NULL);
if (!handler->active) {
gst_bin_add ((GstBin *) handler->uridecodebin, handler->urisourcebin);
handler->active = TRUE;
}
if (!gst_element_sync_state_with_parent (handler->urisourcebin))
return GST_STATE_CHANGE_FAILURE;
return GST_STATE_CHANGE_SUCCESS;
}
static void
link_src_pad_to_db3 (GstURIDecodeBin3 * uridecodebin, GstSourcePad * spad)
{
GstSourceHandler *handler = spad->handler;
GstPad *sinkpad = NULL;
sinkpad = gst_element_request_pad_simple (uridecodebin->decodebin, "sink_%u");
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 && handler->play_item->sub_item
&& !handler->play_item->sub_item->handler) {
GstStateChangeReturn ret;
/* The state lock is taken to ensure we can atomically change the
* urisourcebin back to NULL in case of failures */
GST_STATE_LOCK (uridecodebin);
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) {
free_source_handler (uridecodebin, handler->play_item->sub_item->handler,
FALSE);
handler->play_item->sub_item->handler = NULL;
GST_STATE_UNLOCK (uridecodebin);
goto sub_item_activation_failed;
}
GST_STATE_UNLOCK (uridecodebin);
}
return;
sub_item_activation_failed:
{
GST_ERROR_OBJECT (uridecodebin,
"failed to activate subtitle playback item");
return;
}
}
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;
gboolean inactive_previous_item = old_pads == NULL;
GstMessage *pending_buffering_msg = NULL;
gboolean pending_about_to_finish = FALSE;
/* 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;
old_spad->db3_sink_pad = NULL;
/* NOTE : Pad will be linked further down */
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) {
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 existing pads */
for (iternew = new_pads; iternew; iternew = iternew->next) {
GstSourcePad *new_spad = iternew->data;
if (new_spad->db3_sink_pad)
gst_pad_link (new_spad->src_pad, new_spad->db3_sink_pad);
}
/* Request (and link) new 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;
}
/* If the previous play item was not active at all (i.e. was never linked to
* decodebin3), this one *also* becomes the output one */
if (inactive_previous_item) {
GST_DEBUG_OBJECT (uridecodebin,
"Previous play item was never activated, discarding");
uridecodebin->play_items =
g_list_remove (uridecodebin->play_items, uridecodebin->input_item);
free_play_item (uridecodebin, uridecodebin->input_item);
uridecodebin->output_item = new_item;
}
/* and set new one as input item */
uridecodebin->input_item = new_item;
pending_about_to_finish = new_item->pending_about_to_finish;
if (new_item->main_item->handler->pending_buffering_msg) {
pending_buffering_msg = new_item->main_item->handler->pending_buffering_msg;
new_item->main_item->handler->pending_buffering_msg = NULL;
}
/* If we have to post message or emit signals, it might trigger some
* re-entring actions (like setting the next URI). Make sure we release the
* lock when posting/emitting */
if (pending_buffering_msg || pending_about_to_finish) {
PLAY_ITEMS_UNLOCK (uridecodebin);
/* If the new source is already drained, propagate about-to-finish */
if (pending_about_to_finish) {
emit_and_handle_about_to_finish (uridecodebin, new_item);
}
/* Finally propagate pending buffering message */
if (pending_buffering_msg) {
GST_DEBUG_OBJECT (uridecodebin,
"Posting pending buffering message %" GST_PTR_FORMAT,
pending_buffering_msg);
GST_BIN_CLASS (parent_class)->handle_message ((GstBin *) uridecodebin,
pending_buffering_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);
} else {
/* No peer, just drop it (since we're returning HANDLED below) */
gst_event_unref (event);
}
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_broadcast (&handler->uridecodebin->input_source_drained);
}
PLAY_ITEMS_UNLOCK (handler->uridecodebin);
ret = GST_PAD_PROBE_HANDLED;
break;
}
case GST_EVENT_STREAM_START:
{
GstStream *stream = NULL;
GstQuery *q = gst_query_new_selectable ();
guint group_id = GST_GROUP_ID_INVALID;
srcpad->saw_eos = FALSE;
gst_event_parse_group_id (event, &group_id);
/* Unify group id */
if (handler->play_item->group_id == GST_GROUP_ID_INVALID) {
GST_DEBUG_OBJECT (pad,
"Setting play item to group_id %" G_GUINT32_FORMAT, group_id);
handler->play_item->group_id = group_id;
} else if (handler->play_item->group_id != group_id) {
GST_DEBUG_OBJECT (pad, "Updating event group-id to %" G_GUINT32_FORMAT,
handler->play_item->group_id);
event = gst_event_make_writable (event);
GST_PAD_PROBE_INFO_DATA (info) = event;
gst_event_set_group_id (event, handler->play_item->group_id);
}
gst_event_parse_stream (event, &stream);
if (stream) {
GST_DEBUG_OBJECT (srcpad->src_pad, "Got GstStream %" GST_PTR_FORMAT,
stream);
gst_object_replace ((GstObject **) & srcpad->stream,
(GstObject *) stream);
gst_object_unref (stream);
}
/* Remember whether upstream is selectable or not */
if (gst_pad_query (pad, q)) {
PLAY_ITEMS_LOCK (handler->uridecodebin);
gst_query_parse_selectable (q, &handler->upstream_selected);
GST_DEBUG_OBJECT (srcpad->src_pad, "Upstream is selectable : %d",
handler->upstream_selected);
PLAY_ITEMS_UNLOCK (handler->uridecodebin);
}
gst_query_unref (q);
break;
}
case GST_EVENT_STREAM_COLLECTION:
{
GstStreamCollection *collection = NULL;
PLAY_ITEMS_LOCK (handler->uridecodebin);
if (!handler->upstream_selected) {
gst_event_parse_stream_collection (event, &collection);
if (collection) {
GST_DEBUG_OBJECT (srcpad->src_pad, "Seen collection with %d streams",
gst_stream_collection_get_size (collection));
if (handler->expected_pads == 1) {
handler->expected_pads =
gst_stream_collection_get_size (collection);
}
gst_object_unref (collection);
}
}
PLAY_ITEMS_UNLOCK (handler->uridecodebin);
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;
/* We only block on buffers, buffer list and gap events. Everything else is
* passed to the regular (non-blocking) probe */
if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info)) &&
GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_GAP) {
return GST_PAD_PROBE_PASS;
}
GST_DEBUG_OBJECT (pad, "blocking on buffer or gap");
PLAY_ITEMS_LOCK (handler->uridecodebin);
if (srcpad->block_probe_id == 0) {
GST_DEBUG_OBJECT (pad, "pad has already been unblocked");
ret = GST_PAD_PROBE_REMOVE;
} else 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 */
GST_DEBUG_OBJECT (pad, "Waiting for input source to be drained");
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");
/* We are shutting down, we both want to remove this probe and propagate a
* GST_FLOW_FLUSHING upstream (to cause tasks to stop) */
if (srcpad->block_probe_id)
gst_pad_remove_probe (pad, srcpad->block_probe_id);
srcpad->block_probe_id = 0;
PLAY_ITEMS_UNLOCK (handler->uridecodebin);
GST_PAD_PROBE_INFO_FLOW_RETURN (info) = GST_FLOW_FLUSHING;
gst_mini_object_unref (GST_PAD_PROBE_INFO_DATA (info));
return GST_PAD_PROBE_HANDLED;
}
}
static void
src_pad_added_cb (GstElement * element, GstPad * pad,
GstSourceHandler * handler)
{
GstSourcePad *spad = g_new0 (GstSourcePad, 1);
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;
GstSourcePad *spad = handler_get_source_pad (handler, pad);
if (!spad)
return;
GST_DEBUG_OBJECT (uridecodebin,
"Source %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT " peer %"
GST_PTR_FORMAT, element, pad, spad->db3_sink_pad);
if (spad->db3_sink_pad)
gst_element_release_request_pad (uridecodebin->decodebin,
spad->db3_sink_pad);
if (spad->stream)
gst_object_unref (spad->stream);
handler->sourcepads = g_list_remove (handler->sourcepads, spad);
g_free (spad);
}
static void
src_source_setup_cb (GstElement * element, GstElement * source,
GstSourceHandler * handler)
{
g_signal_emit (handler->uridecodebin,
gst_uri_decode_bin3_signals[SIGNAL_SOURCE_SETUP], 0, source, NULL);
}
static void
src_about_to_finish_cb (GstElement * element, GstSourceHandler * handler)
{
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, GstPlayItem * item,
gboolean is_main)
{
GstSourceHandler *handler;
handler = g_new0 (GstSourceHandler, 1);
handler->uridecodebin = uridecodebin;
handler->play_item = item;
handler->is_main_source = is_main;
handler->urisourcebin = gst_element_factory_make ("urisourcebin", NULL);
/* Set pending properties */
g_object_set (handler->urisourcebin,
"connection-speed", uridecodebin->connection_speed / 1000,
"download", uridecodebin->download,
"download-dir", uridecodebin->download_dir,
"use-buffering", uridecodebin->use_buffering,
"buffer-duration", uridecodebin->buffer_duration,
"buffer-size", uridecodebin->buffer_size,
"ring-buffer-max-size", uridecodebin->ring_buffer_max_size,
"parse-streams", TRUE, NULL);
handler->pad_added_id =
g_signal_connect (handler->urisourcebin, "pad-added",
(GCallback) src_pad_added_cb, handler);
handler->pad_removed_id =
g_signal_connect (handler->urisourcebin, "pad-removed",
(GCallback) src_pad_removed_cb, handler);
handler->source_setup_id =
g_signal_connect (handler->urisourcebin, "source-setup",
(GCallback) src_source_setup_cb, handler);
handler->about_to_finish_id =
g_signal_connect (handler->urisourcebin, "about-to-finish",
(GCallback) src_about_to_finish_cb, 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)
{
GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (object);
switch (prop_id) {
case PROP_URI:
gst_uri_decode_bin3_set_uri (dec, g_value_get_string (value));
break;
case PROP_SUBURI:
gst_uri_decode_bin3_set_suburi (dec, g_value_get_string (value));
break;
case PROP_CONNECTION_SPEED:
GST_OBJECT_LOCK (dec);
dec->connection_speed = g_value_get_uint64 (value) * 1000;
GST_OBJECT_UNLOCK (dec);
break;
case PROP_BUFFER_SIZE:
dec->buffer_size = g_value_get_int (value);
break;
case PROP_BUFFER_DURATION:
dec->buffer_duration = g_value_get_int64 (value);
break;
case PROP_DOWNLOAD:
dec->download = g_value_get_boolean (value);
break;
case PROP_DOWNLOAD_DIR:
dec->download_dir = g_value_dup_string (value);
break;
case PROP_USE_BUFFERING:
dec->use_buffering = g_value_get_boolean (value);
break;
case PROP_RING_BUFFER_MAX_SIZE:
dec->ring_buffer_max_size = g_value_get_uint64 (value);
break;
case PROP_CAPS:
GST_OBJECT_LOCK (dec);
if (dec->caps)
gst_caps_unref (dec->caps);
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;
}
}
static void
gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (object);
switch (prop_id) {
case PROP_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->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);
}
break;
}
case PROP_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->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);
}
break;
}
case PROP_CONNECTION_SPEED:
GST_OBJECT_LOCK (dec);
g_value_set_uint64 (value, dec->connection_speed / 1000);
GST_OBJECT_UNLOCK (dec);
break;
case PROP_BUFFER_SIZE:
GST_OBJECT_LOCK (dec);
g_value_set_int (value, dec->buffer_size);
GST_OBJECT_UNLOCK (dec);
break;
case PROP_BUFFER_DURATION:
GST_OBJECT_LOCK (dec);
g_value_set_int64 (value, dec->buffer_duration);
GST_OBJECT_UNLOCK (dec);
break;
case PROP_DOWNLOAD:
g_value_set_boolean (value, dec->download);
break;
case PROP_DOWNLOAD_DIR:
g_value_set_string (value, dec->download_dir);
break;
case PROP_USE_BUFFERING:
g_value_set_boolean (value, dec->use_buffering);
break;
case PROP_RING_BUFFER_MAX_SIZE:
g_value_set_uint64 (value, dec->ring_buffer_max_size);
break;
case PROP_CAPS:
GST_OBJECT_LOCK (dec);
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;
}
}
/* lock_state: TRUE if the STATE LOCK should be taken. Set to FALSE if the
* caller already has taken it */
static void
free_source_handler (GstURIDecodeBin3 * uridecodebin,
GstSourceHandler * handler, gboolean lock_state)
{
GST_LOG_OBJECT (uridecodebin, "source handler %p", handler);
if (handler->active) {
GList *iter;
if (lock_state)
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);
if (lock_state)
GST_STATE_UNLOCK (uridecodebin);
g_list_free (handler->sourcepads);
}
if (handler->pending_buffering_msg)
gst_message_unref (handler->pending_buffering_msg);
g_free (handler);
}
static GstSourceItem *
new_source_item (GstURIDecodeBin3 * dec, GstPlayItem * item, gchar * uri)
{
GstSourceItem *sourceitem = g_new0 (GstSourceItem, 1);
sourceitem->play_item = item;
sourceitem->uri = uri;
return sourceitem;
}
static void
free_source_item (GstURIDecodeBin3 * uridecodebin, GstSourceItem * item)
{
GST_LOG_OBJECT (uridecodebin, "source item %p", item);
if (item->handler)
free_source_handler (uridecodebin, item->handler, TRUE);
g_free (item->uri);
g_free (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)
{
GstPlayItem *item = g_new0 (GstPlayItem, 1);
item->uridecodebin = dec;
item->group_id = GST_GROUP_ID_INVALID;
return item;
}
static void
free_play_item (GstURIDecodeBin3 * dec, GstPlayItem * item)
{
GST_LOG_OBJECT (dec, "play item %p", item);
if (item->main_item)
free_source_item (dec, item->main_item);
if (item->sub_item)
free_source_item (dec, item->sub_item);
g_free (item);
}
static void
play_item_set_uri (GstPlayItem * item, const gchar * uri)
{
if (!uri)
return;
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 (!uri) {
if (item->sub_item) {
free_source_item (item->uridecodebin, item->sub_item);
item->sub_item = NULL;
}
return;
}
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);
PLAY_ITEMS_LOCK (dec);
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;
}
}
PLAY_ITEMS_UNLOCK (dec);
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 ? */
if (dec->input_item->posted_about_to_finish) {
/* WARNING : Setting sub-uri in gapless mode is unreliable */
GST_ELEMENT_WARNING (dec, CORE, NOT_IMPLEMENTED,
("Setting sub-uri in gapless mode is not handled"),
("Setting sub-uri in gapless mode is not implemented"));
} else {
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
assign_handlers_to_item (GstURIDecodeBin3 * dec, GstPlayItem * item)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
if (item->main_item == NULL)
return GST_STATE_CHANGE_FAILURE;
/* Create missing handlers */
if (item->main_item->handler == NULL) {
/* The state lock is taken to ensure we can atomically change the
* urisourcebin back to NULL in case of failures */
GST_STATE_LOCK (dec);
item->main_item->handler = new_source_handler (dec, item, TRUE);
ret = activate_source_item (item->main_item);
if (ret == GST_STATE_CHANGE_FAILURE) {
free_source_handler (dec, item->main_item->handler, FALSE);
item->main_item->handler = NULL;
}
GST_STATE_UNLOCK (dec);
}
return ret;
}
/* Called to activate the next play item */
static GstStateChangeReturn
activate_play_item (GstPlayItem * item)
{
GstStateChangeReturn ret;
GST_DEBUG_OBJECT (item->uridecodebin, "Activating play item");
ret = assign_handlers_to_item (item->uridecodebin, item);
if (ret != GST_STATE_CHANGE_FAILURE) {
item->active = TRUE;
}
return ret;
}
/* Remove all but the last play item */
static void
purge_play_items (GstURIDecodeBin3 * dec)
{
GST_DEBUG_OBJECT (dec, "Purging play items");
PLAY_ITEMS_LOCK (dec);
g_cond_broadcast (&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);
}
dec->output_item = dec->input_item = dec->play_items->data;
dec->output_item->posted_about_to_finish = FALSE;
PLAY_ITEMS_UNLOCK (dec);
}
static GstStateChangeReturn
gst_uri_decode_bin3_change_state (GstElement * element,
GstStateChange transition)
{
GstStateChangeReturn ret;
GstURIDecodeBin3 *uridecodebin = (GstURIDecodeBin3 *) element;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
g_object_set (uridecodebin->decodebin, "caps", uridecodebin->caps, NULL);
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
g_atomic_int_set (&uridecodebin->shutdown, 0);
ret = activate_play_item (uridecodebin->input_item);
current_updated (uridecodebin);
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_broadcast (&uridecodebin->input_source_drained);
PLAY_ITEMS_UNLOCK (uridecodebin);
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE)
goto failure;
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
/* 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;
}
return ret;
/* ERRORS */
failure:
{
if (transition == GST_STATE_CHANGE_READY_TO_PAUSED)
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 GstMessage *
gst_uri_decode_bin3_handle_redirection (GstURIDecodeBin3 * uridecodebin,
GstMessage * message, const GstStructure * details)
{
gchar *uri = NULL;
GstSourceHandler *handler;
const gchar *location;
gchar *current_uri;
PLAY_ITEMS_LOCK (uridecodebin);
/* Find the matching handler (if any) */
handler = find_source_handler_for_element (uridecodebin, message->src);
if (!handler || !handler->play_item || !handler->play_item->main_item)
goto beach;
current_uri = handler->play_item->main_item->uri;
location = gst_structure_get_string ((GstStructure *) details,
"redirect-location");
GST_DEBUG_OBJECT (uridecodebin, "Handle redirection message from '%s' to '%s",
current_uri, location);
if (gst_uri_is_valid (location)) {
uri = g_strdup (location);
} else if (current_uri) {
uri = gst_uri_join_strings (current_uri, location);
}
if (!uri)
goto beach;
if (g_strcmp0 (current_uri, uri)) {
gst_message_unref (message);
message = NULL;
/* We only want to handle the redirection once */
if (!handler->saw_redirection) {
gboolean was_instant = uridecodebin->instant_uri;
handler->saw_redirection = TRUE;
GST_DEBUG_OBJECT (uridecodebin, "Doing instant switch to '%s'", uri);
uridecodebin->instant_uri = TRUE;
/* Force instant switch */
PLAY_ITEMS_UNLOCK (uridecodebin);
gst_uri_decode_bin3_set_uri (uridecodebin, uri);
PLAY_ITEMS_LOCK (uridecodebin);
uridecodebin->instant_uri = was_instant;
}
}
g_free (uri);
beach:
PLAY_ITEMS_UNLOCK (uridecodebin);
return message;
}
static GstMessage *
update_message_with_uri (GstURIDecodeBin3 * uridecodebin, GstMessage * msg)
{
gchar *uri = NULL;
gboolean unlock_after = FALSE;
if (gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg),
(GstObject *) uridecodebin->decodebin)) {
uri = uridecodebin->output_item->main_item->uri;
} else {
GstSourceHandler *handler;
PLAY_ITEMS_LOCK (uridecodebin);
unlock_after = TRUE;
/* Find the matching handler (if any) */
if ((handler = find_source_handler_for_element (uridecodebin, msg->src))) {
uri = handler->play_item->main_item->uri;
}
}
if (uri) {
GstStructure *details;
msg = gst_message_make_writable (msg);
details = gst_message_writable_details (msg);
if (details) {
gst_structure_set (details, "uri", G_TYPE_STRING, uri, NULL);
}
}
if (unlock_after)
PLAY_ITEMS_UNLOCK (uridecodebin);
return msg;
}
static void
gst_uri_decode_bin3_handle_message (GstBin * bin, GstMessage * msg)
{
GstURIDecodeBin3 *uridecodebin = (GstURIDecodeBin3 *) bin;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_STREAM_COLLECTION:
{
GstSourceHandler *handler;
GST_DEBUG_OBJECT (uridecodebin, "Handle stream collection");
PLAY_ITEMS_LOCK (uridecodebin);
/* Find the matching handler (if any) */
if ((handler = find_source_handler_for_element (uridecodebin, msg->src))) {
gboolean selectable = FALSE;
GstQuery *query = gst_query_new_selectable ();
/* We only want to forward this message if the source is selectable,
* else we will let decodebin3 do its emission. */
if (gst_element_query ((GstElement *) msg->src, query)) {
gst_query_parse_selectable (query, &selectable);
GST_DEBUG_OBJECT (uridecodebin,
"%s is selectable : %d",
GST_ELEMENT_NAME (handler->urisourcebin), selectable);
}
gst_query_unref (query);
if (!selectable) {
GST_DEBUG_OBJECT (uridecodebin,
"%s doesn't handle selection, dropping stream-collection message",
GST_ELEMENT_NAME (handler->urisourcebin));
gst_message_unref (msg);
msg = NULL;
}
}
PLAY_ITEMS_UNLOCK (uridecodebin);
break;
}
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) {
GST_LOG_OBJECT (uridecodebin, "No handler for message, dropping it");
gst_message_unref (msg);
msg = NULL;
} else if (!uridecodebin->input_item->main_item
|| handler != uridecodebin->input_item->main_item->handler) {
GST_LOG_OBJECT (uridecodebin,
"Handler isn't active input item, storing message");
/* 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;
}
case GST_MESSAGE_ERROR:
{
const GstStructure *details = NULL;
gst_message_parse_error_details (msg, &details);
if (details && gst_structure_has_field (details, "redirect-location"))
msg =
gst_uri_decode_bin3_handle_redirection (uridecodebin, msg, details);
if (msg)
msg = update_message_with_uri (uridecodebin, msg);
break;
}
case GST_MESSAGE_WARNING:
case GST_MESSAGE_INFO:
msg = update_message_with_uri (uridecodebin, msg);
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)
{
GstURIDecodeBin3 *self = GST_URI_DECODE_BIN3 (element);
if (GST_EVENT_IS_UPSTREAM (event) && self->decodebin)
return gst_element_send_event (self->decodebin, event);
return GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
}