PHP
Article

YouTube Videos in PHP: Categories, Search and Suggestions

By Younes Rafie

How to Build a YouTube API App with Laravel

In the first part, we introduced the YouTube API and built a small demo to list the most popular videos on YouTube. In this part, we will extend our application to have search functionality, and we’ll also list the available categories on YouTube to let the user narrow down their area of interest. Let’s get started.

YouTube Logo

Listing Categories

YouTube videos are grouped by categories. We can retrieve the list of categories for a certain country using their ISO code. The result can also be localized using an optional parameter like en_US, fr_FR, etc. You can read more in the documentation.

// app/Http/routes.php

Route::get('/categories', ['uses' => 'YouTubeAPIController@categories']);
// app/Http/Controllers/YouTubeAPIController.php

public function categories()
{
  $youtube = \App::make('youtube');
  $categories = $youtube->videoCategories->listVideoCategories('snippet', ['regionCode' => 'MA']);

  dump($categories);
}

As before, the first parameter is the part, and I used my country code as a filter. The result looks like the following.

Categories

Back to our demo, we need to use the list of categories and give the user the ability to filter the list of videos.

// app/Http/Controllers/YouTubeAPIController.php

public function videos()
{
  $options = ['chart' => 'mostPopular', 'maxResults' => 16];
  $regionCode = 'MA';
  $selectedCategory = 0;

  if (\Input::has('page')) {
    $options['pageToken'] = \Input::get('page');
  }

  if (\Input::has('category')) {
    $selectedCategory = \Input::get('category');
    $options['videoCategoryId'] = $selectedCategory;
  }

  $youtube = \App::make('youtube');
  $categories = $youtube->videoCategories->listVideoCategories('snippet', ['regionCode' => $regionCode])->getItems();
  $videos = $youtube->videos->listVideos('id, snippet', $options);

  return view('videos', ['videos' => $videos, 'categories' => $categories, 'selectedCategory' => $selectedCategory]);
}

When the user selects a category, we pass the category ID as a parameter to the videos method. This should work fine with the pagination, so let’s see our view markup changes.

// resources/views/videos.blade.php

{!! Form::open(['route' => 'videos', 'name' => 'formCategory', 'class' => 'form-inline']) !!}
    <label for="category">Filter by category: </label>
    <select name="category" id="category" class="form-control" onchange="this.form.submit()">
        <option value="0">All</option>
        @foreach($categories as $category)
            <option value="{{ $category->getId() }}" @if($selectedCategory == $category->getId()) selected @endif>{{ $category['snippet']['title'] }}</option>
        @endforeach
    </select>
{!! Form::close() !!}

<!-- Pagination -->
<li @if($videos->getPrevPageToken() == null) class="disabled" @endif>
  <a href="/videos?page={{$videos->getPrevPageToken()}}&category={{ $selectedCategory }}" aria-label="Previous">
    <span aria-hidden="true">Previous &laquo;</span>
  </a>
</li>
<li @if($videos->getNextPageToken() == null) class="disabled" @endif>
  <a href="/videos?page={{$videos->getNextPageToken()}}&category={{ $selectedCategory }}" aria-label="Next">
    <span aria-hidden="true">Next &raquo;</span>
  </a>
</li>

The form is submitted on change event of the list of categories, and we make sure to pass the selected category along with the page token on the navigation. The zero category resets our filter.

Categories filter

Searching is an important aspect of the YouTube platform, and we need to have this functionality in our demo. The user can search and paginate through the list of results.

Search

// app/Http/routes.php

Route::any('/search', ['as' => 'search', 'uses' => 'YouTubeAPIController@search']);
// app/Http/Controllers/YouTubeAPIController.php

public function search()
{
  if (!\Input::has('query')) {
    return view("search");
  }

  $options = ['maxResults' => 16, 'q' => \Input::get("query")];
  if (\Input::has('page')) {
    $options['pageToken'] = \Input::get('page');
  }

  $youtube = \App::make('youtube');
  $videos = $youtube->search->listSearch("snippet", $options);

  return view("search", ['videos' => $videos, 'query' => \Input::get('query')]);
}

