Building a dynamic YouTube video gallery

I’m working on a project right now that includes a gallery of recent YouTube videos. I had assumed we could use an existing plugin for this. It seems like a common need, and YouTube has a straightforward API, so there must be a good plugin already.

I tested about 5 popular plugins from the WordPress.org repo and they were all terrible. They all included large admin areas with confusing options, annoying up-sells to their premium version, enqueued CSS and JS files site-wide for functionality that is only on one internal page, and poor code quality.

I wasn’t comfortable installing any of these plugins on my client’s site, so I decided to build my own.

Getting Started

I’m creating a shortcode, [ea_youtube_gallery], that will display a video gallery. There will be a large video player at the top, and thumbnails of recent videos below it. Clicking the thumbnail will update the player to display that video.

First, I create the base shortcode with placeholder content:

<?php
/**
* YouTube Gallery
*
* @package CoreFunctionality
* @author Bill Erickson
* @since 1.0.0
* @license GPL-2.0+
**/
/**
* Youtube Gallery
*/
function ea_youtube_gallery_shortcode() {
$output = '<div class="youtube-gallery">';
// Video Player
$output .= '<div class="youtube-gallery--player">';
$output .= '<h4>Video goes here</h4>';
$output .= '</div>';
// Video Listing
$output .= '<div class="youtube-gallery--listing">';
for( $i = 0; $i < 24; $i++ ) {
$class = 0 == $i ? 'item active' : 'item';
$output .= '<a href="#main-content" class="' . $class . '">';
$output .= '<span class="title">thumbnail goes here</span>';
$output .= '</a>';
}
$output .= '</div>';
$output .= '</div>';
return $output;
}
add_shortcode( 'ea_youtube_gallery', 'ea_youtube_gallery_shortcode' );
view raw shortcodes.php hosted with ❤ by GitHub

Accessing YouTube Data

We’re ultimately going to do a search request to get the recent videos. The request will use the channelId parameter, so first we need to convert the username into a Channel ID using a channels request. And we’ll need an API key to do all of these requests.

API Key

  1. Log into Google Developer Console and create a project.
  2. In the Library section, search for “youtube” and select YouTube Data API v3 (screenshot).
  3. Click “Manage”, then in the left column click “Credentials” (screenshot). Click “Create Credentials > API Key” to create a new API key.

You could create a settings page for entering/storing this key, or just store it in your plugin as a constant. In my main plugin file I added:

define( 'EA_YOUTUBE_API_KEY', 'key-goes-here' );

Channel ID

I’m going to pass the username into the shortcode ( [ea_youtube_gallery user="username-here"] ), then use my ea_get_youtube_channel() shown below to get the Channel ID associated with that username. Since this ID won’t be changing, I’m going to store it in an option so I don’t have to keep making the API request. Here’s the combined code at this point.

<?php
/**
* Get Youtube Channel
*
*/
function ea_get_youtube_channel( $user = '' ) {
if( empty( $user ) )
return;
$channels = get_option( 'ea_youtube_gallery_channels' );
if( empty( $channels ) || empty( $channels[ $user ] ) ) {
$url = add_query_arg(
array(
'forUsername' => esc_attr( $user ),
'part' => 'id',
'key' => EA_YOUTUBE_API_KEY,
),
'https://www.googleapis.com/youtube/v3/channels'
);
$response = wp_remote_get( $url );
$data = json_decode( wp_remote_retrieve_body( $response ) );
if( !empty( $data->items ) )
$channels[ $user ] = $data->items[0]->id;
else
$channels[ $user ] = false;
update_option( 'ea_youtube_gallery_channels', $channels );
}
return $channels[ $user ];
}
view raw shortcodes.php hosted with ❤ by GitHub

Recent Videos

Now that we have the API key and Channel ID, we can ask for recent videos using a search request. I’m going to limit it to 24 videos. You can request up to 50, and the pageToken parameter can be used to request previous/next pages if you were to build some form of pagination, but that’s beyond the scope of my simple plugin.

I’m going to store the API response as a 24 hour transient, so I’m only asking for new videos once a day. Here’s the combined code at this point.

<?php
/**
* Get Youtube Videos
*
*/
function ea_get_youtube_videos( $user = '' ) {
if( empty( $user ) )
return;
$key = 'ea_youtube_gallery_' . sanitize_text_field( $user );
$data = get_transient( $key );
if( false === $data ) {
$channel = ea_get_youtube_channel( $user );
$url = add_query_arg( array(
'part' => 'snippet',
'channelId' => $channel,
'order' => 'date',
'maxResults' => 24,
'key' => EA_YOUTUBE_API_KEY,
), 'https://www.googleapis.com/youtube/v3/search' );
$data = wp_remote_retrieve_body( wp_remote_get( $url ) );
set_transient( $key, $data, DAY_IN_SECONDS );
}
return $data;
}
view raw shortcodes.php hosted with ❤ by GitHub

Putting it together

I now have a $data variable containing the YouTube API response to my request for videos. Each video has metadata like title, videoId, and thumbnails.

I’m going to use the videoId from the first video to create an embedded video. Then for the listing at the bottom, I’m going to loop through each of the videos, add the thumbnail and title, and add a data attribute containing the videoId.

Finally, using JavaScript (in my theme’s global.js file – wasn’t worth enqueuing another JS file for this), when someone clicks on a thumbnail I’ll update the embed code in the player area to use that ID, changing the video.

