@ayden-fc2/riffle-bridge-web 1.0.4 → 1.0.6

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.
package/README.md CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  ## 1. tweaks和RiffleBridge用法总览
4
4
 
5
- 本文档为当前Web应用提供Bridge使用指导,以连接外层native应用。分为必选的tweaks和可选的RiffleBridge两个API
5
+ 本文档为当前Web应用提供Bridge使用指导,分为必选的tweaks和可选的RiffleBridge两个API,从而与app应用通信。
6
6
 
7
7
  **安装依赖:**
8
8
  ```bash
9
9
  npm install @ayden-fc2/riffle-bridge-web
10
10
  ```
11
11
 
12
- tweaks用于联通web应用与native的自定义参数,传递支持颜色、数值、文本、布尔、选项类型的JsonSchema格式的tweaksConfig,原则上web应用内的颜色、数值、文本、布尔、选项都要通过tweaks层配置,从而保证native能够响应式控制,使用参考文章第二节。
12
+ tweaks用于联通web应用与app应用的自定义参数,传递支持颜色、数值、文本、布尔、选项类型的JsonSchema格式的tweaksConfig,原则上web应用内的颜色、数值、文本、布尔、选项都要通过tweaks层配置,从而保证app应用能够响应式控制,使用参考文章第二节。
13
13
 
14
- RiffleBridge用于调用震动、音频、相机等模块的native功能,展开对应文章第三节各个模块,根据所需要实现的web应用功能,按需参考模块取用即可;
14
+ RiffleBridge用于调用震动、音频、相机等模块的app应用功能,展开对应文章第三节各个模块,根据所需要实现的web应用功能,按需参考模块取用即可;
15
15
 
16
16
  ## 2. tweaks 用法
17
17
 
@@ -23,20 +23,20 @@ RiffleBridge用于调用震动、音频、相机等模块的native功能,展
23
23
 
24
24
  ```
25
25
  const tweaksConfig = {
26
- // 颜色类型 - Native 端显示颜色选择器
26
+ // 颜色类型 - app应用 端显示颜色选择器
27
27
  color: { name: '颜色', type: 'color', value: '#667eea' },
28
28
 
29
- // 数值类型 - Native 端显示滑块
29
+ // 数值类型 - app应用 端显示滑块
30
30
  size: { name: '大小', type: 'number', value: 150, min: 50, max: 300 },
31
31
  opacity: { name: '透明度', type: 'number', value: 1, min: 0, max: 1, step: 0.1 },
32
32
 
33
- // 文本类型 - Native 端显示文本输入框
33
+ // 文本类型 - app应用 端显示文本输入框
34
34
  title: { name: '标题', type: 'string', value: 'Hello World' },
35
35
 
36
- // 布尔类型 - Native 端显示开关
36
+ // 布尔类型 - app应用 端显示开关
37
37
  enabled: { name: '启用', type: 'boolean', value: true },
38
38
 
39
- // 选项类型 - Native 端显示下拉选择器
39
+ // 选项类型 - app应用 端显示下拉选择器
40
40
  mode: { name: '模式', type: 'select', value: 'normal', options: ['normal', 'fast', 'slow'] },
41
41
  vibration: { name: '震动', type: 'select', value: 'medium', options: [
42
42
  { label: '轻', value: 'light' },
@@ -48,7 +48,7 @@ const tweaksConfig = {
48
48
 
49
49
  ### 2.2 初始化(必须传入React实例)
50
50
 
51
- 通过 { React } 参数显式传入初始化,保证web和native内外层数据联通,响应式更新,初始化代码如下:
51
+ 通过 { React } 参数显式传入初始化,保证web和app应用数据联通,响应式更新,初始化代码如下:
52
52
 
53
53
  ```
54
54
  import React, { useMemo } from 'react';
@@ -64,7 +64,7 @@ const tweaks = useMemo(() => createTweaks(tweaksConfig, { React }), []);
64
64
 
