Skip to content

Cloudinary Video Player: “Drinking Our Own Champagne”

Using a company’s software in-house was made famous by Microsoft’s adoption of the term “eating our own dog food” to refer to the request to employees to use the alpha and beta versions of their products. This story, “Drinking Our Own Champagne” refers to our serving videos created by our Customer Training team with the Cloudinary Video Player to implement a business initiative and enhance site performance.

A Cloudinary cross-functional team with employees from Customer Training and Marketing are working on the initiative of releasing video podcasts biweekly. The subject matter is split between two podcast brands:

  • Dev Jams, which showcases projects created by our customers in an effort to get to know them better.
  • MX Matters, which serves as a platform for our product team to share information about our evolving product line.

The video podcasts are about an hour in length, access to which (both video and audio) are through YouTube, Spotify, Apple Podcasts, Google Podcasts, Overcast, and Stitcher. In addition, you can view the videos on a cloudinary.com webpage.

For a superior user experience, we did the following:

  1. Offered smooth-playing, no-buffering streaming with minimal configuration through Adaptive Bitrate Streaming (ABR).
  2. Presented the video collections as a grid with modal popups for easy full-screen viewing.

ABR is a video-streaming technique that can be run in the application layer of the internet: HTTP. Essentially, the video server chunks up the video and the client browser reads them in with HTTP GET requests. ABR works by detecting the user’s bandwidth on the client side and adjusting the streaming quality accordingly. If the client’s network supports a heavier load, the video’s quality is higher. For clients with a lower bandwidth network, ABR lowers the quality of the chunks, resulting in smaller file sizes. The net result is a smooth playback and less or no buffering. Not only does the Cloudinary Video Player help with ABR, but it can also process the various renditions, segmenting them into chunks and creating manifests that describe the chunks.

ABR contains two protocols: Dynamic Adaptive Streaming Over HTTP (MPEG-DASH) and Apple’s HTTP Live Streaming (HLS), both of which are operationally and functionally equivalent. The differences lie in device support, e.g., HLS is natively supported in Safari but not in Chrome.

A strength of Cloudinary is its ability to create multi-codec ABR, which means that you can use the Cloudinary Video Player with HLS in all major browsers, e.g., you can build profiles for HLS/h.264, HLS/h.265, and DASH/VP9. Each player would then choose the best codec based on the browser or device it’s running on. Cloudinary’s podcast webpage runs HLS/h.264.

It’s a good idea to become familiar with both MPEG-DASH and HLS, which offer different browser and video codec compatibilities. Browsers support specific video formats and codecs, which are different algorithms for compression. Also, some advanced codecs require specific formats. Here are the details.

Both MPEG-DASH and HLS define a manifest that lists all the chunks to be requested.

The HLS manifest, which has an extension of m3u8, contains two levels of manifest data:

  • The master manifest (see the image below for an example), which lists the secondary manifests with the details of the bitrate and video size for the various bitrates. master manifest
  • The secondary manifests, which detail the chunks for a given bitrate. The example below shows two climbing.ts chunks. secondary manifests

Your browser’s network tab shows the .ts files being loaded and even preloaded. Mousing over one of those files displays a transformation for both resizing and bitrate, as in this example:

Network tab Network tab .ts files

The height and width might vary with the bandwidth based on the ABR algorithm. The transformations shown above were derived from the original MP4 format of the file uploaded to Cloudinary.

Workflows hinge on the organization’s people and their skill sets. In the case of our podcast videos, the workflow contains two major steps:

  1. The Customer Training team creates, edits, uploads, and transforms the videos, and then hands them off to Marketing.
  2. Marketing works with an agency to code and build the webpages for publication.

Podcast Production and Delivery Workflow Podcast Production and Delivery Workflow

Cloudinary has the tools for the various workflows. Modularizing the above workflow enables different people to work on different aspects of the product, as in the six-step process below:

  1. Record the video with Zoom.
  2. Process the video with Descript, a tool that cleans up and transcribes the language. Cloudinary’s add-ons from Google and Microsoft can create video transcription files for closed captioning during video upload.
  3. Annotate and enhance the video with visuals.
  4. Upload the video as an MP4 file to Cloudinary.
  5. Execute a script to create the transformations (chunking) that prepare the video for ABR in the Cloudinary Video Player. If feasible in the workflow, you can turn the script into an upload preset for use during upload.
  6. Hand off the public ID of the derived video to Marketing for publication on the website.

