How do I return a video with Spring MVC so that it

2019-01-08 10:37发布

问题:

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

回答1:

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



回答2:

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).



回答3:

I know this is an old post but in case it is useful to anyone else out there asking the same/similar questions.

Now-a-days there are projects like Spring Content that natively support video streaming. All the code you would need for the simplest implementation would be:-

@StoreRestResource(path="videos")
public interface VideoStore extends Store<String> {}

And this would be enough to create a Java API and a set of REST endpoints that would allow you to PUT/POST, GET and DELETE streams of video. GET support byte ranges and will play properly in HTML5 video players and such like.



回答4:

In fact, it is the front end who shows the video controls for the <video> tag.

Each browser for a special video format, has a default control panel.

You can use html and css to create your own control with media API. Media api

From Div into HTML5 [By default, the <video> element will not expose any sort of player controls. You can create your own controls with plain old HTML, CSS, and JavaScript. The <video> element has methods like play() and pause() and a read/write property called currentTime. There are also read/write volume and muted properties. So you really have everything you need to build your own interface.]