DEV Community

loading...
Cover image for Five Ways for Integrating the Cloudinary Video Player Into React Applications

Five Ways for Integrating the Cloudinary Video Player Into React Applications

Rebeccca Peltz
Software engineer turned instructor
・10 min read

Code

Demo

CodeSandbox

As of this writing npm trends reports over 10,000,000 weekly downloads of the React library—and no wonder. The disruption created by Covid in 2020 is expected to persist, significantly upping the consumer demand for video as a means of enhancing experiences in online work, shopping, education, healthcare, social interactions, and entertainment. Hence the many questions on how to add a video player to a React application. This post shows you five ways in which to do that with the Cloudinary Video Player in a React component.

Cloudinary Video Player

The Cloudinary Video Player is a feature-enhanced library that builds upon the popular, open-source Video JS player. Like that player, the Cloudinary one is implemented on a webpage by rendering a <video> tag and then binding JavaScript and CSS functionality to the video DOM element. Also, Cloudinary has added the following features to the video-playing experience:

In addition to the Video Player library, we'll add the Cloudinary Core library which is required and supports core Cloudinary functionality like transformations.

Bundle analyzer visualizations provide a look at the JavaScript weight for each alternative.

Bundle with light option

Light bundle

Bundle with standard option

Standard bundle

React Components

With React, you can wrap functionality into a reusable component that renders HTML, CSS, and JavaScript. Timing is important in many cases, however. In the case of the Video Player, you can’t initialize it until a <video> tag has been rendered.

This exercise leverages the following React hooks:

  • useState This function returns an immutable data value and a setter that can update it.
  • useEffect This function is called when the component that holds it is rendered and then when stateful data within the function changes. useEffect helps determine that a component has been rendered and, in some respects, replaces the componentDidMount and componentDidUpdate life cycles in a Class function.
  • useContext This function, which serves as a container for stateful values, can be passed to a component function to specify the state. useContext defines a provider function made up of useState functions. Sharing context between components is one way to implement inter-component communications.

Also, custom hooks can refactor stateful code for reuse in many components. You’ll learn how to create a custom hook later in this post.

The Code

Here are the five components that manage state, logic, and event handling and with which you can host the Cloudinary Video Player:

  1. A function-based component for an embedded, cloud-hosted video player
  2. A class-based component
  3. A function-based component with useHooks
  4. A function-based component with Context
  5. A function-based component with a custom hook called useCloudinaryVideoPlayer for use in any component that hosts video

The external data for all the components in this exercise is defined in App.js. An object called video options contains the Cloudinary cloudName and publicId. You can pass other video options to the Video Player’s components. For simplicity, however, those two are the only ones that are required to identify the video you want to host.

const videoOptions = { cloudName: "demo", publicId: "race_road_car" };
Enter fullscreen mode Exit fullscreen mode

For details on the options available for the Cloudinary Video Player, see Video Player API Reference.

Function-Based, Cloud-Hosted Video Player

On the Cloudinary site is a demo page on which you can experiment with Video Player options. Once you are satisfied with the features you have selected, the demo generates the JavaScript code or an embeddable <iframe> tag with the options set for hosting in the cloud. You’ll also get a URL with all of the settings, including cloudName and publicId.

In the Apps.js file is the component JSX for rendering a card that contains the name of the component and the video player hosting the video specified in the video options. In this case, the component is named VideoPlayerCloudHosted.

{
  <div>
    <h2>Video Player Cloud Hosted</h2>
    <div>
      <VideoPlayerCloudHosted options={videoOptions} />
    </div>
  </div>
}
Enter fullscreen mode Exit fullscreen mode

In the code below, the URL in the src attribute contains the cloud name, the public ID, and the options specified in the Video Player demo page. Also, the cloud name and the public ID here are passed to the component through props, which is passed from the parent.

import React from "react";

function VideoPlayerCloudHosted(props) {
  const url = `https://player.cloudinary.com/embed/?public_id=${props.options.publicId}&cloud_name=${props.options.cloudName}&player%5Bfluid%5D=true&player%5Bcontrols%5D=true&source%5Bsource_types%5D%5B0%5D=mp4`;

  return (
    <>
    <div className="iframe-container">
      <iframe className="responsive-iframe"
        title="Cloud Hosted Video Player"
        src={url}
        width="640"
        height="480"
        allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
        allowFullScreen
        frameBorder="0"
      ></iframe>
      </div>
    </>
  );
}
export default VideoPlayerCloudHosted;
Enter fullscreen mode Exit fullscreen mode

All you’re doing in this functional component is rendering the <iframe> tag by passing along the videoOptions data.

 {
<div className="video-card">
  <h2>Video Player Cloud Hosted</h2>
  <div className="vp">
    <VideoPlayerCloudHosted options={videoOptions} />
  </div>
</div>
}
Enter fullscreen mode Exit fullscreen mode

Add CSS code for a responsive <iframe> tag:

.iframe-container {
  position: relative;
  width: 100%;
  overflow: hidden;
  padding-top: 56.25%; /* 16:9 Aspect Ratio */
}

.responsive-iframe {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 100%;
  height: 100%;
  border: none;
}

