本文为您介绍如何将 HarmonyOS NEXT 点播 SDK 集成至您的项目中。
当前版本的主要功能如下表所示。
功能 | 说明 | |
---|---|---|
播放协议与格式 | 音视频格式 | 支持 HLS、MP4 等点播场景常见的视频格式。 |
DirectUrl 播放 | 支持以 DirectUrl 方式播放本地视频和网络视频。 | |
H.264 编码格式 | 支持播放 H.264 编码协议的视频流。 | |
H.265 编码格式 | 支持播放 H.265 编码协议的视频流。 | |
播放控制 | 基础播放控制 | 支持开始、结束、暂停和恢复等播放控制功能。 |
Seek | 支持拖动到指定位置。 | |
重播 | 支持视频播放结束后手动触发重播。 | |
续播 | 支持设置续播起播时间点。 | |
循环播放 | 支持视频播放结束后自动重播。 | |
倍速播放 | 支持变速播放,与此同时音频变速不变调。 | |
清晰度切换 | 支持用户流畅无卡顿地切换视频的多路清晰度流。 | |
音频效果 | 音量调节 | 支持调用系统接口调节观看视频的音量。 |
静音 | 支持开启和关闭静音。 | |
视频效果 | 填充模式 | 默认为等比例填充。若视频比例和容器比例不一致,可能存在黑边 |
播放性能 | 预渲染 | 在播放当前视频时,提前创建播放器并对下一个视频进行解码和渲染,同时可将预渲染的首帧用作视频封面,提前展示给用户。 |
多实例 | 支持在同一界面添加多个播放器并同时播放。 | |
播放失败重试 | 播放失败时自动重试。 | |
事件回调 | 支持对播放状态回调、首帧回调、播放完成或失败回调。 | |
视频安全 | Referer 黑白名单 | 支持通过播放请求中携带的 Referer 字段识别请求来源,以黑名单或白名单方式对请求来源进行控制。 |
质量上报 | 日志上报 | 支持上报播放器 SDK 日志,统计点播视频播放的埋点数据。 |
播放数据大盘 | 支持观测播放量、播放质量等大盘数据。 |
类别 | 说明 |
---|---|
开发环境 | DevEco Studio(推荐使用最新版本) |
兼容的最低 SDK 版本 |
|
ABI 兼容性 | 架构要求:arm64 |
HarmonyOS NEXT 点播 SDK 目前以 har 包的形式发布,仅支持通过本地配置的方式集成。
将 har 文件放置于下图所示的路径中。
在 entry
下的 oh-package.json5
中添加依赖:
{ "name": "entry", "version": "1.0.0", "description": "Please describe the basic information.", "main": "", "author": "", "license": "", "dependencies": { "@volcengine/simkit_api": "file:./libs/simkit_api.har", } }
在工程 oh-package.json5
中添加依赖:
{ "modelVersion": "5.0.0", "description": "Please describe the basic information.", ... "overrides": { "@vcloud/ttlicense2": "file:./entry/libs/ttlicense2-0.0.2.har" } }
在 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 过期后无法使用点播播放器。更多信息,请见如何处理 License 相关错误?
// 初始化 applog。需传入您在视频点播控制台获取的 App ID ApplogWrapper.intApplog(getContext(), 'your app id') VodEnv.init() // 传入 license 文件 VodEnv.addLicenseFile('{"Signature":"A1VA/mZ63eEUKLZEs4mzMa8xLVqUHkEzetG792AsxfvRrXUdCRb........')
this.simPlayer = SimKitService.instance().createSimPlayer();
当前仅支持以 DirectUrl 方式播放。
// 视频 URL let url = "http://www.example.com/h264.mp4"; // 播放源唯一标识,与视频源一一对应,如 url 的 MD let sourceID = getMd5(url); this.playRequest = new PlayRequestBuilder() .setSourceID(sourceID) .setPlayAddr([url]) .build()
build() { Stack() { // 构造视图控件并传入播放器和播放源 SimVideoView({ playRequest: this.playRequest, simPlayer: this.simPlayer, }) .width('100%') .height('100%') } .width('100%') .height('100%') }
this.simPlayer.play(playRequest, PlayOptions.create());
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() .setLoop(true));
this.simPlayer.play(playRequest, PlayOptions.create() // 单位为毫秒,以下示例表示从 1 秒处起播 .setInitialStartTimeMs(1000);
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()) { // 自行封装鸿蒙系统播放器 }
详见以下鸿蒙官方文档: