/注意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
/**
- 下载成功
*/
- (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 delegate;
/**
- 创建M3U8文件
*/
- (void)createLocalM3U8file;
@end
下载器ZYLVideoDownLoader.m文件
#import “ZYLVideoDownLoader.h”
#import “M3U8SegmentModel.h”
#import “SegmentDownloader.h”
@interface ZYLVideoDownLoader ()
@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];
//拼接M3U8
tsStr = [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会从头开始播放。
TS文件下载器
上面的下载器将每一个TS文件单独封装,单独下载,下面我们来看看每一个TS文件是如何下载的
TS文件下载器 SegmentDownloader.h文件
#import <Foundation/Foundation.h>
@class SegmentDownloader;
@protocol SegmentDownloaderDelegate
/**
- 下载成功
*/
- (void)segmentDownloadFinished:(SegmentDownloader *)downloader;
/**
- 下载失败
*/
- (void)segmentDownloadFailed:(SegmentDownloader *)downloader;
/**
- 监听进度
*/
- (void)segmentProgress:(SegmentDownloader *)downloader TotalUnitCount:(int64_t)totalUnitCount completedUnitCount:(int64_t)completedUnitCount;
@end
@interface SegmentDownloader : NSObject
@property (nonatomic, copy) NSString *fileName;
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, copy) NSString *downloadUrl;
@property (assign, nonatomic) NSInteger duration;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip1024b 备注Java获取(资料价值较高,非无偿)
总结
虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。
上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料
有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。
提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**
[外链图片转存中…(img-EOKc5rRI-1711548094469)]
[外链图片转存中…(img-k3aL4fln-1711548094470)]
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip1024b 备注Java获取(资料价值较高,非无偿)
[外链图片转存中…(img-ySIiSxzl-1711548094470)]
总结
虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。
[外链图片转存中…(img-YbnVwNTa-1711548094470)]
[外链图片转存中…(img-wNxL287Z-1711548094471)]
上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料
有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。