@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 +383 -164
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/index.mjs +6 -0
- package/package.json +1 -1
- package/src/controllers/index.ts +7 -0
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -1,264 +1,483 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Riffle Bridge API
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
18
|
-
import { RiffleBridge } from '@ayden-fc2/riffle-bridge-web';
|
|
16
|
+
## 2. tweaks 用法
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
使用 `createTweaks` 创建配置实例,使用步骤如下:
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
51
|
+
通过 { React } 参数显式传入初始化,保证web和native内外层数据联通,响应式更新,初始化代码如下:
|
|
30
52
|
|
|
31
|
-
```
|
|
53
|
+
```
|
|
32
54
|
import React, { useMemo } from 'react';
|
|
33
55
|
import { createTweaks } from '@ayden-fc2/riffle-bridge-web';
|
|
34
56
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
77
|
+
## 3. RiffleBridge用法
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
通过 `RiffleBridge` 类调用 native 功能:
|
|
79
80
|
|
|
80
|
-
```
|
|
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
|
-
|
|
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
|
-
###
|
|
107
|
+
### 3.2 传感器模块
|
|
93
108
|
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
+
bridge.sensor.onBarometer((data) => {
|
|
141
|
+
const { pressure, relativeAltitude } = data[data.length - 1];
|
|
142
|
+
console.log('气压:', pressure, 'hPa');
|
|
143
|
+
});
|
|
134
144
|
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
149
|
+
**工具函数:**
|
|
146
150
|
|
|
147
151
|
```typescript
|
|
148
|
-
//
|
|
149
|
-
|
|
152
|
+
// 计算向量模长
|
|
153
|
+
const mag = bridge.utils.magnitude(x, y, z);
|
|
150
154
|
|
|
151
|
-
//
|
|
152
|
-
const
|
|
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
|
|
157
|
-
|
|
158
|
+
// 倾斜方向 → 'forward' | 'backward' | 'left' | 'right' | 'center'
|
|
159
|
+
const tilt = bridge.utils.getTiltDirection(x, y);
|
|
160
|
+
```
|
|
158
161
|
|
|
159
|
-
|
|
160
|
-
await bridge.fileStorage.addCustomFileMapping('local://avatar', uri);
|
|
161
|
-
const cached = await bridge.fileStorage.getLocalFileByUrl('local://avatar');
|
|
162
|
+
### 3.3 音频模块(录音、播放)
|
|
162
163
|
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
172
|
+
// 播放并循环(适用于背景音乐)
|
|
173
|
+
await bridge.audio.playOnline({ uri: playUri, isLooping: true });
|
|
171
174
|
|
|
172
|
-
|
|
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
|
-
|
|
184
|
+
**录音模块:**
|
|
186
185
|
|
|
187
|
-
```
|
|
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.
|
|
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
|
-
|
|
249
|
+
**实时相机滤镜**
|
|
209
250
|
|
|
210
|
-
|
|
211
|
-
// 请求权限
|
|
212
|
-
const perm = await bridge.microphone.requestPermission();
|
|
251
|
+
注意:滤镜是通过覆盖在相机层上的 View 实现,支持 RN 样式属性(`backgroundColor`、`opacity` 等),不支持 CSS `filter` 属性。、
|
|
213
252
|
|
|
214
|
-
|
|
215
|
-
|
|
253
|
+
```
|
|
254
|
+
// 预设滤镜
|
|
255
|
+
await bridge.camera.setFilter('grayscale'); // 'sepia' | 'invert' | 'none'
|
|
216
256
|
|
|
217
|
-
//
|
|
218
|
-
bridge.
|
|
219
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
272
|
+
**拍照/相册**
|
|
228
273
|
|
|
229
|
-
|
|
230
|
-
// 向量模长
|
|
231
|
-
const mag = bridge.utils.magnitude(x, y, z);
|
|
274
|
+
不打开实时相机时,拍照/从相册选择图片
|
|
232
275
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
239
|
-
|
|
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
|
-
|
|
298
|
+
闪光灯必须打开实时相机才能调用,参考实时相机的说明。
|
|
245
299
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
##
|
|
351
|
+
## 4. 主要类型定义
|
|
257
352
|
|
|
258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/src/controllers/index.ts
CHANGED
|
@@ -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
|
}
|