YouTube on Rails

Share this article

This article is out of date, as YouTube has released version 3 of their API. If you need a tutorial on using version 3, check out this post instead.
yt-rails

YouTube. The third most visited resource in the world. We have all visited it and, probably, uploaded videos to it. But how can we, as Rails developers, fetch information about YouTube videos? How can we display them on our website? Are there any libraries that can help us with that? Let’s investigate, shall we?

In this article, I will show you how to create a web application that allows users to add and watch videos from YouTube. Upon adding, the video’s info will be fetched automatically via the YouTube API. This app will also display these added videos using the typical YouTube player with the help of the YouTube IFrame API. Lastly, there are some potential problems and gotchas that you might face.

The source code is available on GitHub.

The working demo can be found on Heroku.

Preparing a Demo Application

For this article I will be using Rails 3.2.17, but you can implement practically the same solution with Rails 4.

Start by creating a new Rails app without the default testing suite:

$ rails new yt_videos -T

First of all, we should grab the gems that will be used in this demo. We are going to use youtube_it, a convenient gem written by Kyle J. Ginavan, to work with the YouTube API. For styling, let’s use our old friend Twitter Bootstrap and, specifically, the bootstrap-sass gem. Go ahead and add these two lines into your Gemfile:

Gemfile

gem 'bootstrap-sass', '~> 3.1.1.0'
gem 'youtube_it', '~> 2.4.0'

Then run

$ bundle install

Now add

//= require bootstrap

to the application.js and

@import "bootstrap";

to the application.css.scss to include the Bootstrap styles and scripts. Of course, in a real application you would choose only the required components.

We should think about what our Video model will look like. Of course, it should contain a link to the video that the user has added. The YouTube API only supplies the unique video identifier (which we will learn to extract, shortly), so we’ll need to grab it. Also, we’ll want to store some info about the video. Here is the list of all attributes for our ActiveRecord model:

  • id – integer, primary key, indexed, unique
  • link – string
  • uid – string. It is also a good idea to add an index here to enforce uniqueness. For this demo, we don’t want users to add the same videos multiple times
  • title – string. This will contain the video’s title extracted from YouTube
  • author – string. Author’s name extracted from YouTube
  • duration – string. We will store the video’s duration, formatted like so: “00:00:00”
  • likes – integer. Likes count for a video
  • dislikes – integer. Dislikes count for a video.

That will be enough for the purposes of this demo, but keep in mind that the YouTube API allows you to fetch a lot more information.

Run this command to create a model and a corresponding migration:

$ rails g model Video link:string title:string author:string duration:string likes:integer dislikes:integer

Now set up the routes:

routes.rb

resources :videos, only: [:index, :new, :create]
root to: 'videos#index'

We will not need the edit, update, show and destroy actions for the purposes of this demo. Also, do not forget to remove the public/index.html file if you are working with Rails 3.

The last thing to do is add a block for rendering flash messages into the layout:

application.html.erb

[...]

<div class="container">
  <% flash.each do |key, value| %>
    <div class="alert alert-<%= key %>">
      <button type="button" class="close" data-dismiss="alert">&times;</button>
      <%= value %>
    </div>
  <% end %>
</div>

[...]

Adding Videos

When a user visits our website, the first thing he probably should see is the “Add video” button.

videos/index.html.erb

<div class="jumbotron">
  <div class="container">
    <h1>Share your videos with the world!</h1>
    <p>Click the button below to share your video from YouTube.</p>
    <p>
      <%= link_to 'Add video now!', new_video_path, class: 'btn btn-primary btn-lg' %>
    </p>
  </div>
</div>

Thanks to Bootstrap, this block will look pretty nice. The next step is to implement the new action.

But let’s stop for a second and check the models/video.rb file. If you are using Rails 3, all the attributes (except for id, updated_at and created_at) were made accessible by default. The only thing we need from the user is the link to the video he wants to add. Other attributes will be populated automatically, so let’s make the appropriate modifications:

models/video.rb

[...]

attr_accessible :link

[...]

For Rails 4, you should only permit the link attribute in the controller:

params.require(:video).permit(:link)

Okay, now we can move on. Add these two methods into the controller:

controllers/videos_controller.rb

[...]

def new
  @video = Video.new
end

def create
  @video = Video.new(params[:video])
  if @video.save
    flash[:success] = 'Video added!'
    redirect_to root_url
  else
    render 'new'
  end
end

[...]

Nothing fancy going on here.

The view:

videos/new.html.erb

<div class="container">
  <h1>New video</h1>

  <%= form_for @video do |f| %>
    <%= render 'shared/errors', object: @video %>

    <div class="form-group">
      <%= f.label :link %>
      <%= f.text_field :link, class: 'form-control', required: true %>
      <span class="help-block">A link to the video on YouTube.</span>
    </div>

    <%= f.submit class: 'btn btn-default' %>
  <% end %>
