2012-10-07 5 views
6

HTML5 비디오 태그를 통해 내 IPad로 비디오를 스트리밍하고 싶습니다. backend에서 tapestry5 (5.3.5)를 사용하고 싶습니다. 일반적으로 서버 측 프레임 워크는이 점에서 역할을하지 않아야하지만 어떻게 든 작동합니다.ipad 로의 비디오 스트리밍이 Tapestry5에서 작동하지 않습니다.

어쨌든 여기 누군가가 나를 도울 수 있습니다. 제 프로젝트는 매우 프로토 타입이며, 설명하는 내용은 관련 부분으로 단순화/축소된다는 점을 명심하십시오. 사람들이 "당신이 잘못된 일을하기를 원합니다"또는 문제와 관련이없는 보안/성능 닉픽으로 응답하지 않으면 매우 감사 할 것입니다.

그래서 여기 간다 :

설정

내가 애플 HTML5에서 가져온 비디오는 내가 형식이 문제가 아님을 알 수 있도록 전시 할 수 있습니다. 나는 단순히 "비디오"태그를 포함하는 간단한 tml 페이지 "재생"이 있습니다.

문제는 참조 된 비디오 파일을 열고 클라이언트에 스트리밍 비디오 컨트롤에서 요청을 처리하는 RequestFilter를 구현하여 시작했다. 기본은 "경로가 '파일'로 시작하고 파일 입력 스트림을 응답 출력 스트림으로 복사하는 경우"입니다. 이것은 Chrome에서 잘 작동하지만 Ipad에서는 작동하지 않습니다. 좋아, 나는 비록 내가 놓친 일부 헤더가 있어야한다. 그래서 나는 Apple Showcase를 다시보고 똑같은 헤더와 내용 유형을 포함했지만 기쁨은 없었다.

다음으로 나는 t5가 파일을 제공하도록하면 어떻게되는지 보도록하겠습니다. 웹 응용 프로그램 컨텍스트에 비디오를 복사하고 요청 필터를 비활성화하고 간단한 파일 이름을 비디오의 src 속성에 넣습니다. Chrome 및 iPad에서 작동합니다. 저에게 놀라움을 금치 못했으며 T5가 정적 파일/컨텍스트 요청을 처리하는 방법을 살펴 보았습니다. 지금까지는 하드 라이팅 된 "비디오 src"를 @Path ("컨텍스트 :")가있는 자산으로 전환하여 확인한 두 가지 경로가있는 것만 큼 느낄 수있었습니다. 다시 Chrome에서 작동하지만 IPad에서는 작동하지 않습니다.

그래서 나는 정말로 여기에서 길을 잃는다. IPad에서 작동하도록 허용하는 "간단한 컨텍스트"요청에서이 비밀 주스는 무엇입니까? 특별한 일이 없지만 이것이 유일한 방법입니다. 문제는

솔루션 그래서

... 난 정말 내 웹 애플리케이션 컨텍스트에서 해당 VIDS 서비스를 제공 할 수 없습니다, 그것은 "범위"라는이 HTTP 헤더가 있음을 밝혀과 아이 패드 것으로, 크롬 사용 달리 비디오와 함께. "비밀 소스"는 정적 리소스 요청을위한 서블릿 핸들러가 T5가없는 동안 범위 요청을 처리하는 방법을 알고 있다는 것입니다. 여기 내 맞춤 구현은 다음과 같습니다.

 OutputStream os = response.getOutputStream("video/mp4"); 
     InputStream is = new BufferedInputStream(new FileInputStream(f)); 
     try { 
      String range = request.getHeader("Range"); 
      if(range != null && !range.equals("bytes=0-")) { 
       logger.info("Range response _______________________"); 
       String[] ranges = range.split("=")[1].split("-"); 
       int from = Integer.parseInt(ranges[0]); 
       int to = Integer.parseInt(ranges[1]); 
       int len = to - from + 1 ; 

       response.setStatus(206); 
       response.setHeader("Accept-Ranges", "bytes"); 
       String responseRange = String.format("bytes %d-%d/%d", from, to, f.length()); 
       logger.info("Content-Range:" + responseRange); 
       response.setHeader("Connection", "close"); 
       response.setHeader("Content-Range", responseRange); 
       response.setDateHeader("Last-Modified", new Date().getTime()); 
       response.setContentLength(len); 
       logger.info("length:" + len); 

       byte[] buf = new byte[4096]; 
       is.skip(from); 
       while(len != 0) { 

        int read = is.read(buf, 0, len >= buf.length ? buf.length : len); 
        if(read != -1) { 
         os.write(buf, 0, read); 
         len -= read; 
        } 
       } 


      } else { 
        response.setStatus(200); 
        IOUtils.copy(is, os); 
      } 

     } finally { 
      os.close(); 
      is.close(); 
     } 

답변

7

내 세련된 솔루션을 위에서 게시하고 싶습니다. 바라기를 이것은 누군가에게 유용 할 것입니다.

그래서 기본적으로 문제는 내가 IPad가 싫어하는 "Range"http 요청 헤더를 무시하고있는 것처럼 보였습니다.요컨대이 헤더는 클라이언트가 응답의 특정 부분 (이 경우 바이트 범위) 만 원한다는 것을 의미합니다.

이 그것은 아이 패드가 첫 번째 바이트를 원한다는 것을 의미처럼 ::

