FLV Scrubbing with throttling
Posted on Apr 30, 2010 by
Paul WhiteIn the old days of the internet if you wanted to provide videos on your website, you would use embed tags, or play an FLV via a
flash SWF. But the problem with this the huge amount of wasted transfer that results. So I set out to develop a customized handler using
asp.net that would allow FLV scrubbing, and also allow throttling to save on bandwidth.
What is FLV Scrubbing?
Those of you who have been to youtube of any modern porn site have noticed how you can drag the slider and jump to later cue points in the movie without having to load the entire movie. This is beneficial in two ways. It allows people to skip the parts of the video they don't want to watch and get right to the climax of the video. For
Websites it allows them to save a few KB to a few MB of transfer since they don't have to send over the boring parts of the video. So it enhances the user experience as well as saves money on the
hosting side. Its a win win, but providing this kind of interface is no easy task.
What is FLV throttling?
Throttling is the act of artificially limiting the rate of transfer. In the case of FLV videos, they would normally be downloaded by the client as fast as the network and server's hardware would allow. This might be at a full 100M/bit if the client is on a fast enough connection. But lets say the client was going a watch a video that was 5 minutes long, and 100 MB in size. They get through the first 20 seconds and realize they don't like the video so they browse to something else. However the 100 MB video has already downloaded. that was probably about 90 MB of transfer wasted. Instead of letting the client download the entire video as fast as they could what if you throttled the rate of transfer to the actual rate of demand. There is no reason you should have to feed data to the client faster than they need it. So if your video plays at 100KB/sec there is no reason to transfer the data faster then that. Of course there is no reason you should force people to wait for the initial buffering time. So we will give the client full transfer speed as a buffering turbo, then throttle their transfer rate after they have a good 2-3 second buffer.
FLV scrubbing and throttling with asp.net
There are a few issues we need to overcome before we can effectively do this. The first issue is FLV files by default do not have cue points. To create the cue points we need to modify them with a flvtool. FLVTOOL is an dos executable that will automatically modify the FLV with the proper cue points, so we can scrub the video. The other issue is in order to effectively throttle the data rate we need to know what they minimum rate of transfer is. In
ASP.NET throttling the transfer rate is nothing more than using a System Pause for X miliseconds between sending over each buffer of data. But this presents even more problems. Not every client is going to transfer at the same rate. So your code is going to have to be smart and determine what the optimal pause time is. Second we need to create a non throttled buffer to ensure people wait as little as possible for the data to buffer up and the video to start playing.
Working Example
Here is my FLVStreaming.cs file.
I just dump this into my App_Code directory, makes some edits to my web.config and it automatically will intercept all requests for FLV files. Whenever a Request for an FLV is handled by default it will be throttled to 150 KB /sec, after a 3 second full speed buffer. However if a duration variable is passed in the QueryString of the FLV request, it will use that to determine what the best transfer rate is, and throttle it to that. If you want to use the Scrubbing feature you will need to impliment a SWF FLV video player that supports it. I have links at the bottom of the page to a few other sites that include a SWF example.
using System;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Web;
using System.Net;
using System.Data;
using System.Data.Odbc;
public class FLVStreaming : IHttpHandler
private static readonly byte _flvheader = HexToByte("464C5601010000000900000009"); //"FLVx1x1000x9000x9"
public FLVStreaming()
public void ProcessRequest(HttpContext context)
try
int pos;
int length;
// Check start parameter if present
string filename = Path.GetFileName(context.Request.FilePath);
decimal duration = 0;
if(context.Request.Params"duration"!=null && context.Request.Params"duration"!="")
duration=Convert.ToDecimal(context.Request.Params"duration");
decimal datalength=0;
using (FileStream fs = new FileStream(context.Server.MapPath(filename), FileMode.Open, FileAccess.Read, FileShare.Read))
string qs = context.Request.Params"start";
if (string.IsNullOrEmpty(qs))
pos = 0;
length = Convert.ToInt32(fs.Length);
datalength= Convert.ToDecimal(fs.Length);
else
pos = Convert.ToInt32(qs);
length = Convert.ToInt32(fs.Length - pos) + _flvheader.Length;
datalength= Convert.ToDecimal(fs.Length);
// Add HTTP header stuff: cache, content type and length
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetLastModified(DateTime.Now);
context.Response.AppendHeader("Content-Type", "video/x-flv");
context.Response.AppendHeader("duration", Convert.ToString(duration));
context.Response.AppendHeader("Content-Length", length.ToString());
// Append FLV header when sending partial file
if (pos > 0)
context.Response.OutputStream.Write(_flvheader, 0, _flvheader.Length);
fs.Position = pos;
// This much data will be sent over at a time
const int buffersize = 16384; // 16KB buffer
// Calculate the transfer rate required for continous play
decimal maxTransferRate = 153600; // 150KB/ sec
if(duration!=null && duration!=0)
// if duration was provided then we can calculate this video's required maxTransferRate
maxTransferRate=(datalength/duration);
// how many buffers need to be flushed / second
decimal buffersPerSecond = maxTransferRate/buffersize;
// how much time each buffer needs to take
decimal delayPerBuffer= (1/buffersPerSecond)1000;
// how many initial seconds of video should load at full speed
decimal secondsToBuffer=3;
// how many initial cycles will be at full speed
int speedboost=Convert.ToInt32((maxTransferRatesecondsToBuffer)/buffersize);
decimal sleepTime = 0;
decimal currentTransferRate=0;
byte buffer = new bytebuffersize;
int count = fs.Read(buffer, 0, buffersize);
while (count > 0)
if (context.Response.IsClientConnected)
Stopwatch stopWatch = new Stopwatch();
// start stop watch
stopWatch.Start();
// transfer data
context.Response.OutputStream.Write(buffer, 0, count);
context.Response.Flush();
count = fs.Read(buffer, 0, buffersize);
// stop stop watch
stopWatch.Stop();
//calculate transfer rate
TimeSpan ts = stopWatch.Elapsed;
if(speedboost<0)
// determine how long we need to sleep the thread to throttle down the bandwidth
sleepTime=delayPerBuffer-Convert.ToDecimal(1000ts.TotalSeconds);
// sleep the thread
Thread.Sleep(Convert.ToInt32(sleepTime));
else
speedboost--;
else
count = -1;
catch (Exception ex)
System.Diagnostics.Debug.WriteLine(ex.ToString());
public bool IsReusable
get return true;
private static byte HexToByte(string hexString)
byte returnBytes = new bytehexString.Length / 2;
for (int i = 0; i < returnBytes.Length; i++)
returnBytesi = Convert.ToByte(hexString.Substring(i 2, 2), 16);
return returnBytes;
public string ByteArrayToString(byte bytes)
string byteString = string.Empty;
foreach (byte b in bytes)
byteString += Convert.ToChar(b).ToString();
return byteString;
public double GetNextDouble(FileStream fileStream, int offset, int length)
// move the desired number of places in the array
fileStream.Seek(offset, SeekOrigin.Current);
// create byte array
byte bytes = new bytelength;
// read bytes
int result = fileStream.Read(bytes, 0, length);
// convert to double (all flass values are written in reverse order)
return ByteArrayToDouble(bytes, true);
public double ByteArrayToDouble(byte bytes, bool readInReverse)
if (bytes.Length != 8)
throw new Exception("bytes must be exactly 8 in Length");
if (readInReverse)
Array.Reverse(bytes);
return BitConverter.ToDouble(bytes, 0);
You will also need to make sure you add the following into your web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<defaultDocument>
<files>
<remove value="Default.htm" />
</files>
</defaultDocument>
<handlers>
<add name="FLVStreaming" path=".flv" verb="" type="FLVStreaming" resourceType="Unspecified" preCondition="integratedMode" />
</handlers>
</system.webServer>
</configuration>
Also not to forget the many sites that had the initial idea, I give them full credit for the scrubbing examples, the addition of the throttling was my own idea.
http://blogs.ugidotnet.org/kfra/archive/2006/10/04/50003.aspxhttp://www.topfstedt.de/weblog/?page_id=20812969 Visitors
12969 Views
Hello, nice post!