iOS开发中,m3u8格式视频的下载缓存

文档更新说明:

•   2016年07月22日 v1.0  初稿

原创内容,转载请注明出处。谢谢!

这里就不普及m3u8文件是什么了,不懂的可以谷歌一下,一大堆的资料,苹果也有专门的文档来介绍m3u8格式的视频。

序言

距离上篇博客已经有两个月时间了,这两个月时间无数次提笔写博客,可是每每提笔写博客,写两行又想放下,可能是广州的天气太炎热吧,不过究其原因还是我们很多时候不知道自己想要的是什么吧。唐僧那句"贫僧自东土大唐而来去往西天取经",确实非常完美,说出了自己是谁,从哪里来,要去何处。我们平时忙忙碌碌,不就是为了得到这几个问题答案吗?
开头磨叽几句,会少多少浏览量?哈哈!

下载思路

1、获取m3u8文件;
2、解析m3u8文件,将其中的每个ts文件的地址拿到;
3、分别将每个ts文件下载下来,放到文件夹中;
4、打开本地服务器,把本地服务器的根目录设置到刚才的下载路径;
5、使用AVPlayer播放本地的m3u8文件。

关键代码

1、下载m3u8文件和ts文件共用同一个方法,使用AFN来创建下载请求,将对应的url放进来,下载到制定的文件目录

- (void)downloadWithUrl:(NSURL *)url {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
        NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:path];
        return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
    } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        path = [path stringByAppendingPathComponent:@"video"];
        NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:path];
        documentsDirectoryURL = [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
        NSFileManager *fileManager = [[NSFileManager alloc] init];
        NSError *err;
        [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
        [fileManager moveItemAtURL:filePath toURL:documentsDirectoryURL error:&err];
        NSLog(@"File downloaded to: %@", path);
    }];
    
    [downloadTask resume];
}

2、解析m3u8文件,将其中的每个ts文件的地址拿到
3、分别将每个ts文件下载下来,放到文件夹中

  • 这里使用了M3U8Handler这个第三方库来解析m3u8文件,需要注意的是这个库并不能解析所有的m3u8文件,所以可能会和你的m3u8文件格式不兼容,遇到这种情况就需要你自己写解析代码了。
- (IBAction)startHandleClick:(UIButton *)sender {
    M3U8Handler *m3u8Handler = [M3U8Handler new];
    
    [m3u8Handler praseM3U8With:[NSURL URLWithString:VideoUrl] handlerDelegate:self];
}


#pragma mark - M3U8HandlerDelegate
- (void)praseM3U8InfoFinish:(M3U8Handler *)handler {
    NSLog(@"解析完成");
    for (SegmentInfo *url in handler.segments) {
        [self downloadWithUrl:[NSURL URLWithString:url.tsURL]];
    }
    
}

- (void)M3U8Handler:(M3U8Handler *)handler praseError:(NSError *)error {
    NSLog(@"解析错误-- %@", error);
}

4、打开本地服务器,把本地服务器的根目录设置到刚才的下载路径
这里我使用的是GCDWebServer这个库来创建本地服务器的,本地服务器初始化代码如下:

- (void)setWebSever {
    self.webServer = [[GCDWebServer alloc] init];
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    path = [NSString stringWithFormat:@"%@/video/", path];
    [self.webServer addGETHandlerForBasePath:@"/" directoryPath:path indexFilename:@"list.m3u8" cacheAge:3600 allowRangeRequests:YES];
    [self.webServer startWithPort:8080 bonjourName:nil];
}

5、使用AVPlayer播放本地的m3u8文件。

AVPlayer就做了些简单的初始化,这里要注意的是把路径换成本地路径,AVPlayer的请求路径、本地服务器的根目录路径、下载文件保存路径,这三者一定要正确,不然会造成找不到数据的问题

- (void)buildPlayer {
    NSURL *url = [NSURL URLWithString:@"http://localhost:8080/list.m3u8"];
    
    AVURLAsset *playerAsset = [AVURLAsset assetWithURL:url];
    
    AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:playerAsset];
    
    AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
    
    playerLayer.frame = CGRectMake(0, 64, 320, 160);
    
    [self.view.layer addSublayer:playerLayer];
    
    self.player = player;
    
    //监听播放状态变化
    [self.player addObserver:self forKeyPath:@"status"
                     options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                     context:nil];
}

不足与展望

这个demo只实现了用户点击下载的需求,而不能实现边播边缓存,这样会造成这样的问题,如果用户需要在播放的时候下载,那么就要请求两次,这势必会给服务器带来额外的压力,也会浪费用户的流量。(我之前做过一个边下边播的尝试,但是不理想,征集上面会卡顿。)
如果想要把这个用到实际项目中,也会有很多的问题需要考虑,比如说下载文件的目录名,需要不重复,并且要每个视频对应一个文件夹,这样才能够做到真正地有用。
时间关系,这里只做了最简单的实现,至少说明这个思路是可以用在m3u8视频的下载上面的。如果您有更好的方案,也欢迎与我联系。

项目代码

附上这个demo的完整工程
https://github.com/fengqiangboy/AVPlayerCacheDemo

之前边下边播的残疾demo也附上来吧,希望大家能共同讨论下这个问题:https://github.com/fengqiangboy/AVPlayerWithGCDwebserver