You need to enable JavaScript to run this app.
导航
集成 SDK
最近更新时间:2024.08.30 11:25:11首次发布时间:2024.08.16 14:11:59

本文为您介绍如何将 HarmonyOS NEXT 点播 SDK 集成至您的项目中。

功能详情

当前版本的主要功能如下表所示。

功能

说明

播放协议与格式

音视频格式

支持 HLS、MP4 等点播场景常见的视频格式。

DirectUrl 播放

支持以 DirectUrl 方式播放本地视频和网络视频。

H.264 编码格式

支持播放 H.264 编码协议的视频流。

H.265 编码格式

支持播放 H.265 编码协议的视频流。

播放控制

基础播放控制

支持开始、结束、暂停和恢复等播放控制功能。

Seek

支持拖动到指定位置。

重播

支持视频播放结束后手动触发重播。

续播

支持设置续播起播时间点。

循环播放

支持视频播放结束后自动重播。

倍速播放

支持变速播放,与此同时音频变速不变调。

清晰度切换

支持用户流畅无卡顿地切换视频的多路清晰度流。

音频效果

音量调节

支持调用系统接口调节观看视频的音量。

静音

支持开启和关闭静音。

视频效果

填充模式

默认为等比例填充。若视频比例和容器比例不一致,可能存在黑边

播放性能

预渲染

在播放当前视频时,提前创建播放器并对下一个视频进行解码和渲染,同时可将预渲染的首帧用作视频封面,提前展示给用户。

多实例

支持在同一界面添加多个播放器并同时播放。

播放失败重试

播放失败时自动重试。

事件回调

支持对播放状态回调、首帧回调、播放完成或失败回调。

视频安全

Referer 黑白名单

支持通过播放请求中携带的 Referer 字段识别请求来源,以黑名单或白名单方式对请求来源进行控制。

质量上报

日志上报

支持上报播放器 SDK 日志,统计点播视频播放的埋点数据。

播放数据大盘

支持观测播放量、播放质量等大盘数据。

集成准备

环境要求

类别

说明

开发环境

DevEco Studio(推荐使用最新版本)

兼容的最低 SDK 版本

"compatibleSdkVersion": "5.0.0(12)"

ABI 兼容性

架构要求:arm64

创建应用并获取 License

获取 SDK

HarmonyOS NEXT 点播 SDK 目前以 har 包的形式发布,仅支持通过本地配置的方式集成。

鸿蒙点播SDK离线包-1.0.0.zip
未知大小

添加依赖

  1. 将 har 文件放置于下图所示的路径中。
    图片

  2. 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",
      }
    }
    
  3. 在工程 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

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

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

最佳实践

短视频场景预渲染

Feed 流页面

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

Item 播放页面

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

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

监听 License 错误

this.playEventObserver = this.simPlayer
.subscribeObserver(this.playRequest.getSourceID(), '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.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;
    }
})

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

VodEnv.addLicenseFile(this.license)

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

详见以下鸿蒙官方文档: