@ohos-cpf/3rdloop 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/vendor/Server/Routes/controllers/LoopEngineController.js +36 -3
  3. package/vendor/Server/Routes/controllers/OrchestratorController.js +193 -9
  4. package/vendor/Server/Skills/flutter-build-test/SKILL.md +252 -0
  5. package/vendor/Server/Skills/flutter-build-test/assets/BUILDENV_TEMPLATE.md +42 -0
  6. package/vendor/Server/Skills/flutter-build-test/assets/BUILD_REPORT_TEMPLATE.md +64 -0
  7. package/vendor/Server/Skills/flutter-build-test/assets/README_SECTION_TEMPLATE.md +78 -0
  8. package/vendor/Server/Skills/flutter-build-test/references/BUILD_TROUBLESHOOTING.md +128 -0
  9. package/vendor/Server/Skills/flutter-build-test/references/DOC_UPDATE_GUIDE.md +119 -0
  10. package/vendor/Server/Skills/flutter-build-test/references/FLVM_GUIDE.md +89 -0
  11. package/vendor/Server/Skills/flutter-build-test/scripts/build-matrix.cjs +374 -0
  12. package/vendor/Server/Skills/flutter-build-test/scripts/locate-example.cjs +189 -0
  13. package/vendor/Server/Skills/flutter-build-test/scripts/update-buildenv.cjs +171 -0
  14. package/vendor/Server/Skills/flutter-code-use/SKILL.md +316 -0
  15. package/vendor/Server/Skills/flutter-code-use/references/event-channel.md +440 -0
  16. package/vendor/Server/Skills/flutter-code-use/references/federated.md +295 -0
  17. package/vendor/Server/Skills/flutter-code-use/references/ffi-binding-translate.md +130 -0
  18. package/vendor/Server/Skills/flutter-code-use/references/ffi-compile-from-source.md +169 -0
  19. package/vendor/Server/Skills/flutter-code-use/references/ffi-fetch-at-build.md +161 -0
  20. package/vendor/Server/Skills/flutter-code-use/references/ffi-prebuilt-bundle.md +175 -0
  21. package/vendor/Server/Skills/flutter-code-use/references/ffi-rhttp-guide.md +235 -0
  22. package/vendor/Server/Skills/flutter-code-use/references/ffi-rust-cross-compile.md +514 -0
  23. package/vendor/Server/Skills/flutter-code-use/references/ffi.md +220 -0
  24. package/vendor/Server/Skills/flutter-code-use/references/method-channel.md +643 -0
  25. package/vendor/Server/Skills/flutter-code-use/references/monorepo.md +188 -0
  26. package/vendor/Server/Skills/flutter-code-use/references/ohos-api-pitfalls.md +717 -0
  27. package/vendor/Server/Skills/flutter-code-use/references/platform-view.md +448 -0
  28. package/vendor/Server/Skills/flutter-code-use/references/pure-dart.md +180 -0
  29. package/vendor/Server/Skills/flutter-code-use/references/texture.md +459 -0
  30. package/vendor/Server/Skills/flutter-demo-code-generator/SKILL.md +270 -0
  31. package/vendor/Server/Skills/flutter-demo-code-generator/assets/PAGE_TEMPLATES.md +544 -0
  32. package/vendor/Server/Skills/flutter-demo-code-generator/references/CODE_STANDARDS.md +328 -0
  33. package/vendor/Server/Skills/flutter-demo-code-generator/references/DEMO_DOC_PARSING.md +126 -0
  34. package/vendor/Server/Skills/flutter-demo-code-generator/references/EXAMPLES.md +629 -0
  35. package/vendor/Server/Skills/flutter-demo-code-generator/scripts/validate-flutter-demo.cjs +268 -0
  36. package/vendor/Server/Skills/flutter-demo-doc-generator/SKILL.md +227 -0
  37. package/vendor/Server/Skills/flutter-demo-doc-generator/assets/DEMO_DOC_TEMPLATE.md +78 -0
  38. package/vendor/Server/Skills/flutter-demo-doc-generator/references/COVERAGE_REPORT_PARSING.md +174 -0
  39. package/vendor/Server/Skills/flutter-demo-doc-generator/references/EXAMPLES.md +162 -0
  40. package/vendor/Server/Skills/flutter-demo-doc-generator/references/MCP_TOOL_GUIDE.md +123 -0
  41. package/vendor/Server/Skills/flutter-demo-doc-generator/references/OUTPUT_FORMAT.md +190 -0
  42. package/vendor/Server/Skills/flutter-demo-doc-generator/references/QUALITY_CHECKLIST.md +83 -0
  43. package/vendor/Server/Skills/flutter-demo-doc-generator/scripts/validate-skill.cjs +259 -0
  44. package/vendor/Server/Skills/flutter-library-demo-coverage/SKILL.md +175 -0
  45. package/vendor/VERSION +3 -3