65
65
  ```
66
66
  function Demo() {
67
- // .useState() 自动响应式订阅,Native 修改时自动重渲染
67
+ // .useState() 自动响应式订阅,app应用 修改时自动重渲染
68
68
  const color = tweaks.color.useState();
69
69
  const size = tweaks.size.useState();
70
70
 
@@ -76,7 +76,7 @@ function Demo() {
76
76
 
77
77
  ## 3. RiffleBridge用法
78
78
 
79
- 通过 `RiffleBridge` 类调用 native 功能:
79
+ 通过 `RiffleBridge` 类调用app应用功能:
80
80
 
81
81
  ```
82
82
  import { RiffleBridge } from '@ayden-fc2/riffle-bridge-web';
@@ -85,6 +85,8 @@ const bridge = new RiffleBridge();
85
85
 
86
86
  针对RiffleBridge,分为震动模块、传感器模块、音频模块、相机模块、麦克风模块、文件存储模块、设备信息模块,各自的使用方法与注意事项已经分别说明,根据web应用所需模块,按需阅读使用即可。
87
87
 
88
+ **注意音频模块、相机模块、麦克风模块部分依赖于文件存储模块,可以先阅读文件存储模块的代码,辅助理解这三个模块中的文件存储操作**
89
+
88
90
  ### 3.1 震动模块
89
91
 
90
92
  ```
@@ -112,7 +114,7 @@ await bridge.haptic.intensity(0.5);
112
114
  - **y轴**:正值指向设备顶部
113
115
  - **z轴**:正值指向屏幕外(朝向用户)
114
116
 
115
- > iOS 和 Android 原始坐标系不同,Bridge 已在 Native 层自动处理,WebView 端收到的数据保持一致。
117
+ > iOS 和 Android 原始坐标系不同,Bridge 已在 app应用 层自动处理,WebView 端收到的数据保持一致。
116
118
 
117
119
  ```typescript
118
120
  // 启动传感器
@@ -160,14 +162,14 @@ const tilt = bridge.utils.getTiltDirection(x, y);
160
162
  ```
161
163
 
162
164
  ### 3.3 音频模块(录音、播放)
163
- > 特别注意拿到的uri文件资源是webview外的native文件路径,当前webview无法直接播放音频,请参考录音模块代码,先调用readAsBase64转码再播放!
165
+ > 特别注意拿到的uri文件资源是app应用文件路径,当前web应用无法直接播放音频,请参考录音模块代码,先调用readAsBase64转码再播放!
164
166
 
165
167
  **播放模块:**
166
168
 
167
169
  单例模式实现,主要用于不需要用户交互就直接播放的背景音乐等,点击音效等仍由web应用本身实现
168
170
 
169
171
  ```
170
- // 播放,支持传入 local://xxx 或 file://xxx 的native本地路径,或 https://xxx 的url网址
172
+ // 播放,支持传入 local://xxx 或 file://xxx 的app应用本地路径,或 https://xxx 的url网址
171
173
  await bridge.audio.playOnline({ uri: playUri });
172
174
 
173
175
  // 播放并循环(适用于背景音乐)
@@ -208,14 +210,14 @@ console.log('时长:', result.durationMillis / 1000, '秒');
208
210
 
209
211
  // 保存录音(以aydens-voice为例,实际可以替换为web应用的独特键,或者result.uri的独特文件名)
210
212
  if (result?.uri) {
211
- // 保存文件到native应用并记录map
213
+ // 保存文件到app应用应用并记录map
212
214
  await bridge.fileStorage.addCustomFileMapping('local://recording/aydens-voice', result.uri);
213
215
  }
214
216
 
215
- // 1. 后续让native层播放
217
+ // 1. 后续让app应用播放
216
218
  await bridge.audio.playOnline({ uri: 'local://recording/aydens-voice' });
217
219
 
