Skip to content

Commit

Permalink
Merge pull request #1308 from canalplus/feat/reload-keySystems
Browse files Browse the repository at this point in the history
Add the possibility to set a new `keySystems` option on the `reload` API
  • Loading branch information
peaBerberian committed Nov 14, 2023
2 parents d1fde0d + da4007f commit fb57bf8
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 9 deletions.
11 changes: 11 additions & 0 deletions doc/api/Basic_Methods/reload.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<Object> | 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.
Expand Down
8 changes: 8 additions & 0 deletions src/core/api/option_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ function parseConstructorOptions(
*/
function checkReloadOptions(options?: {
reloadAt?: { position?: number; relative?: number };
keySystems?: IKeySystemOption[];
autoPlay?: boolean;
}): void {
if (options === null ||
(typeof options !== "object" && options !== undefined)) {
Expand All @@ -414,6 +416,12 @@ function checkReloadOptions(options?: {
options?.reloadAt?.relative !== undefined) {
throw new Error("API: reload - Invalid 'reloadAt.relative' option format.");
}
if (!Array.isArray(options?.keySystems) && options?.keySystems !== undefined) {
throw new Error("API: reload - Invalid 'keySystems' option format.");
}
if (options?.autoPlay !== undefined && typeof options.autoPlay !== "boolean") {
throw new Error("API: reload - Invalid 'autoPlay' option format.");
}
}

/**
Expand Down
14 changes: 13 additions & 1 deletion src/core/api/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
IConstructorOptions,
IDecipherabilityUpdateContent,
IKeySystemConfigurationOutput,
IKeySystemOption,
ILoadVideoOptions,
IPeriod,
IPlayerError,
Expand Down Expand Up @@ -577,6 +578,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
*/
reload(reloadOpts?: {
reloadAt?: { position?: number; relative?: number };
keySystems?: IKeySystemOption[];
autoPlay?: boolean;
}): void {
const { options,
Expand Down Expand Up @@ -609,6 +611,13 @@ class Player extends EventEmitter<IPublicAPIEvent> {
autoPlay = !reloadInPause;
}

let keySystems : IKeySystemOption[] | undefined;
if (reloadOpts?.keySystems !== undefined) {
keySystems = reloadOpts.keySystems;
} else if (this._priv_reloadingMetadata.options?.keySystems !== undefined) {
keySystems = this._priv_reloadingMetadata.options.keySystems;
}

const newOptions = { ...options,
initialManifest: manifest };
if (startAt !== undefined) {
Expand All @@ -617,6 +626,9 @@ class Player extends EventEmitter<IPublicAPIEvent> {
if (autoPlay !== undefined) {
newOptions.autoPlay = autoPlay;
}
if (keySystems !== undefined) {
newOptions.keySystems = keySystems;
}
this._priv_initializeContentPlayback(newOptions);
}

Expand All @@ -626,7 +638,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
if (features.createDebugElement === null) {
throw new Error("Feature `DEBUG_ELEMENT` not added to the RxPlayer");
}
const canceller = new TaskCanceller() ;
const canceller = new TaskCanceller();
features.createDebugElement(element, this, canceller.signal);
return {
dispose() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,21 @@ describe("HTML Text buffer utils - areNearlyEqual", () => {
it("should return true if input number are equals", () => {
expect(areNearlyEqual(5, 5)).toBe(true);
});
it(
"should return false if input number are not nearly equals with delta parameter",
() => {
expect(areNearlyEqual(5, 5.1, 0.02)).toBe(false);
});
it(
"should return true if input number are nearly equals with delta parameter",
() => {
expect(areNearlyEqual(5, 5.01, 0.02)).toBe(true);
});
it(
"should return true if input number are equals with delta parameter",
() => {
expect(areNearlyEqual(5, 5, 0.02)).toBe(true);
});
});

describe("HTML Text buffer utils - removeCuesInfosBetween", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ import {
removeCuesInfosBetween,
} from "./utils";

/**
* first or last IHTMLCue in a group can have a slighlty different start
* or end time than the start or end time of the ICuesGroup due to parsing
* approximation.
* DELTA_CUES_GROUP defines the tolerance level when comparing the start/end
* of a IHTMLCue to the start/end of a ICuesGroup.
* Having this value too high may lead to have unwanted subtitle displayed
* Having this value too low may lead to have subtitles not displayed
*/
const DELTA_CUES_GROUP = 1e-3;

/**
* segment_duration / RELATIVE_DELTA_RATIO = relative_delta
*
* relative_delta is the tolerance to determine if two segements are the same
*/
const RELATIVE_DELTA_RATIO = 5;
/**
* Manage the buffer of the HTMLTextSegmentBuffer.
* Allows to add, remove and recuperate cues at given times.
Expand Down Expand Up @@ -72,6 +89,19 @@ export default class TextTrackCuesStore {
ret.push(cues[j].element);
}
}
// first or last IHTMLCue in a group can have a slighlty different start
// or end time than the start or end time of the ICuesGroup due to parsing
// approximation.
// Add a tolerance of 1ms to fix this issue
if (ret.length === 0 && cues.length > 0) {
for (let j = 0; j < cues.length; j++) {
if (areNearlyEqual(time, cues[j].start, DELTA_CUES_GROUP)
|| areNearlyEqual(time, cues[j].end, DELTA_CUES_GROUP)
) {
ret.push(cues[j].element);
}
}
}
return ret;
}
}
Expand Down Expand Up @@ -163,6 +193,11 @@ export default class TextTrackCuesStore {
insert(cues : IHTMLCue[], start : number, end : number) : void {
const cuesBuffer = this._cuesBuffer;
const cuesInfosToInsert = { start, end, cues };
// it's preferable to have a delta depending on the duration of the segment
// if the delta is one fifth of the length of the segment:
// a segment of [0, 2] is the "same" segment as [0, 2.1]
// but [0, 0.04] is not the "same" segement as [0,04, 0.08]
const relativeDelta = Math.abs(start - end) / RELATIVE_DELTA_RATIO;

/**
* Called when we found the index of the next cue relative to the cue we
Expand All @@ -175,7 +210,7 @@ export default class TextTrackCuesStore {
function onIndexOfNextCueFound(indexOfNextCue : number) : void {
const nextCue = cuesBuffer[indexOfNextCue];
if (nextCue === undefined || // no cue
areNearlyEqual(cuesInfosToInsert.end, nextCue.end)) // samey end
areNearlyEqual(cuesInfosToInsert.end, nextCue.end, relativeDelta)) // samey end
{
// ours: |AAAAA|
// the current one: |BBBBB|
Expand Down Expand Up @@ -210,8 +245,8 @@ export default class TextTrackCuesStore {
for (let cueIdx = 0; cueIdx < cuesBuffer.length; cueIdx++) {
let cuesInfos = cuesBuffer[cueIdx];
if (start < cuesInfos.end) {
if (areNearlyEqual(start, cuesInfos.start)) {
if (areNearlyEqual(end, cuesInfos.end)) {
if (areNearlyEqual(start, cuesInfos.start, relativeDelta)) {
if (areNearlyEqual(end, cuesInfos.end, relativeDelta)) {
// exact same segment
// ours: |AAAAA|
// the current one: |BBBBB|
Expand Down Expand Up @@ -257,7 +292,7 @@ export default class TextTrackCuesStore {
// - add ours before the current one
cuesBuffer.splice(cueIdx, 0, cuesInfosToInsert);
return;
} else if (areNearlyEqual(end, cuesInfos.start)) {
} else if (areNearlyEqual(end, cuesInfos.start, relativeDelta)) {
// our cue goes just before the current one:
// ours: |AAAAAAA|
// the current one: |BBBB|
Expand All @@ -268,7 +303,7 @@ export default class TextTrackCuesStore {
cuesInfos.start = end;
cuesBuffer.splice(cueIdx, 0, cuesInfosToInsert);
return;
} else if (areNearlyEqual(end, cuesInfos.end)) {
} else if (areNearlyEqual(end, cuesInfos.end, relativeDelta)) {
// ours: |AAAAAAA|
// the current one: |BBBB|
// Result: |AAAAAAA|
Expand Down Expand Up @@ -297,7 +332,7 @@ export default class TextTrackCuesStore {
}
// else -> start > cuesInfos.start

if (areNearlyEqual(cuesInfos.end, end)) {
if (areNearlyEqual(cuesInfos.end, end, relativeDelta)) {
// ours: |AAAAAA|
// the current one: |BBBBBBBB|
// Result: |BBAAAAAA|
Expand Down Expand Up @@ -333,6 +368,22 @@ export default class TextTrackCuesStore {
}
}
}

if (cuesBuffer.length) {
const lastCue = cuesBuffer[cuesBuffer.length - 1];
if (areNearlyEqual(lastCue.end, start, relativeDelta)) {
// Match the end of the previous cue to the start of the following one
// if they are close enough. If there is a small gap between two segments
// it can lead to having no subtitles for a short time, this is noticeable when
// two successive segments displays the same text, making it diseappear
// and reappear quickly, which gives the impression of blinking
//
// ours: |AAAAA|
// the current one: |BBBBB|...
// Result: |BBBBBBBAAAAA|
lastCue.end = start;
}
}
// no cues group has the end after our current start.
// These cues should be the last one
cuesBuffer.push(cuesInfosToInsert);
Expand Down
21 changes: 19 additions & 2 deletions src/core/segment_buffers/implementations/text/html/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ import {
* Setting a value too high might lead to two segments targeting different times
* to be wrongly believed to target the same time. In worst case scenarios, this
* could lead to wanted text tracks being removed.
*
* When comparing 2 segments s1 and s2, you may want to take into account the duration
* of the segments:
* - if s1 is [0, 2] and s2 is [0, 2.1] s1 and s2 can be considered as nearly equal as
* there is a relative difference of: (2.1-2) / 2 = 5%;
* Formula: (end_s1 - end_s2) / duration_s2 = relative_difference
* - if s1 is [0, 0.04] and s2 is [0.04, 0.08] s1 and s2 may not considered as nearly
* equal as there is a relative difference of: (0.04-0.08) / 0.04 = 100%
*
* To compare relatively to the duration of a segment you can provide and additional
* parameter "delta" that remplace MAX_DELTA_BUFFER_TIME.
* If parameter "delta" is higher than MAX_DELTA_BUFFER_TIME, MAX_DELTA_BUFFER_TIME
* is used instead of delta. This ensure that segments are nearly equal when comparing
* relatively AND absolutely.
*
* @type Number
*/
const MAX_DELTA_BUFFER_TIME = 0.2;
Expand All @@ -58,10 +73,12 @@ const MAX_DELTA_BUFFER_TIME = 0.2;
* @see MAX_DELTA_BUFFER_TIME
* @param {Number} a
* @param {Number} b
* @param {Number} delta
* @returns {Boolean}
*/
export function areNearlyEqual(a : number, b : number) : boolean {
return Math.abs(a - b) <= MAX_DELTA_BUFFER_TIME;
export function areNearlyEqual(
a : number, b : number, delta: number = MAX_DELTA_BUFFER_TIME) : boolean {
return Math.abs(a - b) <= Math.min(delta, MAX_DELTA_BUFFER_TIME);
}

/**
Expand Down

0 comments on commit fb57bf8

Please sign in to comment.