Skip to content

Commit 61c392c

Browse files
committed
Thumbnails: Add supplementary metadata to getAvailableThumbnailTracks
Based on #1496 Problem ------- We're currently trying to provide a complete[1] and easy to-use API for DASH thumbnail tracks in the RxPlayer. Today the proposal is to have an API called `renderThumbnail`, to which an application would just provide an HTML element and a timestamp, and the RxPlayer would do all that's necessary to fetch the corresponding thumbnail and display it in the corresponding element. The API is like so: ```js rxPlayer.renderThumbnail({ element, time }) .then(() => console.log("The thumbnail is now rendered in the element")); ``` This works and seems to me very simple to understand. Yet, we've known of advanced use cases where an application might not just want to display a single thumbnail for a single position. For example, there's very known examples where an application displays a window of multiple thumbnails at once on the player's UI to facilitate navigation inside the content. To do that under the solution proposed in #1496, an application could just call `renderThumbnail` with several `element` and `time` values. Yet for this type of feature, what the interface would want is not really to indicate a `time` values, it actually wants basically a list of distinct thumbnails around/before/after a given position. By just being able to set a `time` value, an application is blind on which `time` value is going to lead to a different timestamp (i.e. is the thumbnail for the `time` `11` different than the thumbnail for the `time` `12`? Nobody - but the RxPlayer - knows). So we have to find a solution for this [1] By complete, I here mean that we want to be able to handle its complexities inside the RxPlayer, to ensure complex DASH situations like multi-CDN, retry settings for requests and so on while still allowing all potential use cases for an application. Solution -------- In this solution, I experiment with a second thumbnail API, `getAvailableThumbnailTracks` (it already exists in #1496, but its role there was only to list the various thumbnail qualities, if there are several size for example). As this solution build upon yet stays compatible to #1496, I chose to open this second PR on top of that previous one. I profit from the fact that most standardized thumbnail implementations I know of (BIF, DASH) seem follow the principle of having evenly-spaced (in terms of time) thumbnails (though I do see a possibility for that to change, e.g. to have thumbnails corresponding to "important" scenes instead, so our implementation has to be resilient). So here, what this commit does is to add the following properties (all optional) to a track returned by the `getAvailableThumbnailTracks` API: - `start`: The initial `time` the first thumbnail of that track will apply to - `end`: The last `time` the last thumbnail of that track will apply to - thumbnailsPerSegment: Individual thumbnails may be technically part of "segments" containing multiple consecutive thumbnails each. `thumbnailsPerSegment` is the number of thumbnails each of those segments contain. 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. - `segmentDuration`: The "duration" (in seconds) each segments of thumbnails applies to (with the exception of the last thumbnail, which just fills until `end`) Then, an application should have all information needed to calculate a `time` which correspond to a different thumbnail. Though this solution lead to a minor issue: by letting application make the `time` operation themselves with `start`, `end`, `segmentDuration` and so on, there's a risk of rounding errors leading to a `time` which does not correspond to the thumbnail wanted but the one before or after. To me, we could just indicate in our API documentation to application developers that they should be extra careful and may add an epsilon (or even choose a `time` in the "middle" of thumbnails each time) if they want that type of thumbnail list feature. Thoughts?
1 parent 3fb82ce commit 61c392c

21 files changed

+481
-0
lines changed

src/main_thread/api/public_api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
799799
width: Math.floor(t.width / t.horizontalTiles),
800800
height: Math.floor(t.height / t.verticalTiles),
801801
mimeType: t.mimeType,
802+
start: t.start,
803+
end: t.end,
804+
segmentDuration: t.segmentDuration,
805+
thumbnailsPerSegment: t.thumbnailsPerSegment,
802806
};
803807
});
804808
}

