本文为您介绍如何实现 HarmonyOS NEXT 播放器 SDK 的基础功能。
注意
本文适用于播放器 SDK 3.1.2-tob 及之后的版本。若您使用更早版本,请见HarmonyOS NEXT 播放器 SDK 使用文档(历史版本)。
初始化播放器 SDK 之前,开启日志,便于调试和排查问题。
注意
线上版本请务必关闭日志,减少性能开销。
VodEnv.openLog()
初始化 SDK 并配置 License 文件。这是全局接口,app 生命周期内仅需调用一次。
注意
自 3.0.45-tob 版本起支持 License 文件自动更新。若您使用 3.0.45-tob 之前的版本,License 文件需由应用服务端下发,定期更新,否则 License 过期后您将无法使用播放器 SDK。更多信息,请见如何处理 License 相关错误?
export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { // 初始化 applog,需传入您在视频点播控制台获取的 App ID ApplogWrapper.intApplog(this.context, 'your app id') // 初始化 license 模块,在 EntryAbility 中调用必须传入 this.context,不能使用 getContext() VodEnv.init(this.context) // 传入 license 文件,必须由 appServer 下发,定期更新,否则 license 过期后无法使用火山引擎播放器 SDK。 VodEnv.addLicenseFile('{"Signature":"A1VA/mZ63eEUKLZEs4mzMa8xLVqUHkEzeUdC........') // 初始化播放器 SDK,在 EntryAbility 中调用必须传入 this.context,不能使用 getContext() let mdlConfig = new MdlConfig(); mdlConfig.use_origin_url = true; let model = new SimKitInitModel(); model.globalPlayOptions = GlobalOptions.create() // 支持 BASH(经火山引擎优化的升级版 DASH 协议)视频流的播放 .setEnableDash(true) .setEnableMdlConfig(true) .setMdlConfigs(mdlConfig); SimKitService.instance().init(model, context); } }
播放器 SDK 支持设置 Vid 和 DirectUrl 播放源。
Vid 播放源:如果您已将视频上传至火山引擎视频点播服务,可使用 Vid 方式播放视频。您需要将播放器的 vid 参数设为视频点播服务生成的 Vid,将 playAuthToken 参数设为临时播放 Token。这两个参数是由应用服务端下发给客户端。详情请见通过临时播放 Token 播放。
let vid = "your vid"; // appServer 下发的 vid let playAuthToken = "your video id's play auth token"; // appServer 下发的临时播放 Token // 构造播放源 let dataSource = new VidSource(vid, playAuthToken)
DirectUrl 播放源:如使用 DirectUrl 播放源,您需要将播放地址设置给播放器。播放地址可以是第三方点播地址或火山引擎视频点播服务生成的播放地址。此外,你还必须设置以下参数:
vid:播放源唯一标识,必须与视频源一一对应。vid 可以是您自己的视频管理系统中的唯一标识,也可以在 App 层自行生成一个 ID。此 ID 会用于播放器 SDK 的日志上报、预渲染等功能中。fileHash:作为缓存 key 使用,需能和视频资源文件一一对应,不带特殊字符,能作为文件名。可使用 URL 的 MD5 作为缓存 key。// 视频源与 vid 必须一一对应 let vid = "your vid"; // 视频 url let url = "http://www.example.com/h264.mp4"; // 播放源唯一标识,与视频源一一对应,如 url 的 MD5 let fileHash = getMd5(url); // 构造播放源 let videoInfo = new VideoInfo(); videoInfo.urls = [url] videoInfo.fileHash = fileHash let dataSource = new VideoModel(); dataSource.videoInfos = [videoInfo]; dataSource.vid = vid;
this.player = new VePlayer(this.dataSource)
build() { Stack() { TTAVPlayerView2({ // 设置显示模式 // FillXY :可能会变形;画面宽高都充满控件;画面不被裁剪;无黑边 // AspectFit:无变形;等比例缩放;画面不被裁剪;可能有黑边 // AspectFill:无变形;等比例缩放;画面可能被裁剪;无黑边 scaleType: TTAVScaleType.AspectFit, onLoad: (window) => { // window 加载成功绑定到 player this.player?.bindWindow(window) } }) } .width('100%') .height('100%') }
调用 play 开始播放。SDK 默认会边播边缓存。
this.player.play()
this.player.release()
// 订阅播放回调,每次调用 SDK 内部会创建新的 IPlayEventObserver 并返回。 // 同一次播放可订阅多次,使用完需要解除订阅。 this.playEventObserver = this.player.subscribeObserver('ShortVideoView') .onPrepared((sourceID) => { }) .onPaused((sourceID) => { }) .onPlaying((sourceID) => { }) .onBuffering((sourceID, start) => { }) .onTimeChange((sourceID, currentDuration) => { }) .onBufferingPercent((sourceID, percent) => { }) .onError((sourceID, error) => { }) // 结束播放时,取消订阅 this.player.unsubscribeObserver(this.playEventObserver)
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 /** * 播放至结尾回调,不论用户是否开启loop,播放至结尾均触发回调 */ onPlayEnd(callback: Callback<string | undefined>): IPlayEventObserver /** * 出现卡顿回调 * @param start 为 true 开始卡顿;start 为 false 卡顿结束。 */ onBuffering(callback: (vid: string | undefined, start: boolean) => void): IPlayEventObserver /** * 播放器已经缓存的可播放进度 * @param percent 百分比 */ onBufferingPercent(callback: (vid: string | undefined, percent: number) => void): IPlayEventObserver /** * 播放进度变化回调,默认间隔 1s 上报,因用户操作(seek)产生的时间变化会立刻上报 * @param currentDuration 当前播放进度,单位 ms */ onTimeChange(callback: (vid: string | undefined, currentDuration: number) => void): IPlayEventObserver /** * seek 完成回调 * @param seekDoneTime seek 到的位置,单位 ms。 * 精准位置需要通过 onTimeChange 获取,seek 回调的 time 仅代表完成用户某一次请求。 */ onSeekDone(callback: (vid: string | undefined, seekDoneTime: number) => void): IPlayEventObserver /** * 播放错误回调 * @param error 错误信息,error.code 为错误码 */ onError(callback: (vid: string | undefined, error: BusinessError) => void): IPlayEventObserver /** * 获取到 VideoModel 回调 * @param videoModel,播放使用的 VideoModel */ onFetchVideoModel(callback: (vid: string | undefined, videoModel: VideoModel) => void): IPlayEventObserver /** * 视频宽高变化回调 * @param width 视频宽 * @param height 视频高 */ onVideoSizeChange(callback: (vid: string | undefined, width: number, height: number) => void): IPlayEventObserver }
// 暂停播放 this.player.pause() // 恢复播放 this.player.play()
调用 play 后,通过 seekTo 方法 Seek 到指定位置进行播放,实现拖拽进度条到指定时间开始播放的功能。
// 演示 seek 到 1 秒的位置 this.player.seek(1000);
在调用 play 前通过 setStartTime 方法指定开始播放时间点,用于实现从指定时间开始播放或跳过片头等功能。示例代码如下:
let options = PlayOptions.default() // 单位为毫秒,以下示例表示从 1 秒处起播 .setInitialStartTimeMs(1000); this.player = new VePlayer(this.dataSource, options)
// 默认值为 1,取值为 0.5, 1, 1.5, 2, 2.5, 3 this.player.setSpeed(2)
// 循环播放默认关闭 let options = PlayOptions.default() .setLoop(true) this.player = new VePlayer(this.dataSource, options)
// 静音 this.player.setVolume(0); // 取消静音 this.player.setVolume(1);
// 单位为毫秒 let duration = this.player.getDuration()
Vid 模式下播放视频时,视频点播服务会根据生成临时 Token 时的参数配置下发一个或多个清晰度的播放地址。播放器触发 onFetchVideoModel 回调后,您可调用 getVideoList 方法获取包含所有清晰度信息的数组,基于该数组实现清晰度列表的展示和清晰度切换逻辑。
播放 Vid 视频源时,起播分辨率的选择逻辑如下:
let dataSource = new VidSource(videoDetail.vid, videoDetail.playAuthToken, Resolution.resolution_360p)
播放器触发 onFetchVideoModel 回调后,调用 getVideoList 方法获取包含所有清晰度信息的数组。
.onFetchVideoModel((sourceID, videoModel) => { let videoList = this.player.getVideoList(); for (let videoInfo of videoList) { // 清晰度字段,见下面清晰度枚举 console.info("definition is" + videoInfo.definition) } })
获取清晰度列表后,传入要切换清晰度在列表中的 index。
this.player.switchVideo(index);
Resolution 枚举如下表所示:
key | 视频清晰度 |
|---|---|
resolution_360p | 360p |
resolution_480p | 480p |
resolution_540p | 540p |
resolution_720p | 720p |
resolution_1080p | 1080p |
resolution_2k | 2k |
resolution_4k | 4k |
UnionInfo 是播放端从设备中提取的用于标识访问或设备唯一性的信息。播放器 SDK 通过 UnionInfo 向应用服务端发起播放请求,应用服务端通过服务端 SDK 本地签发包含 UnionInfo 的 PlayAuthToken 并下发给播放器 SDK,即可播放火山引擎私有加密视频。更多信息,请见火山引擎私有加密方案。您可通过以下代码生成 UnionInfo:
let uniqueId = VodEnv.getEngineUniqueId();
let options = PlayOptions.default() .setEnableMediaCodecRender(true); this.player = new VePlayer(this.dataSource, options)
当 License 过期或其他错误发生时,播放器会报错 -30001,无法正常使用。此时建议切换至鸿蒙系统播放器进行播放。系统播放器效果较差,因此建议及时更新 License,避免过期。
this.playEventObserver = this.player.subscribeObserver('FeedItemView') .onError((sourceID, error) => { // license 校验失败 if (error.codec == TTPlayerErrorCode.LICENSE_FAIL) { } })
VodEnv.addLicenseFile(this.license) ..... export struct ShortVideoView { // 检查 license,检查通过使用自研播放器 isOwnPlayer 设置为 true,检查失败使用系统播放器播放。 private isOwnPlayer: boolean = VodEnv.isLicenseAvailable(); // 系统播放器需使用 XComponent,需要 xComponentID 和 xComponentController private xComponentID: string = util.generateRandomUUID() private xComponentController: XComponentController = new XComponentController() // 使用系统播放器需自行调整 XComponent 宽、高 @Local playerWidth: number = 100; @Local playerHeight: number = 100; aboutToAppear() { // 使用自研播放器 isOwnPlayer 设置为 true // 使用系统播放器 isOwnPlayer 设置为 false this.player = new VePlayer(this.dataSource, PlayOptions.default(), this.isOwnPlayer) } build() { Stack() { if (this.isOwnPlayer) { // 自研播放器使用 TTAVPlayerView2 TTAVPlayerView2({ scaleType: TTAVScaleType.AspectFit, onLoad: (window) => { // window 加载成功绑定到 player this.player?.bindWindow(window) } }) } else { // 系统播放器使用 XComponent XComponent({ id: this.xComponentID, type: XComponentType.SURFACE, controller: this.xComponentController }).width(this.playerWidth + 'px') .height(this.playerHeight + 'px') .onLoad(() => { // surfaceID 绑定到 player let surfaceID = this.xComponentController.getXComponentSurfaceId() this.player?.bindSurfaceID(surfaceID) }) } } } subscribeObserver() { this.playEventObserver = this.player.subscribeObserver(TAG) .onVideoSizeChange((vid, width, height) => { // 使用系统播放器需要根据视频比例设置显示组件宽高 if (!this.isOwnPlayer) { // 获取屏幕宽高比 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()) { // 接入方自行封装鸿蒙系统播放器。 }
详见以下鸿蒙官方文档: