@facepe/widget-mobile-app 1.3.0 → 1.3.1
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 +1 -0
- package/dist/FacePeMobileAvatar.js +41 -5
- package/dist/types.d.ts +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -253,6 +253,7 @@ const styles = StyleSheet.create({
|
|
|
253
253
|
| `objectFit` | `'cover' \| 'contain'` | `'cover'` | Video aspect scaling mode. |
|
|
254
254
|
| `mirror` | `boolean` | `false` | Mirror video orientation. |
|
|
255
255
|
| `autoStart` | `boolean` | `true` | Automatically connect and start stream on mount. |
|
|
256
|
+
| `posterUrl` | `string \| null` | avatar's portrait | Picture shown until the video starts. Defaults to the avatar's portrait from FacePe; pass `null` for none. |
|
|
256
257
|
| `enableMic` | `boolean` | `true` | Automatically enable microphone on connect. |
|
|
257
258
|
| `onTranscript` | `function` | `undefined` | Fires when speech transcription arrives. |
|
|
258
259
|
| `onSpeakStart` | `function` | `undefined` | Fires when avatar starts speaking. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState, useImperativeHandle, forwardRef, useCallback, } from 'react';
|
|
3
|
-
import { View, StyleSheet, ActivityIndicator, Text, TouchableOpacity, Platform } from 'react-native';
|
|
3
|
+
import { View, StyleSheet, ActivityIndicator, Text, TouchableOpacity, Platform, Image } from 'react-native';
|
|
4
4
|
import { SDK_VERSION } from './version';
|
|
5
5
|
import { useFacePeMobileContext } from './FacePeMobileProvider';
|
|
6
6
|
// Resolve LiveKit dynamically at runtime so bare/Expo projects can run smoothly.
|
|
@@ -58,8 +58,36 @@ function ensureLiveKitGlobals(LiveKitRN) {
|
|
|
58
58
|
console.warn('[FacePeMobileAvatar] registerGlobals warning:', err);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
export const FacePeMobileAvatar = forwardRef(({ avatarKey, language = 'en', style, videoStyle, objectFit = 'cover', mirror = false, autoStart = true, enableMic = true, onTranscript, onSpeakStart, onSpeakEnd, onUserSpeakStart, onUserSpeakEnd, onOrderUpdate, onCheckout, onStatusChange, onError, onConversationEnd, }, ref) => {
|
|
61
|
+
export const FacePeMobileAvatar = forwardRef(({ avatarKey, language = 'en', style, videoStyle, objectFit = 'cover', mirror = false, autoStart = true, enableMic = true, onTranscript, onSpeakStart, onSpeakEnd, onUserSpeakStart, onUserSpeakEnd, onOrderUpdate, onCheckout, onStatusChange, onError, onConversationEnd, posterUrl, }, ref) => {
|
|
62
62
|
const { token, loading: tokenLoading, error: tokenError, apiBaseUrl, refreshToken } = useFacePeMobileContext();
|
|
63
|
+
// The avatar's portrait, shown until its video arrives. Looked up from the
|
|
64
|
+
// avatars this token may use, unless the app gave one (or null for none).
|
|
65
|
+
const [autoPoster, setAutoPoster] = useState(null);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (posterUrl !== undefined || !token)
|
|
68
|
+
return;
|
|
69
|
+
let cancelled = false;
|
|
70
|
+
void (async () => {
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch(`${apiBaseUrl.replace(/\/+$/, '')}/avatars`, {
|
|
73
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
74
|
+
});
|
|
75
|
+
if (!res.ok)
|
|
76
|
+
return;
|
|
77
|
+
const avatars = (await res.json())?.data?.avatars ?? [];
|
|
78
|
+
const avatar = avatarKey ? avatars.find((a) => a.id === avatarKey) : avatars[0];
|
|
79
|
+
if (!cancelled)
|
|
80
|
+
setAutoPoster(avatar?.previewUrl ?? null);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* no picture: the plain background stays */
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
return () => {
|
|
87
|
+
cancelled = true;
|
|
88
|
+
};
|
|
89
|
+
}, [token, apiBaseUrl, avatarKey, posterUrl]);
|
|
90
|
+
const poster = posterUrl !== undefined ? posterUrl : autoPoster;
|
|
63
91
|
const [status, setStatus] = useState('idle');
|
|
64
92
|
const [videoTrack, setVideoTrack] = useState(null);
|
|
65
93
|
const [isMicEnabled, setIsMicEnabled] = useState(enableMic);
|
|
@@ -645,9 +673,9 @@ export const FacePeMobileAvatar = forwardRef(({ avatarKey, language = 'en', styl
|
|
|
645
673
|
// A plain object, not an array: VideoView spreads `style` into its
|
|
646
674
|
// container ({ ...style }), and spreading an array loses every
|
|
647
675
|
// size — the video got no height and stayed invisible.
|
|
648
|
-
style: StyleSheet.flatten([styles.video, videoStyle]), objectFit: objectFit, mirror: mirror })) : (
|
|
649
|
-
|
|
650
|
-
|
|
676
|
+
style: StyleSheet.flatten([styles.video, videoStyle]), objectFit: objectFit, mirror: mirror })) : (_jsxs(View, { style: [styles.container, styles.center, !!poster && (status === 'idle' || status === 'disconnected') && styles.overPoster], children: [poster && _jsx(Image, { source: { uri: poster }, style: StyleSheet.absoluteFill, resizeMode: "cover" }), poster && status !== 'idle' && status !== 'disconnected' && _jsx(View, { style: [StyleSheet.absoluteFill, styles.dim] }), status === 'connecting' || status === 'reconnecting' || status === 'connected' ? (
|
|
677
|
+
// connected: the stream is up and the first video frame is on its way.
|
|
678
|
+
_jsxs(_Fragment, { children: [_jsx(ActivityIndicator, { size: "large", color: "#8B5CF6" }), _jsx(Text, { style: styles.loadingText, children: status === 'reconnecting' ? 'Reconnecting stream…' : 'Connecting to FacePe AI Avatar…' })] })) : status === 'error' ? (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.errorTitle, children: "Connection Failed" }), _jsx(Text, { style: styles.errorText, children: errorMessage || 'Unable to connect to stream' }), _jsx(TouchableOpacity, { style: styles.retryButton, onPress: () => void startStream(), children: _jsx(Text, { style: styles.retryText, children: "Reconnect" }) })] })) : (_jsx(TouchableOpacity, { style: styles.startButton, onPress: () => void startStream(), children: _jsx(Text, { style: styles.startText, children: "Start Avatar Session" }) }))] })), status === 'connected' && (isAvatarSpeaking || isUserSpeaking) && (_jsxs(View, { style: styles.speakerPill, children: [_jsx(View, { style: [
|
|
651
679
|
styles.speakerDot,
|
|
652
680
|
isAvatarSpeaking ? styles.dotAvatar : styles.dotUser,
|
|
653
681
|
] }), _jsx(Text, { style: styles.speakerText, children: isAvatarSpeaking ? 'Avatar speaking' : 'Listening to you…' })] }))] }));
|
|
@@ -667,6 +695,14 @@ const styles = StyleSheet.create({
|
|
|
667
695
|
width: '100%',
|
|
668
696
|
height: '100%',
|
|
669
697
|
},
|
|
698
|
+
// Over a portrait, keep the face clear: the button sits at the bottom.
|
|
699
|
+
overPoster: {
|
|
700
|
+
justifyContent: 'flex-end',
|
|
701
|
+
paddingBottom: 28,
|
|
702
|
+
},
|
|
703
|
+
dim: {
|
|
704
|
+
backgroundColor: 'rgba(15, 23, 42, 0.45)',
|
|
705
|
+
},
|
|
670
706
|
center: {
|
|
671
707
|
justifyContent: 'center',
|
|
672
708
|
alignItems: 'center',
|
package/dist/types.d.ts
CHANGED
|
@@ -100,6 +100,11 @@ export interface FacePeMobileAvatarProps {
|
|
|
100
100
|
onCheckout?: () => void;
|
|
101
101
|
/** Called when stream connection status changes */
|
|
102
102
|
onStatusChange?: (status: FacePeConnectionStatus) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Picture shown until the video starts (idle, connecting, errors). By default
|
|
105
|
+
* the avatar's own portrait from FacePe; pass your own URL, or null for none.
|
|
106
|
+
*/
|
|
107
|
+
posterUrl?: string | null;
|
|
103
108
|
/** Called once when a conversation ends, with the whole conversation (stays in your app) */
|
|
104
109
|
onConversationEnd?: (conversation: FacePeConversation) => void;
|
|
105
110
|
/** Called when an error occurs */
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, reported in conversation records. Keep in step with package.json. */
|
|
2
|
-
export declare const SDK_VERSION = "1.3.
|
|
2
|
+
export declare const SDK_VERSION = "1.3.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, reported in conversation records. Keep in step with package.json. */
|
|
2
|
-
export const SDK_VERSION = '1.3.
|
|
2
|
+
export const SDK_VERSION = '1.3.1';
|
package/package.json
CHANGED