@ayden-fc2/riffle-bridge-web 1.0.1 → 1.0.3

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
@@ -1,264 +1,496 @@
1
- # @ayden-fc2/riffle-bridge-web
1
+ # Riffle Bridge API
2
2
 
3
- Riffle Bridge Web SDK - WebView 与 React Native 通信桥接库
3
+ ## 1. tweaks和RiffleBridge用法总览
4
4
 
5
- ## 安装
5
+ 本文档为Web应用提供Bridge使用指导,以连接外层native应用。分为必选的tweaks和可选的RiffleBridge两个API。
6
6
 
7
+ **安装依赖:**
7
8
  ```bash
8
9
  npm install @ayden-fc2/riffle-bridge-web
9
- # 或
10
- pnpm add @ayden-fc2/riffle-bridge-web
11
10
  ```
12
11
 
13
- ## 快速开始
12
+ tweaks用于联通web应用与native的自定义参数,传递支持颜色、数值、文本、布尔、选项类型的JsonSchema格式的tweaksConfig,原则上web应用内的颜色、数值、文本、布尔、选项都要通过tweaks层配置,从而保证native能够响应式控制,使用参考文章第二节。
14
13
 
15
- ### 基础使用
14
+ RiffleBridge用于调用震动、音频、相机等模块的native功能,展开对应文章第三节各个模块,根据所需要实现的web应用功能,按需参考模块取用即可;
16
15
 
17
- ```typescript
18
- import { RiffleBridge } from '@ayden-fc2/riffle-bridge-web';
16
+ ## 2. tweaks 用法
19
17
 
20
- const bridge = new RiffleBridge();
18
+ 使用 `createTweaks` 创建配置实例,使用步骤如下:
21
19
 
22
- // 直接使用(如果不在 RN WebView 中会抛出错误)
23
- await bridge.vibrate('success');
20
+ ### 2.1 定义配置
24
21
 
25
- const deviceInfo = await bridge.device.getDeviceInfo();
26
- console.log(deviceInfo);
22
+ 抽离出web应用中颜色、数值、文本、布尔、选项类型的参数,使用JsonSchema格式组织,示例如下:
23
+
24
+ ```
25
+ const tweaksConfig = {
26
+ // 颜色类型 - Native 端显示颜色选择器
27
+ color: { name: '颜色', type: 'color', value: '#667eea' },
28
+
29
+ // 数值类型 - Native 端显示滑块
30
+ size: { name: '大小', type: 'number', value: 150, min: 50, max: 300 },
31
+ opacity: { name: '透明度', type: 'number', value: 1, min: 0, max: 1, step: 0.1 },
32
+
33
+ // 文本类型 - Native 端显示文本输入框
34
+ title: { name: '标题', type: 'string', value: 'Hello World' },
35
+
36
+ // 布尔类型 - Native 端显示开关
37
+ enabled: { name: '启用', type: 'boolean', value: true },
38
+
39
+ // 选项类型 - Native 端显示下拉选择器
40
+ mode: { name: '模式', type: 'select', value: 'normal', options: ['normal', 'fast', 'slow'] },
41
+ vibration: { name: '震动', type: 'select', value: 'medium', options: [
42
+ { label: '轻', value: 'light' },
43
+ { label: '中', value: 'medium' },
44
+ { label: '重', value: 'heavy' },
45
+ ]},
46
+ };
27
47
  ```
28
48
 
29
- ### Tweaks 配置系统
49
+ ### 2.2 初始化(必须传入React实例)
50
+
51
+ 通过 { React } 参数显式传入初始化,保证web和native内外层数据联通,响应式更新,初始化代码如下:
30
52
 
31
- ```tsx
53
+ ```
32
54
  import React, { useMemo } from 'react';
33
55
  import { createTweaks } from '@ayden-fc2/riffle-bridge-web';
34
56
 
35
- // 定义配置
36
- const tweaksConfig = {
37
- color: { name: '颜色', type: 'color' as const, value: '#667eea' },
38
- size: { name: '大小', type: 'number' as const, value: 150, min: 50, max: 300 },
39
- enabled: { name: '启用', type: 'boolean' as const, value: true },
40
- };
57
+ // 传入 React 以启用 useSyncExternalStore 响应式订阅
58
+ const tweaks = useMemo(() => createTweaks(tweaksConfig, { React }), []);
59
+ ```
41
60
 