218
- // 2. 后续直接让web播放
220
+ // 2. 后续直接让web应用播放
219
221
  const audioData = await bridge.fileStorage.readAsBase64(result.uri);
220
222
  const audio = new Audio(audioData.dataUrl); // dataUrl 已是完整 'data:audio/mp4;base64,...'
221
223
  audio.play();
@@ -227,7 +229,7 @@ audio.play();
227
229
 
228
230
  ### 3.4 相机模块(实时相机、拍照、相册、闪光灯)
229
231
 
230
- >> 特别注意拿到的uri文件资源是webview外的native文件路径,当前webview无法直接渲染,请参考下方**拍照/相册**的代码,先调用readAsBase64转码再渲染!
232
+ >> 特别注意拿到的uri文件资源是app应用件路径,当前web应用无法直接渲染,请参考下方**拍照/相册**的代码,先调用readAsBase64转码再渲染!
231
233
 
232
234
  **实时相机**
233
235
 
@@ -244,7 +246,7 @@ document.getElementById('root')!.style.background = 'transparent';
244
246
 
245
247
  // 拍照
246
248
  // ⚠️ 拍照后不要直接渲染,参考下方**拍照/相册**的代码,先调用readAsBase64转码再渲染!
247
- // ⚠️ 拍照后如果需要保存,也参考下方**拍照/相册**的代码调用native的文件存储模块保存并记录map
249
+ // ⚠️ 拍照后如果需要保存,也参考下方**拍照/相册**的代码调用app应用的文件存储模块保存并记录map
248
250
  const photo = await bridge.camera.takePhoto();
249
251
 
250
252
  // 切换摄像头
@@ -292,14 +294,14 @@ await bridge.camera.setFilter('none');
292
294
  // 1. 拍照(以aydens-demo-photo为例,实际web应用使用picked.uri文件名或组特的key)
293
295
  const photo = await bridge.fileStorage.takePhoto();
294
296
  if (!photo?.cancelled) {
295
- // 保存文件到native应用并记录map
297
+ // 保存文件到app应用并记录map
296
298
  await bridge.fileStorage.addCustomFileMapping('local://photos/aydens-demo-photo', photo.uri);
297
299
  }
298
300
 
299
301
  // 2. 或从相册选择(以aydens-photo为例,实际web应用使用picked.uri文件名或组特的key)
300
302
  const picked = await bridge.fileStorage.pickFromGallery();
301
303
  if (!picked?.cancelled) {
302
- // 保存文件到native应用并记录map
304
+ // 保存文件到app应用并记录map
303
305
  await bridge.fileStorage.addCustomFileMapping('local://photos/aydens-demo-photo', picked.uri);
304
306
  }
305
307
 