</div>

As you can see, we only ask the user to enter a link to a video on YouTube. Here, also render a shared partial to display any errors, which looks like:

shared/_errors.html.erb

<% if object.errors.any? %>
  <div class="panel panel-danger">
    <div class="panel-heading">
      <h3 class="panel-title">The following errors were found while submitting the form:</h3>
    </div>

    <div class="panel-body">
      <ul>
        <% object.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  </div>
<% end %>

It is time for the fun part – extracting information about the video specified by the user. We are going to do this inside a callback before creating a record.

models/video.rb

before_create -> do
  # Our code here
end

To fetch information from the YouTube API, we will need the video’s unique identifier. Actually, this identifier is already present in the link that the user provides. The trick here is that these links can be specified in multiple formats, for example:

  • http://www.youtube.com/watch?v=0zM3nApSvMg&feature=feedrecgrecindex
  • http://www.youtube.com/user/IngridMichaelsonVEVO#p/a/u/1/QdK8U-VIH_o
  • http://www.youtube.com/v/0zM3nApSvMg?fs=1&hl=en_US&rel=0
  • http://www.youtube.com/watch?v=0zM3nApSvMg#t=0m10s
  • http://www.youtube.com/embed/0zM3nApSvMg?rel=0
  • http://www.youtube.com/watch?v=0zM3nApSvMg
  • http://youtu.be/0zM3nApSvMg

All these URLs point to the same video with the id 0zM3nApSvMg (you can read more about it on StackOverflow).

It is also worth mentioning that a video’s uID contains 11 symbols. To fetch the uID from the link, we will use the following RegExp:

/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/i

Before we do that, though, let’s make sure we have a valid link:

models/video.rb

YT_LINK_FORMAT = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/i

validates :link, presence: true, format: YT_LINK_FORMAT

[...]

Ok, fetch the uID inside the callback:

models/video.rb

[...]

before_create -> do
  uid = link.match(YT_LINK_FORMAT)
  self.uid = uid[2] if uid && uid[2]

  if self.uid.to_s.length != 11
    self.errors.add(:link, 'is invalid.')
    false
  elsif Video.where(uid: self.uid).any?
    self.errors.add(:link, 'is not unique.')
    false
  else
    get_additional_info
  end
end

Here, there are some validations for the uid, specifically for checking its length and uniqueness. If everything is okay, call a method to fetch information about the video:

models/video.rb

[...]

private

def get_additional_info
  begin
    client = YouTubeIt::OAuth2Client.new(dev_key: 'Your_YT_developer_key')
    video = client.video_by(uid)
    self.title = video.title
    self.duration = parse_duration(video.duration)
    self.author = video.author.name
    self.likes = video.rating.likes
    self.dislikes = video.rating.dislikes
  rescue
    self.title = '' ; self.duration = '00:00:00' ; self.author = '' ; self.likes = 0 ; self.dislikes = 0
  end
end

def parse_duration(d)
  hr = (d / 3600).floor
  min = ((d - (hr * 3600)) / 60).floor
  sec = (d - (hr * 3600) - (min * 60)).floor

  hr = '0' + hr.to_s if hr.to_i < 10
  min = '0' + min.to_s if min.to_i < 10
  sec = '0' + sec.to_s if sec.to_i < 10

  hr.to_s + ':' + min.to_s + ':' + sec.to_s
end

This is where youtube_it comes into play. First, a client variable is being created – it will be used to issue queries. You might be wondering “What is a YouTube developer key?” This key is used to issue queries against the public YouTube data. Please note that some actions require user authorization, which you can read more about here.

To get your developer key, register a new app at https://code.google.com/apis/console. Open its settings, go to “APIs & auth”, then “Credentials”. Create a new public key for browser applications, which is your developer key.

After initializing a client, fetch the video using video_by, getting the information about it. Note that the video’s duration is presented in seconds, so we have to implement a parse_duration method to format it the way we want.

At this point our app allows users to add their videos. It also fetches some info and provides validation for the user input. Nice, isn’t it?

Displaying the Videos

OK, we have the videos, now to display them. Add the following to your controller:

controllers/videos_controller.rb

def index
  @videos = Video.order('created_at DESC')
end

On to the view:

videos/index.html.erb

[...]

