From 00e6a5aae3758aca45151c8a71164babad04728d Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Tue, 18 Apr 2023 14:15:25 +0200 Subject: [PATCH] Add `representationListUpdate` event for the v4 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. --- doc/api/Player_Events.md | 61 ++++++++++++++++++++++++++++++++++++++ src/core/api/public_api.ts | 58 ++++++++++++++++++++++++++++++++++++ src/public_types.ts | 7 +++++ 3 files changed, 126 insertions(+) diff --git a/doc/api/Player_Events.md b/doc/api/Player_Events.md index 4cc291d57d1..8e2fe1bb04d 100644 --- a/doc/api/Player_Events.md +++ b/doc/api/Player_Events.md @@ -657,6 +657,67 @@ The payload for this event is an object with the following properties: application's logic. +
+This event is not sent in DirectFile mode (see +transport option) +
+ +### 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. + +
This event is not sent in DirectFile mode (see transport option) diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts index 7be15500b88..080d1e750ce 100644 --- a/src/core/api/public_api.ts +++ b/src/core/api/public_api.ts @@ -57,6 +57,7 @@ import { ILockedAudioRepresentationsSettings, ILockedVideoRepresentationsSettings, ITrackUpdateEventPayload, + IRepresentationListUpdateContext, IPeriod, IPeriodChangeEvent, IPlayerError, @@ -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"; @@ -2162,6 +2165,60 @@ class Player extends EventEmitter { 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); } /** @@ -2654,6 +2711,7 @@ interface IPublicAPIEvent { newAvailablePeriods : IPeriod[]; brokenRepresentationsLock : IBrokenRepresentationsLockContext; trackUpdate : ITrackUpdateEventPayload; + representationListUpdate : IRepresentationListUpdateContext; seeking : null; seeked : null; streamEvent : IStreamEvent; diff --git a/src/public_types.ts b/src/public_types.ts index 2f4e2f0774b..379ffb25bf7 100644 --- a/src/public_types.ts +++ b/src/public_types.ts @@ -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;