Reputation: 56894
I am trying to allow my Java backend to "stream" video files (MP4, etc.) to browsers. I was worried that I would have to write really complicated, low-level-practically-NIO type code like:
public class VideoController extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
File f = new File("/opt/videos/video19394.mp4");
PrintStream ps = resp.getWriter();
while(still reading f) {
writeTheVideoBytesToStream(f, ps);
}
}
}
But it seems like this is all taken care of with the HTML5 <video/>
element (yes??). This way, from the client-side, I can just specify:
<video width="500" height="500" url="http://myapp.example.com/videos/19394" />
And then, on the server-side, even in something as simple as web.xml
, I can just specify the mapping between URL requests like http://myapp.example.com/videos/19394
and the MP4 file located on the server at /opt/videos/video19394.mp4
. And the <video/>
element just takes care of things automagically.
Am I correct here, or even if I used <video/>
, would I still need to implement low-level byte/socket streaming stuff on the server?
Upvotes: 5
Views: 11232
Reputation: 2479
As suggested by @saganas you would need to implement a controller that supports byte-range requests. However, Spring Content supports video streaming (i.e. byte-range support) out of the box and provides a very simple programming interface. Using Spring Content for the File System, for example, you can create a video streaming service by defining a single interface in your code. That's it.
Here's how. Create a new Spring Boot project via start.spring.io or via your IDE spring project wizard (Spring Boot 1.5.10 at time of writing). Add the following Spring Boot/Content dependencies so you end up with these:-
<dependencies>
<!-- Standard Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.3</version>
</dependency>
<!-- Spring Content -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-fs-boot-starter</artifactId>
<version>0.0.9</version>
</dependency>
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
In your Spring Boot Application class, create your VideoStore interface. This causes Spring Content to inject a java implementation (of this interface) as well as REST endpoints meaning you dont have to worry about writing these yourself:-
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@StoreRestResource(path="videos")
public interface VideoStore extends Store<String> {}
}
NB:- by default Spring Content will create a store under java.io.tmpdir so you will also need to set the SPRING_CONTENT_FS_FILESYSTEM_ROOT environment variable to point to the root of your "store".
Put your video into this "root" location. Start the application and your video(s) will be streamable from:-
/videos/ExampleVideo.mp4
Spring Content supports other "storage" too. Like S3 for example. So you don't have to use the filesystem. But it is well suited to getting up and running quickly. If you do want to use a different store it is just a matter of switching in the correct Spring Content dependency.
Upvotes: 3
Reputation: 565
You can use Spring Boot framework and just serve an InputStream from your REST endpoint. This will allow to scroll using video tag in HTML5 as well.
The code would look like something like this:
@RequestMapping(value = "videos/file-name", method = GET)
@ResponseBody
public final ResponseEntity<InputStreamResource>
retrieveResource(@PathVariable(value = "file-name")
final String fileName,
@RequestHeader(value = "Range", required = false)
String range) throws Exception {
long rangeStart = Longs.tryParse(range.replace("bytes=","").split("-")[0]);//parse range header, which is bytes=0-10000 or something like that
long rangeEnd = Longs.tryParse(range.replace("bytes=","").split("-")[0]);//parse range header, which is bytes=0-10000 or something like that
long contentLenght = ;//you must have it somewhere stored or read the full file size
InputStream inputStream = Resources.getResource(fileName).openStream();//or read from wherever your data is into stream
HttpHeaders headers = new HttpHeaders();
headers.setContentType("video/mp4");
headers.set("Accept-Ranges", "bytes");
headers.set("Expires", "0");
headers.set("Cache-Control", "no-cache, no-store");
headers.set("Connection", "keep-alive");
headers.set("Content-Transfer-Encoding", "binary");
headers.set("Content-Length", String.valueOf(rangeEnd - rangeStart));
//if start range assume that all content
if (rangeStart == 0) {
return new ResponseEntity<>(new InputStreamResource(inputStream), headers, OK);
} else {
headers.set("Content-Range", format("bytes %s-%s/%s", rangeStart, rangeEnd, contentLenght));
return new ResponseEntity<>(new InputStreamResource(inputStream), headers, PARTIAL_CONTENT);
}
}
Upvotes: 5