@@ -309,6 +311,43 @@ const base64 = await bridge.fileStorage.readAsBase64('local://photos/aydens-demo
309
311
  <img src={`data:image/jpeg;base64,${base64}`} />
310
312
  ```
311
313
 
314
+ **保存到系统相册**
315
+
316
+ 将图片或视频保存到用户的系统相册(会请求系统相册写入权限)
317
+
318
+ `saveToGallery` 支持自动解析多种 URI 格式,web 应用无需关心传入的是真实路径还是虚拟映射键:
319
+ - `file://...` 真实文件路径:直接使用
320
+ - `local://...` 虚拟映射键:自动从 MAP 查询实际路径
321
+ - `http(s)://...` 网络 URL:自动查找缓存文件
322
+
323
+ ```
324
+ // 拍照后保存到系统相册(直接使用 file:// 路径为例)
325
+ const photo = await bridge.fileStorage.takePhoto();
326
+ if (!photo?.cancelled) {
327
+ // 默认存入系统相册Riffle
328
+ const result = await bridge.fileStorage.saveToGallery(photo.uri);
329
+ console.log('保存成功:', result.filename);
330
+
331
+ // 指定自定义相册名
332
+ const result2 = await bridge.fileStorage.saveToGallery(photo.uri, 'MyApp相册');
333
+ }
334
+
335
+ ```
336
+
337
+ 返回值类型:
338
+ ```typescript
339
+ interface SaveToGalleryResult {
340
+ success: boolean; // 是否成功
341
+ assetId?: string; // 系统相册资产ID
342
+ uri?: string; // 保存后的URI
343
+ filename?: string; // 文件名
344
+ mediaType?: string; // 媒体类型 'photo' | 'video'
345
+ width?: number; // 图片宽度
346
+ height?: number; // 图片高度
347
+ duration?: number; // 视频时长(秒)
348
+ }
349
+ ```
350
+
312
351
  **闪光灯**
313
352
 
314
353
  闪光灯必须打开实时相机才能调用,参考实时相机的说明。
@@ -316,7 +355,7 @@ const base64 = await bridge.fileStorage.readAsBase64('local://photos/aydens-demo
316
355
  当应用需要闪光灯而不要实时相机时,可以使用非透明web应用背景来遮挡实时相机
317
356
 
318
357
  ### 3.5 文件存储模块
319
- >> 特别注意拿到的uri文件资源是webview外的native文件路径,当前webview无法直接渲染图片或播放音频,请参考下方代码,先调用readAsBase64转码再渲染或播放!相机&录音等模块也都有联动说明
358
+ >> 特别注意拿到的uri文件资源是app应用文件路径,当前web应用无法直接渲染图片或播放音频,请参考下方代码,先调用readAsBase64转码再渲染或播放!相机&录音等模块也都有联动说明
320
359
 
321
360
  文件映射系统维护了一个MAP映射表,支持本地文件和网络资源两种存储,web应用要根据需求选择使用哪种
322
361
 
@@ -332,7 +371,7 @@ if (!photo?.cancelled) {
332
371
  // 查询(查看是否存在)
333
372
  const cached = await bridge.fileStorage.getLocalFileByUrl('local://avatar/123');
334
373
 
335
- // 拿到base64(真正供web使用)
374
+ // 拿到base64
336
375
  const base64 = await bridge.fileStorage.readAsBase64('local://avatar/123');
337
376
  // 在 Web 中渲染
338
377
  <img src={`data:image/jpeg;base64,${base64}`} />
@@ -349,7 +388,7 @@ console.log(result.cached ? '使用缓存' : '已下载', result.uri);
349
388
  const base64 = await bridge.fileStorage.readAsBase64(url);
350
389
  ```
351
390
 
352
- **读取资源并解码base64,从native传入web**
391
+ **读取资源并解码base64,从app应用传入web应用**
353
392
  ```
354
393
  // uri为mapRecord记录值
355
394
  const base64 = await bridge.fileStorage.readAsBase64(mapRecord);
package/dist/index.d.mts CHANGED
@@ -405,6 +405,27 @@ declare class FileStorageController {
405
405
  added: boolean;
406
406
  }>;
407
407
  readAsBase64(uriOrKey: string): Promise<Base64Result>;
408
+ /**
409
+ * 解析 URI,返回实际的 file:// 路径
410
+ * @param uri 支持 file://、local:// 或其他映射键
411
+ */
412
+ private resolveUri;
413
+ /**
414
+ * 保存图片/视频到系统相册
415
+ * 自动解析 URI:支持 file:// 真实路径、local:// 虚拟映射键、http(s):// 缓存 URL
416
+ * @param uri 文件 URI(支持 file://、local:// 或 http(s):// 路径)
417
+ * @param albumName 相册名称(默认 'Riffle')
418
+ */
419
+ saveToGallery(uri: string, albumName?: string): Promise<{
420
+ success: boolean;
421
+ assetId?: string;
422
+ uri?: string;
423
+ filename?: string;
424
+ mediaType?: string;
425
+ width?: number;
426
+ height?: number;
427
+ duration?: number;
428
+ }>;
408
429
  }
409
430
  interface PlayOptions {
410
431
  uri: string;
package/dist/index.d.ts CHANGED
@@ -405,6 +405,27 @@ declare class FileStorageController {
405
405
  added: boolean;
406
406
  }>;
407
407
  readAsBase64(uriOrKey: string): Promise<Base64Result>;
408
+ /**
409
+ * 解析 URI,返回实际的 file:// 路径
410
+ * @param uri 支持 file://、local:// 或其他映射键
411
+ */
412
+ private resolveUri;
413
+ /**
414
+ * 保存图片/视频到系统相册
415
+ * 自动解析 URI:支持 file:// 真实路径、local:// 虚拟映射键、http(s):// 缓存 URL
416
+ * @param uri 文件 URI(支持 file://、local:// 或 http(s):// 路径)
417
+ * @param albumName 相册名称(默认 'Riffle')
418
+ */
419
+ saveToGallery(uri: string, albumName?: string): Promise<{
420
+ success: boolean;
421
+ assetId?: string;
422
+ uri?: string;
423
+ filename?: string;
424
+ mediaType?: string;
425
+ width?: number;
426
+ height?: number;
427
+ duration?: number;
428
+ }>;
408
429
  }
409
430
  interface PlayOptions {
410
431
  uri: string;
package/dist/index.js CHANGED
@@ -419,6 +419,44 @@ var FileStorageController = class {
419
419
  async readAsBase64(uriOrKey) {
420
420
  return this.core.send("fileStorage", "readAsBase64", { uriOrKey });
421
421
  }
422
+ /**
423
+ * 解析 URI,返回实际的 file:// 路径
424
+ * @param uri 支持 file://、local:// 或其他映射键
425
+ */
426
+ async resolveUri(uri) {
427
+ if (!uri) throw new Error("URI is required");
428
+ if (uri.startsWith("file://")) {
429
+ return uri;
430
+ }
431
+ if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
432
+ const cached = await this.getLocalFileByUrl(uri);
433
+ if (cached?.exists && cached.uri) {
434
+ return cached.uri;
435
+ }
436
+ throw new Error(`File mapping not found: ${uri}`);
437
+ }
438
+ try {
439
+ const cached = await this.getLocalFileByUrl(uri);
440
+ if (cached?.exists && cached.uri) {
441
+ return cached.uri;
442
+ }
443
+ } catch {
444
+ }
445
+ return uri;
446
+ }
447
+ /**
448
+ * 保存图片/视频到系统相册
449
+ * 自动解析 URI:支持 file:// 真实路径、local:// 虚拟映射键、http(s):// 缓存 URL
450
+ * @param uri 文件 URI(支持 file://、local:// 或 http(s):// 路径)
451
+ * @param albumName 相册名称(默认 'Riffle')
452
+ */
453
+ async saveToGallery(uri, albumName = "Riffle") {
454
+ const resolvedUri = await this.resolveUri(uri);
455
+ return this.core.send("fileStorage", "saveToGallery", {
456
+ saveUri: resolvedUri,
457
+ albumName
458
+ });
459
+ }
422
460
  };
423
461
  var AudioController = class {
424
462
  constructor(core, fileStorage) {
package/dist/index.mjs CHANGED
@@ -377,6 +377,44 @@ var FileStorageController = class {
377
377
  async readAsBase64(uriOrKey) {
378
378
  return this.core.send("fileStorage", "readAsBase64", { uriOrKey });
379
379
  }
380
+ /**
381
+ * 解析 URI,返回实际的 file:// 路径
382
+ * @param uri 支持 file://、local:// 或其他映射键
383
+ */
384
+ async resolveUri(uri) {
385
+ if (!uri) throw new Error("URI is required");
386
+ if (uri.startsWith("file://")) {
387
+ return uri;
388
+ }
389
+ if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
390
+ const cached = await this.getLocalFileByUrl(uri);
391
+ if (cached?.exists && cached.uri) {
392
+ return cached.uri;
393
+ }
394
+ throw new Error(`File mapping not found: ${uri}`);
395
+ }
396
+ try {
397
+ const cached = await this.getLocalFileByUrl(uri);
398
+ if (cached?.exists && cached.uri) {
399
+ return cached.uri;
400
+ }
401
+ } catch {
402
+ }
403
+ return uri;
404
+ }
405
+ /**
406
+ * 保存图片/视频到系统相册
407
+ * 自动解析 URI:支持 file:// 真实路径、local:// 虚拟映射键、http(s):// 缓存 URL
408
+ * @param uri 文件 URI(支持 file://、local:// 或 http(s):// 路径)
409
+ * @param albumName 相册名称(默认 'Riffle')
410
+ */
411
+ async saveToGallery(uri, albumName = "Riffle") {
412
+ const resolvedUri = await this.resolveUri(uri);
413
+ return this.core.send("fileStorage", "saveToGallery", {
414
+ saveUri: resolvedUri,
415
+ albumName
416
+ });
417
+ }
380
418
  };
381
419
  var AudioController = class {
382
420
  constructor(core, fileStorage) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ayden-fc2/riffle-bridge-web",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Riffle Bridge Web SDK - WebView 与 Native 通信桥接库",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -301,6 +301,65 @@ export class FileStorageController {
301
301
  async readAsBase64(uriOrKey: string): Promise<Base64Result> {
302
302
  return this.core.send<Base64Result>('fileStorage', 'readAsBase64', { uriOrKey });
303
303
  }
304
+
305
+ /**
306
+ * 解析 URI,返回实际的 file:// 路径
307
+ * @param uri 支持 file://、local:// 或其他映射键
308
+ */
309
+ private async resolveUri(uri: string): Promise<string> {
310
+ if (!uri) throw new Error('URI is required');
311
+
312
+ // file:// 协议直接使用
313
+ if (uri.startsWith('file://')) {
314
+ return uri;
315
+ }
316
+
317
+ // 非 http/https 协议(如 local://),从 MAP 查询
318
+ if (!uri.startsWith('http://') && !uri.startsWith('https://')) {
319
+ const cached = await this.getLocalFileByUrl(uri);
320
+ if (cached?.exists && cached.uri) {
321
+ return cached.uri;
322
+ }
323
+ throw new Error(`File mapping not found: ${uri}`);
324
+ }
325
+
326
+ // http/https URL,先查缓存
327
+ try {
328
+ const cached = await this.getLocalFileByUrl(uri);
329
+ if (cached?.exists && cached.uri) {
330
+ return cached.uri;
331
+ }
332
+ } catch {
333
+ // 查询失败,继续使用原始 URL
334
+ }
335
+
336
+ return uri;
337
+ }
338
+
339
+ /**
340
+ * 保存图片/视频到系统相册
341
+ * 自动解析 URI:支持 file:// 真实路径、local:// 虚拟映射键、http(s):// 缓存 URL
342
+ * @param uri 文件 URI(支持 file://、local:// 或 http(s):// 路径)
343
+ * @param albumName 相册名称(默认 'Riffle')
344
+ */
345
+ async saveToGallery(uri: string, albumName = 'Riffle'): Promise<{
346
+ success: boolean;
347
+ assetId?: string;
348
+ uri?: string;
349
+ filename?: string;
350
+ mediaType?: string;
351
+ width?: number;
352
+ height?: number;
353
+ duration?: number;
354
+ }> {
355
+ // 先解析为实际的 file:// 路径
356
+ const resolvedUri = await this.resolveUri(uri);
357
+
358
+ return this.core.send('fileStorage', 'saveToGallery', {
359
+ saveUri: resolvedUri,
360
+ albumName,
361
+ });
362
+ }
304
363
  }
305
364
 
306
365
  // ============================================