@@ -0,0 +1,459 @@
1
+ # 外接纹理插件鸿蒙适配
2
+
3
+ ## 适用条件
4
+
5
+ - 插件使用 `Texture` widget 显示原生渲染内容
6
+ - 典型场景:视频播放器、相机预览、自定义渲染
7
+
8
+ ---
9
+
10
+ ## 第一部分:工程搭建
11
+
12
+ ### 工程创建
13
+
14
+ ```bash
15
+ flutter create -t plugin --platforms ohos .
16
+ ```
17
+
18
+ ### 目录结构
19
+
20
+ ```
21
+ ohos/src/main/ets/components/plugin/
22
+ ├── XxxPlugin.ets # 插件主入口(MethodChannel + TextureRegistry)
23
+ └── XxxPlayer.ets # 播放器/渲染器管理类(可选,拆分逻辑)
24
+ ```
25
+
26
+ ### 配置要点
27
+
28
+ > `ohos/` 目录由 `flutter create -t plugin --platforms ohos .` 自动生成。本部分只描述对生成结果的自定义配置。**不要手动创建 `build-profile.json5`、`hvigorfile.ts`、`module.json5` 等配置文件。**
29
+
30
+ **oh-package.json5 — 添加三方依赖(按需)**
31
+
32
+ 视频/相机类插件可能需要添加多媒体相关 ohpm 三方包到 `ohos/oh-package.json5` 的 `dependencies` 中。
33
+
34
+ > **`@ohos/flutter_ohos` 依赖由 Flutter 构建工具自动注入,无需手动添加。** 如果 `flutter create` 生成了 `"@ohos/flutter_ohos": "file:./libs/flutter.har"`,**必须将其移除**(将 dependencies 设为 `{}`),否则会导致 `Failed to resolve OhmUrl` 编译错误。
35
+
36
+ **权限配置**(`module.json5`,视功能而定):
37
+ ```json5
38
+ {
39
+ "module": {
40
+ "requestPermissions": [
41
+ { "name": "ohos.permission.CAMERA" },
42
+ { "name": "ohos.permission.MICROPHONE" },
43
+ { "name": "ohos.permission.INTERNET" }
44
+ ]
45
+ }
46
+ }
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 第二部分:编码实现
52
+
53
+ ### 核心概念
54
+
55
+ 外接纹理的工作原理:
56
+ 1. ETS 端通过 `TextureRegistry` 先申请 `textureId`
57
+ 2. 再用 `registerTexture(textureId)` 注册纹理,拿到 `SurfaceTextureEntry`
58
+ 3. 从 `SurfaceTextureEntry` 获取 `surfaceId`,交给原生渲染器
59
+ 4. Dart 端用 `Texture(textureId: id)` widget 显示
60
+ 5. 销毁时通过 `TextureRegistry.unregisterTexture(textureId)` 注销纹理
61
+
62
+ ### 完整插件骨架
63
+
64
+ ```ets
65
+ import {
66
+ BinaryMessenger,
67
+ FlutterPlugin,
68
+ FlutterPluginBinding,
69
+ MethodCall,
70
+ MethodCallHandler,
71
+ MethodChannel,
72
+ MethodResult,
73
+ SurfaceTextureEntry,
74
+ TextureRegistry,
75
+ } from '@ohos/flutter_ohos';
76
+
77
+ export default class XxxPlugin implements FlutterPlugin, MethodCallHandler {
78
+ private channel: MethodChannel | null = null;
79
+ private binaryMessenger: BinaryMessenger | null = null;
80
+ private textureRegistry: TextureRegistry | null = null;
81
+ private textureEntries: Map<number, SurfaceTextureEntry> = new Map();
82
+
83
+ onAttachedToEngine(binding: FlutterPluginBinding): void {
84
+ this.textureRegistry = binding.getTextureRegistry();
85
+ this.binaryMessenger = binding.getBinaryMessenger();
86
+ this.channel = new MethodChannel(this.binaryMessenger, 'channel_name');
87
+ this.channel.setMethodCallHandler(this);
88
+ }
89
+
90
+ onDetachedFromEngine(binding: FlutterPluginBinding): void {
91
+ this.textureEntries.forEach((entry: SurfaceTextureEntry, textureId: number) => {
92
+ this.releaseNativeRenderer(textureId);
93
+ if (this.textureRegistry !== null) {
94
+ this.textureRegistry.unregisterTexture(textureId);
95
+ }
96
+ });
97
+ this.textureEntries.clear();
98
+
99
+ this.channel?.setMethodCallHandler(null);
100
+ this.channel = null;
101
+ this.binaryMessenger = null;
102
+ this.textureRegistry = null;
103
+ }
104
+
105
+ onMethodCall(call: MethodCall, result: MethodResult): void {
106
+ switch (call.method) {
107
+ case 'create':
108
+ this.handleCreate(call, result);
109
+ break;
110
+ case 'dispose':
111
+ this.handleDispose(call, result);
112
+ break;
113
+ case 'play':
114
+ this.handlePlay(call, result);
115
+ break;
116
+ case 'pause':
117
+ this.handlePause(call, result);
118
+ break;
119
+ default:
120
+ result.notImplemented();
121
+ break;
122
+ }
123
+ }
124
+
125
+ private async handleCreate(call: MethodCall, result: MethodResult): Promise<void> {
126
+ if (this.textureRegistry === null) {
127
+ result.error('NOT_INITIALIZED', 'TextureRegistry is null', null);
128
+ return;
129
+ }
130
+
131
+ try {
132
+ const textureId = this.textureRegistry.getTextureId();
133
+ const entry = this.textureRegistry.registerTexture(textureId);
134
+ const surfaceId = entry.getSurfaceId().toString();
135
+
136
+ this.textureEntries.set(textureId, entry);
137
+
138
+ // 将 surfaceId 传给原生播放器/相机
139
+ await this.initNativeRenderer(textureId, surfaceId, call);
140
+
141
+ const resultMap = new Map<string, Object>();
142
+ resultMap.set('textureId', textureId);
143
+ result.success(resultMap);
144
+ } catch (err) {
145
+ result.error('CREATE_ERROR', (err as Error).message, null);
146
+ }
147
+ }
148
+
149
+ private async handleDispose(call: MethodCall, result: MethodResult): Promise<void> {
150
+ const textureId = call.argument('textureId') as number;
151
+ if (this.textureEntries.has(textureId)) {
152
+ this.releaseNativeRenderer(textureId);
153
+ if (this.textureRegistry !== null) {
154
+ this.textureRegistry.unregisterTexture(textureId);
155
+ }
156
+ this.textureEntries.delete(textureId);
157
+ }
158
+ result.success(null);
159
+ }
160
+
161
+ private async initNativeRenderer(textureId: number, surfaceId: string, call: MethodCall): Promise<void> {
162
+ // 子类或具体实现中初始化原生渲染器
163
+ }
164
+
165
+ private releaseNativeRenderer(textureId: number): void {
166
+ // 子类或具体实现中释放原生渲染器
167
+ }
168
+
169
+ private handlePlay(call: MethodCall, result: MethodResult): void {
170
+ result.notImplemented();
171
+ }
172
+
173
+ private handlePause(call: MethodCall, result: MethodResult): void {
174
+ result.notImplemented();
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### 视频播放器实现
180
+
181
+ ```ets
182
+ import { media } from '@kit.MediaKit';
183
+
184
+ export default class VideoPlayerPlugin implements FlutterPlugin, MethodCallHandler {
185
+ private players: Map<number, media.AVPlayer> = new Map();
186
+ // ... textureRegistry、channel 等与骨架相同
187
+
188
+ private async handleCreate(call: MethodCall, result: MethodResult): Promise<void> {
189
+ if (this.textureRegistry === null) {
190
+ result.error('NOT_INITIALIZED', 'TextureRegistry is null', null);
191
+ return;
192
+ }
193
+
194
+ try {
195
+ const textureId = this.textureRegistry.getTextureId();
196
+ const entry = this.textureRegistry.registerTexture(textureId);
197
+ const surfaceId = entry.getSurfaceId().toString();
198
+ this.textureEntries.set(textureId, entry);
199
+
200
+ const avPlayer = await media.createAVPlayer();
201
+ this.players.set(textureId, avPlayer);
202
+
203
+ avPlayer.on('stateChange', async (state: media.AVPlayerState) => {
204
+ if (state === 'initialized') {
205
+ avPlayer.surfaceId = surfaceId;
206
+ await avPlayer.prepare();
207
+ }
208
+ });
209
+
210
+ const url = call.argument('url') as string;
211
+ avPlayer.url = url;
212
+
213
+ const resultMap = new Map<string, Object>();
214
+ resultMap.set('textureId', textureId);
215
+ result.success(resultMap);
216
+ } catch (err) {
217
+ result.error('CREATE_ERROR', (err as Error).message, null);
218
+ }
219
+ }
220
+
221
+ private async handlePlay(call: MethodCall, result: MethodResult): Promise<void> {
222
+ const textureId = call.argument('textureId') as number;
223
+ const player = this.players.get(textureId);
224
+ if (player !== undefined) {
225
+ await player.play();
226
+ result.success(null);
227
+ } else {
228
+ result.error('NOT_FOUND', 'Player not found', null);
229
+ }
230
+ }
231
+
232
+ private async handlePause(call: MethodCall, result: MethodResult): Promise<void> {
233
+ const textureId = call.argument('textureId') as number;
234
+ const player = this.players.get(textureId);
235
+ if (player !== undefined) {
236
+ await player.pause();
237
+ result.success(null);
238
+ } else {
239
+ result.error('NOT_FOUND', 'Player not found', null);
240
+ }
241
+ }
242
+
243
+ private async handleDispose(call: MethodCall, result: MethodResult): Promise<void> {
244
+ const textureId = call.argument('textureId') as number;
245
+
246
+ const player = this.players.get(textureId);
247
+ if (player !== undefined) {
248
+ await player.release();
249
+ this.players.delete(textureId);
250
+ }
251
+
252
+ if (this.textureEntries.has(textureId)) {
253
+ if (this.textureRegistry !== null) {
254
+ this.textureRegistry.unregisterTexture(textureId);
255
+ }
256
+ this.textureEntries.delete(textureId);
257
+ }
258
+
259
+ result.success(null);
260
+ }
261
+ }
262
+ ```
263
+
264
+ ### 相机预览实现骨架
265
+
266
+ ```ets
267
+ import camera from '@ohos.multimedia.camera';
268
+
269
+ private async handleCreateCamera(call: MethodCall, result: MethodResult): Promise<void> {
270
+ if (this.textureRegistry === null) {
271
+ result.error('NOT_INITIALIZED', 'TextureRegistry is null', null);
272
+ return;
273
+ }
274
+
275
+ try {
276
+ const textureId = this.textureRegistry.getTextureId();
277
+ const entry = this.textureRegistry.registerTexture(textureId);
278
+ const surfaceId = entry.getSurfaceId().toString();
279
+ this.textureEntries.set(textureId, entry);
280
+
281
+ const cameraManager = camera.getCameraManager(this.context!);
282
+ const cameras = cameraManager.getSupportedCameras();
283
+ if (cameras.length === 0) {
284
+ result.error('NO_CAMERA', 'No camera available', null);
285
+ return;
286
+ }
287
+
288
+ const cameraDevice = cameras[0];
289
+ const sceneMode = camera.SceneMode.NORMAL_PHOTO;
290
+ const cameraInput = cameraManager.createCameraInput(cameraDevice);
291
+ await cameraInput.open();
292
+
293
+ const outputCapability = cameraManager.getSupportedOutputCapability(cameraDevice, sceneMode);
294
+ // 示例里直接取一个可用 profile;真实项目要按分辨率、比例、像素格式再筛一层。
295
+ const previewProfile = outputCapability.previewProfiles[0];
296
+ const previewOutput = cameraManager.createPreviewOutput(previewProfile, surfaceId);
297
+
298
+ const session = cameraManager.createSession(sceneMode);
299
+ session.beginConfig();
300
+ session.addInput(cameraInput);
301
+ session.addOutput(previewOutput);
302
+ await session.commitConfig();
303
+ await session.start();
304
+
305
+ const resultMap = new Map<string, Object>();
306
+ resultMap.set('textureId', textureId);
307
+ result.success(resultMap);
308
+ } catch (err) {
309
+ result.error('CAMERA_ERROR', (err as Error).message, null);
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### SurfaceTextureEntry 生命周期管理
315
+
316
+ **关键原则**:Texture 主清理路径是 `TextureRegistry.unregisterTexture(textureId)`,不要把 `SurfaceTextureEntry.release()` 当成主骨架。
317
+
318
+ ```
319
+ 创建: textureRegistry.getTextureId() + registerTexture(textureId)
320
+
321
+ 使用: textureId → Dart, entry.getSurfaceId() → 原生渲染器
322
+
323
+ 释放: textureRegistry.unregisterTexture(textureId)(用户主动 dispose 或插件卸载时)
324
+ ```
325
+
326
+ **多纹理管理**:如果插件支持同时创建多个纹理实例(如多个视频播放器),用 Map 按 textureId 管理:
327
+
328
+ ```ets
329
+ private textureEntries: Map<number, SurfaceTextureEntry> = new Map();
330
+ private players: Map<number, media.AVPlayer> = new Map();
331
+
332
+ // 创建时存入
333
+ this.textureEntries.set(textureId, entry);
334
+ this.players.set(textureId, avPlayer);
335
+
336
+ // 销毁时清理
337
+ this.players.get(textureId)?.release();
338
+ this.textureRegistry?.unregisterTexture(textureId);
339
+ this.players.delete(textureId);
340
+ this.textureEntries.delete(textureId);
341
+
342
+ // 插件卸载时全部清理
343
+ onDetachedFromEngine(binding: FlutterPluginBinding): void {
344
+ this.players.forEach((player: media.AVPlayer) => { player.release(); });
345
+ this.textureEntries.forEach((entry: SurfaceTextureEntry, textureId: number) => {
346
+ this.textureRegistry?.unregisterTexture(textureId);
347
+ });
348
+ this.players.clear();
349
+ this.textureEntries.clear();
350
+ }
351
+ ```
352
+
353
+ ### 参考文档
354
+
355
+ 通过 Skill 检索「外接纹理适配」「Flutter OHOS 外接纹理」获取详细实现说明。
356
+
357
+ ---
358
+
359
+ ## 第三部分:常见编译错误与修复
360
+
361
+ ### 1. `Cannot find module '@ohos.multimedia.media'`
362
+
363
+ **原因**:多媒体模块导入路径可能因 SDK 版本而异。
364
+
365
+ **修复**:
366
+ - 确认 SDK 版本是否支持该模块
367
+ - 尝试 Kit 导入:`import { media } from '@kit.MediaKit'`
368
+ - 通过 `harmonyos-sdk-api-lookup` 确认正确的导入路径
369
+
370
+ ### 2. `Property 'createSurfaceTexture' does not exist on type 'TextureRegistry'`
371
+
372
+ **原因**:使用了旧骨架。当前主路径不应再依赖 `createSurfaceTexture()`。
373
+
374
+ **修复**:改为:
375
+ ```ets
376
+ const textureId = this.textureRegistry.getTextureId();
377
+ const entry = this.textureRegistry.registerTexture(textureId);
378
+ const surfaceId = entry.getSurfaceId().toString();
379
+ ```
380
+
381
+ 如果还同时报 `Property 'id' does not exist on type 'SurfaceTextureEntry'`,同样说明还在沿用旧样例。
382
+
383
+ ### 3. `Type 'number' is not assignable to type 'string'`(surfaceId)
384
+
385
+ **原因**:`getSurfaceId()` 返回 number,但部分 API 需要 string。
386
+
387
+ **修复**:显式转换:
388
+ ```ets
389
+ const surfaceId = entry.getSurfaceId().toString();
390
+ ```
391
+
392
+ ### 4. `Property 'getBinaryMessenger' does not exist on type 'MethodChannel'`
393
+
394
+ **原因**:`BinaryMessenger` 应从 `FlutterPluginBinding` 获取并保存,不能再从 `MethodChannel` 反取。
395
+
396
+ **修复**:
397
+ ```ets
398
+ private binaryMessenger: BinaryMessenger | null = null;
399
+
400
+ onAttachedToEngine(binding: FlutterPluginBinding): void {
401
+ this.binaryMessenger = binding.getBinaryMessenger();
402
+ this.channel = new MethodChannel(this.binaryMessenger, 'channel_name');
403
+ }
404
+ ```
405
+
406
+ ### 5. `Object is possibly 'null'`(textureRegistry)
407
+
408
+ **原因**:`textureRegistry` 声明为可空类型。
409
+
410
+ **修复**:在使用前断言非空或检查:
411
+ ```ets
412
+ if (this.textureRegistry === null) {
413
+ result.error("NOT_INITIALIZED", "TextureRegistry is null", null);
414
+ return;
415
+ }
416
+ const textureId = this.textureRegistry.getTextureId();
417
+ const entry = this.textureRegistry.registerTexture(textureId);
418
+ ```
419
+ 不要默认使用非空断言把真实初始化问题压过去。
420
+
421
+ ### 6. `Property 'id' does not exist on type 'SurfaceTextureEntry'`
422
+
423
+ **原因**:当前 `SurfaceTextureEntry` 不应再通过 `entry.id()` 反查纹理 id。
424
+
425
+ **修复**:`textureId` 应来自 `TextureRegistry.getTextureId()`,不要从 `SurfaceTextureEntry` 读取。
426
+
427
+ ### 7. AVPlayer 状态机错误
428
+
429
+ **原因**:AVPlayer 有严格的状态机,不能在错误状态调用方法(如未 prepare 就 play)。
430
+
431
+ **修复**:遵循 AVPlayer 状态流转:
432
+ ```
433
+ idle → initialized → prepared → playing ↔ paused → stopped → released
434
+ ```
435
+ - `url/fdSrc` 赋值后自动进入 `initialized`
436
+ - 调用 `prepare()` 后进入 `prepared`
437
+ - 调用 `play()` 后进入 `playing`
438
+ - 必须在正确状态调用对应方法
439
+
440
+ ### 8. 相机权限导致编译后运行崩溃
441
+
442
+ **原因**:`ohos.permission.CAMERA` 是 `user_grant` 权限,需要运行时申请。
443
+
444
+ **修复**:
445
+ - `module.json5` 中声明权限
446
+ - 在使用相机前动态申请权限(通过 `harmonyos-docs-lookup` 检索「申请权限」和鸿蒙权限文档)
447
+ - 编译不会报错,但运行时不申请权限会导致 API 调用失败
448
+
449
+ ### 9. `forEach` 回调参数类型不匹配
450
+
451
+ **原因**:ArkTS 的 `Map.forEach` 回调参数顺序和类型要求严格。
452
+
453
+ **修复**:
454
+ ```ets
455
+ // Map<number, SurfaceTextureEntry> 的 forEach
456
+ this.textureEntries.forEach((value: SurfaceTextureEntry, key: number) => {
457
+ this.textureRegistry?.unregisterTexture(key);
458
+ });
459
+ ```