src/manifest/classes/__tests__/adaptation.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ const minimalRepresentationIndex: IRepresentationIndex = {
5151
addPredictedSegments() {
5252
/* noop */
5353
},
54+
getTargetSegmentDuration() {
55+
return undefined;
56+
},
5457
_replace() {
5558
/* noop */
5659
},

src/manifest/classes/__tests__/representation.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ const minimalIndex: IRepresentationIndex = {
4747
canBeOutOfSyncError(): true {
4848
return true;
4949
},
50+
getTargetSegmentDuration() {
51+
return undefined;
52+
},
5053
_replace() {
5154
return;
5255
},

src/manifest/classes/__tests__/update_period_in_place.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ function generateFakeThumbnailTrack({ id }: { id: string }) {
174174
width: 200,
175175
horizontalTiles: 5,
176176
verticalTiles: 3,
177+
start: 0,
178+
end: 100,
179+
segmentDuration: 2,
180+
thumbnailsPerSegment: 2,
177181
index: {
178182
_update() {
179183
/* noop */
@@ -1433,6 +1437,10 @@ describe("Manifest - updatePeriodInPlace", () => {
14331437
id: "thumb-2",
14341438
mimeType: "image/png",
14351439
verticalTiles: 3,
1440+
start: 0,
1441+
end: 100,
1442+
segmentDuration: 2,
1443+
thumbnailsPerSegment: 2,
14361444
width: 200,
14371445
},
14381446
],
@@ -1443,6 +1451,10 @@ describe("Manifest - updatePeriodInPlace", () => {
14431451
id: "thumb-1",
14441452
mimeType: "image/png",
14451453
verticalTiles: 3,
1454+
start: 0,
1455+
end: 100,
1456+
segmentDuration: 2,
1457+
thumbnailsPerSegment: 2,
14461458
width: 200,
14471459
},
14481460
],
@@ -1510,6 +1522,10 @@ describe("Manifest - updatePeriodInPlace", () => {
15101522
id: "thumb-2",
15111523
mimeType: "image/png",
15121524
verticalTiles: 3,
1525+
start: 0,
1526+
end: 100,
1527+
segmentDuration: 2,
1528+
thumbnailsPerSegment: 2,
15131529
width: 200,
15141530
},
15151531
],
@@ -1520,6 +1536,10 @@ describe("Manifest - updatePeriodInPlace", () => {
15201536
id: "thumb-1",
15211537
mimeType: "image/png",
15221538
verticalTiles: 3,
1539+
start: 0,
1540+
end: 100,
1541+
segmentDuration: 2,
1542+
thumbnailsPerSegment: 2,
15231543
width: 200,
15241544
},
15251545
],
@@ -1588,6 +1608,10 @@ describe("Manifest - updatePeriodInPlace", () => {
15881608
id: "thumb-1",
15891609
mimeType: "image/png",
15901610
verticalTiles: 3,
1611+
start: 0,
1612+
end: 100,
1613+
segmentDuration: 2,
1614+
thumbnailsPerSegment: 2,
15911615
width: 200,
15921616
},
15931617
],
@@ -1660,6 +1684,10 @@ describe("Manifest - updatePeriodInPlace", () => {
16601684
id: "thumb-1",
16611685
mimeType: "image/png",
16621686
verticalTiles: 3,
1687+
start: 0,
1688+
end: 100,
1689+
segmentDuration: 2,
1690+
thumbnailsPerSegment: 2,
16631691
width: 200,
16641692
},
16651693
],

src/manifest/classes/period.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ export default class Period implements IPeriodMetadata {
145145
width: thumbnailTrack.width,
146146
horizontalTiles: thumbnailTrack.horizontalTiles,
147147
verticalTiles: thumbnailTrack.verticalTiles,
148+
start: thumbnailTrack.start,
149+
end: thumbnailTrack.end,
150+
segmentDuration: thumbnailTrack.segmentDuration,
151+
thumbnailsPerSegment: thumbnailTrack.thumbnailsPerSegment,
148152
}));
149153
this.duration = args.duration;
150154
this.start = args.start;
@@ -305,6 +309,10 @@ export default class Period implements IPeriodMetadata {
305309
width: thumbnailTrack.width,
306310
horizontalTiles: thumbnailTrack.horizontalTiles,
307311
verticalTiles: thumbnailTrack.verticalTiles,
312+
start: thumbnailTrack.start,
313+
end: thumbnailTrack.end,
314+
segmentDuration: thumbnailTrack.segmentDuration,
315+
thumbnailsPerSegment: thumbnailTrack.thumbnailsPerSegment,
308316
})),
309317
};
310318
}
@@ -344,4 +352,59 @@ export interface IThumbnailTrack {
344352
* images contained vertically in a whole loaded thumbnail resource.
345353
*/
346354
verticalTiles: number;
355+
/**
356+
* Starting `position` the first thumbnail of this thumbnail track applies to,
357+
* if known.
358+
*/
359+
start: number | undefined;
360+
/**
361+
* Ending `position` the last thumbnail of this thumbnail track applies to,
362+
* if known.
363+
*/
364+
end: number | undefined;
365+
/**
366+
* Individual thumbnails may be technically part of "segments" containing
367+
* multiple consecutive thumbnails each.
368+
*
369+
* `thumbnailsPerSegment` is the number of `thumbnails` each segments have.
370+
*
371+
* For example you could have stored on the server a segment which is a grid
372+
* of 2 x 3 (2 horizontal rows and 3 vertical columns) thumbnails, which the
373+
* RxPlayer will load at once then "cut" the right way when calling
374+
* `renderThumbnail`. In that example, `thumbnailsPerSegment` would be set to
375+
* `6` (2*3).
376+
*
377+
* Note that the last segment of a content may contain less thumbnails as
378+
* anounced here depending on the duration of the content.
379+
*
380+
* You may want to rely on this information alongside `segmentDuration` to
381+
* construct a list of available thumbnails and/or of available segments of
382+
* thumbnails.
383+
*/
384+
thumbnailsPerSegment: number | undefined;
385+
/**
386+
* When loaded, thumbnails are part of so-called "segments" which may contain
387+
* either a single thumbnail or a grid of them (@see `thumbnailsPerSegment`).
388+
*
389+
* This `segmentDuration` property indicates a duration in seconds each
390+
* segment applies to. You might then want to divide that value by
391+
* `thumbnailsPerSegment` to get the duration each thumbnail applies to.
392+
*
393+
* Note that the last segment of a content may have a lower duration depending
394+
* on the duration of the content.
395+
*
396+
* Set to `undefined` either the duration is unknown or if the duration
397+
* depends from segment to segments.
398+
*
399+
* For example, with a `start` set to `10`, an `end` set to `21`, a
400+
* `thumbnailsPerSegment` set to `2` and a `SegmentDuration` set to
401+
* `4`, there should be 3 segments, each with 2 thumbnails:
402+
* 1. A segment of 2 thumbnails for the seconds: 10-14
403+
* (The first thumbnail in that segment for 10-12, the second for 12-14)
404+
* 2. A segment of 2 thumbnails for the seconds: 14-18
405+
* (The first thumbnail in that segment for 14-16, the second for 16-18)
406+
* 3. A segment of 2 thumbnails for the seconds: 18-21 (the end)
407+
* (The first thumbnail in that segment for 18-20, the second for 20-21)
408+
*/
409+
segmentDuration: number | undefined;
347410
}

