@facepe/widget-mobile-app 1.0.0 → 1.2.0
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 +293 -176
- package/dist/FacePeMobileAvatar.d.ts +1 -6
- package/dist/FacePeMobileAvatar.js +667 -95
- package/dist/FacePeMobileProvider.d.ts +2 -2
- package/dist/types.d.ts +35 -7
- package/dist/useFacePeAvatar.d.ts +3 -1
- package/dist/useFacePeAvatar.js +42 -1
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -1,176 +1,293 @@
|
|
|
1
|
-
# @facepe/widget-mobile-app
|
|
2
|
-
|
|
3
|
-
The official **FacePe AI
|
|
4
|
-
|
|
5
|
-
Embed interactive, photorealistic, lip-synced 3D digital avatars directly inside your native mobile apps with
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 📱 Features
|
|
10
|
-
|
|
11
|
-
- **⚡
|
|
12
|
-
- **🗣️ Two-Way
|
|
13
|
-
- **🍎 Full iOS Support**:
|
|
14
|
-
- **🤖 Full Android Support**:
|
|
15
|
-
- **💬 Real-Time
|
|
16
|
-
- **🛒 Conversational
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
yarn
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
1
|
+
# @facepe/widget-mobile-app
|
|
2
|
+
|
|
3
|
+
The official **FacePe AI Digital Human Live Streaming SDK** for **React Native** (supporting both **iOS** and **Android**).
|
|
4
|
+
|
|
5
|
+
Embed interactive, photorealistic, lip-synced 3D digital avatars directly inside your native mobile apps with pure WebRTC media streaming, native microphone capture, bidirectional voice dialog, and live data events — **without webviews**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📱 Features
|
|
10
|
+
|
|
11
|
+
- **⚡ Native WebRTC Streaming**: Pure native media streaming powered by `@livekit/react-native` (no WebViews or embedded iframes).
|
|
12
|
+
- **🗣️ Natural Two-Way Voice Dialog**: Real-time microphone capture with hardware echo cancellation, noise suppression, and server VAD.
|
|
13
|
+
- **🍎 Full iOS Support**: Hardware-accelerated video decoding on iOS 13.0+ (iPhone & iPad).
|
|
14
|
+
- **🤖 Full Android Support**: Hardware-accelerated video decoding on Android 7.0+ (API 24+).
|
|
15
|
+
- **💬 Real-Time Transcriptions**: Bidirectional captions for avatar speech and user speech.
|
|
16
|
+
- **🛒 Conversational Commerce**: Automatic detection of cart items, orders, and payment intents.
|
|
17
|
+
- **🎛️ Imperative Controls**: Ref methods to start/stop stream, mute/unmute mic, speak text, or interrupt speech.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
In your React Native project:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @facepe/widget-mobile-app @livekit/react-native @livekit/react-native-webrtc livekit-client @anam-ai/js-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
*(or using yarn / pnpm)*:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn add @facepe/widget-mobile-app @livekit/react-native @livekit/react-native-webrtc livekit-client @anam-ai/js-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
All avatars play natively (no WebView). The streaming libraries are loaded
|
|
36
|
+
only when a conversation starts. The app needs a development build (Expo Go
|
|
37
|
+
cannot load the native WebRTC module).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 🛠️ Platform Permissions Setup
|
|
42
|
+
|
|
43
|
+
### iOS Setup (`ios/Podfile` & `Info.plist`)
|
|
44
|
+
|
|
45
|
+
1. Open `ios/YourAppName/Info.plist` and add permission strings for microphone and audio:
|
|
46
|
+
|
|
47
|
+
```xml
|
|
48
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
49
|
+
<string>We need access to your microphone to converse with the FacePe AI avatar.</string>
|
|
50
|
+
<key>NSCameraUsageDescription</key>
|
|
51
|
+
<string>Camera access for interactive features.</string>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
2. Run CocoaPods:
|
|
55
|
+
```bash
|
|
56
|
+
cd ios && pod install && cd ..
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### Android Setup (`android/app/src/main/AndroidManifest.xml`)
|
|
62
|
+
|
|
63
|
+
Open `android/app/src/main/AndroidManifest.xml` and include permissions:
|
|
64
|
+
|
|
65
|
+
```xml
|
|
66
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
67
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
68
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
69
|
+
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
70
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🚀 Quick Start Example
|
|
76
|
+
|
|
77
|
+
### 1. Server-side Token Endpoint
|
|
78
|
+
|
|
79
|
+
Exchange your `accessKey` and `secretKey` on your secure backend (Node.js / Express / Next.js / Python / Go) so credentials are never exposed inside mobile apps:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// Express server route: GET /api/facepe-token
|
|
83
|
+
app.get('/api/facepe-token', async (req, res) => {
|
|
84
|
+
const response = await fetch('https://api.facepe.ai/api/v1/sdk/token', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
accessKey: process.env.FACEPE_SDK_ACCESS_KEY,
|
|
89
|
+
secretKey: process.env.FACEPE_SDK_SECRET_KEY,
|
|
90
|
+
channel: 'sdk',
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
res.send(data.data.token);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### 2. React Native Component
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import React, { useRef } from 'react';
|
|
104
|
+
import { StyleSheet, View, SafeAreaView, Text, TouchableOpacity } from 'react-native';
|
|
105
|
+
import {
|
|
106
|
+
FacePeMobileProvider,
|
|
107
|
+
FacePeMobileAvatar,
|
|
108
|
+
FacePeMobileAvatarRef,
|
|
109
|
+
useFacePeAvatar,
|
|
110
|
+
} from '@facepe/widget-mobile-app';
|
|
111
|
+
|
|
112
|
+
export default function App() {
|
|
113
|
+
const avatarRef = useRef<FacePeMobileAvatarRef>(null);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<SafeAreaView style={styles.container}>
|
|
117
|
+
<Text style={styles.title}>FacePe AI Native Digital Human</Text>
|
|
118
|
+
|
|
119
|
+
<FacePeMobileProvider
|
|
120
|
+
apiBaseUrl="https://api.facepe.ai/api/v1/sdk"
|
|
121
|
+
getToken={async () => {
|
|
122
|
+
const res = await fetch('https://your-backend.com/api/facepe-token');
|
|
123
|
+
return await res.text();
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<AvatarScreen avatarRef={avatarRef} />
|
|
127
|
+
</FacePeMobileProvider>
|
|
128
|
+
</SafeAreaView>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function AvatarScreen({ avatarRef }: { avatarRef: React.RefObject<FacePeMobileAvatarRef> }) {
|
|
133
|
+
const { transcripts, isAvatarSpeaking, avatarProps } = useFacePeAvatar();
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<View style={styles.content}>
|
|
137
|
+
<View style={styles.avatarContainer}>
|
|
138
|
+
<FacePeMobileAvatar
|
|
139
|
+
ref={avatarRef}
|
|
140
|
+
language="en"
|
|
141
|
+
objectFit="cover"
|
|
142
|
+
autoStart={true}
|
|
143
|
+
enableMic={true}
|
|
144
|
+
{...avatarProps}
|
|
145
|
+
/>
|
|
146
|
+
</View>
|
|
147
|
+
|
|
148
|
+
{/* Control bar */}
|
|
149
|
+
<View style={styles.controls}>
|
|
150
|
+
<TouchableOpacity
|
|
151
|
+
style={styles.button}
|
|
152
|
+
onPress={() => avatarRef.current?.toggleMic()}
|
|
153
|
+
>
|
|
154
|
+
<Text style={styles.buttonText}>Toggle Mic</Text>
|
|
155
|
+
</TouchableOpacity>
|
|
156
|
+
|
|
157
|
+
<TouchableOpacity
|
|
158
|
+
style={styles.button}
|
|
159
|
+
onPress={() => avatarRef.current?.interrupt()}
|
|
160
|
+
>
|
|
161
|
+
<Text style={styles.buttonText}>Interrupt</Text>
|
|
162
|
+
</TouchableOpacity>
|
|
163
|
+
</View>
|
|
164
|
+
|
|
165
|
+
{/* Real-time speech transcript log */}
|
|
166
|
+
<View style={styles.transcriptBox}>
|
|
167
|
+
{transcripts.slice(-3).map((item, idx) => (
|
|
168
|
+
<Text key={idx} style={styles.transcriptLine}>
|
|
169
|
+
<Text style={item.role === 'assistant' ? styles.avatarTag : styles.userTag}>
|
|
170
|
+
{item.role === 'assistant' ? 'Avatar: ' : 'You: '}
|
|
171
|
+
</Text>
|
|
172
|
+
{item.text}
|
|
173
|
+
</Text>
|
|
174
|
+
))}
|
|
175
|
+
</View>
|
|
176
|
+
</View>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const styles = StyleSheet.create({
|
|
181
|
+
container: {
|
|
182
|
+
flex: 1,
|
|
183
|
+
backgroundColor: '#0F172A',
|
|
184
|
+
},
|
|
185
|
+
content: {
|
|
186
|
+
flex: 1,
|
|
187
|
+
padding: 16,
|
|
188
|
+
},
|
|
189
|
+
title: {
|
|
190
|
+
color: '#FFFFFF',
|
|
191
|
+
fontSize: 20,
|
|
192
|
+
fontWeight: 'bold',
|
|
193
|
+
textAlign: 'center',
|
|
194
|
+
marginVertical: 12,
|
|
195
|
+
},
|
|
196
|
+
avatarContainer: {
|
|
197
|
+
flex: 1,
|
|
198
|
+
borderRadius: 24,
|
|
199
|
+
overflow: 'hidden',
|
|
200
|
+
backgroundColor: '#1E293B',
|
|
201
|
+
},
|
|
202
|
+
controls: {
|
|
203
|
+
flexDirection: 'row',
|
|
204
|
+
justifyContent: 'center',
|
|
205
|
+
gap: 12,
|
|
206
|
+
marginVertical: 14,
|
|
207
|
+
},
|
|
208
|
+
button: {
|
|
209
|
+
backgroundColor: '#7C3AED',
|
|
210
|
+
paddingHorizontal: 20,
|
|
211
|
+
paddingVertical: 10,
|
|
212
|
+
borderRadius: 12,
|
|
213
|
+
},
|
|
214
|
+
buttonText: {
|
|
215
|
+
color: '#FFFFFF',
|
|
216
|
+
fontWeight: '600',
|
|
217
|
+
fontSize: 14,
|
|
218
|
+
},
|
|
219
|
+
transcriptBox: {
|
|
220
|
+
backgroundColor: '#1E293B',
|
|
221
|
+
padding: 12,
|
|
222
|
+
borderRadius: 12,
|
|
223
|
+
minHeight: 80,
|
|
224
|
+
},
|
|
225
|
+
transcriptLine: {
|
|
226
|
+
color: '#E2E8F0',
|
|
227
|
+
fontSize: 13,
|
|
228
|
+
marginBottom: 4,
|
|
229
|
+
},
|
|
230
|
+
avatarTag: {
|
|
231
|
+
color: '#A78BFA',
|
|
232
|
+
fontWeight: '700',
|
|
233
|
+
},
|
|
234
|
+
userTag: {
|
|
235
|
+
color: '#60A5FA',
|
|
236
|
+
fontWeight: '700',
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## ⚙️ Props & Configuration
|
|
244
|
+
|
|
245
|
+
### `<FacePeMobileAvatar />`
|
|
246
|
+
|
|
247
|
+
| Prop | Type | Default | Description |
|
|
248
|
+
| :--- | :--- | :--- | :--- |
|
|
249
|
+
| `avatarKey` | `string` | *(optional)* | Specific avatar key or ID assigned to your company. |
|
|
250
|
+
| `language` | `string` | `'en'` | Spoken language code (`'en'`, `'hi'`, `'es'`, `'ta'`, `'te'`, etc.). |
|
|
251
|
+
| `style` | `ViewStyle` | `undefined` | Style applied to avatar container. |
|
|
252
|
+
| `videoStyle` | `ViewStyle` | `undefined` | Style applied to native video view. |
|
|
253
|
+
| `objectFit` | `'cover' \| 'contain'` | `'cover'` | Video aspect scaling mode. |
|
|
254
|
+
| `mirror` | `boolean` | `false` | Mirror video orientation. |
|
|
255
|
+
| `autoStart` | `boolean` | `true` | Automatically connect and start stream on mount. |
|
|
256
|
+
| `enableMic` | `boolean` | `true` | Automatically enable microphone on connect. |
|
|
257
|
+
| `onTranscript` | `function` | `undefined` | Fires when speech transcription arrives. |
|
|
258
|
+
| `onSpeakStart` | `function` | `undefined` | Fires when avatar starts speaking. |
|
|
259
|
+
| `onSpeakEnd` | `function` | `undefined` | Fires when avatar finishes speaking. |
|
|
260
|
+
| `onUserSpeakStart` | `function` | `undefined` | Fires when user voice activity begins. |
|
|
261
|
+
| `onUserSpeakEnd` | `function` | `undefined` | Fires when user voice activity ends. |
|
|
262
|
+
| `onOrderUpdate` | `function` | `undefined` | Fires when cart items or intents are recognized. |
|
|
263
|
+
| `onCheckout` | `function` | `undefined` | Fires when customer requests checkout / payment. |
|
|
264
|
+
| `onStatusChange` | `function` | `undefined` | Stream lifecycle status: `'idle' \| 'connecting' \| 'connected' \| 'reconnecting' \| 'disconnected' \| 'error'`. |
|
|
265
|
+
| `onError` | `function` | `undefined` | Stream error callback `{ code, message }`. |
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### Imperative Ref (`FacePeMobileAvatarRef`)
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
const avatarRef = useRef<FacePeMobileAvatarRef>(null);
|
|
273
|
+
|
|
274
|
+
// Start streaming session
|
|
275
|
+
await avatarRef.current?.start();
|
|
276
|
+
|
|
277
|
+
// Stop streaming session & finalize billing usage
|
|
278
|
+
await avatarRef.current?.stop();
|
|
279
|
+
|
|
280
|
+
// Toggle mic on/off (returns new state boolean)
|
|
281
|
+
const isNowMuted = await avatarRef.current?.toggleMic();
|
|
282
|
+
|
|
283
|
+
// Make avatar speak programmatically
|
|
284
|
+
await avatarRef.current?.speak("Hello! How can I assist you today?");
|
|
285
|
+
|
|
286
|
+
// Interrupt avatar speech immediately
|
|
287
|
+
await avatarRef.current?.interrupt();
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 📄 License
|
|
293
|
+
MIT © FacePe AI
|
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { FacePeMobileAvatarProps } from './types';
|
|
3
|
-
export interface FacePeMobileAvatarRef {
|
|
4
|
-
start: () => Promise<void>;
|
|
5
|
-
stop: () => Promise<void>;
|
|
6
|
-
toggleMic: () => Promise<void>;
|
|
7
|
-
}
|
|
2
|
+
import type { FacePeMobileAvatarProps, FacePeMobileAvatarRef } from './types';
|
|
8
3
|
export declare const FacePeMobileAvatar: React.ForwardRefExoticComponent<FacePeMobileAvatarProps & React.RefAttributes<FacePeMobileAvatarRef>>;
|