Video streaming to ipad does not work with Tapestry5

柔情痞子 提交于 2019-12-05 07:05:27

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

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.

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

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

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

        }

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).

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.

I suspect this is more about iPad than about Tapestry.

I might invoke Response.disableCompression() before writing the stream to the response; Tapestry may be trying to GZIP your stream, and the iPad may not be prepared for that, as video and image formats are usually already compressed.

Also, I don't see a content type header being set; again the iPad may simply be more sensitive to that than Chrome.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!