Asynchronously streaming video with ASP.NET Web API

ยท 841 words ยท 4 minutes to read

A lot of people think that ASP.NET Web API is basically a fancy framework for building APIs - which couldn’t be further from the truth. Web API is mainly about the new .NET HTTP programming model it brings to the table - embracing HTTP to perform a whole magnitude of web related tasks; and APIs are just a small portion of that. Hopefully if you follow this blog, you have seen examples of that already, as we often wander in the unkown areas of ASP.NET Web API, beyond building “traditional” APIs.

Today, let’s go back to the HTTP programming model and use Web API to asynchronously stream videos.

More after the jump.

The concept of asynchronous streaming ๐Ÿ”—

To perform the async streaming task, we will revert to the class we already previously used on this blog - and that is PushStreamContent. It allows the developer to progressively push packets of data down to the receiving client. Previously, we used PushStreamContent and JavaScript’s Server Sent Events to create an HTML5 chat.

In our new scenario, we will read the video stream from the file on the server’s hard drive, and flush it down to the client (using PushStreamContent) in the packets of 65 536 bytes. The streaming video playback could then start immediately (client doesn’t have to wait for the entire video to be flushed down), without causing unnecessary load on our server, especially as the process of writing to the client happens asynchronously. Once the client disconnects, the writing stops.

Implementation ๐Ÿ”—

PushStreamContent takes an Action in its constructor:

public PushStreamContent(Action<Stream, HttpContent, TransportContext> onStreamAvailable);  

This Action is called as soon as the output stream (HTTP content to be flushed to the client) becomes available.

Therefore, we could create a small helper class, which would read the from the disk, and expose this activity for PushStreamContent to call repeatedly.

public class VideoStream  
{  
private readonly string _filename;

public VideoStream(string filename, string ext)  
{  
_filename = @"C:UsersFilipDownloads" + filename + "."+ext;  
}

public async void WriteToStream(Stream outputStream, HttpContent content, TransportContext context)  
{  
try  
{  
var buffer = new byte[65536];

using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read))  
{  
var length = (int)video.Length;  
var bytesRead = 1;

while (length > 0 && bytesRead > 0)  
{  
bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));  
await outputStream.WriteAsync(buffer, 0, bytesRead);  
length -= bytesRead;  
}  
}  
}  
catch (HttpException ex)  
{  
return;  
}  
finally  
{  
outputStream.Close();  
}  
}  
}  

This class is obviously little simplified, for the sake of demo purposes, but hopefully you get the idea. We allow the consumer of the class to give as information about the file (for which we arbitrarily look in a specific location, the Downloads folder in this case). In the WriteToStream method, we proceed to read the file progressively and flush these bits to the output stream.

Notice that the signature of this method matches the Action expected by PushStreamContent and as a result can be used by it.

Now let’s move to the controller action, which, by now, will be very simple (all the heavy lifting happens in the above VideoStream class).

public class VideosController : ApiController  
{  
public HttpResponseMessage Get(string filename, string ext)  
{  
var video = new VideoStream(filename, ext);

var response = Request.CreateResponse();  
response.Content = new PushStreamContent(video.WriteToStream, new MediaTypeHeaderValue("video/"+ext));

return response;  
}  
}  

We allow the client to pass video info (name, extension), construct the instance of VideoStream and then create and instance of PushStreamContent which gets returned to the client.

All we need now is just a route registration:

config.Routes.MapHttpRoute(  
name: "DefaultVideo",  
routeTemplate: "api/{controller}/{ext}/{filename}"  
);  

Consuming the asynchronous video stream ๐Ÿ”—

We can now make an HTML5 web page, with the video tag.

  
  
<video width="480" height="320" controls="controls" autoplay="autoplay"><source src="/api/videos/webm/CkY96QuiteBitterBeings" type="video/webm"></video>  
  
  

In this case, I will be requesting this video: C:UsersFilipDownloadsCkY96QuiteBitterBeings.webm.

If we run this in the browser that supports WebM, we could see that the video (especially if you open the network inspection console) is streamed rather than loaded at once. To better illustrate this, I have recorded a short video which shows the application in action. I’ve put a breakpoint inside WriteToStream method, to show that subsequent packets of video get sent down *after* the playback has already started; the client can commence the playback already after the first 64kB packet. Also, as soon as the breakpoint hits, nothing more gets sent to the client, yet the browser still continues the playback of what it has already received.

Video below (choose 480p when playing for better quality):

Summary ๐Ÿ”—

While there are arguably many better solution for video streaming (media protocols, media servers and so on), in my opinion, this type of functionality is a pretty nifty example of how flexible Web API can be in terms of working with HTTP programming - and how many different things it can do.

On a side, PushStreamContent itself is a very interesting class, and if used correctly, can be a very powerful weapon in the arsenal of Web API developer. A very interesting article about a similar topic (async with PushStreamContent) can be found here, by Andrรฉs Vettor. Really worth a read!

About


Hi! I'm Filip W., a cloud architect from Zรผrich ๐Ÿ‡จ๐Ÿ‡ญ. I like Toronto Maple Leafs ๐Ÿ‡จ๐Ÿ‡ฆ, Rancid and quantum computing. Oh, and I love the Lowlands ๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP