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.
Files changed (62) hide show
  1. package/README.md +272 -24
  2. package/android/src/main/java/com/omikitplugin/OmiLocalCameraView.kt +112 -0
  3. package/android/src/main/java/com/omikitplugin/OmiRemoteCameraView.kt +99 -0
  4. package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +5 -4
  5. package/android/src/main/java/com/omikitplugin/OmikitPluginPackage.kt +11 -8
  6. package/ios/CallProcess/CallManager.swift +99 -29
  7. package/ios/Library/OmikitPlugin.m +18 -0
  8. package/ios/Library/OmikitPlugin.swift +233 -1
  9. package/ios/OmikitPlugin-Bridging-Header.h +1 -0
  10. package/ios/OmikitPlugin.xcodeproj/project.pbxproj +4 -4
  11. package/ios/VideoCall/OmiLocalCameraViewBridge.m +14 -0
  12. package/ios/VideoCall/OmiLocalCameraViewManager.swift +41 -0
  13. package/ios/VideoCall/OmiRemoteCameraViewBridge.m +14 -0
  14. package/ios/VideoCall/OmiRemoteCameraViewManager.swift +40 -0
  15. package/lib/commonjs/NativeOmikitPlugin.js +2 -1
  16. package/lib/commonjs/NativeOmikitPlugin.js.map +1 -1
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/omi_audio_type.js +5 -7
  19. package/lib/commonjs/omi_audio_type.js.map +1 -1
  20. package/lib/commonjs/omi_call_state.js +5 -3
  21. package/lib/commonjs/omi_call_state.js.map +1 -1
  22. package/lib/commonjs/omi_local_camera.js +19 -17
  23. package/lib/commonjs/omi_local_camera.js.map +1 -1
  24. package/lib/commonjs/omi_remote_camera.js +20 -17
  25. package/lib/commonjs/omi_remote_camera.js.map +1 -1
  26. package/lib/commonjs/omi_start_call_status.js +5 -24
  27. package/lib/commonjs/omi_start_call_status.js.map +1 -1
  28. package/lib/commonjs/omikit.js +56 -3
  29. package/lib/commonjs/omikit.js.map +1 -1
  30. package/lib/commonjs/types/index.d.js.map +1 -1
  31. package/lib/module/NativeOmikitPlugin.js.map +1 -1
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/omi_audio_type.js +4 -7
  34. package/lib/module/omi_audio_type.js.map +1 -1
  35. package/lib/module/omi_call_state.js +4 -3
  36. package/lib/module/omi_call_state.js.map +1 -1
  37. package/lib/module/omi_local_camera.js +19 -18
  38. package/lib/module/omi_local_camera.js.map +1 -1
  39. package/lib/module/omi_remote_camera.js +20 -18
  40. package/lib/module/omi_remote_camera.js.map +1 -1
  41. package/lib/module/omi_start_call_status.js +4 -24
  42. package/lib/module/omi_start_call_status.js.map +1 -1
  43. package/lib/module/omikit.js +49 -1
  44. package/lib/module/omikit.js.map +1 -1
  45. package/lib/module/types/index.d.js.map +1 -1
  46. package/omikit-plugin.podspec +1 -1
  47. package/package.json +2 -11
  48. package/react-native.config.js +14 -0
  49. package/src/NativeOmikitPlugin.ts +1 -0
  50. package/src/omi_call_state.tsx +1 -0
  51. package/src/omi_local_camera.tsx +15 -19
  52. package/src/omi_remote_camera.tsx +16 -19
  53. package/src/omikit.tsx +63 -0
  54. package/src/types/index.d.ts +344 -62
  55. package/android/src/main/java/com/omikitplugin/FLLocalCameraModule.kt +0 -34
  56. package/android/src/main/java/com/omikitplugin/FLLocalCameraView.kt +0 -44
  57. package/android/src/main/java/com/omikitplugin/FLRemoteCameraModule.kt +0 -37
  58. package/android/src/main/java/com/omikitplugin/FLRemoteCameraView.kt +0 -23
  59. package/ios/VideoCall/FLLocalCameraView.m +0 -17
  60. package/ios/VideoCall/FLLocalCameraView.swift +0 -44
  61. package/ios/VideoCall/FLRemoteCameraView.m +0 -18
  62. package/ios/VideoCall/FLRemoteCameraView.swift +0 -124
@@ -12,6 +12,14 @@ import SwiftUI
12
12
  import OmiKit
13
13
  import AVFoundation
14
14
 
