# 托管网页中的媒体播放 Web组件提供了应用接管网页中媒体播放的能力,用来支持应用增强网页的媒体播放,如画质增强等。 ## 使用场景 网页播放媒体时,存在着网页视频不够清晰、网页的播放器界面简陋功能少、一些视频不能播放的问题。 此时,应用开发者可以使用该功能,通过自己或者第三方的播放器,接管网页媒体播放来改善网页的媒体播放体验。 ## 实现原理 ### ArkWeb内核播放媒体的框架 不开启该功能时,ArkWeb内核的播放架构如下所示: ![arkweb media pipeline](figures/arkweb_media_pipeline.png) > **说明:** > > - 上图中1表示ArkWeb内核创建WebMediaPlayer来播放网页中的媒体资源。 > - 上图中2表示WebMediaPlayer使用系统解码器来渲染媒体数据。 开启该功能后,ArkWeb内核的播放架构如下: ![arkweb native media player](figures/arkweb_native_media_player.png) > **说明:** > > - 上图中1表示ArkWeb内核创建WebMediaPlayer来播放网页中的媒体资源。 > - 上图中2表示WebMediaPlayer使用应用提供的本地播放器(NativeMediaPlayer)来渲染媒体数据。 ### ArkWeb内核与应用的交互 ![interactions between arkweb and native media player](figures/interactions_between_arkweb_and_native_media_player.png) > **说明:** > > - 上图中1的详细说明见[开启接管网页媒体播放](#开启接管网页媒体播放)。 > - 上图中2的详细说明见[创建本地播放器](#创建本地播放器nativemediaplayer)。 > - 上图中3的详细说明见[绘制本地播放器组件](#绘制本地播放器组件)。 > - 上图中4的详细说明见[执行 ArkWeb 内核发送给本地播放器的播控指令](#执行arkweb内核发送给本地播放器的播控指令)。 > - 上图中5的详细说明见[将本地播放器的状态信息通知给 ArkWeb 内核](#将本地播放器的状态信息通知给arkweb内核)。 ## 开发指导 ### 开启接管网页媒体播放 需要先通过[enableNativeMediaPlayer](../reference/apis-arkweb/ts-basic-components-web.md#enablenativemediaplayer12)接口,开启接管网页媒体播放的功能。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; @Entry @Component struct WebComponent { controller: webview.WebviewController = new webview.WebviewController(); build() { Column() { Web({ src: 'www.example.com', controller: this.controller }) .enableNativeMediaPlayer({ enable: true, shouldOverlay: false }) } } } ``` ### 创建本地播放器(NativeMediaPlayer) 该功能开启后,每当网页中有媒体需要播放时,ArkWeb内核会触发由[onCreateNativeMediaPlayer](../reference/apis-arkweb/js-apis-webview.md#oncreatenativemediaplayer12)注册的回调函数。 开发者则需要调用 `onCreateNativeMediaPlayer` 来注册一个创建本地播放器的回调函数。 该回调函数需要根据媒体信息来判断是否要创建一个本地播放器来接管当前的网页媒体资源。 * 如果应用不接管当前的为网页媒体资源, 需要在回调函数里返回 `null` 。 * 如果应用接管当前的为网页媒体资源, 需要在回调函数里返回一个本地播放器实例。 本地播放器需要实现[NativeMediaPlayerBridge](../reference/apis-arkweb/js-apis-webview.md#nativemediaplayerbridge12)接口,以便ArkWeb内核对本地播放器进行播控操作。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; // 实现 webview.NativeMediaPlayerBridge 接口。 // ArkWeb 内核调用该类的方法来对 NativeMediaPlayer 进行播控。 class NativeMediaPlayerImpl implements webview.NativeMediaPlayerBridge { // ... 实现 NativeMediaPlayerBridge 里的接口方法 ... constructor(handler: webview.NativeMediaPlayerHandler, mediaInfo: webview.MediaInfo) {} updateRect(x: number, y: number, width: number, height: number) {} play() {} pause() {} seek(targetTime: number) {} release() {} setVolume(volume: number) {} setMuted(muted: boolean) {} setPlaybackRate(playbackRate: number) {} enterFullscreen() {} exitFullscreen() {} } @Entry @Component struct WebComponent { controller: webview.WebviewController = new webview.WebviewController(); build() { Column() { Web({ src: 'www.example.com', controller: this.controller }) .enableNativeMediaPlayer({ enable: true, shouldOverlay: false }) .onPageBegin((event) => { this.controller.onCreateNativeMediaPlayer((handler: webview.NativeMediaPlayerHandler, mediaInfo: webview.MediaInfo) => { // 判断需不需要接管当前的媒体。 if (!shouldHandle(mediaInfo)) { // 本地播放器不接管该媒体。 // 返回 null 。ArkWeb 内核将用自己的播放器来播放该媒体。 return null; } // 接管当前的媒体。 // 返回一个本地播放器实例给 ArkWeb 内核。 let nativePlayer: webview.NativeMediaPlayerBridge = new NativeMediaPlayerImpl(handler, mediaInfo); return nativePlayer; }); }) } } } // stub function shouldHandle(mediaInfo: webview.MediaInfo) { return true; } ``` ### 绘制本地播放器组件 应用接管网页的媒体后,开发者需要将本地播放器组件及视频画面绘制到ArkWeb内核提供的Surface上。ArkWeb内核再将Surface与网页进行合成,最后上屏显示。 该流程与[同层渲染绘制](web-same-layer.md)相同。 1. 在应用启动阶段,应保存UIContext,以便后续的同层渲染绘制流程能够使用该UIContext。 ```ts // xxxAbility.ets import { UIAbility } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; export default class EntryAbility extends UIAbility { onWindowStageCreate(windowStage: window.WindowStage): void { windowStage.loadContent('pages/Index', (err, data) => { if (err.code) { return; } // 保存UIContext, 在后续的同层渲染绘制中使用。 AppStorage.setOrCreate("UIContext", windowStage.getMainWindowSync().getUIContext()); }); } // ... 其他需要重写的方法 ... } ``` 2. 使用ArkWeb内核创建的Surface进行同层渲染绘制。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; import { BuilderNode, FrameNode, NodeController, NodeRenderType } from '@kit.ArkUI'; interface ComponentParams {} class MyNodeController extends NodeController { private rootNode: BuilderNode<[ComponentParams]> | undefined; constructor(surfaceId: string, renderType: NodeRenderType) { super(); // 获取之前保存的 UIContext 。 let uiContext = AppStorage.get("UIContext"); this.rootNode = new BuilderNode(uiContext as UIContext, { surfaceId: surfaceId, type: renderType }); } makeNode(uiContext: UIContext): FrameNode | null { if (this.rootNode) { return this.rootNode.getFrameNode() as FrameNode; } return null; } build() { // 构造本地播放器组件 } } @Entry @Component struct WebComponent { node_controller?: MyNodeController; controller: webview.WebviewController = new webview.WebviewController(); @State show_native_media_player: boolean = false; build() { Column() { Stack({ alignContent: Alignment.TopStart }) { if (this.show_native_media_player) { NodeContainer(this.node_controller) .width(300) .height(150) .backgroundColor(Color.Transparent) .border({ width: 2, color: Color.Orange }) } Web({ src: 'www.example.com', controller: this.controller }) .enableNativeMediaPlayer({ enable: true, shouldOverlay: false }) .onPageBegin((event) => { this.controller.onCreateNativeMediaPlayer((handler: webview.NativeMediaPlayerHandler, mediaInfo: webview.MediaInfo) => { // 接管当前的媒体。 // 使用同层渲染流程提供的 surface 来构造一个本地播放器组件。 this.node_controller = new MyNodeController(mediaInfo.surfaceInfo.id, NodeRenderType.RENDER_TYPE_TEXTURE); this.node_controller.build(); // 展示本地播放器组件。 this.show_native_media_player = true; // 返回一个本地播放器实例给 ArkWeb 内核。 return null; }); }) } } } } ``` 动态创建组件并绘制到Surface上的详细介绍见[同层渲染绘制](web-same-layer.md) 。 ### 执行ArkWeb内核发送给本地播放器的播控指令 为了方便ArkWeb内核对本地播放器进行播控操作,应用开发者需要令本地播放器实现[NativeMediaPlayerBridge](../reference/apis-arkweb/js-apis-webview.md#nativemediaplayerbridge12)接口,并根据每个接口方法的功能对本地播放器进行相应操作。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; class ActualNativeMediaPlayerListener { constructor(handler: webview.NativeMediaPlayerHandler) {} } class NativeMediaPlayerImpl implements webview.NativeMediaPlayerBridge { constructor(handler: webview.NativeMediaPlayerHandler, mediaInfo: webview.MediaInfo) { // 1. 创建一个本地播放器的状态监听。 let listener: ActualNativeMediaPlayerListener = new ActualNativeMediaPlayerListener(handler); // 2. 创建一个本地播放器。 // 3. 监听该本地播放器。 // ... } updateRect(x: number, y: number, width: number, height: number) { //