Building a Video Player for Seven Dave Grohls

And just keeping it simple

Seven Dave Grohls take on East West

You played drums in one of the most pivotal bands of all time. After which, you founded what is now one of the greatest bands in rock history. In your free time, you’ve collaborated with everyone from Puff Daddy to Paul McCartney. So, what do you do to challenge yourself? This is a line of questioning only one person can answer: Dave Grohl. His answer? Composing an original 23 minute rock epic which he would perform (and film) on several different instruments, some of which he was proficient in and also keys. Why? To give perspective that his journey as a musician is as unfinished and explorative as a child learning her first chord progression. And, god damnit, he did it.

“To any musician, young or old, a beautiful studio full of instruments is like a playground.” — Dave Grohl — PLAY.

After the videos were shot and packaging was designed, I was tasked with turning this project into a digital reality. In addition to serving up the incredibly edited master film, I thought it would be nice to give users the ability to isolate any of the instruments as they traversed the performance. This was possible because Dave forced himself to capture each instrument in a single take. All 23 minutes. The performances you see are the actual performances used in the final edit. Yes, there are some long shots of Dave playing tambourine.

I had enough time to do some pretty exciting UX concepts but instead… I focused on keeping this experience very simple. Dave has a huge following from all walks of life (and web environments) and I knew that this experience was 99.99% a video player for the incredible content. My goal was to present an interface that immediately felt familiar while inviting viewers to explore and get informed. Did we accomplish that? You be the judge and read on to find out how it was done.

Player

Original Framer Prototype

I went into the player development with a couple of problems I needed solutions for. In total, there would be 15 videos we wanted to serve:

  • 1 master edit of the performance prepended with a mini doc (31:18)
  • 7 individual instrument performances with isolated audio (23:02)
  • 7 individual instrument performances with master audio (23:02)

As you can see, the master video was of a different length than the individual tracks. This would prove tricky when switching between the master and instrument videos while retaining current time. Why not cut the intro off and serve it separately? Well, that’s another issue you see. I developed on the knowledge that we would be serving the master from YouTube and the other videos from the cloud. So, the solution I required should handle several videos of various lengths hosted on multiple platforms. I should also be able to start any of the videos at a specified time depending on the viewer’s current time position.

I’ve built custom players for YouTube, Vimeo, and HTML5 before so I felt like what I needed was a library that normalized all of those Javascript APIs into a set of actions and events I could manipulate with a custom interface. I’m not sure what I typed into Google to find it but I am sure that I laughed out loud when I saw the description of Plyr:

A simple, accessible and customisable media player for Video, Audio, YouTube and Vimeo

Amazing. Sam Potts, you are a legend.

His incredibly documented library had all the functionality I required.

  1. The ability to create a custom interface by hiding any default controls during configuration and creating my own in HTML and CSS
  2. Methods for the core player functions: play, pause, rewind, forward, mute, and seeking
  3. A setter for the player’s current (or starting) time
  4. Player events to help evolve the interface during playback
  5. And most importantly, a setter for changing the source video in real time

Speaking of sources, I knew that the master video would be served from YouTube but I didn’t have a solution for the other 14 videos. Video hosting in general can be pretty pricey. Video hosting of 14 23 min videos served to one of the largest group of fans in rock? Yikes. I then remembered our Premium Vimeo account for Roswell Films. As it turns out, on some account tiers, Vimeo will provide links to the actual videos files on their servers for custom player development. This feature cannot be understated and it’s just another reason that I will keep renewing my personal Vimeo Pro subscription. Using a link to the actual video file will prevent Plyr from serving up the Vimeo embed player on accident.

Full disclaimer: We actually ended up serving the master video for our experience via Vimeo also. This was mostly because YouTube does things like hijacking the user’s current time and preventing the Intro from playing again on load. While some people might see that as a feature (saving a user’s current time) it felt more like a problem for our experience. I was also worried that YouTube would serve up ads and ruin our custom aesthetic. A lot of these decisions were made late in development and if I knew what I know now, I would have hosted everything on Vimeo and split the intro from performance. In the end, Plyr offered me the ability to roll with the punches as I always felt like the logic I was developing could handle our shifting needs.

Switch Instruments

A little masking tape goes a long way

Switching between the master and instrument videos involved considerations regarding tech, user experience, and user interface. I wanted users to be able to select any video at anytime but have the current time stay synced. Let’s start with the technical solution. First, you’ll want an array of videos like so:

