SpringReactive SpringVideo Streaming using Spring Webflux

Video Streaming using Spring Webflux

In this tutorial, I’ll like to show you how to use Spring WebFlux to accomplish video streaming. Spring will do all of the heavy work for us, so it will be a lot easier than you think.

The static material presented in this post is not required to be served. It’s just a fun little experiment to show how Spring WebFlux works.

Video Streaming

When you watch a movie via Amazon Prime or a video tutorial via YouTube or Udemy, the browser client does NOT download the complete video content. It initially receives some material. It obtains the next few pieces in a streaming way while playing the material. So that the consumer is engaged with the video as soon as he clicks on it, rather than having to wait for the entire material to download.

Users will frequently try to watch the movie in the middle by clicking and moving the seek bar, as illustrated above. In this situation, obtaining the video chunks from the beginning is unnecessary. Instead, the client will request that the video material be served from a specified byte index. The byte-range requests will be accepted by the server, and the content will be served accordingly.

Sample Application:

We’ll create a basic application to offer video material. We’ll use an HTML user interface. The server will receive the request and play the content when the user hits the play button.

Project Setup

Create a Spring project with the dependencies listed below.

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 press the play button, the server sends a request to an endpoint (http://localhost:8080/video/Dexter_Laboratory) that we specify.

<div class="container mt-5">
    <h2>Spring WebFlux</h2>
    <video src="video/Dexter_Laboratory" width="720px" height="480px" controls preload="none">

    </video>
</div>

Spring WebFlux Video Steaming:

As I previously stated, the service class is very basic. Get the Resource object and then return it.

package com.onurdesk.webfluxvideostreaming.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@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)));
    }

}

Thats it. It is very simple. Run the application.

Spring WebFlux Video Steaming – Demo:

Let’s have a look at how it works behind the scenes first.

  • The video player will submit a request that looks something like this. Use the command line to execute this curl request.
    • 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.

We can adjust the byte ranges and receive video responses accordingly.

  1. We can see many requests (sequentially) to the server to get the video content.
  2. Check the server response with status code 206 saying that it is partial content.
  3. You can adjust the seek bar and we will see that a new request is sent for partial byte content.

Functional Endpoint:

Functional endpoints are likewise supported by Spring WebFlux. Instead of using RestController, we can use functional endpoints to serve the content, as demonstrated 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="video/**" width="720px" height="480px" controls preload="none">

</video>

Summary

With Spring WebFlux, we were able to successfully demonstrate video streaming. We also discovered that we don’t need to do anything specific to handle HTTP Range requests because Spring does it for us.

1 COMMENT

  1. “we don’t need to do anything specific to handle HTTP Range requests because Spring does it for us.”

    How exactly is it supposed to work on the server side? The getVideo() method has no idea about the requested Range header and thus returns full video. I’m migrating to webflux and trying to solve exactly the same problem – streaming range of bytes from a file.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe Today

GET EXCLUSIVE FULL ACCESS TO PREMIUM CONTENT

[tds_leads input_placeholder="Your email address" btn_horiz_align="content-horiz-center" pp_msg="SSd2ZSUyMHJlYWQlMjBhbmQlMjBhY2NlcHQlMjB0aGUlMjAlM0NhJTIwaHJlZiUzRCUyMiUyMyUyMiUzRVByaXZhY3klMjBQb2xpY3klM0MlMkZhJTNFLg==" pp_checkbox="yes" tdc_css="eyJhbGwiOnsibWFyZ2luLXRvcCI6IjMwIiwibWFyZ2luLWJvdHRvbSI6IjMwIiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tdG9wIjoiMjAiLCJtYXJnaW4tYm90dG9tIjoiMjAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" display="column" gap="eyJhbGwiOiIyMCIsInBvcnRyYWl0IjoiMTAifQ==" f_msg_font_family="702" f_input_font_family="702" f_btn_font_family="702" f_pp_font_family="789" f_pp_font_size="eyJhbGwiOiIxNCIsInBvcnRyYWl0IjoiMTIifQ==" f_btn_font_spacing="1" f_btn_font_weight="600" f_btn_font_size="eyJhbGwiOiIxNiIsImxhbmRzY2FwZSI6IjE0IiwicG9ydHJhaXQiOiIxMyJ9" f_btn_font_transform="uppercase" btn_text="Subscribe Today" btn_bg="#000000" btn_padd="eyJhbGwiOiIxOCIsImxhbmRzY2FwZSI6IjE0IiwicG9ydHJhaXQiOiIxNCJ9" input_padd="eyJhbGwiOiIxNSIsImxhbmRzY2FwZSI6IjEyIiwicG9ydHJhaXQiOiIxMCJ9" pp_check_color_a="#000000" f_pp_font_weight="500" pp_check_square="#000000" msg_composer="" pp_check_color="rgba(0,0,0,0.56)"]

Get unlimited access to our EXCLUSIVE Content and our archive of subscriber stories.

Exclusive content

Latest article

More article