<% if @videos.any? %>
  <div class="container">
    <h1>Latest videos</h1>

    <div id="player-wrapper"></div>

    <% @videos.in_groups_of(3) do |group| %>
      <div class="row">
        <% group.each do |video| %>
          <% if video %>
            <div class="col-md-4">
              <div class="yt_video thumbnail">
                <%= image_tag "https://img.youtube.com/vi/#{video.uid}/mqdefault.jpg", alt: video.title,
                              class: 'yt_preview img-rounded', :"data-uid" => video.uid %>

                <div class="caption">
                  <h5><%= video.title %></h5>
                  <p>Author: <b><%= video.author %></b></p>
                  <p>Duration: <b><%= video.duration %></b></p>
                  <p>
                    <span class="glyphicon glyphicon glyphicon-thumbs-up"></span> <%= video.likes %>
                    <span class="glyphicon glyphicon glyphicon-thumbs-down"></span> <%= video.dislikes %>
                  </p>
                </div>
              </div>
            </div>
          <% end %>
        <% end %>
      </div>
    <% end %>
  </div>
<% end %>

The #player-wrapper is an empty block where the YouTube player will be shown. The in_groups_of method groups our records by 3 and to displays them in rows. Please note, if there is not enough elements to form a group, Rails replaces each missing element with nil, so we have to add a if video condition.

Another important thing to mention is the video preview image. To get a preview image for a video, use one of the following links:

  • https://img.youtube.com/vi/mqdefault.jpg – 320×180 image with no black stripes above and below the picture;
  • https://img.youtube.com/vi/hqdefault.jpg – 480×360 image with black stripes above and below the picture;
  • https://img.youtube.com/vi/<1,2,3>.jpg – 120×90 image with different scenes from the video with black stripes above and below the picture.

Also, when rendering video preview images we are storing video’s uID using HTML5 data-* attribute. This attribute will be used shortly.

To display the actual videos on our website, the YouTube IFrame API will be used. It creates the YouTube player with certain parameters, allowing the user to change the video, pause and stop it, etc. You can read more about it here. Hook up the required javascript files like this:

layouts/application.html.erb

[...]

  <script src="https://www.google.com/jsapi"></script>
  <script src="https://www.youtube.com/iframe_api"></script>
</head>

[...]

Now, let’s write some CoffeeScript code in a new yt_player.coffee file:

javascripts/yt_player.coffee

jQuery ->
  # Initially the player is not loaded
  window.ytPlayerLoaded = false

  makeVideoPlayer = (video) ->
    if !window.ytPlayerLoaded
      player_wrapper = $('#player-wrapper')
      player_wrapper.append('<div id="ytPlayer"><p>Loading player...</p></div>')

      window.ytplayer = new YT.Player('ytPlayer', {
        width: '100%'
        height: player_wrapper.width() / 1.777777777
        videoId: video
        playerVars: {
          wmode: 'opaque'
          autoplay: 0
          modestbranding: 1
        }
        events: {
          'onReady': -> window.ytPlayerLoaded = true
          'onError': (errorCode) -> alert("We are sorry, but the following error occured: " + errorCode)
        }
      })
    else
      window.ytplayer.loadVideoById(video)
      window.ytplayer.pauseVideo()
    return
  return

First of all, we initialize the ytPlayerLoaded boolean variable which checks whether the player has been loaded. After that, create a makeVideoPlayer function which takes one argument – the video’s uID – and creates a YouTube player or changes the video being played. If the player is not yet loaded, we append a new #ytPlayer block to the #player-wrapper which is eventually replaced by the player. With all that in place, finally create the YouTube player object assigning it to the ytplayer (we will use it to call API functions).

Let’s stop for a second a talk a bit about the arguments that are being passed when creating this object. ytPlayer is the DOM id of the block that should be replaced with the player. The second argument is a javascript object containing settings for the player:

  • width – width of the player. Our site has responsive layout so we set it to 100%
  • height – height of the player. We have to calculate it based on the width. The typical resolution for YouTube is 16:9 which means 1.7(7) ratio
  • videoId – uID of the video to load
  • wmode – this parameter controls layering and transparency of Flash object (learn more). If we do not set it to opaque, the player will overlay modal windows (for example the ones created with jQuery UI) and look terrible
  • autoplay – set to 1, the video will play on load. In this demo, we do not want this to happen
  • modestbranding – if this is set to 1, the YouTube logo will not be shown in the control bar, but still will be visible in the upper right corner when the video is paused
  • events – here we specify two events to handle. When the player has loaded (onReady), set ytPlayerLoaded to true. If an error occurrs while loading the player or video (for example, this video was deleted), alert the user.

If the player has already been loaded, we use loadVideoById function to change the video being played and then pause it using pauseVideo (for demonstration purposes).

Okay, our function is ready. We want to load the player and display the latest video as soon as user opens our website. How do we achieve this? We are going to use a special function presented by Google API:

javascripts/yt_player.coffee

[...]

