rx-player
Version:
Canal+ HTML5 Video Player
252 lines (186 loc) • 10.3 kB
Markdown
# Transport code
## Overview
The `transports` code in the `transports/` directory is the code translating the streaming
protocols available into a unified API.
Its roles are to:
- download the Manifest and parse it into an object that can be understood by the core of
the rx-player
- download segments, convert them into a decodable format if needed, and report important
information about them (like the duration of a segment)
- give networking metrics to allow the core to better adapt to poor networking conditions
As such, most network request needed by the player are directly performed by the
`transports` code.
Note: the only HTTP request which might be done elsewhere would be the request for a
`directfile` content. That request is not done explicely with a JavaScript API but
implicitely by the browser (inclusion of an `src` attribute).
## Implementation
This code is completely divided by streaming protocols used. E.g. `DASH` streaming is
entirely defined in its own directory and the same thing is true for `Smooth Streaming` or
`MetaPlaylist` contents.
When playing a `DASH` content only the DASH-related code will be called. When switching to
a `Smooth Streaming` content, only the `Smooth Streaming` code will be used instead.
To allow this logic, any streaming protocol exposed in `transports` exposes the same
interface and abstracts the difference to the rest of the code. For the core of the
rx-player, we do not have any difference between playing any of the streaming protocols
available.
This also means that the code relative to a specific streaming technology is within the
`transports` directory. This allows to greatly simplify code maintenance and evolutivity.
For example, managing a new streaming protocol would mainly just need us to add some code
there (you might also need to add parsers in the `parsers` directory). Same thing for
adding a new feature to e.g. `DASH` or `Smooth`.
Each streaming protocol implementation present in the `transports` code exports a single
`transport` function.
The object returned by that function is often referenced as the `transport pipelines`.
## Transport pipeline
Each streaming protocol defines a function that takes some options in arguments and
returns an object. This object is often referenced as the `transport pipelines` of the
streaming protocol.
This object then contains the following functions:
- a Manifest "loader"
- a Manifest "parser"
- multiple segment "loaders" (one per type of buffer, like "audio", "video", "text"...).
- multiple segment "parsers"
As you can see, there's two recurrent concepts here: the loader and the parser.
### A loader
A loader in the transport pipeline is a function whose role is to "load" the resource.
Depending on the streaming technology, this can mean doing a request or just creating it
from the information given.
Its concept can be illustrated as such:
```
INPUT: OUTPUT:
------ -------
URL and other information +--------+ loaded resource
about the wanted resource | |
============================>| LOADER |==============================>
| |
+--------+
```
As the wanted resource could be obtained asynchronously (like when an HTTP request has to
be performed), the loader returns a Promise which resolves once the full resource is
loaded.
This Promise will reject on any problem arising during that step, such as an HTTP error.
In some specific conditions, the loader can also emit the wanted resource in multiple
sub-parts. This allows for example to play a media file while still downloading it and is
at the basis of low-latency streaming. To allow such use cases, the segment loaders can
also emit the wanted resource by cutting it into chunks and emitting them through a
callback as it becomes available. This is better explained in the related chapter below.
### A parser
A parser's role is to extract the data and other important information from a loaded
resource. It is connected in some ways to the response of the loader (which gives the
loaded resource) and will be the last step before that resource is actually handled by the
rest of the player.
Its concept can be illustrated as such:
```
INPUT: OUTPUT:
------ -------
loaded resource + exploitable resource and
resource information + +--------+ parsed information from it
request scheduler [1] | |
============================>| PARSER |==============================>
| |
+--------+
```
Depending on the type of parser (e.g. Manifest parser or segment parser), that task can be
synchronous or asynchronous.
In asynchronous cases, the parser will return a Promise resolving with the result when
done and rejecting if an error is encountered.
In synchronous cases, the parser returns directly the result, and can throw directly
when/if an error is encountered.
[1] a parser could also need to perform requests (e.g. it needs to fetch the current time
from a server). In such cases, the parser is given a special callback, which allows it to
receive the same error-handling perks than a loader, such as multiple retries, just for
those requests.
### Manifest loader
The Manifest loader is the "loader" downloading the Manifest (or MPD) file.
It is a function which receives as argument the URL of the manifest and then returns a
Promise resolving with the corresponding loaded Manifest when it finished downloading it:
```
INPUT: OUTPUT:
------ -------
Manifest/MPD URL +----------+ Manifest in a generic format
| | (e.g. string, Document...)
=======================>| MANIFEST |=================================>
| LOADER |
| |
+----------+
```
### Manifest parser
The Manifest parser is a function whose role is to parse the Manifest in its original form
to convert it to the RxPlayer's internal representation of it.
It receives in argument the downloaded Manifest, some Manifest-related information (e.g.
its URL) and a specific function called `scheduleRequest`, allowing it to ask for
supplementary requests before completing (e.g. to fetch the current time from an URL or to
load sub-parts of the Manifests only known at parse-time).
This function returns either the parsed Manifest object directly or wrapped in a Promise:
```
INPUT: OUTPUT:
------ -------
Manifest in a generic format + +----------+ RxPlayer's `Manifest`
URL + request scheduler | | structure
===============================>| MANIFEST |===========================>
| PARSER |
| |
+----------+
```
### Segment loader
A Transport pipeline declares one Segment loader per type of buffer (e.g. audio, text,
video...)
A segment loader is the "loader" for any segment. Its role is to retrieve a given
segment's data.
It receives information linked to the segment you want to download:
- The related `Manifest` data structure
- The `Period` it is linked to
- The `Adaptation` it is linked to
- The `Representation` it is linked to
- The `Segment` object it is linked to
It then return a Promise resolving when the segment is loaded.
```
INPUT: OUTPUT:
------ -------
Segment information +----------+ Segment in a generic format
| | (e.g. ArrayBuffer, string...)
=======================>| SEGMENT |=================================>
| LOADER |
| |
+----------+
```
The events sent in output depend on the "mode" chosen by the loader to download the
segment. There are two possible modes:
- the regular mode, where the loader wait for the segments to be completely downloaded
before sending it
- the low-latency mode, where the loader emits segments by chunks at the same time they
are downloaded.
The latter mode is usually active under the following conditions:
- low-latency streaming is enabled through the corresponding `loadVideo` option
- we're loading a DASH content.
- we're not loading an initialization segment.
- the segment is in a CMAF container
- the `Fetch` JS API is available
In most other cases, it will be in the regular mode, where the segment is fully
communicated as the returned Promise resolves.
In the low-latency mode, chunks of the data are sent through a callback given to the
segment loaded and the promise only resolves once all chunks have been communicated that
way.
### Segment parser
A segment parser is a function whose role is to extract some information from the
segment's data:
- what its precize start time and duration is
- whether the segment should be offseted when decoded and by what amount
- the decodable data (which can be wrapped in a container e.g. subtitles in an ISOBMFF
container).
- the attached protection information and data to be able to decrypt that segment.
It receives the segment or sub-segment as argument and related information:
```
INPUT: OUTPUT:
------ -------
Segment in a generic format + Decodable data + time
isChunked? [1] + Segment +----------+ information + segment protection
information | | information
===============================>| SEGMENT |===========================>
| PARSER |
| |
+----------+
```
[1] The parser can make different guess on the time information of the segment depending
on if the loaded segment corresponds to the whole segment or just a small chunk of it. The
`isChunked` boolean allows it to be aware of that.