Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I return a video with Spring MVC so that it can be navigated using the html5 <video> tag?

If I have a file in the web server (Tomcat) and create a tag, I can watch the video, pause it, navigate through it, and restart it after it finishes.

But if I create a REST interface that sends the video file when requested, and add its URL to a tag, I can only play and pause. No rewinding, no fast forward, no navigating, nothing.

So, is there a way for this to be fixed? Am I missing something somewhere?

Video files are in the same server as the REST interface, and the REST interface only checks session and sends the video after finding out which one it should send.

These are the methods I've tried so far. They all work, but none of them allow navigating.

Method 1, ResponseEntity:

/*
 * This will actually load the whole video file in a byte array in memory,
 * so it's not recommended.
 */
@RequestMapping(value = "/{id}/preview", method = RequestMethod.GET)
@ResponseBody public ResponseEntity<byte[]> getPreview1(@PathVariable("id") String id, HttpServletResponse response) {
    ResponseEntity<byte[]> result = null;
    try {
        String path = repositoryService.findVideoLocationById(id);
        Path path = Paths.get(pathString);
        byte[] image = Files.readAllBytes(path);

        response.setStatus(HttpStatus.OK.value());
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentLength(image.length);
        result = new ResponseEntity<byte[]>(image, headers, HttpStatus.OK);
    } catch (java.nio.file.NoSuchFileException e) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
    } catch (Exception e) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    }
    return result;
}

Method 2, Stream copy:

/*
 * IOUtils is available in Apache commons io
 */
@RequestMapping(value = "/{id}/preview2", method = RequestMethod.GET)
@ResponseBody public void getPreview2(@PathVariable("id") String id, HttpServletResponse response) {
    try {
        String path = repositoryService.findVideoLocationById(id);
        File file = new File(path)
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader("Content-Disposition", "attachment; filename="+file.getName().replace(" ", "_"));
        InputStream iStream = new FileInputStream(file);
        IOUtils.copy(iStream, response.getOutputStream());
        response.flushBuffer();
    } catch (java.nio.file.NoSuchFileException e) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
    } catch (Exception e) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    }
}

Method 3, FileSystemResource:

@RequestMapping(value = "/{id}/preview3", method = RequestMethod.GET)
@ResponseBody public FileSystemResource getPreview3(@PathVariable("id") String id, HttpServletResponse response) {
    String path = repositoryService.findVideoLocationById(id);
    return new FileSystemResource(path);
}
like image 427
Calabacin Avatar asked Dec 17 '13 12:12

Calabacin


People also ask

Why video is not playing in html?

If you encountered “HTML5 video not found” error while playing a video on any website then it implies your browser doesn't support the HTML5 format codecs or your browser doesn't have the proper video codec installed.

Which object is returned from controller in spring?

Instead, your @Controllers directly return Java objects, which Spring MVC will conveniently serialize to JSON/XML or any other format that the user requested with the help of HttpMessageConverters.

Can we return view in rest controller?

@RestController is not meant to be used to return views to be resolved. It is supposed to return data which will be written to the body of the response, hence the inclusion of @ResponseBody .


2 Answers

A simple solution for handling non-static resources:

@SpringBootApplication
public class DemoApplication {

    private final static File MP4_FILE = new File("/home/ego/bbb_sunflower_1080p_60fps_normal.mp4");

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Controller
    final static class MyController {

        @Autowired
        private MyResourceHttpRequestHandler handler;

        // supports byte-range requests
        @GetMapping("/")
        public void home(
                HttpServletRequest request,
                HttpServletResponse response
        ) throws ServletException, IOException {

            request.setAttribute(MyResourceHttpRequestHandler.ATTR_FILE, MP4_FILE);
            handler.handleRequest(request, response);
        }

        // does not support byte-range requests
        @GetMapping(path = "/plain", produces = "video/mp4")
        public FileSystemResource plain() {

            return new FileSystemResource(MP4_FILE);
        }
    }

    @Component
    final static class MyResourceHttpRequestHandler extends ResourceHttpRequestHandler {

        private final static String ATTR_FILE = MyResourceHttpRequestHandler.class.getName() + ".file";

        @Override
        protected Resource getResource(HttpServletRequest request) throws IOException {

            final File file = (File) request.getAttribute(ATTR_FILE);
            return new FileSystemResource(file);
        }
    }
}

(inspired by Spring Boots LogFileMvcEndpoint and more or less equal to Paul-Warrens (@paul-warren) StoreByteRangeHttpRequestHandler which I found later on).

Hopefully this is something which Spring will support in the near future, see https://jira.spring.io/browse/SPR-13834 (please vote for it).

like image 174
bgraves Avatar answered Oct 14 '22 01:10

bgraves


The HTTP resume download function might be your friend. I had the same problem before. After implementing http range the navigation in the video was possible:

http://balusc.blogspot.com/2009/02/fileservlet-supporting-resume-and.html

like image 5
user3395533 Avatar answered Oct 14 '22 01:10

user3395533