Authorized reprint, Ming Tsai Su (Pinterest)
In the previous article, "Using Avplayer to play network music" introduced the basic use of Avplayer, the following describes how to implement Avplayer cache through Avassetresourceloader.
Demand Grooming
No tool can be used in all scenarios, in the process of using avplayer, we will find that it has many limitations, such as playing network music, often do not control its internal playback logic, for example, we will find that the playback of seek will fail, after loading the data can not get to the data file for other operations, So we need to find a way to compensate for its shortcomings, and here we have chosen Avassetresourceloader.
Avassetresourceloader: Let us take control of the loading of avplayer data, including obtaining information about the data avplayer needs, and how much data can be transferred to Avplayer.
The location of the Avassetresourceloader in Avplayer is as follows :
Achieve the core
There are ways to implement avassetresourceloaderdelegate using Avassetresourceloader:
-(BOOL) Resourceloader: (Avassetresourceloader *) Resourceloadershouldwaitforloadingofrequestedresource: ( Avassetresourceloadingrequest *) Loadingrequest;
The proxy method that requires loading the resource, we need to save loadingrequest and read or download the data specified by it, and when the data is read or downloaded, we can complete the operation on Loadingrequest.
-(void) Resourceloader: (Avassetresourceloader *) Resourceloaderdidcancelloadingrequest: ( Avassetresourceloadingrequest *) Loadingrequest;
To cancel the proxy method of loading the resource, we need to cancel the read or download operation of the data specified by Loadingrequest.
Implementation strategy
There are many strategies to implement the cache through Avassetresourceloader, there are no absolute advantages and disadvantages, as long as it meets our actual needs.
Here's an example of how the Avassetresourceloader implementation of caching is illustrated by imitating Penguin music.
First observe and guess the cache strategy of Penguin Music (of course it is not played with Avplayer):
1, start playing, and start to download the full file, when the file download is complete, save to the cache folder;
2. When seek
(1) If seek to the part that has been downloaded, direct seek succeeds; (such as download Progress 60%,seek Progress 50%)
(2) If seek to the part that is not downloaded, start a new download (such as download Progress 60%,seek Progress 70%)
PS1: The file download scope is now 70%-100%
PS2: The previously downloaded section was deleted
PS3: Repeat step 2 if there is another seek operation, and if you seek to progress 40% at this point, a new download will start (range 40%-100%)
3, when the beginning of a new download, because the file is incomplete, after the download is completed will not be saved to the cache folder;
4, the next time you play the same song, if it exists in the cache folder, then play the cache file directly;
Implementation process
Process:
1. Create Avplayer by customizing scheme and assign Proxy to Avurlasset (Suplayer object)
Avurlasset * Asset = [Avurlasset urlassetwithurl:[self.url customschemeurl] options:nil]; [Asset.resourceloader setDelegate:self.resourceLoader queue:dispatch_get_main_queue ()];self.currentitem = [ Avplayeritem Playeritemwithasset:asset];self.player = [Avplayer PlayerWithPlayerItem:self.currentItem];
2, Agent Implementation Avassetresourceloader Agent method (Suresourceloader object)
-(BOOL) Resourceloader: (Avassetresourceloader *) Resourceloader Shouldwaitforloadingofrequestedresource: ( Avassetresourceloadingrequest *) loadingrequest {[Self addloadingrequest:loadingrequest]; return YES;} -(void) Resourceloader: (Avassetresourceloader *) Resourceloader didcancelloadingrequest: ( Avassetresourceloadingrequest *) loadingrequest {[Self removeloadingrequest:loadingrequest];}
3, the treatment of Loadingrequest (Addloadingrequest method)
(1) Add it to the Requestlist
[Self.requestlist Addobject:loadingrequest];
(2) If the download has not started, the request data is started, otherwise the download of the data is still pending
[Self newtaskwithloadingrequest:loadingrequest cache:yes];
(3) If the loadingrequest after seek, determine where the request started, and if it is already buffered, read the data directly
if (LoadingRequest.dataRequest.requestedOffset >= self.requestTask.requestOffset && LoadingRequest.dataRequest.requestedOffset <= Self.requestTask.requestOffset + self.requestTask.cacheLength) {[ Self processrequestlist];}
3.4 If it is not buffered, re-request
if (self.seekrequired) {[self newtaskwithloadingrequest:loadingrequest cache:no];}
4. Processing of data requests (Newtaskwithloadingrequest method)
(1) First determine if there is already a download task, if any, then cancel the task first
if (self.requesttask) {filelength = Self.requestTask.fileLength; Self.requestTask.cancel = YES;}
(2) Create a new request, set up an agent
Self.requesttask = [[Surequesttask alloc]init];self.requesttask.requesturl = LoadingRequest.request.URL; Self.requestTask.requestOffset = Loadingrequest.datarequest.requestedoffset;self.requesttask.cache = Cache;if ( Filelength > 0) {self.requestTask.fileLength = Filelength;} Self.requestTask.delegate = self; [Self.requesttask start];self.seekrequired = NO;
5. Data response Processing (Processrequestlist method)
Fills the response data for the loadingrequest inside the requestlist and removes it from requestlist if it is fully responsive
-(void) processrequestlist {Nsmutablearray * finishrequestlist = [Nsmutablearray array]; For (Avassetresourceloadingrequest * loadingrequest-Self.requestlist) {if ([self finishloadingwithloadingrequest:lo Adingrequest]) {[Finishrequestlist addobject:loadingrequest]; }} [Self.requestlist removeobjectsinarray:finishrequestlist];}
The process for populating the response data is as follows:
(1) Fill in Contentinformationrequest information, note contentlength need to fill in the total length of the downloaded file, contenttype need to convert
Cfstringref ContentType = Uttypecreatepreferredidentifierfortag (Kuttagclassmimetype, (__bridge CFStringRef) ( MimeType), NULL); loadingRequest.contentInformationRequest.contentType = Cfbridgingrelease (ContentType); loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES; LoadingRequest.contentInformationRequest.contentLength = Self.requestTask.fileLength;
(2) Calculate the length of the data that can be responded to, note that the starting position of the data read is the current Avplayer current playback position, the end position is the end position of the loadingrequest or the location where the file is currently downloaded
Nsuinteger cachelength = self.requestTask.cacheLength; Nsuinteger Requestedoffset = loadingrequest.datarequest.requestedoffset;if ( LoadingRequest.dataRequest.currentOffset! = 0) {requestedoffset = LoadingRequest.dataRequest.currentOffset;} Nsuinteger canreadlength = cachelength-(requestedoffset-self.requesttask.requestoffset); Nsuinteger respondlength = MIN (Canreadlength, loadingRequest.dataRequest.requestedLength);
(3) Read the data and populate it with Loadingrequest
[Loadingrequest.datarequest respondwithdata:[sufilehandle Readtempfiledatawithoffset:requestedoffset- Self.requestTask.requestOffset Length:respondlength]];
(4) If you fully respond to the required data, complete the loadingrequest, and be aware of the location where the end of the response data ends >= Loadingrequest
Nsuinteger Nowendoffset = Requestedoffset + canreadlength; Nsuinteger Reqendoffset = LoadingRequest.dataRequest.requestedOffset + loadingRequest.dataRequest.requestedLength; if (Nowendoffset >= reqendoffset) {[Loadingrequest finishloading]; return YES;} return NO;
6, the time to deal with Requestlist
When there is a new loadingrequest or file download progress update, you need to process requestlist
7. New process for requesting task implementation (Surequesttask object)
(1) When initializing, you need to delete the old temporary file and create a new blank temporary file
-(Instancetype) init {if (self = [super init]) {[Sufilehandle createtempfile]; } return self;}
(2) Establish a new connection, if it is a request after seek, specify the scope of its request content
- (void) start { nsmutableurlrequest * request = [ Nsmutableurlrequest requestwithurl:[self.requesturl originalschemeurl] cachepolicy: nsurlrequestreloadignoringcachedata timeoutinterval:requesttimeout]; if ( self.requestoffset > 0) { [request addvalue:[nsstring stringwithformat:@ "Bytes=%ld-%ld", self.requestoffset, self.filelength - 1] forhttpheaderfield:@ "Range"]; } self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultsessionconfiguration] delegate:self delegatequeue:[nsoperationqueue mainqueue]]; self.task = [self.session dataTaskWithRequest:request]; [self.task resume];}
(3) When the data is received, the data is written to the temporary file, update the download progress, and notify the agent to process Requestlist
-(void) Urlsession: (Nsurlsession *) session Datatask: (Nsurlsessiondatatask *) datatask didreceivedata: (NSData *) Data {i F (self.cancel) return; [Sufilehandle Writetempfiledata:data]; Self.cachelength + = Data.length; if (self.delegate && [self.delegate respondstoselector: @selector (Requesttaskdidupdatecache)]) {[Self.deleg Ate Requesttaskdidupdatecache]; }}
(4) If the cached condition is satisfied when the download is complete, copy the temporary file to the cache folder
if (Self.cache) {[Sufilehandle cachetempfilewithfilename:[nsstring FileNameWithURL:self.requestURL];} if (self.delegate && [self.delegate respondstoselector: @selector (Requesttaskdidfinishloadingwithcache:)]) { [Self.delegate RequestTaskDidFinishLoadingWithCache:self.cache];}
Sample Demo
The above is the overall implementation process, of course, each person's thinking is different, you can understand it deep enough to use more efficient and more secure way to achieve.
This demo is available for download on my GitHub: Github:sucacheloader
This demo is a cache of Watercress FM songs (MP4 format) as an example, if you pursue a more perfect effect, you can start from the following aspects:
1, the cache format support processing: Not all file formats are supported Oh, for unsupported formats, you should not use the cache function;
2, the processing of various errors in the cache process: such as download timeout, connection failure, read data errors and so on processing;
3, the cache file naming processing, if the cache file does not have the suffix (such as. mp4), may cause the playback to fail;
4, Avplayer playback status of processing, to achieve the perfect playback experience, in this regard to the next point of effort;
Next:
The next step is to introduce Audiofilestream + Audioqueue to play local files, network files, cache implementations.
iOS Audio article: Avplayer cache implementation