You need to enable JavaScript to run this app.
文档中心
视频点播

视频点播

复制全文
下载 pdf
历史文档
HarmonyOS NEXT 播放器 SDK 使用文档(历史版本)
复制全文
下载 pdf
HarmonyOS NEXT 播放器 SDK 使用文档(历史版本)

本文为您介绍如何以最简单快捷的方式让视频播放起来。

适用版本

此文档适用于 HarmonyOS NEXT 播放器 SDK 3.1.1-tob 之前的版本。

前提条件

请确保您已将 HarmonyOS NEXT 播放器 SDK 集成至您的项目中,详见集成 SDK

开始播放

步骤 1:开启日志调试

初始化播放器 SDK 之前,开启日志,便于调试和排查问题。

注意

线上版本请务必关闭日志,减少性能开销。

VodEnv.openLog()

步骤 2:初始化 SDK 并配置 License

初始化 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());

步骤 3:创建播放器

this.simPlayer = SimKitService.instance().createSimPlayer();

步骤 4:构造播放源

播放器 SDK 支持设置 Vid 和 DirectUrl 播放源。

  • Vid 播放源:如果您已将视频上传至火山引擎视频点播服务,可使用 Vid 方式播放视频。您需要设置 VidPlayAuthToken 参数。这两个参数是由应用服务端下发给客户端。详情请见通过临时播放 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);
    

步骤 5:构造视图控件

build() {
  Stack() {
    // 构造视图控件并传入播放器和播放源
    SimVideoView({
      playRequest: this.dataSource,
      simPlayer: this.simPlayer,
    })
      .width('100%')
      .height('100%')
  }
  .width('100%')
  .height('100%')
}

步骤 6:播放视频

调用 play 开始播放。SDK 默认会边播边缓存。

this.simPlayer.play(this.dataSource, PlayOptions.create()
  .setEnableMdl(true));

说明

HLS 视频目前不支持边播边缓存。播放 HLS 视频时,需将 setEnableMdl 设为 false

步骤 7:释放播放器

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 到指定位置播放

// 演示 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

DirectUrl 模式

  1. 添加预加载任务:预加载与播放使用相同的数据源结构。为保证播放能命中预加载缓存,需确保构造预加载数据源与构造播放数据源时使用的 vidurlcacheKey 一致。示例代码如下:

    // 构造播放源
    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();
    
  2. 取消预加载任务:取消未执行和正在下载的任务,已完成的任务不受影响。

    // 取消预加载任务
    preloadModel.cancel();
    

Vid 模式

  1. 添加预加载任务:预加载与播放使用相同的播放源结构。为保证播放能命中预加载缓存,需确保预加载与播放构造的播放源 vidplayAuthTokenresolution 一致。

    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();
    
  2. 取消预加载任务:取消未执行和正在下载的任务,已完成的任务不受影响。

    // 取消预加载任务
    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));

短视频场景预渲染

Feed 流页面

@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
    })
  }
}

Item 播放页面

@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 相关错误?

当 License 过期或其他错误发生时,播放器会报错 -30001,无法正常使用。此时建议切换至鸿蒙系统播放器进行播放。系统播放器效果较差,因此建议及时更新 License,避免过期。

监听 License 错误

this.playEventObserver = this.simPlayer.subscribeObserverByPlayRequest(this.dataSource, 'FeedItemView')
  .onError((sourceID, error) => {
      // license 校验失败
      if (error.codec == TTPlayerErrorCode.LICENSE_FAIL) {
      }
  })

方案 1:使用播放器 SDK 封装的系统播放器

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;
    }
})

方案 2:自行封装鸿蒙系统播放器

VodEnv.addLicenseFile(this.license)

.....
// addLicenseFile 后,检查 license,如不可用,使用自己封装的系统播放器。
if (!VodEnv.isLicenseAvailable()) {
  // 接入方自行封装鸿蒙系统播放器
}

详见以下鸿蒙官方文档:

最近更新时间:2025.06.06 16:20:17
这个页面对您有帮助吗?
有用
有用
无用
无用