Deep-dive on the Next Gen Platform. Join the Webinar!

Skip Navigation
Show nav
Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
    • .NET
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
Hide categories

Categories

  • Heroku Architecture
    • Compute (Dynos)
      • Dyno Management
      • Dyno Concepts
      • Dyno Behavior
      • Dyno Reference
      • Dyno Troubleshooting
    • Stacks (operating system images)
    • Networking & DNS
    • Platform Policies
    • Platform Principles
  • Developer Tools
    • Command Line
    • Heroku VS Code Extension
  • Deployment
    • Deploying with Git
    • Deploying with Docker
    • Deployment Integrations
  • Continuous Delivery & Integration (Heroku Flow)
    • Continuous Integration
  • Language Support
    • Node.js
      • Working with Node.js
      • Troubleshooting Node.js Apps
      • Node.js Behavior in Heroku
    • Ruby
      • Rails Support
      • Working with Bundler
      • Working with Ruby
      • Ruby Behavior in Heroku
      • Troubleshooting Ruby Apps
    • Python
      • Working with Python
      • Background Jobs in Python
      • Python Behavior in Heroku
      • Working with Django
    • Java
      • Java Behavior in Heroku
      • Working with Java
      • Working with Maven
      • Working with Spring Boot
      • Troubleshooting Java Apps
    • PHP
      • PHP Behavior in Heroku
      • Working with PHP
    • Go
      • Go Dependency Management
    • Scala
    • Clojure
    • .NET
      • Working with .NET
  • Databases & Data Management
    • Heroku Postgres
      • Postgres Basics
      • Postgres Getting Started
      • Postgres Performance
      • Postgres Data Transfer & Preservation
      • Postgres Availability
      • Postgres Special Topics
      • Migrating to Heroku Postgres
    • Heroku Key-Value Store
    • Apache Kafka on Heroku
    • Other Data Stores
  • AI
    • Working with AI
  • Monitoring & Metrics
    • Logging
  • App Performance
  • Add-ons
    • All Add-ons
  • Collaboration
  • Security
    • App Security
    • Identities & Authentication
      • Single Sign-on (SSO)
    • Private Spaces
      • Infrastructure Networking
    • Compliance
  • Heroku Enterprise
    • Enterprise Accounts
    • Enterprise Teams
    • Heroku Connect (Salesforce sync)
      • Heroku Connect Administration
      • Heroku Connect Reference
      • Heroku Connect Troubleshooting
  • Patterns & Best Practices
  • Extending Heroku
    • Platform API
    • App Webhooks
    • Heroku Labs
    • Building Add-ons
      • Add-on Development Tasks
      • Add-on APIs
      • Add-on Guidelines & Requirements
    • Building CLI Plugins
    • Developing Buildpacks
    • Dev Center
  • Accounts & Billing
  • Troubleshooting & Support
  • Integrating with Salesforce
  • Add-ons
  • All Add-ons
  • Telestream Cloud
Telestream Cloud

This add-on is operated by Telestream LLC

Video encoding service with a pretty, yet powerful API

Telestream Cloud

Last updated July 27, 2023

This article is a work in progress, or documents a feature that is not yet released to all users. This article is unlisted. Only those with the link can access it.

Table of Contents

  • Prerequisites
  • Add the Heroku add-on
  • Install the gem
  • Configure Panda
  • Set up locally
  • Test your setup
  • Integrating with Rails
  • Using WebHooks

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:create 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="video[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

Keep reading

  • All Add-ons

Feedback

Log in to submit feedback.

Zara 4 Temporize Scheduler

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure
  • .NET

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing
  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Github
  • LinkedIn
  • © 2025 Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
  • heroku.com
  • Legal
  • Terms of Service
  • Privacy Information
  • Responsible Disclosure
  • Trust
  • Contact
  • Cookie Preferences
  • Your Privacy Choices