Build a custom YouTube playlist player

Extend the basic embedded player to include the same features as the native YouTube.com player

The embeddable YouTube playlist player doesn't have all the features of the native player. In this article, Joseph McCarthy shows how to use the YouTube API, jQuery, JsRender, and Bootstrap to create a more fully functional embedded player for YouTube playlists.

Joseph P. McCarthy (mccarthy.joseph.p@gmail.com), Java Developer, Consultant

Photo of Joseph McCarthyJoseph McCarthy is a Java™ and JavaScript developer, with more than a decade of experience in both front-end and server-side applications. In his spare time he enjoys exploring public APIs of popular services (such as Facebook, Twitter, and Google) to enhance the user experience.



27 June 2014 (First published 27 May 2014)

Also available in Chinese Japanese Spanish

Sign up for IBM Bluemix
This cloud platform is stocked with free services, runtimes, and infrastructure to help you quickly build and deploy your next mobile or web application.

Launched in 2005, YouTube has evolved into the dominant video-sharing website. Playlists are one of YouTube's most widely used features. You can build playlists of your own and other users' uploaded videos and share your playlists on YouTube. You can also share your playlists by embedding them on your website, blog, or social media page. However, the embedded playlist player lacks the full functionality of the native player on YouTube.com.

Figure 1 shows the native youtube.com playlist player.

Figure 1. Native playlist player
Screenshot shows the native YouTube playlist player

Click to see larger image

Figure 1. Native playlist player

Screenshot shows the native YouTube playlist player

Figure 2 shows the default embedded player.

Figure 2. Embedded playlist player
Screenshot shows the default embedded YouTube playlist player

In the embedded player:

  • The list of included videos is hidden by default and only appears as an overlay, not beside the player as it does on YouTube.com.
  • The ability to randomize the playlist is removed.
  • The notes added to the playlist by its author are removed.

I encountered these limitations when I decided to embed a playlist featuring some of the best goals scored in the qualifying games for the 2014 FIFA World Cup on my blog. With help from the YouTube API, jQuery, the JsRender templating engine, and the Bootstrap front-end framework, I extended and improved the default embedded player to create a version equivalent to the native player. I'll take you through the same process in this article. You'll build a playlist on YouTube, add notes with the times of highlights (such as goals), then use the same tools to build an embedded player that restores the missing functionality and improves the user experience.

The complete project code is stored on DevOps Services. I've deployed the application to IBM Bluemix™ so you can see it in operation. You can use any host site, including Bluemix, to deploy your code.

Flash security settings

Because of the security sandbox for Flash objects, the calls to the YouTube API will not work if you open the index.html file from your system in a browser. The application must be hosted on a web server (Bluemix, for example).

Note: To fork the code for this article's project, click the EDIT CODE button in the upper right-hand corner (enter your DevOps Services credentials if you're not already logged in) and click the FORK button on the menu to create a new project.

The first step is to obtain a key for accessing the YouTube API.

Obtain a YouTube API key

To access the API for any Google service, including YouTube, you must first register a project on the Google Developers Console and create an API key. Access to the various APIs is free up to a certain number of requests per day, which varies from service to service, and is available to anyone with a Google account.

Sign into the Google Developers Console with your Google credentials and click Create Project. By default, the project name and project ID text boxes contain random values. Enter your project name and ID instead and click Create.

Figure 3. Creating a new Google API project
Screenshot of the Google Developer Console's New Project dialog box

In the project dashboard, click APIs & auth to open the list of available APIs. Scroll down to YouTube Data API v3 and click the associated button labeled Off to enable access for your project. Select APIs & Auth > Credentials and select Create New Key under Public API Access. Choose Browser key.

In the Create a browser key and configure allowed referers dialog, you can restrict access to the API key to requests from certain domains, such as your own site or Bluemix. Unless you restrict access, your API key will be visible to everyone in your app's HTML source. During development this is a non-issue, so leave the dialog blank and click Create. But when the project is deployed, return to this step and restrict access to requests from your app's domain so third parties can't use the key in other applications.

If you cloned the project from DevOps Services, you can insert the key now where indicated in the index.html file to enable the code to run successfully.


Configure the Google JavaScript client library to access the YouTube API

Google provides client libraries for various languages, including JavaScript. Import the JavaScript client into your HTML document by including this HTML tag in the document's <body> tag (not in the <head> tag):

