rx-player
Version:
Canal+ HTML5 Video Player
204 lines (143 loc) • 9.24 kB
Markdown
# The SegmentSinks
| Consideration | Status |
| ----------------------- | --------------------------------- |
| Preferred import style | Directory-only _[1]_ |
| Multithread environment | Should be runnable in a WebWorker |
_[1]_ Only the `segment_sinks` directory itself should be imported and relied on by the
rest of the code, not its inner files (thus `./index.ts` should export everything that may
be imported by outside code).
## Overview
The `core/segment_sinks` directory contains the part of the code directly related to the
insertion and removal of media segments to a buffer for later decoding.
This is either done through native `SourceBuffers`, which are JavaScript objects
implemented by the browser or a custom buffer implementation entirely defined in the code
of the RxPlayer.
The interface through which the RxPlayer's code push segment is called the `SegmentSink`,
it comes on top of the buffer implementation (regardless if it comes from native
`SourceBuffer` objects or if it is a custom one).
A `SegmentSink` is defined for a type of media (e.g. "video", "audio", "text"...) they are
defined in the `src/core/segment_sinks/implementations` directory.
Here's a simplified architecture schema of the code in that directory:
```
+--------------------------------------------------------------------------+
| Rest of the RxPlayer's code |
+--------------------------------------------------------------------------+
| ^
Ask to get / create / remove | | Returns created SegmentSink or
SegmentSink for a given type | | wanted information
or get information about the | |
available types | |
| |
V |
+--------------------------------------------------------------------------+
| SegmentSinksStore |
+--------------------------------------------------------------------------+
| | |
| creates | creates | creates
| (if needed) | (if needed) | (if needed)
| | |
V V V
+-------------+ +-------------+ +-------------+
| AudioVideo | | AudioVideo | | |
|SegmentSink| |SegmentSink| | Text |
| (video) | | (audio) | |SegmentSink|
| | | | | |
+-------------+ +-------------+ +-------------+
Uses both Uses both Uses
| | | | |
| | | | |
V V V V V
+-----+ +-----+ +-----+ +-----+ +-----+
| SI* | | SB* | | SI* | | SB* | | SI* |
+-----+ +-----+ +-----+ +-----+ +-----+
SI*: SegmentInventory. Store the metadata on every segments currently
available in the associated media buffer.
SB*: SourceBuffer (browser implementation of a media buffer).
```
## SegmentSinksStore
The `SegmentSinksStore` is the main export from there. It facilitates the creation and
destruction of these `SegmentSinks`.
Its roles are to:
- announce which types of `SegmentSink` can be currently created on the HTMLMediaElement
(example of a type of buffer would be "audio", "video" or "text").
For example, no "video" `SegmentSink` should be created on an `<audio>` element (though
it wouldn't cause any problem, it would be useless as video cannot be rendered here). To
give another example, you should not create a "text" `SegmentSink` if no text track
parser has been added to the RxPlayer.
- Create only one `SegmentSink` instance per type of buffer.
Multiple `SegmentSink` for a single type could lead to browser issues and to conflicts
in the RxPlayer code.
- Provide a synchronization mechanism to announce when all `SourceBuffers` are ready to
receive segments.
I'll explain:
`SourceBuffers` are browser implementations for media data buffers. They typically are
used by the "video" and "audio" `SegmentSink`.
Among several other constraints, all `SourceBuffers` needed to play a given content
should be created before we can start pushing segments to any of them. This is a browser
limitation.
This is where this synchronization mechanism can become useful. The `SegmentSinksStore`
will signal when all of the `SourceBuffers` needed for the given contents are created,
so that the rest of the RxPlayer knows when it can begin to push segments to those.
Note that this means that `SourceBuffers` for an un-needed type (e.g. an audio content
won't need a video `SourceBuffer`) have to be explicitely "disabled" here, as the
`SegmentSinksStore` cannot know whether it should wait until those `SourceBuffers` are
created of if you just don't need it.
## SegmentSinks implementations
A `SegmentSink` is an Object maintaining a media buffer for a given type (e.g. "audio",
"video", "text" etc.) used for later decoding.
There exists several `SegmentSink` implementations in the RxPlayer's code depending on the
type concerned. An implementation takes the form of a class with a well-defined API shared
with every other implementations. It allows to push segments, remove data and retrieve
information about the data that is contained within it.
At its core, it can either rely on a browser-defined `SourceBuffer` Object or can be
entirely defined in the code of the RxPlayer.
A `SegmentSink` also keeps an inventory containing the metadata of all segments currently
contained in it, with the help of a `SegmentInventory` Object (see corresponding chapter).
It is the main interface the rest of the RxPlayer code has with media buffers.
## BufferGarbageCollector
The `BufferGarbageCollector` is a function used by the RxPlayer to periodically perform
"garbage collection" manually on a given `SegmentSink`.
It is based on the following building bricks:
- A playback observer emitting the current time (in seconds) when the garbage collection
task should be performed
- The `SegmentSink` on which the garbage collection task should run
- The maximum time margin authorized for the buffer behind the current position
- The maximum time margin authorized for the buffer ahead of the current position
Basically, each times the given playback observer emits, the BufferGarbageCollector will
ensure that the volume of data before and ahead of the current position does not grow into
a larger value than what is configured.
For now, its code is completely decoupled for the rest of the code in that directory. This
is why it is not included in the schema included on the top of this page.
## The SegmentInventory
The SegmentInventory is a class which registers some information about every segments
currently present in a `SegmentSink`.
One of them is created for every new `SegmentSink`.
This helps the RxPlayer to avoid re-downloading segments unnecessarily and know when old
one have been garbage collected. For example, we could decide not to re-download a segment
in any of the following cases:
- The same segment is already completely present in the `SegmentSink`
- The same segment is partially present in the `SegmentSink` (read: a part has been
removed or garbage collected), but enough is still there for what we want to play
- Another segment is in the `SegmentSink` at the wanted time, but it is the same content
in a better or samey quality
On the contrary, we need to download the segment in the following cases:
- No segment has been pushed at the given time
- A segment has been pushed, but at a lower quality than what we currently want
- A segment has been pushed at a sufficient quality, but we miss to much of it for our
needs (too much has been garbage collected, removed or the segment is just too short).
- A segment has been pushed but is not exactly the content we want (example: it is in
another language)
Thanks to the SegmentInventory, we can infer on which situation we are at any time.
### Implementation
The SegmentInventory is merely a "Store", meaning it will just store and process the data
you give to it, without searching for the information itself.
It contains in its state an array, the _inventory_, which stores every segments which
should be present in the `SegmentSink` in a chronological order.
To construct this inventory, three methods can be used:
- one to add information about a new chunk (part of a segment or the whole segment), which
should have been pushed to the `SegmentSink`.
- one to indicate that every chunks from a given segment have been pushed.
- one to synchronize the currently pushed segments with what the `SegmentSink` says it has
buffered (which can be different for example after an automatic garbage collection).
After calling the synchronization one, you should be able to tell which parts of which
segments are currently _living_ in your `SegmentSink`.