This section delves into the back-end and front-end code that creates the derived video chunks and the code for rendering the Cloudinary Video Player.

The script below creates the derived videos that can be requested by the Cloudinary Video Player to enable ABR:

require("dotenv").config();
const cloudinary = require("cloudinary").v2;

// use explicit because upload takes place at a different
// time and is performed by a different person
function explictHDProfile(publicId) {
  const options = {
    resource_type: "video",
    eager: [
      { streaming_profile: "hd", format: "m3u8" },
      {
        format: "mp4",
        transformation: [{ quality: "auto" }],
      },
    ],
    eager_async: true,
    eager_notification_url:
      "https://webhook.site",
    type: "upload",
    invalidate: true,
  };
  cloudinary.uploader.explicit(publicId, options, function (error, result) {
    if (error) console.log("error", error);
    else console.log(result);
  });
}

The code is set up as a function that accepts the Cloudinary public ID for the original, uploaded video. By way of explanation:

  • The function specifies an explicit transformation, a Cloudinary term that mandates a transformation to be applied to an uploaded asset as opposed to an on-the-fly transformation that is applied to the original asset during upload. Creating ABR is CPU intensive and done in an asynchronous mode, called eager in Cloudinary lingo.
  • In order for us to be notified that the entire process is complete, we set the eager_notification_url key with a website that can capture and report such notifications, in this case webhook.site, which is free to use. The site accords users a unique URL for notifications.
  • A built-in streaming profile created by Cloudinary and a named hd profile specify the transformations. A streaming profile defines the transformations.
  • This code leverages Cloudinary’s Node.js SDK. The function cloudinary.uploader.explicit calls upon the Upload API’s explicit method to act on a previously uploaded video with the public ID, a unique identifier assigned to an asset as a parameter for the explictHDProfile function.
  • The options, highlighted in the code above, specify that the asset is a video. The eager key lists an array of transformations, including a streaming profile that results in multiple transformations. Another transformation is a fallback to MP4, which enables browsers and devices to choose the optimal codec. As mentioned above, the Cloudinary Video Player can accept multiple codecs for ABR. By creating multiple streaming profiles based on different codecs, you can let the device choose the optimal profile. Here, an optimized MP4 is specified as a fallback. In addition:
    • You must set resource_type to video for transformations that are video specific.
    • Because the workflow dictates that this script be run after the upload, the eager key defines the streaming profile and transformations.
    • eager_notification_url monitors the progress of the derived videos.
    • The type option upload is the default that renders the derived videos public.
    • The invalidate option, set to true, invalidates previously cached versions of the derived files, if any, so that they can be replaced.

The cloudinary.com website offers Lightbox features with a jQuery library called fancyBox. Sean Massa, our front-end developer who implemented this capability, characterized the requirements and solution like this:

“The challenge for this project was to build a single modal and video player in which the content would dynamically change based on the thumbnail clicked. There were many approaches that seemed to work when we were dealing with just a single video implementation, but when it came to reloading the player with a new video on the fly, we ran into a few walls along the way and had to step back and rethink our approach. The result was to set up an instance of the player, including all the parameters and transformations, explicitly through JavaScript, because adding a mix of JS and inline to the player created issues. We then used data attributes to set the poster and source on click of the desired item. We also had to set the poster before the video source or it would not work.”

As shown in the final code for the front end, we created multiple modal video players on a single page by rendering a single <video> tag in the fancyBox popup and then attaching the Cloudinary Video Player code and options to that tag when the user clicks a video link.

For the stripped-down implementation, see the demo.

The Cloudinary Video Player interprets the classes that we added to the <video> tag, such as cld-fluid, which enable the player to fill its container. Located in a single <div>, which is rendered but not displayed, the <video> tag is specified by sample-video-id with which the Cloudinary Video Player locates the tag. You’ll find the tag rendered in your inspector tool but not on the webpage until a fancyBox link has been clicked.

 <div id="video-wrapper" style="display: none">
      <video
        id="sample-video-id"
        class="cloudinary-video cld-video-player cld-video-player-skin-dark cld-fluid"
        controls
      ></video>
 </div>

