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

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,483 @@
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 定义配置
21
+
22
+ 抽离出web应用中颜色、数值、文本、布尔、选项类型的参数,使用JsonSchema格式组织,示例如下:
24
23
 
25
- const deviceInfo = await bridge.device.getDeviceInfo();
26
- console.log(deviceInfo);
27
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
+ };
47
+ ```
48
+
49
+ ### 2.2 初始化(必须传入React实例)
28
50
 
29
- ### Tweaks 配置系统
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
-
51
- if (!enabled) return null;
52
70
 
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
+ ```
85
+
86
+ 针对RiffleBridge,分为震动模块、传感器模块、音频模块、相机模块、麦克风模块、文件存储模块、设备信息模块,各自的使用方法与注意事项已经分别说明,根据web应用所需模块,按需阅读使用即可。
82
87
 
83
- // 检测
84
- bridge.isAvailable() // 是否在 RN WebView 中
85
- bridge.waitForReady(timeout) // 等待 Bridge 就绪
86
- bridge.getVersion() // SDK 版本
88
+ ### 3.1 震动模块
87
89
 
88
- // 便捷方法
89
- bridge.vibrate(type) // 震动
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
+ **坐标系约定(已跨平台统一):**
109
110
 
110
- ### 传感器模块 (bridge.sensor)
111
+ - **x轴**:正值指向设备右侧
112
+ - **y轴**:正值指向设备顶部
113
+ - **z轴**:正值指向屏幕外(朝向用户)
114
+
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 音频模块(录音、播放)
162
163
 
163
- // 智能下载(自动缓存)
164
- const result = await bridge.fileStorage.downloadWithCache(url);
164
+ **播放模块:**
165
+
166
+ 单例模式实现,主要用于不需要用户交互就直接播放的背景音乐等,点击音效等仍由web应用本身实现
165
167
 
166
- // 读取为 Base64
167
- const base64 = await bridge.fileStorage.readAsBase64(uriOrKey);
168
168
  ```
169
+ // 播放,支持传入 local://xxx 或 file://xxx 的本地路径,或 https://xxx 的url网址
170
+ await bridge.audio.playOnline({ uri: playUri });
169
171
 
170
- ### 音频模块 (bridge.audio)
172
+ // 播放并循环(适用于背景音乐)
173
+ await bridge.audio.playOnline({ uri: playUri, isLooping: true });
171
174
 
172
- ```typescript
173
- // 播放(智能 URI 解析)
174
- await bridge.audio.playOnline({ uri: 'https://example.com/audio.mp3' });
175
- await bridge.audio.playOnline({ uri: 'local://recording/memo' });
176
-
177
- // 控制
175
+ // 调用playOnline后调整播放状态,音量,速度等
178
176
  await bridge.audio.pause();
179
177
  await bridge.audio.resume();
180
178
  await bridge.audio.stop();
