iOS开发之视频播放功能、边播放边缓存
最近新做一个类似奖励视频的内置视频播放功能,并且实现边下载边播放,缓存后下次直接播放本地视频,自动适应横竖屏展示,给大家分享下核心代码
有不太清楚的地方可以加我微信一起探讨、主要六个文件如下
ECGRewardVideoView.h、
ECGRewardVideoView.m
ECGPlayVideoResourceLoaderDelegate.h
ECGPlayVideoResourceLoaderDelegate.m
ECGPlayVideoRequestTask.h
ECGPlayVideoRequestTask.m
代码如下,直接复制粘贴会有报错地方,但是我觉得你们肯定一看就懂的哈,如有不懂记得微信交流。
外部调用代码
参数说明:url-视频url、duration-播放时间、width-视频宽度、height-视频高度。
[[ECGRewardVideoView sharedInstance] showInsideRewardVideoWithUrl:url duration:duration width:width height:height];
ECGRewardVideoView.h代码,这是一个继承单例类
#import <UIKit/UIKit.h> #import "ECGBaseSingleInstance.h" @interface ECGRewardVideoView : ECGBaseSingleInstance /** 显示内置奖励视频*/ - (void)showInsideRewardVideoWithUrl:(NSString *)url duration:(NSInteger)duration width:(NSInteger)width height:(NSInteger)height; @end
ECGRewardVideoView.m代码实现
#import "ECGRewardVideoView.h" #import <AVKit/AVKit.h> #import "CSUtility.h" #import "ECGNCMultiLanguage.h" #import "ECGPlayVideoUtility.h" #import "ECGPlayVideoResourceLoaderDelegate.h" #import "ECGNativeiOSAdapter.h" #import "ECGCustomAlertView.h" #import "ECGNCUtility.h" @interface ECGRewardVideoView () /** 关闭视频按钮*/ @property (strong, nonatomic) UIButton *closeButton; /** 关闭视频按钮*/ @property (strong, nonatomic) UIImageView *skipImageView; /** 倒计时文本*/ @property (strong, nonatomic) UILabel *downTimeLabel; /** 播放视频layer*/ @property (strong, nonatomic) AVPlayerLayer *playerLayer; /** 播放视频背景*/ @property (strong, nonatomic) UIView *playBGView; /** 播放器*/ @property (strong, nonatomic) AVPlayer *player; /** 播放器*/ @property (strong, nonatomic) AVPlayerItem *playerItem; /** 视频是否播放完毕*/ @property (assign, nonatomic) BOOL launchVideoIsFinish; /** 是否横屏*/ @property (assign, nonatomic) BOOL isHorizontalScreen; /** 视频播放urlasset*/ @property (strong, nonatomic) AVURLAsset *urlAsset; /** 缓存代理*/ @property (strong, nonatomic) ECGPlayVideoResourceLoaderDelegate *resourceLoaderDelegate; /** 监听播放进度*/ @property (strong, nonatomic) id timeObserverToken; @property (strong, nonatomic) UIActivityIndicatorView *loadingView; @end @implementation ECGRewardVideoView /** 显示内置奖励视频*/ - (void)showInsideRewardVideoWithUrl:(NSString *)urlStr duration:(NSInteger)duration width:(NSInteger)width height:(NSInteger)height { self.isHorizontalScreen = NO; self.launchVideoIsFinish = NO; UIViewController *showVC = [CSUtility cs_getCurrentShowViewController]; CGRect rect = [UIScreen mainScreen].bounds; CGSize size = rect.size; NSURL *url = nil; if ([urlStr hasPrefix:@"http://"] || [urlStr hasPrefix:@"https://"]) { url = [NSURL URLWithString:urlStr]; NSString *strUrl = urlStr; //判断是否已经下载完毕,本地缓存路径 NSString *strLocalPath = [ECGPlayVideoUtility pathForSaveFullVideoDataWithVideoFileName:strUrl.lastPathComponent]; if ([ECGPlayVideoUtility isFileExistAtPath:strLocalPath]) { url = [NSURL fileURLWithPath:strLocalPath]; _playerItem = [AVPlayerItem playerItemWithURL:url]; } else { //实现的边下载边缓存 NSURL *customUrl = [ECGPlayVideoUtility customUrlForStandardUrl:url]; _urlAsset = [AVURLAsset assetWithURL:customUrl]; self.resourceLoaderDelegate = [[ECGPlayVideoResourceLoaderDelegate alloc] initWithVideoOrigionalUrlString:strUrl]; self.resourceLoaderDelegate.didFailLoadingBlock = ^(NSError *error) { ECGNativeLogHCL(@"HCL视频加载失败 - error %@", error); }; [_urlAsset.resourceLoader setDelegate:self.resourceLoaderDelegate queue:dispatch_get_main_queue()]; _playerItem = [AVPlayerItem playerItemWithAsset:_urlAsset]; } } else { if ([CSUtility cs_isExistFileForPath:urlStr]) { url = [NSURL fileURLWithPath:urlStr]; _playerItem = [AVPlayerItem playerItemWithURL:url]; } else { return; } } AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:_playerItem]; _player = player; _playBGView = [[UIView alloc] initWithFrame:rect]; _playBGView.backgroundColor = [UIColor blackColor]; _playBGView.userInteractionEnabled = YES; [[UIApplication sharedApplication].delegate.window addSubview:_playBGView]; UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleTap:)]; doubleTapGesture.numberOfTapsRequired = 2; [_playBGView addGestureRecognizer:doubleTapGesture]; _playerLayer = [AVPlayerLayer playerLayerWithPlayer:player]; _playerLayer.frame = _playBGView.bounds; _playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; [_playBGView.layer addSublayer:_playerLayer]; [_playerLayer.player play]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(0, 0, 100, 50); button.center = CGPointMake(size.width-50, 50); [button addTarget:self action:@selector(closeVideoButtonClick) forControlEvents:UIControlEventTouchUpInside]; [[UIApplication sharedApplication].delegate.window addSubview:button]; self.closeButton = button; UIImageView *imageview = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ecg_video_close"]]; imageview.frame = CGRectMake(40, 5, 35, 35); [button addSubview:imageview]; _skipImageView = imageview; UILabel *label = [[UILabel alloc] init]; label.font = [UIFont systemFontOfSize:15]; label.textColor = [UIColor whiteColor]; label.textAlignment = NSTextAlignmentCenter; label.frame = CGRectMake(0, 5, 40, 35); [button addSubview:label]; _downTimeLabel = label; _downTimeLabel.text = @(duration).stringValue; if (height < width) { _playBGView.frame = CGRectMake(0, 0, size.height, size.width); _playBGView.center = CGPointMake(size.width/2, size.height/2); _playBGView.transform = CGAffineTransformRotate(_playBGView.transform, M_PI_2); _playerLayer.frame = _playBGView.bounds; button.center = CGPointMake(size.width - 50, size.height - 50); button.transform = CGAffineTransformRotate(button.transform, M_PI_2); self.isHorizontalScreen = YES; } [[UIApplication sharedApplication].delegate.window addSubview:self.loadingView]; [_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:NULL]; [_playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:NULL]; [_playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:NULL]; [self delayStartAnimatingLoadingView]; //注册播放结束监听 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(videoPlayEndNeedCallBackGame) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; CMTime interval = CMTimeMakeWithSeconds(0.5, NSEC_PER_SEC); dispatch_queue_t mainQueue = dispatch_get_main_queue(); __weak typeof(self) weakSelf = self; //监听播放进度 self.timeObserverToken = [self.player addPeriodicTimeObserverForInterval:interval queue:mainQueue usingBlock:^(CMTime time) { [weakSelf updatePlayProgressUIWithTime:time]; }]; } #pragma mark - 观察播放器状态 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"status"]) { [self handlePlayerStatusChange:change]; }else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {//正在缓冲 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayStartAnimatingLoadingView) object:nil]; [self performSelector:@selector(delayStartAnimatingLoadingView) withObject:nil afterDelay:2]; } else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {//缓冲到可播放程度了 [self handlePlaybackLikelyToKeepUpWithChange:change]; } } /** 双击手势处理*/ - (void)handleDoubleTap:(UITapGestureRecognizer *)tap { if (_playerLayer.videoGravity == AVLayerVideoGravityResizeAspectFill) { _playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; } else { _playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; } } /** 处理 playbackLikelyToKeepUp 变化*/ - (void)handlePlaybackLikelyToKeepUpWithChange:(NSDictionary<NSKeyValueChangeKey,id> *)change { id newValue = [change objectForKey:NSKeyValueChangeNewKey]; BOOL playbackLikelyToKeepUp = [newValue boolValue]; if (!playbackLikelyToKeepUp) { return; } [self stopAnimatingLoadingView]; } /** 处理播放器状态变化*/ - (void)handlePlayerStatusChange:(NSDictionary<NSKeyValueChangeKey,id> *)change { NSInteger iNewStatus = [[change objectForKey:NSKeyValueChangeNewKey] integerValue]; if (AVPlayerItemStatusReadyToPlay == iNewStatus) { ECGNativeLogHCL(@"HCL ============= 广告视频播放成功"); } else if (AVPlayerItemStatusFailed == iNewStatus) { ECGNativeLogHCL(@"HCL ============= 广告视频播放失败!"); } else { ECGNativeLogHCL(@"HCL ============= 广告视频播放状态异常:%@!", @(iNewStatus)); } } /** 延迟显示loading动画*/ - (void)delayStartAnimatingLoadingView { [self.loadingView startAnimating]; } /** 停止loading动画*/ - (void)stopAnimatingLoadingView { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayStartAnimatingLoadingView) object:nil]; [self.loadingView stopAnimating]; } #pragma mark - 成员延迟初始化 - (UIActivityIndicatorView *)loadingView { if (nil == _loadingView) { _loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; _loadingView.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/2); _loadingView.hidesWhenStopped = true; } return _loadingView; } /** 更新播放倒计时UI*/ - (void)updatePlayProgressUIWithTime:(CMTime)time { double total = CMTimeGetSeconds(_playerItem.duration); double current = CMTimeGetSeconds(time); double progress = current/total; int downTime = (int)(total-current); if (downTime > 0) { NSString *downTimeStr = [NSString stringWithFormat:@"%d", downTime]; _downTimeLabel.text = downTimeStr; } if (current >= total) { _downTimeLabel.text = [ECGNCMultiLanguage multiplyLanguageForDialogId:@"149906"]; //149906=关闭 } } /** 关闭视频按钮点击*/ - (void)closeVideoButtonClick { if (self.launchVideoIsFinish) { [self removeVideoView]; //视频播放完成,满足奖励 [ECGNativeiOSAdapter playInsideRewerdVideoFinishCallBackResult:1]; } else { __weak typeof(self) weakSelf = self; NSString *cancelStr = [ECGNCMultiLanguage multiplyLanguageForDialogId:@"108532"]; //108532=取消 NSString *confirmStr = [ECGNCMultiLanguage multiplyLanguageForDialogId:@"140369"]; //140369=退出 NSString *message = [ECGNCMultiLanguage multiplyLanguageForDialogId:@"660505"]; //660505=当前视频未播放完毕无法获得奖励,您确定要退出吗? ECGCustomAlertView *alertView = [[ECGCustomAlertView alloc] initWithTitle:nil message:message cancelTitle:cancelStr cancelClickBlock:^{ weakSelf.closeButton.userInteractionEnabled = YES; } confirmTitle:confirmStr confirmClickBlock:^{ weakSelf.closeButton.userInteractionEnabled = YES; [weakSelf removeVideoView]; if (weakSelf.launchVideoIsFinish) { //视频播放完成,奖励 [ECGNativeiOSAdapter playInsideRewerdVideoFinishCallBackResult:1]; } else { //视频未播放完成,没有奖励 [ECGNativeiOSAdapter playInsideRewerdVideoFinishCallBackResult:0]; } }]; [alertView showInWindow]; if (self.isHorizontalScreen) { alertView.transform = CGAffineTransformRotate(alertView.transform, M_PI_2); } //让关闭按钮不能点击 _closeButton.userInteractionEnabled = NO; } } /** 播放结束*/ - (void)videoPlayEndNeedCallBackGame { self.launchVideoIsFinish = YES; [self stopAnimatingLoadingView]; } /** 播放视频结束*/ - (void)removeVideoView { if (nil != _timeObserverToken && nil != _player) { [_player removeTimeObserver:_timeObserverToken]; } //播放器结束 [_player pause]; [_player replaceCurrentItemWithPlayerItem:nil]; //移除视频 [_playerLayer removeFromSuperlayer]; _playerLayer = nil; [_playBGView removeFromSuperview]; _playBGView = nil; _playerItem = nil; _player = nil; _urlAsset = nil; _skipImageView = nil; _downTimeLabel = nil; //移除button [_closeButton removeFromSuperview]; _closeButton = nil; [self stopAnimatingLoadingView]; } @end
实现边下载边播放的类
ECGPlayVideoResourceLoaderDelegate.h代码
#import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> typedef void(^ECGPlayVideoResourceLoaderDelegateVoidBlock)(void); typedef void(^ECGPlayVideoResourceLoaderDelegateErrorBlock)(NSError *error); @interface ECGPlayVideoResourceLoaderDelegate : NSObject <AVAssetResourceLoaderDelegate> @property (readonly, nonatomic) NSString *videoOrigionalUrlString;//视频原始url,区别于替换成自定义url Scheme后的url @property (assign, nonatomic) BOOL isUserChangePlayProgress;//用户是否改变了播放进度,比如滑动进度条 @property (copy, nonatomic) ECGPlayVideoResourceLoaderDelegateErrorBlock didFailLoadingBlock; @property (copy, nonatomic) ECGPlayVideoResourceLoaderDelegateVoidBlock didFinishLoadingBlock; - (instancetype)initWithVideoOrigionalUrlString:(NSString *)strUrl; @end
ECGPlayVideoResourceLoaderDelegate.m实现代码
#import <MobileCoreServices/MobileCoreServices.h> #import "ECGPlayVideoResourceLoaderDelegate.h" #import "ECGPlayVideoRequestTask.h" #import "ECGPlayVideoUtility.h" @interface ECGPlayVideoResourceLoaderDelegate () @property (copy, nonatomic) NSString *videoOrigionalUrlString; @property (strong, nonatomic) NSMutableArray<AVAssetResourceLoadingRequest *> *loadingRequestArray; @property (strong, nonatomic) ECGPlayVideoRequestTask *requestTask; @end @implementation ECGPlayVideoResourceLoaderDelegate - (instancetype)initWithVideoOrigionalUrlString:(NSString *)strUrl { self = [super init]; if (self) { self.videoOrigionalUrlString = strUrl; } return self; } #pragma mark - 业务函数 /** 给请求填充信息*/ - (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest { NSString *mimeType = self.requestTask.mimeType; CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); contentInformationRequest.byteRangeAccessSupported = YES; contentInformationRequest.contentType = CFBridgingRelease(contentType); contentInformationRequest.contentLength = self.requestTask.videoLength; } /** 响应请求数据*/ - (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest { long long startOffset = dataRequest.requestedOffset; if (dataRequest.currentOffset != 0) { startOffset = dataRequest.currentOffset; } if ((self.requestTask.offset +self.requestTask.downLoadingOffset) < startOffset) { //NSLog(@"NO DATA FOR REQUEST"); return NO; } if (startOffset < self.requestTask.offset) { return NO; } NSAssert([self.videoOrigionalUrlString hasPrefix:@"http"], @"respondWithDataForRequest:self.videoOrigionalUrlString异常,应该是http开头的网络url"); NSString *strTempPath = [ECGPlayVideoUtility pathForTempVideoDataWithVideoFileName:[_videoOrigionalUrlString lastPathComponent]]; NSData *filedata = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:strTempPath] options:NSDataReadingMappedIfSafe error:nil]; // This is the total data we have from startOffset to whatever has been downloaded so far NSUInteger unreadBytes = self.requestTask.downLoadingOffset - ((NSInteger)startOffset - self.requestTask.offset); // Respond with whatever is available if we can't satisfy the request fully yet NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes); [dataRequest respondWithData:[filedata subdataWithRange:NSMakeRange((NSUInteger)startOffset- self.requestTask.offset, (NSUInteger)numberOfBytesToRespondWith)]]; long long endOffset = startOffset + dataRequest.requestedLength; BOOL didRespondFully = (self.requestTask.offset + self.requestTask.downLoadingOffset) >= endOffset; return didRespondFully; } /** 处理请求数组*/ - (void)processLoadingRequestArray { NSMutableArray<AVAssetResourceLoadingRequest *> *finishRequests = [NSMutableArray array]; for (AVAssetResourceLoadingRequest *loadingRequest in self.loadingRequestArray) { [self fillInContentInformation:loadingRequest.contentInformationRequest]; //对每次请求加上长度,文件类型等信息 BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; //判断此次请求的数据是否处理完全 if (didRespondCompletely) { [finishRequests addObject:loadingRequest]; [loadingRequest finishLoading]; } } [self.loadingRequestArray removeObjectsInArray:finishRequests]; finishRequests = nil; } /** 处理视频数据请求*/ - (void)processLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { if (![loadingRequest.request.URL.lastPathComponent isEqualToString:_videoOrigionalUrlString.lastPathComponent]) { NSAssert(NO, @"processLoadingRequest:当前请求的视频不是期望请求的视频?"); return; } if (_requestTask.downLoadingOffset > 0) { [self processLoadingRequestArray]; } if (nil == _requestTask || _isUserChangePlayProgress) { [self.requestTask setVideoUrl:[NSURL URLWithString:_videoOrigionalUrlString] offset:0]; } } #pragma mark - AVAssetResourceLoaderDelegate - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { NSAssert([loadingRequest.request.URL.absoluteString hasPrefix:[ECGPlayVideoUtility customUrlScheme]], @"没有自定义url scheme?"); [self.loadingRequestArray addObject:loadingRequest]; [self processLoadingRequest:loadingRequest]; return YES; } - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { [self.loadingRequestArray removeObject:loadingRequest]; } #pragma mark - 成员延迟初始化 - (NSMutableArray<AVAssetResourceLoadingRequest *> *)loadingRequestArray { if (nil == _loadingRequestArray) { _loadingRequestArray = [NSMutableArray array]; } return _loadingRequestArray; } - (ECGPlayVideoRequestTask *)requestTask { if (nil == _requestTask) { _requestTask = [[ECGPlayVideoRequestTask alloc] init]; __weak typeof(self) weakSelf = self; _requestTask.didReceiveVideoDataBlock = ^{ [weakSelf processLoadingRequestArray]; }; _requestTask.didFailLoadingBlock = ^(NSError *error) { if (weakSelf.didFailLoadingBlock) { weakSelf.didFailLoadingBlock(error); } }; _requestTask.didFinishLoadingBlock = ^{ if (weakSelf.didFinishLoadingBlock) { weakSelf.didFinishLoadingBlock(); } }; } return _requestTask; } #pragma mark - 属性设置方法 - (void)setIsUserChangePlayProgress:(BOOL)isUserChangePlayProgress { _isUserChangePlayProgress = isUserChangePlayProgress; _requestTask.isUserChangePlayProgress = isUserChangePlayProgress; } @end
ECGPlayVideoRequestTask.h代码
#import <Foundation/Foundation.h> typedef void(^ECGPlayVideoRequestTaskVoidBlock)(void); typedef void(^ECGPlayVideoRequestTaskErrorBlock)(NSError *error); @interface ECGPlayVideoRequestTask : NSObject @property (readonly, nonatomic) NSURL *url; @property (readonly, nonatomic) NSUInteger offset; @property (readonly, nonatomic) NSUInteger videoLength; @property (readonly, nonatomic) NSUInteger downLoadingOffset; @property (readonly, nonatomic) NSString *mimeType; @property (readonly, nonatomic) BOOL isFinishLoad; @property (copy, nonatomic) ECGPlayVideoRequestTaskVoidBlock didReceiveVideoDataBlock; @property (copy, nonatomic) ECGPlayVideoRequestTaskVoidBlock didFinishLoadingBlock; @property (copy, nonatomic) ECGPlayVideoRequestTaskErrorBlock didFailLoadingBlock; @property (assign, nonatomic) BOOL isUserChangePlayProgress;//用户是否改变了播放进度,比如滑动进度条 - (void)setVideoUrl:(NSURL *)url offset:(NSUInteger)offset; - (void)cancel; - (void)clearData; @end
ECGPlayVideoRequestTask.m代码
#import "ECGPlayVideoRequestTask.h" #import "ECGPlayVideoUtility.h" @interface ECGPlayVideoRequestTask () <NSURLSessionDataDelegate> @property (strong, nonatomic) NSURL *url; @property (assign, nonatomic) NSUInteger offset; @property (assign, nonatomic) NSUInteger videoLength; @property (assign, nonatomic) NSUInteger downLoadingOffset; @property (copy, nonatomic) NSString *mimeType; @property (assign, nonatomic) BOOL isFinishLoad; @property (copy, nonatomic) NSString *tempFilePath; @property (strong, nonatomic) NSURLSessionDataTask *dataTask; @property (strong, nonatomic) NSFileHandle *fileHandle; @end @implementation ECGPlayVideoRequestTask #pragma mark - 内部函数 /** 处理用户修改播放进度*/ - (void)handleUserChangePlayProgress { [self clearData]; self.offset = 0; self.videoLength = 0; self.downLoadingOffset = 0; self.mimeType = nil; self.isFinishLoad = NO; self.tempFilePath = nil; self.fileHandle = nil; //删除临时文件 [[NSFileManager defaultManager] removeItemAtPath:_tempFilePath error:nil]; } /** 处理收到Response*/ - (void)handleDidReceiveResponse:(NSURLResponse *)response { _isFinishLoad = NO; NSAssert([response isKindOfClass:[NSHTTPURLResponse class]], @"didReceiveResponse:不是 NSHTTPURLResponse"); NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; NSDictionary *dicAllHeaderFields = [httpResponse allHeaderFields] ; NSString *content = [dicAllHeaderFields valueForKey:@"Content-Range"]; NSArray *array = [content componentsSeparatedByString:@"/"]; NSString *strLength = array.lastObject; NSUInteger videoLength = 0; if (strLength.length < 1) { videoLength = (NSUInteger)httpResponse.expectedContentLength; } else { videoLength = [strLength integerValue]; } self.videoLength = videoLength; NSString *strContentType = [dicAllHeaderFields objectForKey:@"Content-Type"]; if ([strContentType isKindOfClass:[NSString class]] && strContentType.length > 0) { self.mimeType = strContentType; }else { self.mimeType = @"video/mp4"; } self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:_tempFilePath]; } /** 处理收到Data*/ - (void)handleDidReceiveData:(NSData *)data { [self.fileHandle seekToEndOfFile]; [self.fileHandle writeData:data]; _downLoadingOffset += data.length; if (self.didReceiveVideoDataBlock) { self.didReceiveVideoDataBlock(); } } /** 处理数据下载完毕*/ - (void)handleFinishReceiveData { _isFinishLoad = YES; //如果用户没有修改播放进度,则保存临时文件 if (!_isUserChangePlayProgress) { NSString *moveToPath = [ECGPlayVideoUtility pathForSaveFullVideoDataWithVideoFileName:_url.lastPathComponent]; //这里不要用moveItemToPath,因为视频可能正在播放 [[NSFileManager defaultManager] copyItemAtPath:_tempFilePath toPath:moveToPath error:nil]; } if (self.didFinishLoadingBlock) { self.didFinishLoadingBlock(); } } #pragma mark - 属性设置方法 - (void)setIsUserChangePlayProgress:(BOOL)isUserChangePlayProgress { _isUserChangePlayProgress = isUserChangePlayProgress; if (_isUserChangePlayProgress) { //删除临时文件 [self handleUserChangePlayProgress]; } } #pragma mark - 对外接口 - (void)setVideoUrl:(NSURL *)url offset:(NSUInteger)offset { _url = url; _offset = offset; self.tempFilePath = [ECGPlayVideoUtility pathForTempVideoDataWithVideoFileName:[_url lastPathComponent]]; if (![ECGPlayVideoUtility isFileExistAtPath:_tempFilePath]) { [[NSFileManager defaultManager] createFileAtPath:_tempFilePath contents:nil attributes:nil]; } _downLoadingOffset = 0; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15]; if (offset > 0 && self.videoLength > 0) { [request addValue:[NSString stringWithFormat:@"bytes=%ld-%ld",(unsigned long)offset, (unsigned long)self.videoLength - 1] forHTTPHeaderField:@"Range"]; } [self cancel]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]]; self.dataTask = [session dataTaskWithRequest:request]; [self.dataTask resume]; [session finishTasksAndInvalidate]; } - (void)cancel { [self.dataTask cancel]; } - (void)clearData { [self cancel]; //移除文件 [[NSFileManager defaultManager] removeItemAtPath:_tempFilePath error:nil]; } #pragma mark - NSURLSessionDataDelegate - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { [self handleDidReceiveResponse:response]; if (completionHandler) { completionHandler(NSURLSessionResponseAllow); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self handleDidReceiveData:data]; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (nil != error) { [session finishTasksAndInvalidate]; if (self.didFailLoadingBlock) { self.didFailLoadingBlock(error); } }else { [self handleFinishReceiveData]; } } - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error { [session finishTasksAndInvalidate]; } @end