一、簡述
? ? 最近的項(xiàng)目有個m3u8文件的下載需求,m3u8文件和其他文件有個根本的區(qū)別是它只是一個地址的集合文件,我們?nèi)绻枰螺dm3u8文件的話需要將它列表里面所有的文件都一一下載下來,然后還要生成一個播放的排列文件,最后開始播放。
二、難點(diǎn)
1、多文件下載。
2、播放文件的生成。
3、緩存的存取周期管理。
三、相關(guān)代碼詳解
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;
//設(shè)置本地視頻下載狀態(tài)為已下載
+ (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.創(chuàng)建主文件夾
? ? [self creatDir:DownloadFile];
? ? //創(chuàng)建子文件夾
? ? NSString *file = [DownloadFile stringByAppendingPathComponent:name];
? ? [self creatDir:file];
? ? //2.通過 M3U8 URL 進(jìn)行初始化
? ? 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.創(chuàng)建主文件夾
? ? [self creatDir:DownloadFile];
? ? //創(chuàng)建子文件夾
? ? 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];
? ? ? ? }
? ? }
? ? //清除臨時緩存內(nèi)的播放器
? ? 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];
? ? //循環(huán)刪除本地下載文件
? ? [deleteLisst enumerateObjectsUsingBlock:^(NSDictionary *? _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
? ? ? ? NSString *fileName = [obj objectOrNilForKey:@"videoEpisodeId"];
? ? ? ? [self creatDir:DownloadFile];
? ? ? ? //創(chuàng)建子文件夾
? ? ? ? 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];
? ? }];
? ? //最后刪除視頻數(shù)組里面的數(shù)據(jù)
? ? 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];
}
/**
創(chuàng)建文件夾
@param dirPath 文件夾名稱
@return 創(chuàng)建成功或失敗
*/
+ (BOOL)creatDir:(NSString *)dirPath {
? ? if ([[NSFileManager defaultManager] fileExistsAtPath:dirPath]) {//判斷dirPath路徑文件夾是否已存在,此處dirPath為需要新建的文件夾的絕對路徑
? ? ? ? NSLog(@"沙盒目錄已經(jīng)存在%@",dirPath);
? ? ? ? return NO;
? ? }else {
? ? ? ? [[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];//創(chuàng)建文件夾
? ? ? ? NSLog(@"創(chuàng)建成功%@",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;
}
//設(shè)置本地視頻下載狀態(tài)為已下載
+ (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:@"已經(jīng)在后臺下載了!"];
? ? ? ? });
? ? ? ? return;
? ? }
? ? //4.判斷本地有無下載
? ? if ([BYM3U3DownLoadManger checkDownloadHaveVideoWith:videoId]) {
? ? ? ? asyncMainBlock(^{
? ? ? ? ? ? [kKeyWindow makeToast:@"本地已存在下載文件!"];
? ? ? ? });
? ? ? ? return;
? ? }
? ? [downloader start];
? ? downloader.downloadTSSuccessExeBlock = ^(NSString *event) {
? ? ? ? NSLog(@"這里是每一個TS下載完成的回調(diào)方法:%@", event);
? ? };
? ? downloader.downloadStartExeBlock = ^(void) {
? ? ? ? NSLog(@"這里是開始下載的回調(diào)方法");
? ? };
? ? //下載影片信息存儲在本地
? ? 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:@"已開始后臺下載,請到下載列表查看"];
? ? });
}
其他的就是下載進(jìn)度的一些展示了,播放的方法在這里
//開始播放
? ? ? ? ? ? ? ? [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'
后面的以后再說吧。