HLS下载流程

解码
解码这一步就做一件事情,拿到播放链接,读取M3U8索引文件,解析出每一个TS文件的下载地址和时长,封装到Model中,供后面使用。
解码器ZYLM3U8Handler.h文件

#import <Foundation/Foundation.h>
#import "M3U8Playlist.h"
@class ZYLM3U8Handler;
@protocol ZYLM3U8HandlerDelegate <NSObject>
/*** 解析M3U8连接失败*/
- (void)praseM3U8Finished:(ZYLM3U8Handler *)handler;
/*** 解析M3U8成功*/
- (void)praseM3U8Failed:(ZYLM3U8Handler *)handler;
@end
@interface ZYLM3U8Handler : NSObject
/*** 解码M3U8*/
- (void)praseUrl:(NSString *)urlStr;
/*** 传输成功或者失败的代理*/
@property (weak, nonatomic)id <ZYLM3U8HandlerDelegate> delegate;
/*** 存储TS片段的数组*/
@property (strong, nonatomic) NSMutableArray *segmentArray;
/*** 打包获取的TS片段*/
@property (strong, nonatomic) M3U8Playlist *playList;
/*** 存储原始的M3U8数据*/
@property (copy, nonatomic) NSString *oriM3U8Str;
@end
ZYLM3U8Handler.m文件
#import "ZYLM3U8Handler.h"
#import "M3U8SegmentModel.h"
@implementation ZYLM3U8Handler
#pragma mark - 解析M3U8链接
- (void)praseUrl:(NSString *)urlStr {//判断是否是HTTP连接if (!([urlStr hasPrefix:@"http://"] || [urlStr hasPrefix:@"https://"])) {if (self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Failed:)]) {[self.delegate praseM3U8Failed:self];}return;}//解析出M3U8NSError *error = nil;NSStringEncoding encoding;NSString *m3u8Str = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:urlStr] usedEncoding:&encoding error:&error];//这一步是耗时操作,要在子线程中进行
self.oriM3U8Str = m3u8Str;/*注意1、请看代码下方注意1*/if (m3u8Str == nil) {if (self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Failed:)]) {[self.delegate praseM3U8Failed:self];}return;}//解析TS文件NSRange segmentRange = [m3u8Str rangeOfString:@"#EXTINF:"];if (segmentRange.location == NSNotFound) {//M3U8里没有TS文件if (self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Failed:)]) {[self.delegate praseM3U8Failed:self];}return;}
if (self.segmentArray.count > 0) {[self.segmentArray removeAllObjects];
}//逐个解析TS文件,并存储while (segmentRange.location != NSNotFound) {//声明一个model存储TS文件链接和时长的modelM3U8SegmentModel *model = [[M3U8SegmentModel alloc] init];//读取TS片段时长NSRange commaRange = [m3u8Str rangeOfString:@","];NSString* value = [m3u8Str substringWithRange:NSMakeRange(segmentRange.location + [@"#EXTINF:" length], commaRange.location -(segmentRange.location + [@"#EXTINF:" length]))];model.duration = [value integerValue];//截取M3U8m3u8Str = [m3u8Str substringFromIndex:commaRange.location];//获取TS下载链接,这需要根据具体的M3U8获取链接,可以根据自己公司的需求NSRange linkRangeBegin = [m3u8Str rangeOfString:@","];NSRange linkRangeEnd = [m3u8Str rangeOfString:@".ts"];NSString* linkUrl = [m3u8Str substringWithRange:NSMakeRange(linkRangeBegin.location + 2, (linkRangeEnd.location + 3) - (linkRangeBegin.location + 2))];model.locationUrl = linkUrl;[self.segmentArray addObject:model];m3u8Str = [m3u8Str substringFromIndex:(linkRangeEnd.location + 3)];segmentRange = [m3u8Str rangeOfString:@"#EXTINF:"];}/*注意2、请看代码下方注意2*///已经获取了所有TS片段,继续打包数据[self.playList initWithSegmentArray:self.segmentArray];self.playList.uuid = @"moive1";//到此数据TS解析成功,通过代理发送成功消息if (self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Finished:)]) {[self.delegate praseM3U8Finished:self];}
}
#pragma mark - getter
- (NSMutableArray *)segmentArray {
if (_segmentArray == nil) {_segmentArray = [[NSMutableArray alloc] init];
}
return _segmentArray;
}
- (M3U8Playlist *)playList {
if (_playList == nil) {_playList = [[M3U8Playlist alloc] init];
}
return _playList;
}
@end

