This is a blog of AmberBit - a Ruby on Rails web development company. Hire us for your project!

Ruby FLV pseudostreaming implemented with Sinatra and Rack::Evil

There are many ways you could implement FLV video streaming. The most “proper” way to do it is to use RTMP (Flash Media) Server, which you can purchase directly from Adobe. A few cheaper/free/open source alternatives exist. For me, most promissing one is Mammoth, but it’s still in early stage of development. However, most popular alternative is Red5, however I didn’t find it either easy to configure or being reliable at serving files. Some big guys are using it, but it takes time and experience to set it up and maintain properly.

Here comes pseudostreaming

FLV pseudostreaming is a technique that allows you to simulate FLV streaming, without use of RTMP protocol. Most popular player that supports pseudostreaming if Flowplayer, and they also give you nice overview of this method on their site.

Implementations for pseudostreaming exist for Ngnix, Lighttpd, Apache or PHP, and it’s probably best if you use ones written in C (fastest). However, when you can’t use Lighttpd or need to integrate FLV streaming directly into your Ruby apps, you can use mine solution.

How it works?

Pseudostreaming of FLV files is possible when you insert special frame index into file header. Most popular tool to do it is flvtool2, and yes – it’s written in Ruby and available as ruby gem!

$ gem install flvtool2

To append FLV data to your movie file, use command:

$ flvtool2 -U video_file.flv

When player starts to buffer video file via standard HTTP protocol, first thing send out is metadata that sits in file header, and pseudostreaming is possible.

But applications that serve FLV files with pseudostreaming support, must respond to specified URI scheme, that accepts “start=XXX” parameter to make it work. XXX is just an offset in bytes from beginning of file, so our application must seek to given byte and start sending output from there. It is implemented almost identical in PHP, or C (in both Ngnix and Lighttpd), and we are going to do it in Ruby.

For example, if we seek to the middle of video, Flowplayer calculates that position should be, say 2343443 bytes from start, and sends request to: http://myserver.com/myvideofile.flv?start=2343443. We need to output file from this position. Dead easy.

Not quite dead easy

There is one problem with Ruby applications that use Rack (Rails included) – how do we actually stream a file? We could use send_file or send_data methods from within Rails, but Rack architecture forces our file to be buffered in memory, which is something we don’t want to do for 1.5GB movies.

To work this around, we will use Rack::Evil module that allows us to throw and catch custom object that will be processed as HTTP response.

First, let’s get Rack::Evil, which is part of rack-contrib:

$ gem install rack-contrib `</pre>

In your config.ru file or config/environment.rb you need to enable Rack::Evil:

require 'rack/contrib'
... 
use Rack::Evil 
...

Now, in your application, in place where you need to output a file, you must construct object that responds to “each” method. You will throw this object and Rack::Evil will catch it, and call “each” on it. Each content that yields from this method, will be sent directly to user. If we implement actual file reading inside “each” method – we avoid loading whole file to memory.

 ...
 def each
 if @start_pos &gt; 0
 yield "FLV\x01\x01\x00\x00\x00\x09\x00\x00\x00\x09" # If we are not starting from beggining
 # we must prepend FLV header to output 
 @start_pos = 0 
 end 

 begin (chunk = @file.read(4*1024)) # Go and experiment with best buffer size for you 
 yield chunk 
 end while chunk.size == 4*1024 
 end 
... 

Easy! For more information look into application.rb and config.ru files! Have fun!

Using this example

First clone repository from Github:

$ git clone git://github.com/hubertlepicki/sinatra-flv-streaming-example.git sinatra_flv_streaming_example
$ cd sinatra_flv_streaming_example

Now, get dependencies and some Rack-compatible server (I’ll use Thin).

$ gem install rack rack-contrib thin

Start the server and visit demo page:

$ thin -C config.yml -p 4567 start 

Demo page is located on http://localhost:4567/index.html. Please note that port 4567 is hardcoded in index.html file soyou must use it or change index.html file.

Notes

This is just an example how to use it, you shouldn’t use this code in production but develop your own solution based on this ideas.

You must not use Rack modules like mine responseassembler that iterates on response object and process it to one string – this way you’ll end up with loading all video file to memory at once.

Demonstration on how to set up Flowplayer to use pseudostreaming is in file index.html.

Flowplayer binaries distributed with this source code are licensed under GPLv3, and you should read their licensing policy before using it on your site read more.

by Hubert Łępicki, twitter: @hubertlepicki

Do you need skilled professionals to help you build Rails applications? Hire us for your project!
comments powered by Disqus

Want to get in touch? Drop us a line!