15
+ // UIWindow that passes all touches through to the window underneath
16
+ class PassthroughWindow: UIWindow {
17
+ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
18
+ // Always return nil — all touches pass through to React window below
19
+ return nil
20
+ }
21
+ }
22
+
15
23
  class CallManager {
16
24
 
17
25
  static private var instance: CallManager? = nil // Instance
@@ -19,12 +27,39 @@ class CallManager {
19
27
  private lazy var omiLib: OMISIPLib = {
20
28
  return OMISIPLib.sharedInstance()
21
29
  }()
22
- var videoManager: OMIVideoViewManager?
23
30
  var isSpeaker = false
31
+ // Container views for video — created lazily, strong reference to keep alive
32
+ var remoteContainerView: UIView?
33
+ var localContainerView: UIView?
34
+ // Separate UIWindow for video (fallback only)
35
+ var videoWindow: UIWindow?
36
+ private var isVideoSetup = false
37
+ private var setupVideoRetryCount = 0
24
38
  private var guestPhone : String = ""
25
39
  private var lastStatusCall : String?
26
40
  private var tempCallInfo : [String: Any]?
27
41
  private var lastTimeCall : Date = Date()
42
+ // Store original backgrounds to restore after video cleanup
43
+ private var savedBackgrounds: [(UIView, UIColor?)] = []
44
+
45
+ // Recursively make all views transparent, saving original colors
46
+ static func makeViewHierarchyTransparent(_ view: UIView) {
47
+ let manager = CallManager.shareInstance()
48
+ manager.savedBackgrounds.append((view, view.backgroundColor))
49
+ view.backgroundColor = .clear
50
+ for child in view.subviews {
51
+ makeViewHierarchyTransparent(child)
52
+ }
53
+ }
54
+
55
+ // Restore saved backgrounds
56
+ func restoreSavedBackgrounds() {
57
+ for (view, color) in savedBackgrounds {
58
+ view.backgroundColor = color
59
+ }
60
+ savedBackgrounds.removeAll()
61
+ }
62
+
28
63
  /// Get instance
29
64
  static func shareInstance() -> CallManager {
30
65
  if (instance == nil) {
@@ -281,13 +316,29 @@ class CallManager {
281
316
  name: NSNotification.Name.OMICallVideoInfo,
282
317
  object: nil
283
318
  )
319
+ // Observe app foreground for video recovery (BG→FG)
320
+ NotificationCenter.default.addObserver(instance,
321
+ selector: #selector(self.appDidBecomeActive),
322
+ name: UIApplication.didBecomeActiveNotification,
323
+ object: nil
324
+ )
284
325
  }
285
326
  }
286
-
327
+
287
328
  func removeVideoEvent() {
288
- DispatchQueue.main.async {
329
+ DispatchQueue.main.async { [weak self] in
289
330
  guard let instance = CallManager.instance else { return }
290
331
  NotificationCenter.default.removeObserver(instance, name: NSNotification.Name.OMICallVideoInfo, object: nil)
332
+ NotificationCenter.default.removeObserver(instance, name: UIApplication.didBecomeActiveNotification, object: nil)
333
+ // Cleanup video when events are removed (screen dismissed)
334
+ self?.cleanupVideo()
335
+ }
336
+ }
337
+
338
+ @objc func appDidBecomeActive() {
339
+ // Recover video after background → foreground transition
340
+ if isVideoSetup {
341
+ OMIVideoCallManager.shared().prepareForVideoDisplay()
291
342
  }
292
343
  }
293
344
 
@@ -385,8 +436,8 @@ class CallManager {
385
436
 
386
437
  switch (callState) {
387
438
  case OMICallState.confirmed.rawValue:
388
- if (videoManager == nil && call.isVideo) {
389
- videoManager = OMIVideoViewManager.init()
439
+ if call.isVideo {
440
+ setupVideo()
390
441
  }
391
442
  isSpeaker = call.speaker
392
443
  lastStatusCall = "answered"
@@ -397,9 +448,7 @@ class CallManager {
397
448
  break
398
449
  case OMICallState.disconnected.rawValue:
399
450
  tempCallInfo = getCallInfo(call: call)
400
- if (videoManager != nil) {
401
- videoManager = nil
402
- }
451
+ cleanupVideo()
403
452
  lastStatusCall = nil
404
453
  guestPhone = ""
405
454
  var combinedDictionary: [String: Any] = dataToSend
@@ -596,32 +645,53 @@ func startCall(_ phoneNumber: String, isVideo: Bool, completion: @escaping (_: S
596
645
  return OmiClient.getCurrentAudio()
597
646
  }
598
647
 
599
- //video call
600
- func toggleCamera() {
601
- if let videoManager = videoManager {
602
- videoManager.toggleCamera()
648
+ // MARK: - Video Call (OMIVideoCallManager API)
649
+
650
+ /// Setup video — only succeeds if containers are already set and in window.
651
+ /// Does NOT defer or retry. Call setupVideoWithContainers() to create + setup in one step.
652
+ @objc func setupVideo() {
653
+ guard !isVideoSetup else {
654
+ NSLog("📹 [RN-CallManager] setupVideo: already setup, skipping")
655
+ return
603
656
  }
657
+ guard let remote = self.remoteContainerView,
658
+ let local = self.localContainerView,
659
+ remote.window != nil else {
660
+ NSLog("📹 [RN-CallManager] setupVideo: containers not ready or not in window")
661
+ return
662
+ }
663
+
664
+ NSLog("📹 [RN-CallManager] setupVideo: calling OMIVideoCallManager.setupWithRemoteView")
665
+ OMIVideoCallManager.shared().setup(withRemoteView: remote, localView: local)
666
+ self.isVideoSetup = true
604
667
  }
605
-
606
- func getCameraStatus() -> Bool {
607
- guard let videoManager = videoManager else { return false }
608
- return videoManager.isCameraOn
609
- }
610
-
611
- func switchCamera() {
612
- if let videoManager = videoManager {
613
- videoManager.switchCamera()
668
+
669
+ /// Cleanup video resources
670
+ func cleanupVideo() {
671
+ if isVideoSetup {
672
+ OMIVideoCallManager.shared().cleanup()
673
+ isVideoSetup = false
674
+ }
675
+ // Remove containers from window and clear references
676
+ DispatchQueue.main.async { [weak self] in
677
+ self?.remoteContainerView?.removeFromSuperview()
678
+ self?.localContainerView?.removeFromSuperview()
679
+ self?.remoteContainerView = nil
680
+ self?.localContainerView = nil
681
+ NSLog("📹 [RN-CallManager] cleanupVideo: removed video views from window")
614
682
  }
615
683
  }
616
-
617
- func getLocalPreviewView(frame: CGRect) -> UIView? {
618
- guard let videoManager = videoManager else { return nil}
619
- return videoManager.createView(forVideoLocal: frame)
684
+
685
+ func toggleCamera() {
686
+ OMIVideoCallManager.shared().toggleCamera()
620
687
  }
621
-
622
- func getRemotePreviewView(frame: CGRect) -> UIView? {
623
- guard let videoManager = videoManager else { return nil }
624
- return videoManager.createView(forVideoRemote: frame)
688
+
689
+ func getCameraStatus() -> Bool {
690
+ return OMIVideoCallManager.shared().isCameraOn
691
+ }
692
+
693
+ func switchCamera() {
694
+ OMIVideoCallManager.shared().switchCamera()
625
695
  }
626
696
 
627
697
  func logout() {
@@ -65,6 +65,15 @@ RCT_EXTERN_METHOD(sendDTMF:(id)data
65
65
  resolver:(RCTPromiseResolveBlock)resolve
66
66
  rejecter:(RCTPromiseRejectBlock)reject)
67
67
 
68
+ // Configure camera view style (iOS Fabric — native window rendering)
69
+ RCT_EXTERN_METHOD(setCameraConfig:(NSDictionary *)data
70
+ resolver:(RCTPromiseResolveBlock)resolve
71
+ rejecter:(RCTPromiseRejectBlock)reject)
72
+
73
+ // Setup video containers (iOS Fabric — creates and adds to window)
74
+ RCT_EXTERN_METHOD(setupVideoContainers:(RCTPromiseResolveBlock)resolve
75
+ rejecter:(RCTPromiseRejectBlock)reject)
76
+
68
77
  // Switch camera
69
78
  RCT_EXTERN_METHOD(switchOmiCamera:(RCTPromiseResolveBlock)resolve
70
79
  rejecter:(RCTPromiseRejectBlock)reject)
@@ -81,6 +90,15 @@ RCT_EXTERN_METHOD(logout:(RCTPromiseResolveBlock)resolve
81
90
  RCT_EXTERN_METHOD(registerVideoEvent:(RCTPromiseResolveBlock)resolve
82
91
  rejecter:(RCTPromiseRejectBlock)reject)
83
92
 
93
+ // Attach video containers to React views by nativeID (Fabric interop)
94
+ RCT_EXTERN_METHOD(attachRemoteView:(NSString *)nativeID
95
+ resolver:(RCTPromiseResolveBlock)resolve
96
+ rejecter:(RCTPromiseRejectBlock)reject)
97
+
98
+ RCT_EXTERN_METHOD(attachLocalView:(NSString *)nativeID
99
+ resolver:(RCTPromiseResolveBlock)resolve
100
+ rejecter:(RCTPromiseRejectBlock)reject)
101
+
84
102
  // Remove video event
85
103
  RCT_EXTERN_METHOD(removeVideoEvent:(RCTPromiseResolveBlock)resolve
86
104
  rejecter:(RCTPromiseRejectBlock)reject)
@@ -92,7 +92,6 @@ public class OmikitPlugin: RCTEventEmitter {
92
92
  reject("INVALID_DATA", "Expected a dictionary with user credentials.", nil)
93
93
  return
94
94
  }
95
- // ✅ Bước 2: Gọi initWithUserPasswordEndpoint() và kiểm tra kết quả
96
95
  let result = CallManager.shareInstance().initWithUserPasswordEndpoint(params: dataOmi)
97
96
  if result {
98
97
  resolve(true)
@@ -203,6 +202,170 @@ public class OmikitPlugin: RCTEventEmitter {
203
202
  }
204
203
 
205
204
 
205
+ // Configure camera view style (iOS Fabric mode — native window rendering)
206
+ // target: "local" or "remote"
207
+ @objc(setCameraConfig:resolver:rejecter:)
208
+ func setCameraConfig(_ data: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
209
+ let target = data["target"] as? String ?? "local"
210
+
211
+ DispatchQueue.main.async {
212
+ let callManager = CallManager.shareInstance()
213
+ let view: UIView?
214
+ if target == "remote" {
215
+ view = callManager.remoteContainerView
216
+ } else {
217
+ view = callManager.localContainerView
218
+ }
219
+
220
+ guard let targetView = view else {
221
+ resolve(false)
222
+ return
223
+ }
224
+
225
+ // Frame: x, y, width, height
226
+ if let x = data["x"] as? CGFloat,
227
+ let y = data["y"] as? CGFloat,
228
+ let width = data["width"] as? CGFloat,
229
+ let height = data["height"] as? CGFloat {
230
+ targetView.frame = CGRect(x: x, y: y, width: width, height: height)
231
+ }
232
+
233
+ // Border radius
234
+ if let borderRadius = data["borderRadius"] as? CGFloat {
235
+ targetView.layer.cornerRadius = borderRadius
236
+ targetView.clipsToBounds = true
237
+ }
238
+
239
+ // Border width + color
240
+ if let borderWidth = data["borderWidth"] as? CGFloat {
241
+ targetView.layer.borderWidth = borderWidth
242
+ }
243
+ if let borderColor = data["borderColor"] as? String {
244
+ targetView.layer.borderColor = Self.parseColor(borderColor).cgColor
245
+ }
246
+
247
+ // Background color
248
+ if let bgColor = data["backgroundColor"] as? String {
249
+ targetView.backgroundColor = Self.parseColor(bgColor)
250
+ }
251
+
252
+ // Opacity
253
+ if let opacity = data["opacity"] as? CGFloat {
254
+ targetView.alpha = opacity
255
+ }
256
+
257
+ // Hidden
258
+ if let hidden = data["hidden"] as? Bool {
259
+ targetView.isHidden = hidden
260
+ }
261
+
262
+ // Scale mode (contentMode for video sublayers)
263
+ // "fill" = aspect fill, "fit" = aspect fit, "stretch" = scale to fill
264
+ if let scaleMode = data["scaleMode"] as? String {
265
+ let mode: UIView.ContentMode
266
+ switch scaleMode {
267
+ case "fit":
268
+ mode = .scaleAspectFit
269
+ case "stretch":
270
+ mode = .scaleToFill
271
+ default: // "fill"
272
+ mode = .scaleAspectFill
273
+ }
274
+ targetView.contentMode = mode
275
+ // Apply to all sublayers/subviews (Metal/GL rendering layers)
276
+ for subview in targetView.subviews {
277
+ subview.contentMode = mode
278
+ }
279
+ }
280
+
281
+ resolve(true)
282
+ }
283
+ }
284
+
285
+ // Parse hex color string (#RRGGBB or #RRGGBBAA) to UIColor
286
+ private static func parseColor(_ hex: String) -> UIColor {
287
+ var hexStr = hex.trimmingCharacters(in: .whitespacesAndNewlines)
288
+ if hexStr.hasPrefix("#") { hexStr.removeFirst() }
289
+ var rgbValue: UInt64 = 0
290
+ Scanner(string: hexStr).scanHexInt64(&rgbValue)
291
+ if hexStr.count == 8 {
292
+ return UIColor(
293
+ red: CGFloat((rgbValue >> 24) & 0xFF) / 255.0,
294
+ green: CGFloat((rgbValue >> 16) & 0xFF) / 255.0,
295
+ blue: CGFloat((rgbValue >> 8) & 0xFF) / 255.0,
296
+ alpha: CGFloat(rgbValue & 0xFF) / 255.0
297
+ )
298
+ }
299
+ return UIColor(
300
+ red: CGFloat((rgbValue >> 16) & 0xFF) / 255.0,
301
+ green: CGFloat((rgbValue >> 8) & 0xFF) / 255.0,
302
+ blue: CGFloat(rgbValue & 0xFF) / 255.0,
303
+ alpha: 1.0
304
+ )
305
+ }
306
+
307
+ // Create video containers and add to window — called from JS when video call screen mounts.
308
+ // On Fabric, RCTViewManager.view() is NOT called, so we create containers here.
309
+ // Remote covers top portion of screen, local is PiP. User can adjust via setCameraConfig().
310
+ @objc(setupVideoContainers:rejecter:)
311
+ func setupVideoContainers(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
312
+ NSLog("📹 [OmikitPlugin] setupVideoContainers: CALLED from JS")
313
+ DispatchQueue.main.async {
314
+ NSLog("📹 [OmikitPlugin] setupVideoContainers: on main thread")
315
+ let manager = CallManager.shareInstance()
316
+
317
+ guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
318
+ resolve(false)
319
+ return
320
+ }
321
+
322
+ // Create remote container
323
+ if manager.remoteContainerView == nil {
324
+ let remote = UIView()
325
+ remote.backgroundColor = .black
326
+ remote.clipsToBounds = true
327
+ manager.remoteContainerView = remote
328
+ }
329
+ // Create local container
330
+ if manager.localContainerView == nil {
331
+ let local = UIView()
332
+ local.backgroundColor = UIColor(red: 0.118, green: 0.192, blue: 0.314, alpha: 1.0)
333
+ local.clipsToBounds = true
334
+ local.layer.cornerRadius = 12
335
+ manager.localContainerView = local
336
+ }
337
+
338
+ guard let remote = manager.remoteContainerView,
339
+ let local = manager.localContainerView else {
340
+ resolve(false)
341
+ return
342
+ }
343
+
344
+ // Add to window if not already
345
+ if remote.superview == nil {
346
+ let controlsHeight: CGFloat = 200
347
+ remote.frame = CGRect(x: 0, y: 0, width: window.bounds.width, height: window.bounds.height - controlsHeight)
348
+ remote.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
349
+ remote.isUserInteractionEnabled = false
350
+ window.addSubview(remote)
351
+ NSLog("📹 [OmikitPlugin] setupVideoContainers: added remote to window")
352
+ }
353
+ if local.superview == nil {
354
+ let pipW: CGFloat = 120
355
+ let pipH: CGFloat = 160
356
+ local.frame = CGRect(x: window.bounds.width - pipW - 16, y: 56, width: pipW, height: pipH)
357
+ local.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin]
358
+ local.isUserInteractionEnabled = false
359
+ window.addSubview(local)
360
+ NSLog("📹 [OmikitPlugin] setupVideoContainers: added local to window")
361
+ }
362
+
363
+ // Trigger SDK video setup
364
+ manager.setupVideo()
365
+ resolve(true)
366
+ }
367
+ }
368
+
206
369
  @objc(switchOmiCamera:rejecter:)
207
370
  func switchOmiCamera(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
208
371
  CallManager.shareInstance().switchCamera()
@@ -228,6 +391,75 @@ public class OmikitPlugin: RCTEventEmitter {
228
391
  CallManager.shareInstance().registerVideoEvent()
229
392
  resolve(true)
230
393
  }
394
+
395
+ /// Find a UIView by nativeID in the view hierarchy
396
+ private func findViewByNativeID(_ nativeID: String, in root: UIView?) -> UIView? {
397
+ guard let root = root else { return nil }
398
+ if root.accessibilityIdentifier == nativeID {
399
+ return root
400
+ }
401
+ for subview in root.subviews {
402
+ if let found = findViewByNativeID(nativeID, in: subview) {
403
+ return found
404
+ }
405
+ }
406
+ return nil
407
+ }
408
+
409
+ @objc(attachRemoteView:resolver:rejecter:)
410
+ func attachRemoteView(_ nativeID: String, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
411
+ DispatchQueue.main.async {
412
+ let manager = CallManager.shareInstance()
413
+ guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }),
414
+ let targetView = self.findViewByNativeID(nativeID, in: window) else {
415
+ NSLog("📹 [OmikitPlugin] attachRemoteView: view with nativeID=\(nativeID) not found")
416
+ resolve(false)
417
+ return
418
+ }
419
+ let container = manager.remoteContainerView ?? {
420
+ let v = UIView()
421
+ v.backgroundColor = .black
422
+ v.clipsToBounds = true
423
+ manager.remoteContainerView = v
424
+ return v
425
+ }()
426
+ container.removeFromSuperview()
427
+ container.frame = targetView.bounds
428
+ container.autoresizingMask = [.flexibleWidth, .flexibleHeight]
429
+ targetView.addSubview(container)
430
+ NSLog("📹 [OmikitPlugin] Attached remote container to view nativeID=\(nativeID)")
431
+ manager.setupVideo()
432
+ resolve(true)
433
+ }
434
+ }
435
+
436
+ @objc(attachLocalView:resolver:rejecter:)
437
+ func attachLocalView(_ nativeID: String, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
438
+ DispatchQueue.main.async {
439
+ let manager = CallManager.shareInstance()
440
+ guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }),
441
+ let targetView = self.findViewByNativeID(nativeID, in: window) else {
442
+ NSLog("📹 [OmikitPlugin] attachLocalView: view with nativeID=\(nativeID) not found")
443
+ resolve(false)
444
+ return
445
+ }
446
+ let container = manager.localContainerView ?? {
447
+ let v = UIView()
448
+ v.backgroundColor = UIColor(red: 0.118, green: 0.192, blue: 0.314, alpha: 1.0)
449
+ v.clipsToBounds = true
450
+ v.layer.cornerRadius = 12
451
+ manager.localContainerView = v
452
+ return v
453
+ }()
454
+ container.removeFromSuperview()
455
+ container.frame = targetView.bounds
456
+ container.autoresizingMask = [.flexibleWidth, .flexibleHeight]
457
+ targetView.addSubview(container)
458
+ NSLog("📹 [OmikitPlugin] Attached local container to view nativeID=\(nativeID)")
459
+ manager.setupVideo()
460
+ resolve(true)
461
+ }
462
+ }
231
463
 
232
464
  @objc(removeVideoEvent:rejecter:)
233
465
  func removeVideoEvent(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
@@ -11,6 +11,7 @@
11
11
  // ✅ Import React Native headers
12
12
  #import <React/RCTBridgeModule.h>
13
13
  #import <React/RCTEventEmitter.h>
14
+ #import <React/RCTViewManager.h>
14
15
 
15
16
  // ✅ Import OmiKit framework (nếu cần thiết cho Objective-C code)
16
17
  // #import <OmiKit/OmiKit.h>
@@ -30,8 +30,8 @@
30
30
  C8FBCFCE29A5F49600AA4A22 /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
31
31
  C8FBCFCF29A5F49600AA4A22 /* StringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtils.swift; sourceTree = "<group>"; };
32
32
  C8FBCFD029A5F49600AA4A22 /* NSUserActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserActivity.swift; sourceTree = "<group>"; };
33
- C8FBCFD229A5F49600AA4A22 /* FLLocalCameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLLocalCameraView.swift; sourceTree = "<group>"; };
34
- C8FBCFD329A5F49600AA4A22 /* FLRemoteCameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLRemoteCameraView.swift; sourceTree = "<group>"; };
33
+ C8FBCFD229A5F49600AA4A22 /* OmiLocalCameraViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmiLocalCameraViewManager.swift; sourceTree = "<group>"; };
34
+ C8FBCFD329A5F49600AA4A22 /* OmiRemoteCameraViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmiRemoteCameraViewManager.swift; sourceTree = "<group>"; };
35
35
  C8FBCFD529A5F49600AA4A22 /* Constant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = "<group>"; };
36
36
  F4FF95D5245B92E700C19C63 /* OmikitPlugin-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OmikitPlugin-Bridging-Header.h"; sourceTree = "<group>"; };
37
37
  F4FF95D6245B92E800C19C63 /* OmikitPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmikitPlugin.swift; sourceTree = "<group>"; };
@@ -83,8 +83,8 @@
83
83
  C8FBCFD129A5F49600AA4A22 /* VideoCall */ = {
84
84
  isa = PBXGroup;
85
85
  children = (
86
- C8FBCFD229A5F49600AA4A22 /* FLLocalCameraView.swift */,
87
- C8FBCFD329A5F49600AA4A22 /* FLRemoteCameraView.swift */,
86
+ C8FBCFD229A5F49600AA4A22 /* OmiLocalCameraViewManager.swift */,
87
+ C8FBCFD329A5F49600AA4A22 /* OmiRemoteCameraViewManager.swift */,
88
88
  );
89
89
  path = VideoCall;
90
90
  sourceTree = "<group>";
@@ -0,0 +1,14 @@
1
+ //
2
+ // OmiLocalCameraViewBridge.m
3
+ // omikit-plugin
4
+ //
5
+ // Bridge for OmiLocalCameraView — exposes Swift ViewManager to ObjC runtime.
6
+ // RCT_EXTERN_MODULE is REQUIRED to prevent linker from stripping Swift symbols.
7
+ //
8
+
9
+ #import <Foundation/Foundation.h>
10
+ #import <React/RCTBridgeModule.h>
11
+ #import <React/RCTViewManager.h>
12
+
13
+ @interface RCT_EXTERN_MODULE(OmiLocalCameraView, RCTViewManager)
14
+ @end
@@ -0,0 +1,41 @@
1
+ //
2
+ // OmiLocalCameraViewManager.swift
3
+ // omikit-plugin
4
+ //
5
+ // Plain UIView container for OMIVideoCallManager.
6
+ // SDK creates and manages camera preview subview internally.
7
+ //
8
+
9
+ import Foundation
10
+ import React
11
+ import UIKit
12
+ import OmiKit
13
+
14
+ @objc(OmiLocalCameraView)
15
+ class OmiLocalCameraViewManager: RCTViewManager {
16
+
17
+ override class func requiresMainQueueSetup() -> Bool {
18
+ return true
19
+ }
20
+
21
+ override func view() -> UIView! {
22
+ // Create fresh container each time and register with CallManager
23
+ let container = UIView()
24
+ container.backgroundColor = UIColor(red: 0.118, green: 0.192, blue: 0.314, alpha: 1.0)
25
+ container.clipsToBounds = true
26
+ container.layer.cornerRadius = 12
27
+ NSLog("📹 [OmiLocalCameraViewManager] view() called — creating container")
28
+ CallManager.shareInstance().localContainerView = container
29
+ return container
30
+ }
31
+
32
+ @objc(refresh:withRejecter:)
33
+ func refresh(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
34
+ // With new SDK API, refresh is handled by SDK automatically
35
+ // prepareForVideoDisplay handles local camera restart if needed
36
+ DispatchQueue.main.async {
37
+ OMIVideoCallManager.shared().prepareForVideoDisplay()
38
+ }
39
+ resolve(true)
40
+ }
41
+ }
@@ -0,0 +1,14 @@
1
+ //
2
+ // OmiRemoteCameraViewBridge.m
3
+ // omikit-plugin
4
+ //
5
+ // Bridge for OmiRemoteCameraView — exposes Swift ViewManager to ObjC runtime.
6
+ // RCT_EXTERN_MODULE is REQUIRED to prevent linker from stripping Swift symbols.
7
+ //
8
+
9
+ #import <Foundation/Foundation.h>
10
+ #import <React/RCTBridgeModule.h>
11
+ #import <React/RCTViewManager.h>
12
+
13
+ @interface RCT_EXTERN_MODULE(OmiRemoteCameraView, RCTViewManager)
14
+ @end
@@ -0,0 +1,40 @@
1
+ //
2
+ // OmiRemoteCameraViewManager.swift
3
+ // omikit-plugin
4
+ //
5
+ // Plain UIView container for OMIVideoCallManager.
6
+ // SDK creates and manages Metal (OMIVideoPreviewView) subview internally.
7
+ //
8
+
9
+ import Foundation
10
+ import React
11
+ import UIKit
12
+ import OmiKit
13
+
14
+ @objc(OmiRemoteCameraView)
15
+ class OmiRemoteCameraViewManager: RCTViewManager {
16
+
17
+ override class func requiresMainQueueSetup() -> Bool {
18
+ return true
19
+ }
20
+
21
+ override func view() -> UIView! {
22
+ // Create fresh container each time and register with CallManager
23
+ let container = UIView()
24
+ container.backgroundColor = .black
25
+ container.clipsToBounds = true
26
+ NSLog("📹 [OmiRemoteCameraViewManager] view() called — creating container")
27
+ CallManager.shareInstance().remoteContainerView = container
28
+ return container
29
+ }
30
+
31
+ @objc(refresh:withRejecter:)
32
+ func refresh(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
33
+ // With new SDK API, refresh just calls prepareForVideoDisplay
34
+ // SDK handles reconnecting Metal view to container automatically
35
+ DispatchQueue.main.async {
36
+ OMIVideoCallManager.shared().prepareForVideoDisplay()
37
+ }
38
+ resolve(true)
39
+ }
40
+ }
@@ -5,5 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _reactNative = require("react-native");
8
- var _default = exports.default = _reactNative.TurboModuleRegistry.get('OmikitPlugin');
8
+ var _default = _reactNative.TurboModuleRegistry.get('OmikitPlugin');
9
+ exports.default = _default;
9
10
  //# sourceMappingURL=NativeOmikitPlugin.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeOmikitPlugin.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GA8JpCC,gCAAmB,CAACC,GAAG,CAAO,cAAc,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["_reactNative","require","_default","TurboModuleRegistry","get","exports","default"],"sourceRoot":"../../src","sources":["NativeOmikitPlugin.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GA+JpCC,gCAAmB,CAACC,GAAG,CAAO,cAAc,CAAC;AAAAC,OAAA,CAAAC,OAAA,GAAAJ,QAAA"}
@@ -1 +1 @@
1
- {"version":3,"names":["_omikit","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_omi_local_camera","_omi_remote_camera","_omi_call_state","_omi_start_call_status","_omi_audio_type"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,OAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,OAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,OAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,iBAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,iBAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,iBAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,iBAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,kBAAA,GAAAV,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAQ,kBAAA,EAAAP,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,kBAAA,CAAAN,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAE,kBAAA,CAAAN,GAAA;IAAA;EAAA;AAAA;AACA,IAAAO,eAAA,GAAAX,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAS,eAAA,EAAAR,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,eAAA,CAAAP,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAG,eAAA,CAAAP,GAAA;IAAA;EAAA;AAAA;AACA,IAAAQ,sBAAA,GAAAZ,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAU,sBAAA,EAAAT,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAQ,sBAAA,CAAAR,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAI,sBAAA,CAAAR,GAAA;IAAA;EAAA;AAAA;AACA,IAAAS,eAAA,GAAAb,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAW,eAAA,EAAAV,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAS,eAAA,CAAAT,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAK,eAAA,CAAAT,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
1
+ {"version":3,"names":["_omikit","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_omi_local_camera","_omi_remote_camera","_omi_call_state","_omi_start_call_status","_omi_audio_type"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;AAAA,IAAAA,OAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,OAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,OAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,OAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,iBAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,iBAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,iBAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,iBAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,kBAAA,GAAAV,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAQ,kBAAA,EAAAP,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,kBAAA,CAAAN,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAE,kBAAA,CAAAN,GAAA;IAAA;EAAA;AAAA;AACA,IAAAO,eAAA,GAAAX,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAS,eAAA,EAAAR,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,eAAA,CAAAP,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAG,eAAA,CAAAP,GAAA;IAAA;EAAA;AAAA;AACA,IAAAQ,sBAAA,GAAAZ,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAU,sBAAA,EAAAT,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAQ,sBAAA,CAAAR,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAI,sBAAA,CAAAR,GAAA;IAAA;EAAA;AAAA;AACA,IAAAS,eAAA,GAAAb,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAW,eAAA,EAAAV,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAS,eAAA,CAAAT,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAK,eAAA,CAAAT,GAAA;IAAA;EAAA;AAAA"}
@@ -7,14 +7,12 @@ exports.OmiAudioType = void 0;
7
7
  /**
8
8
  * Audio output types for setAudio()
9
9
  */
10
- let OmiAudioType = exports.OmiAudioType = /*#__PURE__*/function (OmiAudioType) {
10
+ let OmiAudioType; // Wired headphones
11
+ exports.OmiAudioType = OmiAudioType;
12
+ (function (OmiAudioType) {
11
13
  OmiAudioType[OmiAudioType["receiver"] = 0] = "receiver";
12
- // Phone receiver (earpiece)
13
14
  OmiAudioType[OmiAudioType["speaker"] = 1] = "speaker";
14
- // Phone speaker
15
15
  OmiAudioType[OmiAudioType["bluetooth"] = 2] = "bluetooth";
16
- // Bluetooth device
17
- OmiAudioType[OmiAudioType["headphones"] = 3] = "headphones"; // Wired headphones
18
- return OmiAudioType;
19
- }({});
16
+ OmiAudioType[OmiAudioType["headphones"] = 3] = "headphones";
17
+ })(OmiAudioType || (exports.OmiAudioType = OmiAudioType = {}));
20
18
  //# sourceMappingURL=omi_audio_type.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["OmiAudioType","exports"],"sourceRoot":"../../src","sources":["omi_audio_type.tsx"],"mappings":";;;;;;AAAA;AACA;AACA;AAFA,IAGYA,YAAY,GAAAC,OAAA,CAAAD,YAAA,0BAAZA,YAAY;EAAZA,YAAY,CAAZA,YAAY;EACJ;EADRA,YAAY,CAAZA,YAAY;EAEJ;EAFRA,YAAY,CAAZA,YAAY;EAGJ;EAHRA,YAAY,CAAZA,YAAY,oCAIJ;EAAA,OAJRA,YAAY;AAAA","ignoreList":[]}
1
+ {"version":3,"names":["OmiAudioType","exports"],"sourceRoot":"../../src","sources":["omi_audio_type.tsx"],"mappings":";;;;;;AAAA;AACA;AACA;AAFA,IAGYA,YAAY,EAIJ;AAAAC,OAAA,CAAAD,YAAA,GAAAA,YAAA;AAAA,WAJRA,YAAY;EAAZA,YAAY,CAAZA,YAAY;EAAZA,YAAY,CAAZA,YAAY;EAAZA,YAAY,CAAZA,YAAY;EAAZA,YAAY,CAAZA,YAAY;AAAA,GAAZA,YAAY,KAAAC,OAAA,CAAAD,YAAA,GAAZA,YAAY"}