Enter fullscreen mode Exit fullscreen mode

An embedded video player has a few downsides, especially in production: you might not have as much control as you would like and, depending on network conditions, loading might be slower.

Class-Based Component

In App.js, you import your class-based video player and render a card that uses the VideoPlayerClass component.

{
  import VideoPlayerClass from "./VideoPlayerClass";

  <div className="video-card">
    <h2>Video Player in Class</h2>
    <div className="vp">
      <VideoPlayerClass />
    </div>
  </div>
}
Enter fullscreen mode Exit fullscreen mode

The Class component requires importing two libraries, cloudinary-core and cloudinary-video-player, to create a Cloudinary Video Player within Class. Also, import the CSS code to support the video player. You’ll see those imports in another example, which will ultimately justify the creation of a custom hook.

import React, { Component } from "react";
import { Cloudinary } from "cloudinary-core";
import "cloudinary-video-player/dist/cld-video-player.light.min";
import "cloudinary-video-player/dist/cld-video-player.light.min.css";
Enter fullscreen mode Exit fullscreen mode

Next, render the VideoPlayerClass component in App.js, passing along videoOptions.

{
  <div className="video-card">
    <h2>Video Player in Class</h2>
    <div className="vp">
      <VideoPlayerClass options={videoOptions} />
    </div>
  </div>
}
Enter fullscreen mode Exit fullscreen mode

The Video Player is then initialized with the specified options. This component implements the code required for initializing the video player and then binds the player to the DOM element, which is the first <video> tag with the class some-video in the videoPlayerInit function. The component then renders a <video> tag, after which the lifecycle function componentDidMount calls the videoPlayerInit function.

class VideoPlayerClass extends Component {
  cld = () => {
    return new Cloudinary({ cloud_name: this.props.options.cloudName, secure: true });
  };
  videoPlayerInit = () => {
    const cld = this.cld();
    cld.videoPlayer("some-video", {
      publicId: this.props.options.publicId,
      fluid: true,
      controls: true,
      preload: "auto",
      mute: true,
      autoplay: false
    });
  };
  componentDidMount() {
    this.videoPlayerInit();
  }
  render() {
    return (
      <>
        <video id="some-video" />
      </>
    );
  }
}
export default VideoPlayerClass;
Enter fullscreen mode Exit fullscreen mode

Function Based Component

Now load the libraries and wait for the <video> tag to render in a function-based approach.
First, render the component in App.js:

{
  <div className="video-card">
    <h2>Video Player in Function</h2>
    <div className="vp">
      <VideoPlayerFunction options={videoOptions} />
    </div>
  </div>
}
Enter fullscreen mode Exit fullscreen mode

Next, import the libraries. Below is the same code in the class-based example:

import React, { useEffect } from "react";
import { Cloudinary } from "cloudinary-core";
import "cloudinary-video-player/dist/cld-video-player.light.min";
import "cloudinary-video-player/dist/cld-video-player.light.min.css";
Enter fullscreen mode Exit fullscreen mode

Finally, set up a functional component. The videoPlayerInit function looks the same as it did in the class-based approach. Note that props is passed to the function rather than being implicitly added to the class context, as in the class-based function.
Instead of relying on the componentDidMount function to notify you that the <video> tag has been rendered, you can determine that with the useEffect functional React hook and call the Video Player’s init function.
Keep in mind that the instructions of useEffect, which is called any time the component re-renders, are executed as if the function was called in componentDidMound and componentDidUpdate in a class-based component. Since you don’t want to call initVideoPlayer except after the <video> tag is first rendered, be sure to guard against that scenario.

function VideoPlayerFunction(props) {
  const cloudinary = new Cloudinary({
    cloud_name: props.options.cloudName,
    secure: true,
  });
  const videoPlayerInit = () => {
    cloudinary.videoPlayer(document.querySelector(".fn-video"), {
      publicId: props.options.publicId,
      fluid: true,
      controls: true,
      preload: "auto",
      mute: true,
      autoplay: false,
    });
  };

  useEffect(() => {
    return (videoPlayerInit(),[]);
  });
  return (
    <>
      <video className="fn-video" />
    </>
  );
}

export default VideoPlayerFunction;
Enter fullscreen mode Exit fullscreen mode

Function-Based Component With Context

The React hook useContext includes both useState and Provider functions. The convention for naming the provider is to give it the same name as the context object. In this case, you’ll have VideoOptionsContext and VideoOptionsProvider, which can share logic and state between components.
Start with creating VideoOptionsContext for holding and granting access to state. useState is a function that returns the current value of the state and a setter function that will set a new state value. You’ll capture the options cloudName and publicId in that context.

The data is an object that contains those two video options. Create the context and name it VideoOptionsContext:

const video = { options: { cloudName: "demo", publicId: "race_road_car" } };  
export const VideoOptionsContext = createContext();  

Enter fullscreen mode Exit fullscreen mode

Next, implement and export VideoOptionsProvider, which sets up the state for the options. Specify the default values for videoOptions, which are cloudName and publicId.

