mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 12:11:13 +00:00
1063 lines
30 KiB
C
1063 lines
30 KiB
C
/* GStreamer
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1
|
|
*
|
|
* Copyright (C) 2013 Google Inc. All rights reserved.
|
|
* Copyright (C) 2013 Orange
|
|
* Copyright (C) 2013-2017 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
|
|
* Copyright (C) 2015, 2016 Igalia, S.L
|
|
* Copyright (C) 2015, 2016 Metrological Group B.V.
|
|
* Copyright (C) 2022, 2023 Collabora Ltd.
|
|
*
|
|
* 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:gstmediasource
|
|
* @title: GstMediaSource
|
|
* @short_description: Media Source
|
|
* @symbols:
|
|
* - GstMediaSource
|
|
*
|
|
* #GstMediaSource is the entry point into the W3C Media Source API. It offers
|
|
* functionality similar to #GstAppSrc for client-side web or JavaScript
|
|
* applications decoupling the source of media from its processing and playback.
|
|
*
|
|
* To interact with a Media Source, connect it to a #GstMseSrc that is in some
|
|
* #GstPipeline using gst_media_source_attach(). Then create at least one
|
|
* #GstSourceBuffer using gst_media_source_add_source_buffer(). Finally, feed
|
|
* some media data to the Source Buffer(s) using
|
|
* gst_source_buffer_append_buffer() and play the pipeline.
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
|
|
/**
|
|
* GstMediaSource:
|
|
* Since: 1.24
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <gst/mse/mse-enumtypes.h>
|
|
#include "gstmediasource.h"
|
|
#include "gstmediasource-private.h"
|
|
|
|
#include "gstmselogging-private.h"
|
|
#include "gstmsemediatype-private.h"
|
|
#include "gstsourcebuffer-private.h"
|
|
#include "gstsourcebufferlist-private.h"
|
|
|
|
#include "gstmsesrc.h"
|
|
#include "gstmsesrc-private.h"
|
|
|
|
G_DEFINE_TYPE (GstMediaSource, gst_media_source, GST_TYPE_OBJECT);
|
|
G_DEFINE_QUARK (gst_media_source_error_quark, gst_media_source_error);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_SOURCE_BUFFERS,
|
|
PROP_ACTIVE_SOURCE_BUFFERS,
|
|
PROP_READY_STATE,
|
|
PROP_POSITION,
|
|
PROP_DURATION,
|
|
|
|
N_PROPS,
|
|
};
|
|
|
|
typedef enum
|
|
{
|
|
ON_SOURCE_OPEN,
|
|
ON_SOURCE_ENDED,
|
|
ON_SOURCE_CLOSE,
|
|
|
|
N_SIGNALS,
|
|
} MediaSourceEvent;
|
|
|
|
typedef struct
|
|
{
|
|
GstDataQueueItem item;
|
|
MediaSourceEvent event;
|
|
} MediaSourceEventItem;
|
|
|
|
static GParamSpec *properties[N_PROPS];
|
|
static guint signals[N_SIGNALS];
|
|
|
|
#define DEFAULT_READY_STATE GST_MEDIA_SOURCE_READY_STATE_CLOSED
|
|
#define DEFAULT_POSITION GST_CLOCK_TIME_NONE
|
|
#define DEFAULT_DURATION GST_CLOCK_TIME_NONE
|
|
|
|
static void rebuild_active_source_buffers (GstMediaSource * self);
|
|
|
|
/**
|
|
* gst_media_source_is_type_supported:
|
|
* @type: (transfer none): A MIME type value
|
|
*
|
|
* Determines whether the current Media Source configuration can process media
|
|
* of the supplied @type.
|
|
*
|
|
* Returns: `TRUE` when supported, `FALSE` otherwise
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
gboolean
|
|
gst_media_source_is_type_supported (const gchar * type)
|
|
{
|
|
gst_mse_init_logging ();
|
|
g_return_val_if_fail (type != NULL, FALSE);
|
|
|
|
if (g_strcmp0 (type, "") == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
GstMediaSourceMediaType media_type = GST_MEDIA_SOURCE_MEDIA_TYPE_INIT;
|
|
if (!gst_media_source_media_type_parse (&media_type, type)) {
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean supported = gst_media_source_media_type_is_supported (&media_type);
|
|
|
|
gst_media_source_media_type_reset (&media_type);
|
|
|
|
return supported;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_new:
|
|
*
|
|
* Creates a new #GstMediaSource instance. The instance is in the
|
|
* %GST_MEDIA_SOURCE_READY_STATE_CLOSED state and is not associated with any
|
|
* media player.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-constructor)
|
|
*
|
|
* Returns: (transfer full): a new #GstMediaSource instance
|
|
* Since: 1.24
|
|
*/
|
|
GstMediaSource *
|
|
gst_media_source_new (void)
|
|
{
|
|
gst_mse_init_logging ();
|
|
return g_object_ref_sink (g_object_new (GST_TYPE_MEDIA_SOURCE, NULL));
|
|
}
|
|
|
|
static inline void
|
|
empty_buffers (GstMediaSource * self)
|
|
{
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i);
|
|
if (buf == NULL) {
|
|
break;
|
|
}
|
|
gst_object_unparent (GST_OBJECT_CAST (buf));
|
|
gst_object_unref (buf);
|
|
}
|
|
gst_source_buffer_list_remove_all (self->buffers);
|
|
}
|
|
|
|
static void
|
|
gst_media_source_dispose (GObject * object)
|
|
{
|
|
GstMediaSource *self = (GstMediaSource *) object;
|
|
|
|
gst_media_source_detach (self);
|
|
|
|
g_clear_object (&self->active_buffers);
|
|
|
|
if (self->buffers) {
|
|
empty_buffers (self);
|
|
}
|
|
gst_clear_object (&self->buffers);
|
|
|
|
gst_clear_object (&self->event_queue);
|
|
|
|
G_OBJECT_CLASS (gst_media_source_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_media_source_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstMediaSource *self = GST_MEDIA_SOURCE (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_SOURCE_BUFFERS:
|
|
g_value_take_object (value, gst_media_source_get_source_buffers (self));
|
|
break;
|
|
case PROP_ACTIVE_SOURCE_BUFFERS:
|
|
g_value_take_object (value,
|
|
gst_media_source_get_active_source_buffers (self));
|
|
break;
|
|
case PROP_READY_STATE:
|
|
g_value_set_enum (value, gst_media_source_get_ready_state (self));
|
|
break;
|
|
case PROP_POSITION:
|
|
g_value_set_uint64 (value, gst_media_source_get_position (self));
|
|
break;
|
|
case PROP_DURATION:
|
|
g_value_set_uint64 (value, gst_media_source_get_duration (self));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_media_source_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstMediaSource *self = GST_MEDIA_SOURCE (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DURATION:{
|
|
GstClockTime duration = (GstClockTime) g_value_get_uint64 (value);
|
|
gst_media_source_set_duration (self, duration, NULL);
|
|
break;
|
|
}
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_media_source_class_init (GstMediaSourceClass * klass)
|
|
{
|
|
GObjectClass *oclass = G_OBJECT_CLASS (klass);
|
|
|
|
oclass->dispose = GST_DEBUG_FUNCPTR (gst_media_source_dispose);
|
|
oclass->get_property = GST_DEBUG_FUNCPTR (gst_media_source_get_property);
|
|
oclass->set_property = GST_DEBUG_FUNCPTR (gst_media_source_set_property);
|
|
|
|
/**
|
|
* GstMediaSource:source-buffers:
|
|
*
|
|
* A #GstSourceBufferList of every #GstSourceBuffer in this Media Source
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-sourcebuffers)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
properties[PROP_SOURCE_BUFFERS] = g_param_spec_object ("source-buffers",
|
|
"Source Buffers",
|
|
"A SourceBufferList of all SourceBuffers in this Media Source",
|
|
GST_TYPE_SOURCE_BUFFER_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GstMediaSource:active-source-buffers:
|
|
*
|
|
* A #GstSourceBufferList of every #GstSourceBuffer in this Media Source that
|
|
* is considered active
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-activesourcebuffers)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
properties[PROP_ACTIVE_SOURCE_BUFFERS] =
|
|
g_param_spec_object ("active-source-buffers", "Active Source Buffers",
|
|
"A SourceBufferList of all SourceBuffers that are active in this Media Source",
|
|
GST_TYPE_SOURCE_BUFFER_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GstMediaSource:ready-state:
|
|
*
|
|
* The Ready State of the Media Source
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
properties[PROP_READY_STATE] = g_param_spec_enum ("ready-state",
|
|
"Ready State",
|
|
"The Ready State of the Media Source",
|
|
GST_TYPE_MEDIA_SOURCE_READY_STATE, DEFAULT_READY_STATE, G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GstMediaSource:position:
|
|
*
|
|
* The position of the player consuming from the Media Source
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
properties[PROP_POSITION] = g_param_spec_uint64 ("position",
|
|
"Position",
|
|
"The Position of the Media Source as a GstClockTime",
|
|
GST_CLOCK_TIME_NONE, G_MAXUINT64, DEFAULT_DURATION, G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GstMediaSource:duration:
|
|
*
|
|
* The Duration of the Media Source as a #GstClockTime
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
properties[PROP_DURATION] = g_param_spec_uint64 ("duration",
|
|
"Duration",
|
|
"The Duration of the Media Source as a GstClockTime",
|
|
GST_CLOCK_TIME_NONE, G_MAXUINT64, DEFAULT_DURATION, G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (oclass, N_PROPS, properties);
|
|
|
|
/**
|
|
* GstMediaSource::on-source-open:
|
|
* @self: The #GstMediaSource that has just opened
|
|
*
|
|
* Emitted when @self has been opened.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceopen)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
signals[ON_SOURCE_OPEN] = g_signal_new ("on-source-open",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
|
|
|
/**
|
|
* GstMediaSource::on-source-ended:
|
|
* @self: The #GstMediaSource that has just ended
|
|
*
|
|
* Emitted when @self has ended, normally through
|
|
* gst_media_source_end_of_stream().
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceended)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
signals[ON_SOURCE_ENDED] = g_signal_new ("on-source-ended",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
|
|
|
/**
|
|
* GstMediaSource::on-source-closed:
|
|
* @self: The #GstMediaSource that has just closed
|
|
*
|
|
* Emitted when @self has closed, normally when detached from a #GstMseSrc.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceclose)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
signals[ON_SOURCE_CLOSE] = g_signal_new ("on-source-close",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
|
|
|
}
|
|
|
|
static inline void
|
|
reset_live_seekable_range (GstMediaSource * self)
|
|
{
|
|
self->live_seekable_range.start = 0;
|
|
self->live_seekable_range.end = 0;
|
|
}
|
|
|
|
static inline gboolean
|
|
is_updating (GstMediaSource * self)
|
|
{
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i);
|
|
if (buf == NULL)
|
|
break;
|
|
gboolean updating = gst_source_buffer_get_updating (buf);
|
|
gst_object_unref (buf);
|
|
if (updating) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static inline gboolean
|
|
is_attached (GstMediaSource * self)
|
|
{
|
|
return GST_IS_MSE_SRC (self->element);
|
|
}
|
|
|
|
static inline void
|
|
network_error (GstMediaSource * self)
|
|
{
|
|
if (is_attached (self)) {
|
|
gst_mse_src_network_error (self->element);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
decode_error (GstMediaSource * self)
|
|
{
|
|
if (is_attached (self)) {
|
|
gst_mse_src_decode_error (self->element);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
update_duration (GstMediaSource * self)
|
|
{
|
|
if (is_attached (self)) {
|
|
gst_mse_src_set_duration (self->element, self->duration);
|
|
}
|
|
}
|
|
|
|
static void
|
|
schedule_event (GstMediaSource * self, MediaSourceEvent event)
|
|
{
|
|
MediaSourceEventItem item = {
|
|
.item = {.destroy = g_free,.visible = TRUE,.size = 1,.object = NULL},
|
|
.event = event,
|
|
};
|
|
|
|
gst_mse_event_queue_push (self->event_queue, g_memdup2 (&item,
|
|
sizeof (MediaSourceEventItem)));
|
|
}
|
|
|
|
static void
|
|
dispatch_event (MediaSourceEventItem * item, GstMediaSource * self)
|
|
{
|
|
g_signal_emit (self, signals[item->event], 0);
|
|
}
|
|
|
|
static void
|
|
gst_media_source_init (GstMediaSource * self)
|
|
{
|
|
self->buffers = gst_source_buffer_list_new ();
|
|
self->active_buffers = gst_source_buffer_list_new ();
|
|
self->ready_state = DEFAULT_READY_STATE;
|
|
self->duration = DEFAULT_DURATION;
|
|
reset_live_seekable_range (self);
|
|
self->element = NULL;
|
|
self->event_queue =
|
|
gst_mse_event_queue_new ((GstMseEventQueueCallback) dispatch_event, self);
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_attach:
|
|
* @self: #GstMediaSource instance
|
|
* @element: (transfer none): #GstMseSrc source Element
|
|
*
|
|
* Associates @self with @element.
|
|
* Normally, the Element will be part of a #GstPipeline that plays back the data
|
|
* submitted to the Media Source's Source Buffers.
|
|
*
|
|
* #GstMseSrc is a special source element that is designed to consume media from
|
|
* a #GstMediaSource.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dfn-attaching-to-a-media-element)
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
void
|
|
gst_media_source_attach (GstMediaSource * self, GstMseSrc * element)
|
|
{
|
|
g_return_if_fail (GST_IS_MEDIA_SOURCE (self));
|
|
g_return_if_fail (GST_IS_MSE_SRC (element));
|
|
|
|
if (is_attached (self))
|
|
gst_media_source_detach (self);
|
|
|
|
self->element = gst_object_ref_sink (element);
|
|
gst_mse_src_attach (element, self);
|
|
|
|
self->ready_state = GST_MEDIA_SOURCE_READY_STATE_OPEN;
|
|
schedule_event (self, ON_SOURCE_OPEN);
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_detach:
|
|
* @self: #GstMediaSource instance
|
|
*
|
|
* Detaches @self from any #GstMseSrc element that it may be associated with.
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
void
|
|
gst_media_source_detach (GstMediaSource * self)
|
|
{
|
|
g_return_if_fail (GST_IS_MEDIA_SOURCE (self));
|
|
|
|
self->ready_state = GST_MEDIA_SOURCE_READY_STATE_CLOSED;
|
|
gst_media_source_set_duration (self, GST_CLOCK_TIME_NONE, NULL);
|
|
|
|
gst_source_buffer_list_remove_all (self->active_buffers);
|
|
empty_buffers (self);
|
|
|
|
if (is_attached (self)) {
|
|
gst_mse_src_detach (self->element);
|
|
gst_clear_object (&self->element);
|
|
}
|
|
|
|
schedule_event (self, ON_SOURCE_CLOSE);
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_get_source_element:
|
|
* @self: #GstMediaSource instance
|
|
*
|
|
* Gets the #GstMseSrc currently attached to @self or `NULL` if there is none.
|
|
*
|
|
* Returns: (transfer full) (nullable): a #GstMseSrc instance or `NULL`
|
|
*/
|
|
GstMseSrc *
|
|
gst_media_source_get_source_element (GstMediaSource * self)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL);
|
|
GST_OBJECT_LOCK (self);
|
|
GstMseSrc *element = self->element == NULL ? NULL
|
|
: gst_object_ref (self->element);
|
|
GST_OBJECT_UNLOCK (self);
|
|
return element;
|
|
}
|
|
|
|
void
|
|
gst_media_source_open (GstMediaSource * self)
|
|
{
|
|
g_return_if_fail (GST_IS_MEDIA_SOURCE (self));
|
|
if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) {
|
|
self->ready_state = GST_MEDIA_SOURCE_READY_STATE_OPEN;
|
|
schedule_event (self, ON_SOURCE_OPEN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_get_source_buffers:
|
|
* @self: #GstMediaSource instance
|
|
*
|
|
* Gets a #GstSourceBufferList containing all the Source Buffers currently
|
|
* associated with this Media Source. This object will reflect any future
|
|
* changes to the parent Media Source as well.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-sourcebuffers)
|
|
*
|
|
* Returns: (transfer full): a #GstSourceBufferList instance
|
|
* Since: 1.24
|
|
*/
|
|
GstSourceBufferList *
|
|
gst_media_source_get_source_buffers (GstMediaSource * self)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL);
|
|
return g_object_ref (self->buffers);
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_get_active_source_buffers:
|
|
* @self: #GstMediaSource instance
|
|
*
|
|
* Gets a #GstSourceBufferList containing all the Source Buffers currently
|
|
* associated with this Media Source that are considered "active."
|
|
* For a Source Buffer to be considered active, either its video track is
|
|
* selected, its audio track is enabled, or its text track is visible or hidden.
|
|
* This object will reflect any future changes to the parent Media Source as
|
|
* well.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-activesourcebuffers)
|
|
*
|
|
* Returns: (transfer full): a new #GstSourceBufferList instance
|
|
* Since: 1.24
|
|
*/
|
|
GstSourceBufferList *
|
|
gst_media_source_get_active_source_buffers (GstMediaSource * self)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL);
|
|
return g_object_ref (self->active_buffers);
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_get_ready_state:
|
|
* @self: #GstMediaSource instance
|
|
*
|
|
* Gets the current Ready State of the Media Source.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate)
|
|
*
|
|
* Returns: the current #GstMediaSourceReadyState value
|
|
* Since: 1.24
|
|
*/
|
|
GstMediaSourceReadyState
|
|
gst_media_source_get_ready_state (GstMediaSource * self)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), DEFAULT_READY_STATE);
|
|
return self->ready_state;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_get_position:
|
|
* @self: #GstMediaSource instance
|
|
*
|
|
* Gets the current playback position of the Media Source.
|
|
*
|
|
* Returns: the current playback position as a #GstClockTime
|
|
* Since: 1.24
|
|
*/
|
|
GstClockTime
|
|
gst_media_source_get_position (GstMediaSource * self)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), DEFAULT_POSITION);
|
|
if (is_attached (self))
|
|
return gst_mse_src_get_position (self->element);
|
|
return DEFAULT_POSITION;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_get_duration:
|
|
* @self: #GstMediaSource instance
|
|
*
|
|
* Gets the current duration of @self.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration)
|
|
*
|
|
* Returns: the current duration as a #GstClockTime
|
|
* Since: 1.24
|
|
*/
|
|
GstClockTime
|
|
gst_media_source_get_duration (GstMediaSource * self)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), DEFAULT_DURATION);
|
|
if (self->ready_state == GST_MEDIA_SOURCE_READY_STATE_CLOSED)
|
|
return GST_CLOCK_TIME_NONE;
|
|
return self->duration;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_set_duration:
|
|
* @self: #GstMediaSource instance
|
|
* @duration: The new duration to apply to @self.
|
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
|
*
|
|
* Sets the duration of @self.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration)
|
|
*
|
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
|
* Since: 1.24
|
|
*/
|
|
gboolean
|
|
gst_media_source_set_duration (GstMediaSource * self, GstClockTime duration,
|
|
GError ** error)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE);
|
|
self->duration = duration;
|
|
update_duration (self);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_received_init_segment (G_GNUC_UNUSED GstSourceBuffer * source_buffer,
|
|
gpointer user_data)
|
|
{
|
|
GstMediaSource *self = GST_MEDIA_SOURCE (user_data);
|
|
if (!is_attached (self)) {
|
|
GST_DEBUG_OBJECT (self, "received init segment while detached, ignoring");
|
|
return;
|
|
}
|
|
|
|
GPtrArray *all_tracks = g_ptr_array_new ();
|
|
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i);
|
|
if (buf == NULL) {
|
|
break;
|
|
}
|
|
GPtrArray *tracks = gst_source_buffer_get_all_tracks (buf);
|
|
g_ptr_array_extend (all_tracks, tracks, NULL, NULL);
|
|
g_ptr_array_unref (tracks);
|
|
gst_object_unref (buf);
|
|
}
|
|
|
|
gst_mse_src_emit_streams (self->element,
|
|
(GstMediaSourceTrack **) all_tracks->pdata, all_tracks->len);
|
|
|
|
g_ptr_array_unref (all_tracks);
|
|
}
|
|
|
|
static void
|
|
on_duration_changed (G_GNUC_UNUSED GstSourceBuffer * source_buffer,
|
|
gpointer user_data)
|
|
{
|
|
GstMediaSource *self = GST_MEDIA_SOURCE (user_data);
|
|
GstClockTime current = self->duration;
|
|
GstClockTime max = 0;
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i);
|
|
if (buf == NULL) {
|
|
break;
|
|
}
|
|
GstClockTime duration = gst_source_buffer_get_duration (buf);
|
|
if (GST_CLOCK_TIME_IS_VALID (duration)) {
|
|
max = MAX (max, duration);
|
|
}
|
|
gst_object_unref (buf);
|
|
}
|
|
if (current == max) {
|
|
return;
|
|
}
|
|
GST_DEBUG_OBJECT (self, "updating %" GST_TIMEP_FORMAT "=>%" GST_TIMEP_FORMAT,
|
|
¤t, &max);
|
|
gst_media_source_set_duration (self, max, NULL);
|
|
}
|
|
|
|
static GHashTable *
|
|
source_buffer_list_as_set (GstSourceBufferList * list)
|
|
{
|
|
GHashTable *buffers = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
|
gst_object_unref, NULL);
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (list, i);
|
|
if (buf == NULL) {
|
|
break;
|
|
}
|
|
g_hash_table_add (buffers, buf);
|
|
}
|
|
return buffers;
|
|
}
|
|
|
|
static void
|
|
rebuild_active_source_buffers (GstMediaSource * self)
|
|
{
|
|
// TODO: Lock the source buffer lists
|
|
GST_DEBUG_OBJECT (self, "rebuilding active source buffers");
|
|
GHashTable *previously_active =
|
|
source_buffer_list_as_set (self->active_buffers);
|
|
|
|
gst_source_buffer_list_notify_freeze (self->active_buffers);
|
|
gst_source_buffer_list_remove_all (self->active_buffers);
|
|
|
|
gboolean added = FALSE;
|
|
gboolean removed = FALSE;
|
|
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i);
|
|
if (buf == NULL) {
|
|
break;
|
|
}
|
|
if (gst_source_buffer_get_active (buf)) {
|
|
gst_source_buffer_list_append (self->active_buffers, buf);
|
|
added |= !g_hash_table_contains (previously_active, buf);
|
|
} else {
|
|
gst_source_buffer_list_append (self->active_buffers, buf);
|
|
removed |= g_hash_table_contains (previously_active, buf);
|
|
}
|
|
gst_object_unref (buf);
|
|
}
|
|
g_hash_table_unref (previously_active);
|
|
|
|
gst_source_buffer_list_notify_cancel (self->active_buffers);
|
|
gst_source_buffer_list_notify_thaw (self->active_buffers);
|
|
|
|
if (added) {
|
|
GST_DEBUG_OBJECT (self, "notifying active source buffer added");
|
|
gst_source_buffer_list_notify_added (self->active_buffers);
|
|
}
|
|
if (removed) {
|
|
GST_DEBUG_OBJECT (self, "notifying active source buffer removed");
|
|
gst_source_buffer_list_notify_removed (self->active_buffers);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_active_state_changed (GstSourceBuffer * source_buffer, gpointer user_data)
|
|
{
|
|
GstMediaSource *self = GST_MEDIA_SOURCE (user_data);
|
|
rebuild_active_source_buffers (self);
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_add_source_buffer:
|
|
* @self: #GstMediaSource instance
|
|
* @type: (transfer none): A MIME type describing the format of the incoming media
|
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
|
*
|
|
* Add a #GstSourceBuffer to this #GstMediaSource of the specified media type.
|
|
* The Media Source must be in the #GstMediaSourceReadyState %GST_MEDIA_SOURCE_READY_STATE_OPEN.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-addsourcebuffer)
|
|
*
|
|
* Returns: (transfer full): a new #GstSourceBuffer instance on success, otherwise `NULL`
|
|
* Since: 1.24
|
|
*/
|
|
GstSourceBuffer *
|
|
gst_media_source_add_source_buffer (GstMediaSource * self, const gchar * type,
|
|
GError ** error)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL);
|
|
g_return_val_if_fail (type != NULL, NULL);
|
|
|
|
if (g_strcmp0 (type, "") == 0) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
|
|
"supplied content type is empty");
|
|
return NULL;
|
|
}
|
|
|
|
if (!gst_media_source_is_type_supported (type)) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED,
|
|
"unsupported content type");
|
|
return NULL;
|
|
}
|
|
|
|
if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
|
"media source is not open");
|
|
return NULL;
|
|
}
|
|
|
|
GstSourceBufferCallbacks callbacks = {
|
|
.duration_changed = on_duration_changed,
|
|
.received_init_segment = on_received_init_segment,
|
|
.active_state_changed = on_active_state_changed,
|
|
};
|
|
|
|
GError *source_buffer_error = NULL;
|
|
GstSourceBuffer *buf = gst_source_buffer_new_with_callbacks (type,
|
|
GST_OBJECT (self), &callbacks, self, &source_buffer_error);
|
|
if (source_buffer_error) {
|
|
g_propagate_prefixed_error (error, source_buffer_error,
|
|
"failed to create source buffer");
|
|
gst_clear_object (&buf);
|
|
return NULL;
|
|
}
|
|
|
|
gst_source_buffer_list_append (self->buffers, buf);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_remove_source_buffer:
|
|
* @self: #GstMediaSource instance
|
|
* @buffer: (transfer none): #GstSourceBuffer instance
|
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
|
*
|
|
* Remove @buffer from @self.
|
|
*
|
|
* @buffer must have been created as a child of @self and @self must be in the
|
|
* #GstMediaSourceReadyState %GST_MEDIA_SOURCE_READY_STATE_OPEN.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-removesourcebuffer)
|
|
*
|
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
|
* Since: 1.24
|
|
*/
|
|
gboolean
|
|
gst_media_source_remove_source_buffer (GstMediaSource * self,
|
|
GstSourceBuffer * buffer, GError ** error)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE);
|
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (buffer), FALSE);
|
|
|
|
if (!gst_source_buffer_list_contains (self->buffers, buffer)) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_NOT_FOUND,
|
|
"the supplied source buffer was not found in this media source");
|
|
return FALSE;
|
|
}
|
|
|
|
if (gst_source_buffer_get_updating (buffer))
|
|
gst_source_buffer_teardown (buffer);
|
|
|
|
gst_source_buffer_list_remove (self->active_buffers, buffer);
|
|
|
|
gst_object_unparent (GST_OBJECT (buffer));
|
|
gst_source_buffer_list_remove (self->buffers, buffer);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
abort_all_source_buffers (GstMediaSource * self)
|
|
{
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i);
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
GST_LOG_OBJECT (self, "shutting down %" GST_PTR_FORMAT, buf);
|
|
gst_source_buffer_abort (buf, NULL);
|
|
gst_object_unref (buf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_end_of_stream:
|
|
* @self: #GstMediaSource instance
|
|
* @eos_error: The error type, if any
|
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
|
*
|
|
* Mark @self as reaching the end of stream, disallowing new data inputs.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-endofstream)
|
|
*
|
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
|
* Since: 1.24
|
|
*/
|
|
gboolean
|
|
gst_media_source_end_of_stream (GstMediaSource * self,
|
|
GstMediaSourceEOSError eos_error, GError ** error)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE);
|
|
|
|
if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
|
"media source is not open");
|
|
return FALSE;
|
|
}
|
|
|
|
if (is_updating (self)) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
|
"some buffers are still updating");
|
|
return FALSE;
|
|
}
|
|
|
|
self->ready_state = GST_MEDIA_SOURCE_READY_STATE_ENDED;
|
|
schedule_event (self, ON_SOURCE_ENDED);
|
|
|
|
switch (eos_error) {
|
|
case GST_MEDIA_SOURCE_EOS_ERROR_NETWORK:
|
|
network_error (self);
|
|
break;
|
|
case GST_MEDIA_SOURCE_EOS_ERROR_DECODE:
|
|
decode_error (self);
|
|
break;
|
|
default:
|
|
update_duration (self);
|
|
abort_all_source_buffers (self);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_set_live_seekable_range:
|
|
* @self: #GstMediaSource instance
|
|
* @start: The earliest point in the stream considered seekable
|
|
* @end: The latest point in the stream considered seekable
|
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
|
*
|
|
* Set the live seekable range for @self. This range informs the component
|
|
* playing this Media Source what it can allow the user to seek through.
|
|
*
|
|
* If the ready state is not %GST_MEDIA_SOURCE_READY_STATE_OPEN, or the supplied
|
|
* @start time is later than @end it will fail and set an error.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-setliveseekablerange)
|
|
*
|
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
|
* Since: 1.24
|
|
*/
|
|
gboolean
|
|
gst_media_source_set_live_seekable_range (GstMediaSource * self,
|
|
GstClockTime start, GstClockTime end, GError ** error)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE);
|
|
if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
|
"media source is not open");
|
|
return FALSE;
|
|
}
|
|
|
|
if (start > end) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
|
|
"bad time range: start must be earlier than end");
|
|
return FALSE;
|
|
}
|
|
|
|
self->live_seekable_range.start = start;
|
|
self->live_seekable_range.end = end;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_clear_live_seekable_range:
|
|
* @self: #GstMediaSource instance
|
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
|
*
|
|
* Clear the live seekable range for @self. This will inform the component
|
|
* playing this Media Source that there is no seekable time range.
|
|
*
|
|
* If the ready state is not %GST_MEDIA_SOURCE_READY_STATE_OPEN, it will fail
|
|
* and set an error.
|
|
*
|
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-clearliveseekablerange)
|
|
*
|
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
|
* Since: 1.24
|
|
*/
|
|
gboolean
|
|
gst_media_source_clear_live_seekable_range (GstMediaSource * self,
|
|
GError ** error)
|
|
{
|
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE);
|
|
|
|
if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) {
|
|
g_set_error (error,
|
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
|
"media source is not open");
|
|
return FALSE;
|
|
}
|
|
|
|
reset_live_seekable_range (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_media_source_get_live_seekable_range:
|
|
* @self: #GstMediaSource instance
|
|
* @range: (out) (transfer none): time range
|
|
*
|
|
* Get the live seekable range of @self. Will fill in the supplied @range with
|
|
* the current live seekable range.
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
void
|
|
gst_media_source_get_live_seekable_range (GstMediaSource * self,
|
|
GstMediaSourceRange * range)
|
|
{
|
|
g_return_if_fail (GST_IS_MEDIA_SOURCE (self));
|
|
g_return_if_fail (range != NULL);
|
|
|
|
range->start = self->live_seekable_range.start;
|
|
range->end = self->live_seekable_range.end;
|
|
}
|
|
|
|
void
|
|
gst_media_source_seek (GstMediaSource * self, GstClockTime time)
|
|
{
|
|
g_return_if_fail (GST_IS_MEDIA_SOURCE (self));
|
|
for (guint i = 0;; i++) {
|
|
GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i);
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
gst_source_buffer_seek (buf, time);
|
|
gst_object_unref (buf);
|
|
}
|
|
}
|