- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest; - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest;
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:@"customscheme://host/myfile.mp3"] options:nil]; [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset]; [self addObserversForPlayerItem:item]; self.player = [AVPlayer playerWithPlayerItem:playerItem]; [self addObserversForPlayer];
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest{ NSURL *resourceURL = [loadingRequest.request URL]; if([resourceURL.scheme isEqualToString:@"customscheme"]){ LSFilePlayerResourceLoader *loader = [self resourceLoaderForRequest:loadingRequest]; if(loader==nil){ loader = [[LSFilePlayerResourceLoader alloc] initWithResourceURL:resourceURL session:self.session]; loader.delegate = self; [self.resourceLoaders setObject:loader forKey:[self keyForResourceLoaderWithURL:resourceURL]]; } [loader addRequest:loadingRequest]; return YES; } return NO; } - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest{ LSFilePlayerResourceLoader *loader = [self resourceLoaderForRequest:loadingRequest]; [loader removeRequest:loadingRequest]; }
@interface LSFilePlayerResourceLoader : NSObject @property (nonatomic,readonly,strong)NSURL *resourceURL; @property (nonatomic,readonly)NSArray *requests; @property (nonatomic,readonly,strong)YDSession *session; @property (nonatomic,readonly,assign)BOOL isCancelled; @property (nonatomic,weak)id<LSFilePlayerResourceLoaderDelegate> delegate; - (instancetype)initWithResourceURL:(NSURL *)url session:(YDSession *)session; - (void)addRequest:(AVAssetResourceLoadingRequest *)loadingRequest; - (void)removeRequest:(AVAssetResourceLoadingRequest *)loadingRequest; - (void)cancel; @end @protocol LSFilePlayerResourceLoaderDelegate <NSObject> @optional - (void)filePlayerResourceLoader:(LSFilePlayerResourceLoader *)resourceLoader didFailWithError:(NSError *)error; - (void)filePlayerResourceLoader:(LSFilePlayerResourceLoader *)resourceLoader didLoadResource:(NSURL *)resourceURL; @end
- (void)addRequest:(AVAssetResourceLoadingRequest *)loadingRequest{ if(self.isCancelled==NO){ NSURL *interceptedURL = [loadingRequest.request URL]; [self startOperationFromOffset:loadingRequest.dataRequest.requestedOffset length:loadingRequest.dataRequest.requestedLength]; [self.pendingRequests addObject:loadingRequest]; } else{ if(loadingRequest.isFinished==NO){ [loadingRequest finishLoadingWithError:[self loaderCancelledError]]; } } }
- (void)startOperationFromOffset:(unsigned long long)requestedOffset length:(unsigned long long)requestedLength{ [self cancelAllPendingRequests]; [self cancelOperations]; __weak typeof (self) weakSelf = self; void(^failureBlock)(NSError *error) = ^(NSError *error) { [weakSelf performBlockOnMainThreadSync:^{ if(weakSelf && weakSelf.isCancelled==NO){ [weakSelf completeWithError:error]; } }]; }; void(^loadDataBlock)(unsigned long long off, unsigned long long len) = ^(unsigned long long offset,unsigned long long length){ [weakSelf performBlockOnMainThreadSync:^{ NSString *bytesString = [NSString stringWithFormat:@"bytes=%lld-%lld",offset,(offset+length-1)]; NSDictionary *params = @{@"Range":bytesString}; id<YDSessionRequest> req = [weakSelf.session partialContentForFileAtPath:weakSelf.path withParams:params response:nil data:^(UInt64 recDataLength, UInt64 totDataLength, NSData *recData) { [weakSelf performBlockOnMainThreadSync:^{ if(weakSelf && weakSelf.isCancelled==NO){ LSDataResonse *dataResponse = [LSDataResonse responseWithRequestedOffset:offset requestedLength:length receivedDataLength:recDataLength data:recData]; [weakSelf didReceiveDataResponse:dataResponse]; } }]; } completion:^(NSError *err) { if(err){ failureBlock(err); } }]; weakSelf.dataOperation = req; }]; }; if(self.contentInformation==nil){ self.contentInfoOperation = [self.session fetchStatusForPath:self.path completion:^(NSError *err, YDItemStat *item) { if(weakSelf && weakSelf.isCancelled==NO){ if(err==nil){ NSString *mimeType = item.path.mimeTypeForPathExtension; CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType,(__bridge CFStringRef)(mimeType),NULL); unsigned long long contentLength = item.size; weakSelf.contentInformation = [[LSContentInformation alloc] init]; weakSelf.contentInformation.byteRangeAccessSupported = YES; weakSelf.contentInformation.contentType = CFBridgingRelease(contentType); weakSelf.contentInformation.contentLength = contentLength; [weakSelf prepareDataCache]; loadDataBlock(requestedOffset,requestedLength); weakSelf.contentInfoOperation = nil; } else{ failureBlock(err); } } }]; } else{ loadDataBlock(requestedOffset,requestedLength); } }
- (void)prepareDataCache{ self.cachedFilePath = [[self class] pathForTemporaryFile]; NSError *error = nil; if ([[NSFileManager defaultManager] fileExistsAtPath:self.cachedFilePath] == YES){ [[NSFileManager defaultManager] removeItemAtPath:self.cachedFilePath error:&error]; } if (error == nil && [[NSFileManager defaultManager] fileExistsAtPath:self.cachedFilePath] == NO) { NSString *dirPath = [self.cachedFilePath stringByDeletingLastPathComponent]; [[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error]; if (error == nil) { [[NSFileManager defaultManager] createFileAtPath:self.cachedFilePath contents:nil attributes:nil]; self.writingFileHandle = [NSFileHandle fileHandleForWritingAtPath:self.cachedFilePath]; @try { [self.writingFileHandle truncateFileAtOffset:self.contentInformation.contentLength]; [self.writingFileHandle synchronizeFile]; } @catch (NSException *exception) { NSError *error = [[NSError alloc] initWithDomain:LSFilePlayerResourceLoaderErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey:@"can not write to file"}]; [self completeWithError:error]; return; } self.readingFileHandle = [NSFileHandle fileHandleForReadingAtPath:self.cachedFilePath]; } } if (error != nil) { [self completeWithError:error]; } }
- (void)didReceiveDataResponse:(LSDataResonse *)dataResponse{ [self cacheDataResponse:dataResponse]; self.receivedDataLength=dataResponse.currentOffset; [self processPendingRequests]; }
- (void)cacheDataResponse:(LSDataResonse *)dataResponse{ unsigned long long offset = dataResponse.dataOffset; @try { [self.writingFileHandle seekToFileOffset:offset]; [self.writingFileHandle writeData:dataResponse.data]; [self.writingFileHandle synchronizeFile]; } @catch (NSException *exception) { NSError *error = [[NSError alloc] initWithDomain:LSFilePlayerResourceLoaderErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey:@"can not write to file"}]; [self completeWithError:error]; } }
- (NSData *)readCachedData:(unsigned long long)startOffset length:(unsigned long long)numberOfBytesToRespondWith{ @try { [self.readingFileHandle seekToFileOffset:startOffset]; NSData *data = [self.readingFileHandle readDataOfLength:numberOfBytesToRespondWith]; return data; } @catch (NSException *exception) {} return nil; }
- (void)processPendingRequests{ NSMutableArray *requestsCompleted = [[NSMutableArray alloc] init]; for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests){ [self fillInContentInformation:loadingRequest.contentInformationRequest]; BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; if (didRespondCompletely){ [loadingRequest finishLoading]; [requestsCompleted addObject:loadingRequest]; } } [self.pendingRequests removeObjectsInArray:requestsCompleted]; }
- (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest{ if (contentInformationRequest == nil || self.contentInformation == nil){ return; } contentInformationRequest.byteRangeAccessSupported = self.contentInformation.byteRangeAccessSupported; contentInformationRequest.contentType = self.contentInformation.contentType; contentInformationRequest.contentLength = self.contentInformation.contentLength; }
- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest{ long long startOffset = dataRequest.requestedOffset; if (dataRequest.currentOffset != 0){ startOffset = dataRequest.currentOffset; } // Don't have any data at all for this request if (self.receivedDataLength < startOffset){ return NO; } // This is the total data we have from startOffset to whatever has been downloaded so far NSUInteger unreadBytes = self.receivedDataLength - startOffset; // Respond with whatever is available if we can't satisfy the request fully yet NSUInteger numberOfBytesToRespondWith = MIN(dataRequest.requestedLength, unreadBytes); BOOL didRespondFully = NO; NSData *data = [self readCachedData:startOffset length:numberOfBytesToRespondWith]; if(data){ [dataRequest respondWithData:data]; long long endOffset = startOffset + dataRequest.requestedLength; didRespondFully = self.receivedDataLength >= endOffset; } return didRespondFully; }
@protocol YDSessionRequest <NSObject> - (void)cancel; @end - (id<YDSessionRequest>)fetchDirectoryContentsAtPath:(NSString *)path completion:(YDFetchDirectoryHandler)block; - (id<YDSessionRequest>)fetchStatusForPath:(NSString *)path completion:(YDFetchStatusHandler)block;
- (id<YDSessionRequest>)partialContentForFileAtPath:(NSString *)srcRemotePath withParams:(NSDictionary *)params response:(YDDidReceiveResponseHandler)response data:(YDPartialDataHandler)data completion:(YDHandler)completion;
- (id<YDSessionRequest>)partialContentForFileAtPath:(NSString *)srcRemotePath withParams:(NSDictionary *)params response:(YDDidReceiveResponseHandler)response data:(YDPartialDataHandler)data completion:(YDHandler)completion{ return [self downloadFileFromPath:srcRemotePath toFile:nil withParams:params response:response data:data progress:nil completion:completion]; } - (id<YDSessionRequest>)downloadFileFromPath:(NSString *)path toFile:(NSString *)aFilePath withParams:(NSDictionary *)params response:(YDDidReceiveResponseHandler)responseBlock data:(YDPartialDataHandler)dataBlock progress:(YDProgressHandler)progressBlock completion:(YDHandler)completionBlock{ NSURL *url = [YDSession urlForDiskPath:path]; if (!url) { completionBlock([NSError errorWithDomain:kYDSessionBadArgumentErrorDomain code:0 userInfo:@{@"getPath": path}]); return nil; } BOOL skipReceivedData = NO; if(aFilePath==nil){ aFilePath = [[self class] pathForTemporaryFile]; skipReceivedData = YES; } NSURL *filePath = [YDSession urlForLocalPath:aFilePath]; if (!filePath) { completionBlock([NSError errorWithDomain:kYDSessionBadArgumentErrorDomain code:1 userInfo:@{@"toFile": aFilePath}]); return nil; } YDDiskRequest *request = [[YDDiskRequest alloc] initWithURL:url]; request.fileURL = filePath; request.params = params; request.skipReceivedData = skipReceivedData; [self prepareRequest:request]; NSURL *requestURL = [request.URL copy]; request.callbackQueue = _callBackQueue; request.didReceiveResponseBlock = ^(NSURLResponse *response, BOOL *accept) { if(responseBlock){ responseBlock(response); } }; request.didGetPartialDataBlock = ^(UInt64 receivedDataLength, UInt64 expectedDataLength, NSData *data){ if(progressBlock){ progressBlock(receivedDataLength,expectedDataLength); } if(dataBlock){ dataBlock(receivedDataLength,expectedDataLength,data); } }; request.didFinishLoadingBlock = ^(NSData *receivedData) { if(skipReceivedData){ [[self class] removeTemporaryFileAtPath:aFilePath]; } NSDictionary *userInfo = @{@"URL": requestURL, @"receivedDataLength": @(receivedData.length)}; [[NSNotificationCenter defaultCenter] postNotificationInMainQueueWithName:kYDSessionDidDownloadFileNotification object:self userInfo:userInfo]; completionBlock(nil); }; request.didFailBlock = ^(NSError *error) { if(skipReceivedData){ [[self class] removeTemporaryFileAtPath:aFilePath]; } NSDictionary *userInfo = @{@"URL": requestURL}; [[NSNotificationCenter defaultCenter] postNotificationInMainQueueWithName:kYDSessionDidFailToDownloadFileNotification object:self userInfo:userInfo]; completionBlock([NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]); }; [request start]; NSDictionary *userInfo = @{@"URL": request.URL}; [[NSNotificationCenter defaultCenter] postNotificationInMainQueueWithName:kYDSessionDidStartDownloadFileNotification object:self userInfo:userInfo]; return (id<YDSessionRequest>)request; }
- (instancetype)initWithDelegate:(id<YDSessionDelegate>)delegate callBackQueue:(dispatch_queue_t)queue{ self = [super init]; if (self) { _delegate = delegate; _callBackQueue = queue; } return self; } YDDiskRequest *request = [[YDDiskRequest alloc] initWithURL:url]; request.fileURL = filePath; request.params = params; [self prepareRequest:request]; request.callbackQueue = _callBackQueue;
Source: https://habr.com/ru/post/249605/
All Articles