本文提供视频点播 iOS 端集成短剧场景的方案概览,并为您详细介绍如何快速集成短剧 Demo。
火山引擎视频点播针对短剧场景提供四种客户端集成方案。下表列出这四种解决方案的详细区别。
集成方案 | 集成短剧 Demo | 集成播放控件 PlayerKit | 集成播放器 SDK |
---|---|---|---|
方案说明 | 基于短视频场景控件,提供支持防录屏、剧集切换、 切换页面无缝续播、付费内容解锁播放等短剧场景特色功能。 | 基于播放器 SDK,提供 View 层播放能力,屏蔽播放器使用细节。 | 直接集成播放器 SDK。 |
适用客户 | 适用于开发新 App、可切换业务接口层逻辑的客户。 | 适用于开发新 App 或者已有 App 但可切换业务播放架构的客户。 |
|
开发工作 | 仅需切换业务接口层逻辑和转换数据结构。 | 客户需自行实现网络数据交互、短剧播放控件 UI 浮层、短剧播放页面切换、短剧支付等功能。 | 客户自行实现播放器功能以及短剧场景特色功能。 |
上线时间 | 最快一周上线 | 两周至一个月 | 一个月左右 |
相关链接 | 本文 |
在集成短剧 Demo 前,建议您先参考文档跑通 Demo,体验短剧场景的功能。
短剧 Demo 源码位于在 VEVodDemo-iOS 仓库 VESceneModule
文件夹内,其目录结构说明如下:
|--VOLCDemo |--|--VOLCDemo // 主 app(壳工程) |--|--VEVodMain // Demo 入口+资源文件 |--|--VEBaseKit // 依赖的基础组建 |--|--VEPlayerKit // 播放控件层 |--|--VEPlayerUIModule // 播放控件使用的 UI 组件(计划废弃) |--|--VESceneModule // 各种场景源码 |--|--|--ShortDrama // 短剧场景源码 |--|--|--ShortVideo // 短视频场景源码(类似抖音推荐页面) |--|--|--FeedVideo // 中视频场景源码(类似西瓜推荐页面) |--|--|--LongVideo // 长视频场景源码 |--|--|--CustomPlayVideo// 自定义 URL 方式播放场景源码 |--|--|--Data // API 请求+ViewModel |--|--|--Setting // Demo setting 页面配置 |--|--|--UIComponents // 通用 UI 组件
运行以下命令将源码下载至本地:
git clone https://github.com/volcengine/VEVodDemo-iOS cd VEVodDemo-iOS/VOLCDemo
将以下文件夹拷贝至您的项目根目录下:
VEBaseKit VEPlayerKit VEPlayerUIModule VESceneModule VEVodMain // 这里存放资源文件
说明
复制完成后,建议运行一次 git commit
,并在 commit message 中记录 VEVodDemo-iOS
当前最新的 Commit ID。因为根据业务需要,源码可能会有变动,这次 commit 可以帮您追溯。
参考 VEVodDemo-iOS/VOLCDemo/Podfile 文件将下列的依赖拷贝到您自己的 Podfile 文件中。对于 VEBaseKit
、VESceneModule
、VEPlayerKit
、VEPlayerUIModule
,请根据您项目中实际存放依赖库的路径进行相应的修改。如果源码依赖的第三方开源库与业务代码中实际使用的版本存在冲突,以您使用的版本为准。
# TTSDK-Player pod 'TTSDKFramework', '1.41.3.5-premium', :subspecs => ['Player-SR'] # vod main pod 'VEVodMain', :path=> './VEVodMain/' # vod base kit pod 'VEBaseKit', :path=> './VEBaseKit/' # vod all scene exhibition pod 'VESceneModule', :path=> './VESceneModule/' # vod player kit pod 'VEPlayerKit', :path=> './VEPlayerKit/' # vod player control UI kit pod 'VEPlayerUIModule', :path=> './VEPlayerUIModule/' # Third libray pod 'Masonry' pod 'SDWebImage' pod 'MBProgressHUD', '~> 1.2.0' pod 'Reachability' pod 'MJRefresh' pod 'JSONModel'
短剧场景源码位于 VOLCDemo/VESceneModule/ShortDrama
文件夹。如果您只需要集成短剧场景,可以删除 VOLCDemo/VESceneModule
文件夹下其他场景的代码。
参考 AppDelegate.m 文件初始化播放器 SDK。
- (void)initTTSDK { #ifdef DEBUG /// 建议 Debug 期间打开 Log 开关 [TTVideoEngine setLogFlag:TTVideoEngineLogFlagAll]; #endif /// appid 和 license 不能为空,详见[应用管理](https://www.volcengine.com/docs/4/79594)和 [License 包管理](https://www.volcengine.com/docs/4/65772) NSString *appId = @""; NSString *licenseName = @""; // 以下是 Demo 检查有没有指定 appid 和 license,您可删除 //if (appId.length == 0 || licenseName.length == 0) { // UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedStringFromTable(@"tip_license_required", @"VodLocalizable", nil) preferredStyle:UIAlertControllerStyleAlert]; // UIAlertAction *vidSource = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { // exit(0); // }]; // [alert addAction:vidSource]; // [self.window.rootViewController presentViewController:alert animated:YES completion:nil]; // return; //} /// initialize ttsdk, configure Liscene ,this step cannot be skipped !!!!! TTSDKConfiguration *configuration = [TTSDKConfiguration defaultConfigurationWithAppID:appId licenseName:licenseName]; /// 播放器 CacheSize,默认 100M,建议设置 300M TTSDKVodConfiguration *vodConfig = [[TTSDKVodConfiguration alloc] init]; vodConfig.cacheMaxSize = 300 * 1024 * 1024; configuration.vodConfiguration = vodConfig; [TTSDKManager startWithConfiguration:configuration]; }
VEShortDramaPagingViewController
是短剧场景的主页面对象,包含默认的“剧场”和“推荐”两个标签页。您可以根据实际业务需求来扩展更多标签页。您可以将 VEShortDramaPagingViewController
添加到业务 UINavigationController
的 rootViewController
中进行集成。
参考以下步骤快速将短剧 Demo 与您的业务逻辑进行适配:
ShortDrama/Data/DataManager/VEDramaDataManager.h
中定义的 API。├ VESceneModule │ └ ShortDrama │ └ VEShortDramaPagingViewController.h // 短剧主页面 UI │ └ Data │ ├ DataManager │ └ VEDramaDataManager.h // 短剧 HTTP API 接口类 │ ├ Model │ └ VEDramaInfoModel.h // 短剧信息 │ └ VEDramaEpisodeInfoModel.h // 单集信息 │ └ VEDramaVideoInfoModel.h // 单集视频 │ └ VEDramaPayInfoModel.h // 单集付费信息 │ ├ ShortDramaListController │ └ VEShortDramaListViewController.h // 剧场页面 │ ├ ShortDramaFeedController │ ├ DramaRecommod │ └ VEShortDramaVideoFeedViewController.h // 推荐页面 │ └ ShortDramaRecommodPlayerModuleLoader.h // 推荐页面 Modules Loader 组件 │ ├ DramaDetail │ └ VEShortDramaDetailFeedViewController.h // 剧集详情页 │ └ ShortDramaDetailPlayerModuleLoader.h // 剧集详情页 Modules Loader 组件 │ ├ DramaCollect │ └ ShortDramaCollectViewController.h // 收藏组件,业务可以对该组件修改实际业务逻辑 │ ├ DramaPraise │ └ ShortDramaPraiseViewController.h // 点赞组件,业务可以对该组件修改实际业务逻辑 │ ├ DramaPay │ └ ShortDramaPayViewController.h // 支付组件,业务可以对该组件修改实际支付逻辑 │ └ ShortDramaCachePayManager.h // Demo 管理支持缓存的对象,实际业务开发一般用不到,应该以Server返回的支付状态为准 │ ├ DramaSelection │ └ ShortDramaSelectionViewController.h // 剧集选集的组件 │ └ PlayerModules // 短剧播控相关所有 Modules 组件,通过 Modules Loader 加载 │ └ ShortDramaPlayButtonModule.h // 播放/暂停 Module │ └ ShortDramaPlayerSpeedModule.h // 倍速 Module │ └ //... 更多 Module 不一一列举,可自行阅读源码 │ └ ShortDramaUIView │ └ ShortDramaIntroduceView.h // 剧集介绍 View │ └ ShortDramaSelectionView.h // 选集介绍 View │ └ //... 更多 View 不一一列举,可自行阅读源码
为构建短剧业务逻辑,短剧 Demo 层定义了 VEDramaInfoModel
(短剧信息)、VEDramaEpisodeInfoModel
(单集视频)、VEDramaEpisodeInfoModel
(单集信息)、VEDramaPayInfoModel
(单集付费解锁信息)数据结构。您需要将你自己的应用服务端返回的数据结构转换为上述数据结构。数据结构具体说明如下表所示。
类 | 字段 | 类型 | 是否必需 | 描述 |
---|---|---|---|---|
VEDramaInfoModel | dramaId | String | 必需 | 短剧 ID |
dramaTitle | String | 必需 | 短剧名 | |
coverUrl | String | 必需 | 短剧封面 | |
totalEpisodeNumber | Int | 必需 | 短剧总集数 | |
latestEpisodeNumber | Int | 可选 | 短剧最新集数 | |
VEDramaVideoInfoModel | videoId | Int | 必需 | 视频 ID |
title | String | 可选 | 视频标题 | |
duration | Double | 必需 | 视频时长(单位:秒) | |
coverUrl | String | 必需 | 封面地址 | |
videoUrl | String | 使用 DirectUrl 数据源必需 | 视频播放地址 | |
playAuthToken | String | 用 Vid 数据源必需 | Vid 数据源的 PlayAuthToken。AppServer 可使用视频点播服务端 SDK 签发。 | |
subtitleAuthToken | String | 可选 | Vid 数据源的获取字幕 Token。AppServer 使用视频点播服务端 SDK 签发。 | |
dramaEpisodeInfo | VEDramaEpisodeInfoModel | 必需 | 短剧单集信息 | |
payInfo | VEDramaPayInfoModel | 付费解锁必需 | 短剧单集支付解锁信息 | |
VEDramaEpisodeInfoModel | dramaInfo | VEDramaInfoModel | 必需 | 短剧信息 |
episodeNumber | Int | 必需 | 短剧集数 | |
episodeDesc | String | 必需 | 短剧单集描述 | |
VEDramaPayInfoModel | payStatus | VEDramaPayStatus | 必需 | 短剧付费解锁类型:
|
推荐页接口适配:参考 VEDramaDataManager
对象的 requestDramaRecommondList
接口实现在推荐页中获取短剧推荐流。您可将 API 请求修改为您自己的实际业务请求,并将请求到的数据封装为 VEDramaVideoInfoModel
对象返回给页面进行展示。如果当前 VEDramaVideoInfoModel
对象中的字段无法满足业务当前需求,您也可修改源码增加新的字段。
+ (void)requestDramaRecommondList:(NSInteger)offset pageSize:(NSInteger)pageSize result:(RequestDataComplete)complete { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableArray *dramas = [NSMutableArray array]; NSMutableDictionary *param = [NSMutableDictionary dictionary]; [param setObject:@"mini-drama-video" forKey:@"authorId"]; [param setObject:@"mini-drama-video" forKey:@"userID"]; [param setObject:@(VEVideoCodecType_H264) forKey:@"codec"]; [param setObject:@(VEVideoFormatType_MP4) forKey:@"format"]; [param setObject:@(offset) forKey:@"offset"]; [param setObject:@(pageSize) forKey:@"pageSize"]; [VENetworkHelper requestDataWithUrl:requestDramaRecommondListUrl httpMethod:@"POST" parameters:param success:^(id _Nonnull responseObject) { if (responseObject && [ responseObject isKindOfClass:[NSDictionary class]]) { NSArray* results = [responseObject objectForKey:@"result"]; for (NSDictionary *dic in results) { // 将请求到的数据封装为 VEDramaVideoInfoModel 返回给页面进行展示 VEDramaVideoInfoModel *dramaVideoInfoModel = [[VEDramaVideoInfoModel alloc] initWithDictionary:dic error:nil]; [dramas addObject:dramaVideoInfoModel]; } } if (complete) { complete(dramas, nil); } } failure:^(NSString * _Nonnull errorMessage) { if (complete) { complete(nil, errorMessage); } }]; }); }
详情页接口适配:参考 VEDramaDataManager
对象 requestDramaEpisodeList
接口实现在详情页中获取短剧详情流。您可将 API 请求修改为您自己的实际业务请求,并将请求到的数据封装为 VEDramaVideoInfoModel
返回给页面进行展示。如果当前 VEDramaVideoInfoModel
对象中的字段无法满足业务当前需求,您也可修改源码增加新的字段。
+ (void)requestDramaEpisodeList:(NSString *)dramaId episodeNumber:(NSInteger)episodeNumber offset:(NSInteger)offset pageSize:(NSInteger)pageSize result:(RequestDataComplete)complete { if (!dramaId) { if (complete) { complete(nil, @"dramaId is nil !!!"); } return; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableArray *dramas = [NSMutableArray array]; NSMutableDictionary *param = [NSMutableDictionary dictionary]; [param setObject:@"mini-drama-video" forKey:@"authorId"]; [param setObject:@"mini-drama-video" forKey:@"userID"]; [param setObject:@(VEVideoCodecType_H264) forKey:@"codec"]; [param setObject:@(VEVideoFormatType_MP4) forKey:@"format"]; [param setObject:@(offset) forKey:@"offset"]; [param setObject:@(pageSize) forKey:@"pageSize"]; [param setObject:dramaId ?: @"" forKey:@"dramaId"]; @weakify(self); [VENetworkHelper requestDataWithUrl:requestDramaEpisodeUrl httpMethod:@"POST" parameters:param success:^(id _Nonnull responseObject) { @strongify(self); if (responseObject && [ responseObject isKindOfClass:[NSDictionary class]]) { NSArray* results = [responseObject objectForKey:@"result"]; for (NSDictionary *dic in results) { // 将请求到的数据封装为 VEDramaVideoInfoModel 返回给页面进行展示 VEDramaVideoInfoModel *dramaVideoInfoModel = [[VEDramaVideoInfoModel alloc] initWithDictionary:dic error:nil]; [dramas addObject:dramaVideoInfoModel]; } // 这部分代码为 Demo 模拟剧集是否付费增加的测试代码,业务实际使用不需要,可删除 //if ([ShortDramaCachePayManager shareInstance].openPayTest) { // [[self class] testDramaPayVideoInfo:dramas]; //} } if (complete) { complete(dramas, nil); } } failure:^(NSString * _Nonnull errorMessage) { if (complete) { complete(nil, errorMessage); } }]; }); }
剧场页接口适配:参考 VEDramaDataManager
对象 requestDramaList
接口实现在剧场页中获取短剧列表。您可将 API 请求修改为您自己的实际业务请求,并将请求到的数据封装为 VEDramaInfoModel
返回给页面进行展示。如果当前 VEDramaInfoModel
对象中的字段无法满足业务当前需求,您也可修改源码增加新的字段。
+ (void)requestDramaList:(NSInteger)offset pageSize:(NSInteger)pageSize result:(RequestDataComplete)complete { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableArray *dramas = [NSMutableArray array]; NSMutableDictionary *param = [NSMutableDictionary dictionary]; [param setObject:@"mini-drama-video" forKey:@"authorId"]; [param setObject:@"mini-drama-video" forKey:@"userID"]; [param setObject:@(offset) forKey:@"offset"]; [param setObject:@(pageSize) forKey:@"pageSize"]; [VENetworkHelper requestDataWithUrl:requestDramaListUrl httpMethod:@"POST" parameters:param success:^(id _Nonnull responseObject) { if (responseObject && [responseObject isKindOfClass:[NSDictionary class]]) { NSArray* results = [responseObject objectForKey:@"result"]; for (NSDictionary *dic in results) { // 将请求到的数据封装为 VEDramaInfoModel 返回给页面进行展示 VEDramaInfoModel *dramaModel = [[VEDramaInfoModel alloc] initWithDictionary:dic error:nil]; [dramas addObject:dramaModel]; } } if (complete) { complete(dramas, nil); } } failure:^(NSString * _Nonnull errorMessage) { if (complete) { complete(nil, errorMessage); } }]; }); }
参考 VEVideoPlayerController+DisRecordScreen.h
通过监听系统录屏通知实现防录屏。
- (void)registerScreenCapturedDidChangeNotification { if (@available(iOS 11.0, *)) { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIScreenCapturedDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onScreenCapturedChange:) name:UIScreenCapturedDidChangeNotification object:nil]; } } - (void)onScreenCapturedChange:(NSNotification *)notification { if (@available(iOS 11.0, *)) { UIScreen *screen = notification.object; if (screen) { if ([screen isCaptured]) { // 正在录屏,停止播放 } else { // 没有录屏,正常播放 } } } }
短剧 Demo 中付费内容解锁播放的流程具体如下图所示。
短剧 Demo 中实现了完整付费解锁流程,但未接入第三方支付 SDK,仅在 ShortDramaPayViewController 中模拟支付流程逻辑,您需要将这部分替换为真实链路。
Demo 中的播放控件 VEPlayerKit
分为以下三层:
PlayerCore
:播放器核心封装层,通常不需要修改。PlayerModules
:核心开发层。在这一层进行与播放控制相关的开发工作。播放控件层内置了常用的 PlayerModule
,支持您将支付、短剧 UI 等业务逻辑自定义为 PlayerModule
。Scene
:场景层。这一层主要负责在不同场景下调用 PlayerCore
和 PlayerModules
,实现不同场景下的播放功能。
短剧相关的 PlayerModule
位于 VESceneModule/ShortDrama/PlayerModules
目录。如果 Demo 中已实现的短剧场景无法完全满足实际业务需求,您可以通过自定义 PlayerModule
来满足自定义需求,比如修改播放控件样式、增加新的展示模块等。本节以新增短剧播放按钮 ShortDramaPlayButtonModule
为例介绍自定义 PlayerModule
的步骤。
定义播控实例和 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;
链接播控实例:
说明
更多示例代码请见 VEPlayerKit/Interaction。
VEPlayerContextDILink(playerInterface, VEVideoPlayback, self.context); VEPlayerContextDILink(gestureService, VEPlayerGestureServiceInterface, self.context); VEPlayerContextDILink(actionViewInterface, VEPlayerActionViewInterface, self.context);
绑定播放监听:
// 监听播放 Action,修改播放按钮显示状态 // Demo 中内置了常用 Action 事件,您也可以根据需求新增 Action @weakify(self); [self.context addKey:VEPlayerContextKeyPlayAction withObserver:self handler:^(id _Nullable object, NSString *key) { @strongify(self); if (object) { [self updatePlayButtonWithPlayState:YES]; } }];
处理手势事件:
// 播放控件封装手机操作,业务使用非常简单 // 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]; }
实现 PlayerModule
生命周期函数:
#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
实例操作播放器视图。您可以将自定义 PlayerModule
添加到 actionViewInterface
的子视图上。以下示例展示将播放按钮添加到 playbackControlView
视图中,使用常用的布局组件进行布局:
- (void)configuratoinCustomView { [self.actionViewInterface.playbackControlView addSubview:self.playButton]; [self.playButton mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self.actionViewInterface.playbackControlView); }]; }
VEPlayerActionViewInterface
对象负责管理播放器视图。Demo 中对视图进行分层,基本可以满足短、中、长场景的业务需求。以下代码详细介绍不同层级的视图适合添加哪些业务 Module:
/** --------------------------------------- */ /** 播放器视图层级 **/ /** -playerContainerView **/ /** --EnginePlayerView **/ /** --ActionView **/ /** ---ControlView **/ /** --------------------------------------- */ @protocol VEPlayerActionViewInterface <NSObject> /// 播放器交互视图,业务不要直接使用 @property (nonatomic, strong, readonly) VEPlayerActionView *actionView; /// 播放器的父容器视图。如果当前组件超出播放器视图范围,可以用播放器的父视图进行布局 @property (nonatomic, weak, nullable) UIView *playerContainerView; /// 在 playbackControl 的下面,比如弹幕 @property (nonatomic, strong, readonly) VEPlayerControlView *underlayControlView; /// 播放控制层。所有能控制播放器的都叫做 control。Module 中控制功能的控件会加到这个 view 上,控制整体控件的消失和出现 @property (nonatomic, strong, readonly) VEPlayerControlView * playbackControlView; /// 锁屏时的控制层,例如解锁的按钮等 @property (nonatomic, strong, readonly) VEPlayerControlView * playbackLockControlView; /// 在 playbackControl 的上面,比如各种的提示、loading 等,不会随着 control 隐藏而隐藏 @property (nonatomic, strong, readonly) VEPlayerControlView * overlayControlView; @end
通过 PlayerModuleLoader
加载自定义 PlayerModule
。以下示例代码展示如何在短剧详情页加载 PlayerModule
。
@interface ShortDramaDetailPlayerModuleLoader : VEPlayerBaseModuleLoader @end @implementation ShortDramaDetailPlayerModuleLoader - (NSArray<id<VEPlayerBaseModuleProtocol>> *)getCoreModules { NSMutableArray *coreModules = [NSMutableArray array]; [coreModules addObject:[ShortDramaPlayerMaskModule new]]; [coreModules addObject:[VEPlayerLoadingModule new]]; [coreModules addObject:[ShortDramaPlayButtonModule new]]; [coreModules addObject:[VEPlayerSeekModule new]]; [coreModules addObject:[VEPlayerSeekProgressModule new]]; [coreModules addObject:[ShortDramaIntroduceModule new]]; // ... 其它 player module return coreModules; }
初始化播放器时配置 PlayerModuleLoader
。
VEVideoPlayerConfiguration *playerConfig = [VEVideoPlayerConfiguration defaultPlayerConfiguration]; /// 初始化 PlayerModuleLoader ShortDramaDetailPlayerModuleLoader *moduleLoader = [[ShortDramaDetailPlayerModuleLoader alloc] init]; /// 初始化播放器实例 VEVideoPlayerController *playerController = [[VEVideoPlayerController alloc] initWithConfiguration:playerConfig moduleLoader:moduleLoader];