omikit-plugin 4.0.1 → 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.
Files changed (64) hide show
  1. package/README.md +344 -45
  2. package/android/build.gradle +21 -71
  3. package/android/gradle.properties +4 -4
  4. package/android/src/main/java/com/omikitplugin/OmiLocalCameraView.kt +112 -0
  5. package/android/src/main/java/com/omikitplugin/OmiRemoteCameraView.kt +99 -0
  6. package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +57 -38
  7. package/android/src/main/java/com/omikitplugin/OmikitPluginPackage.kt +11 -8
  8. package/ios/CallProcess/CallManager.swift +99 -29
  9. package/ios/Library/OmikitPlugin.m +18 -0
  10. package/ios/Library/OmikitPlugin.swift +233 -1
  11. package/ios/OmikitPlugin-Bridging-Header.h +1 -0
  12. package/ios/OmikitPlugin.xcodeproj/project.pbxproj +4 -4
  13. package/ios/VideoCall/OmiLocalCameraViewBridge.m +14 -0
  14. package/ios/VideoCall/OmiLocalCameraViewManager.swift +41 -0
  15. package/ios/VideoCall/OmiRemoteCameraViewBridge.m +14 -0
  16. package/ios/VideoCall/OmiRemoteCameraViewManager.swift +40 -0
  17. package/lib/commonjs/NativeOmikitPlugin.js +2 -1
  18. package/lib/commonjs/NativeOmikitPlugin.js.map +1 -1
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/commonjs/omi_audio_type.js +5 -7
  21. package/lib/commonjs/omi_audio_type.js.map +1 -1
  22. package/lib/commonjs/omi_call_state.js +5 -3
  23. package/lib/commonjs/omi_call_state.js.map +1 -1
  24. package/lib/commonjs/omi_local_camera.js +20 -12
  25. package/lib/commonjs/omi_local_camera.js.map +1 -1
  26. package/lib/commonjs/omi_remote_camera.js +21 -12
  27. package/lib/commonjs/omi_remote_camera.js.map +1 -1
  28. package/lib/commonjs/omi_start_call_status.js +5 -24
  29. package/lib/commonjs/omi_start_call_status.js.map +1 -1
  30. package/lib/commonjs/omikit.js +91 -21
  31. package/lib/commonjs/omikit.js.map +1 -1
  32. package/lib/commonjs/types/index.d.js.map +1 -1
  33. package/lib/module/NativeOmikitPlugin.js.map +1 -1
  34. package/lib/module/index.js.map +1 -1
  35. package/lib/module/omi_audio_type.js +4 -7
  36. package/lib/module/omi_audio_type.js.map +1 -1
  37. package/lib/module/omi_call_state.js +4 -3
  38. package/lib/module/omi_call_state.js.map +1 -1
  39. package/lib/module/omi_local_camera.js +20 -13
  40. package/lib/module/omi_local_camera.js.map +1 -1
  41. package/lib/module/omi_remote_camera.js +21 -13
  42. package/lib/module/omi_remote_camera.js.map +1 -1
  43. package/lib/module/omi_start_call_status.js +4 -24
  44. package/lib/module/omi_start_call_status.js.map +1 -1
  45. package/lib/module/omikit.js +85 -20
  46. package/lib/module/omikit.js.map +1 -1
  47. package/lib/module/types/index.d.js.map +1 -1
  48. package/omikit-plugin.podspec +1 -1
  49. package/package.json +2 -11
  50. package/react-native.config.js +14 -0
  51. package/src/NativeOmikitPlugin.ts +1 -0
  52. package/src/omi_call_state.tsx +1 -0
  53. package/src/omi_local_camera.tsx +16 -15
  54. package/src/omi_remote_camera.tsx +17 -15
  55. package/src/omikit.tsx +104 -28
  56. package/src/types/index.d.ts +344 -62
  57. package/android/src/main/java/com/omikitplugin/FLLocalCameraModule.kt +0 -34
  58. package/android/src/main/java/com/omikitplugin/FLLocalCameraView.kt +0 -44
  59. package/android/src/main/java/com/omikitplugin/FLRemoteCameraModule.kt +0 -37
  60. package/android/src/main/java/com/omikitplugin/FLRemoteCameraView.kt +0 -23
  61. package/ios/VideoCall/FLLocalCameraView.m +0 -17
  62. package/ios/VideoCall/FLLocalCameraView.swift +0 -44
  63. package/ios/VideoCall/FLRemoteCameraView.m +0 -18
  64. 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.1
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.10.34 |
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
 
