The SDK provides thumbnail preview support through `ThumbnailsAPI`.

![](img/thumbs.png)

## Engine overview

`ThumbnailsAPI` supports two thumbnail engines:

- `.castlabs`: Uses `ThumbnailsPlugin` and supports WebVTT/grid loading with local cache.
- `.apple`: Uses native `AVPlayer`-based preview rendering (`renderOn`).

`PRESTOplaySDK.shared.thumbnails(for:player)` defaults to `.castlabs`.

## Setup

Enable plugins during SDK setup.

For castlabs thumbnails:

```swift
import PRESTOplay
import CastlabsThumbnails

_ = PRESTOplaySDK.shared.setup("LICENSE", [ThumbnailsPlugin()])
```

For Apple native player thumbnails, ensure `HLSPlugin` is enabled.

## Basic usage (`.castlabs`)

Create a player, get thumbnails API, load data, then query images by timestamp:

```swift
let player = PRESTOplaySDK.shared.player()
guard let thumbnails = PRESTOplaySDK.shared.thumbnails(for: player, engine: .castlabs),
      let vttURL = URL(string: "https://example.com/thumbs.vtt")
else { return }

thumbnails.loadThumbnailsFrom(webVTTtrack: vttURL) { error in
    guard error == nil else { return }

    if let thumb = thumbnails.getThumbnail(atTimestamp: 12.0) {
        // thumb.imageData, thumb.timeStampStart, thumb.timeStampEnd
    }
}
```

`getThumbnail(atTimestamp: CMTime)` is deprecated. Use `Double` seconds.

## WebVTT thumbnails

WebVTT thumbnail tracks can reference single images or grid regions.

Single image entries:

```text
WEBVTT

00:00:00.000 --> 00:00:09.999
thumb1.png

00:00:10.000 --> 00:00:19.999
thumb2.png
```

The timestamp needs to be in the exact _(HH:)MM:SS.MMM_ format.

Grid-region entries:

```text
WEBVTT

00:00:00.000 --> 00:00:09.999
thumbsGrid.png#0,0,430,242

00:00:10.000 --> 00:00:19.999
thumbsGrid.png#430,0,430,242
```

![](img/thumbs_grid.jpg)

Absolute URLs are also supported in WebVTT entries.
For bundled local WebVTT file please make sure all the grid images are also bundled at the same path as WebVTT file (or relative to):

![](img/bundled_webvtt.png)

or are a valid Url to the image:

```text
WEBVTT

00:00:00.000 --> 00:00:10.000
https://demo.castlabs.com/media/Route66/dash/thumbs/1_7.jpg#xywh=0,0,262,180

00:00:10.000 --> 00:00:20.000
https://demo.castlabs.com/media/Route66/dash/thumbs/1_7.jpg#xywh=262,0,262,180

00:00:20.000 --> 00:00:30.000
https://demo.castlabs.com/media/Route66/dash/thumbs/1_7.jpg#xywh=524,0,262,180
```

## Master grid thumbnails

You can define grid-based thumbnails without WebVTT by using `GridThumbnail`.

Explicit template:

```swift
let grid = GridThumbnail(
    baseUrl: URL(string: "https://demo.cf.castlabs.com/media/Route66/dash/thumbs")!,
    pathTemplate: "$index$_7.jpg",
    gridHeight: 10,
    gridWidth: 10,
    durationMs: 10000,
    maxIndex: 7
)
```

Single URL pattern (max index derived from `.../$index$_N.ext`):

```swift
let grid = GridThumbnail(
    baseUrl: URL(string: "https://demo.cf.castlabs.com/media/Route66/dash/thumbs/$index$_7.jpg")!,
    gridHeight: 10,
    gridWidth: 10,
    durationMs: 10000
)
```

Load grid thumbnails:

```swift
thumbnails.loadThumbnailsFrom(gridThumbnail: grid) { error in
    // Handle completion
}
```

## Cache management

Loaded WebVTT/grid resources are cached on device storage for reuse (including offline reuse).

```swift
thumbnails.deleteLocalCache()
```

## Apple TV player - ApplePlayerViewController

To display thumbnails on the Apple TV player, I-Frames are required in the HLS manifest file.

```text
# I-Frame Playlists
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=241218,BANDWIDTH=1074106,CODECS="avc1.4D401F",RESOLUTION=1280x720,URI="media-1/iframes.m3u8"
```



## Native Apple thumbnails (`.apple`) - Experimental!

Based on `AVPlayer` track selection - `AVPlayer` decides whether to use I-Frames tracks or current playback track to form thumbnails. To display thumbnails a dedicated `UIView` is needed.

### Seek tolerance

Configure `nativeThumbnailsSeekTolerance` on `HLSSettings` to control how accurately the Apple native thumbnails engine seeks when rendering thumbnails.

Set `nativeThumbnailsSeekTolerance` to `0` to request exact frame extraction from the video data. Use a non-zero tolerance, such as the default `0.5`, to allow `AVPlayer` to seek to a nearby frame; for HLS streams with I-Frame playlists, this lets native thumbnails use the I-Frame track from the HLS playlist.

```swift
let hlsSettings = HLSSettings(nativeThumbnailsSeekTolerance: 0.5)
_ = PRESTOplaySDK.shared.setup("LICENSE", [HLSPlugin(hlsSettings)])
```

For native preview rendering:

```swift
let thumbnails = PRESTOplaySDK.shared.thumbnails(for: player, engine: .apple)

DispatchQueue.main.async {
    thumbnails?.getThumbnail(atTimestamp: 10.0, renderOn: thumbnailView) { error in
        // Optional completion
    }
}
```

IFrame track requirements are described in the following documents:
1. https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.6.3
2. https://developer.apple.com/documentation/http-live-streaming/hls-authoring-specification-for-apple-devices#Trick-Play
