Skip to content

Releases: canalplus/rx-player

v3.32.1

19 Oct 17:19
Compare
Choose a tag to compare

Release v3.32.1 (2023-10-19)

NOTE: This is the v3.32.1 and not a v3.32.0 as you could have expected due to a small mistake when publishing our TypeScript types to npm for a v3.32.0. We detected it and fixed it immediately with a new release, the v3.32.1.

🔍 Overview

The v3.32.1 mainly brings stability improvements over the v3.31.0, as we start to focus more and more on the future v4.0.0 release.

On that matter we'll probably soon release the first v4.0.0 release candidate (paving the way for the first official v4.0.0 release), after a long period of alpha and beta releases (the next beta release should come just after this one) where we were still figuring out some API details.

Note that we're however still commited to maintain the v3.x.x releases for some time, at least in terms of providing bug fixes, as we know doing the switch to the v4 may not be in your agenda for now.
As such, even when the official v4.0.0 will be released, we will continue publishing some v3.x.x releases, only with a special npm tag (e.g. rx-player@v3).

📑 Changelog

Features

  • DASH: add optional isSpatialAudio boolean property to Representation returned by getAvailableAudioTracks, getAudioTrack, corresponding events, and trackInfo optional property of MediaError objects to signal Dolby Atmos techology [#1275]
  • LOCAL: add isSpatialAudio property to Representation of the experiment "local" transport (used for offline playback) [#1275]
  • addFeatures static method is now available on all RxPlayer builds. It was previously only in the minimal (rx-player/minimal import path) [#1287]
  • The NATIVE_TEXT_BUFFER, HTML_TEXT_BUFFER and IMAGE_BUFFER features are now totally optional [#1287, #1293]

Bug fixes

  • Fix setVideoBitrate and setAudioBitrate API which may have led to a higher quality than wanted in the default "seamless" manualBitrateSwitchingMode if our buffer-based adaptive logic decided to [#1267, #1271]
  • On the PlayStation 5, only switch to the "LOADED" state once the HTMLMediaElement's readyState of 4 has been reached, as it seems to switch to 3 too soon there [#1257]
  • DASH: Fix potential track duplication if more than two AdaptationSet have an adaptation-set-switching <SupplementalProperty> between one another [#1279]
  • DASH-WASM: availabilityTimeOffset is actually a floating number [#1278]

Other improvements

  • Do not load the last text segment if the current position goes after it as it is unnecessary [#1256]
  • Implement better NetworkError messages [#1274]
  • Set a better error message for when no keySystems option is set when playing an encrypted content
  • Fix very small memory leak when reloading a content [#1286]
  • Re-check for segments to load immediately after the manifest has been refreshed [#1282]
  • When "fallbacking" an undecipherable Representation, now empty the whole buffer if we can't make out where content was in the buffer [#1283]
  • Improve segment start detection in buffer when there's unknown data buffered before it [#1284]
  • DRM: Selection of alternative EME API like those used on IE11 or Safari has been refactored to facilitate future developments [#1261]

Deprecated

  • Deprecate the manifestUpdateUrl loadVideo option as it doesn't seem used anymore [#1288]
  • Deprecate the NATIVE_TEXT_BUFFER, HTML_TEXT_BUFFER and IMAGE_BUFFER features as they are now unneeded [#1287, #1293]

New isSpatialAudio property

This v3.32.1 release brings the new isSpatialAudio property to audio tracks on the following API:

This property is for now only set to true when an audio track relies on Dolby Atmos under the "Dolby Digital Plus Joint Object Coding" (or JOC) technology, which allows to distribute Dolby Atmos audio content in a format compatible with Dolby Digital Plus audio (most probably under the "ec-3" codec), which is Dolby's recommended way of distributing DASH contents with Dolby Atmos audio.

So in short, for now isSpatialAudio is only set to true when an audio track relies on Dolby Atmos technology. For any other cases for now, it is not defined (a value of false would semantically mean that we're sure that the audio track is not spatial audio, which is a guess we prefer not to make for now).

Due to what's allowed in DASH, the isSpatialAudio property is set on "representations" (a.k.a. qualities), not on the track itself:

function hasSpatialAudio(audioTrack) {
    if (!audioTrack) {
        return false;
    }
    return audioTrack.representations
        .some(representation => representation.isSpatialAudio === true);
}

if (hasSpatialAudio(player.getAudioTrack()) {
    console.log("We're currently playing an audio track with spatial audio");
}

The majority of the development on this feature has been provided by an external contributor, @klatoszewski-oke, which we thank again.

addFeatures now globally available

On previous RxPlayer releases you were forced to rely on the minimal RxPlayer build to use any of its experimental features like DASH_WASM (WebAssembly-based DASH MPD parser), LOCAL_MANIFEST (playback of offline contents), DEBUG_ELEMENT (debugging UI) or METAPLAYLIST (client-side content generation).

If instead you used the default RxPlayer build, for example through a simple import RxPlayer from "rx-player"; import in your files, you were just left with the default set of features.

Now any build of the RxPlayer can directly call the addFeatures static method to add any of those optional features. For example to add the DEBUG_ELEMENT feature (and thus be able to call the createDebugElement method), you can write:

// 1. Add `DEBUG_ELEMENT` feature
import RxPlayer from "rx-player";
import { DEBUG_ELEMENT } from "rx-player/experimental/features";

RxPlayer.addFeatures([DEBUG_ELEMENT]);

// 2. Create RxPlayer instance and call method
const player = new RxPlayer({ /* ... */ });
player.createDebugElement(myElement);

Consequently, the features documentation has been moved from the "Getting started" part to our regular "API" documentation.

Deprecation of the NATIVE_TEXT_BUFFER and HTML_TEXT_BUFFER features

Both the NATIVE_TEXT_BUFFER feature and the HTML_TEXT_BUFFER features, which are the arguments of the addFeatures static method, have been deprecated as they are now unneeded.

They're basically optional now that importing a text parser for the corresponding type of buffer is going to implicitely add the feature anyway.

This means that if you were previously doing:

import RxPlayerMinimal from "rx-player/minimal";
import { HTML_TEXT_BUFFER, HTML_TTML_PARSER } from "rx-player/features";
RxPlayerMinimal.addFeatures([HTML_TEXT_BUFFER, HTML_TTML_PARSER]);

You can now just omit the HTML_TEXT_BUFFER feature:

import RxPlayerMinimal from "rx-player/minimal";
import { HTML_TTML_PARSER } from "rx-player/features";
RxPlayerMinimal.addFeatures([HTML_TTML_PARSER]);

As you have no reason to import one of those features without at least one parser, making it optional and implicit will generally just simplify your code.

Many small fixes

This v3.32.1 brings a lot of small fixes and improvements, that you can see on the changelog on top of this release note.

A good chunk of them were found as we were extensively testing both the future v4.0.0 as well as our proof-of-concept of trying to run the RxPlayer's internal logic in a WebWorker (more details on this hopefully soon).

We also reinforced our debugging capabilities by continuing our work on RxPaired, our remote debugger specialized for the RxPlayer that we're using internally at Canal+.
This helps a lot when debugging the RxPlayer on devices such as smart TVs, chromecast and game consoles which often have their own issues.
It helped us ...

Read more

v4.0.0-beta.2

27 Jun 15:06
ab78c0f
Compare
Choose a tag to compare
v4.0.0-beta.2 Pre-release
Pre-release

Release v4.0.0-beta.2 (2023-06-27)

Quick Links:
📖 API documentation - Demo - 🎓 Migration guide from v3

🔍 Overview

The new v4 beta release, based on the v3.31.0, is here.
It contains all improvements from previous v4 alpha and beta releases, as well as some further improvements mostly related to DRM, all described in this release note.

About v4 beta releases

As a reminder, beta v4 versions are RxPlayer pre-releases (as some minor API changes are still done, see changelog) for the future official v4 release, a successor to the current v3 releases of the RxPlayer.

We're currently testing it on several applications. As we're doing it, our team as well as people porting it can propose some small API improvements, which may then be added to the next beta releases. After enough people have made the switch and are satisfied with the new API, the first official v4 release will be published (we're also in the process to have some applications running it in production to ensure its stability with a large enough population).

We will still continue maintaining and providing improvements to the v3 for at least as long as the v4 is in beta (and we will probably continue to provide bug fixes for the v3 for some time after the official v4.0.0 is released).
This process is long on purpose to be sure that we're providing a useful v4 API for applications and also to avoid alienating application developers, as the migration from the v3 might take time.

📑 Changelog

Changes

  • If all Representations from the current track become undecipherable, automatically switch to another track (also send a trackUpdate event) instead of stopping on error [#1234]
  • Only send MediaError errors with the NO_PLAYABLE_REPRESENTATION error code when no Representation from all tracks of a given type can be played [#1234]

Features

  • Add representationListUpdate event for when the list of available Representation for a current track changes [#1240]
  • Add "no-playable-representation" as a reason for trackUpdate events when the track switch is due to encrypted Representations [#1234]

Other improvements

  • DRM: Reload when playback is unexpectedly frozen with encrypted but only decipherable data in the buffer to work-around rare encryption-related issues [#1236]

NO_PLAYABLE_REPRESENTATION behavior change

In the v3 and previous v4 beta releases, if you could not play any Representation (i.e. quality) from your chosen audio or video track due to encryption matters, you would obtain a MediaError error with the NO_PLAYABLE_REPRESENTATION error code.

Stopping on error when no quality of the chosen track can be played seemed logical at the time, but we're now encountering use cases where it would be better if the RxPlayer automatically took the decision to change the current track instead, to one that perhaps has decipherable Representation(s).
The main example we encountered was cases where we had separate video tracks, each linked to another dynamic range (e.g. an HDR and a SDR track) and with different security policies (tracks with a high dynamic range would have more drastic security policies for example).
Here, I would guess that an application would prefer that by default we switch to the SDR video track if no Representation in the HDR one is decipherable, instead of just stopping playback with a NO_PLAYABLE_REPRESENTATION error.

Before:
-------

+----------------+
|    Selected    |  Not decipherable
|   Video Track  |  --------------------> NO_PLAYABLE_REPRESENTATION
| (example: HDR) |                        Error
+----------------+

Now:
----

+----------------+                        +---------------------+
|    Selected    |  Not decipherable      |      Automatic      |
|   Video Track  |  --------------------> |     fallback to     |
| (example: HDR) |                        | another video Track |
+----------------+                        |   (example: SDR)    |
                                          +---------------------+

Note that the NO_PLAYABLE_REPRESENTATION error might still be thrown, only now it is when no Representation of all tracks for the given type are decipherable.

New trackUpdate reason

Because an application might still want to be notified or even stop playback by itself when the initially-chosen track has no playable Representation, we also brought added the "no-playable-representation" reason to the trackUpdate event, which indicates that the current track for any Period of the current content was updated due to this situation.

player.addEventListener("trackUpdate", (payload) => {
  if (payload.reason === "no-playable-representation") {
    console.warn(
      `A ${payload.trackType} track was just changed ` +
      "because it had no playable Representation"
    );
  }
});

New representationListUpdate event

This new beta version of the v4 also brings a new event: representationListUpdate.

The problem without this event

Let's consider for example an application storing information on the currently available qualities (a.k.a. Representation) for the chosen video track of the currently-playing Period (said another way: being played right now).
With the v4 that application can simply get the initial list of Representation when that Period begins to be played through the periodChange event and update this list any time the video track changes, by listening to the trackUpdate event:

let currentVideoRepresentations = null;

function updateVideoRepresentations() {
  const videoTrack = player.getVideoTrack();
  if (videoTrack === undefined || videoTrack === null) {
    currentVideoRepresentations = null;
  } else {
    currentVideoRepresentations = videoTrack.representations;
  }
}

// Set it when the Period is initially played
player.addEventListener("periodChange", () => {
  updateVideoRepresentations();
});

// Set it when the video track is changed
player.addEventListener("trackUpdate", (t) => {
  // We only want to consider the currently-playing Period
  const currentPeriodId = player.getCurrentPeriod()?.id;
  if (t.trackType === "video" && t.period.id === currentPeriodId) {
    updateVideoRepresentations();
  }
});

// Remove it when no content is loaded
player.addEventListener("playerStateChange", () => {
  if (!player.isContentLoaded()) {
    currentVideoRepresentations = null;
  }
});

This seems sufficient at first.

But now let's consider that we're playing encrypted contents, and that one of the Representation of those current video tracks became un-decipherable at some point (e.g. after its license has been fetched and communicated to your browser's Content Decryption Module).
Here, an application won't be able to select that Representation anymore, so it generally will want to remove it from its internal state. However, there was no event to indicate that the list of available Representations had changed when neither the video track itself nor the Period has changed

The new event

We thus decided to add the representationListUpdate event. Exactly like the trackUpdate event, it is only triggered when the Representation list changes, i.e. not initially, when the track is chosen.

So taking into consideration that point, the previous code can be written:

let currentVideoRepresentations = null;

function updateVideoRepresentations() {
  const videoTrack = player.getVideoTrack();
  if (videoTrack === undefined || videoTrack === null) {
    currentVideoRepresentations = null;
  } else {
    currentVideoRepresentations = videoTrack.representations;
  }
}

// Set it when the Period is initially played
player.addEventListener("periodChange", () => {
  updateVideoRepresentations();
});

// Set it when the video track is changed
player.addEventListener("trackUpdate", (t) => {
  // We only want to consider the currently-playing Period
  const currentPeriodId = player.getCurrentPeriod()?.id;
  if (t.trackType === "video" && t.period.id === currentPeriodId) {
    updateVideoRepresentations();
  }
});

// Remove it when no content is loaded
player.addEventListener("playerStateChange", () => {
  if (!player.isContentLoaded()) {
    currentVideoRepresentations = null;
  }
});

// What's new:
...
Read more

v3.31.0

14 Jun 16:42
16f75e7
Compare
Choose a tag to compare

Release v3.31.0 (2023-06-14)

🔍 Overview

The v3.31.0 release is now here!

It brings:

  • Many new API to improve developer experience when relying on the RxPlayer: isContentLoaded, isBuffering, isPaused, play and paused events, getLastStoredContentPosition and the trackInfo property on error

  • A refactored adaptive logic allowing to limit the frequency of quality switches, generally seen as a poor experience and which could lead to rebuffering in some extreme situations

  • A fix for the "direct" audioTrackSwitchingMode which in some situations could lead to the absence of sound when changing the audio track

  • A fix for DASH multi-Period contents, where we could be led to an infinite rebuffering cases in-between two periods in some conditions

  • "forced" subtitles support in "directfile" mode on Safari

  • Some Playstation 5 fixes to avoid playback issues on this platform

  • Better error reporting when an issue arises when initializing the DASH_WASM experimental Webassembly-based MPD parser

The future v4.0.0-beta.2 release, which will be based on this one alongside v4 features, will be released soon (in the coming days).

📑 Changelog

Features

  • Add isContentLoaded, isBuffering, isPaused, and getLastStoredContentPosition methods [#1248]
  • Add play and paused events [#1253]
  • Add trackInfo property to some MediaError to expose information on the track that caused the error [#1241]

Bug fixes

  • DASH: Fix issue which could lead to infinite rebuffering when switching between multiple Periods [#1232]
  • Return actual ending duration through the getVideoDuration method when playing dynamic contents whose future end is already known [#1235]
  • DASH/WASM: actually reject the DASH_WASM.initialize's Promise if it fails [#1238]
  • On the PlayStation 5, set Infinity MediaSource duration for live contents to prevent playback issues [#1250]

Other improvements

  • adaptive: Perform various adaptive tweaks to avoid switching too much between qualities in some conditions [#1237]
  • Directfile: Detect "forced" subtitles on Safari when playing directfile contents (such as HLS) [#1239]
  • Improve "direct" audioTrackSwitchingMode compatibility by re-seeking [#1246]
  • The DEBUG_ELEMENT feature now uses the monospace fallback font as a default for a better rendering on apple devices
  • doc: externalize documentation-generator code

isContentLoaded and isBuffering method

Until now, an application had to obtain the player's state (either through the getPlayerState method or through the "playerStateChange" event) to know whether a content was loaded, buffering, ended and so on.
This is fine but we thought that we could do better in terms of API.

Amongst player states, we noticed that some are frequently grouped together in some situation.

Consequently, we added the two following methods to the RxPlayer API in the v3.31.0.

  1. isContentLoaded: Most states, with the only exceptions of the "LOADING", "RELAODING" and "STOPPED" states, could all be grouped together to indicate that a a content is currently loaded.

    In turn, knowing that a content is or isn't loaded is important because when none is loaded, many methods such as setAudioTrack, setVideoBitrate and so on cannot be called.

    RELOADING.mp4

    Video: The three cases where a video is not considered loaded are visible in this video:
    1. When loading a content
    2. When reloading it (here we have changed the video bitrate while the manualBitrateSwitchingMode option was set to "reload")
    3. When stopped

    Because detecting any of the corresponding states is important in the RxPlayer API, we chose to add the isContentLoaded method to the RxPlayer's API, which will return true when a content can be considered as loaded (which is exactly the same than checking the corresponding states, though it is now more explicit).

    This method is documented here.

  2. isBuffering: Most applications want to show a visual indicator such as a spinner for when a content is loaded or being loaded, but not enough data is buffered for playback to happen. This is most commonly called "buffering".

    BUFFERING.mp4

    Video: Rebuffering cases both when loading and after seeking in a content.

    This corresponds to the "SEEKING" (buffering due to a seek operation), "LOADING" (initial buffering), "RELOADING" (buffering due to the necessity to re-create media buffers) and "BUFFERING" (other regular cases of buffering) states.

    The v3.31.0 version of the RxPlayer now adds the isBuffering method to the RxPlayer API, which will return true when the current player's state corresponds to any of those.

    Note however that you still may want to divide, depending on your applications further those states. For example the "LOADING" and "RELOADING" states may both be associated to a black screen, whereas you'll most likely have the last frame displayed when encountering the "BUFFERING" and "SEEKING" states. So, you might still want to rely on the general state depending on what you want to do in your application.

    This method is documented here.

isPaused method

The isPaused method may initially seem similar to the isContentLoaded and isBuffering method but unlike those two, which just facilitate the exploitation of the RxPlayer's state, isPaused actually adds a functionality.

In a media player, you frequently want to know whether the RxPlayer is "technically paused" or not, for example to know whether you should display the play or pause button in your user interface.

pause_button
play_button

Screenshots: Two schreenshots of a player's control bar. In each of them, we can see at the bottom left the button which may represent a "play" order or a "paused" order depending on if the playback is currently playing or not.
Here the top one display a "pause" button (and thus we're probably currently playing) whereas the bottom one display a "play" button (we're probably currently paused).

Although the "PAUSED" player state exists, it isn't sufficient to determine this. For example, you could technically be seeking, and thus in the "SEEKING" state, yet paused (which means that once enough data has been loaded you won't be playing, but paused).

Based on this, the player's state is not sufficient to determine whether playback is paused or not.
Until now, applications generally kept track of their last play/pause calls and of the last RxPlayer states to deduce whether playback was paused.

However, it should be easier and less error prone to obtain such an important information directly and explicitly from the RxPlayer.
This can now be done through the isPaused method which will return false when playback is considered as paused, regardless of the player's state.

const isPaused = rxPlayer.isPaused();
if (isPaused) {
  console.log("playback is currently paused");
} else {
  console.log("playback is currently not paused");
}

This method is documented here.

"play" and "pause" events

Because now that you have the new isPaused method you may also want to know when the RxPlayer enters or exits a pause.

For example to properly implement a "play/pause button", you might want to switch it reactively after a play or pause call has been processed. To al...

Read more

v4.0.0-beta.1

08 Mar 15:50
9ca93b8
Compare
Choose a tag to compare
v4.0.0-beta.1 Pre-release
Pre-release

Release v4.0.0-beta.1 (2023-03-08)

Quick Links:
📖 API documentation - Demo - 🎓 Migration guide from v3

Overview

This is an update of the previous v4.0.0-beta.0 release.

Compared to it, the v4.0.0-beta.1:

  • is now based on the v3.30.0 stable release of which it inherits all features, bug fixes and improvements (and not a partial list of them like for the beta.0)
  • Fix a memory leak just seen on the v4.0.0-beta.0
  • Provide other minor fixes over the v4.0.0-beta.0 (see the Changelog below)

Changelog

Bug fixes

  • (v4.0.0-beta.0-only issue) Fix memory leak
  • (v4.0.0-beta.0-only issue) Fix MediaSource duration when the maximum buffered end is inferior to current duration but superior to calculated duration
  • (v4.0.0-beta.0-only issue) Fix stopAtEnd option by also switching to STOPPED state on ended
  • (v4.0.0-beta.0-only issue) Fix some target's support by not relying on Promise.prototype.finally anymore [#1224]
  • (v4.0.0-beta.0-only issue) For dynamic contents, always set a very high duration [#1220]
  • (v4.0.0-beta.0-only issue) DRM: Fix fallbacking for an already-played content by checking key statuses initially linked to a MediaKeySession

Other improvements

  • Based on the v3.30.0 of which it inherits all the features, bug fixes and other improvements

v3.30.0

07 Mar 16:39
ebd1266
Compare
Choose a tag to compare

Release v3.30.0 (2023-03-07)

🔍 Overview

The v3.30.0 release is, at last, available!

It's a relatively big release which took a long time to test and finish, mainly because it internally completely removes our dependency on the RxJS library. This is the end of a long time effort started with the v3.26.1 (2021-09-14), whose goal is mainly to improve the approachability of the code and the debugging experience.

Note that the v4.0.0-beta.0 pre-release already included an early version of many features introduced here, so you might already know some of them. On that matter, we will release soon after this release a v4.0.0-beta.1 which re-implements future v4 features on top of this release.

The v3.30.0 release brings:

  • A new updateContentUrls method allowing to change the Manifest's URL, as long as it represents the same content.

  • "forced" narrative subtitles handling.

  • DRM: A new getKeySystemConfiguration API which allows to obtain the complete key system configuration obtained from the browser

  • DASH: The SegmentTemplate@endNumber attribute is now handled

  • A new createDebugElement experimental API (behind the new "DEBUG_ELEMENT" experimental feature) was added, allowing to show debugging information.

  • Panasonic 2019 TVs are now properly supported. We had some issues before with content with DRMs

  • LG TV: Contents with DRM are now also better supported

  • A new BUFFER_FULL_ERROR avoiding mechanism, trying to limit some of such (rare) errors seen in the wild

📑 Changelog

Features

  • Add updateContentUrls API, allowing to update the Manifest's URL during playback [#1182]
  • DASH: implement forced-subtitles, adding the forced property to the audio tracks API and selecting by default a forced text track linked to the audio track's language if present [#1187]
  • DRM: add the getKeySystemConfiguration method to the RxPlayer [#1202]
  • add experimental DEBUG_ELEMENT feature and createDebugElement method to render a default debugging HTML element [#1200]

Deprecated

  • Deprecate the getVideoLoadedTime method which can be easily replaced (see Deprecated method documentation)
  • Deprecate the getVideoPlayedTime method which can be easily replaced (see Deprecated method documentation)
  • Deprecate the transportOptions.aggressiveMode option
  • DRM: Deprecate the keySystems[].onKeyStatusesChange callback as no good use case was found for it.

Bug fixes

  • Fix segment requesting error when playing a DASH content without an url and without BaseURL elements [#1192]
  • API: Stop sending events if the content is stopped due to a side-effect of one of the event handler [#1197]
  • text-tracks/ttml: fix inconsistent line spacing when resizing the textTrackElement [#1191]
  • DRM: Fix race condition leading to a JS error instead of a NO_PLAYABLE_REPRESENTATION [#1201]
  • DRM/Compat: Renew MediaKeys at each loadVideo on all WebOS (LG TV) platforms to work around issues [#1188]

Other improvements

  • DASH: better detect closed captions [#1187]
  • DASH: handle endNumber DASH attribute [#1186]
  • DASH: Do not merge AdaptationSet with role "main" anymore [#1214]
  • DASH: parse transferCharacteristics property in the MPD to better detect hdr [#1212]
  • Support encrypted contents on Panasonic 2019 TVs [#1226]
  • Better handle SourceBuffer's QuotaExceededError, responsible for MediaError with the BUFFER_FULL_ERROR code [#1221]
  • API: send available...TracksChange events in the very unlikely scenario where tracks are added after a manifest update [#1197]
  • Completely remove RxJS dependency from the RxPlayer's source code [#1193]
  • DRM: Request PR recommendation when PlayReady is asked and try default recommendation robustnesses [#1189]

Forced subtitles

Note: this feature was first presented in the v4.0.0-beta.0 beta release

Forced subtitles, also referred as forced narrative or just narrative subtitles are subtitles which are meant to be displayed by default when no other subtitle track is selected.

They allow to clarify audio or visual cues that might not be clear for the user, by providing text translation to:

  • clarification of audio when it is not clearly audible (for example due to a strong accent or due to distorted audio)
  • foreign - or even sometimes invented (e.g. Klingon) - languages spoke in films
  • other types of communication which might not be easily understood (a frequent example would be sign language)
  • text in foreign languages present on the video track

klingon
Screenshot: french forced narrative subtitles translating what the character is saying. The spoken language here is Klingon which few people know how to speak (besides Klingons of course - and yes, I just repeated the same joke on both release notes).

In previous RxPlayer versions, forced subtitles were treated as any other type of text track, with no mean to identify them.
This version adds a new forced property to text tracks described through text tracks API (the getAvailableTextTracks and getTextTrack methods, the availableTextTracksChangeandtextTrackChange` events) to help you identify which text track is a forced one, as you most likely don't want to list it with other regular tracks:

const textTracks = rxPlayer.getAvailableTextTracks();
const textTracksToDisplay = textTracks.filter(t => !t.forced);

Also, the RxPlayer will for now automatically select a forced text track by default (instead of no text track) if one is present for the same language than the default audio track chosen - unless text tracks are explicitely disabled, for example by setting null in the preferredTextTracks.
This means that - if you're using forced narrative subtitles - you might want to be careful if switching the audio track to also switch the text track if a forced one exist in that new language or if the old one is not adapted anymore.

updateContentUrls API

Note: this feature was first presented in the v4.0.0-beta.0 beta release

Some applications recently introduced us to a new need: being able to change the Manifest's URL while the content is playing.
The URL would represent the same content, it's here just a change of URL in itself that is wanted. For example, a usage would be to switch from an overloaded CDN during playback or to change a token present in the URL.

The v3.30.0 gives an answer to these needs by introducing a new method: updateContentUrls.
It allows to update the URL(s) of the Manifest, and even to trigger a manifest refresh directly, through a simple call:

rxPlayer.updateContentUrls([newUrl], {
  // trigger the refresh immediately
  refresh: true,
});

As usual it has been properly documented in its own documentation page.

For encrypted contents: getKeySystemConfiguration API

Note: this feature was first presented in the v4.0.0-beta.0 beta release

Encryption-related issues are the most frequent category of issues currently investigated by the RxPlayer team and its associated applications at least made by Canal+ and our partners.

In that context, we missed a crucial API allowing to facilitate encryption-related debugging on various platforms: One that would allow to indicate easily the current key system configuration relied on.

Particularly the robustness level: PlayReady SL3000 or SL2000? Widevine L1, L2 or L3? And so on.

That's why this release adds the getKeySystemConfiguration API which returns both the actual key system string and the
MediaKeySystemConfiguration currently relied on.

I put there an emphasis on "actual" because, in opposition to the getCurrentKeySystem API, which is now removed, it is the name actually reported by the MediaKeySystemAccess that is here reported, whereas getCurrentKeySystem returned the exact same string than the type originally set on the keySystems option - an arguably less useful value when the RxPlayer could have made translations in between.

An experimental debug element

Applications relying on the RxPlayer often include a special element displaying various playback-related metrics, used for debugging.

That debugging element generally depended on RxPlayer API as well as on attributes of the media element.
The fact th...

Read more

v4.0.0-beta.0

27 Jan 15:33
Compare
Choose a tag to compare
v4.0.0-beta.0 Pre-release
Pre-release

Release v4.0.0-beta.0 (2023-01-27)

Quick Links:
📖 API documentation - Demo - 🎓 Migration guide from v3

🔍 Overview

The first v4.0.0 beta version, v4.0.0-beta.0 is finally here!
The focus of this release was to provide a better API in the now more evolved streaming landscape, especially to better handle DASH multi-Period contents - which are becoming more and more frequent (thanks to new server-side features such as ad-switching, key rotation, live contents with multiple track lists depending on the program etc.). Another main point of this new major release was to clean-up deprecated features from the API, some preventing us from doing advanced work we plan to make in the future.

This is a very big release in terms of changes but don't be scared! This beta version represents a v4 API which though it can be seen as stable (after a less stable, mostly internal, alpha phase), can still evolve before its official release if issues with it are spotted. Also, new v3 releases with bug fixes, improvements and even features will still be made for the foreseeable future.

The goal of this first open beta release is too give you time to make the switch, ask questions, propose changes and report issues that you see with it.
Because we want to make the migration experience as painless as possible, we wrote a complete migration guide, accessible in our documentation pages for the v4.

As the changelog is here pretty big and thus less interesting to read. we moved it back at the end of the release note for this release only.
Also note that some of the changes made in this beta release will be brought in the next v3.30.0 release. They also will be documented in the corresponding release note.

Note: As this is not a stable release, installing/upgrading to it must generally be done explicity, either by adding the [email protected] package through your package manager, by adding it through its next tag (which will point toward the last beta release), by explicitly specificying its version on your package.json file(s), or through other related techniques.

A new player state: "FREEZING"

A new player state (gettable either through the playerStateChange event or the getPlayerState method) has been added: "FREEZING".

It's important to note that this new new state does not characterize a new behavior, it only put a better word onto a specific problematic playback state. Previously, it was either reported as a "BUFFERING", "SEEKING", or even "PLAYING" state depending on the situation.

This state appears when playback is temporarily stuck in place, though in opposition to the more usual "BUFFERING" and "SEEKING" state, this is not due to buffer starvation but to another, generally unknown, factor. As such, it can in most cases be handled just like a "BUFFERING" state in your application (e.g. by displaying a spinner on top of the media element) but it may be logged differently to help you pinpoint playback issues.

buffering
Screenshot: A "FREEZING" state can be treated just like a "BUFFERING" one, here with a pink spinner on top of the video.

Under that state, which is generally much rarer than a "BUFFERING" state for example, the RxPlayer will try various tricks to try to un-freeze playback. If they become too frequent or if those tricks don't work, it might be an issue worth investigating.

A more flexible track API

One of the focus of this new major release was to improve the RxPlayer API on DASH multi-Period contents - which are contents with various set of tracks and Representations (qualities) depending on the time period.

The RxPlayer's previous track API (e.g. setAudioTrack and getAvailableAudioTracks) only allowed to get the list and update the track for the currently-playing Period.

// Example setting the first english audio track for the current Period if found
const availableAudioTracks = rxPlayer.getAvailableAudioTracks();
const englishAudioTrack = availableAudioTracks.find((track) => {
  return track.language === "eng";
});

if (englishAudioTrack !== undefined) {
  rxPlayer.setAudioTrack(englishAudioTrack.id);
}

The new track API, which is still compatible with the old one (meaning the old calls still work), now allows to set the track for any Period:

// Example setting a french audio track for the first Period if found
// and an english track for the second (curious use case, but why not?)
const availablePeriods = rxPlayer.getAvailablePeriods();
if (availablePeriods.length >= 2) {
  const availableAudioTracks1 = rxPlayer.getAvailableAudioTracks(
    availablePeriods[0].id
  );
  const frenchAudioTrack1 = availableAudioTracks.find((track) => {
    return track.language === "fra";
  });

  if (englishAudioTrack1 !== undefined) {
    rxPlayer.setAudioTrack({
      trackId: englishAudioTrack1.id,
      periodId: availablePeriods[0].id,

      // Note: it's now also possible to set the switching mode per-track change
      switchingMode: "direct",

      // It's also possible to only authorize some Representations from being
      // played
      lockedRepresentations: englishAudioTrack1.representations
        .filter((representation) => {
          // Only authorize Dolby Digital+ Representations from this track
          return representation.codec === "ec-3";
        });
    });
  }

  const availableAudioTracks2 = rxPlayer.getAvailableAudioTracks(
    availablePeriods[1].id
  );
  const englishAudioTrack2 = availableAudioTracks.find((track) => {
    return track.language === "eng";
  });

  if (frenchAudioTrack1 !== undefined) {
    rxPlayer.setAudioTrack({
      trackId: frenchAudioTrack1.id,
      periodId: availablePeriods[1].id,
    });
  }
}

As you can see, the newer syntax allows more flexibility than the previous one. However it should be noted that the old syntax still work and still update the track for the currently-playing Period.

Removal of the track preferences API

When associated with new RxPlayer events also added in this version, the new track API make the preferences API re-implementable in its entirety by an application.

Because of this, and after much thinking on our side, we decided to remove the track preference API from our v4.0.0 even if we understand that it implies some work on your side to make the switch.

Don't worry the new track API is both easy to understand and more flexible than the set of methods and options that were part of the track preferences API. We also made a complete preference migration guide here, which includes code to completely replace the old preference API.

Do not hesitate to open an issue if you find that documentation not too clear or even if you're not OK with the removal of the old track preference API.
To be perfectly honest, we're still hesitating and we may bring it back in the future if it proves to have clear advantages. But for now, the new track API just seems more complete and easier to understand for applications (we had in the past several issues from people poorly understanding the behavior of the preferences API), which is why we're orienting you towards it.

Improved Representation selection

Previous RxPlayer versions only allow...

Read more

v3.29.0

16 Nov 18:02
48d1f84
Compare
Choose a tag to compare

Release v3.29.0 (2022-11-16)

⚠️ If you're reading this from the global "releases" page on GitHub, it seems that long-ish release notes, like those we like to write, are now truncated by default.
To be able to read the full one, you may need to click on the release title to be redirected to the complete content of this release note - which I find is not something made too clear. Sorry about that :/.
I also consequently decided to move the changelog closer to the beginning of that release note, so the shortened yet complete list of features, fixes and improvements become directly visible on the "releases" page.

🔍 Overview

It is time for a new RxPlayer release, the v3.29.0. It contains among other things:

  • the possibility to change the used timeout for manifest and segment HTTP requests
  • A new option to configure the behavior the RxPlayer will have on decryption key expiration
  • A better URL prioritization algorithm for DASH contents listing multiple URL for segments
  • Fixes of multiple Directfile issues
  • A lot compatibility improvements, especially for LG and Samsung TVs
  • Reverse playback through performing frequent small seek is now better considered by the RxPlayer (see below)

You can look at the complete changelog of this release just below, then this release note will focus on some of its main features, fixes and improvements.

📑 Changelog

Features

  • add networkConfig.segmentRequestTimeout and networkConfig.manifestRequestTimeout options to loadVideo to configure the timeout of respectively segment and manifest requests [#1156]
  • add timeout property to the first argument communicated to a segmentLoader (from loadVideo's transportOptions) [#1156]
  • add timeout property to a new third argument communicated to a manifestLoader (from loadVideo's transportOptions) [#1156]
  • DRM: add keySystems[].onKeyExpiration to loadVideo options to configure the behavior the RxPlayer should have on key expiration [#1157]
  • DRM: add keyStatuses property to an EncryptedMediaError with the KEY_STATUS_CHANGE_ERROR code to communicate which key id and key status caused issues. [#1157]

Deprecated

  • DRM: Deprecate keySystems[].throwOnLicenseExpiration loadVideo option as this boolean can be replaced with more customizability by the new keySystems[].onKeyExpiration loadVideo option [#1157]

Bug fixes

  • Directfile: Fix long-running issues with rare "directfile" contents and some browsers/platforms (seen on Chrome PC and PlayStation 5) where playback would stay in LOADING state indefinitely despite playing [#1174]
  • DRM: Fix undocumented keySystems[].videoRobustnesses loadVideo option. audioRobustnesses was previously used even for video capabilities [#1171]
  • Compat/Directfile: Fix an issue with WebOS (LG TVs) when playing multiple directfile contents with the stopAtEnd player option set to true [#1154]
  • Compat/DRM: Fix infinite loading on WebOS (LG TVs) 2021 and 2022 when loading more than once an encrypted content by resetting decryption capabilities each time [#1175]
  • Compat: To work around an issue on WebOS (LG TVs), also specify a request timeout manually through a setTimeout call when XMLHttpRequests are created for Manifest and segment requests [#1152]
  • Compat/Directfile: Fix an issue on Tizen (Samsung TVs) where playing directfile contents could randomly lead to not having audio [#1170]
  • Compat: Fix issue with Tizen (Samsung TVs) where starting playback on a discontinuity could lead to infinite rebuffering [#1140, #1176]
  • Compat/Directfile: For "directfile" contents, also consider AudioTrack with a description (without an "s") as audio-description audio tracks to work-around what seems to be a Safari typo [#1160]
  • DRM: When using persistent licenses, create new MediaKeySession when load resolves with false, instead of relying the same, to fix issues with such persistent sessions if the browser cleaned it up [#1139]
  • Only call "MediaSource.endOfStream" once, the most visible side-effect should have been repeated logs [#1163]

Other improvements

  • DASH: Improve multi-CDN configurations, by smartly selecting the right CDN depending on past status [#1165]
  • Allow reverse playback use cases by not skipping gaps and most discontinuities when the playback rate has been set to 0 or a negative value [#1138]
  • In the experimental "local" transport, add incomingRanges property to signal the time ranges of remaining data, allowing better discontinuity handling and duration estimates for sill-loading dowloaded contents [#1151]
  • Only send, through "warning" events, one EncryptedMediaError with a KEY_STATUS_CHANGE_ERROR code when multiple ones arises at the same time [#1157]

Configurable timeout values for the manifest and segment requests

Request timeouts

The RxPlayer almost continuously download media video and audio data called "segments", each with its own HTTP request, which will be then communicated to the browser so playback can start quickly, even if the full content is not yet loaded.
If one of those requests takes too much time to respond, we risk to enter a phase where playback cannot continue because we're awaiting on that data. This type of situation is called "rebuffering".

Sometimes the reason behind a request hanging for too long was just a temporary server-side issue, that may not be present anymore if the request is tried again. For those cases, the RxPlayer almost always set a timeout on Manifest and segment HTTP(S) requests it performs.

output_timeout_graphs.mp4

Video: To the top left, visualizations of the audio and video buffer, to the bottom left, current amount of buffer ahead: if it comes close to 0, it's rebuffering time during which playback is paused.
To the right, requests as seen in the Chrome inspector. We can see that two media segments called seg_16.mp4 (which are the next video and audio segments to download) each take a long time to respond. The first of these requests is then set in red and then retried: this is the timeout mechanism.
We can see the buffer quickly diving to 0 as the request hangs while playback continued.

But because we wanted to avoid as much as possible the possibility of aborting requests that are long for legitimate reasons (large data being downloaded, server processing, server-originated delay for a real reason etc.), that timeout has always been set to a relatively high value: 30 seconds.

For most contents, 30 seconds during which a particular request hangs lead to a very high chance of encountering rebuffering phase (as the pre-buffered media data is at most also 30 seconds by default), thus this made this limit not optimal.
Thankfully, requests hanging that way is relatively rare, so though we knew we could improve this situation, it hasn't been treated as a high priority until now.

Encountered issue

We recently worked on an issue partners were having where we would sometimes encounter hanging requests due to what seemed server-side issues. This in turn led to long rebuffering phases which are very unpleasant for a user.

output_timeout.mp4

Video: In this example the two media segments called seg_103.mp4 (which are the next video and audio segments to download) each take a long time to respond, with again, a timeout.
Here we can visualize that the player had to pause because we've reached the end of the buffer while waiting that next segment.

Though it may indicate that the original problem is more on the CDN side, we considered that the RxPlayer had still to provide a better experience in that case.

Moreover, resources requested in that case where small: media segments, generally the larger files being requested, were very short both in terms of size than in terms of duration.
We consequently thought about a potential improvement: if we lowered the timeout to a much shorter value (in our case 8 seconds), there may be much less chance of rebuffering in cases when requests hang. Moreover, even if we actually had a rebuffering phase, it would be much shorter in time as the request would be retried much more quickly.

The timeout API

The v3.29.0 now allows to set a tim...

Read more

v3.28.0

12 Jul 17:16
ac3afd5
Compare
Choose a tag to compare

Release v3.28.0 (2022-07-12)

Overview

The v3.28.0 release has now been published to npm.
Though it is technically a minor release, in terms of semantic versioning, it only have few new features and mostly brings bug fixes and improvements:

  • Tracks API now also optionally have a label string attribute if it found one in the Manifest
  • A content's duration may now evolve in function of the current tracks chosen, to give a more precize one
  • The RELOADING state is now triggered mostly asynchronously to facilitate its handling
  • Media data completely garbage collected by the browser is now properly detected to avoid remaining very rare request loops
  • TTML: more EBU-TT subtitles are now parsed, by considering the potential tt XML namespace we may find in those files.
  • DASH: multiple small fixes have been added to raise compatibility with some peculiar MPD
  • An issue has been fixed which made a set maxVideoBufferSize appear to be inaccurate in some situations.
  • DRM: If the key id is not found in the Manifest file, it may be parsed directly from the media data itself, to unlock advanced features with such contents.
  • Multiple other fixes and improvements (you can find the changelog at the bottom of this release note).

Changelog

Features

  • Add label to audio, video and text track APIs (such as getAvailableAudioTracks) which gives a human-readable description of the corresponding track, if available in the Manifest [#1105, #1109]
  • Automatically set the LogLevel to "DEBUG" if a global __RX_PLAYER_DEBUG_MODE__ constant is set to true, to simplify debugging [#1115]

Bug fixes

  • Use the first compatible codec of the current AdaptationSet when creating a SourceBuffer [#1094]
  • DASH/DRM: Fix potential infinite rebuffering when a KID is not announced in the MPD [#1113]
  • DRM: Fix quality fallback when loading a content whose license has been cached under an extended singleLicensePer setting and when starting (and staying) with a quality whose key id is not in it [#1133]
  • DASH: Avoid infinite loop due to rounding errors while parsing multi-Periods MPDs [#1111, #1110]
  • After a RELOADING state, stay in PAUSED if the media element was paused synchronously before the side-effect which triggered the reloading (usually coming from the API) was perform [#1132]
  • Fix issue with maxVideoBufferSize setting which could lead to too much data being buffered [#1125]
  • Prevent possibility of requests loops and infinite rebuffering when a pushed segment is always completely and immediately garbage collected by the browser [#1123]
  • DASH: Fix potential rare memory leak when stopping the content after it has reloaded at least once [#1135]
  • Directfile: Properly announce the audio track's audioDescription accessibility attribute in directfile mode on Safari [#1136]
  • DASH: Fix issues that could arise if a segment is calculated to start at a negative position [#1122]
  • DASH: Fix possibility of wrong segments being requested when a SegmentTimeline in a given Period (whose Period@end is set) had an S@r set to -1 at its end [#1098]
  • DASH: If the first <S> has its S@t attribute not set, make as if it is set to 0 [#1118]

Other improvements

  • TTML: Add support for percent based thickness for textOutline in TTML Subtitles [#1108]
  • If seeking after the last potential position, load last segments before ending [#1097]
  • Improve TypeScript's language servers auto import feature with the RxPlayer by better redirecting to the exported type [#1126]
  • The duration set on the media element is now only relative to the current chosen tracks (it was previously relative to all potential track). This allows to seek later when switching e.g. to a longer video track [#1102]
  • Errors coming from an HTMLMediaElement now have the browser's error message if it exists [#1112]
  • TTML: Better handle EBU-TT subtitles by handling the tt XML namespace in our TTML parser [#1131]
  • DRM: Information on persisted DRM sessions are now automatically updated to their last version when possible [#1096]
  • Only log values which are relatively inexpensive to stringify to reduce the difference between debugging sessions and what is usually seen in production [#1116]

Add a label property to audio, video, and text tracks

Current track labeling

To choose between several audio, video or text tracks, an application had previously only access to its key characteristics: its language (for audio and text tracks), codecs, accessibility features, resolution of its qualities (for video tracks) etc.

If they had to display a track choice to the user, applications usually then derived a name from these characteristics.
For example, french closed captions could be proposed as "French [CC]", a dolby digital + italian audio track as "Itialian [Dolby Digital +]", and so on.

languages

Screenshot: one of the user interfaces used at Canal+ for french users. The various audio and text tracks' names proposed there are actually entirely derived from the tracks main characteristics such as the language and accessibility features.

All this works pretty well but this system have some limitations. For example, different tracks could have the same exposed characteristics in which case they would have the same name.
Moreover, it severely limits the information you can give to the final user to only those exposed characteristics. What if you want to indicate that an audio track of some sport game only contain stadium noise for example?

The DASH <Label> element

The DASH-IF IOP, a DASH standard we follow, comes to the rescue by authorizing setting a Label element on the corresponding <AdaptationSet>, which contains a description of what the track is about. This is explicitely done to provide a description of the track to the user in the final user interface.

label

Example of Label elements on multiple text AdaptationSets, generally signalling how the track choice should be worded.

This is not limited to DASH either. For example HLS also has a NAME attribute for roughly the same thing (though only DASH's Label is handled for now).

In the RxPlayer API

Now, if <Label> elements are present in a DASH MPD, the RxPlayer will provide a label property through its various tracks API:

Output of a getAvailableTextTracks call in the Chrome inspector's console after playing the content linked to the previously's pictured MPD

In cases where no <Label> element is present on the corresponding <AdaptationSet>, the label property may either not be defined on the track objects or set to undefined.

As any DASH feature, this is available both with our default JavaScript DASH parser and with our WebAssembl...

Read more

v3.27.0

31 Mar 14:34
8651e85
Compare
Choose a tag to compare

Release v3.27.0 (2022-03-31)

The v3.27.0 release has now been published to npm.
It is a big release which includes many features, fixes and improvements, among which:

  • A new option, maxVideoBufferSize, allows to indicate to the RxPlayer that the current device has limited available memory.
    The RxPlayer will then use that value to prevent overflowing it by loading video segments adaptively

  • A new audioTrackSwitchingMode, "reload", allows to work-around some compatibility issues seen on some recent Chrome versions, where audio could be lost after switching audio track in the "direct" mode

  • Safari support has been improved for some advanced usages

  • DRM: the keySystems[].singleLicensePer option now accepts a third license requesting mode: "periods". It enables optimizations on use cases like key rotation and support of targets with a very limited number of key slots available.

  • DRM: the "content" mode of the keySystems[].singleLicensePer option is now more optimized: persistent and non-persistent caches should now lead to more cache hit than before under this mode.

  • Many other bug fixes and improvements, you can refer to the changelog for more information

Configuring a maximum video buffer size

Issues with low-end devices

At Canal+, we use the RxPlayer in production on a large panel of devices. Some of them being real powerhouses without any performance or memory issue, others being more limited on processing speed and memory.
When playing high-bitrate media contents - such as with ultra-high video definition (UHD) - on some devices of that second category, we sometimes entered a situation where the memory of the device would quickly become full as video content was buffered by the RxPlayer, leading to all kinds of performance and media issues.

The situation was particularly problematic on some older smart TVs, where we had temporarily advised application developpers that they should set for now a lower wantedBufferAhead.

This configuration option configures the amount of media content the RxPlayer will pre-load in advance. Setting a lower value thus reduces the amount of loaded media data and thus lowers memory usage, but it also raises the risk of rebuffering.

wba-ex
Image: Difference between a low wantedBufferAhead (left) and a high one (right), we can see that we load more segments in the buffer when that value is high (visible both in the buffered grey part of the progress bar or in the visual representation of the audio and video buffer's content at the bottom - don't mind the color used there, they are randomized)

Moreover, it was a general setting: lower-bitrate video or even audio would also respect this value - even if only high-bitrate video was problematic.
We thus decided to provide a proper answer to this specific problem.

The maxVideoBufferSize solution

The solution we came with was to add a maxVideoBufferSize option to the RxPlayer.

The idea is that the application developers might know in some cases much better than the RxPlayer the limitations of a specific device their application runs on.
Setting a maxVideoBufferSize will thus serve as a hint telling the RxPlayer how much video data, in kilobytes, it should buffer at maximum to prevent most of those memory-related issues.

The RxPlayer will then use this hint to fill the video buffer adaptively:

  • while video data is still below this maxVideoBufferSize limit, the RxPlayer will try to reach the wantedBufferAhead setting, as usual
  • as soon as this memory limit is encountered, the RxPlayer will in most cases stop loading new video data until that limit can be respected again - this happens for example once old video data is garbage-collected.

To implement this feature, the RxPlayer now estimates the storage size used by video media segments present in the browser's buffer as well as of future video segments it wants to download. Additionning those allows to check if the maxVideoBufferSize limit is still respected.

mvbs-ex
Image: Here we're playing the same content with equal wantedBufferAhead and maxVideoBufferSize (10 Megabytes) but different qualities. To the left is a video of a lower quality, to the right a higher one. We can see that the buffer size constructed on the right is much lower. This is here because the maxVideoBufferSize limit was reached. We can also see that on the left, the constructed buffer is roughly equal between audio and video, this indicates that the maxVideoBufferSize was probably never reached before the wantedBufferAhead.

However that type of estimate is not an exact science, the real memory reserved by the browser to handle those segments might actually be higher or lower. The RxPlayer may also decide by itself to still load some video data once that limit is reached if it judges that the buffer built is too low for smooth playback.
As such, this maxVideoBufferSize option only acts as a hint and not a true hard limit.

The maxVideoBufferSize API

This maxVideoBufferSize limit can be set at either one of two places.

Either in the constructor, documented in the constructor options page:

const rxPlayer = new RxPlayer({
  videoElement,
  /**
   * This is expressed in kilobytes, so here this corresponds to 50MB or
   * ~47.68 MiB.
   */
  maxVideoBufferSize: 50000,
});

Or at any time (even as content plays) through a method:

rxPlayer.setMaxVideoBufferSize(50000);

This method's documentation is under this link.

The last set maxVideoBufferSize can be retrieved through the following getter:

rxPlayer.getMaxVideoBufferSize();

This method's documentation is under this link.

Note that by default this value is set to Infinity, which is also the value to set it to to disable this limit.

DRM: A "periods" mode for the keySystems[].singleLicensePer loadVideo option

The singleLicensePer: "content" mode

Last year (v3.24.0), we added the singleLicensePer property to our DRM-related options.
It was added to optimize the playback of contents with multiple decryption keys: by only performing a single license request for all decryption keys instead of the usual one per key, we were able to reduce the time and resources needed to play those contents both on the client and on the server-side.

slpid
Image: By default, the RxPlayer will load a single license per new encryption metadata encountered. This is adapted when the license server emits only the key asked, like illustrated here.

slpc
Image: Through the singleLicensePer: "content" mode we implemented at the time, the RxPlayer will load a single license for the whole content instead. This only works if the license server returns all keys linked to the content at once, and not just the asked one.

Technically, this option changes the RxPlayer's behavior on two aspects:

  1. Only a single HTTP(S) request is performed - even when multiple keys are needed concurrently
  2. It also communicates to the RxPlayer the fact that any key that is not found in the license - yet should have been in it - are keys voluntarily witheld by the license server.
    Thus the Representation (a.k.a. profile or quality) corresponding to those witheld keys should not be played (and be fallbacked from if already buffered).

no-key
Image: In this example, the license request for the video 1080p did not return a key for the 4K video quality. Because we're in a singleLicensePer: "content" mode and thus should have received all available keys, the RxPlayer deduce that the 4K video quality is not decipherable. It will thus avoid this quality and remove it from the video buffer if it was already pushed in it.

This option is used a lot at Canal+, where the majority of customer-facing applications use that "content" mode, so we can play contents faster and put less stress on our back-end.

Inconvenients of this mode

However, performing only a single license request for the whole content has some drawbacks, the main ones being:

  • All keys needed to decrypt any part of the content has to be known in advance, even decryption keys for future sub-contents, because no new license request will be performed in the future (all those keys we didn't know of in the first license request will be assumed to have been voluntarily witheld).

  • Some contents could need a lot of decryption keys and some peculiar devices have a relatively low limit of "key slots" available.
    In that case, it might be problematic to push a single license containing more keys than what the device can handle, leading to all kinds of playback errors.

    _Image: Case of a device with only 3 "key slots" available. Here, if receiving a license containing 4 keys, the device would not be able to use...

Read more

v3.26.2

11 Jan 18:19
d2ad3aa
Compare
Choose a tag to compare

Release v3.26.2 (2022-01-11)

The v3.26.2 release has now been published.

It includes multiple bug fixes and improvements:

  • The RxPlayer has now a better adaptive logic when playing DASH low-latency contents. This is explained with details below.
  • Low-latency contents using a $Time$ indexing scheme, are now better handled
  • The RxPlayer's documentation pages have been updated in depth, now (finally) including search capabilities!
  • The player is now more resilient when parsing an MPD with unreachable optional resources, such as the ones behind <UTCTiming> elements
  • With DASH contents, <ContentProtection> elements can now be declared at the <Representation> level
  • many other small bug fixes and improvements (see the changelog at the bottom of this release note).

We'll now go in more details on some of those improvements.

New adaptive algorithm for DASH Low Latency contents

The low-latency adaptive status

It has now been more than two years since the RxPlayer introduced the lowLatencyMode option, allowing to play efficiently DASH Low Latency contents (often called DASH-LL or LL-DASH).

output.mp4

video: Example of playing the same DASH-LL content without lowLatencyMode (to the left) and with it (to the right). We can see that the right one plays almost ten seconds forward.

However there was one area where we knew we could do much better: the selection of the quality to play a.k.a. the adaptive logic.

The adaptive logic is notoriously an important issue when it comes to playing low-latency contents, due to specificies explained below.
When we started to implement low-latency playback, we tried to devise an algorithm specifically for it, but we didn't get good-enough result for us to go on with it.

What we decided instead was to keep our current less-than-ideal adaptive algorithm (which just risk to play a lower quality than what could be expected) and mostly wait for a more suitable adaptive algorithm to make its apparition. We felt that it was now the right time!

Problems with the current adaptive algorithms

The current adaptive algorithms

Previously, the RxPlayer relied on two kinds of algorithms for its adaptive logic:

  • bandwidth-based algorithms: where we measure the current user's bandwidth by observing how fast media segments are downloading and choose a media quality with the highest bitrate compatible with that bandwidth.

    bwbased
    Depiction of the content of the video buffer when running the RxPlayer's bandwidth-based algorithm only.
    The blue color corresponds to the initial quality (which is lower than ideal), yellow is the ideal one and the red bar corresponds to the current playing position. Here we can see it reaches the ideal quality after some time loading the initial one. This is because it first had to perform bandwidth calculation with that initial quality before settling on the yellow.

  • buffer-based algorithms: where we choose a quality depending on how much data has already been buffered in advance.
    If we have not much data in the buffer, we switch to a low quality to fill it, if however we have a lot of data in advance in the buffer, we can switch to a higher quality without much risks of rebuffering.

    bufferbased
    Depiction of the content of the video buffer when running the RxPlayer's buffer-based algorithm only.
    The blue color corresponds to the initial quality (which is lower than ideal), yellow is the ideal one, green is better than the ideal one and the red bar still corresponds to the current playing position. Here we can see it reaches the ideal quality after more time than with the bandwidth-based approach but also reaches a better-than ideal quality at the end of the buffer, because the buffer has been filled.

Both are widely used type of algorithms which have advantages and disadvantages:

  • bandwidth-based algorithms are often very fast to settle on a good-enough approximate of the ideal quality, yet in its current form are often set to be a little too pessimistic to make-up for potential network latencies and bandwidth variations.

  • buffer-based algorithms allows to reach a higher quality as long as enough data is buffered. Sometimes even better quality than what the current network connection would allow (when enough buffer has been built).
    Yet it is slow to start (as data has to be buffered initially) and needs the ability to pre-buffer data, which is problematic for live contents because there the player plays too close to the live edge to be able to pre-load much of it.

Which is why in normal playback (non-low-latency ones), the RxPlayer will use a mix of the two: in most cases it will consider the best quality between what both kinds of algorithm estimate.

Issues with DASH low-latency contents

Sadly both buffer-based and network-based approachs break down when playing DASH Low Latency contents:

One of the principles behind DASH Low Latency is that small chunks of media data, called segments, can be requested while it is still generated in the back-end.
When that is the case, the request for a segment stays open until it is fully generated and transfered. In that situation, it's difficult to rely on the time it took to perform the request (as we do to perform bandwidth calculations), because a large part of that time might just have been spent "waiting" for the segment to be generated.

Also, the main point of low-latency being to reduce the delay between video/audio capture and its presentation to the user, the RxPlayer will play too close to the live edge to build enough buffer for buffer-based algorithms to be useful.

closetolive
Depiction of a video buffer playing too close to the live edge (the red bar is the current position, the end of the container represents the current live position) to be able to build much buffer for the buffer-based approach

So none of the two previous approaches really worked, leading to a poorer quality thans what could be expected.

Algorithm specific for low-latency contents

As we were not in a hurry, our initial strategy was consequently to wait and see until the technology was more mature.
Some months ago, we noticed that multiple new published algorithms could finally be used for low-latency content specifically, some even having already an implementation in the open-source dash.js media player.

We first tried to implement one of those algorithms, called Learn2Adapt. Yet the implementation was less than ideal due to the difficulty to provide one of its input. When we checked dash.js, which can also rely on this algorithm for low-latency contents, that algorithm was also not satisfying.

After this failure, we redoubled our efforts and we worked on multiple fronts:

  • We tried to implement another algorithm from scratch.
    It was written as a temporary solution which was better than what we had today, but could be less optimal than another, low-latency specific, one.

  • we worked on a separate tool, whose goal was to test our adaptive algorithm in various network conditions (poor bandwidth, high latency, variable conditions etc.) and compare it to other media players (mostly dash.js and the shaka-player).
    benchmark

    Example of a graph outputed by this tool. Between other things, it allows to quickly check the evolution of the video bitrate and of the size of the buffer

Thanks to this approach, we were able to develop a new algorithm with confidence.

That new algorithm was called the "guess-based [quality] chooser".
It may sound scary to have the word "guess" in there, but it translates well what this algorithm is doing: trying, gradually, to guess what the ideal quality is.

The guess-based chooser

The GuessedBasedChooser is actually a class in the RxPlayer that implements the corresponding "guess-based" algorithm.

It relies, among other inputs, on a core "maintainability score" concept linked to each quality.
This score indicates if we seem to be loading that quality faster or slower that the time it takes to play it:

  • if we load it faster, this quality can be maintained without the RxPlayer rebuffering. In that case the quality will have a high maintainability score.
  • if we load it slower than the time it takes to play it, the RxPlayer has a high risk of rebuffering (at some point, we'll have to wait for data to load). Here the quality will have a low maintainability score.

The idea is then to use that score (and other inputs), to know if a quality is maintainable or not, and take the corresponding actions depending on that:

  • if it seems maintainable enough for a long time, we may try the immediately superior quality, we could say the RxPlayer is "guessing" that the immediately-superior quality might be maintainable.
  • it it doesn't seem maintainable (even for a very short time), we might go the other way: switch to the immediately inferior quality. We call that situation "taking a wrong guess", as the current quality was not a "good" (maintainable) guess.

    wrong
    _Example of a wrong guess. The RxPlayer was initially playing the mainta...

Read more