Make JW Player Your Website’s Hero

A few pages on our website use full-motion backgrounds to catch the eye of our visitors. Our recently redesigned homepage as well as our 404 page use JW Player to load a playlist and cycle through the videos in with smooth transitions in between. I’ll share with you how we do it, and give some tips on best practices for using video for your website’s hero.

Choosing the Right Videos for Your Background

On a typical website, a hero image is used to draw attention to above-the-fold content. As with any background, attention must be directed the content layer on top of it. We’ve found that good background video should have low contrast and soft movement (or preferably both). Our current playlist on the homepage contains three different videos: “Whiskey Smoke”, “Jellyfish”, and “Brooklyn Bridge”.

Why Not Just Use an HTML5 Video Tag?

Using a vanilla <video> tag requires a lot of development and administrative work upfront, which can be a pain for developers. JW Player takes care of hosting, content management, cross-browser compatibility, and content delivery so you can get up and running super quick. Check out our features to learn more.

The Steps, in a Nutshell

  1. Prepare your videos
  2. Upload and Tag Your Videos
  3. Set up the Player
  4. Style Your Content

Prepare Your Videos

The first thing you’ll want to do is get your background video ready. Trim off any blank frames from the beginning and end of the video. Trimming these blank frames will help your videos transition smoothly as the playlist progresses.

The next step is to grab the first frame and save that as an image – this will become the video’s poster image. The trick that we’ll be doing later will make the player fade out before finishing, so that the next video in the playlist can fade in after it’s loaded and starts playing.

Thirdly, if you don’t plan on making the video interactive, a good idea would be to strip out any audio tracks that may be present. Removing the audio provides two advantages: the final result will have less data to load, and baked-in media controls won’t appear on newer mobile devices. I’ll get into that a little later.

Now that you have your fresh-cut videos and first-frame poster images to go along with them, it’s time to dive into the dashboard.

Upload and Tag Your Videos

After uploading your videos, make sure to upload the poster images created earlier. We also recommend tagging your videos so you can easily create a Curated Playlist if you have multiple background videos that you’d like to loop through.

Set up the Player

So now that we have our videos uploaded and a playlist created, let’s look at the different parts that go into the code. Here’s how we want the experience to go:

Use an AJAX call to grab the JSON feed for the playlist

First, we grab the JSON feed data using the JW Player Feeds API. This allows us to store the playlist config and modify it before setting up the player (for instance, we shuffle the playlist order when you load the page).

var fullPlaylist;
var mediaId = 'CSh1f2GU'; // this would be your playlist's media ID

$.ajax({
	url: 'https://content.jwplatform.com/feeds/' + mediaId + '.json',
	dataType: 'JSON'
}).done(function(data) {
	fullPlaylist = data.playlist; // storing the playlist data for later use
	setupPlayer();
});

Player Setup

With our playlist object, we then create the player. The player should have a few defaults set so it works best:

  • The video should autoplay
  • Controls should be disabled
  • Stretching should be ‘fill’ so it doesn’t show black bars

The setup might look similar to this:

var videoInstance = jwplayer('hero-video').setup({
	autostart: true,
	controls: false,
	playlist: fullPlaylist,
	androidhls: false,
	mute: true,
	repeat: true,
	stretching: 'fill',
	height: '100%',
	width: '100%'
});

Now that our player is set up, let’s add some event handlers so we can control the experience when certain things happen. One goal is to present a smooth transition between videos in the playlist.

Note: These methods are still a good idea even if you’re only using one video. If you use the Feed API with the media ID of a single video, the API will return a playlist that contains just that one video. Since the player behaves the same way for any number of videos, the next steps will provide a good visual experience.

What we want to do is eliminate the blank step while the player loads the next video. This is what the poster image is for – we use the “first frame” image as a fading transition between videos. Here’s what we’re going for:

  1. Fade the video out to transparent with a CSS class (opacity: 0;).
  2. When the video starts playing, remove the CSS class, which will trigger a transition event.
  3. When the transition ends and the video is fully opaque, set the background-image of the hero container to the poster image of the next-up video, as designated by the fullPlaylist object.
  4. When the video is within 2 seconds of finishing, repeat at step 1.

So we’ll need some CSS setup:

.hero-video {
  opacity: 1;
  transition: opacity 2s ease-out;
}

.hero-video.is-hidden {
  opacity: 0;
}
var videoContainer = $('#hero-video-container');
var isFading = false; // This flag makes sure we aren't constantly adding the class when we don't need to

videoInstance.on('play', function() {
  videoContainer.removeClass('is-hidden');
});

videoInstance.on('complete', function() {
  isFading = false;
});

videoInstance.on('time', function(e) {
  if (e.position >= (e.duration - 2) && !isFading) {
    isFading = true;
    videoContainer.addClass('is-hidden');
  }
});

videoContainer.on('transitionend', function(e) {
  if (!videoContainer.hasClass('is-hidden')) {
    setPosterImage(videoInstance.getPlaylistIndex() + 1);
  }
});

The last piece calls setPosterImage which grabs the poster image for the next video and sets it to the background of the video container:

function setPosterImage(playlistIndex) {
  var posterImage;
  playlistIndex = playlistIndex >= fullPlaylist.length ? 0 : playlistIndex;
  posterImage = fullPlaylist[playlistIndex]['image'];
  // create an img tag to preload, then set it as the bg of the videoContainer
  $('<img>').attr('src', posterImage).load(function() {
    $(this).remove();
    videoContainer.css('background-image', 'url(' + posterImage + ')');
  });
}

Style Your Content

At this point you’re probably thinking this is great, but it’s not the hero I was expecting. The rest comes down to HTML structure and CSS styling. The trick is to let the contents of your hero dictate its height and width, instead of using the video (which is configured to take up as much space as possible). Here’s what that may look like:

<header class="hero">
  <div id="hero-video-container">
    <div id="hero-video" class="hero-video" data-mediaid="CSh1f2GU"></div>
  </div>
  <div class="hero-content">
    <h1>The Hero We Deserve</h1>
  </div>
</header>
.hero {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vmin;
  position: relative;
  width: 100%;
}

.hero-video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

Bits and Pieces

Going back to why we removed the audio tracks from the videos, the answer is pretty simple. When media has audio on mobile devices, playing that media will pause anything else that’s playing on the device. For example, if you’re listening to music on your phone while browsing the web, auto-playing media will pause your music. I felt this was a frustrating experience, especially when it’s a video used for aesthetic and not for high-focus content. This behavior is almost entirely fixed by removing the audio tracks before uploading the video. One odd case shows up when using HLS on Android – playing HLS content in Chrome will pause other background media and present the system’s media controls regardless of whether or not it has audio. This can be resolved by adding androidhls: false to your JW Player config.

We also use an overlay on top of the video via an ::after pseudo-element. This lets us modify the video and adjust the contrast so the content on top of the video stands out better. We have a few additional steps that use an accent color, which is added as a custom value on the video. The Feed API returns custom values that you can set in the Dashboard, so you can keep all configuration info in one place, and have it load dynamically with various JW Player API calls and events.

Final Notes

The biggest advantage that JW Player has over building an experience like a background video hero from scratch is that everything needed to get up and running can be referenced dynamically. This means that Content Editors and Publishers can handle all of the metadata, playlists, and custom variables, and Developers can focus on making their ideas stand out. Similar to the Insights Video Experience, all of the content is handled by the content creators, so updating the playlists or video titles doesn’t require any tedious development work. It’s a win-win!