Introduction

Here is the video we will use to illustrate how to build the thumbnails file.
A new opportunity to try the autoplay feature.
We hide the volume button, set the volume to 0 and the volumeForced to true so we can be sure that the video will be muted even on refresh.
Yes ! that's really silly but even if your media has no audio track, some browsers will stubbornly refuse to autoplay it unless volume is explicitly set to 0.
It should be great if this can be done automatically when the video has no sound as this is the case here. But the lack of audioTracks property support by most browsers prevents this...

HTML

<div class="video-container"> <video><!-- Our video container --></video> </div>

JAVASCRIPT

new AmstramgramMediaPlayer( document.querySelector('video'),//Our target tag { autoplay: true,//Set autoplay mute: { hidden: true },//Hide the volume button src: 'assets/thumbnails/timeCode.mp4',//Our video source thumbnails: {//Our thumbnails properties src: 'assets/thumbnails/timeCode-thumbs.jpg',//The source width: 120,//The width of one thumbnail. Just for demo since 120 is the default value. int: 1.24//The time interval between two thumbnails in seconds }, volume: 0,//Set the volume to 0 volumeForced: true//Force the player to ignore the volumeGroup value stored in session storage }, function () {//Callback //listen to the 'amst__play' event dispatched by the player when playback begins. this.on('amst__play', function onFirstPlay() { this.off('amst__play', onFirstPlay)//Remove the listener this.hideControls()//Hide the player controls //Listen to the media 'ended' event this.params.media.addEventListener('ended', _ => { this.play()//Re-launch the playback this.hideControls()//And hide the controls }) }) } )

Methodology

Throughout this presentation, the thumbnail world will be used to designate a snapshot of the video captured at regular time intervals, for example one picture every second.
But beware !!!
We must not reason in terms of time, but in terms of number of frames, since a video is still and always only a succession of discrete frames.

With a 25 fps video that is two minutes long, taking a 120 pixels wide thumbnail every 25 frames (ie every second) will generate a 12,000 pixels wide file.
The interval between each snapshot must therefore be adapted to the video duration so as not to generate a too heavy file.

The thumbnails file used by our video player consists of a collection of thumbnails joined together on a single line.
It is set as background-image of a div whose dimensions correspond to that of one thumbnail.
The css background-position-x property is adapted accordingly to the thumbnail to display.

The height of the div container is simply given by the natural height of the thumbnails container file, known once it has been loaded.
On the other hand, we can't guess its width. We indeed have access to the natural width of the container but we have no information on the number of thumbnails or the width of a thumbnail.

So, we already have two data to provide to the player :
- the thumbnails container path.
- the width of a thumbnail.

Knowing a thumbnail width, it's easy to get the number of thumbnails included in the container :
Number of thumbnails = Container Natural Width / Thumbnail Width
Yep !

However, to set to the background-position-x property accordingly to the video timestamp pointed by the user, we also have to know what duration each thumbnail covers...
And this duration is impossible to guess from what we already know.

Yes... I know... You don't trust me !
So, let's try anyway !

Suppose we have a video of 1.2 seconds, with a frame rate of 25 and so 1.2 * 25 = 30 frames.
Note that a frame lasts 1 / 25 = 0.04 s = 40 ms.
Below, you can see how it looks. Be aware that, like for an array, first frame index is 0.

Now, suppose we take a 120 pixels wide snapshot every 7 frames.
We'll get 5 thumbnails (frame 0, frame 7, frame 14, frame 21, frame 28) and the width of the resulting file is 5 * 120 = 600 pixels.

Thumbnail indexed 0 covers frames from 0 to 6, ie 7 * 40 = 280ms.
Thumbnail indexed 1 covers frames from 7 to 13 (ie 280ms).
Thumbnail indexed 2 covers frames from 14 to 20 (ie 280ms).
Thumbnail indexed 3 covers frames from 21 to 27 (ie 280ms).
Thumbnail indexed 4 covers frames 28 and 29, ie 2 * 40 = 80ms !

We just tell to the player that the thumbnail width is 120 pixels.
As it is a smart guy, it deduces that there are :
600 / 120 = 5 thumbnails.

But then ?

It could think that each thumbnail covers :
VIDEO_DURATION / THUMBNAILS_NUMBER = 1200 / 5 = 240 ms ie 6 frames...

Not so smart !...

Indeed, it should rather be something like this :
VIDEO_DURATION / (THUMBNAILS_NUMBER - 1) = 1200 / 4 = 300 ms ie 7.5 frames

(Yes.. It's not easy for everyone...
Let simplify a little bit more the current example and think of a ten frames video with a snapshot every 3 frames.
We get 4 thumbnails : 0, 3, 6, 9.
But :
10 / 4 = 2
Whereas :
10 / (4 - 1) = 3
End of the parenthesis)

But even that is also wrong !
Because, our player can't know that the last thumbnail covers only 80ms...

So ! There is no alternative !
To get the right correspondence between a given timestamp of the video and the thumbnail to display, we have to know the time interval between two thumbnails.
In our example, this interval is 280 ms.
Consequently :
- a timestamp of 520 ms (Time Code = 00:00:00:13. Frame indexed Math.floor( 520 / 40 ) = 13) will require the thumbnail indexed Math.floor( 520 / 280 ) = 1;
- a timestamp of 1,02 s (Time Code between 00:00:00:25 and 00:00:00:26. Frame indexed Math.floor( 1020 / 40 ) = 25) the thumbnail indexed Math.floor( 1020 / 280 ) = 3;
[...]

To summarize, our player needs three data to display thumbnails :
- the thumbnails container file source;
- the width of one thumbnail;
- the time interval between two thumbnails.

The last point to examine is : how much thumbnails do we need ?
And the answer is : it depends...
I think that an hundred could be set as a maximum in all cases just because the width of the seeking area will rarely exceed 1900 pixels. Even in this borderline case, a pointer horizontal displacement of 19 pixels would be sufficient to update the thumbnail.
But for a video duration less than 1 minute and 40 seconds (100 seconds), it would result in a thumbnail in less than a second, which is not very reasonable and above all quite useless.
So we can decide that the interval between two thumbnails should be at least one second for videos less than 100 seconds long and about one hundredth of their duration for others.

That being said, the big question remains : how to get this set of thumbnails in a single file ?
FFmpeg and its toolbox kindly brings a solution to our problem...

Video informations

We need two informations about our video : its duration and its number of frames.
ffprobe can give us these two data with one quite simple shell command line :
(Okay, this assumes the FFmpeg folder is specified in your path environment variable and that you launch a terminal in the video file folder... But I think you're brilliant enough to guess it !)

ffprobe -v error -select_streams v:0 -show_entries stream=duration,nb_frames -of csv=p=0 -i timeCode.mp4
  • -v error : hide the info output for a better readability.
  • -select_streams v:0 : select only the video stream.
  • -show_entries stream=duration,nb_frames : show the entries for duration and nb_frames.
  • -of csv=p=0 : minimize the output.
  • -i timeCode.mp4 : set our video as input.

You can get the full explanation and some alternative methods here.

And here is the output for our timeCode.mp4 video file :

126.440000,3161

Or in a more human friendly way : our video lasts 126.44 seconds and contains 3161 frames.

Let's say we like to have 100 thumbnails.
First thumbnail will be the first video frame.
We therefore have 99 snapshots left to distribute, i.e. one every Math.floor (3161 / 99 ) = 31 frames.
So, we should take a snapshot every 31 frames.
But, if we do so, we leave the three last seconds without a snapshot since 99 * 31 = 3069.
So, we have two solutions :
- keep a 31 frames interval but take 102 thumbnails : (102 - 1) * 31 = 3131.
- choose a 32 frames interval and extract 99 thumbnails : (99 - 1) * 32 = 3136.
Okay ! Let's go with the first one...

31 frames give a duration of 31 * 126.44 / 3161 = 1.24 s.
This will be one of the data to transmit to our player so that it can do its job properly.

Generating

Here is the magic line for FFmpeg to do what we need :

ffmpeg -i timeCode.mp4 -y -frames 1 -q:v 10 -vf "select=not(mod(n\,31)),scale=120:-1,tile=102x1" timeCode-thumbs.jpg

And here is what we get :