diff --git a/girs/GstMse-1.0.gir b/girs/GstMse-1.0.gir
new file mode 100644
index 0000000000..4818df5a86
--- /dev/null
+++ b/girs/GstMse-1.0.gir
@@ -0,0 +1,1135 @@
+
+
+
+
+
+
+
+ #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.
+
+
+ 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)
+
+
+ a new #GstMediaSource instance
+
+
+
+
+ Determines whether the current Media Source configuration can process media
+of the supplied @type.
+
+
+ `TRUE` when supported, `FALSE` otherwise
+
+
+
+
+ A MIME type value
+
+
+
+
+
+ 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)
+
+
+ a new #GstSourceBuffer instance on success, otherwise `NULL`
+
+
+
+
+ #GstMediaSource instance
+
+
+
+ A MIME type describing the format of the incoming media
+
+
+
+
+
+ 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)
+
+
+
+
+
+
+ #GstMediaSource instance
+
+
+
+ #GstMseSrc source Element
+
+
+
+
+
+ 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)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstMediaSource instance
+
+
+
+
+
+ Detaches @self from any #GstMseSrc element that it may be associated with.
+
+
+
+
+
+
+ #GstMediaSource instance
+
+
+
+
+
+ Mark @self as reaching the end of stream, disallowing new data inputs.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-endofstream)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstMediaSource instance
+
+
+
+ The error type, if any
+
+
+
+
+
+ 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)
+
+
+ a new #GstSourceBufferList instance
+
+
+
+
+ #GstMediaSource instance
+
+
+
+
+
+ Gets the current duration of @self.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration)
+
+
+ the current duration as a #GstClockTime
+
+
+
+
+ #GstMediaSource instance
+
+
+
+
+
+ Get the live seekable range of @self. Will fill in the supplied @range with
+the current live seekable range.
+
+
+
+
+
+
+ #GstMediaSource instance
+
+
+
+ time range
+
+
+
+
+
+ Gets the current playback position of the Media Source.
+
+
+ the current playback position as a #GstClockTime
+
+
+
+
+ #GstMediaSource instance
+
+
+
+
+
+ Gets the current Ready State of the Media Source.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate)
+
+
+ the current #GstMediaSourceReadyState value
+
+
+
+
+ #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)
+
+
+ a #GstSourceBufferList instance
+
+
+
+
+ #GstMediaSource instance
+
+
+
+
+
+ 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)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstMediaSource instance
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ Sets the duration of @self.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstMediaSource instance
+
+
+
+ The new duration to apply to @self.
+
+
+
+
+
+ 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)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstMediaSource instance
+
+
+
+ The earliest point in the stream considered seekable
+
+
+
+ The latest point in the stream considered seekable
+
+
+
+
+
+ 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)
+
+
+
+ The Duration of the Media Source as a #GstClockTime
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration)
+
+
+
+ The position of the player consuming from the Media Source
+
+
+
+ The Ready State of the Media Source
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate)
+
+
+
+ A #GstSourceBufferList of every #GstSourceBuffer in this Media Source
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-sourcebuffers)
+
+
+
+
+
+
+
+
+ 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)
+
+
+
+
+
+ Emitted when @self has been opened.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceopen)
+
+
+
+
+
+
+
+
+
+
+
+
+ Reasons for ending a #GstMediaSource using gst_media_source_end_of_stream().
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-endofstreamerror)
+
+ End the stream successfully
+
+
+ End the stream due to a networking error
+
+
+ End the stream due to a decoding error
+
+
+
+ Any error that can occur within #GstMediaSource or #GstSourceBuffer APIs.
+These values correspond directly to those in the Web IDL specification.
+
+[Specification](https://webidl.spec.whatwg.org/#idl-DOMException-error-names)
+
+
+
+
+
+
+
+
+
+
+
+ Any error type that can be reported by the Media Source API.
+
+
+
+
+
+
+ A structure describing a simplified version of the TimeRanges concept in the
+HTML specification, only representing a single @start and @end time.
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#timeranges)
+
+
+ The start of this range.
+
+
+
+ The end of this range.
+
+
+
+
+ Describes the possible states of the Media Source.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-readystate)
+
+ The #GstMediaSource is not connected to
+any playback element.
+
+
+ The #GstMediaSource is connected to a
+playback element and ready to append data to its #GstSourceBuffer (s).
+
+
+ gst_media_source_end_of_stream() has
+been called on the current #GstMediaSource
+
+
+
+ #GstMseSrc is a source Element that interacts with a #GstMediaSource to
+consume #GstSample<!-- -->s processed by the Media Source and supplies them
+to the containing #GstPipeline. In the perspective of the Media Source API,
+this element fulfills the basis of the Media Element's role relating to
+working with a Media Source. The remaining responsibilities are meant to be
+fulfilled by the application and #GstPlay can be used to satisfy many of
+them.
+
+Once added to a Pipeline, this element should be attached to a Media Source
+using gst_media_source_attach().
+
+
+
+ Gets the duration of @self.
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration)
+
+
+ The duration of this stream as a #GstClockTime
+
+
+
+
+ #GstMseSrc instance
+
+
+
+
+
+
+
+ the number of audio tracks available from this source
+
+
+
+
+ #GstMseSrc instance
+
+
+
+
+
+
+
+ the number of text tracks available from this source
+
+
+
+
+ #GstMseSrc instance
+
+
+
+
+
+
+
+ the number of video tracks available from this source
+
+
+
+
+ #GstMseSrc instance
+
+
+
+
+
+ Gets the current playback position of @self.
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position)
+
+
+ The playback position of this Element as a #GstClockTime
+
+
+
+
+ #GstMseSrc instance
+
+
+
+
+
+ The Ready State of @self, describing to what level it can supply content for
+the current #GstMseSrc:position. This is a separate concept from
+#GstMediaSource:ready-state: and corresponds to the HTML Media Element's
+Ready State.
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states)
+
+
+ the current #GstMseSrcReadyState
+
+
+
+
+ #GstMseSrc instance
+
+
+
+
+
+ The duration of the stream as a #GstClockTime
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration)
+
+
+
+ The number of audio tracks in the Media Source
+
+
+
+ The number of text tracks in the Media Source
+
+
+
+ The number of video tracks in the Media Source
+
+
+
+ The playback position as a #GstClockTime
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position)
+
+
+
+ The Ready State of this element, describing to what level it can supply
+content for the current #GstMseSrc:position. This is a separate concept
+from #GstMediaSource:ready-state: and corresponds to the HTML Media
+Element's Ready State.
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Describes how much information a #GstMseSrc has about the media it is playing
+back at the current playback #GstMseSrc:position. This type corresponds
+directly to the ready state of a HTML Media Element and is a separate concept
+from #GstMediaSourceReadyState.
+
+[Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states)
+
+ No information is available about the
+stream
+
+
+ The duration is available and video
+dimensions are available if the stream contains video
+
+
+ The current playback position can
+be presented but future information is not available
+
+
+ There is data for the current
+position and some amount in the future and any text tracks are ready.
+
+
+ Either there is enough data to
+play the stream through at the current playback and input rate or the input
+buffer is full.
+
+
+
+ The Source Buffer is the primary means of data flow between an application
+and the Media Source API. It represents a single timeline of media,
+containing some combination of audio, video, and text tracks.
+An application is responsible for feeding raw data into the Source Buffer
+using gst_source_buffer_append_buffer() and the Source Buffer will
+asynchronously process the data into tracks of time-coded multimedia samples.
+
+The application as well as the associated playback component can then select
+to play media from any subset of tracks across all Source Buffers of a Media
+Source.
+
+A few control points are also provided to customize the behavior.
+
+ - #GstSourceBuffer:append-mode controls how timestamps of processed samples are
+ interpreted. They are either inserted in the timeline directly where the
+ decoded media states they should, or inserted directly after the previously
+ encountered sample.
+
+ - #GstSourceBuffer:append-window-start / #GstSourceBuffer:append-window-end
+ control the planned time window where media from appended data can be added
+ to the current timeline. Any samples outside that range may be ignored.
+
+ - #GstSourceBuffer:timestamp-offset is added to the start time of any sample
+ processed.
+
+
+ Attempts to end any processing of the currently pending data and reset the
+media parser.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-abort)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ Schedules the bytes inside @buf to be processed by @self. When it is possible
+to accept the supplied data, it will be processed asynchronously and fill in
+the track buffers for playback purposes.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendbuffer)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+ The media data to append
+
+
+
+
+
+ Attempts to change the content type of @self to @type. Any new data appended
+to the Source Buffer must be of the supplied @type afterward.
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+ the desired content type
+
+
+
+
+
+ [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
+
+
+ The current #GstSourceBufferAppendMode
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ Returns the current append window end time. Any segment processed that starts
+after this value will be ignored.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
+
+
+ The current Append Window end time as a #GstClockTime
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ Returns the current append window start time. Any segment processed that ends
+earlier than this value will be ignored.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
+
+
+ The current Append Window start time as a #GstClockTime
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ Returns a sequence of #GstMediaSourceRange values representing which segments
+of @self are buffered in memory.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered)
+
+
+ a #GArray of #GstMediaSourceRange values.
+
+
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ Returns the current content type of @self.
+
+
+ a string representing the content type
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
+
+
+ The current timestamp offset as a #GstClockTime
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating)
+
+
+ Whether @self is currently adding or removing media content.
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+
+
+ Attempts to remove any parsed data between @start and @end from @self.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-remove)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+ The beginning timestamp of data to remove
+
+
+
+ The end timestamp of data to remove
+
+
+
+
+
+ Changes the Append Mode of @self. This influences what timestamps will be
+assigned to media processed by this Source Buffer. In Segment mode, the
+timestamps in each segment determine the position of each sample after it
+is processed. In Sequence mode, the timestamp of each processed sample is
+generated based on the end of the most recently processed segment.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+ #GstSourceBufferAppendMode the desired Append Mode
+
+
+
+
+
+ Modifies the current append window end of @self. If successful, samples
+processed after setting this value that start after this point will be
+ignored.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+ the append window end
+
+
+
+
+
+ Modifies the current append window start of @self. If successful, samples
+processed after setting this value that end before this point will be
+ignored.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+ the append window end
+
+
+
+
+
+ Attempt to set the timestamp offset of @self. Any media processed after this
+value is set will have this value added to its start time.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
+
+
+ `TRUE` on success, `FALSE` otherwise
+
+
+
+
+ #GstSourceBuffer instance
+
+
+
+ The new timestamp offset
+
+
+
+
+
+ Affects how timestamps of processed media segments are interpreted.
+In %GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS, the start timestamp of a
+processed media segment is used directly along with
+#GstSourceBuffer:timestamp-offset .
+In %GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE, the timestamp of a
+processed media segment is ignored and replaced with the end time of the
+most recently appended segment.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
+
+
+
+ Any segments processed which have a start time greater than this value will
+be ignored by this Source Buffer.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
+
+
+
+ Any segments processed which end before this value will be ignored by this
+Source Buffer.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
+
+
+
+ The set of Time Intervals that have been loaded into the current Source
+Buffer
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered)
+
+
+
+
+
+ The MIME content-type of the data stream
+
+
+
+ The next media segment appended to the current Source Buffer will have its
+start timestamp increased by this amount.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
+
+
+
+ Whether the current source buffer is still asynchronously processing
+previously issued commands.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating)
+
+
+
+ Emitted when @self was aborted after a call to gst_source_buffer_abort().
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onabort)
+
+
+
+
+
+ Emitted when @self has encountered an error after a call to
+gst_source_buffer_append_buffer().
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onerror)
+
+
+
+
+
+ Emitted when @self has successfully processed data after a call to
+gst_source_buffer_append_buffer().
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdate)
+
+
+
+
+
+ Emitted when @self is no longer in the updating state after a call to
+gst_source_buffer_append_buffer(). This can happen after a successful or
+unsuccessful append.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdateend)
+
+
+
+
+
+ Emitted when @self has begun to process data after a call to
+gst_source_buffer_append_buffer().
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdatestart)
+
+
+
+
+
+
+ [Specification](https://www.w3.org/TR/media-source-2/#dom-appendmode)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The Source Buffer List is a list of #GstSourceBuffer<!-- -->s that can be
+indexed numerically and monitored for changes. The list itself cannot be
+modified through this interface, though the Source Buffers it holds can be
+modified after retrieval.
+
+It is used by #GstMediaSource to provide direct access to its child
+#GstSourceBuffer<!-- -->s through #GstMediaSource:source-buffers as well as
+informing clients which of the Source Buffers are active through
+#GstMediaSource:active-source-buffers.
+
+
+ [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length)
+
+
+ The number of #GstSourceBuffer objects in the list
+
+
+
+
+ #GstSourceBufferList instance
+
+
+
+
+
+ Retrieves the #GstSourceBuffer at @index from @self. If @index is greater than
+the highest index in the list, it will return `NULL`.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dfn-sourcebufferlist-getter)
+
+
+ The requested #GstSourceBuffer or `NULL`
+
+
+
+
+ #GstSourceBufferList instance
+
+
+
+ index of requested Source Buffer
+
+
+
+
+
+ The number of #GstSourceBuffer<!-- -->s contained by this structure
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length)
+
+
+
+ Emitted when a #GstSourceBuffer has been added to this list.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onaddsourcebuffer)
+
+
+
+
+
+ Emitted when a #GstSourceBuffer has been removed from this list.
+
+[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onremovesourcebuffer)
+
+
+
+
+
+
+
+
+
+
+
+
+ Any error type that can be reported by the Media Source API.
+
+
+
+
+
+
diff --git a/subprojects/gst-plugins-bad/docs/libs/mse/index.md b/subprojects/gst-plugins-bad/docs/libs/mse/index.md
new file mode 100644
index 0000000000..95f019eca5
--- /dev/null
+++ b/subprojects/gst-plugins-bad/docs/libs/mse/index.md
@@ -0,0 +1,3 @@
+# W3C Media Source Extensions Library
+
+> NOTE: This library API is considered *unstable*
diff --git a/subprojects/gst-plugins-bad/docs/libs/mse/sitemap.txt b/subprojects/gst-plugins-bad/docs/libs/mse/sitemap.txt
new file mode 100644
index 0000000000..4f91fcd8a3
--- /dev/null
+++ b/subprojects/gst-plugins-bad/docs/libs/mse/sitemap.txt
@@ -0,0 +1 @@
+gi-index
diff --git a/subprojects/gst-plugins-bad/docs/meson.build b/subprojects/gst-plugins-bad/docs/meson.build
index 582de7d0fd..3b884e6af2 100644
--- a/subprojects/gst-plugins-bad/docs/meson.build
+++ b/subprojects/gst-plugins-bad/docs/meson.build
@@ -134,6 +134,7 @@ if build_gir
{'name': 'codecs', 'gir': codecs_gir, 'lib': gstcodecs_dep},
{'name': 'cuda', 'gir': gst_cuda_gir, 'lib': gstcuda_dep, 'c_source_patterns': ['*.h', '*.cpp']},
{'name': 'dxva', 'gir': dxva_gir, 'lib': gstdxva_dep, 'c_source_patterns': ['*.h', '*.cpp']},
+ {'name': 'mse', 'gir': mse_gir, 'lib': gstmse_dep, 'suffix': 'lib'},
]
if gstopencv_dep.found()
diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json
index e5a8fe8c41..80e64a24ba 100644
--- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json
+++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json
@@ -220390,6 +220390,126 @@
"tracers": {},
"url": "Unknown package origin"
},
+ "mse": {
+ "description": "W3C Media Source Extensions Support",
+ "elements": {
+ "msesrc": {
+ "author": "Collabora",
+ "description": "Implements a GStreamer Source for the gstreamer-mse API",
+ "hierarchy": [
+ "GstMseSrc",
+ "GstElement",
+ "GstObject",
+ "GInitiallyUnowned",
+ "GObject"
+ ],
+ "interfaces": [
+ "GstURIHandler"
+ ],
+ "klass": "Generic/Source",
+ "pad-templates": {
+ "src_%%s": {
+ "caps": "ANY",
+ "direction": "src",
+ "presence": "sometimes",
+ "type": "GstMseSrcPad"
+ }
+ },
+ "properties": {
+ "duration": {
+ "blurb": "The duration of the stream as a GstClockTime",
+ "conditionally-available": false,
+ "construct": false,
+ "construct-only": false,
+ "controllable": false,
+ "default": "0",
+ "max": "18446744073709551615",
+ "min": "0",
+ "mutable": "null",
+ "readable": true,
+ "type": "guint64",
+ "writable": true
+ },
+ "n-audio": {
+ "blurb": "The number of audio tracks in the Media Source",
+ "conditionally-available": false,
+ "construct": false,
+ "construct-only": false,
+ "controllable": false,
+ "default": "0",
+ "max": "2147483647",
+ "min": "0",
+ "mutable": "null",
+ "readable": true,
+ "type": "guint",
+ "writable": false
+ },
+ "n-text": {
+ "blurb": "The number of text tracks in the Media Source",
+ "conditionally-available": false,
+ "construct": false,
+ "construct-only": false,
+ "controllable": false,
+ "default": "0",
+ "max": "2147483647",
+ "min": "0",
+ "mutable": "null",
+ "readable": true,
+ "type": "guint",
+ "writable": false
+ },
+ "n-video": {
+ "blurb": "The number of video tracks in the Media Source",
+ "conditionally-available": false,
+ "construct": false,
+ "construct-only": false,
+ "controllable": false,
+ "default": "0",
+ "max": "2147483647",
+ "min": "0",
+ "mutable": "null",
+ "readable": true,
+ "type": "guint",
+ "writable": false
+ },
+ "position": {
+ "blurb": "The playback position as a GstClockTime",
+ "conditionally-available": false,
+ "construct": false,
+ "construct-only": false,
+ "controllable": false,
+ "default": "18446744073709551615",
+ "max": "18446744073709551615",
+ "min": "0",
+ "mutable": "null",
+ "readable": true,
+ "type": "guint64",
+ "writable": false
+ },
+ "ready-state": {
+ "blurb": "The Ready State of this Element",
+ "conditionally-available": false,
+ "construct": false,
+ "construct-only": false,
+ "controllable": false,
+ "default": "nothing (0)",
+ "mutable": "null",
+ "readable": true,
+ "type": "GstMseSrcReadyState",
+ "writable": false
+ }
+ },
+ "rank": "none"
+ }
+ },
+ "filename": "gstmse",
+ "license": "LGPL",
+ "other-types": {},
+ "package": "GStreamer Bad Plug-ins",
+ "source": "gst-plugins-bad",
+ "tracers": {},
+ "url": "Unknown package origin"
+ },
"musepack": {
"description": "Musepack decoder",
"elements": {
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/meson.build b/subprojects/gst-plugins-bad/gst-libs/gst/meson.build
index fc4ba6942b..7772d8c2b1 100644
--- a/subprojects/gst-plugins-bad/gst-libs/gst/meson.build
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/meson.build
@@ -13,6 +13,7 @@ subdir('insertbin')
subdir('interfaces')
subdir('isoff')
subdir('mpegts')
+subdir('mse')
subdir('opencv')
subdir('play')
subdir('player')
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline-private.h
new file mode 100644
index 0000000000..c44a3440fb
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline-private.h
@@ -0,0 +1,102 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 2016, 2017 Metrological Group B.V.
+ * Copyright (C) 2016, 2017 Igalia S.L
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_APPEND_PIPELINE (gst_append_pipeline_get_type())
+
+GST_MSE_PRIVATE
+G_DECLARE_FINAL_TYPE (GstAppendPipeline, gst_append_pipeline, GST,
+ APPEND_PIPELINE, GstObject);
+
+typedef struct
+{
+ void (*received_init_segment) (GstAppendPipeline * self,
+ gpointer user_data);
+ void (*duration_changed) (GstAppendPipeline * self,
+ gpointer user_data);
+ void (*new_sample) (GstAppendPipeline * self,
+ GstMediaSourceTrack * track,
+ GstSample * sample,
+ gpointer user_data);
+ void (*eos) (GstAppendPipeline * self,
+ GstMediaSourceTrack * track,
+ gpointer user_data);
+ void (*error) (GstAppendPipeline * self,
+ gpointer user_data);
+} GstAppendPipelineCallbacks;
+
+GST_MSE_PRIVATE
+GstAppendPipeline * gst_append_pipeline_new (
+ GstAppendPipelineCallbacks * callbacks, gpointer user_data,
+ GError ** error);
+
+GST_MSE_PRIVATE
+GstFlowReturn gst_append_pipeline_append (GstAppendPipeline * self,
+ GstBuffer * buffer);
+
+GST_MSE_PRIVATE
+GstFlowReturn gst_append_pipeline_eos (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+gboolean gst_append_pipeline_stop (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+gboolean gst_append_pipeline_reset (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+gsize gst_append_pipeline_n_tracks (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+gboolean gst_append_pipeline_has_init_segment (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+GstClockTime gst_append_pipeline_get_duration (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+GPtrArray *gst_append_pipeline_get_audio_tracks (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+GPtrArray *gst_append_pipeline_get_text_tracks (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+GPtrArray *gst_append_pipeline_get_video_tracks (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+gboolean gst_append_pipeline_get_eos (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+void gst_append_pipeline_fail (GstAppendPipeline * self);
+
+GST_MSE_PRIVATE
+gboolean gst_append_pipeline_get_failed (GstAppendPipeline * self);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline.c
new file mode 100644
index 0000000000..db9cb25181
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline.c
@@ -0,0 +1,978 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 2016, 2017 Metrological Group B.V.
+ * Copyright (C) 2016, 2017 Igalia S.L
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstappendpipeline-private.h"
+
+#include "mse.h"
+
+#include "gstmselogging-private.h"
+#include "gstmsemediatype-private.h"
+#include "gstmediasourcetrack-private.h"
+
+#include
+#include
+#include
+
+typedef enum
+{
+ PARSE_ERROR,
+ IGNORED,
+ ADDED,
+} AddTrackResult;
+
+typedef struct
+{
+ GstAppendPipeline *pipeline;
+ GstTask *task;
+ GRecMutex mutex;
+ GstBus *bus;
+} BackgroundTask;
+
+typedef struct
+{
+ GstAppendPipelineCallbacks callbacks;
+ gpointer user_data;
+} Callbacks;
+
+typedef struct
+{
+ GstAppendPipeline *parent;
+ GstPad *src_pad;
+ GstAppSink *sink;
+ GstMediaSourceTrack *mse_track;
+ GstStream *stream;
+ GstClockTime previous_pts;
+} Track;
+
+typedef struct
+{
+ GstClockTime duration;
+ GPtrArray *video_tracks;
+ GPtrArray *audio_tracks;
+ GPtrArray *text_tracks;
+} InitSegment;
+
+struct _GstAppendPipeline
+{
+ GstObject parent_instance;
+
+ GstPipeline *pipeline;
+ GstAppSrc *src;
+ GstElement *parsebin;
+ GstBus *bus;
+
+ GstStreamCollection *streams;
+ GArray *tracks;
+
+ gboolean received_init_segment;
+ gboolean have_outstanding_samples;
+ InitSegment init_segment;
+
+ gboolean encountered_error;
+
+ BackgroundTask *task;
+
+ Callbacks callbacks;
+};
+
+G_DEFINE_TYPE (GstAppendPipeline, gst_append_pipeline, GST_TYPE_OBJECT);
+
+#define END_OF_APPEND "end-of-append"
+#define ABORT "abort"
+#define SHUTDOWN "shutdown"
+
+static void process_init_segment (GstAppendPipeline *);
+
+static gboolean
+send_abort (GstAppendPipeline * self)
+{
+ return gst_bus_post (self->bus, gst_message_new_application (NULL,
+ gst_structure_new_empty (ABORT)));
+}
+
+static gboolean
+send_shutdown (GstAppendPipeline * self)
+{
+ return gst_bus_post (self->bus, gst_message_new_application (NULL,
+ gst_structure_new_empty (SHUTDOWN)));
+}
+
+static GstEvent *
+new_end_of_append_event (void)
+{
+ GstStructure *structure = gst_structure_new_empty (END_OF_APPEND);
+ return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, structure);
+}
+
+static gboolean
+is_end_of_append_event (GstEvent * event)
+{
+ return GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM
+ && gst_event_has_name (event, END_OF_APPEND);
+}
+
+static inline guint
+n_tracks (GstAppendPipeline * self)
+{
+ return self->tracks->len;
+}
+
+static inline Track *
+index_track (GstAppendPipeline * self, guint i)
+{
+ return &g_array_index (self->tracks, Track, i);
+}
+
+static inline void
+call_parse_error (GstAppendPipeline * self)
+{
+ GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks;
+ gpointer user_data = self->callbacks.user_data;
+
+ if (callbacks->error) {
+ callbacks->error (self, user_data);
+ GST_TRACE_OBJECT (self, "done");
+ } else {
+ GST_TRACE_OBJECT (self, "dropping");
+ }
+}
+
+static inline void
+call_received_init_segment (GstAppendPipeline * self)
+{
+ GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks;
+ gpointer user_data = self->callbacks.user_data;
+
+ if (callbacks->received_init_segment) {
+ callbacks->received_init_segment (self, user_data);
+ GST_TRACE_OBJECT (self, "done");
+ } else {
+ GST_TRACE_OBJECT (self, "dropping");
+ }
+}
+
+static inline void
+call_new_sample (GstAppendPipeline * self, GstMediaSourceTrack * track,
+ GstSample * sample)
+{
+ GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks;
+ gpointer user_data = self->callbacks.user_data;
+
+ if (callbacks->new_sample) {
+ callbacks->new_sample (self, track, sample, user_data);
+ GST_TRACE_OBJECT (self, "done");
+ } else {
+ GST_TRACE_OBJECT (self, "dropping");
+ }
+}
+
+static inline void
+call_duration_changed (GstAppendPipeline * self)
+{
+ GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks;
+ gpointer user_data = self->callbacks.user_data;
+
+ if (callbacks->duration_changed) {
+ callbacks->duration_changed (self, user_data);
+ GST_TRACE_OBJECT (self, "done");
+ } else {
+ GST_TRACE_OBJECT (self, "dropping");
+ }
+}
+
+static inline void
+call_eos (GstAppendPipeline * self, GstMediaSourceTrack * track)
+{
+ GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks;
+ gpointer user_data = self->callbacks.user_data;
+
+ if (callbacks->eos) {
+ callbacks->eos (self, track, user_data);
+ GST_TRACE_OBJECT (self, "done");
+ } else {
+ GST_TRACE_OBJECT (self, "dropping");
+ }
+}
+
+static inline GstSample *
+patch_missing_duration (GstAppendPipeline * self, GstSample * sample)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
+ GST_BUFFER_DURATION (buffer) = GST_SECOND / 60;
+ GST_TRACE_OBJECT (self, "sample is missing duration, patched to %"
+ GST_TIMEP_FORMAT, &buffer->duration);
+ }
+ return sample;
+}
+
+static inline GstSample *
+patch_missing_pts (GstAppendPipeline * self, GstSample * sample, GstClockTime
+ fallback)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ if (!GST_BUFFER_PTS_IS_VALID (buffer) && GST_CLOCK_TIME_IS_VALID (fallback)) {
+ GST_TRACE_OBJECT (self, "sample is missing pts, patching with %"
+ GST_TIMEP_FORMAT, &fallback);
+ GST_BUFFER_PTS (buffer) = fallback;
+ }
+ return sample;
+}
+
+static inline GstSample *
+patch_missing_dts (GstAppendPipeline * self, GstSample * sample)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ if (!GST_BUFFER_DTS_IS_VALID (buffer) && GST_BUFFER_PTS_IS_VALID (buffer)) {
+ GST_TRACE_OBJECT (self, "sample is missing dts, patching with pts %"
+ GST_TIMEP_FORMAT, &buffer->pts);
+ GST_BUFFER_DTS (buffer) = GST_BUFFER_PTS (buffer);
+ }
+ return sample;
+}
+
+static gboolean
+consume_sample_from_track (GstAppendPipeline * self, Track * track)
+{
+ GstSample *sample = gst_app_sink_try_pull_sample (track->sink, 0);
+ if (sample == NULL) {
+ return FALSE;
+ }
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ if (!GST_IS_BUFFER (buffer)) {
+ GST_WARNING_OBJECT (self, "got null buffer in sample");
+ goto done;
+ }
+ sample = patch_missing_pts (self, sample, track->previous_pts);
+ sample = patch_missing_duration (self, sample);
+ sample = patch_missing_dts (self, sample);
+ track->previous_pts = GST_BUFFER_PTS (buffer);
+ call_new_sample (self, track->mse_track, sample);
+
+done:
+ gst_clear_sample (&sample);
+ return TRUE;
+}
+
+static void
+consume_all_samples (GstAppendPipeline * self)
+{
+ if (!self->received_init_segment) {
+ GST_DEBUG_OBJECT (self, "not all tracks are available, delaying");
+ self->have_outstanding_samples = TRUE;
+ return;
+ }
+ guint track_count = n_tracks (self);
+ while (TRUE) {
+ gboolean sample_consumed = FALSE;
+ for (guint i = 0; i < track_count; i++) {
+ Track *track = index_track (self, i);
+ sample_consumed |= consume_sample_from_track (self, track);
+ }
+ if (!sample_consumed) {
+ break;
+ }
+ }
+ call_duration_changed (self);
+ self->have_outstanding_samples = FALSE;
+}
+
+static void
+handle_shutdown (BackgroundTask * task)
+{
+ gst_task_stop (task->task);
+ GstAppendPipeline *self = task->pipeline;
+ guint track_count = n_tracks (self);
+ for (guint i = 0; i < track_count; i++) {
+ Track *track = index_track (self, i);
+ call_eos (self, track->mse_track);
+ }
+ call_eos (self, NULL);
+}
+
+static void
+handle_abort (BackgroundTask * task)
+{
+ gst_task_stop (task->task);
+}
+
+static void
+task_function (gpointer user_data)
+{
+ BackgroundTask *task = (BackgroundTask *) user_data;
+ GstAppendPipeline *self = task->pipeline;
+ GstMessage *message = gst_bus_timed_pop (task->bus, GST_CLOCK_TIME_NONE);
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_APPLICATION:{
+ if (gst_message_has_name (message, END_OF_APPEND)) {
+ GST_TRACE_OBJECT (self, "end of append");
+ consume_all_samples (self);
+ goto done;
+ }
+ if (gst_message_has_name (message, SHUTDOWN)) {
+ GST_DEBUG_OBJECT (self, "shutdown");
+ handle_shutdown (task);
+ goto done;
+ }
+ if (gst_message_has_name (message, ABORT)) {
+ GST_DEBUG_OBJECT (self, "abort");
+ handle_abort (task);
+ goto done;
+ }
+ g_error ("received unsupported application message");
+ }
+ case GST_MESSAGE_STREAM_COLLECTION:{
+ GST_DEBUG_OBJECT (self, "stream collection");
+ GstStreamCollection *streams;
+ gst_message_parse_stream_collection (message, &streams);
+ gst_clear_object (&self->streams);
+ self->streams = streams;
+ process_init_segment (self);
+ goto done;
+ }
+ case GST_MESSAGE_EOS:
+ GST_DEBUG_OBJECT (self, "end of stream");
+ if (self->have_outstanding_samples) {
+ GST_DEBUG_OBJECT (self, "consuming remaining samples before EOS");
+ consume_all_samples (self);
+ }
+ handle_shutdown (task);
+ goto done;
+ case GST_MESSAGE_ERROR:
+ GST_DEBUG_OBJECT (self, "error: %" GST_PTR_FORMAT, message);
+ self->encountered_error = TRUE;
+ call_parse_error (self);
+ handle_shutdown (task);
+ goto done;
+ default:
+ GST_TRACE_OBJECT (self, "ignoring message %" GST_PTR_FORMAT, message);
+ goto done;
+ }
+done:
+ gst_message_unref (message);
+}
+
+static inline GstAppSink *
+new_appsink (GstAppendPipeline * self, GstStreamType type)
+{
+ const gchar *type_name = gst_stream_type_get_name (type);
+ gchar *name = g_strdup_printf ("%s-%u", type_name, n_tracks (self));
+ GstAppSink *appsink =
+ GST_APP_SINK (gst_element_factory_make ("appsink", name));
+ gst_base_sink_set_sync (GST_BASE_SINK (appsink), FALSE);
+ gst_base_sink_set_async_enabled (GST_BASE_SINK (appsink), FALSE);
+ gst_base_sink_set_drop_out_of_segment (GST_BASE_SINK (appsink), FALSE);
+ gst_base_sink_set_last_sample_enabled (GST_BASE_SINK (appsink), FALSE);
+ g_free (name);
+ return appsink;
+}
+
+static GstPadProbeReturn
+black_hole_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ g_return_val_if_fail (GST_PAD_PROBE_INFO_TYPE (info) &
+ GST_PAD_PROBE_TYPE_BUFFER, GST_PAD_PROBE_DROP);
+ return GST_PAD_PROBE_DROP;
+}
+
+static GstPadProbeReturn
+event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstAppendPipeline *self = GST_APPEND_PIPELINE (user_data);
+ GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+
+ if (is_end_of_append_event (event)) {
+ GST_TRACE_OBJECT (self, "end of append event");
+ if (gst_bus_post (self->bus, gst_message_new_application (NULL,
+ gst_structure_new_empty (END_OF_APPEND)))) {
+ return GST_PAD_PROBE_DROP;
+ } else {
+ GST_ERROR_OBJECT (self, "failed to post end of append");
+ goto error;
+ }
+ }
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
+ GST_DEBUG_OBJECT (self, "eos event");
+ if (send_shutdown (self)) {
+ return GST_PAD_PROBE_OK;
+ } else {
+ GST_ERROR_OBJECT (self, "failed to post shutdown");
+ goto error;
+ }
+ }
+
+ return GST_PAD_PROBE_OK;
+
+error:
+ GST_PAD_PROBE_INFO_FLOW_RETURN (info) = GST_FLOW_ERROR;
+ gst_event_unref (event);
+ return GST_PAD_PROBE_HANDLED;
+}
+
+static AddTrackResult
+add_track (GstAppendPipeline * self, GstPad * pad, GstStream * stream,
+ GstCaps * caps, Track * added_track)
+{
+ GstStreamType type = gst_stream_get_stream_type (stream);
+ GstMediaSourceTrackType track_type =
+ gst_media_source_track_type_from_stream_type (type);
+
+ switch (type) {
+ case GST_STREAM_TYPE_AUDIO:
+ case GST_STREAM_TYPE_TEXT:
+ case GST_STREAM_TYPE_VIDEO:
+ break;
+ default:{
+ GST_DEBUG_OBJECT (self, "unexpected caps %" GST_PTR_FORMAT
+ ", using black hole probe", caps);
+ gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, black_hole_probe, self,
+ NULL);
+ return IGNORED;
+ }
+ }
+
+ if (type != GST_STREAM_TYPE_TEXT &&
+ !gst_media_source_media_type_is_caps_supported (caps)) {
+ GST_ERROR_OBJECT (self, "unsupported caps: %" GST_PTR_FORMAT, caps);
+ return PARSE_ERROR;
+ }
+
+ GstAppSink *appsink = new_appsink (self, type);
+ gst_bin_add (GST_BIN (self->pipeline), GST_ELEMENT (appsink));
+ gst_element_sync_state_with_parent (GST_ELEMENT (appsink));
+
+ GstPad *appsink_pad =
+ gst_element_get_static_pad (GST_ELEMENT (appsink), "sink");
+ GstPadLinkReturn link_result = gst_pad_link (pad, appsink_pad);
+ gst_clear_object (&appsink_pad);
+ if (GST_PAD_LINK_FAILED (link_result)) {
+ g_error ("failed to link parser to appsink: %s",
+ gst_pad_link_get_name (link_result));
+ }
+
+ Track track_template = {
+ .parent = self,
+ .sink = gst_object_ref (appsink),
+ .src_pad = gst_object_ref (pad),
+ .stream = gst_object_ref (stream),
+ .mse_track = gst_media_source_track_new_with_initial_caps (track_type,
+ GST_OBJECT_NAME (appsink), caps),
+ .previous_pts = GST_CLOCK_TIME_NONE,
+ };
+ g_array_append_val (self->tracks, track_template);
+
+ GST_TRACE_OBJECT (self, "added appsink %s to pad %s",
+ GST_OBJECT_NAME (appsink), GST_OBJECT_NAME (pad));
+
+ *added_track = track_template;
+
+ return ADDED;
+}
+
+static void
+clear_track (Track * track)
+{
+ gst_clear_object (&track->sink);
+ gst_clear_object (&track->src_pad);
+ gst_clear_object (&track->mse_track);
+ gst_clear_object (&track->stream);
+}
+
+static inline GPtrArray *
+init_segment_tracks_for (InitSegment * self, GstStreamType type)
+{
+ switch (type) {
+ case GST_STREAM_TYPE_AUDIO:
+ return self->audio_tracks;
+ case GST_STREAM_TYPE_TEXT:
+ return self->text_tracks;
+ case GST_STREAM_TYPE_VIDEO:
+ return self->video_tracks;
+ default:
+ g_assert_not_reached ();
+ return NULL;
+ }
+}
+
+static void
+process_init_segment_track (GstPad * pad, GstAppendPipeline * self)
+{
+ GST_OBJECT_LOCK (self);
+ InitSegment *init_segment = &self->init_segment;
+ GstStream *stream = gst_pad_get_stream (pad);
+ GstCaps *caps = gst_stream_get_caps (stream);
+ GST_DEBUG_OBJECT (self, "%" GST_PTR_FORMAT " got caps %" GST_PTR_FORMAT, pad,
+ caps);
+
+ if (gst_pad_is_linked (pad)) {
+ GST_TRACE_OBJECT (self, "%" GST_PTR_FORMAT " is already linked, skipping",
+ pad);
+ goto done;
+ }
+
+ if (!GST_IS_CAPS (caps)) {
+ GST_ERROR_OBJECT (self, "no caps on %" GST_PTR_FORMAT
+ " after stream collection", pad);
+ call_parse_error (self);
+ goto done;
+ }
+
+ Track track;
+ AddTrackResult result = add_track (self, pad, stream, caps, &track);
+ GstMediaSourceTrack *mse_track = track.mse_track;
+ switch (result) {
+ case ADDED:{
+ GstStreamType type = gst_stream_get_stream_type (stream);
+ GPtrArray *tracks = init_segment_tracks_for (init_segment, type);
+ if (tracks->len < 1) {
+ gst_media_source_track_set_active (mse_track, TRUE);
+ }
+ g_ptr_array_add (tracks, gst_object_ref (mse_track));
+ break;
+ }
+ case IGNORED:
+ break;
+ case PARSE_ERROR:
+ call_parse_error (self);
+ break;
+ }
+
+done:
+ gst_clear_object (&stream);
+ gst_clear_caps (&caps);
+ GST_OBJECT_UNLOCK (self);
+}
+
+static void
+on_pad_added (GstElement * parsebin, GstPad * pad, gpointer user_data)
+{
+ GstAppendPipeline *self = GST_APPEND_PIPELINE (user_data);
+ process_init_segment_track (pad, self);
+ process_init_segment (self);
+}
+
+static gboolean
+has_track_for_stream (GstAppendPipeline * self, GstStream * stream)
+{
+ guint track_count = n_tracks (self);
+ for (guint i = 0; i < track_count; i++) {
+ Track *track = index_track (self, i);
+ if (track->stream == stream) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+has_all_tracks (GstAppendPipeline * self)
+{
+ if (!GST_IS_STREAM_COLLECTION (self->streams)) {
+ return FALSE;
+ }
+ for (guint i = 0; i < gst_stream_collection_get_size (self->streams); i++) {
+ GstStream *stream = gst_stream_collection_get_stream (self->streams, i);
+ switch (gst_stream_get_stream_type (stream)) {
+ case GST_STREAM_TYPE_AUDIO:
+ case GST_STREAM_TYPE_VIDEO:
+ case GST_STREAM_TYPE_TEXT:
+ break;
+ default:
+ continue;
+ }
+ if (!has_track_for_stream (self, stream)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+process_init_segment (GstAppendPipeline * self)
+{
+ gint64 duration;
+ InitSegment *init_segment = &self->init_segment;
+
+ GST_OBJECT_LOCK (self);
+
+ if (!has_all_tracks (self)) {
+ goto done;
+ }
+
+ if (gst_element_query_duration (self->parsebin, GST_FORMAT_TIME, &duration)) {
+ init_segment->duration = MAX (0, duration);
+ } else {
+ init_segment->duration = GST_CLOCK_TIME_NONE;
+ }
+
+ GST_DEBUG_OBJECT (self, "init segment says duration=%" GST_TIME_FORMAT,
+ GST_TIME_ARGS ((GstClockTime) duration));
+
+ self->received_init_segment = TRUE;
+
+ call_received_init_segment (self);
+
+done:
+ GST_OBJECT_UNLOCK (self);
+}
+
+static inline void
+init_segment_init (InitSegment * self)
+{
+ self->audio_tracks = g_ptr_array_new_with_free_func (gst_object_unref);
+ self->text_tracks = g_ptr_array_new_with_free_func (gst_object_unref);
+ self->video_tracks = g_ptr_array_new_with_free_func (gst_object_unref);
+ self->duration = GST_CLOCK_TIME_NONE;
+}
+
+static inline void
+init_segment_finalize (InitSegment * self)
+{
+ g_ptr_array_free (self->audio_tracks, TRUE);
+ g_ptr_array_free (self->text_tracks, TRUE);
+ g_ptr_array_free (self->video_tracks, TRUE);
+}
+
+static GArray *
+new_tracks_array (void)
+{
+ GArray *tracks = g_array_new (TRUE, TRUE, sizeof (Track));
+ g_array_set_clear_func (tracks, (GDestroyNotify) clear_track);
+ return tracks;
+}
+
+static BackgroundTask *
+background_task_new (GstAppendPipeline * pipeline)
+{
+ BackgroundTask *task = g_new0 (BackgroundTask, 1);
+ g_rec_mutex_init (&task->mutex);
+ task->task = gst_task_new (task_function, task, NULL);
+ task->pipeline = pipeline;
+ task->bus = gst_object_ref (pipeline->bus);
+ gst_task_set_lock (task->task, &task->mutex);
+ return task;
+}
+
+static gboolean
+background_task_start (BackgroundTask * task)
+{
+ gst_bus_set_flushing (task->bus, FALSE);
+ return gst_task_start (task->task);
+}
+
+static gboolean
+background_task_stop (BackgroundTask * task)
+{
+ send_abort (task->pipeline);
+ gst_task_join (task->task);
+ gst_bus_set_flushing (task->bus, TRUE);
+ return TRUE;
+}
+
+static void
+background_task_cleanup (gpointer ptr)
+{
+ BackgroundTask *task = (BackgroundTask *) ptr;
+ background_task_stop (task);
+ task->pipeline = NULL;
+ gst_clear_object (&task->task);
+ gst_clear_object (&task->bus);
+ g_rec_mutex_clear (&task->mutex);
+ g_free (task);
+}
+
+static void
+gst_append_pipeline_init (GstAppendPipeline * self)
+{
+ GstElement *appsrc = GST_ELEMENT (gst_element_factory_make ("appsrc", "src"));
+ GstElement *parsebin =
+ GST_ELEMENT (gst_element_factory_make ("parsebin", "parse"));
+ GstElement *pipeline = gst_pipeline_new ("append-pipeline");
+
+ GstPad *appsrc_pad = GST_PAD (gst_element_get_static_pad (appsrc, "src"));
+ gst_pad_add_probe (appsrc_pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+ event_probe, self, NULL);
+ gst_object_unref (appsrc_pad);
+
+ gst_bin_add_many (GST_BIN (pipeline), appsrc, parsebin, NULL);
+ if (!gst_element_link (appsrc, parsebin)) {
+ g_error ("failed to link appsrc to parsebin");
+ }
+
+ self->bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+ self->pipeline = gst_object_ref_sink (GST_PIPELINE (pipeline));
+ self->src = GST_APP_SRC (appsrc);
+ self->parsebin = parsebin;
+
+ self->task = background_task_new (self);
+
+ g_signal_connect_object (parsebin, "pad-added", G_CALLBACK (on_pad_added),
+ self, 0);
+
+ self->received_init_segment = FALSE;
+ self->encountered_error = FALSE;
+ self->tracks = new_tracks_array ();
+ init_segment_init (&self->init_segment);
+}
+
+static void
+gst_append_pipeline_dispose (GObject * object)
+{
+ GstAppendPipeline *self = (GstAppendPipeline *) object;
+
+ send_shutdown (self);
+ g_clear_pointer (&self->task, background_task_cleanup);
+
+ gst_element_set_state (GST_ELEMENT (self->pipeline), GST_STATE_NULL);
+
+ G_OBJECT_CLASS (gst_append_pipeline_parent_class)->dispose (object);
+}
+
+static void
+gst_append_pipeline_finalize (GObject * object)
+{
+ GstAppendPipeline *self = (GstAppendPipeline *) object;
+
+ gst_clear_object (&self->pipeline);
+ gst_clear_object (&self->bus);
+
+ init_segment_finalize (&self->init_segment);
+
+ g_array_free (self->tracks, TRUE);
+ gst_clear_object (&self->streams);
+
+ G_OBJECT_CLASS (gst_append_pipeline_parent_class)->finalize (object);
+}
+
+static void
+gst_append_pipeline_class_init (GstAppendPipelineClass * klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = GST_DEBUG_FUNCPTR (gst_append_pipeline_dispose);
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_append_pipeline_finalize);
+}
+
+GstAppendPipeline *
+gst_append_pipeline_new (GstAppendPipelineCallbacks * callbacks,
+ gpointer user_data, GError ** error)
+{
+ gst_mse_init_logging ();
+ GstAppendPipeline *self = g_object_new (GST_TYPE_APPEND_PIPELINE, NULL);
+ GstStateChangeReturn started =
+ gst_element_set_state (GST_ELEMENT (self->pipeline), GST_STATE_PLAYING);
+ if (started != GST_STATE_CHANGE_SUCCESS) {
+ GST_ERROR_OBJECT (self, "failed to start: %s",
+ gst_element_state_change_return_get_name (started));
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "failed to start append pipeline");
+ goto error;
+ }
+ if (callbacks) {
+ self->callbacks.callbacks = *callbacks;
+ self->callbacks.user_data = user_data;
+ }
+ if (!background_task_start (self->task)) {
+ GST_ERROR_OBJECT (self, "failed to start background task");
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "failed to start append pipeline's background task");
+ goto error;
+ }
+ return gst_object_ref_sink (self);
+error:
+ gst_clear_object (&self);
+ return NULL;
+}
+
+GstFlowReturn
+gst_append_pipeline_append (GstAppendPipeline * self, GstBuffer * buffer)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), GST_FLOW_ERROR);
+ GstFlowReturn push_result = gst_app_src_push_buffer (self->src, buffer);
+ if (push_result != GST_FLOW_OK)
+ return push_result;
+
+ if (!gst_element_send_event (GST_ELEMENT_CAST (self->src),
+ new_end_of_append_event ())) {
+ GST_ERROR_OBJECT (self, "failed to push end-of-append event");
+ return GST_FLOW_ERROR;
+ }
+ return GST_FLOW_OK;
+}
+
+GstFlowReturn
+gst_append_pipeline_eos (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), GST_FLOW_ERROR);
+ return gst_app_src_end_of_stream (self->src);
+}
+
+gboolean
+gst_append_pipeline_stop (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE);
+
+ GstElement *pipeline = GST_ELEMENT (self->pipeline);
+
+ GstStateChangeReturn stopped =
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+ if (stopped != GST_STATE_CHANGE_SUCCESS) {
+ GST_ERROR_OBJECT (self, "failed to stop: %s",
+ gst_element_state_change_return_get_name (stopped));
+ return FALSE;
+ }
+ self->received_init_segment = FALSE;
+ self->encountered_error = FALSE;
+
+ return TRUE;
+}
+
+gboolean
+gst_append_pipeline_reset (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE);
+
+ GstElement *pipeline = GST_ELEMENT (self->pipeline);
+
+ GstStateChangeReturn stopped =
+ gst_element_set_state (pipeline, GST_STATE_READY);
+ if (stopped != GST_STATE_CHANGE_SUCCESS) {
+ GST_ERROR_OBJECT (self, "failed to stop: %s",
+ gst_element_state_change_return_get_name (stopped));
+ return FALSE;
+ }
+
+ background_task_stop (self->task);
+
+ init_segment_finalize (&self->init_segment);
+ gst_clear_object (&self->streams);
+ g_array_free (self->tracks, TRUE);
+
+ self->received_init_segment = FALSE;
+ self->have_outstanding_samples = FALSE;
+ self->encountered_error = FALSE;
+ self->tracks = new_tracks_array ();
+ init_segment_init (&self->init_segment);
+
+ if (!background_task_start (self->task)) {
+ GST_ERROR_OBJECT (self, "failed to start background task");
+ return FALSE;
+ }
+
+ GstStateChangeReturn started =
+ gst_element_set_state (pipeline, GST_STATE_PLAYING);
+ if (started == GST_STATE_CHANGE_SUCCESS) {
+ return TRUE;
+ } else {
+ GST_ERROR_OBJECT (self, "failed to start: %s",
+ gst_element_state_change_return_get_name (started));
+ return FALSE;
+ }
+}
+
+gsize
+gst_append_pipeline_n_tracks (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), 0);
+ return n_tracks (self);
+}
+
+gboolean
+gst_append_pipeline_has_init_segment (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE);
+ return self->received_init_segment;
+}
+
+GstClockTime
+gst_append_pipeline_get_duration (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), GST_CLOCK_TIME_NONE);
+ if (self->received_init_segment) {
+ return self->init_segment.duration;
+ } else {
+ return GST_CLOCK_TIME_NONE;
+ }
+}
+
+GPtrArray *
+gst_append_pipeline_get_audio_tracks (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), NULL);
+ if (self->received_init_segment) {
+ return self->init_segment.audio_tracks;
+ } else {
+ return NULL;
+ }
+}
+
+GPtrArray *
+gst_append_pipeline_get_text_tracks (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), NULL);
+ if (self->received_init_segment) {
+ return self->init_segment.text_tracks;
+ } else {
+ return NULL;
+ }
+}
+
+GPtrArray *
+gst_append_pipeline_get_video_tracks (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), NULL);
+ if (self->received_init_segment) {
+ return self->init_segment.video_tracks;
+ } else {
+ return NULL;
+ }
+}
+
+gboolean
+gst_append_pipeline_get_eos (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE);
+ return gst_task_get_state (self->task->task) != GST_TASK_STARTED;
+}
+
+void
+gst_append_pipeline_fail (GstAppendPipeline * self)
+{
+ g_return_if_fail (GST_IS_APPEND_PIPELINE (self));
+ gst_bus_post (self->bus, gst_message_new_error (NULL, NULL, NULL));
+}
+
+gboolean
+gst_append_pipeline_get_failed (GstAppendPipeline * self)
+{
+ g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE);
+ return self->encountered_error;
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource-private.h
new file mode 100644
index 0000000000..0b4e1d08cf
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource-private.h
@@ -0,0 +1,58 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "gstmediasource.h"
+#include "gstmseeventqueue-private.h"
+
+G_BEGIN_DECLS
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_is_attached (GstMediaSource * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_open (GstMediaSource * self);
+
+GST_MSE_PRIVATE
+GstMseSrc * gst_media_source_get_source_element (GstMediaSource * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_seek (GstMediaSource * self, GstClockTime time);
+
+struct _GstMediaSource
+{
+ GstObject parent_instance;
+
+ GstMseSrc *element;
+ GstMseEventQueue *event_queue;
+
+ GstSourceBufferList *buffers;
+ GstSourceBufferList *active_buffers;
+
+ GstMediaSourceRange live_seekable_range;
+
+ GstClockTime duration;
+ GstMediaSourceReadyState ready_state;
+};
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.c
new file mode 100644
index 0000000000..f499f54f24
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.c
@@ -0,0 +1,1063 @@
+/* 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);
+ }
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.h
new file mode 100644
index 0000000000..304da3e624
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.h
@@ -0,0 +1,203 @@
+/* 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "gstsourcebufferlist.h"
+
+G_BEGIN_DECLS
+
+/**
+ * GstMediaSourceReadyState:
+ * @GST_MEDIA_SOURCE_READY_STATE_CLOSED: The #GstMediaSource is not connected to
+ * any playback element.
+ * @GST_MEDIA_SOURCE_READY_STATE_OPEN: The #GstMediaSource is connected to a
+ * playback element and ready to append data to its #GstSourceBuffer (s).
+ * @GST_MEDIA_SOURCE_READY_STATE_ENDED: gst_media_source_end_of_stream() has
+ * been called on the current #GstMediaSource
+ *
+ * Describes the possible states of the Media Source.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-readystate)
+ *
+ * Since: 1.24
+ */
+typedef enum
+{
+ GST_MEDIA_SOURCE_READY_STATE_CLOSED,
+ GST_MEDIA_SOURCE_READY_STATE_OPEN,
+ GST_MEDIA_SOURCE_READY_STATE_ENDED,
+} GstMediaSourceReadyState;
+
+/**
+ * GstMediaSourceError:
+ * @GST_MEDIA_SOURCE_ERROR_INVALID_STATE:
+ * @GST_MEDIA_SOURCE_ERROR_TYPE:
+ * @GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED:
+ * @GST_MEDIA_SOURCE_ERROR_NOT_FOUND:
+ * @GST_MEDIA_SOURCE_ERROR_QUOTA_EXCEEDED:
+ *
+ * Any error that can occur within #GstMediaSource or #GstSourceBuffer APIs.
+ * These values correspond directly to those in the Web IDL specification.
+ *
+ * [Specification](https://webidl.spec.whatwg.org/#idl-DOMException-error-names)
+ *
+ * Since: 1.24
+ */
+typedef enum
+{
+ GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ GST_MEDIA_SOURCE_ERROR_TYPE,
+ GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED,
+ GST_MEDIA_SOURCE_ERROR_NOT_FOUND,
+ GST_MEDIA_SOURCE_ERROR_QUOTA_EXCEEDED,
+} GstMediaSourceError;
+
+/**
+ * GstMediaSourceEOSError:
+ * @GST_MEDIA_SOURCE_EOS_ERROR_NONE: End the stream successfully
+ * @GST_MEDIA_SOURCE_EOS_ERROR_NETWORK: End the stream due to a networking error
+ * @GST_MEDIA_SOURCE_EOS_ERROR_DECODE: End the stream due to a decoding error
+ *
+ * Reasons for ending a #GstMediaSource using gst_media_source_end_of_stream().
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-endofstreamerror)
+ *
+ * Since: 1.24
+ */
+typedef enum
+{
+ GST_MEDIA_SOURCE_EOS_ERROR_NONE,
+ GST_MEDIA_SOURCE_EOS_ERROR_NETWORK,
+ GST_MEDIA_SOURCE_EOS_ERROR_DECODE,
+} GstMediaSourceEOSError;
+
+/**
+ * GstMediaSourceRange:
+ * @start: The start of this range.
+ * @end: The end of this range.
+ *
+ * A structure describing a simplified version of the TimeRanges concept in the
+ * HTML specification, only representing a single @start and @end time.
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#timeranges)
+ *
+ * Since: 1.24
+ */
+typedef struct
+{
+ GstClockTime start;
+ GstClockTime end;
+} GstMediaSourceRange;
+
+GST_MSE_API
+gboolean gst_media_source_is_type_supported (const gchar * type);
+
+#define GST_TYPE_MEDIA_SOURCE (gst_media_source_get_type())
+#define GST_MEDIA_SOURCE_ERROR (gst_media_source_error_quark())
+
+/**
+ * gst_media_source_error_quark:
+ *
+ * Any error type that can be reported by the Media Source API.
+ *
+ * Since: 1.24
+ */
+GST_MSE_API
+GQuark gst_media_source_error_quark (void);
+
+GST_MSE_API
+G_DECLARE_FINAL_TYPE (GstMediaSource, gst_media_source, GST, MEDIA_SOURCE,
+ GstObject);
+
+GST_MSE_API
+GstMediaSource *gst_media_source_new (void);
+
+GST_MSE_API
+void gst_media_source_attach (GstMediaSource * self, GstMseSrc * element);
+
+GST_MSE_API
+void gst_media_source_detach (GstMediaSource * self);
+
+GST_MSE_API
+GstSourceBufferList * gst_media_source_get_source_buffers (
+ GstMediaSource * self);
+
+GST_MSE_API
+GstSourceBufferList * gst_media_source_get_active_source_buffers (
+ GstMediaSource * self);
+
+GST_MSE_API
+GstMediaSourceReadyState gst_media_source_get_ready_state (
+ GstMediaSource * self);
+
+GST_MSE_API
+GstClockTime gst_media_source_get_position (GstMediaSource * self);
+
+GST_MSE_API
+GstClockTime gst_media_source_get_duration (GstMediaSource * self);
+
+GST_MSE_API
+gboolean gst_media_source_set_duration (GstMediaSource * self,
+ GstClockTime duration,
+ GError ** error);
+
+GST_MSE_API
+GstSourceBuffer * gst_media_source_add_source_buffer (GstMediaSource * self,
+ const gchar * type,
+ GError ** error);
+
+GST_MSE_API
+gboolean gst_media_source_remove_source_buffer (GstMediaSource * self,
+ GstSourceBuffer * buffer,
+ GError ** error);
+
+GST_MSE_API
+gboolean gst_media_source_end_of_stream (GstMediaSource * self,
+ GstMediaSourceEOSError eos_error,
+ GError ** error);
+
+GST_MSE_API
+gboolean gst_media_source_set_live_seekable_range (GstMediaSource * self,
+ GstClockTime start,
+ GstClockTime end,
+ GError ** error);
+
+GST_MSE_API
+gboolean gst_media_source_clear_live_seekable_range (GstMediaSource * self,
+ GError ** error);
+
+GST_MSE_API
+void gst_media_source_get_live_seekable_range (GstMediaSource * self,
+ GstMediaSourceRange * range);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap-private.h
new file mode 100644
index 0000000000..f22c79bf98
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap-private.h
@@ -0,0 +1,94 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MEDIA_SOURCE_SAMPLE_MAP \
+ (gst_media_source_sample_map_get_type())
+
+GST_MSE_PRIVATE
+G_DECLARE_FINAL_TYPE (GstMediaSourceSampleMap, gst_media_source_sample_map, GST,
+ MEDIA_SOURCE_SAMPLE_MAP, GstObject);
+
+GST_MSE_PRIVATE
+GstMediaSourceSampleMap *gst_media_source_sample_map_new (void);
+
+GST_MSE_PRIVATE
+void gst_media_source_sample_map_add (GstMediaSourceSampleMap * self,
+ GstSample * sample);
+
+GST_MSE_PRIVATE
+void gst_media_source_sample_map_remove (GstMediaSourceSampleMap * self,
+ GstSample * sample);
+
+GST_MSE_PRIVATE
+gsize gst_media_source_sample_map_remove_range (GstMediaSourceSampleMap * self,
+ GstClockTime earliest, GstClockTime latest);
+
+GST_MSE_PRIVATE
+gsize
+gst_media_source_sample_map_remove_range_from_start (GstMediaSourceSampleMap
+ * self, GstClockTime latest_dts);
+
+GST_MSE_PRIVATE
+gsize
+gst_media_source_sample_map_remove_range_from_end (GstMediaSourceSampleMap
+ * self, GstClockTime earliest_dts);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_sample_map_contains (GstMediaSourceSampleMap * self,
+ GstSample * sample);
+
+GST_MSE_PRIVATE
+GSequenceIter * gst_media_source_sample_map_get_begin_iter_by_pts (
+ GstMediaSourceSampleMap * self);
+
+GST_MSE_PRIVATE
+GstClockTime gst_media_source_sample_map_get_highest_end_time (
+ GstMediaSourceSampleMap * self);
+
+GST_MSE_PRIVATE
+guint gst_media_source_sample_map_get_size (GstMediaSourceSampleMap * self);
+
+GST_MSE_PRIVATE
+gsize gst_media_source_sample_map_get_storage_size (
+ GstMediaSourceSampleMap * self);
+
+GST_MSE_PRIVATE
+GstIterator *
+gst_media_source_sample_map_iter_samples_by_dts (GstMediaSourceSampleMap * map,
+ GMutex * lock, guint32 * master_cookie, GstClockTime start_dts,
+ GstSample * start_sample);
+
+GST_MSE_PRIVATE
+GstIterator *
+gst_media_source_sample_map_iter_samples_by_pts (GstMediaSourceSampleMap * map,
+ GMutex * lock, guint32 * master_cookie, GstClockTime start_pts,
+ GstSample *start_sample);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap.c
new file mode 100644
index 0000000000..6728c6c567
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap.c
@@ -0,0 +1,551 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstmediasourcesamplemap-private.h"
+#include "gstmselogging-private.h"
+
+struct _GstMediaSourceSampleMap
+{
+ GstObject parent_instance;
+
+ GHashTable *samples;
+ GSequence *samples_by_dts;
+ GSequence *samples_by_pts;
+
+ gsize storage_size;
+};
+
+G_DEFINE_TYPE (GstMediaSourceSampleMap, gst_media_source_sample_map,
+ GST_TYPE_OBJECT);
+
+static gint compare_pts (GstSample * a, GstSample * b, gpointer user_data);
+static GSequenceIter *find_sample_containing_dts (GstMediaSourceSampleMap *
+ self, GstClockTime dts);
+static GSequenceIter *find_sample_containing_pts (GstMediaSourceSampleMap *
+ self, GstClockTime pts);
+static GSequenceIter *find_sequentially (GSequence * sequence, gpointer item);
+
+static inline GstClockTime
+sample_dts (GstSample * sample)
+{
+ return GST_BUFFER_DTS (gst_sample_get_buffer (sample));
+}
+
+static inline GstClockTime
+sample_pts (GstSample * sample)
+{
+ return GST_BUFFER_PTS (gst_sample_get_buffer (sample));
+}
+
+static gsize
+sample_buffer_size (GstSample * sample)
+{
+ return gst_buffer_get_size (gst_sample_get_buffer (sample));
+}
+
+static gboolean
+iter_is_delta_unit (GSequenceIter * iter)
+{
+ if (g_sequence_iter_is_end (iter)) {
+ return TRUE;
+ }
+ GstSample *sample = g_sequence_get (iter);
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ return GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
+}
+
+static gint
+compare_dts (GstSample * a, GstSample * b, gpointer user_data)
+{
+ GstClockTime a_dts = sample_dts (a);
+ GstClockTime b_dts = sample_dts (b);
+ if (a_dts == b_dts) {
+ return compare_pts (a, b, user_data);
+ }
+ return a_dts > b_dts ? +1 : -1;
+}
+
+static gint
+compare_pts (GstSample * a, GstSample * b, gpointer user_data)
+{
+ GstClockTime a_pts = sample_pts (a);
+ GstClockTime b_pts = sample_pts (b);
+ if (a_pts == b_pts) {
+ return 0;
+ }
+ return a_pts > b_pts ? +1 : -1;
+}
+
+static inline void
+remove_sample (GSequence * sequence, GCompareDataFunc compare,
+ GstSample * sample, GstMediaSourceSampleMap * self)
+{
+ GSequenceIter *match = g_sequence_lookup (sequence, sample, compare, self);
+ if (match == NULL) {
+ return;
+ }
+ g_sequence_remove (match);
+}
+
+GstMediaSourceSampleMap *
+gst_media_source_sample_map_new (void)
+{
+ return gst_object_ref_sink (g_object_new (GST_TYPE_MEDIA_SOURCE_SAMPLE_MAP,
+ NULL));
+}
+
+static void
+gst_media_source_sample_map_finalize (GObject * object)
+{
+ GstMediaSourceSampleMap *self = GST_MEDIA_SOURCE_SAMPLE_MAP (object);
+ g_sequence_free (self->samples_by_dts);
+ g_sequence_free (self->samples_by_pts);
+ g_hash_table_unref (self->samples);
+ G_OBJECT_CLASS (gst_media_source_sample_map_parent_class)->finalize (object);
+}
+
+static void
+gst_media_source_sample_map_class_init (GstMediaSourceSampleMapClass * klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_media_source_sample_map_finalize);
+}
+
+static void
+gst_media_source_sample_map_init (GstMediaSourceSampleMap * self)
+{
+ self->samples = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ (GDestroyNotify) gst_sample_unref, NULL);
+ self->samples_by_dts = g_sequence_new ((GDestroyNotify) gst_sample_unref);
+ self->samples_by_pts = g_sequence_new ((GDestroyNotify) gst_sample_unref);
+}
+
+void
+gst_media_source_sample_map_add (GstMediaSourceSampleMap * self,
+ GstSample * sample)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self));
+ g_return_if_fail (GST_IS_SAMPLE (sample));
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ g_return_if_fail (GST_BUFFER_DTS_IS_VALID (buffer));
+ g_return_if_fail (GST_BUFFER_PTS_IS_VALID (buffer));
+ g_return_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer));
+
+ if (g_hash_table_contains (self->samples, sample)) {
+ return;
+ }
+
+ g_hash_table_add (self->samples, gst_sample_ref (sample));
+ g_sequence_insert_sorted (self->samples_by_dts, gst_sample_ref (sample),
+ (GCompareDataFunc) compare_dts, self);
+ g_sequence_insert_sorted (self->samples_by_pts, gst_sample_ref (sample),
+ (GCompareDataFunc) compare_pts, self);
+ self->storage_size += gst_buffer_get_size (buffer);
+ GST_TRACE_OBJECT (self, "new storage size=%" G_GSIZE_FORMAT,
+ self->storage_size);
+}
+
+void
+gst_media_source_sample_map_remove (GstMediaSourceSampleMap * self,
+ GstSample * sample)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self));
+ g_return_if_fail (GST_IS_SAMPLE (sample));
+
+ if (!g_hash_table_contains (self->samples, sample)) {
+ return;
+ }
+
+ gsize buffer_size = sample_buffer_size (sample);
+ remove_sample (self->samples_by_dts, (GCompareDataFunc) compare_dts, sample,
+ self);
+ remove_sample (self->samples_by_pts, (GCompareDataFunc) compare_pts, sample,
+ self);
+ g_hash_table_remove (self->samples, sample);
+ self->storage_size -= MIN (buffer_size, self->storage_size);
+}
+
+gboolean
+gst_media_source_sample_map_contains (GstMediaSourceSampleMap * self,
+ GstSample * sample)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), FALSE);
+ return g_hash_table_contains (self->samples, sample);
+}
+
+gsize
+gst_media_source_sample_map_remove_range (GstMediaSourceSampleMap * self,
+ GstClockTime earliest, GstClockTime latest)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), 0);
+ g_return_val_if_fail (earliest <= latest, 0);
+
+ GSequenceIter *start_by_dts = find_sample_containing_dts (self, earliest);
+ GSequenceIter *end_by_dts = find_sample_containing_dts (self, latest);
+
+ GstSample *start = g_sequence_get (start_by_dts);
+ GstSample *end = g_sequence_get (end_by_dts);
+
+ GstClockTime start_time =
+ start == NULL ? GST_CLOCK_TIME_NONE : sample_dts (start);
+ GstClockTime end_time = end == NULL ? start_time : sample_dts (end);
+
+ GST_TRACE_OBJECT (self, "remove range [%" GST_TIMEP_FORMAT ",%"
+ GST_TIMEP_FORMAT ")", &start_time, &end_time);
+
+ GList *to_remove = NULL;
+ while (g_sequence_iter_compare (start_by_dts, end_by_dts) < 1) {
+ GstSample *sample = g_sequence_get (start_by_dts);
+ to_remove = g_list_prepend (to_remove, sample);
+ start_by_dts = g_sequence_iter_next (start_by_dts);
+ }
+ gsize bytes_removed = 0;
+ for (GList * iter = to_remove; iter != NULL; iter = g_list_next (iter)) {
+ GstSample *sample = iter->data;
+ bytes_removed += sample_buffer_size (sample);
+ gst_media_source_sample_map_remove (self, sample);
+ }
+
+ g_list_free (to_remove);
+
+ GST_TRACE_OBJECT (self, "removed=%" G_GSIZE_FORMAT "B, latest=%"
+ GST_TIMEP_FORMAT, bytes_removed, &latest);
+ return bytes_removed;
+}
+
+gsize
+gst_media_source_sample_map_remove_range_from_start (GstMediaSourceSampleMap
+ * self, GstClockTime latest_dts)
+{
+ return gst_media_source_sample_map_remove_range (self, 0, latest_dts);
+}
+
+gsize
+gst_media_source_sample_map_remove_range_from_end (GstMediaSourceSampleMap
+ * self, GstClockTime earliest_dts)
+{
+ return gst_media_source_sample_map_remove_range (self, earliest_dts,
+ GST_CLOCK_TIME_NONE);
+}
+
+GstClockTime
+gst_media_source_sample_map_get_highest_end_time (GstMediaSourceSampleMap *
+ self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self),
+ GST_CLOCK_TIME_NONE);
+ GSequenceIter *iter = g_sequence_get_end_iter (self->samples_by_pts);
+ iter = g_sequence_iter_prev (iter);
+ if (g_sequence_iter_is_begin (iter)) {
+ return GST_CLOCK_TIME_NONE;
+ }
+ GstSample *sample = g_sequence_get (iter);
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ g_return_val_if_fail (GST_BUFFER_PTS_IS_VALID (buffer), GST_CLOCK_TIME_NONE);
+ g_return_val_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer),
+ GST_CLOCK_TIME_NONE);
+ return GST_BUFFER_PTS (buffer) + GST_BUFFER_DURATION (buffer);
+}
+
+guint
+gst_media_source_sample_map_get_size (GstMediaSourceSampleMap * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), 0);
+ return g_hash_table_size (self->samples);
+}
+
+gsize
+gst_media_source_sample_map_get_storage_size (GstMediaSourceSampleMap * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), 0);
+ return self->storage_size;
+}
+
+static inline gboolean
+sample_contains_dts (GstSample * sample, GstClockTime dts)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ g_return_val_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer), FALSE);
+ g_return_val_if_fail (GST_BUFFER_DTS_IS_VALID (buffer), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (dts), FALSE);
+ GstClockTime end = GST_BUFFER_DTS (buffer) + GST_BUFFER_DURATION (buffer);
+ return dts <= end;
+}
+
+static inline gboolean
+sample_contains_pts (GstSample * sample, GstClockTime pts)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ g_return_val_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer), FALSE);
+ g_return_val_if_fail (GST_BUFFER_PTS_IS_VALID (buffer), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (pts), FALSE);
+ GstClockTime end = GST_BUFFER_PTS (buffer) + GST_BUFFER_DURATION (buffer);
+ return pts <= end;
+}
+
+static GSequenceIter *
+find_sequentially (GSequence * sequence, gpointer item)
+{
+ GSequenceIter *iter = g_sequence_get_begin_iter (sequence);
+ for (; !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
+ gpointer current = g_sequence_get (iter);
+ if (current == item) {
+ return iter;
+ }
+ }
+ return iter;
+}
+
+static GSequenceIter *
+find_sample_containing_dts (GstMediaSourceSampleMap * self, GstClockTime dts)
+{
+ if (dts == 0) {
+ return g_sequence_get_begin_iter (self->samples_by_dts);
+ }
+ if (dts == GST_CLOCK_TIME_NONE) {
+ return g_sequence_get_end_iter (self->samples_by_dts);
+ }
+ GSequenceIter *iter = g_sequence_get_begin_iter (self->samples_by_dts);
+ while (!g_sequence_iter_is_end (iter)) {
+ GstSample *sample = g_sequence_get (iter);
+ if (sample_contains_dts (sample, dts)) {
+ return iter;
+ }
+ iter = g_sequence_iter_next (iter);
+ }
+ return iter;
+}
+
+static GSequenceIter *
+find_sample_containing_pts (GstMediaSourceSampleMap * self, GstClockTime pts)
+{
+ if (pts == 0) {
+ return g_sequence_get_begin_iter (self->samples_by_pts);
+ }
+ if (pts == GST_CLOCK_TIME_NONE) {
+ return g_sequence_get_end_iter (self->samples_by_pts);
+ }
+ GSequenceIter *iter = g_sequence_get_begin_iter (self->samples_by_pts);
+ while (!g_sequence_iter_is_end (iter)) {
+ GstSample *sample = g_sequence_get (iter);
+ if (sample_contains_pts (sample, pts)) {
+ return iter;
+ }
+ iter = g_sequence_iter_next (iter);
+ }
+ return iter;
+}
+
+static GSequenceIter *
+find_previous_non_delta_unit (GstMediaSourceSampleMap * self,
+ GSequenceIter * iter)
+{
+ while (!g_sequence_iter_is_begin (iter)) {
+ if (!iter_is_delta_unit (iter)) {
+ GST_TRACE_OBJECT (self, "found valid sample");
+ return iter;
+ }
+ iter = g_sequence_iter_prev (iter);
+ }
+ GST_TRACE_OBJECT (self, "rolled back to the first sample");
+ return iter;
+}
+
+static GSequenceIter *
+gst_media_source_sample_map_iter_starting_dts (GstMediaSourceSampleMap * self,
+ GstClockTime start_dts)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), NULL);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start_dts), NULL);
+ GSequenceIter *iter = find_sample_containing_dts (self, start_dts);
+ return find_previous_non_delta_unit (self, iter);
+}
+
+static GSequenceIter *
+gst_media_source_sample_map_iter_starting_pts (GstMediaSourceSampleMap
+ * self, GstClockTime start_pts)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), NULL);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start_pts), NULL);
+ GSequenceIter *iter = find_sample_containing_pts (self, start_pts);
+ return find_previous_non_delta_unit (self, iter);
+}
+
+typedef struct _SampleMapIterator SampleMapIterator;
+
+struct _SampleMapIterator
+{
+ GstIterator iterator;
+ GstMediaSourceSampleMap *map;
+/* *INDENT-OFF* */
+ GstClockTime (*timestamp_func) (GstSample *);
+ GSequenceIter *(*resync_locator_func) (SampleMapIterator *);
+/* *INDENT-ON* */
+
+ GstClockTime start_time;
+ GstClockTime current_time;
+ GSequenceIter *current_iter;
+ GstSample *current_sample;
+};
+
+static void
+iter_copy (const SampleMapIterator * it, SampleMapIterator * copy)
+{
+ copy->map = gst_object_ref (it->map);
+ copy->timestamp_func = it->timestamp_func;
+ copy->resync_locator_func = it->resync_locator_func;
+
+ copy->current_time = it->current_time;
+ copy->start_time = it->start_time;
+ copy->current_iter = it->current_iter;
+ copy->current_sample = gst_sample_ref (it->current_sample);
+}
+
+static GSequenceIter *
+iter_find_resync_point_dts (SampleMapIterator * it)
+{
+ if (it->current_sample) {
+ GSequenceIter *iter =
+ find_sequentially (it->map->samples_by_dts, it->current_sample);
+ if (!g_sequence_iter_is_end (iter)) {
+ return g_sequence_iter_next (iter);
+ }
+ }
+
+ return gst_media_source_sample_map_iter_starting_dts (it->map,
+ it->current_time);
+}
+
+static GSequenceIter *
+iter_find_resync_point_pts (SampleMapIterator * it)
+{
+ if (it->current_sample) {
+ GSequenceIter *iter =
+ find_sequentially (it->map->samples_by_pts, it->current_sample);
+ if (!g_sequence_iter_is_end (iter)) {
+ return g_sequence_iter_next (iter);
+ }
+ }
+
+ return gst_media_source_sample_map_iter_starting_pts (it->map,
+ it->current_time);
+}
+
+static GstIteratorResult
+iter_next (SampleMapIterator * it, GValue * result)
+{
+
+ if (g_sequence_iter_is_end (it->current_iter)) {
+ return GST_ITERATOR_DONE;
+ }
+
+ GstSample *sample = g_sequence_get (it->current_iter);
+ gst_clear_sample (&it->current_sample);
+ it->current_sample = gst_sample_ref (sample);
+
+ it->current_time = it->timestamp_func (sample);
+ it->current_iter = g_sequence_iter_next (it->current_iter);
+
+ gst_value_set_sample (result, sample);
+
+ return GST_ITERATOR_OK;
+}
+
+static void
+iter_resync (SampleMapIterator * it)
+{
+ GST_TRACE_OBJECT (it->map, "resync");
+ it->current_time = it->start_time;
+ it->current_iter = it->resync_locator_func (it);
+}
+
+static void
+iter_free (SampleMapIterator * it)
+{
+ gst_clear_object (&it->map);
+ gst_clear_sample (&it->current_sample);
+}
+
+GstIterator *
+gst_media_source_sample_map_iter_samples_by_dts (GstMediaSourceSampleMap * map,
+ GMutex * lock, guint32 * master_cookie, GstClockTime start_dts,
+ GstSample * start_sample)
+{
+/* *INDENT-OFF* */
+ SampleMapIterator *it = (SampleMapIterator *) gst_iterator_new (
+ sizeof (SampleMapIterator),
+ GST_TYPE_SAMPLE,
+ lock,
+ master_cookie,
+ (GstIteratorCopyFunction) iter_copy,
+ (GstIteratorNextFunction) iter_next,
+ (GstIteratorItemFunction) NULL,
+ (GstIteratorResyncFunction) iter_resync,
+ (GstIteratorFreeFunction) iter_free
+ );
+/* *INDENT-ON* */
+
+ it->map = gst_object_ref (map);
+ it->timestamp_func = sample_dts;
+ it->resync_locator_func = iter_find_resync_point_dts;
+ it->start_time = start_dts;
+ it->current_time = start_dts;
+ it->current_sample = start_sample ? gst_sample_ref (start_sample) : NULL;
+ it->current_iter = iter_find_resync_point_dts (it);
+
+ return GST_ITERATOR (it);
+}
+
+GstIterator *
+gst_media_source_sample_map_iter_samples_by_pts (GstMediaSourceSampleMap * map,
+ GMutex * lock, guint32 * master_cookie, GstClockTime start_pts,
+ GstSample * start_sample)
+{
+/* *INDENT-OFF* */
+ SampleMapIterator *it = (SampleMapIterator *) gst_iterator_new (
+ sizeof (SampleMapIterator),
+ GST_TYPE_SAMPLE,
+ lock,
+ master_cookie,
+ (GstIteratorCopyFunction) iter_copy,
+ (GstIteratorNextFunction) iter_next,
+ (GstIteratorItemFunction) NULL,
+ (GstIteratorResyncFunction) iter_resync,
+ (GstIteratorFreeFunction) iter_free
+ );
+/* *INDENT-ON* */
+
+ it->map = gst_object_ref (map);
+ it->timestamp_func = sample_pts;
+ it->resync_locator_func = iter_find_resync_point_pts;
+ it->start_time = start_pts;
+ it->current_time = start_pts;
+ it->current_sample = start_sample ? gst_sample_ref (start_sample) : NULL;
+ it->current_iter = iter_find_resync_point_pts (it);
+
+ return GST_ITERATOR (it);
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack-private.h
new file mode 100644
index 0000000000..68c834efbd
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack-private.h
@@ -0,0 +1,110 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO,
+ GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO,
+ GST_MEDIA_SOURCE_TRACK_TYPE_TEXT,
+ GST_MEDIA_SOURCE_TRACK_TYPE_OTHER,
+} GstMediaSourceTrackType;
+
+#define GST_TYPE_MEDIA_SOURCE_TRACK (gst_media_source_track_get_type())
+
+GST_MSE_PRIVATE
+G_DECLARE_FINAL_TYPE (GstMediaSourceTrack, gst_media_source_track, GST,
+ MEDIA_SOURCE_TRACK, GstObject);
+
+GST_MSE_PRIVATE
+GstMediaSourceTrackType gst_media_source_track_get_track_type (
+ GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+const gchar *
+gst_media_source_track_get_id (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_get_active (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+GstMediaSourceTrack *gst_media_source_track_new (GstMediaSourceTrackType type,
+ const gchar * track_id);
+
+GST_MSE_PRIVATE
+GstMediaSourceTrack *
+gst_media_source_track_new_with_initial_caps (GstMediaSourceTrackType type,
+ const gchar * track_id, GstCaps * initial_caps);
+
+GST_MSE_PRIVATE
+GstMediaSourceTrack * gst_media_source_track_new_with_size (
+ GstMediaSourceTrackType type, const gchar * track_id, gsize size);
+
+GST_MSE_PRIVATE
+GstCaps *gst_media_source_track_get_initial_caps (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+GstStreamType
+gst_media_source_track_get_stream_type (GstMediaSourceTrack * self);
+
+GST_MSE_API
+void gst_media_source_track_set_active (GstMediaSourceTrack * self,
+ gboolean active);
+
+GST_MSE_PRIVATE
+GstMiniObject *gst_media_source_track_pop (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_push (GstMediaSourceTrack * self,
+ GstSample * sample);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_try_push (GstMediaSourceTrack * self,
+ GstSample * sample);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_push_eos (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_flush (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_resume (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_is_empty (GstMediaSourceTrack * self);
+
+GST_MSE_PRIVATE
+GstStreamType gst_media_source_track_type_to_stream_type (
+ GstMediaSourceTrackType type);
+
+GST_MSE_PRIVATE
+GstMediaSourceTrackType gst_media_source_track_type_from_stream_type (
+ GstStreamType type);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack.c
new file mode 100644
index 0000000000..378c5c9cf4
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack.c
@@ -0,0 +1,458 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+#include "gstmediasourcetrack-private.h"
+
+#include "gstmselogging-private.h"
+
+#include
+
+struct _GstMediaSourceTrack
+{
+ GstObject parent_instance;
+
+ GstMediaSourceTrackType track_type;
+ gchar *track_id;
+ gboolean active;
+ GstCaps *initial_caps;
+
+ gsize queue_size;
+ GstDataQueue *samples;
+};
+
+G_DEFINE_TYPE (GstMediaSourceTrack, gst_media_source_track, GST_TYPE_OBJECT);
+
+#define DEFAULT_QUEUE_SIZE (1 << 10)
+
+enum
+{
+ PROP_0,
+
+ PROP_TRACK_TYPE,
+ PROP_TRACK_ID,
+ PROP_ACTIVE,
+ PROP_INITIAL_CAPS,
+ PROP_QUEUE_SIZE,
+
+ N_PROPS,
+};
+
+enum
+{
+ ON_NOT_EMPTY,
+
+ N_SIGNALS,
+};
+
+static GParamSpec *properties[N_PROPS];
+static guint signals[N_SIGNALS];
+
+static GstMediaSourceTrack *
+_gst_media_source_track_new_full (GstMediaSourceTrackType type,
+ const gchar * track_id, gsize size, GstCaps * initial_caps)
+{
+ g_return_val_if_fail (type >= 0, NULL);
+ g_return_val_if_fail (type <= GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, NULL);
+
+ gst_mse_init_logging ();
+
+ GstMediaSourceTrack *self = g_object_new (GST_TYPE_MEDIA_SOURCE_TRACK,
+ "track-type", type, "track-id", track_id, "queue-size", size,
+ "initial-caps", initial_caps, NULL);
+
+ return gst_object_ref_sink (self);
+}
+
+GstMediaSourceTrack *
+gst_media_source_track_new_with_size (GstMediaSourceTrackType type,
+ const gchar * track_id, gsize size)
+{
+ return _gst_media_source_track_new_full (type, track_id, size, NULL);
+}
+
+GstMediaSourceTrack *
+gst_media_source_track_new (GstMediaSourceTrackType type,
+ const gchar * track_id)
+{
+ return _gst_media_source_track_new_full (type, track_id, DEFAULT_QUEUE_SIZE,
+ NULL);
+}
+
+GstMediaSourceTrack *
+gst_media_source_track_new_with_initial_caps (GstMediaSourceTrackType type,
+ const gchar * track_id, GstCaps * initial_caps)
+{
+ g_return_val_if_fail (GST_IS_CAPS (initial_caps), NULL);
+ return _gst_media_source_track_new_full (type, track_id, DEFAULT_QUEUE_SIZE,
+ initial_caps);
+}
+
+static void
+gst_media_source_track_finalize (GObject * object)
+{
+ GstMediaSourceTrack *self = (GstMediaSourceTrack *) object;
+
+ g_free (self->track_id);
+ gst_clear_caps (&self->initial_caps);
+ g_object_unref (self->samples);
+
+ G_OBJECT_CLASS (gst_media_source_track_parent_class)->finalize (object);
+}
+
+static void
+gst_media_source_track_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstMediaSourceTrack *self = GST_MEDIA_SOURCE_TRACK (object);
+
+ switch (prop_id) {
+ case PROP_TRACK_TYPE:
+ g_value_set_enum (value, gst_media_source_track_get_track_type (self));
+ break;
+ case PROP_TRACK_ID:
+ g_value_set_static_string (value, gst_media_source_track_get_id (self));
+ break;
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, gst_media_source_track_get_active (self));
+ break;
+ case PROP_INITIAL_CAPS:
+ g_value_take_boxed (value,
+ gst_media_source_track_get_initial_caps (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gst_media_source_track_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstMediaSourceTrack *self = GST_MEDIA_SOURCE_TRACK (object);
+
+ switch (prop_id) {
+ case PROP_TRACK_TYPE:
+ self->track_type = g_value_get_enum (value);
+ break;
+ case PROP_TRACK_ID:
+ self->track_id = g_value_dup_string (value);
+ break;
+ case PROP_ACTIVE:
+ gst_media_source_track_set_active (self, g_value_get_boolean (value));
+ break;
+ case PROP_INITIAL_CAPS:{
+ const GstCaps *caps = gst_value_get_caps (value);
+ self->initial_caps = caps == NULL ? NULL : gst_caps_copy (caps);
+ break;
+ }
+ case PROP_QUEUE_SIZE:{
+ self->queue_size = g_value_get_ulong (value);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gst_media_source_track_class_init (GstMediaSourceTrackClass * klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_media_source_track_finalize);
+ oclass->get_property =
+ GST_DEBUG_FUNCPTR (gst_media_source_track_get_property);
+ oclass->set_property =
+ GST_DEBUG_FUNCPTR (gst_media_source_track_set_property);
+
+ signals[ON_NOT_EMPTY] = g_signal_new ("on-not-empty",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ properties[PROP_TRACK_TYPE] =
+ g_param_spec_enum ("track-type", "Track Type",
+ "Type of media in this Track, either Audio, Video, Text, or Other.",
+ GST_TYPE_MEDIA_SOURCE_TRACK_TYPE, GST_MEDIA_SOURCE_TRACK_TYPE_OTHER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_TRACK_ID] =
+ g_param_spec_string ("track-id", "Track ID",
+ "Identifier for this Track that must be unique within a Source Buffer.",
+ "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ACTIVE] =
+ g_param_spec_boolean ("active", "Active",
+ "Whether this Track requires its parent Source Buffer to be in its "
+ "parent Media Source's Active Source Buffers list", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_INITIAL_CAPS] =
+ g_param_spec_boxed ("initial-caps", "Initial Caps",
+ "GstCaps discovered in the first Initialization Segment",
+ GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_QUEUE_SIZE] =
+ g_param_spec_ulong ("queue-size", "Queue Size",
+ "Maximum Track Queue size",
+ 0, G_MAXULONG, DEFAULT_QUEUE_SIZE,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, N_PROPS, properties);
+}
+
+static gboolean
+check_queue_depth (GstDataQueue * queue, guint visible, guint bytes,
+ guint64 time, gpointer checkdata)
+{
+ GstMediaSourceTrack *self = GST_MEDIA_SOURCE_TRACK (checkdata);
+ return visible >= self->queue_size;
+}
+
+static void
+gst_media_source_track_init (GstMediaSourceTrack * self)
+{
+ self->samples = gst_data_queue_new (check_queue_depth, NULL, NULL, self);
+}
+
+GstMediaSourceTrackType
+gst_media_source_track_get_track_type (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self),
+ GST_MEDIA_SOURCE_TRACK_TYPE_OTHER);
+ return self->track_type;
+}
+
+GstStreamType
+gst_media_source_track_get_stream_type (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self),
+ GST_STREAM_TYPE_UNKNOWN);
+ return gst_media_source_track_type_to_stream_type (self->track_type);
+}
+
+const gchar *
+gst_media_source_track_get_id (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), NULL);
+ return self->track_id;
+}
+
+GstCaps *
+gst_media_source_track_get_initial_caps (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), NULL);
+ if (GST_IS_CAPS (self->initial_caps)) {
+ return self->initial_caps;
+ } else {
+ return NULL;
+ }
+}
+
+gboolean
+gst_media_source_track_get_active (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE);
+ return self->active;
+}
+
+void
+gst_media_source_track_set_active (GstMediaSourceTrack * self, gboolean active)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self));
+ self->active = active;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVE]);
+}
+
+GstMiniObject *
+gst_media_source_track_pop (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), NULL);
+ GstDataQueueItem *item = NULL;
+ if (!gst_data_queue_pop (self->samples, &item)) {
+ return NULL;
+ }
+ GstMiniObject *object = item->object;
+ item->object = NULL;
+ item->destroy (item);
+ return object;
+}
+
+static inline gsize
+sample_size (GstSample * sample)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ return gst_buffer_get_size (buffer);
+}
+
+static inline GstClockTime
+sample_duration (GstSample * sample)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ return GST_BUFFER_DURATION (buffer);
+}
+
+static void
+destroy_item (GstDataQueueItem * item)
+{
+ gst_clear_mini_object (&item->object);
+ g_free (item);
+}
+
+static inline GstDataQueueItem *
+wrap_sample (GstSample * sample)
+{
+ GstDataQueueItem item = {
+ .object = GST_MINI_OBJECT (sample),
+ .size = sample_size (sample),
+ .duration = sample_duration (sample),
+ .destroy = (GDestroyNotify) destroy_item,
+ .visible = TRUE,
+ };
+ return g_memdup2 (&item, sizeof (GstDataQueueItem));
+}
+
+gboolean
+gst_media_source_track_push (GstMediaSourceTrack * self, GstSample * sample)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE);
+ g_return_val_if_fail (GST_IS_SAMPLE (sample), FALSE);
+
+ gboolean was_empty = gst_media_source_track_is_empty (self);
+
+ GstDataQueueItem *item = wrap_sample (sample);
+
+ gboolean result = gst_data_queue_push (self->samples, item);
+
+ if (result) {
+ if (was_empty) {
+ g_signal_emit (self, signals[ON_NOT_EMPTY], 0);
+ }
+ return TRUE;
+ } else {
+ item->destroy (item);
+ return FALSE;
+ }
+}
+
+static inline GstDataQueueItem *
+wrap_eos (void)
+{
+ GstEvent *event = gst_event_ref (gst_event_new_eos ());
+
+ GstDataQueueItem item = {
+ .object = GST_MINI_OBJECT (event),
+ .size = 0,
+ .duration = 0,
+ .destroy = (GDestroyNotify) destroy_item,
+ .visible = TRUE,
+ };
+ return g_memdup2 (&item, sizeof (GstDataQueueItem));
+}
+
+gboolean
+gst_media_source_track_push_eos (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE);
+
+ GstDataQueueItem *item = wrap_eos ();
+
+ gboolean result = gst_data_queue_push (self->samples, item);
+
+ if (result) {
+ return TRUE;
+ } else {
+ item->destroy (item);
+ return FALSE;
+ }
+}
+
+void
+gst_media_source_track_flush (GstMediaSourceTrack * self)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self));
+ gst_data_queue_set_flushing (self->samples, TRUE);
+ gst_data_queue_flush (self->samples);
+}
+
+void
+gst_media_source_track_resume (GstMediaSourceTrack * self)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self));
+ gst_data_queue_set_flushing (self->samples, FALSE);
+}
+
+gboolean
+gst_media_source_track_try_push (GstMediaSourceTrack * self, GstSample * sample)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE);
+ g_return_val_if_fail (GST_IS_SAMPLE (sample), FALSE);
+
+ if (gst_data_queue_is_full (self->samples)) {
+ return FALSE;
+ }
+
+ return gst_media_source_track_push (self, sample);
+}
+
+gboolean
+gst_media_source_track_is_empty (GstMediaSourceTrack * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE);
+ return gst_data_queue_is_empty (self->samples);
+}
+
+GstStreamType
+gst_media_source_track_type_to_stream_type (GstMediaSourceTrackType type)
+{
+ switch (type) {
+ case GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO:
+ return GST_STREAM_TYPE_AUDIO;
+ case GST_MEDIA_SOURCE_TRACK_TYPE_TEXT:
+ return GST_STREAM_TYPE_TEXT;
+ case GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO:
+ return GST_STREAM_TYPE_VIDEO;
+ default:
+ return GST_STREAM_TYPE_UNKNOWN;
+ }
+}
+
+GstMediaSourceTrackType
+gst_media_source_track_type_from_stream_type (GstStreamType type)
+{
+ switch (type) {
+ case GST_STREAM_TYPE_AUDIO:
+ return GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO;
+ case GST_STREAM_TYPE_TEXT:
+ return GST_MEDIA_SOURCE_TRACK_TYPE_TEXT;
+ case GST_STREAM_TYPE_VIDEO:
+ return GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO;
+ default:
+ return GST_MEDIA_SOURCE_TRACK_TYPE_OTHER;
+ }
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer-private.h
new file mode 100644
index 0000000000..8abfa77b61
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer-private.h
@@ -0,0 +1,100 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MEDIA_SOURCE_TRACK_BUFFER \
+ (gst_media_source_track_buffer_get_type())
+
+GST_MSE_PRIVATE
+G_DECLARE_FINAL_TYPE (GstMediaSourceTrackBuffer, gst_media_source_track_buffer,
+ GST, MEDIA_SOURCE_TRACK_BUFFER, GstObject);
+
+GST_MSE_PRIVATE
+GstMediaSourceTrackBuffer *gst_media_source_track_buffer_new (void);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_buffer_is_empty (
+ GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+gint gst_media_source_track_buffer_get_size (GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+GstClockTime gst_media_source_track_buffer_get_highest_end_time (
+ GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+GArray * gst_media_source_track_buffer_get_ranges (
+ GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_buffer_set_group_start (GstMediaSourceTrackBuffer
+ * self, GstClockTime group_start);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_buffer_process_init_segment (
+ GstMediaSourceTrackBuffer * self, gboolean sequence_mode);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_buffer_add (GstMediaSourceTrackBuffer * self,
+ GstSample * sample);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_buffer_remove (GstMediaSourceTrackBuffer * self,
+ GstSample * sample);
+
+GST_MSE_PRIVATE
+gsize
+gst_media_source_track_buffer_remove_range (GstMediaSourceTrackBuffer * self,
+ GstClockTime start, GstClockTime end);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_buffer_clear (GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_track_buffer_eos (GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_buffer_is_eos (
+ GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_track_buffer_await_eos_until (
+ GstMediaSourceTrackBuffer * self, gint64 deadline);
+
+GST_MSE_PRIVATE
+gsize gst_media_source_track_buffer_get_storage_size (
+ GstMediaSourceTrackBuffer * self);
+
+GST_MSE_PRIVATE
+GstIterator * gst_media_source_track_buffer_iter_samples (
+ GstMediaSourceTrackBuffer * buffer, GstClockTime start_dts,
+ GstSample * start_sample);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer.c
new file mode 100644
index 0000000000..a73bdc0901
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer.c
@@ -0,0 +1,377 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstmediasourcetrackbuffer-private.h"
+#include "gstmediasourcesamplemap-private.h"
+#include "gstmediasource.h"
+#include "gstmselogging-private.h"
+
+typedef struct
+{
+ gboolean enabled;
+ GstClockTime group_start;
+ GstClockTime group_end;
+ GstClockTimeDiff offset;
+
+ GstClockTime last_dts;
+ GstClockTime last_duration;
+} Timestamps;
+
+struct _GstMediaSourceTrackBuffer
+{
+ GstObject parent_instance;
+
+ GstMediaSourceSampleMap *samples;
+ Timestamps timestamps;
+ guint eos;
+
+ guint32 master_cookie;
+
+ GCond new_data_cond;
+ GMutex new_data_mutex;
+};
+
+#define g_array_new_ranges() \
+ (g_array_new (TRUE, FALSE, sizeof (GstMediaSourceRange)))
+
+#define NEW_DATA_LOCK(a) (g_mutex_lock (&a->new_data_mutex))
+#define NEW_DATA_UNLOCK(a) (g_mutex_unlock (&a->new_data_mutex))
+#define NEW_DATA_SIGNAL(a) (g_cond_signal (&a->new_data_cond))
+#define NEW_DATA_WAIT(a) (g_cond_wait (&a->new_data_cond, &a->new_data_mutex))
+#define NEW_DATA_WAIT_UNTIL(a, d) \
+ (g_cond_wait_until (&a->new_data_cond, &a->new_data_mutex, d))
+
+static void timestamps_init (Timestamps * self, gboolean enabled);
+static void timestamps_process (Timestamps * self, GstSample * sample);
+
+G_DEFINE_TYPE (GstMediaSourceTrackBuffer, gst_media_source_track_buffer,
+ GST_TYPE_OBJECT);
+
+static void
+invalidate_cookie (GstMediaSourceTrackBuffer * self)
+{
+ self->master_cookie++;
+}
+
+GstMediaSourceTrackBuffer *
+gst_media_source_track_buffer_new (void)
+{
+ return gst_object_ref_sink (g_object_new (GST_TYPE_MEDIA_SOURCE_TRACK_BUFFER,
+ NULL));
+}
+
+static void
+gst_media_source_track_buffer_finalize (GObject * object)
+{
+ GstMediaSourceTrackBuffer *self = GST_MEDIA_SOURCE_TRACK_BUFFER (object);
+ gst_object_unref (self->samples);
+ g_cond_clear (&self->new_data_cond);
+ g_mutex_clear (&self->new_data_mutex);
+ G_OBJECT_CLASS (gst_media_source_track_buffer_parent_class)->finalize
+ (object);
+}
+
+static void
+gst_media_source_track_buffer_class_init (GstMediaSourceTrackBufferClass *
+ klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_media_source_track_buffer_finalize);
+}
+
+static void
+gst_media_source_track_buffer_init (GstMediaSourceTrackBuffer * self)
+{
+ self->samples = gst_media_source_sample_map_new ();
+ self->eos = FALSE;
+ self->master_cookie = 0;
+ timestamps_init (&self->timestamps, FALSE);
+ g_cond_init (&self->new_data_cond);
+ g_mutex_init (&self->new_data_mutex);
+}
+
+void
+gst_media_source_track_buffer_process_init_segment (GstMediaSourceTrackBuffer
+ * self, gboolean sequence_mode)
+{
+ NEW_DATA_LOCK (self);
+
+ timestamps_init (&self->timestamps, sequence_mode);
+
+ NEW_DATA_UNLOCK (self);
+}
+
+void
+gst_media_source_track_buffer_set_group_start (GstMediaSourceTrackBuffer
+ * self, GstClockTime group_start)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self));
+ if (self->timestamps.enabled) {
+ self->timestamps.group_start = group_start;
+ }
+}
+
+void
+gst_media_source_track_buffer_add (GstMediaSourceTrackBuffer * self,
+ GstSample * sample)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self));
+ g_return_if_fail (GST_IS_SAMPLE (sample));
+
+ NEW_DATA_LOCK (self);
+
+ timestamps_process (&self->timestamps, sample);
+ gst_media_source_sample_map_add (self->samples, sample);
+ invalidate_cookie (self);
+
+ NEW_DATA_SIGNAL (self);
+ NEW_DATA_UNLOCK (self);
+}
+
+void
+gst_media_source_track_buffer_remove (GstMediaSourceTrackBuffer * self,
+ GstSample * sample)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self));
+ g_return_if_fail (GST_IS_SAMPLE (sample));
+
+ NEW_DATA_LOCK (self);
+
+ gst_media_source_sample_map_remove (self->samples, sample);
+ invalidate_cookie (self);
+
+ NEW_DATA_SIGNAL (self);
+ NEW_DATA_UNLOCK (self);
+}
+
+gsize
+gst_media_source_track_buffer_remove_range (GstMediaSourceTrackBuffer * self,
+ GstClockTime earliest, GstClockTime latest)
+{
+ NEW_DATA_LOCK (self);
+ gsize size = gst_media_source_sample_map_remove_range (self->samples,
+ earliest, latest);
+ invalidate_cookie (self);
+ NEW_DATA_SIGNAL (self);
+ NEW_DATA_UNLOCK (self);
+ return size;
+}
+
+void
+gst_media_source_track_buffer_clear (GstMediaSourceTrackBuffer * self)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self));
+
+ NEW_DATA_LOCK (self);
+
+ g_set_object (&self->samples, gst_media_source_sample_map_new ());
+
+ NEW_DATA_SIGNAL (self);
+ NEW_DATA_UNLOCK (self);
+}
+
+static gboolean
+is_eos (GstMediaSourceTrackBuffer * self)
+{
+ return g_atomic_int_get (&self->eos);
+}
+
+void
+gst_media_source_track_buffer_eos (GstMediaSourceTrackBuffer * self)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self));
+ NEW_DATA_LOCK (self);
+ g_atomic_int_set (&self->eos, TRUE);
+ NEW_DATA_SIGNAL (self);
+ NEW_DATA_UNLOCK (self);
+}
+
+gboolean
+gst_media_source_track_buffer_is_eos (GstMediaSourceTrackBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self), FALSE);
+ return is_eos (self);
+}
+
+gboolean
+gst_media_source_track_buffer_await_eos_until (GstMediaSourceTrackBuffer * self,
+ gint64 deadline)
+{
+ NEW_DATA_LOCK (self);
+ while (!is_eos (self) && NEW_DATA_WAIT_UNTIL (self, deadline)) {
+ /* wait */
+ }
+ NEW_DATA_UNLOCK (self);
+ return is_eos (self);
+}
+
+gint
+gst_media_source_track_buffer_get_size (GstMediaSourceTrackBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self), 0);
+ return gst_media_source_sample_map_get_size (self->samples);
+}
+
+GstClockTime
+gst_media_source_track_buffer_get_highest_end_time (GstMediaSourceTrackBuffer
+ * self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self),
+ GST_CLOCK_TIME_NONE);
+ return gst_media_source_sample_map_get_highest_end_time (self->samples);
+}
+
+typedef struct
+{
+ GArray *ranges;
+ GstMediaSourceRange current_range;
+} GetRangesAccumulator;
+
+static gboolean
+get_ranges_fold (const GValue * item, GetRangesAccumulator * acc,
+ gpointer user_data)
+{
+ GstSample *sample = gst_value_get_sample (item);
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ GstClockTime start = GST_BUFFER_PTS (buffer);
+ GstClockTime end = start + GST_BUFFER_DURATION (buffer);
+
+ GstMediaSourceRange *range = &acc->current_range;
+
+ if (range->end == 0 || start <= (range->end + (GST_SECOND / 100))) {
+ range->end = end;
+ return TRUE;
+ }
+ g_array_append_val (acc->ranges, *range);
+
+ range->start = start;
+ range->end = end;
+
+ return TRUE;
+}
+
+GArray *
+gst_media_source_track_buffer_get_ranges (GstMediaSourceTrackBuffer * self)
+{
+ GetRangesAccumulator acc = {
+ .ranges = g_array_new_ranges (),
+ .current_range = {.start = 0,.end = 0},
+ };
+
+ /* *INDENT-OFF* */
+ GstIterator *iter = gst_media_source_sample_map_iter_samples_by_pts (
+ self->samples,
+ &self->new_data_mutex,
+ &self->master_cookie,
+ 0,
+ NULL
+ );
+ /* *INDENT-ON* */
+ while (gst_iterator_fold (iter, (GstIteratorFoldFunction) get_ranges_fold,
+ (GValue *) & acc, NULL) == GST_ITERATOR_RESYNC) {
+ gst_iterator_resync (iter);
+ }
+ gst_iterator_free (iter);
+
+ if (acc.current_range.end > 0) {
+ g_array_append_val (acc.ranges, acc.current_range);
+ }
+
+ return acc.ranges;
+}
+
+static void
+timestamps_init (Timestamps * self, gboolean enabled)
+{
+ self->enabled = enabled;
+ self->group_start = GST_CLOCK_TIME_NONE;
+ self->group_end = GST_CLOCK_TIME_NONE;
+ self->offset = 0;
+ self->last_dts = 0;
+ self->last_duration = 0;
+}
+
+static void
+timestamps_process (Timestamps * self, GstSample * sample)
+{
+ if (!self->enabled) {
+ return;
+ }
+
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ GstClockTime duration = GST_BUFFER_DURATION (buffer);
+
+ GstClockTime pts = 0;
+ GstClockTime dts = 0;
+
+ if (GST_CLOCK_TIME_IS_VALID (self->group_start)) {
+ self->offset = self->group_start - pts;
+ self->group_end = self->group_start;
+ self->group_start = GST_CLOCK_TIME_NONE;
+ }
+
+ if (self->offset != 0) {
+ pts += self->offset;
+ dts += self->offset;
+ }
+
+ GstClockTime end_pts = pts + duration;
+
+ self->last_dts = dts;
+ self->last_duration = duration;
+
+ if (GST_CLOCK_TIME_IS_VALID (self->group_end)) {
+ self->group_end = MAX (self->group_end, end_pts);
+ }
+ self->offset = end_pts;
+
+ GST_BUFFER_PTS (buffer) = pts;
+ GST_BUFFER_DTS (buffer) = dts;
+}
+
+gsize
+gst_media_source_track_buffer_get_storage_size (GstMediaSourceTrackBuffer *
+ self)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self), 0);
+ return gst_media_source_sample_map_get_storage_size (self->samples);
+}
+
+GstIterator *
+gst_media_source_track_buffer_iter_samples (GstMediaSourceTrackBuffer * self,
+ GstClockTime start_dts, GstSample * start_sample)
+{
+ /* *INDENT-OFF* */
+ return gst_media_source_sample_map_iter_samples_by_dts (
+ self->samples,
+ &self->new_data_mutex,
+ &self->master_cookie,
+ start_dts,
+ start_sample
+ );
+ /* *INDENT-ON* */
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue-private.h
new file mode 100644
index 0000000000..9b0c9bbb23
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue-private.h
@@ -0,0 +1,46 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+G_BEGIN_DECLS
+
+typedef void (*GstMseEventQueueCallback) (GstDataQueueItem *, gpointer);
+
+#define GST_TYPE_MSE_EVENT_QUEUE (gst_mse_event_queue_get_type())
+
+G_DECLARE_FINAL_TYPE (GstMseEventQueue, gst_mse_event_queue, GST,
+ MSE_EVENT_QUEUE, GstObject);
+
+GST_MSE_PRIVATE
+GstMseEventQueue * gst_mse_event_queue_new (GstMseEventQueueCallback callback,
+ gpointer user_data);
+
+GST_MSE_PRIVATE
+gboolean gst_mse_event_queue_push (GstMseEventQueue * self,
+ GstDataQueueItem * item);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue.c
new file mode 100644
index 0000000000..b49e09c793
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue.c
@@ -0,0 +1,133 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+
+#include "gstmseeventqueue-private.h"
+
+struct _GstMseEventQueue
+{
+ GstObject parent_instance;
+
+ GstMseEventQueueCallback callback;
+ gpointer user_data;
+
+ GstTask *task;
+ GRecMutex lock;
+ GstDataQueue *queue;
+};
+
+G_DEFINE_TYPE (GstMseEventQueue, gst_mse_event_queue, GST_TYPE_OBJECT);
+
+GstMseEventQueue *
+gst_mse_event_queue_new (GstMseEventQueueCallback callback, gpointer user_data)
+{
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ GstMseEventQueue *self = g_object_new (GST_TYPE_MSE_EVENT_QUEUE, NULL);
+
+ self->callback = callback;
+ self->user_data = user_data;
+
+ gst_task_start (self->task);
+
+ return g_object_ref_sink (self);
+}
+
+static void
+gst_mse_background_event_queue_dispose (GObject * obj)
+{
+ GstMseEventQueue *self = GST_MSE_EVENT_QUEUE (obj);
+
+ gst_data_queue_set_flushing (self->queue, TRUE);
+ gst_data_queue_flush (self->queue);
+
+ G_OBJECT_CLASS (gst_mse_event_queue_parent_class)->dispose (obj);
+}
+
+static void
+gst_mse_background_event_queue_finalize (GObject * obj)
+{
+ GstMseEventQueue *self = GST_MSE_EVENT_QUEUE (obj);
+
+ gst_task_join (self->task);
+ g_rec_mutex_clear (&self->lock);
+ gst_clear_object (&self->task);
+ gst_clear_object (&self->queue);
+
+ G_OBJECT_CLASS (gst_mse_event_queue_parent_class)->finalize (obj);
+}
+
+void
+gst_mse_event_queue_class_init (GstMseEventQueueClass * klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = GST_DEBUG_FUNCPTR (gst_mse_background_event_queue_dispose);
+ oclass->finalize =
+ GST_DEBUG_FUNCPTR (gst_mse_background_event_queue_finalize);
+}
+
+static void
+task_func (GstMseEventQueue * self)
+{
+ GstTask *task = self->task;
+ GstDataQueue *queue = self->queue;
+ GstDataQueueItem *item = NULL;
+ if (!gst_data_queue_pop (queue, &item)) {
+ gst_task_stop (task);
+ return;
+ }
+ self->callback (item, self->user_data);
+ if (item->destroy) {
+ item->destroy (item);
+ }
+}
+
+static gboolean
+never_full (GstDataQueue * queue, guint visible, guint bytes, guint64 time,
+ gpointer user_data)
+{
+ return FALSE;
+}
+
+void
+gst_mse_event_queue_init (GstMseEventQueue * self)
+{
+ self->queue = gst_data_queue_new (never_full, NULL, NULL, NULL);
+ self->task = gst_task_new ((GstTaskFunction) task_func, self, NULL);
+ g_rec_mutex_init (&self->lock);
+ gst_task_set_lock (self->task, &self->lock);
+}
+
+gboolean
+gst_mse_event_queue_push (GstMseEventQueue * self, GstDataQueueItem * item)
+{
+ g_return_val_if_fail (GST_IS_MSE_EVENT_QUEUE (self), FALSE);
+ g_return_val_if_fail (item != NULL, FALSE);
+ return gst_data_queue_push (self->queue, item);
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging-private.h
new file mode 100644
index 0000000000..160d1a3606
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging-private.h
@@ -0,0 +1,33 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+
+#define GST_CAT_DEFAULT gst_mse_debug
+
+G_GNUC_INTERNAL
+GST_DEBUG_CATEGORY_EXTERN (gst_mse_debug);
+
+G_GNUC_INTERNAL
+void gst_mse_init_logging (void);
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging.c
new file mode 100644
index 0000000000..0664b8a553
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging.c
@@ -0,0 +1,35 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstmselogging-private.h"
+
+GST_DEBUG_CATEGORY (gst_mse_debug);
+
+void
+gst_mse_init_logging (void)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_mse_debug, "gst-mse", 0, "GstMse");
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype-private.h
new file mode 100644
index 0000000000..dfdb537f35
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype-private.h
@@ -0,0 +1,60 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ gchar *mime_type;
+ GStrv codecs;
+} GstMediaSourceMediaType;
+
+#define GST_MEDIA_SOURCE_MEDIA_TYPE_INIT { 0 }
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_media_type_parse (GstMediaSourceMediaType * self,
+ const gchar * type);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_media_type_is_caps_supported (const GstCaps * caps);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_media_type_is_supported (
+ GstMediaSourceMediaType * self);
+
+GST_MSE_PRIVATE
+gboolean gst_media_source_media_type_generates_timestamp (
+ GstMediaSourceMediaType * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_media_type_reset (GstMediaSourceMediaType * self);
+
+GST_MSE_PRIVATE
+void gst_media_source_media_type_free (GstMediaSourceMediaType * self);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype.c
new file mode 100644
index 0000000000..85c19fa7ab
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype.c
@@ -0,0 +1,544 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 2019 Igalia S.L.
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+
+#include "gstmsemediatype-private.h"
+
+static const gchar tspecials[] = {
+ '(', ')', '<', '>', '@', ',', ';', ':',
+ '\\', '"', '/', '[', ']', '?', '=',
+ 0,
+};
+
+static gboolean
+is_tspecial (const gchar c)
+{
+ for (const gchar * tspecial = tspecials; *tspecial != 0; tspecial++) {
+ if (*tspecial == c) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+is_token_character (const gchar c)
+{
+ if (g_ascii_iscntrl (c) || is_tspecial (c)) {
+ return FALSE;
+ }
+ return g_ascii_isgraph (c);
+}
+
+static gboolean
+is_ascii (const gchar c)
+{
+ return c > 0 && c <= G_MAXINT8;
+}
+
+static gboolean
+is_eos (const gchar c)
+{
+ return c == '\0';
+}
+
+static void
+ignore_whitespace (const gchar ** input)
+{
+ g_return_if_fail (input != NULL);
+
+ const gchar *output;
+ for (output = *input; g_ascii_isspace (*output); output++) {
+ }
+ *input = output;
+}
+
+static gboolean
+any_of (const gchar ** input, const gchar ** choices, gchar ** value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (choices != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ for (const gchar ** choice = choices; *choice != NULL; choice++) {
+ gulong prefix_length = strlen (*choice);
+ if (g_str_has_prefix (*input, *choice)) {
+ *value = g_strndup (*input, prefix_length);
+ *input += prefix_length;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+literal (const gchar ** input, const gchar * value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ if (!g_str_has_prefix (*input, value)) {
+ return FALSE;
+ }
+ *input += strlen (value);
+ return TRUE;
+}
+
+static gboolean
+token (const gchar ** input, gchar ** value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ const gchar *unparsed = *input;
+ for (; is_token_character (unparsed[0]); unparsed++) {
+ }
+ gsize length = unparsed - *input;
+ if (length < 1) {
+ return FALSE;
+ }
+ *value = g_strndup (*input, length);
+ *input = unparsed;
+ return TRUE;
+}
+
+static gboolean
+quoted_string_char (const gchar ** input, gchar * value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ const gchar *unparsed = *input;
+ char c = unparsed[0];
+ if (!is_ascii (c)) {
+ return FALSE;
+ }
+
+ if (c == '"' || c == '\\' || c == '\r') {
+ return FALSE;
+ }
+
+ *value = c;
+ (*input)++;
+ return TRUE;
+}
+
+static gboolean
+escaped_ascii_char (const gchar ** input, gchar * value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ if (!literal (input, "\\")) {
+ return FALSE;
+ }
+
+ gchar c = (*input)[0];
+ if (!is_ascii (c)) {
+ return FALSE;
+ }
+
+ *value = c;
+ (*input)++;
+ return TRUE;
+}
+
+static gboolean
+quoted_string (const gchar ** input, gchar ** value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ const gchar *unparsed = *input;
+ if (!literal (&unparsed, "\"")) {
+ return FALSE;
+ }
+
+ GString *string_value = g_string_new (NULL);
+ for (;;) {
+ if (literal (&unparsed, "\"")) {
+ *input = unparsed;
+ *value = g_string_free (string_value, FALSE);
+ return TRUE;
+ }
+
+ if (is_eos (unparsed[0])) {
+ goto error;
+ }
+
+ gchar c;
+
+ if (quoted_string_char (&unparsed, &c)) {
+ g_string_append_c (string_value, c);
+ continue;
+ }
+
+ if (escaped_ascii_char (&unparsed, &c)) {
+ g_string_append_c (string_value, c);
+ continue;
+ }
+
+ goto error;
+ }
+error:
+ g_string_free (string_value, TRUE);
+ return FALSE;
+}
+
+static const gchar *discrete_media_types[] = {
+ "text",
+ "image",
+ "audio",
+ "video",
+ "application",
+ NULL,
+};
+
+static gboolean
+discrete_media_type (const gchar ** input, gchar ** value)
+{
+ return any_of (input, discrete_media_types, value);
+}
+
+static gboolean
+composite_media_type (const gchar ** input, gchar ** value)
+{
+ return token (input, value);
+}
+
+static gboolean
+media_type_parameter (const gchar ** input, gchar ** param_name,
+ gchar ** param_value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (param_name != NULL, FALSE);
+ g_return_val_if_fail (param_value != NULL, FALSE);
+
+ ignore_whitespace (input);
+ if (!token (input, param_name)) {
+ return FALSE;
+ }
+ if (!literal (input, "=")) {
+ return FALSE;
+ }
+ if (!token (input, param_value)) {
+ if (!quoted_string (input, param_value)) {
+ return FALSE;
+ }
+ }
+ ignore_whitespace (input);
+ return TRUE;
+}
+
+static gboolean
+media_type (const gchar ** input, gchar ** value)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ gchar *discrete = NULL;
+ gchar *composite = NULL;
+
+ if (!discrete_media_type (input, &discrete)) {
+ goto error;
+ }
+ if (!literal (input, "/")) {
+ goto error;
+ }
+ if (!composite_media_type (input, &composite)) {
+ goto error;
+ }
+
+ *value = g_strdup_printf ("%s/%s", discrete, composite);
+ g_clear_pointer (&discrete, g_free);
+ g_clear_pointer (&composite, g_free);
+ return TRUE;
+
+error:
+ g_clear_pointer (&discrete, g_free);
+ g_clear_pointer (&composite, g_free);
+ return FALSE;
+}
+
+static gboolean
+media_type_codecs (const gchar ** input, gchar *** codecs)
+{
+ g_return_val_if_fail (input != NULL, FALSE);
+ g_return_val_if_fail (codecs != NULL, FALSE);
+
+ gchar *param_name = NULL;
+ gchar *codecs_value = NULL;
+ while (!is_eos (*input[0])
+ && media_type_parameter (input, ¶m_name, &codecs_value)) {
+ gboolean is_codecs = g_strcmp0 (param_name, "codecs") == 0;
+ g_clear_pointer (¶m_name, g_free);
+ if (is_codecs) {
+ break;
+ }
+ g_clear_pointer (&codecs_value, g_free);
+ }
+ if (codecs_value == NULL) {
+ return TRUE;
+ }
+
+ gchar **codec_names = g_strsplit (codecs_value, ",", 0);
+ guint n_codecs = g_strv_length (codec_names);
+ GPtrArray *codecs_array = g_ptr_array_new_full (n_codecs, NULL);
+ for (guint i = 0; i < n_codecs; i++) {
+ gchar *codec = g_strstrip (g_strdup (codec_names[i]));
+ if (g_strcmp0 (codec, "") != 0) {
+ g_ptr_array_add (codecs_array, g_strdup (codec));
+ }
+ g_clear_pointer (&codec, g_free);
+ }
+ g_ptr_array_add (codecs_array, NULL);
+ *codecs = (gchar **) g_ptr_array_free (codecs_array, FALSE);
+
+ g_clear_pointer (&codecs_value, g_free);
+ g_strfreev (codec_names);
+
+ return TRUE;
+}
+
+gboolean
+gst_media_source_media_type_parse (GstMediaSourceMediaType * self,
+ const gchar * type)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (type != NULL, FALSE);
+
+ gchar *lowercase = g_ascii_strdown (type, -1);
+ const gchar *input = lowercase;
+
+ ignore_whitespace (&input);
+ if (!media_type (&input, &self->mime_type)) {
+ g_free (lowercase);
+ return FALSE;
+ }
+ self->codecs = NULL;
+ ignore_whitespace (&input);
+ if (is_eos (input[0])) {
+ g_free (lowercase);
+ return TRUE;
+ }
+ if (!literal (&input, ";")) {
+ g_free (lowercase);
+ return TRUE;
+ }
+ ignore_whitespace (&input);
+ media_type_codecs (&input, &self->codecs);
+ g_free (lowercase);
+ return TRUE;
+}
+
+static inline gboolean
+has_any_prefix (const gchar * str, const gchar * prefix, ...)
+{
+ if (g_str_has_prefix (str, prefix)) {
+ return TRUE;
+ }
+
+ va_list varargs;
+
+ va_start (varargs, prefix);
+ for (const gchar * arg = va_arg (varargs, const gchar *); arg != NULL;
+ arg = va_arg (varargs, const gchar *))
+ {
+ if (g_str_has_prefix (str, arg)) {
+ va_end (varargs);
+ return TRUE;
+ }
+ }
+ va_end (varargs);
+ return FALSE;
+}
+
+static const gchar *
+patch_media_type (const gchar * media_type)
+{
+ if (g_strcmp0 (media_type, "video/mp4") == 0) {
+ return "video/quicktime";
+ }
+ if (g_strcmp0 (media_type, "audio/mp4") == 0 ||
+ g_strcmp0 (media_type, "audio/aac") == 0) {
+ return "audio/x-m4a";
+ }
+ if (!has_any_prefix (media_type, "audio/", "video/", NULL)) {
+ return NULL;
+ }
+ return media_type;
+}
+
+static GstStaticCaps h264_caps = GST_STATIC_CAPS ("video/x-h264");
+static GstStaticCaps h265_caps = GST_STATIC_CAPS ("video/x-h265");
+static GstStaticCaps av1_caps = GST_STATIC_CAPS ("video/x-av1");
+static GstStaticCaps vp8_caps = GST_STATIC_CAPS ("video/x-vp8");
+static GstStaticCaps vp9_caps = GST_STATIC_CAPS ("video/x-vp9");
+
+static GstStaticCaps vorbis_caps = GST_STATIC_CAPS ("audio/x-vorbis");
+static GstStaticCaps opus_caps = GST_STATIC_CAPS ("audio/x-opus");
+static GstStaticCaps flac_caps = GST_STATIC_CAPS ("audio/x-flac");
+static GstStaticCaps mp3_caps =
+GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)1, layer=(int)3");
+static GstStaticCaps aac_caps =
+GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)4");
+
+static inline GstStaticCaps *
+mse_codec_id_to_static_caps (const gchar * codec_id)
+{
+ if (has_any_prefix (codec_id, "avc", "x-h264", "mp4v", NULL)) {
+ return &h264_caps;
+ }
+ if (has_any_prefix (codec_id, "hvc1", "hev1", "x-h265", NULL)) {
+ return &h265_caps;
+ }
+ if (has_any_prefix (codec_id, "av01", "av1", "x-av1", NULL)) {
+ return &av1_caps;
+ }
+ if (has_any_prefix (codec_id, "vp8", "x-vp8", NULL)) {
+ return &vp8_caps;
+ }
+ if (has_any_prefix (codec_id, "vp9", "vp09", "x-vp9", NULL)) {
+ return &vp9_caps;
+ }
+
+ if (has_any_prefix (codec_id, "mpeg", "mp4a", NULL)) {
+ return &aac_caps;
+ }
+ if (has_any_prefix (codec_id, "vorbis", "x-vorbis", NULL)) {
+ return &vorbis_caps;
+ }
+ if (has_any_prefix (codec_id, "opus", "x-opus", NULL)) {
+ return &opus_caps;
+ }
+ if (has_any_prefix (codec_id, "flac", "x-flac", NULL)) {
+ return &flac_caps;
+ }
+ if (has_any_prefix (codec_id, "mp3", "audio/mp3", NULL)) {
+ return &mp3_caps;
+ }
+
+ return NULL;
+}
+
+static gboolean
+is_supported (const GstCaps * caps, GList * elements)
+{
+ GList *supporting_elements = gst_element_factory_list_filter (elements, caps,
+ GST_PAD_SINK, FALSE);
+ gboolean supported = supporting_elements != NULL;
+ gst_plugin_feature_list_free (supporting_elements);
+ return supported;
+}
+
+static gboolean
+is_codec_supported (const gchar * codec_id, GList * elements)
+{
+ GstStaticCaps *static_caps = mse_codec_id_to_static_caps (codec_id);
+ if (static_caps == NULL) {
+ return FALSE;
+ }
+ GstCaps *codec_caps = gst_static_caps_get (static_caps);
+ gboolean supported = is_supported (codec_caps, elements);
+ gst_clear_caps (&codec_caps);
+ return supported;
+}
+
+static inline GList *
+get_srcpad_elements (void)
+{
+ return gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DEMUXER
+ | GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_DECRYPTOR |
+ GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER | GST_ELEMENT_FACTORY_TYPE_SINK |
+ GST_ELEMENT_FACTORY_TYPE_PARSER, GST_RANK_MARGINAL);
+}
+
+gboolean
+gst_media_source_media_type_is_caps_supported (const GstCaps * caps)
+{
+ GList *elements = get_srcpad_elements ();
+ gboolean supported = is_supported (caps, elements);
+ gst_plugin_feature_list_free (elements);
+ return supported;
+}
+
+gboolean
+gst_media_source_media_type_is_supported (GstMediaSourceMediaType * self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (self->mime_type != NULL, FALSE);
+
+ const gchar *mime_type = patch_media_type (self->mime_type);
+ if (mime_type == NULL) {
+ return FALSE;
+ }
+
+ GList *elements = get_srcpad_elements ();
+ GstCaps *caps = gst_caps_from_string (mime_type);
+
+ gboolean supported = is_supported (caps, elements);
+ for (gchar ** codec_id = self->codecs;
+ supported && codec_id && *codec_id; codec_id++) {
+ supported &= is_codec_supported (*codec_id, elements);
+ }
+
+ gst_plugin_feature_list_free (elements);
+ gst_clear_caps (&caps);
+
+ return supported;
+}
+
+static const gchar *generate_timestamps_formats[] = {
+ "audio/mpeg",
+ "audio/aac",
+ NULL,
+};
+
+gboolean
+gst_media_source_media_type_generates_timestamp (GstMediaSourceMediaType * self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (self->mime_type != NULL, FALSE);
+ return g_strv_contains (generate_timestamps_formats, self->mime_type);
+}
+
+void
+gst_media_source_media_type_reset (GstMediaSourceMediaType * self)
+{
+ g_return_if_fail (self != NULL);
+ g_clear_pointer (&self->mime_type, g_free);
+ g_clear_pointer (&self->codecs, g_strfreev);
+}
+
+void
+gst_media_source_media_type_free (GstMediaSourceMediaType * self)
+{
+ gst_media_source_media_type_reset (self);
+ g_free (self);
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc-private.h
new file mode 100644
index 0000000000..b40b0a6553
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc-private.h
@@ -0,0 +1,55 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include "gstmsesrc.h"
+#include "gstmediasource.h"
+#include "gstmediasourcetrack-private.h"
+
+G_BEGIN_DECLS
+
+GST_MSE_PRIVATE
+void gst_mse_src_set_duration (GstMseSrc * self, GstClockTime duration);
+
+GST_MSE_PRIVATE
+void gst_mse_src_network_error (GstMseSrc * self);
+
+GST_MSE_PRIVATE
+void gst_mse_src_decode_error (GstMseSrc * self);
+
+GST_MSE_PRIVATE
+void gst_mse_src_emit_streams (GstMseSrc * self, GstMediaSourceTrack ** tracks,
+ gsize n_tracks);
+
+GST_MSE_PRIVATE
+void gst_mse_src_update_ready_state (GstMseSrc * self);
+
+GST_MSE_PRIVATE
+void gst_mse_src_attach (GstMseSrc * self, GstMediaSource * media_source);
+
+GST_MSE_PRIVATE
+void gst_mse_src_detach (GstMseSrc * self);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.c
new file mode 100644
index 0000000000..96e07062ca
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.c
@@ -0,0 +1,1385 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 2009, 2010 Sebastian Dröge
+ * Copyright (C) 2013, 2022, 2023 Collabora Ltd.
+ * Copyright (C) 2013 Orange
+ * Copyright (C) 2014, 2015 Sebastian Dröge
+ * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Igalia, S.L
+ * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Metrological Group B.V.
+ *
+ * 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:gstmsesrc
+ * @title: GstMseSrc
+ * @short_description: Source Element for Media Source playback
+ *
+ * #GstMseSrc is a source Element that interacts with a #GstMediaSource to
+ * consume #GstSamples processed by the Media Source and supplies them
+ * to the containing #GstPipeline. In the perspective of the Media Source API,
+ * this element fulfills the basis of the Media Element's role relating to
+ * working with a Media Source. The remaining responsibilities are meant to be
+ * fulfilled by the application and #GstPlay can be used to satisfy many of
+ * them.
+ *
+ * Once added to a Pipeline, this element should be attached to a Media Source
+ * using gst_media_source_attach().
+ *
+ * Since: 1.24
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+
+#include
+#include "gstmsesrc.h"
+#include "gstmsesrc-private.h"
+
+#include "gstmselogging-private.h"
+
+#include "gstmediasource.h"
+#include "gstmediasource-private.h"
+#include "gstmediasourcetrack-private.h"
+#include "gstsourcebuffer.h"
+#include "gstsourcebuffer-private.h"
+
+#define DEFAULT_POSITION GST_CLOCK_TIME_NONE
+#define DEFAULT_DURATION GST_CLOCK_TIME_NONE
+#define DEFAULT_READY_STATE GST_MSE_SRC_READY_STATE_HAVE_NOTHING
+#define DECODE_ERROR "decode error"
+#define NETWORK_ERROR "network error"
+
+enum
+{
+ PROP_0,
+
+ PROP_POSITION,
+ PROP_DURATION,
+ PROP_READY_STATE,
+
+ PROP_N_AUDIO,
+ PROP_N_TEXT,
+ PROP_N_VIDEO,
+
+ N_PROPS,
+};
+
+enum
+{
+ THRESHOLD_FUTURE_DATA = GST_SECOND * 5,
+ THRESHOLD_ENOUGH_DATA = GST_SECOND * 50,
+};
+
+static GParamSpec *properties[N_PROPS];
+
+static GstStaticPadTemplate gst_mse_src_template =
+GST_STATIC_PAD_TEMPLATE ("src_%s", GST_PAD_SRC, GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+/**
+ * GstMseSrcPad:
+ *
+ * Since: 1.24
+ */
+struct _GstMseSrcPad
+{
+ GstPad base;
+
+ GstStream *stream;
+ GstMediaSourceTrack *track;
+ GstCaps *most_recent_caps;
+ GstSegment segment;
+
+ GstClockTime position;
+
+ gboolean sent_stream_collection;
+ gboolean sent_stream_start;
+ gboolean sent_initial_caps;
+ gboolean does_need_segment;
+
+ GCond linked_or_flushing_cond;
+ GMutex linked_or_flushing_lock;
+ gboolean flushing;
+ gboolean eos;
+};
+
+#define STREAMS_LOCK(a) (g_mutex_lock (&a->streams_lock))
+#define STREAMS_UNLOCK(a) (g_mutex_unlock (&a->streams_lock))
+
+#define LINKED_OR_FLUSHING_LOCK(a) (g_mutex_lock (&a->linked_or_flushing_lock))
+#define LINKED_OR_FLUSHING_UNLOCK(a) (g_mutex_unlock (&a->linked_or_flushing_lock))
+#define LINKED_OR_FLUSHING_SIGNAL(a) (g_cond_signal (&a->linked_or_flushing_cond))
+#define LINKED_OR_FLUSHING_WAIT(a) \
+ (g_cond_wait (&a->linked_or_flushing_cond, &a->linked_or_flushing_lock))
+
+#define FLOW_COMBINER_LOCK(a) (g_mutex_lock (&a->flow_combiner_lock))
+#define FLOW_COMBINER_UNLOCK(a) (g_mutex_unlock (&a->flow_combiner_lock))
+
+G_DEFINE_TYPE (GstMseSrcPad, gst_mse_src_pad, GST_TYPE_PAD);
+
+static gboolean pad_activate_mode (GstMseSrcPad * pad, GstObject * parent,
+ GstPadMode mode, gboolean active);
+
+static GstPadLinkReturn pad_linked (GstMseSrcPad * pad, GstMseSrc * parent,
+ GstPad * sink);
+static gboolean pad_event (GstMseSrcPad * pad, GstMseSrc * parent,
+ GstEvent * event);
+static gboolean pad_query (GstMseSrcPad * pad, GstObject * parent,
+ GstQuery * query);
+static void pad_task (GstMseSrcPad * pad);
+
+static GstPad *
+gst_mse_src_pad_new (GstMediaSourceTrack * track, GstStream * stream,
+ guint id, GstClockTime start, gdouble rate)
+{
+ gchar *name = g_strdup_printf ("src_%u", id);
+ GstMseSrcPad *self = g_object_new (GST_TYPE_MSE_SRC_PAD, "name", name,
+ "direction", GST_PAD_SRC, NULL);
+ g_free (name);
+ self->stream = stream;
+ self->track = track;
+ self->segment.start = start;
+ self->segment.rate = rate;
+
+ return GST_PAD (self);
+}
+
+static void
+gst_mse_src_pad_init (GstMseSrcPad * self)
+{
+ gst_segment_init (&self->segment, GST_FORMAT_TIME);
+ self->sent_stream_collection = FALSE;
+ self->sent_stream_start = FALSE;
+ self->sent_initial_caps = FALSE;
+ self->does_need_segment = TRUE;
+ self->position = DEFAULT_POSITION;
+ self->flushing = FALSE;
+ self->eos = FALSE;
+ g_mutex_init (&self->linked_or_flushing_lock);
+ g_cond_init (&self->linked_or_flushing_cond);
+
+ gst_pad_set_activatemode_function (GST_PAD (self),
+ (GstPadActivateModeFunction) pad_activate_mode);
+ gst_pad_set_link_function (GST_PAD (self), (GstPadLinkFunction) pad_linked);
+ gst_pad_set_event_function (GST_PAD (self), (GstPadEventFunction) pad_event);
+ gst_pad_set_query_function (GST_PAD (self), (GstPadQueryFunction) pad_query);
+}
+
+static void
+gst_mse_src_pad_finalize (GObject * object)
+{
+ GstMseSrcPad *self = GST_MSE_SRC_PAD (object);
+
+ gst_clear_caps (&self->most_recent_caps);
+ g_mutex_clear (&self->linked_or_flushing_lock);
+ g_cond_clear (&self->linked_or_flushing_cond);
+
+ G_OBJECT_CLASS (gst_mse_src_pad_parent_class)->finalize (object);
+}
+
+static void
+gst_mse_src_pad_class_init (GstMseSrcPadClass * klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_mse_src_pad_finalize);
+}
+
+// TODO: Check if this struct is even necessary.
+// The custom pad should be able to keep track of information for each track.
+typedef struct
+{
+ GstMediaSourceTrack *track;
+ GstPad *pad;
+ GstStream *info;
+} Stream;
+
+/**
+ * GstMseSrc:
+ *
+ * Since: 1.24
+ */
+struct _GstMseSrc
+{
+ GstElement base;
+
+ GstMediaSource *media_source;
+
+ guint group_id;
+ GstStreamCollection *collection;
+ GHashTable *streams;
+ GMutex streams_lock;
+
+ GstClockTime duration;
+ GstClockTime start_time;
+ gdouble rate;
+ GstMseSrcReadyState ready_state;
+
+ GstFlowCombiner *flow_combiner;
+ GMutex flow_combiner_lock;
+
+ GCond eos_cond;
+ GMutex eos_lock;
+
+ gchar *uri;
+};
+
+static void gst_mse_src_uri_handler_init (gpointer g_iface,
+ gpointer iface_data);
+static GstStateChangeReturn gst_mse_src_change_state (GstElement * element,
+ GstStateChange transition);
+static gboolean gst_mse_src_send_event (GstElement * element, GstEvent * event);
+static void update_ready_state_for_init_segment (GstMseSrc * self);
+static void update_ready_state_for_sample (GstMseSrc * self);
+
+G_DEFINE_TYPE_WITH_CODE (GstMseSrc, gst_mse_src, GST_TYPE_ELEMENT,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_mse_src_uri_handler_init));
+
+static void
+gst_mse_src_constructed (GObject * object)
+{
+ GstMseSrc *self = GST_MSE_SRC (object);
+ GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_SOURCE);
+}
+
+static void
+gst_mse_src_dispose (GObject * object)
+{
+ GstMseSrc *self = GST_MSE_SRC (object);
+ gst_clear_object (&self->collection);
+ g_clear_pointer (&self->streams, g_hash_table_unref);
+ g_mutex_clear (&self->streams_lock);
+ g_clear_pointer (&self->flow_combiner, gst_flow_combiner_free);
+ g_mutex_clear (&self->flow_combiner_lock);
+ g_cond_clear (&self->eos_cond);
+ g_mutex_clear (&self->eos_lock);
+ G_OBJECT_CLASS (gst_mse_src_parent_class)->dispose (object);
+}
+
+static void
+gst_mse_src_finalize (GObject * object)
+{
+ GstMseSrc *self = GST_MSE_SRC (object);
+
+ g_clear_pointer (&self->uri, g_free);
+
+ G_OBJECT_CLASS (gst_mse_src_parent_class)->finalize (object);
+}
+
+static void
+gst_mse_src_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstMseSrc *self = GST_MSE_SRC (object);
+
+ switch (prop_id) {
+ case PROP_DURATION:
+ g_value_set_uint64 (value, gst_mse_src_get_duration (self));
+ break;
+ case PROP_POSITION:
+ g_value_set_uint64 (value, gst_mse_src_get_position (self));
+ break;
+ case PROP_READY_STATE:
+ g_value_set_enum (value, gst_mse_src_get_ready_state (self));
+ break;
+ case PROP_N_AUDIO:
+ g_value_set_uint (value, gst_mse_src_get_n_audio (self));
+ break;
+ case PROP_N_TEXT:
+ g_value_set_uint (value, gst_mse_src_get_n_text (self));
+ break;
+ case PROP_N_VIDEO:
+ g_value_set_uint (value, gst_mse_src_get_n_video (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_mse_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstMseSrc *self = GST_MSE_SRC (object);
+
+ switch (prop_id) {
+ case PROP_DURATION:
+ gst_mse_src_set_duration (self, g_value_get_uint64 (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_mse_src_class_init (GstMseSrcClass * klass)
+{
+ GObjectClass *oclass = (GObjectClass *) klass;
+ GstElementClass *eclass = (GstElementClass *) klass;
+
+ oclass->constructed = GST_DEBUG_FUNCPTR (gst_mse_src_constructed);
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_mse_src_finalize);
+ oclass->dispose = GST_DEBUG_FUNCPTR (gst_mse_src_dispose);
+ oclass->get_property = GST_DEBUG_FUNCPTR (gst_mse_src_get_property);
+ oclass->set_property = GST_DEBUG_FUNCPTR (gst_mse_src_set_property);
+
+ eclass->change_state = GST_DEBUG_FUNCPTR (gst_mse_src_change_state);
+ eclass->send_event = GST_DEBUG_FUNCPTR (gst_mse_src_send_event);
+
+ /**
+ * GstMseSrc:position:
+ *
+ * The playback position as a #GstClockTime
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_POSITION] = g_param_spec_uint64 ("position",
+ "Position",
+ "The playback position as a GstClockTime",
+ 0, G_MAXUINT64, DEFAULT_POSITION, G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstMseSrc:duration:
+ *
+ * The duration of the stream as a #GstClockTime
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_DURATION] = g_param_spec_uint64 ("duration",
+ "Duration",
+ "The duration of the stream as a GstClockTime",
+ 0, G_MAXUINT64, DEFAULT_DURATION, G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstMseSrc:ready-state:
+ *
+ * The Ready State of this element, describing to what level it can supply
+ * content for the current #GstMseSrc:position. This is a separate concept
+ * from #GstMediaSource:ready-state: and corresponds to the HTML Media
+ * Element's Ready State.
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_READY_STATE] = g_param_spec_enum ("ready-state",
+ "Ready State",
+ "The Ready State of this Element",
+ GST_TYPE_MSE_SRC_READY_STATE,
+ DEFAULT_READY_STATE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstMseSrc:n-audio:
+ *
+ * The number of audio tracks in the Media Source
+ *
+ * Since: 1.24
+ */
+ properties[PROP_N_AUDIO] = g_param_spec_uint ("n-audio",
+ "Number of Audio Tracks",
+ "The number of audio tracks in the Media Source",
+ 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstMseSrc:n-text:
+ *
+ * The number of text tracks in the Media Source
+ *
+ * Since: 1.24
+ */
+ properties[PROP_N_TEXT] = g_param_spec_uint ("n-text",
+ "Number of Text Tracks",
+ "The number of text tracks in the Media Source",
+ 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstMseSrc:n-video:
+ *
+ * The number of video tracks in the Media Source
+ *
+ * Since: 1.24
+ */
+ properties[PROP_N_VIDEO] = g_param_spec_uint ("n-video",
+ "Number of Video Tracks",
+ "The number of video tracks in the Media Source",
+ 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, N_PROPS, properties);
+
+ gst_element_class_set_static_metadata (eclass, "MseSrc",
+ "Generic/Source",
+ "Implements a GStreamer Source for the gstreamer-mse API", "Collabora");
+ gst_element_class_add_static_pad_template (eclass, &gst_mse_src_template);
+
+ gst_mse_init_logging ();
+}
+
+static void
+clear_stream (Stream * stream)
+{
+ gst_clear_object (&stream->track);
+ gst_clear_object (&stream->info);
+ g_free (stream);
+}
+
+static GHashTable *
+streams_init (const GstMseSrc * self)
+{
+ return g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) clear_stream);
+}
+
+static GstStreamCollection *
+collection_init (const GstMseSrc * self)
+{
+ return gst_stream_collection_new (G_OBJECT_TYPE_NAME (self));
+}
+
+static void
+gst_mse_src_init (GstMseSrc * self)
+{
+ self->group_id = gst_util_group_id_next ();
+ self->streams = streams_init (self);
+ self->collection = collection_init (self);
+ self->uri = NULL;
+ self->start_time = 0;
+ self->rate = 1;
+ g_mutex_init (&self->streams_lock);
+ self->flow_combiner = gst_flow_combiner_new ();
+ g_mutex_init (&self->flow_combiner_lock);
+ g_cond_init (&self->eos_cond);
+ g_mutex_init (&self->eos_lock);
+}
+
+/**
+ * gst_mse_src_get_position:
+ * @self: #GstMseSrc instance
+ *
+ * Gets the current playback position of @self.
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position)
+ *
+ * Returns: The playback position of this Element as a #GstClockTime
+ * Since: 1.24
+ */
+GstClockTime
+gst_mse_src_get_position (GstMseSrc * self)
+{
+ g_return_val_if_fail (GST_IS_MSE_SRC (self), GST_CLOCK_TIME_NONE);
+ gint64 position;
+ gboolean success = gst_element_query_position (GST_ELEMENT (self),
+ GST_FORMAT_TIME, &position);
+ if (success)
+ return (GstClockTime) position;
+ else
+ return DEFAULT_POSITION;
+}
+
+static void
+update_pad_duration (GstMseSrc * self, GstMseSrcPad * pad)
+{
+ pad->segment.duration = self->duration;
+ pad->does_need_segment = TRUE;
+}
+
+void
+gst_mse_src_set_duration (GstMseSrc * self, GstClockTime duration)
+{
+ g_return_if_fail (GST_IS_MSE_SRC (self));
+
+ self->duration = duration;
+
+ gst_element_foreach_src_pad (GST_ELEMENT (self),
+ (GstElementForeachPadFunc) update_pad_duration, NULL);
+
+ gst_element_post_message (GST_ELEMENT (self),
+ gst_message_new_duration_changed (GST_OBJECT (self)));
+}
+
+/**
+ * gst_mse_src_get_duration:
+ * @self: #GstMseSrc instance
+ *
+ * Gets the duration of @self.
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration)
+ *
+ * Returns: The duration of this stream as a #GstClockTime
+ * Since: 1.24
+ */
+GstClockTime
+gst_mse_src_get_duration (GstMseSrc * self)
+{
+ g_return_val_if_fail (GST_IS_MSE_SRC (self), DEFAULT_DURATION);
+ return self->duration;
+}
+
+/**
+ * gst_mse_src_get_ready_state:
+ * @self: #GstMseSrc instance
+ *
+ * The Ready State of @self, describing to what level it can supply content for
+ * the current #GstMseSrc:position. This is a separate concept from
+ * #GstMediaSource:ready-state: and corresponds to the HTML Media Element's
+ * Ready State.
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states)
+ *
+ * Returns: the current #GstMseSrcReadyState
+ * Since: 1.24
+ */
+GstMseSrcReadyState
+gst_mse_src_get_ready_state (GstMseSrc * self)
+{
+ g_return_val_if_fail (GST_IS_MSE_SRC (self), DEFAULT_READY_STATE);
+ return self->ready_state;
+}
+
+static guint
+n_streams_by_type (GstMseSrc * self, GstMediaSourceTrackType type)
+{
+ guint count = 0;
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->streams);
+ for (gpointer key; g_hash_table_iter_next (&iter, &key, NULL);) {
+ GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key);
+ GstMediaSourceTrackType stream_type =
+ gst_media_source_track_get_track_type (track);
+ if (type == stream_type) {
+ count++;
+ }
+ }
+ return count;
+}
+
+/**
+ * gst_mse_src_get_n_audio:
+ * @self: #GstMseSrc instance
+ *
+ * Returns: the number of audio tracks available from this source
+ * Since: 1.24
+ */
+guint
+gst_mse_src_get_n_audio (GstMseSrc * self)
+{
+ g_return_val_if_fail (GST_IS_MSE_SRC (self), 0);
+ return n_streams_by_type (self, GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO);
+}
+
+/**
+ * gst_mse_src_get_n_text:
+ * @self: #GstMseSrc instance
+ *
+ * Returns: the number of text tracks available from this source
+ * Since: 1.24
+ */
+guint
+gst_mse_src_get_n_text (GstMseSrc * self)
+{
+ g_return_val_if_fail (GST_IS_MSE_SRC (self), 0);
+ return n_streams_by_type (self, GST_MEDIA_SOURCE_TRACK_TYPE_TEXT);
+}
+
+/**
+ * gst_mse_src_get_n_video:
+ * @self: #GstMseSrc instance
+ *
+ * Returns: the number of video tracks available from this source
+ * Since: 1.24
+ */
+guint
+gst_mse_src_get_n_video (GstMseSrc * self)
+{
+ g_return_val_if_fail (GST_IS_MSE_SRC (self), 0);
+ return n_streams_by_type (self, GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO);
+}
+
+void
+gst_mse_src_decode_error (GstMseSrc * self)
+{
+ g_return_if_fail (GST_IS_MSE_SRC (self));
+ GstMseSrcReadyState ready_state = g_atomic_int_get (&self->ready_state);
+ if (ready_state == GST_MSE_SRC_READY_STATE_HAVE_NOTHING) {
+ GST_ELEMENT_ERROR (self, STREAM, DECODE, (DECODE_ERROR),
+ ("the necessary decoder may be missing from this installation"));
+ } else {
+ GST_ELEMENT_ERROR (self, STREAM, DECODE, (DECODE_ERROR),
+ ("the stream may be corrupt"));
+ }
+}
+
+void
+gst_mse_src_network_error (GstMseSrc * self)
+{
+ g_return_if_fail (GST_IS_MSE_SRC (self));
+ GstMseSrcReadyState ready_state = g_atomic_int_get (&self->ready_state);
+ if (ready_state == GST_MSE_SRC_READY_STATE_HAVE_NOTHING) {
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NETWORK_ERROR),
+ ("an error occurred before any media was read"));
+ } else {
+ GST_ELEMENT_ERROR (self, RESOURCE, READ, (NETWORK_ERROR),
+ ("an error occurred while reading media"));
+ }
+}
+
+static inline gboolean
+is_streamable (GstMediaSourceTrack * track)
+{
+ switch (gst_media_source_track_get_track_type (track)) {
+ case GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO:
+ case GST_MEDIA_SOURCE_TRACK_TYPE_TEXT:
+ case GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static GstStream *
+create_gst_stream (GstMediaSourceTrack * track)
+{
+ gchar *stream_id = g_strdup_printf ("%s-%s",
+ GST_OBJECT_NAME (track), gst_media_source_track_get_id (track));
+ GstStream *stream = gst_stream_new (stream_id,
+ gst_media_source_track_get_initial_caps (track),
+ gst_media_source_track_get_stream_type (track), GST_STREAM_FLAG_SELECT);
+ g_free (stream_id);
+ return stream;
+}
+
+static void
+set_flushing_and_signal (GstMseSrcPad * pad)
+{
+ GST_TRACE_OBJECT (pad, "locking");
+ LINKED_OR_FLUSHING_LOCK (pad);
+ g_atomic_int_set (&pad->flushing, TRUE);
+ LINKED_OR_FLUSHING_SIGNAL (pad);
+ LINKED_OR_FLUSHING_UNLOCK (pad);
+ GST_TRACE_OBJECT (pad, "done");
+}
+
+static void
+clear_flushing (GstMseSrcPad * pad)
+{
+ GST_TRACE_OBJECT (pad, "locking");
+ LINKED_OR_FLUSHING_LOCK (pad);
+ g_atomic_int_set (&pad->flushing, FALSE);
+ LINKED_OR_FLUSHING_UNLOCK (pad);
+ GST_TRACE_OBJECT (pad, "done");
+}
+
+static void
+flush_stream (GstMseSrc * self, Stream * stream, gboolean is_seek)
+{
+ GstMseSrcPad *pad = GST_MSE_SRC_PAD (stream->pad);
+ gst_pad_push_event (GST_PAD (pad), gst_event_new_flush_start ());
+
+ if (is_seek) {
+ GST_DEBUG_OBJECT (pad, "flushing for seek to %" GST_TIMEP_FORMAT,
+ &self->start_time);
+ set_flushing_and_signal (pad);
+ gst_media_source_track_flush (stream->track);
+ gst_pad_stop_task (GST_PAD (pad));
+ GST_DEBUG_OBJECT (pad, "stopped task");
+ GstSegment *segment = &(pad->segment);
+ segment->base = 0;
+ segment->start = self->start_time;
+ segment->time = self->start_time;
+ segment->rate = self->rate;
+ }
+
+ gst_media_source_track_flush (stream->track);
+ g_atomic_int_set (&pad->does_need_segment, TRUE);
+
+ gst_pad_push_event (GST_PAD (pad), gst_event_new_flush_stop (is_seek));
+}
+
+static void
+flush_all_streams (GstMseSrc * self, gboolean is_seek)
+{
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->streams);
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ flush_stream (self, (Stream *) value, is_seek);
+ }
+}
+
+static void
+resume_all_streams (GstMseSrc * self)
+{
+ GstState state;
+ gst_element_get_state (GST_ELEMENT (self), &state, NULL, 0);
+ gboolean active = state > GST_STATE_READY;
+
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->streams);
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ Stream *stream = value;
+ GstPad *pad = GST_PAD (stream->pad);
+ if (active) {
+ clear_flushing (GST_MSE_SRC_PAD (pad));
+ gst_pad_start_task (pad, (GstTaskFunction) pad_task, pad, NULL);
+ }
+ }
+}
+
+static void
+tear_down_stream (GstMseSrc * self, Stream * stream)
+{
+ GST_DEBUG_OBJECT (self, "tearing down stream %s",
+ gst_media_source_track_get_id (stream->track));
+
+ flush_stream (self, stream, FALSE);
+ gst_pad_set_active (stream->pad, FALSE);
+
+ if (gst_stream_collection_get_size (self->collection) > 0) {
+ gst_element_remove_pad (GST_ELEMENT (self), stream->pad);
+ FLOW_COMBINER_LOCK (self);
+ gst_flow_combiner_remove_pad (self->flow_combiner, stream->pad);
+ FLOW_COMBINER_UNLOCK (self);
+ }
+}
+
+
+static void
+tear_down_all_streams (GstMseSrc * self)
+{
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->streams);
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ tear_down_stream (self, (Stream *) value);
+ g_hash_table_iter_remove (&iter);
+ }
+}
+
+static GstStateChangeReturn
+gst_mse_src_change_state (GstElement * element, GstStateChange transition)
+{
+ GstMseSrc *self = GST_MSE_SRC (element);
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ tear_down_all_streams (self);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ gst_mse_src_detach (self);
+ break;
+ default:
+ break;
+ }
+ return GST_ELEMENT_CLASS (gst_mse_src_parent_class)->change_state (element,
+ transition);
+}
+
+static void
+gst_mse_src_seek (GstMseSrc * self, GstClockTime start_time, gdouble rate)
+{
+ self->start_time = start_time;
+ self->rate = rate;
+
+ flush_all_streams (self, TRUE);
+ if (self->media_source) {
+ GST_DEBUG_OBJECT (self, "seeking on media source %" GST_PTR_FORMAT,
+ self->media_source);
+ gst_media_source_seek (self->media_source, start_time);
+ } else {
+ GST_DEBUG_OBJECT (self, "detached, not seeking on media source");
+ }
+ resume_all_streams (self);
+}
+
+static gboolean
+gst_mse_src_send_event (GstElement * element, GstEvent * event)
+{
+ if (GST_EVENT_TYPE (event) != GST_EVENT_SEEK) {
+ return GST_ELEMENT_CLASS (gst_mse_src_parent_class)->send_event (element,
+ event);
+ }
+
+ GstMseSrc *self = GST_MSE_SRC (element);
+
+ gdouble rate;
+ GstFormat format;
+ GstSeekType seek_type;
+ gint64 start;
+ gst_event_parse_seek (event, &rate, &format, NULL, &seek_type, &start, NULL,
+ NULL);
+
+ gst_event_unref (event);
+
+ if (format != GST_FORMAT_TIME || seek_type != GST_SEEK_TYPE_SET) {
+ GST_ERROR_OBJECT (self,
+ "Rejecting unsupported seek event: %" GST_PTR_FORMAT, event);
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT (self, "handling %" GST_PTR_FORMAT, event);
+ gst_mse_src_seek (self, start, rate);
+ return TRUE;
+}
+
+static inline gboolean
+is_flushing (GstMseSrcPad * pad)
+{
+ return g_atomic_int_get (&pad->flushing) || GST_PAD_IS_FLUSHING (pad);
+}
+
+static void
+await_pad_linked_or_flushing (GstMseSrcPad * pad)
+{
+ GST_TRACE_OBJECT (pad, "waiting for link");
+ LINKED_OR_FLUSHING_LOCK (pad);
+ while (!gst_pad_is_linked (GST_PAD_CAST (pad)) && !is_flushing (pad)) {
+ LINKED_OR_FLUSHING_WAIT (pad);
+ }
+ LINKED_OR_FLUSHING_UNLOCK (pad);
+ GST_TRACE_OBJECT (pad, "linked");
+}
+
+static gboolean
+all_pads_eos_fold (const GValue * item, gboolean * all_eos, gpointer user_data)
+{
+ GstMseSrcPad *pad = g_value_get_object (item);
+ if (pad->eos) {
+ return TRUE;
+ } else {
+ *all_eos = FALSE;
+ return FALSE;
+ }
+}
+
+static gboolean
+all_pads_eos (GstMseSrc * self)
+{
+ GstIterator *iter = gst_element_iterate_src_pads (GST_ELEMENT_CAST (self));
+ gboolean all_eos = TRUE;
+ while (gst_iterator_fold (iter,
+ (GstIteratorFoldFunction) all_pads_eos_fold, (GValue *) & all_eos,
+ NULL) == GST_ITERATOR_RESYNC) {
+ gst_iterator_resync (iter);
+ }
+ gst_iterator_free (iter);
+ return all_eos;
+}
+
+static void
+pad_task (GstMseSrcPad * pad)
+{
+ await_pad_linked_or_flushing (pad);
+
+ if (is_flushing (pad)) {
+ GST_TRACE_OBJECT (pad, "pad is flushing");
+ goto pause;
+ }
+
+ GstMseSrc *self = GST_MSE_SRC (gst_pad_get_parent_element (GST_PAD (pad)));
+
+ GstMediaSourceTrack *track = pad->track;
+
+ GstMiniObject *object = gst_media_source_track_pop (track);
+
+ if (object == NULL) {
+ GST_DEBUG_OBJECT (pad, "nothing was popped from track, must be flushing");
+ gst_media_source_track_flush (track);
+ goto pause;
+ }
+
+ if (!g_atomic_int_get (&pad->sent_stream_start)) {
+ const gchar *track_id = gst_media_source_track_get_id (track);
+ GstEvent *event = gst_event_new_stream_start (track_id);
+ gst_event_set_group_id (event, self->group_id);
+ gst_event_set_stream (event, pad->stream);
+ if (!gst_pad_push_event (GST_PAD (pad), event)) {
+ GST_ERROR_OBJECT (pad, "failed to push stream start");
+ goto pause;
+ }
+ GST_TRACE_OBJECT (pad, "stream start");
+ g_atomic_int_set (&pad->sent_stream_start, TRUE);
+ }
+
+ GstCaps *caps = gst_media_source_track_get_initial_caps (track);
+ if (!g_atomic_int_get (&pad->sent_initial_caps) && GST_IS_CAPS (caps)) {
+ GST_DEBUG_OBJECT (pad, "sending initial caps");
+ gst_caps_replace (&pad->most_recent_caps, caps);
+ GstEvent *event = gst_event_new_caps (caps);
+ if (!gst_pad_push_event (GST_PAD (pad), event)) {
+ GST_ERROR_OBJECT (pad, "failed to push caps update");
+ goto pause;
+ }
+ GST_TRACE_OBJECT (pad, "initial caps %" GST_PTR_FORMAT, caps);
+ g_atomic_int_set (&pad->sent_initial_caps, TRUE);
+ }
+
+ if (g_atomic_int_get (&pad->does_need_segment)) {
+ GST_DEBUG_OBJECT (pad, "sending new segment starting@%" GST_TIMEP_FORMAT,
+ &pad->segment.time);
+ GstEvent *event = gst_event_new_segment (&pad->segment);
+ if (!gst_pad_push_event (GST_PAD (pad), event)) {
+ GST_ERROR_OBJECT (pad, "failed to push new segment");
+ goto pause;
+ }
+ GST_TRACE_OBJECT (pad, "segment");
+ g_atomic_int_set (&pad->does_need_segment, FALSE);
+ }
+
+ if (!g_atomic_int_get (&pad->sent_stream_collection)) {
+ GstEvent *event = gst_event_new_stream_collection (self->collection);
+ if (!gst_pad_push_event (GST_PAD (pad), event)) {
+ GST_ERROR_OBJECT (pad, "failed to push stream collection");
+ goto pause;
+ }
+ GST_TRACE_OBJECT (pad, "stream collection");
+ g_atomic_int_set (&pad->sent_stream_collection, TRUE);
+ }
+
+ if (GST_IS_SAMPLE (object)) {
+ GstSample *sample = GST_SAMPLE (object);
+ GstCaps *sample_caps = gst_sample_get_caps (sample);
+
+ if (!gst_caps_is_equal (pad->most_recent_caps, sample_caps)) {
+ gst_caps_replace (&pad->most_recent_caps, sample_caps);
+ GstEvent *event = gst_event_new_caps (gst_caps_ref (sample_caps));
+ if (!gst_pad_push_event (GST_PAD (pad), event)) {
+ GST_ERROR_OBJECT (pad, "failed to push new caps");
+ goto pause;
+ }
+ GST_TRACE_OBJECT (pad, "new caps %" GST_PTR_FORMAT, sample_caps);
+ }
+
+ GstBuffer *buffer = gst_buffer_copy (gst_sample_get_buffer (sample));
+ if (GST_BUFFER_DTS_IS_VALID (buffer)) {
+ pad->position = GST_BUFFER_DTS (buffer);
+ }
+
+ GstFlowReturn push_result = gst_pad_push (GST_PAD (pad), buffer);
+
+ FLOW_COMBINER_LOCK (self);
+ GstFlowReturn combined_result =
+ gst_flow_combiner_update_pad_flow (self->flow_combiner,
+ GST_PAD_CAST (pad), push_result);
+ FLOW_COMBINER_UNLOCK (self);
+
+ if (combined_result != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (pad, "push result: %s, combined result: %s",
+ gst_flow_get_name (push_result), gst_flow_get_name (combined_result));
+ goto pause;
+ }
+ } else if (GST_IS_EVENT (object)) {
+ if (GST_EVENT_TYPE (object) == GST_EVENT_EOS) {
+ g_mutex_lock (&self->eos_lock);
+ pad->eos = TRUE;
+ g_cond_broadcast (&self->eos_cond);
+ g_mutex_unlock (&self->eos_lock);
+ g_mutex_lock (&self->eos_lock);
+ while (!all_pads_eos (self)) {
+ GST_DEBUG_OBJECT (pad, "waiting for eos on all tracks");
+ g_cond_wait (&self->eos_cond, &self->eos_lock);
+ }
+ g_mutex_unlock (&self->eos_lock);
+ GST_DEBUG_OBJECT (pad, "have eos on all tracks");
+ }
+ if (!gst_pad_push_event (GST_PAD (pad), GST_EVENT (object))) {
+ GST_ERROR_OBJECT (self, "failed to push enqueued event");
+ goto pause;
+ }
+ } else {
+ GST_ERROR_OBJECT (self, "unexpected object on track queue"
+ ", only samples and events are supported");
+ g_assert_not_reached ();
+ }
+
+ return;
+
+pause:
+ if (!g_atomic_int_get (&pad->flushing)) {
+ gst_pad_pause_task (GST_PAD (pad));
+ }
+}
+
+static gboolean
+pad_activate_mode (GstMseSrcPad * pad, GstObject * parent, GstPadMode mode,
+ gboolean active)
+{
+ if (mode != GST_PAD_MODE_PUSH) {
+ GST_ERROR_OBJECT (parent, "msesrc only supports push mode");
+ return FALSE;
+ }
+
+ if (active) {
+ gst_pad_start_task (GST_PAD (pad), (GstTaskFunction) pad_task, pad, NULL);
+ } else {
+ set_flushing_and_signal (pad);
+ gst_media_source_track_flush (pad->track);
+ gst_pad_stop_task (GST_PAD (pad));
+ clear_flushing (pad);
+ }
+
+ return TRUE;
+}
+
+static GstPadLinkReturn
+pad_linked (GstMseSrcPad * pad, GstMseSrc * parent, GstPad * sink)
+{
+ GST_DEBUG_OBJECT (pad, "pad is linked to %" GST_PTR_FORMAT ", resuming task",
+ sink);
+ LINKED_OR_FLUSHING_LOCK (pad);
+ LINKED_OR_FLUSHING_SIGNAL (pad);
+ LINKED_OR_FLUSHING_UNLOCK (pad);
+ return GST_PAD_LINK_OK;
+}
+
+static gboolean
+pad_event (GstMseSrcPad * pad, GstMseSrc * parent, GstEvent * event)
+{
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_SEEK:
+ return gst_element_send_event (GST_ELEMENT (parent), event);
+ default:
+ return gst_pad_event_default (GST_PAD (pad), GST_OBJECT (parent), event);
+ }
+}
+
+static gboolean
+pad_query (GstMseSrcPad * pad, GstObject * parent, GstQuery * query)
+{
+ GstMseSrc *self = GST_MSE_SRC (parent);
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_POSITION:{
+ GstClockTime position = pad->position;
+ GstFormat fmt;
+ gst_query_parse_position (query, &fmt, NULL);
+ if (fmt == GST_FORMAT_TIME && GST_CLOCK_TIME_IS_VALID (position)) {
+ GST_TRACE_OBJECT (pad, "position query returning %" GST_TIMEP_FORMAT,
+ &position);
+ gst_query_set_position (query, GST_FORMAT_TIME, position);
+ return TRUE;
+ }
+ break;
+ }
+ case GST_QUERY_DURATION:{
+ GstFormat fmt;
+ gst_query_parse_duration (query, &fmt, NULL);
+ if (fmt == GST_FORMAT_TIME) {
+ gst_query_set_duration (query, GST_FORMAT_TIME, self->duration);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+ case GST_QUERY_SEEKING:{
+ GstFormat fmt;
+ gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
+ if (fmt != GST_FORMAT_TIME) {
+ return FALSE;
+ }
+ gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, self->duration);
+ return TRUE;
+ }
+ default:
+ break;
+ }
+ return gst_pad_query_default (GST_PAD (pad), parent, query);
+}
+
+static void
+append_stream (GstMseSrc * self, GstMediaSourceTrack * track)
+{
+ if (g_hash_table_contains (self->streams, track)) {
+ GST_DEBUG_OBJECT (self, "skipping processed %" GST_PTR_FORMAT, track);
+ return;
+ }
+ GST_DEBUG_OBJECT (self, "creating stream for %" GST_PTR_FORMAT, track);
+ guint pad_index = g_hash_table_size (self->streams);
+ GstStream *info = create_gst_stream (track);
+ Stream stream = {
+ .info = gst_object_ref (info),
+ .track = gst_object_ref (track),
+ .pad = gst_mse_src_pad_new (track, info, pad_index,
+ self->start_time, self->rate),
+ };
+ g_hash_table_insert (self->streams, track,
+ g_memdup2 (&stream, sizeof (Stream)));
+ gst_stream_collection_add_stream (self->collection, stream.info);
+}
+
+void
+gst_mse_src_emit_streams (GstMseSrc * self, GstMediaSourceTrack ** tracks,
+ gsize n_tracks)
+{
+ g_return_if_fail (GST_IS_MSE_SRC (self));
+
+ GstElement *element = GST_ELEMENT (self);
+ GstObject *object = GST_OBJECT (self);
+
+ update_ready_state_for_init_segment (self);
+
+ STREAMS_LOCK (self);
+
+ for (gsize i = 0; i < n_tracks; i++) {
+ GstMediaSourceTrack *track = tracks[i];
+ if (!is_streamable (track)) {
+ continue;
+ }
+ append_stream (self, track);
+ }
+
+ GstState state;
+ gst_element_get_state (element, &state, NULL, 0);
+ gboolean active = state > GST_STATE_READY;
+
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->streams);
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ Stream *stream = value;
+ GstPad *pad = stream->pad;
+ if (active) {
+ gst_pad_set_active (pad, TRUE);
+ }
+ GstElement *parent = gst_pad_get_parent_element (pad);
+ if (parent) {
+ GST_DEBUG_OBJECT (self, "skipping parented pad %" GST_PTR_FORMAT, pad);
+ gst_object_unref (parent);
+ continue;
+ }
+ gst_element_add_pad (element, pad);
+ FLOW_COMBINER_LOCK (self);
+ gst_flow_combiner_add_pad (self->flow_combiner, pad);
+ FLOW_COMBINER_UNLOCK (self);
+ }
+ STREAMS_UNLOCK (self);
+
+ gst_element_no_more_pads (element);
+ gst_element_post_message (element, gst_message_new_stream_collection (object,
+ self->collection));
+}
+
+void
+gst_mse_src_update_ready_state (GstMseSrc * self)
+{
+ g_return_if_fail (GST_IS_MSE_SRC (self));
+ update_ready_state_for_sample (self);
+}
+
+static GstURIType
+gst_mse_src_uri_get_type (GType type)
+{
+ return GST_URI_SRC;
+}
+
+static const gchar *const *
+gst_mse_src_uri_get_protocols (GType type)
+{
+ static const gchar *protocols[] = { "mse", NULL };
+ return protocols;
+}
+
+static gchar *
+gst_mse_src_uri_get_uri (GstURIHandler * handler)
+{
+ GstMseSrc *self = GST_MSE_SRC (handler);
+ return g_strdup (self->uri);
+}
+
+static gboolean
+gst_mse_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
+ GError ** error)
+{
+ GstMseSrc *self = GST_MSE_SRC (handler);
+ g_free (self->uri);
+ self->uri = g_strdup (uri);
+ return TRUE;
+}
+
+static void
+gst_mse_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
+{
+ GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
+ iface->get_type = gst_mse_src_uri_get_type;
+ iface->get_protocols = gst_mse_src_uri_get_protocols;
+ iface->get_uri = gst_mse_src_uri_get_uri;
+ iface->set_uri = gst_mse_src_uri_set_uri;
+}
+
+void
+gst_mse_src_attach (GstMseSrc * self, GstMediaSource * media_source)
+{
+ g_return_if_fail (GST_IS_MSE_SRC (self));
+ g_return_if_fail (GST_IS_MEDIA_SOURCE (media_source));
+ g_set_object (&self->media_source, media_source);
+}
+
+void
+gst_mse_src_detach (GstMseSrc * self)
+{
+ g_return_if_fail (GST_IS_MSE_SRC (self));
+ gst_clear_object (&self->media_source);
+}
+
+static void
+set_ready_state (GstMseSrc * self, GstMseSrcReadyState ready_state)
+{
+ if (ready_state == self->ready_state) {
+ return;
+ }
+ GST_DEBUG_OBJECT (self, "ready state %d=>%d", self->ready_state, ready_state);
+ self->ready_state = ready_state;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_READY_STATE]);
+}
+
+static void
+update_ready_state_for_init_segment (GstMseSrc * self)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE (self->media_source));
+ if (self->ready_state != GST_MSE_SRC_READY_STATE_HAVE_NOTHING) {
+ return;
+ }
+ GstSourceBufferList *buffers = gst_media_source_get_source_buffers
+ (self->media_source);
+ gboolean all_received_init_segment = TRUE;
+ for (guint i = 0; all_received_init_segment; i++) {
+ GstSourceBuffer *buf = gst_source_buffer_list_index (buffers, i);
+ if (buf == NULL) {
+ break;
+ }
+ all_received_init_segment &= gst_source_buffer_has_init_segment (buf);
+ gst_object_unref (buf);
+ }
+ if (!all_received_init_segment) {
+ return;
+ }
+ set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_METADATA);
+}
+
+static gboolean
+has_current_data (GstMseSrc * self)
+{
+ GstClockTime position = gst_mse_src_get_position (self);
+ if (!GST_CLOCK_TIME_IS_VALID (position)) {
+ return FALSE;
+ }
+ GstSourceBufferList *active =
+ gst_media_source_get_active_source_buffers (self->media_source);
+ gboolean has_data = TRUE;
+ for (guint i = 0; has_data; i++) {
+ GstSourceBuffer *buf = gst_source_buffer_list_index (active, i);
+ if (buf == NULL) {
+ if (i == 0) {
+ has_data = FALSE;
+ GST_DEBUG_OBJECT (self,
+ "no active source buffers, nothing is buffered");
+ }
+ break;
+ }
+ has_data = gst_source_buffer_is_buffered (buf, position);
+ gst_object_unref (buf);
+ }
+ g_object_unref (active);
+ return has_data;
+}
+
+static gboolean
+has_future_data (GstMseSrc * self)
+{
+ GstClockTime position = gst_mse_src_get_position (self);
+ GstClockTime duration = self->duration;
+ if (!GST_CLOCK_TIME_IS_VALID (position)
+ || !GST_CLOCK_TIME_IS_VALID (duration)) {
+ return FALSE;
+ }
+ GstClockTime target_position = MIN (position + THRESHOLD_FUTURE_DATA,
+ duration);
+ GstSourceBufferList *active =
+ gst_media_source_get_active_source_buffers (self->media_source);
+ gboolean has_data = TRUE;
+ for (guint i = 0; has_data; i++) {
+ GstSourceBuffer *buf = gst_source_buffer_list_index (active, i);
+ if (buf == NULL) {
+ if (i == 0) {
+ has_data = FALSE;
+ GST_DEBUG_OBJECT (self,
+ "no active source buffers, nothing is buffered");
+ }
+ break;
+ }
+ has_data = gst_source_buffer_is_range_buffered (buf, position,
+ target_position);
+ gst_object_unref (buf);
+ }
+ g_object_unref (active);
+ return has_data;
+}
+
+static gboolean
+has_enough_data (GstMseSrc * self)
+{
+ GstClockTime position = gst_mse_src_get_position (self);
+ GstClockTime duration = self->duration;
+ if (!GST_CLOCK_TIME_IS_VALID (position)
+ || !GST_CLOCK_TIME_IS_VALID (duration)) {
+ return FALSE;
+ }
+ GstClockTime target_position = MIN (position + THRESHOLD_ENOUGH_DATA,
+ duration);
+ GstSourceBufferList *active =
+ gst_media_source_get_active_source_buffers (self->media_source);
+ gboolean has_data = TRUE;
+ for (guint i = 0; has_data; i++) {
+ GstSourceBuffer *buf = gst_source_buffer_list_index (active, i);
+ if (buf == NULL) {
+ if (i == 0) {
+ has_data = FALSE;
+ GST_DEBUG_OBJECT (self,
+ "no active source buffers, nothing is buffered");
+ }
+ break;
+ }
+ has_data = gst_source_buffer_is_range_buffered (buf, position,
+ target_position);
+ gst_object_unref (buf);
+ }
+ g_object_unref (active);
+ return has_data;
+}
+
+static void
+update_ready_state_for_sample (GstMseSrc * self)
+{
+ g_return_if_fail (GST_IS_MEDIA_SOURCE (self->media_source));
+ g_return_if_fail (self->ready_state >= GST_MSE_SRC_READY_STATE_HAVE_METADATA);
+
+ if (has_enough_data (self)) {
+ set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_ENOUGH_DATA);
+ } else if (has_future_data (self)) {
+ set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_FUTURE_DATA);
+ } else if (has_current_data (self)) {
+ set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_CURRENT_DATA);
+ } else {
+ set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_METADATA);
+ }
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.h
new file mode 100644
index 0000000000..1a7ff1411f
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.h
@@ -0,0 +1,94 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 2009, 2010 Sebastian Dröge
+ * Copyright (C) 2013, 2022, 2023 Collabora Ltd.
+ * Copyright (C) 2013 Orange
+ * Copyright (C) 2014, 2015 Sebastian Dröge
+ * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Igalia, S.L
+ * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Metrological Group B.V.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+/**
+ * GstMseSrcReadyState:
+ * @GST_MSE_SRC_READY_STATE_HAVE_NOTHING: No information is available about the
+ * stream
+ * @GST_MSE_SRC_READY_STATE_HAVE_METADATA: The duration is available and video
+ * dimensions are available if the stream contains video
+ * @GST_MSE_SRC_READY_STATE_HAVE_CURRENT_DATA: The current playback position can
+ * be presented but future information is not available
+ * @GST_MSE_SRC_READY_STATE_HAVE_FUTURE_DATA: There is data for the current
+ * position and some amount in the future and any text tracks are ready.
+ * @GST_MSE_SRC_READY_STATE_HAVE_ENOUGH_DATA: Either there is enough data to
+ * play the stream through at the current playback and input rate or the input
+ * buffer is full.
+ *
+ * Describes how much information a #GstMseSrc has about the media it is playing
+ * back at the current playback #GstMseSrc:position. This type corresponds
+ * directly to the ready state of a HTML Media Element and is a separate concept
+ * from #GstMediaSourceReadyState.
+ *
+ * [Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states)
+ *
+ * Since: 1.24
+ */
+typedef enum
+{
+ GST_MSE_SRC_READY_STATE_HAVE_NOTHING,
+ GST_MSE_SRC_READY_STATE_HAVE_METADATA,
+ GST_MSE_SRC_READY_STATE_HAVE_CURRENT_DATA,
+ GST_MSE_SRC_READY_STATE_HAVE_FUTURE_DATA,
+ GST_MSE_SRC_READY_STATE_HAVE_ENOUGH_DATA,
+} GstMseSrcReadyState;
+
+#define GST_TYPE_MSE_SRC (gst_mse_src_get_type())
+#define GST_TYPE_MSE_SRC_PAD (gst_mse_src_pad_get_type())
+
+GST_MSE_API
+G_DECLARE_FINAL_TYPE (GstMseSrc, gst_mse_src, GST, MSE_SRC, GstElement);
+
+GST_MSE_API
+G_DECLARE_FINAL_TYPE (GstMseSrcPad, gst_mse_src_pad, GST, MSE_SRC_PAD, GstPad);
+
+GST_MSE_API
+GstClockTime gst_mse_src_get_position (GstMseSrc * self);
+
+GST_MSE_API
+GstClockTime gst_mse_src_get_duration (GstMseSrc * self);
+
+GST_MSE_API
+GstMseSrcReadyState gst_mse_src_get_ready_state (GstMseSrc * self);
+
+GST_MSE_API
+guint gst_mse_src_get_n_audio (GstMseSrc * self);
+
+GST_MSE_API
+guint gst_mse_src_get_n_text (GstMseSrc * self);
+
+GST_MSE_API
+guint gst_mse_src_get_n_video (GstMseSrc * self);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer-private.h
new file mode 100644
index 0000000000..17ae1af5c3
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer-private.h
@@ -0,0 +1,72 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include "gstsourcebuffer.h"
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ void (*duration_changed) (GstSourceBuffer * self, gpointer user_data);
+ void (*received_init_segment) (GstSourceBuffer * self, gpointer user_data);
+ void (*active_state_changed) (GstSourceBuffer * self, gpointer user_data);
+} GstSourceBufferCallbacks;
+
+GST_MSE_PRIVATE
+GstSourceBuffer * gst_source_buffer_new (const gchar * content_type,
+ GstObject * parent, GError ** error);
+
+GST_MSE_PRIVATE
+GstSourceBuffer * gst_source_buffer_new_with_callbacks (
+ const gchar * content_type, GstObject * parent,
+ GstSourceBufferCallbacks *callbacks, gpointer user_data, GError ** error);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_teardown (GstSourceBuffer * self);
+
+GST_MSE_PRIVATE
+gboolean gst_source_buffer_has_init_segment (GstSourceBuffer * self);
+
+GST_MSE_PRIVATE
+gboolean gst_source_buffer_is_buffered (GstSourceBuffer * self, GstClockTime
+ time);
+
+GST_MSE_PRIVATE
+gboolean gst_source_buffer_is_range_buffered (GstSourceBuffer * self,
+ GstClockTime start, GstClockTime end);
+
+GST_MSE_PRIVATE
+GstClockTime gst_source_buffer_get_duration (GstSourceBuffer * self);
+
+GST_MSE_PRIVATE
+GPtrArray * gst_source_buffer_get_all_tracks (GstSourceBuffer * self);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_seek (GstSourceBuffer * self, GstClockTime time);
+
+GST_MSE_PRIVATE
+gboolean gst_source_buffer_get_active (GstSourceBuffer * self);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.c
new file mode 100644
index 0000000000..7695bad373
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.c
@@ -0,0 +1,2019 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2013 Orange
+ * Copyright (C) 2013-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2014 Sebastian Dröge
+ * Copyright (C) 2015, 2016, 2017 Igalia, S.L
+ * Copyright (C) 2015, 2016, 2017 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:gstsourcebuffer
+ * @title: GstSourceBuffer
+ * @short_description: Source Buffer
+ * @include: mse/mse.h
+ * @symbols:
+ * - GstSourceBuffer
+ *
+ * The Source Buffer is the primary means of data flow between an application
+ * and the Media Source API. It represents a single timeline of media,
+ * containing some combination of audio, video, and text tracks.
+ * An application is responsible for feeding raw data into the Source Buffer
+ * using gst_source_buffer_append_buffer() and the Source Buffer will
+ * asynchronously process the data into tracks of time-coded multimedia samples.
+ *
+ * The application as well as the associated playback component can then select
+ * to play media from any subset of tracks across all Source Buffers of a Media
+ * Source.
+ *
+ * A few control points are also provided to customize the behavior.
+ *
+ * - #GstSourceBuffer:append-mode controls how timestamps of processed samples are
+ * interpreted. They are either inserted in the timeline directly where the
+ * decoded media states they should, or inserted directly after the previously
+ * encountered sample.
+ *
+ * - #GstSourceBuffer:append-window-start / #GstSourceBuffer:append-window-end
+ * control the planned time window where media from appended data can be added
+ * to the current timeline. Any samples outside that range may be ignored.
+ *
+ * - #GstSourceBuffer:timestamp-offset is added to the start time of any sample
+ * processed.
+ *
+ * Since: 1.24
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include "gstsourcebuffer.h"
+#include "gstsourcebuffer-private.h"
+
+#include "gstmselogging-private.h"
+#include "gstmsemediatype-private.h"
+#include "gstmseeventqueue-private.h"
+
+#include "gstappendpipeline-private.h"
+#include "gstmediasource.h"
+#include "gstmediasource-private.h"
+#include "gstmediasourcetrack-private.h"
+#include "gstmediasourcetrackbuffer-private.h"
+#include "gstsourcebufferlist-private.h"
+#include "gstmsesrc.h"
+#include "gstmsesrc-private.h"
+
+#define g_array_new_ranges() \
+ (g_array_new (TRUE, FALSE, sizeof (GstMediaSourceRange)))
+
+typedef struct
+{
+ GstSourceBufferCallbacks callbacks;
+ gpointer user_data;
+} Callbacks;
+
+/**
+ * GstSourceBuffer:
+ * Since: 1.24
+ */
+typedef struct
+{
+ GstSourceBuffer *parent;
+
+ GstMediaSourceTrack *track;
+ GstMediaSourceTrackBuffer *buffer;
+
+ GstTask *task;
+ GRecMutex lock;
+
+ gboolean cancelled;
+} TrackFeedTask;
+
+typedef struct
+{
+ gsize n_samples;
+ GstSample *current_sample;
+ GstClockTime current_dts;
+} TrackFeedAccumulator;
+
+typedef struct
+{
+ const GstClockTime time;
+ gboolean buffered;
+} IsBufferedAccumulator;
+
+typedef struct
+{
+ const GstClockTime start;
+ const GstClockTime end;
+ gboolean start_buffered;
+ gboolean end_buffered;
+} IsRangeBufferedAccumulator;
+
+struct _GstSourceBuffer
+{
+ GstObject parent_instance;
+
+ GstSourceBufferAppendMode append_mode;
+ GstClockTime append_window_start;
+ GstClockTime append_window_end;
+ gchar *content_type;
+ gboolean generate_timestamps;
+ GstClockTime timestamp_offset;
+ gboolean updating;
+ gboolean errored;
+ gsize size_limit;
+ gsize size;
+ GstBuffer *pending_data;
+ GstTask *append_to_buffer_task;
+ GRecMutex append_to_buffer_lock;
+ GstClockTime seek_time;
+ GstAppendPipeline *append_pipeline;
+ GstMseEventQueue *event_queue;
+
+ gboolean processed_init_segment;
+
+ GHashTable *track_buffers;
+ GHashTable *track_feeds;
+
+ Callbacks callbacks;
+};
+
+G_DEFINE_TYPE (GstSourceBuffer, gst_source_buffer, GST_TYPE_OBJECT);
+
+enum
+{
+ PROP_0,
+
+ PROP_APPEND_MODE,
+ PROP_APPEND_WINDOW_START,
+ PROP_APPEND_WINDOW_END,
+ PROP_BUFFERED,
+ PROP_CONTENT_TYPE,
+ PROP_TIMESTAMP_OFFSET,
+ PROP_UDPATING,
+
+ N_PROPS,
+};
+
+typedef enum
+{
+ ON_UPDATE_START,
+ ON_UPDATE,
+ ON_UPDATE_END,
+ ON_ERROR,
+ ON_ABORT,
+
+ N_SIGNALS,
+} SourceBufferEvent;
+
+typedef struct
+{
+ GstDataQueueItem item;
+ SourceBufferEvent event;
+} SourceBufferEventItem;
+
+#define DEFAULT_BUFFER_SIZE 1 << 24
+#define DEFAULT_APPEND_MODE GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS
+
+static GParamSpec *properties[N_PROPS];
+static guint signals[N_SIGNALS];
+
+static void call_received_init_segment (GstSourceBuffer * self);
+static void call_duration_changed (GstSourceBuffer * self);
+static void call_active_state_changed (GstSourceBuffer * self);
+
+static inline gboolean is_removed (GstSourceBuffer * self);
+static void reset_parser_state (GstSourceBuffer * self);
+static void append_error (GstSourceBuffer * self);
+
+static void seek_track_buffer (GstMediaSourceTrack * track,
+ GstMediaSourceTrackBuffer * buffer, GstSourceBuffer * self);
+static void dispatch_event (SourceBufferEventItem * item, GstSourceBuffer *
+ self);
+static void schedule_event (GstSourceBuffer * self, SourceBufferEvent event);
+static void append_to_buffer_task (GstSourceBuffer * self);
+static void track_feed_task (TrackFeedTask * feed);
+static void clear_track_feed (TrackFeedTask * feed);
+static void stop_track_feed (TrackFeedTask * feed);
+static void start_track_feed (TrackFeedTask * feed);
+static void reset_track_feed (TrackFeedTask * feed);
+static TrackFeedTask *get_track_feed (GstSourceBuffer * self,
+ GstMediaSourceTrack * track);
+static GstMediaSourceTrackBuffer *get_track_buffer (GstSourceBuffer * self,
+ GstMediaSourceTrack * track);
+static void add_track_feed (GstMediaSourceTrack * track,
+ GstMediaSourceTrackBuffer * track_buffer, GstSourceBuffer * self);
+static void add_track_buffer (GstMediaSourceTrack * track, GstSourceBuffer *
+ self);
+static void update_msesrc_ready_state (GstSourceBuffer * self);
+
+static void on_duration_changed (GstAppendPipeline * pipeline,
+ gpointer user_data);
+static void on_eos (GstAppendPipeline * pipeline, GstMediaSourceTrack * track,
+ gpointer user_data);
+static void on_error (GstAppendPipeline * pipeline, gpointer user_data);
+static void on_new_sample (GstAppendPipeline * pipeline,
+ GstMediaSourceTrack * track, GstSample * sample, gpointer user_data);
+static void on_received_init_segment (GstAppendPipeline * pipeline,
+ gpointer user_data);
+
+static inline GstMediaSource *
+get_media_source (GstSourceBuffer * self)
+{
+ return GST_MEDIA_SOURCE (gst_object_get_parent (GST_OBJECT (self)));
+}
+
+static GstMseSrc *
+get_msesrc (GstSourceBuffer * self)
+{
+ GstMediaSource *media_source = get_media_source (self);
+ if (media_source == NULL) {
+ return NULL;
+ }
+ return gst_media_source_get_source_element (media_source);
+}
+
+static void
+clear_pending_data (GstSourceBuffer * self)
+{
+ gst_clear_buffer (&self->pending_data);
+}
+
+static GstBuffer *
+take_pending_data (GstSourceBuffer * self)
+{
+ return g_steal_pointer (&self->pending_data);
+}
+
+static void
+set_pending_data (GstSourceBuffer * self, GstBuffer * buffer)
+{
+ clear_pending_data (self);
+ self->pending_data = buffer;
+}
+
+GstSourceBuffer *
+gst_source_buffer_new (const gchar * content_type, GstObject * parent,
+ GError ** error)
+{
+ g_return_val_if_fail (GST_IS_MEDIA_SOURCE (parent), NULL);
+ g_return_val_if_fail (content_type != NULL, NULL);
+
+ gst_mse_init_logging ();
+
+ GstMediaSourceMediaType media_type = GST_MEDIA_SOURCE_MEDIA_TYPE_INIT;
+ gst_media_source_media_type_parse (&media_type, content_type);
+
+ gboolean generate_timestamps = gst_media_source_media_type_generates_timestamp
+ (&media_type);
+ gst_media_source_media_type_reset (&media_type);
+
+ GstSourceBufferAppendMode append_mode = generate_timestamps
+ ? GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE
+ : GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS;
+
+ GstSourceBuffer *self = g_object_new (GST_TYPE_SOURCE_BUFFER,
+ "parent", parent, NULL);
+
+ self->generate_timestamps = generate_timestamps;
+ self->append_mode = append_mode;
+ self->content_type = g_strdup (content_type);
+
+ GstAppendPipelineCallbacks callbacks = {
+ .duration_changed = on_duration_changed,
+ .eos = on_eos,
+ .error = on_error,
+ .new_sample = on_new_sample,
+ .received_init_segment = on_received_init_segment,
+ };
+ GError *append_pipeline_error = NULL;
+ self->append_pipeline =
+ gst_append_pipeline_new (&callbacks, self, &append_pipeline_error);
+ if (append_pipeline_error) {
+ g_propagate_prefixed_error (error, append_pipeline_error,
+ "failed to create source buffer");
+ goto error;
+ }
+
+ return gst_object_ref_sink (self);
+error:
+ gst_clear_object (&self);
+ return NULL;
+}
+
+GstSourceBuffer *
+gst_source_buffer_new_with_callbacks (const gchar * content_type,
+ GstObject * parent, GstSourceBufferCallbacks * callbacks,
+ gpointer user_data, GError ** error)
+{
+ g_return_val_if_fail (callbacks, NULL);
+
+ GError *source_buffer_error = NULL;
+ GstSourceBuffer *self =
+ gst_source_buffer_new (content_type, parent, &source_buffer_error);
+ if (source_buffer_error) {
+ g_propagate_error (error, source_buffer_error);
+ gst_clear_object (&self);
+ return NULL;
+ }
+ self->callbacks.callbacks = *callbacks;
+ self->callbacks.user_data = user_data;
+
+ return self;
+}
+
+static void
+gst_source_buffer_dispose (GObject * object)
+{
+ GstSourceBuffer *self = (GstSourceBuffer *) object;
+
+ if (self->append_to_buffer_task) {
+ gst_task_join (self->append_to_buffer_task);
+ }
+ gst_clear_object (&self->append_to_buffer_task);
+
+ gst_clear_object (&self->append_pipeline);
+
+ g_hash_table_remove_all (self->track_feeds);
+
+ if (!is_removed (self)) {
+ GstMediaSource *parent = get_media_source (self);
+ gst_media_source_remove_source_buffer (parent, self, NULL);
+ gst_object_unref (parent);
+ }
+
+ gst_clear_object (&self->event_queue);
+
+ G_OBJECT_CLASS (gst_source_buffer_parent_class)->dispose (object);
+}
+
+static void
+gst_source_buffer_finalize (GObject * object)
+{
+ GstSourceBuffer *self = (GstSourceBuffer *) object;
+
+ g_clear_pointer (&self->content_type, g_free);
+ g_rec_mutex_clear (&self->append_to_buffer_lock);
+
+ g_hash_table_unref (self->track_buffers);
+ g_hash_table_unref (self->track_feeds);
+
+ G_OBJECT_CLASS (gst_source_buffer_parent_class)->finalize (object);
+}
+
+static void
+gst_source_buffer_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstSourceBuffer *self = GST_SOURCE_BUFFER (object);
+
+ switch (prop_id) {
+ case PROP_APPEND_MODE:
+ g_value_set_enum (value, gst_source_buffer_get_append_mode (self));
+ break;
+ case PROP_APPEND_WINDOW_START:
+ g_value_set_uint64 (value,
+ gst_source_buffer_get_append_window_start (self));
+ break;
+ case PROP_APPEND_WINDOW_END:
+ g_value_set_uint64 (value,
+ gst_source_buffer_get_append_window_end (self));
+ break;
+ case PROP_BUFFERED:
+ g_value_take_boxed (value, gst_source_buffer_get_buffered (self, NULL));
+ break;
+ case PROP_CONTENT_TYPE:
+ g_value_take_string (value, gst_source_buffer_get_content_type (self));
+ break;
+ case PROP_TIMESTAMP_OFFSET:
+ g_value_set_int64 (value, gst_source_buffer_get_timestamp_offset (self));
+ break;
+ case PROP_UDPATING:
+ g_value_set_boolean (value, gst_source_buffer_get_updating (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gst_source_buffer_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstSourceBuffer *self = GST_SOURCE_BUFFER (object);
+
+ switch (prop_id) {
+ case PROP_APPEND_MODE:
+ gst_source_buffer_set_append_mode (self, g_value_get_enum (value), NULL);
+ break;
+ case PROP_CONTENT_TYPE:
+ gst_source_buffer_change_content_type (self,
+ g_value_get_string (value), NULL);
+ break;
+ case PROP_TIMESTAMP_OFFSET:
+ gst_source_buffer_set_timestamp_offset (self,
+ g_value_get_int64 (value), NULL);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gst_source_buffer_class_init (GstSourceBufferClass * klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = GST_DEBUG_FUNCPTR (gst_source_buffer_dispose);
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_source_buffer_finalize);
+ oclass->get_property = GST_DEBUG_FUNCPTR (gst_source_buffer_get_property);
+ oclass->set_property = GST_DEBUG_FUNCPTR (gst_source_buffer_set_property);
+
+ /**
+ * GstSourceBuffer:append-mode:
+ *
+ * Affects how timestamps of processed media segments are interpreted.
+ * In %GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS, the start timestamp of a
+ * processed media segment is used directly along with
+ * #GstSourceBuffer:timestamp-offset .
+ * In %GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE, the timestamp of a
+ * processed media segment is ignored and replaced with the end time of the
+ * most recently appended segment.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_APPEND_MODE] = g_param_spec_enum ("append-mode",
+ "Append Mode",
+ "Either Segments or Sequence",
+ GST_TYPE_SOURCE_BUFFER_APPEND_MODE, DEFAULT_APPEND_MODE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstSourceBuffer:append-window-start:
+ *
+ * Any segments processed which end before this value will be ignored by this
+ * Source Buffer.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_APPEND_WINDOW_START] =
+ g_param_spec_uint64 ("append-window-start", "Append Window Start",
+ "The timestamp representing the start of the append window", 0,
+ GST_CLOCK_TIME_NONE, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstSourceBuffer:append-window-end:
+ *
+ * Any segments processed which have a start time greater than this value will
+ * be ignored by this Source Buffer.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_APPEND_WINDOW_END] = g_param_spec_uint64 ("append-window-end",
+ "Append Window End",
+ "The timestamp representing the end of the append window",
+ 0, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstSourceBuffer:buffered:
+ *
+ * The set of Time Intervals that have been loaded into the current Source
+ * Buffer
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_BUFFERED] = g_param_spec_boxed ("buffered",
+ "Buffered Time Intervals",
+ "The set of Time Intervals that have been loaded into"
+ " the current Source Buffer", G_TYPE_ARRAY, G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstSourceBuffer:content-type:
+ *
+ * The MIME content-type of the data stream
+ *
+ * Since: 1.24
+ */
+ properties[PROP_CONTENT_TYPE] = g_param_spec_string ("content-type",
+ "Content Type",
+ "The MIME content-type of the data stream", NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstSourceBuffer:timestamp-offset:
+ *
+ * The next media segment appended to the current Source Buffer will have its
+ * start timestamp increased by this amount.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_TIMESTAMP_OFFSET] = g_param_spec_int64 ("timestamp-offset",
+ "Timestamp Offset",
+ "The next media segment appended to the current Source Buffer"
+ " will have its start timestamp increased by this amount",
+ 0, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GstSourceBuffer:updating:
+ *
+ * Whether the current source buffer is still asynchronously processing
+ * previously issued commands.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_UDPATING] = g_param_spec_boolean ("updating",
+ "Updating",
+ "Whether the current Source Buffer is still"
+ " asynchronously processing previously issued commands",
+ FALSE, G_PARAM_READABLE);
+
+ g_object_class_install_properties (oclass, N_PROPS, properties);
+
+ /**
+ * GstSourceBuffer::on-update-start:
+ * @self: The #GstSourceBuffer that has just started updating
+ *
+ * Emitted when @self has begun to process data after a call to
+ * gst_source_buffer_append_buffer().
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdatestart)
+ *
+ * Since: 1.24
+ */
+ signals[ON_UPDATE_START] = g_signal_new ("on-update-start",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ /**
+ * GstSourceBuffer::on-update:
+ * @self: The #GstSourceBuffer that has just updated
+ *
+ * Emitted when @self has successfully processed data after a call to
+ * gst_source_buffer_append_buffer().
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdate)
+ *
+ * Since: 1.24
+ */
+ signals[ON_UPDATE] = g_signal_new ("on-update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ /**
+ * GstSourceBuffer::on-update-end:
+ * @self: The #GstSourceBuffer that is no longer updating
+ *
+ * Emitted when @self is no longer in the updating state after a call to
+ * gst_source_buffer_append_buffer(). This can happen after a successful or
+ * unsuccessful append.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdateend)
+ *
+ * Since: 1.24
+ */
+ signals[ON_UPDATE_END] = g_signal_new ("on-update-end",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ /**
+ * GstSourceBuffer::on-error:
+ * @self: The #GstSourceBuffer that has encountered an error
+ *
+ * Emitted when @self has encountered an error after a call to
+ * gst_source_buffer_append_buffer().
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onerror)
+ *
+ * Since: 1.24
+ */
+ signals[ON_ERROR] = g_signal_new ("on-error",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ /**
+ * GstSourceBuffer::on-abort:
+ * @self: The #GstSourceBuffer that has been aborted.
+ *
+ * Emitted when @self was aborted after a call to gst_source_buffer_abort().
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onabort)
+ *
+ * Since: 1.24
+ */
+ signals[ON_ABORT] = g_signal_new ("on-abort",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+static void
+on_duration_changed (GstAppendPipeline * pipeline, gpointer user_data)
+{
+ GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
+ if (is_removed (self)) {
+ return;
+ }
+ call_duration_changed (self);
+}
+
+static void
+on_eos (GstAppendPipeline * pipeline, GstMediaSourceTrack * track,
+ gpointer user_data)
+{
+ GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
+ if (GST_IS_MEDIA_SOURCE_TRACK (track)) {
+ GST_DEBUG_OBJECT (self, "got EOS event on %" GST_PTR_FORMAT, track);
+ GstMediaSourceTrackBuffer *buffer = get_track_buffer (self, track);
+ gst_media_source_track_buffer_eos (buffer);
+ }
+ update_msesrc_ready_state (self);
+}
+
+static void
+on_error (GstAppendPipeline * pipeline, gpointer user_data)
+{
+ GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
+ append_error (self);
+}
+
+static GstMediaSourceTrackBuffer *
+get_track_buffer (GstSourceBuffer * self, GstMediaSourceTrack * track)
+{
+ g_return_val_if_fail (g_hash_table_contains (self->track_buffers, track),
+ NULL);
+ return g_hash_table_lookup (self->track_buffers, track);
+}
+
+static inline TrackFeedTask *
+get_track_feed (GstSourceBuffer * self, GstMediaSourceTrack * track)
+{
+ g_return_val_if_fail (g_hash_table_contains (self->track_feeds, track), NULL);
+ return g_hash_table_lookup (self->track_feeds, track);
+}
+
+static void
+add_track_buffer (GstMediaSourceTrack * track, GstSourceBuffer * self)
+{
+ const gchar *id = gst_media_source_track_get_id (track);
+ if (g_hash_table_contains (self->track_buffers, track)) {
+ GST_DEBUG_OBJECT (self, "already have a track buffer for track %s", id);
+ return;
+ }
+ GstMediaSourceTrackBuffer *buf = gst_media_source_track_buffer_new ();
+ g_hash_table_insert (self->track_buffers, track, buf);
+ GST_DEBUG_OBJECT (self, "added track buffer for track %s", id);
+
+ add_track_feed (track, buf, self);
+}
+
+static void
+add_track_feed (GstMediaSourceTrack * track,
+ GstMediaSourceTrackBuffer * track_buffer, GstSourceBuffer * self)
+{
+ TrackFeedTask *feed = g_new0 (TrackFeedTask, 1);
+ GstTask *task = gst_task_new ((GstTaskFunction) track_feed_task, feed, NULL);
+ g_rec_mutex_init (&feed->lock);
+ gst_task_set_lock (task, &feed->lock);
+ feed->task = task;
+ feed->buffer = track_buffer;
+ feed->track = gst_object_ref (track);
+ feed->parent = self;
+ feed->cancelled = FALSE;
+ g_hash_table_insert (self->track_feeds, track, feed);
+}
+
+static void
+clear_track_feed (TrackFeedTask * feed)
+{
+ gst_object_unref (feed->task);
+ g_rec_mutex_clear (&feed->lock);
+ gst_object_unref (feed->track);
+ g_free (feed);
+}
+
+static void
+stop_track_feed (TrackFeedTask * feed)
+{
+ g_return_if_fail (feed != NULL);
+ gst_media_source_track_flush (feed->track);
+ g_atomic_int_set (&feed->cancelled, TRUE);
+ gst_task_join (feed->task);
+}
+
+static void
+start_track_feed (TrackFeedTask * feed)
+{
+ g_return_if_fail (feed != NULL);
+ g_atomic_int_set (&feed->cancelled, FALSE);
+ gst_media_source_track_resume (feed->track);
+ gst_task_start (feed->task);
+}
+
+static void
+reset_track_feed (TrackFeedTask * feed)
+{
+ stop_track_feed (feed);
+ start_track_feed (feed);
+}
+
+static gboolean
+is_within_append_window (GstSourceBuffer * self, GstSample * sample)
+{
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ GstClockTime start = GST_BUFFER_PTS (buffer);
+ GstClockTime end = start + GST_BUFFER_DURATION (buffer);
+
+ if (start < self->append_window_start) {
+ return FALSE;
+ }
+
+ if (!GST_CLOCK_TIME_IS_VALID (self->append_window_end)) {
+ return TRUE;
+ }
+
+ return end <= self->append_window_end;
+}
+
+static void
+on_new_sample (GstAppendPipeline * pipeline, GstMediaSourceTrack * track,
+ GstSample * sample, gpointer user_data)
+{
+ GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
+
+ g_return_if_fail (self->processed_init_segment);
+
+ GST_OBJECT_LOCK (self);
+
+ if (is_within_append_window (self, sample)) {
+ GstMediaSourceTrackBuffer *track_buffer = get_track_buffer (self, track);
+ GST_TRACE_OBJECT (self, "new sample on %s with %" GST_PTR_FORMAT,
+ gst_media_source_track_get_id (track), gst_sample_get_buffer (sample));
+ gst_media_source_track_buffer_add (track_buffer, sample);
+ TrackFeedTask *feed = get_track_feed (self, track);
+ start_track_feed (feed);
+ }
+
+ GST_OBJECT_UNLOCK (self);
+
+ update_msesrc_ready_state (self);
+}
+
+static void
+call_received_init_segment (GstSourceBuffer * self)
+{
+ GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks;
+ if (callbacks->received_init_segment) {
+ callbacks->received_init_segment (self, self->callbacks.user_data);
+ }
+}
+
+static void
+call_duration_changed (GstSourceBuffer * self)
+{
+ GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks;
+ if (callbacks->duration_changed) {
+ callbacks->duration_changed (self, self->callbacks.user_data);
+ }
+}
+
+static void
+call_active_state_changed (GstSourceBuffer * self)
+{
+ GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks;
+ if (callbacks->active_state_changed) {
+ callbacks->active_state_changed (self, self->callbacks.user_data);
+ }
+}
+
+static void
+update_track_buffer_modes (GstSourceBuffer * self)
+{
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ gboolean enabled =
+ self->append_mode == GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE;
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ GstMediaSourceTrackBuffer *buffer = GST_MEDIA_SOURCE_TRACK_BUFFER (value);
+ gst_media_source_track_buffer_process_init_segment (buffer, enabled);
+ gst_media_source_track_buffer_set_group_start (buffer,
+ self->timestamp_offset);
+ }
+}
+
+static void
+on_received_init_segment (GstAppendPipeline * pipeline, gpointer user_data)
+{
+ GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
+ GST_DEBUG_OBJECT (self, "got init segment, have duration %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (gst_append_pipeline_get_duration (pipeline)));
+
+ GST_OBJECT_LOCK (self);
+
+ if (!self->processed_init_segment) {
+ GST_DEBUG_OBJECT (self, "processing first init segment");
+
+ GPtrArray *audio_tracks = gst_append_pipeline_get_audio_tracks (pipeline);
+ GPtrArray *text_tracks = gst_append_pipeline_get_text_tracks (pipeline);
+ GPtrArray *video_tracks = gst_append_pipeline_get_video_tracks (pipeline);
+
+ g_ptr_array_foreach (audio_tracks, (GFunc) add_track_buffer, self);
+ g_ptr_array_foreach (text_tracks, (GFunc) add_track_buffer, self);
+ g_ptr_array_foreach (video_tracks, (GFunc) add_track_buffer, self);
+ }
+
+ self->processed_init_segment = TRUE;
+
+ update_track_buffer_modes (self);
+
+ GST_OBJECT_UNLOCK (self);
+
+ call_received_init_segment (self);
+ call_active_state_changed (self);
+}
+
+static guint
+track_buffer_hash (GstMediaSourceTrack * track)
+{
+ return g_str_hash (gst_media_source_track_get_id (track));
+}
+
+static gboolean
+track_buffer_equal (GstMediaSourceTrack * a, GstMediaSourceTrack * b)
+{
+ return g_str_equal (gst_media_source_track_get_id (a),
+ gst_media_source_track_get_id (b));
+}
+
+static void
+gst_source_buffer_init (GstSourceBuffer * self)
+{
+
+ self->append_mode = DEFAULT_APPEND_MODE;
+ self->append_window_start = 0;
+ self->append_window_end = GST_CLOCK_TIME_NONE;
+ self->content_type = NULL;
+ self->timestamp_offset = 0;
+ self->updating = FALSE;
+ self->errored = FALSE;
+ self->size_limit = DEFAULT_BUFFER_SIZE;
+ self->size = 0;
+ self->pending_data = NULL;
+ self->processed_init_segment = FALSE;
+ self->event_queue =
+ gst_mse_event_queue_new ((GstMseEventQueueCallback) dispatch_event, self);
+
+ g_rec_mutex_init (&self->append_to_buffer_lock);
+ self->append_to_buffer_task = gst_task_new (
+ (GstTaskFunction) append_to_buffer_task, self, NULL);
+ gst_task_set_lock (self->append_to_buffer_task, &self->append_to_buffer_lock);
+ self->track_buffers = g_hash_table_new_full ((GHashFunc) track_buffer_hash,
+ (GEqualFunc) track_buffer_equal, NULL, gst_object_unref);
+
+ self->track_feeds = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) clear_track_feed);
+ self->seek_time = 0;
+ self->callbacks.callbacks.duration_changed = NULL;
+ self->callbacks.user_data = NULL;
+}
+
+static inline gboolean
+is_removed (GstSourceBuffer * self)
+{
+ GstObject *parent = gst_object_get_parent (GST_OBJECT (self));
+ if (parent == NULL) {
+ return TRUE;
+ }
+ gst_object_unref (parent);
+
+ GstMediaSource *source = get_media_source (self);
+ GstSourceBufferList *buffers = gst_media_source_get_source_buffers (source);
+ gboolean removed = !gst_source_buffer_list_contains (buffers, self);
+
+ gst_object_unref (source);
+ gst_object_unref (buffers);
+
+ return removed;
+}
+
+static inline gboolean
+is_updating (GstSourceBuffer * self)
+{
+ return g_atomic_int_get (&self->updating);
+}
+
+static inline void
+set_updating (GstSourceBuffer * self)
+{
+ g_atomic_int_set (&self->updating, TRUE);
+}
+
+static inline void
+clear_updating (GstSourceBuffer * self)
+{
+ g_atomic_int_set (&self->updating, FALSE);
+}
+
+static inline gboolean
+is_errored (GstSourceBuffer * self)
+{
+ return g_atomic_int_get (&self->errored);
+}
+
+static inline void
+set_errored (GstSourceBuffer * self)
+{
+ g_atomic_int_set (&self->errored, TRUE);
+}
+
+static inline void
+clear_errored (GstSourceBuffer * self)
+{
+ g_atomic_int_set (&self->errored, FALSE);
+}
+
+static inline gboolean
+is_ended (GstSourceBuffer * self)
+{
+ if (is_removed (self)) {
+ return TRUE;
+ }
+
+ GstMediaSource *source = get_media_source (self);
+ gboolean ended = gst_media_source_get_ready_state (source) ==
+ GST_MEDIA_SOURCE_READY_STATE_ENDED;
+
+ gst_object_unref (source);
+
+ return ended;
+}
+
+static void
+open_parent (GstSourceBuffer * self)
+{
+ g_return_if_fail (!is_removed (self));
+ GstMediaSource *source = get_media_source (self);
+ gst_media_source_open (source);
+ gst_object_unref (source);
+}
+
+/**
+ * gst_source_buffer_get_append_mode:
+ * @self: #GstSourceBuffer instance
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
+ *
+ * Returns: The current #GstSourceBufferAppendMode
+ * Since: 1.24
+ */
+GstSourceBufferAppendMode
+gst_source_buffer_get_append_mode (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), DEFAULT_APPEND_MODE);
+ return self->append_mode;
+}
+
+/**
+ * gst_source_buffer_set_append_mode:
+ * @self: #GstSourceBuffer instance
+ * @mode: #GstSourceBufferAppendMode the desired Append Mode
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Changes the Append Mode of @self. This influences what timestamps will be
+ * assigned to media processed by this Source Buffer. In Segment mode, the
+ * timestamps in each segment determine the position of each sample after it
+ * is processed. In Sequence mode, the timestamp of each processed sample is
+ * generated based on the end of the most recently processed segment.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_set_append_mode (GstSourceBuffer * self,
+ GstSourceBufferAppendMode mode, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+
+ if (is_removed (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "buffer is removed");
+ return FALSE;
+ }
+
+ if (is_updating (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "buffer is still updating");
+ return FALSE;
+ }
+
+ if (self->generate_timestamps && mode ==
+ GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
+ "cannot change to segments mode while generate timestamps is active");
+ return FALSE;
+ }
+
+ if (is_ended (self)) {
+ open_parent (self);
+ }
+
+ self->append_mode = mode;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APPEND_MODE]);
+ return TRUE;
+}
+
+/**
+ * gst_source_buffer_get_append_window_start:
+ * @self: #GstSourceBuffer instance
+ *
+ * Returns the current append window start time. Any segment processed that ends
+ * earlier than this value will be ignored.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
+ *
+ * Returns: The current Append Window start time as a #GstClockTime
+ * Since: 1.24
+ */
+GstClockTime
+gst_source_buffer_get_append_window_start (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE);
+ return self->append_window_start;
+}
+
+/**
+ * gst_source_buffer_set_append_window_start:
+ * @self: #GstSourceBuffer instance
+ * @start: the append window end
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Modifies the current append window start of @self. If successful, samples
+ * processed after setting this value that end before this point will be
+ * ignored.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_set_append_window_start (GstSourceBuffer * self,
+ GstClockTime start, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+
+ if (is_removed (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "append window start cannot be set on source buffer "
+ "with no media source");
+ return FALSE;
+ }
+
+ if (is_updating (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "append window start cannot be set on source buffer while updating");
+ return FALSE;
+ }
+
+ if (start < 0 || start <= self->append_window_end) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
+ "append window start must be between zero and append window end");
+ return FALSE;
+ }
+
+ self->append_window_start = start;
+ g_object_notify_by_pspec (G_OBJECT (self),
+ properties[PROP_APPEND_WINDOW_START]);
+ return TRUE;
+}
+
+/**
+ * gst_source_buffer_get_append_window_end:
+ * @self: #GstSourceBuffer instance
+ *
+ * Returns the current append window end time. Any segment processed that starts
+ * after this value will be ignored.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
+ *
+ * Returns: The current Append Window end time as a #GstClockTime
+ * Since: 1.24
+ */
+GstClockTime
+gst_source_buffer_get_append_window_end (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE);
+ return self->append_window_end;
+}
+
+/**
+ * gst_source_buffer_set_append_window_end:
+ * @self: #GstSourceBuffer instance
+ * @end: the append window end
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Modifies the current append window end of @self. If successful, samples
+ * processed after setting this value that start after this point will be
+ * ignored.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_set_append_window_end (GstSourceBuffer * self,
+ GstClockTime end, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+
+ if (is_removed (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "append window end cannot be set on source buffer "
+ "with no media source");
+ return FALSE;
+ }
+
+ if (is_updating (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "append window end cannot be set on source buffer while updating");
+ return FALSE;
+ }
+
+ if (end <= self->append_window_start) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
+ "append window end must be after append window start");
+ return FALSE;
+ }
+
+ self->append_window_end = end;
+ g_object_notify_by_pspec (G_OBJECT (self),
+ properties[PROP_APPEND_WINDOW_END]);
+ return TRUE;
+}
+
+static gboolean
+get_intersection (GstMediaSourceRange * a, GstMediaSourceRange * b,
+ GstMediaSourceRange * intersection)
+{
+ g_return_val_if_fail (a != NULL, FALSE);
+ g_return_val_if_fail (b != NULL, FALSE);
+ g_return_val_if_fail (intersection != NULL, FALSE);
+ GstMediaSourceRange range = {
+ .start = MAX (a->start, b->start),
+ .end = MIN (a->end, b->end),
+ };
+ if (range.start >= range.end) {
+ return FALSE;
+ }
+ *intersection = range;
+ return TRUE;
+}
+
+static GArray *
+intersect_ranges (GstMediaSourceRange * a, GstMediaSourceRange * a_end,
+ GstMediaSourceRange * b, GstMediaSourceRange * b_end)
+{
+ GArray *intersection = g_array_new_ranges ();
+ while (a < a_end && b < b_end) {
+ GstMediaSourceRange range;
+ if (!get_intersection (a, b, &range)) {
+ if (a->end < b->end) {
+ a++;
+ } else {
+ b++;
+ }
+ continue;
+ }
+
+ if (a->end < b->end) {
+ a++;
+ } else {
+ b++;
+ }
+
+ g_array_append_val (intersection, range);
+ }
+ return intersection;
+}
+
+static inline gboolean
+contributes_to_buffered (GstMediaSourceTrack * track)
+{
+ switch (gst_media_source_track_get_track_type (track)) {
+ case GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO:
+ case GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * gst_source_buffer_get_buffered:
+ * @self: #GstSourceBuffer instance
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Returns a sequence of #GstMediaSourceRange values representing which segments
+ * of @self are buffered in memory.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered)
+ *
+ * Returns: (transfer full) (element-type GstMediaSourceRange): a #GArray of #GstMediaSourceRange values.
+ * Since: 1.24
+ */
+GArray *
+gst_source_buffer_get_buffered (GstSourceBuffer * self, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL);
+ GHashTableIter iter;
+ GArray *buffered = NULL;
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ for (gpointer key, value; g_hash_table_iter_next (&iter, &key, &value);) {
+ GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key);
+ GstMediaSourceTrackBuffer *buffer = GST_MEDIA_SOURCE_TRACK_BUFFER (value);
+ if (!contributes_to_buffered (track)) {
+ continue;
+ }
+ GArray *current_ranges = gst_media_source_track_buffer_get_ranges (buffer);
+ if (buffered == NULL) {
+ buffered = current_ranges;
+ continue;
+ }
+ GArray *intersection = intersect_ranges (
+ (GstMediaSourceRange *) buffered->data,
+ ((GstMediaSourceRange *) buffered->data) + buffered->len,
+ (GstMediaSourceRange *) current_ranges->data,
+ ((GstMediaSourceRange *) current_ranges->data) + current_ranges->len);
+ g_array_unref (buffered);
+ buffered = intersection;
+ }
+ if (buffered == NULL) {
+ return g_array_new_ranges ();
+ } else {
+ return buffered;
+ }
+}
+
+/**
+ * gst_source_buffer_get_content_type:
+ * @self: #GstSourceBuffer instance
+ *
+ * Returns the current content type of @self.
+ *
+ * Returns: (transfer full): a string representing the content type
+ * Since: 1.24
+ */
+gchar *
+gst_source_buffer_get_content_type (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL);
+
+ GST_OBJECT_LOCK (self);
+ gchar *content_type = g_strdup (self->content_type);
+ GST_OBJECT_UNLOCK (self);
+
+ return content_type;
+}
+
+/**
+ * gst_source_buffer_change_content_type:
+ * @self: #GstSourceBuffer instance
+ * @type: (transfer none): the desired content type
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Attempts to change the content type of @self to @type. Any new data appended
+ * to the Source Buffer must be of the supplied @type afterward.
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_change_content_type (GstSourceBuffer * self,
+ const gchar * type, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+
+ if (type == NULL || g_strcmp0 (type, "") == 0) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
+ "content type must not be empty");
+ return FALSE;
+ }
+
+ if (is_removed (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "content type cannot be set on source buffer with no media source");
+ return FALSE;
+ }
+
+ if (is_updating (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "content type cannot be set on source buffer that is updating");
+ return FALSE;
+ }
+
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED,
+ "content type cannot be changed");
+ return FALSE;
+}
+
+/**
+ * gst_source_buffer_remove:
+ * @self: #GstSourceBuffer instance
+ * @start: The beginning timestamp of data to remove
+ * @end: The end timestamp of data to remove
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Attempts to remove any parsed data between @start and @end from @self.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-remove)
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_remove (GstSourceBuffer * self, GstClockTime start,
+ GstClockTime end, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+ return TRUE;
+}
+
+/**
+ * gst_source_buffer_get_timestamp_offset:
+ * @self: #GstSourceBuffer instance
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
+ *
+ * Returns: The current timestamp offset as a #GstClockTime
+ * Since: 1.24
+ */
+GstClockTime
+gst_source_buffer_get_timestamp_offset (GstSourceBuffer * self)
+{
+ return self->timestamp_offset;
+}
+
+/**
+ * gst_source_buffer_set_timestamp_offset:
+ * @self: #GstSourceBuffer instance
+ * @offset: The new timestamp offset
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Attempt to set the timestamp offset of @self. Any media processed after this
+ * value is set will have this value added to its start time.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_set_timestamp_offset (GstSourceBuffer * self, GstClockTime
+ offset, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+ if (is_removed (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "source buffer is removed");
+ return FALSE;
+ }
+ if (is_updating (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "source buffer is still updating");
+ return FALSE;
+ }
+ if (is_ended (self)) {
+ GstMediaSource *parent = get_media_source (self);
+ gst_media_source_open (parent);
+ gst_clear_object (&parent);
+ }
+ GST_OBJECT_LOCK (self);
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ GstMediaSourceTrackBuffer *buffer = value;
+ gst_media_source_track_buffer_set_group_start (buffer, offset);
+ }
+ self->timestamp_offset = offset;
+ GST_OBJECT_UNLOCK (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP_OFFSET]);
+ return TRUE;
+}
+
+/**
+ * gst_source_buffer_get_updating:
+ * @self: #GstSourceBuffer instance
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating)
+ *
+ * Returns: Whether @self is currently adding or removing media content.
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_get_updating (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+ return is_updating (self);
+}
+
+static gsize
+compute_total_size_unlocked (GstSourceBuffer * self)
+{
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ gsize total_size = 0;
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ GstMediaSourceTrackBuffer *buffer = value;
+ total_size += gst_media_source_track_buffer_get_storage_size (buffer);
+ }
+ return total_size;
+}
+
+static gboolean
+will_overflow (GstSourceBuffer * self, gsize bytes)
+{
+ GST_OBJECT_LOCK (self);
+ gsize total = compute_total_size_unlocked (self);
+ GST_OBJECT_UNLOCK (self);
+ return total + bytes > self->size_limit;
+}
+
+static void
+evict_coded_frames (GstSourceBuffer * self, gsize space_required,
+ gsize size_limit, GstClockTime position, GstClockTime duration)
+{
+ if (!will_overflow (self, space_required)) {
+ return;
+ }
+
+ if (!GST_CLOCK_TIME_IS_VALID (position)) {
+ GST_ERROR ("invalid position, cannot delete anything");
+ return;
+ }
+
+ GstClockTime min_distance_from_position = GST_SECOND * 5;
+ GstClockTime max_dts = position > min_distance_from_position ?
+ position - min_distance_from_position : 0;
+
+ GST_DEBUG_OBJECT (self, "position=%" GST_TIMEP_FORMAT
+ ", attempting removal from 0 to %" GST_TIMEP_FORMAT, &position, &max_dts);
+
+ GST_OBJECT_LOCK (self);
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
+ GstMediaSourceTrackBuffer *buffer = value;
+ gst_media_source_track_buffer_remove_range (buffer, 0, max_dts);
+ }
+ self->size = compute_total_size_unlocked (self);
+ GST_OBJECT_UNLOCK (self);
+
+ GST_DEBUG_OBJECT (self, "capacity=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT
+ "(%" G_GSIZE_FORMAT "%%)", self->size, self->size_limit,
+ self->size * 100 / size_limit);
+}
+
+static void
+reset_parser_state (GstSourceBuffer * self)
+{
+ clear_pending_data (self);
+ if (gst_append_pipeline_reset (self->append_pipeline)) {
+ clear_errored (self);
+ } else {
+ set_errored (self);
+ }
+}
+
+static void
+append_error (GstSourceBuffer * self)
+{
+ gst_task_stop (self->append_to_buffer_task);
+ reset_parser_state (self);
+ clear_updating (self);
+
+ if (is_removed (self)) {
+ return;
+ }
+
+ schedule_event (self, ON_ERROR);
+ schedule_event (self, ON_UPDATE_END);
+
+ GstMediaSource *source = get_media_source (self);
+ gst_media_source_end_of_stream (source, GST_MEDIA_SOURCE_EOS_ERROR_DECODE,
+ NULL);
+ gst_object_unref (source);
+}
+
+static void
+append_successful (GstSourceBuffer * self, gboolean ended)
+{
+ gst_task_stop (self->append_to_buffer_task);
+ clear_updating (self);
+ schedule_event (self, ON_UPDATE);
+ schedule_event (self, ON_UPDATE_END);
+}
+
+static gboolean
+encountered_bad_bytes (GstSourceBuffer * self)
+{
+ return gst_append_pipeline_get_failed (self->append_pipeline);
+}
+
+static void
+append_to_buffer_task (GstSourceBuffer * self)
+{
+ if (is_removed (self)) {
+ append_successful (self, TRUE);
+ return;
+ }
+
+ if (encountered_bad_bytes (self)) {
+ append_error (self);
+ return;
+ }
+
+ GstBuffer *pending_data = take_pending_data (self);
+
+ if (!GST_IS_BUFFER (pending_data)) {
+ GST_LOG_OBJECT (self, "no pending data");
+ append_successful (self, is_ended (self));
+ return;
+ }
+
+ GstFlowReturn result = gst_append_pipeline_append (self->append_pipeline,
+ pending_data);
+
+ if (result != GST_FLOW_OK) {
+ GST_ERROR_OBJECT (self, "failed to append: %s", gst_flow_get_name (result));
+ append_error (self);
+ return;
+ }
+
+ append_successful (self, is_ended (self));
+}
+
+static gboolean
+track_feed_fold (const GValue * item, TrackFeedAccumulator * acc,
+ TrackFeedTask * feed)
+{
+ GstSample *sample = gst_sample_ref (gst_value_get_sample (item));
+ GstClockTime dts = GST_BUFFER_DTS (gst_sample_get_buffer (sample));
+ acc->n_samples++;
+ acc->current_dts = dts;
+ gst_clear_sample (&acc->current_sample);
+ acc->current_sample = gst_sample_ref (sample);
+ if (gst_media_source_track_push (feed->track, sample)) {
+ return TRUE;
+ } else {
+ gst_sample_unref (sample);
+ return FALSE;
+ }
+}
+
+static void
+track_feed_task (TrackFeedTask * feed)
+{
+ GstSourceBuffer *self = feed->parent;
+ GstMediaSourceTrack *track = feed->track;
+ GstMediaSourceTrackBuffer *buffer = feed->buffer;
+ GstClockTime time = feed->parent->seek_time;
+ const gchar *track_id = gst_media_source_track_get_id (track);
+
+ GST_DEBUG_OBJECT (self, "%s: feed starting@%" GST_TIMEP_FORMAT, track_id,
+ &time);
+
+ TrackFeedAccumulator acc = {
+ .n_samples = 0,
+ .current_dts = time,
+ .current_sample = NULL,
+ };
+ while (TRUE) {
+ gboolean eos = gst_media_source_track_buffer_is_eos (buffer);
+ GstIterator *it = gst_media_source_track_buffer_iter_samples (buffer,
+ acc.current_dts, acc.current_sample);
+ while (TRUE) {
+ GstIteratorResult fold_result = gst_iterator_fold (it,
+ (GstIteratorFoldFunction) track_feed_fold, (GValue *) & acc, feed);
+ if (fold_result != GST_ITERATOR_RESYNC) {
+ break;
+ }
+ if (g_atomic_int_get (&feed->cancelled)) {
+ break;
+ }
+ gst_iterator_resync (it);
+ }
+ gst_iterator_free (it);
+
+ if (eos) {
+ GST_DEBUG_OBJECT (self, "%s: enqueued all %" G_GSIZE_FORMAT " samples",
+ track_id, acc.n_samples);
+ gst_media_source_track_push_eos (track);
+ GST_DEBUG_OBJECT (self, "%s: marked EOS", track_id);
+ gst_task_stop (feed->task);
+ break;
+ }
+
+ if (g_atomic_int_get (&feed->cancelled)) {
+ GST_DEBUG_OBJECT (self, "feed is cancelled, stopping task");
+ gst_task_stop (feed->task);
+ break;
+ }
+
+ GST_DEBUG_OBJECT (self, "%s: resume after %" G_GSIZE_FORMAT " samples",
+ track_id, acc.n_samples);
+ gint64 deadline = g_get_monotonic_time () + G_TIME_SPAN_SECOND;
+ gst_media_source_track_buffer_await_eos_until (buffer, deadline);
+ }
+
+ gst_clear_sample (&acc.current_sample);
+}
+
+static void
+dispatch_event (SourceBufferEventItem * item, GstSourceBuffer * self)
+{
+ g_signal_emit (self, signals[item->event], 0);
+}
+
+static void
+schedule_event (GstSourceBuffer * self, SourceBufferEvent event)
+{
+ g_return_if_fail (event < N_SIGNALS);
+ if (is_removed (self)) {
+ return;
+ }
+ SourceBufferEventItem 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 (SourceBufferEventItem)));
+}
+
+static void
+schedule_append_to_buffer_task (GstSourceBuffer * self)
+{
+ GstTask *task = self->append_to_buffer_task;
+ g_return_if_fail (GST_IS_TASK (task));
+ g_return_if_fail (gst_task_get_state (task) != GST_TASK_STARTED);
+ gst_task_start (task);
+}
+
+static void
+update_msesrc_ready_state (GstSourceBuffer * self)
+{
+ GstMseSrc *element = get_msesrc (self);
+ if (element == NULL) {
+ return;
+ }
+ gst_mse_src_update_ready_state (element);
+ gst_object_unref (element);
+}
+
+/**
+ * gst_source_buffer_append_buffer:
+ * @self: #GstSourceBuffer instance
+ * @buf: (transfer full):The media data to append
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Schedules the bytes inside @buf to be processed by @self. When it is possible
+ * to accept the supplied data, it will be processed asynchronously and fill in
+ * the track buffers for playback purposes.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendbuffer)
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_append_buffer (GstSourceBuffer * self, GstBuffer * buf,
+ GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+ g_return_val_if_fail (GST_IS_BUFFER (buf), FALSE);
+
+ if (is_removed (self) || is_updating (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "source buffer is removed or still updating");
+ return FALSE;
+ }
+
+ if (is_errored (self)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "source buffer has encountered error");
+ return FALSE;
+ }
+
+ if (is_ended (self)) {
+ open_parent (self);
+ }
+
+ GstMediaSource *source = get_media_source (self);
+ gsize buffer_size = gst_buffer_get_size (buf);
+ GstClockTime position = gst_media_source_get_position (source);
+ GstClockTime duration = gst_media_source_get_duration (source);
+
+ gst_object_unref (source);
+
+ evict_coded_frames (self, buffer_size, self->size_limit, position, duration);
+
+ if (will_overflow (self, buffer_size)) {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_QUOTA_EXCEEDED,
+ "buffer is full");
+ return FALSE;
+ }
+
+ g_return_val_if_fail (self->pending_data == NULL, FALSE);
+
+ set_pending_data (self, buf);
+ set_updating (self);
+
+ schedule_event (self, ON_UPDATE_START);
+ schedule_append_to_buffer_task (self);
+
+ return TRUE;
+}
+
+/**
+ * gst_source_buffer_abort:
+ * @self: #GstSourceBuffer instance
+ * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
+ *
+ * Attempts to end any processing of the currently pending data and reset the
+ * media parser.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-abort)
+ *
+ * Returns: `TRUE` on success, `FALSE` otherwise
+ * Since: 1.24
+ */
+gboolean
+gst_source_buffer_abort (GstSourceBuffer * self, GError ** error)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+ if (gst_append_pipeline_eos (self->append_pipeline) == GST_FLOW_OK) {
+ return TRUE;
+ } else {
+ g_set_error (error,
+ GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
+ "failed to abort source buffer");
+ return FALSE;
+ }
+}
+
+gboolean
+gst_source_buffer_has_init_segment (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
+ return gst_append_pipeline_has_init_segment (self->append_pipeline);
+}
+
+static gboolean
+is_buffered_fold (const GValue * item, IsBufferedAccumulator * acc,
+ GstSourceBuffer * self)
+{
+ GstSample *sample = gst_value_get_sample (item);
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ GstClockTime buffer_start = GST_BUFFER_DTS (buffer);
+ GstClockTime buffer_end = buffer_start + GST_BUFFER_DURATION (buffer);
+ if (acc->time < buffer_start) {
+ GST_TRACE_OBJECT (self, "position precedes buffer start, done");
+ acc->buffered = FALSE;
+ return FALSE;
+ }
+ if (acc->time >= buffer_start && acc->time < buffer_end) {
+ GST_TRACE_OBJECT (self, "position is within buffer, done");
+ acc->buffered = TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+gst_source_buffer_is_buffered (GstSourceBuffer * self, GstClockTime time)
+{
+ GHashTableIter iter;
+ gboolean buffered = TRUE;
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ for (gpointer key, value;
+ buffered && g_hash_table_iter_next (&iter, &key, &value);) {
+ GstMediaSourceTrack *track = key;
+ if (!gst_media_source_track_get_active (track)) {
+ continue;
+ }
+ GstMediaSourceTrackBuffer *track_buffer = value;
+ IsBufferedAccumulator acc = {
+ .time = time,
+ .buffered = FALSE,
+ };
+ GstIterator *iter =
+ gst_media_source_track_buffer_iter_samples (track_buffer, time, NULL);
+ while (gst_iterator_fold (iter, (GstIteratorFoldFunction) is_buffered_fold,
+ (GValue *) & acc, self) == GST_ITERATOR_RESYNC) {
+ gst_iterator_resync (iter);
+ }
+ gst_iterator_free (iter);
+ buffered = acc.buffered;
+ }
+ return buffered;
+}
+
+static gboolean
+is_range_buffered_fold (const GValue * item, IsRangeBufferedAccumulator * acc,
+ GstSourceBuffer * self)
+{
+ GstSample *sample = gst_value_get_sample (item);
+ GstBuffer *buffer = gst_sample_get_buffer (sample);
+ GstClockTime buffer_start = GST_BUFFER_DTS (buffer);
+ GstClockTime buffer_end = buffer_start + GST_BUFFER_DURATION (buffer);
+
+ GstClockTime start = acc->start;
+ GstClockTime end = acc->end;
+
+ if (!acc->start_buffered) {
+ if (start < buffer_start) {
+ GST_TRACE_OBJECT (self, "start position precedes buffer start, done");
+ return FALSE;
+ }
+ if (start >= buffer_start && start < buffer_end) {
+ GST_TRACE_OBJECT (self, "start position is within buffer, checking end");
+ acc->start_buffered = TRUE;
+ return TRUE;
+ }
+ } else {
+ if (end < buffer_start) {
+ GST_TRACE_OBJECT (self, "end position precedes buffer start, done");
+ return FALSE;
+ }
+ if (end <= buffer_end) {
+ GST_TRACE_OBJECT (self, "end position is within buffer, done");
+ acc->end_buffered = TRUE;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+gst_source_buffer_is_range_buffered (GstSourceBuffer * self, GstClockTime start,
+ GstClockTime end)
+{
+ GHashTableIter iter;
+ gboolean buffered = TRUE;
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ for (gpointer key, value;
+ buffered && g_hash_table_iter_next (&iter, &key, &value);) {
+ GstMediaSourceTrack *track = key;
+ if (!gst_media_source_track_get_active (track)) {
+ continue;
+ }
+ GstMediaSourceTrackBuffer *track_buffer = value;
+ IsRangeBufferedAccumulator acc = {
+ .start = start,
+ .end = end,
+ .start_buffered = FALSE,
+ .end_buffered = FALSE,
+ };
+ GstIterator *iter =
+ gst_media_source_track_buffer_iter_samples (track_buffer, start, NULL);
+ while (gst_iterator_fold (iter,
+ (GstIteratorFoldFunction) is_range_buffered_fold, (GValue *) & acc,
+ self) == GST_ITERATOR_RESYNC) {
+ gst_iterator_resync (iter);
+ }
+ buffered = acc.end_buffered;
+ gst_iterator_free (iter);
+ }
+ return buffered;
+}
+
+GstClockTime
+gst_source_buffer_get_duration (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE);
+ return gst_append_pipeline_get_duration (self->append_pipeline);
+}
+
+void
+gst_source_buffer_teardown (GstSourceBuffer * self)
+{
+ reset_parser_state (self);
+ clear_updating (self);
+}
+
+GPtrArray *
+gst_source_buffer_get_all_tracks (GstSourceBuffer * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL);
+
+ GPtrArray *tracks = g_ptr_array_new ();
+
+ GPtrArray *audio_tracks = gst_append_pipeline_get_audio_tracks
+ (self->append_pipeline);
+ GPtrArray *text_tracks = gst_append_pipeline_get_text_tracks
+ (self->append_pipeline);
+ GPtrArray *video_tracks = gst_append_pipeline_get_video_tracks
+ (self->append_pipeline);
+
+ if (audio_tracks) {
+ g_ptr_array_extend (tracks, audio_tracks, NULL, NULL);
+ }
+ if (text_tracks) {
+ g_ptr_array_extend (tracks, text_tracks, NULL, NULL);
+ }
+ if (video_tracks) {
+ g_ptr_array_extend (tracks, video_tracks, NULL, NULL);
+ }
+
+ return tracks;
+}
+
+static void
+seek_track_buffer (GstMediaSourceTrack * track,
+ GstMediaSourceTrackBuffer * buffer, GstSourceBuffer * self)
+{
+ TrackFeedTask *feed = get_track_feed (self, track);
+
+ const gchar *track_id = gst_media_source_track_get_id (track);
+ GST_DEBUG_OBJECT (self, "%s: seeking", track_id);
+ reset_track_feed (feed);
+ GST_DEBUG_OBJECT (self, "%s: restarted track feed", track_id);
+}
+
+void
+gst_source_buffer_seek (GstSourceBuffer * self, GstClockTime time)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER (self));
+ g_return_if_fail (GST_CLOCK_TIME_IS_VALID (time));
+ self->seek_time = time;
+ g_hash_table_foreach (self->track_buffers, (GHFunc) seek_track_buffer, self);
+}
+
+gboolean
+gst_source_buffer_get_active (GstSourceBuffer * self)
+{
+ gboolean active = FALSE;
+ GHashTableIter iter;
+ GST_OBJECT_LOCK (self);
+ g_hash_table_iter_init (&iter, self->track_buffers);
+ for (gpointer key; !active && g_hash_table_iter_next (&iter, &key, NULL);) {
+ GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key);
+ active |= gst_media_source_track_get_active (track);
+ }
+ GST_OBJECT_UNLOCK (self);
+ return active;
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.h
new file mode 100644
index 0000000000..965094ae4c
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.h
@@ -0,0 +1,127 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2013 Orange
+ * Copyright (C) 2013-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2014 Sebastian Dröge
+ * Copyright (C) 2015, 2016, 2017 Igalia, S.L
+ * Copyright (C) 2015, 2016, 2017 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+G_BEGIN_DECLS
+
+/**
+ * GstSourceBufferAppendMode:
+ * @GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS:
+ * @GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE:
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-appendmode)
+ *
+ * Since: 1.24
+ */
+typedef enum
+{
+ GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS,
+ GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE,
+} GstSourceBufferAppendMode;
+
+/**
+ * GstSourceBufferInterval:
+ *
+ * Since: 1.24
+ */
+typedef struct
+{
+ GstClockTime start;
+ GstClockTime end;
+} GstSourceBufferInterval;
+
+#define GST_TYPE_SOURCE_BUFFER (gst_source_buffer_get_type())
+
+GST_MSE_API
+G_DECLARE_FINAL_TYPE (GstSourceBuffer, gst_source_buffer, GST, SOURCE_BUFFER,
+ GstObject);
+
+GST_MSE_API
+GstSourceBufferAppendMode gst_source_buffer_get_append_mode (
+ GstSourceBuffer * self);
+
+GST_MSE_API
+gboolean gst_source_buffer_set_append_mode (GstSourceBuffer * self,
+ GstSourceBufferAppendMode mode, GError ** error);
+
+GST_MSE_API
+gchar *gst_source_buffer_get_content_type (GstSourceBuffer * self);
+
+GST_MSE_API
+gboolean gst_source_buffer_get_updating (GstSourceBuffer * self);
+
+GST_MSE_API
+GArray * gst_source_buffer_get_buffered (GstSourceBuffer * self,
+ GError ** error);
+
+GST_MSE_API
+gboolean gst_source_buffer_set_timestamp_offset (GstSourceBuffer * self,
+ GstClockTime offset,
+ GError ** error);
+
+GST_MSE_API
+GstClockTime gst_source_buffer_get_timestamp_offset (GstSourceBuffer * self);
+
+GST_MSE_API
+gboolean gst_source_buffer_set_append_window_start (GstSourceBuffer * self,
+ GstClockTime start,
+ GError ** error);
+
+GST_MSE_API
+GstClockTime gst_source_buffer_get_append_window_start (GstSourceBuffer * self);
+
+GST_MSE_API
+gboolean gst_source_buffer_set_append_window_end (GstSourceBuffer * self,
+ GstClockTime end,
+ GError ** error);
+
+GST_MSE_API
+GstClockTime gst_source_buffer_get_append_window_end (GstSourceBuffer * self);
+
+GST_MSE_API
+gboolean gst_source_buffer_append_buffer (GstSourceBuffer * self,
+ GstBuffer * buf, GError ** error);
+
+GST_MSE_API
+gboolean gst_source_buffer_abort (GstSourceBuffer * self, GError ** error);
+
+GST_MSE_API
+gboolean gst_source_buffer_change_content_type (GstSourceBuffer * self,
+ const gchar * type,
+ GError ** error);
+
+GST_MSE_API
+gboolean gst_source_buffer_remove (GstSourceBuffer * self, GstClockTime start,
+ GstClockTime end, GError ** error);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist-private.h
new file mode 100644
index 0000000000..264fb35e6a
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist-private.h
@@ -0,0 +1,64 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include "gstsourcebufferlist.h"
+#include "gstsourcebuffer.h"
+
+G_BEGIN_DECLS
+
+GST_MSE_PRIVATE
+GstSourceBufferList *gst_source_buffer_list_new (void);
+
+GST_MSE_PRIVATE
+gboolean gst_source_buffer_list_contains (GstSourceBufferList * self,
+ GstSourceBuffer * buf);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_list_append (GstSourceBufferList * self,
+ GstSourceBuffer * buf);
+
+GST_MSE_PRIVATE
+gboolean gst_source_buffer_list_remove (GstSourceBufferList * self,
+ GstSourceBuffer * buf);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_list_remove_all (GstSourceBufferList * self);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_list_notify_freeze (GstSourceBufferList * self);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_list_notify_cancel (GstSourceBufferList * self);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_list_notify_added (GstSourceBufferList * self);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_list_notify_removed (GstSourceBufferList * self);
+
+GST_MSE_PRIVATE
+void gst_source_buffer_list_notify_thaw (GstSourceBufferList * self);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.c
new file mode 100644
index 0000000000..db1f9d038d
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.c
@@ -0,0 +1,461 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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:gstsourcebufferlist
+ * @title: GstSourceBufferList
+ * @short_description: Source Buffer List
+ * @include: mse/mse.h
+ * @symbols:
+ * - GstSourceBufferList
+ *
+ * The Source Buffer List is a list of #GstSourceBuffers that can be
+ * indexed numerically and monitored for changes. The list itself cannot be
+ * modified through this interface, though the Source Buffers it holds can be
+ * modified after retrieval.
+ *
+ * It is used by #GstMediaSource to provide direct access to its child
+ * #GstSourceBuffers through #GstMediaSource:source-buffers as well as
+ * informing clients which of the Source Buffers are active through
+ * #GstMediaSource:active-source-buffers.
+ *
+ * Since: 1.24
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstsourcebufferlist.h"
+#include "gstsourcebufferlist-private.h"
+
+#include "gstmseeventqueue-private.h"
+
+typedef struct
+{
+ gboolean frozen;
+ gboolean added;
+ gboolean removed;
+} PendingNotifications;
+
+/**
+ * GstSourceBufferList:
+ *
+ * Since: 1.24
+ */
+struct _GstSourceBufferList
+{
+ GstObject parent_instance;
+
+ GPtrArray *buffers;
+
+ GstMseEventQueue *event_queue;
+
+ PendingNotifications pending_notifications;
+};
+
+G_DEFINE_TYPE (GstSourceBufferList, gst_source_buffer_list, GST_TYPE_OBJECT);
+
+enum
+{
+ PROP_0,
+ PROP_LENGTH,
+ N_PROPS,
+};
+
+typedef enum
+{
+ ON_SOURCEBUFFER_ADDED,
+ ON_SOURCEBUFFER_REMOVED,
+ N_SIGNALS,
+} SourceBufferListEvent;
+
+typedef struct
+{
+ GstDataQueueItem item;
+ SourceBufferListEvent event;
+} SourceBufferListEventItem;
+
+static GParamSpec *properties[N_PROPS];
+static guint signals[N_SIGNALS] = { 0 };
+
+static gboolean
+is_frozen (GstSourceBufferList * self)
+{
+ return g_atomic_int_get (&self->pending_notifications.frozen);
+}
+
+static void
+set_frozen (GstSourceBufferList * self)
+{
+ g_atomic_int_set (&self->pending_notifications.frozen, TRUE);
+}
+
+static void
+clear_frozen (GstSourceBufferList * self)
+{
+ g_atomic_int_set (&self->pending_notifications.frozen, FALSE);
+}
+
+static void
+set_pending_added (GstSourceBufferList * self)
+{
+ g_atomic_int_set (&self->pending_notifications.added, TRUE);
+}
+
+static void
+set_pending_removed (GstSourceBufferList * self)
+{
+ g_atomic_int_set (&self->pending_notifications.removed, TRUE);
+}
+
+static gboolean
+clear_pending_added (GstSourceBufferList * self)
+{
+ return g_atomic_int_and (&self->pending_notifications.added, FALSE);
+}
+
+static gboolean
+clear_pending_removed (GstSourceBufferList * self)
+{
+ return g_atomic_int_and (&self->pending_notifications.removed, FALSE);
+}
+
+static void
+schedule_event (GstSourceBufferList * self, SourceBufferListEvent event)
+{
+ SourceBufferListEventItem 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 (SourceBufferListEventItem)));
+}
+
+static void
+dispatch_event (SourceBufferListEventItem * item, GstSourceBufferList * self)
+{
+ g_signal_emit (self, signals[item->event], 0);
+}
+
+static void
+call_source_buffer_added (GstSourceBufferList * self)
+{
+ if (is_frozen (self)) {
+ set_pending_added (self);
+ } else {
+ clear_pending_added (self);
+ schedule_event (self, ON_SOURCEBUFFER_ADDED);
+ }
+}
+
+static void
+call_source_buffer_removed (GstSourceBufferList * self)
+{
+ if (is_frozen (self)) {
+ set_pending_removed (self);
+ } else {
+ clear_pending_removed (self);
+ schedule_event (self, ON_SOURCEBUFFER_REMOVED);
+ }
+}
+
+GstSourceBufferList *
+gst_source_buffer_list_new (void)
+{
+ return gst_object_ref_sink (g_object_new (GST_TYPE_SOURCE_BUFFER_LIST, NULL));
+}
+
+static void
+gst_source_buffer_list_dispose (GObject * object)
+{
+ GstSourceBufferList *self = (GstSourceBufferList *) object;
+ gst_clear_object (&self->event_queue);
+
+ G_OBJECT_CLASS (gst_source_buffer_list_parent_class)->dispose (object);
+}
+
+static void
+gst_source_buffer_list_finalize (GObject * object)
+{
+ GstSourceBufferList *self = (GstSourceBufferList *) object;
+
+ g_clear_pointer (&self->buffers, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (gst_source_buffer_list_parent_class)->finalize (object);
+}
+
+static void
+gst_source_buffer_list_get_property (GObject * object, guint prop_id, GValue
+ * value, GParamSpec * pspec)
+{
+ GstSourceBufferList *self = GST_SOURCE_BUFFER_LIST (object);
+
+ switch (prop_id) {
+ case PROP_LENGTH:
+ g_value_set_uint (value, gst_source_buffer_list_get_length (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gst_source_buffer_list_class_init (GstSourceBufferListClass * klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = GST_DEBUG_FUNCPTR (gst_source_buffer_list_dispose);
+ oclass->finalize = GST_DEBUG_FUNCPTR (gst_source_buffer_list_finalize);
+ oclass->get_property =
+ GST_DEBUG_FUNCPTR (gst_source_buffer_list_get_property);
+
+ /**
+ * GstSourceBufferList:length:
+ *
+ * The number of #GstSourceBuffers contained by this structure
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length)
+ *
+ * Since: 1.24
+ */
+ properties[PROP_LENGTH] = g_param_spec_ulong ("length",
+ "Length",
+ "The number of SourceBuffers contained by this structure",
+ 0, G_MAXULONG, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, N_PROPS, properties);
+
+ /**
+ * GstSourceBufferList::on-sourcebuffer-added:
+ * @self: The #GstSourceBufferList that has just added a
+ * #GstSourceBuffer
+ *
+ * Emitted when a #GstSourceBuffer has been added to this list.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onaddsourcebuffer)
+ *
+ * Since: 1.24
+ */
+ signals[ON_SOURCEBUFFER_ADDED] = g_signal_new ("on-sourcebuffer-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ /**
+ * GstSourceBufferList::on-sourcebuffer-removed:
+ * @self: The #GstSourceBufferList that has just removed a
+ * #GstSourceBuffer
+ *
+ * Emitted when a #GstSourceBuffer has been removed from this list.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onremovesourcebuffer)
+ *
+ * Since: 1.24
+ */
+ signals[ON_SOURCEBUFFER_REMOVED] = g_signal_new ("on-sourcebuffer-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+static void
+gst_source_buffer_list_init (GstSourceBufferList * self)
+{
+ set_frozen (self);
+ self->buffers = g_ptr_array_new_with_free_func (gst_object_unref);
+ self->event_queue =
+ gst_mse_event_queue_new ((GstMseEventQueueCallback) dispatch_event, self);
+ clear_pending_added (self);
+ clear_pending_removed (self);
+ clear_frozen (self);
+}
+
+/**
+ * gst_source_buffer_list_index:
+ * @self: #GstSourceBufferList instance
+ * @index: index of requested Source Buffer
+ *
+ * Retrieves the #GstSourceBuffer at @index from @self. If @index is greater than
+ * the highest index in the list, it will return `NULL`.
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dfn-sourcebufferlist-getter)
+ *
+ * Returns: (transfer full) (nullable): The requested #GstSourceBuffer or `NULL`
+ * Since: 1.24
+ */
+GstSourceBuffer *
+gst_source_buffer_list_index (GstSourceBufferList * self, guint index)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), NULL);
+ if (index >= gst_source_buffer_list_get_length (self))
+ return NULL;
+ return gst_object_ref (g_ptr_array_index (self->buffers, index));
+}
+
+/**
+ * gst_source_buffer_list_get_length:
+ * @self: #GstSourceBufferList instance
+ *
+ * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length)
+ *
+ * Returns: The number of #GstSourceBuffer objects in the list
+ * Since: 1.24
+ */
+guint
+gst_source_buffer_list_get_length (GstSourceBufferList * self)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), 0);
+ return self->buffers->len;
+}
+
+gboolean
+gst_source_buffer_list_contains (GstSourceBufferList * self,
+ GstSourceBuffer * buf)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), FALSE);
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER (buf), FALSE);
+ return g_ptr_array_find (self->buffers, buf, NULL);
+}
+
+void
+gst_source_buffer_list_append (GstSourceBufferList * self,
+ GstSourceBuffer * buf)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self));
+ g_ptr_array_add (self->buffers, gst_object_ref (buf));
+ call_source_buffer_added (self);
+}
+
+gboolean
+gst_source_buffer_list_remove (GstSourceBufferList * self,
+ GstSourceBuffer * buf)
+{
+ g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), FALSE);
+ gboolean removed = g_ptr_array_remove (self->buffers, buf);
+ if (removed) {
+ call_source_buffer_removed (self);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+void
+gst_source_buffer_list_remove_all (GstSourceBufferList * self)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self));
+ if (self->buffers->len < 1) {
+ return;
+ }
+ g_ptr_array_set_size (self->buffers, 0);
+ call_source_buffer_removed (self);
+}
+
+/**
+ * gst_source_buffer_list_notify_freeze:
+ * @self: #GstSourceBufferList instance
+ *
+ * Prevents any notifications from being emitted by @self until the next call to
+ * gst_source_buffer_list_notify_thaw().
+ *
+ */
+void
+gst_source_buffer_list_notify_freeze (GstSourceBufferList * self)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self));
+ clear_pending_added (self);
+ clear_pending_removed (self);
+ set_frozen (self);
+}
+
+/**
+ * gst_source_buffer_list_notify_cancel:
+ * @self: #GstSourceBufferList instance
+ *
+ * Cancels any pending notifications that are waiting between calls to
+ * gst_source_buffer_list_notify_freeze() and
+ * gst_source_buffer_list_notify_thaw().
+ *
+ */
+void
+gst_source_buffer_list_notify_cancel (GstSourceBufferList * self)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self));
+ clear_pending_added (self);
+ clear_pending_removed (self);
+}
+
+/**
+ * gst_source_buffer_list_notify_added:
+ * @self: #GstSourceBufferList instance
+ *
+ * Explicitly notifies subscribers to the ::on-sourcebuffer-added signal that an
+ * item has been added to @self.
+ *
+ */
+void
+gst_source_buffer_list_notify_added (GstSourceBufferList * self)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self));
+ g_return_if_fail (!is_frozen (self));
+ call_source_buffer_added (self);
+}
+
+/**
+ * gst_source_buffer_list_notify_removed:
+ * @self: #GstSourceBufferList instance
+ *
+ * Explicitly notifies subscribers to the ::on-sourcebuffer-removed signal that
+ * an item has been removed from @self.
+ *
+ */
+void
+gst_source_buffer_list_notify_removed (GstSourceBufferList * self)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self));
+ g_return_if_fail (!is_frozen (self));
+ call_source_buffer_removed (self);
+}
+
+/**
+ * gst_source_buffer_list_notify_thaw:
+ * @self: #GstSourceBufferList instance
+ *
+ * Resumes notifications emitted from @self after a call to
+ * gst_source_buffer_list_notify_freeze(). If any notifications are pending,
+ * they will be emitted as a result of this call. To prevent pending
+ * notifications from being published, use
+ * gst_source_buffer_list_notify_cancel() before calling this method.
+ *
+ */
+void
+gst_source_buffer_list_notify_thaw (GstSourceBufferList * self)
+{
+ g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self));
+ clear_frozen (self);
+ if (clear_pending_added (self)) {
+ g_signal_emit (self, signals[ON_SOURCEBUFFER_ADDED], 0);
+ }
+ if (clear_pending_removed (self)) {
+ g_signal_emit (self, signals[ON_SOURCEBUFFER_REMOVED], 0);
+ }
+}
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.h
new file mode 100644
index 0000000000..d80ee52078
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.h
@@ -0,0 +1,44 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "gstsourcebuffer.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_SOURCE_BUFFER_LIST (gst_source_buffer_list_get_type())
+
+GST_MSE_API
+G_DECLARE_FINAL_TYPE (GstSourceBufferList, gst_source_buffer_list, GST,
+ SOURCE_BUFFER_LIST, GstObject);
+
+GST_MSE_API
+GstSourceBuffer *gst_source_buffer_list_index (GstSourceBufferList * self,
+ guint index);
+GST_MSE_API
+guint gst_source_buffer_list_get_length (GstSourceBufferList * self);
+
+G_END_DECLS
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/meson.build b/subprojects/gst-plugins-bad/gst-libs/gst/mse/meson.build
new file mode 100644
index 0000000000..fba8613ff0
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/meson.build
@@ -0,0 +1,149 @@
+gstmse_headers_private = files(
+ 'gstappendpipeline-private.h',
+ 'gstmediasourcesamplemap-private.h',
+ 'gstmediasourcetrackbuffer-private.h',
+ 'gstmediasourcetrack-private.h',
+ 'gstmseeventqueue-private.h',
+ 'gstmselogging-private.h',
+ 'gstmsemediatype-private.h',
+)
+
+gstmse_sources_private = files(
+ 'gstappendpipeline.c',
+ 'gstmediasourcesamplemap.c',
+ 'gstmediasourcetrackbuffer.c',
+ 'gstmediasourcetrack.c',
+ 'gstmseeventqueue.c',
+ 'gstmselogging.c',
+ 'gstmsemediatype.c',
+)
+
+gstmse_headers_public = files(
+ 'gstmediasource.h',
+ 'gstsourcebuffer.h',
+ 'gstsourcebufferlist.h',
+ 'gstmsesrc.h',
+ 'mse.h',
+ 'mse-prelude.h',
+)
+
+gstmse_sources_public = files(
+ 'gstmediasource.c',
+ 'gstmsesrc.c',
+ 'gstsourcebuffer.c',
+ 'gstsourcebufferlist.c',
+)
+
+gstmse_header_dir = 'gstreamer-' + api_version + '/gst/mse/'
+
+gstmse_enums_private = gnome.mkenums_simple('mse-enumtypes-private',
+ sources : gstmse_headers_private,
+ body_prefix : '#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif',
+ header_prefix : '#include ',
+ decorator: 'GST_MSE_PRIVATE',
+ install_header: false,
+)
+
+gstmse_enums = gnome.mkenums_simple('mse-enumtypes',
+ sources : gstmse_headers_public,
+ body_prefix : '#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif',
+ header_prefix : '#include ',
+ decorator: 'GST_MSE_API',
+ install_header: true,
+ install_dir : join_paths(get_option('includedir'), gstmse_header_dir),
+)
+
+gstmse_deps = [gstbase_dep, gstapp_dep]
+
+gstmse_enums_private_c = gstmse_enums_private[0]
+gstmse_enums_private_h = gstmse_enums_private[1]
+
+gstmse_enums_c = gstmse_enums[0]
+gstmse_enums_h = gstmse_enums[1]
+
+gstmse_c_args = gst_plugins_bad_args + [
+ '-DBUILDING_GST_MSE',
+ '-DGST_USE_UNSTABLE_API',
+ '-DG_LOG_DOMAIN="GStreamer-MSE"',
+]
+
+gstmse_sources_all = [
+ gstmse_sources_private,
+ gstmse_sources_public,
+ gstmse_enums_c,
+ gstmse_enums_h,
+ gstmse_enums_private_c,
+ gstmse_enums_private_h,
+]
+
+gstmse_private_test = library('gstmse-private-test',
+ gstmse_sources_all,
+ c_args : [gstmse_c_args, '-DBUILDING_GST_MSE_TEST'],
+ include_directories : [configinc, libsinc],
+ dependencies : gstmse_deps,
+)
+
+gstmse_private_test_dep = declare_dependency(
+ compile_args : [gstmse_c_args, '-DBUILDING_GST_MSE_TEST'],
+ link_with : [gstmse_private_test],
+ include_directories : [libsinc],
+)
+
+gstmse = library('gstmse-' + api_version,
+ gstmse_sources_all,
+ c_args : gstmse_c_args,
+ include_directories : [configinc, libsinc],
+ version : libversion,
+ soversion : soversion,
+ darwin_versions : osxversion,
+ install : true,
+ dependencies : gstmse_deps,
+)
+
+pkg_name = 'gstreamer-mse-' + api_version
+gst_libraries += [[pkg_name, {'lib': gstmse}]]
+pkgconfig.generate(gstmse,
+ libraries : [gst_dep],
+ variables : pkgconfig_variables,
+ subdirs : pkgconfig_subdirs,
+ name : pkg_name,
+ description : 'GStreamer Support for W3C Media Source Extensions',
+)
+
+library_def = {'lib': gstmse}
+gen_sources = []
+if build_gir
+ gir = {
+ 'sources' : [
+ gstmse_sources_public,
+ gstmse_headers_public,
+ gstmse_enums_h,
+ gstmse_enums_c,
+ ],
+ 'namespace' : 'GstMse',
+ 'nsversion' : api_version,
+ 'identifier_prefix' : 'Gst',
+ 'symbol_prefix' : 'gst',
+ 'export_packages' : pkg_name,
+ 'includes' : ['Gst-1.0'],
+ 'install' : true,
+ 'extra_args' : gir_init_section + ['-DGST_USE_UNSTABLE_API'] + ['--c-include=gst/mse/mse.h'],
+ 'dependencies' : gstmse_deps,
+ }
+ library_def += {'gir': [gir]}
+ if not static_build
+ mse_gir = gnome.generate_gir(gstmse, kwargs: gir)
+ library_def += {'gir_targets': library_def.get('gir_targets', []) + [mse_gir]}
+ gen_sources += mse_gir
+ endif
+endif
+gst_libraries += [[pkg_name, library_def]]
+
+gstmse_dep = declare_dependency(
+ link_with : [gstmse],
+ include_directories : [libsinc],
+ dependencies : gstmse_deps,
+ sources: [gstmse_enums_h],
+)
+install_headers(gstmse_headers_public, subdir: gstmse_header_dir)
+meson.override_dependency(pkg_name, gstmse_dep)
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse-prelude.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse-prelude.h
new file mode 100644
index 0000000000..eb807e1874
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse-prelude.h
@@ -0,0 +1,48 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+
+#ifndef GST_MSE_API
+# if defined(BUILDING_GST_MSE) && defined(GST_API_EXPORT)
+# define GST_MSE_API GST_API_EXPORT
+# elif defined(GST_API_IMPORT)
+# define GST_MSE_API GST_API_IMPORT
+# else
+# define GST_MSE_API
+# endif
+#endif
+
+#ifndef GST_MSE_PRIVATE
+# if defined(BUILDING_GST_MSE_TEST)
+# define GST_MSE_PRIVATE GST_MSE_API
+# else
+# define GST_MSE_PRIVATE G_GNUC_INTERNAL
+# endif
+#endif
+
+#ifndef GST_USE_UNSTABLE_API
+#warning "The MSE library from gst-plugins-bad is an unstable API and may change in future."
+#warning "You can define GST_USE_UNSTABLE_API to avoid this warning."
+#endif
diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse.h
new file mode 100644
index 0000000000..343213e17a
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse.h
@@ -0,0 +1,32 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+
+#include "mse-prelude.h"
+#include
+#include "gstmsesrc.h"
+#include "gstmediasource.h"
+#include "gstsourcebufferlist.h"
+#include "gstsourcebuffer.h"
diff --git a/subprojects/gst-plugins-bad/gst/meson.build b/subprojects/gst-plugins-bad/gst/meson.build
index 81f8f62092..4232a34aef 100644
--- a/subprojects/gst-plugins-bad/gst/meson.build
+++ b/subprojects/gst-plugins-bad/gst/meson.build
@@ -8,7 +8,7 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux',
'geometrictransform', 'id3tag', 'insertbin', 'inter', 'interlace',
'ivfparse', 'ivtc', 'jp2kdecimator', 'jpegformat', 'librfb',
'midi', 'mpegdemux', 'mpegpsmux', 'mpegtsdemux', 'mpegtsmux',
- 'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy',
+ 'mse', 'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy',
'rawparse', 'removesilence', 'rist', 'rtmp2', 'rtp', 'sdp',
'segmentclip', 'siren', 'smooth', 'speed', 'subenc', 'switchbin',
'timecode', 'transcode', 'unixfd', 'videofilters',
diff --git a/subprojects/gst-plugins-bad/gst/mse/gstmse.c b/subprojects/gst-plugins-bad/gst/mse/gstmse.c
new file mode 100644
index 0000000000..7e8938f42c
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst/mse/gstmse.c
@@ -0,0 +1,49 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+/**
+ * plugin-mse:
+ *
+ * Since: 1.24
+ */
+
+#include "gstmse.h"
+
+/**
+ * SECTION:element-msesrc
+ * @title: msesrc
+ *
+ * Since: 1.24
+ */
+GST_ELEMENT_REGISTER_DEFINE (msesrc, "msesrc", GST_RANK_NONE, GST_TYPE_MSE_SRC);
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ return GST_ELEMENT_REGISTER (msesrc, plugin);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ mse,
+ "W3C Media Source Extensions Support",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/subprojects/gst-plugins-bad/gst/mse/gstmse.h b/subprojects/gst-plugins-bad/gst/mse/gstmse.h
new file mode 100644
index 0000000000..0500b65963
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst/mse/gstmse.h
@@ -0,0 +1,33 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#pragma once
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+#include
+
+GST_ELEMENT_REGISTER_DECLARE (msesrc);
diff --git a/subprojects/gst-plugins-bad/gst/mse/meson.build b/subprojects/gst-plugins-bad/gst/mse/meson.build
new file mode 100644
index 0000000000..e4ac5e72d7
--- /dev/null
+++ b/subprojects/gst-plugins-bad/gst/mse/meson.build
@@ -0,0 +1,12 @@
+mse_sources = ['gstmse.c']
+
+gstmse_plugin = library('gstmse',
+ mse_sources,
+ c_args: gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
+ include_directories: [configinc],
+ dependencies : [gstbase_dep, gstmse_dep],
+ install : true,
+ install_dir : plugins_install_dir,
+)
+
+plugins += [gstmse_plugin]
diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt
index 8fe3b8fc7f..d4f087791c 100644
--- a/subprojects/gst-plugins-bad/meson_options.txt
+++ b/subprojects/gst-plugins-bad/meson_options.txt
@@ -45,6 +45,7 @@ option('mpegdemux', type : 'feature', value : 'auto')
option('mpegpsmux', type : 'feature', value : 'auto')
option('mpegtsdemux', type : 'feature', value : 'auto')
option('mpegtsmux', type : 'feature', value : 'auto')
+option('mse', type : 'feature', value : 'auto')
option('mxf', type : 'feature', value : 'auto')
option('netsim', type : 'feature', value : 'auto')
option('onvif', type : 'feature', value : 'auto')
diff --git a/subprojects/gst-plugins-bad/tests/check/libs/mse.c b/subprojects/gst-plugins-bad/tests/check/libs/mse.c
new file mode 100644
index 0000000000..f3ae4bed16
--- /dev/null
+++ b/subprojects/gst-plugins-bad/tests/check/libs/mse.c
@@ -0,0 +1,1119 @@
+/* GStreamer
+ *
+ * SPDX-License-Identifier: LGPL-2.1
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_VALGRIND
+# include
+#endif
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+static GstCheckLogFilter *
+add_log_filter (GLogLevelFlags level, const gchar * regex)
+{
+ GRegex *gregex = g_regex_new (regex, 0, 0, NULL);
+ return gst_check_add_log_filter ("GStreamer-MSE", level, gregex, NULL, NULL,
+ NULL);
+}
+
+static gchar *
+test_mp4_path (void)
+{
+ return g_build_filename (GST_TEST_FILES_PATH, "mse.mp4", NULL);
+}
+
+static gchar *
+test_webm_path (void)
+{
+ return g_build_filename (GST_TEST_FILES_PATH, "mse.webm", NULL);
+}
+
+static GstMediaSource *
+opened_media_source (void)
+{
+ GstMediaSource *media_source = gst_media_source_new ();
+ media_source->ready_state = GST_MEDIA_SOURCE_READY_STATE_OPEN;
+ return media_source;
+}
+
+static GstSample *
+new_empty_sample_full (GstClockTime dts, GstClockTime pts,
+ GstClockTime duration, GstBufferFlags flags, GstCaps * caps,
+ GstSegment * segment, GstStructure * info)
+{
+ GstBuffer *buffer = gst_buffer_new ();
+ GST_BUFFER_DTS (buffer) = dts;
+ GST_BUFFER_PTS (buffer) = pts;
+ GST_BUFFER_DURATION (buffer) = duration;
+ GST_BUFFER_FLAGS (buffer) = flags;
+ GstSample *sample = gst_sample_new (buffer, caps, segment, info);
+ gst_buffer_unref (buffer);
+ return sample;
+}
+
+static GstSample *
+new_empty_sample_with_timing (GstClockTime dts, GstClockTime pts,
+ GstClockTime duration)
+{
+ return new_empty_sample_full (dts, pts, duration, 0, NULL, NULL, NULL);
+}
+
+static GstSample *
+new_sample_with_bytes_and_timing (GBytes * bytes, GstClockTime dts,
+ GstClockTime pts, GstClockTime duration)
+{
+ GstBuffer *buffer = gst_buffer_new_wrapped_bytes (bytes);
+ GST_BUFFER_DTS (buffer) = dts;
+ GST_BUFFER_PTS (buffer) = pts;
+ GST_BUFFER_DURATION (buffer) = duration;
+ GstSample *sample = gst_sample_new (buffer, NULL, NULL, NULL);
+ gst_buffer_unref (buffer);
+ g_bytes_unref (bytes);
+ return sample;
+}
+
+GST_START_TEST (test_create_and_free)
+{
+ GstMediaSource *media_source = gst_media_source_new ();
+ fail_unless (GST_IS_MEDIA_SOURCE (media_source));
+ gst_check_object_destroyed_on_unref (media_source);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_create_initial_state)
+{
+ GstMediaSource *media_source = gst_media_source_new ();
+
+ GstSourceBufferList *buffers =
+ gst_media_source_get_source_buffers (media_source);
+ GstSourceBufferList *active_buffers =
+ gst_media_source_get_active_source_buffers (media_source);
+
+ fail_unless (gst_media_source_get_ready_state (media_source) ==
+ GST_MEDIA_SOURCE_READY_STATE_CLOSED);
+ fail_unless (gst_source_buffer_list_get_length (buffers) == 0);
+ fail_unless (gst_source_buffer_list_get_length (active_buffers) == 0);
+ fail_unless (gst_media_source_get_position (media_source) ==
+ GST_CLOCK_TIME_NONE);
+
+ gst_object_unref (media_source);
+ gst_object_unref (buffers);
+ gst_object_unref (active_buffers);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_add_source_buffer_with_content_type_null)
+{
+ add_log_filter (G_LOG_LEVEL_CRITICAL,
+ "^.*_add_source_buffer: assertion 'type != NULL' failed");
+
+ GstMediaSource *media_source = gst_media_source_new ();
+
+ g_assert_null (gst_media_source_add_source_buffer (media_source, NULL, NULL));
+
+ gst_object_unref (media_source);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_add_source_buffer_with_content_type_empty)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = gst_media_source_new ();
+ GstSourceBuffer *source_buffer =
+ gst_media_source_add_source_buffer (media_source, "", &error);
+
+ g_assert_null (source_buffer);
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_add_source_buffer_with_content_type_fake)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = gst_media_source_new ();
+ GstSourceBuffer *source_buffer =
+ gst_media_source_add_source_buffer (media_source, "fake/type", &error);
+
+ g_assert_null (source_buffer);
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR,
+ GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_add_source_buffer_to_unopened_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = gst_media_source_new ();
+ GstSourceBuffer *source_buffer =
+ gst_media_source_add_source_buffer (media_source, "video/webm", &error);
+
+ g_assert_null (source_buffer);
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR,
+ GST_MEDIA_SOURCE_ERROR_INVALID_STATE);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_add_source_buffer_to_opened_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = opened_media_source ();
+ GstSourceBufferList *buffers =
+ gst_media_source_get_source_buffers (media_source);
+ guint n_buffers_before = gst_source_buffer_list_get_length (buffers);
+ GstSourceBuffer *source_buffer =
+ gst_media_source_add_source_buffer (media_source, "video/webm", &error);
+ guint n_buffers_after = gst_source_buffer_list_get_length (buffers);
+
+ fail_unless (GST_IS_SOURCE_BUFFER (source_buffer));
+ g_assert_no_error (error);
+ fail_unless (n_buffers_before < n_buffers_after);
+
+ g_object_unref (media_source);
+ g_object_unref (buffers);
+ g_object_unref (source_buffer);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_remove_source_buffer_from_unrelated_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *a = opened_media_source ();
+ GstMediaSource *b = opened_media_source ();
+ GstSourceBuffer *buffer_in_b =
+ gst_media_source_add_source_buffer (b, "video/webm", &error);
+
+ gst_media_source_remove_source_buffer (a, buffer_in_b, &error);
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR,
+ GST_MEDIA_SOURCE_ERROR_NOT_FOUND);
+
+ gst_object_unref (a);
+ gst_object_unref (b);
+ gst_object_unref (buffer_in_b);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_remove_source_buffer_from_parent_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = opened_media_source ();
+ GstSourceBufferList *buffers =
+ gst_media_source_get_source_buffers (media_source);
+ GstSourceBuffer *buffer =
+ gst_media_source_add_source_buffer (media_source, "video/webm", &error);
+
+ guint n_buffers_before = gst_source_buffer_list_get_length (buffers);
+ gst_media_source_remove_source_buffer (media_source, buffer, &error);
+ guint n_buffers_after = gst_source_buffer_list_get_length (buffers);
+
+ g_assert_no_error (error);
+ fail_unless (n_buffers_before > n_buffers_after);
+
+ gst_object_unref (media_source);
+ gst_object_unref (buffers);
+ gst_object_unref (buffer);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_set_live_seekable_range_on_unopened_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = gst_media_source_new ();
+
+ gst_media_source_set_live_seekable_range (media_source, 0, 1, &error);
+
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR,
+ GST_MEDIA_SOURCE_ERROR_INVALID_STATE);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_set_backwards_live_seekable_range_on_opened_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = opened_media_source ();
+
+ gst_media_source_set_live_seekable_range (media_source, 2, 1, &error);
+
+ GstMediaSourceRange range = {
+ .start = GST_CLOCK_TIME_NONE,
+ .end = GST_CLOCK_TIME_NONE,
+ };
+ gst_media_source_get_live_seekable_range (media_source, &range);
+
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE);
+ fail_unless (range.start == 0);
+ fail_unless (range.end == 0);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_set_live_seekable_range_on_opened_media_source)
+{
+ GError *error = NULL;
+ GstClockTime start = 1, end = 2;
+ GstMediaSource *media_source = opened_media_source ();
+
+ gst_media_source_set_live_seekable_range (media_source, start, end, &error);
+
+ GstMediaSourceRange range = {
+ .start = GST_CLOCK_TIME_NONE,
+ .end = GST_CLOCK_TIME_NONE,
+ };
+ gst_media_source_get_live_seekable_range (media_source, &range);
+
+ g_assert_no_error (error);
+ fail_unless (range.start == start);
+ fail_unless (range.end == end);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_clear_live_seekable_range_on_unopened_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = gst_media_source_new ();
+
+ gst_media_source_clear_live_seekable_range (media_source, &error);
+
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR,
+ GST_MEDIA_SOURCE_ERROR_INVALID_STATE);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_clear_live_seekable_range_on_opened_media_source)
+{
+ GError *error = NULL;
+ GstMediaSource *media_source = opened_media_source ();
+ gst_media_source_set_live_seekable_range (media_source, 1, 2, NULL);
+
+ gst_media_source_clear_live_seekable_range (media_source, &error);
+ GstMediaSourceRange range = {
+ .start = GST_CLOCK_TIME_NONE,
+ .end = GST_CLOCK_TIME_NONE,
+ };
+ gst_media_source_get_live_seekable_range (media_source, &range);
+
+ g_assert_no_error (error);
+ fail_unless (range.start == 0);
+ fail_unless (range.end == 0);
+
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_append_pipeline_create_and_free)
+{
+ GError *error = NULL;
+ GstAppendPipeline *pipeline = gst_append_pipeline_new (NULL, NULL, &error);
+ g_assert_no_error (error);
+ fail_unless (GST_IS_APPEND_PIPELINE (pipeline));
+ gst_check_object_destroyed_on_unref (pipeline);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+typedef struct
+{
+ GMutex mutex;
+ GCond eos_cond;
+ GCond error_cond;
+} AppendPipelineTestContext;
+
+static void
+test_append_pipeline_eos (GstAppendPipeline * pipeline,
+ GstMediaSourceTrack * track, gpointer user_data)
+{
+ AppendPipelineTestContext *context = user_data;
+ g_mutex_lock (&context->mutex);
+ g_cond_signal (&context->eos_cond);
+ g_mutex_unlock (&context->mutex);
+ GST_DEBUG_OBJECT (pipeline, "signalled eos");
+}
+
+static void
+test_append_pipeline_error (GstAppendPipeline * pipeline, gpointer user_data)
+{
+ AppendPipelineTestContext *context = user_data;
+ g_mutex_lock (&context->mutex);
+ g_cond_signal (&context->error_cond);
+ g_mutex_unlock (&context->mutex);
+ GST_DEBUG_OBJECT (pipeline, "signalled error");
+}
+
+static void
+test_append_pipeline_await_eos (GstAppendPipeline * pipeline,
+ AppendPipelineTestContext * context)
+{
+ GST_DEBUG_OBJECT (pipeline, "waiting for eos");
+ g_mutex_lock (&context->mutex);
+ while (!gst_append_pipeline_get_eos (pipeline)) {
+ g_cond_wait (&context->eos_cond, &context->mutex);
+ }
+ g_mutex_unlock (&context->mutex);
+ GST_DEBUG_OBJECT (pipeline, "received eos");
+}
+
+static void
+test_append_pipeline_await_error (GstAppendPipeline * pipeline,
+ AppendPipelineTestContext * context)
+{
+ GST_DEBUG_OBJECT (pipeline, "waiting for error");
+ g_mutex_lock (&context->mutex);
+ while (!gst_append_pipeline_get_failed (pipeline)) {
+ g_cond_wait (&context->error_cond, &context->mutex);
+ }
+ g_mutex_unlock (&context->mutex);
+ GST_DEBUG_OBJECT (pipeline, "received error");
+}
+
+static void
+test_append_pipeline (const gchar * filename)
+{
+ AppendPipelineTestContext context = { 0 };
+ GstAppendPipelineCallbacks callbacks = {
+ .eos = test_append_pipeline_eos,
+ };
+ GstAppendPipeline *pipeline =
+ gst_append_pipeline_new (&callbacks, &context, NULL);
+ GError *error = NULL;
+
+ gchar *data;
+ gsize length;
+
+ g_file_get_contents (filename, &data, &length, &error);
+ g_assert_no_error (error);
+
+ fail_unless (gst_append_pipeline_append (pipeline,
+ gst_buffer_new_wrapped (data, length)) == GST_FLOW_OK);
+
+ gst_append_pipeline_eos (pipeline);
+
+ test_append_pipeline_await_eos (pipeline, &context);
+
+ fail_if (gst_append_pipeline_get_failed (pipeline));
+
+ gst_object_unref (pipeline);
+ g_clear_error (&error);
+}
+
+GST_START_TEST (test_append_pipeline_mp4)
+{
+ gchar *filename = test_mp4_path ();
+ test_append_pipeline (filename);
+ g_free (filename);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_append_pipeline_webm)
+{
+ gchar *filename = test_webm_path ();
+ test_append_pipeline (filename);
+ g_free (filename);
+}
+
+GST_END_TEST;
+
+static GstAppendPipeline *
+failed_append_pipeline (GstAppendPipelineCallbacks * callbacks,
+ AppendPipelineTestContext * context)
+{
+ GstAppendPipeline *pipeline =
+ gst_append_pipeline_new (callbacks, context, NULL);
+
+ gst_append_pipeline_fail (pipeline);
+
+ return pipeline;
+}
+
+GST_START_TEST (test_append_pipeline_invalid_data_triggers_error)
+{
+ AppendPipelineTestContext context = { 0 };
+ GstAppendPipelineCallbacks callbacks = {
+ .eos = test_append_pipeline_eos,
+ .error = test_append_pipeline_error,
+ };
+ GstAppendPipeline *pipeline = failed_append_pipeline (&callbacks, &context);
+
+ test_append_pipeline_await_error (pipeline, &context);
+
+ gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_append_pipeline_invalid_data_triggers_eos)
+{
+ AppendPipelineTestContext context = { 0 };
+ GstAppendPipelineCallbacks callbacks = {
+ .eos = test_append_pipeline_eos,
+ .error = test_append_pipeline_error,
+ };
+ GstAppendPipeline *pipeline = failed_append_pipeline (&callbacks, &context);
+
+ test_append_pipeline_await_eos (pipeline, &context);
+
+ gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_append_pipeline_reset_recovery)
+{
+ AppendPipelineTestContext context = { 0 };
+ GstAppendPipelineCallbacks callbacks = {
+ .eos = test_append_pipeline_eos,
+ .error = test_append_pipeline_error,
+ };
+ GstAppendPipeline *pipeline = failed_append_pipeline (&callbacks, &context);
+
+ test_append_pipeline_await_error (pipeline, &context);
+ fail_unless (gst_append_pipeline_get_failed (pipeline));
+
+ fail_unless (gst_append_pipeline_reset (pipeline));
+ fail_if (gst_append_pipeline_get_failed (pipeline));
+
+ gchar *data;
+ gsize length;
+ {
+ GError *error = NULL;
+ gchar *filename = test_webm_path ();
+ g_file_get_contents (filename, &data, &length, &error);
+ g_assert_no_error (error);
+ g_clear_error (&error);
+ g_free (filename);
+ }
+
+ fail_unless (gst_append_pipeline_append (pipeline,
+ gst_buffer_new_wrapped (data, length)) == GST_FLOW_OK);
+
+ gst_append_pipeline_eos (pipeline);
+
+ test_append_pipeline_await_eos (pipeline, &context);
+
+ fail_if (gst_append_pipeline_get_failed (pipeline));
+
+ gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_create_and_free)
+{
+ GstMediaSourceTrack *track =
+ gst_media_source_track_new (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, "");
+ fail_unless (GST_IS_MEDIA_SOURCE_TRACK (track));
+ gst_check_object_destroyed_on_unref (track);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_create_with_invalid_type)
+{
+ add_log_filter (G_LOG_LEVEL_CRITICAL,
+ "^.*track_new_full: assertion .*type .* failed");
+
+ g_assert_null (gst_media_source_track_new (-1, ""));
+ g_assert_null (gst_media_source_track_new (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER +
+ 1, ""));
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_push_with_adequate_space)
+{
+ GstMediaSourceTrack *track =
+ gst_media_source_track_new_with_size (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER,
+ "", 1);
+ GstBuffer *buffer = gst_buffer_new ();
+ GstSample *sample = gst_sample_new (buffer, NULL, NULL, NULL);
+ gboolean result = gst_media_source_track_push (track, sample);
+ fail_unless (result);
+ gst_buffer_unref (buffer);
+ gst_object_unref (track);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_push_with_inadequate_space)
+{
+ GstMediaSourceTrack *track =
+ gst_media_source_track_new_with_size (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER,
+ "", 0);
+ GstBuffer *buffer = gst_buffer_new ();
+ GstSample *sample = gst_sample_new (buffer, NULL, NULL, NULL);
+ gboolean result = gst_media_source_track_try_push (track, sample);
+ fail_if (result);
+ gst_sample_unref (sample);
+ gst_buffer_unref (buffer);
+ gst_object_unref (track);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_buffer_empty)
+{
+ GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new ();
+
+ GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer);
+ fail_unless_equals_uint64 (ranges->len, 0);
+
+ gst_object_unref (buffer);
+ g_array_unref (ranges);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_buffer_single_span)
+{
+ GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new ();
+
+ GstSample *sample = new_empty_sample_with_timing (0, 0, 1);
+ gst_media_source_track_buffer_add (buffer, sample);
+
+ GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer);
+ fail_unless_equals_uint64 (ranges->len, 1);
+
+ GstMediaSourceRange range = g_array_index (ranges, GstMediaSourceRange, 0);
+ fail_unless_equals_uint64 (range.start, 0);
+ fail_unless_equals_uint64 (range.end, 1);
+
+ gst_sample_unref (sample);
+ gst_object_unref (buffer);
+ g_array_unref (ranges);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_buffer_continuous_span)
+{
+ GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new ();
+
+ GstClockTime a_start = 0;
+ GstClockTime a_duration = GST_SECOND;
+ GstClockTime b_start = a_start + a_duration;
+ GstClockTime b_duration = a_duration;
+ GstSample *a = new_empty_sample_with_timing (a_start, a_start, a_duration);
+ GstSample *b = new_empty_sample_with_timing (b_start, b_start, b_duration);
+ gst_media_source_track_buffer_add (buffer, a);
+ gst_media_source_track_buffer_add (buffer, b);
+
+ GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer);
+ fail_unless_equals_uint64 (ranges->len, 1);
+
+ GstMediaSourceRange range = g_array_index (ranges, GstMediaSourceRange, 0);
+ fail_unless_equals_uint64 (range.start, a_start);
+ fail_unless_equals_uint64 (range.end, a_start + a_duration + b_duration);
+
+ gst_sample_unref (a);
+ gst_sample_unref (b);
+ gst_object_unref (buffer);
+ g_array_unref (ranges);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_track_buffer_discontinuous_span)
+{
+ GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new ();
+
+ GstClockTime a_start = 0;
+ GstClockTime a_duration = GST_SECOND;
+ GstClockTime b_start = a_start + a_duration + GST_SECOND;
+ GstClockTime b_duration = a_duration;
+ GstSample *a = new_empty_sample_with_timing (a_start, a_start, a_duration);
+ GstSample *b = new_empty_sample_with_timing (b_start, b_start, b_duration);
+ gst_media_source_track_buffer_add (buffer, a);
+ gst_media_source_track_buffer_add (buffer, b);
+
+ GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer);
+ fail_unless_equals_uint64 (ranges->len, 2);
+
+ GstMediaSourceRange range_a = g_array_index (ranges, GstMediaSourceRange, 0);
+ fail_unless_equals_uint64 (range_a.start, a_start);
+ fail_unless_equals_uint64 (range_a.end, a_start + a_duration);
+
+ GstMediaSourceRange range_b = g_array_index (ranges, GstMediaSourceRange, 1);
+ fail_unless_equals_uint64 (range_b.start, b_start);
+ fail_unless_equals_uint64 (range_b.end, b_start + b_duration);
+
+ gst_sample_unref (a);
+ gst_sample_unref (b);
+ gst_object_unref (buffer);
+ g_array_unref (ranges);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_source_buffer_generate_timestamps_mp4)
+{
+ GstMediaSource *media_source = opened_media_source ();
+ GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer
+ (media_source, "video/mp4", NULL);
+
+ fail_unless_equals_uint64 (gst_source_buffer_get_append_mode (source_buffer),
+ GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS);
+
+ gst_object_unref (source_buffer);
+ gst_object_unref (media_source);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_source_buffer_generate_timestamps_aac)
+{
+ GstMediaSource *media_source = opened_media_source ();
+ GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer
+ (media_source, "audio/aac", NULL);
+
+ fail_unless (GST_IS_SOURCE_BUFFER (source_buffer));
+
+ fail_unless_equals_uint64 (gst_source_buffer_get_append_mode (source_buffer),
+ GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE);
+
+ gst_object_unref (source_buffer);
+ gst_object_unref (media_source);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_source_buffer_change_content_type_null)
+{
+ GstMediaSource *media_source = opened_media_source ();
+ GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer
+ (media_source, "video/mp4", NULL);
+
+ fail_unless (GST_IS_SOURCE_BUFFER (source_buffer));
+
+ GError *error = NULL;
+ gst_source_buffer_change_content_type (source_buffer, NULL, &error);
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE);
+
+ g_clear_error (&error);
+ gst_object_unref (source_buffer);
+ gst_object_unref (media_source);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_source_buffer_change_content_type_empty)
+{
+ GstMediaSource *media_source = opened_media_source ();
+ GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer
+ (media_source, "video/mp4", NULL);
+
+ fail_unless (GST_IS_SOURCE_BUFFER (source_buffer));
+
+ GError *error = NULL;
+ gst_source_buffer_change_content_type (source_buffer, "", &error);
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE);
+
+ g_clear_error (&error);
+ gst_object_unref (source_buffer);
+ gst_object_unref (media_source);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_source_buffer_change_content_type)
+{
+ GstMediaSource *media_source = opened_media_source ();
+ GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer
+ (media_source, "video/mp4", NULL);
+
+ fail_unless (GST_IS_SOURCE_BUFFER (source_buffer));
+
+ GError *error = NULL;
+ gst_source_buffer_change_content_type (source_buffer, "video/webm", &error);
+ g_assert_error (error, GST_MEDIA_SOURCE_ERROR,
+ GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED);
+
+ gst_object_unref (source_buffer);
+ gst_object_unref (media_source);
+ g_clear_error (&error);
+}
+
+GST_END_TEST;
+
+static const gchar *unsupported_content_types[] = {
+ "xxx",
+ "text/html",
+ "image/jpeg",
+};
+
+GST_START_TEST (test_media_source_unsupported_content_type)
+{
+ const gchar *content_type = unsupported_content_types[__i__];
+ ck_assert_msg (!gst_media_source_is_type_supported (content_type),
+ "%s should be rejected as an unsupported MIME type", content_type);
+}
+
+GST_END_TEST;
+
+static const gchar *valid_mp4_content_types[] = {
+ "video/mp4;codecs=\"avc1.4d001e\"", // H.264 Main Profile level 3.0
+ "video/mp4;codecs=\"avc1.42001e\"", // H.264 Baseline Profile level 3.0
+ "audio/mp4;codecs=\"mp4a.40.2\"", // MPEG4 AAC-LC
+ "audio/mp4;codecs=\"mp4a.40.5\"", // MPEG4 HE-AAC
+ "audio/mp4;codecs=\"mp4a.67\"", // MPEG2 AAC-LC
+ "video/mp4;codecs=\"mp4a.40.2\"",
+ "video/mp4;codecs=\"avc1.4d001e,mp4a.40.2\"",
+ "video/mp4;codecs=\"mp4a.40.2 , avc1.4d001e \"",
+ "video/mp4;codecs=\"avc1.4d001e,mp4a.40.5\"",
+ "audio/mp4;codecs=\"Opus\"",
+ "video/mp4;codecs=\"Opus\"",
+ "audio/mp4;codecs=\"fLaC\"",
+ "video/mp4;codecs=\"fLaC\"",
+};
+
+GST_START_TEST (test_media_source_supported_mp4_content_type)
+{
+ const gchar *content_type = valid_mp4_content_types[__i__];
+ ck_assert_msg (gst_media_source_is_type_supported (content_type),
+ "%s must be a supported MP4 content type", content_type);
+}
+
+GST_END_TEST;
+
+static const gchar *valid_webm_content_types[] = {
+ "video/webm;codecs=\"vp8\"",
+ "video/webm;codecs=\"vorbis\"",
+ "video/webm;codecs=\"vp8,vorbis\"",
+ "video/webm;codecs=\"vorbis, vp8\"",
+ "audio/webm;codecs=\"vorbis\"",
+ "AUDIO/WEBM;CODECS=\"vorbis\"",
+ "audio/webm;codecs=vorbis;test=\"6\"",
+ "audio/webm;codecs=\"opus\"",
+ "video/webm;codecs=\"opus\"",
+};
+
+GST_START_TEST (test_media_source_supported_webm_content_type)
+{
+ const gchar *content_type = valid_webm_content_types[__i__];
+ ck_assert_msg (gst_media_source_is_type_supported (content_type),
+ "%s must be a supported WebM content type", content_type);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_sample_map_create_and_destroy)
+{
+ GstMediaSourceSampleMap *map = gst_media_source_sample_map_new ();
+ gst_check_object_destroyed_on_unref (map);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_sample_map_add_valid_sample)
+{
+ GstMediaSourceSampleMap *map = gst_media_source_sample_map_new ();
+
+ GstSample *sample = new_empty_sample_with_timing (0, 0, 0);
+
+ fail_if (gst_media_source_sample_map_contains (map, sample));
+
+ gst_media_source_sample_map_add (map, sample);
+
+ fail_unless (gst_media_source_sample_map_contains (map, sample));
+
+ gst_object_unref (map);
+ gst_sample_unref (sample);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_sample_map_add_invalid_sample)
+{
+ add_log_filter (G_LOG_LEVEL_CRITICAL,
+ "^.*_sample_map_add: assertion .* failed");
+
+ GstMediaSourceSampleMap *map = gst_media_source_sample_map_new ();
+
+ GstSample *sample = new_empty_sample_with_timing (GST_CLOCK_TIME_NONE,
+ GST_CLOCK_STIME_NONE, GST_CLOCK_TIME_NONE);
+
+ gst_media_source_sample_map_add (map, sample);
+
+ fail_if (gst_media_source_sample_map_contains (map, sample));
+
+ gst_object_unref (map);
+ gst_sample_unref (sample);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_sample_map_remove_sample)
+{
+ GstMediaSourceSampleMap *map = gst_media_source_sample_map_new ();
+
+ GstSample *sample = new_empty_sample_with_timing (0, 0, 0);
+ gst_media_source_sample_map_add (map, sample);
+
+ gst_media_source_sample_map_remove (map, sample);
+
+ fail_if (gst_media_source_sample_map_contains (map, sample));
+
+ gst_object_unref (map);
+ gst_sample_unref (sample);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_sample_map_remove_range_from_start)
+{
+ GstMediaSourceSampleMap *map = gst_media_source_sample_map_new ();
+
+ GstSample *samples_to_remove[100] = { NULL };
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) {
+ GstClockTime time = i;
+ GstSample *sample = new_empty_sample_with_timing (time, time, 1);
+ gst_media_source_sample_map_add (map, sample);
+ samples_to_remove[i] = sample;
+ }
+ GstSample *samples_to_preserve[100] = { NULL };
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) {
+ GstClockTime time = i + G_N_ELEMENTS (samples_to_remove);
+ GstSample *sample = new_empty_sample_with_timing (time, time, 0);
+ gst_media_source_sample_map_add (map, sample);
+ samples_to_preserve[i] = sample;
+ }
+
+ gst_media_source_sample_map_remove_range_from_start (map, 100);
+
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) {
+ GstSample *sample = samples_to_remove[i];
+ fail_if (gst_media_source_sample_map_contains (map, sample));
+ gst_sample_unref (sample);
+ }
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) {
+ GstSample *sample = samples_to_preserve[i];
+ fail_unless (gst_media_source_sample_map_contains (map, sample));
+ gst_sample_unref (sample);
+ }
+
+ gst_object_unref (map);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_sample_map_remove_range_from_start_byte_count)
+{
+ GstMediaSourceSampleMap *map = gst_media_source_sample_map_new ();
+
+ GstSample *samples_to_remove[100] = { NULL };
+ const guint8 chunk[1000] = { 0 };
+ gsize total_bytes_to_remove = 0;
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) {
+ GstClockTime time = i;
+ gsize buffer_size = g_random_int_range (0, G_N_ELEMENTS (chunk));
+ GBytes *bytes = g_bytes_new_static (chunk, buffer_size);
+ total_bytes_to_remove += buffer_size;
+ GstSample *sample = new_sample_with_bytes_and_timing (bytes, time, time, 1);
+ gst_media_source_sample_map_add (map, sample);
+ samples_to_remove[i] = sample;
+ }
+ GstSample *samples_to_preserve[100] = { NULL };
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) {
+ GstClockTime time = i + G_N_ELEMENTS (samples_to_remove);
+ GBytes *bytes = g_bytes_new_static (chunk, 1);
+ GstSample *sample = new_sample_with_bytes_and_timing (bytes, time, time, 0);
+ gst_media_source_sample_map_add (map, sample);
+ samples_to_preserve[i] = sample;
+ }
+
+ gsize bytes_removed =
+ gst_media_source_sample_map_remove_range_from_start (map,
+ G_N_ELEMENTS (samples_to_remove));
+ fail_unless_equals_uint64 (bytes_removed, total_bytes_to_remove);
+
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) {
+ gst_sample_unref (samples_to_remove[i]);
+ }
+ for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) {
+ gst_sample_unref (samples_to_preserve[i]);
+ }
+
+ gst_object_unref (map);
+}
+
+GST_END_TEST;
+
+#define DEFAULT_TCASE_TIMEOUT 15
+
+#ifdef HAVE_VALGRIND
+#define TCASE_TIMEOUT (RUNNING_ON_VALGRIND ? (5 * 60) : DEFAULT_TCASE_TIMEOUT)
+#else
+#define TCASE_TIMEOUT DEFAULT_TCASE_TIMEOUT
+#endif
+
+static inline TCase *
+new_tcase (const gchar * name)
+{
+ TCase *tcase = tcase_create (name);
+ tcase_set_timeout (tcase, TCASE_TIMEOUT);
+ return tcase;
+}
+
+static Suite *
+mse_suite (void)
+{
+ Suite *s = suite_create ("GstMse");
+
+ TCase *tc_media_source = new_tcase ("GstMediaSource");
+ TCase *tc_source_buffer = new_tcase ("GstSourceBuffer");
+ TCase *tc_source_buffer_list = new_tcase ("GstSourceBufferList");
+ TCase *tc_append_pipeline = new_tcase ("GstAppendPipeline");
+ TCase *tc_track = new_tcase ("GstMediaSourceTrack");
+ TCase *tc_track_buffer = new_tcase ("GstMediaSourceTrackBuffer");
+ TCase *tc_sample_map = new_tcase ("GstMediaSourceSampleMap");
+
+ tcase_add_test (tc_media_source, test_create_and_free);
+ tcase_add_test (tc_media_source, test_create_initial_state);
+ tcase_add_test (tc_media_source,
+ test_add_source_buffer_with_content_type_null);
+ tcase_add_test (tc_media_source,
+ test_add_source_buffer_with_content_type_empty);
+ tcase_add_test (tc_media_source,
+ test_add_source_buffer_with_content_type_fake);
+ tcase_add_test (tc_media_source,
+ test_add_source_buffer_to_unopened_media_source);
+ tcase_add_test (tc_media_source,
+ test_add_source_buffer_to_opened_media_source);
+ tcase_add_test (tc_media_source,
+ test_remove_source_buffer_from_unrelated_media_source);
+ tcase_add_test (tc_media_source,
+ test_remove_source_buffer_from_parent_media_source);
+ tcase_add_test (tc_media_source,
+ test_set_live_seekable_range_on_unopened_media_source);
+ tcase_add_test (tc_media_source,
+ test_set_backwards_live_seekable_range_on_opened_media_source);
+ tcase_add_test (tc_media_source,
+ test_set_live_seekable_range_on_opened_media_source);
+ tcase_add_test (tc_media_source,
+ test_clear_live_seekable_range_on_unopened_media_source);
+ tcase_add_test (tc_media_source,
+ test_clear_live_seekable_range_on_opened_media_source);
+ tcase_add_loop_test (tc_media_source,
+ test_media_source_unsupported_content_type,
+ 0, G_N_ELEMENTS (unsupported_content_types));
+ tcase_add_loop_test (tc_media_source,
+ test_media_source_supported_mp4_content_type,
+ 0, G_N_ELEMENTS (valid_mp4_content_types));
+ tcase_add_loop_test (tc_media_source,
+ test_media_source_supported_webm_content_type,
+ 0, G_N_ELEMENTS (valid_webm_content_types));
+
+ tcase_add_test (tc_source_buffer, test_source_buffer_generate_timestamps_mp4);
+ tcase_add_test (tc_source_buffer, test_source_buffer_generate_timestamps_aac);
+
+ tcase_add_test (tc_source_buffer,
+ test_source_buffer_change_content_type_null);
+ tcase_add_test (tc_source_buffer,
+ test_source_buffer_change_content_type_empty);
+ tcase_add_test (tc_source_buffer, test_source_buffer_change_content_type);
+
+ tcase_add_test (tc_append_pipeline, test_append_pipeline_create_and_free);
+ tcase_add_test (tc_append_pipeline, test_append_pipeline_mp4);
+ tcase_add_test (tc_append_pipeline, test_append_pipeline_webm);
+ tcase_add_test (tc_append_pipeline,
+ test_append_pipeline_invalid_data_triggers_eos);
+ tcase_add_test (tc_append_pipeline,
+ test_append_pipeline_invalid_data_triggers_error);
+ tcase_add_test (tc_append_pipeline, test_append_pipeline_reset_recovery);
+
+ tcase_add_test (tc_track, test_track_create_and_free);
+ tcase_add_test (tc_track, test_track_create_with_invalid_type);
+ tcase_add_test (tc_track, test_track_push_with_adequate_space);
+ tcase_add_test (tc_track, test_track_push_with_inadequate_space);
+
+ tcase_add_test (tc_track_buffer, test_track_buffer_empty);
+ tcase_add_test (tc_track_buffer, test_track_buffer_single_span);
+ tcase_add_test (tc_track_buffer, test_track_buffer_continuous_span);
+ tcase_add_test (tc_track_buffer, test_track_buffer_discontinuous_span);
+
+ tcase_add_test (tc_sample_map, test_sample_map_create_and_destroy);
+ tcase_add_test (tc_sample_map, test_sample_map_add_valid_sample);
+ tcase_add_test (tc_sample_map, test_sample_map_add_invalid_sample);
+ tcase_add_test (tc_sample_map, test_sample_map_remove_sample);
+ tcase_add_test (tc_sample_map, test_sample_map_remove_range_from_start);
+ tcase_add_test (tc_sample_map,
+ test_sample_map_remove_range_from_start_byte_count);
+
+ suite_add_tcase (s, tc_media_source);
+ suite_add_tcase (s, tc_source_buffer);
+ suite_add_tcase (s, tc_source_buffer_list);
+ suite_add_tcase (s, tc_append_pipeline);
+ suite_add_tcase (s, tc_track);
+ suite_add_tcase (s, tc_track_buffer);
+ suite_add_tcase (s, tc_sample_map);
+
+ return s;
+}
+
+GST_CHECK_MAIN (mse)
diff --git a/subprojects/gst-plugins-bad/tests/check/meson.build b/subprojects/gst-plugins-bad/tests/check/meson.build
index 173722f44f..dcdd098d3c 100644
--- a/subprojects/gst-plugins-bad/tests/check/meson.build
+++ b/subprojects/gst-plugins-bad/tests/check/meson.build
@@ -85,6 +85,7 @@ base_tests = [
[['libs/nalutils.c', '../../gst-libs/gst/codecparsers/nalutils.c'], false, [nalutils_dep]],
[['libs/mpegts.c'], false, [gstmpegts_dep]],
[['libs/mpegvideoparser.c'], false, [gstcodecparsers_dep]],
+ [['libs/mse.c'], false, [gstmse_private_test_dep]],
[['libs/planaraudioadapter.c'], false, [gstbadaudio_dep]],
[['libs/play.c'], not enable_gst_play_tests, [gstplay_dep, libsoup_dep]],
[['libs/vc1parser.c'], false, [gstcodecparsers_dep]],
diff --git a/subprojects/gst-plugins-bad/tests/files/mse.mp4 b/subprojects/gst-plugins-bad/tests/files/mse.mp4
new file mode 100644
index 0000000000..41315ca3c7
Binary files /dev/null and b/subprojects/gst-plugins-bad/tests/files/mse.mp4 differ
diff --git a/subprojects/gst-plugins-bad/tests/files/mse.webm b/subprojects/gst-plugins-bad/tests/files/mse.webm
new file mode 100644
index 0000000000..11c4a2db34
Binary files /dev/null and b/subprojects/gst-plugins-bad/tests/files/mse.webm differ
diff --git a/subprojects/gstreamer/libs/gst/check/gstcheck.c b/subprojects/gstreamer/libs/gst/check/gstcheck.c
index 3ff00be8b1..accf41abae 100644
--- a/subprojects/gstreamer/libs/gst/check/gstcheck.c
+++ b/subprojects/gstreamer/libs/gst/check/gstcheck.c
@@ -353,6 +353,7 @@ static const gchar *log_domains[] = {
"GStreamer-GL",
"GStreamer-InsertBin",
"GStreamer-ISOFF",
+ "GStreamer-MSE",
"GStreamer-MpegTS",
"GStreamer-Net",
"GStreamer-OpenCV",