You need to enable JavaScript to run this app.
导航
核心类使用
最近更新时间:2025.04.14 19:05:20首次发布时间:2025.04.14 19:05:20
我的收藏
有用
有用
无用
无用

本文为您详细介绍 PlayerKit 的架构、核心模块以及核心功能的使用方式。

PlayerKit 架构概览

PlayerKit 包含 SceneKit、PlayerModule 和 PlayerCore 三个核心模块,如下图所示:
Image
各模块的详细介绍如下表:

模块

说明

SceneKit

场景层,基于 PlayerCore 和 PlayerModule 实现不同的播放场景。

PlayerModule

核心开发层,播控相关的开发都在这一层,播放控件都可以是一个 PlayerModule ,业务跟播放相关组件也可以设计为一个 PlayerModule,例如支付、短剧一些显示 UI 等,播放控件层在不同场景内置常用的 PlayerModule 方便业务的使用。

PlayerCore

播放器 SDK 核心封装层。一般情况下,您无需进行修改。

核心模块说明

Context 模块

Context 模块提供基础消息通信功能,允许播放器实例与模块之间、模块与模块之间通过 key-value 方式实现消息的发送与监听,从而达成播放器状态等消息的同步。该模块使用简便,具体实现可参考以下代码:

// 发送状态变更
[self.context post:@(self.playbackState) forKey:VEPlayerContextKeyPlaybackState];

// 监听状态变更
[self.context addKey:VEPlayerContextKeyPlaybackState withObserver:self handler:^(id  _Nullable object, NSString *key) {
    if (self.playerInterface.playbackState == VEVideoPlaybackStatePlaying) {
        [self _startTimer];
    } else {
        [self _invalidateTimer];
    }
}];

DI 模块

DI 模块依托 Context 模块,提供了三个宏定义用于绑定和链接服务。通过 DI 链接的服务可直接使用,从而实现各模块间的解耦。

// VEPlayerInteraction: 初始化绑定 3 个服务
/// _playerModuleManager: 负责所有 Module 的管理
/// _gestureService:      负责处理所有的手势
/// _playerViewService:   负责处理 UI 视图展示
- (instancetype)initWithContext:(VEPlayerContext *)context {
    self = [super init];
    if (self) {
        _context = context;
        _playerModuleManager = [[VEPlayerModuleManager alloc] initWithPlayerContext:context];
        VEPlayerContextDIBind(_playerModuleManager, VEPlayerModuleManagerInterface, self.context);
        
        _gestureService = [[VEPlayerGestureService alloc] init];
        VEPlayerContextDIBind(_gestureService, VEPlayerGestureServiceInterface, self.context);
        
        _playerViewService = [[VEPlayerViewService alloc] init];
        _playerViewService.moduleManager = _playerModuleManager;
        VEPlayerContextDIBind(_playerViewService, VEPlayerActionViewInterface, self.context);
    }
    return self;
}

// 自定义 PlayerModule
@implementation VEPlayerSeekModule

// 在 Moudule 组件 Link 所需的服务
VEPlayerContextDILink(playerInterface, VEVideoPlayback, self.context);
VEPlayerContextDILink(gestureService, VEPlayerGestureServiceInterface, self.context);
VEPlayerContextDILink(actionViewInterface, VEPlayerActionViewInterface, self.context);

@end

播放控件 UI 视图

播放控件默认提供了经过内部积累、适用于各种不同场景的 UI 布局视图,并采用懒加载方式按需创建,供业务使用,基本能够满足大多数业务的使用场景。

视图

说明

playerContainerView

播放器的父容器视图。若当前组件超出播放器视图范围,可利用播放器的父视图进行布局。

actionView

UI 控件的父容器视图,负责管理上层的 controlView。若当前的 controlView 视图无法满足业务需求,可自定义 controlView 并将其添加至 actionView。

underlayControlView

在 playbackControlView 下方、始终展示的视图,例如弹幕、上下遮罩等可放置于此视图。

playbackControlView

播放控制层,所有具备控制播放器功能的元素均被称为“control”,模块中具备控制功能的控件将被添加至该视图,负责管控整体控件的显示与隐藏。

