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

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

HarmonyOS
2024-11-13 10:21:26
652浏览
收藏 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() { 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.

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

分享
微博
QQ
微信
回复
2024-11-13 17:53:40
相关问题
如何实现页面加载从接口获取数据
3521浏览 • 1回复 待解决
页面加载获取网络图片的宽高
1482浏览 • 1回复 待解决
HarmonyOS web加载页面图片不显示
1564浏览 • 1回复 待解决
HarmonyOS webview加载页面无法显示
1797浏览 • 1回复 待解决
HarmonyOS webview提前创建复用
625浏览 • 1回复 待解决
创建多个视频组件无法播放
3060浏览 • 1回复 待解决