gstreamer/subprojects/gst-plugins-base/gst/playback/gsturidecodebin3.c
Jan Schmidt 71c3141672 uridecodebin3/urisourcebin: Reusability fixes
Improvements to uridecodebin3 and urisourcebin so that they are
reusable across a PAUSED->READY->PAUSED transition.

Disconnect and release decodebin3 request pads when urisourcebin
removes src pads.

In urisourcebin, make sure to remove src pads that are exposed
directly (raw pads and static typefind srcpads) when
cleaning up.

Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/768

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1100>
2021-10-10 11:55:19 +00:00

1175 lines
35 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.
*/
/* TODO/FIXME:
*
* * BUFFERING MESSAGES
* ** How/Where do we deal with buffering messages from a new/prerolling
* source ? Ideally we want to re-use the same sourcebin ?
* ** Remember last buffering messages per source handler, if the SourceEntry
* group_id is the one being currently outputted on the source ghostpads,
* 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
*/
/**
* 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 decodebin.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include <gst/gst.h>
#include <gst/gst-i18n-plugin.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;
#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 _OutputPad OutputPad;
/* A structure describing a play item, which travels through the elements
* over time. */
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;
/* Is this play item the one being currently outputted by decodebin3
* and on our source ghostpads */
gboolean currently_outputted;
};
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;
/* Last buffering information */
gint last_perc;
GstMessage *last_buffering_message;
/* 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;
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;
/* 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;
};
/* Controls an output source pad */
struct _OutputPad
{
GstURIDecodeBin3 *uridecodebin;
GstPad *target_pad;
GstPad *ghost_pad;
/* 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;
};
/**
* GstURIDecodeBin3
*
* uridecodebin3 element struct
*/
struct _GstURIDecodeBin3
{
GstBin parent_instance;
GMutex lock; /* lock for constructing */
/* Properties */
GstElement *source;
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;
gboolean use_buffering;
guint64 ring_buffer_max_size;
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) */
/* sources.
* FIXME : Replace by a more modular system later on */
GstSourceHandler *main_handler;
GstSourceHandler *sub_handler;
/* 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 */
/* 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;
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 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
};
#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
#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
enum
{
PROP_0,
PROP_URI,
PROP_CURRENT_URI,
PROP_SUBURI,
PROP_CURRENT_SUBURI,
PROP_SOURCE,
PROP_CONNECTION_SPEED,
PROP_BUFFER_SIZE,
PROP_BUFFER_DURATION,
PROP_DOWNLOAD,
PROP_USE_BUFFERING,
PROP_RING_BUFFER_MAX_SIZE,
PROP_CAPS
};
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_finalize (GObject * obj);
static GstSourceHandler *new_source_handler (GstURIDecodeBin3 * uridecodebin,
gboolean is_main);
static GstStateChangeReturn gst_uri_decode_bin3_change_state (GstElement *
element, GstStateChange transition);
static gboolean gst_uri_decodebin3_send_event (GstElement * element,
GstEvent * event);
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;
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_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;
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_SOURCE,
g_param_spec_object ("source", "Source", "Source object used",
GST_TYPE_ELEMENT, 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::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::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);
klass->select_stream = gst_uridecodebin3_select_stream;
}
static GstPadProbeReturn
db_src_probe (GstPad * pad, GstPadProbeInfo * info, OutputPad * output)
{
/* FIXME : IMPLEMENT */
/* EOS : Mark pad as EOS */
/* STREAM_START : Store group_id and check if currently active
* PlayEntry changed */
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_slice_new0 (OutputPad);
GST_LOG_OBJECT (dec, "Created output %p", output);
output->uridecodebin = dec;
output->target_pad = target_pad;
output->current_group_id = (guint) - 1;
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);
/* 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);
}
}
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 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);
}
}
static void
gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec)
{
g_mutex_init (&dec->lock);
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;
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;
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);
}
static void
gst_uri_decode_bin3_finalize (GObject * obj)
{
GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (obj);
g_mutex_clear (&dec->lock);
g_free (dec->uri);
g_free (dec->suburi);
G_OBJECT_CLASS (parent_class)->finalize (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);
/* if (!gst_element_sync_state_with_parent (handler->urisourcebin)) */
/* return GST_STATE_CHANGE_FAILURE; */
handler->active = TRUE;
}
return GST_STATE_CHANGE_SUCCESS;
}
static void
src_pad_added_cb (GstElement * element, GstPad * pad,
GstSourceHandler * handler)
{
GstURIDecodeBin3 *uridecodebin;
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) {
sinkpad = gst_element_get_static_pad (uridecodebin->decodebin, "sink");
if (gst_pad_is_linked (sinkpad)) {
gst_object_unref (sinkpad);
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;
}
/* 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 (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,
"failed to activate subtitle playback item");
return;
}
}
static void
src_pad_removed_cb (GstElement * element, GstPad * pad,
GstSourceHandler * handler)
{
GstURIDecodeBin3 *uridecodebin = handler->uridecodebin;
GstPad *peer_pad = gst_pad_get_peer (pad);
if (peer_pad) {
GstPadTemplate *templ = gst_pad_get_pad_template (peer_pad);
GST_DEBUG_OBJECT (uridecodebin,
"Source %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT " peer %"
GST_PTR_FORMAT, element, pad, peer_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);
}
gst_object_unref (peer_pad);
}
}
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)
{
/* 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);
}
}
static GstSourceHandler *
new_source_handler (GstURIDecodeBin3 * uridecodebin, gboolean is_main)
{
GstSourceHandler *handler;
handler = g_slice_new0 (GstSourceHandler);
handler->uridecodebin = uridecodebin;
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,
"use-buffering", uridecodebin->use_buffering,
"buffer-duration", uridecodebin->buffer_duration,
"buffer-size", uridecodebin->buffer_size,
"ring-buffer-max-size", uridecodebin->ring_buffer_max_size, 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);
uridecodebin->source_handlers =
g_list_append (uridecodebin->source_handlers, handler);
return handler;
}
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:
if (dec->uri)
g_free (dec->uri);
dec->uri = g_value_dup_string (value);
break;
case PROP_SUBURI:
if (dec->suburi)
g_free (dec->suburi);
dec->suburi = g_value_dup_string (value);
break;
case PROP_CONNECTION_SPEED:
GST_URI_DECODE_BIN3_LOCK (dec);
dec->connection_speed = g_value_get_uint64 (value) * 1000;
GST_URI_DECODE_BIN3_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_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;
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:
{
g_value_set_string (value, dec->uri);
break;
}
case PROP_CURRENT_URI:
{
if (dec->current && dec->current->main_item) {
g_value_set_string (value, dec->current->main_item->uri);
} else {
g_value_set_string (value, NULL);
}
break;
}
case PROP_SUBURI:
{
g_value_set_string (value, dec->suburi);
break;
}
case PROP_CURRENT_SUBURI:
{
if (dec->current && dec->current->sub_item) {
g_value_set_string (value, dec->current->sub_item->uri);
} else {
g_value_set_string (value, NULL);
}
break;
}
case PROP_SOURCE:
{
GST_OBJECT_LOCK (dec);
g_value_set_object (value, dec->source);
GST_OBJECT_UNLOCK (dec);
break;
}
case PROP_CONNECTION_SPEED:
GST_URI_DECODE_BIN3_LOCK (dec);
g_value_set_uint64 (value, dec->connection_speed / 1000);
GST_URI_DECODE_BIN3_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_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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
free_source_handler (GstURIDecodeBin3 * uridecodebin,
GstSourceHandler * handler)
{
GST_LOG_OBJECT (uridecodebin, "source handler %p", handler);
if (handler->active) {
GST_LOG_OBJECT (uridecodebin, "Removing %" GST_PTR_FORMAT,
handler->urisourcebin);
gst_element_set_state (handler->urisourcebin, GST_STATE_NULL);
gst_bin_remove ((GstBin *) uridecodebin, handler->urisourcebin);
}
uridecodebin->source_handlers =
g_list_remove (uridecodebin->source_handlers, handler);
g_slice_free (GstSourceHandler, handler);
}
static GstSourceItem *
new_source_item (GstURIDecodeBin3 * dec, GstPlayItem * item, gchar * uri)
{
GstSourceItem *sourceitem = g_slice_new0 (GstSourceItem);
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);
g_slice_free (GstSourceItem, item);
}
static GstPlayItem *
new_play_item (GstURIDecodeBin3 * dec, gchar * uri, gchar * suburi)
{
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);
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_slice_free (GstPlayItem, item);
}
/* 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;
/* FIXME : Go over existing handlers to see if we can assign some to the
* given item */
/* Create missing handlers */
if (item->main_item->handler == NULL) {
item->main_item->handler = new_source_handler (dec, TRUE);
ret = activate_source_item (item->main_item);
if (ret == GST_STATE_CHANGE_FAILURE)
return ret;
}
return ret;
}
/* Called to activate the next play item */
static GstStateChangeReturn
activate_next_play_item (GstURIDecodeBin3 * dec)
{
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);
ret = assign_handlers_to_item (dec, item);
if (ret == GST_STATE_CHANGE_FAILURE) {
free_play_item (dec, item);
return ret;
}
dec->play_items = g_list_append (dec->play_items, item);
dec->current = dec->play_items->data;
return ret;
}
static void
free_play_items (GstURIDecodeBin3 * dec)
{
GList *tmp;
for (tmp = dec->play_items; tmp; tmp = tmp->next) {
GstPlayItem *item = (GstPlayItem *) tmp->data;
free_play_item (dec, item);
}
g_list_free (dec->play_items);
dec->play_items = NULL;
}
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:
ret = activate_next_play_item (uridecodebin);
if (ret == GST_STATE_CHANGE_FAILURE)
goto failure;
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:
/* FIXME: Cleanup everything */
free_play_items (uridecodebin);
/* Free play item */
uridecodebin->posted_about_to_finish = FALSE;
break;
default:
break;
}
return ret;
/* ERRORS */
failure:
{
if (transition == GST_STATE_CHANGE_READY_TO_PAUSED)
free_play_items (uridecodebin);
return ret;
}
}
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);
}