playbackLockControlView

锁屏状态下的控制视图,在锁屏时不会消失。

overlayControlView

在 playbackControlView 上方、始终展示的视图,例如各类提示信息和加载状态可放置于此视图。

说明

playerContainerView 和 actionView 默认创建,其他显示视图以懒加载方式按需创建。

播放 UI 层级示意图如下:
Image

自定义开发播放控件

如前文所述,使用播放控件进行开发的关键在于依据业务需求开发所需的 UI。若默认提供的播放控件无法完全满足您的业务需求,您可自定义开发播放控件。本节以 PlayerModule 为例,详细介绍如何进行播放控件的自定义开发。

创建 PlayerModule

  1. 定义所需的播控服务对象和 UI 控件:

    // 播放器实例
    @property (nonatomic, weak) id<VEVideoPlayback> playerInterface; 
    // 播控手势实例,可以添加 单击、双击、拖拽收拾
    @property (nonatomic, weak) id<VEPlayerGestureServiceInterface> gestureService; 
    // 播放视图的实例,自定义 UI 控件可以方便添加到播放视图上
    @property (nonatomic, weak) id<VEPlayerActionViewInterface> actionViewInterface;
    
    // 自定义 UI 控件
    @property (nonatomic, strong) UIButton *playButton;
    
  2. 链接播控服务对象:

    VEPlayerContextDILink(playerInterface, VEVideoPlayback, self.context);
    VEPlayerContextDILink(gestureService, VEPlayerGestureServiceInterface, self.context);
    VEPlayerContextDILink(actionViewInterface, VEPlayerActionViewInterface, self.context);
    
  3. 绑定播放监听:

    // 监听播放 Action,修改播放按钮显示状态
    // Demo 中内置了常用 Action 事件给业务使用,业务也可以根据自身需求新增 Action ,也非常简单具体可以参考任意一个 Action
    @weakify(self);
    [self.context addKey:VEPlayerContextKeyPlayAction withObserver:self handler:^(id  _Nullable object, NSString *key) {
        @strongify(self);
        if (object) {
            [self updatePlayButtonWithPlayState:YES];
        }
    }];
    
  4. 处理手势事件:

    // 播放控件封装手机操作,业务使用非常简单
    // 1. 添加需要的手势操作,例如这里需要一个点击播放页面的手势操作
    [self.gestureService addGestureHandler:self forType:VEGestureType_SingleTap];
    
    // 2. 实现手势回调,并在回调中实现自身业务逻辑
    // 注意 PlayerMoudle 需要实现 <VEPlayerGestureHandlerProtocol> 协议
    - (void)handleGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer gestureType:(VEGestureType)gestureType {
        VEVideoPlaybackState playbackState = (VEVideoPlaybackState)[self.context integerForHandlerKey:VEPlayerContextKeyPlaybackState];
        if (playbackState == VEVideoPlaybackStatePlaying) {
            [self.playerInterface pause];
        } else if (playbackState == VEVideoPlaybackStatePaused) {
            [self.playerInterface play];
        }
        [self.context post:@(self.playerInterface.playbackState) forKey:VEPlayerContextKeyPlayButtonSingleTap];
    }
    
  5. 实现 PlayerModue 生命周期函数:

    #pragma mark - Life Cycle
    
    // module 加载完成
    - (void)moduleDidLoad {
        [super moduleDidLoad];
    }
    
    // 该方法跟 UIViewController viewDidLoad 类似,可以在这里添加监听、手势、处理UI等操作
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self configuratoinCustomView];
        
        [self.gestureService addGestureHandler:self forType:VEGestureType_SingleTap];
        
        @weakify(self);
        [self.context addKey:VEPlayerContextKeyPlayAction withObserver:self handler:^(id  _Nullable object, NSString *key) {
            @strongify(self);
            if (object) {
                [self updatePlayButtonWithPlayState:YES];
            }
        }];
    }
    
    // 处理模板,业务可不关注该方法
    - (void)controlViewTemplateDidUpdate {
        [super controlViewTemplateDidUpdate];
    }
    
    // module 卸载,这个方法里释放坚挺、手势、移除UI等操作
    - (void)moduleDidUnLoad {
        [super moduleDidUnLoad];
        [self.gestureService removeGestureHandler:self];
        [self.context removeHandlersForObserver:self];
        if (self.playButton) {
            [self.playButton removeFromSuperview];
            self.playButton = nil;
        }
    }
    

