Skip to content

Commit

Permalink
Add representationListUpdate event for the v4
Browse files Browse the repository at this point in the history
While migrating some Canal+ application to the future v4.0.0 version, I
noticed that an event, listing when the list of available
`Representation`s associated to a currently-chosen track, was missing.

---

Let's consider for example an application logic which stores
information the current available qualities (so `Representation`) of the
current video track.

With the `v4` it can constructs its list any time the video track
is initially set or changes for any Period, simply by respectively
listening to the `newAvailablePeriods` event (for when it is initially
set) and to the `trackUpdate` event (for when it is changed).

This seems sufficient at first.

But now let's consider that we're playing encrypted contents, and that
one of the `Representation` of those current video tracks became
un-decipherable. Here, an application won't be able to select that
`Representation` anymore, so it generally will want to remove it from
its internal state. However, there was no event to indicate that the
list of available `Representation`s had changed.

---

I thus decided to add the `representationListUpdate` event. Exactly like
the `trackUpdate` event, it is only triggered when that list
**changes**, i.e. not when the track was initially chosen.

The exact semantic (and thus documentation wording) is even more blurry
than that sadly: this event is sent when the list of `Representation`
**MAY HAVE** changed. As in, it may also be sent when the exact same
`Representation` are in fact available for that track.

This last part is because implementing a logic checking that it does
have changed is doable, but costs more effort (in terms of code writing,
performance, storage of previous event etc.) than it should save (I
expect that event listener to be relatively idempotent where it matters,
as in: triggering two times the same event should not lead to an issue).

Yet I still prevent most of those false positives by:
  1. checking that the event is only sent once for each track type
     (e.g. "audio", "video"...) and Period couple
  2. checking that the corresponding track is actually the
     currently-chosen one for that Period.
  • Loading branch information
peaBerberian committed Sep 14, 2023
1 parent fc38766 commit 00e6a5a
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
61 changes: 61 additions & 0 deletions doc/api/Player_Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,67 @@ The payload for this event is an object with the following properties:
application's logic.


<div class="warning">
This event is not sent in <i>DirectFile</i> mode (see
<a href="./Loading_a_Content.md#transport">transport option</a>)
</div>

### representationListUpdate

_payload type_: `Object`

Event triggered if the list of available [`Representation`](../Getting_Started/Glossary.md#representation)
linked to the currently-chosen video, audio or text track for any
[Period](../Getting_Started/Glossary.md#period) (for example inspectable
through the `representations` property of audio and video track objects) may
have changed compared to what it was before.

Note that you won't receive a `representationListUpdate` event the initial time
the corresponding track is selected, it is only sent when its linked list of
available `Representation`s might have dynamically changed during playback.
For now, this may only happen if at least one of the Representation in the
chosen track became undecipherable (in which case it is not available anymore)
or decipherable (in which case it becomes available again).

The main point of this event is to let you adjust your tracks and
Representations choice when they depend on the list of available Representation.

The payload for this event is an object with the following properties:

- `trackType` (`string`): The type of track concerned. Can for example be
`audio` for an audio track, `video` for a video track or `text` for a text
track.

- `period` (`Object`): Information about the concerned
[Period](../Getting_Started/Glossary.md#period). This object contains as
properties:

- `start` (`number`): The starting position at which the Period starts, in
seconds.

- `end` (`number|undefined`): The position at which the Period ends, in
seconds.

`undefined` either if not known or if the Period has no end yet (e.g. for
live contents, the end might not be known for now).

- `id` (`string`): `id` of the Period, allowing to call track and
Representation selection APIs (such as `setAudioTrack` and
`lockVideoRepresentations` for example) even when the Period changes.

- `reason` (`string`): The reason for the update.
For now, it can be set to:

- `"decipherability-update"`: The list of available `Representation`s is
being updated either because at least one of that track's
`Representation` became undecipherable or because it became decipherable
again.

Though other reasons may be added in the future (for future reasons not
covered by those values), so you should expect this possibility in your
application's logic.


<div class="warning">
This event is not sent in <i>DirectFile</i> mode (see
<a href="./Loading_a_Content.md#transport">transport option</a>)
Expand Down
58 changes: 58 additions & 0 deletions src/core/api/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ILockedAudioRepresentationsSettings,
ILockedVideoRepresentationsSettings,
ITrackUpdateEventPayload,
IRepresentationListUpdateContext,
IPeriod,
IPeriodChangeEvent,
IPlayerError,
Expand All @@ -70,7 +71,9 @@ import {
IVideoTrack,
IVideoTrackSetting,
IVideoTrackSwitchingMode,
ITrackType,
} from "../../public_types";
import arrayFind from "../../utils/array_find";
import arrayIncludes from "../../utils/array_includes";
import assert from "../../utils/assert";
import assertUnreachable from "../../utils/assert_unreachable";
Expand Down Expand Up @@ -2162,6 +2165,60 @@ class Player extends EventEmitter<IPublicAPIEvent> {
return;
}
}, contentInfos.currentContentCanceller.signal);

manifest.addEventListener("decipherabilityUpdate", (elts) => {
/**
* Array of tuples only including once the Period/Track combination, and
* only when it concerns the currently-selected track.
*/
const periodsAndTrackTypes = elts.reduce(
(acc: Array<[Period, ITrackType]>, elt) => {
const isFound = arrayFind(
acc,
(x) => x[0].id === elt.period.id &&
x[1] === elt.adaptation.type
) === undefined;

if (!isFound) {
// Only consider the currently-selected tracks.
// NOTE: Maybe there's room for optimizations? Unclear.
const { tracksStore } = contentInfos;
if (tracksStore === null) {
return acc;
}
let isCurrent = false;
const periodRef = tracksStore.getPeriodObjectFromPeriod(elt.period);
if (periodRef === undefined) {
return acc;
}
switch (elt.adaptation.type) {
case "audio":
isCurrent = tracksStore
.getChosenAudioTrack(periodRef)?.id === elt.adaptation.id;
break;
case "video":
isCurrent = tracksStore
.getChosenVideoTrack(periodRef)?.id === elt.adaptation.id;
break;
case "text":
isCurrent = tracksStore
.getChosenTextTrack(periodRef)?.id === elt.adaptation.id;
break;
}
if (isCurrent) {
acc.push([elt.period, elt.adaptation.type]);
}
}
return acc;
}, []);
for (const [period, trackType] of periodsAndTrackTypes) {
this._priv_triggerEventIfNotStopped(
"representationListUpdate",
{ period, trackType, reason: "decipherability-update" },
contentInfos.currentContentCanceller.signal
);
}
}, contentInfos.currentContentCanceller.signal);
}

/**
Expand Down Expand Up @@ -2654,6 +2711,7 @@ interface IPublicAPIEvent {
newAvailablePeriods : IPeriod[];
brokenRepresentationsLock : IBrokenRepresentationsLockContext;
trackUpdate : ITrackUpdateEventPayload;
representationListUpdate : IRepresentationListUpdateContext;
seeking : null;
seeked : null;
streamEvent : IStreamEvent;
Expand Down
7 changes: 7 additions & 0 deletions src/public_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,13 @@ export interface ITrackUpdateEventPayload {
string;
}

export interface IRepresentationListUpdateContext {
period : IPeriod;
trackType : ITrackType;
reason : "decipherability-update" |
string;
}

export interface ILockedVideoRepresentationsSettings {
representations : string[];
periodId? : string | undefined;
Expand Down

0 comments on commit 00e6a5a

Please sign in to comment.