播放器轮子制造回顾|项目复盘(二)

简介: 播放器轮子制造回顾|项目复盘

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


主要包括两部分,数据库模型和增删改查等工具


数据库结构

1.png

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播放器内核


工作流程:

image.png

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


六、文章关联


关于播放器其他相关文章

开发播放器框架之全屏处理

开发播放器框架之边下边播边存方案分享


播放器轮子制造回顾,老哥觉得好用还请帮我点个**小星星**传送门

相关文章
|
5月前
|
自然语言处理 安全
线下陪玩游戏系统开发多语言/海外版/成熟技术/方案项目/源码功能
Continuing to develop an offline companion game system may involve the following aspects:
|
5月前
|
测试技术 程序员 API
Eolink使用需要掌握的知识路线
Eolink使用需要掌握的知识路线
48 0
|
区块链 算法框架/工具 Android开发
Stepn跑鞋零撸项目开发详情丨Stepn跑鞋零撸项目系统开发(案例设计)及代码说明
简单说,智能合约是一种用计算机语言取代法律语言去记录条款的合约。智能合约可以由一个计算系统自动执行。可以理解为智能合约就是传统合约的数字化版本。
|
存储 安全 算法
TechFinger游戏搬砖平台系统开发方案详细丨TechFinger搬砖游戏系统开发案例项目/源码功能/成熟技术
去中心化:以分布式网络为基础结构,对数据进行验证、记账、存储、维护和传输等操作,利用纯数学方法建立节点之间的交互信任关系,进而形成去中心化、可信任的分布式系统;
|
开发框架 Rust 安全
Stepn跑鞋趣步跑步运动系统开发(开发逻辑)丨Stepn跑鞋链游项目系统开发(详情及玩法)/成熟技术/源码版
在DApp开发中,智能合约是不可或缺的一部分。智能合约通常使用Solidity语言编写,并且运行在以太坊或其他区块链平台上。在智能合约中,开发人员可以编写代码来定义合约的逻辑和执行流程。智能合约的执行是通过区块链网络的节点共识机制来完成的,保证了其不可篡改性和安全性。
|
存储 缓存 编解码
播放器轮子制造回顾|项目复盘(一)
播放器轮子制造回顾|项目复盘
播放器轮子制造回顾|项目复盘(一)
|
5G 区块链 vr&ar
赛车链游DAPP开发丨赛车链游系统开发实现技术方案丨赛车链游源码部署
 在分布式数字身份系统中,用户身份信息管理是去中心化的,因此也可以避免被随意地泄露和篡改。基于这样的数字身份系统,个体在互联网空间中交流的基础是自己的身份数据,而不需要依赖于特定的第三方平台。而从平台的维度来看,分布式数字身份也更有利于平台之间的平等合作,共同为用户提供服务。
赛车链游DAPP开发丨赛车链游系统开发实现技术方案丨赛车链游源码部署
|
测试技术 iOS开发 Python
热饭的测开成果盘点第二十六期:IOS自动化平台
热饭的测开成果盘点第二十六期:IOS自动化平台
热饭的测开成果盘点第二十六期:IOS自动化平台
|
测试技术 Android开发
热饭的测开成果盘点第二十七期:安卓自动化平台
热饭的测开成果盘点第二十七期:安卓自动化平台
热饭的测开成果盘点第二十七期:安卓自动化平台
|
人工智能 自然语言处理 测试技术
热饭的测开成果盘点第十九期:移动端自动化智能平台
本期介绍的是移动端app智能架构平台,效果和上期一样,也是直接根据用例 来直接执行,它的初衷是可以简单的对我们测试环境几千条用例全部自动执行的框架。在具体稳定和速度上可能不如原始写法,但是对付这种上千条的大需求,是有奇效的。
热饭的测开成果盘点第十九期:移动端自动化智能平台
下一篇
无影云桌面