注意:
1、下面就是解析出来的M3U8索引数据,#EXTINF:10表示的是这段TS的时长是10秒,57b3f432.ts这里表示的是每一个TS的文件名,有的M3U8这里直接是一个完成的http链接。前面说到我们要拼接处每一个TS文件的下载链接,这里应该如何拼接呢,在一开始做这里的时候,我也费解了一段时间,查阅了一些资料和博文都不靠谱,所以不建议大家根据这些不靠谱的信息拼接链接,我这里总结出来的经验是,TS文件一般都存储在.M3U8索引文件所在的路径,只需要将TS文件名替换到.M3U8索引即可,当然最靠谱的做法和你们的服务器小伙伴协商好下载路径。

#EXTM3U
#EXT-X-VERSION:2
#EXT-X-MEDIA-SEQUENCE:102
#EXT-X-TARGETDURATION:12
#EXTINF:10,
57b3f432.ts
#EXTINF:12,
57b3f43c.ts
#EXTINF:9,
57b3f446.ts

2、M3U8Playlist是一个存储一个M3U8数据的Model,存储的是TS下载链接数组,数组的数量。uuid设置为固定的"moive1",主要是用来拼接统一的缓存路径。

下载
拿到每一个TS的链接就可以下载了,下载后缓存到本地。
下载器ZYLVideoDownLoader.h文件

#import <Foundation/Foundation.h>
#import "M3U8Playlist.h"@class ZYLVideoDownLoader;@protocol ZYLVideoDownLoaderDelegate <NSObject>/*** 下载成功*/- (void)videoDownloaderFinished:(ZYLVideoDownLoader *)videoDownloader;/*** 下载失败*/- (void)videoDownloaderFailed:(ZYLVideoDownLoader *)videoDownloader;@end@interface ZYLVideoDownLoader : NSObject@property (strong, nonatomic) M3U8Playlist *playList;/*** 记录原始的M3U8*/@property (copy, nonatomic) NSString *oriM3U8Str;/*** 下载TS数据*/- (void)startDownloadVideo;/*** 储存正在下载的数组*/@property (strong, nonatomic) NSMutableArray *downLoadArray;/*** 下载成功或者失败的代理*/@property (weak, nonatomic) id <ZYLVideoDownLoaderDelegate> delegate;/*** 创建M3U8文件*/- (void)createLocalM3U8file;@end

下载器ZYLVideoDownLoader.m文件