videos: [
{
id: "master",
name: "Master",
url: URL
}, {
id: "drums",
name: "Drums",
url: URL
}
]

As I mentioned earlier, Plyr has a source setter that allows us to change the source video in real time. This video could be from YouTube, Vimeo, or in our case, a hosted URL.

player.source = {
type: 'video',
sources: [
{
src: URL,
type: 'video/mp4'
}
]
}

Simple stuff. It’s then up to you to decide how the user initiates that video change. After exploring various UX experiments, I decided on the most straight-forward option: a drop-down select. There’s just something insanely practical about having the choices fan out from the initial user touch point. Choosing a new video then simply requires a short mouse movement and click.

Using Vue.js, I can easily generate an option select from an array of videos using a combination of v-for (to generate a list of options) and v-model (to keep track of the selected video.)

<select v-model="selectedVideoId">
<option v-for="video in videos" v-bind:value="video.id">
{{ video.name }}
</option>
</select>

I then use this selectedVideoId to power a computed property which finds the current selected video from my video array. This gives me access to the video’s name and url in addition to it’s id.

selectedVideo() {
return this.videos.find((video) => {
return video.id == this.selectedVideoId
})
}

I can then use a Vue watcher to listen for changes to the selectedVideoId and finally called Plyr’s source setter.

watch: {
selectedVideoId(val) {
player.source = {
type: 'video',
sources: [
src:
]
}
}
}

It goes without saying that Vue’s solution to this problem is simple, elegant, and well documented. Thank you Vue.

The final hurdle is educating users that this functionality even exists within the interface. An option select, by default, will simply state the currently selected choice and since our default choice is “Master” this can be pretty obscure and uninviting to most users. It certainly doesn’t scream, “click me to watch Dave play drums.” I figured if I could simply label the select with the phrase “Choose Video,” that would greatly increase interaction. The problem? I just hate those little tutorial elements. You know the ones. A little floating div with an arrow. Hell, a hand drawn arrow with hand written type… After designing all of these solutions on a throw away Figma board, I decided to take on the issue live in the code with a FRESH POT of coffee first thing in the morning.

I began by creating the semantic label element and giving it the text of “Choose video.” Without styling, this label broke my design immediately so I just gave it a position of absolute to keep it under control. This faithful styling placed the label right on top of my select and gave me a thought. How are things labeled in a studio? Masking tape and sharpie. What if I styled this element like a piece of masking tape which revealed the functionality on hover? All it took was the background color #FCF7DD, a bit of padding, and a method to hide on hover. I didn’t hate this solution and we ended up pushing it live.

The final issue involved keeping track of the player’s position. Sadly this isn’t as easy as simply setting the currentTime when a new video gets loaded as the video may not be ready to change position when you call that function. Your safest bet is to change the currentTime once the player begins to play. Plyr offers us an onplay event but we’ll want to understand if our player is in the process of switching tracks so it isn’t jumping around every time a user plays something. To solve this, I simply created a boolean called switching. When the selectedVideoId watcher is called above, I set switching to true. And then, within the play event, the real magic occurs:

player.on('play', event => {
if (this.switching) {
if (this.isMaster) {
player.currentTime = this.globalTime + this.introDuration
} else {
player.currentTime = this.globalTime
}
    this.switching = false
}
})

I’m sure you noticed the two additional properties: isMaster and introDuration Since my master video is a different length, I’ve created a computed property to help me check if the currently selected video is the master. When it is, I’ll need to shift the current time by the duration of the intro, which I also have stored. Oh wait, I’m dumb. Here’s how you actually update globalTime using Plyr’s ontimeupdate event… which is called each time currentTime is changed.

player.on('timeupdate', event => {
if (this.isMaster) {
this.globalTime = this.player.currentTime - this.introDuration
} else {
this.globalTime = this.player.currentTime
}
})

Again, this code could have been greatly simplified if we would have split up the intro and performance videos. However, I would have then extended this tutorial to cover checking to see if the intro ended before starting the performance. It’s okay, Plyr has that event also. It’s called ended. Thanks to the synergy between Vue and Plyr, a little bit of code goes a long way.

One last thing. I was curious which instruments user’s might focus on so I integrated a few vue-analytics tracking events. Here’s the current breakdown.

Breakdown of instrument selections

Collapsing

The best the best the best the best transition

