DEV Community

Matthew Piercey for OverScore Media

Posted on • Updated on

Nuxt, Meet Plyr

This article is part of a series on my experiences with Nuxt.js that I built into the nuxt-toolkit by OverScore Media

GitHub logo overscore-media / nuxt-toolkit

A bunch of useful example code snippets for use with Nuxt.js

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 logo sampotts / plyr

A simple HTML5, YouTube and Vimeo player

Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports modern browsers.

Checkout the demo - Donate - Slack

npm version Gitpod Ready-to-Code Financial Contributors on Open Collective

Image of Plyr

Features

  • πŸ“Ό HTML Video & Audio, YouTube & Vimeo - support for the major formats
  • πŸ’ͺ Accessible - full support for VTT captions and screen readers
  • πŸ”§ Customizable - make the player look how you want with the markup you want
  • 😎 Clean HTML - uses the right elements. <input type="range"> for volume and <progress> for progress and well, <button>s for buttons. There's no <span> or <a href="#"> button hacks
  • πŸ“± Responsive - works with any screen size
  • πŸ’΅ Monetization - make money from your videos
  • πŸ“Ή Streaming - support for hls.js, Shaka and dash.js streaming playback
  • πŸŽ› API - toggle playback, volume, seeking, and more through a standardized API
  • 🎀 Events - no messing around with Vimeo and YouTube APIs, all events are…

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:

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

I made use of the marvelous

GitHub logo redxtech / vue-plyr

A Vue component for the plyr (https://github.com/sampotts/plyr) video & audio player.

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 - 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):

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>
  `
})
Enter fullscreen mode Exit fullscreen mode

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...

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;
}
Enter fullscreen mode Exit fullscreen mode

And implement the component like this:

<video-player source="youtube" vid-id="GHMjD0Lp5DY" />
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)