omikit-plugin 4.0.2 → 4.1.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 +272 -24
- package/android/src/main/java/com/omikitplugin/OmiLocalCameraView.kt +112 -0
- package/android/src/main/java/com/omikitplugin/OmiRemoteCameraView.kt +99 -0
- package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +5 -4
- package/android/src/main/java/com/omikitplugin/OmikitPluginPackage.kt +11 -8
- package/ios/CallProcess/CallManager.swift +99 -29
- package/ios/Library/OmikitPlugin.m +18 -0
- package/ios/Library/OmikitPlugin.swift +233 -1
- package/ios/OmikitPlugin-Bridging-Header.h +1 -0
- package/ios/OmikitPlugin.xcodeproj/project.pbxproj +4 -4
- package/ios/VideoCall/OmiLocalCameraViewBridge.m +14 -0
- package/ios/VideoCall/OmiLocalCameraViewManager.swift +41 -0
- package/ios/VideoCall/OmiRemoteCameraViewBridge.m +14 -0
- package/ios/VideoCall/OmiRemoteCameraViewManager.swift +40 -0
- package/lib/commonjs/NativeOmikitPlugin.js +2 -1
- package/lib/commonjs/NativeOmikitPlugin.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/omi_audio_type.js +5 -7
- package/lib/commonjs/omi_audio_type.js.map +1 -1
- package/lib/commonjs/omi_call_state.js +5 -3
- package/lib/commonjs/omi_call_state.js.map +1 -1
- package/lib/commonjs/omi_local_camera.js +19 -17
- package/lib/commonjs/omi_local_camera.js.map +1 -1
- package/lib/commonjs/omi_remote_camera.js +20 -17
- package/lib/commonjs/omi_remote_camera.js.map +1 -1
- package/lib/commonjs/omi_start_call_status.js +5 -24
- package/lib/commonjs/omi_start_call_status.js.map +1 -1
- package/lib/commonjs/omikit.js +56 -3
- package/lib/commonjs/omikit.js.map +1 -1
- package/lib/commonjs/types/index.d.js.map +1 -1
- package/lib/module/NativeOmikitPlugin.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/omi_audio_type.js +4 -7
- package/lib/module/omi_audio_type.js.map +1 -1
- package/lib/module/omi_call_state.js +4 -3
- package/lib/module/omi_call_state.js.map +1 -1
- package/lib/module/omi_local_camera.js +19 -18
- package/lib/module/omi_local_camera.js.map +1 -1
- package/lib/module/omi_remote_camera.js +20 -18
- package/lib/module/omi_remote_camera.js.map +1 -1
- package/lib/module/omi_start_call_status.js +4 -24
- package/lib/module/omi_start_call_status.js.map +1 -1
- package/lib/module/omikit.js +49 -1
- package/lib/module/omikit.js.map +1 -1
- package/lib/module/types/index.d.js.map +1 -1
- package/omikit-plugin.podspec +1 -1
- package/package.json +2 -11
- package/react-native.config.js +14 -0
- package/src/NativeOmikitPlugin.ts +1 -0
- package/src/omi_call_state.tsx +1 -0
- package/src/omi_local_camera.tsx +15 -19
- package/src/omi_remote_camera.tsx +16 -19
- package/src/omikit.tsx +63 -0
- package/src/types/index.d.ts +344 -62
- package/android/src/main/java/com/omikitplugin/FLLocalCameraModule.kt +0 -34
- package/android/src/main/java/com/omikitplugin/FLLocalCameraView.kt +0 -44
- package/android/src/main/java/com/omikitplugin/FLRemoteCameraModule.kt +0 -37
- package/android/src/main/java/com/omikitplugin/FLRemoteCameraView.kt +0 -23
- package/ios/VideoCall/FLLocalCameraView.m +0 -17
- package/ios/VideoCall/FLLocalCameraView.swift +0 -44
- package/ios/VideoCall/FLRemoteCameraView.m +0 -18
- package/ios/VideoCall/FLRemoteCameraView.swift +0 -124
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The [omikit-plugin](https://www.npmjs.com/package/omikit-plugin) enables VoIP/SIP calling via the OMICALL platform with support for both Old and **New Architecture** (TurboModules + Fabric).
|
|
4
4
|
|
|
5
|
-
**Status:** Active maintenance | **Version:** 4.0
|
|
5
|
+
**Status:** Active maintenance | **Version:** 4.1.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -47,7 +47,36 @@ The [omikit-plugin](https://www.npmjs.com/package/omikit-plugin) enables VoIP/SI
|
|
|
47
47
|
| Platform | SDK | Version |
|
|
48
48
|
|----------|-----|---------|
|
|
49
49
|
| Android | OMIKIT | 2.6.4 |
|
|
50
|
-
| iOS | OmiKit | 1.
|
|
50
|
+
| iOS | OmiKit | 1.11.4 |
|
|
51
|
+
|
|
52
|
+
### Platform Requirements
|
|
53
|
+
|
|
54
|
+
| | Android | iOS |
|
|
55
|
+
|--|---------|-----|
|
|
56
|
+
| **Min SDK** | API 24 (Android 7.0) | iOS 13.0 |
|
|
57
|
+
| **Target SDK** | API 35 (Android 15) | — |
|
|
58
|
+
| **Compile SDK** | API 35 | — |
|
|
59
|
+
|
|
60
|
+
### Device Requirements
|
|
61
|
+
|
|
62
|
+
| Requirement | Platform | Notes |
|
|
63
|
+
|-------------|----------|-------|
|
|
64
|
+
| **Physical device** | iOS (required) | iOS Simulator is **not supported** — OmiKit binary is arm64 device-only |
|
|
65
|
+
| **Physical device** | Android (recommended) | Emulator works for basic UI testing but VoIP/audio routing is unreliable |
|
|
66
|
+
| **Google Play Services** | Android (required) | Required for FCM push notifications |
|
|
67
|
+
| **Microphone** | Both (required) | Required for all calls |
|
|
68
|
+
| **Camera** | Both (optional) | Only required for video calls |
|
|
69
|
+
| **Internet** | Both (required) | SIP registration + RTP media streaming |
|
|
70
|
+
|
|
71
|
+
### Package Size
|
|
72
|
+
|
|
73
|
+
| Component | Size |
|
|
74
|
+
|-----------|------|
|
|
75
|
+
| npm package (total) | ~353 KB |
|
|
76
|
+
| Android native code | ~4.7 MB |
|
|
77
|
+
| iOS native code | ~176 KB |
|
|
78
|
+
|
|
79
|
+
> **Note:** These sizes are for the plugin only. The native SDKs (OmiKit/OMIKIT) are installed separately via CocoaPods/Maven and will add to the final app size.
|
|
51
80
|
|
|
52
81
|
---
|
|
53
82
|
|
|
@@ -945,44 +974,259 @@ omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
|
|
|
945
974
|
|
|
946
975
|
## Video Calls
|
|
947
976
|
|
|
948
|
-
|
|
977
|
+
> **Important:** To use video calls, you must login with `isVideo: true` in `initCallWithUserPassword()`. This enables camera support during SIP registration.
|
|
978
|
+
|
|
979
|
+
### Prerequisites
|
|
949
980
|
|
|
950
981
|
```typescript
|
|
951
|
-
|
|
982
|
+
// Login with video support enabled
|
|
983
|
+
await initCallWithUserPassword({
|
|
984
|
+
userName: 'sip_user',
|
|
985
|
+
password: 'sip_password',
|
|
986
|
+
realm: 'your_realm',
|
|
987
|
+
isVideo: true, // Required for video call support
|
|
988
|
+
fcmToken: fcmToken,
|
|
989
|
+
});
|
|
952
990
|
```
|
|
953
991
|
|
|
954
992
|
### Video Components
|
|
955
993
|
|
|
956
994
|
```tsx
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
995
|
+
import {
|
|
996
|
+
OmiLocalCameraView, // Your camera preview
|
|
997
|
+
OmiRemoteCameraView, // Remote party's video
|
|
998
|
+
registerVideoEvent,
|
|
999
|
+
removeVideoEvent,
|
|
1000
|
+
refreshRemoteCamera,
|
|
1001
|
+
refreshLocalCamera,
|
|
1002
|
+
switchOmiCamera,
|
|
1003
|
+
toggleOmiVideo,
|
|
1004
|
+
setupVideoContainers, // iOS Fabric only
|
|
1005
|
+
setCameraConfig, // iOS Fabric only
|
|
1006
|
+
omiEmitter,
|
|
1007
|
+
OmiCallEvent,
|
|
1008
|
+
OmiCallState,
|
|
1009
|
+
} from 'omikit-plugin';
|
|
962
1010
|
```
|
|
963
1011
|
|
|
964
|
-
###
|
|
1012
|
+
### Platform Differences
|
|
965
1013
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1014
|
+
| Feature | Android | iOS (Old Arch) | iOS (New Arch / Fabric) |
|
|
1015
|
+
|---------|---------|----------------|------------------------|
|
|
1016
|
+
| Video rendering | JSX components | JSX components | Native window containers |
|
|
1017
|
+
| Camera views | `<OmiRemoteCameraView>` in JSX | `<OmiRemoteCameraView>` in JSX | `setupVideoContainers()` from JS |
|
|
1018
|
+
| Style control | React `style` props | React `style` props | `setCameraConfig()` from JS |
|
|
1019
|
+
| Controls overlay | Overlay on video | Overlay on video | Below video (split layout) |
|
|
969
1020
|
|
|
970
|
-
|
|
971
|
-
await startCall({ phoneNumber: '0901234567', isVideo: true });
|
|
1021
|
+
### Cross-Platform Video Call Example
|
|
972
1022
|
|
|
973
|
-
|
|
974
|
-
await toggleOmiVideo(); // on/off video stream
|
|
975
|
-
await switchOmiCamera(); // front/back camera
|
|
1023
|
+
Complete example supporting both Android and iOS (Old + New Architecture):
|
|
976
1024
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1025
|
+
```tsx
|
|
1026
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
1027
|
+
import { View, Text, TouchableOpacity, StyleSheet, Platform, Dimensions } from 'react-native';
|
|
1028
|
+
import {
|
|
1029
|
+
OmiRemoteCameraView, OmiLocalCameraView,
|
|
1030
|
+
omiEmitter, OmiCallEvent, OmiCallState,
|
|
1031
|
+
registerVideoEvent, removeVideoEvent,
|
|
1032
|
+
refreshRemoteCamera, refreshLocalCamera,
|
|
1033
|
+
setupVideoContainers, setCameraConfig,
|
|
1034
|
+
switchOmiCamera, toggleOmiVideo, toggleMute, endCall,
|
|
1035
|
+
} from 'omikit-plugin';
|
|
1036
|
+
|
|
1037
|
+
const { width: SW, height: SH } = Dimensions.get('window');
|
|
1038
|
+
|
|
1039
|
+
export const VideoCallScreen = ({ navigation, route }) => {
|
|
1040
|
+
const [isCallActive, setIsCallActive] = useState(false);
|
|
1041
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
1042
|
+
const [cameraOn, setCameraOn] = useState(true);
|
|
1043
|
+
const hasNavigated = useRef(false);
|
|
1044
|
+
|
|
1045
|
+
// iOS Fabric: configure native video container position/size
|
|
1046
|
+
const configureIOSVideo = useCallback(() => {
|
|
1047
|
+
setCameraConfig({
|
|
1048
|
+
target: 'remote',
|
|
1049
|
+
x: 0, y: 0, width: SW, height: SH * 0.65,
|
|
1050
|
+
scaleMode: 'fill', backgroundColor: '#000',
|
|
1051
|
+
});
|
|
1052
|
+
setCameraConfig({
|
|
1053
|
+
target: 'local',
|
|
1054
|
+
x: SW - 136, y: 60, width: 120, height: 180,
|
|
1055
|
+
borderRadius: 12, scaleMode: 'fill',
|
|
1056
|
+
});
|
|
1057
|
+
}, []);
|
|
1058
|
+
|
|
1059
|
+
useEffect(() => {
|
|
1060
|
+
// iOS: register video events before call
|
|
1061
|
+
if (Platform.OS === 'ios') registerVideoEvent();
|
|
1062
|
+
|
|
1063
|
+
const sub = omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
|
|
1064
|
+
const { status } = data;
|
|
1065
|
+
|
|
1066
|
+
if (status === OmiCallState.confirmed) {
|
|
1067
|
+
setIsCallActive(true);
|
|
1068
|
+
|
|
1069
|
+
if (Platform.OS === 'android') {
|
|
1070
|
+
// Android: connect video feeds to TextureView surfaces
|
|
1071
|
+
refreshRemoteCamera();
|
|
1072
|
+
refreshLocalCamera();
|
|
1073
|
+
} else {
|
|
1074
|
+
// iOS Fabric: create native window containers + connect SDK
|
|
1075
|
+
setupVideoContainers().then(() => configureIOSVideo());
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if ((status === OmiCallState.disconnected || status === 6) && !hasNavigated.current) {
|
|
1080
|
+
hasNavigated.current = true;
|
|
1081
|
+
// iOS: hide native containers before navigating
|
|
1082
|
+
if (Platform.OS === 'ios') {
|
|
1083
|
+
setCameraConfig({ target: 'remote', hidden: true });
|
|
1084
|
+
setCameraConfig({ target: 'local', hidden: true });
|
|
1085
|
+
}
|
|
1086
|
+
setTimeout(() => {
|
|
1087
|
+
navigation.reset({ index: 0, routes: [{ name: 'Home' }] });
|
|
1088
|
+
}, 100);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
return () => {
|
|
1093
|
+
sub.remove();
|
|
1094
|
+
if (Platform.OS === 'ios') removeVideoEvent();
|
|
1095
|
+
};
|
|
1096
|
+
}, [navigation, configureIOSVideo]);
|
|
1097
|
+
|
|
1098
|
+
const handleEndCall = useCallback(() => {
|
|
1099
|
+
if (hasNavigated.current) return;
|
|
1100
|
+
hasNavigated.current = true;
|
|
1101
|
+
if (Platform.OS === 'ios') {
|
|
1102
|
+
setCameraConfig({ target: 'remote', hidden: true });
|
|
1103
|
+
setCameraConfig({ target: 'local', hidden: true });
|
|
1104
|
+
}
|
|
1105
|
+
endCall();
|
|
1106
|
+
setTimeout(() => {
|
|
1107
|
+
navigation.reset({ index: 0, routes: [{ name: 'Home' }] });
|
|
1108
|
+
}, 1000);
|
|
1109
|
+
}, [navigation]);
|
|
1110
|
+
|
|
1111
|
+
return (
|
|
1112
|
+
<View style={styles.container}>
|
|
1113
|
+
{/* ============ VIDEO AREA ============ */}
|
|
1114
|
+
|
|
1115
|
+
{/* Android: camera views via JSX — controls can overlay on top */}
|
|
1116
|
+
{Platform.OS === 'android' && isCallActive && (
|
|
1117
|
+
<>
|
|
1118
|
+
<OmiRemoteCameraView style={StyleSheet.absoluteFillObject} />
|
|
1119
|
+
<OmiLocalCameraView style={styles.localPiP} />
|
|
1120
|
+
</>
|
|
1121
|
+
)}
|
|
1122
|
+
|
|
1123
|
+
{/* iOS Fabric: native video renders on window (top ~65%).
|
|
1124
|
+
React controls render below video area. */}
|
|
1125
|
+
|
|
1126
|
+
{/* ============ CONTROLS AREA ============ */}
|
|
1127
|
+
|
|
1128
|
+
{/* Spacer — pushes controls to bottom */}
|
|
1129
|
+
<View style={{ flex: 1 }} />
|
|
1130
|
+
|
|
1131
|
+
{/* Controls panel */}
|
|
1132
|
+
{isCallActive && (
|
|
1133
|
+
<View style={styles.controls}>
|
|
1134
|
+
<TouchableOpacity onPress={() => { toggleMute(); setIsMuted(m => !m); }}>
|
|
1135
|
+
<Text style={styles.btn}>{isMuted ? 'Unmute' : 'Mute'}</Text>
|
|
1136
|
+
</TouchableOpacity>
|
|
1137
|
+
<TouchableOpacity onPress={() => { toggleOmiVideo(); setCameraOn(c => !c); }}>
|
|
1138
|
+
<Text style={styles.btn}>{cameraOn ? 'Cam Off' : 'Cam On'}</Text>
|
|
1139
|
+
</TouchableOpacity>
|
|
1140
|
+
<TouchableOpacity onPress={switchOmiCamera}>
|
|
1141
|
+
<Text style={styles.btn}>Flip</Text>
|
|
1142
|
+
</TouchableOpacity>
|
|
1143
|
+
<TouchableOpacity onPress={handleEndCall}>
|
|
1144
|
+
<Text style={[styles.btn, { backgroundColor: 'red' }]}>End</Text>
|
|
1145
|
+
</TouchableOpacity>
|
|
1146
|
+
</View>
|
|
1147
|
+
)}
|
|
1148
|
+
|
|
1149
|
+
{/* Answer/Reject for incoming calls */}
|
|
1150
|
+
{!isCallActive && (
|
|
1151
|
+
<View style={styles.controls}>
|
|
1152
|
+
<TouchableOpacity onPress={handleEndCall}>
|
|
1153
|
+
<Text style={[styles.btn, { backgroundColor: 'red' }]}>Reject</Text>
|
|
1154
|
+
</TouchableOpacity>
|
|
1155
|
+
</View>
|
|
1156
|
+
)}
|
|
1157
|
+
</View>
|
|
1158
|
+
);
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
const styles = StyleSheet.create({
|
|
1162
|
+
container: { flex: 1, backgroundColor: '#1E3050' },
|
|
1163
|
+
localPiP: {
|
|
1164
|
+
position: 'absolute', top: 60, right: 16,
|
|
1165
|
+
width: 120, height: 180, borderRadius: 12, overflow: 'hidden', zIndex: 10,
|
|
1166
|
+
},
|
|
1167
|
+
controls: {
|
|
1168
|
+
flexDirection: 'row', justifyContent: 'space-evenly',
|
|
1169
|
+
paddingVertical: 20, paddingHorizontal: 16,
|
|
1170
|
+
backgroundColor: 'rgba(0,0,0,0.5)', borderTopLeftRadius: 24, borderTopRightRadius: 24,
|
|
1171
|
+
paddingBottom: 40,
|
|
1172
|
+
},
|
|
1173
|
+
btn: {
|
|
1174
|
+
color: '#fff', fontSize: 14, paddingVertical: 12, paddingHorizontal: 16,
|
|
1175
|
+
backgroundColor: 'rgba(255,255,255,0.2)', borderRadius: 24, overflow: 'hidden',
|
|
1176
|
+
},
|
|
980
1177
|
});
|
|
1178
|
+
```
|
|
981
1179
|
|
|
982
|
-
|
|
983
|
-
|
|
1180
|
+
> **Key differences per platform:**
|
|
1181
|
+
> - **Android**: `<OmiRemoteCameraView>` + `<OmiLocalCameraView>` render in JSX. Call `refreshRemoteCamera()` + `refreshLocalCamera()` when confirmed. Controls overlay on video.
|
|
1182
|
+
> - **iOS (Old Arch)**: Same as Android — JSX camera views work normally.
|
|
1183
|
+
> - **iOS (New Arch/Fabric)**: `RCTViewManager.view()` not called. Use `setupVideoContainers()` to create native window containers, then `setCameraConfig()` to adjust position/style. Controls render below video (split layout).
|
|
1184
|
+
> - **Disconnect**: On iOS, call `setCameraConfig({ target: 'remote', hidden: true })` before navigating to prevent stale video overlay. Use `setTimeout` for navigation — native event callback may block immediate navigation.
|
|
1185
|
+
> - **End call**: Use `navigation.reset()` instead of `goBack()` to clear stacked screens. Guard with `useRef` to prevent multiple navigations.
|
|
1186
|
+
|
|
1187
|
+
### `setCameraConfig()` — iOS Fabric Only
|
|
1188
|
+
|
|
1189
|
+
Control native video container style from JS:
|
|
1190
|
+
|
|
1191
|
+
```typescript
|
|
1192
|
+
setCameraConfig({
|
|
1193
|
+
target: 'local' | 'remote',
|
|
1194
|
+
x?: number, // X position
|
|
1195
|
+
y?: number, // Y position
|
|
1196
|
+
width?: number, // View width
|
|
1197
|
+
height?: number, // View height
|
|
1198
|
+
borderRadius?: number, // Corner radius
|
|
1199
|
+
borderWidth?: number, // Border width
|
|
1200
|
+
borderColor?: string, // Hex color (#RRGGBB or #RRGGBBAA)
|
|
1201
|
+
backgroundColor?: string,
|
|
1202
|
+
opacity?: number, // 0.0 - 1.0
|
|
1203
|
+
hidden?: boolean, // Show/hide
|
|
1204
|
+
scaleMode?: 'fill' | 'fit' | 'stretch',
|
|
1205
|
+
});
|
|
984
1206
|
```
|
|
985
1207
|
|
|
1208
|
+
### Video API Reference
|
|
1209
|
+
|
|
1210
|
+
| Function | Description | Platform |
|
|
1211
|
+
|----------|-------------|----------|
|
|
1212
|
+
| `registerVideoEvent()` | Register for video notifications | iOS only |
|
|
1213
|
+
| `removeVideoEvent()` | Cleanup video notifications | iOS only |
|
|
1214
|
+
| `refreshRemoteCamera()` | Connect remote video feed to surface | Both |
|
|
1215
|
+
| `refreshLocalCamera()` | Connect local camera feed to surface | Both |
|
|
1216
|
+
| `switchOmiCamera()` | Switch front/back camera | Both |
|
|
1217
|
+
| `toggleOmiVideo()` | Toggle camera on/off during call | Both |
|
|
1218
|
+
| `setupVideoContainers()` | Create native video containers on window | iOS Fabric only |
|
|
1219
|
+
| `setCameraConfig()` | Control video container style/position | iOS Fabric only |
|
|
1220
|
+
|
|
1221
|
+
### Known Limitations
|
|
1222
|
+
|
|
1223
|
+
- **iOS Fabric**: Controls cannot overlay on top of video (native window z-order). Use split layout — video top, controls bottom.
|
|
1224
|
+
- **iOS Fabric**: Camera switch has ~3-6s delay (SDK Metal stabilization).
|
|
1225
|
+
- **Android**: Must login with `isVideo: true` for camera to initialize. Without it, colorbar test pattern shows instead of camera.
|
|
1226
|
+
- **Simulator**: Video calls require physical device on both platforms.
|
|
1227
|
+
|
|
1228
|
+
See [Known Issues](./docs/known-issues.md) for full details.
|
|
1229
|
+
|
|
986
1230
|
---
|
|
987
1231
|
|
|
988
1232
|
## Push Notifications
|
|
@@ -1214,7 +1458,10 @@ if (initialCall) {
|
|
|
1214
1458
|
| Incoming call on Android — no UI | Missing overlay permission | Call `requestSystemAlertWindowPermission()` |
|
|
1215
1459
|
| `startCall` returns 450/451/452 | Missing runtime permissions | Call `requestPermissionsByCodes([code])` |
|
|
1216
1460
|
| No audio during call | Audio route issue | Check `getAudio()` and `setAudio()` |
|
|
1217
|
-
| Video not showing | Video event not registered | Call `registerVideoEvent()` before the call |
|
|
1461
|
+
| Video not showing (iOS) | Video event not registered | Call `registerVideoEvent()` before the call |
|
|
1462
|
+
| Video shows colorbar (Android) | Login with `isVideo: false` | Login with `isVideo: true` to enable camera |
|
|
1463
|
+
| Video black screen (Android) | Feed not connected | Ensure `refreshRemoteCamera()` + `refreshLocalCamera()` called on confirmed |
|
|
1464
|
+
| iOS Fabric: controls hidden | Native video on UIWindow covers React | Use split layout — video top, controls bottom. See [Known Issues](./docs/known-issues.md) |
|
|
1218
1465
|
| NativeEventEmitter warning (iOS) | Old RN bridge issue | Upgrade to v4.0.x with New Architecture |
|
|
1219
1466
|
| `Invalid local URI` in logs | Empty proxy/host in login | Pass `host` parameter in `initCallWithUserPassword` |
|
|
1220
1467
|
| Build error with New Arch | Codegen not configured | Ensure `codegenConfig` exists in `package.json` |
|
|
@@ -1227,6 +1474,7 @@ if (initialCall) {
|
|
|
1227
1474
|
Full documentation in [`./docs/`](./docs/):
|
|
1228
1475
|
|
|
1229
1476
|
- [API Integration Guide (App-to-App)](./docs/api-integration-guide.md)
|
|
1477
|
+
- [Known Issues & Limitations](./docs/known-issues.md)
|
|
1230
1478
|
- [Project Overview & PDR](./docs/project-overview-pdr.md)
|
|
1231
1479
|
- [Codebase Summary](./docs/codebase-summary.md)
|
|
1232
1480
|
- [System Architecture](./docs/system-architecture.md)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package com.omikitplugin
|
|
2
|
+
|
|
3
|
+
import android.graphics.SurfaceTexture
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import android.view.Surface
|
|
6
|
+
import android.view.TextureView
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.widget.LinearLayout
|
|
9
|
+
import com.facebook.react.bridge.Promise
|
|
10
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
+
import com.facebook.react.bridge.ReactMethod
|
|
12
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
13
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
14
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
15
|
+
import vn.vihat.omicall.omisdk.OmiClient
|
|
16
|
+
import vn.vihat.omicall.omisdk.videoutils.ScaleManager
|
|
17
|
+
import vn.vihat.omicall.omisdk.videoutils.Size
|
|
18
|
+
|
|
19
|
+
class OmiLocalCameraView(private val context: ReactApplicationContext) :
|
|
20
|
+
SimpleViewManager<LinearLayout>() {
|
|
21
|
+
|
|
22
|
+
val localView: LinearLayout = LinearLayout(context)
|
|
23
|
+
private val cameraView: TextureView = TextureView(context)
|
|
24
|
+
|
|
25
|
+
// Track whether the surface is ready for rendering
|
|
26
|
+
@Volatile
|
|
27
|
+
private var isSurfaceReady = false
|
|
28
|
+
|
|
29
|
+
// Queued refresh — executed when surface becomes available
|
|
30
|
+
private var pendingRefreshPromise: Promise? = null
|
|
31
|
+
|
|
32
|
+
init {
|
|
33
|
+
localView.addView(cameraView)
|
|
34
|
+
cameraView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
|
|
35
|
+
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
|
|
36
|
+
isSurfaceReady = true
|
|
37
|
+
// Execute queued refresh if any
|
|
38
|
+
pendingRefreshPromise?.let { promise ->
|
|
39
|
+
pendingRefreshPromise = null
|
|
40
|
+
doRefresh(promise)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
|
|
45
|
+
|
|
46
|
+
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
|
|
47
|
+
isSurfaceReady = false
|
|
48
|
+
pendingRefreshPromise = null
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun getName(): String {
|
|
57
|
+
return "OmiLocalCameraView"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override fun createViewInstance(p0: ThemedReactContext): LinearLayout {
|
|
61
|
+
// Detach from previous parent if remounted
|
|
62
|
+
// (avoids "The specified child already has a parent" crash)
|
|
63
|
+
(localView.parent as? ViewGroup)?.removeView(localView)
|
|
64
|
+
return localView
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fun localViewInstance(): LinearLayout {
|
|
68
|
+
return localView
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Exposed to JS via NativeModules.OmiLocalCameraView.refresh()
|
|
72
|
+
@ReactMethod
|
|
73
|
+
fun refresh(promise: Promise) {
|
|
74
|
+
UiThreadUtil.runOnUiThread {
|
|
75
|
+
if (isSurfaceReady && cameraView.surfaceTexture != null) {
|
|
76
|
+
doRefresh(promise)
|
|
77
|
+
} else {
|
|
78
|
+
// Surface not ready yet — queue and execute when available
|
|
79
|
+
pendingRefreshPromise = promise
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private fun doRefresh(promise: Promise) {
|
|
85
|
+
try {
|
|
86
|
+
val surface = Surface(cameraView.surfaceTexture)
|
|
87
|
+
val client = OmiClient.getInstance(context.applicationContext)
|
|
88
|
+
|
|
89
|
+
// Connect local camera feed — delay slightly to ensure camera subsystem ready
|
|
90
|
+
// (matches native SDK sample behavior with AppUtils.postDelay)
|
|
91
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
92
|
+
try {
|
|
93
|
+
client.setupLocalVideoFeed(surface)
|
|
94
|
+
Log.d("OmiLocalCameraView", "Connected local video feed to surface")
|
|
95
|
+
|
|
96
|
+
ScaleManager.adjustAspectRatio(
|
|
97
|
+
cameraView,
|
|
98
|
+
Size(cameraView.width, cameraView.height),
|
|
99
|
+
Size(9, 16)
|
|
100
|
+
)
|
|
101
|
+
} catch (e: Exception) {
|
|
102
|
+
Log.e("OmiLocalCameraView", "Error setting up local feed: ${e.message}")
|
|
103
|
+
}
|
|
104
|
+
}, 300)
|
|
105
|
+
|
|
106
|
+
promise.resolve(true)
|
|
107
|
+
} catch (e: Exception) {
|
|
108
|
+
Log.e("OmiLocalCameraView", "Error refreshing: ${e.message}")
|
|
109
|
+
promise.resolve(false)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
package com.omikitplugin
|
|
2
|
+
|
|
3
|
+
import android.graphics.SurfaceTexture
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import android.view.Surface
|
|
6
|
+
import android.view.TextureView
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import com.facebook.react.bridge.Promise
|
|
9
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
10
|
+
import com.facebook.react.bridge.ReactMethod
|
|
11
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
12
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
13
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
14
|
+
import vn.vihat.omicall.omisdk.OmiClient
|
|
15
|
+
import vn.vihat.omicall.omisdk.videoutils.ScaleManager
|
|
16
|
+
import vn.vihat.omicall.omisdk.videoutils.Size
|
|
17
|
+
|
|
18
|
+
class OmiRemoteCameraView(private val context: ReactApplicationContext) :
|
|
19
|
+
SimpleViewManager<TextureView>() {
|
|
20
|
+
|
|
21
|
+
val remoteView: TextureView = TextureView(context)
|
|
22
|
+
|
|
23
|
+
// Track whether the surface is ready for rendering
|
|
24
|
+
@Volatile
|
|
25
|
+
private var isSurfaceReady = false
|
|
26
|
+
|
|
27
|
+
// Queued refresh — executed when surface becomes available
|
|
28
|
+
private var pendingRefreshPromise: Promise? = null
|
|
29
|
+
|
|
30
|
+
init {
|
|
31
|
+
remoteView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
|
|
32
|
+
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
|
|
33
|
+
isSurfaceReady = true
|
|
34
|
+
// Execute queued refresh if any
|
|
35
|
+
pendingRefreshPromise?.let { promise ->
|
|
36
|
+
pendingRefreshPromise = null
|
|
37
|
+
doRefresh(promise)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
|
|
42
|
+
|
|
43
|
+
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
|
|
44
|
+
isSurfaceReady = false
|
|
45
|
+
pendingRefreshPromise = null
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun getName(): String {
|
|
54
|
+
return "OmiRemoteCameraView"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override fun createViewInstance(p0: ThemedReactContext): TextureView {
|
|
58
|
+
// Detach from previous parent if remounted
|
|
59
|
+
// (avoids "The specified child already has a parent" crash)
|
|
60
|
+
(remoteView.parent as? ViewGroup)?.removeView(remoteView)
|
|
61
|
+
return remoteView
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fun remoteViewInstance(): TextureView {
|
|
65
|
+
return remoteView
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Exposed to JS via NativeModules.OmiRemoteCameraView.refresh()
|
|
69
|
+
@ReactMethod
|
|
70
|
+
fun refresh(promise: Promise) {
|
|
71
|
+
UiThreadUtil.runOnUiThread {
|
|
72
|
+
if (isSurfaceReady && remoteView.surfaceTexture != null) {
|
|
73
|
+
doRefresh(promise)
|
|
74
|
+
} else {
|
|
75
|
+
// Surface not ready yet — queue and execute when available
|
|
76
|
+
pendingRefreshPromise = promise
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private fun doRefresh(promise: Promise) {
|
|
82
|
+
try {
|
|
83
|
+
// Connect TextureView surface to SDK incoming video feed
|
|
84
|
+
val surface = Surface(remoteView.surfaceTexture)
|
|
85
|
+
OmiClient.getInstance(context.applicationContext).setupIncomingVideoFeed(surface)
|
|
86
|
+
Log.d("OmiRemoteCameraView", "Connected remote video feed to surface")
|
|
87
|
+
|
|
88
|
+
ScaleManager.adjustAspectRatio(
|
|
89
|
+
remoteView,
|
|
90
|
+
Size(remoteView.width, remoteView.height),
|
|
91
|
+
Size(1280, 720)
|
|
92
|
+
)
|
|
93
|
+
promise.resolve(true)
|
|
94
|
+
} catch (e: Exception) {
|
|
95
|
+
Log.e("OmiRemoteCameraView", "Error refreshing: ${e.message}")
|
|
96
|
+
promise.resolve(false)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -450,7 +450,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
450
450
|
moduleInstance = this
|
|
451
451
|
reactApplicationContext!!.addActivityEventListener(this)
|
|
452
452
|
Handler(Looper.getMainLooper()).post {
|
|
453
|
-
|
|
453
|
+
// Use applicationContext — pjsip video subsystem needs it for CameraManager access
|
|
454
|
+
val ctx = reactApplicationContext?.applicationContext ?: reactApplicationContext!!
|
|
455
|
+
val client = OmiClient.getInstance(ctx)
|
|
454
456
|
client.addCallStateListener(this)
|
|
455
457
|
client.addCallStateListener(autoUnregisterListener)
|
|
456
458
|
client.setDebug(false)
|
|
@@ -463,10 +465,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
463
465
|
try {
|
|
464
466
|
// ✅ Prepare audio system trước khi start services
|
|
465
467
|
prepareAudioSystem()
|
|
466
|
-
|
|
468
|
+
|
|
467
469
|
OmiClient.getInstance(reactApplicationContext!!).addAccountListener(accountListener)
|
|
468
|
-
|
|
469
|
-
// ✅ Start services - không cần prevent auto-unregister với Silent API
|
|
470
|
+
|
|
470
471
|
OmiClient.getInstance(reactApplicationContext!!).setDebug(false)
|
|
471
472
|
promise.resolve(true)
|
|
472
473
|
} catch (e: Exception) {
|
|
@@ -8,28 +8,31 @@ import com.facebook.react.uimanager.ViewManager
|
|
|
8
8
|
|
|
9
9
|
class OmikitPluginPackage : ReactPackage {
|
|
10
10
|
|
|
11
|
-
private var localView:
|
|
12
|
-
private var remoteView:
|
|
11
|
+
private var localView: OmiLocalCameraView? = null
|
|
12
|
+
private var remoteView: OmiRemoteCameraView? = null
|
|
13
|
+
|
|
13
14
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
14
15
|
if (localView == null) {
|
|
15
|
-
localView =
|
|
16
|
+
localView = OmiLocalCameraView(reactContext)
|
|
16
17
|
}
|
|
17
18
|
if (remoteView == null) {
|
|
18
|
-
remoteView =
|
|
19
|
+
remoteView = OmiRemoteCameraView(reactContext)
|
|
19
20
|
}
|
|
21
|
+
// ViewManagers are also NativeModules — refresh() is accessible via
|
|
22
|
+
// NativeModules.OmiLocalCameraView and NativeModules.OmiRemoteCameraView
|
|
20
23
|
return listOf(
|
|
21
24
|
OmikitPluginModule(reactContext),
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
localView!!,
|
|
26
|
+
remoteView!!,
|
|
24
27
|
)
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
28
31
|
if (localView == null) {
|
|
29
|
-
localView =
|
|
32
|
+
localView = OmiLocalCameraView(reactContext)
|
|
30
33
|
}
|
|
31
34
|
if (remoteView == null) {
|
|
32
|
-
remoteView =
|
|
35
|
+
remoteView = OmiRemoteCameraView(reactContext)
|
|
33
36
|
}
|
|
34
37
|
return listOf(localView!!, remoteView!!)
|
|
35
38
|
}
|