Video Streaming with Akka
I recently worked on a project, where I had to implement a streaming service where users could skip ahead to the content they wanted and stop the stream whenever they wanted. I decided to apply the same principle to video streaming and here is how I implemented it with Akka http and Akka Streams, as well as manually sending chunks of data to the client.
It’s pretty evident that streaming data to a client is needed if you don’t want to deal with having to load so much data into memory. If I had a 3GB file that I need to send from my server to a client, it’s impractical to load the data into memory before sending, a better approach is to stream the data by reading smaller chunks of the file gradually and then sending to the client. Here is a very basic barebone example of me using Akka Streams to stream a 2GB video file to be downloaded by the client.
Nothing much to see here, I just got a reference to the movie file and easily streamed the file to the user with the Content-Disposition header which tells the browser that we want the movie to be downloaded as an attachment with the right filename.
We then use FileIO.fromPath
from Akka Streams to stream the movie to the client, using the application/octet-stream
Content-Type
. This is the basic principle of how I used to stream different kinds of data from databases, s3 to clients. This was my first time streaming with HTML and this kind of streaming just didn’t work.
<!DOCTYPE html>
<html>
<body>
<video width="320" height="240" controls>
<source src="http://localhost:9092/download" type="video/mp4">
Your browser does not support the video tag.
</video>
</body>
</html>
I then figured out that the streaming service I had previously implemented wasn’t compatible with what was required by the client with the html tag. I read this wikipedia entry and figured out that the video tag doesn’t work like regular streaming technologies or Flash, It actually performs chunking by making use of Http RANGE requests to start playing a video without actually having to download the whole video first. So I then had to implement that using akka-http.
Looking at the request header from the video tag, you’ll see the header RANGE:bytes=0-
, this is saying, give me content starting from the first or zero position. Subsequent requests will be made to the src
url with different positions to indicate what part of the stream that is of interest to the front end, another example of such subsequent request is shown below.
Now to handle these requests, I took these basic steps
- Read the Range header to fetch how much data is needed by the video tag
- Stream the requested chunk of data back to the client along with the right headers.
Yeah, it’s really just this simple, although some other things are involved in the process. The first thing is what kind of response we need to send back to the client, here are the response headers that were needed to be sent in my case.
Content-Range: bytes ${start}-${end}/${fileSize}
Accept-Ranges: bytes,
Content-Length: chunksize, //usually (end - start)
Content-Type: video/mp4,
These headers are pretty self explanatory, we send the Content-Range
, so the video tag doesn’t ask for more bytes than expected or exceed the range, start
is where the client asked us to read from, end
is the maximum limit of data we can send and fileSize
corresponds to the overall length of the file. We also send the Content-Length
as well as the Content-Type
which is video/mp4
in my case. It’s also important to note that the Http Status Code should be 216
, for partial content. Yeah enough talk, let’s see some code, here is how I did this in akka-http
What I did here is pretty simple, I extract the Range header, get the start
as well as end
position, using the same streaming technique as provided by Akka Streams, but here instead of attempting to stream the whole file everytime, I just jump to the point where I want to start the stream from as supplied by the third parameter to FileIO.fromPath, and responded with the right headers. You may ask, where’s the Content-Length
,Content-Type
header as well as the 216
status code, all that is handled by the HttpResponse with a status code of PartialContent, Content-Type
is supplied by the MediaTypes.`video/mp4`
and whatever amount of data that was read by the stream is supplied as the Content-Length
, here is an example of a request-response cycle for one of the video tag requests
With this, it’s really so easy to stream my video, and when the user skips to a particular point in the video, the video tag just fetches data starting from the point the user asks for.
Please note that in this case where the video tag was used, it’s important that the response status code is
216
, I tried to use200
, but it only kept on restarting the stream from the beginning whenever I wanted to skip ahead or behind
The only issue I had with this method was that I wasn’t in control of how much chunk of data that was sent to the client, what If I wanted to apply some sort of compression or apply adaptive bitrate streaming or transcoding to each chunk of data?. I could do it with akka streams, but here is a simpler alternative solution I came up with to read a fixed length of only certain parts of a file.
In this method, I try to read only 4MB of data at a time, and I make use of a RandomAccessFile to skip to the position we want to start reading from.
With this method, the readChunk
variable actually references the byte Array that contains your data, and you can then perform whatever task you intended to perform on it or forward the data to another machine for some sort of processing.
The only downside to this is that your browser tends to make more requests than the previous example as each request is bound to return a maximum of 4MB as shown below.
This post is definitely not exhaustive as there are other nuances involved in streaming data, but this was able to suffice in my case.
If you know a better way of doing this, feel free to add a comment.
For more reading, here is an article on streaming with youtube