Play YouTube Videos Through Lightning Web Components

Have you ever wanted to play YouTube videos in Salesforce? I have, and created a YouTube player using Lightning Web Components. As a budding pianist, I built the player to help me learn classical music within a Salesforce app. The app centralizes sheet music, ideal music performances, and practice sessions. Since many great musical performances are readily available on YouTube, I needed a YouTube video player. Furthermore I’ve configured the video player to appear beside the sheet music on a Record page, so I can read the music and play the recording, to memorize best practices.

Since these are Lightning web components, you can also embed them in Communities. For example we can have an entire community of like-minded musicians, where members share what they have been practicing and/or performing.

This video player use YouTube’s IFrame API, which defines the iframe player with event listeners. If the YouTube ID is invalid, the error listener is especially useful for providing the reason why. In the onPlayerError listener, we show the message in an error toast.

You can follow along this blog post to create these components, or head over to the Github repo to install the components. Plus, here’s a video on how to configure the finished components. Enjoy Mozart’s “Eine Kleine Nachtmusik” in the background!

In this post, we will create two components to play YouTube videos. Why two, you ask? One is a component designed for Home or App pages.


The other is a more advanced wrapper component, for use with Record pages.


Let’s get started!

Pre-requisites

This post assumes you have a Salesforce org and Lightning Web Component developer flow set up. If not, follow the steps in the Quick Start: Lightning Web Components Trailhead project.

To test these components you also need a YouTube video ID — it is super easy to find.

We also need two scripts in Static Resource to use YouTube’s IFrame API. We use the API to create an iframe player with event listeners. They are at https://github.com/annyhe/youTubePlayer/tree/master/force-app/main/default/staticresources/YouTubeJS. Make sure they’re uploaded and show up as public in your org’s Static Resources tab.

All set? Let’s start with the simple component for App and Home pages. This component is also a pre-requisite for the wrapper component.

Code walkthrough: basicYouTubePlayer component

Let’s start with the metadata file for the simpler basicYouTubePlayer component. This component takes a YouTube video’s ID through the Lightning App Builder, so we set isExposed to true and add a youTubeId property to take user input. We specify the interface for Home and App pages. The finished basicYouTubePlayer.js-meta.xml looks like this.

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="basicYouTubePlayer">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__AppPage, lightning__HomePage">
            <property name="youTubeId" type="String" placeholder="Please enter the YouTube video ID" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

In the basicYouTubePlayer.js file, we declare the youTubeId property as a public property via the @api annotation, the same property defined in the basicYouTubePlayer.js-meta.xml file. We also have a private property called player, which will reference the YouTube iframe player component we create.
Where do we create the YouTube iframe player? In the renderedCallback, which is called after the template has been rendered with DOM elements. We load the YouTube scripts from static resource and after they’re loaded, we call the onYouTubeIframeAPIReady method.

Notice the onYouTubeIframeAPIReady method manipulates the DOM directly. This is because the YT.player constructor takes in a DOM element, and replaces that element with an iframe element. Within the manipulatable wrapper component, we create a child element for replacement by the iframe.
Inside YT.player we attach the error listener named onPlayerError, which gets automatically invoked when the player errs. For onPlayerError, we bind it to this so it can access this component instance, to call the showErrorToast instance method.

import { LightningElement, api } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import YouTubePath from '@salesforce/resourceUrl/YouTubeJS';

export default class BasicYouTubePlayer extends LightningElement {
    @api youTubeId;
    player;

    renderedCallback() {
        if (!this.youTubeId) {
            return;
        }

        if (window.YT) {
            if (this.player) {
                this.player.cueVideoById(this.youTubeId);
            } else {
                this.onYouTubeIframeAPIReady();
            }
        } else {
            Promise.all([
                loadScript(this, YouTubePath + '/iframe_api.js'),
                loadScript(this, YouTubePath + '/widget_api.js')
            ])
                .then(() => {
                    this.onYouTubeIframeAPIReady();
                })
                .catch(error => {
                    this.showErrorToast(error);
                });
        }
    }

    onPlayerError(e) {
        let explanation = '';
        if (e.data === 2) {
            explanation = 'Invalid YouTube ID';
        } else if (e.data === 5) {
            explanation =
                'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.';
        } else if (e.data === 100) {
            explanation =
                'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.';
        } else if (e.data === 101 || e.data === 150) {
            explanation =
                'The owner of the requested video does not allow it to be played in embedded players.';
        }

        this.showErrorToast(explanation);
    }

    showErrorToast(explanation) {
        const evt = new ShowToastEvent({
            title: 'Error loading YouTube player',
            message: explanation,
            variant: 'error'
        });
        this.dispatchEvent(evt);
    }

