UNPKG

rx-player

Version:
252 lines (186 loc) 10.3 kB
# 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.