我正在使用 iOS 7,并且我有一个 .mp4 视频需要在我的应用程序中下载。视频很大(约 1 GB),这就是为什么它不包含在应用程序中的原因。我希望用户能够在开始下载后立即开始观看视频。我还希望视频能够缓存在 iOS 设备上,这样用户以后就不需要再次下载了。两种播放视频的常规方法(渐进式下载和实时流式传输)似乎都不允许您缓存视频,因此我创建了自己的 Web 服务,将我的视频文件分块并将字节流式传输到客户端。我使用 NSURLConnection 启动流式 HTTP 调用:
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];
但是,如果我在文件完全下载之前启动播放器,视频就可以正常播放了。它甚至在顶部洗涤栏上显示了正确的视频长度。但是,当用户到达视频开始之前我已完成下载的视频位置时,视频就会挂起。如果我关闭并重新打开 MPMoviePlayerViewController,则视频会一直播放到我再次启动 MPMoviePlayerViewController 时所处的位置。如果我等到整个视频下载完毕,则视频播放没有问题。
发生这种情况时,我没有触发任何事件,也没有将错误消息打印到控制台(视频开始后永远不会发送 MPMoviePlayerPlaybackStateDidChangeNotification 和 MPMoviePlayerPlaybackDidFinishNotification)。似乎还有其他东西告诉控制器视频的长度是什么,而不是擦洗器正在使用的......
有谁知道可能导致此问题的原因是什么?我不一定要使用 MPMoviePlayerViewController,所以如果在这种情况下可以使用不同的视频播放方法,我完全赞成。
相关未解决的问题:
使用 AVURLAssets 的 AVPlayer 和渐进式视频下载
更新 1 我发现视频停顿确实是因为视频开始播放时的文件大小。我可以通过在开始下载之前创建一个归零文件并在进行时覆盖它来解决这个问题。由于我可以控制视频流服务器,因此我添加了一个自定义标头,以便我知道正在流式传输的文件的大小(流式传输文件的默认文件大小标头为 -1)。我正在我的 didReceiveResponse 方法中创建文件,如下所示:
- (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 导致应用程序在磁盘上创建 ~1GB 文件时挂起大约 10 秒(在模拟器上它是即时的,只有真实设备有这个问题)。这就是我现在卡住的地方 - 有没有人知道一种更有效地分配文件的方法,或者另一种让视频播放器识别文件大小而无需实际分配的方法?我知道一些文件系统支持“文件大小”和“磁盘大小”作为两个不同的属性......不确定iOS是否有类似的东西?