ios - 下载 iOS 时流式传输视频

标签 ios video-streaming mpmovieplayer nsurlsessiondownloadtask progressive-download

我使用的是 iOS 7,我有一个 .mp4 视频需要在我的应用程序中下载。该视频很大(约 1 GB),这就是为什么它不包含在应用程序中的原因。我希望用户能够在开始下载后立即开始观看视频。我还希望视频能够缓存在 iOS 设备上,这样用户以后就不需要再次下载了。两种播放视频的常规方法(渐进式下载和实时流式传输)似乎都不允许您缓存视频,因此我制作了自己的网络服务,将我的视频文件分 block 并将字节流式传输到客户端。我使用 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];

当我收到数据 block 时,我将其附加到文件本地副本的末尾:

- (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)。似乎还有其他东西在告诉 Controller 视频的长度是多少,而不是洗涤器正在使用的...

有谁知道可能导致此问题的原因是什么?我并不一定要使用 MPMoviePlayerViewController,所以如果在这种情况下可以使用不同的视频播放方法,我完全赞成。

相关 Unresolved 问题:

AVPlayer and Progressive Video Downloads with AVURLAssets

Progressive Video Download on iOS

How to play an in downloading progress video file in IOS

更新 1 我发现视频卡顿确实是因为视频开始播放时的文件大小。我可以通过在开始下载之前创建一个归零文件并在下载过程中覆盖它来解决这个问题。由于我可以控制视频流服务器,因此我添加了一个自定义 header ,因此我知道正在流式传输的文件的大小(流式文件的默认文件大小 header 为 -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' */
        }
    }
}

这很好用,除了截断导致应用程序在磁盘上创建 ~1GB 文件时挂起大约 10 秒(在模拟器上它是即时的,只有真实的设备有这个问题)。这就是我现在陷入困境的地方 - 有谁知道更有效地分配文件的方法,或者让视频播放器识别文件大小而无需实际分配的不同方法?我知道一些文件系统支持“文件大小”和“磁盘大小”作为两个不同的属性...不确定 iOS 是否有类似的东西?

最佳答案

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

首先,由于我的视频是 .mp4 格式,MPMoviePlayerViewController 或 AVPlayer 类可以直接从网络服务器播放它 - 我不需要实现任何特殊的东西,它们仍然可以搜索视频中的任何点。这一定是 .mp4 编码如何与电影播放器​​一起工作的一部分。所以,我只有服务器上可用的原始文件 - 不需要特殊的 header 。

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

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

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

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

NSTimeInterval currentPlaybackTime = videoController.moviePlayer.currentPlaybackTime;

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

此方法最初确实让用户同时下载两个视频文件,但对我的用户将使用的网络速度进行的初步测试表明,它只会增加几秒钟的下载时间。适合我!

关于ios - 下载 iOS 时流式传输视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20912530/

相关文章:

ios - 类型 'UIApplication' 没有成员 'didBecomeActiveNotification'

ios - firebase.init 中的错误 : ReferenceError: Can't find variable: FIROptions

FFMPEG - 更正命令行参数以从一组图像创建 H.264 流 MP4

ios - 选项卡式应用程序的全局音频播放器

iOS 动态移动的图像不会保持不变

ios - 可重用 UITableHeaderFooterView 松散 ImageView 方向状态

video-streaming - 在网页中嵌入 H.264 视频的实时 RTSP 流

video-streaming - 如何在Wowza中发布rtmp流?

ios - 运行 MPMoviePlayerController 时崩溃

ios - 如何处理 MPMoviePlayerViewController 中的下一个和上一个按钮?