<script src="https://apis.google.com/js/client.js?onload=function"></script>

The recommended best practice is to place this <script> tag at the end of the <body> tag.

After the client library loads, the gapi (Google API) object is available in window scope on your page. The onload parameter in the <script> tag you just added refers to a callback function called immediately after the library loads. The definition of that function must precede the <script> tag that loads the client. The function calls the gapi.client.load(api name, version, callback function) method to load the APIs that the client will use. In this case, you load the YouTube API with gapi.client.load('youtube', 'v3', onYouTubeApiLoad). onYouTubeApiLoad is a one-line function that calls the setAPIKey method. In setAPIKey, you set the key to the value of your Google browser key.

The client provides methods for accessing the various API calls for Google services. To get the list of items in a playlist, you'll parse the response from the playlistItems.list method in the YouTube API in an asynchronous function and store the relevant attributes in an instance of a JavaScript object called YouTubePlayList. You'll develop the YouTubePlaylist object throughout this article. The object's constructor is defined in the JavaScript function shown below.

Listing 1. YouTubePlaylist.js
function YouTubePlaylist(id, entries) {
   this.id = id;
   this.entries = entries;
   this.currently_playing = 0;
   this.randomizer = false;
}
  • id is the ID of the playlist.
  • entries is a JSON array of the videos in the playlist.
  • currently_playing is the index of the currently playing video in the entries array.
  • randomizer indicates if the playback is randomized.

Create a JSON object with response parameters

Now create a JSON object with the parameters needed in the response. You need only a small subset of the complete list of available parameters.

The part parameter is a comma-separated value (CSV) list of attributes to be returned by the call. Use the contentDetails and snippet attributes. The snippet attribute contains basic information about each video. contentDetails contains the video ID and any notes added by the playlist author. contentDetails will be important later when you identify the highlights in the video. The playListId parameter is the ID of the playlist that you'll use. For the purposes of this article, I set up a playlist with highlights whose ID is PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe. In the playlist, notice that the times of the goals are added as notes. The JSON requestOptions object now looks like this:

var requestOptions = {
   playlistId: playlist_id,
   part: 'contentDetails,snippet'
};

Calling the gapi.client.youtube.playlistItems.list() method with this JSON object as a parameter returns an object with two methods: execute and subscribe. Call the execute method with an asynchronous function, with the response as a parameter.

request.execute(function(response) {});

The items array in the response contains the list of videos in the playlist. You'll use the jQuery each() method to iterate through the items. You'll store the video ID, the medium-size thumbnail for the video, the title, and the note in a JSON object, then add that to an array.

Listing 2. Adding the JSON object to the entries array
var entries = [];
$.each( response.items, function( key, val ) {

   var entry = {};
   entry.video_id = val.snippet.resourceId.videoId;
   entry.image_src = val.snippet.thumbnails.medium.url;
   entry.title = val.snippet.title;
   entry.note = val.contentDetails.note;
   entries.push(entry);

});

Create the YouTubePlayLiist object

Create the new YouTubePlaylist object by calling the constructor (see Listing 1) with the playlist ID and the entries array, and store the object in window scope as a new variable named with the playlist ID.

window[playlist_id] = new YouTubePlaylist(playlistId, entries);

Now you can access the YouTubePlaylist object by using window[playlist_id]. Later, you'll use window[playlist_id] to call the additional functionality of the YouTubePlaylist object.


Use a template to format the player with JsRender

The outline of your player will look like Figure 4, with the thumbnail, title, and note list on the right made up of the entries in the playlist.

Figure 4. Outline of the new player
Illustration shows the structure of the new playlist player

Click to see larger image

Figure 4. Outline of the new player

Illustration shows the structure of the new playlist player

By default, the HTML character set does not include icons similar to the next, previous, and random icons on the native YouTube player. Your app will use icons provided by the Bootstrap front-end framework. To import the Bootstrap style sheet, add the following snippet to the <head> tag.

<link rel="stylesheet" 
      type="text/css" 
      href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">

You'll render the YouTubePlaylist object by using the JsRender templating engine. You define a JsRender template in a <script> tag with the type attribute set to text/x-jsrender. You generate the HTML by calling the render() method of a x-jsrender template with the object to be rendered. You render variables in the template by using the double curly bracket notation {{:}}. For example, {{:id}} renders the id attribute of the object passed to the template.

