成品APP直播源码HLS直播(M3U8)回看和下载功能的实现
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:@""] || [urlStr hasPrefix:@""])) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(praseM3U8Failed:)]) {
[self.delegate praseM3U8Failed:self];
}
return;
}
//解析出M3U8
NSError *error = nil;
NSStringEncoding encoding;
NSString *m3u8Str = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:urlStr] usedEncoding:&encoding error:&error];//这⼀步是耗时操作,要在⼦线程中进⾏
/*注意1、请看代码下⽅注意1*/
/*注意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 (unt > 0) {
[self.segmentArray removeAllObjects];
}
//逐个解析TS⽂件,并存储
while (segmentRange.location != NSNotFound) {
//声明⼀个model存储TS⽂件链接和时长的model
M3U8SegmentModel *model = [[M3U8SegmentModel alloc] init];
//读取TS⽚段时长
NSRange commaRange = [m3u8Str rangeOfString:@","];
NSString* value = [m3u8Str substringWithRange:NSMakeRange(segmentRange.location + [@"#EXTINF:" length], commaRange.location -(segmentR ange.location + [@"#EXTINF:" length]))];
model.duration = [value integerValue];
//截取M3U8
m3u8Str = [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:[@"111.206.23.
22:55336/tslive/c25_ct_btv2_btvwyHD_smo oth_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 (unt > 0) {
self.playList.segmentArray = newPlaylistArray;
}
//打包完成开始下载
[self.downLoadArray enumerateObjectsUsingBlock:^(SegmentDownloader *obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.flag = YES;
[obj start];
}];
java影视app源码
}
#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,NSUserDomain
Mask,YES) objectAtIndex:0] stringByAppendingPat hComponent:@"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];
//拼接M3U8

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。