UNPKG

rx-player

Version:
338 lines (243 loc) 10.4 kB
# 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.