src/manifest/classes/representation_index/static.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,24 @@ export default class StaticRepresentationIndex implements IRepresentationIndex {
155155
log.error("A `StaticRepresentationIndex` does not need to be initialized");
156156
}
157157

158+
/**
159+
* Returns the `duration` of each segment in the context of its Manifest (i.e.
160+
* as the Manifest anounces them, actual segment duration may be different due
161+
* to approximations), in seconds.
162+
*
163+
* NOTE: we could here do a median or a mean but I chose to be lazy (and
164+
* more performant) by returning the duration of the first element instead.
165+
* As `isPrecize` is `false`, the rest of the code should be notified that
166+
* this is only an approximation.
167+
* @returns {number}
168+
*/
169+
getTargetSegmentDuration(): { duration: number; isPrecize: boolean } | undefined {
170+
return {
171+
duration: Number.MAX_VALUE,
172+
isPrecize: false,
173+
};
174+
}
175+
158176
addPredictedSegments(): void {
159177
log.warn("Cannot add predicted segments to a `StaticRepresentationIndex`");
160178
}

src/manifest/classes/representation_index/types.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,29 @@ export interface IRepresentationIndex {
429429
*/
430430
initialize(segmentList: ISegmentInformation[]): void;
431431

432+
/**
433+
* Returns an approximate for the duration of that `RepresentationIndex`s
434+
* segments, in seconds in the context of its Manifest (i.e. as the Manifest
435+
* anounces them, actual segment duration may be different due to
436+
* approximations), with the exception of the last one (that usually is
437+
* shorter).
438+
* @returns {number}
439+
*/
440+
getTargetSegmentDuration():
441+
| {
442+
/** Approximate duration of any segments but the last one in seconds. */
443+
duration: number;
444+
/**
445+
* If `true`, the given duration should be relatively precize for all
446+
* segments but the last one.
447+
*
448+
* If `false`, `duration` indicates only a general idea of what can be
449+
* expected.
450+
*/
451+
isPrecize: boolean;
452+
}
453+
| undefined;
454+
432455
/**
433456
* Add segments to a RepresentationIndex that were predicted after parsing the
434457
* segment linked to `currentSegment`.

src/manifest/classes/update_period_in_place.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ export default function updatePeriodInPlace(
7575
oldThumbnailTrack.width = newThumbnailTrack.width;
7676
oldThumbnailTrack.horizontalTiles = newThumbnailTrack.horizontalTiles;
7777
oldThumbnailTrack.verticalTiles = newThumbnailTrack.verticalTiles;
78+
oldThumbnailTrack.start = newThumbnailTrack.start;
79+
oldThumbnailTrack.end = newThumbnailTrack.end;
80+
oldThumbnailTrack.segmentDuration = newThumbnailTrack.segmentDuration;
81+
oldThumbnailTrack.thumbnailsPerSegment = newThumbnailTrack.thumbnailsPerSegment;
7882
oldThumbnailTrack.cdnMetadata = newThumbnailTrack.cdnMetadata;
7983
if (updateType === MANIFEST_UPDATE_TYPE.Full) {
8084
oldThumbnailTrack.index._replace(newThumbnailTrack.index);
@@ -88,6 +92,10 @@ export default function updatePeriodInPlace(
8892
width: oldThumbnailTrack.width,
8993
horizontalTiles: oldThumbnailTrack.horizontalTiles,
9094
verticalTiles: oldThumbnailTrack.verticalTiles,
95+
start: oldThumbnailTrack.start,
96+
end: oldThumbnailTrack.end,
97+
segmentDuration: oldThumbnailTrack.segmentDuration,
98+
thumbnailsPerSegment: oldThumbnailTrack.thumbnailsPerSegment,
9199
});
92100
}
93101
}
@@ -105,6 +113,10 @@ export default function updatePeriodInPlace(
105113
width: t.width,
106114
horizontalTiles: t.horizontalTiles,
107115
verticalTiles: t.verticalTiles,
116+
start: t.start,
117+
end: t.end,
118+
segmentDuration: t.segmentDuration,
119+
thumbnailsPerSegment: t.thumbnailsPerSegment,
108120
})),
109121
);
110122
oldPeriod.thumbnailTracks.push(...newThumbnailTracks);

src/manifest/types.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,61 @@ export interface IThumbnailTrackMetadata {
286286
* images contained vertically in a whole loaded thumbnail resource.
287287
*/
288288
verticalTiles: number;
289+
/**
290+
* Starting `position` the first thumbnail of this thumbnail track applies to,
291+
* if known.
292+
*/
293+
start: number | undefined;
294+
/**
295+
* Ending `position` the last thumbnail of this thumbnail track applies to,
296+
* if known.
297+
*/
298+
end: number | undefined;
299+
/**
300+
* Individual thumbnails may be technically part of "segments" containing
301+
* multiple consecutive thumbnails each.
302+
*
303+
* `thumbnailsPerSegment` is the number of `thumbnails` each segments have.
304+
*
305+
* For example you could have stored on the server a segment which is a grid
306+
* of 2 x 3 (2 horizontal rows and 3 vertical columns) thumbnails, which the
307+
* RxPlayer will load at once then "cut" the right way when calling
308+
* `renderThumbnail`. In that example, `thumbnailsPerSegment` would be set to
309+
* `6` (2*3).
310+
*
311+
* Note that the last segment of a content may contain less thumbnails as
312+
* anounced here depending on the duration of the content.
313+
*
314+
* You may want to rely on this information alongside `segmentDuration` to
315+
* construct a list of available thumbnails and/or of available segments of
316+
* thumbnails.
317+
*/
318+
thumbnailsPerSegment: number | undefined;
319+
/**
320+
* When loaded, thumbnails are part of so-called "segments" which may contain
321+
* either a single thumbnail or a grid of them (@see `thumbnailsPerSegment`).
322+
*
323+
* This `segmentDuration` property indicates a duration in seconds each
324+
* segment applies to. You might then want to divide that value by
325+
* `thumbnailsPerSegment` to get the duration each thumbnail applies to.
326+
*
327+
* Note that the last segment of a content may have a lower duration depending
328+
* on the duration of the content.
329+
*
330+
* Set to `undefined` either the duration is unknown or if the duration
331+
* depends from segment to segments.
332+
*
333+
* For example, with a `start` set to `10`, an `end` set to `21`, a
334+
* `thumbnailsPerSegment` set to `2` and a `SegmentDuration` set to
335+
* `4`, there should be 3 segments, each with 2 thumbnails:
336+
* 1. A segment of 2 thumbnails for the seconds: 10-14
337+
* (The first thumbnail in that segment for 10-12, the second for 12-14)
338+
* 2. A segment of 2 thumbnails for the seconds: 14-18
339+
* (The first thumbnail in that segment for 14-16, the second for 16-18)
340+
* 3. A segment of 2 thumbnails for the seconds: 18-21 (the end)
341+
* (The first thumbnail in that segment for 18-20, the second for 20-21)
342+
*/
343+
segmentDuration: number | undefined;
289344
}
290345

