我想通过 HTML5 视频标签在后端使用 Tapestry5 (5.3.5) 将视频流式传输到我的 iPad。通常,服务器端框架甚至不应该在其中发挥作用,但它确实发挥了作用。
无论如何,希望这里有人可以帮助我。请记住,我的项目在很大程度上是一个原型,我所描述的内容已简化/简化为相关部分。如果人们没有回应强制性的“你想做错事”或与问题无关的安全/性能挑剔,我将非常感激。
所以这里是:
设置
我有一段取自 Apple HTML5 展示的视频,所以我知道格式不是问题。我有一个简单的 tml 页面“播放”,它只包含一个“视频”标签。
问题
我首先实现了一个 RequestFilter,它通过打开引用的视频文件并将其流式传输到客户端来处理来自视频控件的请求。这是基本的“如果路径以'文件'开头,则将文件输入流复制到响应输出流”。这适用于 Chrome,但不适用于 Ipad。好吧,不过,我一定是缺少一些标题,所以我再次查看了 Apple Showcase 并包含了相同的标题和内容类型,但没有任何乐趣。
接下来,好吧,让我们看看如果让 t5 提供文件会发生什么。我将视频复制到 webapp 上下文,禁用我的请求过滤器并将简单文件名放在视频的 src 属性中。这适用于 Chrome 和 iPad。这让我感到惊讶,并促使我研究 T5 如何处理静态文件/上下文请求。到目前为止,我只觉得有两种不同的路径,我通过将硬连线的“video src”切换到带有@Path(“context:”)的资产来确认它们。这同样适用于 Chrome,但不适用于 iPad。
所以我真的迷路了。在允许它在 iPad 上工作的“简单上下文”请求中,这个秘密汁液是什么?没有什么特别的事情发生,但这是唯一可行的方法。问题是,我真的不能从我的 webapp 上下文中提供这些视频......
解决方案
因此,事实证明,有一个名为“Range”的 http 标头,并且 iPad 与 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();
}