diff --git a/src/main_thread/api/public_api.ts b/src/main_thread/api/public_api.ts index 23fd021a9c..13e9cba695 100644 --- a/src/main_thread/api/public_api.ts +++ b/src/main_thread/api/public_api.ts @@ -68,6 +68,7 @@ import { toVideoRepresentation, toAudioRepresentation, } from "../../manifest/index.ts"; +import type { IThumbnailTrackMetadata } from "../../manifest/types.ts"; import type { IPlaybackObservation } from "../../playback_observer/index.ts"; import MediaElementPlaybackObserver from "../../playback_observer/media_element_playback_observer.ts"; import type { @@ -961,6 +962,11 @@ class Player extends EventEmitter { width: Math.floor(t.width / t.horizontalTiles), height: Math.floor(t.height / t.verticalTiles), mimeType: t.mimeType, + start: t.start, + end: t.end, + thumbnailDuration: t.tileDuration, + thumbnailsPerSegment: t.horizontalTiles * t.verticalTiles, + lastThumbnailTime: this._getLastThumbnailTime(t), }; }); } @@ -3800,6 +3806,48 @@ class Player extends EventEmitter { } } + /** + * For the given track, returns the position in seconds that will + * correspond to the currently last reachable thumbnail, or `undefined` if + * unknown. + * + * That position may then be passed to the `rxPlayer.renderThumbnail()` method. + * + * @param {Object} metadata + * @returns {number|undefined} + */ + private _getLastThumbnailTime(metadata: IThumbnailTrackMetadata): number | undefined { + if (metadata.start === undefined || metadata.tileDuration === undefined) { + return; + } + + const maximumPosition = this.getMaximumPosition() ?? undefined; + if (maximumPosition === undefined) { + return; + } + + const thumbnailsPerSegment = metadata.horizontalTiles * metadata.verticalTiles; + + /** Amount of seconds a segment of thumbnails span. */ + const segmentDuration = metadata.tileDuration * (thumbnailsPerSegment ?? 1); + + /** + * Seconds at the end of the content for which a thumbnail has not yet been + * generated. + */ + const secondsWithoutThumbnailYet = + (maximumPosition - metadata.start) % segmentDuration; + + /** + * Position that will lead to the last available thumbnail being requested. + */ + const maxThumbnailTime = + Math.min(maximumPosition - secondsWithoutThumbnailYet, metadata.end ?? Infinity) - + metadata.tileDuration; + + return maxThumbnailTime; + } + /** * Returns `true` if the content concerned by those options should load in * multithread mode. diff --git a/src/public_types.ts b/src/public_types.ts index b717b793e9..a9f1d9bb86 100644 --- a/src/public_types.ts +++ b/src/public_types.ts @@ -1414,6 +1414,64 @@ export interface IThumbnailTrackInfo { * `image/jpeg` or `image/png`. */ mimeType: string | undefined; + /** + * Starting `position` the first thumbnail of this thumbnail track applies to, + * if known. + */ + start: number | undefined; + /** + * Ending `position` the last thumbnail of this thumbnail track applies to, + * if known. + */ + end: number | undefined; + /** + * Individual thumbnails may be technically part of "segments" containing + * multiple consecutive thumbnails each. + * + * `thumbnailsPerSegment` is the number of `thumbnails` each segments have. + * + * For example you could have stored on the server a segment which is a grid + * of 2 x 3 (2 horizontal rows and 3 vertical columns) thumbnails, which the + * RxPlayer will load at once then "cut" the right way when calling + * `renderThumbnail`. In that example, `thumbnailsPerSegment` would be set to + * `6` (2*3). + * + * Note that the last segment of a content may contain less thumbnails as + * anounced here depending on the duration of the content. + * + * You may want to rely on this information alongside `thumbnailDuration` to + * construct a list of available thumbnails and/or of available segments of + * thumbnails. + */ + thumbnailsPerSegment: number | undefined; + /** + * When loaded, thumbnails are part of so-called "segments" which may contain + * either a single thumbnail or a grid of them (@see `thumbnailsPerSegment`). + * + * This `thumbnail` property indicates a duration in seconds each thumbnail + * apply to. + * You can multiply that value with `thumbnailsPerSegment` to get the amount + * of time each segment applies to. + * + * Set to `undefined` either the duration is unknown or if the duration + * depends from segment to segments. + * + * For example, with a `start` set to `10`, an `end` set to `26`, a + * `thumbnailsPerSegment` set to `2` and a `tileDuration` set to + * `3`, there should be 3 segments, each with 2 thumbnails of 3 seconds each: + * 1. A segment of 2 thumbnails for the seconds: 10-16 + * (The first thumbnail in that segment for 10-13, the second for 13-16) + * 2. A segment of 2 thumbnails for the seconds: 16-22 + * (The first thumbnail in that segment for 16-19, the second for 19-22) + * 3. A segment of 2 thumbnails for the seconds: 22-26 (the end) + * (The first thumbnail in that segment for 22-25, the second for 25-26) + */ + thumbnailDuration: number | undefined; + /** + * The time the current last thumbnail avaiable for that track applies to, + * in seconds. + */ + lastThumbnailTime: number | undefined; } /** diff --git a/tests/integration/scenarios/dash_render_thumbnail.test.js b/tests/integration/scenarios/dash_render_thumbnail.test.js index ed2c43423d..85e3aa3518 100644 --- a/tests/integration/scenarios/dash_render_thumbnail.test.js +++ b/tests/integration/scenarios/dash_render_thumbnail.test.js @@ -74,6 +74,11 @@ function runDashRenderThumbnailTests({ multithread } = {}) { width: 320, height: 180, mimeType: "image/jpeg", + end: 101.568367, + lastThumbnailTime: 99, + start: 0, + thumbnailDuration: 1, + thumbnailsPerSegment: 10, }, ]);