PandaStream

This add-on is operated by Copper Cloud Inc

Video encoding service with a pretty, yet powerful API

PandaStream

Last Updated: 10 April 2014

encoding panda video

Table of Contents

We provide a Javascript uploader that you embed on a page in your application and sends the video directly to Panda, an encoder that converts it to any format you like, and a REST API to let you manage videos.

Prerequisites

Panda uses Amazon S3 buckets to store encoded videos. Before you can use the add-on, you’ll need your Amazon Access Key ID and Amazon Secret Key. You can find these in the Security Credentials section of your Amazon account. If you don’t have one, you can sign up for an S3 account.

This guide assumes that you’re using Ruby, although Panda will still work with other Heroku-deployable applications.

Add the Heroku add-on

The free plan allows you to upload and encode an unlimited number of videos. However the filesize of uploads must be less than 10Mb. For production usage there a several paid plans available with no upload limit and dedicated encoders.

To install the free version of the Panda add-on, simply run:

$ heroku addons:add pandastream
Adding pandastream:sandbox to myapp... done

You can also do this from the Resources section of your application’s configuration page on Heroku.

When you add your Heroku addon, It automatically creates a new Pandastream account which is only available from Heroku. You should not signup to the website directly.

Running this command will set the Heroku configuration variable PANDASTREAM_URL. You can see it by typing heroku config --long.

Install the gem

Add the Panda gem to your Gemfile so that it will be loaded.

gem 'panda', '~> 1.6.0'

Next install it:

$ bundle install

Finally, create the file config/initializers/panda.rb with the line below so that your application connects to Panda when it starts:

Panda.configure(ENV['PANDASTREAM_URL'])

Configure Panda

Before using Panda in your application, you need to let Panda know where to save encoded videos.

$ heroku addons:open pandastream

Click on Settings and enter your S3 bucket and credentials.

Set up locally

ENV['PANDASTREAM_URL'] isn’t set locally by default. By copying the connection information to config/environments/development.rb you will be able to use your Heroku Panda account in development mode locally.

$ heroku config
PANDASTREAM_URL => http://a8704c6c854d69a031d8:efe2c61c3edfc7772556@api.pandastream.com:443/927a9d9xk37ded62422d4613229c156f

Copy the connection url to config/environments/development.rb:

ENV['PANDASTREAM_URL'] = "http://a8704c6c854d69a031d8:efe2c61c3edfc7772556@api.pandastream.com:443/927a9d9xk37ded62422d4613229c156f"

Test your setup

If you’ve set up your local development environment with your credentials, you can test on your local console by running rails c.

Fire up a console:

$ heroku run console

The initializer should connect you to Panda automatically, and you should be able to list all your encoding profiles.

Panda::Profile.all
=> [<Panda::Profile preset_name: h264, ...>]

Panda encodes your videos to a standard MP4 (H264, AAC) profile by default, but you can set up Encoding Profiles to output whichever format you like through the web interface or the API.

You can now upload your videos to panda providing a source url or a local file. We have also made available a sample video for your convenience.

# import a file to Panda
video = Panda::Video.create(:source_url => "http://panda-test-harness-videos.s3.amazonaws.com/panda.mp4")
# upload a local file
video = Panda::Video.create(:file => "/local/path/panda.mp4")

Now wait until the video has finished encoding (which could be several minutes depending on the size). You can check by doing:

video.reload.status
=> "success"

Your input video has been uploaded to s3

video.encodings['h264'].reload
video.encodings['h264'].encoding_progress
=> 100
video.encodings['h264'].status
"success"

Now you can get the URL of a re-encoded version:

video.encodings['h264'].url
=> "http://s3.amazonaws.com/S3_BUCKET/e40c1f68fbc1c4db46.mp4"

Open this URL in your browser, and you’ll see that Panda has received, re-encoded, and stored your video into your S3 bucket.

Everything’s working nicely. Time to integrate with your application!

Integrating with Rails

First we need to setup our models and controllers.

Create a Video model to save the ID of the video from Panda, we’ll call it panda_video_id:

rails g model Video title:string panda_video_id:string
rake db:migrate

Panda provides its own Panda::Video model, which we’ll wrap with our ActiveRecord model. Edit app/models/video.rb:

