本文为您介绍如何以最简单快捷的方式让视频播放起来。
此文档适用于 HarmonyOS NEXT 播放器 SDK 3.1.1-tob 之前的版本。
请确保您已将 HarmonyOS NEXT 播放器 SDK 集成至您的项目中,详见集成 SDK。
初始化播放器 SDK 之前,开启日志,便于调试和排查问题。
注意
线上版本请务必关闭日志,减少性能开销。
VodEnv.openLog()
初始化 SDK 并配置 License 文件。这是全局接口,app 生命周期内仅需调用一次。
注意
License 文件需由应用服务端下发,定期更新,否则 License 过期后您将无法使用播放器 SDK。更多信息,请见如何处理 License 相关错误?
// 初始化 applog,需传入您在视频点播控制台获取的 App ID ApplogWrapper.intApplog(getContext(), 'your app id') // 初始化 license 模块 VodEnv.init() // 详见本文获取 License 部分 VodEnv.addLicenseFile('{"Signature":"A1VA/mZ63eEUKLZEs4mzMa8xLVqUHkEzeUdC........') // 初始化 SDK SimKitService.instance().init(new SimKitInitModel() ,getContext());
this.simPlayer = SimKitService.instance().createSimPlayer();
播放器 SDK 支持设置 Vid 和 DirectUrl 播放源。
Vid 播放源:如果您已将视频上传至火山引擎视频点播服务,可使用 Vid 方式播放视频。您需要设置 Vid
和 PlayAuthToken
参数。这两个参数是由应用服务端下发给客户端。详情请见通过临时播放 Token 播放。
let vid = "your vid"; // appServer 下发的 videoId let playAuthToken = "your video id's play auth token"; // appServer 下发的临时播放 Token // 构造播放源 let dataSource = new VidSource(vid, playAuthToken) // 使用播放源播放 this.simPlayer.play(dataSource);
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.simPlayer.play(dataSource);
build() { Stack() { // 构造视图控件并传入播放器和播放源 SimVideoView({ playRequest: this.dataSource, simPlayer: this.simPlayer, }) .width('100%') .height('100%') } .width('100%') .height('100%') }
调用 play
开始播放。SDK 默认会边播边缓存。
this.simPlayer.play(this.dataSource, PlayOptions.create() .setEnableMdl(true));
说明
HLS 视频目前不支持边播边缓存。播放 HLS 视频时,需将 setEnableMdl
设为 false
。
this.simPlayer.release(this.dataSource.getVid())
/** * 订阅播放回调,每次调用 SDK 内部会创建新的 IPlayEventObserver 并返回, * 同一次播放可订阅多次,使用完需要解除订阅。 * @param dataSource, 设置的播放源 * @param observerName, Observer命名标识,标记所在业务场景,方便后续异常定位 * @returns */ subscribeObserverByPlayRequest(dataSource: DataSource, observerName: string): IPlayEventObserver /** * 取消订阅,确保与订阅方法逻辑对称。 * @param vid,对应播放源的 vid * @param playEventObserver,需要解除订阅的 IPlayEventObserver */ unsubscribeObserver(vid: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 /** * 播放至结尾回调,不论用户是否开启loop,播放至结尾均触发回调 */ 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 /** * 播放进度变化回调,默认间隔 1s 上报,因用户操作(seek)产生的时间变化会立刻上报 * @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 /** * 获取到 VideoModel 回调 * @param videoModel,播放使用的 VideoModel */ onFetchVideoModel(callback: (sourceID: string | undefined, videoModel: VideoModel) => void): IPlayEventObserver /** * 视频宽高变化回调,仅系统播放器回调 * @param width 视频宽 * @param height 视频高 */ onVideoSizeChange(callback: (sourceID: string | undefined, width: number, height: number) => void): IPlayEventObserver }
// 订阅回调 this.playEventObserver = this.simPlayer .subscribeObserverByPlayRequest(this.dataSource, 'FeedItemView') .onPrepared((sourceID) => { }) .onPaused((sourceID) => { }) .onPlaying((sourceID) => { }) .onBuffering((sourceID, start) => { }) .onTimeChange((sourceID, currentDuration) => { }) .onBufferingPercent((sourceID, percent) => { }) .onError((sourceID, error) => { }) // 结束播放时,取消订阅 this.simPlayer.unsubscribeObserver(this.dataSource.getVid(), this.playEventObserver)
// 暂停播放 this.simPlayer.pause(this.dataSource.getVid()) // 恢复播放 this.simPlayer.resume(this.dataSource.getVid())
// 演示 seek 到 1 秒的位置 this.simPlayer.seek(1000);
this.simPlayer.play(playRequest, PlayOptions.create() // 单位为毫秒,以下示例表示从 1 秒处起播 .setInitialStartTimeMs(1000);
// 默认值为 1。支持取值:0.5, 1, 1.5, 2, 2.5, 3 this.simPlayer.setSpeed(2)
// 循环播放默认关闭 this.simPlayer.play(playRequest, PlayOptions.create() .setLoop(true));
// 静音 this.simPlayer.setVolume(0); // 取消静音 this.simPlayer.setVolume(1);
// 单位为毫秒 let duration = this.simPlayer.getDuration()
this.simPlayer.play(dataSource, PlayOptions.create() .setEnableHardwareDecode(true));
在 Vid 模式下播放视频时,视频点播服务会根据配置下发一个或多个清晰度的播放地址。播放器触发 onFetchVideoModel
回调后,您可调用 getVideoList
方法获取包含所有清晰度信息的数组,基于该数组实现清晰度列表的展示和清晰度切换逻辑。
构造 Vid 播放源时,若指定起播分辨率,则采用该分辨率;否则,使用清晰度列表的首个分辨率。若指定分辨率不存在,则采用最接近的分辨率。
let dataSource = new VidSource(videoDetail.vid, videoDetail.playAuthToken, Resolution.resolution_360p) this.simPlayer.play(dataSource);
播放器触发 onFetchVideoModel
回调后,调用 getVideoList
方法获取包含所有清晰度信息的数组。
.onFetchVideoModel((sourceID, videoModel) => { let videoList = this.simPlayer.getVideoList(); for (let videoInfo of videoList) { // 清晰度字段,见下面清晰度枚举 console.info("definition is" + videoInfo.definition) } })
获取清晰度列表后,传入要切换清晰度在列表中的 index。
this.simPlayer.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 |
预加载是指在开始播放之前提前下载即将播放视频的头部数据,实现快速起播,提升播放体验。预加载实现成本低,效果显著,适用于各种播放场景。更多介绍,请见预加载规则说明。
注意
该功能仅高级版支持。请确保您已购买高级版的 License,详见播放器 License。
添加预加载任务:预加载与播放使用相同的数据源结构。为保证播放能命中预加载缓存,需确保构造预加载数据源与构造播放数据源时使用的 vid
、url
和 cacheKey
一致。示例代码如下:
// 构造播放源 let videoInfo = new VideoInfo(); videoInfo.urls = [url] videoInfo.fileHash = fileHash let dataSource = new VideoModel(); dataSource.videoInfos = [videoInfo]; dataSource.vid = vid; let preloadSize = 800 * 1024; // 预加载大小 800K let startPreloadOffset = 0; // 预加载的起始位置 // 构造预加载实例 let preloadModel = new PreloadVideoModel(dataSource, startPreloadOffset, preloadSize, (type: PreloadTaskEventType, info: string, extraInfo: string) => { switch (type) { case PreloadTaskEventType.Success: // 预加载成功 case PreloadTaskEventType.Failed: // 预加载失败 case PreloadTaskEventType.Cancel: // 预加载取消 break; } }) // 开始预加载 preloadModel.create();
取消预加载任务:取消未执行和正在下载的任务,已完成的任务不受影响。
// 取消预加载任务 preloadModel.cancel();
添加预加载任务:预加载与播放使用相同的播放源结构。为保证播放能命中预加载缓存,需确保预加载与播放构造的播放源 vid
、playAuthToken
和 resolution
一致。
let vid = "your vid"; // appServer 下发 let playAuthToken = "your video id's play auth token"; // appServer 下发 let source = new VidSource(vid, playAuthToken) // 设置分辨率,如不设置起播分辨率,则使用清晰度列表里的第一个起播 // let source = new VidSource(vid, playAuthToken, Resolution.resolution_360p) let preloadSize = 800 * 1024; // 预加载大小 800K let startPreloadOffset = 0; // 预加载的起始位置 let preloadVid = new PreloadVid(source, startPreloadOffset, preloadSize, (type: PreloadTaskEventType, info: string, extraInfo: string) => { switch (type) { case PreloadTaskEventType.Success: // 预加载成功 case PreloadTaskEventType.Failed: // 预加载失败 case PreloadTaskEventType.Cancel: // 预加载取消 break; } }) preloadVid.create();
取消预加载任务:取消未执行和正在下载的任务,已完成的任务不受影响。
// 取消预加载任务 preloadVid.cancel();
视频点播私有加密方案采用火山引擎自研加密算法,安全级别高,能够便捷、高效、安全地保护您的音视频版权。更多介绍,请见火山引擎私有加密方案。播放器 SDK 支持通过 DirectUrl 模式播放私有加密视频。应用服务端从视频点播服务获取用于解密的密钥 playAuth
并下发给播放器 SDK,即可播放火山引擎私有加密视频。
// 视频源与 vid 必须一一对应 let vid = "your vid"; // 视频 url let url = "http://www.example.com/h264.mp4"; // 播放源唯一标识,与视频源一一对应,如 url 的 MD5 let fileHash = getMd5(url); // 使用服务端签发的临时播放 Token let playAuth = "l7wZ9Em+A/xxxxxxx"; // 构造播放源 let videoInfo = new VideoInfo(); videoInfo.urls = [url] videoInfo.fileHash = fileHash videoInfo.decryptionKey = playAuth let dataSource = new VideoModel(); dataSource.videoInfos = [videoInfo]; dataSource.vid = vid; // 播放 this.simPlayer.play(dataSource, PlayOptions.create() .setEnableMdl(true));
@Entry @Component export struct SmallVideoFeed { // 2. Feed 流数据 private data: FeedDataSource = new FeedDataSource(); // 3. 记录当前显示页面的 index @State currentIndex: number = 0 aboutToAppear(): void { } build() { // 1. 使用 Swiper 实现端视频 Feed 流 // Swiper 官网使用说明 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-swiper-V5 Swiper() { LazyForEach(this.data, (dataSource: DataSource, index: number) => { TTPlayerItemView({ dataSource: dataSource, index: index, currentIndex: this.currentIndex }) .width('100%') .height("100%") }, (dataSource: DataSource) => dataSource.getVid()) } .vertical(true) .loop(false) // 4. cachedCount 可根据业务需求设置 .cachedCount(1) .displayCount(1) .width('100%') .height('100%') .indicator(false) .onChange((index) => { // 5. 页面上下滑动结束,触发此事件,更新 currentIndex。 this.currentIndex = index }) } }
@Component export struct TTPlayerItemView { // 1. 当前 View 在 feed 数组里的 index index: number = 0 // 2. Feed 流页面滑动,currentIndex 更新,执行 onIndexChange @Watch('onIndexChange') @Prop currentIndex: number = 0 @State dataSource: DataSource = new DataSource(); private simPlayer?: ISimPlayer; aboutToAppear() { this.simPlayer = SimKitService.instance().createSimPlayer(); if (this.index == 0 && this.currentIndex == 0) { // 3. 进入 Feed 流页面的第一个视频,直接开始播放 this.simPlayer?.play(this.dataSource, PlayOptions.create()); } else { // 4. 滑动到其他页面先进行预渲染,onIndexChange 页面切换完成时再播放 this.simPlayer?.preRender(this.dataSource, PlayOptions.create()); } } onIndexChange() { // 5. 页面切换完成 if (this.index === this.currentIndex) { // 此 View 是要显示的页面,开始播放 this.simPlayer?.play(this.dataSource, PlayOptions.create()); } else { // 此 View 不是要显示的页面,停止播放 this.simPlayer?.stop(this.dataSource?.getVid()) } } aboutToDisappear() { // 6. 画面退出,释放播放器 this.simPlayer?.release(this.dataSource?.getVid()) } build() { Stack() { SimVideoView({ playRequest: this.dataSource, simPlayer: this.simPlayer, }) } } }
当 License 过期或其他错误发生时,播放器会报错 -30001
,无法正常使用。此时建议切换至鸿蒙系统播放器进行播放。系统播放器效果较差,因此建议及时更新 License,避免过期。
this.playEventObserver = this.simPlayer.subscribeObserverByPlayRequest(this.dataSource, '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.dataSource, simPlayer: this.simPlayer, }) .width(this.playerWidth + 'px') .height(this.playerHeight + 'px') } .width('100%') .height('100%') } this.playEventObserver = this.simPlayer.subscribeObserverByPlayRequest(this.dataSource, '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()) { // 接入方自行封装鸿蒙系统播放器 }
详见以下鸿蒙官方文档: