rx-player
Version:
Canal+ HTML5 Video Player
338 lines (243 loc) • 10.4 kB
Markdown
# StreamOchestrator
| Consideration | Status |
| ----------------------- | --------------------------------- |
| Preferred import style | Directory-only _[1]_ |
| Multithread environment | Should be runnable in a WebWorker |
_[1]_ Only the `orchestrator` 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
To be able to play a content, the player has to be able to download chunks of media data -
called segments - and has to push them to media buffers, called `SegmentSinks` in the
RxPlayer code.
In the RxPlayer, the _StreamOrchestrator_ is the entry point for performing all those
tasks.
Basically, the _StreamOrchestrator_:
- dynamically creates various `SegmentSinks` depending on the needs of the given content
- orchestrates segment downloading and "pushing" to allow the content to play in the best
conditions possible.
## Multiple types handling
More often than not, content are divised into multiple "types": "audio", "video" and
"text" segments, for example. They are often completely distinct in a Manifest and as
such, have to be downloaded and decoded separately.
Each type has its own media buffer. For "audio"/"video" contents, we use regular
browser-defined _MSE_ SourceBuffers. For any other types, such as "text", those buffers
are entirely defined in the code of the RxPlayer.
We then create a different Stream for each type. Each will progressively download and push
content of their respective type to their respective media buffer:
```
- AUDIO STREAM -
|====================== |
- VIDEO STREAM -
|================== |
- TEXT STREAM -
|======================== |
```
_(the `|` sign delimits the temporal start and end of the buffer linked to a given Stream,
the `=` sign represent a pushed segment in the corresponding buffer)_
## A special case: SourceBuffers
Media buffers created for the audio and/or video types rely on a _native_ (implemented by
the browser) `SourceBuffer` Object implementation.
Media buffers relying on native SourceBuffers have several differences with the other
"custom" (entirely defined in the RxPlayer) types of media buffers:
- They are managed by the browser where custom ones are implemented in JS. As such, they
must obey to various browser rules, among which:
1. They cannot be lazily created as the content plays. We have to initialize all of them
beforehand.
2. They have to be linked to a specific codec.
3. The `SourceBuffer` has to be linked to the MediaSource, and they have to the
MediaSource after the media HTMLElement has been linked to the MediaSource
- They are in a way more "important" than custom ones. If a problem happens with a
SourceBuffer, we interrupt playback. For a custom media buffer, we can just deactivate
it for the rest of the content.
For example, a problem with subtitles would just disable those with a warning. For a
video problem however, we fail immediately.
- They affect buffering when custom media buffers do not (no text segment for a part of
the content means we will just not have subtitles, no audio segment means we will
completely stop, to await them)
- They affect various API linked to the media element in the DOM. Such as
`HTMLMediaElement.prototype.buffered`. Custom media buffers do not.
Due to these differences, media buffers relying on SourceBuffers are often managed in a
less permissive way than custom ones:
- They will be created at the very start of the content
- An error coming from one of them will lead us to completely stop the content on a fatal
error
## PeriodStreams
The _DASH_ streaming technology has a concept called _Period_. Simply put, it allows to
set various types of content successively in the same manifest.
For example, let's take a manifest describing a live content with chronologically:
1. an english TV Show
2. an old italian film with subtitles
3. an American film with closed captions.
Example:
```
08h05 09h00 10h30 now
|==================|===========================|===================|
TV Show Italian Film American film
```
Those contents are drastically different (they have different languages, the american film
might have more available bitrates than the old italian one).
Moreover, even a library user might want to be able to know when the italian film is
finished, to report about it immediately in a graphical interface.
As such, they have to be considered separately - in a different Period:
```
Period 1 Period 2 Period 3
08h05 09h00 10h30 now
|==================|===========================|===================|
TV Show Italian Film American film
```
In the RxPlayer, we create one _PeriodStream_ per Period **and** per type.
_PeriodStreams_ are automatically created/destroyed during playback. The job of a single
_PeriodStream_ is to process and download optimally the content linked to a single
_Period_ and to a single type:
```
- VIDEO BUFFER -
PERIOD STREAM
08h05 09h00
|========= |
TV Show
- AUDIO BUFFER -
PERIOD STREAM
08h05 09h00
|============= |
TV Show
- TEXT BUFFER -
PERIOD STREAM
08h05 09h00
|====== |
TV Show
```
To allow smooth transitions between them, we also might want to preload content defined by
a subsequent _Period_ once we lean towards the end of the content described by the
previous one. Thus, multiple _PeriodStreams_ might be active at the same time:
```
+---------------------------- AUDIO ----------------------------------+
| |
| PERIOD STREAM 1 PERIOD STREAM 2 PERIOD STREAM 3 |
| 08h05 09h00 10h30 now |
| |============= |================= |================ | |
| TV Show Italian Film American film |
+-------------------------------------------------------------------------+
+------------------------------ VIDEO --------------------------------+
| |
| PERIOD STREAM 1 PERIOD STREAM 2 PERIOD STREAM 3 |
| 08h05 09h00 10h30 now |
| |===== |=== |=== | |
| TV Show Italian Film American film |
+-------------------------------------------------------------------------+
+------------------------------ TEXT ---------------------------------+
| |
| PERIOD STREAM 1 PERIOD STREAM 2 PERIOD STREAM 3 |
| 08h05 09h00 10h30 now |
| (NO SUBTITLES) |========================= |================= | |
| TV Show Italian Film American film |
+-------------------------------------------------------------------------+
```
### Multi-Period management
The creation/destruction of _PeriodStreams_ is actually done in a very precize and optimal
way, which gives a higher priority to immediate content.
To better grasp how it works, let's imagine a regular use-case, with two periods for a
single type of buffer:
---
Let's say that the _PeriodStream_ for the first _Period_ (named P1) is currently actively
downloading segments (the "^" sign is the current position):
```
P1
|==== |
^
```
Once P1 is full (it has no segment left to download):
```
P1
|======|
^
```
We will be able to create a new _PeriodStream_, P2, for the second _Period_:
```
P1 P2
|======| |
^
```
Which will then also download segments:
```
P1 P2
|======|== |
^
```
If P1 needs segments again however (e.g. when the bitrate or the language is changed):
```
P1 P2
|=== |== |
^
```
Then we will destroy P2, to keep it from downloading segments:
```
P1
|=== |
^
```
---
Once P1, goes full again, we re-create P2:
```
P1 P2
|======|== |
^
```
_Note that we still have the segment pushed to P2 available in the corresponding media
buffer_
When the current position go ahead of a _PeriodStream_ (here ahead of P1):
```
P1 P2
|======|=== |
^
```
This _PeriodStream_ is destroyed to free up ressources:
```
P2
|=== |
^
```
---
When the current position goes behind the first currently defined _PeriodStream_:
```
P2
|=== |
^
```
Then we destroy all previous _PeriodStreams_ and [re-]create the one needed:
```
P1
|======|
^
```
In this example, P1 is full (as we already downloaded its segments) so we also can
re-create P2, which will also keep its already-pushed segments:
```
P1 P2
|======|=== |
^
```
---
For multiple types of buffers (example: _audio_ and _video_) the same logic is repeated
(and separated) as many times. An _audio_ _PeriodStream_ will not influence a _video_ one:
```
--------------------------- AUDIO --------------------------------
Period 1 Period 2
|================|============= |
^
--------------------------- VIDEO --------------------------------
Period 1
|======= |
^
---------------------------- TEXT --------------------------------
Period 1 Period 2 Period 3
|================| (NO SUBTITLES) |================ |
^
```
At the end, we should only have _PeriodStream[s]_ for consecutive Period[s]:
- The first chronological one is the one currently seen by the user.
- The last chronological one is the only one downloading content.
- In between, we only have full consecutive _PeriodStreams_.
### Communication with the API
Any "Stream" communicates to the API about creations and destructions of _PeriodStreams_
respectively through `"periodStreamReady"` and `"periodStreamCleared"` callbacks.