Thumbnails
Shows a frame above the timeline as the pointer moves along it.
import { thumbnails } from "react-video-kit/thumbnails";Adds 912 B to a player1.3 KB on its ownSource
Shows a frame from the video above the timeline, along with the time. Hover the timeline in the demo. The line under the player changes when the thumbnail file is fetched, which doesn't happen until that first hover.
thumbnails.vtt hasn't been fetched. Hover the timeline.
"use client";import { useEffect, useState } from "react";import { VideoPlayer } from "react-video-kit/player";import { thumbnails } from "react-video-kit/thumbnails";const FILE = "/media/thumbnails.vtt";const plugins = [thumbnails({ src: FILE })];export default function Thumbnails() { const [fetched, setFetched] = useState(false); // Watches the network, to show that nothing loads until the first hover. useEffect(() => { const observer = new PerformanceObserver((list) => { if (list.getEntries().some(({ name }) => name.endsWith(FILE))) setFetched(true); }); observer.observe({ type: "resource" }); return () => observer.disconnect(); }, []); return ( <> <VideoPlayer src="/media/sample-720.mp4" poster="/media/poster.jpg" plugins={plugins} load="visible" /> <p className="mt-3 text-sm text-current opacity-80"> {fetched ? "thumbnails.vtt has been fetched." : "thumbnails.vtt hasn't been fetched. Hover the timeline."} </p> </> );}Usage
import { VideoPlayer } from "react-video-kit/player";import { thumbnails } from "react-video-kit/thumbnails";const plugins = [thumbnails({ src: "/thumbnails.vtt" })];<VideoPlayer src="/video.mp4" plugins={plugins} />;Thumbnails isn't in defaultPlugins(), so add it to the list if you use those.
To pick a file for each source, pass a function. Return null for a source with no thumbnails:
import { thumbnails } from "react-video-kit/thumbnails";const files: Record<string, string> = { "/film.mp4": "/film.thumbnails.vtt" };thumbnails({ src: ({ src }) => files[src] ?? null });The WebVTT file
Each cue covers a stretch of the video, and its text points at a rectangle in an image, called a sprite:
WEBVTT00:00.000 --> 00:05.000thumbnails.jpg#xywh=0,0,160,9000:05.000 --> 00:10.000thumbnails.jpg#xywh=160,0,160,9000:10.000 --> 00:15.000thumbnails.jpg#xywh=320,0,160,90#xywh=takes the left edge, the top edge, the width, and the height, in whole pixels.#xywh=pixel:0,0,160,90means the same.- The image path is relative to the WebVTT file. A full URL works too.
- Cues can point at different images, so a long video can use several sprites.
- A cue with no
#xywh=shows its whole image at 160×90, cropped to fit, so one image per cue works too. The#xywh=percent:form isn't read, and shows the whole image the same way.
Make a sprite with ffmpeg
This takes a frame every 5 seconds, scales each to 160×90, and tiles them into one image. For a 30-second video, that's 6 tiles in a row:
ffmpeg -i video.mp4 -vf "fps=1/5,scale=160:90,tile=6x1" -frames:v 1 -q:v 5 thumbnails.jpgThen write one cue for each tile, with the left edge at its index times the width, as in the file above.
When the file loads
The player fetches the WebVTT file the first time the preview needs it: when the pointer first hovers the timeline, or when a finger first taps or drags on it. A viewer who never touches the timeline never downloads it. The sprite loads next, and the frame appears once the image is decoded, with no further pointer movement.
The file loads with fetch, so a file on another origin needs CORS headers. Each new source starts over, fetching its own file on its first hover.
Place the part yourself
<Video.Timeline /> with no children includes ThumbnailPreview through its slots. If you give it children, put it in the preview yourself:
import * as Video from "react-video-kit";import { ThumbnailPreview } from "react-video-kit/thumbnails";<Video.Timeline> <Video.TimelineTrack> <Video.TimelineBuffered /> <Video.TimelineFill /> </Video.TimelineTrack> <Video.TimelineThumb /> <Video.TimelinePreview> <ThumbnailPreview /> </Video.TimelinePreview></Video.Timeline>;Behaviour
- Sprite frames are drawn at the size in the cue, one sprite pixel to one CSS pixel.
- Touch has no hover, so on touch the preview shows only during a drag. Seeking with the keyboard doesn't show it.
- A missing file means no thumbnails and no error. The preview still shows the time.
- A time with no cue shows the time alone.
- With the chapters plugin, the frame and the title stack in plugin order.
API
Options
| Option | Type | Description |
|---|---|---|
| src | string | ((source: SourceObject) => string | null) | A WebVTT file of thumbnails, or a function that picks one for each source. |
State
This plugin has no state of its own.
Commands
Call them with useCommands(thumbnails).
| Command | Type | Description |
|---|---|---|
| at | (time: number) => Thumbnail | null | The thumbnail for time, once loaded. Loads on first use, to save bandwidth. |
| whenLoaded | () => Promise<void> | null | Resolves when a load in progress finishes, or null when there's nothing to wait for. |
| prepare | (image: string) => Promise<void> | null | Resolves once the image can paint without a flash. |
Parts
| Part | Description |
|---|---|
| ThumbnailPreview |