Thumbnails¶
PRESTOplay for Android includes a thumbnails plugin that lets you display scrubbing preview images as the viewer seeks through content. The plugin supports several thumbnail formats and provides smart preloading strategies so thumbnails are ready the moment the viewer needs them.
Review Thumbnails first for shared thumbnail formats, manifest signaling, and authoring patterns.
Setup¶
Add the thumbnails plugin dependency:
dependencies {
implementation 'com.castlabs.player:thumbs-plugin:<version>'
}
Register the plugin during application startup:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
PlayerSDK.register(new ThumbsPlugin(true));
PlayerSDK.init(getApplicationContext());
}
}
Passing true to the ThumbsPlugin constructor enables the built-in thumbnail
view component that renders on top of a PlayerView.
Embedded thumbnails¶
For DASH streams that include in-stream thumbnail adaptation sets, the SDK detects and loads thumbnails automatically. You only need to configure how they are displayed (see the built-in thumbnails view section below).
Side-loaded thumbnails¶
If your thumbnails are not embedded in the stream, you can side-load them by
adding a SideloadedTrack to your intent bundle. The plugin infers the
thumbnail type from the URL extension (.vtt, .bif, or .jpg), but you can
also set the type explicitly.
WebVTT thumbnails¶
ArrayList<SideloadedTrack> data = new ArrayList<SideloadedTrack>() {{
add(new SideloadedTrack.ThumbnailBuilder()
.url("thumbs/thumbs.vtt")
.thumbnailType(ThumbnailDataTrack.TYPE_WEBVTT_INDEX)
.get());
}};
intent.putParcelableArrayList(
SdkConsts.INTENT_SIDELOADED_TRACKS_ARRAYLIST, data);
JPEG grid template¶
ArrayList<SideloadedTrack> data = new ArrayList<SideloadedTrack>() {{
add(new SideloadedTrack.ThumbnailBuilder()
.url("http://example.com/content/thumbs/$index$.jpg")
.gridHeight(4)
.gridWidth(5)
.thumbnailType(ThumbnailDataTrack.TYPE_JPEG_TEMPLATE)
.intervalMs(10_000)
.get());
}};
intent.putParcelableArrayList(
SdkConsts.INTENT_SIDELOADED_TRACKS_ARRAYLIST, data);
Retrieving thumbnails¶
Once side-loaded or embedded thumbnails are available, the SDK creates a
ThumbnailProvider accessible from the PlayerController:
ThumbnailProvider provider =
playerController.getComponent(ThumbnailProvider.class);
Call getThumbnail to asynchronously load a thumbnail for a given position. The
method supports concurrent requests and accepts an optional time tolerance so a
nearby already-loaded thumbnail can be returned immediately.
Using the built-in thumbnails view¶
When you register the plugin with the built-in view enabled, you can show and
hide thumbnails by responding to seek-bar scrubbing events. Register a
SeekBarListener on the PlayerControllerView and use the
ThumbnailViewComponent to display previews.
public void onSeekbarScrubbed(long positionUs, double seekBarProgressPercent) {
ThumbsPlugin.ThumbnailViewComponent thumbsView =
playerView.getComponent(ThumbsPlugin.ThumbnailViewComponent.class);
if (thumbsView != null) {
thumbsView.show(positionUs, new DefaultThumbnailView.Callback() {
@Override
public void getThumbnailRect(
Rect output,
DefaultThumbnailView.ThumbnailInfo info,
boolean isSmallScreen) {
ViewGroup videoView = playerView.getVideoView();
output.set(
videoView.getLeft(),
videoView.getTop(),
videoView.getRight(),
videoView.getBottom());
}
}, ThumbsPlugin.THUMBNAIL_INDEX_CURRENT);
}
}
@Override
public void onSeekbarReleased() {
ThumbsPlugin.ThumbnailViewComponent thumbsView =
playerView.getComponent(ThumbsPlugin.ThumbnailViewComponent.class);
if (thumbsView != null) {
thumbsView.hide();
}
}
The Callback provides an output rectangle that you fill with the desired
thumbnail position and size, relative to the thumbnail view container. The
example above places the thumbnail full-screen above the video.
Thumbnail index selection¶
The show() method accepts a third parameter that controls which thumbnail is
returned relative to the requested time:
THUMBNAIL_INDEX_CURRENT— the thumbnail with the highest time at or before the requested positionTHUMBNAIL_INDEX_NEXT— the thumbnail with the lowest time after the requested positionTHUMBNAIL_INDEX_CLOSEST— the thumbnail with the smallest time difference from the requested position
Preloading strategy¶
The plugin uses a wave-based preloading approach to balance responsiveness with resource usage. Each wave loads thumbnails at a different interval, starting coarse and getting progressively finer. The default strategy uses three waves: one-minute intervals, then 15-second intervals, then all remaining thumbnails.
ThumbsPlugin plugin = new ThumbsPlugin(true);
plugin.setLoadingStrategy(new LoadingStrategy.Builder()
.loadStartDelayMs(0) // Start loading immediately
.addPercentageWave(0.10f)
.addTimeWave(1, TimeUnit.MINUTES)
.addTimeWave(20, TimeUnit.SECONDS)
.addStepWave(1)
.get());
PlayerSDK.register(plugin);
PlayerSDK.init(getApplicationContext());
Waves are processed sequentially in the order they are added. Define them in ascending density — waves that load more thumbnails should be added last.
You can also set a loading strategy at runtime before opening the player:
ThumbnailProvider thumbs =
playerController.getComponent(ThumbnailProvider.class);
thumbs.setLoadingStrategy(new LoadingStrategy.Builder()
.loadStartDelayMs(0)
.addPercentageWave(0.10f)
.addTimeWave(1, TimeUnit.MINUTES)
.addTimeWave(20, TimeUnit.SECONDS)
.addStepWave(1)
.get());
playerController.open(/* ... */);
Disk cache¶
On-disk caching for thumbnails is controlled globally through the plugin. The default is disabled (memory only).
// No disk cache (default)
ThumbsPlugin.setDiskCacheMode(ThumbsPlugin.DiskCacheMode.DISABLED);
// Cache to disk and clean up when the provider is released
ThumbsPlugin.setDiskCacheMode(ThumbsPlugin.DiskCacheMode.ENABLED_CLEANUP);
// Cache to disk and retain files across releases
ThumbsPlugin.setDiskCacheMode(ThumbsPlugin.DiskCacheMode.ENABLED_RETAIN);
Network configuration¶
Use NetworkConfiguration to set timeouts for thumbnail downloads:
bundle.put(SdkConsts.INTENT_NETWORK_CONFIGURATION,
new NetworkConfiguration.Builder()
.thumbnailConnectionTimeoutMs(500)
.thumbnailReadTimeoutMs(500)
.get());
Offline support¶
The thumbnails plugin integrates with the downloader plugin for offline
playback. Add ThumbnailData to the intent used to initiate a download, and
the thumbnails are fetched alongside the content. When starting local playback,
pass the same ThumbnailData — remote URLs are automatically translated to
local file paths. This is currently limited to BIF and WebVTT thumbnails.
Standalone thumbnail providers¶
You can obtain a ThumbnailProvider without a PlayerController, which is
useful for fetching thumbnails independently of playback — for example, for a
different piece of content.
DASH embedded thumbnails¶
StandaloneThumbnailProvider provider =
StandaloneThumbnailFactory.getProvider(
new PlayerConfig.Builder(
"https://example.com/manifest.mpd").get());
Future<ThumbnailProvider.ThumbnailResult> result =
provider.getThumbnail(30_000_000L,
ThumbsPlugin.THUMBNAIL_INDEX_CURRENT);
Bitmap bitmap = result.get().thumbnail;
// Free resources when done
provider.destroy();
For live streams, the standalone provider automatically refreshes the manifest
and updates the thumbnail index. Register an IndexRefreshListener to be
notified:
provider.addIndexRefreshListener(provider1 -> {
Future<ThumbnailProvider.ThumbnailResult> thumb =
provider1.getThumbnail(position,
ThumbsPlugin.THUMBNAIL_INDEX_CURRENT);
});
Side-loaded thumbnails¶
StandaloneThumbnailProvider provider =
StandaloneThumbnailFactory.getProvider(
new SideloadedTrack.ThumbnailBuilder()
.url("https://example.com/thumbs/$index$.jpg")
.gridHeight(1)
.gridWidth(1)
.thumbnailType(ThumbnailDataTrack.TYPE_JPEG_TEMPLATE)
.intervalMs(5_000)
.get());