You need to enable JavaScript to run this app.
视频点播

视频点播

Copy page
Download PDF
HarmonyOS NEXT 播放器 SDK
进阶功能
Copy page
Download PDF
进阶功能

本文为您介绍如何实现 HarmonyOS NEXT 播放器 SDK 的进阶功能。

warning

本文适用于播放器 SDK 3.1.2-tob 及之后的版本。若您使用更早版本,请见HarmonyOS NEXT 播放器 SDK 使用文档(历史版本)

自定义预加载

预加载是指在开始播放之前提前下载即将播放视频的头部数据,实现快速起播,提升播放体验。预加载实现成本低,效果显著,适用于各种播放场景。更多介绍,请见预加载规则说明

warning

  • 该功能仅高级版支持。请确保您已购买高级版的 License,详见License 管理
  • 目前不支持 HLS 和 BASH(经火山引擎优化的升级版 DASH 协议)视频流的预加载。

DirectUrl 模式

  1. 添加预加载任务:预加载与播放使用相同的数据源结构。DirectUrl 模式适用于播放来自火山引擎或第三方 CDN 的视频 URL 地址。通过传入视频的直接播放地址来指定播放内容。除了 url,您还必须提供以下参数:

    • vid:视频的唯一标识。可以是您自己的视频管理系统中的唯一标识,也可以在 App 层自行生成一个 ID。此 ID 会用于播放器 SDK 的日志上报、预加载、预渲染等功能中。
    • fileHash:视频的缓存索引 Key。为确保唯一性,推荐使用 URL 的 MD5 值。为确保播放器缓存和下载功能的稳定性,强烈建议您遵循以下规范来设置 fileHash
      • 使用字符范围:取值应仅包含字母 (a-z, A-Z) 和数字 (0-9)。
      • 避免特殊符号不要使用任何特殊符号,例如 _-/?& 等。
        为保证播放能命中预加载缓存,需确保构造预加载数据源与构造播放数据源时使用的 vidurlfileHash 一致。示例代码如下:
    // 构造播放源
    let videoInfo = new VideoInfo();
    videoInfo.urls = [url]
    videoInfo.fileHash = fileHash
    
    let dataSource = new VideoModel();
    dataSource.videoInfos = [videoInfo];
    dataSource.vid = vid;
    
    let preloadSize = 800 * 1024; // 预加载大小 800KB   
    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; // 预加载大小 800KB   
    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);
// 使用服务端签发的 playAuth
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.player = new VePlayer(this.dataSource)
this.player.play();

短视频场景预渲染

Feed 流页面

@Entry
@ComponentV2
export struct ShortVideoFeed {
  // 1. Feed 流数据
  private data: FeedDataSource = new FeedDataSource();
  // 2. 记录当前显示页面的 index
  @Local currentIndex: number = 0
  // 3. 记录已出首帧页面的 index
  @Local renderStartIndex: number = -1

  aboutToAppear(): void {
  }

  build() {
    // 4. 使用 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) => {
        ShortVideoView({
          dataSource: dataSource,
          index: index,
          currentIndex: this.currentIndex,
          renderStartIndex:this.renderStartIndex,
          onRenderStartIndexChange: (val: number) => {
              // 5. 页面出首帧后更新
              this.renderStartIndex = val;
          }
        })
          .width('100%')
          .height("100%")
      }, (dataSource: DataSource) => dataSource.getVid())
    }
    .vertical(true)
    .loop(false)
    // 6. 设置预加载子组件个数,以当前页面为基准,加载当前显示页面的前后个数,根据业务需求设置
    .cachedCount(1)
    .displayCount(1)
    .width('100%')
    .height('100%')
    .indicator(false)
    .onChange((index) => {
      // 7. 页面上下滑动结束,触发此事件,更新 currentIndex。
      this.currentIndex = index
    })
  }
}

Item 播放页面

@ComponentV2
export struct ShortVideoView {
  // 1. 当前页面在 feed 数组里的 index
  @Param @Require index: number
  @Param @Require dataSource: DataSource
  // 2. 记录当前显示页面的 index
  @Param currentIndex: number = 0
  // 3. 记录已出首帧页面的 index
  @Param renderStartIndex: number = -1;
  // 4. 页面出首帧后回调
  @Event onRenderStartIndexChange: (val: number) => void;
 
  private player?: VePlayer;

  aboutToAppear() {
    this.player = new VePlayer(this.dataSource)
    this.subscribeObserver()
    if (this.index == 0 && this.currentIndex == 0) {
      // 5. 进入 Feed 流页面的第一个视频,直接开始播放
      this.player.play()
    }
  }
  
  subscribeObserver() {
      this.playEventObserver = this.player?.subscribeObserver('')
          .onRenderFirstFrame((vid) => {
              this.onRenderStartIndexChange(this.currentIndex)
          })
  }
  
  // 6. Feed 流页面滑动,页面切换完成,currentIndex 更新,执行 onIndexChange
  @Monitor("currentIndex")
  onIndexChange() {
    if (this.index === this.currentIndex) {
      // 此 View 是要显示的页面,开始播放
      this.player?.play();
    } else {
       // 此 View 不是要显示的页面,停止播放
      this.player?.stop()
    }
  }
  
  @Monitor("renderStartIndex")
  onRenderStartIndex() {
    // 7. 收到某播放器出首帧消息,是当前显示页面出了首帧,预渲染下一个视频,onIndexChange 页面切换完成时再播放
    if (this.currentIndex == this.renderStartIndex
      && this.index == this.currentIndex + 1) {
      this.player?.prepare()
    }
  }

  aboutToDisappear() {
    // 8. 画面退出,释放播放器
    this.player?.release()
  }

  build() {
      Stack() {
          TTAVPlayerView2({
            scaleType: TTAVScaleType.AspectFit,
            onLoad: (window) => {
              this.player?.bindWindow(window)
            }
          })
      }
    }
}
Last updated: 2026.02.03 14:37:12