181
- await bridge.audio.setVolume(0.5);
182
- await bridge.audio.setRate(1.5);
179
+ await bridge.audio.setVolume(0.5); // 0-1
180
+ await bridge.audio.setRate(1.5); // 0.5-2.0
181
+ await bridge.audio.setLooping(true); // 动态开启/关闭循环
183
182
  ```
184
183
 
185
- ### 相机模块 (bridge.camera)
184
+ **录音模块:**
186
185
 
187
- ```typescript
186
+ ```
187
+ // 请求麦克风权限
188
+ const perm = await bridge.microphone.requestPermission();
189
+ if (!perm.granted) return;
190
+
191
+ // 开始录音
192
+ await bridge.microphone.start({ enableMonitoring: true });
193
+
194
+ // 监听音量
195
+ bridge.microphone.onVolumeData((data) => {
196
+ const vol = Array.isArray(data) ? data[0] : data;
197
+ console.log('音量:', vol.normalizedVolume); // 0-1
198
+ });
199
+
200
+ // 暂停 / 恢复
201
+ await bridge.microphone.pause();
202
+ await bridge.microphone.resume();
203
+
204
+ // 停止录音
205
+ const result = await bridge.microphone.stop();
206
+ console.log('时长:', result.durationMillis / 1000, '秒');
207
+
208
+ // 保存录音(以aydens-voice为例,实际可以替换为web应用的独特键,或者result.uri的独特文件名)
209
+ if (result?.uri) {
210
+ await bridge.fileStorage.addCustomFileMapping('local://recording/aydens-voice', result.uri);
211
+ }
212
+
213
+ // 后续直接播放
214
+ await bridge.audio.playOnline({ uri: 'local://recording/aydens-voice' });
215
+ ```
216
+
217
+ ### 3.4 相机模块(实时相机、拍照、相册、闪光灯)
218
+
219
+ **实时相机**
220
+
221
+ 相机采用「透明 WebView + 原生相机层」实现,**打开相机时必须设置web应用背景透明**。
222
+
223
+ ```
188
224
  // 打开相机
189
- await bridge.camera.open({ facing: 'back' });
225
+ await bridge.camera.open({ facing: 'back' }); // 或 'front'
190
226
 
191
- // 设置透明背景
227
+ // ⚠️ 设置透明背景
192
228
  document.body.style.background = 'transparent';
229
+ document.documentElement.style.background = 'transparent';
230
+ document.getElementById('root')!.style.background = 'transparent';
193
231
 
194
232
  // 拍照
195
233
  const photo = await bridge.camera.takePhoto();
196
234
 
197
- // 切换
235
+ // 切换摄像头
198
236
  await bridge.camera.toggleFacing();
199
- await bridge.camera.toggleFlash();
200
237
 
201
- // 滤镜
202
- await bridge.camera.setFilter('grayscale');
238
+ // 闪光灯控制
239
+ await bridge.camera.setFlash(true); // 开启闪光灯
240
+ await bridge.camera.setFlash(false); // 关闭闪光灯
241
+ const result = await bridge.camera.toggleFlash(); // 切换闪光灯
242
+ console.log('闪光灯状态:', result.flash); // true | false
203
243
 
204
- // 关闭
244
+ // 关闭相机(恢复背景)
205
245
  await bridge.camera.close();
246
+ document.body.style.background = '';
206
247
  ```
207
248
 
208
- ### 麦克风模块 (bridge.microphone)
249
+ **实时相机滤镜**
209
250
 
210
- ```typescript
211
- // 请求权限
212
- const perm = await bridge.microphone.requestPermission();
251
+ 注意:滤镜是通过覆盖在相机层上的 View 实现,支持 RN 样式属性(`backgroundColor`、`opacity` 等),不支持 CSS `filter` 属性。、
213
252
 
214
- // 录音
215
- await bridge.microphone.start({ enableMonitoring: true });
253
+ ```
254
+ // 预设滤镜
255
+ await bridge.camera.setFilter('grayscale'); // 'sepia' | 'invert' | 'none'
216
256
 
