在下载iOS时stream式传输video

我正在使用iOS 7,我有一个.mp4video,需要在我的应用程序中下载。 video很大(〜1 GB),这就是为什么它不包括在应用程序的一部分。 我希望用户能够在开始下载时立即开始观看video。 我也希望video能够被caching在iOS设备上,以便用户不需要稍后再下载。 播放video的普通方法(渐进式下载和实时stream式传输)似乎不能让您cachingvideo,因此我已经制作了自己的Web服务,将video文件分块并将字节stream式传输到客户端。 我使用NSURLConnection开始HTTPstream式传输:

self.request = [[NSMutableURLRequest alloc] initWithURL:self.url]; [self.request setTimeoutInterval:10]; // Expect data at least every 10 seconds [self.request setHTTPMethod:@"GET"]; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES]; 

当我收到一个数据块时,我将它追加到本地副本的末尾:

 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:[self videoFilePath]]; [handle truncateFileAtOffset:[handle seekToEndOfFile]]; [handle writeData:data]; } 

如果我让设备运行,文件下载成功,我可以使用MPMoviePlayerViewController播放它:

 NSURL *url=[NSURL fileURLWithPath:self.videoFilePath]; MPMoviePlayerViewController *controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url]; controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit; [self presentMoviePlayerViewControllerAnimated:controller]; 

但是,如果我在文件完全下载之前启动播放器,video开始播放就好了。 它甚至在顶部扫描器栏上显示正确的video长度。 但是当用户到达video开始之前我已经完成下载的video中的位置时,video只是挂起。 如果我closures并重新打开MPMoviePlayerViewController,那么video就会播放,直到它到达当时我再次启动MPMoviePlayerViewController的位置。 如果我一直等到整个video下载完毕,那么video播放没有问题。

我没有收到任何事件触发,或者在发生这种情况时向控制台输出错误消息(MPMoviePlayerPlaybackStateDidChangeNotification和MPMoviePlayerPlaybackDidFinishNotification从未在video启动后发送)。 似乎还有别的东西告诉控制器video的长度是什么,而不是什么洗涤器正在使用…

有谁知道什么可能导致这个问题? 我不是一定要使用MPMoviePlayerViewController,所以如果不同的video播放方法在这种情况下工作,我全部为此。

相关的未解决的问题:

AVPlayer和AVURLAssets的渐进式video下载

Progressive Video在iOS上下载

如何在IOS中下载进度video文件

更新1我已经发现,video档的确是因为video开始播放时的文件大小。 我可以通过在开始下载之前创build一个零出来的文件来解决这个问题,并在我去的时候覆盖它。 由于我可以控制videostream媒体服务器,因此我添加了一个自定义标题,以便知道正在stream式处理的文件的大小(stream文件的默认文件大小标头为-1)。 我在我的didReceiveResponse方法中创build文件,如下所示:

 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { // Retrieve the size of the file being streamed. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; NSDictionary *headers = httpResponse.allHeaderFields; NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init]; [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; self.streamingFileSize = [formatter numberFromString:[headers objectForKey:@"StreamingFileSize"]]; // Check if we need to initialize the download file if (![[NSFileManager defaultManager] fileExistsAtPath:self.path]) { // Create the file being downloaded [[NSData data] writeToFile:self.path atomically:YES]; // Allocate the size of the file we are going to download. const char *cString = [self.path cStringUsingEncoding:NSASCIIStringEncoding]; int success = truncate(cString, self.streamingFileSize.longLongValue); if (success != 0) { /* TODO: handle errors here. Probably not enough space... See 'man truncate' */ } } } 

这很好,除了truncate导致应用程序挂起约10秒,而它创build磁盘上的〜1GB文件(在模拟器上它是即时的,只有真正的设备有这个问题)。 这是我现在坚持的地方 – 有谁知道更有效地分配文件的方式,或不同的方式让video播放器识别文件的大小,而不需要实际分配? 我知道一些文件系统支持“文件大小”和“磁盘大小”作为两个不同的属性…不知道如果iOS有这样的东西?

我想出了如何做到这一点,这比我原来的想法简单得多。

首先,由于我的video是在.mp4,MPMoviePlayerViewController或AVPlayer类可以直接从networking服务器播放 – 我不需要实现任何特殊的,他们仍然可以寻求video中的任何一点。 这必须是.mp4编码如何与电影播放器​​一起工作的一部分。 所以,我只是在服务器上提供原始文件 – 不需要特殊的头文件。

接下来,当用户决定播放video时,我立即开始从服务器URL播放video:

 NSURL *url=[NSURL fileURLWithPath:serverVidelFileURLString]; controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url]; controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit; [self presentMoviePlayerViewControllerAnimated:controller]; 

