v3.31.0
Release v3.31.0 (2023-06-14)
- 🔍 Overview
- 📑 Changelog
isContentLoaded
andisBuffering
methodisPaused
method"play"
and"pause"
eventstrackInfo
property on someMediaError
getLastStoredContentPosition
method- Adaptive tweaks
- "forced" subtitles on Safari
- Better "direct"
audioTrackSwitchingMode
- Better documentation: soft navigation
🔍 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
andpaused
events,getLastStoredContentPosition
and thetrackInfo
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
, andgetLastStoredContentPosition
methods [#1248] - Add
play
andpaused
events [#1253] - Add
trackInfo
property to someMediaError
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 themonospace
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
.
-
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 themanualBitrateSwitchingMode
option was set to"reload"
)
3. When stoppedBecause 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 returntrue
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.
-
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 returntrue
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.
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 allow this, two new RxPlayer events have been added:
"play"
(for when playback is resumed after being paused) and"pause"
(for when playback is being paused).
play_pause_events.mp4
Video: RxPlayer's demo page with a console opened logging "play"
and "pause"
events. We can see it corresponds to when the play/pause button is clicked in this case.
Note that this event is only sent when exiting or entering a pause from the time the content has been loaded by the application (e.g. from the loadVideo
or reload
call). As such, it won't be sent for the initial isPaused
value right after loading the content. Here, isPaused
should return false
if the autoPlay
option has been set or true
if not.
The play
event is documented here and the pause
event is documented here.
trackInfo
property on some MediaError
Errors and linked tracks
There's a vast list of errors that the RxPlayer may trigger when encountering minor (through "warning"
events) or major (through "error"
events) issues.
Some of those errors make more sense when linked to a track.
For example, when receiving an error with the "MANIFEST_INCOMPATIBLE_CODECS_ERROR"
code, indicating that a track had none of its Representation (i.e. quality) in a codec compatible to your device, it would be nice to know whether we're talking about an audio or video track, which characteristics has this track, and so on.
The trackInfo
property
To describe characteristics of tracks linked to some errors, the v3.31.0
release adds a trackInfo
property to some MediaError
errors, only when its code
property is set to either:
- "NO_PLAYABLE_REPRESENTATION"
(unplayable track based on DRM policy non-compliance)
- "MANIFEST_INCOMPATIBLE_CODECS_ERROR"
(track with unsupported codec)
- "BUFFER_APPEND_ERROR"
(error while pushing a segment)
- "BUFFER_FULL_ERROR"
(error while pushing a segment which seems specifically linked to that buffer not having enough memory space left)
That trackInfo
property provides characteristics about the track linked to the error, for example whether it is a video or audio track, its codec(s), dynamic range information, language, accessibility features and so on.
It is documented in the Player Errors documentation page.
getLastStoredContentPosition
method
The v3.31.0
version also adds a very specific method, getLastStoredContentPosition
, which returns the last position stored by the RxPlayer for the last loaded content.
This description might seem cryptic, and its difference to the much more useful getPosition
method may initially appear unclear, but there is a simple reason why both may be useful at different point in time.
getPosition
is very useful when a content is loaded (i.e. not in the "STOPPED"
, "LOADING"
or "RELOADING"
player state).
But when no content is loaded, it just returns 0
.
However there might be cases where you want to obtain the last known position of a content the player was at just before it actually stopped (or reloaded).
One frequent such cases is when encountering an error: you might want to know at which playback position the user was when encountering the error, but when the "error"
event is triggered, playback is already stopped and as such, getPosition
will already return 0
.
getlaststoredcontentposition.mp4
Video: Example of usages of both the getPosition
and getLastStoredContentPosition
methods. We can see that the former returns 0
when the content is stopped while the latter returns its last known played position.
The getLastStoredContentPosition
method thus allows to know which position was reached the last time the last content was considered loaded. If no content was loaded since the creation of the RxPlayer
instance, it will return undefined
.
This method is documented here.
Adaptive tweaks
The v3.31.0
was also the occasion to improve our "adaptive logic" (also commonly called "ABR" for Adaptive BitRate).
The adaptive logic is the part of the RxPlayer which will select the right Representation (a.k.a. quality) depending on the user's playback condition, one of the most important factor being the calculated network bandwidth.
We noticed that for some rare users, the Representation selected by that logic was changing too frequently. We try to prevent frequent quality switches because it generally leads to a less enjoyable experience than just staying on a stable one. It also may lead to some buffering cases in some extreme situations explained below.
More non-urgent quality switches
When the RxPlayer's adaptive logic decides that we need to switch to another quality it can ask to do so in one of two ways:
-
Switch to the wanted quality immediately, aborting requests for the previously-selected quality. That's what we call an "urgent" switch.
This can be wasteful because the segment's request might have been close to be finished, but it allows a quicker quality transition.
-
Switch to the wanted quality only once the requests for the previous ones have finished. That's what we call a "non-urgent" switch.
Here we will consequently "wait" a little before switching the quality but requests which already have been started and soon finished will be pushed, reducing rebuffering risks.
urgent-switch.mp4
Video: Here after loading a content, we can see that seg-4.m4f
and seg-9.m4f
are set to canceled
in the Status
column (this table is part of Google Chrome's inspector). They both have been aborted here because of an urgent quality switch.
Before, the choice of whether we should perform an urgent or non-urgent switch was based on a complex logic using many inputs:
- How much buffer we have left
- When the current requests started
- The expected duration of requests in the previous quality
- The difference between the previous and new quality
The logic seemed technically logical most of the time, but it had a risk of leading to rebuffering in some situations. For example, if for some reasons you alternated between multiple qualities where each switch is an urgent one, you might be left in a situation where requests keep being interrupted.
To limit that event from occuring, the v3.31.0 version of the RxPlayer now only performs non-urgent switches when raising up in quality. The logic being that if we raise up in quality, the request for the previous one should finish suffliciently fast and should not lead to rebuffering.
Buffer-based logic with more guards
The RxPlayer adaptive logic is mostly based on:
- bandwidth calculations - through what we call a bandwidth-based algorithm - and
- on observing how the buffer fills up at various quality - through what we call a buffer-based algorithm.
Each algorithm has its strengths and weaknesses, which is why the RxPlayer uses both, generally mostly relying on the bandwidth-based algorithm when there's few data in the buffer and on the buffer-based once the buffer is filled-up.
The buffer-based algorithm has a particularity that it can lead to a better quality than what the user could be expected to have based on its network-throughput. This is not a problem in itself, but it could lead to many quality transitions if the currently-chosen quality is very high relative to the user's bandwidth.
Thus, we now added guards in our buffer-based logic, ensuring that we do not go to high in quality if the user's bandwidth can't keep up, even if we have a lot of buffer. We do that by observing whether each chosen quality is "maintainable" (meaning that it can be loaded faster than it can be played). If it isn't, we will forbid further quality raise.
Updated detection for times where the bandwidth fall
We have a logic detecting sudden fall in bandwidth while a segment is still being loaded.
Previously, if we had not much data left in the buffer AND if the pending request for the next needed segment led us to think that we will rebuffer for at least 2 seconds, we re-calculated a quick-and-dirty bandwidth based on that last request's progress alone.
This led to a temporary very poor (as in: imprecise) bandwidth calculation, which was only corrected once enough new segments where loaded.
This seems fine but was actually a source of a high rhythm of quality fluctuations in some unstable networks, which we want to avoid.
The bandwidth fall detection is now triggered much less often, and we will only reset to a poor bandwidth calculation if the situation appears to be desperate enough (several seconds of estimated rebuffering time and the request was pending for much more time than expected).
This may mean that the RxPlayer will realize later when the bandwidth truly has immediately fallen, but it has also the bigger advantage of not risking an oscillating quality only because a single request took more time than expected.
More pessimistic bandwidth-based algorithm in general
When producing bandwidth estimations, we actually use a "factor", which we multiply with the currently-calculated network bandwidth to obtain the quality we should play.
Previously, the value of this factor depended on the size of the current available buffer but was between 0.72
(for when the buffer is low) and 0.8
(for when the buffer is high).
The idea was too avoid rebuffering as much as possible by being sure that the currently-chosen quality could be loaded fast enough.
Depending on if there's a lot of buffer left or not, the rebuffering risks are not the same and consequently, we may apply a low factor when the risk is high but still allows to switch to a slightly higher quality if there's some buffer left.
This factor variability might have led to too much quality switches if the buffer is low. Consequently, we now aligned it to 0.72
regardless of the size of the buffer.
"forced" subtitles on Safari
The last RxPlayer version, v3.30.0
, brought better support of "forced" narrative subtitles (basically, those are subtitles meant to be displayed when no other subtitles are) by adding the forced
boolean property to text track information, for example returned by the getAvailableTextTracks
API.
Images: Semantic of signaling forced subtitles in both DASH (on top), and HLS (bottom). Though the wording is different, they correspond to roughly the same concept: subtitles that should be displayed by default.
However, this addition wasn't added for "directfile"
contents, which are content playable natively on the current browser, such as mp4/webm files of even HLS contents on Safari.
This is because the RxPlayer rely mostly on the browser to play and parse the characteristics of those contents (unlike other "transports", such as "dash"
, where the RxPlayer loads and parses the various media-related files).
Though it has been a long asked browser feature (see also here), browsers don't actually specify if a given text track is a forced narrative subtitles as no real solution has been found to satisfy everyone so no specification actually exists. Consequently, those text tracks will usually be choosable through API such as getAvailableTextTracks
, but you won't be able to know, through its properties, whether it is a forced one.
As most contents played through the RxPlayer in "directfile"
are simple contents (more complex ones usually being packaged as DASH contents) without forced subtitles, this is generally not that big of a problem.
But we have at least one situation at Canal+ where it was problematic: when playing HLS contents on Safari. There, we might have cases where we do have forced subtitles available, and we would prefer to reliably report those tracks as such to an application.
Thankfully in this case, Safari implemented that feature even if no specification exists for now. On safari, forced narrative subtitles have a kind
property set to forced
.
This lets us now properly advertising forced narrative subtitles on Safari when playing HLS contents in "directfile"
mode, by reusing the same API than for other types of contents.
Better "direct" audioTrackSwitchingMode
The RxPlayer has an option called audioTrackSwitchingMode
allowing to configure the behavior the RxPlayer should have when changing the audio track during playback.
seamless_adtsm.mp4
direct_atsm.mp4
Videos: On top, example of an audio track switch after setting audioTrackSwitchingMode
to "seamless"
: we have here a smooth transition but some small time in the previous audio track before switching. On bottom, example of an audio track switch after setting audioTrackSwitchingMode
to "direct"
: the track switch is effective immediately, but we may have some rebuffering before.
Some RxPlayer users had an issue of inaudible audio after an audio track switch on Chrome when the audioTrackSwitchingMode
option was set to "direct"
.
That issue seems to be at the browser-level (or may even be at a lower level), but as it can be prevented in the RxPlayer with no much disadvantage, it is still a win to work-around it at the RxPlayer-level.
However, we lately found a new technique which seems to fix this issue reliably when the following steps are ttaken:
- The application switches the audio track through the player's API
- The player stops loading the old audio track and begins removing all audio data previously buffered.
- Once the browser validate this removal operation, the player performs a seek to a very close position, just to force the "flushing" of lower-level buffers
- The audio data from the new audio track is now pushed (after it has been loaded of course) to the audio buffer.
- Once that push operation is validated in the buffer, the player seeks again to force a flush again.
The first flush thus prevent from still having the lower-level buffer considering old data in the previous track, the second one fixes the muted audio issue - for some reasons. Most of the time no interruption is visible/audible and when it is, it just seems linked to the audio track switch and does not seem shocking.
For the RxPlayer, only the first flush was performed until now.
This commit now adds the second one, which seems to fix all inaudible audio issues.
Better documentation: soft navigation
As the RxPlayer's is a very technical library with a large API, we try to provide a documentation that is both complete (to allow application developers to use it to its full potential) and pleasant to browse.
A recent improvement, that we already rolled out for some time on the online documentation, was to implement what is commonly called "soft navigation". The idea is to avoid reloading the page when most internal links are clicked on, and even pre-loading the future page as soon as the link is hovered (of course, this is implemented intelligently to avoid duplicate requests, memory overloading, and so on).
That way, the documentation may now appear snappier as well as more usable, keeping the state of a documentation page when clicking on an internal link.
For example, opened sub-groups of pages on the left-side of the page is not resetted anymore each time a page navigation is done, which was before a known pain of documentation users.
doc-smooth-nav.mp4
Video: example of navigating between documentation pages fast thanks to soft navigation. Notice that the selected text on the left side stay selected after navigation, whereas previously the whole page was reloaded after clicking.
We hope that this new functionality improves your experience when browsing the RxPlayer's documentation pages.