Running in MultiThreading mode
Overview
The RxPlayer can rely on a
WebWorker
to run its
main logic, effectively running in a separate thread from your application.
Here is some high level schema of how the RxPlayer roughly works without and with a WebWorker:
Running the RxPlayer without a WebWorker (the default):
+-------------------------------------------------------------------------------+
| Main thread (also running the UI) |
| |
| +------------------+ +----------------------+ +----------------------+ |
| | Your Application | --> | RxPlayer Main [1] | -> | RxPlayer Core [2] | |
| +------------------+ +----------------------+ +----------------------+ |
+-------------------------------------------------------------------------------+
Running with a WebWorker:
+----------------------------------------------------+
| Main thread (also running the UI) |
| |
| +------------------+ +----------------------+ |
| | Your Application | ---> | RxPlayer Main [1] | |
| +------------------+ +----------------------+ |
+--------------------------------------|-------------+
| (messages)
+--------------------------------------|-------------+
| WebWorker V |
| +----------------------+ |
| | RxPlayer Core [2] | |
| +----------------------+ |
+----------------------------------------------------+
[1] RxPlayer Main: Exposes an API to your application, performs some high-level
media monitoring, handles content decryption, displays text tracks and interacts
with web API that are only usable in the main thread.
[2] RxPlayer Core: Loads and parses the Manifest as well as media segments that
will be be played. Also monitors what's being played and will be played to
ensure a smooth playback.
This can be interesting for performance reasons, especially if you encounter one of the following situations:
-
Your application run on devices on which you can encounter performance issues. Here loading contents in a multithread mode might bring a better experience, such as a more responsive application and less rebuffering.
We also noticed on various smart TV devices targeted by Canal+ noticeably far fewer unexpected media quality drops (and consequently, a much more stable video quality). This is because most of those drops were linked to heavy processing performed by the full application, which has an effect on the RxPlayer's adaptive and networking logic.
When running in "multithread" mode, performance issues coming from the application will influence much less negatively the RxPlayer code (and vice-versa), as its main logic will run in a separate thread.
-
Your application is very dynamic and you cannot afford to have a big media buffer (for example because you play live contents, because you're playing very high quality content on a device with limited memory or both).
Here the issue is that lengthy computations started by your application may prevent temporarily the RxPlayer from loading new media data. If the media buffer is small to begin with, we may be left with playback being frozen if the end of the buffer is reached before the RxPlayer had time to load new media data.
When running in "multithreading" mode, loading segments (and in browsers supporting the feature, even buffering segments) is perfomed concurrently to any computation linked to your user interface, ensuring that this important buffering operation can run without being blocked.
If you do not encounter those situations, the advantages of "multithread" mode may be less evident. In any case, you may want to test that mode to see if it is improving your application.
About it being an "experimental" feature
This "multithread" mode is added as an "experimental" feature.
Like all other experimental features, it is considered stable enough to run in production. What we mean by "experimental" in the RxPlayer API is just that we may change its API at any RxPlayer version without impacting the RxPlayer's major version due to semantic versionning principles.
For example, a new minor RxPlayer version could bring with it a complete API change
regarding the corresponding MULTI_THREAD
feature. Still, potential changes would be
fully documented on that release's release note and changelog file.
The choice of labeling this feature as experimental has been made so we can have more freedom if we find ways to provide sensible improvements to its API in the future.
Quick start
You can just test this feature quickly by running the following code:
// Import the RxPlayer (here the minimal version, but it also works with the
// default import)
import RxPlayer from "rx-player/minimal";
// Import the MULTI_THREAD experimental feature
import { MULTI_THREAD } from "rx-player/experimental/features";
// To simplify this example, we'll directly import an "embedded" version of the
// supplementary code loaded by the `MULTI_THREAD` feature.
// We could also load it on demand through an URL
import { EMBEDDED_WORKER } from "rx-player/experimental/features/embeds";
// Add the MULTI_THREAD feature, like any other feature
RxPlayer.addFeatures([MULTI_THREAD]);
// Instantiate your player as usual
const player = new RxPlayer(/* your usual options */);
// After instantiation, you can at any time "attach" a WebWorker so any
// following `loadVideo` call can rely on it when possible.
player.attachWorker({ workerUrl: EMBEDDED_WORKER }).catch((err) => {
console.error("An error arised while initializing the worker", err);
// Note the if `attachWorker` rejects, the next `loadVideo` / `reload` calls
// will not rely on the "multithread" mode anymore.
//
// However the last-loaded content may fail on error if it was already
// loading in "multithread" mode.
});
// Any further call to `loadVideo` may rely on WebWorker if possible (see
// limitations below)
player.loadVideo({
/* ... */
});
// As a long as a content is loaded (or even loading or reloading), you can know
// if we rely on a WebWorker to play it by calling:
const currentModeInfo = player.getCurrentModeInformation();
if (currentModeInfo === null) {
console.info("No content loaded."); // Note that this may also happen when an
// error prevented the content from loading.
} else if (currentModeInfo.useWorker) {
console.info("We're running the RxPlayer's main logic in a WebWorker!");
} else {
console.info("We're running completely in main thread.");
}
Limitations
Note that the "multithread"
mode will only run on a loadVideo
call if all the
following conditions are respected:
-
Your supported platforms are compatible to the
WebWorker
browser feature (the great majority are). -
You've added the
MULTI_THREAD
feature to theRxPlayer
class (as shown in examples) before thatloadVideo
call. -
You've called the
attachWorker
RxPlayer method (as shown in examples) before theloadVideo
call on that same RxPlayer instance, and the returned Promise is either still pending or resolved (i.e. it hasn't rejected). -
You're playing a DASH content (through the
"dash"
transport
option of theloadVideo
call). -
You're not using any of those unsupported
loadVideo
options:-
manifestLoader
-
segmentLoader
-
-
If using the
representationFilter
loadVideo
option is defined, it is under a string form (see corresponding documentation page. -
You did not force the
"main"
mode through themode
loadVideo option. -
You did not dispose the RxPlayer
If any of those conditions may not be respected by your application, you might also want to be able to rely on the usual "main" mode (which runs everything on main thread).
Thankfully, this is very easy to do:
-
If you rely on the minimal build of the RxPlayer, ensure you've added the feature for the wanted streaming protocol(s).
For example to both be able to play DASH contents on the main thread and through a WebWorker, you may do:
import RxPlayer from "rx-player/minimal"; import { MULTI_THREAD } from "rx-player/experimental/features"; import { DASH } from "rx-player/features"; RxPlayer.addFeatures([ // Allow DASH playback on the main thread DASH, // Allow DASH playback in "multithread" mode MULTI_THREAD, ]); // ...
-
If you rely on the default RxPlayer build, you've nothing special to do, the
DASH
andSMOOTH
features being already imported by default.
Also note that the addFeatures
call and the attachWorker
call may be performed in any
order and at any point in time, even after some contents have already been loaded. This
can for example allow dynamic importing of the MULTI_THREAD
feature after some contents
have already been played on the main thread.
How to rely on "multithread" mode
Step 1: Obtaining the Worker file
The RxPlayer will need to be able to obtain an external file to be able to run in
multithread mode, worker.js
, which is the code running in a WebWorker concurrently to
your application.
You can find it at any of the following places:
-
The easiest way is to just import in your application its "embedded" version, exported through the
"rx-player/experimental/features/embeds"
path:import { EMBEDDED_WORKER } from "rx-player/experimental/features/embeds";
This allows to bypass the need to store and serve separately that file.
If you would prefer more control and a smaller bundle size, you may instead consider the other following ways to it as a separate file.
-
With every release note published on GitHub as
worker.js
(you should only use the file linked to the RxPlayer's version you're using), -
It is also available as
dist/worker.js
from the root directory of the project published on npm. As such, it might already be found in your project's directory, for example in thenode_modules
directory (most probably innode_modules/rx-player/dist/
depending on your project).
Optional: Obtaining DASH WASM parser
Optionally, for very specific use-cases where you've seen improvements with it, you can also rely on our DASH WebAssembly parser in those "multithread" scenarios. Note that this is unneeded for most usages.
To do this, you have to explicitely provide the corresponding WebAssembly file. Like for the worker file, it can either be found:
-
As an "embedded" version, exported through the
"rx-player/experimental/features/embeds"
path:import { EMBEDDED_DASH_WASM } from "rx-player/experimental/features/embeds";
Note however that the embedded version of this WebAssembly file is much bigger than the original file. If it is an issue for you, you might want to store and serve the WebAssembly file itself instead.
-
With every release note published on GitHub as
mpd-parser.js
(like forworker.js
, only use the one linked to the RxPlayer's version you're using), -
It is also available as
dist/mpd-parser.js
from the root directory of the project published on npm.
Step 2: importing the MULTI_THREAD
feature and adding it
The MULTI_THREAD
feature, then needs to be imported through the
rx-player/experimental/features
path and added to the RxPlayer's class (and not an
instance, addFeatures
being a static method), like any other feature:
// Import the RxPlayer
// (here through the "minimal" build, though it doesn't change for other builds)
import RxPlayer from "rx-player/minimal";
import { MULTI_THREAD } from "rx-player/experimental/features";
RxPlayer.addFeatures([MULTI_THREAD]);
The point of this step is to provide to the RxPlayer the logic to "link" to a WebWorker. It is a form of dependency injection. That code is not present by default in RxPlayer builds to reduce bundle size when that feature isn't needed.
Step 3: Attaching a WebWorker to your RxPlayer instance
After instantiating an RxPlayer
, you can link it to its own WebWorker
by calling the
attachWorker
method on this instance and by providing the worker.js
file (and
optionally the mpd-parser.wasm
file if you wanted WebAssembly capabilities) by providing
either its URL (obtained in step 1) or its embedded version:
const player = new RxPlayer({
/* ... */
});
player
.attachWorker({ workerUrl: URL_TO_WORKER_FILE })
.then(() => console.log("Worker succesfully attached!"))
.catch((err) => {
console.error("An error arised while initializing the worker", err);
});
// NOTE: With the WebAssembly file, it would have been:
// player.attachWorker({
// workerUrl: URL_TO_WORKER_FILE,
// dashWasmUrl: URL_TO_DASH_WASM_FILE,
// })
// // ...
As you can see, attachWorker
returns a Promise resolving once the Worker attachment
succeeded. It is not necessary to await this promise to load a content in "multithread"
mode.
If you don't await its returned promise before loading a content in "multithread" mode
however, note that in the rare scenario where that Promise would reject (for example if
the workerUrl
or dashWasmUrl
are not accessible or if security mechanisms prevents the
initialization of the Worker), the player may fail to load that content (in which case it
will trigger an "error"
event for it). Once the Promise rejects, the RxPlayer, won't try
to load in "multithread" mode anymore (excepted cases where attachWorker
is called
another time and if the mode
loadVideo
option has been
set to "multithread"
).
Consequently, unless you want to optimize the loading time of the first loaded content, it
can be considered safer and simpler to just await attachWorker
's returned Promise before
loading a content:
const player = new RxPlayer({
/* ... */
});
try {
await player.attachWorker({
workerUrl: URL_TO_WORKER_FILE,
dashWasmUrl: URL_TO_DASH_WASM_FILE,
});
console.log("Worker succesfully attached!");
} catch (err) {
console.warn("An error arised while initializing the Worker", err);
}
// This loaded content may only load in "multithread" mode if `attachWorker`
// succeeded
player.loadVideo({
/* ... */
});
Step 4: Load a content
That should be all!
Now all contents that are compatible with the "multithread" mode should rely on the WebWorker. You can look at the limitations listed on this page to know what are the exact conditions and how you can still rely on the regular "main" mode for other contents.
You can know at any time whether a loaded content is relying on a WebWorker ("multithread"
mode) or not ("main" mode), by calling the
getCurrentModeInformation
method:
const currentModeInfo = player.getCurrentModeInformation();
if (currentModeInfo === null) {
console.info("No content loaded."); // Note that this may also happen when an
// error prevented the content from loading.
} else if (currentModeInfo.useWorker) {
console.info("We're running the RxPlayer's main logic in a WebWorker!");
} else {
console.info("We're running completely in main thread.");
}
Note about ES5 Environment
The Worker file will use es2017 features which should be compatible to a majority of devices.
However, some older devices might not be compatible with ES2017 yet still compatible to
the WebWorker API (and thus could theoretically be compatible with the MULTI_THREAD
feature).
If you need to provide support for the MULTI_THREAD
feature on those platforms, we
recommend that you use a transpiler tool on that worker file to make it compatible to ES5.
Examples of transpiler tools are babel and swc.