/* 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 * 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 #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); } }