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;