前言
iOS多媒體播放主要有2個(gè)技術(shù)層框架可以實(shí)現(xiàn):
AVFoundation庫(kù):OC語(yǔ)言對(duì)底層進(jìn)行封裝的高級(jí)層接口盛垦,其中處理音頻腾夯、視頻播放功能的是AVPlayer俯在。優(yōu)點(diǎn):由于AVPlayer已經(jīng)對(duì)底層諸如音視頻采集跷乐、解編碼等細(xì)節(jié)封裝了愕提,應(yīng)用層不需要關(guān)心這些實(shí)現(xiàn)細(xì)節(jié)浅侨,所以使用簡(jiǎn)單如输,普通開發(fā)者可以不用知道什么是碼率、采樣率等音視頻專業(yè)知識(shí)澳化,即可實(shí)現(xiàn)音視頻播放的功能缎谷。缺點(diǎn)就是:由于高度封裝列林,靈活性較差希痴,例如沒有開放諸如緩存的存取的API润梯,給開發(fā)者控制視頻緩存帶來了難度纺铭。
AudioToolBox: 采用較底層的C語(yǔ)音實(shí)現(xiàn)的音視頻的采集舶赔、I/O處理竟纳、解碼锥累、編碼桶略、PCM等處理的API集合际歼。優(yōu)點(diǎn)是:靈活度高,開發(fā)者可以開發(fā)出專業(yè)的音視頻播放軟件吕粗,主要是供音視頻專業(yè)技術(shù)開發(fā)者使用旭愧。但對(duì)于非音視頻專業(yè)的普通iOS開發(fā)者并不友好颅筋,對(duì)音視頻領(lǐng)域不是很了解的話,有一定的門檻输枯。
作為非音視頻專業(yè)領(lǐng)域垃沦,只是個(gè)APP應(yīng)用的iOS開發(fā)者,AudioToolBox是沒有把握的用押,寫出來也是一堆bug哈哈~
所以主要還是利用AVPlayer實(shí)現(xiàn)播放器,可是如果想播放完一次視頻后蜻拨,下次可以利用緩存播放,AVPlayer并不提供緩存API桩引,我們沒法知道AVPlayer的緩存在哪里缎讼。經(jīng)過研究發(fā)現(xiàn),目前實(shí)現(xiàn)帶緩存功能的AVPlayer播放器主要從2個(gè)方向:
- 在播放視頻的同時(shí)坑匠,開啟一個(gè)線程下載該視頻URL血崭。
- 利用AVAssetResourceLoaderDelegate控制視頻數(shù)據(jù)流的請(qǐng)求。
毫無(wú)疑問,第一種方案播放一個(gè)視頻夹纫,需要耗費(fèi)用戶2倍的流量咽瓷;而第二種方案只要一遍的流量,既播放了視頻舰讹、又緩存了視頻茅姜,所以,我的技術(shù)方案就是采用AVAssetResourceLoaderDelegate實(shí)現(xiàn)月匣。
方案思路
AVAssetResourceLoaderDelegate
首先了解一下AVAssetResourceLoaderDelegate所在的層:
其中核心類:
AVAssetResourceLoader:這個(gè)類負(fù)責(zé)多媒體(音視頻)二進(jìn)制數(shù)據(jù)的加載(下載)钻洒,然后回調(diào)給上層Asset,讓視頻播放。但是這個(gè)類作為AVURLAsset是只讀屬性锄开,但是它允許下面這個(gè)代理去如何加載數(shù)據(jù)資源素标。
AVAssetResourceLoaderDelegate:它是一個(gè)協(xié)議,那么任何實(shí)現(xiàn)了該協(xié)議的對(duì)象都可以充當(dāng)AVAssetResourceLoader的代理來指示視頻數(shù)據(jù)的加載萍悴,既然數(shù)據(jù)資源可以有開發(fā)人員自行加載然后再回填給播放器头遭,那么緩存就可以有自己控制了,OK退腥,這就是我們這個(gè)方案的思路任岸。
注意:通過測(cè)試發(fā)現(xiàn),如果給AVURLAssert設(shè)置成正辰屏酰可以下載的URL時(shí)享潜,AVAssetResourceLoaderDelegate的代理是不觸發(fā)的,很可能的推測(cè)就是AVAssetResourceLoader解析資源URL做了判斷(偽碼):
if (URL可以自行解析下載) {
內(nèi)部自己解析...
} else {
由外部AVAssetResourceLoaderDelegate解析
}
所以嗅蔬,我們?yōu)榱俗孉VURLAssert強(qiáng)行走外部代理解析剑按,我們可以故意給AVURLAssert傳一個(gè)不合法的URL(為了讓AVAssetResourceLoader不能正常解析URL),我們可以在正確的URL前面拼接約定好的標(biāo)識(shí)澜术,然后在后面我們真正去下載前艺蝴,再將特定的標(biāo)識(shí)去掉即可得到能正常下載的URL了。大意是這樣:
架構(gòu)設(shè)計(jì)
- KWResourceLoader:該類負(fù)責(zé)AVAssetResourceLoaderDelegate代理的2個(gè)實(shí)現(xiàn)方法:
resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
上面shouldWait代理表示要等待加載的資源鸟废,在播放中會(huì)觸發(fā)多次猜敢,以便于分片加載資源,resourceLoader中5個(gè)我們需要關(guān)心的:
1.request:請(qǐng)求資源的URL
2.contentInfomationRequest:這個(gè)里面包含了該音視頻資源的頭部信息盒延,如視頻的格式缩擂、總長(zhǎng)度字節(jié)數(shù)、是否支持分片下載等重要信息添寺。這些信息需要我們下載視頻的時(shí)候自行填充這些信息胯盯,以便AVPlayer 播放前知道視頻的duration和格式信息,如果我們不填充視頻頭信息计露,視頻是無(wú)法播放的博脑,這點(diǎn)是需要注意的地方憎乙。
3.dataRequest:這個(gè)里面含有每次分片加載資源的位置offset和請(qǐng)求的長(zhǎng)度length信息,以便于我們下載器分片下載對(duì)應(yīng)的data.
4.finishWithLoading/withError: 每次音視頻data片段加載加載完畢后叉趣,我們要finishLoading ,目的是通知播放器本次資源加載結(jié)束泞边,那么AVAssetResourceLoaderDelegate就又會(huì)觸發(fā)shouldWait方法讓我們繼續(xù)加載后面的data,如此反復(fù),直到資源data全部加載完畢君账。
5.responseWithData: 在finishLoading之前繁堡,我們要將不斷下載得到的data數(shù)據(jù)不斷的塞給resouceLoader,以便播放器在一邊下載數(shù)據(jù)的同時(shí)一邊開始播放乡数。
整體架構(gòu)流程圖如下:
實(shí)現(xiàn)細(xì)節(jié):
KWResouceLoader
- 給AVURLAsset的URL添加特定頭部椭蹄,以便resouceLoader不能正常解析,從而觸發(fā)shouldWait净赴。
- (NSURL *)assetURLWithURL:(NSURL *)url {
if (!url) {
return nil;
}
NSURL *assetURL = [NSURL URLWithString:[kCacheScheme stringByAppendingString:[url absoluteString]]];
return assetURL;
}
然后把拼接的URL傳給AVURLAsset:
//將URL拼接特定標(biāo)識(shí)绳矩,目的是讓AVURLAsset不能自行下載,從而觸發(fā)shouldwait
url = [self.loadManager assetURLWithURL:url];
self.asset = [AVURLAsset URLAssetWithURL:url options:nil];
[self.asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
設(shè)置resouceLoader的delegate, 即可觸發(fā)下面代理方法:
#pragma mark - AVAssetResourceLoaderDelegate
//開始等待加載資源
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest NS_AVAILABLE(10_9, 6_0) {
NSURL *resourceURL = [loadingRequest.request URL];
[KWLog kwLog:@"開始等待資源:%lld-%ld",loadingRequest.dataRequest.requestedOffset,
(long)loadingRequest.dataRequest.requestedLength];
if ([resourceURL.absoluteString hasPrefix:kCacheScheme]) {
//將該資源請(qǐng)求放入待下載列表里
[self.loadManager addResourceLoadReqeust:loadingRequest];
return YES;
}else {
return NO;
}
}
//取消下載觸發(fā)
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest NS_AVAILABLE(10_9, 7_0) {
[KWLog kwLog:@"取消加載的資源:%lld-%ld",loadingRequest.dataRequest.requestedOffset,
(long)loadingRequest.dataRequest.requestedLength];
//取消下載
[self.loadManager cancelResourceLoadReqeust:loadingRequest];
}
上面第一個(gè)是將要加載加載某個(gè)URL片段玖翅,這個(gè)會(huì)多次觸發(fā)翼馆,而且可能一次可能會(huì)觸發(fā)多次片段請(qǐng)求,所以我們應(yīng)該用一個(gè)數(shù)組來保存每次的request金度,最后在全部加載完后移除应媚。
第二個(gè)是觸發(fā)取消下載的委托:通過大量的測(cè)試發(fā)現(xiàn),這個(gè)取消觸發(fā)一般有2種情況下回出現(xiàn):
當(dāng)前request片段較長(zhǎng)猜极,一般是一個(gè)請(qǐng)求至尾的大片段中姜,而當(dāng)前網(wǎng)絡(luò)加載data資源網(wǎng)速欠佳,resouceLoader會(huì)取消這次請(qǐng)求跟伏,然后改成多個(gè)小分片請(qǐng)求丢胚,以保證播放的流暢性。
用戶進(jìn)行seek操作受扳。當(dāng)用戶拖動(dòng)進(jìn)度至一個(gè)尚未下載(加載)的進(jìn)度的時(shí)候携龟,為了立即加載新的進(jìn)度的資源,會(huì)把之前正在加載的請(qǐng)求取消掉勘高。
當(dāng)觸發(fā)了取消代理時(shí)峡蟋,我們應(yīng)該把正在下載的Task cancel掉,以節(jié)省用戶的流量华望。當(dāng)然层亿,如果你不取消之前的Task也是可以的,這里我還是遵從Apple的代理立美,將正在下載的Task取消吧。
KWResouceLoader這個(gè)類不負(fù)責(zé)具體資源的加載方灾、取消邏輯建蹄,它委托了KWResouceLoaderManager這個(gè)類負(fù)責(zé):
@protocol KWResourceLoadManagerDelegate <NSObject>
@required
//開始填充頭部信息
- (void)resouceLoadManager:(KWResourceLoadManager *)manager
fillContentInfomation:(KWHttpInfomation *)infomation
loadReqeust:(AVAssetResourceLoadingRequest *)request;
//接收數(shù)據(jù)
- (void)resouceLoadManager:(KWResourceLoadManager *)manager
didReceiveData:(NSData *)data
loadReqeust:(AVAssetResourceLoadingRequest *)request;
//加載資源結(jié)束
- (void)resouceLoadManager:(KWResourceLoadManager *)manager
didCompleteWithError:(nullable NSError *)error
loadReqeust:(AVAssetResourceLoadingRequest *)request;
@optional
//資源加載進(jìn)度
- (void)resouceLoadManager:(KWResourceLoadManager *)manager
resourceLoadProgress:(float)progress
loadReqeust:(AVAssetResourceLoadingRequest *)request;
@end
fillContentInfomation: 得到資源的頭信息碌更,為播放做準(zhǔn)備
didReceiveData:得到的data調(diào)用responseWithData塞給播放器播放。
completeWithError: 本次request結(jié)束洞慎,觸發(fā)下一輪資源請(qǐng)求痛单。
KWResouceLoaderManager
該類是整個(gè)框架的核心,它維護(hù)了一個(gè)所有要加載資源的隊(duì)列劲腿,并實(shí)現(xiàn)了一個(gè)消費(fèi)-生產(chǎn)模式旭绒,以保證了加載的順序,以及判斷從本地緩存還是網(wǎng)絡(luò)下載該片段焦人。
消費(fèi)-生產(chǎn)模式
當(dāng)shouldWait觸發(fā)時(shí)挥吵,說明有新的請(qǐng)求過來了,我們首先將request加到隊(duì)列中花椭,然后判斷當(dāng)前是否繁忙(由于同一時(shí)刻只能有一個(gè)資源請(qǐng)求忽匈,所以我們應(yīng)該按順序請(qǐng)求資源):
- (void)addResourceLoadReqeust:(AVAssetResourceLoadingRequest *)request {
[self.loadRequestArray addObject:request];
if (self.isRunning) {
//當(dāng)前有正在加載的資源,新添加進(jìn)來資源矿辽,排隊(duì)
return;
}
//空閑狀態(tài)丹允,立即開始加載資源
[self beginLoadResource:request];
}
我用了isRunning標(biāo)識(shí)表示當(dāng)前是否有別的資源正在加載,如果有袋倔,將返回true,新的資源只能待定雕蔽,否則即時(shí)“空閑狀態(tài)”,可以立刻加載新的資源宾娜。
判斷從緩存還是下載獲取資源
根據(jù)request我們獲取要請(qǐng)求的Range:
NSURL *URL = [self originURL:request.request.URL];
long long offset = request.dataRequest.requestedOffset;
long long length = request.dataRequest.requestedLength;
然后根據(jù)range判斷本地是否有該判斷緩存批狐,如果沒有,則下載:
[KWFileManager readLocalBytesOfURL:URL range:NSMakeRange(offset, length) finish:^(NSData * _Nonnull data, NSError * _Nonnull error) {
if (data && !error) {
[KWLog kwLog:@"使用緩存"];
[self finishLoadRequest:request withLocalCacheData:data];
//加載下一個(gè)資源
[self loadNextToLoadedResource:request];
}else {
[KWLog kwLog:@"沒有緩存碳默,開始下載資源"];
NSURLSessionTask *task = [self.downloader downloadURL:URL range:NSMakeRange(offset, length)];
[self.downloadTasks addObject:task];
}
}];
如果本地緩存獲取到data贾陷,(緩存存取由KWFileManager實(shí)現(xiàn),后面再具體說)則將data和頭信息回調(diào)給上層嘱根,然后繼續(xù)加載下一個(gè)資源髓废;如果沒有緩存,則開啟一個(gè)下載任務(wù)该抒。
downloader是由KWResouceDowloader實(shí)現(xiàn):
下載我主要是采用NSURLSessionTask實(shí)現(xiàn)慌洪,由于要指定下載片段,所以我們r(jià)equest要設(shè)置HTTPHeaderField字段range:
- (NSURLSessionTask *)downloadURL:(NSURL *)URL range:(NSRange)range {
self.URL = URL;
self.offset = range.location;
self.length = range.length;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
long long endOffset = self.offset + self.length - 1;
NSString *httpRange = [NSString stringWithFormat:@"bytes=%lld-%lld",self.offset,endOffset];
[request setValue:httpRange forHTTPHeaderField:@"Range"];
NSURLSessionTask *task = [self.session dataTaskWithRequest:request];
[task resume];
self.isDownloading = YES;
self.task = task;
return task;
}
這樣凑保,就會(huì)下載該音視頻指定位置長(zhǎng)度的二進(jìn)制文件冈爹,而不是整部下載,所以這是一個(gè)分片下載器欧引。
然后有SessionDelegate代理得到下載的結(jié)果:
#pragma mark - NSURLSessionDelegate
//開始接受數(shù)據(jù)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//設(shè)置contentInformation
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
if ([self.delegate respondsToSelector:@selector(downloader:didReceiveData:task:)]) {
[self.delegate downloader:self didReceiveData:data task:dataTask];
}
}
//下載完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
[KWLog kwLog:@"下載結(jié)束"];
self.isDownloading = NO;
if ([self.delegate respondsToSelector:@selector(downloader:didCompleteWithError:task:)]) {
[self.delegate downloader:self didCompleteWithError:error task:task];
}
}
關(guān)于contentInfomation
在上面didResponse開始返回?cái)?shù)據(jù)前频伤,我們可以提取該資源的格式、長(zhǎng)度信息:
KWHttpInfomation *info = [[KWHttpInfomation alloc] init];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response;
NSString *acceptRange = HTTPURLResponse.allHeaderFields[@"Accept-Ranges"];
info.byteRangeAccessSupported = [acceptRange isEqualToString:@"bytes"];
//考慮到絕大部分服務(wù)器都支持bytes,這里就全部設(shè)置為支持
info.byteRangeAccessSupported = YES;
info.contentLength = [[[HTTPURLResponse.allHeaderFields[@"Content-Range"]
componentsSeparatedByString:@"/"] lastObject] longLongValue];
if (info.contentLength == 0) {
info.contentLength = [HTTPURLResponse.allHeaderFields[@"Content-Length"] longLongValue];
}
}
NSString *mimeType = response.MIMEType;
CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,
(__bridge CFStringRef)(mimeType),
NULL);
info.contentType = CFBridgingRelease(contentType);
if ([self.delegate respondsToSelector:@selector(downloader:informatiion:task:)]) {
[self.delegate downloader:self informatiion:info task:dataTask];
}
[KWLog kwLog:@"%@",info.debugDescription];
//緩存info
[KWFileManager saveContentInfomation:info URL:dataTask.originalRequest.URL];
注意點(diǎn):
1.info.byteRangeAccessSupported 這里我本來是根據(jù)headerFields獲取是否支持分片下載芝此,后面我發(fā)現(xiàn)很多視頻headerField并沒有指明是否支持分片下載憋肖,但是測(cè)試發(fā)現(xiàn)因痛,這些未指明的視頻都是可以分片下載的,然后我網(wǎng)上查了一下岸更,發(fā)現(xiàn)基本95%以上的服務(wù)器是支持分片下載的鸵膏,所以這里我全部默認(rèn)為可分片下載了。
2.獲取的頭信息由于以便于下次不用下載怎炊,所以這里要寫入本地緩存谭企。
3.提供Task cancel功能,以便于外部可隨時(shí)取消該資源下載评肆。
KWFileManager
該文件負(fù)責(zé)緩存的讀取债查、存儲(chǔ)、清理等功能糟港。
由于是緩存攀操,所以,我放在了Cache主目錄下秸抚,然后新建了音視頻根目錄:
+ (NSString *)cacheRootPath {
//將下載默認(rèn)存放地址移到緩存目錄下
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
NSUserDomainMask, YES) lastObject];
//緩存根目錄
NSString *mediaPath = [cachePath stringByAppendingPathComponent:kMediaCachePath];
return mediaPath;
}
1.將URL的Md5值作為某個(gè)資源的文件夾名稱
由于每個(gè)每個(gè)資源URL長(zhǎng)度不一速和,考慮到Md5唯一性和長(zhǎng)度一致的特性,所以講md5值作為key是非常合適的剥汤。
2.某個(gè)片段文件名以“startOffset_endOffset_sugguestName”命名
NSString *fileName = [NSString stringWithFormat:@"%lld_%lld_%@",offset,
endOffset,task.response.suggestedFilename];
這樣存儲(chǔ)的文件最終形態(tài)如下圖所示:
我是以每個(gè)下載請(qǐng)求的Range+name作為文件名颠放,這樣文件名稱就包含了資源的位置長(zhǎng)度,以便于下次快速檢索從緩存讀取的文件吭敢。
那么碰凶,由于下次請(qǐng)求的位置和長(zhǎng)度不可能剛好和緩存中的一樣長(zhǎng),那么這里采取了2種方式:
- 1.優(yōu)先單個(gè)碎片文件鹿驼,取子集:
for (NSString *fileName in files) {
NSArray *components = [fileName componentsSeparatedByString:@"_"];
if (components.count >= 3) {
NSString *offset = [components firstObject];
NSString *endOffset = components[1];
if(start == 0 && end == 1) {
if (start == [offset longLongValue] &&
end == [endOffset longLongValue]) {
//落在本地某個(gè)片段內(nèi)
targetFileName = fileName;
break;
}
}else if (start >= [offset longLongValue] && end <= [endOffset longLongValue] &&
(start != 0 && end != 1)) {
//落在本地某個(gè)片段內(nèi)
targetFileName = fileName;
break;
}
}
}
- 跨碎片拼接:有些碎片頭尾是可以連接成一個(gè)大的碎片的欲低,然后再去子集:
//嘗試垮碎片查找
NSString *firstFileName = [self fileNameOffset:start files:files];
if (firstFileName) {
NSRange firstRange = [self fileRange:firstFileName];
if (firstRange.length != 0) {
long long firstEnd = firstRange.location + firstRange.length - 1;
NSMutableArray *sequentFiles = [NSMutableArray arrayWithObject:firstFileName];
[self getNextSequentFileNameByLastEnd:firstEnd files:files output:&sequentFiles];
if (sequentFiles.count > 1) {
NSString *lastFileName = [sequentFiles lastObject];
NSRange lastRange = [self fileRange:lastFileName];
if (lastRange.location + lastRange.length - 1 >= end) {
[readFiles addObjectsFromArray:sequentFiles];
}
}
}
}
上面在查找下一個(gè)連續(xù)的碎片采取了遞歸方式:
+ (void)getNextSequentFileNameByLastEnd:(long long)lastEnd files:(NSArray *)files output:(NSMutableArray **)outputArray {
NSString *findFileName = nil;
for (NSString *fileName in files) {
NSRange range = [self fileRange:fileName];
if (range.length != 0) {
if (range.location == lastEnd + 1) {
findFileName = fileName;
break;
}
}
}
if (findFileName) {
NSRange fileRange = [self fileRange:findFileName];
if (fileRange.length != 0) {
long long end = fileRange.location + fileRange.length - 1;
NSMutableArray *outPut = *outputArray;
[outPut addObject:findFileName];
[self getNextSequentFileNameByLastEnd:end files:files output:&outPut];
}
}
}
以上是緩存處理文件的主要難點(diǎn)。其他的文件寫入畜晰、刪除常規(guī)操作砾莱,故不再敘述。
以上就是我這個(gè)項(xiàng)目的主要細(xì)節(jié)凄鼻。
項(xiàng)目評(píng)價(jià)
- 優(yōu)點(diǎn)
1.實(shí)現(xiàn)了邊播放邊緩存資源
2.支持seek操作腊瑟,可立即從新的進(jìn)度繼續(xù)播放,較流暢
3.支持片段緩存块蚌,而不是僅僅整體緩存闰非,節(jié)省了用戶流量
4.支持緩存自定義清理,可以清理單個(gè)資源文件
- 待優(yōu)化點(diǎn)
1.某些資源頻繁cancel請(qǐng)求的問題峭范,當(dāng)?shù)谝淮斡|發(fā)了cancel請(qǐng)求后财松,第二次請(qǐng)求過程中,即便有部分緩存纱控,視頻不會(huì)立即播放辆毡,必須等待本次片段全部請(qǐng)求完畢才開始播放政敢。這個(gè)原因尚不明確,因?yàn)閞esouceLoader是個(gè)黑匣子胚迫,理論上,下載過程中是不必等到finishLoading完畢后才播放資源唾那,只要resonseWithData:給緩沖區(qū)塞給了足夠可播放的data即可播放访锻,但是第一次cancel后新價(jià)值的資源總是要帶到加載完畢才播放,用戶等待較長(zhǎng)闹获,但是一般出現(xiàn)該請(qǐng)求都是弱網(wǎng)絡(luò)產(chǎn)生期犬,一般情況下流暢度不錯(cuò)。
2.緩存獲取我的方案理論上不是最優(yōu)解避诽,因?yàn)槲覜]有考慮本地有部分緩存龟虎,另外部分需要下載的情況。但是沙庐,這中方案理論上會(huì)多出很多的請(qǐng)求次數(shù)鲤妥,增加了請(qǐng)求次數(shù)和復(fù)雜度,所以最終沒有采用拱雏,不排除后續(xù)會(huì)優(yōu)化這一處棉安。
- 總結(jié)
整體項(xiàng)目前后花了大約1星期的時(shí)間完成,由于以前對(duì)AVAssetResourceLoader并沒有接觸過铸抑,很多屬性和方法都是自己摸索著嘗試贡耽,通過大量的測(cè)試,基本上摸清了resouceLoaderDelegate的”脾氣“鹊汛。
整體效果比較滿意蒲赂,當(dāng)然,網(wǎng)上也研究了別的同學(xué)的方案刁憋,最終我還是采用了自己的緩存思路實(shí)現(xiàn)滥嘴,鍛煉了能力,雖然不是最優(yōu)解职祷,但是經(jīng)過了大量?jī)?yōu)化測(cè)試氏涩,bug較少,現(xiàn)在將項(xiàng)目開源出來有梆,和大家一起分享是尖,如果有覺得這個(gè)方案對(duì)自己有用,就給個(gè)star 吧泥耀,同時(shí)以熱烈歡迎對(duì)音視頻有興趣的同學(xué)和我留言饺汹,繼續(xù)探討更優(yōu)化的方案哦,謝謝~
demo效果圖: