v3.29.0
Release v3.29.0 (2022-11-16)
- π Overview
- π Changelog
- Configurable timeout values for the manifest and segment requests
- DRM: Advanced key expiration handling
- Deprecation of
keySystems[].throwOnLicenseExpiration
option - Better URL priorization algorithm for multi-CDN contents
- Better device compatibility
KEY_STATUS_CHANGE_ERROR
Improvements- Consideration of reverse playback
- v4.0.0 in late alpha
- Experimental "local" transport updates
β οΈ 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
andnetworkConfig.manifestRequestTimeout
options to loadVideo to configure the timeout of respectively segment and manifest requests [#1156] - add
timeout
property to the first argument communicated to asegmentLoader
(fromloadVideo
'stransportOptions
) [#1156] - add
timeout
property to a new third argument communicated to amanifestLoader
(fromloadVideo
'stransportOptions
) [#1156] - DRM: add
keySystems[].onKeyExpiration
toloadVideo
options to configure the behavior the RxPlayer should have on key expiration [#1157] - DRM: add
keyStatuses
property to anEncryptedMediaError
with theKEY_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 newkeySystems[].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 totrue
[#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 considerAudioTrack
with adescription
(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 withfalse
, 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, oneEncryptedMediaError
with aKEY_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 calledseg_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 to0
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 timeout to manifest and segment requests.
That timeout can be set through the networkConfig
option of the loadVideo method by setting two properties:
-
manifestRequestTimeout
: the timeout in milliseconds, after which manifest requests are aborted and, depending on other options, retried.You can set it to
-1
if you would prefer having no timeout, or either set it toundefined
or not set it at all to keep the default timeout, now at 30 seconds. -
segmentRequestTimeout
: the timeout in milliseconds, after which segment requests are aborted and, depending on other options, retried.You can set it to
-1
if you would prefer having no timeout, or either set it toundefined
or not set it at all to keep the default timeout, now at 30 seconds.
For example, if you want to set a timeout of 5 seconds for the Manifest and of 10 seconds for segments, you can write:
rxPlayer.loadVideo({
// ...
networkConfig: {
// ...
manifestRequestTimeout: 5000,
segmentRequestTimeout: 10000,
}
});
Note that segment duration is often more important to define the right timeout than the maximum file size, because in a default adaptive bitrate-selection mode, the RxPlayer already selected the media bitrate corresponding the most to current network conditions.
Special cases: manifestLoader
and segmentLoader
By defining its own manifestLoader
and/or segmentLoader
functions, an application can overwrite the default requesting logic of the RxPlayer.
In that case, the RxPlayer won't be able to determine when the actual request begins and ends. That's why the timeout
is now communicated to both of those API, through a timeout
property in a new third object argument for the manifestLoader
and as a supplementary property for the first argument for the segmentLoader
.
Depending on what you want to do, you may want to exploit them, for example by writing something like;
// Example of a manifestLoader exploiting the communicated
// timeout, based on a XMLHttpRequest
function manifestLoader(url, callbacks, options) {
// ...
const xhr = new XMLHttpRequest();
if (options.timeout !== undefined && options.timeout > 0) {
xhr.timeout = options.timeout;
}
xhr.ontimeout = function onXHRTimeout() {
const err = new Error("Timeouted");
reject(err);
};
// ...
}
// Example of a segmentLoader exploiting the communicated
// timeout, based on a XMLHttpRequest
function segmentLoader(args, callbacks) {
// ...
const xhr = new XMLHttpRequest();
if (args.timeout !== undefined && args.timeout > 0) {
xhr.timeout = args.timeout;
}
xhr.ontimeout = function onXHRTimeout() {
const err = new Error("Timeouted");
reject(err);
};
// ...
}
DRM: Advanced key expiration handling
What is key expiration?
In media streaming, copy-protection solutions (which tries to avoid having people being able to share a content to everyone) generally relies on encryption. The idea is to have the media content encrypted, and to only allow decryption in some specific and controlled situations and environments.
The decryption logic relies thus on a multitude of mechanisms to limit the chance where someone is able to provide a decrypted content.
More advanced solutions even depend on specific hardware where the decryption logic can run in isolation from that device's operating system.
Image: "Generic stack" as showcased in the standard defining the main media encryption set of API for the web: the Encrypted Media Extensions (or EME) recommendation.
Its complexity (yet you're seeing here what is advertised as only an "example flow", the actual implementations often need to be yet a little more complex) allows to adapt to most protection levels and mechanisms available.
Another mechanism is that the decryption keys used to decrypt the content are often linked to an expiration date, after which they won't be able to be used anymore.
When and if it does expires, content will not be able to be decrypted anymore, leading to a playback error.
How the RxPlayer previously handled key expiration
Previously, the RxPlayer did not handle directly the eventual expiration of a decryption key: if it happened, playback most likely stopped with an EncryptedMediaError
being triggered with the code KEY_STATUS_CHANGE_ERROR
.
Screenshot: What actually happens on the RxPlayer's demo page when a decryption key for the current content reaches expiration: playback is stopped and the error is displayed.
This wasn't considered a problem before, because expiration was still relatively rare and also mostly because the RxPlayer relied on a second separate mechanism, license-renewal, which in any case could allow (depending on the lower level implementation) to refresh the decryption key before it expired.
We also enabled applications to avoid encountering an error at all on expiration, with a throwOnLicenseExpiration
option, that could be set to false
to try continuing playback. This option was added in a potential case where the license-renewal mechanism would happen at the same time than key expiration as there, we would not want to throw directly on expiration.
However real-life scenarios, especially those based on long-lived persistent licenses, revealed that expiration actually may happen during playback, without necessarily any license-renewal mechanism being run.
At Canal+, we encountered it while re-playing contents whose key has been persisted (through the opt-in "persistent licence feature). If, during playback, that reloaded key came to expiration, playback was stopped with the afforementioned error and the obligation for the application to reload the content.
A new onKeyExpiration
option
The v3.29.0
brings a new loadVideo
option: `keySystems[].onKeyExpiration.
This new option will allow to configure what happens when an expired key is encountered, among which the reloading of the corresponding license (which is used to obtain the new decryption keys) or even the possibility to fallback to media not linked to the expired keys.
The onKeyExpiration
takes the form of a string that can be set to:
-
"error"
: The RxPlayer will stop on an error when any key is expired. This is the default and exact same behavior than before. -
"continue"
: The RxPlayer will not do anything when a key expires. This is equivalent to setting thethrowOnLicenseExpiration
option tofalse
, option that we introduced in september 2018. -
"close-session"
: The RxPlayer will close and re-create a DRM session (and thus re-download the corresponding license) if any of the key associated to this session expired.
It will try to do so in an efficient manner, only reloading the license when the corresponding content plays.Because the buffer is cleaned AND a license may be reloaded (depending on if the session was associated to something that was played currently), the RxPlayer may go into the BUFFERING state when this is going on.
We also observed on some platforms small decoding issues (macro-blocks, no sound) during 1-2 seconds after the stream started back, so this is not a perfect solution, but it is still better than playback stopping. -
"fallback"
: The Representation(s) linked to the expired key(s) will be fallbacked from, meaning the RxPlayer will switch to other Representation (for example, other video qualities) whose key is not expired.The "fallback" mode is very similar to the behavior of the other fallback-able key statuses which are "output-restricted" and "internal-error", configurable through the
keySystems[].fallbackOn
option.
This way, setting onKeyExpiration
to "close-session"
or "fallback"
, depending on what you want to do, may allow to still continue playback even in the case of a key being expired.
Deprecation of keySystems[].throwOnLicenseExpiration
option
Because the keySystems[].throwOnLicenseExpiration
option is completely replaceable by the newly-introduced keySystems[].onKeyExpiration
option, the former has been deprecated.
It can be easily replaced. For example, if previously you had:
rxPlayer.loadVideo({
// ...
keySystems: [ {
throwOnLicenseExpiration: false,
} ],
});
You can now replace it with:
rxPlayer.loadVideo({
// ...
keySystems: [ {
onKeyExpiration: "continue",
} ],
});
Setting keySystems[].throwOnLicenseExpiration
to true
was actually the default so you don't have to do anything to replace it, you can just remove the usage of that option in that case.
Better URL priorization algorithm for multi-CDN contents
Incoming work to implement Content Steering
For Canal+ and our partners, we recently worked on optimizing reliance on multiple CDN to distribute the same content.
The idea was be that a Manifest (for example, the DASH's MPD file) could declare multiple potential URL for resources and that the RxPlayer would select one to request based on a variety of criteria each time.
This has already been supported by DASH and at some level by the RxPlayer (since the v3.19.0
, in march 2020), but it is the very recent work performed by the DASH Industry Forum towards what is called "Content Steering" (which also exists in HLS) that picked our interest.
Schema: Simplified principles for general (not Content Steering) multi-CDN configurations. Here the idea is that the content is available through multiple CDN and the RxPlayer just chooses one based on its own criteria.
The idea behind the newer Content Steering specification is to provide from the server-side a separate document which will indicate an order of priority in which CDN should be tried for any resource. I wrote "order of priority", implying that multiple may be tried each time, for a reason: a player can switch to the next prioritized CDN if the previous one was unable to provide the asked resource.
The file can also plainly tell that a CDN shouldn't be requested by not prioritizing it.
Schema: Example under a Content Steering scenario. In this example, the distributed Content Steering document, called the "Content Steering Manifest", tells the RxPlayer to prioritize CDN A, and if it fails, CDN B.
The difference to the old multi-CDN scenario is that here, the priorization is performed server-side instead of by the RxPlayer.
Such server-side control of CDN priorization looks very interesting for us. One could for example dynamically reorder CDN depending on the load experienced on each one, switch CDN in case of an accident while playback is still pending, only authorize some CDN in some conditions, prioritized the closest CDN to a given customer and so on...
Consequently, even if the specification was not finalized (and as I'm writing those lines, is still not), we began to implement it. The idea was to do it as early as possible to exchange with people writing the specification on things we remarked and ideas we had while implementing the feature, before the specification was finalized, at which point it becomes much more difficult to influence.
Our initial work on Content Steering was finished but not yet added to the v3.29.0
, as we're still waiting for the specification to be finalized before releasing it officially.
What's actually being released: better CDN priorization
Even though Content Steering is not yet implemented in the v3.29.0, we profited from our work on it to improve the RxPlayer's priorization algorithm when playing contents whose media segments are available on multiple CDN.
Previous logic
Before this release, the RxPlayer had a very simple algorithm when playing those types of multi-CDN contents.
The idea was to test for each resource the CDN in the same order: the first one declared, then if it fails, the second and so on.
The CDN choice for a given resource was not persisted among resource. This means that even if the most prioritized CDN is currently unavailable, the RxPlayer will still try it for every new request
Schema: Simple schema representing the old logic. The same CDN are tried in the same order for each resource.
Note that there's no waiting delay between requests on different CDN.
That is, if a request on a first CDN fails, the RxPlayer may perform the request on the second CDN immediately.
However, once every CDN has been tried and as such an already-tried one was to be retried (a CDN may or may not be retried based on complex rules such as the HTTP status of the original response), a delay was applied starting from the last time that CDN was requested, to avoid overloading it.
That delay grew exponentially each time that same CDN returned with failure, the corresponding algorithm bearing the name "exponential backoff".
The new logic
Now, a CDN answering with an error will result with that CDN being temporarily "downgraded" for 60 seconds.
This means that as long as other CDNs are available for that resource, the failing one won't be retried for other resources until those 60 seconds have elapsed.
If all of a resource's CDN are downgraded, they are still retried in the same original order. An updated version of our original prioritizing algorithm is here still present to continue trying each CDN in that unfortunate case.
The exponential backoff algorithm is also still present.
Schema: Schema representing the new logic. Now the previous failures of a CDN are considered when requesting a new resource. In that example, "CDN A" is avoided because it failed recently.
Keeping the previous algorithm and adding on top our multi-CDN priorization layer allows to profit from the exact same logic as before for cases where only one CDN is present, which represents the huge majority of DASH contents today.
Note that, to properly implement that feature, we needed to identify what constituted a particular "CDN" in a DASH MPD.
For those familiar with DASH semantics, we rely for now on the different URL combination we may obtain when combining any DASH Representation's BaseURL
. If multiple combinations exists, they will be considered as different CDN.
Better device compatibility
Those last months, we also worked a lot toward improving the RxPlayer's support for specific devices, mostly for LG and Samsung TVs, the PlayStation 4 and 5, XBox, windows applications, and some others.
Though we often fix some compatibility issues at each release, the number of such fixes is particularly large in this release. It includes:
-
When playing some "directfile" contents in some environments (lastly seen on the PlayStation 5, but it has been seen on Chrome desktop at some point), the RxPlayer may stay indefinitely in
"BUFFERING"
, even if the content appears to play.
This is because when playing those, the browser does not communicate about how much data has been buffered: it is as if nothing is buffered at all.To work-around that issue, the RxPlayer now specifically handles the case where a directfile content appears able to play despite having apparently no data buffered, by mainly:
- relying mainly on the
readyState
attribute of theHTMLMediaElement
to know if the content can start to play or not, and - relying on position updates to know when rebuffering is active or not.
- relying mainly on the
-
On some LG TVs only, and only when playing two consecutive directfile contents, the second one would never play if:
- the stopAtEnd constructor option was set to true
- the second content was loaded immediately once the RxPlayer switched to the "ENDED" state.
This was due to a small race condition on the browser implemented on those devices where an
"ended"
event would still be triggered through aHTMLMediaElement
even if the media changed since the previous one ended.To fix that issue, we changed inner RxPlayer logic so it is less sensible to those kind of race conditions.
-
We noticed of an issue on WebOS (LG TV) 2021 and 2022 models where loading an already-loaded encrypted content would not succeed: it would load indefinitely.
This was due to what seems to be a issue on the browser integration of that device linked to a DRM optimization performed by the RxPlayer.
We contacted LG about it, but we still chose to disable the corresponding optimization on those models which makes the issue disappear. This means that encrypted contents may take more time to load than before on LG 2021 and 2022 LG TVs -
On some LG TVs, setting a timeout through a XMLHttpRequest's
timeout
property didn't have any effect. To work around that, timeout are also added manually on those types of requests. -
Some Samsung TVs had some (but not all) directfile contents playing without sound.
The error was linked to a workaround previously added to improve support for the Safari browser. We fixed the issue by disabling the workaround on LG TVs.
-
On Samsung TV, we [again] encountered some infinite rebuffering cases when gaps in a content are encountered, especially if it was at the content's start. Those have been [painfully] fixed.
-
When playing a
directfile
content on Safari, audio-description tracks were not announced as such when playing.
Funnily enough, this was due to a small typo on Safari-side, where the"description"
kind
was set when it should have been"descriptions"
, with an "s".The RxPlayer now considers both syntaxes.
KEY_STATUS_CHANGE_ERROR
improvements
The KEY_STATUS_CHANGE_ERROR
code is the error code returned when a decryption key switches to a problematic state. It may either lead to playback being stopped, in which case it is sent through an "error"
event (and gettable through the getError
method), or be a more minor error that will still generally influence playback - for example by "fallbacking" to different video qualities - in which case this error is sent through a "warning"
event.
The problematic state the key switched to corresponds to one of the MediaKeyStatus defined in the Encrypted Media Extensions specification, such as "expired"
for when the key expires, "output-restricted"
, for when the key cannot be used due to unmet restrictions or "internal-error"
due to other uncategorized errors.
Previously, the only way to tell which of these states were encountered were by reading the corresponding error's message, which moreover is not part of the stable API, meaning that it could change at any time.
Because an application may want to know what status was actually encountered and even to know which Representations it is linked to, for example for debugging or reporting purposes, the v3.29.0
now adds a new keyStatuses
optional property to EncryptedMediaError
errors, which is the parent Error type for the KEY_STATUS_CHANGE_ERROR
error.
That keyStatuses
property should be set for most, if not all, KEY_STATUS_CHANGE_ERROR
errors and is an array of objects containing two properties:
keyId
(ArrayBuffer
): The key id concerned by the status change indicated bykeyStatus
keyStatus
(MediaKeyStatus
): The problematic key status encountered linked to thekeyId
of the same object.
Here's an example with code of how this may be exploited:
rxPlayer.addEventListener("error", onError);
rxPlayer.addEventListener("warning", onError);
/** Log problematic key status if a `KEY_STATUS_CHANGE_ERROR` is encountered. */
function onError(err) {
if (
err.type === "ENCRYPTED_MEDIA_ERROR" &&
err.code === "KEY_STATUS_CHANGE_ERROR" &&
err.keyStatuses !== undefined
) {
for (const keyStatusObj of err.keyStatuses) {
console.log(
`Error: the key id ${arrayBufferToHex(keyStatusObj.keyId)} has been ` +
`updated to the problematic ${keyStatusObj.keyStatus} MediaKeyStatus`
);
}
}
}
/**
* Simple utility function to convert an ArrayBuffer into a more readable string
* with hexadecimal characters.
* @params {ArrayBuffer} buffer
* @returns {string}
*/
function arrayBufferToHex(buffer) {
const hexResult = []
const u8 = new Uint8Array(buffer);
for (let i = 0; i < u8.length; i++) {
hexResult.push(u8[i].toString(16).padStart(2, "0"));
}
return hexResult.join("");
}
Consideration of reverse playback
We worked recently with an application which tries to implement a feature allowing to play in reverse, by behind the hood performing multiple small seeks backward regularly.
It worked poorly, because the RxPlayer has multiple optimizations that makes it automatically perform very small seeks forward if data is available very close to an initially seeked position. When you want to perform small seeks backward, this might accidentally provoke infinite seeking loops.
Because we want to better handle that legitimate usage, we now disable most of those forward-seeking optimizations if the playback rate (updated through the rxPlayer.setPlaybackRate method) has been either set to 0
or a negative value (note however that setting it to a negative value is poorly handled on most browsers, in which case doing that call lead to an error being thrown).
output_reverse_playback.mp4
Video: The same scene played both under "regular" playback and then in reverse x1 playback, implemented by setting the playback rate to
0
and performing frequent backward seeks.
v4.0.0 in late alpha
Those last months, we also continued to work a lot on the future v4.0.0 release.
After a lot of work being done in the background for it (we started working on it more than a year ago), we began releasing alpha versions on npm
, under the next
tag.
alpha
meaning that the API might still see some major changes as we're finalizing it.
Because we're coming close to relative API stability by testing it internally at Canal+, we also come close to its first beta
release.
To communicate about this beta
, we plan to announce it through a tag on GitHub, like we do for our regular releases (we will still flag it as a pre-release). We will try to keep the release note short and instead link to both a migration guide (to migrate from the v3.x.x) as well as its complete API documentation.
When that moment comes, and if you have the time, don't hesitate to test it on your side and to open issues if you notice any bug or have any remark.
Experimental "local" transport updates
This release updates the experimental LOCAL
feature, to fix bugs linked to duration estimation and discontinuity handling of still-downloading "local"
contents.
The "local"
transport
The "local"
transport is an experimental RxPlayer feature (meaning it can change at any release) which allows to play downloaded contents.
It was written to allow the consideration in the RxPlayer of the specificities of such contents (for example, those contents may not be sequentially downloaded, unlike most other types of contents) while allowing the downloading logic to be implemented separately, and most-of-all staying agnostic of the storage mechanism used, because it may be completely different depending on the device.
This feature is already used on some devices, with their own downloading logic.
What actually changed: the new incomingRanges
property
I will start directly with what changed, so you don't have to read all the reasons behind it if you don't want to!
It is now advised to add an incomingRange
to "representation objects" of Local Manifests.
This object is an array of objects containing two properties "start"
and "end"
, which are the start and end in seconds corresponding to the time ranges of all segments that have not yet been downloaded.
You can refer to the Local Manifest documentation for more information and examples.
Adding this object is not mandatory but may lead to a wrong duration estimation and poor discontinuity handling when playing contents that are still being downloaded.
Issues encountered
We knew of two issues with the "local"
transport:
- Since the
v3.28.0
, the duration announced for not-yet finished local contents through thegetVideoDuration
API (or to theduration
property of the media element) was absurdly large - Since forever discontinuities/gap/holes were never skipped in local contents, unless they have been completely loaded.
Both of those issues only affect local contents that are still being downloaded, once the downloading is finished, none of those issues occurred.
The duration issue
Since the v3.28.0
, we updated the way a content's duration is estimated, by changing it adaptively in function of the currently-chosen audio and video tracks.
When doing that, we wrongly considered all "dynamic contents" (which in the RxPlayer code, designates contents for which new segments might be available in the future - or which might have other evolution) as if there were all live contents. Such contents had their duration set to a very high value, to unlock features such as being able to seek far away in the stream.
Because local contents that are still being downloaded are "dynamic", playing one led to a huge duration being advertised.
To fix this, we defined globally (and not just for the "local"
transport) a difference between the last position that is currently available for any type of content and the last position that will be available once the content has been fully loaded until its end.
The estimate of the content's duration now uses the latter indicator if defined
This has the nice side-effect of also setting the end of live contents whose end is already known in advance (though I'm unsure if this is a frequent occurrence).
The discontinuity issue
This one is actually difficult to explain for those not deep into the RxPlayer's code. I'll still try to be as accessible as possible in my explanation.
Discontinuity handling inside the RxPlayer
Basically, when the RxPlayer encounters the following situation in its audio and/or video buffer:
|=====----=======|
^
^ current position
= segment
- no segment
|---| buffer
It will detect that an hole is to be encountered in the buffer, perhaps leading to a rebuffering, and to an infinite rebuffering if that hole will never be filled.
To avoid the latter situation, the RxPlayer tries to determine if that hole:
- corresponds just to a segment that is not yet downloaded, or
- does not correspond to any segment that ever will be loaded
In the second case, the RxPlayer will skip that hole, now called "discontinuity" in the code, to prevent an infinite rebuffering situation.
To determine if a segment will ever correspond to that hole, the RxPlayer mainly looks at available segments in chronological order. If there's no segment that can be requested to fill that hole, but there's one starting after the hole, then chances are that the hole will never be filled.
Problems with non sequentially-loaded contents
This works quite well for most contents because new segments are generally either all available directly (VoDs) or available in chronological order (live contents).
For local contents that are still being downloaded however, the tool doing the downloading might decide to load segments in any order it chooses. For example, in response of a user seeking in the stream while it is still being loaded, the downloading tool might decide to prioritize later segments at the time of that seek instead, the user can then seek back, leading the downloading tool now loading earlier segments.
Under those conditions, it becomes much harder (or even impossible) for the RxPlayer to tell if a hole will correspond to a segment available in the future for a non-finished local content if it doesn't know about which segments will be available in advance and if there's no guarantee that segments are made available in chronological order.
Discontinuity handling was thus disabled before this release for non-finished local contents.
Solution: incomingRanges
Yet, now that we have the incomingRanges
array defined, we can know which segments will be available in the future.
The RxPlayer is thus now able, even for non sequentially-loaded contents, to determine whether a hole seen in a buffer may be filled by a segment in the future, just by looking if it corresponds to one of the range announced in the incomingRanges
array.
If incomingRanges
is not defined however, the RxPlayer will continue to wait until "local"
contents are completely loaded before skipping discontinuities.