视频流到ipad不适用于Tapestry5

Wul*_*ulf 7 servlets tapestry ipad html5-video

我想通过后端的tapestry5(5.3.5)通过HTML5视频标签将视频流式传输到我的IPad.通常,服务器端框架甚至不应该在这方面发挥作用,但它确实如此.

无论如何,希望有人在这里可以帮助我.请记住,我的项目非常原型,我所描述的内容被简化/简化为相关部分.如果人们没有回答强制性的"你想做错事"或与问题无关的安全/表现挑剔,我将非常感激.

所以这里:

建立

我有一个从Apple HTML5展示的视频,所以我知道格式不是问题.我有一个简单的tml页面"Play",它只包含一个"视频"标签.

问题

我首先实现了一个RequestFilter,它通过打开引用的视频文件并将其传输到客户端来处理来自视频控件的请求.这是基本的"如果路径以'文件'开头,则将文件输入流复制到响应输出流".这适用于Chrome,但不适用于Ipad.好吧,但是,我必须是一些我不知道的标题,所以我再次看了Apple Showcase并包含相同的标题和内容类型,但没有快乐.

接下来,我会,好吧,让我们看看如果我让t5提供文件会发生什么.我将视频复制到webapp上下文,禁用了我的请求过滤器,并将简单文件名放在视频的src属性中.这适用于Chrome和iPad.这让我很吃惊,并促使我看看T5如何处理静态文件/上下文请求.到目前为止,我只是觉得有两种不同的路径,我通过将带有@Path("上下文:")的硬连线"视频src"切换到资产来确认.这同样适用于Chrome,但不适用于iPad.

所以我真的迷失在这里.什么是"简单上下文"中的秘密请求允许它在IPad上工作?没有什么特别的事情,但这是唯一的方法.问题是,我无法真正从我的webapp上下文中提供这些视频...

因此,事实证明,有一个名为"Range"的http标头,而且与Chrome不同,它与视频一起使用它.然后,"秘密酱"是静态资源请求的servlet处理程序知道如何处理范围请求,而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();
        }
Run Code Online (Sandbox Code Playgroud)

Wul*_*ulf 8

我想从上面发布我的精炼解决方案.希望这对某人有用.

所以基本上问题似乎是我忽略了IPad不喜欢的"Range"http请求标头.简而言之,此标头意味着客户端只需要响应的某个部分(在本例中为字节范围).

这就是iPad html视频请求的样子::

[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
Run Code Online (Sandbox Code Playgroud)

这意味着iPad只需要第一个字节.如果您忽略此标题并仅使用完整正文发送200响应,则视频将无法播放.因此,您需要发送206响应(部分响应)并设置以下响应标头:

[INFO] RequestLogger Content-Range:bytes 0-1/357772702
[INFO] RequestLogger Content-Length:2
Run Code Online (Sandbox Code Playgroud)

这意味着"我正在向您发送357772702个可用字节的字节0到1".

当您实际开始播放视频时,下一个请求将如下所示(除了范围标题之外的所有内容都被忽略):

[INFO] RequestLogger Range:bytes=0-357772701
Run Code Online (Sandbox Code Playgroud)

所以我的精炼解决方案如下所示:

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

        }
Run Code Online (Sandbox Code Playgroud)

这个解决方案比我的第一个更好,因为它处理"范围"请求的所有情况,这似乎是像Chrome这样的客户能够支持在视频中跳过的先决条件(此时他们会发出一个范围请求)点在视频中).

但它仍然不完美.进一步改进将正确设置"Last-Modified"标头并正确处理客户端请求无效范围或除了字节之外的其他内容.