Displaying YouTube Videos in PHP
In this two-part article, we’re going to learn how to work with version 3 of the YouTube API and we’ll build a demo along the way. Let’s get started.
What We’re Building
We’re going to build a demo that lets the user browse popular videos on YouTube, search for a video, browse videos by categories, and select a video to watch.
I will be using Laravel 5 as my framework of choice, and Vagrant for my development environment. The final product will look something like this:
Preparing the Project
After installing Laravel 5 on your machine, install google/apiclient
:
composer require google/apiclient
To set up a new project on the Google Developers Console, check out this quick tip to get started.
Registering Environment Variables
After getting our credentials from the Google Developers Console, we need to register them inside our application.
// .env
APP_DEBUG=true
app_name='Your app name (optional)'
client_id='Your client id'
client_secret='Your client secret'
api_key='Your developer key'
// config/google.php
return [
'app_name' => env('app_name'),
'client_id' => env('client_id'),
'client_secret' => env('client_secret'),
'api_key' => env('api_key')
];
Laravel automatically loads the environment variables from the .env
file in the root of our application folder. Now that we have our configuration set up, we can start the login process.
Login & Authorization
Before logging in with the Google API, we need to talk about scopes, and why they are important to the authorization process.
Scopes
Scopes define the level of authorization accorded to the application, so be sure to only request what you need. YouTube’s API has four scopes:
https://www.googleapis.com/auth/youtube
: Manage your YouTube account.https://www.googleapis.com/auth/youtube.readonly
: View your YouTube account.https://www.googleapis.com/auth/youtube.upload
: Upload YouTube videos and manage your YouTube videos.https://www.googleapis.com/auth/youtubepartner-channel-audit
: Retrieve the auditDetails part in a channel resource.
For our demo, I’m only going to use the first one, and we can extend it depending on our application’s needs. You can read more about authorization in the documentation.
// app/Services/GoogleLogin.php
namespace App\Services;
class GoogleLogin
{
protected $client;
public function __construct(\Google_Client $client)
{
$this->client = $client;
$this->client->setClientId(\Config::get('google.client_id'));
$this->client->setClientSecret(\Config::get('google.client_secret'));
$this->client->setDeveloperKey(\Config::get('google.api_key'));
$this->client->setRedirectUri(\Config::get('app.url') . "/loginCallback");
$this->client->setScopes([
'https://www.googleapis.com/auth/youtube',
]);
$this->client->setAccessType('offline');
}
public function isLoggedIn()
{
if (\Session::has('token')) {
$this->client->setAccessToken(\Session::get('token'));
}
if ($this->client->isAccessTokenExpired()) {
\Session::set('token', $this->client->getRefreshToken());
}
return !$this->client->isAccessTokenExpired();
}
public function login($code)
{
$this->client->authenticate($code);
$token = $this->client->getAccessToken();
\Session::put('token', $token);
return $token;
}
public function getLoginUrl()
{
$authUrl = $this->client->createAuthUrl();
return $authUrl;
}
}
After constructing a Google_Client
instance using our credentials, we set the desired scopes. The setAccessType
method gives our application the ability to refresh the token when the user is not present.
Login Controller
// app/Http/routes.php
Route::get('/login', ['uses' => 'GoogleLoginController@index', 'as' => 'login']);
Route::get('/loginCallback', ['uses' => 'GoogleLoginController@store', 'as' => 'loginCallback']);
// app/Http/Controllers/GoogleLoginController.php
class GoogleLoginController extends Controller
{
public function index(\App\Services\GoogleLogin $ga)
{
if ($ga->isLoggedIn()) {
return \Redirect::to('/');
}
$loginUrl = $ga->getLoginUrl();
return "<a href='{$loginUrl}'>login</a>";
}
public function store(\App\Services\GoogleLogin $ga)
{
// User rejected the request
if(\Input::has('error')){
dd(\Input::get('error'));
}
if (\Input::has('code')) {
$code = \Input::get('code');
$ga->login($code);
return \Redirect::to('/');
} else {
throw new \InvalidArgumentException("Code attribute is missing.");
}//else
}
}
The GoogleLoginController@index
method will test if the user is logged in and if so, will redirect them to the home page. Otherwise, we generate a new login URL.
After passing the authorization phase, Google will redirect the user to the callback URL defined in the Google Developers Console with the code parameter. This code is used in exchange for a token.
Listing Videos
The Google_Service_YouTube
class is our door to the YouTube API. It provides access to all YouTube data. The class takes a Google_Client
instance as a parameter. It makes sense to create a provider to bind our implementation so that we don’t have to repeat the same code everywhere. You can read more about service providers in the documentation.
php artisan make:provider YouTubeServiceProvider
This command will create a new class inside our app/Providers
folder. We need to add our service provider to the list of providers inside config/app.php
.
// config/app.php
'providers' => [
'App\Providers\YouTubeServiceProvider',
]
// app/Providers/YouTubeServiceProvider.php
public function register()
{
$app = $this->app;
$this->app->bind('GoogleClient', function () {
$googleClient = new \Google_Client();
$googleClient->setAccessToken(\Session::get("token"));
return $googleClient;
});
$this->app->bind('youtube', function () use ($app) {
$googleClient = \App::make('GoogleClient');
$youtube = new \Google_Service_YouTube($googleClient);
return $youtube;
});
}
The https://www.googleapis.com/youtube/v3/videos
endpoint returns the list of videos, and it can be accessed using our YouTube class via $youtube->videos
.
// app/Http/Controllers/YouTubeAPIController.php
public function videos()
{
$youtube = \App::make('youtube');
$videos = $youtube->videos->listVideos('snippet', ['chart' => 'mostPopular']);
dump($video);
}
The first parameter to the listVideos
method is called part
and it defines the information contained in the result. You may add statistics
to get data about votes count, likes, etc. We’ll talk more about the part
parameter later in this article.You can read more about the supported values in the documentation.
The second parameter must contain a filter for the videos. In this case, we are getting the most popular videos on YouTube. You may also request your liked or disliked videos, etc. You can read more about filters in the documentation.
// app/Http/Controllers/YouTubeAPIController.php
public function videos()
{
$options = ['chart' => 'mostPopular', 'maxResults' => 16];
if (\Input::has('page')) {
$options['pageToken'] = \Input::get('page');
}
$youtube = \App::make('youtube');
$videos = $youtube->videos->listVideos('id, snippet', $options);
return view('videos', ['videos' => $videos]);
}
This method is self explanatory. The page parameter is for pagination as we’ll see next.
// resources/views/videos.blade.php
<ul class="list-unstyled video-list-thumbs row">
@foreach($videos as $video)
<li class="col-lg-3 col-sm-4 col-xs-6">
<a href="https://www.youtube.com/watch?v={{ $video->getId() }}" title="{{ $video['snippet']['title'] }}" target="_blank">
<img src="{{ $video['snippet']['thumbnails']['medium']['url'] }}" alt="{{ $video['snippet']['title'] }}" />
<h2 class="truncate">{{ $video['snippet']['title'] }}</h2>
</a>
</li>
@endforeach
</ul>
<ul class="pagination pagination-lg">
<li @if($videos->getPrevPageToken() == null) class="disabled" @endif>
<a href="/videos?page={{$videos->getPrevPageToken()}}" aria-label="Previous">
<span aria-hidden="true">Previous «</span>
</a>
</li>
<li @if($videos->getNextPageToken() == null) class="disabled" @endif>
<a href="/videos?page={{$videos->getNextPageToken()}}" aria-label="Next">
<span aria-hidden="true">Next »</span>
</a>
</li>
</ul>
Our view is very basic – we only output the video title, thumbnail and a link. Our API response also contains likes, dislikes, view count, etc, so you may use them to enrich your page using some special markup. The pagination links give the user the ability to navigate through videos. You may have noticed that it’s done through tokens and not the normal page parameter.
When you click on a video, you will be redirected to watch the video on YouTube using the ID. Why not get the video details and create a page containing the player along with more info about the video?
Single Video Page
Getting a single video is just a matter of specifying an option to the same endpoint from before. Since the result is a single item, we set the maxResults
parameter to 1
, and we remove the chart
attribute because it’s not relevant in this request.
// app/Http/routes.php
Route::get('/video/{id}', ['uses' => 'YouTubeAPIController@video', 'as' => 'video']);
// app/Http/Controllers/YouTubeAPIController.php
public function video($id){
$options = ['maxResults' => 1, 'id' => $id];
$youtube = \App::make('youtube');
$videos = $youtube->videos->listVideos('id, snippet, player, contentDetails, statistics, status', $options);
if(count($videos->getItems()) == 0){
return redirect('404');
}
return view('video', ['video' => $videos[0]]);
}
Because it’s a single item page, I tried to pull as much information as I can using the part
parameter. If the item doesn’t exist, we redirect the user to the 404
not found page. Otherwise, we render our view with the first item from the list.
// resources/views/video.blade.php
<div class="row">
<h2>{{ $video["snippet"]["title"] }}</h2>
</div>
<div class="row">
<iframe type='text/html' src='http://www.youtube.com/embed/{{ $video->getId() }}' width='100%' height='500' frameborder='0' allowfullscreen='true'></iframe>
</div>
<div class="row">
(<span>{{ $video["statistics"]["likeCount"] }} <i class="glyphicon glyphicon-thumbs-up"></i></span>)
(<span>{{ $video["statistics"]["dislikeCount"] }} <i class="glyphicon glyphicon-thumbs-down"></i></span>)
(<span>{{ $video["statistics"]["favoriteCount"] }} <i class="glyphicon glyphicon-heart"></i></span>)
(<span>{{ $video["statistics"]["commentCount"] }} <i class="glyphicon glyphicon-bullhorn"></i></span>)
</div>
<hr/>
<div class="row">
<p>{{ $video["snippet"]["description"] }}</p>
</div>
Because we asked the YouTube API for the player
part, we can directly access it using $video['player']['embedHtml']
, but if you want to customize the player dimensions you can build it using the video ID as we did in our example.
One thing to note: every request has a cost of one call, and when you ask for the snippet, statistics, etc, you add up new costs. You can read more about quotas and costs in the documentation.
Wrapping Up
In this first part, we introduced the YouTube API and built a small demo to query the list of most popular videos on YouTube with the pagination links, and a page to view a single video. In the next part we will be exploring video categories and the search functionality, so stay tuned. You can check the final result on Github, and if you have any questions or comments, you can post them below.