一、简述
最近的项目有个m3u8文件的下载需求,m3u8文件和其他文件有个根本的区别是它只是一个地址的集合文件,我们如果需要下载m3u8文件的话需要将它列表里面所有的文件都一一下载下来,然后还要生成一个播放的排列文件,最后开始播放。
二、难点
1、多文件下载。
2、播放文件的生成。
3、缓存的存取周期管理。
三、相关代码详解
1、为了方便,我们先写一个下载的管理类 BYM3U3DownLoadManger
//返回下载类对象
+ (AriaM3U8Downloader *)plistGetDownloadWith:(NSString *)name dowloadUrl:(NSString *)url;
//删除下载,单集
+ (void)plistDeleteDwonloadWith:(NSString *)name type:(NSString *)poType;
//删除下载,传入电视剧videoID,删除该电视剧下所有剧集
+ (void)plistDeleteAllTVDownloadWith:(NSString *)videoId;
//添加视频下载信息
+ (void)addVideoDownloadDataWith:(NSDictionary *)dict;
+ (NSArray *)getAllVideoDownloadData;
//检查下本地有无下载信息
+ (BOOL)checkDownloadHaveVideoWith:(NSString *)videoId;
//设置本地视频下载状态为已下载
+ (void)setDownloadVideoCompleteWith:(NSString *)videoId;
//传入字典,返回下载类对象
+ (AriaM3U8Downloader *)plistGetDownloadWith:(NSString *)name dowloadUrl:(NSString *)url {
//先判断临时缓存里面有没有
__block AriaM3U8Downloader *cashDownload = [AriaM3U8Downloader new];
__block BOOL isInList = NO;
NSArray *cashDownloadList = [BYDownloadHelper sharedDownloadMark].downAriaM3U8Arr;
[cashDownloadList enumerateObjectsUsingBlock:^(BYCashDownloadModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *cashName = obj.fileName;
if ([cashName isEqualToString:name]) {
cashDownload = obj.downModel;
isInList = YES;
*stop = YES;
}
}];
if (isInList) {
return cashDownload;
}
//1.创建主文件夹
[self creatDir:DownloadFile];
//创建子文件夹
NSString *file = [DownloadFile stringByAppendingPathComponent:name];
[self creatDir:file];
//2.通过 M3U8 URL 进行初始化
AriaM3U8Downloader *downloader = [[AriaM3U8Downloader alloc] initWithURLString:url outputPath:file tag:[name integerValue]];
if (downloader) {
//添加到临时缓存里面去
BYCashDownloadModel *downModel = [BYCashDownloadModel new];
downModel.fileName = name;
downModel.downModel = downloader;
[[BYDownloadHelper sharedDownloadMark] addNewModelWith:downModel];
}
return downloader;
}
//传入字典,删除本地下载文件夹
+ (void)plistDeleteDwonloadWith:(NSString *)name type:(NSString *)poType {
//1.创建主文件夹
[self creatDir:DownloadFile];
//创建子文件夹
NSString *file = [DownloadFile stringByAppendingPathComponent:name];
[self creatDir:file];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
[fileManager removeItemAtPath:file error:nil];
NSLog(@"缓存清理成功");
}
//清除本地存储的视频信息
if ([poType isEqualToString:@"1"]) {//电影
NSArray *videoDownloadList = [kUserDefaults archivedForKey:KVideoDownloadList];
NSMutableArray *newMuarr = [[NSMutableArray alloc] initWithArray:videoDownloadList];
[videoDownloadList enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[obj objectOrNilForKey:@"videoId"] isEqualToString:name]) {
[newMuarr removeObject:obj];
*stop = YES;
}
}];
[kUserDefaults setArchived:newMuarr forKey:KVideoDownloadList];
[kUserDefaults synchronize];
} else {
NSArray *videoTVDownloadList = [kUserDefaults archivedForKey:KVideoTVDownloadList];
NSMutableArray *newMuarr = [[NSMutableArray alloc] initWithArray:videoTVDownloadList];
__block NSString *espVideoId;
[videoTVDownloadList enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
espVideoId = [obj objectOrNilForKey:@"videoId"];
if ([[obj objectOrNilForKey:@"videoEpisodeId"] isEqualToString:name]) {
[newMuarr removeObject:obj];
*stop = YES;
}
}];
[kUserDefaults setArchived:newMuarr forKey:KVideoTVDownloadList];
[kUserDefaults synchronize];
//判断下该视频下还有无剧集在本地,没有的话直接把剧集页删了
if (![self checkDownloadHaveOtherTVVideoWith:espVideoId]) {
NSArray *videoDownloadList = [kUserDefaults archivedForKey:KVideoDownloadList];
NSMutableArray *asdMuArr = [[NSMutableArray alloc] initWithArray:videoDownloadList];
[videoDownloadList enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[obj objectOrNilForKey:@"videoId"] isEqualToString:espVideoId]) {
[asdMuArr removeObject:obj];
*stop = YES;
}
}];
[kUserDefaults setArchived:asdMuArr forKey:KVideoDownloadList];
[kUserDefaults synchronize];
}
}
//清除临时缓存内的播放器
NSArray *cashDownloadList = [BYDownloadHelper sharedDownloadMark].downAriaM3U8Arr;
__block BYCashDownloadModel *deleteModel = [BYCashDownloadModel new];
[cashDownloadList enumerateObjectsUsingBlock:^(BYCashDownloadModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *cashName = obj.fileName;
if ([cashName isEqualToString:name]) {
deleteModel = obj;
[deleteModel.downModel stop];//停止下载
*stop = YES;
}
}];
[[BYDownloadHelper sharedDownloadMark] deleteModelWith:deleteModel];
}
//删除下载,传入电视剧videoID,删除该电视剧下所有剧集
+ (void)plistDeleteAllTVDownloadWith:(NSString *)videoId {
NSArray *videoTVDownloadList = [kUserDefaults archivedForKey:KVideoTVDownloadList];
NSMutableArray *newMuarr = [[NSMutableArray alloc] initWithArray:videoTVDownloadList];
NSMutableArray *deleteLisst = [[NSMutableArray alloc] init];
[videoTVDownloadList enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[obj objectOrNilForKey:@"videoId"] isEqualToString:videoId]) {
[deleteLisst addObject:obj];
[newMuarr removeObject:obj];
*stop = YES;
}
}];
[kUserDefaults setArchived:newMuarr forKey:KVideoTVDownloadList];
[kUserDefaults synchronize];
//循环删除本地下载文件
[deleteLisst enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *fileName = [obj objectOrNilForKey:@"videoEpisodeId"];
[self creatDir:DownloadFile];
//创建子文件夹
NSString *file = [DownloadFile stringByAppendingPathComponent:fileName];
[self creatDir:file];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
[fileManager removeItemAtPath:file error:nil];
NSLog(@"缓存清理成功");
}
//删除临时缓存的播放器
NSArray *cashDownloadList = [BYDownloadHelper sharedDownloadMark].downAriaM3U8Arr;
__block BYCashDownloadModel *deleteModel = [BYCashDownloadModel new];
[cashDownloadList enumerateObjectsUsingBlock:^(BYCashDownloadModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *cashName = obj.fileName;
if ([cashName isEqualToString:fileName]) {
deleteModel = obj;
[deleteModel.downModel stop];//停止下载
*stop = YES;
}
}];
[[BYDownloadHelper sharedDownloadMark] deleteModelWith:deleteModel];
}];
//最后删除视频数组里面的数据
NSArray *videoDownloadList = [kUserDefaults archivedForKey:KVideoDownloadList];
NSMutableArray *newMuarrList = [[NSMutableArray alloc] initWithArray:videoDownloadList];
[videoDownloadList enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[obj objectOrNilForKey:@"videoId"] isEqualToString:videoId]) {
[newMuarrList removeObject:obj];
*stop = YES;
}
}];
[kUserDefaults setArchived:newMuarrList forKey:KVideoDownloadList];
[kUserDefaults synchronize];
}
/**
创建文件夹
@param dirPath 文件夹名称
@return 创建成功或失败
*/
+ (BOOL)creatDir:(NSString *)dirPath {
if ([[NSFileManager defaultManager] fileExistsAtPath:dirPath]) {//判断dirPath路径文件夹是否已存在,此处dirPath为需要新建的文件夹的绝对路径
NSLog(@"沙盒目录已经存在%@",dirPath);
return NO;
}else {
[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];//创建文件夹
NSLog(@"创建成功%@",dirPath);
return YES;
}
}
//视频下载信息
+ (void)addVideoDownloadDataWith:(NSDictionary *)dict {
NSArray *videoDownloadList = [kUserDefaults archivedForKey:KVideoDownloadList];
NSMutableArray *newMuarr = [[NSMutableArray alloc] initWithArray:videoDownloadList];
[newMuarr addObject:dict];
[kUserDefaults setArchived:newMuarr forKey:KVideoDownloadList];
[kUserDefaults synchronize];
}
+ (NSArray *)getAllVideoDownloadData {
return [kUserDefaults archivedForKey:KVideoDownloadList];
}
+ (BOOL)checkDownloadHaveVideoWith:(NSString *)videoId {
__block BOOL isHave = NO;
NSArray *allDataArr = [kUserDefaults archivedForKey:KVideoDownloadList];
[allDataArr enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[obj objectOrNilForKey:@"videoId"] isEqualToString:videoId]) {
isHave = YES;
*stop = YES;
}
}];
return isHave;
}
//设置本地视频下载状态为已下载
+ (void)setDownloadVideoCompleteWith:(NSString *)videoId {
NSArray *allDataArr = [kUserDefaults archivedForKey:KVideoDownloadList];
NSMutableArray *allDataMuArr = [[NSMutableArray alloc] initWithArray:allDataArr];
[allDataArr enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[obj objectOrNilForKey:@"videoId"] isEqualToString:videoId]) {
NSMutableDictionary *muDict = [[NSMutableDictionary alloc] initWithDictionary:obj];
[muDict setObject:@(1) forKey:@"videoComplete"];
[allDataMuArr replaceObjectAtIndex:idx withObject:muDict];
*stop = YES;
}
}];
[kUserDefaults setArchived:allDataMuArr forKey:KVideoDownloadList];
[kUserDefaults synchronize];
}
2、模型
@interface BYCashDownloadModel : NSObject
@property (nonatomic, copy) NSString *fileName; //文件名
@property (nonatomic, strong) AriaM3U8Downloader *downModel; //下载器对象
@end
//下载单例
@interface BYDownloadHelper : NSObject
+ (instancetype)sharedDownloadMark;
@property (nonatomic, strong) NSArray<BYCashDownloadModel *> *downAriaM3U8Arr; //下载器
- (void)addNewModelWith:(BYCashDownloadModel *)model;
- (void)deleteModelWith:(BYCashDownloadModel *)model;
- (NSString *)nameWithModel:(AriaM3U8Downloader *)download;
@end
+ (instancetype)sharedDownloadMark{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_showWaterMark = [[super allocWithZone:NULL] init];
});
return _showWaterMark;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [BYDownloadHelper sharedDownloadMark];
}
- (id)copyWithZone:(struct _NSZone *)zone {
return [BYDownloadHelper sharedDownloadMark];
}
- (void)addNewModelWith:(BYCashDownloadModel *)model {
NSMutableArray *dataMuArr = [[NSMutableArray alloc] initWithArray:self.downAriaM3U8Arr];
[dataMuArr addObject:model];
self.downAriaM3U8Arr = [dataMuArr copy];
}
- (void)deleteModelWith:(BYCashDownloadModel *)model {
NSMutableArray *dataMuArr = [[NSMutableArray alloc] initWithArray:self.downAriaM3U8Arr];
[dataMuArr removeObject:model];
self.downAriaM3U8Arr = [dataMuArr copy];
}
- (NSString *)nameWithModel:(AriaM3U8Downloader *)download {
NSMutableArray *dataMuArr = [[NSMutableArray alloc] initWithArray:self.downAriaM3U8Arr];
__block NSString *strName;
[dataMuArr enumerateObjectsUsingBlock:^(BYCashDownloadModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.downModel == download) {
strName = obj.fileName;
*stop = YES;
}
}];
return strName;
}
3、开始下载
NSArray *defaultList = [[self.allUrlPlayDatArr firstObject] objectOrNilForKey:@"defaultList"];
BYMovieDownLoadView *downLoadView = [[BYMovieDownLoadView alloc] initWithAlertWithArr:defaultList];
[downLoadView showAlert];
downLoadView.clickDownloadBlock = ^(NSDictionary * _Nonnull dict) {
asyncGlobalBlock(^{
[self startDownloadVideoWith:dict];
});
};
//电影下载
- (void)startDownloadVideoWith:(NSDictionary *)dict {
NSString *url = [dict objectOrNilForKey:@"value"];
NSString *videoId = self.post_id;
AriaM3U8Downloader *downloader = [BYM3U3DownLoadManger plistGetDownloadWith:videoId dowloadUrl:url];
//3.开始下载
if (downloader.downloadStatus == AriaDownloadStatusIsDownloading) {
asyncMainBlock(^{
[kKeyWindow makeToast:@"已经在后台下载了!"];
});
return;
}
//4.判断本地有无下载
if ([BYM3U3DownLoadManger checkDownloadHaveVideoWith:videoId]) {
asyncMainBlock(^{
[kKeyWindow makeToast:@"本地已存在下载文件!"];
});
return;
}
[downloader start];
downloader.downloadTSSuccessExeBlock = ^(NSString *event) {
NSLog(@"这里是每一个TS下载完成的回调方法:%@", event);
};
downloader.downloadStartExeBlock = ^(void) {
NSLog(@"这里是开始下载的回调方法");
};
//下载影片信息存储在本地
NSDictionary *videoDict = @{@"videoId": self.post_id,
@"displayInfo": [self.dataDict objectOrNilForKey:@"displayInfo"],
@"videoTitle": [self.dataDict objectOrNilForKey:@"videoTitle"],
@"imgUrl": [self.dataDict objectOrNilForKey:@"videoBigImg"],
@"videoDownloadUrl": url,
@"videoType": self.post_Type,
@"videoComplete":@(0)
};
[BYM3U3DownLoadManger addVideoDownloadDataWith:videoDict];
asyncMainBlock(^{
[kKeyWindow makeToast:@"已开始后台下载,请到下载列表查看"];
});
}
其他的就是下载进度的一些展示了,播放的方法在这里
//开始播放
[AriaM3U8LocalServer.shared startWithPath:DownloadFile port:8080 bonjourName:@"AriaM3U8LocalServer"];
// [playdownloader createLocalM3U8FileWithTSCount:-1];
NSString *localServer = [AriaM3U8LocalServer.shared getLocalServerURLString];
if ( localServer == nil ) {
NSLog(@"获取Local Server失败...");
[self.view makeToast:@"播放失败"];
return;
}
NSString *indexM3U8 = [NSString stringWithFormat:@"%@/%@/index.m3u8", localServer,[dict objectOrNilForKey:@"videoId"]];
NSURL *indexURL = [NSURL URLWithString:indexM3U8];
NSLog(@"%@", indexURL);
[self startPlayWith:indexURL];
3、用到的Pod工具
swift 下载
pod 'AriaM3U8Downloader', :git => 'https://github.com/moxcomic/AriaM3U8Downloader.git'
pod 'AriaM3U8Downloader/LocalServer', :git => 'https://github.com/moxcomic/AriaM3U8Downloader.git'
后面的以后再说吧。