绑定播放视图

通过 PlayerModule 中所链接的 actionViewInterface 实例对播放器上的 UI 进行操作。当前的播放 UI 应添加至 playbackControlView 的子视图中。以下示例展示了如何将播放按钮添加到 playbackControlView 视图,并使用常用的布局组件进行布局:

- (void)configuratoinCustomView {
    [self.actionViewInterface.playbackControlView addSubview:self.playButton];
    [self.playButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.actionViewInterface.playbackControlView);
    }];
}

加载自定义 PlayerModule

通过 PlayerModuleLoader 加载自定义的 PlayerModule:

@interface SimplePlayerKitPlayerModuleLoader : VEPlayerBaseModuleLoader

@end

@implementation SimplePlayerKitPlayerModuleLoader

- (NSArray<id<VEPlayerBaseModuleProtocol>> *)getCoreModules {
    NSMutableArray *coreModules = [NSMutableArray array];
    [coreModules addObject:[VEPlayerLoadingModule new]];
    [coreModules addObject:[VEPlayerSeekModule new]];
    [coreModules addObject:[VEPlayerSeekProgressModule new]];

    [coreModules addObject:[VEPlayerPlayModule new]];
    // ... 其它 player module 
    
    return coreModules;
}

播放流程

创建播放源

TTVideoEngineMediaSource 类是 PlayerKit 中对播放源的定义。支持 Vid、DirectUrl 和 VideoModel 三种播放源,您可根据自身业务需求,选择创建相应的播放源。

Vid 播放源

如果您已将视频上传至火山引擎视频点播服务,可使用 Vid 方式播放视频。您需要将 vid 参数设为视频点播服务生成的 Vid,将 playAuthToken 参数设为临时播放 Token。Vid 和临时播放 Token 由应用服务端下发的。应用客户端无需关心,调用应用服务端的接口获取即可,详情请见通过临时播放 Token 播放
与 DirectUrl 和 VideoModel 播放源相比,使用 Vid 播放源时,SDK 内部会发起 HTTP 请求,调用 GetPlayInfo 接口以获取 VideoModel,这会增加视频首帧的耗时。Vid 播放源支持多档位切换,您需指定一个起播分辨率。

- (id<TTVideoEngineMediaSource>)createVidMediaSource  {
    NSString *vid = @"your video id";
    NSString *playAuthToken = @"your video id's playAuthToken";
    // 播放档位选择
    TTVideoEngineResolutionType resolution = TTVideoEngineResolutionTypeFullHD; 
    
    TTVideoEngineVidSource *vidSource = [[TTVideoEngineVidSource alloc] initWithVid:vid playAuthToken:playAuthToken resolution:resolution];
    
    return vidSource;
}

DirectURL 播放源

若采用 DirectUrl 方式播放视频,需将 url 参数设定为视频播放地址。该播放地址可以是第三方点播地址,也可以是视频点播服务生成的播放地址。url 可设置为数组形式,支持主、备用 URL 切换。此类播放源不支持多档位切换。
此外,针对 DirectUrl 播放源,还需设置 cacheKey 参数。cacheKey 是本地播放缓存的唯一标识。若同一播放地址的 cacheKey 发生更改,本地缓存将无法进行正确索引,从而导致重新加载数据,造成流量浪费。建议对播放 URL 中的固定部分进行 MD5 加密,并将加密结果作为 cacheKey。

- (id<TTVideoEngineMediaSource>)createUrlMediaSource  {
    NSString *url = @"your play url";
    // 一般使用 url 去掉时间戳的 path 部分作为缓存 key
    NSString *cacheKey = [url btd_md5String];
    
    TTVideoEngineUrlSource *urlSrouce = [[TTVideoEngineUrlSource alloc] initWithUrl:url cacheKey:cacheKey];
    
    return urlSrouce;
}