    onYouTubeIframeAPIReady() {
        const containerElem = this.template.querySelector('.wrapper');
        const playerElem = document.createElement('DIV');
        playerElem.className = 'player';
        containerElem.appendChild(playerElem);

        this.player = new window.YT.Player(playerElem, {
            height: '390',
            width: '100%',
            videoId: this.youTubeId,
            events: {
                onError: this.onPlayerError.bind(this)
            }
        });
    }
}

Onto the markup. basicYouTubePlayer.html displays the iframe HTML element only if the youTubeId property is set. Another conditional markup renders an error if youTubeId is not found, since the youTubeId property can be passed in from another component.

If the youTubeId property exists, we add an empty div with a class to make it selectable, and lwc:dom="manual" to make it manipulatable with JavaScript. The lwc:dom="manual" ensures when we call appendChild() in the basicYouTubePlayer.js file, the element is manually inserted into the DOM.

<template>
    <template if:true={youTubeId}>
        <div class="wrapper" lwc:dom="manual"></div>
    </template>
    <template if:false={youTubeId}>
        <lightning-card  title="Empty YouTube ID found. Please specify a YouTube ID."></lightning-card>
    </template>
</template>

This is what basicYouTubePlayer looks like on an App page. It looks good and sounds even better! The component you see plays Mozart, referencing this YouTube video.


That was fun! Next up, let’s walk through the wrapper component which reuses the basicYouTubePlayer component, for Record page.

Code walkthrough: youTubePlayerRecordWrapper component

Let’s start with the youTubePlayerRecordWrapper.js-meta.xml file. The admin can set which field in the record holds the YouTube video ID, so we have a fieldName property for that. We also add a target tag since the component is for a Record page. The finished youTubePlayerRecordWrapper.js-meta.xml should look like this.

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="youTubePlayerRecordWrapper">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <property name="fieldName" type="String" placeholder="Which field has the YouTube video ID?" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

The youTubePlayerRecordWrapper.js file retrieves the field value, given the public properties, which are annotated with @api. Since the component is on a record page we use @objectApiName to get its object API Name. The wire service then uses Lightning Data Service to get the youTubeId and saves the result to the record property. To construct the fields the wire service needs, we use a getter to concatenate the public properties. Finally, we use another getter to parse youTubeId from the wire result.

import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class YouTubePlayerRecordWrapper extends LightningElement {
    @api fieldName;
    @api objectApiName;
    @api recordId;

    @wire(getRecord, { recordId: '$recordId', fields: '$fields' })
    record;

    get youTubeId() {
        return this.record.data
            ? this.record.data.fields[this.fieldName].value
            : '';
    }

    get fields() {
        return [this.objectApiName + '.' + this.fieldName];
    }
}

Onto the markup youTubePlayerRecordWrapper.html. The component displays the child component basicYouTubePlayer if the youTubeId field exists, and passes the youTubeId property to the child component. If the youTubeId field is non-existent or empty, the markup shows an error message.

<template>
    <template if:true={youTubeId}>
        <c-basic-you-tube-player
            you-tube-id={youTubeId}
        ></c-basic-you-tube-player>
    </template>
    <template if:false={youTubeId}>
        <lightning-card  title="YouTube ID field not found. Please double check the field name."></lightning-card>
    </template>
</template>

What does the youTubePlayerRecordWrapper component look like on a Record page? Like this. Looking good, Mozart — and the music sounds great!
image

Lessons learned

Share, get feedback, improve the component

I built the basicYouTubePlayer first, then shared it with Peter Chittum. He mentioned it would be lovely to extend the YouTube component to load videos from record values. Why not? That’s when I learned the next two lessons.

Use composition instead of overloading a component

I re-doctored the Lightning component to work for all three page types: App, Home, and Record. However the code was confusing to read. Finally at the suggestion of Christophe Coenraets, composition, where a component wraps another one, made more sense: let the wrapper component extract the YouTube video ID from the record and let the child component render the video. The end result is two clean, reusable components.

Use Lightning Data Service to fetch data on a Record page

There are multiple ways to retrieve a field value, given the record ID. One way is to use Apex with record ID and fieldName. However since the public property objectApiName and getRecord method are readily available, we get the field value using Lightning Data Service. This means less code to maintain, and we save API calls for when it is needed.

Takeaways

These two Lightning web components are now ready to play YouTube videos in Salesforce orgs! I’ve configured them to play classical music. You can use them for product marketing, training/enablement, corporate messaging…the sky is the limit! For example, you can add marketing videos from customers to the Account page to reference the customer’s latest campaign during sales calls.

Since these are Lightning web components, they also work in Salesforce Communities and provide an easy and effective way to share content with customers and partners. For example, you can include how-to videos to drive feature adoption and users can put the video beside the new feature in their org.

Try out the components today! Here is the code and installation instructions.

Resources

Leave your comments...

Play YouTube Videos Through Lightning Web Components