在下载iOS时流式传输视频

我正在使用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,所以如果不同的视频播放方法可以在这种情况下工作,我都是为了这一点.

相关未解决的问题:

AVPlayer and Progressive Video Downloads with AVURLAssets

Progressive Video Download on iOS

How to play an in downloading progress video file in IOS

更新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' */
        }
    }
}

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

解决方法

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

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

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

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

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

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

NSTimeInterval currentPlaybackTime = videoController.moviePlayer.currentPlaybackTime;

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

这种方法确实让用户最初同时下载了两个视频文件,但是在网络上的初始测试速度,我的用户将会使用它,只会增加下载时间几秒钟.为我工作!

相关文章

当我们远离最新的 iOS 16 更新版本时,我们听到了困扰 Apple...
欧版/美版 特别说一下,美版选错了 可能会永久丧失4G,不过只...
一般在接外包的时候, 通常第三方需要安装你的app进行测...
前言为了让更多的人永远记住12月13日,各大厂都在这一天将应...