视频流到ipad不适用于Tapestry5 [英] Video streaming to ipad does not work with Tapestry5

查看:103
本文介绍了视频流到ipad不适用于Tapestry5的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to stream a video to my IPad via the HTML5 video tag with tapestry5 (5.3.5) on the backend. Usually the serverside framework shouldn't even play a role in this but somehow it does.

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

Anyway, hopefully someone here can help me out. Please keep in mind that my project is very much a prototype and that what I describe is simplified / reduced to the relevant parts. I would very much appreciate it if people didn't respond with the obligatory "you want to do the wrong thing" or security/performance nitpicks that aren't relevant to the problem.

所以这就是:

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

I have a video taken from the Apple HTML5 showcase so I know that format isn't an issue. I have a simple tml page "Play" that just contains a "video" tag.

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

I started by implementing a RequestFilter that handles the request from the video control by opening the referenced video file and streaming it to client. That's basic "if path starts with 'file' then copy file inputstream to response outputstream". This works very well with Chrome but not with the Ipad. Fine, I though, must be some headers I'm missing so I looked at the Apple Showcase again and included the same headers and content type but no joy.

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

Next, I though, well, let's see what happens if I let t5 serve the file. I copied the video to the webapp context, disabled my request filter and put the simple filename in the video's src attribute. This works in Chrome AND IPad. That surprised me and prompted me to look at how T5 handles static files / context request. Thus far I've only gotten so far as to feel like there are two different paths which I've confirmed by switching out the hardwired "video src" to an Asset with a @Path("context:"). This, again, works on Chrome but not on IPad.

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

So I'm really lost here. What's this secret juice in the "simple context" requests that allow it to work on the IPad? There is nothing special going on and yet it's the only way this works. Problem is, I can't really serve those vids from my webapp context ...

所以事实证明,有一个名为Range的http标头,而且与Chrome不同,它与视频一起使用它。然后,秘密酱是静态资源请求的servlet处理程序知道如何处理范围请求,而T5则不知道。这是我的自定义实现:

So, it turns out that there is this http header called "Range" and that the IPad, unlike Chrome uses it with video. The "secret sauce" then is that the servlet handler for static resource request know how to deal with range requests while T5's doesn't. Here is my custom implementation:

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


推荐答案

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

I want to post my refined solution from above. Hopefully this will be useful to someone.

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

So basically the problem seemed to be that I was disregarding the "Range" http request header which the IPad didn't like. In a nutshell this header means that the client only wants a certain part (in this case a byte range) of the response.

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

This is what an iPad html video request looks like::

[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只需要第一个字节。如果你忽略这个标题,只是发送一个200响应全身,那么视频将无法播放。因此,您需要发送206响应(部分响应)并设置以下响应标头:

It means that the iPad only wants the first byte. If you disregard this header and simply send a 200 response with the full body then the video won't play. So, you need send a 206 response (partial response) and set the following response headers:

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

这表示我正在向您发送357772702个可用字节的字节0到1。

This means "I'm sending you byte 0 through 1 of 357772702 total bytes available".

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

When you actually start playing the video, the next request will look like this (everything except the range header ommited):

[INFO] RequestLogger Range:bytes=0-357772701

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

So my refined solution looks like this:

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等客户能够支持在视频中跳过的先决条件(此时他们会在视频中发出该点的范围请求)。

This solution is better then my first one because it handles all cases for "Range" requests which seems to be a prereq for clients like Chrome to be able to support skipping within the video ( at which point they'll issue a range request for that point in the video).

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

It's still not perfect though. Further improvments would be setting the "Last-Modified" header correctly and doing proper handling of clients requests an invalid range or a range of something else then bytes.

这篇关于视频流到ipad不适用于Tapestry5的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