The final problem was finding some real estate for all of the information we wanted to make available: purchasing, sheet music, input lists, and a list of music organizations. I wanted viewers to be able to consume this information while continuing to listen to the performance. On first glance, a fullscreen video player offers little room for this but I had an inspiration: YouTube TV. I subscribed to YouTube TV during the World Cup and really enjoyed their simplified interface and solution for presenting more information below the fold. It involves detecting either a mousewheel movement or navigation link click to initiate a collapse of the top video area and an expansion of the otherwise hidden info section below. From a UX perspective, it sorta feels springy in that once a certain point of tension is reached, the transition occurs. It’s nice.

Now I’m not exactly sure exactly how they pulled it off but I reverse engineered a similar solution over a period of several Codepens while testing in as many environments as I could. The first step was simply to provide some semantic elements for the layout. Including, a section for both the video and info areas. In addition, I’ll add in the footer (controls) area to #video and an article element within #info which will house our actual content.

<main>
<section id="video">
<video>
<source src="URL" type="video/mp4">
</video>
    <footer></footer>    
</section>
  <section id="info">
<article>
<!-- Info -->
</article>
</section>
</main>

Then you’ll want to add a bit of CSS to create the overall structure. First, set the body and main element to be 100% of the browser’s height while hiding any overflow. This gives you an area which is locked into 100% the browser height and width.

html, body, main{
height: 100%;
}
main{
overflow: hidden;
}

Next we’ll style the #video section and it’s children. First, set the height and width of both #video and the contained <video> element to 100% the browser’s height and width. Then make sure the #video section hides any overflow, positions children relatively, and transitions any height changes. For this example, we’ll set the <video> element to cover the available area using the handy object-fit method. Finally, I’ve positioned a footer element absolutely to the bottom of the #video area and gave it a gradient similar to the final solution for visual testing.

#video, video{
height: 100%;
width: 100%;
}
#video{
overflow: hidden;
position: relative;
transition: 0.8s height ease-out;
}
#video video{
object-fit: cover;
}
#video footer{
background: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5) 50%, white 50%);
bottom: 0;
height: 160px;
position: absolute;
width: 100%;
}

Now the #info element is pretty basic. We’re going to give it a calculated height equal to the total browser height minus the height of our previous <footer> element which I’ll explain in a second. You’ll want to bring back automatic overflow and finally set the contained <article> to 3 times the view height (for testing.)

#info{
background: #FBFAF6;
height: calc(100vh - 160px);
overflow: auto;
}
#info article{
height: 300vh;
}

The final bit of CSS is only a single line but it accomplishes a lot visually.

main.collapsed #video{
height: 160px;
}

Let’s think about it. We have a <main> element which is currently 100% the view height and hiding anything overflowing beyond that point. Our #video section is currently, 100% tall and our #info section (albeit hidden) is 100% — 160px tall. What do you think happens when we adjust the height of the #video section to 160px? You guessed it. The #video section collapses to the size of the contained footer, allowing the #info section to slide into view. Since it’s 100% — 160px tall, our shared element height between #video and #info is still 100% the view height. By setting the #info section to automatically overflow earlier, we’re able to scroll the info’s <article> content.

However, this magic only occurs when the <main> element gains a class of “collapsed.” We’ll want a Javascript function that can toggle this class on mousewheel movements to complete our experience. Here’s what I would like to see happen. When a user wheels in a downwards direction, the #video section will collapse. When the user wheels upwards at the top of the #info section, the “collapsed” class will be removed and the full video player will return. Using a combination of the onwheel event and scrollTop, we can pull off exactly that.

document.onwheel = data => {
if (data.deltaY > 0) {
document.getElementById('app').classList.add('collapsed')
} else {
if (document.getElementById('info').scrollTop <= 0) {
document.getElementById('app').classList.remove('collapsed')
}
}
}

For mobile devices, I created a slightly different technique using touch events to toggle the class. Message me on Twitter if you’re curious about that solution.

Thanks

There’s so many talented people connected to a project like this. At the end of the day, I’m just providing a couple of 1s and 0s to help give art such as this the best structure for success online.

Shout out to Mark Monroe and the video production team for helping Dave capture this crazy idea. The design side of this site was made easy by the art direction created by Morning Breath for the physical packaging. Wait until you see the final design! Obviously, my relationship with management is legendary at this point. There is no harder working team in rock and I take pleasure in every stressful project I’m allowed to participate in. Finally, thanks to Dave for green-lighting the full vision of this digital execution and continually redefining what it means to be a modern rockstar.