2013-12-17 5 views
15

Jeśli mam plik na serwerze internetowym (Tomcat) i utworzę tag, mogę obejrzeć wideo, wstrzymać go, poruszać się po nim i ponownie uruchomić po jego zakończeniu.Jak mogę zwrócić wideo ze Spring MVC, aby można było nawigować za pomocą znacznika html5 <video>?

Ale jeśli utworzę interfejs REST, który wyśle ​​plik wideo na żądanie, i doda jego adres URL do znacznika, mogę tylko odtwarzać i wstrzymywać. Bez przewijania, bez szybkiego przewijania do przodu, bez nawigacji, nic.

Czy istnieje sposób, aby to naprawić? Czy gdzieś czegoś brakuje?

Pliki wideo znajdują się na tym samym serwerze, co interfejs REST, a interfejs REST sprawdza tylko sesję i wysyła wideo po ustaleniu, który z nich należy wysłać.

Oto metody, które próbowałem do tej pory. Wszystkie działają, ale żaden z nich nie pozwala na nawigację.

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

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

Metoda 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); 
} 
+0

Uważam, że filmów nie można nawigować, jeśli są one przesyłane strumieniowo, w przeciwieństwie do w pełni dostępnego pliku. Ale nie jestem tego pewien. – Calabacin

+2

Uwielbiam trzecią metodę. Metoda 1 jest droga, a obie metody 1 i 2 naruszają wzorzec MVC (przy użyciu 'HttpServletResponse's' OutputStream'). Kontrolery powinny delegować zadania przeglądania do 'Widok' lub' HttpMessageConverter'. –

+0

Niestety, żadna z nich nie była wystarczająco dobra do przesyłania strumieniowego wideo, ponieważ należy wziąć pod uwagę częściowe żądania http. Dodałem klasę kontrolera, która zarządzała tego rodzaju żądaniami całkowicie oddzielonymi od dowolnej usługi. – Calabacin

Odpowiedz

3

Funkcja pobierania wznowienia HTTP może być Twoim przyjacielem. Miałem ten sam problem wcześniej. Po wdrożeniu zakres http nawigacja w filmie było możliwe:

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

+0

To był dokładnie problem! Po zmodyfikowaniu skryptu na tej stronie, zgodnie z moimi potrzebami (Spring-MVC itp.) Działało idealnie. Z dzienników widać, że Chrome rzeczywiście wykonuje kilka połączeń, wykonuje częściowe żądania i respektuje konfigurację pamięci podręcznej. Wielkie dzięki, uratowałeś mi dzień :-) – Calabacin

+2

Czy możesz napisać dokładny kod zgodnie z pytaniem? – gstackoverflow

+1

http://stackoverflow.com/a/28479001/2674303 – gstackoverflow

0

W rzeczywistości, to jest front-end, który pokazuje elementy sterowania wideo dla tagu <wideo>.

Każda przeglądarka dla specjalnego formatu wideo ma domyślny panel sterowania.

Możesz użyć html i css, aby utworzyć własną kontrolę za pomocą Media API. Media api

Od Div into HTML5 [domyślnie <wideo> element nie narazi jakiejkolwiek kontroli gracza. Możesz tworzyć własne formanty ze zwykłym starym HTML, CSS i JavaScript. Element <wideo> ma metody takie jak play() i pause() oraz właściwość odczytu/zapisu o nazwie currentTime. Istnieją również woluminy do odczytu i zapisu oraz właściwości wyciszania. Więc naprawdę masz wszystko, czego potrzebujesz, aby zbudować własny interfejs.]

+0

Wiem, ale myślę, że ta kontrola będzie zachowywać się inaczej w zależności od tego, jak plik zostanie odebrany. – Calabacin

+0

Nie sądzę. To tylko adres URL atrybutu src tagu wideo. Przeglądarka zna tylko ładowanie bajtów z adresu URL i to, czy film został pobrany całkowicie, czy nie. Nie wie, czy serwer umieszcza wszystkie bajty w odpowiedzi czy nie. – Sho

+0

Okazuje się, że Chrome robi o wiele więcej. Próbuje częściowo pobrać wideo i chociaż ta funkcja jest domyślnie włączona podczas wskazywania pliku, musiałem go ręcznie zakodować, np. @ user3395533 powiedział – Calabacin

2

Wiem, że to stary post, ale w przypadku jest przydatna każdemu, kto jeszcze tam pytając samych/podobnych pytań.

Obecnie dostępne są projekty takie jak Spring Content, które natywnie obsługują przesyłanie strumieniowe wideo. Cały kod będzie trzeba dla najprostszej implementacji byłoby: - ​​

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

I to byłoby na tyle, aby utworzyć Java API i zestaw punktów końcowych REST, która pozwala umieścić/POST, GET i DELETE strumieni wideo. GET obsługuje zakresy bajtów i będzie odtwarzany poprawnie w odtwarzaczach wideo HTML5 i tym podobnych.

+0

To brzmi świetnie. Zapoznam się z tym. Dziękuję Ci – Calabacin

5

Prostym rozwiązaniem do obsługi niestatyczny zasoby:

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

(zainspirowany Wiosna Butach LogFileMvcEndpoint i mniej więcej równe Pawła Nor (@ paul-warren) StoreByteRangeHttpRequestHandler które znalazłem później).

Mamy nadzieję, że jest to coś, co Spring będzie wspierać w najbliższej przyszłości, zobacz https://jira.spring.io/browse/SPR-13834 (proszę zagłosuj na niego).