291346
export interface ILoadedThumbnailData {

src/parsers/manifest/dash/common/indexes/base.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,29 @@ export default class BaseRepresentationIndex implements IRepresentationIndex {
433433
log.warn("Cannot add predicted segments to a `BaseRepresentationIndex`");
434434
}
435435

436+
/**
437+
* Returns the `duration` of each segment in the context of its Manifest (i.e.
438+
* as the Manifest anounces them, actual segment duration may be different due
439+
* to approximations), in seconds.
440+
*
441+
* NOTE: we could here do a median or a mean but I chose to be lazy (and
442+
* more performant) by returning the duration of the first element instead.
443+
* As `isPrecize` is `false`, the rest of the code should be notified that
444+
* this is only an approximation.
445+
* @returns {number}
446+
*/
447+
getTargetSegmentDuration(): { duration: number; isPrecize: boolean } | undefined {
448+
const { timeline, timescale } = this._index;
449+
const firstElementInTimeline = timeline[0];
450+
if (firstElementInTimeline === undefined) {
451+
return undefined;
452+
}
453+
return {
454+
duration: firstElementInTimeline.duration / timescale,
455+
isPrecize: false,
456+
};
457+
}
458+
436459
/**
437460
* Replace in-place this `BaseRepresentationIndex` information by the
438461
* information from another one.

0 commit comments

Comments
 (0)