January 10, 2018
  • All
  • stencil
  • Tutorials

Make a Video Web Component, the Stencil Way

Prosper Otemuyiwa

TL;DR
Stencil is a JavaScript tool that enables you to build framework-independent and standards-compliant web components using technologies, such as TypeScript and JSX.
Stencil provides APIs that makes writing fast components a breeze. These APIs — Virtual DOM, JSX, Async rendering and Reactive data-binding — equip developers with superpowers to create custom web components. Popular JavaScript frameworks, including React, Vue and Angular, all have something in common – the ability to create custom and reusable components. Check out this StencilJS Guide written by Christian Nwamba for a succinct primer on generating pure custom components.

Let’s start our journey by showing how to create a custom web component with Stencil. We’ll use one of my favorite APIs, Cloudinary, as the functional service for which we’ll craft a web component.

What’s Cloudinary?

Cloudinary is a cloud-based, end-to-end media management solution. As a critical part of the developer stack, Cloudinary automates and streamlines your entire media asset workflow. It handles a wide variety of media types, from images, video and audio to emerging rich and interactive media types. Cloudinary’s powerful APIs are used by developers to automate every stage of the media management lifecycle, including media selection, upload, analysis and administration, manipulation optimization and delivery.

Existing Components

Fortunately for developers, Cloudinary offers SDKs for different languages and frameworks, including Ruby on Rails, PHP, Angular, React, JQuery, Android, iOS, Python and JavaScript. Let’s look quickly at the Cloudinary components that exist for a couple of these frameworks: Angular and React.

Angular SDK

<cl-video> component. 
<cl-transformation> component.

Repository
Usage:

   <cl-video cloud-name="my_other_cloud" public-id="watchme" secure="true" class="my-videos">
        <cl-transformation overlay="text:arial_60:watchme" gravity="north" y="20"></cl-transformation>
   </cl-video>

React SDK

<Video> component. 
<Transformation> component.

Repository
Usage:

<Video publicId="dog" >
  <Transformation width="300" height="200" crop="crop" />
</Video>

In September 2017, David Walsh wrote an excellent article on how to create a Cloudinary video component. I skimmed through the blog post and decided to do the same with Stencil.

Stencil Component

Without further ado, we’ll go ahead to build our Stencil component. Let’s start with the component template.

Component Template

Let’s take a look at the JSX for the component. In Stencil, the HTML Skeleton resides in the render method.

render() {
    return (
      <div onMouseEnter={this.showPreview.bind(this)} onMouseLeave={this.hidePreview.bind(this)} class="cloudinary-video-item" style={{'width':`${this.width}px`,'height':`${this.height}px`}}>
        <div class="cloudinary-video-item-active">
          <video id="previewVideo" poster={this.poster} autoplay loop width={this.width} height={this.height}></video>
        </div>
        <div class="cloudinary-video-item-video">
          <video id="fullVideo" autoplay controls width={this.width} height={this.height}></video>
        </div>
        <svg
           onClick={this.play.bind(this)}
           xmlnsDc="http://purl.org/dc/elements/1.1/"
           xmlnsCc="http://creativecommons.org/ns#"
           xmlnsRdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
           xmlnsSvg="http://www.w3.org/2000/svg"
           xmlns="http://www.w3.org/2000/svg"
           id="play-icon"
           version="1.1"
           height="50"
           width="50"
           viewBox="0 0 1200 1200">
          <path
             d="M 600,1200 C 268.65,1200 0,931.35 0,600 0,268.65 268.65,0 600,0 c 331.35,0 600,268.65 600,600 0,331.35 -268.65,600 -600,600 z M 450,300.45 450,899.55 900,600 450,300.45 z"
             id="path16995" />
        </svg>
      </div>
    );
  }

In the code above, there is a root div that houses three child elements. The first two elements are divs, while the last is an SVG. Next, let’s configure the component and the props it will receive once it’s in use.

Component Properties

import { Element, State, Component, Prop } from '@stencil/core';
@Component({
  tag: 'cloudinary-video',
  styleUrl: 'cloudinary-video.scss'
})
export class CloudinaryVideo {
  @Prop() account: string;
  @Prop() width: string;
  @Prop() height: string;
  @Prop() alias: string;
  @Element() videoEl: HTMLElement;
  @State() fullVideo: string;
  @State() preview: string;
  @State() poster: string;
…..

In the code above, we specified the number of properties that a user can pass to the component. account, width, height, and alias. We can specify many more, but for the sake of this article, it will be limited to just four props. To do this we can use the @Prop() decorator. @Prop() is a decorator that enables components to explicitly declare props.