Here’s the combined code (shortcodes.php), along with the JS and basic styling that I included in my theme. I placed the shortcodes.php file in my core functionality plugin, which also included the API key constant.

/* Youtube Gallery Shortcode
--------------------------------------------- */
.youtube-gallery--player {
max-width: 970px;
margin: 0 auto 70px;
h4 {
margin-top: 14px;
}
}
.youtube-gallery--listing {
.title {
@extend h5;
display: block;
margin-bottom: 0;
}
.item.active,
.item:hover {
text-decoration: none;
.title {
color: $highlight;
}
}
@include media(">=tablet") {
// IE Fallback
.item {
float: left;
margin-left: 32px / 1168px * 100%;
margin-bottom: 40px;
width: 368px / 1168px * 100%;
&:nth-child(3n+1) {
clear: both;
margin-left: 0;
}
}
// Modern Browsers
display: grid;
grid-column-gap: 32px;
grid-row-gap: 40px;
grid-template-columns: repeat( 3, 1fr );
.item {
float: none;
margin: 0;
width: 100%;
}
}
}
jQuery(function($){
// Youtube Gallery
$(document).on('click', '.youtube-gallery--listing a', function(e){
$('.youtube-gallery--listing .active').removeClass('active');
$(this).addClass('active');
$('.youtube-gallery--player iframe').attr('src', 'https://www.youtube.com/embed/' + $(this).data('id') + '?rel=0&showinfo=0' );
$('.youtube-gallery--player h4').text( $(this).find('.title').text() );
});
});
view raw global.js hosted with ❤ by GitHub
<?php
/**
* YouTube Gallery
*
* @package CoreFunctionality
* @author Bill Erickson
* @since 1.0.0
* @license GPL-2.0+
**/
/**
* Youtube Gallery
*/
function ea_youtube_gallery_shortcode( $atts = array() ) {
$atts = shortcode_atts(
array(
'user' => false,
),
$atts,
'ea_youtube_gallery'
);
if( empty( $atts['user'] ) )
return;
$data = ea_get_youtube_videos( $atts['user'] );
if( empty( $data ) )
return;
$data = json_decode( $data );
if( empty( $data->items ) )
return;
$output = '<div class="youtube-gallery">';
// Video Player
$output .= '<div class="youtube-gallery--player">';
$output .= '<iframe width="560" height="315" src="https://www.youtube.com/embed/' . esc_attr( $data->items[0]->id->videoId ) . '?rel=0&amp;showinfo=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>';
$output .= '<h4>' . $data->items[0]->snippet->title . '</h4>';
$output .= '</div>';
// Video Listing
$output .= '<div class="youtube-gallery--listing">';
foreach( $data->items as $i => $item ) {
$class = 0 == $i ? 'item active' : 'item';
$output .= '<a href="#main-content" class="' . $class . '" data-id="' . esc_attr( $item->id->videoId ) . '">';
$output .= '<img src="' . esc_url( $item->snippet->thumbnails->high->url ) . '" />';
$output .= '<span class="title">' . esc_html( $item->snippet->title ) . '</span>';
$output .= '</a>';
}
$output .= '</div>';
$output .= '</div>';
return $output;
}
add_shortcode( 'ea_youtube_gallery', 'ea_youtube_gallery_shortcode' );
/**
* Get Youtube Videos
*
*/
function ea_get_youtube_videos( $user = '' ) {
if( empty( $user ) )
return;
$key = 'ea_youtube_gallery_' . sanitize_text_field( $user );
$data = get_transient( $key );
if( false === $data ) {
$channel = ea_get_youtube_channel( $user );
$url = add_query_arg( array(
'part' => 'snippet',
'channelId' => $channel,
'order' => 'date',
'maxResults' => 24,
'key' => EA_YOUTUBE_API_KEY,
), 'https://www.googleapis.com/youtube/v3/search' );
$data = wp_remote_retrieve_body( wp_remote_get( $url ) );
set_transient( $key, $data, DAY_IN_SECONDS );
}
return $data;
}
/**
* Get Youtube Channel
*
*/
function ea_get_youtube_channel( $user = '' ) {
if( empty( $user ) )
return;
$channels = get_option( 'ea_youtube_gallery_channels' );
if( empty( $channels ) || empty( $channels[ $user ] ) ) {
$url = add_query_arg(
array(
'forUsername' => esc_attr( $user ),
'part' => 'id',
'key' => EA_YOUTUBE_API_KEY,
),
'https://www.googleapis.com/youtube/v3/channels'
);
$response = wp_remote_get( $url );
$data = json_decode( wp_remote_retrieve_body( $response ) );
if( !empty( $data->items ) )
$channels[ $user ] = $data->items[0]->id;
else
$channels[ $user ] = false;
update_option( 'ea_youtube_gallery_channels', $channels );
}
return $channels[ $user ];
}
view raw shortcodes.php hosted with ❤ by GitHub

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. Josh Visick says

    It’s interesting to see how other devs approach a common task like video galleries. I like your use of options and transients. I always forget about leveraging them for stuff like this!

    • Bill Erickson says

      You can see the full source code at the end of this post. Click the “View Raw” link in the bottom right corner to copy/paste the code from one of the files.

  2. Nami says

    Looks good but there must be some instructions missing cause it’s not working. When you say user name. Is it the user ID? Name of the channel?