42 XComponent 与视频渲染

系列五:直播与画中画 · 第 2 篇

对应工程:features/multishoppinglive/src/main/ets/components/LiveContent.ets

引言

上一篇我们把直播页拆成了"总装车间",这一篇钻进视频渲染的底层。HarmonyOS 的视频画面不在普通组件里画——AVPlayer 解码出的视频帧需要一个"Surface 画布"来承载,而这个画布在 ArkUI 里就是 XComponent。它是 ArkUI 与系统原生渲染能力之间的桥:一端是 UI 组件树里的节点,另一端是 surfaceId 指向的原生 Surface。本项目 LiveContent.ets 完整演示了「XComponent 声明 → onLoad 拿 surfaceId → 交给 AVPlayer」的标准链路,并且它的 XComponentController 还被画中画复用——这是直播页能无缝进入 PiP 的关键设计。

XComponent 组件能力

XComponent 支持三种 type

  • SURFACE:承载原生 Surface,视频/相机/OpenGL 渲染都走它。
  • TEXTURE:纹理类型,用于相机等需要二次加工的流。
  • NODE:原生节点,性能最高但侵入性最强。
直播视频用 SURFACE 即可。LiveContent 的声明如下:

XComponent({
  type: XComponentType.SURFACE,
  controller: this.pipWindowUtil?.getXComponentController()
})
  .onLoad(() => {
    if (this.pipWindowUtil === undefined) {
      return;
    }
    this.surfaceId = this.pipWindowUtil.getXComponentController().getXComponentSurfaceId();
    if (this.avPlayerUtil === undefined) {
      return;
    }
    this.avPlayerUtil.createAvPlayer(this.surfaceId).catch((error: BusinessError) => {
      Logger.error(`Failed to create AvPlayer. Code: ${error.code}, message: ${error.message}`);
    });
  })

两个细节值得注意:

  • controller 来自 PipWindowUtilXComponentControllerPipWindowUtilprivate mXComponentController = new XComponentController() 持有并对外暴露 getXComponentController()。这样同一个控制器实例既服务页面上的 XComponent,又会在画中画创建时作为 componentController 传入——PiP 窗口接管的是同一个 Surface,视频画面才能无缝"飞出"页面。
  • onLoad 才拿 surfaceId。XComponent 的 Surface 是异步创建的,getXComponentSurfaceId() 必须在 onLoad(Surface 就绪)之后调用,否则拿到空字符串,AVPlayer 绑定会失败。

surfaceId 的获取与传递

getXComponentSurfaceId() 返回的是 Surface 的 id 字符串。拿到后立即传给 avPlayerUtil.createAvPlayer(surfaceId),后续由 AVPlayer 封装类完成"绑定 surfaceId → prepare → play"的完整流程(第 3 篇详解)。这层封装的价值在于:UI 层只关心"我拿到了画布 id",播放器状态机与资源管理全部下沉到 AvPlayerUtil,组件代码保持极薄。

LiveContent 的完整字段很简单:

@ComponentV2
export struct LiveContent {
  private static readonly VIDEO_ASPECT_RATIO: number = 16 / 9;
  @Param public widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_SM;
  @Local private surfaceId: string = '';
  private pipWindowUtil: PipWindowUtil | undefined = PipWindowUtil.getPipWindowUtil(this.getUIContext());
  private avPlayerUtil: AvPlayerUtil | undefined = AvPlayerUtil.getAvPlayerUtil(this.getUIContext());
}

两个单例都在组件初始化时经 getUIContext() 获取——注意用的是 this.getUIContext() 而非全局 getContext(this),因为单例内部要通过 uiContext.getHostContext()resourceManager 读 rawfile(第 10 篇)。

多端尺寸适配

视频区最怕"变形"。LiveContent 的做法是:宽断点定宽度、窄断点定高度,再叠加固定 16:9 比例

.width(new WidthBreakpointType('100%', '100%', 'auto', 'auto').getValue(this.widthBp))
.height(new WidthBreakpointType('auto', 'auto', '100%', '100%').getValue(this.widthBp))
.aspectRatio(LiveContent.VIDEO_ASPECT_RATIO)

  • XS/SM(手机竖屏):宽度撑满 100%,高度 auto,由 16:9 推导——视频横向铺满屏幕。
  • MD/LG/XL(平板/PC 宽屏):高度撑满 100%,宽度 auto,由 16:9 推导——视频纵向铺满舞台。
两层布局把画布居中:外层 Column().justifyContent(FlexAlign.Center) + 内层 Stack({ alignContent: Alignment.Center }),保证任何断点下视频都在舞台正中。外层 Stack 还挂了一个 onClick 做播放/暂停切换:

Stack({ alignContent: Alignment.Center }) {
  Column() { XComponent({ ... }) }
    .width('100%').height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
}
.width('100%')
.layoutWeight(1)
.onClick(() => {
  if (this.avPlayerUtil === undefined) {
    return;
  }
  this.avPlayerUtil.playerStateControl();
})

playerStateControl 是"播放 ↔ 暂停"的总开关(第 9 篇展开),点击视频任意位置即可切换,符合直播间"点画面暂停"的交互直觉。

生命周期中的资源释放

视频组件消失时必须归还播放器,否则后台会残留解码线程与文件描述符:

aboutToDisappear(): void {
  if (this.avPlayerUtil === undefined) {
    return;
  }
  this.avPlayerUtil.release();
}

注意这里只释放 AVPlayer,不碰 PiP——PiP 的停止由页面层 LiveBroadCastPage.aboutToDisappear 负责,职责分离避免双重释放。第 4 篇会看到 stopPip 内部还有 Promise 串行化防止并发调用冲突。

注意事项与总结

  • type 别用错:视频播放必须 XComponentType.SURFACE;用 TEXTURE 会拿不到预期的 Surface 语义,NODE 类型则与 ArkUI 状态系统耦合更深。
  • onLoad 是唯一安全时机:在 aboutToAppear 里调 getXComponentSurfaceId() 大概率拿到空串,Surface 尚未创建。
  • controller 全局唯一:XComponent 的 controller 要与 PiP 共用同一实例,这是"画面从页面平滑过渡到画中画"的前提;新建一个 controller 传给 PiP 会导致 PiP 窗口黑屏。
  • 比例适配:用 aspectRatio(16/9) + 断点控制"撑宽还是撑高",比硬编码宽高更抗多端变化。
XComponent 是视频渲染的"画布",下一篇我们看"画笔":AVPlayer 的状态机与 AvPlayerUtil 的播放控制封装。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