HarmonyOS 如何在页面创建前,提前加载视频,在页面显示时立刻播放?

短视频流AvPlayer/自研播放器需要surfaceID用来显示视频流,但surface创建依赖xComponent的显示。如何在切换到页面前即预先做好AVplayer的的prepare,提升起播速度?

HarmonyOS
4天前
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
superinsect

参考此demo构造了一个简单的例子,在首页tab通过BuilderNode预先隐藏地创建4个xComponent(触发surface的创建)并保存到池中,播放页面使用池中的xComponent实例。

import { LazyDataSource } from './datasource'; 
import { BuilderNode, FrameNode, NodeController } from '@ohos.arkui.node'; 
import promptAction from '@ohos.promptAction'; 
 
@Observed 
class VideoMeta { 
  static _id = -1; 
  uid: string = ''; 
  src: string = ''; 
 
  constructor() { 
    this.uid = (VideoMeta._id + 1).toString(); 
    VideoMeta._id = VideoMeta._id + 1; 
    this.src = Math.random().toString(); 
  } 
} 
 
async function loadMoreVideoMeta(lazyDataSource: LazyDataSource<VideoMeta>) { 
  lazyDataSource.addData(new VideoMeta()) 
} 
 
@Entry 
@Component 
struct Index { 
  @State message: string = 'Hello World'; 
  @State dataSource: LazyDataSource<VideoMeta> = new LazyDataSource<VideoMeta>(); 
  private xcControllers: Array<PlayerNodeController> = []; 
  private cacheCount = 1; 
  @State xcPreloaded: number = 0; 
 
  aboutToAppear(): void { 
    loadMoreVideoMeta(this.dataSource); 
    for (let index = 0; index < this.cacheCount + 3; index++) { 
      this.xcControllers.push(new PlayerNodeController()) 
    } 
  } 
 
  build() { 
    Tabs() { 
      TabContent() { 
        Stack() { 
          Image($r('app.media.startIcon')) 
          if (this.xcPreloaded < this.cacheCount + 3) { 
            ForEach(this.xcControllers, (c: PlayerNodeController, index: number) => { 
              NodeContainer(c).onAppear(() => { 
                this.xcPreloaded += 1; 
              }).onDisAppear(() => { 
                if (index == 0) { 
                  c.p2ff(this.dataSource.getData(0)); 
                } 
                c.release(); 
              }).visibility(Visibility.None) 
            }, (c: PlayerNodeController) => c.playerContext.playerId) 
          } 
        }.backgroundColor(Color.Pink).width('100%').height('100%') 
      }.tabBar("首页") 
 
      TabContent() { 
        Swiper() { 
          LazyForEach(this.dataSource, (item: VideoMeta, index: number) => { 
            Stack() { 
              VideoComponent({ videoMeta: item }) 
            }.backgroundColor(Color.Green).width('100%').height('100%') 
            .onAppear(() => { 
              // 即将触底时提前增加数据 
              if (index + 1 >= this.dataSource.totalCount() - 1) { 
                for (let i = 0; i < 5; i++) { 
                  loadMoreVideoMeta(this.dataSource); 
                } 
              } 
            }) 
          }, (item: VideoMeta) => item.uid) 
        } 
        .cachedCount(this.cacheCount) 
        .loop(false) 
        .indicator(false) 
        .vertical(true) 
      }.tabBar("视频") 
    }.barPosition(BarPosition.End) 
  } 
} 
 
@Reusable 
@Component 
struct VideoComponent { 
  @State videoMeta: VideoMeta | undefined = undefined; 
  @State playerId: string | undefined = ""; 
  playerNodeController: PlayerNodeController | undefined; 
 
  aboutToAppear(): void { 
    console.log("VideoComponent aboutToAppear"); 
    this.playerNodeController = new PlayerNodeController(this.videoMeta); 
    this.playerId = this.playerNodeController.playerContext.playerId; 
    this.playerNodeController.p2ff(this.videoMeta!); 
  } 
 
  aboutToDisappear(): void { 
    console.log("VideoComponent aboutToDisappear") 
    this.playerNodeController!.release(); 
  } 
 
  aboutToReuse(params: Record<string, ESObject>): void { 
    this.videoMeta = params.videoMeta; 
    this.playerNodeController!.update(this.videoMeta!); 
    this.playerId = this.playerNodeController!.playerContext.playerId; 
    this.playerNodeController!.p2ff(this.videoMeta!); 
  } 
 
  aboutToRecycle(): void { 
    console.log("VideoComponent aboutToRecycle") 
    this.playerNodeController!.release(); 
  } 
 