The template shown in Listing 3 embeds a YouTube player in a web page.

Listing 3. Template for embedding a YouTube player
<object width="640" 
        height="360" 
data="http://www.youtube.com/v/video id?version=3&amp;enablejsapi=1&amp;playerapiid=id"
        id="player id" 
        type="application/x-shockwave-flash">
   <param value="always" name="allowScriptAccess">
   <param value="true" name="allowFullScreen">
</object>

In Listing 3, video id is the ID of the video to be played, and player id is the ID of the player object itself. On initialisation, you'll cue the first video in the entries array, so enter {{:entries[0].video_id}} as the video ID in the embedded-player template. For the player ID, use the playlist ID, namely {{:id}}.

On initialization of a YouTube player object, the player will automatically call the onYouTubePlayerReady() function with the ID of the player as a parameter to customize its behavior when its state changes. The states of a player are unstarted, ended, playing, paused, buffering, and video cued; these are enumerated as -1, 0, 1, 2, 3, and 4. In the current version of the API, the callback function cannot be customized. For now, you'll define a function in window scope to load the next video in the playlist (you'll define this function in the YouTubePlaylist object later) and add it as an event listener to the player.

Listing 4. Function for loading the next video in the playlist
function onYouTubePlayerReady(playerApiId) {
   var player = document.getElementById(playerApiId);
   window["onStateChange" + playerApiId] = function(state) {   
      switch(state) {
         case 0:                         
var video_player = document.getElementById(player_id);
video_player.loadVideoById(window[player_id].getNextVideo(), 0, "large"); 
break; } }; player.addEventListener("onStateChange", "onStateChange" + playerApiId); }

To iterate through the array of videos, you'll use the JsRender {{for}} tag. You'll create each playlist entry on the right in Figure 4 with the template in Listing 5.

Listing 5. Template for creating each playlist entry in the list
{{for entries}}
<div class="playListEntry {{if #index == 0}}nowPlaying{{/if}}" id="{{:video_id}}">
   <div class="playListEntryThumbnail">
      <img src="{{:image_src}}"/>
   </div>
   <div class="playListEntryDescription">
      <div class="playListEntryTitle">{{:title}}</div>
      <div class="playListEntryNote">{{:note}}</div>
   </div>
</div>
{{/for}}

In Listing 5, entries is the entries attribute in the YouTubePlaylist object. The index of the object in the array is stored in the #index variable. Because the first entry in the list is the video loaded in the player on creation, you'll apply the nowPlaying CSS class to the first playListEntry class by using the {{if}} tag to identify it in the for loop.

The controls at the bottom of the player are created by applying the Bootstrap glyphicon class to a span, using the glyphicon-backward, glyphicon-forward, and glyphicon-random classes for the icons.

<div class="playListControls">
   <span class="playListControl disabled glyphicon glyphicon-backward"/>
   <span class="playListControl glyphicon glyphicon-forward"/>
   <span class="playListControl glyphicon glyphicon-random"/>
</div>

Note that initially, the "previous" icon is disabled because when the player first loads, it defaults to the first entry on the playlist, so there's no previous video to play.

You'll add the rendered HTML to an empty div on the page with the ID playlist with this code snippet (remembering that window[player_id] refers to the object you created in the "Create the YouTubePlaylist" section.

$('#' + player_id).html($('#playListPlayerTemplate').render(window[player_id]));

To make this code reusable, move it to a function with the signature addPlaylistToElement(playlist_id, element_id) Then it can be called with addPlaylistToElement('PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe', 'playlist').


Add controls

Return to the YouTubePlaylist object and start adding functionality. Later, you'll use this enhanced version of the object to complete the player in the web page.

Add six functions to the object: previous(), next(), getCurrentlyPlaying(), setCurrentlyPlaying(), randomize(), and isRandomized(). The previous and next functions move to the relevant videos in the playlist and return true if the action succeeds or false if it doesn't (that is, if the user clicks "previous" on the first entry in the playlist or "next" on the last entry). getCurrentlyPlaying() returns the ID of the currently playing video in the playlist. randomize() sets or unsets the random attribute in the object, and isRandomizer() returns the value of the random attribute.