#import "ZYLVideoDownLoader.h"
#import "M3U8SegmentModel.h"
#import "SegmentDownloader.h"@interface ZYLVideoDownLoader () <SegmentDownloaderDelegate>@property (assign, nonatomic) NSInteger index;//记录一共多少TS文件@property (strong, nonatomic) NSMutableArray *downloadUrlArray;//记录所有的下载链接@property (assign, nonatomic) NSInteger sIndex;//记录下载成功的文件的数量@end@implementation ZYLVideoDownLoader-(instancetype)init {self = [super init];if (self) {self.index = 0;self.sIndex = 0;}return self;}
#pragma mark - 下载TS数据- (void)startDownloadVideo {//首相检查是否存在路径[self checkDirectoryIsCreateM3U8:NO];__weak __typeof(self)weakSelf = self;
/*注意1,请看下方注意1*///将解析的数据打包成一个个独立的下载器装进数组[self.playList.segmentArray enumerateObjectsUsingBlock:^(M3U8SegmentModel *obj, NSUInteger idx, BOOL * _Nonnull stop) {//检查此下载对象是否存在__block BOOL isE = NO;[weakSelf.downloadUrlArray enumerateObjectsUsingBlock:^(NSString *inObj, NSUInteger inIdx, BOOL * _Nonnull inStop) {if ([inObj isEqualToString:obj.locationUrl]) {//已经存在isE = YES;*inStop = YES;} else {//不存在isE = NO;}}];if (isE) {//存在} else {//不存在NSString *fileName = [NSString stringWithFormat:@"id%ld.ts", (long)weakSelf.index];SegmentDownloader *sgDownloader = [[SegmentDownloader alloc] initWithUrl:[@"http://111.206.23.22:55336/tslive/c25_ct_btv2_btvwyHD_smooth_t10/" stringByAppendingString:obj.locationUrl] andFilePath:weakSelf.playList.uuid andFileName:fileName withDuration:obj.duration withIndex:weakSelf.index];sgDownloader.delegate = weakSelf;[weakSelf.downLoadArray addObject:sgDownloader];[weakSelf.downloadUrlArray addObject:obj.locationUrl];weakSelf.index++;}}];/*注意2,请看下方注意2*///根据新的数据更改新的playList__block NSMutableArray *newPlaylistArray = [[NSMutableArray alloc] init];[self.downLoadArray enumerateObjectsUsingBlock:^(SegmentDownloader *obj, NSUInteger idx, BOOL * _Nonnull stop) {M3U8SegmentModel *model = [[M3U8SegmentModel alloc] init];model.duration = obj.duration;model.locationUrl = obj.fileName;model.index = obj.index;[newPlaylistArray addObject:model];}];if (newPlaylistArray.count > 0) {self.playList.segmentArray = newPlaylistArray;}//打包完成开始下载[self.downLoadArray enumerateObjectsUsingBlock:^(SegmentDownloader *obj, NSUInteger idx, BOOL * _Nonnull stop) {obj.flag = YES;[obj start];}];}
#pragma mark - 检查路径- (void)checkDirectoryIsCreateM3U8:(BOOL)isC {//创建缓存路径NSString *pathPrefix = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];NSString *saveTo = [[pathPrefix stringByAppendingPathComponent:@"Downloads"] stringByAppendingPathComponent:self.playList.uuid];NSFileManager *fm = [NSFileManager defaultManager];//路径不存在就创建一个BOOL isD = [fm fileExistsAtPath:saveTo];if (isD) {//存在} else {//不存在BOOL isS = [fm createDirectoryAtPath:saveTo withIntermediateDirectories:YES attributes:nil error:nil];if (isS) {NSLog(@"路径不存在创建成功");} else {NSLog(@"路径不存在创建失败");}}}
#pragma mark - SegmentDownloaderDelegate/*注意3,请看下方注意3*/
#pragma mark - 数据下载成功- (void)segmentDownloadFinished:(SegmentDownloader *)downloader {//数据下载成功后再数据源中移除当前下载器self.sIndex++;if (self.sIndex >= 3) {//每次下载完成后都要创建M3U8文件[self createLocalM3U8file];//证明所有的TS已经下载完成[self.delegate videoDownloaderFinished:self];}}
#pragma mark - 数据下载失败- (void)segmentDownloadFailed:(SegmentDownloader *)downloader {[self.delegate videoDownloaderFailed:self];}
#pragma mark - 进度更新- (void)segmentProgress:(SegmentDownloader *)downloader TotalUnitCount:(int64_t)totalUnitCount completedUnitCount:(int64_t)completedUnitCount {//NSLog(@"下载进度:%f", completedUnitCount * 1.0 / totalUnitCount * 1.0);}/*注意4,请看下方注意4*/
#pragma mark - 创建M3U8文件- (void)createLocalM3U8file {[self checkDirectoryIsCreateM3U8:YES];//创建M3U8的链接地址NSString *path = [[[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0] stringByAppendingPathComponent:@"Downloads"] stringByAppendingPathComponent:self.playList.uuid] stringByAppendingPathComponent:@"movie.m3u8"];//拼接M3U8链接的头部具体内容//NSString *header = @"#EXTM3U\n#EXT-X-VERSION:2\n#EXT-X-MEDIA-SEQUENCE:371\n#EXT-X-TARGETDURATION:12\n";NSString *header = [NSString stringWithFormat:@"#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-TARGETDURATION:15\n"];//填充M3U8数据__block NSString *tsStr = [[NSString alloc] init];[self.playList.segmentArray enumerateObjectsUsingBlock:^(M3U8SegmentModel *obj, NSUInteger idx, BOOL * _Nonnull stop) {//文件名NSString *fileName = [NSString stringWithFormat:@"id%ld.ts", obj.index];//文件时长NSString* length = [NSString stringWithFormat:@"#EXTINF:%ld,\n",obj.duration];//拼接M3U8tsStr = [tsStr stringByAppendingString:[NSString stringWithFormat:@"%@%@\n", length, fileName]];}];//M3U8头部和中间拼接,到此我们完成的新的M3U8链接的拼接header = [header stringByAppendingString:tsStr];
/*注意5,请看下方注意5*/header = [header stringByAppendingString:@"#EXT-X-ENDLIST"];//拼接完成,存储到本地NSMutableData *writer = [[NSMutableData alloc] init];NSFileManager *fm = [NSFileManager defaultManager];//判断m3u8是否存在,已经存在的话就不再重新创建if ([fm fileExistsAtPath:path isDirectory:nil]) {//存在这个链接NSLog(@"存在这个链接");} else {//不存在这个链接NSString *saveTo = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0] stringByAppendingPathComponent:@"Downloads"] stringByAppendingPathComponent:self.playList.uuid];BOOL isS = [fm createDirectoryAtPath:saveTo withIntermediateDirectories:YES attributes:nil error:nil];if (isS) {NSLog(@"创建目录成功");} else {NSLog(@"创建目录失败");}}[writer appendData:[header dataUsingEncoding:NSUTF8StringEncoding]];BOOL bSucc = [writer writeToFile:path atomically:YES];if (bSucc) {//成功NSLog(@"M3U8数据保存成功");} else {//失败NSLog(@"M3U8数据保存失败");}NSLog(@"新数据\n%@", header);}
#pragma mark - 删除缓存文件- (void)deleteCache {//获取缓存路径NSString *pathPrefix = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];NSString *saveTo = [[pathPrefix stringByAppendingPathComponent:@"Downloads"] stringByAppendingPathComponent:@"moive1"];NSFileManager *fm = [NSFileManager defaultManager];//路径不存在就创建一个BOOL isD = [fm fileExistsAtPath:saveTo];if (isD) {//存在NSArray *deleteArray = [_downloadUrlArray subarrayWithRange:NSMakeRange(0, _downloadUrlArray.count - 20)];//清空当前的M3U8文件[deleteArray enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {BOOL isS = [fm removeItemAtPath:[saveTo stringByAppendingPathComponent:[NSString stringWithFormat:@"%@", obj]] error:nil];if (isS) {NSLog(@"多余路径存在清空成功%@", obj);} else {NSLog(@"多余路径存在清空失败%@", obj);}}];}}
#pragma mark - getter- (NSMutableArray *)downLoadArray {if (_downLoadArray == nil) {_downLoadArray = [[NSMutableArray alloc] init];}return _downLoadArray;}- (NSMutableArray *)downloadUrlArray {if (_downloadUrlArray == nil) {_downloadUrlArray = [[NSMutableArray alloc] init];}return _downloadUrlArray;}@end

