Overview:
In this tutorial, Let’s build a simple application to stream video files with Spring WebFlux Video Streaming. It is going to be a lot simpler than you think as Spring does all the heavy lifting for us. (You do not have to serve the static content as shown in this post. It is a simple fun project to demonstrate how Spring WebFlux works here.)
Video Streaming:
When you watch a movie via Netflix or a video tutorial via YouTube, the browser client does NOT download the whole video content. It gets some content first. While it is playing the content, it also gets next few chunks in a streaming fashion. So that we keep the user engaged with the video as soon as he clicks on some video, instead of having the user to wait for whole content to download.
Often times, the users will also try to watch the video somewhere in the middle by clicking/dragging the seek bar as shown above. In this case, there is no need in getting the video chunks from the beginning. Instead, the client will request the server to serve the video content from a specific byte index. The server will accept the byte range requests and serve the content accordingly.
This is what we are going to build 🙂
Sample Application:
Our application will serve video content. We will have a simple HTML UI. When the user clicks on the play button, the server will receive the request and play the content.
The user can adjust the seek bar to watch the video from any moment.
Project Setup:
- Create a Spring project with the following dependency.
- Under src/main/resources, I keep the video file and static html as shown here.
- The HTML is nothing significant than what I have here.
- When we click on the play button, it will send a request to the server to an end point (http://localhost:8080/video/tom-jerry)
<video src="video/tom-jerry" width="720px" height="480px" controls preload="none">
</video>
Spring WebFlux Video Steaming:
- As I had mentioned earlier, the service class is very simple. Just get the Resource object and return.
@Service
public class StreamingService {
private static final String FORMAT = "classpath:videos/%s.mp4";
@Autowired
private ResourceLoader resourceLoader;
public Mono<Resource> getVideo(String title) {
return Mono.fromSupplier(() -> this.resourceLoader.getResource(String.format(FORMAT, title)));
}
}
- The controller – we get the video title name here and load the corresponding video resource. In our case I have only one. But you get the point!
- Range request header contains the byte range request the client might send.
- I am not using the Range request header and just printing it on the console. But Spring uses it for us. We can see that in the demo.
- Do NOT forget to add the content type “video/mp4“.
@RestController
public class StreamingController {
@Autowired
private StreamingService service;
@GetMapping(value = "video/{title}", produces = "video/mp4")
public Mono<Resource> getVideo(@PathVariable String title, @RequestHeader("Range") String range) {
System.out.println(range);
return service.getVideo(title);
}
}
Thats it. It is very simple. Run the application.
Spring WebFlux Video Steaming – Demo:
Let’s first see how it works behind the scenes.
- The video player will send a request more or less as shown below. Try this curl request in a command line.
- We are requesting for specific byte range – here 0 to 500.
curl http://localhost:8080/video/tom-jerry -i -H "Range: bytes=0-500"
- We could see the output as shown below on the server side.
- It shows that it has received the byte range in the request header.
2021-08-22 16:02:08.845 INFO 25964 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080
2021-08-22 16:02:08.864 INFO 25964 --- [ main] c.v.w.WebfluxVideoStreamingApplication : Started WebfluxVideoStreamingApplication in 2.006 seconds (JVM running for 2.455)
bytes=0-500
- The curl response could be like this.
- Note HTTP – 206 status code as it is partial content.
- The total video length is 21589849 bytes.
- We have received 0-500 bytes which is 501 bytes.
- After the headers, the response in bytes which is NOT in the readable format. Lets not worry about it.
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: video/mp4
Content-Range: bytes 0-500/21589849
Content-Length: 501
ftypisomisomiso2avc1mp4freeG%�mdatQ�e��7�U5ŀ��h�S0Pjʃj-h<��*s����Rll����J��$�;ŗ*)��uw���9_� 5R180qu�+N@+{Ir�*��8?���́���9��˳��2��n��{�ּ5��(�VQ
�
�R�=��l�C�
��{Zϸ����d>T~pPj�P
�3�Y��p:�])`�xL�b}A92o�����c}Ǵ��j
���l�,�2�څ����!<~��G��$aPx:P������%����(8w����V�_��'0�V�q�����,v��(�X��E�Џ��v9$@?�
7��ww
�h�
{
2����"�2IɌ�x�LJ��"����V"���r�5�=��������+}�z�������⼸#
���^������!;����"�����p�%
- We can adjust the byte ranges and receive video responses accordingly.
- Now Let’s access the HTML page & click on the play button.
- We can see many requests (sequentially) to the server to get the video content.
- Check the server response with status code 206 saying that it is partial content.
- You can adjust the seek bar and we will see that a new request is sent for partial byte content.
Functional Endpoint:
Spring WebFlux also supports functional endpoint. Instead of RestController, we can also serve the content with functional endpoints as shown below.
@Configuration
public class FunctionalEndPointConfig {
@Autowired
private StreamingService service;
@Bean
public RouterFunction<ServerResponse> router(){
return RouterFunctions.route()
.GET("fun-ep/video/{title}", this::videoHandler)
.build();
}
private Mono<ServerResponse> videoHandler(ServerRequest serverRequest){
String title = serverRequest.pathVariable("title");
return ServerResponse.ok()
.contentType(MediaType.valueOf("video/mp4"))
.body(this.service.getVideo(title), Resource.class);
}
}
- In this case, the video tag src should be as shown below.
<video src="fun-ep/video/tom-jerry" width="720px" height="480px" controls preload="none">
</video>
Summary:
We were able to successfully demonstrate Spring WebFlux Video Streaming. We also learnt that we do not have do anything special to handle the HTTP Range requests as Spring takes care of that.
The source code is available here.
Learn more about Spring WebFlux.
Thank you for sharing this information with us. Really nice blog.
How to serve video to only authorized users for ex. sending jwt header?
Yes, you can add authentication always to any endpoint.
Am I the only one that doesn’t get multiple requests to fetch the video content? (206)
Please use the seeking bar to move to different time.
I am using it. The difference is that I did not use the ResourceLoader as my files are not under the resources and used FileInputStream. I found out that using the file path URI did the trick for me
resourceLoader.getResource(Path.of(absolute_file_path).toUri().toString())
Thanks for explaining the things smoothly.