最近新做一个类似奖励视频的内置视频播放功能,并且实现边下载边播放,缓存后下次直接播放本地视频,自动适应横竖屏展示,给大家分享下核心代码
有不太清楚的地方可以加我微信一起探讨、主要六个文件如下
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
知识兔