diff --git a/.gitignore b/.gitignore index 651702e49b..00185f3ecf 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,6 @@ /src/parsers/manifest/dash/wasm-parser/target +# IDE files /.idea +/.vscode diff --git a/CHANGELOG.md b/CHANGELOG.md index 40acf1b351..8df6b0d9a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## Unreleased + +### Features + + - Add `getLivePosition` RxPlayer method [#1300] + - Add `startAt.fromLivePosition` `loadVideo` option [#1300] + - Add the possibility to set a new `keySystems` option on the `reload` API [#1308] + +### Bug Fixes + + - Fix subtitles "blinking" in some specific conditions, especially with some DASH low-latency contents [#1314] + - DASH: Fix Period overlap resolution logic for when the first Period is removed [#1311] + - Fix export of the `LOCAL_MANIFEST` experimental feature [#1305] + +### Other improvements + + - DASH: rely on SCTE214 `supplementalCodecs` instead of `codecs` if it's supported to better support backward compatible Dolby Vision contents [#1307] + - DASH: Provide better support of the `availabilityTimeOffset` attribute [#1300] + - DEBUG_ELEMENT: Add unsupported and undecipherable bitrates to the debug element [#1321] + - DEBUG_ELEMENT: update buffer graph maximum size so it becomes more readable for lengthy contents [#1316] + - DEBUG_ELEMENT: always synchronize inventory of segments before rendering it [#1317] + + ## v3.32.1 (2023-10-19) ### Features diff --git a/FILES.md b/FILES.md index b383a6dc2b..4529509f3e 100644 --- a/FILES.md +++ b/FILES.md @@ -20,7 +20,7 @@ At the time of writing, there are two distinct demos: ## `dist/`: Builds -The `demo/` directory stores the player builds of the last version released. +The `dist/` directory stores the player builds of the last version released. Contains the minified (``rx-player.min.js``) and the non-minified files (``rx-player.js``). Both are automatically generated with scripts at every new diff --git a/README.md b/README.md index 3fd88bd9ff..527da2e76a 100644 --- a/README.md +++ b/README.md @@ -179,21 +179,11 @@ Demo pages for our previous versions are also available -## Your questions ############################################################## - -You can ask directly your questions about the project on [our -gitter](https://gitter.im/canalplus/rx-player). -We will try our best to answer them as quickly as possible. - - - ## Contribute ################################################################## Details on how to contribute is written in the [CONTRIBUTING.md file](./CONTRIBUTING.md) at the root of this repository. -If you need more information, you can contact us via our [gitter -room](https://gitter.im/canalplus/rx-player). ### Dependencies ############################################################### @@ -238,7 +228,7 @@ Canal+ Group is a media company with many advanced needs when it comes to media playback: it provides both live and VoD stream with multiple encryption requirements, supports a very large panel of devices and has many other specificities (like adult content restrictions, ad-insertion, Peer-To-Peer -integration...). +integration, low-latency live streaming...). When the time came to switch from a plugin-based web player approach to an HTML5 one back in 2015, no media player had the key features we wanted, and including @@ -249,7 +239,7 @@ The R&D department of Canal+ Group thus started to work on a new featureful media-player: the RxPlayer. To both help and profit from the community, it also decided to share it to everyone under a permissive open-source licence. -Now, more than 6 years later, the RxPlayer continues to evolve at the same fast +Now, more than 8 years later, the RxPlayer continues to evolve at the same fast pace to include a lot of features and improvements you may not find in other media players. You can look at our @@ -296,24 +286,3 @@ them. Amongst those: risks always low. \* In "directfile" mode, on compatible browsers - - -## Target support ############################################################## - -Here is a basic list of supported platforms: - -| | Chrome | IE [1] | Edge | Firefox | Safari | Opera | -|-------------|:-------:|:-------:|:------:|:---------:|:--------:|:-------:| -| Windows | >= 30 | >= 11 | >= 12 | >= 42 | >= 8 | >= 25 | -| OSX | >= 30 | - | - | >= 42 | >= 8 | >= 25 | -| Linux | >= 37 | - | - | >= 42 | - | >= 25 | -| Android [2] | >= 30 | - | - | >= 42 | - | >= 15 | -| iOS | No | - | - | No | No | No | - -[1] Only on Windows >= 8. - -[2] Android version >= 4.2 - -And more. A good way to know if the browser should be supported by our player is -to go on the page https://www.youtube.com/html5 and check for "Media Source -Extensions" support. diff --git a/demo/full/scripts/contents.ts b/demo/full/scripts/contents.ts index 42ec62dff7..27f01bd121 100644 --- a/demo/full/scripts/contents.ts +++ b/demo/full/scripts/contents.ts @@ -114,7 +114,7 @@ const DEFAULT_CONTENTS: IDefaultContent[] = [ }, { "name": "Multi Video Tracks", - "url": "https://utils.ssl.cdn.cra.cz/dash/1/manifest.mpd", + "url": "https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd", "transport": "dash", "live": false, }, diff --git a/demo/full/scripts/controllers/ControlBar.tsx b/demo/full/scripts/controllers/ControlBar.tsx index 02e002e6db..081f0542e1 100644 --- a/demo/full/scripts/controllers/ControlBar.tsx +++ b/demo/full/scripts/controllers/ControlBar.tsx @@ -39,6 +39,7 @@ function ControlBar({ const isStopped = useModuleState(player, "isStopped"); const liveGap = useModuleState(player, "liveGap"); const lowLatencyMode = useModuleState(player, "lowLatencyMode"); + const livePosition = useModuleState(player, "livePosition"); const maximumPosition = useModuleState(player, "maximumPosition"); const playbackRate = useModuleState(player, "playbackRate"); @@ -79,15 +80,16 @@ function ControlBar({ const isAtLiveEdge = isLive && isCloseToLive && !isCatchingUp; const onLiveDotClick = React.useCallback(() => { - if (maximumPosition == null) { + const livePos = livePosition ?? maximumPosition; + if (livePos == null) { /* eslint-disable-next-line no-console */ console.error("Cannot go back to live: live position not found"); return; } if (!isAtLiveEdge) { - player.actions.seek(maximumPosition - (lowLatencyMode ? 4 : 10)); + player.actions.seek(livePos - (lowLatencyMode ? 4 : 10)); } - }, [isAtLiveEdge, player, maximumPosition, lowLatencyMode]); + }, [isAtLiveEdge, player, livePosition, maximumPosition, lowLatencyMode]); return (
diff --git a/demo/full/scripts/controllers/ProgressBar.tsx b/demo/full/scripts/controllers/ProgressBar.tsx index 43d81c0486..a1e5f53c09 100644 --- a/demo/full/scripts/controllers/ProgressBar.tsx +++ b/demo/full/scripts/controllers/ProgressBar.tsx @@ -21,6 +21,7 @@ function ProgressBar({ const isContentLoaded = useModuleState(player, "isContentLoaded"); const isLive = useModuleState(player, "isLive"); const minimumPosition = useModuleState(player, "minimumPosition"); + const livePosition = useModuleState(player, "livePosition"); const maximumPosition = useModuleState(player, "maximumPosition"); const [timeIndicatorVisible, setTimeIndicatorVisible] = React.useState(false); @@ -189,7 +190,7 @@ function ProgressBar({ onMouseMove={onMouseMove} position={currentTime} minimumPosition={minimumPosition} - maximumPosition={maximumPosition} + maximumPosition={livePosition ?? maximumPosition} bufferGap={bufferGap} /> } diff --git a/demo/full/scripts/modules/player/catchUp.ts b/demo/full/scripts/modules/player/catchUp.ts index b8d7fb3fa1..3b66b99192 100644 --- a/demo/full/scripts/modules/player/catchUp.ts +++ b/demo/full/scripts/modules/player/catchUp.ts @@ -69,19 +69,19 @@ export default class CatchUpModeController { this._state.updateBulk({ isCatchingUp: false, playbackRate: 1 }); } else { const checkCatchUp = () => { - const maximumPosition = this._rxPlayer.getMaximumPosition(); - if (maximumPosition === null) { + const livePos = this._rxPlayer.getLivePosition() ?? + this._rxPlayer.getMaximumPosition(); + if (livePos === null) { this._rxPlayer.setPlaybackRate(1); this._state.updateBulk({ isCatchingUp: false, playbackRate: 1 }); return; } const position = this._rxPlayer.getPosition(); - const liveGap = maximumPosition - position; + const liveGap = livePos - position; if (liveGap >= CATCH_UP_SEEKING_STEP) { // If we're too far from the live to just change the playback rate, // seek directly close to live - this._rxPlayer - .seekTo(maximumPosition - LIVE_GAP_GOAL_WHEN_CATCHING_UP); + this._rxPlayer.seekTo(livePos - LIVE_GAP_GOAL_WHEN_CATCHING_UP); this._rxPlayer.setPlaybackRate(1); this._state.updateBulk({ isCatchingUp: false, playbackRate: 1 }); return; diff --git a/demo/full/scripts/modules/player/events.ts b/demo/full/scripts/modules/player/events.ts index d7726bb66b..a50a12d250 100644 --- a/demo/full/scripts/modules/player/events.ts +++ b/demo/full/scripts/modules/player/events.ts @@ -57,20 +57,24 @@ function linkPlayerEventsToState( const position = player.getPosition(); const duration = player.getVideoDuration(); const videoTrack = player.getVideoTrack(); + const livePosition = player.getLivePosition(); const maximumPosition = player.getMaximumPosition(); let bufferGap = player.getVideoBufferGap(); bufferGap = !isFinite(bufferGap) || isNaN(bufferGap) ? 0 : bufferGap; + + const livePos = livePosition ?? maximumPosition; state.updateBulk({ currentTime: player.getPosition(), wallClockDiff: player.getWallClockTime() - position, bufferGap, duration: Number.isNaN(duration) ? undefined : duration, + livePosition, minimumPosition: player.getMinimumPosition(), - maximumPosition: player.getMaximumPosition(), - liveGap: typeof maximumPosition === "number" ? - maximumPosition - player.getPosition() : + maximumPosition, + liveGap: typeof livePos === "number" ? + livePos - player.getPosition() : undefined, playbackRate: player.getPlaybackRate(), videoTrackHasTrickMode: videoTrack !== null && @@ -210,6 +214,7 @@ function linkPlayerEventsToState( stateUpdates.duration = undefined; stateUpdates.minimumPosition = undefined; stateUpdates.maximumPosition = undefined; + stateUpdates.livePosition = undefined; break; } diff --git a/demo/full/scripts/modules/player/index.ts b/demo/full/scripts/modules/player/index.ts index da4e6e09e1..3b53894589 100644 --- a/demo/full/scripts/modules/player/index.ts +++ b/demo/full/scripts/modules/player/index.ts @@ -134,6 +134,7 @@ export interface IPlayerModuleState { liveGap: number | undefined; loadedVideo: ILoadVideoOptions | null; lowLatencyMode: boolean; + livePosition: null | undefined | number; maximumPosition: null | undefined | number; minimumPosition: null | undefined | number; playbackRate: number; @@ -185,6 +186,7 @@ const PlayerModule = declareModule( liveGap: undefined, loadedVideo: null, lowLatencyMode: false, + livePosition: undefined, maximumPosition: undefined, minimumPosition: undefined, playbackRate: 1, diff --git a/doc/api/Basic_Methods/reload.md b/doc/api/Basic_Methods/reload.md index cc3534ba03..d64547a486 100644 --- a/doc/api/Basic_Methods/reload.md +++ b/doc/api/Basic_Methods/reload.md @@ -35,6 +35,17 @@ The options argument is an object containing : content was playing the last time it was played and stay in the `"LOADED"` state (and paused) if it was paused last time it was played. +- _keySystems_ (`Array. | undefined`): If set, a new configuration will + be set on this reloaded content regarding its decryption. + + The value of this property follows the exact same structure than for the + original `loadVideo` call, it is described in the [decryption options + documentation page](../Decryption_Options.md). + + You might for example want to update that way the `keySystems` option compared + to the one of the original `loadVideo` call when you suspect that there is a + decryption-related issue with the original `keySystems` given. + Note that despite this method's name, the player will not go through the `RELOADING` state while reloading the content but through the regular `LOADING` state - as if `loadVideo` was called on that same content again. diff --git a/doc/api/Loading_a_Content.md b/doc/api/Loading_a_Content.md index b4c890f77e..0cae7663bc 100644 --- a/doc/api/Loading_a_Content.md +++ b/doc/api/Loading_a_Content.md @@ -202,6 +202,16 @@ can be either: - for VoD contents, it is the difference between the starting position and the end position of the content. + +- **fromLivePosition** relative position relative to the content's live edge + (for live contents, it is the position that is intended to be broadcasted + at the current time) if it makes sense, in seconds. Should be a negative + number. + + If the live edge is unknown or if it does not make sense for the current + content (for example, it won't make sense for a VoD content), that setting + repeats the same behavior than **fromLastPosition**. + - **percentage** (`Number`): percentage of the wanted position. `0` being the minimum position possible (0 for static content, buffer depth for dynamic contents) and `100` being the maximum position possible diff --git a/doc/api/Miscellaneous/Debug_Element.md b/doc/api/Miscellaneous/Debug_Element.md index 34a89d5a15..c77c2aa267 100644 --- a/doc/api/Miscellaneous/Debug_Element.md +++ b/doc/api/Miscellaneous/Debug_Element.md @@ -104,8 +104,20 @@ reflect exactly what's going on at a particular point in time. - **vt**: _Video tracks_. List of the video tracks' `id` property. The line begins with a number indicating the number of available video tracks, followed by `:`, followed by each video track's id separated by a space. The current video track is prepended by a `*` character. - **at**: _Audio tracks_. List of the audio tracks' `id` property. The line begins with a number indicating the number of available audio tracks, followed by `:`, followed by each audio track's id separated by a space. The current audio track is prepended by a `*` character. - **tt**: _Text tracks_. List of the text tracks' `id` property. The line begins with a number indicating the number of available text tracks, followed by `:`, followed by each text track's id separated by a space. The current text track is prepended by a `*` character. - - **vb**: _Video Bitrates_. The available video bitrates in the current video track, separated by a space. - - **ab**: _Audio Bitrates_. The available audio bitrates in the current audio track, separated by a space. + - **vb**: _Video Bitrates_. The available video bitrates in the current + video track, separated by a space. + Each bitrate value can optionally be followed by an "`U!`", in which case + the codec of the corresponding Representation is unsupported, and/or be + followed by an "`E!`", in which case it is undecipherable currently. + In both of those cases the corresponding video Representation won't be + played by the RxPlayer. + - **ab**: _Audio Bitrates_. The available audio bitrates in the current + audio track, separated by a space. + Each bitrate value can optionally be followed by an "`U!`", in which case + the codec of the corresponding Representation is unsupported, and/or be + followed by an "`E!`", in which case it is undecipherable currently. + In both of those cases the corresponding audio Representation won't be + played by the RxPlayer. - Buffer information - **vbuf**: _Graphical representation of the video buffer_. The red rectangle indicates the current position, the different colors indicate different video qualities in the buffer. diff --git a/doc/api/Miscellaneous/Low_Latency.md b/doc/api/Miscellaneous/Low_Latency.md index fcc010dfae..d9f0e5bf17 100644 --- a/doc/api/Miscellaneous/Low_Latency.md +++ b/doc/api/Miscellaneous/Low_Latency.md @@ -70,7 +70,7 @@ rxPlayer.loadVideo({ url: "https://www.example.com/content.mpd", transport: "dash", lowLatencyMode: true, - startAt: { fromLastPosition: 2 }, // Play 2 seconds from the live edge instead + startAt: { fromLivePosition: 2 }, // Play 2 seconds from the live edge instead // (beware of much more frequent rebuffering // risks) }); diff --git a/doc/reference/API_Reference.md b/doc/reference/API_Reference.md index b42b2409e8..115a558d64 100644 --- a/doc/reference/API_Reference.md +++ b/doc/reference/API_Reference.md @@ -104,6 +104,9 @@ properties, methods, events and so on. - [`keySystems[].licenseStorage`](../api/Decryption_Options.md#licensestorage): Allows to ask for the DRM session to persist the license. + - [`keySystems[].onKeyExpiration`](../api/Decryption_Options.md#onkeyexpiration): + Behavior when a key has an `"expired"` status. + - [`keySystems[].fallbackOn`](../api/Decryption_Options.md#fallbackon): Allows to fallback to another quality when a key is refused. @@ -235,6 +238,11 @@ properties, methods, events and so on. - [`defaultTextTrack`](../api/Loading_a_Content.md#defaulttexttrack): [Deprecated] Default characteristics wanted for the text track. +## Static methods + + - [`addFeatures`](../api/RxPlayer_Features.md): + Add features to the RxPlayer (e.g.: multithreading, offline playback etc.). + ## Methods - [`loadVideo`](../api/Loading_a_Content.md): Load a content. @@ -454,15 +462,18 @@ properties, methods, events and so on. - [`isContentLoaded`](../api/Playback_Information/isContentLoaded.md): Returns `true` if a content is loaded. - + - [`isBuffering`](../api/Playback_Information/isBuffering.md): Returns `true` if the player is buffering. - + - [`isPaused`](../api/Playback_Information/isPaused.md): Returns `true` if the `