本文为您介绍如何将 HarmonyOS NEXT 点播 SDK 集成至您的项目中。
当前版本的主要功能如下表所示。
功能 | 说明 | |
---|---|---|
播放协议与格式 | 音视频格式 | 支持 HLS、MP4 等点播场景常见的视频格式。 |
DirectUrl 播放 | 支持以 DirectUrl 方式播放本地视频和网络视频。 | |
H.264 编码格式 | 支持播放 H.264 编码协议的视频流。 | |
H.265 编码格式 | 支持播放 H.265 编码协议的视频流。 | |
播放控制 | 基础播放控制 | 支持开始、结束、暂停和恢复等播放控制功能。 |
Seek | 支持拖动到指定位置。 | |
重播 | 支持视频播放结束后手动触发重播。 | |
续播 | 支持设置续播起播时间点。 | |
循环播放 | 支持视频播放结束后自动重播。 | |
倍速播放 | 支持变速播放,与此同时音频变速不变调。 | |
清晰度切换 | 支持用户流畅无卡顿地切换视频的多路清晰度流。 | |
音频效果 | 音量调节 | 支持调用系统接口调节观看视频的音量。 |
静音 | 支持开启和关闭静音。 | |
视频效果 | 填充模式 | 默认为等比例填充。若视频比例和容器比例不一致,可能存在黑边 |
播放性能 | 预渲染 | 在播放当前视频时,提前创建播放器并对下一个视频进行解码和渲染,同时可将预渲染的首帧用作视频封面,提前展示给用户。 |
多实例 | 支持在同一界面添加多个播放器并同时播放。 | |
播放失败重试 | 播放失败时自动重试。 | |
事件回调 | 支持对播放状态回调、首帧回调、播放完成或失败回调。 | |
视频安全 | Referer 黑白名单 | 支持通过播放请求中携带的 Referer 字段识别请求来源,以黑名单或白名单方式对请求来源进行控制。 |
质量上报 | 日志上报 | 支持上报播放器 SDK 日志,统计点播视频播放的埋点数据。 |
播放数据大盘 | 支持观测播放量、播放质量等大盘数据。 |
类别 | 说明 |
---|---|
开发环境 | DevEco Studio(推荐使用最新版本) |
兼容的最低 SDK 版本 |
|
ABI 兼容性 | 架构要求:arm64 |
在项目根目录下创建 .ohpmrc
文件并配置 OpenHarmony 三方库中心仓和火山引擎仓库地址。
registry=https://ohpm.openharmony.cn/ohpm/,https://artifact.bytedance.com/repository/byted-ohpm/
示意图如下:
在 entry
下的 oh-package.json5
中添加 SDK 依赖:
说明
请参见发布历史获取 SDK 最新版本号。
{ "name": "entry", "version": "1.0.0", "description": "Please describe the basic information.", "main": "", "author": "", "license": "", "dependencies": { // 将 xxx 替换成最新版本号 "@simplayer/simkit_api": "xxx", } }
示意图如下:
在 enrty
下的 module.json5
文件中声明权限:
"requestPermissions": [ { // 网络权限,建议添加 "name": "ohos.permission.INTERNET" }, { // 获取 WIFI 信息,如获取 mac,建议添加 "name": "ohos.permission.GET_WIFI_INFO" }, { // 获取网络信息,建议添加 "name": "ohos.permission.GET_NETWORK_INFO" }, { // 资产持久化,建议添加 "name": "ohos.permission.STORE_PERSISTENT_DATA" }, ],
初始化点播 SDK 之前,开启日志,便于调试和排查问题。
注意
线上版本请务必关闭日志,减少性能开销。
VodEnv.openLog()
初始化 SDK 并传入 License 文件。这是全局接口,app 生命周期内仅需调用一次。
注意
License 文件需由应用服务端下发,定期更新,否则 License 过期后您将无法使用点播 SDK。更多信息,请见如何处理 License 相关错误?
// 初始化 applog,需传入您在视频点播控制台获取的 App ID ApplogWrapper.intApplog(getContext(), 'your app id') // 初始化 license 模块 VodEnv.init() // 获取 license 见集成准备“创建应用并获取 License”部分 VodEnv.addLicenseFile('{"Signature":"A1VA/mZ63eEUKLZEs4mzMa8xLVqUHkEzeUdC........') // 初始化 SDK SimKitService.instance().init(new SimKitInitModel() ,getContext());
this.simPlayer = SimKitService.instance().createSimPlayer();
当前仅支持以 DirectUrl 方式播放。您需要将 url
参数设为视频播放地址。播放地址可以是第三方点播地址或视频点播服务生成的播放地址。此外,你还必须设置以下参数:
sourceID
:播放源唯一标识,必须与视频源一一对应。sourceID
可以是您自己的视频管理系统中的唯一标识,也可以在 App 层自行生成一个 ID。此 ID 会用于点播 SDK 的日志上报、预渲染等功能中。fileHash
:作为缓存 key 使用,需能和视频资源文件一一对应,不带特殊字符,能作为文件名。可使用 URL 的 MD5 作为缓存 key。// 播放源唯一标识 let sourceID = "source id"; // 视频 URL let url = "http://www.example.com/h264.mp4"; // 缓存 key let fileHash = getMd5(url); let urlModel = new PlayUrlModel([url]); urlModel.setFileHash(fileHash) let playRequest = new PlayRequest(sourceID, urlModel)
build() { Stack() { // 构造视图控件并传入播放器和播放源 SimVideoView({ playRequest: this.playRequest, simPlayer: this.simPlayer, }) .width('100%') .height('100%') } .width('100%') .height('100%') }
调用 play
开始播放。SDK 默认会边播边缓存。
this.simPlayer.play(playRequest, PlayOptions.create() .setEnableMdl(true));
说明
HLS 视频目前不支持边播边缓存。播放 HLS 视频时,setEnableMdl
需设置为 false
。
this.simPlayer.release(this.playRequest.getSourceID())
/** * 订阅播放回调。每次调用此接口,SDK 内部会创建新的 IPlayEventObserver 并返回 * 同一次播放可订阅多次回调。使用完需解除订阅 * @param sourceID 对应 IPlayRequest 的 sourceID * @param observerName Observer 命名标识,标记所在业务场景,方便后续异常定位 * @returns */ subscribeObserver(sourceID:string, observerName: string): IPlayEventObserver /** * 取消订阅,确保与订阅方法逻辑对称 * @param sourceID 对应 IPlayRequest 的 sourceID * @param playEventObserver,需要解除订阅的 IPlayEventObserver */ unsubscribeObserver(sourceID:string, playEventObserver: IPlayEventObserver)
export interface IPlayEventObserver { /** * 首帧回调 */ onRenderFirstFrame(callback: Callback<string | undefined>): IPlayEventObserver /** * prepare 完成回调 */ onPrepared(callback: Callback<string | undefined>): IPlayEventObserver /** * 开始播放回调 */ onPlaying(callback: Callback<string | undefined>): IPlayEventObserver /** * 调用暂停后回调 */ onPaused(callback: Callback<string | undefined>): IPlayEventObserver /** * 调用 stop 后回调 */ onStopped(callback: Callback<string | undefined>): IPlayEventObserver /** * 播放至结尾回调。不论用户是否开启循环播放,播放至结尾均触发此回调 */ onPlayEnd(callback: Callback<string | undefined>): IPlayEventObserver /** * 出现卡顿回调 * @param start 为 true 表示开始卡顿;start 为 false 表示卡顿结束。 */ onBuffering(callback: (sourceID: string | undefined, start: boolean) => void): IPlayEventObserver /** * 播放器已经缓存的可播放进度 * @param percent 百分比 */ onBufferingPercent(callback: (sourceID: string | undefined, percent: number) => void): IPlayEventObserver /** * 播放进度变化回调,默认间隔 1 秒回调,因用户操作 (seek) 产生进度变化 SDK 会立刻回调 * @param currentDuration 当前播放进度,单位 ms */ onTimeChange(callback: (sourceID: string | undefined, currentDuration: number) => void): IPlayEventObserver /** * Seek 完成回调 * @param seekDoneTime Seek 到的位置,单位 ms。 * 精准的播放位置需要通过 onTimeChange 获取,seek 回调的 time 仅代表完成用户某一次请求。 */ onSeekDone(callback: (sourceID: string | undefined, seekDoneTime: number) => void): IPlayEventObserver /** * 播放错误回调 * @param error 错误信息,error.code 为错误码 */ onError(callback: (sourceID: string | undefined, error: BusinessError) => void): IPlayEventObserver /** * 视频宽高变化回调,仅系统播放器回调 * @param width 视频宽度 * @param height 视频高度 */ onVideoSizeChange(callback: (sourceID: string | undefined, width: number, height: number) => void): IPlayEventObserver }
// 订阅回调 this.playEventObserver = this.simPlayer .subscribeObserver(this.playRequest.getSourceID(), 'FeedItemView') .onPrepared((sourceID) => { }) .onPaused((sourceID) => { }) .onPlaying((sourceID) => { }) .onBuffering((sourceID, start) => { }) .onTimeChange((sourceID, currentDuration) => { }) .onBufferingPercent((sourceID, percent) => { }) .onError((sourceID, error) => { }) // 结束播放时,取消订阅 this.simPlayer.unsubscribeObserver(this.playRequest.getSourceID(), this.playEventObserver)
// 暂停播放 this.simPlayer.pause(this.playRequest.getSourceID()) // 恢复播放 this.simPlayer.resume(this.playRequest.getSourceID())
// 演示 seek 到 1 秒的位置 this.simPlayer.seek(1000);
// 静音 this.simPlayer.setVolume(0); // 取消静音 this.simPlayer.setVolume(1);
// 默认值为 1。支持取值:0.5, 1, 1.5, 2, 2.5, 3 this.simPlayer.setSpeed(2)
// 单位为毫秒 let duration = this.simPlayer.getDuration()
this.simPlayer.play(playRequest, PlayOptions.create() .setEnableHardwareDecode(true));
// 循环播放默认关闭 this.simPlayer.play(playRequest, PlayOptions.create() .setLoop(true));
this.simPlayer.play(playRequest, PlayOptions.create() // 单位为毫秒,以下示例表示从 1 秒处起播 .setInitialStartTimeMs(1000);
火山引擎视频点播私有加密方案采用火山引擎自研加密算法,安全级别高,能够便捷、高效、安全地保护您的音视频版权。更多信息,请见火山引擎私有加密方案介绍。点播 SDK 支持通过 DirectUrl 模式播放私有加密视频。应用服务端从视频点播服务获取用于解密的密钥 playAuth
并下发给点播 SDK,即可播放火山引擎私有加密视频。
// 播放源唯一标识,视频源与 sourceID 必须一一对应 let sourceID = "source id"; // 视频 URL let url = "http://www.example.com/h264.mp4"; // 缓存 Key,如 URL 的 MD5 let fileHash = getMd5(url); // 从视频点播服务获取的用于解密的密钥,详见[获取播放信息](https://www.volcengine.com/docs/4/2918) String playAuth = "l7wZ9Em+A/xxxxxxx"; let urlModel = new PlayUrlModel([url]); urlModel.setFileHash(fileHash) urlModel.setDecryptionKey(playAuth) let playRequest = new PlayRequest(sourceID, urlModel) // 播放 this.simPlayer.play(playRequest, PlayOptions.create() .setEnableMdl(true));
export struct SmallVideoFeed { // 2. Feed 流数据 private data: FeedDataSource = new FeedDataSource(); // 3. 记录当前页面 index @State currentIndex: number = 0 // 4. 初始化短视频场景播放器 private scenePlayer: IScene = SimKitService.instance().createScene('FeedIView') aboutToAppear(): void { let playRequests = new Array<Pair<IPlayRequest, PlayOptions>>() // 5. 设置播放列表,列表内容与 FeedDataSource 中一致 this.scenePlayer.setPlayRequests(playRequests) } build() { // 1. 使用 Swiper 实现端视频 feed 流 Swiper() { LazyForEach(this.data, (videoSource: VideoSource, index: number) => { TTPlayerItemView({ sourceID: videoSource.source_id, playUrl: videoSource.play_url, scene: this.scenePlayer, index: index, currentIndex: this.currentIndex }) .width('100%') .height("100%") }, (videoSource: VideoSource) => videoSource.source_id) } .vertical(true) .loop(false) .cachedCount(0) .displayCount(1) .width('100%') .height('100%') .indicator(false) .onChange((index) => { // 6. 页面上下滑动,更新 scenePlayer index this.currentIndex = index this.scenePlayer.videoSelected(index) }) } }
export struct TTPlayerItemView { index: number = 0 // 1. Feed 页面滑动,currentIndex 更新,会通知到 onIndexChange @Watch('onIndexChange') @Prop currentIndex: number = 0 private sourceID: string = ''; private playUrl: string = ''; @State playRequest ?: IPlayRequest = new PlayRequestBuilder().build() private simPlayer?: ISimPlayer; private scene?: IScene; aboutToAppear() { this.playRequest = new PlayRequestBuilder() .setSourceID(this.sourceID) .setPlayAddr([this.playUrl]) .build() // 2. 用场景播放器获取播放实例 this.simPlayer = this.scene?.getSimPlayer(); if (this.index == 0) { // 3. index 为 0 表示第一个视频,可直接播放并设置 scene index this.simPlayer.play(this.playRequest, PlayOptions.create()); this.scene?.videoSelected(0) } } aboutToDisappear() { // 6. 画面退出,释放播放器 this.simPlayer?.release(this.playRequest?.getSourceID()) } build() { Stack() { SimVideoView({ playRequest: this.playRequest, simPlayer: this.simPlayer, }) } } onIndexChange() { // 4. 页面切换,根据 index 判断开始播放或停止播放。 if (this.index === this.currentIndex) { this.simPlayer?.play(this.playRequest, PlayOptions.create()); } else { this.simPlayer?.release(this.playRequest?.getSourceID()) } } }
当 License 过期或其他错误发生时,播放器会报错 -30001
,无法正常使用。此时建议切换至鸿蒙系统播放器进行播放。系统播放器效果较差,因此建议及时更新 License,避免过期。
this.playEventObserver = this.simPlayer .subscribeObserver(this.playRequest.getSourceID(), 'FeedItemView') .onError((sourceID, error) => { // License 校验失败 if (error.codec == TTPlayerErrorCode.LICENSE_FAIL) { } })
VodEnv.addLicenseFile(this.license) ..... // addLicenseFile 后,检查 license,如不可用设置使用系统播放器播放。 if (!VodEnv.isLicenseAvailable()) { VodEnv.setUseOwnPlayer(false) }
使用系统播放器时,视频拉伸变形铺满视图。您需根据视频宽高比自行调整 SimVideoView
宽高。
build() { Stack() { // 构造视图控件并传入播放器和播放源 SimVideoView({ playRequest: this.playRequest, simPlayer: this.simPlayer, }) .width(this.playerWidth + 'px') .height(this.playerHeight + 'px') } .width('100%') .height('100%') } this.playEventObserver = this.simPlayer .subscribeObserver(this.playRequest.getSourceID(), 'FeedItemView') .onVideoSizeChange((sourceID, width, height) => { // 获取屏幕宽高比 let displaySc = display.getDefaultDisplaySync().width / display.getDefaultDisplaySync().height // 获取视频宽高比 let videoSc = width / height; if (videoSc > displaySc) { // 视图宽填满屏幕,高等比例适配留黑边 this.playerWidth = display.getDefaultDisplaySync().width; this.playerHeight = this.playerWidth * height / width; } else { // 视图高填满屏幕,宽等比例适配留黑边 this.playerHeight = display.getDefaultDisplaySync().height; this.playerWidth = this.playerHeight * width / height; } })
VodEnv.addLicenseFile(this.license) ..... // addLicenseFile 后,检查 license,如不可用,使用您自行封装的系统播放器 if (!VodEnv.isLicenseAvailable()) { // 自行封装鸿蒙系统播放器 }
详见以下鸿蒙官方文档: