@cometchat/skills 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +42 -2
  2. package/bin/install.js +448 -57
  3. package/package.json +1 -1
  4. package/skills/cometchat/SKILL.md +337 -80
  5. package/skills/cometchat-a11y/SKILL.md +476 -0
  6. package/skills/cometchat-android-v5/SKILL.md +1 -1
  7. package/skills/cometchat-android-v5-calls/SKILL.md +413 -0
  8. package/skills/cometchat-android-v5-calls/references/README.md +53 -0
  9. package/skills/cometchat-android-v5-calls/references/add-calls-to-existing-chat.md +195 -0
  10. package/skills/cometchat-android-v5-calls/references/audio-controls.md +91 -0
  11. package/skills/cometchat-android-v5-calls/references/background-handling.md +168 -0
  12. package/skills/cometchat-android-v5-calls/references/call-layouts.md +123 -0
  13. package/skills/cometchat-android-v5-calls/references/call-logs.md +113 -0
  14. package/skills/cometchat-android-v5-calls/references/call-session.md +274 -0
  15. package/skills/cometchat-android-v5-calls/references/custom-ui.md +150 -0
  16. package/skills/cometchat-android-v5-calls/references/event-listeners.md +123 -0
  17. package/skills/cometchat-android-v5-calls/references/group-calls.md +205 -0
  18. package/skills/cometchat-android-v5-calls/references/idle-timeout.md +111 -0
  19. package/skills/cometchat-android-v5-calls/references/in-call-chat.md +134 -0
  20. package/skills/cometchat-android-v5-calls/references/join-session.md +120 -0
  21. package/skills/cometchat-android-v5-calls/references/migration-v4-to-v5.md +206 -0
  22. package/skills/cometchat-android-v5-calls/references/participant-management.md +110 -0
  23. package/skills/cometchat-android-v5-calls/references/picture-in-picture.md +106 -0
  24. package/skills/cometchat-android-v5-calls/references/raise-hand.md +242 -0
  25. package/skills/cometchat-android-v5-calls/references/recording.md +101 -0
  26. package/skills/cometchat-android-v5-calls/references/ringing-integration.md +152 -0
  27. package/skills/cometchat-android-v5-calls/references/screen-sharing.md +82 -0
  28. package/skills/cometchat-android-v5-calls/references/server-fcm-voip.md +215 -0
  29. package/skills/cometchat-android-v5-calls/references/session-settings.md +121 -0
  30. package/skills/cometchat-android-v5-calls/references/setup.md +137 -0
  31. package/skills/cometchat-android-v5-calls/references/share-invite.md +151 -0
  32. package/skills/cometchat-android-v5-calls/references/video-controls.md +87 -0
  33. package/skills/cometchat-android-v5-calls/references/voip-calling.md +526 -0
  34. package/skills/cometchat-android-v5-components/SKILL.md +1 -1
  35. package/skills/cometchat-android-v5-core/SKILL.md +1 -1
  36. package/skills/cometchat-android-v5-customization/SKILL.md +1 -1
  37. package/skills/cometchat-android-v5-extensions/SKILL.md +1 -1
  38. package/skills/cometchat-android-v5-features/SKILL.md +1 -1
  39. package/skills/cometchat-android-v5-placement/SKILL.md +1 -1
  40. package/skills/cometchat-android-v5-production/SKILL.md +1 -1
  41. package/skills/cometchat-android-v5-push/SKILL.md +1 -1
  42. package/skills/cometchat-android-v5-testing/SKILL.md +1 -1
  43. package/skills/cometchat-android-v5-theming/SKILL.md +1 -1
  44. package/skills/cometchat-android-v5-troubleshooting/SKILL.md +1 -1
  45. package/skills/cometchat-android-v6/SKILL.md +1 -1
  46. package/skills/cometchat-android-v6-builder-settings/SKILL.md +1 -1
  47. package/skills/cometchat-android-v6-calls/SKILL.md +375 -0
  48. package/skills/cometchat-android-v6-calls/references/add-calls-to-existing-chat.md +97 -0
  49. package/skills/cometchat-android-v6-calls/references/call-layouts.md +91 -0
  50. package/skills/cometchat-android-v6-calls/references/call-session.md +163 -0
  51. package/skills/cometchat-android-v6-calls/references/device-management.md +171 -0
  52. package/skills/cometchat-android-v6-calls/references/group-calls.md +191 -0
  53. package/skills/cometchat-android-v6-calls/references/idle-timeout.md +107 -0
  54. package/skills/cometchat-android-v6-calls/references/in-call-chat.md +136 -0
  55. package/skills/cometchat-android-v6-calls/references/raise-hand.md +186 -0
  56. package/skills/cometchat-android-v6-calls/references/ringing-integration.md +176 -0
  57. package/skills/cometchat-android-v6-calls/references/server-fcm-voip.md +127 -0
  58. package/skills/cometchat-android-v6-calls/references/share-invite.md +105 -0
  59. package/skills/cometchat-android-v6-compose-components/SKILL.md +2 -2
  60. package/skills/cometchat-android-v6-compose-customization/SKILL.md +1 -1
  61. package/skills/cometchat-android-v6-compose-placement/SKILL.md +1 -1
  62. package/skills/cometchat-android-v6-compose-theming/SKILL.md +1 -1
  63. package/skills/cometchat-android-v6-core/SKILL.md +1 -1
  64. package/skills/cometchat-android-v6-events/SKILL.md +1 -1
  65. package/skills/cometchat-android-v6-extensions/SKILL.md +1 -1
  66. package/skills/cometchat-android-v6-features/SKILL.md +1 -1
  67. package/skills/cometchat-android-v6-kotlin-components/SKILL.md +1 -1
  68. package/skills/cometchat-android-v6-kotlin-customization/SKILL.md +1 -1
  69. package/skills/cometchat-android-v6-kotlin-placement/SKILL.md +1 -1
  70. package/skills/cometchat-android-v6-kotlin-theming/SKILL.md +1 -1
  71. package/skills/cometchat-android-v6-migration/SKILL.md +398 -0
  72. package/skills/cometchat-android-v6-production/SKILL.md +1 -1
  73. package/skills/cometchat-android-v6-push/SKILL.md +1 -1
  74. package/skills/cometchat-android-v6-testing/SKILL.md +1 -1
  75. package/skills/cometchat-android-v6-troubleshooting/SKILL.md +1 -1
  76. package/skills/cometchat-angular-calls/SKILL.md +511 -0
  77. package/skills/cometchat-angular-calls/references/add-calls-to-existing-chat.md +95 -0
  78. package/skills/cometchat-angular-calls/references/call-layouts.md +106 -0
  79. package/skills/cometchat-angular-calls/references/call-session.md +167 -0
  80. package/skills/cometchat-angular-calls/references/custom-ui.md +231 -0
  81. package/skills/cometchat-angular-calls/references/device-management.md +135 -0
  82. package/skills/cometchat-angular-calls/references/group-calls.md +412 -0
  83. package/skills/cometchat-angular-calls/references/idle-timeout.md +145 -0
  84. package/skills/cometchat-angular-calls/references/in-call-chat.md +148 -0
  85. package/skills/cometchat-angular-calls/references/lazy-loading-pitfalls.md +205 -0
  86. package/skills/cometchat-angular-calls/references/migration-v4-to-v5.md +53 -0
  87. package/skills/cometchat-angular-calls/references/ngzone-and-async-callbacks.md +183 -0
  88. package/skills/cometchat-angular-calls/references/picture-in-picture.md +285 -0
  89. package/skills/cometchat-angular-calls/references/raise-hand.md +167 -0
  90. package/skills/cometchat-angular-calls/references/recording-screen-share.md +267 -0
  91. package/skills/cometchat-angular-calls/references/ringing-integration.md +131 -0
  92. package/skills/cometchat-angular-calls/references/server-web-push-vapid.md +100 -0
  93. package/skills/cometchat-angular-calls/references/share-invite.md +116 -0
  94. package/skills/cometchat-angular-components/SKILL.md +1 -1
  95. package/skills/cometchat-angular-core/SKILL.md +1 -1
  96. package/skills/cometchat-angular-customization/SKILL.md +1 -1
  97. package/skills/cometchat-angular-features/SKILL.md +1 -1
  98. package/skills/cometchat-angular-patterns/SKILL.md +1 -1
  99. package/skills/cometchat-angular-placement/SKILL.md +1 -1
  100. package/skills/cometchat-angular-production/SKILL.md +1 -1
  101. package/skills/cometchat-angular-push/SKILL.md +403 -0
  102. package/skills/cometchat-angular-testing/SKILL.md +333 -0
  103. package/skills/cometchat-angular-theming/SKILL.md +1 -1
  104. package/skills/cometchat-angular-troubleshooting/SKILL.md +1 -1
  105. package/skills/cometchat-astro-patterns/SKILL.md +1 -1
  106. package/skills/cometchat-calls/SKILL.md +358 -0
  107. package/skills/cometchat-calls/references/use-case-broadcast.md +194 -0
  108. package/skills/cometchat-calls/references/use-case-marketplace.md +158 -0
  109. package/skills/cometchat-calls/references/use-case-support.md +190 -0
  110. package/skills/cometchat-calls/references/use-case-team.md +169 -0
  111. package/skills/cometchat-calls/references/use-case-telehealth.md +154 -0
  112. package/skills/cometchat-components/SKILL.md +1 -1
  113. package/skills/cometchat-core/SKILL.md +1 -1
  114. package/skills/cometchat-customization/SKILL.md +2 -2
  115. package/skills/cometchat-features/SKILL.md +2 -2
  116. package/skills/cometchat-flutter-v5/SKILL.md +1 -1
  117. package/skills/cometchat-flutter-v5-calls/SKILL.md +325 -94
  118. package/skills/cometchat-flutter-v5-calls/references/add-calls-to-existing-chat.md +169 -0
  119. package/skills/cometchat-flutter-v5-calls/references/call-layouts.md +98 -0
  120. package/skills/cometchat-flutter-v5-calls/references/call-session.md +330 -0
  121. package/skills/cometchat-flutter-v5-calls/references/device-management.md +120 -0
  122. package/skills/cometchat-flutter-v5-calls/references/group-calls.md +187 -0
  123. package/skills/cometchat-flutter-v5-calls/references/idle-timeout.md +97 -0
  124. package/skills/cometchat-flutter-v5-calls/references/in-call-chat.md +132 -0
  125. package/skills/cometchat-flutter-v5-calls/references/migration-v4-to-v5.md +160 -0
  126. package/skills/cometchat-flutter-v5-calls/references/raise-hand.md +244 -0
  127. package/skills/cometchat-flutter-v5-calls/references/ringing-integration.md +193 -0
  128. package/skills/cometchat-flutter-v5-calls/references/server-push-bridge.md +101 -0
  129. package/skills/cometchat-flutter-v5-calls/references/share-invite.md +142 -0
  130. package/skills/cometchat-flutter-v5-conversations/SKILL.md +1 -1
  131. package/skills/cometchat-flutter-v5-core/SKILL.md +1 -1
  132. package/skills/cometchat-flutter-v5-customization/SKILL.md +1 -1
  133. package/skills/cometchat-flutter-v5-events/SKILL.md +1 -1
  134. package/skills/cometchat-flutter-v5-messages/SKILL.md +1 -1
  135. package/skills/cometchat-flutter-v5-production/SKILL.md +1 -1
  136. package/skills/cometchat-flutter-v5-push/SKILL.md +1 -1
  137. package/skills/cometchat-flutter-v5-testing/SKILL.md +390 -0
  138. package/skills/cometchat-flutter-v5-theming/SKILL.md +1 -1
  139. package/skills/cometchat-flutter-v5-troubleshooting/SKILL.md +1 -1
  140. package/skills/cometchat-flutter-v5-users-groups/SKILL.md +1 -1
  141. package/skills/cometchat-flutter-v6/SKILL.md +1 -1
  142. package/skills/cometchat-flutter-v6-calls/SKILL.md +308 -243
  143. package/skills/cometchat-flutter-v6-calls/references/add-calls-to-existing-chat.md +134 -0
  144. package/skills/cometchat-flutter-v6-calls/references/call-layouts.md +102 -0
  145. package/skills/cometchat-flutter-v6-calls/references/call-session.md +229 -0
  146. package/skills/cometchat-flutter-v6-calls/references/device-management.md +99 -0
  147. package/skills/cometchat-flutter-v6-calls/references/group-calls.md +190 -0
  148. package/skills/cometchat-flutter-v6-calls/references/idle-timeout.md +80 -0
  149. package/skills/cometchat-flutter-v6-calls/references/in-call-chat.md +122 -0
  150. package/skills/cometchat-flutter-v6-calls/references/raise-hand.md +268 -0
  151. package/skills/cometchat-flutter-v6-calls/references/ringing-integration.md +171 -0
  152. package/skills/cometchat-flutter-v6-calls/references/server-push-bridge.md +93 -0
  153. package/skills/cometchat-flutter-v6-calls/references/share-invite.md +102 -0
  154. package/skills/cometchat-flutter-v6-components/SKILL.md +1 -1
  155. package/skills/cometchat-flutter-v6-conversations/SKILL.md +1 -1
  156. package/skills/cometchat-flutter-v6-core/SKILL.md +1 -1
  157. package/skills/cometchat-flutter-v6-customization/SKILL.md +1 -1
  158. package/skills/cometchat-flutter-v6-events/SKILL.md +1 -1
  159. package/skills/cometchat-flutter-v6-features/SKILL.md +1 -1
  160. package/skills/cometchat-flutter-v6-messages/SKILL.md +1 -1
  161. package/skills/cometchat-flutter-v6-migration/SKILL.md +1 -1
  162. package/skills/cometchat-flutter-v6-placement/SKILL.md +1 -1
  163. package/skills/cometchat-flutter-v6-production/SKILL.md +1 -1
  164. package/skills/cometchat-flutter-v6-push/SKILL.md +452 -0
  165. package/skills/cometchat-flutter-v6-testing/SKILL.md +319 -0
  166. package/skills/cometchat-flutter-v6-theming/SKILL.md +1 -1
  167. package/skills/cometchat-flutter-v6-troubleshooting/SKILL.md +1 -1
  168. package/skills/cometchat-flutter-v6-users-groups/SKILL.md +1 -1
  169. package/skills/cometchat-i18n/SKILL.md +358 -0
  170. package/skills/cometchat-ios/SKILL.md +5 -5
  171. package/skills/cometchat-ios-calls/SKILL.md +409 -0
  172. package/skills/cometchat-ios-calls/references/add-calls-to-existing-chat.md +172 -0
  173. package/skills/cometchat-ios-calls/references/avaudiosession-routing.md +189 -0
  174. package/skills/cometchat-ios-calls/references/call-layouts.md +136 -0
  175. package/skills/cometchat-ios-calls/references/call-session.md +221 -0
  176. package/skills/cometchat-ios-calls/references/callkit-and-pushkit.md +353 -0
  177. package/skills/cometchat-ios-calls/references/custom-ui.md +322 -0
  178. package/skills/cometchat-ios-calls/references/device-management.md +171 -0
  179. package/skills/cometchat-ios-calls/references/group-calls.md +396 -0
  180. package/skills/cometchat-ios-calls/references/idle-timeout.md +185 -0
  181. package/skills/cometchat-ios-calls/references/in-call-chat.md +183 -0
  182. package/skills/cometchat-ios-calls/references/migration-v4-to-v5.md +138 -0
  183. package/skills/cometchat-ios-calls/references/picture-in-picture.md +246 -0
  184. package/skills/cometchat-ios-calls/references/raise-hand.md +239 -0
  185. package/skills/cometchat-ios-calls/references/recording-screen-share.md +339 -0
  186. package/skills/cometchat-ios-calls/references/ringing-integration.md +142 -0
  187. package/skills/cometchat-ios-calls/references/server-apns-pushkit.md +267 -0
  188. package/skills/cometchat-ios-calls/references/share-invite.md +152 -0
  189. package/skills/cometchat-ios-calls/references/swiftui-uikit-hosting.md +246 -0
  190. package/skills/cometchat-ios-calls/references/voip-cert-setup.md +166 -0
  191. package/skills/cometchat-ios-components/SKILL.md +1 -1
  192. package/skills/cometchat-ios-core/SKILL.md +1 -1
  193. package/skills/cometchat-ios-customization/SKILL.md +1 -1
  194. package/skills/cometchat-ios-features/SKILL.md +1 -1
  195. package/skills/cometchat-ios-placement/SKILL.md +1 -1
  196. package/skills/cometchat-ios-production/SKILL.md +1 -1
  197. package/skills/cometchat-ios-push/SKILL.md +1 -1
  198. package/skills/cometchat-ios-testing/SKILL.md +371 -0
  199. package/skills/cometchat-ios-theming/SKILL.md +1 -1
  200. package/skills/cometchat-ios-troubleshooting/SKILL.md +1 -1
  201. package/skills/cometchat-native-bare-patterns/SKILL.md +45 -3
  202. package/skills/cometchat-native-calls/SKILL.md +599 -0
  203. package/skills/cometchat-native-calls/references/add-calls-to-existing-chat.md +203 -0
  204. package/skills/cometchat-native-calls/references/call-layouts.md +124 -0
  205. package/skills/cometchat-native-calls/references/call-session.md +182 -0
  206. package/skills/cometchat-native-calls/references/custom-ui.md +250 -0
  207. package/skills/cometchat-native-calls/references/device-management.md +135 -0
  208. package/skills/cometchat-native-calls/references/expo-vs-bare.md +179 -0
  209. package/skills/cometchat-native-calls/references/group-calls.md +291 -0
  210. package/skills/cometchat-native-calls/references/idle-timeout.md +128 -0
  211. package/skills/cometchat-native-calls/references/in-call-chat.md +143 -0
  212. package/skills/cometchat-native-calls/references/migration-v4-to-v5.md +72 -0
  213. package/skills/cometchat-native-calls/references/picture-in-picture.md +276 -0
  214. package/skills/cometchat-native-calls/references/raise-hand.md +203 -0
  215. package/skills/cometchat-native-calls/references/recording-screen-share.md +290 -0
  216. package/skills/cometchat-native-calls/references/ringing-integration.md +118 -0
  217. package/skills/cometchat-native-calls/references/server-push-bridge.md +135 -0
  218. package/skills/cometchat-native-calls/references/server-push-payloads.md +193 -0
  219. package/skills/cometchat-native-calls/references/share-invite.md +144 -0
  220. package/skills/cometchat-native-calls/references/voip-push-end-to-end.md +317 -0
  221. package/skills/cometchat-native-components/SKILL.md +1 -1
  222. package/skills/cometchat-native-core/SKILL.md +1 -1
  223. package/skills/cometchat-native-customization/SKILL.md +1 -1
  224. package/skills/cometchat-native-expo-patterns/SKILL.md +18 -1
  225. package/skills/cometchat-native-features/SKILL.md +1 -1
  226. package/skills/cometchat-native-placement/SKILL.md +1 -1
  227. package/skills/cometchat-native-production/SKILL.md +1 -1
  228. package/skills/cometchat-native-push/SKILL.md +1 -1
  229. package/skills/cometchat-native-testing/SKILL.md +1 -1
  230. package/skills/cometchat-native-theming/SKILL.md +1 -1
  231. package/skills/cometchat-native-troubleshooting/SKILL.md +1 -1
  232. package/skills/cometchat-nextjs-patterns/SKILL.md +1 -1
  233. package/skills/cometchat-placement/SKILL.md +29 -1
  234. package/skills/cometchat-production/SKILL.md +1 -1
  235. package/skills/cometchat-react-calls/SKILL.md +539 -0
  236. package/skills/cometchat-react-calls/references/add-calls-to-existing-chat.md +145 -0
  237. package/skills/cometchat-react-calls/references/call-layouts.md +161 -0
  238. package/skills/cometchat-react-calls/references/call-session.md +235 -0
  239. package/skills/cometchat-react-calls/references/custom-ui.md +201 -0
  240. package/skills/cometchat-react-calls/references/device-management.md +206 -0
  241. package/skills/cometchat-react-calls/references/group-calls.md +262 -0
  242. package/skills/cometchat-react-calls/references/idle-timeout.md +175 -0
  243. package/skills/cometchat-react-calls/references/in-call-chat.md +212 -0
  244. package/skills/cometchat-react-calls/references/migration-v4-to-v5.md +172 -0
  245. package/skills/cometchat-react-calls/references/picture-in-picture.md +245 -0
  246. package/skills/cometchat-react-calls/references/raise-hand.md +238 -0
  247. package/skills/cometchat-react-calls/references/recording-screen-share.md +143 -0
  248. package/skills/cometchat-react-calls/references/ringing-integration.md +255 -0
  249. package/skills/cometchat-react-calls/references/server-web-push-vapid.md +241 -0
  250. package/skills/cometchat-react-calls/references/share-invite.md +176 -0
  251. package/skills/cometchat-react-calls/references/testing-calls-on-web.md +198 -0
  252. package/skills/cometchat-react-calls/references/voip-and-web-push.md +165 -0
  253. package/skills/cometchat-react-patterns/SKILL.md +56 -8
  254. package/skills/cometchat-react-push/SKILL.md +448 -0
  255. package/skills/cometchat-react-router-patterns/SKILL.md +1 -1
  256. package/skills/cometchat-react-testing/SKILL.md +404 -0
  257. package/skills/cometchat-theming/SKILL.md +3 -3
  258. package/skills/cometchat-troubleshooting/SKILL.md +2 -2
