2012-10-07 21 views
6

Tôi muốn truyền phát video đến IPad của mình qua thẻ video HTML5 với tấm thảm5 (5.3.5) trên chương trình phụ trợ. Thông thường, framework serverside thậm chí không nên đóng một vai trò nào trong điều này, nhưng bằng cách nào đó nó cũng vậy.Phát trực tuyến video tới ipad không hoạt động với Tapestry5

Dù sao, hy vọng ai đó ở đây có thể giúp tôi. Hãy ghi nhớ rằng dự án của tôi là rất nhiều mẫu thử nghiệm và những gì tôi mô tả được đơn giản hóa/giảm xuống các phần liên quan. Tôi sẽ rất cảm kích nếu mọi người không đáp ứng với nghĩa vụ bắt buộc "bạn muốn làm điều sai trái" hoặc các nitpicks bảo mật/hiệu suất không liên quan đến vấn đề.

Vì vậy, ở đây nó đi:

Cài đặt

Tôi có một đoạn video lấy từ Apple HTML5 giới thiệu vì vậy tôi biết rằng định dạng không phải là một vấn đề. Tôi có một trang tml "Play" đơn giản chỉ chứa thẻ "video".

Vấn đề

tôi bắt đầu bằng cách thực hiện một RequestFilter để xử lý các yêu cầu từ các điều khiển video bằng cách mở file video tham chiếu và truyền nó cho khách hàng. Đó là cơ bản "nếu đường dẫn bắt đầu với 'tập tin' sau đó sao chép tập tin đầu vào để đáp ứng outputstream". Tính năng này hoạt động rất tốt với Chrome nhưng không hoạt động với Ipad. Tốt thôi, tôi nghĩ, phải là một số tiêu đề tôi đang mất tích vì vậy tôi nhìn vào Apple Showcase một lần nữa và bao gồm cùng một tiêu đề và loại nội dung nhưng không có niềm vui.

Tiếp theo, mặc dù vậy, chúng ta hãy xem điều gì sẽ xảy ra nếu tôi để t5 phân phối tệp. Tôi đã sao chép video vào ngữ cảnh webapp, tắt bộ lọc yêu cầu của tôi và đặt tên tệp đơn giản vào thuộc tính src của video. Tính năng này hoạt động trong Chrome và iPad. Điều đó khiến tôi ngạc nhiên và nhắc tôi xem xét cách xử lý các tệp tĩnh/yêu cầu ngữ cảnh. Vì vậy, đến nay tôi đã chỉ nhận được cho đến nay như để cảm thấy như có hai con đường khác nhau mà tôi đã xác nhận bằng cách chuyển ra hardwired "video src" để một tài sản với một @Path ("bối cảnh:"). Điều này, một lần nữa, hoạt động trên Chrome nhưng không hoạt động trên iPad.

Vì vậy, tôi thực sự bị mất ở đây. Nước bí mật này trong các yêu cầu "bối cảnh đơn giản" nào cho phép nó hoạt động trên IPad? Không có gì đặc biệt xảy ra và đó là cách duy nhất hoạt động. Vấn đề là, tôi có thể không thực sự phục vụ những vids từ bối cảnh webapp của tôi ...

Giải pháp

Vì vậy, nó quay ra rằng có tiêu đề http này được gọi là "Range" và rằng IPad, không giống như Chrome sử dụng với video. Các "bí mật nước sốt" sau đó là xử lý servlet cho yêu cầu tài nguyên tĩnh biết làm thế nào để đối phó với các yêu cầu phạm vi trong khi T5 không. Dưới đây là triển khai tùy chỉnh của tôi:

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

Trả lời

7

Tôi muốn đăng giải pháp tinh chỉnh của mình từ trên cao. Hy vọng rằng điều này sẽ hữu ích cho ai đó.

Vì vậy, về cơ bản vấn đề dường như là tôi đã bỏ qua tiêu đề yêu cầu http "Phạm vi" mà IPad không thích.Tóm lại, tiêu đề này có nghĩa là khách hàng chỉ muốn một phần nhất định (trong trường hợp này là một phạm vi byte) của phản hồi.

Đây là những gì một chiếc iPad yêu cầu phim html trông giống như ::

[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 

Nó có nghĩa là iPad chỉ muốn byte đầu tiên. Nếu bạn bỏ qua tiêu đề này và chỉ cần gửi phản hồi 200 với toàn bộ nội dung thì video sẽ không phát. Vì vậy, bạn cần gửi một 206 phản ứng (phản ứng một phần) và thiết lập các tiêu đề phản ứng sau đây:

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

này có nghĩa là "Tôi sẽ gửi cho bạn bytes 0 đến 1 trong tổng số 357.772.702 tổng byte có sẵn".

Khi bạn thực sự bắt đầu phát video, yêu cầu tiếp theo sẽ giống như thế này (tất cả mọi thứ trừ tiêu đề phạm vi Bỏ qua phần):

[INFO] RequestLogger Range:bytes=0-357772701 

Vì vậy, giải pháp tinh tế của tôi trông như thế này:

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

     } 

Giải pháp này tốt hơn giải pháp đầu tiên của tôi vì giải pháp này xử lý tất cả các trường hợp cho các yêu cầu "Phạm vi" có vẻ như là điều kiện tiên quyết cho khách hàng như Chrome để có thể hỗ trợ bỏ qua trong video (tại thời điểm đó họ sẽ đưa ra yêu cầu phạm vi cho điều đó trong video).

Nó vẫn chưa hoàn hảo. Các cải tiến tiếp theo sẽ đặt tiêu đề "Được sửa đổi lần cuối" một cách chính xác và xử lý đúng các yêu cầu của khách hàng là phạm vi không hợp lệ hoặc phạm vi của một cái gì đó khác sau đó là byte.

+0

Đây là thông tin hữu ích; không có lý do tại sao Tapestry không thể xử lý điều này tự động bên trong mã xử lý tài sản chuẩn; chúng tôi chỉ không biết rằng nó cần làm. Việc thêm mức thông tin này vào JIRA của chúng tôi là bước đầu tiên. –

+0

Câu trả lời hay. Làm việc như một say mê ngay lập tức. Cảm ơn rất nhiều. –

0

Tôi nghi ngờ điều này nhiều hơn về iPad so với Tapestry.

Tôi có thể gọi Response.disableCompression() trước khi ghi luồng vào phản hồi; Tấm thảm có thể đang cố gắng GZIP luồng của bạn và iPad có thể không được chuẩn bị cho điều đó vì các định dạng video và hình ảnh thường đã được nén.

Ngoài ra, tôi không thấy tiêu đề loại nội dung được đặt; một lần nữa iPad có thể đơn giản nhạy cảm hơn so với Chrome.

+0

Xin chào Howard. Tôi nghĩ rằng đó là tuyệt vời mà bạn dành thời gian để trả lời T5 (một khuôn khổ tuyệt vời) ở đây trên Stackoverflow. Dù sao, tôi phát hiện ra vấn đề là gì và thêm giải pháp cho câu hỏi của tôi. Phiên bản TL; DR là iPad không thích nó nếu bạn bỏ qua tiêu đề yêu cầu http "Phạm vi". Điều này có thể là một vấn đề đối với T5 cũng bởi vì từ những gì tôi nói, khi khuôn khổ đang phục vụ một tài sản, nó cũng sẽ bỏ qua tiêu đề Phạm vi. Tôi sẽ đăng câu trả lời với nhiều chi tiết hơn. – Wulf

Các vấn đề liên quan