28

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);
}
3
  • I believe that videos cannot be navigated if they are being streamed, as opposed to a fully available file. But I'm not sure of this.
    – Calabacin
    Dec 17, 2013 at 13:01
  • 3
    I love the third method. Method 1 is expensive, and both methods 1 and 2 violate MVC pattern (by using HttpServletResponse 's OutputStream). Controllers should delegate view tasks to View or HttpMessageConverter. Oct 4, 2016 at 15:06
  • Unfortunately neither was good enough for video streaming since partial http requests must be attended. I added a controller class that managed these kinds of requests completely separated from any service.
    – Calabacin
    Oct 5, 2016 at 23:39

5 Answers 5

13

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

4
  • ERROR 12362 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Circular view path [plain]: would dispatch back to the current handler URL [/plain] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)] with root cause
    – PeMa
    Mar 24, 2018 at 13:15
  • Adding @ResponseBody like in the oops suggestion resolved the issue
    – PeMa
    Mar 24, 2018 at 14:01
  • 2
    Could you update your answer now that the feature is supported in Spring? Jul 29, 2018 at 18:53
  • 1
    @BrianClozel could you let me know where this is supported, any link, in Spring, I tried looking in the Spring reference document and the above mentioned jira but couldn't find any.
    – msmani
    Sep 28, 2018 at 9:01
5

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

1
  • This was exactly the issue! After modifying the script in that page to suit my needs (Spring-MVC, etc.) it worked perfectly. I can see from the logs that Chrome is indeed making several calls, makes partial requests and respects the cache configuration. Thanks a lot, you saved my day :-)
    – Calabacin
    Mar 12, 2014 at 17:07
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.

1
  • That sounds great. I will look into it. Thank you
    – Calabacin
    Jul 19, 2017 at 17:02
1

To support Safari, you must handle the range requests and provide the proper 206 return code. https://www.rfc-editor.org/rfc/rfc7233

https://melgenek.github.io/spring-video-service is a Spring example.

1
  • Agreed. Attending partial requests is the only way to do this and it works on all browsers.
    – Calabacin
    Jun 10, 2020 at 11:19
0

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

3
  • I know, but I think this control will behave differently depending on how the file is received.
    – Calabacin
    Dec 17, 2013 at 13:14
  • I don't think so. It's just an url for the video tag src attribute. Browser only knows to load bytes from a url and whether the video has been downloaded completely or not. It doesn't know whether server puts all bytes in the response or not.
    – Sho
    Dec 17, 2013 at 13:22
  • It turns out Chrome does a lot more. It tries to download the video partially, and although that functionality is enabled by default when pointing to a file, I had to code it manually, like @user3395533 said
    – Calabacin
    Oct 16, 2014 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.