export const VideoOptionsProvider = ({ children }) => {
  const [videoOptions, setVideoOptions] = useState(video.options);

  return (
    <VideoOptionsContext.Provider
      value={{
        videoOptions,
        setVideoOptions,
      }}
    >
      {children}
    </VideoOptionsContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

To use this context in a functional component, import VideoOptionsContext into App.js and wrap the rendering of the VideoPlayerContext in this Context component:

import VideoPlayerContext from "./VideoPlayerContext";
import { VideoOptionsProvider } from "./VideoOptionsContext";

{
<div className="video-card">
  <h2>Video Player in Function with Context Provider</h2>
  <div className="vp">
    <VideoOptionsProvider>
      <VideoPlayerContext />
    </VideoOptionsProvider>
  </div>
</div>
}
Enter fullscreen mode Exit fullscreen mode

VideoPlayerContext resembles VideoPlayerFunction, except that the former gets the options from context rather than from props.

Note in the code that you import VideoOptionsContext and then pull options with the useContext hook. You can then reference the options as options.videoOptions.cloudName and options.videoOptions.publicId.

import React, { useEffect, useContext } from "react";
import { VideoOptionsContext } from "./VideoOptionsContext";
import { Cloudinary } from "cloudinary-core";
import "cloudinary-video-player/dist/cld-video-player.light.min";
import "cloudinary-video-player/dist/cld-video-player.light.min.css";

function VideoPlayerContext() {
  const options = useContext(VideoOptionsContext);

  const cloudinary = new Cloudinary({
    cloud_name: options.videoOptions.cloudName,
    secure: true,
  });
  const videoPlayerInit = () => {
    const player = cloudinary.videoPlayer(
      document.querySelector(".context-video"),
      {
        publicId: options.videoOptions.publicId,
        fluid: true,
        controls: true,
        preload: "auto",
        mute: true,
        autoplay: false,
      }
    );

    player.on("loadedmetadata", (e) => {
      console.log("app detected", e);
    });
  };

  useEffect(() => {
    return (videoPlayerInit(),[]);
  });
  return (
    <>
      <video className="context-video" />
    </>
  );
}

export default VideoPlayerContext;  
Enter fullscreen mode Exit fullscreen mode

When you return useEffect, pass the second parameter as an empty array to avoid re-rendering

Function-Based Component With Custom Hook

You’ve now seen code duplicated across several component examples: library imports, the init video-player function, Cloudinary instantiation. And you might wonder, “How can I create a reactor?’ The answer is with a custom hook.
Since the convention for naming hooks is to prefix the functionality you’re capturing with use, create a useCloudinaryVideoPlayer hook. Also, since you’ve specified different classes for each of the samples, the hook must work with arguments and maintain the state for the following:

  • The cloud name
  • The public ID
  • The class name of the <video> tag, which serves as an element selector

Other than pulling the values of those three variables, the code looks like the functional components you created earlier.

import { useState, useEffect } from 'react';
import { Cloudinary } from "cloudinary-core";
import "cloudinary-video-player/dist/cld-video-player.light.min";
import "cloudinary-video-player/dist/cld-video-player.light.min.css";

export const useCloudinaryVideoPlayer  = (props) =>{

  const [cloudName] = useState(props.cloudName);
  const [publicId] = useState(props.publicId);
  const [className] = useState(props.videoClass);

  const cloudinary = new Cloudinary({
    cloud_name: cloudName,
    secure: true,
  });

  const videoPlayerInit = () => {
    return cloudinary.videoPlayer(document.querySelector(`.${className}`), {
      publicId: publicId,
      fluid: true,
      controls: true,
      preload: "auto",
      mute: true,
      autoplay: false    
    });
  };

  useEffect(() => {
    return (videoPlayerInit(),[]);
  });

  return "OK";
}

Enter fullscreen mode Exit fullscreen mode

You need not capture the setters because they won’t serve any purpose. App.js will continue to pass an object with only the cloud name and public ID.

<div className="video-card">
    <h2>Video Player Custom Hook</h2>
    <div className="vp">
      <VideoPlayerCustomHook options={videoOptions} />
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

In the new VideoPlayerCustomHooks component, add the class name to the object that is passed to the useCloudinaryVideoPlayer hook.

import React from "react";
import { useCloudinaryVideoPlayer } from "./useCloudinaryVideoPlayer";

function VideoPlayerCustomHook(props) {
  const videoClass = "custom-video";
  useCloudinaryVideoPlayer({ ...props.options, videoClass: videoClass });

  return (
    <>
      <video className={videoClass} />
    </>
  );
}
export default VideoPlayerCustomHook;
Enter fullscreen mode Exit fullscreen mode

Now that it can import the reusable code from the hook, the actual code for the Video Player is much simpler. Just add the video class to a new object that includes props and that serves as a parameter for the custom hook.

Recommendations

There are many ways for creating a component with React and, therefore, one for hosting the Cloudinary Video player.
What’s the best way, you ask? For class-based components, you might want to use the example in this exercise, but you can also introduce function-based components into an app with class-based components. If you’ll be creating components that vary by certain data, consider leveraging a custom hook. Though probably not the best use case, custom hooks enable the use of context with a functional component. In general, the direction forward with React is through function-based components with hooks.

Discussion (0)