42
- function App() {
43
- // 初始化(必须传入 React)
44
- const tweaks = useMemo(() => createTweaks(tweaksConfig, { React }), []);
45
-
46
- // 响应式获取值
61
+ ### 2.3 在组件中使用
62
+
63
+ 使用 ${初始化的tweaks实例}.${tweaksConfig参数}.useState(),示例代码如下:
64
+
65
+ ```
66
+ function Demo() {
67
+ // .useState() 自动响应式订阅,Native 修改时自动重渲染
47
68
  const color = tweaks.color.useState();
48
69
  const size = tweaks.size.useState();
49
- const enabled = tweaks.enabled.useState();
50
70
 
51
- if (!enabled) return null;
52
-
53
- return (
54
- <div style={{ backgroundColor: color, width: size, height: size }}>
55
- Hello Tweaks!
56
- </div>
57
- );
71
+ return <div style={{ backgroundColor: color, width: size, height: size }} />;
58
72
  }
59
73
  ```
60
74
 
61
- ### 错误处理
62
-
63
- 如果不在 React Native WebView 环境中运行,SDK 会抛出明确的错误:
64
-
65
- ```typescript
66
- try {
67
- await bridge.vibrate('success');
68
- } catch (error) {
69
- // [RiffleBridge] Not available. This SDK requires React Native WebView environment.
70
- console.error(error.message);
71
- }
72
- ```
73
75
 
74
- ## API 文档
75
76
 
76
- ### RiffleBridge
77
+ ## 3. RiffleBridge用法
77
78
 
78
- 主类,提供所有 Native 功能访问。
79
+ 通过 `RiffleBridge` 类调用 native 功能:
79
80
 
80
- ```typescript
81
+ ```
82
+ import { RiffleBridge } from '@ayden-fc2/riffle-bridge-web';
81
83
  const bridge = new RiffleBridge();
84
+ ```
82
85
 
83
- // 检测
84
- bridge.isAvailable() // 是否在 RN WebView 中
85
- bridge.waitForReady(timeout) // 等待 Bridge 就绪
86
- bridge.getVersion() // SDK 版本
86
+ 针对RiffleBridge,分为震动模块、传感器模块、音频模块、相机模块、麦克风模块、文件存储模块、设备信息模块,各自的使用方法与注意事项已经分别说明,根据web应用所需模块,按需阅读使用即可。
87
87
 
88
- // 便捷方法
89
- bridge.vibrate(type) // 震动
88
+ ### 3.1 震动模块
89
+
90
+ ```
91
+ // 7种震动类型
92
+ await bridge.vibrate('light'); // 普通-轻触
93
+ await bridge.vibrate('medium'); // 普通-中等
94
+ await bridge.vibrate('heavy'); // 普通-重
95
+ await bridge.vibrate('selection'); // 选择
96
+ await bridge.vibrate('success'); // 成功 ✓
97
+ await bridge.vibrate('warning'); // 警告 ⚠
98
+ await bridge.vibrate('error'); // 错误 ✗
99
+
100
+ // 震动序列(依次播放,间隔300ms)
101
+ await bridge.haptic.sequence(['light', 'medium', 'heavy'], 300);
102
+
103
+ // 按强度震动(0-1,自动映射到 light/medium/heavy)
104
+ await bridge.haptic.intensity(0.5);
90
105
  ```
91
106
 
92
- ### 震动模块 (bridge.haptic)
107
+ ### 3.2 传感器模块
93
108
 
94
- ```typescript
95
- await bridge.haptic.light();
96
- await bridge.haptic.medium();
97
- await bridge.haptic.heavy();
98
- await bridge.haptic.success();
99
- await bridge.haptic.warning();
100
- await bridge.haptic.error();
101
- await bridge.haptic.selection();
102
-
103
- // 序列震动
104
- await bridge.haptic.sequence(['light', 'medium', 'heavy'], 200);
105
-
106
- // 根据强度震动
107
- await bridge.haptic.intensity(0.5); // 0-1
108
- ```
109
+ **坐标系约定(已跨平台统一):**
110
+
111
+ - **x轴**:正值指向设备右侧
112
+ - **y轴**:正值指向设备顶部
113
+ - **z轴**:正值指向屏幕外(朝向用户)
109
114
 
110
- ### 传感器模块 (bridge.sensor)
115
+ > iOS 和 Android 原始坐标系不同,Bridge 已在 Native 层自动处理,WebView 端收到的数据保持一致。
111
116
 
112
117
  ```typescript
113
- // 启动
118
+ // 启动传感器
114
119
  await bridge.sensor.start({
115
- types: ['accelerometer', 'gyroscope'],
116
- interval: 100
120
+ types: ['accelerometer', 'gyroscope', 'magnetometer', 'barometer'],
121
+ interval: 100 // 更新间隔 ms
117
122
  });
118
123
 
119
- // 监听
124
+ // 监听数据(回调参数是数组,取最后一个为最新值)
120
125
  bridge.sensor.onAccelerometer((data) => {
121
126
  const { x, y, z } = data[data.length - 1];
122
127
  console.log('加速度:', x, y, z);
123
128
  });
124
129
 
125
- bridge.sensor.onGyroscope((data) => { /* ... */ });
126
- bridge.sensor.onMagnetometer((data) => { /* ... */ });
127
- bridge.sensor.onBarometer((data) => { /* ... */ });
130
+ bridge.sensor.onGyroscope((data) => {
131
+ const { x, y, z } = data[data.length - 1];
132
+ console.log('陀蝗仪:', x, y, z);
133
+ });
128
134
 
129
- // 停止
130
- await bridge.sensor.stop();
131
- ```
135
+ bridge.sensor.onMagnetometer((data) => {
136
+ const { x, y, z } = data[data.length - 1];
137
+ console.log('磁力计:', x, y, z);
138
+ });
132
139
 
133
- ### 设备信息模块 (bridge.device)
140
+ bridge.sensor.onBarometer((data) => {
141
+ const { pressure, relativeAltitude } = data[data.length - 1];
142
+ console.log('气压:', pressure, 'hPa');
143
+ });
134
144
 
135
- ```typescript
136
- const device = await bridge.device.getDeviceInfo();
137
- const battery = await bridge.device.getBatteryInfo();
138
- const network = await bridge.device.getNetworkInfo();
139
- const system = await bridge.device.getSystemInfo();
140
- const app = await bridge.device.getAppInfo();
141
- const screen = await bridge.device.getScreenInfo();
142
- const all = await bridge.device.getAllInfo();
145
+ // 停止传感器
146
+ await bridge.sensor.stop();
143
147
  ```
144
148
 
145
- ### 文件存储模块 (bridge.fileStorage)
149
+ **工具函数:**
146
150
 
147
151
  ```typescript
148
- // 初始化
149
- await bridge.fileStorage.init();
152
+ // 计算向量模长
153
+ const mag = bridge.utils.magnitude(x, y, z);
150
154
 
151
- // 拍照/相册
152
- const photo = await bridge.fileStorage.takePhoto();
153
- const picked = await bridge.fileStorage.pickFromGallery();
155
+ // 摇晃强度 → 'none' | 'light' | 'medium' | 'strong'
156
+ const shake = bridge.utils.getShakeIntensity(x, y, z);
154
157
 
155
- // 文件操作
156
- const files = await bridge.fileStorage.listFiles('images');
157
- await bridge.fileStorage.deleteFile(filename, subfolder);
158
+ // 倾斜方向 → 'forward' | 'backward' | 'left' | 'right' | 'center'
159
+ const tilt = bridge.utils.getTiltDirection(x, y);
160
+ ```
158
161
 
159
- // URL 映射
160
- await bridge.fileStorage.addCustomFileMapping('local://avatar', uri);
161
- const cached = await bridge.fileStorage.getLocalFileByUrl('local://avatar');
162
+ ### 3.3 音频模块(录音、播放)
163
+ > 特别注意拿到的uri文件资源是webview外的native文件路径,当前webview无法直接播放音频,请参考录音模块代码,先调用readAsBase64转码再播放!
162
164
 
163
- // 智能下载(自动缓存)
164
- const result = await bridge.fileStorage.downloadWithCache(url);
165
+ **播放模块:**
165
166
 
166
- // 读取为 Base64
167
- const base64 = await bridge.fileStorage.readAsBase64(uriOrKey);
168
- ```
167
+ 单例模式实现,主要用于不需要用户交互就直接播放的背景音乐等,点击音效等仍由web应用本身实现
169
168
 
170
- ### 音频模块 (bridge.audio)
169
+ ```
170
+ // 播放,支持传入 local://xxx 或 file://xxx 的本地路径,或 https://xxx 的url网址
171
+ await bridge.audio.playOnline({ uri: playUri });
171
172
 
172
- ```typescript
173
- // 播放(智能 URI 解析)
174
- await bridge.audio.playOnline({ uri: 'https://example.com/audio.mp3' });
175
- await bridge.audio.playOnline({ uri: 'local://recording/memo' });
173
+ // 播放并循环(适用于背景音乐)
174
+ await bridge.audio.playOnline({ uri: playUri, isLooping: true });
176
175
 
177
- // 控制
176
+ // 调用playOnline后调整播放状态,音量,速度等
178
177
  await bridge.audio.pause();
179
178
  await bridge.audio.resume();
180
179
  await bridge.audio.stop();
181
- await bridge.audio.setVolume(0.5);
182
- await bridge.audio.setRate(1.5);
180
+ await bridge.audio.setVolume(0.5); // 0-1
181
+ await bridge.audio.setRate(1.5); // 0.5-2.0
182
+ await bridge.audio.setLooping(true); // 动态开启/关闭循环
183
183
  ```
184
184
 
185
- ### 相机模块 (bridge.camera)
185
+ **录音模块:**
186
186
 
187
- ```typescript
187
+ ```
188
+ // 请求麦克风权限
189
+ const perm = await bridge.microphone.requestPermission();
190
+ if (!perm.granted) return;
191
+
192
+ // 开始录音
193
+ await bridge.microphone.start({ enableMonitoring: true });
194
+
195
+ // 监听音量
196
+ bridge.microphone.onVolumeData((data) => {
197
+ const vol = Array.isArray(data) ? data[0] : data;
198
+ console.log('音量:', vol.normalizedVolume); // 0-1
199
+ });
200
+
201
+ // 暂停 / 恢复
202
+ await bridge.microphone.pause();
203
+ await bridge.microphone.resume();
204
+
205
+ // 停止录音
206
+ const result = await bridge.microphone.stop();
207
+ console.log('时长:', result.durationMillis / 1000, '秒');
208
+
209
+ // 保存录音(以aydens-voice为例,实际可以替换为web应用的独特键,或者result.uri的独特文件名)
210
+ if (result?.uri) {
211
+ await bridge.fileStorage.addCustomFileMapping('local://recording/aydens-voice', result.uri);
212
+ }
213
+
214
+ // 1. 后续让native层播放
215
+ await bridge.audio.playOnline({ uri: 'local://recording/aydens-voice' });
216
+
217
+ // 2. 后续直接让web播放
218
+ const audioData = await bridge.fileStorage.readAsBase64(result.uri);
219
+ const audio = new Audio(audioData.dataUrl); // dataUrl 已是完整 'data:audio/mp4;base64,...'
220
+ audio.play();
221
+
222
+ // 或使用 React 方式
223
+ <audio src={audioData.dataUrl} controls />
224
+
225
+ ```
226
+
227
+ ### 3.4 相机模块(实时相机、拍照、相册、闪光灯)
228
+
229
+ >> 特别注意拿到的uri文件资源是webview外的native文件路径,当前webview无法直接渲染,请参考下方**拍照/相册**的代码,先调用readAsBase64转码再渲染!
230
+
231
+ **实时相机**
232
+
233
+ 相机采用「透明 WebView + 原生相机层」实现,**打开相机时必须设置web应用背景透明**。
234
+
235
+ ```
188
236
  // 打开相机
189
- await bridge.camera.open({ facing: 'back' });
237
+ await bridge.camera.open({ facing: 'back' }); // 或 'front'
190
238
 
191
- // 设置透明背景
239
+ // ⚠️ 设置透明背景
192
240
  document.body.style.background = 'transparent';
241
+ document.documentElement.style.background = 'transparent';
242
+ document.getElementById('root')!.style.background = 'transparent';
193
243
 
194
- // 拍照
244
+ // 拍照 ⚠️ 拍照后不要直接渲染,参考下方**拍照/相册**的代码,先调用readAsBase64转码再渲染!
195
245
  const photo = await bridge.camera.takePhoto();
196
246
 
197
- // 切换
247
+ // 切换摄像头
198
248
  await bridge.camera.toggleFacing();
199
- await bridge.camera.toggleFlash();
200
249
 
201
- // 滤镜
202
- await bridge.camera.setFilter('grayscale');
250
+ // 闪光灯控制
251
+ await bridge.camera.setFlash(true); // 开启闪光灯
252
+ await bridge.camera.setFlash(false); // 关闭闪光灯
253
+ const result = await bridge.camera.toggleFlash(); // 切换闪光灯
254
+ console.log('闪光灯状态:', result.flash); // true | false
203
255
 
204
- // 关闭
256
+ // 关闭相机(恢复背景)
205
257
  await bridge.camera.close();
258
+ document.body.style.background = '';
206
259
  ```
207
260
 
208
- ### 麦克风模块 (bridge.microphone)
261
+ **实时相机滤镜**
209
262
 
210
- ```typescript
211
- // 请求权限
212
- const perm = await bridge.microphone.requestPermission();
263
+ 注意:滤镜是通过覆盖在相机层上的 View 实现,支持 RN 样式属性(`backgroundColor`、`opacity` 等),不支持 CSS `filter` 属性。、
213
264
 
214
- // 录音
215
- await bridge.microphone.start({ enableMonitoring: true });
265
+ ```
266
+ // 预设滤镜
267
+ await bridge.camera.setFilter('grayscale'); // 'sepia' | 'invert' | 'none'
216
268
 
217
- // 监听音量
218
- bridge.microphone.onVolumeData((data) => {
219
- console.log('音量:', data[0].normalizedVolume);
269
+ // 自定义样式(React Native View 样式对象)
270
+ await bridge.camera.setFilter({
271
+ backgroundColor: 'rgba(255, 0, 0, 0.3)', // 红色蒙版
272
+ opacity: 0.8
220
273
  });
221
274
 
222
- // 停止
223
- const result = await bridge.microphone.stop();
224
- console.log('URI:', result.uri);
275
+ // 渐变蒙版效果
276
+ await bridge.camera.setFilter({
277
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
278
+ });
279
+
280
+ // 关闭滤镜
281
+ await bridge.camera.setFilter('none');
225
282
  ```
226
283
 
227
- ### 工具函数 (bridge.utils)
284
+ **拍照/相册**
228
285
 
229
- ```typescript
230
- // 向量模长
231
- const mag = bridge.utils.magnitude(x, y, z);
286
+ 不打开实时相机时,拍照/从相册选择图片
232
287
 
233
- // 倾斜方向
234
- const tilt = bridge.utils.getTiltDirection(x, y);
235
- // => 'forward' | 'backward' | 'left' | 'right' | 'center'
288
+ ```
289
+ // 1. 拍照(以aydens-demo-photo为例,实际web应用使用picked.uri文件名或组特的key)
290
+ const photo = await bridge.fileStorage.takePhoto();
291
+ if (!photo?.cancelled) {
292
+ // photo.uri 是沙箱内的本地路径
293
+ await bridge.fileStorage.addCustomFileMapping('local://photos/aydens-demo-photo', photo.uri);
294
+ }
236
295
 
237
- // 摇晃强度
238
- const shake = bridge.utils.getShakeIntensity(x, y, z);
239
- // => 'none' | 'light' | 'medium' | 'strong'
296
+ // 2. 或从相册选择(以aydens-photo为例,实际web应用使用picked.uri文件名或组特的key)
297
+ const picked = await bridge.fileStorage.pickFromGallery();
298
+ if (!picked?.cancelled) {
299
+ await bridge.fileStorage.addCustomFileMapping('local://photos/aydens-demo-photo', picked.uri);
300
+ }
301
+
302
+ // web应用中拿到图片文件并渲染
303
+ const base64 = await bridge.fileStorage.readAsBase64('local://photos/aydens-demo-photo');
304
+ // 在 Web 中渲染
305
+ <img src={`data:image/jpeg;base64,${base64}`} />
240
306
  ```
241
307
 
242
- ## 类型定义
308
+ **闪光灯**
243
309
 
244
- 完整的 TypeScript 类型支持,无需额外安装 @types 包。
310
+ 闪光灯必须打开实时相机才能调用,参考实时相机的说明。
311
+
312
+ 当应用需要闪光灯而不要实时相机时,可以使用非透明web应用背景来遮挡实时相机
313
+
314
+ ### 3.5 文件存储模块
315
+ >> 特别注意拿到的uri文件资源是webview外的native文件路径,当前webview无法直接渲染图片或播放音频,请参考下方代码,先调用readAsBase64转码再渲染或播放!相机&录音等模块也都有联动说明
316
+
317
+ 文件映射系统维护了一个MAP映射表,支持本地文件和网络资源两种存储,web应用要根据需求选择使用哪种
318
+
319
+ **本地文件(手动管理Map映射,以avatar/123为例)**
320
+
321
+ ```
322
+ // 本地文件添加
323
+ const photo = await bridge.fileStorage.takePhoto();
324
+ if (!photo?.cancelled) {
325
+ await bridge.fileStorage.addCustomFileMapping('local://avatar/123', photo.uri);
326
+ }
327
+
328
+ // 查询(查看是否存在)
329
+ const cached = await bridge.fileStorage.getLocalFileByUrl('local://avatar/123');
330
+
331
+ // 拿到base64(真正供web使用)
332
+ const base64 = await bridge.fileStorage.readAsBase64('local://avatar/123');
333
+ // 在 Web 中渲染
334
+ <img src={`data:image/jpeg;base64,${base64}`} />
335
+ ```
336
+
337
+ **网络资源(利用Map自动管理缓存,避免重复下载)**
338
+
339
+ ```
340
+ // 智能下载(命中缓存直接返回)
341
+ const result = await bridge.fileStorage.downloadWithCache(url);
342
+ console.log(result.cached ? '使用缓存' : '已下载', result.uri);
343
+
344
+ // 拿到base64
345
+ const base64 = await bridge.fileStorage.readAsBase64(url);
346
+ ```
347
+
348
+
349
+
350
+ ### 3.6 设备信息模块
351
+
352
+ 拿到json信息,内部内容不保证,因此谨慎处理获取到的信息的格式
245
353
 
246
- ```typescript
247
- import type {
248
- HapticFeedbackType,
249
- SensorData,
250
- DeviceInfo,
251
- TweakConfig,
252
- // ... 更多类型
253
- } from '@ayden-fc2/riffle-bridge-web';
254
354
  ```
355
+ const device = await bridge.device.getDeviceInfo(); // 设备型号
356
+ const battery = await bridge.device.getBatteryInfo(); // 电池状态
357
+ const network = await bridge.device.getNetworkInfo(); // 网络类型
358
+ const system = await bridge.device.getSystemInfo(); // 系统版本
359
+ const app = await bridge.device.getAppInfo(); // 应用信息
360
+ const screen = await bridge.device.getScreenInfo(); // 屏幕尺寸
361
+ const all = await bridge.device.getAllInfo(); // 全部信息
362
+ ```
363
+
364
+ ## 4. 主要类型定义
365
+
366
+ ```typescript
367
+ // 基础类型
368
+ type HapticFeedbackType = 'light' | 'medium' | 'heavy' | 'selection' | 'success' | 'warning' | 'error';
369
+ type SensorType = 'accelerometer' | 'gyroscope' | 'magnetometer' | 'barometer';
370
+ type CameraFacing = 'front' | 'back';
371
+ type CameraFilter = 'none' | 'grayscale' | 'sepia' | 'invert' | Record<string, unknown>;
372
+
373
+ // 传感器数据
374
+ interface SensorData {
375
+ x: number;
376
+ y: number;
377
+ z: number;
378
+ timestamp?: number;
379
+ }
380
+
381
+ interface BarometerData {
382
+ pressure: number;
383
+ relativeAltitude?: number;
384
+ timestamp?: number;
385
+ }
386
+
387
+ // 音频相关
388
+ interface VolumeData {
389
+ metering: number;
390
+ normalizedVolume: number;
391
+ durationMillis: number;
392
+ timestamp?: number;
393
+ }
394
+
395
+ interface AudioStatus {
396
+ isLoaded: boolean;
397
+ isPlaying: boolean;
398
+ isLooping?: boolean;
399
+ volume: number;
400
+ rate: number;
401
+ durationMillis?: number;
402
+ positionMillis?: number;
403
+ }
404
+
405
+ interface RecordingResult {
406
+ isRecording: boolean;
407
+ isDoneRecording?: boolean;
408
+ durationMillis?: number;
409
+ canRecord: boolean;
410
+ isPaused?: boolean;
411
+ uri?: string;
412
+ }
413
+
414
+ // 文件存储相关
415
+ interface UrlMapping {
416
+ url: string;
417
+ localUri: string;
418
+ size?: number;
419
+ sizeFormatted?: string;
420
+ createdAt?: number;
421
+ }
422
+
423
+ interface CachedFileInfo {
424
+ exists: boolean;
425
+ uri?: string;
426
+ size?: number;
427
+ sizeFormatted?: string;
428
+ }
429
+
430
+ interface DownloadResult {
431
+ cached: boolean;
432
+ uri: string;
433
+ size?: number;
434
+ sizeFormatted?: string;
435
+ }
255
436
 
256
- ## Native 端配合
437
+ interface PhotoResult {
438
+ cancelled: boolean;
439
+ filename?: string;
440
+ uri?: string;
441
+ size?: number;
442
+ sizeFormatted?: string;
443
+ width?: number;
444
+ height?: number;
445
+ facing?: CameraFacing;
446
+ }
257
447
 
258
- 此包需要配合 React Native 端的 `RiffleBridge` 和 `GamePlayer` 组件使用。
448
+ // 权限
449
+ interface PermissionResult {
450
+ granted: boolean;
451
+ canAskAgain: boolean;
452
+ status: string;
453
+ }
259
454
 
260
- Native 端需要在 WebView 中注入精简版通信脚本,详见项目文档。
455
+ // Tweaks 配置
456
+ interface TweakConfigBase {
457
+ name: string;
458
+ type: 'color' | 'number' | 'boolean' | 'string' | 'select';
459
+ group?: string;
460
+ description?: string;
461
+ index?: number;
462
+ }
463
+
464
+ interface ColorTweakConfig extends TweakConfigBase {
465
+ type: 'color';
466
+ value: string;
467
+ }
261
468
 
262
- ## License
469
+ interface NumberTweakConfig extends TweakConfigBase {
470
+ type: 'number';
471
+ value: number;
472
+ min?: number;
473
+ max?: number;
474
+ step?: number;
475
+ }
476
+
477
+ interface BooleanTweakConfig extends TweakConfigBase {
478
+ type: 'boolean';
479
+ value: boolean;
480
+ }
481
+
482
+ interface StringTweakConfig extends TweakConfigBase {
483
+ type: 'string';
484
+ value: string;
485
+ }
486
+
487
+ interface SelectTweakConfig extends TweakConfigBase {
488
+ type: 'select';
489
+ value: string;
490
+ options: (string | { label: string; value: string })[];
491
+ }
492
+
493
+ type TweakConfig = ColorTweakConfig | NumberTweakConfig | BooleanTweakConfig | StringTweakConfig | SelectTweakConfig;
494
+ type TweaksConfig = Record<string, TweakConfig>;
495
+ ```
263
496
 
264
- MIT
package/dist/index.d.mts CHANGED
@@ -86,6 +86,7 @@ interface VolumeData {
86
86
  interface AudioStatus {
87
87
  isLoaded: boolean;
88
88
  isPlaying: boolean;
89
+ isLooping?: boolean;
89
90
  volume: number;
90
91
  rate: number;
91
92
  durationMillis?: number;
@@ -441,6 +442,12 @@ declare class AudioController {
441
442
  setRate(rate: number): Promise<{
442
443
  rate: number;
443
444
  }>;
445
+ /**
446
+ * 设置循环播放
447
+ */
448
+ setLooping(isLooping: boolean): Promise<{
449
+ isLooping: boolean;
450
+ }>;
444
451
  getStatus(): Promise<AudioStatus>;
445
452
  }
446
453
  declare class CameraController {
package/dist/index.d.ts CHANGED
@@ -86,6 +86,7 @@ interface VolumeData {
86
86
  interface AudioStatus {
87
87
  isLoaded: boolean;
88
88
  isPlaying: boolean;
89
+ isLooping?: boolean;
89
90
  volume: number;
90
91
  rate: number;
91
92
  durationMillis?: number;
@@ -441,6 +442,12 @@ declare class AudioController {
441
442
  setRate(rate: number): Promise<{
442
443
  rate: number;
443
444
  }>;
445
+ /**
446
+ * 设置循环播放
447
+ */
448
+ setLooping(isLooping: boolean): Promise<{
449
+ isLooping: boolean;
450
+ }>;
444
451
  getStatus(): Promise<AudioStatus>;
445
452
  }
446
453
  declare class CameraController {
package/dist/index.js CHANGED
@@ -485,6 +485,12 @@ var AudioController = class {
485
485
  async setRate(rate) {
486
486
  return this.core.send("audio", "setRate", { rate });
487
487
  }
488
+ /**
489
+ * 设置循环播放
490
+ */
491
+ async setLooping(isLooping) {
492
+ return this.core.send("audio", "setLooping", { isLooping });
493
+ }
488
494
  async getStatus() {
489
495
  return this.core.send("audio", "getStatus");
490
496
  }
package/dist/index.mjs CHANGED
@@ -443,6 +443,12 @@ var AudioController = class {
443
443
  async setRate(rate) {
444
444
  return this.core.send("audio", "setRate", { rate });
445
445
  }
446
+ /**
447
+ * 设置循环播放
448
+ */
449
+ async setLooping(isLooping) {
450
+ return this.core.send("audio", "setLooping", { isLooping });
451
+ }
446
452
  async getStatus() {
447
453
  return this.core.send("audio", "getStatus");
448
454
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ayden-fc2/riffle-bridge-web",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Riffle Bridge Web SDK - WebView 与 Native 通信桥接库",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -399,6 +399,13 @@ export class AudioController {
399
399
  return this.core.send('audio', 'setRate', { rate });
400
400
  }
401
401
 
402
+ /**
403
+ * 设置循环播放
404
+ */
405
+ async setLooping(isLooping: boolean): Promise<{ isLooping: boolean }> {
406
+ return this.core.send('audio', 'setLooping', { isLooping });
407
+ }
408
+
402
409
  async getStatus(): Promise<AudioStatus> {
403
410
  return this.core.send<AudioStatus>('audio', 'getStatus');
404
411
  }
package/src/types.ts CHANGED
@@ -70,6 +70,7 @@ export interface VolumeData {
70
70
  export interface AudioStatus {
71
71
  isLoaded: boolean;
72
72
  isPlaying: boolean;
73
+ isLooping?: boolean;
73
74
  volume: number;
74
75
  rate: number;
75
76
  durationMillis?: number;