In this tutorial, we'll dive deep into how you can seamlessly integrate a video player in your React.js applications. Besides introducing the HTML5 <video>
tag, exploring its capabilities and limitations, we'll also be talking about making use of the popular Video.js library to power some advanced recipes like adding Adaptive Bitrate Streaming (ABS) to our video playback.
Table of Contents
- Setting up the dependencies
- Basic video playback recipes with HTML5
<video>
tag - Advanced video playback recipes with video.js
- ImageKit.io for Video Transformations and Optimisation
Setting up the dependencies
Before we start, we would need to setup our react project and its dependencies. For this tutorial, we would be using React 18.
Creating a new react app
The easiest and the most straight-forward way to create a new React.js app is using create-react-app. This simply exposes a single-line command which takes care of setting up the boilerplate.
npx create-react-app@latest react-video-player-ik-demo
When we run the above command, the utility starts setting up a fresh project boilerplate in the directory we specified i.e. react-video-player-ik-demo
. This might take a few minutes, but once it is completed, you should see a success message on your terminal.
We can now open the project directory in an editor of our choice (for this tutorial, we would be using Visual Studio Code) and this is what it should look like.
This indicates that the project boilerplate is ready and we can run it using the npm start
command from within the project directory. When we execute the command, it should just start up our application in the browser. At this stage, this is what we see in our browser (you can manually visit http://localhost:3000
in your browser if it doesn’t open by itself).
If this is what you see in your browser, congratulations - you have a fresh React.js app successfully running and are ready for the next step.
Basic video playback recipes with HTML5 <video>
tag
We can make use of the HTML5 <video>
tag to create a basic video player in our react app, without having to rely on third-party libraries or plugins.
Getting started with <video>
To do this, let’s go ahead and edit our App.js
with the code below.
import React from 'react';
export default function App() {
return (
<video
src='https://ik.imagekit.io/ikmedia/example_video.mp4'
width='1440'
height='680'
/>
);
}
The src
attribute accepts a local or a remote path to a video file that needs to be bound with the player.
On saving these changes and trying it out in the browser, you might see a video thumbnail but not the actual video.
This is the expected behaviour. But weren’t we supposed to add a video playback support? Correct, the reason for this is that we haven’t added any controls yet. Let’s go ahead and add a controls
attribute to our <video>
tag like:
<video
src='https://ik.imagekit.io/ikmedia/example_video.mp4'
width='1440'
height='680'
controls
/>
You should now see the basic controls like the play/pause toggle, the seek bar, full screen view etc. on your video.
<video>
tag provides us with a few attributes that we can use to control the behaviour of the video player. You can check out the MDN Web Docs for a comprehensive list of all attributes supported by the <video>
tag.And that’s it. This is how we build a basic video player in React.
Automatically playing videos
To automatically play the video as soon as the page loads, we can use the autoPlay
attribute like:
<video
src='https://ik.imagekit.io/ikmedia/example_video.mp4'
width='1440'
height='680'
controls
autoPlay
/>
autoPlay
attribute. To fix this, we require another attribute muted
to be added.On some other devices / browsers (eg: Safari on iOS), you might need to add an attribute
playsInline
which basically indicates that the video is to be played “inline”, within the element’s playback area.After the above-mentioned changes, our App.js
should look like:
import React from 'react';
export default function App() {
return (
<video
src='https://ik.imagekit.io/ikmedia/example_video.mp4'
width='1440'
height='680'
controls
autoPlay
muted
playsInline
/>
);
}
To disable video autoplay, autoplay="false"
will not work; the video will autoplay if the attribute is there in the <video>
tag at all. To remove autoplay, the attribute needs to be removed altogether.
Adding a thumbnail poster
We can also display an image as a poster on the video until the video loads and a user action is registered on it. To do this, we can make use of the poster
attribute like:
<video
src='https://ik.imagekit.io/ikmedia/example_video.mp4'
poster='https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=w-1200,h-680'
width='1440'
height='680'
controls
/>
The poster
attribute accepts a local or a remote path to an image that needs to be displayed as a poster.
If this attribute isn't specified, nothing is displayed until the first frame is available, then the first frame is shown as the poster frame.
poster
attribute is used along with autoPlay
, the latter over-shadows the final behaviour and the video starts playing as soon as it is loaded without waiting for a user action.Adjusting the width and height
We can adjust the width and height of the player by using the width
and height
attributes respectively. So to build a 600x300 player, our <video>
element would look like:
<video
src='https://ik.imagekit.io/ikmedia/example_video.mp4'
poster='https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=w-1200,h-680'
width='600'
height='300'
controls
/>
In such cases, to optimise overall network bandwidth and improve user experience, we can modify the actual video file according to the container size. We'll talk about how ImageKit's Video API can do this for us in the further sections.
Looping the video
In some use-cases like marketing pages etc., we might want to play a video visual indefinitely. To loop over a video, we can use the loop
attribute provided by the <video>
tag like:
<video
src='https://ik.imagekit.io/ikmedia/example_video.mp4'
poster='https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=w-1200,h-680'
width='1440'
height='680'
controls
loop
/>
Advanced video playback recipes with Video.js
Now, while the native <video>
tag might just work for the basic setups, more advanced use-cases around video playbacks such as multiple playback sources, multiple stream formats, support for ABS calls etc. for a mature framework. Fortunately, there are quite a few frameworks built to help us overcome limitations or just make our implementations streamlined. Let’s talk about one such library - Video.js.
Video.js is the most popular web video player library out there, being used by over 700,000 websites. It is built from the ground up for an HTML5 world and supports HTML5 video and Media Source Extensions, as well as other sources like YouTube and Vimeo (through plugins) on both desktops and mobile devices.
Getting started with Video.js
The easiest way to get started with Video.js in React is to install the library using npm
.
npm install video.js@7.18.1
Once done, you should see it added to your dependencies list in package.json
.
7.18.1
version which is the last stable version which reliably supports some of the plugins we’d be using ahead in this tutorial. Make sure you stick to the same to avoid running into dependency issues in your implementations.Once we have Video.js installed within our project, the next step is to start making changes in our project to create a basic video player.
Step 1: Make a VideoJS
component
The first step is to import the videojs
methods and classes from the library and bind them to the corresponding DOM element which will eventually be decorated as a fully-functioning video player.
Let’s create a new file called VideoJS.js
inside our src/components
directory. If the directory doesn’t exist, we create it ourselves. After this step, our project directory structure should look something like:
├── node_modules/
├── README
├── package.json
└── src/
├── App.css
├── App.js
├── App.test.js
├── components/
│ └── VideoJS.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
In the VideoJS.js
file, we’re going to setup our video player component.
import React, { useEffect } from 'react';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
export const VideoJS = (props) => {
const videoRef = React.useRef(null);
const playerRef = React.useRef(null);
const { options, onReady } = props;
useEffect(() => {
// Make sure Video.js player is only initialized once
if (!playerRef.current) {
// The Video.js player needs to be _inside_ the component el for React 18 Strict Mode.
const videoElement = document.createElement("video-js");
videoElement.classList.add('vjs-big-play-centered');
videoRef.current.appendChild(videoElement);
const player = videojs(videoElement, options, () => {
videojs.log('player is ready');
onReady && onReady(player);
});
playerRef.current = player;
// You could update an existing player in the `else` block here
// on prop change, for example:
} else {
const player = playerRef.current;
player.autoplay(options.autoplay);
player.src(options.sources);
}
}, [onReady, options, videoRef]);
// Dispose the Video.js player when the functional component unmounts
React.useEffect(() => {
const player = playerRef.current;
return () => {
if (player && !player.isDisposed()) {
player.dispose();
playerRef.current = null;
}
};
}, [playerRef]);
return (
<div data-vjs-player>
<div ref={videoRef} />
</div>
);
}
export default VideoJS;
Note that we are utilizing a placeholder element
videoElement
to append our Video.js element.This approach is necessary because React 18 StrictMode introduces additional behaviours that aim to prepare for a reusable state in dev mode. Specifically, during a component's initial mounting, React simulates mounting, unmounting, and then mounting again.
When React simulates unmount, the video.js
dispose
removes the video element from the DOM, which invalidates the ref that React held, leading to an error. Hence, by wrapping it in the parent element, we can get rid of this error.Step 2: Use the VideoJS
component in our app
And now in our App.js
, we import and use the component we built above like:
import React from 'react';
import videojs from 'video.js';
import VideoJS from './components/VideoJS';
export default function App() {
const playerRef = React.useRef(null);
const videoJsOptions = {
controls: true,
responsive: true,
fluid: true,
sources: [{
src: 'https://ik.imagekit.io/ikmedia/example_video.mp4',
type: 'video/mp4'
}]
};
const handlePlayerReady = (player) => {
playerRef.current = player;
// You can handle player events here, for example:
player.on('waiting', () => {
videojs.log('player is waiting');
});
player.on('dispose', () => {
videojs.log('player will dispose');
});
};
return (
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
);
}
Now, at this step, your project should successfully rebuild and on refreshing the browser, you should see a video player like shown below.
Autoplay, thumbnail posters, basic resizing and loops
Adding the basic autoplay, loops, posters etc. all can be done by modifying the configuration object like:
const videoJsOptions = {
autoplay: true, // autoplay the videos
muted: true, // required by newest versions of browsers for autoplay to work
controls: true, // show controls on the player
responsive: true, // make the player responsive
fluid: true,
loop: true, // loop the video
sources: [{
src: 'https://ik.imagekit.io/ikmedia/example_video.mp4',
type: 'video/mp4'
}],
// adding a thumbnail poster
poster: 'https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=w-1200,h-680'
};
Now, unlike the <video>
tag, to adjust the width and height in video.js, we can add a style attribute to the element. In this case, we’ll just create a new <div>
block and apply the style to it.
<div style={{ width: '1440px', height: '900px' }}>
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
</div>
This will render a player container of size 1440x900.
Adaptive Bitrate Streaming (ABS)
Adaptive Bitrate Streaming (ABS) is an approach to video streaming that is an absolute game changer for content creators and viewers alike – adjusting the quality of the video stream in real-time, based on the viewer's internet connection. This means that regardless of the viewer's device or network speed, they can enjoy a seamless viewing experience with minimal buffering or lag.
Video.js player supports ABS out-of-the-box, which means when given a supported video file (for example, M3U8), it adjusts the stream source as per the bitrate. Besides, it also shows up controls for manual selections for users. Let’s see how to implement this.
Step 1: Setting up the dependencies
For enabling support for ABS, we need two more libraries to be installed to our project - videojs-http-source-selector
and videojs-contrib-quality-levels
. As the name suggests, the first library is used to enrich the player with user controls which help the user explicitly choose the quality of video playback, and the second library provides a framework of working with source quality levels.
Let’s go ahead and install them.
npm install videojs-http-source-selector@2.1.0 videojs-http-source-selector@1.1.6
After this step, you should see these two added as a dependency to your package.json
.
Step 2: Configuring the imports in our VideoJS
component
To make sure our player uses the libraries, we need to import them in our VideoJS
component.
// Inside components/VideoJS.js
// For ABS Support
import "videojs-contrib-quality-levels";
import "videojs-http-source-selector";
Also, we would need to bind the source selector on our player
instance for our player to be able to use the underlying methods. Let’s go ahead and do that.
// Make sure Video.js player is only initialized once
if (!playerRef.current) {
...
...
playerRef.current = player;
// Binding to the source selector for ABS
player.httpSourceSelector();
...
...
} else {
...
...
player.src(options.sources);
// Binding to the source selector for ABS
player.httpSourceSelector();
...
...
}
Step 3: Setting up a source video stream
Once we have the player set up for ABS, we need to update the source stream with a supported file. This can be done by using multiple raw video files embedded in a UTF8-encoded manifest file, eg. .m3u8
.
For this demo, we are playing the video from the following manifest file - https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.m3u8?tr=sr-240_360_480_720_1080
.
Later in this tutorial, we'll be talking about how we can use ImageKit's Video API to generate such
.m3u8
or .mpd
manifests.Let’s head over to our App.js
and update the source stream in the config.
const videoJsOptions = {
...
...
sources: [{
src: `https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.m3u8?tr=sr-240_360_480_720_1080`,
type: 'application/x-mpegURL'
}]
};
And that’s it. Let’s head over to our application in the browser to see it working.
If this is what you see in your browser - Congratulations! You’ve successfully implemented ABS on your stream using Video.js 🎉
ImageKit.io for Video Transformations and Optimisation
Video files, when transferred over a network to a user's device, can significantly impact web experience if not optimized. Implementing best practices for transformation and optimisation before delivery, hence, becomes vital. The techniques include, but are not limited to:
- Resizing to proper dimensions.
- Encoding to compatible formats.
- Creating multiple bitrate versions for Adaptive Bitrate Streaming (ABS) for users on slower networks.
ImageKit.io is a powerful platform that offers real-time image and video optimisation tools. It serves as a comprehensive solution for end-to-end media management, optimisation, and delivery while supporting various transformations, resizing, cropping, format conversion, quality adjustment, etc. The platform also offers a generous free tier to get you started.
Let’s look at some use-cases where ImageKit’s Video API can not only help deliver better video playback experiences for your users but also optimise your video delivery and save on your network costs.
Resizing the video to adapt to the player’s dimensions
In the resizing approaches we’ve seen earlier in this tutorial, we have been resizing our player’s container dimensions. Although this works well to fit your player well within the UI, but is it optimised? The answer is no, and here’s why.
When you’re using a 4k raw video file which has the dimensions 3840x2160 and trying to load it over the network for the user only to fit the video inside a player of dimensions 640x480, there’s a huge unnecessary network overhead both for the user and for your systems. And a network overhead comes with a cost - in this case, both in terms of money and user experience.
So, how do you solve for this problem? A straightforward approach is creating multiple raw video files with different dimensions and call for the most compatible one based on the player’s container size. And here, you introduce overhead storage costs and a good amount of complexity in your code.
With ImageKit’s Video Transformations, you can transform your raw video file into an appropriate size on-the-go while loading it within your player’s container - with just a query parameter. Let’s see how.
If we use the raw video file, we would be transferring ~37.7 MB over the network. To resize the video file for a 640x480 container, we need to add a query parameter tr
with the value w-640
.
Updated URL: https://ik.imagekit.io/ikmedia/example_video.mp4?tr=w-640
After resizing, we have reduced the network overhead to ~1.7 MB (a steep 95.49% reduction).
Let’s update our code to make use of this. In App.js
,
// Add a constant
const playerWidth = 640;
// Updating the url in our sources configuration to use the constant
const videoJsOptions = {
controls: true,
responsive: true,
fluid: true,
sources: [{
src: `https://ik.imagekit.io/ikmedia/example_video.mp4${playerWidth ? `?tr=w-${playerWidth}` : ''}`,
type: 'video/mp4'
}],
poster: 'https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=w-1200,h-680'
};
// In the return statement, let's put a width to our element
return (
<div style={{ width: `${playerWidth}px` }}>
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
</div>
);
To learn more about video resizing, you can check out the documentation here: Resize, crop, and other common video transformations | ImageKit.io Docs
Delivering the most suitable video format
With ImageKit’s Video API, you don’t need to worry about format selection or adding multiple sources as it will automatically identify formats compatible with your users' devices and deliver the most relevant and optimised format. Having said that, you can also deliver a particular format as per your need.
This can be achieved with the query parameter tr
like tr=f-webm
for WebM formats, tr=f-mp4
for MP4 formats and tr=f-auto
for setting it to auto selection as mentioned above. You can learn more about this here: Automatic video format conversion | ImageKit.io Docs
Adaptive Bitrate Streaming (ABS)
Generating ABS manifests is a one-step process, thanks to ImageKit’s Video API which supports generating MPEG-DASH and HLS manifests on-the-fly with a single source video file.
If you're new to Adaptive Bitrate Streaming (ABS), you can check out this article which talks about ABS in-depth and compares both the protocols to help you pick one that suits you.
MPEG-DASH
To create an MPEG-DASH ABS playlist, you need to suffix the URL path with ik-master.mpd
and use the tr
query parameter with the sr
value to specify the resolutions. Here’s an example:
https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.mpd?tr=sr-240_360_480_720_1080
HLS
To create an HLS ABS playlist, you need to suffix the URL path with ik-master.m3u8
and use the tr
query parameter with the sr
value to specify the resolutions. Here’s an example:
https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.m3u8?tr=sr-240_360_480_720_1080
Generating video thumbnail posters
In the sections above, we’ve seen how both <video>
tag and Video.js library supports thumbnail posters which act as a “loading state” for our player. This poster can be a local image or a remote image which is referenced by its URL.
With ImageKit, you can generate posters from a video just by suffixing the URL path with ik-thumbnail.jpg
. Adding to that, you can use a whole lot of transformation parameters to modify the poster as per your needs.
Let’s take an example to demonstrate this - we want the frame at the 10th second of the video to be our thumbnail and the poster size should be the same as the player size. Here’s how we’ll create a URL for the poster:
https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-10,w-640
Now, if we update this in our code like:
const playerWidth = 640;
...
...
const videoJsOptions = {
controls: true,
responsive: true,
fluid: true,
sources: [{
src: `https://ik.imagekit.io/ikmedia/example_video.mp4${playerWidth ? `?tr=w-${playerWidth}` : ''}`,
type: 'video/mp4'
}],
poster: `https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-10${playerWidth ? `,w-${playerWidth}` : ''}`
};
We should be able to see the poster on our video player.
After delving into the fundamental playback setup with the HTML5 <video>
tag and exploring its various configuration options, including its limitations like the absence of ABS support, we turned to the Video.js library for more advanced functionalities. By experimenting with features like ABS, it's evident that React.js, coupled with the versatility of Video.js and the advanced transformation and optimization capabilities of ImageKit, can truly enhance your video playback experience. With these powerful tools at your disposal, you can elevate your video content delivery to unparalleled levels of quality and performance.
You can find the code implementation for this tutorial on Github.