KJPlayerBaseViewDelegate控件载体协议
控件相关委托代理
/* 单双击手势反馈 */ - (void)kj_basePlayerView:(KJBasePlayerView*)view isSingleTap:(BOOL)tap; /* 长按手势反馈 */ - (void)kj_basePlayerView:(KJBasePlayerView*)view longPress:(UILongPressGestureRecognizer*)longPress; /* 进度手势反馈,不替换UI请返回当前时间和总时间,范围-1 ~ 1 */ - (NSArray*)kj_basePlayerView:(KJBasePlayerView*)view progress:(float)progress end:(BOOL)end; /* 音量手势反馈,是否替换自带UI,范围0 ~ 1 */ - (BOOL)kj_basePlayerView:(KJBasePlayerView*)view volumeValue:(float)value; /* 亮度手势反馈,是否替换自带UI,范围0 ~ 1 */ - (BOOL)kj_basePlayerView:(KJBasePlayerView*)view brightnessValue:(float)value; /* 是否锁屏,根据KJPlayerButton的type来确定当前按钮类型 */ - (void)kj_basePlayerView:(KJBasePlayerView*)view PlayerButton:(KJPlayerButton*)button;
KJPlayerType
枚举文件夹和公共方法管理
KJPlayerState:播放器状态
KJPlayerCustomCode:错误code
KJPlayerGestureType:手势操作
KJPlayerPlayType:播放类型
KJPlayerDeviceDirection:手机方向
KJPlayerVideoGravity:播放器充满类型
KJPlayerVideoFromat:视频格式
DBPlayerDataInfo
主要包括两部分,数据库模型和增删改查等工具
数据库结构
dbid:唯一id,视频链接去除scheme然后md5 videoUrl:视频链接 saveTime:存储时间戳 sandboxPath:沙盒地址 videoFormat:视频格式 videoTime:视频时间 videoData:视频数据
数据库工具
方法 | 功能 |
kj_insertData:Data: | 插入数据,重复数据替换处理 |
kj_deleteData: | 删除数据 |
kj_addData: | 新添加数据 |
kj_updateData:Data: | 更新数据 |
kj_checkData: | 查询数据,传空传全部数据 |
kCheckAppointDatas | 指定条件查询 |
KJResourceLoader
中间桥梁作用,把网络请求缓存到本地的临时数据传递给播放器
关于这块的详细介绍,我单独写了一篇 开发播放器框架之边下边播边存方案分享
KJPlayer - AVPlayer播放器内核
工作流程:
1、获取视频类型,根据网址来确定,目前没找到更好的方式(知道的朋友可以指点一下)
/// 根据链接获取Asset类型 NS_INLINE KJPlayerAssetType kPlayerVideoAesstType(NSURL *url){ if (url == nil) return KJPlayerAssetTypeNONE; if (url.pathExtension.length) { if ([url.pathExtension containsString:@"m3u8"] || [url.pathExtension containsString:@"ts"]) { return KJPlayerAssetTypeHLS; } } NSArray * array = [url.path componentsSeparatedByString:@"."]; if (array.count == 0) { return KJPlayerAssetTypeNONE; }else{ if ([array.lastObject containsString:@"m3u8"] || [array.lastObject containsString:@"ts"]) { return KJPlayerAssetTypeHLS; } } return KJPlayerAssetTypeFILE; }
2、处理视频,这里才用队列组来处理,子线程处理解决第一次加载卡顿问题
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([weakself kj_dealVideoURL:&tempURL]) { if (![tempURL.absoluteString isEqualToString:self->_videoURL.absoluteString]) { self->_videoURL = tempURL; [weakself kj_initPreparePlayer]; }else{ [weakself kj_playerReplay]; } } });
3、处理视频链接地址,这里分两种情况,使用缓存就从缓存当中读取
/* 判断当前资源文件是否有缓存,修改为指定链接地址 */ - (void)kj_judgeHaveCacheWithVideoURL:(NSURL * _Nonnull __strong * _Nonnull)videoURL{ self.locality = NO; KJCacheManager.kJudgeHaveCacheURL(^(BOOL locality) { self.locality = locality; if (locality) { self.playError = [DBPlayerDataInfo kj_errorSummarizing:KJPlayerCustomCodeCachedComplete]; } }, videoURL); }
获取数据库当中的数据
/* 判断是否有缓存,返回缓存链接 */ + (void(^)(void(^)(BOOL),NSURL * _Nonnull __strong * _Nonnull))kJudgeHaveCacheURL{ return ^(void(^locality)(BOOL),NSURL * _Nonnull __strong * _Nonnull videoURL){ NSArray<DBPlayerData*>*temps = [DBPlayerDataInfo kj_checkData:kPlayerIntactName(*videoURL)]; BOOL boo = NO; if (temps.count) { DBPlayerData *data = temps.firstObject; NSString *path = data.sandboxPath; if (data.videoIntact && [KJCacheManager kj_haveFileSandboxPath:&path]) { //移出之前的临时文件 NSString *tempPath = [path stringByAppendingPathExtension:kTempReadName]; [[NSFileManager defaultManager] removeItemAtPath:tempPath error:NULL]; *videoURL = [NSURL fileURLWithPath:path]; boo = YES; } } kGCD_player_main(^{ if (locality) locality(boo); }); }; }
4、判断地址是否可用,添加下载和播放桥梁
PLAYER_WEAKSELF; if (!kPlayerHaveTracks(*videoURL, ^(AVURLAsset * asset) { if (weakself.useCacheFunction && !weakself.localityData) { weakself.state = KJPlayerStateBuffering; weakself.loadState = KJPlayerLoadStateNone; NSURL * tempURL = weakself.connection.kj_createSchemeURL(*videoURL); weakself.asset = [AVURLAsset URLAssetWithURL:tempURL options:weakself.requestHeader]; [weakself.asset.resourceLoader setDelegate:weakself.connection queue:dispatch_get_main_queue()]; }else{ weakself.asset = asset; } }, self.requestHeader)) { self.ecode = KJPlayerCustomCodeVideoURLFault; self.state = KJPlayerStateFailed; [self kj_destroyPlayer]; return NO; }
5、播放准备操作设置playerItem
,然后初始化player
,添加时间观察者处理播放
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(_timeSpace, NSEC_PER_SEC) queue:dispatch_queue_create("kj.player.time.queue", NULL) usingBlock:^(CMTime time) { NSTimeInterval sec = CMTimeGetSeconds(time); if (isnan(sec) || sec < 0) sec = 0; if (weakself.totalTime <= 0) return; if ((NSInteger)sec >= (NSInteger)weakself.totalTime) { [weakself.player pause]; weakself.state = KJPlayerStatePlayFinished; weakself.currentTime = 0; }else if (weakself.userPause == NO && weakself.buffered) { weakself.state = KJPlayerStatePlaying; weakself.currentTime = sec; } if (sec > weakself.tryTime && weakself.tryTime) { [weakself kj_pause]; if (!weakself.tryLooked) { weakself.tryLooked = YES; kGCD_player_main(^{ if (weakself.tryTimeBlock) weakself.tryTimeBlock(); }); } }else{ weakself.tryLooked = NO; } }];
6、处理视频状态,kvo监听播放器五种状态
status
:监听播放器状态
loadedTimeRanges
:监听播放器缓冲进度
presentationSize
:监听视频尺寸
playbackBufferEmpty
:监听缓存不够的情况
playbackLikelyToKeepUp
:监听缓存足够
大致流程就差不多这样子,Demo也写的很详细,可以自己去看看
五、总结思考:
在重构播放器的时候,其中遇见不少细节上面的问题,现在应该还是存在不少的问题,也希望朋友们指出,然后我好慢慢修改完善。
我Demo写的很详细,感兴趣的老哥可以去下载来玩玩
Demo地址
Demo地址:KJPlayerDemo