class Video < ActiveRecord::Base
  validates_presence_of :panda_video_id

  def panda_video
    @panda_video ||= Panda::Video.find(panda_video_id)
  end
end

Now you can access the wrapped object with myvideo.panda_video, or go directly to the encodings with myvideo.panda_video.encodings. This call requires a call to the Panda API, so we cache it with an instance variable to save time.

We’ll use a simple VideosController following the REST pattern. Create app/controllers/videos_controller.rb:

class VideosController < ApplicationController
  def show
    @video = Video.find(params[:id])
    @original_video = @video.panda_video
    @h264e = @original_video.encodings["h264"]
  end

  def new
    @video = Video.new
  end

  def create
    @video = Video.create!(params[:video])
    redirect_to :action => :show, :id => @video.id
  end
end

Next we need to create an upload form.

Create or edit your layout /app/views/layouts/application.html.erb, and include the uploader.

<script src="//cdn.pandastream.com/u/2.0/panda-uploader.min.js"></script>

Now create a view which will be the video upload form: /app/views/videos/new.html.erb.

<%= form_for @video do |f| %>

  <!-- field where the video ID will be stored after the upload -->
  <input type="hidden" name="panda_video_id"/>

  <label>Title</label>
  <input type="text" name='video[title]' placeholder="Give a title">

  <!-- optional upload progress bar -->
  <div class='progress'><span id="progress-bar" class='bar'></span></div>

  <!-- file selector -->
  <div id="browse">Choose file</div>

<% end %>

<script>
  panda.uploader.init({
    'buttonId': 'browse',
    'progressBarId': 'progress-bar',
    'onSuccess': function(file, data) {
      $("#panda_video_id").val(data.id)
    },
    'onComplete': function(){
      $("#new_video").submit();
    }
  });
</script>

Before you can upload a video you’ll need to authenticate and reserve an upload url for each file. The authentication process of your new upload occurs via an HTTP request to a configurable authentication url when the file is ready to be uploaded. The HTTP request is executed via AJAX POST request. The destination of the authentication request can be configured by setting the authorizeUrl.

The default route is /panda/authorize_upload

# app/controllers/panda_controller.rb
class PandaController < ApplicationController
  def authorize_upload
    payload = JSON.parse(params['payload'])
    upload = Panda.post('/videos/upload.json', {
      file_name: payload['filename'],
      file_size: payload['filesize'],
      profiles: "h264",
    })

    render :json => {:upload_url => upload['location']}
  end
end

Next we need to create a basic view to display the video once uploaded. For this we will simply use an HTML5 player.

Edit your show page in app/views/videos/show.html.erb:

<% if @h264e.status == "success" %>
  <video id="movie" width="<%= @h264e.width %>" height="<%= @h264e.height %>" preload="none"
    poster="<%= @h264e.screenshots.first %>" controls>
    <source src="<%= @h264e.url %>" type="video/mp4">
  </video>
<% else %>
   Your video is processing: <%= @h264e.encoding_progress.to_i %>%
<% end %>

Finally, update your routes in config/routes.rb.

match "/panda/authorize_upload", :to => "panda#authorize_upload"
resources :videos
root :to => "videos#new"

Your upload page is now ready. Once the video has been encoded, refresh the page and you’ll see the video!

You can view the code of the complete example on github.

Using WebHooks

Polling data from Panda is not very efficient but very simple to implement at first. A more efficient way to do it, is to use Panda webhooks and Async workers (In this example, we are going to use Sidekiq)

class PandaController < ApplicationController
  def notifications
    if params['event'] == 'video-encoded'
        video = Video.find_by_panda_video_id(video_id)
        if video
            UpdateVideoState.perform_async(video.id)
        else
            # might have been deleted
        end
    end
  end
end

class UpdateVideoState
  include Sidekiq::Worker

  def perform(id)
    video = Video.find(id)
    if video.panda_video.status == 'fail'
        # video failed to be uploaded to your bucket
    else
        h264e = video.panda_video.encodings['h264']
        if h264e.status == 'success'
            # save h264e.url in your models to avoid querying panda each time
        else # handle a failed job
            # h264e.error_class; h264e.error_message
            # a log file has been dumped in your bucket
        end
    end
  end
end