@@ -0,0 +1,353 @@
1
+ # CallKit + PushKit on iOS — the canonical implementation
2
+
3
+ iOS is the only platform where the OS owns the incoming-call UI. CallKit + PushKit isn't optional for production — without them, calls don't ring on locked devices and Apple rejects production builds. This reference is the full implementation; the SKILL.md sketches it, here is the code.
4
+
5
+ ---
6
+
7
+ ## Why two frameworks
8
+
9
+ | Framework | Role |
10
+ |---|---|
11
+ | **PushKit** | Receives high-priority VoIP push payloads, even when the app is terminated. Separate from APNs. |
12
+ | **CallKit** | Presents the system incoming-call UI (lock-screen, even on locked device), reports call state to the OS, integrates with Phone app's "Recents" list. |
13
+
14
+ Combined: a VoIP push wakes the app via PushKit; the app immediately reports the call to CallKit; CallKit takes over and shows the system UI.
15
+
16
+ ---
17
+
18
+ ## Apple Developer setup (one-time, manual)
19
+
20
+ The skill cannot automate these:
21
+
22
+ 1. **Create a VoIP Services certificate** in Apple Developer portal → Certificates → "+" → VoIP Services.
23
+ 2. **Pin to the bundle ID** of your app.
24
+ 3. **Download the .p12** and convert to .pem if your push server needs it (`openssl pkcs12 -in cert.p12 -out cert.pem -nodes`).
25
+ 4. **Upload to your push server.** This is a SEPARATE certificate from your standard APNs cert.
26
+
27
+ The skill prints these steps and waits for the user to confirm before proceeding.
28
+
29
+ ---
30
+
31
+ ## Xcode setup
32
+
33
+ In the target's "Signing & Capabilities":
34
+
35
+ 1. Click "+" → "Push Notifications" → adds the entitlement.
36
+ 2. Click "+" → "Background Modes" → check:
37
+ - ☑ Audio, AirPlay, and Picture in Picture
38
+ - ☑ Voice over IP
39
+ - ☑ Remote notifications
40
+
41
+ Verify the resulting `Info.plist` has:
42
+
43
+ ```xml
44
+ <key>UIBackgroundModes</key>
45
+ <array>
46
+ <string>audio</string>
47
+ <string>voip</string>
48
+ <string>remote-notification</string>
49
+ </array>
50
+ <key>NSMicrophoneUsageDescription</key>
51
+ <string>So you can talk during voice and video calls.</string>
52
+ <key>NSCameraUsageDescription</key>
53
+ <string>So you can be seen during video calls.</string>
54
+ ```
55
+
56
+ ---
57
+
58
+ ## App entry — register PushKit + CallKit
59
+
60
+ ### SwiftUI (`@main App`)
61
+
62
+ ```swift
63
+ import SwiftUI
64
+ import PushKit
65
+ import CallKit
66
+ import CometChatSDK
67
+ import CometChatCallsSDK
68
+
69
+ @main
70
+ struct YourApp: App {
71
+ @StateObject private var callManager = CallManager.shared
72
+
73
+ var body: some Scene {
74
+ WindowGroup {
75
+ ContentView()
76
+ .onAppear {
77
+ CometChat.init(appId: Secrets.cometchatAppID, appSettings: appSettings) { isInitialized, error in
78
+ guard error == nil else { return }
79
+ CometChatCalls.init(callAppSettings: callAppSettings) { _ in
80
+ callManager.registerForPushKit() // wait for SDK ready
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### UIKit (`AppDelegate`)
90
+
91
+ ```swift
92
+ import UIKit
93
+ import PushKit
94
+ import CallKit
95
+
96
+ @main
97
+ class AppDelegate: UIResponder, UIApplicationDelegate {
98
+ func application(_ app: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
99
+ CometChat.init(appId: Secrets.cometchatAppID, appSettings: appSettings) { _, error in
100
+ guard error == nil else { return }
101
+ CometChatCalls.init(callAppSettings: callAppSettings) { _ in
102
+ CallManager.shared.registerForPushKit()
103
+ }
104
+ }
105
+ return true
106
+ }
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## CallManager — the shared state holder
113
+
114
+ ```swift
115
+ import PushKit
116
+ import CallKit
117
+ import CometChatCallsSDK
118
+
119
+ class CallManager: NSObject, ObservableObject {
120
+ static let shared = CallManager()
121
+
122
+ private let pushRegistry = PKPushRegistry(queue: .main)
123
+ private let provider: CXProvider
124
+ private let callController = CXCallController()
125
+
126
+ private override init() {
127
+ let config = CXProviderConfiguration(localizedName: "YourApp")
128
+ config.supportsVideo = true
129
+ config.maximumCallGroups = 1
130
+ config.maximumCallsPerCallGroup = 1
131
+ config.supportedHandleTypes = [.generic]
132
+ config.iconTemplateImageData = UIImage(named: "CallKitIcon")?.pngData()
133
+ config.ringtoneSound = "ringtone.caf"
134
+
135
+ self.provider = CXProvider(configuration: config)
136
+ super.init()
137
+ provider.setDelegate(self, queue: .main)
138
+ }
139
+
140
+ func registerForPushKit() {
141
+ pushRegistry.delegate = self
142
+ pushRegistry.desiredPushTypes = [.voIP]
143
+ }
144
+
145
+ func reportIncomingCall(uuid: UUID, callerName: String, callerUid: String, hasVideo: Bool) {
146
+ let update = CXCallUpdate()
147
+ update.remoteHandle = CXHandle(type: .generic, value: callerUid)
148
+ update.localizedCallerName = callerName
149
+ update.hasVideo = hasVideo
150
+
151
+ provider.reportNewIncomingCall(with: uuid, update: update) { error in
152
+ if let error = error {
153
+ print("Failed to report incoming call: \(error)")
154
+ }
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## PushKit delegate — the 5-second rule
163
+
164
+ ```swift
165
+ extension CallManager: PKPushRegistryDelegate {
166
+ func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
167
+ guard type == .voIP else { return }
168
+ let token = credentials.token.map { String(format: "%02x", $0) }.joined()
169
+ // Send this token to your server, keyed by the logged-in CometChat UID
170
+ Task {
171
+ await sendVoipTokenToServer(token: token, uid: CometChat.getLoggedInUser()?.uid ?? "")
172
+ }
173
+ }
174
+
175
+ func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
176
+ guard type == .voIP else { completion(); return }
177
+
178
+ // CRITICAL: report to CallKit IMMEDIATELY. iOS terminates apps that delay this >~5s.
179
+ let data = payload.dictionaryPayload["data"] as? [String: Any] ?? [:]
180
+ let sessionId = data["sessionId"] as? String ?? UUID().uuidString
181
+ let callerName = data["callerName"] as? String ?? "Unknown"
182
+ let callerUid = data["callerUid"] as? String ?? ""
183
+ let hasVideo = (data["callType"] as? String) == "video"
184
+
185
+ let uuid = UUID(uuidString: sessionId) ?? UUID()
186
+ reportIncomingCall(uuid: uuid, callerName: callerName, callerUid: callerUid, hasVideo: hasVideo)
187
+
188
+ // Store sessionId for later when CXAnswerCallAction fires
189
+ self.pendingSessionIds[uuid] = sessionId
190
+
191
+ completion() // ← tell iOS we're done
192
+ }
193
+
194
+ // iOS may invalidate the token (OS upgrade, app reinstall)
195
+ func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
196
+ // Token will re-register; wait for didUpdate
197
+ }
198
+ }
199
+ ```
200
+
201
+ **The 5-second rule:** Apple's `PKPushRegistry` documentation says you must call `reportNewIncomingCall` "promptly." Practical limit: ~5 seconds. If you exceed it, iOS terminates your app process and won't deliver future VoIP pushes for ~24 hours (apologies, no public retry mechanism).
202
+
203
+ ---
204
+
205
+ ## CallKit delegate — answer + end actions
206
+
207
+ ```swift
208
+ extension CallManager: CXProviderDelegate {
209
+ func providerDidReset(_ provider: CXProvider) {
210
+ // OS reset everything — clean up local state
211
+ pendingSessionIds.removeAll()
212
+ }
213
+
214
+ func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
215
+ // User tapped "Answer" on the lock screen
216
+ guard let sessionId = pendingSessionIds[action.callUUID] else {
217
+ action.fail()
218
+ return
219
+ }
220
+
221
+ Task {
222
+ do {
223
+ // Accept via Chat SDK
224
+ try await CometChat.acceptCall(sessionID: sessionId)
225
+
226
+ // Configure AVAudioSession for voice/video call audio
227
+ try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat)
228
+ try AVAudioSession.sharedInstance().setActive(true)
229
+
230
+ // Build session settings via SessionSettingsBuilder (matches upstream
231
+ // ringing sample at calls-sdk-ios-5/sample-apps/cometchat-calls-sample-
232
+ // app-ringing-ios/CometChatCallsRinging/CallView.swift).
233
+ let settings = SessionSettingsBuilder()
234
+ .setTitle("CometChat Call")
235
+ .startVideoPaused(false)
236
+ .startAudioMuted(false)
237
+ .build()
238
+
239
+ // Single-call joinSession(sessionID:callSetting:container:) — the SDK
240
+ // generates the token internally. Do NOT call generateToken separately.
241
+ // Container is the UIView the ongoing-call UI will be drawn into.
242
+ CometChatCalls.joinSession(
243
+ sessionID: sessionId,
244
+ callSetting: settings,
245
+ container: callContainerView // a UIView with measurable bounds
246
+ ) { success in
247
+ // Register listeners on the resulting CallSession AFTER onSuccess
248
+ let session = CometChatCallsSDK.CallSession.shared
249
+ session.addSessionStatusListener(callListener)
250
+ session.addButtonClickListener(callListener)
251
+ action.fulfill() // tell CallKit we answered successfully
252
+ } onError: { error in
253
+ print("joinSession failed: \(error?.errorDescription ?? "unknown")")
254
+ action.fail()
255
+ }
256
+
257
+ // Navigate the app's UI to the ongoing-call screen
258
+ await MainActor.run {
259
+ // ...your nav logic — push the view that hosts callContainerView...
260
+ }
261
+ } catch {
262
+ action.fail()
263
+ }
264
+ }
265
+ }
266
+
267
+ func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
268
+ // User tapped "End" or call ended remotely.
269
+ // Use CallSession.shared.leaveSession() — `CometChatCalls.endSession()`
270
+ // doesn't exist on iOS (matches upstream ringing sample).
271
+ CometChatCallsSDK.CallSession.shared.leaveSession()
272
+
273
+ // CRITICAL: deactivate audio session (rule 1.5)
274
+ do {
275
+ try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
276
+ } catch {
277
+ // log but don't block
278
+ }
279
+
280
+ pendingSessionIds.removeValue(forKey: action.callUUID)
281
+ action.fulfill()
282
+ }
283
+
284
+ func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
285
+ CometChatCalls.muteAudio(action.isMuted)
286
+ action.fulfill()
287
+ }
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Outgoing calls — also through CallKit
294
+
295
+ For caller-initiated calls, report via `CXStartCallAction` so the call appears in the iOS Phone app's Recents:
296
+
297
+ ```swift
298
+ extension CallManager {
299
+ func startOutgoingCall(receiverUid: String, callType: CallType) {
300
+ let uuid = UUID()
301
+ let handle = CXHandle(type: .generic, value: receiverUid)
302
+ let action = CXStartCallAction(call: uuid, handle: handle)
303
+ action.isVideo = (callType == .video)
304
+
305
+ let transaction = CXTransaction(action: action)
306
+ callController.request(transaction) { error in
307
+ if let error = error {
308
+ print("Failed to start outgoing call: \(error)")
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ extension CallManager {
315
+ // CXProviderDelegate
316
+ func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
317
+ Task {
318
+ do {
319
+ let outgoing = Call(receiverUid: action.handle.value, receiverType: .user,
320
+ callType: action.isVideo ? .video : .voice)
321
+ let initiated = try await CometChat.initiateCall(call: outgoing)
322
+ pendingSessionIds[action.callUUID] = initiated.sessionID
323
+ provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
324
+ action.fulfill()
325
+ } catch {
326
+ action.fail()
327
+ }
328
+ }
329
+ }
330
+ }
331
+ ```
332
+
333
+ This gives the user a "Calling..." UI in the iOS system. When the receiver accepts, `provider.reportOutgoingCall(connectedAt:)` switches the UI to "Connected."
334
+
335
+ ---
336
+
337
+ ## Common failure modes
338
+
339
+ | Symptom | Cause | Fix |
340
+ |---|---|---|
341
+ | App terminates 5s after PushKit delivery | Async work before `reportNewIncomingCall` | Report first, then do anything else |
342
+ | "VoIP services not available" at registry setup | Background Modes capability missing | Add VoIP capability in Xcode and rebuild |
343
+ | Token never registers | App not signed with VoIP-capable provisioning profile | Re-download provisioning profile |
344
+ | Call rings but answering does nothing | `CXAnswerCallAction` delegate doesn't fail/fulfill | Always call `action.fulfill()` or `action.fail()` |
345
+ | Music doesn't resume after hangup | Missing `setActive(false, .notifyOthersOnDeactivation)` | Add to `CXEndCallAction` handler |
346
+ | Lock-screen rings but unlocking shows wrong screen | App didn't navigate to ongoing-call view in `CXAnswerCallAction` | Navigate inside the action handler |
347
+ | Outgoing call never shows "Calling..." UI | Started via `initiateCall` directly without `CXStartCallAction` | Use `CXTransaction` flow |
348
+
349
+ ---
350
+
351
+ ## Testing without a push server
352
+
353
+ Same workaround as RN — call `CallManager.shared.reportIncomingCall(...)` from a debug button. Bypasses PushKit entirely. Useful for testing the in-app + CallKit-UI flow but doesn't exercise the lock-screen ring (which requires PushKit delivery to an unlocked or terminated app).
@@ -0,0 +1,322 @@
1
+ # Custom call UI on iOS
2
+
3
+ Same shape as the other families' custom-ui references. iOS-specific because of CallKit interplay (rule 1.7: CallKit owns standalone-mode incoming UI, so custom in-app incoming UI is additive-mode-only) and AVFoundation routing.
4
+
5
+ ---
6
+
7
+ ## Two paths
8
+
9
+ 1. **Style the kit's `CometChatOngoingCall`** UIViewController via theming + property overrides. Cheapest. Covers most apps.
10
+ 2. **Build your own UIViewController on the SDK** — direct `CometChatCalls.joinSession` with your own UIView containers. Maximum control.
11
+
12
+ This reference covers path 2.
13
+
14
+ ---
15
+
16
+ ## Architecture
17
+
18
+ ```
19
+ Your UIViewController
20
+ ├── containerView for remote participant(s) ← UIView the SDK draws into
21
+ ├── localPreviewView ← AVCaptureVideoPreviewLayer or SDK preview
22
+ ├── controlPanel (mute / camera / end / switch)
23
+ └── statusLabel (call state, duration)
24
+ ```
25
+
26
+ The Calls SDK draws video into UIViews you provide. You don't render WebRTC tracks yourself (unlike web/RN where you wire `<video>` elements).
27
+
28
+ ---
29
+
30
+ ## Custom OngoingCallViewController
31
+
32
+ ```swift
33
+ import UIKit
34
+ import AVFoundation
35
+ import CometChatCallsSDK
36
+
37
+ class CustomOngoingCallViewController: UIViewController {
38
+ private let containerView = UIView()
39
+ private let localPreviewView = UIView()
40
+ private lazy var controlPanel = ControlPanelView()
41
+ private let statusLabel = UILabel()
42
+
43
+ private let sessionID: String
44
+ private let isAudioOnly: Bool
45
+ private var listener: CometChatCallsEventsListener?
46
+
47
+ init(sessionID: String, isAudioOnly: Bool) {
48
+ self.sessionID = sessionID
49
+ self.isAudioOnly = isAudioOnly
50
+ super.init(nibName: nil, bundle: nil)
51
+ modalPresentationStyle = .fullScreen
52
+ }
53
+
54
+ required init?(coder: NSCoder) { fatalError() }
55
+
56
+ override func viewDidLoad() {
57
+ super.viewDidLoad()
58
+ view.backgroundColor = .black
59
+ setupLayout()
60
+ startCall()
61
+ }
62
+
63
+ override func viewWillDisappear(_ animated: Bool) {
64
+ super.viewWillDisappear(animated)
65
+ cleanup()
66
+ }
67
+
68
+ private func startCall() {
69
+ listener = self
70
+ // Build SessionSettings — match upstream sample shape.
71
+ // For custom UI, you typically build your own controls and just need the SDK
72
+ // to feed video tiles into your container. enableDefaultLayout is not part
73
+ // of SessionSettings — disable individual default-UI elements via the
74
+ // hide* setters on SessionSettingsBuilder if you want a minimal surface.
75
+ let settings = SessionSettingsBuilder()
76
+ .setTitle(isAudioOnly ? "Voice Call" : "Video Call")
77
+ .startVideoPaused(isAudioOnly)
78
+ .startAudioMuted(false)
79
+ .build()
80
+
81
+ // Configure audio session BEFORE joining
82
+ try? configureAudioSession()
83
+
84
+ // Single-call joinSession(sessionID:callSetting:container:) — the SDK
85
+ // generates the token internally. NO separate generateToken call.
86
+ CometChatCalls.joinSession(
87
+ sessionID: sessionID,
88
+ callSetting: settings,
89
+ container: containerView
90
+ ) { [weak self] success in
91
+ guard let self = self else { return }
92
+ // Register listeners on the resulting CallSession AFTER onSuccess.
93
+ let session = CometChatCallsSDK.CallSession.shared
94
+ session.addSessionStatusListener(self.listener)
95
+ session.addButtonClickListener(self.listener)
96
+ } onError: { error in
97
+ print("joinSession failed: \(error?.errorDescription ?? "unknown")")
98
+ }
99
+ }
100
+
101
+ private func configureAudioSession() throws {
102
+ let session = AVAudioSession.sharedInstance()
103
+ try session.setCategory(
104
+ .playAndRecord,
105
+ mode: isAudioOnly ? .voiceChat : .videoChat,
106
+ options: [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker]
107
+ )
108
+ try session.setActive(true)
109
+ }
110
+
111
+ private func cleanup() {
112
+ // CometChatCalls.endSession() does NOT exist on iOS — use CallSession.shared.leaveSession()
113
+ CometChatCallsSDK.CallSession.shared.leaveSession()
114
+ try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
115
+ }
116
+
117
+ private func setupLayout() {
118
+ [containerView, localPreviewView, controlPanel, statusLabel].forEach {
119
+ $0.translatesAutoresizingMaskIntoConstraints = false
120
+ view.addSubview($0)
121
+ }
122
+
123
+ NSLayoutConstraint.activate([
124
+ containerView.topAnchor.constraint(equalTo: view.topAnchor),
125
+ containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
126
+ containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
127
+ containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
128
+
129
+ localPreviewView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
130
+ localPreviewView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
131
+ localPreviewView.widthAnchor.constraint(equalToConstant: 120),
132
+ localPreviewView.heightAnchor.constraint(equalToConstant: 160),
133
+
134
+ controlPanel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
135
+ controlPanel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
136
+ controlPanel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
137
+ controlPanel.heightAnchor.constraint(equalToConstant: 80),
138
+
139
+ statusLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
140
+ statusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
141
+ ])
142
+ }
143
+ }
144
+
145
+ extension CustomOngoingCallViewController: CometChatCallsEventsListener {
146
+ func onCallEnded() {
147
+ DispatchQueue.main.async { [weak self] in
148
+ self?.cleanup()
149
+ self?.dismiss(animated: true)
150
+ }
151
+ }
152
+
153
+ func onUserListUpdated(_ users: [Any]) {
154
+ DispatchQueue.main.async { [weak self] in
155
+ self?.statusLabel.text = "\(users.count) participants"
156
+ }
157
+ }
158
+
159
+ func onError(_ error: Error) {
160
+ print("Call error: \(error)")
161
+ }
162
+ }
163
+ ```
164
+
165
+ The `DispatchQueue.main.async` wrapping is the iOS equivalent of NgZone.run on Angular — SDK callbacks fire on background queues; UI updates must be on main.
166
+
167
+ ---
168
+
169
+ ## Control panel
170
+
171
+ ```swift
172
+ final class ControlPanelView: UIView {
173
+ let muteButton = UIButton(type: .system)
174
+ let cameraButton = UIButton(type: .system)
175
+ let switchCameraButton = UIButton(type: .system)
176
+ let endButton = UIButton(type: .system)
177
+
178
+ var onMute: (() -> Void)?
179
+ var onCamera: (() -> Void)?
180
+ var onSwitch: (() -> Void)?
181
+ var onEnd: (() -> Void)?
182
+
183
+ override init(frame: CGRect) {
184
+ super.init(frame: frame)
185
+ backgroundColor = UIColor(white: 0, alpha: 0.4)
186
+ layer.cornerRadius = 40
187
+
188
+ let stack = UIStackView(arrangedSubviews: [muteButton, cameraButton, switchCameraButton, endButton])
189
+ stack.distribution = .equalCentering
190
+ stack.alignment = .center
191
+ stack.translatesAutoresizingMaskIntoConstraints = false
192
+ addSubview(stack)
193
+
194
+ NSLayoutConstraint.activate([
195
+ stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
196
+ stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
197
+ stack.centerYAnchor.constraint(equalTo: centerYAnchor),
198
+ ])
199
+
200
+ muteButton.setTitle("Mute", for: .normal)
201
+ cameraButton.setTitle("Cam", for: .normal)
202
+ switchCameraButton.setTitle("Flip", for: .normal)
203
+ endButton.setTitle("End", for: .normal)
204
+ endButton.tintColor = .red
205
+
206
+ muteButton.addAction(UIAction { [weak self] _ in self?.onMute?() }, for: .touchUpInside)
207
+ cameraButton.addAction(UIAction { [weak self] _ in self?.onCamera?() }, for: .touchUpInside)
208
+ switchCameraButton.addAction(UIAction { [weak self] _ in self?.onSwitch?() }, for: .touchUpInside)
209
+ endButton.addAction(UIAction { [weak self] _ in self?.onEnd?() }, for: .touchUpInside)
210
+ }
211
+
212
+ required init?(coder: NSCoder) { fatalError() }
213
+ }
214
+ ```
215
+
216
+ Wire actions in the VC:
217
+
218
+ ```swift
219
+ override func viewDidLoad() {
220
+ super.viewDidLoad()
221
+ // ...
222
+ controlPanel.onMute = { [weak self] in
223
+ guard let self = self else { return }
224
+ self.muted.toggle()
225
+ CometChatCalls.muteAudio(self.muted)
226
+ }
227
+ controlPanel.onCamera = { [weak self] in
228
+ guard let self = self else { return }
229
+ self.cameraOff.toggle()
230
+ CometChatCalls.pauseVideo(self.cameraOff)
231
+ }
232
+ controlPanel.onSwitch = { CometChatCalls.switchCamera() }
233
+ controlPanel.onEnd = { [weak self] in
234
+ self?.cleanup()
235
+ self?.dismiss(animated: true)
236
+ }
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Local preview before connect
243
+
244
+ ```swift
245
+ private lazy var captureSession = AVCaptureSession()
246
+ private lazy var previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
247
+
248
+ private func startLocalPreview() {
249
+ guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front),
250
+ let input = try? AVCaptureDeviceInput(device: device) else { return }
251
+
252
+ captureSession.addInput(input)
253
+ previewLayer.frame = localPreviewView.bounds
254
+ previewLayer.videoGravity = .resizeAspectFill
255
+ localPreviewView.layer.addSublayer(previewLayer)
256
+
257
+ DispatchQueue.global(qos: .userInitiated).async {
258
+ self.captureSession.startRunning()
259
+ }
260
+ }
261
+
262
+ private func stopLocalPreview() {
263
+ captureSession.stopRunning()
264
+ previewLayer.removeFromSuperlayer()
265
+ }
266
+ ```
267
+
268
+ Stop the preview BEFORE `joinSession` — the SDK takes over the camera and your `AVCaptureSession` will fight with it.
269
+
270
+ ---
271
+
272
+ ## SwiftUI hosting
273
+
274
+ Wrap with `UIViewControllerRepresentable` (see `references/swiftui-uikit-hosting.md`):
275
+
276
+ ```swift
277
+ struct CustomOngoingCallView: UIViewControllerRepresentable {
278
+ let sessionID: String
279
+ let isAudioOnly: Bool
280
+
281
+ func makeUIViewController(context: Context) -> CustomOngoingCallViewController {
282
+ CustomOngoingCallViewController(sessionID: sessionID, isAudioOnly: isAudioOnly)
283
+ }
284
+
285
+ func updateUIViewController(_ vc: CustomOngoingCallViewController, context: Context) {}
286
+ }
287
+ ```
288
+
289
+ Present via `.fullScreenCover` (NOT `.sheet`).
290
+
291
+ ---
292
+
293
+ ## Picture-in-picture
294
+
295
+ iOS supports PiP for video calls via `AVPictureInPictureController`:
296
+
297
+ ```swift
298
+ import AVKit
299
+
300
+ class CustomOngoingCallViewController {
301
+ private var pipController: AVPictureInPictureController?
302
+
303
+ func setupPiP() {
304
+ guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
305
+ let layer = AVSampleBufferDisplayLayer()
306
+ containerView.layer.addSublayer(layer)
307
+ let contentSource = AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: layer, playbackDelegate: self)
308
+ pipController = AVPictureInPictureController(contentSource: contentSource)
309
+ pipController?.canStartPictureInPictureAutomaticallyFromInline = true
310
+ }
311
+ }
312
+ ```
313
+
314
+ Setting up PiP for WebRTC video is involved — the SDK doesn't expose a `CMSampleBuffer` stream directly. For most apps, `enableDefaultLayout(true)` and let the kit's PiP work; custom UI authors who need PiP can use `CometChatPictureInPicture` (kit class) as a hybrid — kit's PiP, custom rest of UI.
315
+
316
+ ---
317
+
318
+ ## When NOT to go custom
319
+
320
+ The kit's `CometChatOngoingCall` covers 80% of apps. Custom is for design-system requirements that the kit's theming + property overrides can't reach. Same calculus as web/RN/Angular.
321
+
322
+ The dispatcher asks before scaffolding custom and defaults to "no — use kit."