• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 同层渲染
2
3在系统中,应用可以使用Web组件加载Web网页。在非系统框架的UI组件功能或性能不如系统组件时,可使用同层渲染,使用ArkUI组件渲染这些组件(简称为同层组件)。
4
5## 使用场景
6### Web网页
7小程序的地图组件,可以使用ArkUI的XComponent组件渲染来提升性能。小程序的输入框组件,可以使用ArkUI的TextInput组件渲染,达到与系统应用一致的输入体验。
8- 在网页侧,应用开发者可将<embed>、<object>的网页UI组件(简称为同层标签),按一定规则进行同层渲染,详细规格见同层渲染规格小节。
9
10- 在应用侧,应用开发者可以通过Web组件的同层渲染事件上报接口,感知到H5同层标签的生命周期以及输入事件,进行同层渲染组件的相应业务逻辑处理。
11
12- 在应用侧,应用开发者可以使用ArkUI的NodeContainer等接口,构建H5同层标签对应的同层渲染组件。可支持同层渲染的ArkUI常用组件包括:[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/ts-basic-components-web.md)。具体规格可参见[同层渲染规格小节](#规格约束)。
13
14### 三方UI框架
15Flutter提供了PlatformView与Texture抽象组件,这些组件可使用系统组件渲染,用来支持Flutter组件功能不足的部分。Weex2.0框架的Camera、Video、Canvas组件。
16
17- 在三方框架页面侧,由于Flutter、Weex等三方框架不在操作系统范围,本文不列举可被同层渲染的三方框架UI组件的范围与使用方式。
18
19- 在应用侧,应用开发者可以使用ArkUI的NodeContainer等接口,构建三方框架同层标签对应的同层渲染组件。可支持同层渲染的ArkUI常用组件包括:[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/ts-basic-components-web.md)。具体规格可参见[同层渲染规格](#规格约束)。
20
21## 整体架构
22ArkWeb同层渲染特性主要提供两种能力:同层标签生命周期和事件命中转发处理。
23
24同层标签生命周期主要关联前端标签(<embed>/<object>),同时命中到同层标签的事件会被上报到开发者侧,由开发者分发到对应组件树。整体框架如下图所示:
25
26**图1** 同层渲染整体架构
27
28![web-same-layer](figures/web-same-layer-develop-architecture.png)
29
30## 规格约束
31### 可被同层渲染的ArkUI组件
32
33以下规格对Web网页和三方框架场景均生效。
34
35**支持的组件范围:**
36
37- 基础组件:[AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md), [Blank](../reference/apis-arkui/arkui-ts/ts-basic-components-blank.md), [Button](../reference/apis-arkui/arkui-ts/ts-basic-components-button.md), [CalendarPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-calendarpicker.md), [Checkbox](../reference/apis-arkui/arkui-ts/ts-basic-components-checkbox.md), [CheckboxGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-checkboxgroup.md), [ContainerSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md), [DataPanel](../reference/apis-arkui/arkui-ts/ts-basic-components-datapanel.md), [DatePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-datepicker.md), [Divider](../reference/apis-arkui/arkui-ts/ts-basic-components-divider.md), [Gauge](../reference/apis-arkui/arkui-ts/ts-basic-components-gauge.md), [Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md), [Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md), [ImageAnimator](../reference/apis-arkui/arkui-ts/ts-basic-components-imageanimator.md), [ImageSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-imagespan.md), [LoadingProgress](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md), [Marquee](../reference/apis-arkui/arkui-ts/ts-basic-components-marquee.md), [PatternLock](../reference/apis-arkui/arkui-ts/ts-basic-components-patternlock.md), [Progress](../reference/apis-arkui/arkui-ts/ts-basic-components-progress.md), [QRCode](../reference/apis-arkui/arkui-ts/ts-basic-components-qrcode.md), [Radio](../reference/apis-arkui/arkui-ts/ts-basic-components-radio.md), [Rating](../reference/apis-arkui/arkui-ts/ts-basic-components-rating.md), [Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md), [ScrollBar](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md), [Span](../reference/apis-arkui/arkui-ts/ts-basic-components-span.md), [Select](../reference/apis-arkui/arkui-ts/ts-basic-components-select.md), [Slider](../reference/apis-arkui/arkui-ts/ts-basic-components-slider.md), [Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md), [TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md), [TextClock](../reference/apis-arkui/arkui-ts/ts-basic-components-textclock.md), [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [TextPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-textpicker.md), [TextTimer](../reference/apis-arkui/arkui-ts/ts-basic-components-texttimer.md), [TimePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-timepicker.md), [Toggle](../reference/apis-arkui/arkui-ts/ts-basic-components-toggle.md)
38
39- 容器类组件:[Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md), [Column](../reference/apis-arkui/arkui-ts/ts-container-column.md), [ColumnSplit](../reference/apis-arkui/arkui-ts/ts-container-columnsplit.md), [Counter](../reference/apis-arkui/arkui-ts/ts-container-counter.md), [Flex](../reference/apis-arkui/arkui-ts/ts-container-flex.md), [GridCol](../reference/apis-arkui/arkui-ts/ts-container-gridcol.md), [GridRow](../reference/apis-arkui/arkui-ts/ts-container-gridrow.md), [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md), [GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md),[List](../reference/apis-arkui/arkui-ts/ts-container-list.md), [ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md), [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md), [RelativeContainer](../reference/apis-arkui/arkui-ts/ts-container-relativecontainer.md), [Row](../reference/apis-arkui/arkui-ts/ts-container-row.md), [RowSplit](../reference/apis-arkui/arkui-ts/ts-container-rowsplit.md), [Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md), [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md), [Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md), [TabContent](../reference/apis-arkui/arkui-ts/ts-container-tabcontent.md), [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), [SideBarContainer](../reference/apis-arkui/arkui-ts/ts-container-sidebarcontainer.md), [Stepper](../reference/apis-arkui/arkui-ts/ts-basic-components-stepper.md), [StepperItem](../reference/apis-arkui/arkui-ts/ts-basic-components-stepperitem.md), [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md), [FlowItem](../reference/apis-arkui/arkui-ts/ts-container-flowitem.md)
40
41- 自绘制类组件:[XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/ts-basic-components-web.md)
42
43- 命令式自定义绘制节点:[BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md), [ComponentContent](../reference/apis-arkui/js-apis-arkui-ComponentContent.md), [ContentSlot](../reference/apis-arkui/arkui-ts/ts-components-contentSlot.md), [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md), [Graphics](../reference/apis-arkui/js-apis-arkui-graphics.md), [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md), [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md), [XComponentNode](../reference/apis-arkui/js-apis-arkui-xcomponentNode.md), [AttributeUpdater](../reference/apis-arkui/js-apis-arkui-AttributeUpdater.md),[CAPI](../reference/apis-arkui/_ark_u_i___native_module.md)(支持同层渲染的组件范围同ArkTS)
44
45**支持的组件通用属性与事件:**
46
47- 不支持的通用属性:[分布式迁移标识](../reference/apis-arkui/arkui-ts/ts-universal-attributes-restoreId.md),[特效绘制合并](../reference/apis-arkui/arkui-ts/ts-universal-attributes-use-effect.md)。
48
49- 其他未明确标注不支持的属性与事件及组件能力,均默认支持。
50
51### Web网页的同层渲染标签
52此规格仅针对Web网页,不适用于三方框架场景。
53
54如果应用需要在Web组件加载的网页中使用同层渲染,需要按照以下规格将网页中的<embed>、<object>标签指定为同层渲染组件。
55
56**支持的产品形态:**
57当前仅支持移动设备和平板形态。
58
59**支持的H5标签:**
60- 支持<embed>标签:在开启同层渲染后,仅支持type类型为native前缀的标签识别为同层组件,不支持自定义属性。
61
62- 支持<object>标签:在开启同层渲染后,支持将非标准MIME type的object标签识别为同层组件,支持通过param/value的自定义属性解析。
63
64- 不支持W3C规范标准标签(如<input>、<video>)定义为同层标签。
65
66- 不支持同时配置<object>标签和<embed>标签作为同层标签。
67
68- 标签类型只支持英文字符,不区分大小写。
69
70**同层标签支持的css属性:**
71
72display,position,z-index,visibility,opacity,
73background-color,background-image,width,height,padding,padding-left,padding-top,padding-right,padding-bottom,margin,margin-left,margin-top,margin-right,margin-bottom,border-width,border-style,border-color,border-left-width,border-left-style,border-left-color,border-top-width,border-top-style,border-top-color,border-right-width,border-right-style,border-right-color,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-right,border-top,border-bottom,border,border-top-left-radius,border-top-right-radius,border-bottom-left-radius,border-bottom-right-radius,border-radius,transition,transform(仅支持translate/scale,scale对应参数只支持大于等于0的值)
74
75 除上面支持的css属性范围,其他的css属性均不保证符合预期,比如transform属性中的rotate,shew等。
76
77**同层标签的生命周期管理:**
78当Embed标签生命周期变化时触发[onNativeEmbedLifecycleChange()](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedlifecyclechange11)回调。
79
80- 支持创建、销毁、位置宽高变化、不支持可见状态变化。
81
82- 支持同层组件所在Web页面进入前进后退缓存。
83
84**同层标签的输入事件分发处理:**
85- 支持触摸事件TouchEvent的DOWN/UP/MOVE/CANCEL。支持[配置触摸事件消费结果](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedgestureevent11),默认为应用侧消费。
86
87- 不支持同层标签所在的应用页面缩放和[initialScale](../reference/apis-arkweb/ts-basic-components-web.md#initialscale9)、[zoom](../reference/apis-arkweb/js-apis-webview.md#zoom)、[zoomIn](../reference/apis-arkweb/js-apis-webview.md#zoomin)、[zoomOut](../reference/apis-arkweb/js-apis-webview.md#zoomout)等缩放接口。
88
89- 暂不支持鼠标、键盘、触摸板事件。
90
91**约束限制:**
92
93- Web页面内不建议超过5个同层标签。超过5个后,渲染性能将会下降。
94
95- 受GPU限制,同层标签最大高度不超过8000px,最大纹理大小为8000px。
96
97- 开启同层渲染后,Web组件打开的所有Web页面将不支持同步渲染模式[RenderMode](../reference/apis-arkweb/ts-basic-components-web.md#rendermode12枚举说明)。
98
99- Video组件:在非全屏Video变为全屏时,Video组件变为非纹理导出模式,视频播放状态保持延续;恢复为非全屏时,变为纹理导出模式,视频播放状态保持延续。
100
101- Web组件:仅支持一层同层渲染嵌套,不支持多层同层渲染嵌套。输入事件只支持滑动、点击、缩放、长按 ,不支持拖拽、旋转。
102
103- 涉及界面交互的ArkUI组件(如TextInput等):建议在页面布局中使用Stack包裹同层组件容器与BuilderNode,并使两者位置一致,NodeContainer要与<embed>/<object>标签对齐,以保障组件正常交互。如两者位置不一致,可能出现的问题有:TextInput/TextArea等附属的文本选择框位置错位(如下图)、LoadingProgress/Marquee等组件的动画启停与组件可见状态不匹配。
104
105  **图2** 未使用Stack包裹,TextInput的位置错位
106
107  ![web-same-layer-develop](figures/web-same-layer-develop-textinput1.png)
108
109  **图3** 使用Stack包裹,TextInput的位置正常
110
111  ![web-same-layer-develop](figures/web-same-layer-develop-textinput2.png)
112
113## Web页面中同层渲染输入框
114在Web页面中,可以使用ArkUI系统的TextInput组件进行同层渲染。此处利用同层渲染展示三个输入框,渲染效果图如下:
115
116**图4** 同层渲染输入框
117
118  ![web-same-layer-develop](figures/web-same-layer-develop-input.png)
119
1201. 在Web页面中标记需要同层渲染的HTML标签。
121
122   同层渲染支持<embed>/<object>两种标签。type类型可任意指定,两个字符串参数均不区分大小写,ArkWeb内核将会统一转换为小写。其中,tag字符串使用全字符串匹配,type使用字符串前缀匹配。
123
124   若开发者不使用该接口或该接口接收的为非法字符串(空字符串)时,ArkWeb内核将使用默认设置,即"embed" + "native/"前缀模式。若指定类型与w3c定义的object或embed标准类型重合,如registerNativeEmbedRule("object", "application/pdf"),ArkWeb将遵循w3c标准行为,不会将其识别为同层标签。
125
126   - 采用<embed>标签。
127
128     ```html
129     <!--HAP's src/main/resources/rawfile/text.html-->
130     <!DOCTYPE html>
131     <html>
132     <head>
133         <title>同层渲染测试html</title>
134         <meta name="viewport">
135     </head>
136
137     <body style="background:white">
138
139     <embed id = "input1" type="native/view" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"/>
140
141     <embed id = "input2" type="native/view2" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/>
142
143     <embed id = "input3" type="native/view3" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/>
144
145     </body>
146     </html>
147     ```
148
149   - 采用&lt;object&gt;标签。
150
151     需要使用registerNativeEmbedRule注册object标签。
152     ```ts
153     // ...
154     Web({src: $rawfile("text.html"), controller: this.browserTabController})
155       // 注册同层标签为"object",类型为"test"前缀
156       .registerNativeEmbedRule("object", "test")
157       // ...
158     ```
159
160     与registerNativeEmbedRule相对应的前端页面代码,类型可使用"test"及以"test"为前缀的字串。
161
162      ```html
163      <!--HAP's src/main/resources/rawfile/text.html-->
164      <!DOCTYPE html>
165      <html>
166      <head>
167          <title>同层渲染测试html</title>
168          <meta name="viewport">
169      </head>
170
171      <body style="background:white">
172
173      <object id = "input1" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"></object>
174
175      <object id = "input2" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object>
176
177      <object id = "input3" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object>
178
179      </body>
180      </html>
181      ```
182
1832. 在应用侧开启同层渲染功能。
184
185   同层渲染功能默认不开启,如果要使用同层渲染的功能,可通过enableNativeEmbedMode来开启。
186
187   ```ts
188   // xxx.ets
189   import { webview } from '@kit.ArkWeb';
190   @Entry
191   @Component
192   struct WebComponent {
193     controller: webview.WebviewController = new webview.WebviewController();
194
195     build() {
196       Column() {
197         Web({ src: 'www.example.com', controller: this.controller })
198           // 配置同层渲染开关开启。
199           .enableNativeEmbedMode(true)
200       }
201     }
202   }
203   ```
204
2053. 创建自定义组件。
206
207   同层渲染功能开启后,展示在对应区域的系统组件。
208
209   ```ts
210   @Component
211   struct TextInputComponent {
212     @Prop params: Params
213     @State bkColor: Color = Color.White
214
215     build() {
216       Column() {
217         TextInput({text: '', placeholder: 'please input your word...'})
218           .placeholderColor(Color.Gray)
219           .id(this.params?.elementId)
220           .placeholderFont({size: 13, weight: 400})
221           .caretColor(Color.Gray)
222           .width(this.params?.width)
223           .height(this.params?.height)
224           .fontSize(14)
225           .fontColor(Color.Black)
226       }
227       //自定义组件中的最外层容器组件宽高应该为同层标签的宽高
228       .width(this.params.width)
229       .height(this.params.height)
230     }
231   }
232
233   @Builder
234   function TextInputBuilder(params:Params) {
235     TextInputComponent({params: params})
236       .width(params.width)
237       .height(params.height)
238       .backgroundColor(Color.White)
239   }
240   ```
241
2424. 创建节点控制器。
243
244   用于控制和反馈对应NodeContainer上的节点行为。
245
246   ```ts
247   class MyNodeController extends NodeController {
248     private rootNode: BuilderNode<[Params]> | undefined | null;
249     private embedId_: string = "";
250     private surfaceId_: string = "";
251     private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
252     private width_: number = 0;
253     private height_: number = 0;
254     private type_: string = "";
255     private isDestroy_: boolean = false;
256
257     setRenderOption(params: NodeControllerParams) {
258       this.surfaceId_ = params.surfaceId;
259       this.renderType_ = params.renderType;
260       this.embedId_ = params.embedId;
261       this.width_ = params.width;
262       this.height_ = params.height;
263       this.type_ = params.type;
264     }
265
266     // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。
267     // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。
268     makeNode(uiContext: UIContext): FrameNode | null {
269       if (this.isDestroy_) { // rootNode为null
270         return null;
271       }
272       if (!this.rootNode) {// rootNode 为undefined时
273         this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ });
274         if(this.rootNode) {
275           this.rootNode.build(wrapBuilder(TextInputBuilder), {  textOne: "myTextInput", width: this.width_, height: this.height_  })
276           return this.rootNode.getFrameNode();
277         }else{
278           return null;
279         }
280       }
281       // 返回FrameNode节点。
282       return this.rootNode.getFrameNode();
283     }
284
285     setBuilderNode(rootNode: BuilderNode<Params[]> | null): void {
286       this.rootNode = rootNode;
287     }
288
289     getBuilderNode(): BuilderNode<[Params]> | undefined | null {
290       return this.rootNode;
291     }
292
293     updateNode(arg: Object): void {
294       this.rootNode?.update(arg);
295     }
296
297     getEmbedId(): string {
298       return this.embedId_;
299     }
300
301     setDestroy(isDestroy: boolean): void {
302       this.isDestroy_ = isDestroy;
303       if (this.isDestroy_) {
304         this.rootNode = null;
305       }
306     }
307
308     postEvent(event: TouchEvent | undefined): boolean {
309       return this.rootNode?.postTouchEvent(event) as boolean
310     }
311   }
312   ```
313
3145. 监听同层渲染的生命周期变化。
315
316   开启该功能后,每当网页中存在同层渲染支持的标签时,ArkWeb内核会触发由[onNativeEmbedLifecycleChange](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedlifecyclechange11)注册的回调函数。
317
318   开发者则需要调用[onNativeEmbedLifecycleChange](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedlifecyclechange11)来监听同层渲染标签的生命周期变化。
319
320    ```ts
321    build() {
322      Row() {
323        Column() {
324          Stack() {
325            ForEach(this.componentIdArr, (componentId: string) => {
326              NodeContainer(this.nodeControllerMap.get(componentId))
327                .position(this.positionMap.get(componentId))
328                .width(this.widthMap.get(componentId))
329                .height(this.heightMap.get(componentId))
330            }, (embedId: string) => embedId)
331            // Web组件加载本地text.html页面
332            Web({src: $rawfile("text.html"), controller: this.browserTabController})
333              // 配置同层渲染开关开启
334              .enableNativeEmbedMode(true)
335                // 注册同层标签为"object",类型为"test"前缀
336              .registerNativeEmbedRule("object", "test")
337                // 获取embed标签的生命周期变化数据
338              .onNativeEmbedLifecycleChange((embed) => {
339                console.log("NativeEmbed surfaceId" + embed.surfaceId);
340                // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id
341                const componentId = embed.info?.id?.toString() as string
342                if (embed.status == NativeEmbedStatus.CREATE) {
343                  console.log("NativeEmbed create" + JSON.stringify(embed.info));
344                  // 创建节点控制器、设置参数并rebuild
345                  let nodeController = new MyNodeController()
346                  // embed.info.widthembed.info.height单位是px格式,需要转换成ets侧的默认单位vp
347                  nodeController.setRenderOption({surfaceId : embed.surfaceId as string,
348                    type : embed.info?.type as string,
349                    renderType : NodeRenderType.RENDER_TYPE_TEXTURE,
350                    embedId : embed.embedId as string,
351                    width : this.uiContext.px2vp(embed.info?.width),
352                    height : this.uiContext.px2vp(embed.info?.height)})
353                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
354                  nodeController.setDestroy(false);
355                  //根据web传入的embed的id属性作为key,将nodeController存入Map
356                  this.nodeControllerMap.set(componentId, nodeController);
357                  this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
358                  this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
359                  this.positionMap.set(componentId, this.edges);
360                  // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后
361                  this.componentIdArr.push(componentId)
362                } else if (embed.status == NativeEmbedStatus.UPDATE) {
363                  let nodeController = this.nodeControllerMap.get(componentId);
364                  console.log("NativeEmbed update" + JSON.stringify(embed));
365                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
366                  this.positionMap.set(componentId, this.edges);
367                  this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
368                  this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
369                  nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject)
370                } else if (embed.status == NativeEmbedStatus.DESTROY) {
371                  console.log("NativeEmbed destroy" + JSON.stringify(embed));
372                  let nodeController = this.nodeControllerMap.get(componentId);
373                  nodeController?.setDestroy(true)
374                  this.nodeControllerMap.clear();
375                  this.positionMap.delete(componentId);
376                  this.widthMap.delete(componentId);
377                  this.heightMap.delete(componentId);
378                  this.componentIdArr.filter((value: string) => value != componentId)
379                } else {
380                  console.log("NativeEmbed status" + embed.status);
381                }
382              })
383          }.height("80%")
384        }
385      }
386    }
387    ```
388
3896. 同层渲染手势事件。
390
391   开启该功能后,每当在同层渲染的区域进行触摸操作时,ArkWeb内核会触发[onNativeEmbedGestureEvent](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedgestureevent11)注册的回调函数。
392
393   开发者则需要调用[onNativeEmbedGestureEvent](../reference/apis-arkweb/ts-basic-components-web.md#onnativeembedgestureevent11)来监听同层渲染同层渲染区域的手势事件。
394
395    ```ts
396    build() {
397      Row() {
398        Column() {
399          Stack() {
400            ForEach(this.componentIdArr, (componentId: string) => {
401              NodeContainer(this.nodeControllerMap.get(componentId))
402                .position(this.positionMap.get(componentId))
403                .width(this.widthMap.get(componentId))
404                .height(this.heightMap.get(componentId))
405            }, (embedId: string) => embedId)
406            // Web组件加载本地text.html页面。
407            Web({src: $rawfile("text.html"), controller: this.browserTabController})
408              // 配置同层渲染开关开启。
409              .enableNativeEmbedMode(true)
410                // 获取embed标签的生命周期变化数据。
411              .onNativeEmbedLifecycleChange((embed) => {
412                // 生命周期变化实现
413              })
414              .onNativeEmbedGestureEvent((touch) => {
415                console.log("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
416                this.componentIdArr.forEach((componentId: string) => {
417                  let nodeController = this.nodeControllerMap.get(componentId);
418                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上
419                  if(nodeController?.getEmbedId() == touch.embedId) {
420                    let ret = nodeController?.postEvent(touch.touchEvent)
421                    if(ret) {
422                      console.log("onNativeEmbedGestureEvent success " + componentId);
423                    } else {
424                      console.log("onNativeEmbedGestureEvent fail " + componentId);
425                    }
426                    if(touch.result) {
427                      // 通知Web组件手势事件消费结果
428                      touch.result.setGestureEventResult(ret);
429                    }
430                  }
431                })
432              })
433          }
434        }
435      }
436    }
437    ```
438
439**完整示例:**
440
441使用前请在module.json5中添加网络权限,添加方法请参考[在配置文件中声明权限](../security/AccessToken/declare-permissions.md)。
442
443  ```
444  "requestPermissions":[
445      {
446        "name" : "ohos.permission.INTERNET"
447      }
448    ]
449  ```
450
451应用侧代码。
452
453  ```ts
454  // 创建NodeController
455  import webview from '@ohos.web.webview';
456  import { UIContext } from '@ohos.arkui.UIContext';
457  import { NodeController, BuilderNode, NodeRenderType, FrameNode } from "@ohos.arkui.node";
458
459  @Observed
460  declare class Params{
461    elementId: string
462    textOne: string
463    textTwo: string
464    width: number
465    height: number
466  }
467
468  declare class NodeControllerParams {
469    surfaceId: string
470    type: string
471    renderType: NodeRenderType
472    embedId: string
473    width: number
474    height: number
475  }
476
477  // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。
478  class MyNodeController extends NodeController {
479    private rootNode: BuilderNode<[Params]> | undefined | null;
480    private embedId_: string = "";
481    private surfaceId_: string = "";
482    private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
483    private width_: number = 0;
484    private height_: number = 0;
485    private type_: string = "";
486    private isDestroy_: boolean = false;
487
488    setRenderOption(params: NodeControllerParams) {
489      this.surfaceId_ = params.surfaceId;
490      this.renderType_ = params.renderType;
491      this.embedId_ = params.embedId;
492      this.width_ = params.width;
493      this.height_ = params.height;
494      this.type_ = params.type;
495    }
496
497    // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。
498    // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。
499    makeNode(uiContext: UIContext): FrameNode | null {
500      if (this.isDestroy_) { // rootNode为null
501        return null;
502      }
503      if (!this.rootNode) {// rootNode 为undefined时
504        this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ });
505        if(this.rootNode) {
506          this.rootNode.build(wrapBuilder(TextInputBuilder), {  textOne: "myTextInput", width: this.width_, height: this.height_  })
507          return this.rootNode.getFrameNode();
508        }else{
509          return null;
510        }
511      }
512      // 返回FrameNode节点。
513      return this.rootNode.getFrameNode();
514    }
515
516    setBuilderNode(rootNode: BuilderNode<Params[]> | null): void {
517      this.rootNode = rootNode;
518    }
519
520    getBuilderNode(): BuilderNode<[Params]> | undefined | null {
521      return this.rootNode;
522    }
523
524    updateNode(arg: Object): void {
525      this.rootNode?.update(arg);
526    }
527
528    getEmbedId(): string {
529      return this.embedId_;
530    }
531
532    setDestroy(isDestroy: boolean): void {
533      this.isDestroy_ = isDestroy;
534      if (this.isDestroy_) {
535        this.rootNode = null;
536      }
537    }
538
539    postEvent(event: TouchEvent | undefined): boolean {
540      return this.rootNode?.postTouchEvent(event) as boolean
541    }
542  }
543
544  @Component
545  struct TextInputComponent {
546    @Prop params: Params
547    @State bkColor: Color = Color.White
548
549    build() {
550      Column() {
551        TextInput({text: '', placeholder: 'please input your word...'})
552          .placeholderColor(Color.Gray)
553          .id(this.params?.elementId)
554          .placeholderFont({size: 13, weight: 400})
555          .caretColor(Color.Gray)
556          .fontSize(14)
557          .fontColor(Color.Black)
558      }
559      //自定义组件中的最外层容器组件宽高应该为同层标签的宽高
560      .width(this.params.width)
561      .height(this.params.height)
562    }
563  }
564
565  // @Builder中为动态组件的具体组件内容。
566  @Builder
567  function TextInputBuilder(params:Params) {
568    TextInputComponent({params: params})
569      .width(params.width)
570      .height(params.height)
571      .backgroundColor(Color.White)
572  }
573
574  @Entry
575  @Component
576  struct Page{
577    browserTabController: WebviewController = new webview.WebviewController()
578    private nodeControllerMap: Map<string, MyNodeController> = new Map();
579    @State componentIdArr: Array<string> = [];
580    @State posMap: Map<string, Position | undefined> = new Map();
581    @State widthMap: Map<string, number> = new Map();
582    @State heightMap: Map<string, number> = new Map();
583    @State positionMap: Map<string, Edges> = new Map();
584    @State edges: Edges = {};
585    uiContext: UIContext = this.getUIContext();
586
587    build() {
588      Row() {
589        Column() {
590          Stack() {
591            ForEach(this.componentIdArr, (componentId: string) => {
592              NodeContainer(this.nodeControllerMap.get(componentId))
593                .position(this.positionMap.get(componentId))
594                .width(this.widthMap.get(componentId))
595                .height(this.heightMap.get(componentId))
596            }, (embedId: string) => embedId)
597            // Web组件加载本地text.html页面。
598            Web({src: $rawfile("text.html"), controller: this.browserTabController})
599              // 配置同层渲染开关开启。
600              .enableNativeEmbedMode(true)
601              // 获取embed标签的生命周期变化数据。
602              .onNativeEmbedLifecycleChange((embed) => {
603                 console.log("NativeEmbed surfaceId" + embed.surfaceId);
604                 // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id
605                 const componentId = embed.info?.id?.toString() as string
606                 if (embed.status == NativeEmbedStatus.CREATE) {
607                   console.log("NativeEmbed create" + JSON.stringify(embed.info));
608                   // 创建节点控制器、设置参数并rebuild
609                   let nodeController = new MyNodeController()
610                   // embed.info.widthembed.info.height单位是px格式,需要转换成ets侧的默认单位vp
611                   nodeController.setRenderOption({surfaceId : embed.surfaceId as string,
612                     type : embed.info?.type as string,
613                     renderType : NodeRenderType.RENDER_TYPE_TEXTURE,
614                     embedId : embed.embedId as string,
615                     width : this.uiContext.px2vp(embed.info?.width),
616                     height : this.uiContext.px2vp(embed.info?.height)})
617                   this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
618                   nodeController.setDestroy(false);
619                   //根据web传入的embed的id属性作为key,将nodeController存入Map
620                   this.nodeControllerMap.set(componentId, nodeController);
621                   this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
622                   this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
623                   this.positionMap.set(componentId, this.edges);
624                   // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后
625                   this.componentIdArr.push(componentId)
626                 } else if (embed.status == NativeEmbedStatus.UPDATE) {
627                   let nodeController = this.nodeControllerMap.get(componentId);
628                   console.log("NativeEmbed update" + JSON.stringify(embed));
629                   this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
630                   this.positionMap.set(componentId, this.edges);
631                   this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
632                   this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
633                   nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject)
634                 } else if (embed.status == NativeEmbedStatus.DESTROY) {
635                   console.log("NativeEmbed destroy" + JSON.stringify(embed));
636                   let nodeController = this.nodeControllerMap.get(componentId);
637                   nodeController?.setDestroy(true)
638                   this.nodeControllerMap.clear();
639                   this.positionMap.delete(componentId);
640                   this.widthMap.delete(componentId);
641                   this.heightMap.delete(componentId);
642                   this.componentIdArr.filter((value: string) => value != componentId)
643                 } else {
644                   console.log("NativeEmbed status" + embed.status);
645                 }
646               })// 获取同层渲染组件触摸事件信息。
647              .onNativeEmbedGestureEvent((touch) => {
648                console.log("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
649                this.componentIdArr.forEach((componentId: string) => {
650                  let nodeController = this.nodeControllerMap.get(componentId);
651                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上
652                  if(nodeController?.getEmbedId() == touch.embedId) {
653                    let ret = nodeController?.postEvent(touch.touchEvent)
654                    if(ret) {
655                      console.log("onNativeEmbedGestureEvent success " + componentId);
656                    } else {
657                      console.log("onNativeEmbedGestureEvent fail " + componentId);
658                    }
659                    if(touch.result) {
660                      // 通知Web组件手势事件消费结果
661                      touch.result.setGestureEventResult(ret);
662                    }
663                  }
664                })
665              })
666          }
667        }
668      }
669    }
670  }
671  ```
672
673## 绘制XComponent+AVPlayer和Button组件
674
675开发者可通过[enableNativeEmbedMode()](../reference/apis-arkweb/ts-basic-components-web.md#enablenativeembedmode11)控制同层渲染开关。Html文件中需要显式使用embed标签,并且embed标签内type必须以“native/”开头。同层标签对应的元素区域的背景为透明。
676
677- 应用侧代码组件使用示例。
678
679  ```ts
680  // HAP's src/main/ets/pages/Index.ets
681  // 创建NodeController
682  import { webview } from '@kit.ArkWeb';
683  import { UIContext, NodeController, BuilderNode, NodeRenderType, FrameNode } from "@kit.ArkUI";
684  import { AVPlayerDemo } from './PlayerDemo';
685
686  @Observed
687  declare class Params {
688    textOne : string
689    textTwo : string
690    width : number
691    height : number
692  }
693
694  declare class NodeControllerParams {
695    surfaceId : string
696    type : string
697    renderType : NodeRenderType
698    embedId : string
699    width : number
700    height : number
701  }
702
703  // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。
704  class MyNodeController extends NodeController {
705    private rootNode: BuilderNode<[Params]> | undefined | null;
706    private embedId_ : string = "";
707    private surfaceId_ : string = "";
708    private renderType_ :NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
709    private width_ : number = 0;
710    private height_ : number = 0;
711    private type_ : string = "";
712    private isDestroy_ : boolean = false;
713
714    setRenderOption(params : NodeControllerParams) {
715      this.surfaceId_ = params.surfaceId;
716      this.renderType_ = params.renderType;
717      this.embedId_ = params.embedId;
718      this.width_ = params.width;
719      this.height_ = params.height;
720      this.type_ = params.type;
721    }
722    // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。
723    // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。
724    makeNode(uiContext: UIContext): FrameNode | null{
725      if (this.isDestroy_) { // rootNode为null
726        return null;
727      }
728      if (!this.rootNode) { // rootNode 为undefined时
729        this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_});
730        if (this.type_ === 'native/video') {
731          this.rootNode.build(wrapBuilder(VideoBuilder), {textOne: "myButton", width : this.width_, height : this.height_});
732        } else {
733          // other
734        }
735      }
736      // 返回FrameNode节点。
737      return this.rootNode.getFrameNode();
738    }
739
740    setBuilderNode(rootNode: BuilderNode<Params[]> | null): void{
741      this.rootNode = rootNode;
742    }
743
744    getBuilderNode(): BuilderNode<[Params]> | undefined | null{
745      return this.rootNode;
746    }
747
748    updateNode(arg: Object): void {
749      this.rootNode?.update(arg);
750    }
751    getEmbedId() : string {
752      return this.embedId_;
753    }
754
755    setDestroy(isDestroy : boolean) : void {
756      this.isDestroy_ = isDestroy;
757      if (this.isDestroy_) {
758        this.rootNode = null;
759      }
760    }
761
762    postEvent(event: TouchEvent | undefined) : boolean {
763      return this.rootNode?.postTouchEvent(event) as boolean
764    }
765  }
766
767  @Component
768  struct VideoComponent {
769    @ObjectLink params: Params
770    @State bkColor: Color = Color.Red
771    mXComponentController: XComponentController = new XComponentController();
772    @State player_changed: boolean = false;
773    player?: AVPlayerDemo;
774
775    build() {
776      Column() {
777        Button(this.params.textOne)
778
779        XComponent({ id: 'video_player_id', type: XComponentType.SURFACE, controller: this.mXComponentController})
780          .border({width: 1, color: Color.Red})
781          .onLoad(() => {
782            this.player = new AVPlayerDemo();
783            this.player.setSurfaceID(this.mXComponentController.getXComponentSurfaceId());
784            this.player_changed = !this.player_changed;
785            this.player.avPlayerLiveDemo()
786          })
787          .width(300)
788          .height(200)
789      }
790      //自定义组件中的最外层容器组件宽高应该为同层标签的宽高
791      .width(this.params.width)
792      .height(this.params.height)
793    }
794  }
795  // @Builder中为动态组件的具体组件内容。
796  @Builder
797  function VideoBuilder(params: Params) {
798    VideoComponent({ params: params })
799      .backgroundColor(Color.Gray)
800  }
801
802  @Entry
803  @Component
804  struct WebIndex {
805    browserTabController: WebviewController = new webview.WebviewController()
806    private nodeControllerMap: Map<string, MyNodeController> = new Map();
807    @State componentIdArr: Array<string> = [];
808    uiContext: UIContext = this.getUIContext();
809
810    aboutToAppear() {
811      // 配置web开启调试模式。
812      webview.WebviewController.setWebDebuggingAccess(true);
813    }
814
815    build(){
816      Row() {
817        Column() {
818          Stack() {
819            ForEach(this.componentIdArr, (componentId: string) => {
820              NodeContainer(this.nodeControllerMap.get(componentId))
821            }, (embedId: string) => embedId)
822            // Web组件加载本地test.html页面。
823            Web({ src: $rawfile("test.html"), controller: this.browserTabController })
824              // 配置同层渲染开关开启。
825              .enableNativeEmbedMode(true)
826                // 获取embed标签的生命周期变化数据。
827              .onNativeEmbedLifecycleChange((embed) => {
828                console.log("NativeEmbed surfaceId" + embed.surfaceId);
829                // 1. 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id
830                const componentId = embed.info?.id?.toString() as string
831                if (embed.status == NativeEmbedStatus.CREATE) {
832                  console.log("NativeEmbed create" + JSON.stringify(embed.info))
833                  // 创建节点控制器,设置参数并rebuild。
834                  let nodeController = new MyNodeController()
835                  // 1. embed.info.widthembed.info.height单位是px格式,需要转换成ets侧的默认单位vp
836                  nodeController.setRenderOption({surfaceId : embed.surfaceId as string, type : embed.info?.type as string,
837                    renderType : NodeRenderType.RENDER_TYPE_TEXTURE, embedId : embed.embedId as string,
838                    width : this.uiContext.px2vp(embed.info?.width), height : this.uiContext.px2vp(embed.info?.height)})
839                  nodeController.setDestroy(false);
840                  // 根据web传入的embed的id属性作为key,将nodeController存入map。
841                  this.nodeControllerMap.set(componentId, nodeController)
842                  // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。
843                  this.componentIdArr.push(componentId)
844                } else if (embed.status == NativeEmbedStatus.UPDATE) {
845                  let nodeController = this.nodeControllerMap.get(componentId)
846                  nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject)
847                } else {
848                  let nodeController = this.nodeControllerMap.get(componentId);
849                  nodeController?.setDestroy(true)
850                  this.nodeControllerMap.clear();
851                  this.componentIdArr.length = 0;
852                }
853              })// 获取同层渲染组件触摸事件信息。
854              .onNativeEmbedGestureEvent((touch) => {
855                console.log("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
856                this.componentIdArr.forEach((componentId: string) => {
857                  let nodeController = this.nodeControllerMap.get(componentId)
858                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上
859                  if (nodeController?.getEmbedId() === touch.embedId) {
860                    let ret = nodeController?.postEvent(touch.touchEvent)
861                    if (ret) {
862                      console.log("onNativeEmbedGestureEvent success " + componentId)
863                    } else {
864                      console.log("onNativeEmbedGestureEvent fail " + componentId)
865                    }
866                    if (touch.result) {
867                      // 通知Web组件手势事件消费结果
868                      touch.result.setGestureEventResult(ret);
869                    }
870                  }
871                })
872              })
873          }
874        }
875      }
876    }
877  }
878  ```
879
880- 应用侧代码,视频播放示例,使用时需替换正确的视频链接地址。
881
882  ```ts
883  // HAP's src/main/ets/pages/PlayerDemo.ets
884  import { media } from '@kit.MediaKit';
885  import { BusinessError } from '@ohos.base';
886
887  export class AVPlayerDemo {
888    private count: number = 0;
889    private surfaceID: string = ''; // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法。
890    private isSeek: boolean = true; // 用于区分模式是否支持seek操作。
891
892    setSurfaceID(surface_id: string){
893      console.log('setSurfaceID : ' + surface_id);
894      this.surfaceID = surface_id;
895    }
896    // 注册avplayer回调函数。
897    setAVPlayerCallback(avPlayer: media.AVPlayer) {
898      // seek操作结果回调函数。
899      avPlayer.on('seekDone', (seekDoneTime: number) => {
900        console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
901      })
902      // error回调监听函数,当avplayer在操作过程中出现错误时,调用reset接口触发重置流程。
903      avPlayer.on('error', (err: BusinessError) => {
904        console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
905        avPlayer.reset();
906      })
907      // 状态机变化回调函数。
908      avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
909        switch (state) {
910          case 'idle': // 成功调用reset接口后触发该状态机上报。
911            console.info('AVPlayer state idle called.');
912            avPlayer.release(); // 调用release接口销毁实例对象。
913            break;
914          case 'initialized': // avplayer 设置播放源后触发该状态上报。
915            console.info('AVPlayer state initialized called.');
916            avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置。
917            avPlayer.prepare();
918            break;
919          case 'prepared': // prepared调用成功后上报该状态机。
920            console.info('AVPlayer state prepared called.');
921            avPlayer.play(); // 调用播放接口开始播放。
922            break;
923          case 'playing': // play成功调用后触发该状态机上报。
924            console.info('AVPlayer state prepared called.');
925            if(this.count !== 0) {
926              if (this.isSeek) {
927                console.info('AVPlayer start to seek.');
928                avPlayer.seek(avPlayer.duration); // seek到视频末尾。
929              } else {
930                // 当播放模式不支持seek操作时继续播放到结尾。
931                console.info('AVPlayer wait to play end.');
932              }
933            } else {
934              avPlayer.pause(); // 调用暂停接口暂停播放。
935            }
936            this.count++;
937            break;
938          case 'paused': // pause成功调用后触发该状态机上报。
939            console.info('AVPlayer state paused called.');
940            avPlayer.play(); // 再次播放接口开始播放。
941            break;
942          case 'completed': //播放接口后触发该状态机上报。
943            console.info('AVPlayer state paused called.');
944            avPlayer.stop(); // 调用播放接口接口。
945            break;
946          case 'stopped': // stop接口后触发该状态机上报。
947            console.info('AVPlayer state stopped called.');
948            avPlayer.reset(); // 调用reset接口初始化avplayer状态。
949            break;
950          case 'released': //播放接口后触发该状态机上报。
951            console.info('AVPlayer state released called.');
952            break;
953          default:
954            break;
955        }
956      })
957    }
958
959    // 通过url设置网络地址来实现播放直播码流。
960    async avPlayerLiveDemo(){
961      // 创建avPlayer实例对象
962      let avPlayer: media.AVPlayer = await media.createAVPlayer();
963      // 创建状态机变化回调函数。
964      this.setAVPlayerCallback(avPlayer);
965      this.isSeek = false; // 不支持seek操作。
966      // 使用时需要自行替换视频链接
967      avPlayer.url = 'https://xxx.xxx/demo.mp4';
968    }
969  }
970  ```
971
972- 前端页面示例。
973
974  ```html
975  <!--HAP's src/main/resources/rawfile/test.html-->
976  <!DOCTYPE html>
977  <html>
978  <head>
979      <title>同层渲染测试html</title>
980      <meta name="viewport">
981  </head>
982  <body>
983  <div>
984      <div id="bodyId">
985          <embed id="nativeVideo" type = "native/video" width="1000" height="1500" src="test" style = "background-color:red"/>
986      </div>
987  </div>
988  </body>
989  </html>
990  ```
991
992  ![web-same-layer](figures/web-same-layer.png)