ResourceLoaderDelegate實(shí)現(xiàn)AVPlayer緩存邊播邊下視頻播放器

前言

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所在的層:

ResouceLoader層次圖

其中核心類:

  • 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了。大意是這樣:


拼接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)流程圖如下:

架構(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)如下圖所示:

緩存目錄結(jié)構(gòu)

我是以每個(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)化的方案哦,謝謝~

KWResourceLoader

demo效果圖:

demo截圖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痰催,一起剝皮案震驚了整個(gè)濱河市兜辞,隨后出現(xiàn)的幾起案子迎瞧,更是在濱河造成了極大的恐慌,老刑警劉巖逸吵,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凶硅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡扫皱,警方通過查閱死者的電腦和手機(jī)足绅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來韩脑,“玉大人氢妈,你說我怎么就攤上這事《味啵” “怎么了首量?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)进苍。 經(jīng)常有香客問我加缘,道長(zhǎng),這世上最難降的妖魔是什么琅捏? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任生百,我火速辦了婚禮,結(jié)果婚禮上柄延,老公的妹妹穿的比我還像新娘蚀浆。我一直安慰自己,他們只是感情好搜吧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布市俊。 她就那樣靜靜地躺著,像睡著了一般滤奈。 火紅的嫁衣襯著肌膚如雪摆昧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天蜒程,我揣著相機(jī)與錄音绅你,去河邊找鬼。 笑死昭躺,一個(gè)胖子當(dāng)著我的面吹牛忌锯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播领炫,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼偶垮,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起似舵,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤脚猾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后砚哗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體龙助,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蛛芥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泌参。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡常空,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盖溺,到底是詐尸還是另有隱情漓糙,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布烘嘱,位于F島的核電站昆禽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝇庭。R本人自食惡果不足惜醉鳖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哮内。 院中可真熱鬧盗棵,春花似錦、人聲如沸北发。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琳拨。三九已至瞭恰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狱庇,已是汗流浹背惊畏。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留密任,地道東北人颜启。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像批什,于是被迫代替她去往敵國(guó)和親农曲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容