Skip to content

Commit 68e8549

Browse files
committed
Thumbnails: Store pending thumbnail request
When I demoed how DASH thumbnail worked in the RxPlayer demo, I was unhappy to realize that there was a noticeable delay when quickly moving the mouse over the seekbar. This is both because we have a timer in our demo of a few milliseconds before doing the actual thumbnail request, and even much more importantly because the RxPlayer cancel the last thumbnail request when the application decides to fetch one from a new position instead. Yet positions that are sufficiently close (which is frequent when moving the mouse over a seekbar) are often going to lead to the same thumbnail request. Here cancelling the last one is unfortunate: we should reuse the same request. --- We already have a thumbnail cache but sadly enough it is only populated once the request is done. If the request is still pending and an application ask a thumbnail for a new position, we're going to run a new request even if it leads to the same thumbnail. Our "last loaded" cache was in main thread, which does not even know the relation between the wanted time and the corresponding thumbnail, so this new logic cannot be added there. Instead I added another level of "cache" in our `ThumbnailFetcher` (in `core/fetchers`) for the pending request only. The result is much more satisfying.
1 parent ebc174c commit 68e8549

6 files changed

Lines changed: 213 additions & 124 deletions

File tree

demo/scripts/components/ThumbnailPreview.tsx

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,24 @@ export default function ThumbnailPreview({
7373

7474
startSpinnerTimeoutIfNotAlreadyStarted();
7575

76-
// load thumbnail after a timer to avoid doing too many requests when the
77-
// user quickly moves its pointer or whatever is calling this
78-
loadThumbnailTimeout = window.setTimeout(() => {
79-
loadThumbnailTimeout = null;
76+
// There's two available ways of displaying thumbnails
77+
//
78+
// 1. Through what's called a "trickmode track", which is a video track
79+
// only containing intra-frames. Such thumbnails are shown through a
80+
// video tag thanks the the `VideoThumbnailLoader` tool
81+
//
82+
// 2. Through an especially-purposed "thumbnail track" in a Manifest
83+
// which usually is based on tiles of jpg/png images. Those are loadd
84+
// through specific RxPlayer method.
85+
if (showVideoThumbnail) {
86+
if (videoThumbnailLoader === null) {
87+
return;
88+
}
89+
// load thumbnail after a timer to avoid doing too many requests when the
90+
// user quickly moves its pointer or whatever is calling this
91+
loadThumbnailTimeout = window.setTimeout(() => {
92+
loadThumbnailTimeout = null;
8093

81-
// There's two available ways of displaying thumbnails
82-
//
83-
// 1. Through what's called a "trickmode track", which is a video track
84-
// only containing intra-frames. Such thumbnails are shown through a
85-
// video tag thanks the the `VideoThumbnailLoader` tool
86-
//
87-
// 2. Through an especially-purposed "thumbnail track" in a Manifest
88-
// which usually is based on tiles of jpg/png images. Those are loadd
89-
// through specific RxPlayer method.
90-
if (showVideoThumbnail) {
91-
if (videoThumbnailLoader === null) {
92-
return;
93-
}
9494
videoThumbnailLoader
9595
.setTime(ceiledTime)
9696
.then(hideSpinner)
@@ -108,43 +108,43 @@ export default function ThumbnailPreview({
108108
console.error("Error while loading thumbnails:", err);
109109
}
110110
});
111-
} else {
112-
const metadata = player.actions.getAvailableThumbnailTracks(ceiledTime);
113-
const thumbnailTrack = metadata.reduce((acc: IThumbnailTrackInfo | null, t) => {
114-
if (acc === null || acc.height === undefined) {
115-
return t;
116-
}
117-
if (t.height === undefined) {
118-
return acc;
119-
}
120-
if (acc.height > t.height) {
121-
return t.height > 100 ? t : acc;
122-
} else {
123-
return acc.height > 100 ? acc : t;
124-
}
125-
}, null);
126-
if (thumbnailTrack === null || imageThumbnailRef.current === null) {
127-
hideSpinner();
128-
return;
111+
}, 30);
112+
} else {
113+
const metadata = player.actions.getAvailableThumbnailTracks(ceiledTime);
114+
const thumbnailTrack = metadata.reduce((acc: IThumbnailTrackInfo | null, t) => {
115+
if (acc === null || acc.height === undefined) {
116+
return t;
129117
}
130-
player.actions
131-
.renderThumbnail(imageThumbnailRef.current, ceiledTime, thumbnailTrack.id)
132-
.then(hideSpinner)
133-
.catch((err) => {
134-
if (
135-
typeof err === "object" &&
136-
err !== null &&
137-
(err as Partial<Record<string, unknown>>).code === "ABORTED"
138-
) {
139-
return;
140-
} else {
141-
hideSpinner();
142-
// eslint-disable-next-line no-console
143-
console.warn("Error while loading thumbnails:", err);
144-
}
145-
});
118+
if (t.height === undefined) {
119+
return acc;
120+
}
121+
if (acc.height > t.height) {
122+
return t.height > 100 ? t : acc;
123+
} else {
124+
return acc.height > 100 ? acc : t;
125+
}
126+
}, null);
127+
if (thumbnailTrack === null || imageThumbnailRef.current === null) {
128+
hideSpinner();
129+
return;
146130
}
147-
}, 30);
131+
player.actions
132+
.renderThumbnail(imageThumbnailRef.current, ceiledTime, thumbnailTrack.id)
133+
.then(hideSpinner)
134+
.catch((err) => {
135+
if (
136+
typeof err === "object" &&
137+
err !== null &&
138+
(err as Partial<Record<string, unknown>>).code === "ABORTED"
139+
) {
140+
return;
141+
} else {
142+
hideSpinner();
143+
// eslint-disable-next-line no-console
144+
console.warn("Error while loading thumbnails:", err);
145+
}
146+
});
147+
}
148148

149149
return () => {
150150
if (loadThumbnailTimeout !== null) {

src/core/fetchers/index.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import type {
2323
import ManifestFetcher from "./manifest";
2424
import type { SegmentQueue, ISegmentQueueCreatorBackoffOptions } from "./segment";
2525
import SegmentQueueCreator from "./segment";
26-
import createThumbnailFetcher, { getThumbnailFetcherRequestOptions } from "./thumbnails";
26+
import createThumbnailFetcher from "./thumbnails";
2727
import type { IThumbnailFetcher } from "./thumbnails";
2828

2929
export type {
@@ -34,10 +34,4 @@ export type {
3434
SegmentQueue,
3535
IThumbnailFetcher,
3636
};
37-
export {
38-
CdnPrioritizer,
39-
ManifestFetcher,
40-
SegmentQueueCreator,
41-
createThumbnailFetcher,
42-
getThumbnailFetcherRequestOptions,
43-
};
37+
export { CdnPrioritizer, ManifestFetcher, SegmentQueueCreator, createThumbnailFetcher };
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import createThumbnailFetcher, {
2-
getThumbnailFetcherRequestOptions,
3-
} from "./thumbnail_fetcher";
1+
import createThumbnailFetcher from "./thumbnail_fetcher";
42
import type { IThumbnailFetcher } from "./thumbnail_fetcher";
53

64
export default createThumbnailFetcher;
7-
export { getThumbnailFetcherRequestOptions };
85
export type { IThumbnailFetcher };

0 commit comments

Comments
 (0)