Nuxt, Meet Plyr
Tags: javascript, nuxt, vue, plyr
Published: 2020-04-14 05:32:31

This article is part of a series on my experiences with Nuxt.js that I built into the [nuxt-toolkit](https://github.com/overscore-media/nuxt-toolkit) by [OverScore Media](https://overscore.media) {% github https://github.com/overscore-media/nuxt-toolkit no-readme %}

See a live example at https://nuxt-toolkit.overscore.media! :]

-----

Oh, videos - the third Wonder of the Web (TM) - beyond plain text and images, that is... It really is astounding that global Internet connections are generally fast enough to be able to stream actual videos to people's phones, tablets, TVs, desktops, laptops, Ouya's, Raspberry Pi's, and brains - OK maybe not the last one yet - with minimal to no noticeable latency (depending on the video quality, of course).

So, in this Web 3/4.0 world, this the age of the Dailymotions and the Vudus by Walmart (what? you're telling me there are other video streaming platforms out there?), shouldn't websites finally be catching up? 

(What number are we on, anyway? We're definitely past 2.0, right? Is it like semantic versioning or is it completely arbitrary? When was Web 2.5? Web 1.7? Web 1.3.4 rc 13?) 

The thing is, most video streaming/hosting sites like the Vimeos of the world (see, I know what Vimeo is; does that make me cool/hip/vibin'?) come with their own embedding APIs/iFrames/whatever you kids are using these days. But let's be honest with ourselves for a second... They're kinda generic, right?

Why can't we inject a little individuality into our video players? Why can't we make the most of modern HTML5 and JavaScript, with a bit of CSS thrown in haphazardly (as always) as the gush from the bottom of a can of spray whip cream? I dunno; why are you asking all these open-ended questions? Wait, now _I'm_ the one asking these questions? No, I don't think so. That couldn't be the case... Could it?

Now, before this article goes _completely_ off the rails... (_before_? really? how wide were those rails?)

Introducing:

{% github https://github.com/sampotts/plyr %}

Yes, `plyr` is very fun indeed. Basically, it's a JavaScript wrapper for HTML5 audio and video that supports YouTube and Vimeo out-of-the-box. Sure, you _could_ just make a Vue component for the iframe embed for YouTube and Vimeo, but Plyr is such an elegant solution, that I thought it was too good to pass by.

## The Component in Question

I made this Vue component as a Nuxt plugin. I called it `video-player.js`, put it in the `plugins` directory of my Nuxt app, and imported it in my `nuxt.config.js` file like so:

```js
// ...
css: [
  'plyr/dist/plyr.css'
],
plugins: [
  { src: '~/plugins/video-player', mode: 'client' }
]
// ...
```

I made use of the marvelous {% github https://github.com/redxtech/vue-plyr no-readme %}

which actually comes with a build specially-suited to applications like Nuxt projects (for Server-Side Rendering or SSR). Fun times.

Here's what my plugin looked like in the end. Admittedly, I went a bit over-the-top on the props, and I doubt I'll end up remembering all of them anyway, but it's an example, right? Right! (Right?)

Assuming you're using the [compiler build of Vue](https://github.com/nuxt/nuxt.js/issues/1142) - if not, you can't make global components this way (though I guess you could just as easily modify it to make it local without the need for making it a plugin):

```js
import Vue from 'vue'
import VuePlyr from 'vue-plyr/dist/vue-plyr.ssr'

Vue.component('video-player', {
  components: {
    VuePlyr
  },
  props: {
    // eslint-disable-next-line vue/require-prop-types
    plyr: {
      fullscreen: {
        enabled: true
      }
    },
    // eslint-disable-next-line vue/require-prop-type-constructor
    emit: ['embed'],
    type: {
      type: String,
      default: 'video', // 'audio' or 'video'
      required: false
    },
    source: {
      type: String,
      default: 'web', // 'youtube', 'web', or 'vimeo'
      required: false
    },
    vidId: {
      type: String,
      required: false // GHMjD0Lp5DY
    },
    mp3URL: {
      type: String,
      required: false // https://example.com/audio.mp3
    },
    oggURL: {
      type: String,
      required: false // https://example.com/audio.ogg
    },
    videoURL: {
      type: String,
      required: false // https://example.com/video.mp4
    },
    thumbnail: {
      type: String,
      required: false // poster.png
    },
    videoType: {
      type: String,
      required: false,
      default: 'video/mp4'
    },
    videoSourceSizes: {
      type: Array, // [720, 1080]
      required: false
    },
    videoSizeURLS: {
      type: Array, // [video-720p.mp4, video-1080p.mp4]
      required: false
    },
    videoCaptions: {
      type: Object, // {[name: 'English', lang: 'en', src: 'captions-en.vtt'], [name: 'Spanish', lang: 'es', src: 'captions-es.vtt']}
      required: false
    }
  },
  computed: {
    vimeoURL () {
      return `https://player.vimeo.com/video/${this.vidId}?loop=false&byline=false&portrait=false&title=false&speed=true&transparent=0&gesture=media`
    },
    youtubeURL () {
      return `https://www.youtube.com/embed/${this.vidId}?iv_load_policy=3&modestbranding=1&playsinline=1&showinfo=0&rel=0&enablejsapi=1`
    }
  },
  template: `
    <div class="video-player">
      <vue-plyr v-if="type === 'video' && source === 'youtube'">
        <div class="plyr__video-embed">
          <iframe
            :src="youtubeURL"
            allowfullscreen allowtransparency allow="autoplay">
          </iframe>
        </div>
      </vue-plyr>
      <vue-plyr v-if="type === 'video' && source === 'vimeo'">
        <div class="plyr__video-embed">
          <iframe
            :src="vimeoURL"
            allowfullscreen allowtransparency allow="autoplay">
          </iframe>
        </div>
      </vue-plyr>
      <vue-plyr v-if="type === 'video' && source === 'web'">
        <video :poster="thumbnail" :src="videoURL">
          <source v-for="(url, index) in videoSizeURLS" :key="url" :src="url" :type="videoType" :size="videoSourceSizes[index]">
          <track kind="captions" label="English" srclang="en" src="captions-en.vtt" default>
        </video>
      </vue-plyr>
      <vue-plyr v-if="type === 'audio' && source === 'web'">
        <source v-if="mp3URL" :src="mp3URL" type="audio/mp3" />
        <source v-if="oggURL" :src="oggURL" type="audio/ogg" />
      </vue-plyr>
    </div>
  `
})
```

Now, I will admit - it may not be elegant, but it allows me to do this and it works exactly as you'd think it would: `<video-player source="youtube" id="GHMjD0Lp5DY">`

Admittedly, to make it _look_ the way I wanted it to look, I had to get... shall we say "creative" with some CSS. Not my best work, but it did the job. I just wanted to change some of the button colours to match https://botinabox.ca, and it ended up working. Also, I know that Plyr has SCSS and I could've imported the files I needed and changed the variables around, but that strangely only worked in development mode. Oh well...

```scss
div.plyr {
  @media only screen and (min-width: 800px) {
    max-height: 80vh !important;
    max-width: 80vw !important;
    margin-left: auto !important;
    margin-right: auto !important;
  }
}

.plyr__control--overlaid {
  background-color: #e2e2e2 !important;
  &:not(button[data-plyr='captions'], button[data-plyr='settings'], button[data-plyr='fullscreen']) {
    color: #2f2f2f !important;
  }
  &:hover {
    background-color: #c9c3b2 !important;
    color: #1818a7 !important;
  }
}

button[data-plyr='play'] {
  color: #2f2f2f !important;
}

button.plyr__controls__item,
button.plyr__control,
div.plyr__menu__container {
  background-color: #e2e2e2 !important;
  &:not(button[data-plyr='captions'], button[data-plyr='settings'], button[data-plyr='fullscreen']) {
    color: #2f2f2f !important;
  }
  &:hover {
    &:not(div[id^='plyr-settings-']) {
      background-color: #c9c3b2 !important;
    }
    color: #1818a7 !important;
  }
}

button.plyr__control::before {
  background-color: #1818a7 !important;
}

div.plyr__volume {
  button.plyr__control {
    background-color: hsla(0, 0%, 0%, 0) !important;
    &:hover {
      background-color: #e2e2e2 !important;
    }
  }
}

button[data-plyr='captions'],
button[data-plyr='settings'],
button[data-plyr='fullscreen'] {
  background-color: hsla(0, 0%, 0%, 0) !important;
  &:hover {
    background-color: #e2e2e2 !important;
  }
}

input[id^='plyr-seek-'],
input[id^='plyr-volume-'] {
  color: #e2e2e2 !important;
}
```

And implement the component like this:

```vue
<video-player source="youtube" vid-id="GHMjD0Lp5DY" />
```

Well that was... fun. It was a bit of a struggle to get it all set up, admittedly, but it's doing exactly what I want it to do, so I'm not going to complain about it. I hope this was somehow helpful in your quest to learn more about Nuxt/Vue/JS/web development. Stay safe, and keep on coding!