这使得用户可以观看video并寻找他们想要的任何位置。 然后,我开始使用NSURLConnection手动下载文件,就像我上面做的那样,除了现在我没有stream式传输文件,我只是直接下载它。 这样,我不需要自定义标头,因为文件大小包含在HTTP响应中。

当我的后台下载完成时,我把播放项从服务器URL切换到本地文件。 这对于networking性能非常重要,因为电影播放器​​只能在用户正在观看的几秒之前下载。 能够尽快切换到本地文件是避免下载太多重复数据的关键:

 NSTimeInterval currentPlaybackTime = videoController.moviePlayer.currentPlaybackTime; [controller.moviePlayer setContentURL:url]; [controller.moviePlayer setCurrentPlaybackTime:currentPlaybackTime]; [controller.moviePlayer play]; 

这个方法确实让用户最初同时下载两个video文件,但是初始testingnetworking速度是我的用户使用的,显示它只增加下载时间几秒钟。 为我工作!

你必须创build一个内部networking服务器,就像一个代理! 然后将您的播放器设置为从本地主机播放电影。

当使用HTTP协议通过MPMoviePlayerViewController播放video时,播放器所做的第一件事就是要求字节范围0-1(前2个字节) 来获得文件长度 。 然后,播放器使用“字节范围”HTTP命令(目的是节省一些电池)请求video的“块”。

你必须做的是实现这个内部服务器,将video传送给播放器, 但是你的“代理”必须将video的长度视为文件的全长 ,即使实际文件没有被完全下载来自networking。

然后,您将您的播放器设置为从“http:// localhost:someport”播放电影

我之前做过这个…它完美的工作!

祝你好运!

我只能假设MPMoviePlayerViewController在启动时caching文件的文件长度。

解决这个问题的方法是首先确定文件的大小。 然后创build一个长度的文件。 保持偏移指针,当文件下载时,可以用真实数据覆盖文件中的“空”值。

所以你下载一个特定的点,启动MPMoviePlayerViewController,让它运行。 我还build议你使用“F_NOCACHE”标志(与fcntl()),所以你绕过文件块caching(这意味着你会降低你的内存占用)。

这种架构的缺点是,如果你停下来,电影播放器​​超过你,那么用户将有一个非常糟糕的经验。 不知道是否有任何方法可以监视并采取先发制人的行动。

编辑:它很有可能不会顺序读取video,但某些信息需要玩家本质上向前看东西。 如果是这样,那么这注定是失败的。 唯一的其他可能的解决scheme是使用一些软件工具来顺序sorting文件(我不是video专家,所以不能从上述任何经验评论)。

为了testing这一点,你可以构build一个不同长度的“损坏”的video,并testing看看有什么作品,什么不可以。 例如,假设你有一个100Meg的文件。 写一点实用程序,然后用零填写最后的50个数据。 现在播放这个video。 它应该失败1/2通过。 如果它马上失败,那么你现在知道它在文件中寻找。

如果不是顺序的,那么它可能会查看最近的1000个字节,在这种情况下,如果你不覆盖那些东西,你想要的。 如果你幸运的话,那么你最终会下载最后的1000个字节,然后从文件的前面开始。

将真实networking引入图片之前 ,真正要find一些方法来播放部分文件。 你一定会发现,人为地引入networking条件,而不是真正做到实时。