Listing 6 shows the Next() function.

Listing 6. The Next() function
next: function() {
   var retVal = false;
   if(this.randomizer) {
      retVal = true;
      this.currently_playing = Math.floor((Math.random() * this.entries.length));
   }
   else if(this.currently_playing <= this.entries.length) {
      retVal = true;
      this.currently_playing++;
   } 
   return retVal;

In the Next() function, first check if the random attribute is set, and if it is, set the currently_playing index to a random value in the entries array. If random attribute isn't set, and the currently_playing index is less than the number of videos in the array (that is, you have not passed the last video in the playlist), increment the index's value by one to move to the next video and return true to indicate the operation was successful. If it wasn't successful, return false.

Listing 7 shows the previous() function. If the currently_playing index is greater than zero (that is, the user is watching any video except the first video in the playlist), decrement the index by one and return true to indicate that the operation was successful; otherwise, return false.

Listing 7. The previous() function
previous: function() {
   var retVal = false;
   if(this.currently_playing > 0) {
      retVal = true;
      this.currently_playing--;
   } 
   return retVal;
}

In the getCurrentlyPlaying() function, return the video ID of the currently playing index in the entries array.

getCurrentlyPlaying: function() {
   return this.entries[this.currently_playing].video_id;
}

Listing 8 shows the setCurrentlyPlaying() function. Given a video_id from the current playlist, set currently_playing to the index of the element in the entries array with that value.

Listing 8. The setCurrentlyPlaying() function
setCurrentlyPlaying: function(video_id) {
   for(var index = 0; index < this.entries.length; index++) {
      if (this.entries[index].video_id === video_id) {
         this.currently_playing = index;
         break;
      }
   }
}

In the randomize() function, invert the value of the randomizer attribute — from true to false and vice versa — and return the new value.

randomize: function() {
   this.randomizer = !(this.randomizer);
   return this.randomizer;
}

The isRandomized() function returns the value of the randomizer attribute of the playlist — that is, whether the playlist is on random play:

isRandomized: function() {
   return this.randomizer;
}

Use the added functionality

Now add functions to use the added functionality of the JavaScript object.

First, add a helper function to arrange the controls of a playlist. If the player is on random play:

  • The "random" icon is highlighted.
  • The "previous" icon is always disabled because you're not recording the previously played video anywhere.
  • The "next" icon is always enabled because the playlist can never play the "last" video on random. (Think about it: When your MP3 player is on random, does it ever stop playing?)

If the playlist is not on random play:

  • The "previous" icon is disabled only if the first entry in a playlist is playing.
  • The "next" icon is disabled only if the last entry in the playlist is playing.
  • The "random" icon is disabled until it is clicked again.

Listing 9 shows the helper function.

Listing 9. Helper function for arranging the playlist controls
function arrangePlayerControls(player_id) {
   var playListPlayer = $('#' + player_id + 'playListPlayer');
   if(window[player_id].isRandomized()) {
      $('#' + player_id + 'Backward').addClass('disabled');
      $('#' + player_id + 'Forward').removeClass('disabled');
      $('#' + player_id + 'Random').addClass('randomizeActive');
   }
   else {
      $('#' + player_id + 'Random').removeClass('randomizeActive');
      var playListEntries = $('#' + player_id + 'playListEntries');
      if(playListEntries.children(":first").hasClass('nowPlaying')) {
         $('#' + player_id + 'Backward').addClass('disabled');
      }
      else {
         $('#' + player_id + 'Backward').removeClass('disabled');
      }
      if(playListEntries.children(":last").hasClass('nowPlaying')) {
         $('#' + player_id + 'Forward').addClass('disabled');
      }
      else {
         $('#' + player_id + 'Forward').removeClass('disabled');
      }
   }
}

Next, add a function to load a video into the player for a given playlist ID and the time index to begin it at. Remember to remove the nowPlaying class from the div for the currently playing video and add it to the div for the new video. Then call the helper function in Listing 9 to arrange the playlist icons. Listing 10 shows video-loading function.

Listing 10. Function for loading a video into the player
function loadVideoForPlayer(currently_playing_video_id, player_id, time) {
   time = time || 0;
   var video_id = window[player_id].getCurrentlyPlaying();
   $('#' + currently_playing_video_id).removeClass('nowPlaying')
   $('#' + video_id).addClass('nowPlaying');
   $('#' + player_id + 'playListEntries').scrollTop($('#' + video_id).index() * 80);
   document.getElementById(player_id).loadVideoById(video_id, time, "large");
   arrangePlayerControls(player_id);
}

Finally, add a function to load the next video in a given playlist, but only if another video is in the playlist (that is, you are not on random play and the current video is not the last video).

Listing 11. Function for loading the video if a next video exists
function loadNextVideo(player_id) {
   var currently_playing_video_id = window[player_id].getCurrentlyPlaying();
   if(window[player_id].next()) {
      loadVideoForPlayer(currently_playing_video_id, player_id);
   }
}

This function is similar to the anonymous function you declared in onYouTubePlayerReady() (see Listing 4), so refactor the case 0 block in onYouTubePlayerReady() to call loadNextVideo() instead.

You might have noticed that I added in the time for the goals in each video in the playlist. With these new functions, you can use the goal times as link hotspots to jump straight to the goal in the video, instead of needing to watch the whole thing through. In the $.each() loop in addPlaylistToElement(), store the value of the note for each playlistItem object in a local variable, then use the JavaScript match() function to return an array of times from the note, using the regular expression /[0-9]*:[0-5][0-9]/g to find each time. You can then loop through this array and replace the value of each time in the variable with a link to call the cueThisVideo() function with the player ID, the video to be played, and the time index to start at. Remember that the YouTube API loadVideoById() call takes the time in seconds, so split the time into an array, using the colon in the string as a delimiter. Multiply the value in the first index (the minutes) by 60 to convert it to seconds, and then add it to the seconds in the second index to get the total number of seconds. For example, 1:30 becomes the array [1, 30] (1 * 60) + 30 = 90 seconds. Finally, replace the time in the note with the new link. When each time in the note has been processed, store the completed string as the note for the entry in the entries array, as shown in Listing 12.

Listing 12. Replacing the times in the note with a link
var note = val.contentDetails.note;
var times = note.match(/[0-9]*:[0-5][0-9]/g);
times.forEach(function(value, index, array) {
   var time = value.split(":");
   var seconds = parseInt(time[0]) * 60;
   seconds += parseInt(time[1]);
   note = note.replace(value, 
     "<span class='timeLink' onclick='cueThisVideo(\"" 
     + player_id + "\", \"" 
     + video_id + "\", " 
     + seconds + ");'>" 
     + value + "</span>");
});
entry.note = note;

All that remains is to revisit the template and add in calls to these new functions. You want the user to be able to queue a video by clicking the title or the thumbnail, queue the previous or next videos in the playlist, and randomize the playlist with the relevant buttons in the control panel. Listing 13 shows the completed changes in the template to use the added functionality.

Listing 13. Refactored template code
<div onclick="cueThisVideo('{{:~player_id}}', '{{:video_id}}');" 
     class="playListEntryThumbnail">
          <img src="{{:image_src}}"/>
</div>
<div onclick="cueThisVideo('{{:~player_id}}', '{{:video_id}}');" 
     class="playListEntryTitle">
     {{:title}}
</div>

<span id="{{:id}}Backward" 
     class="playListControl disabled glyphicon glyphicon-backward" 
     onclick="if(!$(this).hasClass('disabled'))	{   loadPreviousVideo('{{:id}}')   }">
</span>
<span id="{{:id}}Forward" 
     class="playListControl glyphicon glyphicon-forward" 
     onclick="if(!$(this).hasClass('disabled')) {   loadNextVideo('{{:id}}')   }">
</span>
<span id="{{:id}}Random" 
     class="playListControl glyphicon glyphicon-random" 
     onclick="window['{{:id}}'].randomize();arrangePlayerControls('{{:id}}');">
</span>

Now your custom player is ready to rock!


Conclusion

This article has demonstrated how to use the YouTube API and some simple JavaScript and styling to render an embedded YouTube playlist with equivalent functionality to the native playlist on YouTube.com. See the README file in my DevOps Services page for the project for some suggestions for enhancing the player further. Feel free to fork the project code to implement any or all of those suggestions.

Resources

Learn

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Cloud computing
ArticleID=972281
ArticleTitle=Build a custom YouTube playlist player
publish-date=06272014