_run = ->
  # Runs as soon as Google API is loaded
  $('.yt_preview').first().click()
  return

google.setOnLoadCallback _run

[...]

This google.setOnLoadCallback _run means that the _run function should be called as soon as the API has finished loading. If we do not use this callback and try to load the player as soon as DOM is ready, an error occurs stating that YT.Player is not a function – this is because API has not loaded yet.

The last thing to do is to bind a click event handler to the video previews:

javascripts/yt_player.coffee

[...]

$('.yt_preview').click -> makeVideoPlayer $(this).data('uid')

[...]

And that is all! Now our users can add videos and watch them on our website.

Actually, there is one small problem left. We specified the player’s width in percent, but the player’s height is specified in pixels. That means that if a user tries to resize the window, the player will become narrower, but it will preserve the same width – this is bad. To solve this, we can bind a resize event handler to the window like this:

javascripts/yt_player.coffee

[...]

$(window).on 'resize', ->
  player = $('#ytPlayer')
  player.height(player.width() / 1.777777777) if player.size() > 0
  return

[...]

Now the player’s height will be rescaled correctly – go on and give it a try. You can also delay firing this event until the user stops resizing, otherwise it will be fired constantly. To do this, use the BindWithDelay library written by Brian Grinstead. It is super simple:

javascripts/yt_player.coffee

[...]

$(window).bindWithDelay('resize', ->
  player = $('#ytPlayer')
  player.height(player.width() / 1.777777777) if player.size() > 0
  return
, 500)

[...]

With this structure we are delaying firing this event by 500ms.

Conclusion

This brings us to the end of the article. We talked about the youtube_it gem which can be used to work with the YouTube API and about the YouTube IFrame API. I hope that information provided here was useful. Do not forget to share your opinion about this article in the comments. See you again soon!

Frequently Asked Questions (FAQs) about YouTube Rails

How can I embed a YouTube video in my Rails application?

Embedding a YouTube video in your Rails application is quite straightforward. You can use the iframe tag to embed the video. First, go to the YouTube video you want to embed, click on the share button, and then click on the embed button. You will see an iframe tag which you can copy and paste into your Rails application. Remember to sanitize the iframe tag to prevent cross-site scripting (XSS) attacks.

How can I use the video_tag helper in Rails?

The video_tag helper in Rails is used to generate a video tag for your Rails application. You can use it to embed a video file that is stored in your application. To use the video_tag helper, you need to pass the path of the video file as an argument. For example, video_tag “sample.mp4” will generate a video tag for the video file named “sample.mp4”.

Can I use HTML5 video tag in my Rails application?

Yes, you can use the HTML5 video tag in your Rails application. The HTML5 video tag is used to embed video content in a document. It can be used in conjunction with the source tag to specify multiple video files. The browser will choose the most suitable one based on the video type and network conditions.

How can I control the playback of a YouTube video in my Rails application?

You can control the playback of a YouTube video in your Rails application by using the YouTube Player API. The API allows you to control the video playback programmatically. You can play, pause, stop, and seek the video, as well as adjust the volume and playback rate.

How can I make a YouTube video responsive in my Rails application?

To make a YouTube video responsive in your Rails application, you can use CSS to create a responsive container. The container should maintain a 16:9 aspect ratio, which is the standard aspect ratio for YouTube videos. You can then place the iframe tag for the YouTube video inside this container.

How can I add captions to a YouTube video in my Rails application?

Adding captions to a YouTube video in your Rails application can be done by using the YouTube Data API. The API allows you to upload a caption file for a video. The caption file should be in a format that YouTube supports, such as .srt, .sbv, or .vtt.

Can I use the YouTube Data API in my Rails application?

Yes, you can use the YouTube Data API in your Rails application. The API allows you to interact with YouTube to perform a variety of operations, such as retrieving video information, uploading videos, and managing playlists.

How can I handle errors when embedding a YouTube video in my Rails application?

When embedding a YouTube video in your Rails application, you may encounter errors such as the video not being found or the video not being playable. You can handle these errors by using the onerror event of the iframe tag. The onerror event is triggered when an error occurs while loading the external resource.

Can I customize the appearance of a YouTube video in my Rails application?

Yes, you can customize the appearance of a YouTube video in your Rails application. You can use the YouTube Player API to change the player’s size, theme, color, and other settings. You can also use CSS to style the iframe tag that contains the video.

How can I optimize the loading time of a YouTube video in my Rails application?

To optimize the loading time of a YouTube video in your Rails application, you can use the defer attribute in the script tag. The defer attribute tells the browser to only execute the script after the document has been fully parsed. This can significantly improve the loading time of the video.

Ilya Bodrov-KrukowskiIlya Bodrov-Krukowski
View Author

Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week