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 position

  • THUMBNAIL_INDEX_NEXT — the thumbnail with the lowest time after the requested position

  • THUMBNAIL_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());
Next topic: PRESTOplay for Apple
Previous topic: Getting started