@@ -166,11 +195,11 @@ allprojects {
166
195
  Then add your credentials to `~/.gradle/gradle.properties` (or project-level `gradle.properties`):
167
196
 
168
197
  ```properties
169
- OMI_USER=your_github_username
170
- OMI_TOKEN=your_github_personal_access_token
198
+ OMI_USER=omi_github_username
199
+ OMI_TOKEN=omi_github_access_token
171
200
  ```
172
201
 
173
- > **Note:** The GitHub token needs `read:packages` scope. Generate one at [GitHub Settings > Tokens](https://github.com/settings/tokens).
202
+ > **Note:** Contact the OMICall development team to get `OMI_USER` and `OMI_TOKEN` credentials.
174
203
 
175
204
  ### 4. New Architecture (Optional)
176
205
 
@@ -264,11 +293,11 @@ Then run `cd ios && pod install`.
264
293
  │ │ │ │ │ │
265
294
  │ ▼ │ │ ▼ │
266
295
  │ OMIKIT SDK │ │ OmiKit SDK │
267
- │ (v2.6.4) │ │ (v1.10.34) │
296
+ │ (v2.6.4) │ │ (v1.10.34) │
268
297
  │ │ │ │ │ │
269
298
  │ ▼ │ │ ▼ │
270
299
  │ SIP Stack │ │ SIP Stack │
271
- │ (OMSIP) │ │ (OMSIP) │
300
+ │ (OMSIP) │ │ (OMSIP) │
272
301
  └─────────────┘ └──────────────┘
273
302
  ```
274
303
 
@@ -288,7 +317,9 @@ import {
288
317
  OmiCallState,
289
318
  } from 'omikit-plugin';
290
319
 
291
- // Step 1: Start SDK services (call once on app launch)
320
+ // Step 1: Start SDK services
321
+ // ⚠️ Call ONCE on app launch (e.g., in App.tsx / index.js / useEffect in root component)
322
+ // Do NOT call this multiple times — it initializes native audio and event listeners.
292
323
  await startServices();
293
324
 
294
325
  // Step 2: Login with SIP credentials
@@ -299,7 +330,7 @@ const loginResult = await initCallWithUserPassword({
299
330
  host: '', // SIP proxy, defaults to vh.omicrm.com
300
331
  isVideo: false,
301
332
  fcmToken: 'your_fcm_token',
302
- projectId: 'your_project_id', // optional
333
+ projectId: 'your_project_id', // firebase project id
303
334
  });
304
335
 
305
336
  // Step 3: Listen to call events
@@ -365,7 +396,7 @@ await initCallWithUserPassword({
365
396
  host?: string, // SIP proxy server (optional)
366
397
  isVideo: boolean, // Enable video capability
367
398
  fcmToken: string, // Firebase token for push notifications
368
- projectId?: string, // OMICALL project ID (optional)
399
+ projectId: string, // Firebase project ID
369
400
  isSkipDevices?: boolean, // true = Customer mode, false = Agent mode (default)
370
401
  });
371
402
  ```
@@ -416,6 +447,51 @@ await initCallWithApiKey({
416
447
  });
417
448
  ```
418
449
 
450
+ ### Option 3: App-to-App API (v4.0+)
451
+
452
+ Starting from **v4.0**, customers using the **App-to-App** service must call the OMICALL API to provision SIP extensions before initializing the SDK. The API returns SIP credentials that you pass to `initCallWithUserPassword()` with `isSkipDevices: true`.
453
+
454
+ > For full API documentation (endpoints, request/response formats), see the [API Integration Guide](./docs/api-integration-guide.md).
455
+
456
+ **Quick flow:**
457
+
458
+ ```
459
+ Your Backend OMICALL API Mobile App (SDK)
460
+ │ │ │
461
+ │ 1. POST .../init │ │
462
+ ├─────────────────────────────►│ │
463
+ │ {domain, extension, │ │
464
+ │ password, proxy} │ │
465
+ │◄─────────────────────────────┤ │
466
+ │ │ │
467
+ │ 2. Return credentials │ │
468
+ ├──────────────────────────────────────────────────────────►│
469
+ │ │ 3. startServices() │
470
+ │ │ 4. initCallWithUserPassword
471
+ │ │ (isSkipDevices: true) │
472
+ │ │ │
473
+ ```
474
+
475
+ ```typescript
476
+ // After getting credentials from your backend:
477
+ await startServices();
478
+
479
+ await initCallWithUserPassword({
480
+ userName: credentials.extension, // from API response
481
+ password: credentials.password, // from API response
482
+ realm: credentials.domain, // from API response
483
+ host: credentials.outboundProxy, // from API response
484
+ isVideo: false,
485
+ fcmToken: 'your-fcm-token',
486
+ isSkipDevices: true, // Required for App-to-App
487
+ });
488
+ ```
489
+
490
+ > **Important:**
491
+ > - Call the OMICALL API from your **backend server** only — never expose the Bearer token in client-side code.
492
+ > - You **must** call the [Logout API](./docs/api-integration-guide.md#5-logout) before switching users. Otherwise, both devices using the same SIP extension will receive incoming calls simultaneously.
493
+ > - Use getter functions (`getProjectId()`, `getAppId()`, `getDeviceId()`, `getFcmToken()`, `getVoipToken()`) to retrieve device params for the [Add Device](./docs/api-integration-guide.md#4-add-device) and Logout APIs.
494
+
419
495
  ---
420
496
 
421
497
  ## Call Flows
@@ -470,8 +546,8 @@ await initCallWithApiKey({
470
546
  │◄────────────────────┤ (event emitted) │
471
547
  │ │ │
472
548
  │ ┌──────────────┐ │ │
473
- │ │ Show Call UI │ │ │
474
- │ │ [Accept][Deny]│ │ │
549
+ │ │ Show Call UI │ │ │
550
+ │ │[Accept][Deny]│ │ │
475
551
  │ └──────────────┘ │ │
476
552
  │ │ │
477
553
  │ joinCall() │ │
@@ -495,7 +571,7 @@ await initCallWithApiKey({
495
571
  │ │ │◄──────────────┤
496
572
  │ │ │ │
497
573
 
498
- ┌───────────────── iOS (VoIP Push) ──────────────────┐
574
+ ┌───────────────── iOS (VoIP Push) ───────────────────┐
499
575
  │ │ │ │ │ │
500
576
  │ │ │ PushKit VoIP │ │ │
501
577
  │ │ │◄──────────────┤ │ │
@@ -511,9 +587,9 @@ await initCallWithApiKey({
511
587
  │ │◄──────────────┤ │ │ │
512
588
  │ │ incoming (2) │ │ │ │
513
589
  │ │◄──────────────┤ │ │ │
514
- └───────────────────────────────────────────────────────┘
590
+ └─────────────────────────────────────────────────────┘
515
591
 
516
- ┌───────────────── Android (FCM) ────────────────────┐
592
+ ┌───────────────── Android (FCM) ─────────────────────┐
517
593
  │ │ │ │ │ │
518
594
  │ │ │ FCM Message │ │ │
519
595
  │ │ │◄──────────────┤ │ │
@@ -528,7 +604,7 @@ await initCallWithApiKey({
528
604
  │ │◄──────────────┤ │ │ │
529
605
  │ │ incoming (2) │ │ │ │
530
606
  │ │◄──────────────┤ │ │ │
531
- └───────────────────────────────────────────────────────┘
607
+ └─────────────────────────────────────────────────────┘
532
608
 
533
609
  │ │ │ │
534
610
  │ joinCall() │ │ │
@@ -558,8 +634,8 @@ await initCallWithApiKey({
558
634
  │ disconnected (6) │ 200 OK │
559
635
  │◄────────────────────┤────────────────────►│
560
636
  │ │ │
561
- │ │ Show Missed Call
562
- │ │ Notification
637
+ │ │ Show Missed Call
638
+ │ │ Notification
563
639
  │ │ │
564
640
  │ (user taps notif) │ │
565
641
  │ │ │
@@ -601,14 +677,14 @@ await initCallWithApiKey({
601
677
  │ incoming (2) │ SIP INVITE │
602
678
  │◄────────────────────┤◄────────────────────┤
603
679
  │ │ │
604
- ┌── rejectCall() ──┐
680
+ ┌── rejectCall() ───┐
605
681
  │ Decline this │ 486 Busy Here │
606
- │ device only ├─────────────────────────►│
682
+ │ device only ├─────────────────────────► │
607
683
  └───────────────────┘ (other devices ring) │
608
684
  │ │ │
609
- ┌── dropCall() ────┐
685
+ ┌── dropCall() ─────┐
610
686
  │ Decline + stop │ 603 Decline │
611
- │ ALL devices ├─────────────────────────►│
687
+ │ ALL devices ├─────────────────────────► │
612
688
  └───────────────────┘ (PBX stops all ringing) │
613
689
  │ │ │
614
690
  ```
@@ -621,7 +697,7 @@ await initCallWithApiKey({
621
697
 
622
698
  | Function | Returns | Description |
623
699
  |----------|---------|-------------|
624
- | `startServices()` | `Promise<boolean>` | Initialize SDK. Call once on app launch |
700
+ | `startServices()` | `Promise<boolean>` | Initialize SDK. **Call once** on app launch (e.g., `App.tsx` or `index.js`). Do not call multiple times |
625
701
  | `initCallWithUserPassword(data)` | `Promise<boolean>` | Login with SIP username/password |
626
702
  | `initCallWithApiKey(data)` | `Promise<boolean>` | Login with API key |
627
703
  | `logout()` | `Promise<boolean>` | Logout and unregister SIP |
@@ -898,48 +974,265 @@ omiEmitter.addListener(OmiCallEvent.onCallStateChanged, (data) => {
898
974
 
899
975
  ## Video Calls
900
976
 
901
- ### Setup
977
+ > **Important:** To use video calls, you must login with `isVideo: true` in `initCallWithUserPassword()`. This enables camera support during SIP registration.
978
+
979
+ ### Prerequisites
902
980
 
903
981
  ```typescript
904
- import { OmiLocalCamera, OmiRemoteCamera } from 'omikit-plugin';
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
+ });
905
990
  ```
906
991
 
907
992
  ### Video Components
908
993
 
909
994
  ```tsx
910
- // Local camera preview (your camera)
911
- <OmiLocalCamera style={{ width: 120, height: 160 }} />
912
-
913
- // Remote camera view (other party's video)
914
- <OmiRemoteCamera style={{ width: '100%', height: '100%' }} />
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';
915
1010
  ```
916
1011
 
917
- ### Video Call Flow
1012
+ ### Platform Differences
918
1013
 
919
- ```typescript
920
- // 1. Register for video events BEFORE starting call
921
- await registerVideoEvent();
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) |
922
1020
 
923
- // 2. Start video call
924
- await startCall({ phoneNumber: '0901234567', isVideo: true });
1021
+ ### Cross-Platform Video Call Example
925
1022
 
926
- // 3. Toggle video during call
927
- await toggleOmiVideo(); // on/off video stream
928
- await switchOmiCamera(); // front/back camera
1023
+ Complete example supporting both Android and iOS (Old + New Architecture):
929
1024
 
930
- // 4. Listen for remote video ready
931
- omiEmitter.addListener(OmiCallEvent.onRemoteVideoReady, () => {
932
- // Remote video is now available - show OmiRemoteCamera
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
+ },
933
1177
  });
1178
+ ```
1179
+
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.
934
1186
 
935
- // 5. Cleanup when call ends
936
- await removeVideoEvent();
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
+ });
937
1206
  ```
938
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
+
939
1230
  ---
940
1231
 
941
1232
  ## Push Notifications
942
1233
 
1234
+ > **Setup Guide:** To configure VoIP (iOS) and FCM (Android) for receiving incoming calls, follow the detailed guide at [OMICall Mobile SDK Setup](https://omicrm.io/post/detail/mobile-sdk-post89?lng=vi&p=BrwVVWCLGM).
1235
+
943
1236
  ### Configuration
944
1237
 
945
1238
  ```typescript
@@ -1165,10 +1458,14 @@ if (initialCall) {
1165
1458
  | Incoming call on Android — no UI | Missing overlay permission | Call `requestSystemAlertWindowPermission()` |
1166
1459
  | `startCall` returns 450/451/452 | Missing runtime permissions | Call `requestPermissionsByCodes([code])` |
1167
1460
  | No audio during call | Audio route issue | Check `getAudio()` and `setAudio()` |
1168
- | 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) |
1169
1465
  | NativeEventEmitter warning (iOS) | Old RN bridge issue | Upgrade to v4.0.x with New Architecture |
1170
1466
  | `Invalid local URI` in logs | Empty proxy/host in login | Pass `host` parameter in `initCallWithUserPassword` |
1171
1467
  | Build error with New Arch | Codegen not configured | Ensure `codegenConfig` exists in `package.json` |
1468
+ | iOS Simulator build fails (arm64) | OmiKit binary does not include simulator slice | **iOS Simulator is not supported.** OmiKit SDK is device-only (`arm64` real device). Always build and test on a physical iOS device |
1172
1469
 
1173
1470
  ---
1174
1471
 
@@ -1176,6 +1473,8 @@ if (initialCall) {
1176
1473
 
1177
1474
  Full documentation in [`./docs/`](./docs/):
1178
1475
 
1476
+ - [API Integration Guide (App-to-App)](./docs/api-integration-guide.md)
1477
+ - [Known Issues & Limitations](./docs/known-issues.md)
1179
1478
  - [Project Overview & PDR](./docs/project-overview-pdr.md)
1180
1479
  - [Codebase Summary](./docs/codebase-summary.md)
1181
1480
  - [System Architecture](./docs/system-architecture.md)