VideoModel 播放源

使用 VideoModel 播放源可避免因调用 GetPlayInfo 接口带来的网络请求,缩短视频首帧耗时。Vid 播放源支持多档位切换。当前 PlayerKit 未提供 VideoModel 类作为播放源构造方式,开发者需依据 GetPlayInfo 返回参数的 JSON 结构手动构建 VideoModel 播放源。

- (id<TTVideoEngineMediaSource>)createVideoModelMediaSource  {
    NSString *vid = @"your video id";
    NSString *videoModelJson = @"your video model json";
    // 播放档位选择
    TTVideoEngineResolutionType resolution = TTVideoEngineResolutionTypeFullHD; 
    
    TTVideoEngineModel *engineModel = [TTVideoEngineModel videoModelWithMediaJsonString:videoModelJson];
    
    TTVideoEngineVideoModelSource *videoModelSource = [[TTVideoEngineVideoModelSource alloc] initWithVid:vid resolution:resolution videoModel:engineModel];
    
    return videoModelSource;
}

创建 ModuleLoader 并绑定播放控件

通常情况下,一个播放场景需要创建一个 ModuleLoader。ModuleLoader 负责绑定并加载该场景下的所有播放控件。以下示例代码展示了播放器 ModuleLoader 绑定并加载了加载状态提示(loading)、进度条和播放按钮这三个基础播放控件:

@interface SimplePlayerKitPlayerModuleLoader : VEPlayerBaseModuleLoader

@end

@implementation SimplePlayerKitPlayerModuleLoader

- (NSArray<id<VEPlayerBaseModuleProtocol>> *)getCoreModules {
    NSMutableArray *coreModules = [NSMutableArray array];
    [coreModules addObject:[VEPlayerLoadingModule new]];
    [coreModules addObject:[VEPlayerSeekModule new]];
    [coreModules addObject:[VEPlayerPlayModule new]];
    // ... 其它 player module 
    
    return coreModules;
}

初始化播放器配置

您可设置播放器的初始化状态,例如显示模式、是否静音、是否循环播放等。

@interface VEVideoPlayerConfiguration : NSObject

// 播放器默认显示模式, 默认值 VEVideoViewModeAspectFill
@property (nonatomic, assign) VEVideoViewMode videoViewMode;
// 是否音频模式播放, 默认值 NO
@property (nonatomic, assign) BOOL audioMode;
// 是否静音播放, 默认值 NO
@property (nonatomic, assign) BOOL muted;
// 是否循环播放, 默认值 NO
@property (nonatomic, assign) BOOL looping;
// 播放倍速, 默认值 1.0
@property (nonatomic, assign) CGFloat playbackRate;
// 起播时间点, 默认值 0
@property (nonatomic, assign) NSTimeInterval startTime;
// 是否开启H.265播放,H.265播放源开启, 默认关闭
@property (nonatomic, assign) BOOL isH265;
// 是否开启硬解, 默认开启
@property (nonatomic, assign) BOOL isOpenHardware;
// 是否开启超分, 默认关闭
@property (nonatomic, assign) BOOL isOpenSR;

+ (VEVideoPlayerConfiguration *)defaultPlayerConfiguration;

@end

初始化播放器并播放

// 1. 初始化播放器实例
/// 初始化播放器配置
VEVideoPlayerConfiguration *playerConfiguration = [VEVideoPlayerConfiguration defaultPlayerConfiguration];
/// 初始化 modules loader
_moduleLoader = [[SimplePlayerKitPlayerModuleLoader alloc] init];
/// 初始化播放器
_playerController = [[VEVideoPlayerController alloc] initWithConfiguration:playerConfiguration moduleLoader:_moduleLoader];

// 2. 添加播放视图到显示视图
[self.view addSubview:self.playerController.view];
[self.playerController.view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.left.right.equalTo(self.view);
    make.bottom.equalTo(self.view).with.offset(-84);
}];

