HLS直播(M3U8)回看和下载功能的实现,SpringBoot项目瘦身指南

/注意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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

img
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip1024b 备注Java获取(资料价值较高,非无偿)
img

总结

虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。

上面提到的关于这些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 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。

  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值