If the query parameter is not present, that means we need just to render an empty page with the search input, otherwise, we add the query parameter to the list of options and we see if we have a pagination parameter. The view is almost identical to the videos one.

// resources/views/search.blade.php

{!! Form::open(['route' => 'search', 'class' => 'form-inline']) !!}
    <input type="text" name="query" class="form-control" style="width: 50%;" autofocus="true" value="{{ $query }}"/>
    <input type="submit" class="btn btn-default" value="Search"/>
{!! Form::close() !!}

@if(isset($videos))
    <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="{{ URL::route('video', ['id' => $video->getId()->getVideoId()]) }}" title="{{ $video['snippet']['title'] }}" target="_blank">
                <img src="{{ $video['snippet']['thumbnails']['medium']['url'] }}" alt="{{ $video['snippet']['title'] }}" class="img-responsive" height="130px" />
                <h2 class="truncate">{{ $video['snippet']['title'] }}</h2>
                <span class="glyphicon glyphicon-play-circle"></span>
            </a>
        </li>
    @endforeach
    </ul>

    <nav class="text-center">
      <ul class="pagination pagination-lg">
        <li @if($videos->getPrevPageToken() == null) class="disabled" @endif>
          <a href="/search?page={{$videos->getPrevPageToken()}}&query={{ $query }}" aria-label="Previous">
            <span aria-hidden="true">Previous &laquo;</span>
          </a>
        </li>
        <li @if($videos->getNextPageToken() == null) class="disabled" @endif>
          <a href="/search?page={{$videos->getNextPageToken()}}&query={{ $query }}" aria-label="Next">
            <span aria-hidden="true">Next &raquo;</span>
          </a>
        </li>
      </ul>
    </nav>
@else
    <h2>No result.</h2>
@endif

You see that the videos result is identical to the video page, except that the $video->getId() method returns a resource, and you need to get the video ID using a chained call. YouTube API results have array access, so you can work with them as arrays instead of calling getter methods.
The response also contains information about the video channel, creation date, quality, etc. You may use them to enrich your page.

When you visit a single video page on YouTube, you see a list of suggestions based on your current video. The search endpoint offers this possibility through some options:

// 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);
  $relatedVideos = $youtube->search->listSearch("snippet", ['maxResults' => 16, 'type' => 'video', 'relatedToVideoId' => $id]);

  if (count($videos->getItems()) == 0) {
    return redirect('404');
  }

  return view('video', ['video' => $videos[0], 'relatedVideos' => $relatedVideos]);
}

Suggestions

According to the documentation, you need to set the type parameter to video when you want to do a related video search. The search endpoint has a lot of options like restricting the search to a certain quality, a certain category or a certain channel, etc. Be sure to check the documentation for the list of available options.

Extending the Demo

Because we can’t cover everything in this tutorial, you can take the demo from here and start extending it with some new functionality. You can start by displaying video channel names and links under every video element, and when the user clicks on the link, redirect them to the /channel/channel_id route and only display the channel videos; you can group the video by playlists, etc.

Quotas & Caching

An important part of working with APIs are quotas. YouTube gives you 50,000,000 units/day, and as we said earlier, units are calculated per request and can be increased when you ask for more information. Some API calls cost more than others. For example, calls to the search endpoint cost 100 units. Apart from that, you are limited to 3000 requests/second/user.

Quotas

Etags Caching

If you’ve noticed in the previous screenshots, we have an Etag attribute which stamps the current state of the entity result. This attribute can be sent inside the HTTP headers as described on the HTTP spec page and if the entity has changed you’ll get a refreshed version, otherwise you get a 304 (not modified) response. You can read more about optimization and performance in the documentation.

Conclusion

In this article, we introduced the YouTube API v3 and built a demo along the way. The API offers a lot of features, like working with user activities, playlists, channels, subscriptions, etc. Be sure to check the getting started guide to familiarize yourself with the limits and possibilities.

You can check the final demo on Github, it includes an installation guide to get you started. If you have any comments or questions, don’t hesitate to post them below and I will do my best to answer them.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.