You can access the Cloudinary Video Player code with NPM and incorporate it into JavaScript frameworks. For the podcast webpage, we leverage jQuery, fetching all external libraries through a content delivery network (CDN). To use the Cloudinary Video Player, you must import cloudinary-core-shrinkwrap, cloudinary-video-player, JavaScript, and CSS. For the fancyBox modal, we imported both jQuery and fancyBox JavaScript and CSS.

<script
      src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
      integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI="
      crossorigin="anonymous"
    ></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css"
    />
    <script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>
    <link
      href="https://unpkg.com/cloudinary-video-player@1.5.1/dist/cld-video-player.min.css"
      rel="stylesheet"
    />
    <script
      src="https://unpkg.com/cloudinary-core@latest/cloudinary-core-shrinkwrap.min.js"
      type="text/javascript"
    ></script>
    <script
      src="https://unpkg.com/cloudinary-video-player@1.5.1/dist/cld-video-player.min.js"
      type="text/javascript"
    ></script>

Each video that we want to open with the Cloudinary Video Player in the modal is set up with data that contains the Cloudinary public ID and poster link. Even though the Cloudinary Video Player can create a poster from a midpoint frame of the video itself, we elected to link to an image on the podcast webpage. The public ID and poster link are the only data handed off to the JavaScript that opens the Cloudinary Video Player. See the code below.

<div>
  <a
    class="fancybox video-trigger"
    href="#video-wrapper"
    data-video-id="climbing"
data-video-poster="https://cloudinary-res.cloudinary.com/images/f_auto,q_auto/v1614262961/Featured_Podcast/Featured_Podcast.png"
  >
    Watch climbing here
  </a>
</div>

Note the following:

  • The video-trigger class attaches a click event.
  • data-video-id contains the Cloudinary public ID.
  • data-video-poster contains a link to an optimized image on Cloudinary.
  • href links to the hidden <video> tag.
  • Clicking the anchor tag below hands off the data to the Cloudinary Video Player.

The JavaScript shown below calls the fancybox() function, which takes as input all the HTML, JavaScript, and CSS that the videoPlayer function attaches to the <video> tag. Also note the following:

  • To call videoPlayer, we added the <video> tag selector sample-video-id**.
  • We added options to instruct the Cloudinary Video Player to use the hd streaming profile or the mp4 fallback.
  • Users can control the speeds at which they stream the video with playbackRates.
  • We added a click event handler with jQuery. On a user click, we pull data-video-id and data-video-poster out of the anchor tag.
  • Finally, we call the Cloudinary Video Player’s source function to activate the Video Player.
jQuery(document).ready(function () {
  jQuery(".fancybox").fancybox();
  // Initialize player
  var cloudinaryCld = cloudinary.Cloudinary.new({
    cloud_name: "cloudinary-training",
  });
  var options = {
    sourceTypes: ["hls", "mp4"],
    bigPlayButton: "init",
    muted: false,
    sourceTransformation: {
      "hls": [{ streaming_profile: "hd" }],
      "mp4": [{ quality: "auto" }],
    },
    playbackRates: [0.5, 1, 1.5, 2],
  };
  var media = cloudinaryCld.videoPlayer("sample-video-id", options);
  jQuery(document).on("click", ".fancybox.video-trigger", function () {
    var videoID = jQuery(this).data("video-id");
    var videoPoster = jQuery(this).data("video-poster");
    media.posterOptions({ publicId: videoPoster });
    media.source({ publicId: videoID });
  });
});  

The options on the front end depend on the uploaded and derived assets on the backend. If you are specifying hls, ensure that you’ve created the derived m3u8 and ts files in Cloudinary.

If you are implementing this code for your own videos, replace the cloud_name variable with your Cloudinary account’s cloud name.

Page styling is very important for the setup of this multi-video player webpage. The sample code in the shared GitHub repo is not styled to the extent that the Cloudinary podcast webpage is. In particular, be careful while setting the CSS max-width property on the <video> tag because that configuration can interfere with full-screen display.

There is always a next step in webpage development, but it’s nice to reach a milestone where you feel confident about a workflow and can then focus on the content. As more websites embrace video content, we look forward to learning and sharing with you techniques that enhance the developer experience.

Back to top

Featured Post