注意:

1、这里获取到的M3U8数据包含了很多TS文件,并不会在下载器里直接下载,而是要对每一个TS文件再次封装,然后每一个封装好的数据模型单独下载;
2、这里更新playlist的目的是为了后续创建.M3U8索引,可以暂时略过这里,到了创建索引的地方自然就懂了;
3、这是数据下载成功的代理,由于本文使用的测试连接每一个M3U8里有3个TS文件,所以当第一次3个文件全部下载完成后告诉系在工具类下载完成,后续没下载完成一个就告诉下载工具类一次;
4、在第一次3个TS文件下载成功和后续每有一个TS下载成功后,都会更新.M3U8索引文件,保证索引文件的更新;
5、这里要注意,添加了#EXT-X-ENDLIST,表明这个源事HLS的点播源,当播放的时候,HLS会从头开始播放。

成品APP直播源码HLS直播(M3U8)回看和下载功能的实现相关推荐

  1. Android视频直播源码开发直播平台、点播播放器哪家强?

    Android视频直播源码开发直播平台.点播播放器哪家强? 最近在项目中要加入视频直播和点播功能,那么问题来了,我需要一个播放器来播放视频流,那该如何选择呢?除了原生的VideoView(VideoV ...

  2. iOS流媒体开发之三:HLS直播(M3U8)回看和下载功能的实现

    1.概要 流媒体开发第一篇文章就说要把这些不是随便就可以百度到的知识献给"简书",拖了一个多月了,总算弄完了,深深松了口气,万幸没有食言,否则对不起小伙伴们. 流媒体始终是大众生活 ...

  3. 在线直播源码中直播间内大转盘功能的实现

    年前,云豹在线直播源码更新时,我们为其添加了"大转盘功能",该功能在直播间中,可实现"付费转动转盘获得礼物"的功能,那么该功能是如何实现的呢? 用户在使用该功能 ...

  4. 短视频+直播源码,直播老司机带你飞

    短视频+直播源码,直播老司机带你飞 脱胎于视频直播的短视频以近乎疯狂的速度攫取着互联网的流量,当抖音.快手的月活已经足够强大,他们又毫不犹豫地切入了直播腹地. 当互联网竞争进入下半场,依靠互联网人口. ...

  5. 在线直播源码搭建直播平台的后端

    在线直播源码搭建直播平台的后端 后端项目初始化 1.全局安装express脚手架 额,这个应该是属于准备工作的.给忘记了,那就凑合放在这里吧,别打我,我知道错了,但我就是不改[狗头保命] cnpm i ...

  6. 优质在线直播源码进行直播间搭建时需要它的“帮助”才行

    在直播间搭建这方面,可能绝大多数人都存在疑惑:是不是找一开发公司购买一套优质的在线直播源码就可以了?其实源码对于开发来说,确确实实是非常重要的,而且也是最基础的一部分.但是在此基础之上,还需要更多技术 ...

  7. 一对一视频直播源码 视频直播你应该知道的基础功能

    一对一视频直播系统有直播和社交双重属性,因此要比我们常见的一对多视频直播平台功能丰富的多.一对一视频直播系统源码还要具备哪些基础功能呢 一.什么是视频直播源码 视频直播源码与很多应用程序源代码一样,有 ...

  8. 通过vue实现在线直播源码的直播点赞

    我们可以发现,在线直播源码的直播间中,在右下角会有给主播点赞的按钮,点赞越多,对主播来说好处越多,那么在线直播源码是怎么实现点赞功能的呢? 实现在线直播源码观众的点赞功能. 功能描述:最后一次点赞三秒 ...

  9. 一对一直播源码一对一直播源码搭建你要知道的功能

    一对一直播源码一对一直播源码搭建你要知道的功能 2020年初全国疫情使的各方面企业都在停工,经济压力巨大.通过全国人民的努力与积极配合我国疫情得到得到控制并取得巨大的胜利,全国各地都在积极复工复产!大 ...

最新文章

  1. 丢手帕java_java基于双向环形链表解决丢手帕问题的方法示例
  2. mongoose设置unique不生效问题解决
  3. 前端笔记—第15篇js中的DOM操作
  4. Less语法基于node\grunt/gulp编译
  5. %02 java_02_Java语法
  6. C# - 接口,继承
  7. Sklearn-RandomForest
  8. SQLSERVER是怎麽通过索引和统计信息来找到目标数据的(第二篇)
  9. Silverlight 与 WPF 的一些差异
  10. Simulink嵌入式自动代码DSP F28335(4)——SVPWM
  11. Grasshopper不显示gha插件的解决方法
  12. ModelSim 仿真常见问题及解决办法
  13. stm32的人体红外传感器的初步使用
  14. mcu 与电量计 BQ40Z80 通信,读取电芯电量
  15. 基于FPGA的数据采集—信号产生篇
  16. Bombs CodeForces - 350C
  17. C++ ,C 筛法求素数
  18. cuda矩阵相乘_CUDA入门实战2:将矩阵乘法速度提升5000倍
  19. solidworks安装问题:安装管理程序无法在此注册表位置生成注册表项: HKLM\Software\Wow6432Node\SolidWorks
  20. HTTPS报文分析(Wireshark)

热门文章

  1. 智慧时代,如何让路口交通安全又高效?
  2. 共享自习室预约小程序APP系统开发设计方案
  3. Unity场景素材导出为 FBX文件的方法
  4. word禁止编辑,禁止复制
  5. refactoring的思考
  6. App地推活动怎么做才能事半功倍
  7. 抖音落地页一键复制微信号跳转微信的方法
  8. (小脚本) (python) 批量修改文件后缀名
  9. NOIP2020总结
  10. 2014-2015 少年辛苦终身事,莫向光阴惰寸功