Video Streaming With Akka Streams
Streaming videos should not give you a headache. Proper tools like Akka Streams will increase your efficiency. Here you can learn more about streaming.
Join the DZone community and get the full member experience.
Join For FreeFirst of all, do not let the title deceive you — it will not be simple println(“Hello world”)
but with Actor System. Today, we will implement your first (sorry if I assumed wrongly) video streaming service. Namely, I will use Akka HTTP and Streams to create a REST API capable of streaming a video file, in mp4 format, in a way that matches the expectations of HTML5 <video> tag. Besides, I will also add a few words about Akka and some components like Akka Streams to give you some theoretical background before you start coding. But first, one question.
Before we start — a quick disclaimer
Here you can find only the most interesting code samples. The full source code is available in my GitHub repository.
Why Video Streaming?
There are three main reasons behind choosing video streaming as the topic for today’s article. The first one is that it is an absorbing and complex subject for me, especially on a large scale (like Netflix) and I have always wanted to learn more about it. The next reason is that it is a niche topic, so a project based around video streaming can be a great addition to anyone's portfolio - this one is especially important for people who want to start their journey with Scala and Akka. Last but not least it is very interesting way to get familiar with Akka Streams, which in fact makes this whole operation much easier.
After this brief introduction, we can move to the first of the main topics for today.
What Is Akka And Is It Tasty?
In general, it is an open-source toolkit whose aim is to make the creation of multi-threaded and distributed applications easy and also provide runtime for such applications. Akka-based systems tend to scale very, very well. Akka is based around Actor Model and actor-based concurrency and draws lots of inspiration from Erlang. It is written in Scala but provides DSLs for both Scala and Java.
When it comes to the tastiness of Akka, if you prefer eating your source code then for sure it is very tasty. If you want to learn more about Akka I recommend starting from reading this.
Here most of the source code will be based around HTTP and Streams features and there will be almost no features from the standard Akka Actors package.
When we cover the absolute basics of Akka we can dive deeper into today’s text.
What Is Akka Streams?
Akka Streams are simply a package built on top of normal Akka Actors to help us with the processing of infinite (or finite but very, very large) sequences of data. The main motivation behind the creation of Akka Streams was problems with the correct configuration of actors in the actor system to achieve a stable flow of streamed data.
An important fact is that Akka Streams have a built-in back-pressure mechanism. Thanks to that one of the most complex problems in the streaming world, configuring the producer to react correctly when the consumer cannot keep up with load, is taken care of by a tool you use and you do not have to care about it too much.
Additionally, Akka Streams provides an API that is compliant with interfaces required by Reactive Streams SPI. As a side note, it is worth noting that Akka itself is one of the founding members of the Reactive Stream initiative.
Akka Streams checked so now we can jump to the next part of the theory.
What Is Akka HTTP?
Similarly to Akka Streams, it is a package provided by Akka creators. It is built on top of Akka Streams and Akka Actors and it helps you to interact with outside worlds via HTTP. It provides both sides of HTTP stack, so you can build a REST API and you can use it as HTTP client for sending requests to some external service.
So now, when you have some basic understating of tools which we will use to implement our backend, the last thing left to describe is the most important part of our application’s frontend.
Details Of HTML5 <video> Tag
It is a new tag introduced in HTML 5 to replace adobe video player. As you may guess, its main responsibility is to embed media player, capable of playing videos, into an HTML document. The idea is very similar to plain old <img>
tag.
Inside <video>
tag you place <source>
tag with two important attributes. The first one is src attribute used to point out video which we want to display. The second important field is type, responsible for keeping our video format
You can also write some text inside <video> </video>
tags and it will be used as fallback in browsers that do not support the element. As for now, even Internet Explorer supports it so this scenario is almost impossible.
How Streaming With HTML <video> And FileIO Will Work
Although it sounds complex at the first glance, after some reading it becomes quite easy. Almost everything is ready and we only have to integrate different components.
On the backend side, most of the heavy lifting will be done by FileIO object, which will emit events chunked part of our file. Of course, the size of chunked elements is configurable. Moreover, the starting position is also configurable which will allow us to start playing our video from a certain point, not exactly from the beginning. All these, features are perfectly suited for <video>
tag performing HTTP GET request with Range header to play video without downloading it first.
Examples of requests made by HTML video:
For anyone interested, request header – Range:bytes=x- is responsible for picking the starting position of our video. The first request will be send at the beginning of processing video while the second one can be sent when you decide to move to a certain point on the video timeline.
With this lengthy introduction, it is finally time for some coding. Are you happy?
Let’s Write Streaming Service
In the next few paragraphs, I will implement the backend of our streaming service and then I will create a simple web page in HTML to verify if it works correctly.
Because I like doing assumptions, I assumed that anyone interested knows some basics of Scala and SBT.
1. We will add all necessary dependencies to our build.sbt file, for this project we will need only 3 packages: akka-http, akka-actor-typed (in theory, package akka-actor is enough but remember to always type safe), akka-stream.
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.14",
"com.typesafe.akka" %% "akka-stream" % "2.6.14",
"com.typesafe.akka" %% "akka-http" % "10.2.4"
)
2. Now we can create the main class responsible for starting our application. I choose to extend App — for me, it is more convenient than creating main method. In the next step, we will put there Actor System and HTTP server starters.
object Runner extends App {
}
3. After creating the main class we can add the code mentioned in previous step.
object Runner extends App {
val (host, port) = ("localhost", 8090)
implicit val system: ActorSystem[Nothing] = ActorSystem(Behaviors.empty, "akka-video-stream")
Http().newServerAt(host, port)
}
As for now, such configuration is enough. In the last step, we will add the call of bind method at the end to expose our REST API. The current configuration will run Actor System with name akka-video-stream and HTTP server on port 8090 of your local machine. Remember not to omit implicit keyword near Actor System definition, as such implicit parameter is required by signature of Http method.
4. Here we will finally implement REST API endpoint used to process requests from <video>
tag.
object Streamer {
val route: Route =
path("api" / "files" / "default") {
get {
optionalHeaderValueByName("Range") {
case None =>complete(StatusCodes.RangeNotSatisfiable)
case Some(range) => complete(HttpResponse(StatusCodes.OK))
}
}
}
}
As you can see, I have created an endpoint with URL "api/files/default". It checks if Range header is present in request. If our server is able to find it, the client will get a response with code 200 (OK), otherwise, we return a response with code 416 (Range Not Satisfiable).
5. The fifth step is perfect for implementing the method which is the main course of the whole article.
private def stream(rangeHeader: String): HttpResponse = {
val path = "path/to/file"
val file = new File(path)
val fileSize = file.length()
val range = rangeHeader.split("=")(1).split("-")
val start = range(0).toInt
val end = fileSize - 1
val headers = List(
RawHeader("Content-Range", s"bytes ${start}-${end}/${fileSize}"),
RawHeader("Accept-Ranges", s"bytes")
)
val fileSource: Source[ByteString, Future[IOResult]] = FileIO.fromPath(file.toPath, 1024, start)
val responseEntity = HttpEntity(MediaTypes.`video/mp4`, fileSource)
HttpResponse(StatusCodes.PartialContent, headers, responseEntity)
}
Here I have done a few things:
- I have loaded the file I want to stream to the application, then based on the header from quest and file info I have calculated starting point of streaming and Content Range response header.
- With help of FileIO I have created a stream from the previously loaded file. Then I used this stream as data in HttpEntity.
- I have created HttpResposne with code 206 (Partial content), headers and responseEntity as body.
I also want to describe FileIO in more details as it is the most magical thing in this article. So, what exactly happened in this line: FileIO.fromPath(file.toPath, 1024, start)
?
It created Source (Akka Streams counterpart of Producer from Reactive Streams) from the content of file under the provided path. Each element emitted by the source will have exactly 1 MB of size. The first emitted element will be in the position marked by the start parameter, so if you set 0 as the start parameter value, the first emitted element will be the first MB of file under the provided path.
6. As we have implemented the main magic of today, we have to refactor our app to be able to use it.
We will start from changes in REST API definition:
complete(HttpResponse(StatusCodes.Ok)) => complete(stream(range))
So now instead, of simply returning OK, we call our stream method with range as a parameter and start streaming.
We have to remember that our API is still not exposed so we have to do the following modification to the fragment responsible for starting HTTP server.
Http().newServerAt(host, port) => Http().newServerAt(host, port).bind(Streamer.route)
Et voila, now we have working backend and our REST API is accessible for anyone interested. Now we just have to test it.
Streaming Service Test
For the purpose of testing the application, we will create a simple HTTP page that will include almost solely <video>
tag. There is nothing here that is not explained in the paragraphs above so I simply put the ready HTML document below.
The final effect should look more or less like the one presented here. Of course, the same colors and sizes are not mandatory.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Akka streaming example</title>
</head>
<body style="background: black">
<div style="display: flex; justify-content: center">
<video width="1280" height="960" muted autoplay controls>
<source src="http://localhost:8090/api/files/default" type="video/mp4">
Update your browser to one from XXI century
</video>
</div>
</body>
</html>
There are only 5 notable things here:
- I have used
<source>
tag instead of using same attributes in<video>
- I have used
<source>
src attribute to link presented video with URL of application’s backend - I have used
<source>
type attribute to set mark media type of streamed file - I have added autoplay and muted attributes for video to start playing automatically
- I have added controls attribute to show embedded player controls
Do not care to much about <div>
it is there just for style.
To test if everything works correctly, we just have to run our backend and then open the HTML document from above in any modern browser. The result should be similar to the one presented in the following image.
Be Aware
The video will not start playing automatically until you add the muted and autoplay attributes to <video>
tag in HTML document. Till then you have to manually press the start button.
As in any normal project, after the implementation and tests, we have time to rethink our initial choices and perfect our code.
What Can Be Implemented Better?
In fact, if you do not want to stream multiple files, there are no big changes to make here. For sure we can move the file path to some config file but I am not able to come up with any more complex upgrades.
On the other hand, if you want to handle multiple files you can implement something akin to content store and make a separate service responsible for querying it and returning files. You can also change the endpoint to allow passing a filename, instead of hard coded string, to streaming service. Then you can use some kind of a resolver to translate passed names to exact video files.
Summary
Today I demonstrated that the implementation of a simple streaming service does not have to be as complex as it looks as long as you use the correct tools. Akka and a proper HTML tag can greatly reduce the necessary amount of work. Nevertheless, please keep in mind that this use case is very simple and for commercial projects, it may not be enough.
Anyway, I hope that you learned something new while reading this article or that I have managed to deepen your knowledge in some of the mentioned topics. Thank you for your time.
Opinions expressed by DZone contributors are their own.
Comments