  build() { 
    Stack() { 
      NodeContainer(this.playerNodeController) 
      Column({ space: 5 }) { 
        Text("playerId: " + this.playerId).fontColor(Color.Pink) 
        Text("videoId: " + this.videoMeta!.uid).fontColor(Color.Pink) 
      } 
    }.onVisibleAreaChange([0, 1], (isVisible: boolean, ratio: number) => { 
      if (isVisible && ratio > 0 && 
        this.playerNodeController!.playerContext.preparedVideoUid != this.videoMeta!.uid) { 
        this.playerNodeController!.p2ff(this.videoMeta!) 
      } 
      if (isVisible && ratio == 1) { 
        this.playerNodeController?.play(); 
      } 
    }) 
  } 
} 
 
class PlayerContext { 
  private static _id = -1; 
  playerId: string; 
  playerBuilderNode: BuilderNode<[PlayerContext]> | undefined; 
  xComponentController: XComponentController = new XComponentController(); 
  preparedVideoUid: string | undefined; 
 
  constructor() { 
    this.playerId = (PlayerContext._id + 1).toString(); 
    PlayerContext._id = PlayerContext._id + 1; 
  } 
} 
 
@Builder 
function xcBuilder(xcParams: PlayerContext) { 
  XComponent({ 
    id: xcParams.playerId, 
    type: XComponentType.SURFACE, 
    controller: xcParams.xComponentController 
  }) 
    .width('100%') 
    .height('100%') 
} 
 
let playerNodePool: Array<PlayerContext> = [] 
 
class PlayerNodeController extends NodeController { 
  public playerContext: PlayerContext; 
 
  constructor(videoMeta?: VideoMeta) { 
    super(); 
    const index = playerNodePool.findIndex((value: PlayerContext) => value.preparedVideoUid == videoMeta?.uid); 
    if (index >= 0) { 
      this.playerContext = playerNodePool[index]; 
      playerNodePool.splice(index, 1); 
    } else { 
      this.playerContext = playerNodePool.shift() ?? new PlayerContext(); 
    } 
  } 
 
  update(videoMeta: VideoMeta) { 
    const index = playerNodePool.findIndex((value: PlayerContext) => value.preparedVideoUid == videoMeta?.uid); 
    if (index >= 0) { 
      this.playerContext = playerNodePool[index]; 
      playerNodePool = playerNodePool.splice(index, 1); 
    } else { 
      this.playerContext = playerNodePool.shift() ?? new PlayerContext(); 
    } 
  } 
 
  release() { 
    playerNodePool.push(this.playerContext); 
  } 
 
  makeNode(uiContext: UIContext): FrameNode { 
    if (this.playerContext.playerBuilderNode == undefined) { 
      this.playerContext.playerBuilderNode = new BuilderNode(uiContext); 
      this.playerContext.playerBuilderNode.build(wrapBuilder(xcBuilder), this.playerContext); 
    } 
    return this.playerContext.playerBuilderNode!.getFrameNode()!; 
  } 
 
  async p2ff(videoMeta: VideoMeta) { 
    const surfaceId = this.playerContext.xComponentController.getXComponentSurfaceId(); 
    if (surfaceId != "" && this.playerContext.preparedVideoUid != videoMeta.uid) { 
      promptAction.showToast({ message: "p2ff: " + videoMeta.uid + " in player " + this.playerContext.playerId }) 
      setTimeout(() => { 
        // simulate actual p2ff 
        this.playerContext.preparedVideoUid = videoMeta.uid; 
      }, 200) 
    } 
  } 
  async play() { 
  } 
  async pause() { 
  } 
}

基于此可以实现surface的预创建,在视频显示时复用。代码仅供参考思路。

分享
微博
QQ
微信
回复
4天前
相关问题
如何实现页面加载从接口获取数据
2313浏览 • 1回复 待解决
HarmonyOS webview提前创建复用
62浏览 • 1回复 待解决
HarmonyOS web加载页面图片不显示
321浏览 • 1回复 待解决
HarmonyOS webview加载页面无法显示
358浏览 • 1回复 待解决
页面加载获取网络图片的宽高
578浏览 • 1回复 待解决
如何在navigation跳转页面返回传参
1470浏览 • 1回复 待解决
创建多个视频组件无法播放
2123浏览 • 1回复 待解决
HarmonyOS如何根据条件加载页面
236浏览 • 1回复 待解决
hsp子模块中如何加载悬浮窗页面
1774浏览 • 1回复 待解决
使用terminateSelf如何退回到一个页面
1807浏览 • 1回复 待解决