[INFO] RequestLogger Accept:*/* 
[INFO] RequestLogger Accept-Encoding:identity 
[INFO] RequestLogger Connection:keep-alive 
[INFO] RequestLogger Host:mars:8080 
[INFO] RequestLogger If-Modified-Since:Wed, 10 Oct 2012 22:27:38 GMT 
[INFO] RequestLogger Range:bytes=0-1 
[INFO] RequestLogger User-Agent:AppleCoreMedia/1.0.0.9B176 (iPad; U; CPU OS 5_1 like Mac OS X; en_us) 
[INFO] RequestLogger X-Playback-Session-Id:BC3B397D-D57D-411F-B596-931F5AD9879F 

아이 패드 HTML 비디오 요청이 모습입니다. 이 헤더를 무시하고 전신에 200 개의 응답을 보내면 비디오가 재생되지 않습니다.

[INFO] RequestLogger Content-Range:bytes 0-1/357772702 
[INFO] RequestLogger Content-Length:2 

이 "나는 당신이 1 개의 357,772,702의 총 바이트 수를 0 바이트 보내고있다"의미 : 그래서, 당신은 다음과 같은 응답 헤더를 206 응답 (부분 반응)을 전송하고 설정해야합니다.

실제로 동영상 재생을 시작하면 다음 요청이 (ommited 범위 헤더를 제외한 모든)과 같이 표시됩니다

OutputStream os = response.getOutputStream("video/mp4"); 

     try { 
       String range = request.getHeader("Range"); 
       /** if there is no range requested we will just send everything **/ 
       if(range == null) { 
        InputStream is = new BufferedInputStream(new FileInputStream(f)); 
        try { 
         IOUtils.copy(is, os); 
         response.setStatus(200); 
        } finally { 
         is.close(); 
        } 
        return true; 
       } 
       requestLogger.info("Range response _______________________"); 


       String[] ranges = range.split("=")[1].split("-"); 
       int from = Integer.parseInt(ranges[0]); 
       /** 
       * some clients, like chrome will send a range header but won't actually specify the upper bound. 
       * For them we want to send out our large video in chunks. 
       */ 
       int to = HTTP_DEFAULT_CHUNK_SIZE + from; 
       if(to >= f.length()) { 
        to = (int) (f.length() - 1); 
       } 
       if(ranges.length == 2) { 
        to = Integer.parseInt(ranges[1]); 
       } 
       int len = to - from + 1 ; 

       response.setStatus(206); 
       response.setHeader("Accept-Ranges", "bytes"); 
       String responseRange = String.format("bytes %d-%d/%d", from, to, f.length()); 

       response.setHeader("Content-Range", responseRange); 
       response.setDateHeader("Last-Modified", new Date().getTime()); 
       response.setContentLength(len); 

       requestLogger.info("Content-Range:" + responseRange); 
       requestLogger.info("length:" + len); 
       long start = System.currentTimeMillis(); 
       RandomAccessFile raf = new RandomAccessFile(f, "r"); 
       raf.seek(from); 
       byte[] buf = new byte[IO_BUFFER_SIZE]; 
       try { 
        while(len != 0) { 
         int read = raf.read(buf, 0, buf.length > len ? len : buf.length); 
         os.write(buf, 0, read); 
         len -= read; 
        } 
       } finally { 
        raf.close(); 
       } 
       logger.info("r/w took:" + (System.currentTimeMillis() - start)); 




     } finally { 
      os.close(); 

     } 

:

[INFO] RequestLogger Range:bytes=0-357772701 

그래서 내 정제 솔루션은 다음과 같습니다

이 솔루션은 크롬과 같은 클라이언트가 비디오 내에서 건너 뛰기를 지원할 수있는 사전 요구 사항 인 것처럼 보이는 "범위"요청에 대한 모든 사례를 처리하기 때문에 첫 번째 방법보다 좋습니다 (어느 시점에서 해당 범위 요청을 발행할지 비디오의 포인트).

아직 완벽하지는 않습니다. 추가 개선 사항은 "Last-Modified"헤더를 올바르게 설정하고 클라이언트가 올바르지 않은 범위 또는 다른 범위의 바이트 범위를 요구 한 다음 적절한 처리를 수행하는 것입니다.

+0

이것은 유용한 정보입니다. Tapestry가 표준 자산 처리 코드 내에서 이것을 자동으로 처리 할 수없는 이유는 없습니다. 우리는 단지 그것이 필요하다는 것을 인식하지 못합니다. 이 수준의 정보를 JIRA에 추가하는 것이 첫 번째 단계입니다. –

+0

우수 답변. 바로 매력처럼 작동합니다. 고마워. –

0

나는 이것이 Tapestry에 관한 것보다 더 많은 것으로 의심됩니다.

스트림을 응답에 기록하기 전에 Response.disableCompression()을 호출 할 수 있습니다. Tapestry가 스트림을 GZIP하려고 할 수 있습니다. 비디오 및 이미지 형식이 일반적으로 이미 압축되어 있으므로 iPad에서 준비하지 못할 수도 있습니다.

또한 콘텐츠 유형 헤더가 설정되지 않습니다. 다시 iPad는 단순히 Chrome보다 민감합니다.

+0

안녕하세요. Stackoverflow에서 T5 (위대한 프레임 워크)에 대답하는 시간을 갖는 것이 좋습니다. 어쨌든, 나는 문제가 무엇인지 알아 내 질문에 대한 해결책을 추가했습니다. TL; DR 버전은 "Range"http 요청 헤더를 무시하면 iPad에서 마음에 들지 않습니다. 이것은 T5에서도 문제가 될 수 있습니다. 왜냐하면 프레임 워크가 자산을 제공 할 때 Range 헤더도 무시할 것이기 때문입니다. 나는 세부 사항을 가진 응답을 배치 할 것이다. – Wulf

관련 문제