// 3. 设置播放源
id<TTVideoEngineMediaSource> urlSource = [self createUrlMediaSource];
[self.playerController setMediaSource:urlSource];

// 4. 开始播放
[self.playerController play];

播放控制

设置起播位置

VEVideoPlayerConfiguration *playerConfiguration = [VEVideoPlayerConfiguration defaultPlayerConfiguration];
playerConfiguration.startTime = 10.0; // 10 秒起播

准备播放

// 使用预渲染需调用该方法,调用后会触发首帧渲染
// 正常播放直接调用 play 方法即可,无需调用该方法
[self.playerController prepareToPlay];

开始播放

[self.playerController play];

暂停播放

[self.playerController pause];

Seek 到指定位置播放

// seek 到 20 秒
[self.playerController seekToTime:20 complete:^(BOOL success) {
    // seek 完成
} renderComplete:^{
    // seek 后渲染完成
}];

释放播放器

[self.playerController close];

获取视频时长

// 单位秒
NSTimeInterval duration = [self.playerController duration];

获取播放进度

// 单位秒
NSTimeInterval currentPlaybackTime = [self.playerController currentPlaybackTime];

获取缓存进度

// 单位秒
NSTimeInterval playableDuration = [self.playerController playableDuration]; 

调节音量

// 取值范围 [0, 1],建议使用系统音量方法进行调节。
self.playerController.playbackVolume = 1;

静音

// YES 静音;NO 取消静音
self.playerController.muted = YES;

倍速播放

// 取值范围 (0, 3]
self.playerController.playbackRate = 1;

循环播放

// YES 开启循环播放;NO 关闭循环播放
self.playerController.looping = YES;

播放器回调

@protocol VEVideoPlaybackDelegate <NSObject>

@optional

/**
 * @brief 播放器准备完成回调
 * @param player 播放实例
 */
- (void)videoPlayerPrepared:(id<VEVideoPlayback> _Nullable)player;

/**
 * @brief 播放器首帧回调
 * @param player 播放实例
 */
- (void)videoPlayerReadyToDisplay:(id<VEVideoPlayback> _Nullable)player;

/**
 * @brief 播放器播放状态改变回调
 * @param player 播放实例
 * @param state 播放器加载状态
 */
- (void)videoPlayer:(id<VEVideoPlayback> _Nullable)player loadStateDidChange:(VEVideoLoadState)state;

/**
 * @brief 播放器加载状态改变回调
 * @param player 播放实例
 * @param state 播放器播放状态
 */
- (void)videoPlayer:(id<VEVideoPlayback> _Nullable)player playbackStateDidChange:(VEVideoPlaybackState)state;

/**
 * @brief 播放器播放结束回调
 * @param player 播放实例
 * @param finishStatus 播放器播放结束原因
 */
- (void)videoPlayer:(id<VEVideoPlayback> _Nullable)player didFinishedWithStatus:(VEPlayFinishStatus *_Nullable)finishStatus;

/**
 * @brief 播放器播放是否命中缓存的回调
 * @param player 播放实例
 * @param dataSize 命中缓存的大小
 */
- (void)videoPlayer:(id<VEVideoPlayback> _Nullable)player key:(NSString * _Nullable)key hitVideoPreloadDataSize:(NSInteger)dataSize;

/**
 * @brief 播放器切换分辨率档位的回调
 * @param player 播放实例
 * @param resolution 切换后的分辨率
 * @param bitrate 切换后的码率
 */
- (void)videoPlayerBitrateDidChange:(id<VEVideoPlayback> _Nullable)player resolution:(TTVideoEngineResolutionType)resolution bitrate:(NSInteger)bitrate;

/**
 * @brief 播放器播放视图宽高改变的回调
 * @param player 播放实例
 * @param videoWidth 播放器显示视图宽
 * @param videoHeight 播放器显示视图高
 */
- (void)videoPlayerViewSizeDidChange:(id<VEVideoPlayback> _Nullable)player videoWidth:(NSInteger)videoWidth videoHeight:(NSInteger)videoHeight;

@end