2012-10-07 15 views
6

バックエンドでtapestry5(5.3.5)のHTML5ビデオタグを使用してIPadにビデオをストリーミングしたい。通常、サーバー側のフレームワークはこれで役割を果たしてはいけませんが、何らかの形でそれがあります。ipadへのビデオストリーミングがTapestry5で動作しない

とにかく、ここの誰かが私を助けてくれたらうれしいです。私のプロジェクトは非常にプロトタイプであり、私が記述したものは関連する部分に単純化/縮小されていることに留意してください。人々が義務的な「あなたが間違ったことをしたい」、または問題に関連しないセキュリティ/パフォーマンスのニックピックで応答しなかった場合、私は非常に感謝しています。

だからここには行く:

セットアップ

を私はフォーマットが問題ではないことを知っているので、AppleのHTML5ショーケースから取り出したビデオを持っています。私は単に "ビデオ"タグを含む単純なtmlページ "再生"を持っています。

問題は、私は、参照ビデオファイルを開くと、それをクライアントにストリーミングすることにより、ビデオコントロールからのリクエストを処理RequestFilterを実装することによって開始しました。これは基本的なものです。もしパスが 'file'で始まり、ファイル入力ストリームを応答出力ストリームにコピーするならば。これはChromeではうまく動作しますが、Ipadではうまく動作しません。さて、Appleのショーケースをもう一度見て、同じヘッダーとコンテンツの種類が含まれていますが、喜びはありませんでした。

次に、私はt5にファイルを提供させるとどうなるか見てみましょう。私はビデオをwebappコンテキストにコピーし、リクエストフィルタを無効にして、シンプルなファイル名をビデオのsrc属性に入れました。これはChromeとiPadで動作します。 それは私に驚いて、T5がどのように静的ファイル/コンテキスト要求を処理するかを見るように促しました。これまでのところ、ハードワイヤードの「ビデオsrc」を@Path(「コンテキスト:」)を持つ資産に切り替えることで、私が確認した2つの異なるパスがあるように感じました。これは、Chromeでも動作しますが、IPadでは動作しません。

ここで私は本当に迷っています。 IPad上で動作することを可能にする「シンプルコンテキスト」要求では、この秘密のジュースは何ですか?特別なことは何もありませんが、これが唯一の方法です。問題が

ソリューションので

...私は本当に私のWebアプリケーションコンテキストから、それらの分割動画を提供することはできません、それは「範囲」と呼ばれるこのHTTPヘッダがあることが判明とiPadのことを、Chromeが使用していますとは違ってビデオ付きです「秘密のソース」は、静的リソース要求のサーブレットハンドラが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

私は上から洗練されたソリューションを投稿したいと思います。うまくいけば、これは誰かにとって役に立ちます。

したがって、基本的に問題は、私が「範囲」のHTTP要求ヘッダーを無視しているように見えました。要約すると、このヘッダーは、クライアントが応答の特定の部分(この場合はバイト範囲)だけを必要としていることを意味します。

これは、iPadが最初のバイトだけを望んでいることを意味のように::

[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 

iPadのHTMLビデオ要求が見えるものです。このヘッダーを無視して、全身で200の応答を送信すると、ビデオは再生されません。だから、あなたは206応答(パーシャルレスポンス)を送信し、次の応答ヘッダを設定する必要があります。

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

これは、「私はあなたが利用可能357772702の総バイト数の1から0にバイト送るよ」という意味します。

あなたが実際にビデオの再生を開始すると、次の要求は次のように(ommited範囲ヘッダー以外のすべてを)になります。

[INFO] RequestLogger Range:bytes=0-357772701 

だから私の洗練されたソリューションは、次のようになります。

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

     } 

このソリューションは、Chromeのようなクライアントがビデオ内でスキップできるようにするための前提条件と思われる「範囲」要求のすべてのケースを処理するため、最初の方が優れています(その時点で、ビデオ内のポイント)。

まだ完全ではありません。さらに改良された点は、 "Last-Modified"ヘッダーを正しく設定し、クライアントの適切な処理を行って無効な範囲または何か他の範囲のバイトを要求することです。

+0

これは便利な情報です。 Tapestryが標準の資産処理コード内でこれを自動的に処理できない理由はありません。私たちはそれが必要であることを認識していません。このレベルの情報をJIRAに追加することが、最初のステップです。 –

+0

優れた答え。すぐに魅力のように動作します。どうもありがとう。 –

0

私はこれがTapestryについてよりもiPadに近いと考えています。

ストリームをレスポンスに書き込む前に、Response.disableCompression()を呼び出すことがあります。 TapestryがあなたのストリームをGZIPしようとしている可能性があります。そして、ビデオや画像のフォーマットは通常圧縮されているため、iPadはその準備ができていないかもしれません。

また、コンテンツタイプヘッダーが設定されていません。やはりiPadは単にChromeよりも敏感かもしれません。

+0

こんにちはハワード。私はあなたがT5(偉大なフレームワーク)にStackoverflowで答える時間を取ることは素晴らしいことだと思います。とにかく、私は問題が何であるかを知り、解決策を私の質問に加えました。 TL; DRバージョンは、あなたが "Range" httpリクエストヘッダを無視すると、iPadがそれを気に入らないということです。フレームワークがアセットを提供しているとき、Rangeヘッダーも無視するので、これはT5でも問題になる可能性があります。私は詳細を記した回答を投稿します。 – Wulf

関連する問題