217
- // 监听音量
218
- bridge.microphone.onVolumeData((data) => {
219
- console.log('音量:', data[0].normalizedVolume);
257
+ // 自定义样式(React Native View 样式对象)
258
+ await bridge.camera.setFilter({
259
+ backgroundColor: 'rgba(255, 0, 0, 0.3)', // 红色蒙版
260
+ opacity: 0.8
220
261
  });
221
262
 
222
- // 停止
223
- const result = await bridge.microphone.stop();
224
- console.log('URI:', result.uri);
263
+ // 渐变蒙版效果
264
+ await bridge.camera.setFilter({
265
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
266
+ });
267
+
268
+ // 关闭滤镜
269
+ await bridge.camera.setFilter('none');
225
270
  ```
226
271
 
227
- ### 工具函数 (bridge.utils)
272
+ **拍照/相册**
228
273
 
229
- ```typescript
230
- // 向量模长
231
- const mag = bridge.utils.magnitude(x, y, z);
274
+ 不打开实时相机时,拍照/从相册选择图片
232
275
 
233
- // 倾斜方向
234
- const tilt = bridge.utils.getTiltDirection(x, y);
235
- // => 'forward' | 'backward' | 'left' | 'right' | 'center'
276
+ ```
277
+ // 1. 拍照(以aydens-demo-photo为例,实际web应用使用picked.uri文件名或组特的key)
278
+ const photo = await bridge.fileStorage.takePhoto();
279
+ if (!photo?.cancelled) {
280
+ // photo.uri 是沙箱内的本地路径
281
+ await bridge.fileStorage.addCustomFileMapping('local://photos/aydens-demo-photo', photo.uri);
282
+ }
236
283
 
237
- // 摇晃强度
238
- const shake = bridge.utils.getShakeIntensity(x, y, z);
239
- // => 'none' | 'light' | 'medium' | 'strong'
284
+ // 2. 或从相册选择(以aydens-photo为例,实际web应用使用picked.uri文件名或组特的key)
285
+ const picked = await bridge.fileStorage.pickFromGallery();
286
+ if (!picked?.cancelled) {
287
+ await bridge.fileStorage.addCustomFileMapping('local://photos/aydens-demo-photo', picked.uri);
288
+ }
289
+
290
+ // web应用中拿到图片文件并渲染
291
+ const base64 = await bridge.fileStorage.readAsBase64('local://photos/aydens-demo-photo');
292
+ // 在 Web 中渲染
293
+ <img src={`data:image/jpeg;base64,${base64}`} />
240
294
  ```
241
295
 
242
- ## 类型定义
296
+ **闪光灯**
243
297
 
244
- 完整的 TypeScript 类型支持,无需额外安装 @types 包。
298
+ 闪光灯必须打开实时相机才能调用,参考实时相机的说明。
245
299
 
246
- ```typescript
247
- import type {
248
- HapticFeedbackType,
249
- SensorData,
250
- DeviceInfo,
251
- TweakConfig,
252
- // ... 更多类型
253
- } from '@ayden-fc2/riffle-bridge-web';
300
+ 当应用需要闪光灯而不要实时相机时,可以使用非透明web应用背景来遮挡实时相机
301
+
302
+ ### 3.5 文件存储模块
303
+
304
+ 文件映射系统维护了一个MAP映射表,支持本地文件和网络资源两种存储,web应用要根据需求选择使用哪种
305
+
306
+ **本地文件(手动管理Map映射,以avatar/123为例)**
307
+
308
+ ```
309
+ // 本地文件添加
310
+ const photo = await bridge.fileStorage.takePhoto();
311
+ if (!photo?.cancelled) {
312
+ await bridge.fileStorage.addCustomFileMapping('local://avatar/123', photo.uri);
313
+ }
314
+
315
+ // 查询(查看是否存在)
316
+ const cached = await bridge.fileStorage.getLocalFileByUrl('local://avatar/123');
317
+
318
+ // 拿到base64(真正供web使用)
319
+ const base64 = await bridge.fileStorage.readAsBase64('local://avatar/123');
320
+ // 在 Web 中渲染
321
+ <img src={`data:image/jpeg;base64,${base64}`} />
322
+ ```
323
+
324
+ **网络资源(利用Map自动管理缓存,避免重复下载)**
325
+
326
+ ```
327
+ // 智能下载(命中缓存直接返回)
328
+ const result = await bridge.fileStorage.downloadWithCache(url);
329
+ console.log(result.cached ? '使用缓存' : '已下载', result.uri);
330
+
331
+ // 拿到base64
332
+ const base64 = await bridge.fileStorage.readAsBase64(url);
333
+ ```
334
+
335
+
336
+
337
+ ### 3.6 设备信息模块
338
+
339
+ 拿到json信息,内部内容不保证,因此谨慎处理获取到的信息的格式
340
+
341
+ ```
342
+ const device = await bridge.device.getDeviceInfo(); // 设备型号
343
+ const battery = await bridge.device.getBatteryInfo(); // 电池状态
344
+ const network = await bridge.device.getNetworkInfo(); // 网络类型
345
+ const system = await bridge.device.getSystemInfo(); // 系统版本
346
+ const app = await bridge.device.getAppInfo(); // 应用信息
347
+ const screen = await bridge.device.getScreenInfo(); // 屏幕尺寸
348
+ const all = await bridge.device.getAllInfo(); // 全部信息
254
349
  ```
255
350
 
256
- ## Native 端配合
351
+ ## 4. 主要类型定义
257
352
 
258
- 此包需要配合 React Native 端的 `RiffleBridge` 和 `GamePlayer` 组件使用。
353
+ ```typescript
354
+ // 基础类型
355
+ type HapticFeedbackType = 'light' | 'medium' | 'heavy' | 'selection' | 'success' | 'warning' | 'error';
356
+ type SensorType = 'accelerometer' | 'gyroscope' | 'magnetometer' | 'barometer';
357
+ type CameraFacing = 'front' | 'back';
358
+ type CameraFilter = 'none' | 'grayscale' | 'sepia' | 'invert' | Record<string, unknown>;
359
+
360
+ // 传感器数据
361
+ interface SensorData {
362
+ x: number;
363
+ y: number;
364
+ z: number;
365
+ timestamp?: number;
366
+ }
259
367
 
260
- Native 端需要在 WebView 中注入精简版通信脚本,详见项目文档。
368
+ interface BarometerData {
369
+ pressure: number;
370
+ relativeAltitude?: number;
371
+ timestamp?: number;
372
+ }
373
+
374
+ // 音频相关
375
+ interface VolumeData {
376
+ metering: number;
377
+ normalizedVolume: number;
378
+ durationMillis: number;
379
+ timestamp?: number;
380
+ }
261
381
 
262
- ## License
382
+ interface AudioStatus {
383
+ isLoaded: boolean;
384
+ isPlaying: boolean;
385
+ isLooping?: boolean;
386
+ volume: number;
387
+ rate: number;
388
+ durationMillis?: number;
389
+ positionMillis?: number;
390
+ }
391
+
392
+ interface RecordingResult {
393
+ isRecording: boolean;
394
+ isDoneRecording?: boolean;
395
+ durationMillis?: number;
396
+ canRecord: boolean;
397
+ isPaused?: boolean;
398
+ uri?: string;
399
+ }
400
+
401
+ // 文件存储相关
402
+ interface UrlMapping {
403
+ url: string;
404
+ localUri: string;
405
+ size?: number;
406
+ sizeFormatted?: string;
407
+ createdAt?: number;
408
+ }
409
+
410
+ interface CachedFileInfo {
411
+ exists: boolean;
412
+ uri?: string;
413
+ size?: number;
414
+ sizeFormatted?: string;
415
+ }
416
+
417
+ interface DownloadResult {
418
+ cached: boolean;
419
+ uri: string;
420
+ size?: number;
421
+ sizeFormatted?: string;
422
+ }
423
+
424
+ interface PhotoResult {
425
+ cancelled: boolean;
426
+ filename?: string;
427
+ uri?: string;
428
+ size?: number;
429
+ sizeFormatted?: string;
430
+ width?: number;
431
+ height?: number;
432
+ facing?: CameraFacing;
433
+ }
434
+
435
+ // 权限
436
+ interface PermissionResult {
437
+ granted: boolean;
438
+ canAskAgain: boolean;
439
+ status: string;
440
+ }
441
+
442
+ // Tweaks 配置
443
+ interface TweakConfigBase {
444
+ name: string;
445
+ type: 'color' | 'number' | 'boolean' | 'string' | 'select';
446
+ group?: string;
447
+ description?: string;
448
+ index?: number;
449
+ }
450
+
451
+ interface ColorTweakConfig extends TweakConfigBase {
452
+ type: 'color';
453
+ value: string;
454
+ }
455
+
456
+ interface NumberTweakConfig extends TweakConfigBase {
457
+ type: 'number';
458
+ value: number;
459
+ min?: number;
460
+ max?: number;
461
+ step?: number;
462
+ }
463
+
464
+ interface BooleanTweakConfig extends TweakConfigBase {
465
+ type: 'boolean';
466
+ value: boolean;
467
+ }
468
+
469
+ interface StringTweakConfig extends TweakConfigBase {
470
+ type: 'string';
471
+ value: string;
472
+ }
473
+
474
+ interface SelectTweakConfig extends TweakConfigBase {
475
+ type: 'select';
476
+ value: string;
477
+ options: (string | { label: string; value: string })[];
478
+ }
479
+
480
+ type TweakConfig = ColorTweakConfig | NumberTweakConfig | BooleanTweakConfig | StringTweakConfig | SelectTweakConfig;
481
+ type TweaksConfig = Record<string, TweakConfig>;
482
+ ```
263
483
 
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.2",
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;