  • account: Cloudinary account name of the user
  • width: Width of the video container
  • height: Height of the video container
  • alias: Name of the video to be played

Next, we have the state attributes, fullVideo, preview, poster. To do this we can use the @State() decorator, which enables components to manage data internally.

  • fullVideo: URL of the complete video
  • preview: URL of the video preview
  • poster: URL of the video poster

@Element() is a decorator that enables the component to get access to the host element within the class instance. Basically, it means we can get access to the component itself and manipulate it and its child elements to achieve whatever we want.

Component Methods

These are the methods that will perform the key operations when certain events trigger in our component. In the component template, we have the onMouseEnter, onMouseLeave and onClick events.

  play() {
    // Hide the preview
    this.hidePreview();
    // Set the state to "playing" for showPreview and hidePreview checks
    this.videoEl.setAttribute('state', 'playing');
    // Set the full video element src
    this.videoEl.querySelector('#fullVideo').setAttribute('src', this.fullVideo);
    // set the svg play button to disappear
    this.videoEl.querySelector('svg').style.display = 'none';
  }

  showPreview() {
    // If the full video is loaded and playing, ignore this event
    if(this.videoEl.getAttribute('state') === 'playing') {
      return;
    }
    // set the preview video to the src attribute of the video tag
    this.videoEl.querySelector('#previewVideo').setAttribute('src', this.preview);
  }

  hidePreview() {
    // If the full video is loaded and playing, ignore this event
    if(this.videoEl.getAttribute('state') === 'playing') {
      return;
    }
    // Set the video to go to the beginning
    this.videoEl.querySelector('video').currentTime = 0;
    // ..then pause the video
    this.videoEl.querySelector('video').pause();
  }

We have the play, showPreview, and hidePreview methods.

play: This method hides the preview, sets the state attribute of the component to playing, assigns the full video URL to the video element and eliminates the SVG play button at the middle of the video container.
showPreview: This method assigns the preview video URL to the video element. If the video is currently playing, it does nothing.
hidePreview: This method resets video time to the beginning of the video and pauses it. If the video is currently playing, it does nothing.

Worthy of note is the Stencil Lifecycle method we invoked in our component.

 componentDidLoad() {
    this.fullVideo = `http://res.cloudinary.com/${this.account}/video/upload/${this.alias}.mp4`;
    this.preview = `http://res.cloudinary.com/${this.account}/video/upload/so_0,du_2/l_video:${this.alias},fl_splice,so_12/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_24/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_36/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_48/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_80/du_2/fl_layer_apply/${this.alias}.mp4`;
    this.poster = `http://res.cloudinary.com/${this.account}/video/upload/${this.alias}.jpg`;
  }

Add Video Magic

The componentDidLoad is a lifecycle hook that is invoked when a component is loaded. In this component, we assigned the on-the-fly Cloudinary transformation URLs to the variables above.
The preview video URL looks a little scary. What are all those URL parameters? Oh, that’s one of the superpowers of Cloudinary. Cloudinary encodes and deliver videos on-the-fly and also applies transformations to videos via URLs. In this URL, there is fl_splice, fl_layer_apply. These are flags you can use for overlaying videos and concatenating to the container video.
Check out the documentation for several transformation tricks for video.

Component CSS

The CSS for this component is fairly simple.
components/cloudinary-video/cloudinary-video.scss

cloudinary-video {
  .cloudinary-video-item {
    position: relative;
  }
  .cloudinary-video-item > div {
    position: absolute;
    top: 0;
    left: 0;
  }
  .cloudinary-video-item svg {
    position: absolute;
    top: 40%;
    left: 45%;
    cursor: pointer;
    opacity: 0.6;
  }
  .cloudinary-video-item svg:hover {
    opacity: 0.9;
  }
  .cloudinary-video-item-active, .cloudinary-video-item-video {
    display: block;
  }
}

Using the Component

Using the component is as simple as:

  <cloudinary-video
    account="unicodeveloper"
    alias="cartoon"
    width="640"
    height="360">
  </cloudinary-video>

This code can simply be dropped into your HTML file.
The code for the Cloudinary Stencil Video Component is on GitHub.

There are many kinds of components that can be generated with Stencil. Stencil’s API makes it easy to create standards-compliant web components as shown above. With Cloudinary, you can create robust media web components that developers can latch onto for building media-heavy web software. I look forward to seeing you create standards-compliant web components with Stencil. In the next post, you will learn how to build an image gallery with Stencil Custom Components using Cloudinary.

Author Bio:
Prosper Otemuyiwa is a food ninja, open source advocate and self-proclaimed developer evangelist.


Prosper Otemuyiwa