diff --git a/docs/.vuepress/components/CallKitSwitch.vue b/docs/.vuepress/components/CallKitSwitch.vue
new file mode 100644
index 000000000..ca3471869
--- /dev/null
+++ b/docs/.vuepress/components/CallKitSwitch.vue
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
diff --git a/docs/.vuepress/components/Sidebar.vue b/docs/.vuepress/components/Sidebar.vue
index b2da9e2e7..dccf521e7 100644
--- a/docs/.vuepress/components/Sidebar.vue
+++ b/docs/.vuepress/components/Sidebar.vue
@@ -3,6 +3,7 @@
import PlatformSwitch from './PlatformSwitch.vue'
import PrivateSwitch from './PrivateSwitch.vue'
import UIKitSwitch from './UIKitSwitch.vue'
+ import CallKitSwitch from './CallKitSwitch.vue'
import { usePageData } from '@vuepress/client'
import { ref, watch } from 'vue'
@@ -10,11 +11,13 @@
const showPlatformSwitch = ref(false)
const showPrivateSwitch = ref(false)
const showUIKitSwitch = ref(false)
+ const showCallKitSwitch = ref(false)
watch(pageData, ()=> {
const pagePath = pageData.value.path
showPrivateSwitch.value = pagePath.indexOf('/private/') == 0
showPlatformSwitch.value = pagePath.indexOf('/document/') == 0
showUIKitSwitch.value = pagePath.indexOf('/uikit/') == 0
+ showCallKitSwitch.value = pagePath.indexOf('/callkit/') == 0
}, {immediate:true})
@@ -37,6 +40,11 @@
+
+
+
+
+
diff --git a/docs/.vuepress/navbar/index.ts b/docs/.vuepress/navbar/index.ts
index 4b1141ef9..5a2086e80 100644
--- a/docs/.vuepress/navbar/index.ts
+++ b/docs/.vuepress/navbar/index.ts
@@ -77,6 +77,26 @@ export const zhNavbar = navbar([
}
]
},
+ {
+ text: 'CallKit',
+ children: [
+ {
+ text: 'Android',
+ icon: '/icon-Android.svg',
+ link: '/callkit/android/product_overview.html'
+ },
+ {
+ text: 'iOS',
+ icon: '/icon-iOS.svg',
+ link: '/callkit/ios/product_overview.html'
+ },
+ {
+ text: 'Web',
+ icon: '/icon-web.svg',
+ link: '/callkit/web/product_overview.html'
+ },
+ ]
+ },
{
text: 'SDK/REST 集成',
children: [
diff --git "a/docs/.vuepress/public/.vuepress - \345\277\253\346\215\267\346\226\271\345\274\217.lnk" "b/docs/.vuepress/public/.vuepress - \345\277\253\346\215\267\346\226\271\345\274\217.lnk"
deleted file mode 100644
index b5b6dfb04..000000000
Binary files "a/docs/.vuepress/public/.vuepress - \345\277\253\346\215\267\346\226\271\345\274\217.lnk" and /dev/null differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_video_callee_invitation.png b/docs/.vuepress/public/images/callkit/android/1v1_video_callee_invitation.png
new file mode 100644
index 000000000..76029bba0
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_video_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_video_caller_invitation.png b/docs/.vuepress/public/images/callkit/android/1v1_video_caller_invitation.png
new file mode 100644
index 000000000..858d380a1
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_video_caller_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_video_float.png b/docs/.vuepress/public/images/callkit/android/1v1_video_float.png
new file mode 100644
index 000000000..72dd47fd7
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_video_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_video_notification_inapp.png b/docs/.vuepress/public/images/callkit/android/1v1_video_notification_inapp.png
new file mode 100644
index 000000000..0e405225b
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_video_notification_inapp.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_video_ongoing.png b/docs/.vuepress/public/images/callkit/android/1v1_video_ongoing.png
new file mode 100644
index 000000000..7aee43ab2
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_video_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_voice_callee_invitation.png b/docs/.vuepress/public/images/callkit/android/1v1_voice_callee_invitation.png
new file mode 100644
index 000000000..1aa7d2290
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_voice_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_voice_caller_invitation.png b/docs/.vuepress/public/images/callkit/android/1v1_voice_caller_invitation.png
new file mode 100644
index 000000000..866e6fa47
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_voice_caller_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_voice_float.png b/docs/.vuepress/public/images/callkit/android/1v1_voice_float.png
new file mode 100644
index 000000000..9dd3e900e
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_voice_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_voice_notification_inapp.png b/docs/.vuepress/public/images/callkit/android/1v1_voice_notification_inapp.png
new file mode 100644
index 000000000..41a7339f2
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_voice_notification_inapp.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/1v1_voice_ongoing.png b/docs/.vuepress/public/images/callkit/android/1v1_voice_ongoing.png
new file mode 100644
index 000000000..41e36e15f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/1v1_voice_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/architecture.png b/docs/.vuepress/public/images/callkit/android/architecture.png
new file mode 100644
index 000000000..f399b8263
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/architecture.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/callkit_architecture.png b/docs/.vuepress/public/images/callkit/android/callkit_architecture.png
new file mode 100644
index 000000000..78b0127cf
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/callkit_architecture.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/callkit_poster.png b/docs/.vuepress/public/images/callkit/android/callkit_poster.png
new file mode 100644
index 000000000..70ed915af
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/callkit_poster.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/float_permission_apply_1.png b/docs/.vuepress/public/images/callkit/android/float_permission_apply_1.png
new file mode 100644
index 000000000..82d2d7110
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/float_permission_apply_1.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/float_permission_apply_2.png b/docs/.vuepress/public/images/callkit/android/float_permission_apply_2.png
new file mode 100644
index 000000000..e4bd6c91c
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/float_permission_apply_2.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/group_call_callee_invitation.png b/docs/.vuepress/public/images/callkit/android/group_call_callee_invitation.png
new file mode 100644
index 000000000..4e197fe33
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/group_call_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/group_call_caller_user_selection.png b/docs/.vuepress/public/images/callkit/android/group_call_caller_user_selection.png
new file mode 100644
index 000000000..deffeb093
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/group_call_caller_user_selection.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/group_call_float.png b/docs/.vuepress/public/images/callkit/android/group_call_float.png
new file mode 100644
index 000000000..123a36ddb
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/group_call_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/group_call_notification_inapp.png b/docs/.vuepress/public/images/callkit/android/group_call_notification_inapp.png
new file mode 100644
index 000000000..e823f01fd
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/group_call_notification_inapp.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/group_call_ongoing.png b/docs/.vuepress/public/images/callkit/android/group_call_ongoing.png
new file mode 100644
index 000000000..4474633a6
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/group_call_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/group_call_ongoing_add.png b/docs/.vuepress/public/images/callkit/android/group_call_ongoing_add.png
new file mode 100644
index 000000000..0ddb024fd
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/group_call_ongoing_add.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_audio.png b/docs/.vuepress/public/images/callkit/android/notification_audio.png
new file mode 100644
index 000000000..beac3c311
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_audio.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_background.png b/docs/.vuepress/public/images/callkit/android/notification_background.png
new file mode 100644
index 000000000..d44dc37b4
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_background.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_device_info.png b/docs/.vuepress/public/images/callkit/android/notification_device_info.png
new file mode 100644
index 000000000..e50ae39af
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_device_info.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_device_info_near.png b/docs/.vuepress/public/images/callkit/android/notification_device_info_near.png
new file mode 100644
index 000000000..91fd956ed
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_device_info_near.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_easemob.png b/docs/.vuepress/public/images/callkit/android/notification_easemob.png
new file mode 100644
index 000000000..7651126c7
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_easemob.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_lock.png b/docs/.vuepress/public/images/callkit/android/notification_lock.png
new file mode 100644
index 000000000..666de59de
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_lock.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_phone.png b/docs/.vuepress/public/images/callkit/android/notification_phone.png
new file mode 100644
index 000000000..94d52df30
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_phone.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_photo_video.png b/docs/.vuepress/public/images/callkit/android/notification_photo_video.png
new file mode 100644
index 000000000..837e00524
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_photo_video.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/notification_system.png b/docs/.vuepress/public/images/callkit/android/notification_system.png
new file mode 100644
index 000000000..df36216e5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/notification_system.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/project_runthrough.png b/docs/.vuepress/public/images/callkit/android/project_runthrough.png
new file mode 100644
index 000000000..e859ba4df
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/project_runthrough.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/quickstart_run.png b/docs/.vuepress/public/images/callkit/android/quickstart_run.png
new file mode 100644
index 000000000..7c1f96ec0
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/quickstart_run.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/voip_account_enable.png b/docs/.vuepress/public/images/callkit/android/voip_account_enable.png
new file mode 100644
index 000000000..91c28a35c
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/voip_account_enable.png differ
diff --git a/docs/.vuepress/public/images/callkit/android/voip_call_enable.png b/docs/.vuepress/public/images/callkit/android/voip_call_enable.png
new file mode 100644
index 000000000..6d866e888
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/android/voip_call_enable.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/1XfpZuKaNBD26GW.png b/docs/.vuepress/public/images/callkit/design/1XfpZuKaNBD26GW.png
new file mode 100644
index 000000000..a6a2fcb1c
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/1XfpZuKaNBD26GW.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/Action2.png b/docs/.vuepress/public/images/callkit/design/Action2.png
new file mode 100644
index 000000000..cfcdf2024
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/Action2.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/Cover.png b/docs/.vuepress/public/images/callkit/design/Cover.png
new file mode 100644
index 000000000..abf27d0e6
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/Cover.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/Label.png b/docs/.vuepress/public/images/callkit/design/Label.png
new file mode 100644
index 000000000..683d347de
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/Label.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/PiP.png b/docs/.vuepress/public/images/callkit/design/PiP.png
new file mode 100644
index 000000000..7ff01d2bc
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/PiP.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/Subtitle.png b/docs/.vuepress/public/images/callkit/design/Subtitle.png
new file mode 100644
index 000000000..d6c915e7f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/Subtitle.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/actionslist.png b/docs/.vuepress/public/images/callkit/design/actionslist.png
new file mode 100644
index 000000000..b9b69071d
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/actionslist.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/audio_answer.png b/docs/.vuepress/public/images/callkit/design/audio_answer.png
new file mode 100644
index 000000000..342688b2a
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/audio_answer.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/audio_calling.png b/docs/.vuepress/public/images/callkit/design/audio_calling.png
new file mode 100644
index 000000000..dde35c909
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/audio_calling.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/audio_receive.png b/docs/.vuepress/public/images/callkit/design/audio_receive.png
new file mode 100644
index 000000000..b8f6fc99f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/audio_receive.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/avatar.png b/docs/.vuepress/public/images/callkit/design/avatar.png
new file mode 100644
index 000000000..0220ace7e
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/avatar.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/bg.png b/docs/.vuepress/public/images/callkit/design/bg.png
new file mode 100644
index 000000000..c55ec42db
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/bg.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/botton.png b/docs/.vuepress/public/images/callkit/design/botton.png
new file mode 100644
index 000000000..9009bb2f5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/botton.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/call_msg.png b/docs/.vuepress/public/images/callkit/design/call_msg.png
new file mode 100644
index 000000000..fd8d65e68
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/call_msg.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/callnoti.png b/docs/.vuepress/public/images/callkit/design/callnoti.png
new file mode 100644
index 000000000..83c686feb
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/callnoti.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/callnoti2.png b/docs/.vuepress/public/images/callkit/design/callnoti2.png
new file mode 100644
index 000000000..2e1462dc2
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/callnoti2.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11211.png b/docs/.vuepress/public/images/callkit/design/cruk11211.png
new file mode 100644
index 000000000..c46fb5943
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11211.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11212.png b/docs/.vuepress/public/images/callkit/design/cruk11212.png
new file mode 100644
index 000000000..c3e99ba8a
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11212.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1122.png b/docs/.vuepress/public/images/callkit/design/cruk1122.png
new file mode 100644
index 000000000..34ef4c7ba
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1122.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1123.png b/docs/.vuepress/public/images/callkit/design/cruk1123.png
new file mode 100644
index 000000000..f675a4aac
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1123.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1124.png b/docs/.vuepress/public/images/callkit/design/cruk1124.png
new file mode 100644
index 000000000..b1c63984e
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1124.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk113.png b/docs/.vuepress/public/images/callkit/design/cruk113.png
new file mode 100644
index 000000000..e1ee0af98
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk113.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1131.png b/docs/.vuepress/public/images/callkit/design/cruk1131.png
new file mode 100644
index 000000000..69a054e8d
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1131.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11321.png b/docs/.vuepress/public/images/callkit/design/cruk11321.png
new file mode 100644
index 000000000..d987a1736
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11321.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11322.png b/docs/.vuepress/public/images/callkit/design/cruk11322.png
new file mode 100644
index 000000000..a9d4b47b0
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11322.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11341.png b/docs/.vuepress/public/images/callkit/design/cruk11341.png
new file mode 100644
index 000000000..81fc02465
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11341.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11342.png b/docs/.vuepress/public/images/callkit/design/cruk11342.png
new file mode 100644
index 000000000..7eb55d0d5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11342.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11343.png b/docs/.vuepress/public/images/callkit/design/cruk11343.png
new file mode 100644
index 000000000..6b0725d6c
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11343.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1141.png b/docs/.vuepress/public/images/callkit/design/cruk1141.png
new file mode 100644
index 000000000..2a1365f6b
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1141.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1151.png b/docs/.vuepress/public/images/callkit/design/cruk1151.png
new file mode 100644
index 000000000..5b54276ca
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1151.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11521.png b/docs/.vuepress/public/images/callkit/design/cruk11521.png
new file mode 100644
index 000000000..8b1faf285
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11521.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk11522.png b/docs/.vuepress/public/images/callkit/design/cruk11522.png
new file mode 100644
index 000000000..7ff98d075
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk11522.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1161.png b/docs/.vuepress/public/images/callkit/design/cruk1161.png
new file mode 100644
index 000000000..c8daeb483
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1161.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk116a.png b/docs/.vuepress/public/images/callkit/design/cruk116a.png
new file mode 100644
index 000000000..e5aba6b4e
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk116a.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk116b.png b/docs/.vuepress/public/images/callkit/design/cruk116b.png
new file mode 100644
index 000000000..e524c73c1
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk116b.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk131.png b/docs/.vuepress/public/images/callkit/design/cruk131.png
new file mode 100644
index 000000000..0e21a5ee3
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk131.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk132.png b/docs/.vuepress/public/images/callkit/design/cruk132.png
new file mode 100644
index 000000000..8347b0037
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk132.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk146a.png b/docs/.vuepress/public/images/callkit/design/cruk146a.png
new file mode 100644
index 000000000..b6d23a6f7
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk146a.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk146b.png b/docs/.vuepress/public/images/callkit/design/cruk146b.png
new file mode 100644
index 000000000..ff22669ed
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk146b.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1522a.png b/docs/.vuepress/public/images/callkit/design/cruk1522a.png
new file mode 100644
index 000000000..90d414301
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1522a.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk1522b.png b/docs/.vuepress/public/images/callkit/design/cruk1522b.png
new file mode 100644
index 000000000..145ec8f2f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk1522b.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk161.png b/docs/.vuepress/public/images/callkit/design/cruk161.png
new file mode 100644
index 000000000..c2d24d802
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk161.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/cruk162.png b/docs/.vuepress/public/images/callkit/design/cruk162.png
new file mode 100644
index 000000000..2d0f519d2
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/cruk162.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/header.png b/docs/.vuepress/public/images/callkit/design/header.png
new file mode 100644
index 000000000..94b845df7
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/header.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/info.png b/docs/.vuepress/public/images/callkit/design/info.png
new file mode 100644
index 000000000..c2a1dc00a
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/info.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/multi_answer.png b/docs/.vuepress/public/images/callkit/design/multi_answer.png
new file mode 100644
index 000000000..670ff6ea8
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/multi_answer.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/multi_calling.png b/docs/.vuepress/public/images/callkit/design/multi_calling.png
new file mode 100644
index 000000000..5b269b51e
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/multi_calling.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/signal.png b/docs/.vuepress/public/images/callkit/design/signal.png
new file mode 100644
index 000000000..6ba8e7c55
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/signal.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/stream.png b/docs/.vuepress/public/images/callkit/design/stream.png
new file mode 100644
index 000000000..2fb1ff85c
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/stream.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/streamslist.png b/docs/.vuepress/public/images/callkit/design/streamslist.png
new file mode 100644
index 000000000..81cebe7ba
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/streamslist.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/text.png b/docs/.vuepress/public/images/callkit/design/text.png
new file mode 100644
index 000000000..7390f705f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/text.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/title.png b/docs/.vuepress/public/images/callkit/design/title.png
new file mode 100644
index 000000000..44f601523
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/title.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/video_answer.png b/docs/.vuepress/public/images/callkit/design/video_answer.png
new file mode 100644
index 000000000..4108c0500
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/video_answer.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/video_calling.png b/docs/.vuepress/public/images/callkit/design/video_calling.png
new file mode 100644
index 000000000..3377ddaa8
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/video_calling.png differ
diff --git a/docs/.vuepress/public/images/callkit/design/video_receive.png b/docs/.vuepress/public/images/callkit/design/video_receive.png
new file mode 100644
index 000000000..6a678bfa9
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/design/video_receive.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_call_pip.png b/docs/.vuepress/public/images/callkit/ios/1v1_call_pip.png
new file mode 100644
index 000000000..bd8cab9c4
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_call_pip.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_call_pip_flow.png b/docs/.vuepress/public/images/callkit/ios/1v1_call_pip_flow.png
new file mode 100644
index 000000000..7055c06cc
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_call_pip_flow.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_video_call.png b/docs/.vuepress/public/images/callkit/ios/1v1_video_call.png
new file mode 100644
index 000000000..e49dce58c
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_video_call.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_video_callee_invitation.png b/docs/.vuepress/public/images/callkit/ios/1v1_video_callee_invitation.png
new file mode 100644
index 000000000..ab7be5e86
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_video_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_video_caller_invitation.png b/docs/.vuepress/public/images/callkit/ios/1v1_video_caller_invitation.png
new file mode 100644
index 000000000..fc20fef3c
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_video_caller_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_video_float.png b/docs/.vuepress/public/images/callkit/ios/1v1_video_float.png
new file mode 100644
index 000000000..ce4ef35f2
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_video_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_video_notification_inapp.png b/docs/.vuepress/public/images/callkit/ios/1v1_video_notification_inapp.png
new file mode 100644
index 000000000..23162eb81
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_video_notification_inapp.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_video_ongoing.png b/docs/.vuepress/public/images/callkit/ios/1v1_video_ongoing.png
new file mode 100644
index 000000000..32aa83f9f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_video_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_voice_callee_invitation.png b/docs/.vuepress/public/images/callkit/ios/1v1_voice_callee_invitation.png
new file mode 100644
index 000000000..aa9807034
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_voice_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_voice_caller_invitation.png b/docs/.vuepress/public/images/callkit/ios/1v1_voice_caller_invitation.png
new file mode 100644
index 000000000..b8c399f92
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_voice_caller_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_voice_float.png b/docs/.vuepress/public/images/callkit/ios/1v1_voice_float.png
new file mode 100644
index 000000000..5a674e90b
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_voice_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_voice_notification_inapp.png b/docs/.vuepress/public/images/callkit/ios/1v1_voice_notification_inapp.png
new file mode 100644
index 000000000..476bb1010
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_voice_notification_inapp.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/1v1_voice_ongoing.png b/docs/.vuepress/public/images/callkit/ios/1v1_voice_ongoing.png
new file mode 100644
index 000000000..e6fee8d88
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/1v1_voice_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/backgroundCameraAccess.png b/docs/.vuepress/public/images/callkit/ios/backgroundCameraAccess.png
new file mode 100644
index 000000000..da7fdb24f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/backgroundCameraAccess.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/call_answer_flowchart.png b/docs/.vuepress/public/images/callkit/ios/call_answer_flowchart.png
new file mode 100644
index 000000000..3a8667415
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/call_answer_flowchart.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/call_hangup_flowchart.png b/docs/.vuepress/public/images/callkit/ios/call_hangup_flowchart.png
new file mode 100644
index 000000000..99bedeb9b
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/call_hangup_flowchart.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/call_mute_flowchart.png b/docs/.vuepress/public/images/callkit/ios/call_mute_flowchart.png
new file mode 100644
index 000000000..4cd8bc4c5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/call_mute_flowchart.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/call_resource_bundle.png b/docs/.vuepress/public/images/callkit/ios/call_resource_bundle.png
new file mode 100644
index 000000000..2b86dc380
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/call_resource_bundle.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/callkit_poster.png b/docs/.vuepress/public/images/callkit/ios/callkit_poster.png
new file mode 100644
index 000000000..146f8f902
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/callkit_poster.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/certificate_add.png b/docs/.vuepress/public/images/callkit/ios/certificate_add.png
new file mode 100644
index 000000000..10ff895a3
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/certificate_add.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/certificate_create.png b/docs/.vuepress/public/images/callkit/ios/certificate_create.png
new file mode 100644
index 000000000..caf4660b1
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/certificate_create.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/certificate_request.png b/docs/.vuepress/public/images/callkit/ios/certificate_request.png
new file mode 100644
index 000000000..ef8ed9911
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/certificate_request.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/certificate_voip.png b/docs/.vuepress/public/images/callkit/ios/certificate_voip.png
new file mode 100644
index 000000000..6f48f57c3
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/certificate_voip.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/certificate_voip_set.png b/docs/.vuepress/public/images/callkit/ios/certificate_voip_set.png
new file mode 100644
index 000000000..1e50cd735
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/certificate_voip_set.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_callee_invitation.png b/docs/.vuepress/public/images/callkit/ios/group_call_callee_invitation.png
new file mode 100644
index 000000000..b759703db
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_caller_user_selection.png b/docs/.vuepress/public/images/callkit/ios/group_call_caller_user_selection.png
new file mode 100644
index 000000000..e9a4b8f0d
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_caller_user_selection.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_float.png b/docs/.vuepress/public/images/callkit/ios/group_call_float.png
new file mode 100644
index 000000000..808a66d08
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_notification_inapp.png b/docs/.vuepress/public/images/callkit/ios/group_call_notification_inapp.png
new file mode 100644
index 000000000..3880ca597
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_notification_inapp.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_ongoing.png b/docs/.vuepress/public/images/callkit/ios/group_call_ongoing.png
new file mode 100644
index 000000000..a20ecd755
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_ongoing_add.png b/docs/.vuepress/public/images/callkit/ios/group_call_ongoing_add.png
new file mode 100644
index 000000000..e12c245c5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_ongoing_add.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_pip.png b/docs/.vuepress/public/images/callkit/ios/group_call_pip.png
new file mode 100644
index 000000000..c18c1720e
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_pip.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/group_call_pip_interaction.png b/docs/.vuepress/public/images/callkit/ios/group_call_pip_interaction.png
new file mode 100644
index 000000000..5703106d6
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/group_call_pip_interaction.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/incoming_call_flow.png b/docs/.vuepress/public/images/callkit/ios/incoming_call_flow.png
new file mode 100644
index 000000000..bac221b15
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/incoming_call_flow.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/incoming_call_flowchart.png b/docs/.vuepress/public/images/callkit/ios/incoming_call_flowchart.png
new file mode 100644
index 000000000..2b32ae046
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/incoming_call_flowchart.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/notification_call_answered.png b/docs/.vuepress/public/images/callkit/ios/notification_call_answered.png
new file mode 100644
index 000000000..f881ea4c3
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/notification_call_answered.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/notification_lock.png b/docs/.vuepress/public/images/callkit/ios/notification_lock.png
new file mode 100644
index 000000000..280d04b8e
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/notification_lock.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/notification_system.png b/docs/.vuepress/public/images/callkit/ios/notification_system.png
new file mode 100644
index 000000000..cef6e61d7
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/notification_system.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/project_runthrough.png b/docs/.vuepress/public/images/callkit/ios/project_runthrough.png
new file mode 100644
index 000000000..3fd8290e7
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/project_runthrough.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/quickstart_run.png b/docs/.vuepress/public/images/callkit/ios/quickstart_run.png
new file mode 100644
index 000000000..49eee6dc7
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/quickstart_run.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/signaling_busy_callee.png b/docs/.vuepress/public/images/callkit/ios/signaling_busy_callee.png
new file mode 100644
index 000000000..e62c8005f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/signaling_busy_callee.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/signaling_cancel_caller.png b/docs/.vuepress/public/images/callkit/ios/signaling_cancel_caller.png
new file mode 100644
index 000000000..cf9d0d099
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/signaling_cancel_caller.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/signaling_entire.png b/docs/.vuepress/public/images/callkit/ios/signaling_entire.png
new file mode 100644
index 000000000..298431006
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/signaling_entire.png differ
diff --git a/docs/.vuepress/public/images/callkit/ios/signaling_group.png b/docs/.vuepress/public/images/callkit/ios/signaling_group.png
new file mode 100644
index 000000000..89b770eef
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/ios/signaling_group.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_activation.png b/docs/.vuepress/public/images/callkit/product/rtc_activation.png
new file mode 100644
index 000000000..69e1e3b71
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_activation.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_billing_confirm.png b/docs/.vuepress/public/images/callkit/product/rtc_billing_confirm.png
new file mode 100644
index 000000000..153f953a5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_billing_confirm.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_capacity_total.png b/docs/.vuepress/public/images/callkit/product/rtc_capacity_total.png
new file mode 100644
index 000000000..c9974937f
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_capacity_total.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_package_purchase.png b/docs/.vuepress/public/images/callkit/product/rtc_package_purchase.png
new file mode 100644
index 000000000..2bd1f40e0
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_package_purchase.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_package_upgrade.png b/docs/.vuepress/public/images/callkit/product/rtc_package_upgrade.png
new file mode 100644
index 000000000..00dba8b13
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_package_upgrade.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_package_view.png b/docs/.vuepress/public/images/callkit/product/rtc_package_view.png
new file mode 100644
index 000000000..b0856bf8d
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_package_view.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_plus_activation.png b/docs/.vuepress/public/images/callkit/product/rtc_plus_activation.png
new file mode 100644
index 000000000..9ae32c3f5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_plus_activation.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_plus_confirm.png b/docs/.vuepress/public/images/callkit/product/rtc_plus_confirm.png
new file mode 100644
index 000000000..d72be01c3
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_plus_confirm.png differ
diff --git a/docs/.vuepress/public/images/callkit/product/rtc_plus_purchase.png b/docs/.vuepress/public/images/callkit/product/rtc_plus_purchase.png
new file mode 100644
index 000000000..6d29aeedd
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/product/rtc_plus_purchase.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_video_callee_invitation.png b/docs/.vuepress/public/images/callkit/web/1v1_video_callee_invitation.png
new file mode 100644
index 000000000..23a2a9438
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_video_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_video_caller_invitation.png b/docs/.vuepress/public/images/callkit/web/1v1_video_caller_invitation.png
new file mode 100644
index 000000000..eb507e7f6
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_video_caller_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_video_float.png b/docs/.vuepress/public/images/callkit/web/1v1_video_float.png
new file mode 100644
index 000000000..da41202e5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_video_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_video_ongoing.png b/docs/.vuepress/public/images/callkit/web/1v1_video_ongoing.png
new file mode 100644
index 000000000..210a156af
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_video_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_voice_callee_invitation.png b/docs/.vuepress/public/images/callkit/web/1v1_voice_callee_invitation.png
new file mode 100644
index 000000000..be7ab332b
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_voice_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_voice_caller_invitation.png b/docs/.vuepress/public/images/callkit/web/1v1_voice_caller_invitation.png
new file mode 100644
index 000000000..ecf24cdd5
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_voice_caller_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_voice_float.png b/docs/.vuepress/public/images/callkit/web/1v1_voice_float.png
new file mode 100644
index 000000000..b726980cb
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_voice_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/1v1_voice_ongoing.png b/docs/.vuepress/public/images/callkit/web/1v1_voice_ongoing.png
new file mode 100644
index 000000000..9ad15a129
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/1v1_voice_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/group_call_callee_invitation.png b/docs/.vuepress/public/images/callkit/web/group_call_callee_invitation.png
new file mode 100644
index 000000000..34c3ddc94
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/group_call_callee_invitation.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/group_call_caller_user_selection.png b/docs/.vuepress/public/images/callkit/web/group_call_caller_user_selection.png
new file mode 100644
index 000000000..f0713f518
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/group_call_caller_user_selection.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/group_call_float.png b/docs/.vuepress/public/images/callkit/web/group_call_float.png
new file mode 100644
index 000000000..1d82727aa
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/group_call_float.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/group_call_notification_inapp.png b/docs/.vuepress/public/images/callkit/web/group_call_notification_inapp.png
new file mode 100644
index 000000000..128c34698
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/group_call_notification_inapp.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/group_call_ongoing.png b/docs/.vuepress/public/images/callkit/web/group_call_ongoing.png
new file mode 100644
index 000000000..4dcad47ec
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/group_call_ongoing.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/group_call_ongoing_add.png b/docs/.vuepress/public/images/callkit/web/group_call_ongoing_add.png
new file mode 100644
index 000000000..563dcbb5b
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/group_call_ongoing_add.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/project_runthrough1.png b/docs/.vuepress/public/images/callkit/web/project_runthrough1.png
new file mode 100644
index 000000000..8ceb54582
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/project_runthrough1.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/project_runthrough2.png b/docs/.vuepress/public/images/callkit/web/project_runthrough2.png
new file mode 100644
index 000000000..33e99c881
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/project_runthrough2.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/project_runthrough3.png b/docs/.vuepress/public/images/callkit/web/project_runthrough3.png
new file mode 100644
index 000000000..586cb0549
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/project_runthrough3.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/project_runthrough4.png b/docs/.vuepress/public/images/callkit/web/project_runthrough4.png
new file mode 100644
index 000000000..b8d00d1b3
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/project_runthrough4.png differ
diff --git a/docs/.vuepress/public/images/callkit/web/quickstart_run.png b/docs/.vuepress/public/images/callkit/web/quickstart_run.png
new file mode 100644
index 000000000..c073405bb
Binary files /dev/null and b/docs/.vuepress/public/images/callkit/web/quickstart_run.png differ
diff --git a/docs/.vuepress/public/images/console/content_moderation.png b/docs/.vuepress/public/images/console/content_moderation.png
index be7eb5659..27e63aafd 100644
Binary files a/docs/.vuepress/public/images/console/content_moderation.png and b/docs/.vuepress/public/images/console/content_moderation.png differ
diff --git a/docs/.vuepress/public/images/console/operation_data_rtc_usage_all.png b/docs/.vuepress/public/images/console/operation_data_rtc_usage_all.png
new file mode 100644
index 000000000..2f0792b72
Binary files /dev/null and b/docs/.vuepress/public/images/console/operation_data_rtc_usage_all.png differ
diff --git a/docs/.vuepress/public/images/console/operation_data_rtc_usage_specific.png b/docs/.vuepress/public/images/console/operation_data_rtc_usage_specific.png
new file mode 100644
index 000000000..27a01e623
Binary files /dev/null and b/docs/.vuepress/public/images/console/operation_data_rtc_usage_specific.png differ
diff --git a/docs/.vuepress/public/images/console/push_activation.png b/docs/.vuepress/public/images/console/push_activation.png
index 302de3a86..ee5d5e9c2 100644
Binary files a/docs/.vuepress/public/images/console/push_activation.png and b/docs/.vuepress/public/images/console/push_activation.png differ
diff --git a/docs/.vuepress/public/images/console/server_api_activation.png b/docs/.vuepress/public/images/console/server_api_activation.png
index 2798cbdd7..e37ec9707 100644
Binary files a/docs/.vuepress/public/images/console/server_api_activation.png and b/docs/.vuepress/public/images/console/server_api_activation.png differ
diff --git a/docs/.vuepress/public/images/console/translation_activation.png b/docs/.vuepress/public/images/console/translation_activation.png
index 268681fb9..b24b08801 100644
Binary files a/docs/.vuepress/public/images/console/translation_activation.png and b/docs/.vuepress/public/images/console/translation_activation.png differ
diff --git a/docs/.vuepress/public/images/console/translation_statistics.png b/docs/.vuepress/public/images/console/translation_statistics.png
index 88cefa232..9ebb32b9c 100644
Binary files a/docs/.vuepress/public/images/console/translation_statistics.png and b/docs/.vuepress/public/images/console/translation_statistics.png differ
diff --git a/docs/.vuepress/sidebar/callkit.ts b/docs/.vuepress/sidebar/callkit.ts
new file mode 100644
index 000000000..1e4a267a4
--- /dev/null
+++ b/docs/.vuepress/sidebar/callkit.ts
@@ -0,0 +1,134 @@
+import path from "node:path";
+import fs from "node:fs";
+
+const getSubDirectories = (dir) =>
+ fs
+ .readdirSync(dir)
+ .filter((item) => fs.statSync(path.join(dir, item)).isDirectory());
+const CALL_DOC_PATH = path.resolve(__dirname, "../../callkit");
+const callKitPlatformList = getSubDirectories(CALL_DOC_PATH);
+
+const callKitSidebar = [
+ {
+ /*
+ text: 分组标题
+ children: 分组导航列表
+ text: 显示的文本
+ link: 链接地址
+ show: 不存在或者值为 true 时,菜单显示;存在并且值为 false 时,菜单不显示
+ only: 数组形式,只有在数组中的平台下显示
+ except: 数组形式,除了数组中指定的平台外都显示
+ collapsible: 子菜单是否允许展开/收起,true: 允许; false: 不允许。请参考「子菜单示例」
+ children: 子菜单。请参考「子菜单示例」
+ */
+ text: "产品介绍",
+ collapsible: true,
+ children: [
+ { text: "产品概述", link: "product_overview.html" },
+ { text: "开通服务", link: "product_activation.html" },
+ { text: "购买指南", link: "product_purchase.html" }
+ ]
+ },
+ {
+ text: "快速开始",
+ collapsible: true,
+ children: [
+ { text: "跑通示例项目", link: "sample_runthrough.html" },
+ { text: "快速开始", link: "quickstart.html" }
+ ]
+ },
+ {
+ text: "集成文档",
+ collapsible: true,
+ children: [
+ { text: "CallKit 架构", link: "architecture.html" },
+ { text: "集成 CallKit", link: "integration.html" },
+ { text: "权限", link: "permission.html", only: ["android"] },
+ {
+ text: "使用 LiveCommunicationKit", link: "livecommunicationkit.html", only: ["ios"]
+ },
+ { text: "画中画", link: "picture_in_picture.html", only: ["ios"] },
+ { text: "使用 Telecom", link: "telecom.html", only: ["android"] },
+ { text: "来电通知和悬浮窗", link: "float_top.html", only: ["android"] },
+ { text: "自定义资源", link: "customization.html" },
+ { text: "通话信令", link: "signaling.html" },
+ { text: "API 概览", link: "api_overview.html" },
+ {
+ text: "常见问题",
+ collapsible: true,
+ children: [
+ { text: "常见问题", link: "common_issue.html" },
+ ]
+ }
+ ]
+ },
+ {
+ text: "设计文档",
+ collapsible: true,
+ children: [{ text: "设计指南", link: "design_guide.html" }]
+ },
+ {
+ text: "历史文档",
+ collapsible: true,
+ children: [{ text: "历史文档", link: "easecallkit.html" }]
+ }
+];
+
+function buildCallKitSidebar() {
+ const result = {};
+ callKitPlatformList.forEach((platform) => {
+ const key = `/callkit/${platform}/`;
+ result[key] = callKitSidebar
+ .map((sidebar) =>
+ handleSidebarItem(platform, sidebar, CALL_DOC_PATH, "callkit")
+ )
+ .filter((s) => s);
+ });
+ return result;
+}
+
+function linkExists(platform: string, link: string, docPath: string): boolean {
+ try {
+ const filePath = `${docPath}/${platform}/${link.replace(/.html$/, ".md")}`;
+ return fs.existsSync(filePath);
+ } catch (e) {
+ console.error(`Error checking file existence: ${e}`);
+ return false;
+ }
+}
+
+function handleSidebarItem(platform, sidebar, docPath, kitType) {
+ const hasChildren =
+ sidebar.hasOwnProperty("children") && sidebar.children.length > 0;
+ const hasOnly = sidebar.hasOwnProperty("only") && sidebar.only.length > 0;
+ const hasExcept =
+ sidebar.hasOwnProperty("except") && sidebar.except.length > 0;
+
+ let needThisPlatform = true;
+ if (hasOnly) {
+ needThisPlatform = sidebar.only.indexOf(platform) > -1;
+ }
+ if (hasExcept) {
+ needThisPlatform = sidebar.except.indexOf(platform) == -1;
+ }
+
+ if (!needThisPlatform) {
+ return null;
+ }
+
+ if (hasChildren) {
+ let newchildren = sidebar.children
+ .map((s) => handleSidebarItem(platform, s, docPath, kitType))
+ .filter((s) => s);
+ if (newchildren.length > 0) {
+ return { ...sidebar, children: newchildren };
+ }
+ } else {
+ if (linkExists(platform, sidebar.link, docPath)) {
+ const newLink = `/${kitType}/${platform}/${sidebar.link}`;
+ return { ...sidebar, link: newLink };
+ }
+ }
+}
+
+export const CALL_KIT_SIDEBAR = buildCallKitSidebar();
diff --git a/docs/.vuepress/sidebar/index.ts b/docs/.vuepress/sidebar/index.ts
index 82aeee0b9..941954831 100644
--- a/docs/.vuepress/sidebar/index.ts
+++ b/docs/.vuepress/sidebar/index.ts
@@ -2,6 +2,7 @@ import { sidebar } from "vuepress-theme-hope";
import { DOC_SIDEBAR } from "./document";
import { CHAT_UIKIT_SIDEBAR, CHATROOM_UIKIT_SIDEBAR } from "./uikit";
import { PRIVATE_IM_SIDEBAR, PRIVATE_MEDIA_SIDEBAR } from "./private";
+import { CALL_KIT_SIDEBAR } from "./callkit";
import { PUSH_SIDEBAR } from "./push";
export const zhSidebar = sidebar({
@@ -589,6 +590,7 @@ export const zhSidebar = sidebar({
...DOC_SIDEBAR,
...CHAT_UIKIT_SIDEBAR,
...CHATROOM_UIKIT_SIDEBAR,
+ ...CALL_KIT_SIDEBAR,
"/private/im/": PRIVATE_IM_SIDEBAR,
"/private/media/": PRIVATE_MEDIA_SIDEBAR,
"/push": PUSH_SIDEBAR,
diff --git a/docs/.vuepress/styles/index.scss b/docs/.vuepress/styles/index.scss
index f8a12e46e..f0aac9626 100644
--- a/docs/.vuepress/styles/index.scss
+++ b/docs/.vuepress/styles/index.scss
@@ -101,4 +101,14 @@ $fade-amount: 20; // 每级字体颜色变淡的递减值
}
}
}
-}
\ No newline at end of file
+}
+
+.hint-container.details {
+ padding: 12px;
+ background: var(--bg-color-back);
+ border: 1px solid var(--border-color-dark);
+}
+
+.hint-container.details summary::before {
+ display: none;
+}
diff --git a/docs/README.md b/docs/README.md
index b4397bdf4..430eb7e3d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -98,13 +98,13 @@ starter:
- title: "CallKit"
platform:
- icon: /sdk/android.svg
- link: /document/android/easecallkit.html
+ link: /callkit/android/product_overview.html
text: Android
- icon: /sdk/iOS.svg
- link: /document/ios/easecallkit.html
+ link: /callkit/ios/product_overview.html
text: iOS
- icon: /sdk/web.svg
- link: /document/web/easecallkit.html
+ link: /callkit/web/product_overview.html
text: Web
projects:
- title: SDK/服务端功能
diff --git a/docs/callkit/README.md b/docs/callkit/README.md
new file mode 100644
index 000000000..39034e97c
--- /dev/null
+++ b/docs/callkit/README.md
@@ -0,0 +1,3 @@
+---
+title: CallKit
+---
diff --git a/docs/callkit/android/api_overview.md b/docs/callkit/android/api_overview.md
new file mode 100644
index 000000000..4e6cf44aa
--- /dev/null
+++ b/docs/callkit/android/api_overview.md
@@ -0,0 +1,82 @@
+# API 参考
+
+## 主要方法
+
+`CallKitClient` 中的主要方法如下表所示:
+
+| 方法 | 描述 | 参数 |
+| :------------------- | :----- | :-------------------------------------------- |
+| `init(context, config)` | 初始化 CallKit |- `context`: 上下文
- `config`: 配置对象 |
+| `startSingleCall(type, userId, ext)` | 发起一对一通话 | - `type`: 通话类型
- `userId`: 对方用户 ID
- `ext`: 扩展信息 |
+| `startGroupCall(groupId, ext)` | 发起群组通话 | - `groupId`: 群组 ID
- `ext`: 扩展信息 |
+| `endCall()` | 结束通话 | 无 |
+| `getCache()` | 获取缓存管理器 | 无 |
+
+## 通话类型
+
+通话类型 `CallType` 如下表所示:
+
+| 类型 | 描述 |
+| :------------------- | :----- |
+| `SINGLE_VIDEO_CALL` | 一对一视频通话 |
+| `SINGLE_VOICE_CALL` | 一对一语音通话 |
+| `GROUP_CALL` | 群组通话 |
+
+## 通话结束原因
+
+通话结束原因 `CallEndReason` 如下表所示:
+
+| 原因 | 描述 |
+| :------------------- | :----- |
+| `CallEndReasonHangup` | 正常挂断 |
+| `CallEndReasonCancel` | 本地用户取消通话 |
+| `CallEndReasonRemoteCancel` | 对方取消通话 |
+| `CallEndReasonRefuse` | 本地用户拒绝接听 |
+| `CallEndReasonRemoteRefuse` | 对方拒绝接听 |
+| `CallEndReasonBusy` | 忙线中 |
+| `CallEndReasonNoResponse` | 本地用户无响应 |
+| `CallEndReasonRemoteNoResponse` | 对方无响应 |
+| `CallEndReasonHandleOnOtherDevice` | 在其他设备接听 |
+| `CallEndReasonRemoteDrop` | 通话中断 |
+
+## 监听方法
+
+环信 CallKit 提供 `CallKitListener` 监听通话过程。你可以设置监听器用于处理通话相关的回调。
+
+**所有回调方法都不在主线程执行,需要使用 `runOnUiThread` 来更新 UI。**
+
+| 方法 | 描述 | 参数 |
+| :------------------- | :----- | :---------- |
+| `onEndCallWithReason(reason, callInfo)` | 通话结束回调 | - `reason`: 结束原因
- `callInfo`: 通话信息 |
+| `onCallError(errorType, errorCode, description)` | 通话错误回调 | - `errorType`: 错误类型
- `errorCode`: 错误码
- `description`: 错误描述 |
+| `onReceivedCall(userId, callType, ext)` | 收到通话邀请 | - `userId`: 邀请方的用户 ID
- `callType`: 通话类型
- `ext`: 扩展信息 |
+| `onRemoteUserJoined(userId, callType, channelName)` | 远端用户加入 | - `userId`: 用户 ID
- `callType`: 通话类型
- `channelName`: 频道名称 |
+| `onRemoteUserLeft(userId, callType, channelName)` | 远端用户离开 | - `userId`: 用户ID
- `callType`: 通话类型
- `channelName`: 频道名称 |
+| `onRtcEngineCreated(engine)` | RTC 引擎创建 | `engine`: RTC 引擎实例 |
+
+## 错误类型
+
+### 通话错误类型
+
+`CallErrorType` 类中提供三类通话错误类型:
+
+| 通话错误类型 | 描述 |
+|------|------|
+| `BUSINESS_ERROR` | 业务逻辑异常。 |
+| `RTC_ERROR` | 音视频异常,详见 [声网 RTC 错误码](https://doc.shengwang.cn/doc/rtc/android/error-code)。 |
+| `IM_ERROR` | 即时通讯 IM 异常,详见 [环信即时通讯 IM 错误码](/document/android/error.html) |
+
+### 业务错误类型
+
+`CALL_BUSINESS_ERROR` 类中提供三类业务错误类型:
+
+| 业务错误类型 | 描述 |
+|------|------|
+| `CALL_STATE_BUSY_ERROR` | 通话状态错误: 调用呼叫 API 时,当前设备不处于空闲状态。|
+| `CALL_PARAM_ERROR` | 参数错误:主要为呼叫 API 调用参数错误为空等。 |
+| `CALL_SIGNALING_ERROR` | 信令错误:大多为信令回复的方法中某些参数错误,例如,对方发的信令里缺少某种参数。 |
+
+### 获取日志
+
+- 日志 TAG 中包含 `Callkit` 字段的所有内容均为 CallKit 日志。你可以通过查看日志进行代码问题排查。关于如何获取日志,详见 [环信即时通讯 IM 文档](/document/android/log.html)。
+- 线上获取 SDK 日志,需要设备在登录状态下联系环信技术支持。技术支持获取到线上设备的日志,排查线上用户的问题。
\ No newline at end of file
diff --git a/docs/callkit/android/architecture.md b/docs/callkit/android/architecture.md
new file mode 100644
index 000000000..8d9f99242
--- /dev/null
+++ b/docs/callkit/android/architecture.md
@@ -0,0 +1,47 @@
+# CallKit 架构
+
+## 项目概述
+
+环信 Android CallKit 是基于环信即时通讯 IM SDK 和声网 RTC SDK 开发的实时音视频通话框架。项目采用 Kotlin 作为开发语言,使用 MVVM 架构模式来分离业务逻辑和 UI 展示。通过 Kotlin Flow(StateFlow/SharedFlow)进行响应式状态管理,确保 UI 能够及时响应数据变化。异步操作统一使用 Kotlin Coroutines 处理,提供流畅的用户体验。
+
+## 主要模块
+
+环信 CallKit 的整体架构采用模块化设计,各个模块职责清晰,便于维护和扩展。
+
+
+
+### CallKitClient
+
+`CallKitClient` 是 CallKit 的核心管理器,负责初始化、对外 API、通话状态、通话类型、资源回收,聚合内部模块。
+
+| 组件 | 说明 |
+| :--- | :--- |
+| `RtcManager` | - 管理声网 RTC 引擎:加入/离开频道、前后台质量、参与者、摄像头/麦克风、切换等。
- [查看更多功能实现](https://doc.shengwang.cn/doc/rtc/android/get-started/quick-start#%E5%AE%9E%E7%8E%B0%E5%B8%B8%E7%94%A8%E5%9B%9E%E8%B0%83)。 |
+| `SignalingManager` | 负责信令交互(邀请、Alert/仲裁、接听/拒绝/取消、超时控制),触发 UI(Activity/悬浮窗/Telecom)和 RTC 引擎执行对应的动作。[点击查看具体信令](signaling.html)。 |
+| `AudioController` | 负责铃声播放(外呼、来电、结束 ding),支持 assets/raw/绝对文件路径。 |
+| `FloatWindow` | 悬浮窗的显示。 |
+| `IncomingCallTopWindow` | 来电通知栏的显示。 |
+| `CallKitCache` | 用户信息、群组信息、Token 等缓存。 |
+| 其他 | 前台服务 `CallForegroundService`,通知工具 `CallKitNotifier`。 |
+
+### UI 层
+
+UI 层实现通话界面。
+
+| 组件 | 说明 |
+| :--- | :--- |
+| `BaseCallActivity` | 通话 Activity 基类:负责统一处理锁屏显示、动态权限申请、前后台切换、悬浮窗权限引导。 |
+| `SingleCallActivity` | 一对一通话界面。 |
+| `MultiCallActivity` | 群组通话界面。 |
+| `ViewModel` | `SingleCallViewModel`/`MultipleCallViewModel` 等:封装一对一通话/群组通话的业务动作(接听、挂断、展示悬浮窗等)。 |
+
+### Telecom
+
+Telecom 模块提供系统来电功能。
+
+| 组件 | 说明 |
+| :--- | :--- |
+| `TelecomHelper` | 统一处理 Telecom 服务。 |
+| `IncomingCallService` | `TelecomManager.addNewIncomingCall(...)` 触发系统来电界面。 |
+| `VoipConnectionService` | `ConnectionService` 实现,将系统接听/拒绝动作桥接到 `SignalingManager`。 |
+| `PhoneAccountHelper` | VoIP 账户的注册、启用检测和设置引导。 |
diff --git a/docs/callkit/android/common_issue.md b/docs/callkit/android/common_issue.md
new file mode 100644
index 000000000..ed6eb87e2
--- /dev/null
+++ b/docs/callkit/android/common_issue.md
@@ -0,0 +1,109 @@
+# 常见问题
+
+## 1. 初始化失败
+
+若 CallKit 初始化返回 `false`,你可进行如下排查:
+- 检查是否在主进程中初始化。
+- 确保 `Context` 不为 `null`。
+- 检查环信即时通讯 IM SDK 是否正确初始化。
+
+## 2. 通话无声音
+
+若通话过程中听不到声音,你可进行如下排查:
+- 检查 `RECORD_AUDIO` 权限是否已授权。
+- 检查设备音量设置。
+- 确认麦克风未被其他应用占用。
+
+## 3. 视频无画面
+
+若频通话看不到画面,你可进行如下排查:
+- 检查 `CAMERA` 权限是否已授权。
+- 确认摄像头未被其他应用占用。
+- 检查网络连接状况。
+
+## 4. 锁屏无法显示来电
+
+若锁屏状态下收不到来电通知,你可进行如下排查:
+- 检查 `USE_FULL_SCREEN_INTENT` 权限。
+- 确认通知权限已开启。
+- 检查电池优化设置。
+
+## 5. 悬浮窗无法显示
+
+若通话时无法显示悬浮窗,你可进行如下排查:
+- 检查 `SYSTEM_ALERT_WINDOW` 权限。
+- 在设置中手动开启应用的悬浮窗权限。
+- 部分厂商需要在应用管理中单独设置。
+
+## 6. 群组通话连接失败
+
+若群组通话无法正常连接,你可进行如下排查:
+- 确认群组 ID 正确且用户已加入群组。
+- 检查群组成员的授权权限,例如,麦克风和摄像头等权限。
+- 确认网络连接稳定。
+
+## 7. 群组通话人数限制
+
+若界面提示 “人数超出最大限制16人”,你可进行如下排查:
+- 确认群组通话人数。CallKit 限制群组通话最多 16 人参与(包括通话发起者)。若超过该限制,需要减少邀请的成员数量。
+- 可以分批进行多次通话。
+
+## 8. 权限被拒绝后通话自动结束
+
+用户拒绝权限后通话自动结束,这是正常的业务逻辑,原因是:
+- 麦克风权限是通话的基本要求,被拒绝后自动结束。
+- 视频通话需要摄像头权限,被拒绝后会自动结束。
+
+这种情况下,主叫方可自动发送取消信令并退出,被叫方可自动发送拒绝信令并退出。
+
+## 9. 悬浮窗权限申请
+
+悬浮窗权限申请失败或被拒绝,不影响通话功能,而且不会强制结束通话。
+
+可以通过设置手动开启悬浮窗权限,例如,对于某些手机,可以选择 **设置 -> 应用管理 -> 权限管理 -> 悬浮窗**。
+
+## 10. 前台服务和后台保活
+
+应用在后台时通话质量下降或断开:
+- CallKit 会自动启动前台服务保持通话状态。
+- 前台服务会保持摄像头和麦克风权限。
+- 建议将应用加入电池优化白名单。
+- 检查厂商的后台运行策略设置。
+
+## 11. 网络切换和断线重连
+
+若 WiFi 和移动网络切换时通话中断,声网 RTC 具备自动重连机制,因此,短暂的网络波动会自动恢复。若长时间断网,会触发通话结束回调。
+
+建议在网络稳定环境下进行通话。
+
+## 12. 音频路由和蓝牙设备
+
+若蓝牙耳机连接异常或音频路由错误,需检查是否已同意 `BLUETOOTH_CONNECT` 权限(Android 12+)。
+
+CallKit 会自动处理音频路由切换,支持扬声器、听筒、蓝牙耳机之间的切换。
+
+## 13. Telecom 系统集成问题
+
+若锁屏状态下无法显示系统来电界面:
+- 检查是否已申请 `MANAGE_OWN_CALLS` 等 Telecom 权限。
+- 检查 VoIP 账户是否已注册和启用。
+- 路径:以小米手机为例,点击电话拨号图标,点击右上角设置图标,选择 **高级设置 > 通话账户设置**,启用对应的 VoIP 账户。
+
+## 14. VoIP 账户未启用
+
+若提示 `PhoneAccount` 未注册或未启用:
+- CallKit 在使用 Telecom 框架时会检查 VoIP 账户状态。
+- 参考 DEMO 工程的 [MainActivity#checkPhoneAccount](https://github.com/easemob/easemob-demo-android/blob/main/app/src/main/kotlin/com/hyphenate/chatdemo/MainActivity.kt)函数实现,创建并启用 VoIP 账户。
+- 通过 `PhoneAccountHelper.getPhoneAccountStatus()` 检查 VoIP 账户的状态。
+
+## 15. 好友检查
+
+默认情况下,环信 CallKit 支持陌生人之间进行通话,即无需添加好友即可通话。若在即时通讯 IM 控制台 [开启了好友检查](/product/console/basic_user.html#好友关系检查),会导致非好友不能通过 CallKit 进行一对一通话,群组音视频通话信令也会受影响(邀请使用群定向消息,其他信令均为单聊消息)。建议不开启好友检查,后续 SDK 迭代会优化。
+
+## 16. 锁屏/应用在后台时通话页面显示
+
+若要在设备锁屏或应用在后台时显示通话页面,需要手动申请权限(以小米手机为例):
+
+1. 锁屏时: **设置** → **应用设置** → **应用管理** → [你的应用] → **权限管理** → **其他权限** → **锁屏显示** → **允许**。
+2. 应用在后台时:**设置** → **应用设置** → **应用管理** → [你的应用] → **权限管理** → **其他权限** → **后台弹出页面** → **允许**。
+
diff --git a/docs/callkit/android/customization.md b/docs/callkit/android/customization.md
new file mode 100644
index 000000000..cf824d6f4
--- /dev/null
+++ b/docs/callkit/android/customization.md
@@ -0,0 +1,194 @@
+# 自定义
+
+## 铃声配置
+
+CallKit 支持发起呼叫时的声音、接收呼叫时的声音以及被挂断时的声音。建议铃声文件格式为 MP3、WAV 等,铃声时长为 1-20 秒,文件大小不超过 1 MB。
+
+默认铃声策略如下:
+ - 结束提示音(DING):如未设置,则不播放提示音(保持静音)。
+ - 异常降级:自定义铃声解码/播放异常时,非 DING 铃声会自动降级为系统铃声;DING 不降级。
+ - 循环规则:非 DING 铃声循环播放,DING 只播放一次。
+
+你可以自定义铃声:
+
+```kotlin
+val config = CallKitConfig().apply {
+ // 支持三种来源:assets、res/raw、绝对路径
+ // 方式 1:使用 assets 文件夹中的文件
+ incomingRingFile = "assets://incoming_ring.mp3"
+ outgoingRingFile = "assets://outgoing_ring.mp3"
+ dingRingFile = "assets://ding.mp3"
+
+ // 方式 2 :使用 res/raw 文件夹中的文件
+ // incomingRingFile = "raw://incoming_ring"
+
+ // 方式 3 :使用绝对路径
+ // incomingRingFile = "/storage/emulated/0/Download/incoming_ring.mp3"
+}
+CallKitClient.init(context, config)
+```
+
+## 通话超时设置
+
+CallKit 内部呼出/呼入超时时间默认 30 秒,开发者可以通过以下代码实现自定义超时时间。
+```
+val config = CallKitConfig().apply {
+ //(可选)配置通话超时时间(秒)
+ callTimeout = 30 // 30秒
+}
+CallKitClient.init(context, config)
+```
+
+## 布局/样式
+
+开发者可以通过修改布局文件源码(`ease-call-kit/src/main/res/layout/`) 的方式或者在应用层添加一个同名的文件来实现自定义布局。注意修改后或者新的布局文件需要包含原有布局文件里的所有资源,否则运行时会报空指针异常。允许开发者添加新的资源控件、调整控件位置、背景等。
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `activity_single_call.xml` | 一对一通话根布局容器,承载不同状态子视图(incoming/outgoing/connected)。 |
+| `activity_multi_video_call.xml` | 多人通话根布局容器,含成员网格/工具栏等。 |
+| `activity_invite_group_members.xml` | 群成员邀请页面。 |
+| `callkit_titlebar_view.xml` | 通话页通用标题栏组件(返回、标题、右侧操作)。 |
+| `view_incoming_video_single.xml`
`view_incoming_voice_single.xml` | 来电界面(视频/语音)。 |
+| `view_outgoing_video_single.xml`
`view_outgoing_voice_single.xml` | 外呼界面(等待对方接听)。 |
+| `view_connected_video_single.xml`
`view_connected_voice_single.xml` | 通话中界面(视频/语音)。 |
+| `view_incoming_multiple.xml`
`view_connected_multiple.xml` | 群组通话来电/通话中视图。 |
+| `view_call_member.xml`
`view_multi_video_call_member.xml` | 成员头像、昵称、音量/状态指示项。 |
+| `callkit_float_window_video.xml`
`callkit_float_window_voice.xml` | 后台悬浮窗视图(视频小窗/语音小窗)。 |
+| `callkit_incoming_call_top_window.xml` | 顶部来电条(可滑动收起/接听/挂断)。 |
+| `callkit_fragment_base_list.xml` | 通用列表容器(内含 RecyclerView/刷新容器)。 |
+| `callkit_layout_default_no_data.xml`
`callkit_layout_no_data_show_nothing.xml` | 无数据/空占位视图。 |
+| `callkit_layout_group_member_select_item.xml` | 群成员选择单元项(头像/昵称/选中态)。 |
+
+## 图标与图形资源
+
+开发者可直接在源码中使用同名文件替换,或者在应用层对应的 `res/drawable*` 添加一个同名文件,即可实现资源文件的替换。
+
+### 功能图标
+
+#### 通话控制
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_end.png` | 挂断按钮。 |
+| `callkit_accept_tel.png`
`callkit_accept_video_camera.png` | 接听按钮(语音/视频)。 |
+| `callkit_decline.png` | 拒绝按钮。 |
+| `callkit_phone_pick.png` | 电话接听图标。 |
+
+#### 音视频控制
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_mic_on.png`
`callkit_mic_off.png` | 麦克风开启/关闭。 |
+| `callkit_mic_off_small.png` | 小尺寸麦克风关闭图标。 |
+| `callkit_speaker_on.png`
`callkit_speaker_off.png` | 扬声器开启/关闭。 |
+| `callkit_speaker_wave.png` | 扬声器音波图标。 |
+| `callkit_video_camera_on.png`
`callkit_video_camera_off.png` | 摄像头开启/关闭。 |
+| `callkit_camera_front.png`
`callkit_camera_back.png` | 前置/后置摄像头切换。 |
+
+#### 网络质量指示
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_network_good.png`
`callkit_network_poor.png`
`callkit_network_worse.png`
`callkit_network_none.png` | 网络质量指示器(优秀/一般/差/无网络)。 |
+
+#### 界面元素
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_default_avatar.png` | 默认用户头像。 |
+| `callkit_default_group_avatar.png` | 默认群组头像。 |
+| `callkit_invite.png` | 邀请成员图标。 |
+| `callkit_float.png` | 悬浮窗图标。 |
+| `callkit_connecting.png` | 连接中图标。 |
+| `callkit_array_left.png` | 左箭头。 |
+
+#### 复选框状态
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_checkbox_select.png`
`callkit_checkbox_unselect.png`
`callkit_checkbox_available.png` | 选中/未选中/可选择状态。 |
+
+### 背景资源
+
+背景资源存放在 `drawable-xxxhdpi` 中。
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_view_background.webp` | 通话界面背景。 |
+| `callkit_empty_layout.png` | 空状态占位图。 |
+
+## 文案资源
+
+开发者可直接修改 `res/values/callkit_strings.xml` 内资源的定义源码,或者在应用层的 `res/values/strings.xml` 文件中添加相同资源 ID 的文案资源实现文案的替换。
+
+### 基础状态
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_loading` | 加载中... |
+| `callkit_connecting` | 连接中/等待对方接受邀请... |
+| `callkit_waiting`
`callkit_calling` | 待接听/呼叫中。 |
+
+### 通话邀请提示
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+|`alert_request_video`
`alert_request_voice`
`alert_request_multiple_video` | 一对一视频/一对一语音/群组视频通话邀请提示(支持用户名参数 %1$s)。|
+
+### 通话操作
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_accept`
`callkit_decline` | 接听/挂断。 |
+| `callkit_end` | 结束通话。 |
+
+### 功能控制
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_flip` | 摄像头翻转。 |
+| `callkit_mike_on`
`callkit_mike_off` | 麦克风已开/已关。 |
+| `callkit_speaker_on`
`callkit_speaker_off` | 扬声器已开/已关。 |
+| `callkit_camera_on`
`callkit_camera_off` | 摄像头已开/已关。 |
+
+### 通话类型与群组
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_inviting_you_to_a_group_call`
`callkit_inviting_you_to_a_video_call`
`callkit_inviting_you_to_a_voice_call` | 群通话/视频/语音邀请文案。 |
+| `callkit_group_call` | 群组通话。 |
+| `callkit_add` | 添加成员(支持数量参数 %1$d)。 |
+| `callkit_over_max_members` | 超过最大人数限制提示(支持数量参数 %1$d)。 |
+
+### 通话状态与结果
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `callkit_call_duration` | 通话时长显示(支持时间参数 %1$s)。 |
+| `callkit_self_cancel`
`callkit_remote_cancel` | 自己取消/对方取消。 |
+| `callkit_self_refused`
`callkit_remote_refused` | 自己拒绝/对方拒绝。 |
+| `callkit_self_no_response`
`callkit_remote_no_response` | 自己未接听/对方无响应。 |
+| `callkit_remote_busy` | 对方忙线中。 |
+| `callkit_remote_drop` | 通话中断。 |
+
+ ### 多设备处理
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| `The_other_is_received`
`callkit_handle_on_other_device` | 其他设备已接听。 |
+| `The_other_is_refused`
`callkit_refused_on_other_device` | 其他设备已拒绝/结束通话。 |
+| `The_other_is_busy` | 对方忙线中。 |
+
+### 多语言支持
+
+CallKit 提供完整的中文本地化支持,所有英文文案均有对应的中文翻译。
+
+英文文案默认路径应为 `res/values/callkit_strings.xml`,中文文案路径为 `res/values-zh/callkit_strings.xml`。文案的主要差异如下:
+
+| 英文 | 中文 |
+| :------------------- | :----- |
+| `Mike on` | `麦克风已开` |
+| `Speaker off` | `扬声器已关` |
+| `Remote Refused` | `对方拒绝接听` |
+| `Call Duration %1$s` | `通话时长 %1$s` |
\ No newline at end of file
diff --git a/docs/callkit/android/design_guide.md b/docs/callkit/android/design_guide.md
new file mode 100644
index 000000000..7fca5d6aa
--- /dev/null
+++ b/docs/callkit/android/design_guide.md
@@ -0,0 +1,521 @@
+# 通话套件人机交互界面工具包设计指南
+
+
+
+环信通话套件人机交互界面工具包基于环信移动端单/群聊人机交互界面工具包进行设计,设计原则、全局样式、小控件样式均复用自环信单/群聊人机交互界面工具包,详见 [单/群聊人机交互界面工具包](https://doc.easemob.com/uikit/chatroomuikit/android/design_guide.html)。
+
+## 总设计原则
+
+- 功能与行为上确保通用、普遍、一般。
+- 风格上易于自定义。
+
+## 全局样式
+
+### UIKit 色彩规范
+
+#### 颜色配置说明
+
+##### 颜色类
+
+一般颜色类(Color Class)分为八类:
+- 主题色(Theme Color):Primary、 Secondary、Error 三类;
+- 渐变主题色(Primary Gradient)一类(含 8 种);
+- 透明色(Alpha Color):On Light、On Dark 两类;
+- 中性色(Neutral Colors):Neutral、Neutral Special 两类;
+
+##### 颜色模式
+
+颜色模式(Hsla Model)为比较直观的 hsla 模式:
+- 整个模型是一个圆柱体,圆柱体底面周长划分为 360°,对应不同的色相(Hue);
+- 圆柱体的半径为饱和度(Saturation),圆心为 0(最灰),半径值为 100(最艳);
+- 圆柱体的高为亮度(Lightness),起始点为 0(纯黑色),中心点是 50(标准色,),结束点为 100(纯白色)。
+
+##### 模型概览
+
+
+
+#### 三种主题色的色彩规范
+
+##### 用户可配项
+
+用户可设定颜色类的可配项 Hue(0-360)为任意数值,修改后每类颜色的色相会发生变化,以贴合用户场景所需要的主题颜色(Theme Color)。
+Hue 值(0-360)与色相的对应关系大致如以下图示所例:
+
+
+
+用户可依据自身产品的品牌色指定色相数值(Hue),从而确认主题色 Primary(主要用于 UI 组件中关键操作与重要文本展示,如推荐的 action、高亮显示的文本等),以及用于积极提示的 Secondary,和表示警示提示的 Error。
+
+
+
+##### 饱和度
+
+饱和度(Saturation)不开放给用户设置,三种主题色 Primary、 Secondary、Error 默认饱和度为 100%,Neutral 默认为 8%,Neutral Special 默认为 36%
+
+
+
+##### 亮度级别
+
+亮度(Lightness)百分比用户不可随意设置,每个颜色类提供:0(0%) / 1(10%) / 2(20%) / 3(30%) / 4(40%) / 5(50%) / 6(60%) / 7(70%) / 8(80%) / 9(90%) / 95(95%) / 98(98%) / 100(100%)十三个级别供用户可选;
+
+
+
+##### 示例
+
+如指定主题色 Primary 色相(Hue)为 203,成功色 Secondary 色相(Hue)为 155,警示色 Error 色相(Hue)为 350,则会生成如下 39 种主题色可供用户在指定 UI 件块(View)颜色时使用:
+
+
+
+其中,主题色 Primary 的 L5 为亮色模式下的基色(Key Color),L6 为暗色模式下的基色(Key Color)。所有的颜色体系都是依照基色生成。
+
+#### 渐变主题色规范
+
+渐变主题色(Primary Gradient)是由 Primary 色派生出的渐变色,为线性渐变(Linear Gradient),渐变方向依图示坐标系分为 8 类:
+
+
+
+##### 渐变色的起始色
+
+渐变色中 Start Color 规则和 Primary 类的色值保持一致;
+
+
+
+##### 渐变色的结束色
+
+End Color 用户可配置色相(Hue),亮度以 0(20%) / 1(30%) / 2(40%) / 3(50%) / 4(60%) / 5(70%) / 6(75%) / 7(80%) / 8(85%) / 9(90%) / 95(95%) / 98(98%) / 100(100%)(对应 Primary 的 13 级亮度梯度值)为固定梯度值
+
+以下以 Hue:233 为例,按照 End Color 颜色公式依旧得到 13 级颜色:
+
+
+
+起始色和结束色结合,得到相应的渐变结果
+
+
+
+##### 渐变主题色可配项
+
+用户仅可配置渐变色中 End Color 的色相(Hue)以达成与用户业务场景符合的渐变颜色效果;
+
+##### 示例
+
+用户设置 End Color Hue = 233,选择渐变方向为“↓”,则可得到如下效果:
+
+
+
+如使用渐变主题色,那么它将替代掉所有应用于背景色的 Primary 色
+
+
+
+但一般不替代 UI 件块的前景色,因为没有什么意义,且有干扰文字阅读的可能性
+
+
+
+#### 透明色(Alpha)的规范
+
+在本案内带有透明度的组件仅有模态背景色、轻提示背景色,应用范围有限,单独定义两个特殊的颜色类用于以上四种组件:Alpha onlight(hsl0, 0%, 0%) 和 Alpha ondark(hsl0, 0%, 100%),Alpha 值被指定为 0(0.0) / 1(0.1) / 2(0.2) / 3(0.3) / 4(0.4) / 5(0.5) / 6(0.6) / 7(0.7) / 8(0.8) / 9(0.9) / 95(0.95) / 98(0.98) / 100(1.0) 十三个梯度值,共 26 种颜色用例,以调整组件的背景色透明度。
+
+
+
+Alpha onlight 和 Alpha ondark 均为默认值,无任何可配置项。
+
+### 中性色
+
+#### 中性色
+
+中性色(Neutral)仅有一个可配项:色相(Hue),饱和度(Saturation)固定值为 8,亮度级别(Lightness level)也和主题色相同,分为 0(0%) / 1(10%) / 2(20%) / 3(30%) / 4(40%) / 5(50%) / 6(60%) / 7(70%) / 8(80%) / 9(90%) / 95(95%) / 98(98%) / 100(100%)十三个级别供用户可选;
+
+
+
+Neutral 和 Primary 的默认 Hue 值(色相)相同,也建议用户设置和主题色相同的 Hue 值已达成主题颜色和无彩色系的配套。但这仅仅是建议;
+
+#### 示例
+
+如指定主题色 Primary 色相(Hue)为 203,饱和度(Saturation)固定值为 100%,中性色(Neutral)则也指定色相(Hue)为 203,饱和度(Saturation)固定值为 8%,则得到以下色列可供用户选择使用:
+
+
+
+其中,L98 为亮色模式下背景色的主色,L1 为亮色模式下前景色的主色;L1 为暗色模式下背景色的主色,L98 为暗色模式下前景色的主色。
+
+
+
+### 特殊中性色
+
+特殊中性色 Neutral Special 主要用于级别低于 Primary 和 Secondary 的强调信息,如当前页面状态、消息发送者的昵称等。
+Neutral Special 和 Primary 的默认 Hue 值(色相)类似,为近似色,也建议用户设置和主题色近似的 Hue 值已达成主题色和无彩色系的配套。但这仅仅是建议;
+
+
+
+
+
+#### 示例
+
+如指定主题色 Primary 色相(Hue)为 203,特殊中性色(Neutral)通过相似色原理(正负 30 度内)指定色相(Hue)为 220,饱和度(Saturation)固定值为 36%,则得到以下色列可供用户选择使用:
+
+
+
+## 主题
+
+本期主题分为 1 种,依据场景仅保留黑暗(Dark)色彩模式。
+
+(主题样式图片展示)
+
+## 图标
+
+### 图标模板
+
+图标(Icon)参照 Material Icon Font 的模板 ,以 24 为基本栅格,须在安全区域(20x20 的中心区域)内绘制,基本描边控制为 1.5 栅格。
+
+
+
+### 图标命名
+
+为防止将图标语意固定,icon 命名需要尽力避免定义操作行为,而是以“看见什么就是什么“进行命名,方便相同图标在不同操作行为下的复用,例如:
+
+
+
+## 字体
+
+### 字族(Font Family)
+
+#### iOS 字族
+
+默认 SF Pro 为基本西文(拉丁字母、希腊字母、西里尔字母等)字体(Typography);
+默认 SF Arabic、SF Hebrew 等为基本右向左(Dextral-sinistral)文字字体;
+默认苹方(PingFang SC、TC、HK)为中文(简体中文、繁体中文、香港繁体中文)字体;
+
+#### Android 字族
+
+默认 Roboto 为基本西文(拉丁字母、希腊字母、西里尔字母等)字体;
+默认 Noto Sans Arabic、Noto Sans Hebrew 等为基本右向左(Dextral-sinistral)文字字体;
+默认思源黑体(Noto Sans SC、TC、HK)为中文(简体中文、繁体中文、香港繁体中文)字体;
+
+#### Web 字族
+
+默认 Roboto 为基本西文(拉丁字母、希腊字母、西里尔字母等)字体;
+默认 Noto Sans Arabic、Noto Sans Hebrew 等为基本右向左(Dextral-sinistral)文字字体;
+默认思源黑体(Noto Sans SC、TC、HK)为中文(简体中文、繁体中文、香港繁体中文)字体;
+
+### 字号
+
+#### 最小字号
+
+移动端最小字号(Font Size)为:11;web 端最小字号为:12
+
+#### 字号规则
+
+除移动端最小字号外,字号以 2 为梯度递增:
+11,12,14,16,18,20
+
+### 字重
+
+字重(Font Weight)分为标准(Regular, 400)、中等(Medium,510)、加粗(semibold,590)三种;
+在一些跨平台框架中,如遇不支持设置非百位整数字重,则取近似值百位整数;
+如字族没有 semibold,则以 bold 替换。
+
+### 行高
+
+行高(Line height)依照以下固定值(字号/行高):
+11/14,12/16,14/20,16/22,18/26,20/28。
+
+### 字体角色
+
+字体角色(Font Role)分为 3 类:
+大标题 Headline、标题 Title、标签 Label、正文 Body
+需要注意的是,这些角色只是推荐的角色指示,并不具有完全的指定性,具体使用什么角色的字体需依照所使用的组件的实际情况(组件内信息的层级重要性,越重要的越大越重)而使用。
+
+### 字体 Token
+
+依照依照 4.1-4.5 规则,设定以下西文字体排版 token:
+
+
+
+简体中文字体 token 示意:
+
+
+
+## 效果
+
+所应用的效果(Effects)主要分为两种:背景模糊(Background Blur)和阴影(Shadow)。
+
+### 背景模糊
+
+背景模糊(Background Blur)主要应用于组件背景色使用 Alpha color 时,如组件背景色的透明度会造成组件前后层级干扰的话,则推荐使用背景模糊解决,
+也应用于模态显示的弹出层的背景虚化;
+
+背景模糊的模糊半径值默认为 20
+
+```
+/* bg_blur_modal */ backdrop-filter: blur(20);
+```
+
+### 阴影
+
+阴影(Shadow)应用于弹窗(Alert)、浮层(pop)、抽屉(Drawer)等,为区分层级,凸显聚焦的组件。
+
+#### 阴影型号
+
+阴影分为小(small)、中(medium)、大(Large)三种型号(Size),应用于不同尺寸的组件中,总体原则为:越小的组件越推荐使用小的阴影、反之越大的组件推荐使用大的阴影;圆角越小的组件越推荐使用小的阴影、反之亦然。
+
+#### 阴影 token
+
+为保证阴影效果自然柔和,每个阴影都有两层不同偏移、不同模糊度、不同透明度的值。同时针对亮色/暗色模式有两套不同颜色的阴影。
+
+**Shadow on Light:**
+
+```
+/* shadow/onlight/large */
+box-shadow: x0 y24 blur36 color(Neutral3) Alpha0.15, x8 y0 blur24 color(Neutral1) Alpha0.1
+
+/* shadow/onlight/medium */
+box-shadow: x0 y4 blur4 color(Neutral3) Alpha0.15, x2 y0 blur8 color(Neutral1) Alpha0.1
+
+/* shadow/onlight/small */
+box-shadow: x0 y1 blur3 color(Neutral3) Alpha0.15, x1 y0 blur2 color(Neutral1) Alpha0.1
+```
+
+
+
+**Shadow on Dark:**
+
+```
+/* shadow/onlight/large */
+box-shadow: x0 y24 blur36 color(Neutral4) Alpha0.15, x8 y0 blur24 color(Neutral1) Alpha0.1
+
+/* shadow/onlight/medium */
+box-shadow: x0 y4 blur4 color(Neutral4) Alpha0.15, x2 y0 blur8 color(Neutral1) Alpha0.1
+
+/* shadow/onlight/small */
+box-shadow: x0 y1 blur3 color(Neutral4) Alpha0.15, x1 y0 blur2 color(Neutral1) Alpha0.1
+```
+
+
+
+## 圆角
+
+### 一般圆角
+
+一般圆角(Radius)分为 None(r=0)、Extra Small(r=4)、Small(r=8)、Medium(r=12)、Large(r=16)、Extra Large(r=½ Height)六个枚举值,
+一般情况下组件的四个圆角为同一值
+
+
+
+#### Extra Small(r=4)
+
+通常适用于如下组件:
+- Button(Small Radius)
+- Input(Small Radius)
+- Float(Small Radius)
+- Message Bubble(Small Radius)
+- Avatar(Small Radius)
+- Popover
+- Global Broadcast(Small Radius)
+
+#### Small(r=8)
+
+通常适用于如下组件:
+- Alert(Small Radius)
+- Drawer(Small Radius)
+
+#### Medium(r=12)
+
+通常适用于如下组件
+本案暂不涉及
+
+#### Large(r=16)
+
+通常适用于如下组件:
+- Input Area(Large Radius)
+- Alert(Large Radius)
+- Drawer(Large Radius)
+- Float(Large Radius)
+
+#### Extra Large(r=½ Height)
+
+通常适用于如下组件:
+Input Area(Large Radius)
+Alert(Large Radius)
+Drawer(Large Radius)
+Message Bubble(Large Radius)
+
+### 特殊圆角
+
+特殊圆角应用于有背景色的 IM 聊天消息组件:
+Message Bubble(Large Radius)
+
+
+
+## 小控件
+
+小控件(Widgets)为最基础的视觉交互模块。
+
+### 按钮
+
+按钮组件(Button)在通话界面开发工具包中仅包含图标按钮一种,分为大、中、小三种尺寸、支持前景色、背景色配置。支持点击操作。
+
+
+
+### 头像
+
+本项目中的头像(Avatar)组件,复用环信单/群聊人机交互界面工具包的相关组件。依据当前视图需要,显示对应的头像值。
+
+
+
+### 文本
+
+本项目中的文本(Text),分为三种样式:标题(Title)、小标题(Subtitle)、标签(Label)三类。
+
+#### 标题
+
+标题(Title)用于显示当前控件或视图中,最重要的文本信息,对应的字体排版 token 为:Title/Large
+
+
+
+#### 小标题
+
+小标题(Subtitle)用于显示当前控件或视图中,次重要的文本信息,对应的字体排版 token 为:Title/Small
+
+
+
+#### 标签
+
+标签(Label)用于显示当前控件或视图中,需解释说明的文本,对应的字体排版 token 为:Label/Small(web端)、Label/Extra Small(mobile端)。
+
+
+
+### 操作项
+
+操作项(Action Item)为控制当前视图功能的主要操作项,如:接听、挂断、麦克风和摄像头的开关等等。支持点击操作
+大小(Size)上分为大(Large)、标准(Standard)、小(Small)三种尺寸,状态(Status)上分为普通(Normal)、点按(Pressed)、禁用(Disabled)三种状态,样式上分为开、关两种样式。底部说明文字支持显示/隐藏。
+
+
+
+### 流信息
+
+流信息(stream info)用以展示当前视频流的相关信息和状态,支持显示一个名称字段和至多两个icon状态显示。
+
+
+
+### 信号展示
+
+信号展示(Signal)用以展示当前视频流的信号强度,分为未知、信号弱、信号中等、信号强四种状态。
+
+
+
+### 背景图
+
+背景图(Background Picture)用于界面的背景图展示,支持用户自定义图片。
+
+
+
+## 控件(Components)
+
+### 顶部条
+
+顶部条(TopBar)用于展示当前视图标题,并可对当前页面进行整体的控制。
+支持左侧一个操作(最小化),右侧一个操作(一般为群组通话添加成员),并支持两个操作的显示/隐藏。
+中间展示三个字段:头像、当前视图名称(title)、附加消息(Subtitle),并支持显示信息的显示/隐藏。
+
+
+
+### 通话操作
+
+通话操作(Actions)为针对当前会话的相关操作项集合。
+操作数量上依照当前视图场景的业务需求可做增减。
+
+
+
+### 视频流
+
+视频流(Video Stream)展示会话中拉到或者本地获取的视频流,左下角展示流信息,右上角展示当前流信号强度。
+
+
+
+### 视频流列表
+
+在多人会话中,多个视频流组成的列表(Video Stream list),拥有一般列表展示状态和聚焦列表展示状态。
+
+
+
+### 通话浮窗
+
+通话浮窗(PiP)用以在通话最小化时显示会话状态/一对一视频通话中次要视频流的展示,分为群通话、一对一视频通话样式,且悬浮窗可被收起。
+
+
+
+### 来电通知
+
+来电通知(Incoming Call Notification)用以在App内与当前通话不相关页面展示时的来电提示。
+以条幅通知的形式展示在界面顶部,显示字段有:头像、主标题、次标题、通话成员头像列表;支持接听、挂断、点击展示通话详情三个点击操作。
+并依照当前Chat UIKit主题,显示不同的模式(onlight/ondark)。
+
+
+
+## 视图(View)
+
+## 一对一音频通话视图
+
+一对一音频通话视图(One-on-One Audio Call View) 包括主叫视图(receive)、被叫视图(answer)和通话中视图(calling)。
+
+### 主叫视图
+
+主叫视图(receive)用于一对一音频通话接通前的展示和操作,分为顶部信息、底部操作、背景图三部分。
+
+
+
+### 被叫视图
+
+被叫视图(answer)用于接到一对一音频通话邀请时的展示和操作,分为顶部信息、底部操作、背景图三部分。
+
+
+
+### 通话中视图
+
+通话中视图(calling)用于一对一音频通话中的展示和操作,分为顶部信息、底部操作、背景图三部分。
+
+
+
+## 一对一视频通话视图
+
+一对一视频通话视图(One-on-One Video Call View)包括主叫视图(receive)、被叫视图(answer)和通话中视图(calling)。
+
+### 主叫视图
+
+主叫视图(receive)用于一对一视频通话接通前的展示和操作,分为顶部信息、底部操作、本地视频流展示三部分。
+
+
+
+### 被叫视图
+
+被叫视图(answer)用于接到一对一视频通话邀请的展示和操作,分为顶部信息、底部操作、本地视频流展示三部分。
+
+
+
+### 通话中视图
+
+通话中视图(calling)用于一对一视频通话中的展示和操作,分为顶部信息、底部操作、双方视频流展示三部分。
+
+
+
+## 多人通话视图(Multi Call View)
+
+### 被叫视图
+
+被叫视图(answer)用于接到多人通话接邀请的展示和操作,分为顶部信息、底部操作、本地视频流展示三部分。
+
+
+
+### 通话中视图
+
+通话中视图(calling)用于多人通话进行中的展示和操作,分为顶部信息、底部操作、多人视频流展示三部分。
+
+
+
+## 通话相关消息样式
+
+
+
+关于通话相关消息样式(Call Msg),详见 [单/群聊人机交互界面工具包](/uikit/chatroomuikit/android/design_guide.html)。
+
+
+## 设计资源
+
+设计资源(Design Resources)详见 [Figma 链接](https://www.figma.com/community/file/1540653110561556906/easemob-callkit)。
diff --git a/docs/callkit/android/easecallkit.md b/docs/callkit/android/easecallkit.md
new file mode 100644
index 000000000..1f85307ad
--- /dev/null
+++ b/docs/callkit/android/easecallkit.md
@@ -0,0 +1,471 @@
+# EaseCallKit 使用指南
+
+
+
+## 功能概述
+
+`EaseCallKit` 是一套基于环信 IM 和声网音视频结合开发的音视频 UI 库,实现了一对一语音和视频通话以及多人音视频通话的功能。基于 `EaseCallKit` 可以快速实现通用音视频功能。
+
+**利用 `EaseCallKit` 通话过程中,使用环信 ID 加入频道,方便音视频视图中显示用户名。如果用户不使用 `EaseCallKit` 而直接调用声网 API,也可以直接使用数字 UID 加入频道。**
+
+:::tip
+Demo 中 EaseCallKit 使用的 token 和 UID 均由你自己生成。若你需要使用声网对应的音视频服务,需单独在声网申请。
+:::
+
+## 跑通 Demo
+
+EaseCallKit 集成在环信开源 IM Demo 中,你可以通过以下两种方式下载 Demo:
+
+- 你可以通过进入 [环信 Demo 下载页面](https://www.easemob.com/download/demo) ,选择 Android 端下载 Demo。
+- 你可以下载 [Android IM 源码](https://github.com/easemob/easemob-demo-android) 跑通 Demo。
+
+1. 环境准备
+
+- 推荐 Android Studio Meerkat | 2024.3.1 Patch 2及以上
+- 推荐 Gradle 8.0 及以上
+- targetVersion 33 及以上
+- Android SDK API 21 及以上
+- JDK 17 及以上
+
+2. 运行 Demo
+
+- 下载源码后,用 Android Studio 打开项目,连接手机,然后运行。
+
+## 准备条件
+
+集成该库之前,你需要满足以下条件:
+
+- 分别创建 [环信应用](/product/enable_and_configure_IM.html) 及 [声网应用](https://doc.shengwang.cn/doc/rtc/android/get-started/enable-service#创建声网项目);
+- 已完成环信 IM 的基本功能,包括登录、好友、群组以及会话等的集成;
+- 上线之前开通声网 Token 验证时,用户需要实现自己的 [App Server](https://github.com/easemob/easemob-im-app-server/tree/master/agora-app-server),用于生成 Token。具体请参见 [创建 Token 服务及使用 App Server 生成 Token](https://doc.shengwang.cn/doc/rtc/android/basic-features/token-authentication)。
+
+## 快速集成
+
+使用 `EaseCallKit` 库完成音视频通话的基本流程如下:
+
+1. `EaseCallKit` 库进行初始化并设置 `EaseCallKit` 监听;
+2. 主叫方调用发起通话邀请接口,进入通话界面;
+3. 被叫方收到邀请自动弹出通话邀请界面,在通话邀请界面选择接通或者拒绝;
+4. 主叫或者被叫挂断通话。
+
+### 导入 EaseCallKit 库
+
+`EaseCallKit` 主要依赖于 `com.hyphenate:hyphenate-chat:xxx` 版本和 `io.agora.rtc:full-sdk:xxx` 版本等库;
+
+`EaseCallKit` 库可通过 Gradle 方式和源码两种方式集成。
+
+#### Gradle 方式集成
+
+- 在 `build.gradle` 中添加以下代码,重新 build 你的项目即可。[点击查看最新版本号](https://central.sonatype.com/artifact/io.hyphenate/ease-call-kit/versions)
+
+```gradle
+implementation 'io.hyphenate:ease-call-kit:4.15.1'
+```
+
+:::tip
+`EaseCallKit` 必须依赖环信 IM SDK (即 hyphenate-chat) ,因而在使用 `EaseCallKit` 时必须同时添加环信 IM SDK 依赖。
+:::
+
+#### 源码集成
+
+- 下载 [EaseCallKit 源码](https://github.com/easemob/easecallkitui-android);
+- 在 `build.gradle` 中增加以下内容,重新 build 你的项目即可。
+
+```gradle
+implementation project(':ease-call-kit')
+```
+
+`EaseCallKit` 中如果要修改 `hyphenate-chat` 和 `agora.rtc` 中版本号,可修改以下依赖:
+
+```gradle
+//环信 SDK
+implementation 'io.hyphenate:hyphenate-chat:4.15.1'
+//声网 SDK
+implementation 'io.agora.rtc:full-rtc-basic:4.1.0'
+```
+
+### 添加权限
+
+根据场景需要,本库需要增加麦克风、相机和悬浮窗等权限:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+ ...
+
+
+```
+
+### 添加 EaseCallKit Activity
+
+在清单中增加 `EaseCallKit` 中的 `EaseVideoCallActivity` 和 `EaseMultipleVideoActivity`:
+
+```xml
+
+
+```
+
+### 初始化
+
+在环信 IM SDK 初始化完成后,可以开始初始化 `EaseCallKit`,同时增加监听回调,设置常用配置项。代码如下:
+
+```java
+//初始化 `EaseCallUIKit`。
+EaseCallKitConfig callKitConfig = new EaseCallKitConfig();
+//设置默认头像。
+String headImage = EaseFileUtils.getModelFilePath(context,"watermark.png");
+callKitConfig.setDefaultHeadImage(headImage);
+//设置振铃文件。
+String ringFile = EaseFileUtils.getModelFilePath(context,"huahai.mp3");
+callKitConfig.setRingFile(ringFile);
+//设置呼叫超时时间,单位为秒。
+callKitConfig.setCallTimeOut(30 * 1000);
+//设置声网的 appId。
+callKitConfig.setAgoraAppId("xxxxxxxxxxxxxxx");
+Map userInfoMap = new HashMap<>();
+userInfoMap.put("***",new EaseCallUserInfo("****",null));
+callKitConfig.setUserInfoMap(userInfoMap);
+EaseCallKit.getInstance().init(context,callKitConfig);
+addCallkitListener();
+```
+
+可设置的配置项包括以下内容:
+
+```java
+/**
+ * EaseCallKit 相关的用户配置选项。
+ * defaultHeadImage 用户默认头像。该参数的值为本地文件绝对路径或者 URL。
+ * userInfoMap 用户相关信息。该信息为 key-value 格式,key 为用户的环信 ID,value 为 `EaseCallUserInfo`。
+ * callTimeOut 呼叫超时时间,单位为毫秒,默认为 30 秒。
+ * audioFile 振铃文件。该参数的值为本地文件绝对路径。
+ * enableRTCToken 是否开启 RTC 验证。该功能通过声网后台控制,默认为关闭。
+ */
+public class EaseCallKitConfig {
+ private String defaultHeadImage;
+ private Map userInfoMap = new HashMap<>();
+ private String RingFile;
+ private String agoraAppId = "****";
+ private long callTimeOut = 30 * 1000;
+ public EaseCallKitConfig(){
+ ...
+}
+```
+
+### 发起通话邀请
+
+`EaseCallKit` 初始化完成后,可以发起音视频通话。
+
+#### 一对一音视频通话
+
+一对一通话可分为视频通话和语音通话,接口如下所示:
+
+```java
+/**
+ * 发起一对一通话。
+ * @param type 通话类型。该参数只能设置为 `SIGNAL_VOICE_CALL` 或 `SIGNAL_VIDEO_CALL`。
+ * @param user 被叫用户 ID,即环信 ID。
+ * @param ext 自定义扩展字段,描述通话扩展信息。
+ */
+public void startSingleCall(final EaseCallType type, final String user,final String ext){}
+```
+
+#### 多人音视频通话
+
+你可以从群组成员列表或者好友列表中选择,发起多人音视频邀请,具体实现可参考 demo 中的 `ConferenceInviteActivity`。
+
+```java
+/**
+ * 邀请用户加入多人通话。
+ * @param users 用户 ID 列表,即环信 ID 列表。
+ * @param ext 自定义扩展字段,描述通话扩展信息。
+ */
+public void startInviteMultipleCall(final String[] users,final String ext){}
+```
+
+发起通话后的 UI 界面如下:
+
+
+
+### 被叫收到通话邀请
+
+主叫方发起邀请后,如果被叫方在线且当前不在通话中,会弹出邀请通话界面,被叫可以选择接听或者拒绝。
+
+被叫收到邀请后会触发 `EaseCallKitListener` 中的 `onRevivedCall` 回调:
+
+```java
+/**
+ * 收到通话邀请回调。
+ * @param callType 通话类型。
+ * @param userId 邀请方的用户 ID。
+ * @param ext 自定义扩展字段,描述通话扩展信息。
+ */
+void onRevivedCall(EaseCallType callType, String userId,String ext){}
+```
+
+收到通话邀请后的界面如下:
+
+
+
+### 多人通话中邀请
+
+多人通话中,当前用户可以点击通话界面右上角的邀请按钮再次向其他用户发起邀请。这种情况下,会触发 `EaseCallKitListener` 中的 `onInviteUsers` 回调:
+
+```java
+/**
+ * 邀请好友进行多人通话。
+ * @param context 通话上下文。
+ * @param users 当前通话中已经存在的成员。
+ * @param ext 自定义扩展字段,描述通话扩展信息。
+ */
+public void onInviteUsers(Context context,String userId[],String ext) {
+}
+```
+
+### 加入频道成功回调
+
+用户加入通话后,当前用户以及其他与会者会收到 `EaseCallKitListener` 中的 `onRemoteUserJoinChannel` 回调。该接口自从 SDK 3.8.1 新增。
+
+```java
+@Override
+public void onRemoteUserJoinChannel(String channelName, String userName, int uid, EaseGetUserAccountCallback callback){
+ //此时,可以获取当前频道中已有用户的声网 ID 与环信 ID 的映射表,并将映射表设置到 `EaseCallKit`,同时也可以更新用户的头像和昵称。
+ // callback.onUserAccount(accounts);
+}
+```
+
+### 通话结束
+
+在一对一音视频通话中,若其中一方挂断,双方的通话会自动结束,而多人音视频通话中需要主动挂断才能结束通话。通话结束后,会触发 `onEndCallWithReason` 回调:
+
+```java
+/**
+ * 通话结束回调。
+ * @param callType 通话类型。
+ * @param reason 通话结束原因。
+ * @param callTime 通话时长。
+ */
+void onEndCallWithReason(EaseCallType callType, String channelName, EaseCallEndReason reason, long callTime){}
+
+//通话结束原因如下:
+public enum EaseCallEndReason {
+ EaseCallEndReasonHangup(0), //正常挂断。
+ EaseCallEndReasonCancel(1), //您已取消通话。
+ EaseCallEndReasonRemoteCancel(2), //对方取消通话。
+ EaseCallEndReasonRefuse(3),//对方拒绝接听。
+ EaseCallEndReasonBusy(4), //忙线中。
+ EaseCallEndReasonNoResponse(5), //您未接听。
+ EaseCallEndReasonRemoteNoResponse(6), //对端无响应。
+ EaseCallEndReasonHandleOnOtherDevice(7); //已在其他设备处理。
+ ....
+}
+```
+
+## 进阶功能
+
+### 通话异常回调
+
+通话过程中如果有异常或者错误发生,会触发 `EaseCallKitListener` 中的 `onCallError` 回调:
+
+```java
+/**
+ * 通话异常回调。
+ * @param type 错误类型。
+ * @param errorCode 错误码。
+ * @param description 错误描述。
+ */
+void onCallError(EaseCallKit.EaseCallError type, int errorCode, String description){}
+```
+
+`EaseCallError` 异常包括业务逻辑异常、音视频异常以及 Easemob IM 异常。
+
+```java
+/**
+ * 通话错误类型。
+ *
+ */
+public enum EaseCallError{
+ PROCESS_ERROR, //业务逻辑异常。
+ RTC_ERROR, //音视频异常。
+ IM_ERROR //Easemob IM 异常。
+}
+```
+
+### 配置修改
+
+`EaseCallKit` 库初始化之后,可修改有关配置,接口和示例如下:
+
+```java
+/**
+ * 获取当前 `EaseCallKit` 的配置。
+ *
+ */
+public EaseCallKitConfig getCallKitConfig(){}
+
+//修改默认头像。
+EaseCallKitConfig config = EaseCallKit.getInstance().getCallKitConfig();
+if(config != null){
+ String Image = EaseFileUtils.getModelFilePath(context,"bryant.png");
+ callKitConfig.setDefaultHeadImage(Image);
+}
+```
+
+### 头像昵称修改
+
+自 `EaseCallKit` 3.8.1 开始,新增了修改头像昵称的接口,用户可以在加入频道后,修改自己和通话中其他人的头像昵称,修改方法如下:
+
+```java
+@Override
+public void onRemoteUserJoinChannel(String channelName, String userName, int uid, EaseGetUserAccountCallback callback){
+ if(userName == null || userName == ""){
+ // 根据用户的 Agora ID 获取 环信 ID。url 为获取用户信息的请求 URL,请参考 [Easemob IM demo](https://www.easemob.com/download/demo)。
+ getUserIdAgoraUid(uid, url, callback);
+ // 将获取到的用户信息设置给回调,具体实现请参考 [Easemob IM demo](https://www.easemob.com/download/demo)。
+ //callback.onUserAccount(userAccounts);
+ }else{
+ // 设置用户昵称和头像。
+ setEaseCallKitUserInfo(userName);
+ EaseUserAccount account = new EaseUserAccount(uid,userName);
+ List accounts = new ArrayList<>();
+ accounts.add(account);
+ callback.onUserAccount(accounts);
+ }
+}
+```
+
+### 私有化部署
+
+CallKit 4.8.2 及更高版本支持私有化部署,包括初始化和初测监听器。
+
+#### 初始化
+
+配置私有化 AgoraAppId。其他可配置的选项,详见本文档中的[初始化](https://doc.easemob.com/document/android/easecallkit.html#初始化)一节。
+
+```kotlin
+EaseCallKitConfig().apply {
+ ……
+ agoraAppId = "2d4f114e22304cee8d31ae909f3289d2"
+ ……
+ EaseCallKit.getInstance().init(context, this)
+}
+```
+
+#### 注册监听器
+
+监听 `com.hyphenate.easecallkit.base.EaseCallKitListener#onRtcEngineCreated` 事件,在 RTC 引擎创建的回调里进行私有化配置。详见 [API 参考](https://doc.shengwang.cn/api-ref/rtc/android/API/toc_network#api_irtcengine_setlocalaccesspoint)。
+
+```kotlin
+private val callKitListener by lazy { object :EaseCallKitListener {
+ ……
+
+ override fun onRtcEngineCreated(engine: RtcEngine?) {
+ var configuration= LocalAccessPointConfiguration().apply {
+ //设置你的私有化地址
+ ipList = arrayListOf().apply { add("101.111.111.111" )}
+ verifyDomainName = "ap.955011.agora.local"
+ mode = LOCAL_RPOXY_LOCAL_ONLY
+ }
+ engine?.setLocalAccessPoint(configuration)
+ }
+
+ ……
+
+ } }
+
+EaseCallKit.getInstance().setCallKitListener(callKitListener)
+```
+
+## 参考
+
+### 获取声网 token
+
+加入音视频时,你需获取声网 token 以进行鉴权,需要在 `EaseCallKitListener` 中将 token 回调给 `EaseCallKit`。
+
+如果不需要鉴权,可以直接回调 token 为 `null`,或者在设置 `callKitConfig.setEnableRTCToken(false)` 的前提下不实现该回调, 具体接口和使用如下:
+
+```java
+/**
+ * 用户生成 token 回调。
+ * @param userId 用户 ID,即用户的环信 ID。
+ * @param channelName 频道名称。
+ * @param agoraAppId 声网的 App ID。
+ * @param callback 生成的 token 回调。若成功生成 token,即返回 token;若未能成功生成 token,返回错误码和错误消息。
+ */
+default void onGenerateToken(String userId,String channelName,String agoraAppId,EaseCallKitTokenCallback callback){};
+
+@Override
+public void onGenerateToken(String userId, String channelName, String agoraAppId, EaseCallKitTokenCallback )callback{
+ if(callback != null){
+ // 若无需 token 鉴权,你需向 `token` 传 `null`,向 `uid` 传 `0`。
+ //callback.onSetToken(null, 0);
+
+
+ // 若需要 token 鉴权,则调用你的 App Server 生成 token,然后将生成的 token 回调给 `EaseCallKit`。
+ // url 为拼接参数请求 token 的 URL,可参考[官网上的 Easemob IM demo](https://www.easemob.com/download/demo)。
+ getRtcToken(url, callback);
+ //获取到 token 及 uid 后,将其回调给 callback 即可。
+ //callback.onSetToken(token, uid);
+ }
+}
+```
+
+自 EaseCallKit 3.8.1 版本开始,`EaseCallKitTokenCallback` 中的 `onSetToken` 方法添加了 `uid` 参数,你可以使用数字 uid 加入声网频道。
+
+```java
+void onSetToken(String token, int uId);
+```
+
+### 离线推送
+
+为保证被叫用户 App 在后台运行或离线时也能收到通话请求,用户需开启离线推送。关于如何开启离线推送,请参见 [开启 Android Push](/document/android/push/push_notification_mode_dnd.html)。开启离线推送后,用户在离线情况下收到呼叫请求时,其手机通知页面会弹出一条通知消息,用户点击该消息可唤醒 App 并进入振铃页面。
+
+关于离线推送场景方案,请参见 [安卓端设置推送](/document/android/push/push_overview.html)。
+
+## API 列表
+
+`EaseCallKit` 中提供的 API 列表如下:
+
+| 方法 | 说明 |
+| :---------------------- | :---------------------------- |
+| init | 初始化 `EaseCallKit`。 |
+| setCallKitListener | 设置监听。 |
+| startSingleCall | 发起单人通话。 |
+| startInviteMultipleCall | 邀请用户加入多人通话。 |
+| getCallKitConfig | 获取 `EaseCallKit` 相关配置。 |
+
+回调模块 `EaseCallKitListener` 的 API 列表如下:
+
+| 事件 | 说明 |
+| :---------------------- | :--------------------------------------------------------------- |
+| onEndCallWithReason | 通话结束时触发该事件。 |
+| onInviteUsers | 多人通话中点击邀请按钮触发该事件。 |
+| onReceivedCall | 振铃时触发该事件。 |
+| onGenerateToken | 获取声网 token 回调。用户将获取到的 token 回调到 `EaseCallKit`。 |
+| onCallError | 通话异常时触发该回调。 |
+| onInViteCallMessageSent | 通话邀请消息回调。 |
+| onRemoteUserJoinChannel | 用户加入频道时触发。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+
+`EaseGetUserAccountCallback` 的 API 列表如下:
+
+| 事件 | 说明 |
+| :-------------------- | :---------------------------------------------------------------------- |
+| onUserAccount | 传入环信 ID 与声网 uid 的映射。 该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+| onSetUserAccountError | 获取用户信息失败触发。方法自 `EaseCallKit` 3.8.1 版本添加。 |
diff --git a/docs/callkit/android/float_top.md b/docs/callkit/android/float_top.md
new file mode 100644
index 000000000..4b8fa05ef
--- /dev/null
+++ b/docs/callkit/android/float_top.md
@@ -0,0 +1,50 @@
+# 来电通知和悬浮窗
+
+要展示来电通知栏和悬浮窗,需要用户授予悬浮窗权限(`android.permission.SYSTEM_ALERT_WINDOW`)。
+- 来电通知栏:主要包括接听和拒绝按钮。
+- 悬浮窗:位于屏幕右上角。在视频通话时,小悬浮窗展示对方的视频画面或者图像,音频通话时展示计时器。
+
+## 来电通知栏
+
+来电时(`SignalingManager` 收到有效来电确认后),CallKit 首先播放来电铃声,然后按以下方式展示通话界面:
+
+- 若应用处于前台或处于后台且有悬浮窗权限,显示来电通知栏 `IncomingCallTopWindow.showIncomingCallTopWindow()`。
+- 若设备处于锁屏(无论有或无悬浮窗权限),或者 App 在后台且无悬浮窗权限,使用系统来电界面 `TelecomHelper.startCallImmediately(...)`。
+- 若应用处于前台且无悬浮窗权限,直接启动通话 Activity,使用 CallKit 默认 UI 界面。
+
+
+
+
+
+
+
+## 悬浮窗
+
+若申请了悬浮窗权限,悬浮窗的展示如下:
+
+- 应用在前台进入后台,展示悬浮窗。
+- 应用在前台点击悬浮窗按钮,展示悬浮窗。
+- 应用后台回到前台,隐藏悬浮窗。
+
+
+
+
+
+
+
+## 相关 API
+
+- 来电通知栏
+
+ `showIncomingCallTopWindow()`:展示来电通知栏。
+
+ `hideIncomingCallTopWindow()`:隐藏来电通知栏。
+
+- 悬浮窗
+
+ `showFloatWindow()`:展示悬浮窗。
+
+ `hideFloatWindow()`:隐藏悬浮窗。
+
+ `isFloatWindowShowing()`:悬浮窗是否在展示。
+
diff --git a/docs/callkit/android/integration.md b/docs/callkit/android/integration.md
new file mode 100644
index 000000000..c372cbc4b
--- /dev/null
+++ b/docs/callkit/android/integration.md
@@ -0,0 +1,446 @@
+# CallKit 集成指南
+
+## 功能概述
+
+环信 CallKit 是一套基于环信即时通讯 IM(基于 IM 4.16.0 及以上)和声网 RTC 结合开发的音视频 UI 库。使用环信 CallKit 之前,你需要将其集成到你的应用中。
+
+
+
+
+
+
+## 推荐开发环境
+
+- Android SDK: API Level 24 及以上
+- Android Studio: 推荐最新版本
+- Kotlin: 2.0.21
+- JDK: 17
+- Gradle 版本: 8.13
+
+## 前提条件
+
+在集成 CallKit 之前,你需要完成以下准备工作:
+
+1. 在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+ - [注册环信账号](/product/console/account_register.html#注册账号)。
+ - [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+ - [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID。
+ - [创建群组](/product/console/operation_group.html#创建群组),获取群组 ID。将用户加入群组。
+ - [开通音视频服务](product_activation.html)。
+
+2. 集成环信即时通讯 IM SDK。
+
+确保已集成环信 IM SDK 并完成登录。
+
+## 集成步骤
+
+### 步骤 1 添加依赖
+
+#### 方式一:(推荐)Gradle 远程依赖
+
+1. 在 Project 工程根目录下的 `settings.gradle.kts` 文件内,添加 `mavenCentral()` 仓库:
+
+```kotlin
+pluginManagement {
+ repositories {
+ ...
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ ...
+ mavenCentral()
+ }
+}
+```
+
+2. 在 app(module) 目录的 `build.gradle.kts` 文件中添加以下依赖:
+
+```kotlin
+dependencies {
+ ...
+ implementation("io.hyphenate:chat-call-kit:4.16.0")
+}
+```
+
+#### 方式二:本地源码集成
+
+从 GitHub 获取音视频 [CallKit 源码](https://github.com/easemob/easemob-callkit-android.git),克隆到本地。按照以下步骤集成:
+
+1. 在 Project 工程根目录下的 `settings.gradle.kts` 文件中添加如下代码:
+
+```kotlin
+include(":ease-call-kit")
+// "../easemob-callkit-android" 要替换成你克隆的实际工程路径,后边要拼接 "/ease-call-kit"
+project(":ease-call-kit").projectDir = File("../easemob-callkit-android/ease-call-kit")
+```
+
+2. 在 app(module) 目录的 `build.gradle.kts` 文件中添加如下代码:
+
+```kotlin
+dependencies {
+ ...
+ implementation(project(":ease-call-kit"))
+}
+```
+
+
+### 步骤 2 初始化 CallKit
+
+在应用启动时(通常在 `Application` 或主 `Activity` 中)初始化 CallKit:
+
+1. 初始化 IM SDK。CallKit 基于即时通讯 IM 作为信令通道,因此需先初始化 IM SDK。
+ - 填入你的应用的 App Key。
+ - 设置即时通讯 IM SDK 中的一些选项(`EMOptions` 类),例如,是否自动登录。
+2. 初始化 CallKit。你可以自定义铃声和通话超时时间。
+
+在整个应用生命周期中,初始化一次即可。
+
+```kotlin
+class MainActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ initCallKit()
+ }
+
+ private fun initCallKit() {
+ // 1. 初始化环信 IM SDK
+ val options = ChatOptions().apply {
+ appKey = "your_app_key" // 替换为你的 App Key
+ autoLogin = false
+ }
+ ChatClient.getInstance().init(this, options)
+ ChatClient.getInstance().setDebugMode(true)
+
+ // 2. 初始化 CallKit
+ val config = CallKitConfig().apply {
+ // (可选)配置自定义铃声
+ incomingRingFile = "assets://incoming_ring.mp3"
+ outgoingRingFile = "assets://outgoing_ring.mp3"
+ dingRingFile = "assets://ding.mp3"
+
+ // (可选)配置通话超时时间(秒)
+ callTimeout = 30 // 30秒
+ }
+
+ CallKitClient.init(this, config)
+ }
+}
+```
+
+### 步骤 3 配置监听器
+
+环信 CallKit 提供 `CallKitListener` 监听通话过程。你可以在应用初始化时设置监听器用于处理通话相关的回调:
+
+```kotlin
+class MainActivity : AppCompatActivity() {
+
+ private val callKitListener = object : CallKitListener {
+
+ // 通话结束
+ override fun onEndCallWithReason(reason: CallEndReason, callInfo: CallInfo?) {
+ runOnUiThread {
+ when (reason) {
+ CallEndReason.CallEndReasonHangup -> {
+ showToast("通话已挂断")
+ }
+ CallEndReason.CallEndReasonCancel -> {
+ showToast("通话已取消")
+ }
+ CallEndReason.CallEndReasonRemoteRefuse -> {
+ showToast("对方拒绝通话")
+ }
+ CallEndReason.CallEndReasonRemoteNoResponse -> {
+ showToast("对方无响应")
+ }
+ // ... 其他结束原因
+ }
+ }
+ }
+
+ // 通话错误
+ override fun onCallError(
+ errorType: CallKitClient.CallErrorType,
+ errorCode: Int,
+ description: String?
+ ) {
+ runOnUiThread {
+ showToast("通话错误: $description")
+ }
+ }
+
+ // 收到通话邀请
+ override fun onReceivedCall(userId: String, callType: CallType, ext: JSONObject?) {
+ runOnUiThread {
+ val typeStr = when (callType) {
+ CallType.SINGLE_VIDEO_CALL -> "视频通话"
+ CallType.SINGLE_VOICE_CALL -> "语音通话"
+ CallType.GROUP_CALL -> "群组通话"
+ }
+ showToast("收到来自 $userId 的$typeStr")
+ }
+ }
+
+ // 远端用户加入
+ override fun onRemoteUserJoined(userId: String, callType: CallType, channelName: String) {
+ runOnUiThread {
+ showToast("$userId 加入通话")
+ }
+ }
+
+ // 远端用户离开
+ override fun onRemoteUserLeft(userId: String, callType: CallType, channelName: String) {
+ runOnUiThread {
+ showToast("$userId 离开通话")
+ }
+ }
+
+ // RTC 引擎创建(可用于私有化部署配置)
+ override fun onRtcEngineCreated(engine: RtcEngine) {
+ // 如需私有化部署,在此处配置
+ }
+ }
+
+ private fun initCallKit() {
+ // ... 初始化代码
+
+ // 设置监听器
+ CallKitClient.callKitListener = callKitListener
+ }
+}
+```
+
+### 步骤 4 登录
+
+```kotlin
+ChatClient.getInstance().loginWithToken(username, token, object : ChatCallback {
+ override fun onSuccess() {
+ runOnUiThread {
+ showToast("登录成功")
+ }
+ }
+
+ override fun onError(code: Int, error: String?) {
+ runOnUiThread {
+ showToast("登录失败: $error")
+ }
+ }
+ })
+
+```
+
+### 步骤 5 发起通话
+
+#### 发起一对一通话
+
+你可以使用 `startSingleCall` 方法发起一对一通话,`CallType` 设置为 `SINGLE_VIDEO_CALL` 为视频通话,`SINGLE_VOICE_CALL` 为音频通话。
+
+
+
+
+
+
+- 发起一对一视频通话
+
+```kotlin
+private fun startVideoCall() {
+ // 检查登录状态
+ if (!ChatClient.getInstance().isLoggedInBefore) {
+ showToast("请先登录")
+ return
+ }
+
+ val targetUserId = "peer_user_id" // 对方用户 ID
+ val ext = JSONObject().apply {
+ put("customKey", "customValue") // 可选的扩展信息
+ }
+
+ CallKitClient.startSingleCall(
+ CallType.SINGLE_VIDEO_CALL,
+ targetUserId,
+ ext // 可传 null
+ )
+}
+```
+
+- 发起一对一语音通话
+
+```kotlin
+private fun startVoiceCall() {
+ val targetUserId = "peer_user_id"
+
+ CallKitClient.startSingleCall(
+ CallType.SINGLE_VOICE_CALL,
+ targetUserId,
+ null
+ )
+}
+```
+
+#### 发起群组通话
+
+- **创建群组**:要发起群组通话,你需要首先创建群组,在群组中添加用户,详见 [即时通讯 IM Android SDK 文档](/document/android/group_manage.html#创建群组) 或 [环信控制台文档](/product/console/operation_group.html#创建群组)。
+- **发起群组通话**:指定群组 ID 后,CallKit 会自动拉起群成员选择界面,界面显示群组中的所有成员(群主、管理员、普通成员),用户可以选择要邀请的成员,选中人数会实时显示。为了保证通话质量和性能,CallKit 限制群组通话最多支持 **16 人** 同时参与(包括发起者)。若选择的成员数量超过 16 人时,系统会自动提示 “人数超出最大限制16人” 并阻止发起通话。
+- **通话扩展信息**:`ext` 会在 `CallKitListener#onReceivedCall` 中回调给接收方。
+- **通话中邀请他人**:群组通话中,当前用户可以点击通话界面右上角的邀请按钮向其他用户发起邀请。
+
+```kotlin
+private fun startGroupCall() {
+ // 群组 ID
+ val groupId = "your_group_id"
+
+ val ext = JSONObject().apply {
+ put("meetingTitle", "项目讨论会")
+ }
+ // ext 可传 null
+ CallKitClient.startGroupCall(groupId, ext)
+}
+```
+
+
+
+
+
+### 步骤 5 接听通话
+
+当接收到通话邀请时,CallKit 会自动触发 `onReceivedCall` 回调:
+1. 弹出通话邀请界面。
+2. 播放来电铃声。
+3. 显示通话邀请通知(当 App 在后台时)。
+
+被叫用户可选择接听、拒绝或挂断通话。
+
+
+
+
+
+
+
+### 步骤 7 离线推送
+
+为保证被叫用户 App 在离线时也能收到通话请求,用户需开启离线推送。关于如何开启离线推送,请参见 [开启 Android Push](/document/android/push/push_notification_mode_dnd.html)。开启离线推送后,用户在离线情况下收到呼叫请求时,其手机通知页面会弹出一条通知消息,用户点击该消息可唤醒 App 并进入振铃页面。
+
+关于离线推送场景方案,请参见 [Android 端离线推送文档](/document/android/push/push_overview.html)。
+
+
+
+
+
+
+## 进阶功能
+
+### 用户信息
+
+默认情况下,音视频通话时,对于用户信息,CallKit 会显示默认图像和用户 ID;对于群信息,CallKit 会根据群组 ID 从 SDK 中拉取群信息来对应显示群组名称和群图像。
+
+如果要在一对一通话界面显示自定义用户头像和昵称,群聊通话显示自定义群图像和群名称,你可以通过 `CallInfoProvider` 实现自定义用户信息。
+
+```kotlin
+class MyCallInfoProvider : CallInfoProvider {
+
+ override fun asyncFetchUsers(
+ userIds: List,
+ onValueSuccess: OnValueSuccess>
+ ) {
+ // 异步获取用户信息
+ GlobalScope.launch {
+ val userInfos = mutableListOf()
+
+ userIds.forEach { userId ->
+ // 从你的用户系统获取用户信息
+ val userInfo = getUserFromApi(userId)
+ userInfos.add(
+ CallKitUserInfo().apply {
+ this.userId = userId
+ this.nickName = userInfo.nickname
+ this.avatar = userInfo.avatar
+ }
+ )
+ }
+
+ // 回调用户信息
+ onValueSuccess.onSuccess(userInfos)
+ }
+ }
+
+ override fun asyncFetchGroupInfo(
+ groupId: String,
+ onValueSuccess: OnValueSuccess
+ ) {
+ // 异步获取群组信息
+ GlobalScope.launch {
+ val groupInfo = getGroupFromApi(groupId)
+ val callKitGroupInfo = CallKitGroupInfo().apply {
+ this.groupID = groupId
+ this.groupName = groupInfo.name
+ this.groupAvatar = groupInfo.avatar
+ }
+
+ onValueSuccess.onSuccess(callKitGroupInfo)
+ }
+ }
+
+ private suspend fun getUserFromApi(userId: String): UserInfo {
+ // 实现你的用户信息获取逻辑
+ return UserInfo(userId, "昵称", "头像URL")
+ }
+
+ private suspend fun getGroupFromApi(groupId: String): GroupInfo {
+ // 实现你的群组信息获取逻辑
+ return GroupInfo(groupId, "群组名称", "群组头像URL")
+ }
+}
+
+// 设置用户信息提供者
+CallKitClient.callInfoProvider = MyCallInfoProvider()
+```
+
+### 自定义视频分辨率
+
+环信 CallKit 中默认设置的分辨率为 1280x720。网络连接不稳定时,声网 RTC SDK 会主动降低分辨率或帧率。
+
+若要修改本地摄像头视频采集的分辨率,可以在创建声网 RTC 引擎时进行配置。
+
+```kotlin
+private val callKitListener = object : CallKitListener {
+
+ override fun onRtcEngineCreated(engine: RtcEngine) {
+ val configuration= VideoEncoderConfiguration()
+ //例如,修改分辨率为 1280x720
+ configuration.dimensions= VD_1280x720
+ rtcEngine?.setVideoEncoderConfiguration(configuration)
+ }
+
+ // ... 其他回调
+}
+```
+更多其他配置可以参考 [声网 RTC 文档](https://doc.shengwang.cn/doc/rtc/android/basic-features/video-profile#视频参数推荐值)。
+
+### 声网 RTC 私有化部署
+
+若使用私有化的声网服务,可以在声网 RTC 引擎创建时进行配置。
+
+详情可以参考 [声网 RTC 文档](https://doc.shengwang.cn/api-ref/rtc/android/API/class_Localaccesspointconfiguration#LocalAccessPointConfiguration)。
+
+```kotlin
+private val callKitListener = object : CallKitListener {
+
+ override fun onRtcEngineCreated(engine: RtcEngine) {
+ // 私有化部署配置
+ val configuration = LocalAccessPointConfiguration().apply {
+ // 将 `111.111.111.111` 替换为你的私有化地址
+ ipList = arrayListOf("111.111.111.111")
+ // 将 `ap.xxx.agora.local` 替换为你的域名
+ verifyDomainName = "ap.xxx.agora.local"
+ mode = LOCAL_RPOXY_LOCAL_ONLY
+ }
+ engine.setLocalAccessPoint(configuration)
+ }
+
+ // ... 其他回调
+}
+```
diff --git a/docs/callkit/android/permission.md b/docs/callkit/android/permission.md
new file mode 100644
index 000000000..61bd6d515
--- /dev/null
+++ b/docs/callkit/android/permission.md
@@ -0,0 +1,148 @@
+# 权限与业务逻辑
+
+## 权限声明
+
+环信 CallKit 所需权限已在 `Manifest.xml` 文件中声明。若不需要某项功能,可根据实际情况修改。
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## 动态权限
+
+动态权限也称为运行时权限。一些权限需要动态申请,申请逻辑位于 `BaseCallActivity#initPermissions` 函数。
+
+例如,以下为动态权限:
+
+- 录音:`RECORD_AUDIO`
+- 摄像头:`CAMERA`
+- Android 12+:`READ_PHONE_STATE`、`BLUETOOTH_CONNECT`
+- Android 13+:`POST_NOTIFICATIONS`
+- 申请权限被拒:逻辑统一在 `BaseCallActivity.onRequestPermissionsResult` 中。
+ 如果任一必要权限被拒,结束当前通话流程:
+ - 主叫:发送取消信令 `SignalingManager.cancelCall(...)` ,取消通话。
+ - 被叫:发送拒绝信令 `SignalingManager.refuseCall()`,拒绝接听。
+
+
+
+
+
+
+
+
+
+
+## 悬浮窗
+
+当用户点击悬浮窗按钮或者在通话状态时 app 回到后台,会触发悬浮窗权限检查:
+- 悬浮窗权限:`SYSTEM_ALERT_WINDOW`
+- 检查悬浮窗权限:`PermissionHelper.hasFloatWindowPermission(...)`
+- 引导开启悬浮窗权限:`requestFloatWindowPermission(...)`
+- 开启悬浮窗权限被拒:CallKit 在 `SingleCallViewModel.handleRequestFloatWindowPermissionCancel()` 会按当前 `CallState` 执行取消/拒绝/挂断的兜底。
+- 悬浮窗显示时,为保证通话正常进行(即摄像头、麦克风正常采集数据流),需要开启媒体类型前台服务。
+ 前台服务权限为 `FOREGROUND_SERVICE`,媒体类型为 `camera` `microphone` 和 `mediaPlayback`。
+
+申请悬浮窗权限的界面展示如下:
+
+
+
+
+
+
+## 锁屏唤醒
+
+锁屏唤醒权限:`USE_FULL_SCREEN_INTENT`、`WAKE_LOCK`、`DISABLE_KEYGUARD`。
+
+若 App 在后台运行,收到来电时,app 会被唤醒,显示来电通知。
+
+## 最佳实践
+
+为了确保流畅的用户通话体验,需在启动 app 后就提前申请以下权限:
+
+```Kotlin
+ 1. 动态权限申请
+ ActivityCompat.requestPermissions(this,getRequiredPermissions(),Constant.PERMISSION_REQ_ID)
+
+ // 获取体验实时音视频互动所需的录音、摄像头等权限
+ private fun getRequiredPermissions(): Array {
+ var basePermission = arrayOf(
+ Manifest.permission.RECORD_AUDIO, // 录音权限
+ Manifest.permission.CAMERA, // 摄像头权限
+ )
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ basePermission += arrayOf(
+ Manifest.permission.READ_PHONE_STATE, // 读取电话状态权限
+ Manifest.permission.BLUETOOTH_CONNECT, // 蓝牙连接权限
+ )
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
+ basePermission += arrayOf(
+ Manifest.permission.POST_NOTIFICATIONS //通知权限
+ )
+ }
+ return basePermission
+ }
+
+ 2. 悬浮窗权限申请
+ PermissionHelper.showPermissionExplanationDialog(
+ this,
+ onConfirm = {
+ PermissionHelper.requestFloatWindowPermission(
+ this,
+ Constant.FLOAT_WINDOW_PERMISSION_REQUEST_CODE
+ )
+ },
+ onCancel = {
+
+ }
+ )
+```
\ No newline at end of file
diff --git a/docs/callkit/android/product_activation.md b/docs/callkit/android/product_activation.md
new file mode 100644
index 000000000..0b4b36b98
--- /dev/null
+++ b/docs/callkit/android/product_activation.md
@@ -0,0 +1,32 @@
+# 开通服务
+
+使用环信音视频通话 CallKit 之前,你需要在 [环信控制台](https://console.easemob.com/user/login) 开通实时音视频服务。
+
+开通音视频服务后,你还可以开通在微信中集成 MiniAppSDK,实现轻量级实时音视频功能。
+
+## 开通音视频通话服务
+
+即使你当前使用免费版 IM 套餐包,也可以开通环信音视频通话服务。
+
+1. 登录 [环信控制台](https://console.easemob.com/user/login)。
+2. 选择页面上方的 **应用管理**。在弹出的应用列表页面,单击你的应用的 **操作** 栏中的 **管理**。
+3. 在左侧导航栏,选择 **服务开通** > **增值服务** > **实时音视频**。
+4. 点击 **立即开通**。
+
+
+
+## 开通微信中集成音视频通话服务
+
+你可在微信中集成声网 RTC 的 MiniAppSDK,实现轻量级实时音视频功能。**注意该服务开通后不能关闭。**
+
+1. 登录 [环信控制台](https://console.easemob.com/user/login)。
+2. 选择页面上方的 **应用管理**。在弹出的应用列表页面,单击你的应用的 **操作** 栏中的 **管理**。
+3. 在左侧导航栏,选择 **功能配置** > **增值服务** > **实时音视频**。
+4. 点击 **立即开通**。
+
+
+
+
+
+
+
diff --git a/docs/callkit/android/product_overview.md b/docs/callkit/android/product_overview.md
new file mode 100644
index 000000000..426d8c5ce
--- /dev/null
+++ b/docs/callkit/android/product_overview.md
@@ -0,0 +1,65 @@
+# 产品概述
+
+## CallKit 介绍
+
+环信音视频通话 CallKit 是基于环信即时通讯 IM 和声网实时音视频 RTC 深度整合开发的实时音视频通话框架,实现了一对一及群组音视频通话功能。开发者只需简单集成,即可快速获得稳定流畅的高品质音视频通话能力。环信音视频通话 CallKit 可用于在线互动课堂、视频客服中心、远程会诊系统或视频相亲等场景。
+
+
+
+## CallKit 优势
+
+| 优势 | 描述 |
+| :-------------- | :----- |
+| 三位一体技术整合 | - 环信即时通讯 IM + 声网实时音视频 RTC + UI 组件深度整合
- 双平台服务一键开通,免除多系统对接成本 |
+| 多重唤醒系统 | Android 平台支持离线推送 和 Telecom 实现来电通知 |
+| 高质量通话品质 | - 声网全球网络:超过 99.99% 服务可用性
- 超低延时:低于 76ms 的端到端延迟
- 抗丢包技术:80% 丢包仍可流畅通话 |
+
+## 主要功能
+
+| 基本功能 | 高级功能 | 功能优势 |
+| :-------------- | :----- | :------- |
+| - 一对一语音/视频通话
- 群组语音/视频通话(16 人及以下):通话中邀请他人
- 自定义铃声:支持主叫、被叫、挂断、超时铃声
- 打开/关闭悬浮窗
- 自定义 UI 界面 | - 高画质/高音质音视频
- 离线推送
- 通话质量检测
- 全球互通
- 弱网卡顿优化
- 视频降噪| - 高质量音视频通话
- 完善的 UI 交互
- 支持多平台互联互通
- 离线推送稳定且多样化 |
+
+## 界面效果展示
+
+### 一对一视频通话
+
+
+
+
+
+
+
+
+### 一对一音频通话
+
+
+
+
+
+
+
+
+### 群组通话
+
+
+
+
+
+
+
+
+
+### 来电通知
+
+
+
+
+
+
+
+
+## 使用限制
+
+- 群组音视频通话默认最多支持 16 人。
+- 关于声网 RTC 的使用限制,详见 [声网 RTC 关键性能指标](https://doc.shengwang.cn/doc/rtc/android/overview/product-overview#%E5%85%B3%E9%94%AE%E6%80%A7%E8%83%BD%E6%8C%87%E6%A0%87) 和 [配额限制](https://doc.shengwang.cn/doc/rtc/android/overview/product-overview#%E9%85%8D%E9%A2%9D%E9%99%90%E5%88%B6)。
diff --git a/docs/callkit/android/product_purchase.md b/docs/callkit/android/product_purchase.md
new file mode 100644
index 000000000..9a9462787
--- /dev/null
+++ b/docs/callkit/android/product_purchase.md
@@ -0,0 +1,79 @@
+# 购买指南
+
+实时音视频服务通过 RTC 套餐包和加油包计费。
+
+## 订阅/升级套餐包
+
+实时音视频服务开通时默认订阅免费版套餐包,你可以随时升级套餐包为付费版本。套餐升级后当日立即生效并计费,并自动续费。
+
+关于声网 RTC 套餐包的计费详情,请参见声网官网的 [RTC 计费策略](https://doc.shengwang.cn/doc/rtc/android/billing/billing-strategy#套餐内订阅费用) 和 [付费方式](https://doc.shengwang.cn/doc/rtc/android/billing/payment#套餐包) 说明。
+
+:::tip
+套餐包订阅/升级需按照该自然月的剩余天数交费或补足差价,价格均摊和配额均摊规则详见 [付费方式](https://doc.shengwang.cn/doc/rtc/android/billing/payment#套餐包)。如有疑问,请联系环信商务。
+:::
+
+你可以按以下步骤升级声网 RTC 套餐包:
+
+1. 登录 [环信控制台](https://console.easemob.com/user/login)。
+2. 选择页面上方的 **应用管理**。在弹出的应用列表页面,单击你的应用的 **操作** 栏中的 **管理**。
+3. 在左侧导航栏,选择 **服务开通** > **增值服务** > **实时音视频**。
+4. 点击 **升级套餐**。
+
+
+
+5. 选择 RTC 套餐版本,点击 **立即购买**。
+
+
+
+6. 确认账单信息,点击 **下一步**。在弹出的 **付款确认** 对话框中,确认付款信息后,阅读并同意《环信云服务购买协议》,点击 **立即支付**。
+
+
+
+7. 购买套餐后,可在 **实时音视频服务** 页面查看你的套餐包容量。
+
+
+
+## 订阅/升级加油包
+
+加油包支持按时长购买,购买后立即生效,并可抵扣本月 1 日至次年该月月底产生的超量费用。
+
+关于 RTC 加油包的计费详情,请参见声网官网的 [RTC 计费策略](https://doc.shengwang.cn/doc/rtc/android/billing/billing-strategy#使用加油包抵扣) 和 [付费方式](https://doc.shengwang.cn/doc/rtc/android/billing/payment#加油包) 说明。
+
+:::tip
+1. 加油包不支持退订。
+2. 加油包支持叠加购买,如购买多个加油包,按照购买时间依次扣除。
+:::
+
+你可以按以下步骤购买声网 RTC 加油包:
+
+1. 登录 [环信控制台](https://console.easemob.com/user/login)。
+2. 选择页面上方的 **应用管理**。在弹出的应用列表页面,单击你的应用的 **操作** 栏中的 **管理**。
+3. 在左侧导航栏,选择 **服务开通** > **增值服务** > **实时音视频**。
+4. 点击 **服务总览** 栏中的 **购买加油包**。
+
+
+
+5. 确认加油包信息,点击 **下一步**。在弹出的 **付款确认** 对话框中,确认付款信息后,阅读并同意《环信云服务购买协议》,点击 **立即支付**。
+
+
+
+6. 购买套餐包后,你可以查看当前音视频服务的总量。
+
+
+
+## 续费套餐包
+
+所有版本默认自动续订。如需取消续订,请将套餐切换为免费版。
+
+## 套餐降级
+
+套餐包每自然月仅可降级一次,请谨慎操作,如需撤回或修改,请联系商务。
+
+**套餐降级次月 1 号生效,该自然月套餐不变。**
+
+## 充值和账单
+
+登录环信控制台,将鼠标悬停在页面右上角的 **账单中心**,选择 **账户中心** 可以查看当前的账户余额,点击 **立即充值** 对该账户充值。选择 **订单中心**、**消费账单** 或 **充值记录**,查看 RTC 服务的订单、消费账单和充值记录。更多详情,请参见 [环信控制台文档](/product/console/account_center.html)。
+
+
+
diff --git a/docs/callkit/android/quickstart.md b/docs/callkit/android/quickstart.md
new file mode 100644
index 000000000..292f1eb99
--- /dev/null
+++ b/docs/callkit/android/quickstart.md
@@ -0,0 +1,586 @@
+# 快速开始
+
+利用环信 CallKit(基于 IM 4.16.0 或以上版本),你可以轻松实现一对一通话和群组通话功能。本文介绍如何快速实现发起音视频通话。
+
+## 推荐环境
+
+- Android SDK: API Level 24 或以上版本
+- Android Studio: 推荐最新版本
+- Kotlin: 2.0.21
+- JDK: 17
+- Gradle 版本: 8.13
+
+## 前提条件
+
+在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+1. [注册环信账号](/product/console/account_register.html#注册账号)。
+2. [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+3. [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID 和 Token。
+4. [开通音视频服务](product_activation.html)。为了保障流畅的用户体验,开通服务后,你需等待 15 分钟才能实现发起音视频通话。
+
+## 快速开始
+
+### 步骤 1 创建项目
+
+本节介绍将环信 CallKit 引入项目中的必要环境配置。
+
+本示例使用 `Android Studio Narwhal | 2025.1.1`、`gradle version : 8.13` 和 `gradle plugin version:8.11.1`。你也可以直接参考 Android Studio 官网文档 [创建应用](https://developer.android.com/studio/projects/create-project)。
+
+1. 打开 Android Studio,点击左上角菜单 **File > New > New Project**。
+2. 在 **New Project** 界面,**Phone and Tablet** 标签下,选择 **Empty Views Activity**,然后点击 **Next**。
+3. 在 **Empty Views Activity** 界面,依次填入以下内容:
+ - **Name**:你的 Android 项目名称,如 CallKitQuickstart。
+ - **Package name**:你的项目包的名称,如 com.hyphenate.callkit.quickstart。
+ - **Save location**:项目的存储路径。
+ - **Language**:项目的编程语言,如 Kotlin。
+ - **Minimum SDK**:项目的最低 API 等级,如 API 24。
+ - **Build configuration language**:工程构建语言,如 Kotlin DSL(build.gradle.kts)。
+4. 点击 **Finish**。根据屏幕提示,安装所需插件。
+
+### 步骤 2 引入 CallKit
+
+#### 添加依赖
+
+**远程依赖**
+
+- 在 Project 工程根目录下的 `settings.gradle.kts` 文件内,添加 `mavenCentral()` 仓库:
+
+```kotlin
+pluginManagement {
+ repositories {
+ ...
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ ...
+ mavenCentral()
+ }
+}
+```
+
+- 在 app(module) 目录的 `build.gradle.kts` 文件中添加以下依赖:
+
+```kotlin
+dependencies {
+ ...
+ implementation("io.hyphenate:chat-call-kit:4.16.0")
+}
+```
+
+**本地依赖**
+
+从 GitHub 获取音视频 [CallKit 源码](https://github.com/easemob/easemob-callkit-android.git),克隆到本地。按照下面的方式集成:
+
+- 在 Project 工程根目录下的 `settings.gradle.kts` 文件中添加如下代码:
+
+```kotlin
+include(":ease-call-kit")
+// "../easemob-callkit-android"要替换成你clone下来的实际工程路径,后边要拼接"/ease-call-kit"
+project(":ease-call-kit").projectDir = File("../easemob-callkit-android/ease-call-kit")
+```
+
+- 在 app(module) 目录的 `build.gradle.kts` 文件中添加如下代码:
+
+```kotlin
+dependencies {
+ ...
+ implementation(project(":ease-call-kit"))
+}
+```
+
+#### 配置 ViewBinding
+
+在 app 项目的 `build.gradle.kts` 文件中添加如下代码:
+
+```kotlin
+android {
+ ...
+ buildFeatures{
+ viewBinding = true
+ }
+}
+```
+
+#### Android Support 库向 AndroidX 转换配置
+
+在 `Project` 工程根目录下的 `gradle.properties` 文件中额外添加如下配置:
+
+```
+android.enableJetifier=true
+```
+
+#### 防止代码混淆
+
+在 app 的 `proguard-rules.pro` 文件中添加如下代码:
+
+```
+-keep class com.hyphenate.** {*;}
+-dontwarn com.hyphenate.**
+-keep class io.agora.** {*;}
+-dontwarn io.agora.**
+```
+
+### 步骤 3 创建快速开始页面
+
+1. 打开 `app/src/main/res/values/strings.xml` 文件,替换为如下内容。
+
+你需要将 **app_key** 替换为你申请的环信 App Key,**user_name** 替换为你的用户名,**token** 替换为你用户名对应的token。
+
+```xml
+
+ CallKitQuickstart
+ app_key
+ your userId
+ your token
+
+```
+
+2. 打开 `app/src/main/res/layout/activity_main.xml` 文件,替换为如下内容:
+
+::: details app/src/main/res/layout/activity_main.xml 文件中的替换代码
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+:::
+
+### 步骤 4 实现代码逻辑
+
+1. 初始化 CallKit。
+2. 实现登录和退出逻辑。
+3. 实现通话功能。
+
+打开 `MainActivity` 文件,替换为如下代码(以包名 `com.hyphenate.callkit.quickstart` 为例):
+
+::: details MainActivity 文件中的替换代码
+```kotlin
+package com.hyphenate.callkit.quickstart
+
+import android.R.attr.password
+import android.os.Bundle
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import com.hyphenate.callkit.CallKitClient
+import com.hyphenate.callkit.CallKitConfig
+import com.hyphenate.callkit.bean.CallType
+import com.hyphenate.callkit.bean.CallEndReason
+import com.hyphenate.callkit.bean.CallInfo
+import com.hyphenate.callkit.interfaces.CallKitListener
+import com.hyphenate.callkit.utils.ChatClient
+import com.hyphenate.callkit.utils.ChatCallback
+import com.hyphenate.callkit.utils.ChatOptions
+import com.hyphenate.callkit.utils.ChatConnectionListener
+import com.hyphenate.callkit.quickstart.databinding.ActivityMainBinding
+import com.hyphenate.callkit.utils.ChatLog
+import io.agora.rtc2.RtcEngine
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.json.JSONObject
+import android.view.KeyEvent
+import android.view.inputmethod.InputMethodManager
+import android.view.inputmethod.EditorInfo
+import android.content.Context
+
+class MainActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityMainBinding
+ private var isLoggedIn = false
+ private val TAG = this::class.simpleName
+
+ // CallKit 监听器
+ private val rtcListener: CallKitListener = object : CallKitListener {
+
+ override fun onEndCallWithReason(reason: CallEndReason, callInfo: CallInfo?) {
+ runOnUiThread {
+ val msg = "通话结束: $reason ,callInfo: $callInfo"
+ ChatLog.d(TAG, msg)
+ Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onCallError(
+ errorType: CallKitClient.CallErrorType,
+ errorCode: Int,
+ description: String?
+ ) {
+ runOnUiThread {
+ val msg = "通话错误: $errorType ,errorCode: $errorCode ,description: $description"
+ ChatLog.d(TAG, msg)
+ Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onReceivedCall(userId: String, callType: CallType, ext: JSONObject?) {
+ runOnUiThread {
+ val msg = "收到通话邀请: $userId ,callType: $callType ,ext: $ext"
+ ChatLog.d(TAG, msg)
+ Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onRemoteUserJoined(userId: String, callType: CallType, channelName: String) {
+ runOnUiThread {
+ val msg = "远端用户加入: $userId ,callType: $callType ,channelName: $channelName"
+ ChatLog.d(TAG, msg)
+ Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onRemoteUserLeft(userId: String, callType: CallType, channelName: String) {
+ runOnUiThread {
+ val msg = "远端用户离开: $userId ,callType: $callType ,channelName: $channelName"
+ ChatLog.d(TAG, msg)
+ Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onRtcEngineCreated(engine: RtcEngine) {
+ runOnUiThread {
+ val msg = "RTC引擎创建: $engine"
+ ChatLog.d(TAG, msg)
+ Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ }
+
+ // 连接状态监听器
+ private val connectionListener = object : ChatConnectionListener {
+ override fun onConnected() {
+ runOnUiThread {
+ updateConnectionStatus(true, "连接状态: 已连接")
+ }
+ }
+
+ override fun onDisconnected(errorCode: Int) {
+ runOnUiThread {
+ updateConnectionStatus(false, "连接状态: 已断开")
+ }
+ }
+
+ override fun onLogout(errorCode: Int) {
+ runOnUiThread {
+ updateConnectionStatus(false, "连接状态: 已登出")
+ isLoggedIn = false
+ updateButtonStates()
+ }
+ }
+
+ override fun onTokenExpired() {
+ runOnUiThread {
+ updateConnectionStatus(false, "连接状态: Token已过期")
+ showToast("Token已过期,请重新登录")
+ }
+ }
+
+ override fun onTokenWillExpire() {
+ runOnUiThread {
+ showToast("Token即将过期")
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ initCallKit()
+ setupClickListeners()
+ updateButtonStates()
+ setupConnectionListener()
+ }
+
+ private fun initCallKit() {
+ val appkey = getString(R.string.app_key)
+ if (appkey.isEmpty()) {
+ showToast("请先设置您的AppKey!")
+ return
+ }
+
+ // 初始化环信IM SDK
+ val options = ChatOptions().apply {
+ this.appKey = appkey
+ autoLogin = false
+ }
+ ChatClient.getInstance().init(this, options)
+ ChatClient.getInstance().setDebugMode(true)
+
+ // 初始化CallKit
+ val config = CallKitConfig()
+
+ CallKitClient.init(this, config)
+ CallKitClient.callKitListener = rtcListener
+ }
+
+ private fun setupConnectionListener() {
+ ChatClient.getInstance().addConnectionListener(connectionListener)
+ updateConnectionStatus(false, "连接状态: 未连接")
+ }
+
+ private fun updateConnectionStatus(isConnected: Boolean, statusText: String) {
+ binding.tvConnectionStatus.text = statusText
+ if (isConnected) {
+ binding.statusIndicator.setBackgroundColor(0xFF4CAF50.toInt()) // 绿色
+ binding.tvConnectionStatus.setTextColor(0xFF4CAF50.toInt())
+ } else {
+ binding.statusIndicator.setBackgroundColor(0xFF808080.toInt()) // 灰色
+ binding.tvConnectionStatus.setTextColor(0xFF808080.toInt())
+ }
+ }
+
+ private fun setupClickListeners() {
+ // 设置键盘监听
+ setupKeyboardListeners()
+
+ binding.btnLogin.setOnClickListener {
+ val username = getString(R.string.user_name)
+ val token = getString(R.string.token)
+ if (username.isEmpty() || token.isEmpty()) {
+ showToast("用户名或token不能为空!")
+ return@setOnClickListener
+ }
+ login(username, token)
+ }
+
+ binding.btnLogout.setOnClickListener { logout() }
+ binding.btnSingleVideo.setOnClickListener { startSingleVideoCall() }
+ binding.btnSingleAudio.setOnClickListener { startSingleAudioCall() }
+ }
+
+ private fun setupKeyboardListeners() {
+
+ // 为对方用户ID输入框添加键盘监听
+ binding.etPeerId.setOnEditorActionListener { _, actionId, event ->
+ if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_DONE ||
+ actionId == android.view.inputmethod.EditorInfo.IME_ACTION_NEXT ||
+ (event?.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) {
+ hideKeyboard()
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ private fun hideKeyboard() {
+ val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ currentFocus?.let { view ->
+ inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
+ view.clearFocus()
+ }
+ }
+
+ private fun login(username: String, token: String) {
+ if (isLoggedIn) {
+ showToast("已经登录")
+ return
+ }
+
+ ChatClient.getInstance().loginWithToken(username, token, object : ChatCallback {
+ override fun onSuccess() {
+ runOnUiThread {
+ isLoggedIn = true
+ updateButtonStates()
+ showToast("登录成功")
+ }
+ }
+
+ override fun onError(code: Int, error: String?) {
+ runOnUiThread {
+ showToast("登录失败: $error")
+ }
+ }
+ })
+ }
+
+ private fun logout() {
+ if (!isLoggedIn) {
+ showToast("尚未登录")
+ return
+ }
+
+ ChatClient.getInstance().logout(true, object : ChatCallback {
+ override fun onSuccess() {
+ runOnUiThread {
+ updateConnectionStatus(false, "连接状态: 已登出")
+ isLoggedIn = false
+ updateButtonStates()
+ CallKitClient.endCall()
+ showToast("登出成功")
+ }
+ }
+
+ override fun onError(code: Int, error: String?) {
+ runOnUiThread {
+ showToast("登出失败: $error")
+ }
+ }
+ })
+ }
+
+ private fun startSingleVideoCall() {
+ if (!isLoggedIn) {
+ showToast("请先登录")
+ return
+ }
+
+ val remoteUserID = binding.etPeerId.text.toString().trim()
+ if (remoteUserID.isEmpty()) {
+ showToast("对方用户ID不能为空")
+ return
+ }
+
+ CallKitClient.startSingleCall(CallType.SINGLE_VIDEO_CALL, remoteUserID, null)
+ }
+
+ private fun startSingleAudioCall() {
+ if (!isLoggedIn) {
+ showToast("请先登录")
+ return
+ }
+
+ val remoteUserID = binding.etPeerId.text.toString().trim()
+ if (remoteUserID.isEmpty()) {
+ showToast("对方用户ID不能为空")
+ return
+ }
+
+ CallKitClient.startSingleCall(CallType.SINGLE_VOICE_CALL, remoteUserID, null)
+ }
+
+
+ private fun updateButtonStates() {
+ binding.btnLogin.isEnabled = !isLoggedIn
+ binding.btnLogout.isEnabled = isLoggedIn
+ binding.btnSingleVideo.isEnabled = isLoggedIn
+ binding.btnSingleAudio.isEnabled = isLoggedIn
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ ChatClient.getInstance().removeConnectionListener(connectionListener)
+ }
+
+ private fun showToast(msg: String) {
+ CoroutineScope(Dispatchers.Main).launch {
+ Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+}
+```
+:::
+
+点击 Android Studio 菜单栏中的 `Sync Project with Gradle Files` 同步工程。现在可以发起首次通话。
+
+### 步骤 5 发起首次通话
+
+1. 登录:点击 **登录**。等待连接状态指示器变绿,显示 **已连接**。
+2. 发起通话:输入对方用户 ID,点击 **发起一对一视频通话** 或 **发起一对一音频通话**。
+3. 授权权限:在弹出的权限请求中,允许访问摄像头和麦克风等权限。
+4. 通话控制:在通话中可以控制静音、摄像头、扬声器等,或者点击挂断按钮结束通话。
+
+
+
+## 运行应用
+
+运行应用前,你需要授权摄像头、麦克风、悬浮窗等权限。
+
+1. 在 Android Studio 中,点击 **Run 'app'**,将应用运行到你的设备或者模拟器上。
+2. 点击 **登录** 进行登录,登录成功或者失败有 `Toast` 提示。
+3. 更改用户名和token后,在另一台设备上运行并点击登录。
+4. 在主叫设备上输入被叫方的用户 ID,点击对应的通话按钮,即可发起音视频通话。
+
+运行应用过程中的常见问题排查如下:
+- 连接失败:检查 App Key、用户名、token 是否正确配置。
+- 通话无声音:检查麦克风权限是否已授权。
+- 视频无画面:检查摄像头权限是否已授权。
\ No newline at end of file
diff --git a/docs/callkit/android/sample_runthrough.md b/docs/callkit/android/sample_runthrough.md
new file mode 100644
index 000000000..a7fbeb6de
--- /dev/null
+++ b/docs/callkit/android/sample_runthrough.md
@@ -0,0 +1,60 @@
+# 跑通示例项目
+
+本文档基于 MainActivity 示例,帮助你快速集成和运行环信 CallKit(基于 IM 4.16.0 或以上版本),实现一对一音视频通话和群组音视频通话功能。
+
+## 推荐环境
+
+- Android SDK: API Level 24 或以上版本
+- Android Studio: 推荐最新版本
+- Kotlin: 2.0.21
+- JDK: 17
+- Gradle 版本:8.9
+
+## 前提条件
+
+在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+1. [注册环信账号](/product/console/account_register.html#注册账号)。
+2. [创建应用](/product/console/app_create.html),[获取应用的 App Key](https://doc.easemob.com/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+3. [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID 和 IM token。
+4. [创建群组](/product/console/operation_group.html#创建群组),获取群组 ID。将用户加入群组。
+5. [开通音视频服务](product_activation.html)。为了保障流畅的用户体验,开通服务后,你需等待 15 分钟才能跑通示例项目。
+
+## 操作步骤
+
+### 步骤 1 配置项目
+
+1. 在 [Github](https://github.com/easemob/easemob-callkit-android.git) 中克隆或下载代码。
+
+```bash
+git clone https://github.com/easemob/easemob-callkit-android.git
+```
+
+2. 在 Android Studio 中打开项目。
+
+选择 **File** > **New** > **Import Project**,导入下载或克隆的项目 `easemob-callkit-android`。
+
+3. 等待 Gradle 同步完成。
+
+4. 在 `MainActivity.kt` 中进行如下修改:
+
+```kotlin
+private val selfUserID = "your_user_id" // 你的用户 ID
+private val remoteUserID = "target_user_id" // 对方用户 ID,用于一对一音视频通话
+private val imToken="your_im_token" // 替换为登录Token
+private val groupID = "your_group_id" // 群组 ID
+private val imAppkey = "your_org#your_app" // 你的 App Key
+```
+### 步骤 2 运行应用
+
+1. 连接 Android 设备或启动模拟器。
+2. 点击 **Run ‘app’** 运行应用。
+
+### 步骤 3 开始通话
+
+1. 点击 **登录**。等待连接,观察连接状态指示器变绿。
+2. 点击 **发起一对一视频通话**、**发起一对一音频通话** 或 **发起群组音视频通话** 发起通话。
+3. 在弹出的页面中授权必要权限(摄像头、麦克风、悬浮窗等)。
+4. 点击 **登出** 退出登录。
+
+
+
diff --git a/docs/callkit/android/signaling.md b/docs/callkit/android/signaling.md
new file mode 100644
index 000000000..bfd1931e4
--- /dev/null
+++ b/docs/callkit/android/signaling.md
@@ -0,0 +1,7 @@
+---
+{
+ pageUri: "/callkit/ios/signaling.html",
+ title: "音视频通话信令交互逻辑
+"
+}
+---
\ No newline at end of file
diff --git a/docs/callkit/android/telecom.md b/docs/callkit/android/telecom.md
new file mode 100644
index 000000000..cca88cb18
--- /dev/null
+++ b/docs/callkit/android/telecom.md
@@ -0,0 +1,77 @@
+# Telecom
+
+Android 系统中的 Telecom 框架主要负责管理设备上的所有通话,包括传统的基于 SIM 卡的通话和 VoIP 通话。当有来电时,Telecom 框架会处理来电显示、接听、挂断等功能,并通知相关的应用程序。
+
+
+
+
+
+
+
+
+
+
+## 应用场景
+
+- 客户端已集成 FCM 推送且后台无 app 进程存活,当客户端收到推送时,会唤醒 app 进程。如果客户端已设置了自动登录,会拉取离线消息,触发 Telecom 系统原生通话界面的唤起,确保系统级来电体验。
+- 若进程在前台运行或需在后台存活,即时通讯 IM SDK 正常收发消息时,通话通知界面的显示会遵循以下策略:
+
+| 场景 | 界面显示 |
+| :------------------- | :----- |
+| 锁屏 + 有悬浮窗权限 | 使用 Telecom 系统原生来电界面。 |
+| 锁屏 + 无悬浮窗权限 | 使用 Telecom 系统原生来电界面。 |
+| 后台 + 无悬浮窗权限 | 使用 Telecom 系统原生来电界面。 |
+| 后台 + 有悬浮窗权限 | 使用 CallKit 顶部悬浮窗。 |
+| 前台 + 有悬浮窗权限 | 使用 CallKit 顶部悬浮窗。 |
+| 前台 + 无悬浮窗权限 | 使用 CallKit 默认 UI 界面。 |
+
+## 核心组件
+
+| 组件 | 说明 |
+| :--- | :--- |
+| `IncomingCallService` | 用于接收 CallKit 来电请求,并将其转发给你自己的 `ConnectionService` 进行处理。在该服务中,通过 `TelecomManager.addNewIncomingCall(handle, extras)` 触发系统来电。失败或账号未启用时,通过 `CallKitClient.signalingManager.startSendEvent()` 跳转到默认来电 UI 界面。 |
+| `VoipConnectionService`(`ConnectionService`) | 充当你的 VoIP 应用与 Android 系统原生通话 UI 和逻辑之间的桥梁,唤起来电界面。系统接听/拒绝,分别调用 `signalingManager.answerCall()` / `signalingManager.refuseCall()` 并启动 CallKit 通话界面。 |
+| `PhoneAccountHelper` | VoIP 账户的注册、启用检测和设置引导。 |
+
+## 所需权限
+
+使用 Telecom 需要以下权限,CallKit 内部已进行了声明:
+
+- `MANAGE_OWN_CALLS`
+- `READ_PHONE_STATE`
+- `CALL_PHONE`
+- `USE_SIP`
+- `READ_PHONE_NUMBERS`
+- `FOREGROUND_SERVICE_PHONE_CALL`
+
+## 接入指引
+
+若使应用具备 VoIP 功能,你需要在启动 app 后创建并启用 VoIP 账户。可参考 Demo 工程的 [MainActivity#checkPhoneAccount](https://github.com/easemob/easemob-demo-android/blob/main/app/src/main/kotlin/com/hyphenate/chatdemo/MainActivity.kt) 函数实现。
+
+1. 注册 VoIP 账户:
+
+```kotlin
+PhoneAccountHelper.registerPhoneAccount(context)
+```
+
+2. 引导用户启用创建的 VoIP 账户,可采用以下两种方式:
+ - 以小米手机为例,点击电话拨号图标,点击右上角设置图标,选择 **高级设置 > 通话账户设置**,启用对应的 VoIP 账户。
+ - 通过 `PhoneAccountHelper` 已封装好的弹窗来引导用户开启 VoIP 账户。
+
+```kotlin
+ PhoneAccountHelper.showPhoneAccountEnableGuide(context)
+```
+
+
+
+
+
+
+3. 启用 Telecom 前,CallKit 内部会检查 VoIP 账户的状态:
+ - `status.isSupported`:设备是否支持 VoIP 功能。
+ - `isRegistered`:是否已注册。
+ - `isEnabled`:是否已开启。
+
+```kotlin
+val status = PhoneAccountHelper.getPhoneAccountStatus(context)
+```
diff --git a/docs/callkit/ios/api_overview.md b/docs/callkit/ios/api_overview.md
new file mode 100644
index 000000000..6fff9b539
--- /dev/null
+++ b/docs/callkit/ios/api_overview.md
@@ -0,0 +1,93 @@
+
+# API 参考
+
+## 主要方法
+
+`CallKitManager` 中的主要方法如下表所示:
+
+| 方法 | 说明 | 参数 |
+| :--------- | :----- | :---------- |
+| `setup(_ config)` | 初始化 CallKit | - `config`: 配置对象 |
+| `call(userId, type, extensionInfo)` | 发起一对一通话 | - `type`: 通话类型
- `userId`: 对方用户 ID
- (可选)`extensionInfo`: 扩展信息 |
+| `groupCall(groupId, extensionInfo)` | 发起群组通话 | - `groupId`: 群组 ID
- (可选)`extensionInfo`: 扩展信息 |
+| `hangup()` | 结束通话 | 用户调用即可内部会自行根据状态判断 |
+| `CallKitManager.shared.usersCache` | 缓存属性可读可写 | 无 |
+| `cleanUserDefaults()` | 清理持久化资源,例如 Token 和 RTC UID 等 | 无 |
+| `tearDown()` | 销毁 CallKit 释放所有资源 | 无 |
+| `CallKitManager.shared.currentUserInfo` | 当前用户信息、可读写 | 无 |
+| `CallKitManager.shared.callInfo` | 当前通话信息、可读写 | 无 |
+| `CallKitManager.shared.profileProvider = self` | 信息提供代理 | 无 |
+| `CallKitManager.shared.addListener(self)` | 监听器 | `listener`: `CallServiceListener` 实现了 `CallServiceListener` 的对象|
+
+## 通话类型
+
+通话类型 `CallType` 如下表所示:
+
+| 类型 | 说明 |
+| :--------- | :----- |
+| `singleAudio` | 一对一语音通话 |
+| `singleVideo` | 一对一视频通话 |
+| `group` | 群组通话 |
+
+## 通话结束原因
+
+通话结束原因 `CallEndReason` 如下表所示:
+
+| 原因 | 说明 |
+| :--------- | :----- |
+| `CallEndReasonHangup` | 正常挂断 |
+| `CallEndReasonCancel` | 本地用户取消通话 |
+| `CallEndReasonRemoteCancel` | 对方取消通话 |
+| `CallEndReasonRefuse` | 本地用户拒绝接听 |
+| `CallEndReasonRemoteRefuse` | 对方拒绝接听 |
+| `CallEndReasonBusy` | 忙线中 |
+| `CallEndReasonNoResponse` | 本地用户无响应 |
+| `CallEndReasonRemoteNoResponse` | 对方无响应 |
+| `CallEndReasonHandleOnOtherDevice` | 在其他设备接听 |
+| `CallEndReasonRemoteDrop` | 通话中断 |
+
+## 监听方法
+
+环信 CallKit 提供 `CallKitListener` 监听通话过程。你可以设置监听器用于处理通话相关的回调。
+
+**所有回调方法都不在主线程执行,需要使用 `runOnUiThread` 来更新 UI。**
+
+| 方法 | 描述 | 参数 |
+| :--------- | :----- | :---------- |
+| `@objc optional func didUpdateCallEndReason(reason: CallEndReason,info: CallInfo)` | 通话结束回调 | - `reason`: 结束原因
- `callInfo`: 通话信息 |
+| `@objc optional func didOccurError(error: CallError)` | 通话错误回调 | - `error`: 错误对象
|
+| `@objc optional func onReceivedCall(callType: CallType, userId: String, extensionInfo: [String:Any]?)` | 收到通话邀请 | - `userId`: 邀请方的用户 ID
- `callType`: 通话类型
- `extensionInfo`: 扩展信息 |
+| `@objc optional func remoteUserDidJoined(userId: String, channelName: String, type: CallType)` | 远端用户加入 | - `userId`: 用户 ID
- `callType`: 通话类型
- `channelName`: 频道名称 |
+| `@objc optional func remoteUserDidLeft(userId: String, channelName: String, type: CallType)` | 远端用户离开 | - `userId`: 用户ID
- `callType`: 通话类型
- `channelName`: 频道名称 |
+| `@objc optional func onRtcEngineCreated(engine: AgoraRtcEngineKit)` | RTC 引擎创建 | `engine`: RTC 引擎实例 |
+
+## 错误类型
+
+### 通话错误类型
+
+`CallErrorModule` 类中提供三类通话错误类型:
+
+| 通话错误类型 | 描述 |
+| :--------- | :----- |
+| `business` | 业务逻辑异常。 |
+| `rtc` | 音视频异常,详见 [声网 RTC 错误码](https://doc.shengwang.cn/doc/rtc/ios/error-code)。 |
+| `im` | 即时通讯 IM 异常,详见 [环信即时通讯 IM 错误码](/document/ios/error.html) |
+| `unknown` | 未知错误。 |
+
+### 业务错误类型
+
+`CallBusinessErrorCode` 类中提供四类业务错误类型:
+
+| 业务错误类型 | 描述 |
+| :--------- | :----- |
+| `state ` | 通话状态错误:
- "A call is already in progress":当前已有通话在进行中。
- "MultiCallParticipantsController is already presented":多人通话邀请界面已展示,表示重复调用群组通话 API。
- "Call already in progress with different group ID":当前已有通话在进行中且群组 ID 不同,表示点击多人通话页面右上角时,呼叫的群组 ID 有误或者中途被其它地方改变。 |
+| `param ` | 参数错误:主要为呼叫 API 调用参数错误为空等。 |
+| `signaling` | 信令错误:大多为信令回复的方法中某些参数错误,例如,对方发的信令里缺少某种参数。 |
+| `unknown` | 未知错误。 |
+
+### 获取日志
+
+- 日志中携带 `EaseCallKit Log:` 的所有内容均为 CallKit 日志。你可以通过查看日志进行代码问题排查。关于如何获取日志,详见 [环信即时通讯 IM 文档](/document/ios/log.html)。
+- 线上获取 SDK 日志,需要设备在登录状态下联系环信技术支持。技术支持获取到线上设备的日志,排查线上用户的问题。
+
+
diff --git a/docs/callkit/ios/architecture.md b/docs/callkit/ios/architecture.md
new file mode 100644
index 000000000..7fc79622d
--- /dev/null
+++ b/docs/callkit/ios/architecture.md
@@ -0,0 +1,36 @@
+# CallKit 架构
+
+## 项目概述
+
+环信 iOS CallKit 是基于环信即时通讯 IM SDK 和声网 RTC SDK 开发的实时音视频通话框架。该项目使用 Swift 作为开发语言,包含如下模块:
+
+- `CoreService`:核心协议层以及定义。
+ - `Provider`:包含 `CallProfileProtocol.swift` 用户信息协议以及 `Providers.swift` 信息提供者协议。
+ - `Services`:包含 `CallError.swift` 错误信息以及 `CallMessageService.swift` 呼叫 API 协议以及回调方法和结束原因枚举等。
+ - `Implements`:对应协议的实现组件。
+- `Resource`:图像或本地化文件。
+- `Commons`:包含一些工具类、UI 配置类和主题类等。`CallKitManager` 用到的工具类:`AudioPlayerManager`、`LiveCommunicationManager`、`GlobalTimerManager`。
+- `UI`:包含所有 UI 组件,包括视图控制器、`UIView`、`UITableViewCell` 等。
+
+## 项目结构
+
+```
+Classes
+├─ CoreService // 核心协议层以及定义。
+│ ├─ Provider //CallKit 用户信息获取缓存等。
+│ ├─ Service // 业务协议。
+│ │ ├─ CallMessageService // 呼叫 API 和部分回调,以及常量枚举定义。
+│ └─ Implements // 上面对应协议的实现组件。核心`CallKitManager`实现,分别为扩展处理`CallKitManager+Signaling.swift`、`CallKitManager+RTC.swift`等。
+├─ Resource // 图像或本地化文件。
+├─ Commons
+ ├─ Utils // 一些 CallKitManager 用到的工具类(AudioPlayerManager、LiveCommunicationManager、GlobalTimerManager)以及相关 UI 类。
+ ├─ Appearance // UI 以及资源配置相关。
+ ├─ ConsoleLog // 日志打印相关。
+ ├─ Theme // 主题相关组件,包括颜色、字体、换肤协议及其组件。
+ └─ Extension // 一些方便的系统类扩展。
+│
+└─ UI // 基本 UI 组件,不带业务。
+ ├─ Controllers // 视图控制器。
+ ├─ Views // 所有 UIView。
+ └─ Cells // 所有 UITableViewCell。
+```
\ No newline at end of file
diff --git a/docs/callkit/ios/common_issue.md b/docs/callkit/ios/common_issue.md
new file mode 100644
index 000000000..9ba109e4b
--- /dev/null
+++ b/docs/callkit/ios/common_issue.md
@@ -0,0 +1,18 @@
+# 常见问题
+
+## 1. 兼容性问题
+
+关于环信 CallKit 与系统电话或其它应用的兼容问题,你可以监听系统电话事件,调用 `CallKitManager.shared.hangup()` 方法判断是否需要挂断当前通话。
+
+## 2. 好友检查
+
+默认情况下,环信 CallKit 支持陌生人之间进行通话,即无需添加好友即可通话。若在即时通讯 IM 控制台 [开启了好友检查](/product/console/basic_user.html#好友关系检查),会导致非好友不能通过 CallKit 进行一对一通话,群组音视频通话信令也会受影响(邀请使用群定向消息,其他信令均为单聊消息)。建议不开启好友检查,后续 SDK 迭代会优化。
+
+## 3. 通话无声音/无画面
+
+如果通话无声音或无画面,请检查权限问题。iOS 15 及以上系统需要在 `Info.plist` 中添加`NSMicrophoneUsageDescription` 和 `NSCameraUsageDescription` 描述。若排除权限问题,请联系技术支持查询应用的音视频流状态。
+
+## 4. 退出 IM SDK 账号相关
+
+登出 IM 账号的时候需要调用 `CallKitManager.shared.cleanUserDefaults()` 方法清理 CallKit 相关的持久化数据,这些数据在 app 在后台或者锁屏时供 CallKit 通信加入 RTC 频道使用。
+
diff --git a/docs/callkit/ios/customization.md b/docs/callkit/ios/customization.md
new file mode 100644
index 000000000..2fdc95c23
--- /dev/null
+++ b/docs/callkit/ios/customization.md
@@ -0,0 +1,59 @@
+# 自定义
+
+你可以修改我们提供的 CallKit 源代码,对 CallKit 用户界面进行调整。
+
+## 修改 UI 配置项
+
+`CallAppearance.swift` 是包含所有可配项的类。这些可配项都有默认值,如果要修改某些配置项,需要在初始化对应 UI 控件之前修改其中的属性,配置项才生效。
+
+| 配置项 | 描述 |
+| :------------------- | :----- |
+| `avatarRadius` | 修改头像圆角。 |
+| `avatarPlaceHolder` | 修改头像占位图。 |
+| `resourceBundle` | 整体替换资源 bundle。 |
+| `backgroundImage` | 替换呼叫背景图。 |
+
+## 修改原有资源
+
+CallKit 的资源均放在 `CallResource.bundle` 文件中。
+
+
+
+- 图片资源
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| 导航资源 | 导航相关图标,例如 back 和 boxes。|
+| 背景图片 | 通话背景图 bg.png 等。 |
+| 被叫弹窗 | 例如,phone_hang_mini.png、phone_pick_mini.png。 |
+| 呼叫页面图标 | 例如,phone_hang、phone_pick、speaker_on、speaker_off、camera_on、camera_off、mic_on、mic_off、flip_front、flip_back 等。 |
+| 其他资源 | 例如,person_add.png、网络相关 network_xxx.png、speaking.png 讲话中相关图标等。 |
+
+- 音频资源
+
+| 资源 | 描述 |
+| :----- | :---------- |
+| 音频文件 | - dialing.mp3:拨号声音。
- ringing.mp3:响铃声音。
- busy.mp3:忙音。
铃声文件建议为 MP3、WAV 等格式,铃声时长为 1-20 秒,文件大小不超过 1 MB。 |
+
+- 国际化资源
+
+| 资源 | 描述 |
+| :------------------- | :----- |
+| 国际化语言 | en:英文;zh-Hans:简体中文。 |
+
+## 修改业务可配置项
+
+你可以修改以下业务可配项:
+
+- 开启 VoIP 功能后会自动开启 LiveCommunicationKit。关于上传 VoIP 服务证书,详见 [APNs 推送文档](/document/ios/push/push_apns.html#上传推送证书)。
+- 若开启画中画功能,同时需要开启应用后台摄像头采集权限。详见 [视频通话画中画文档](picture_in_picture.html)。
+- 呼叫超时时间:单位为秒,默认为 30 秒。
+
+```Swift
+ let config = EaseCallUIKit.CallKitConfig()
+ config.enableVOIP = true //开启 VoIP 功能后会自动开启 LiveCommunicationKit,需要在 develop.apple.com 申请证书时勾选。
+ config.enablePIPOn1V1VideoScene = true //开启画中画,同时需要开启应用后台摄像头采集权限。
+ config.ringTimeOut = 30//默认呼叫超时时间。
+ CallKitManager.shared.setup(config)
+```
+
diff --git a/docs/callkit/ios/design_guide.md b/docs/callkit/ios/design_guide.md
new file mode 100644
index 000000000..4334f631b
--- /dev/null
+++ b/docs/callkit/ios/design_guide.md
@@ -0,0 +1,6 @@
+---
+{
+ pageUri: "/callkit/android/design_guide.html",
+ title: "设计指南"
+}
+---
\ No newline at end of file
diff --git a/docs/callkit/ios/easecallkit.md b/docs/callkit/ios/easecallkit.md
new file mode 100644
index 000000000..9f6280a28
--- /dev/null
+++ b/docs/callkit/ios/easecallkit.md
@@ -0,0 +1,400 @@
+# iOS 端 EaseCallKit 使用指南
+
+
+
+## 功能概述
+
+`EaseCallKit` 是一套基于声网音视频服务,使用环信 IM 作为信令通道的开源音视频 UI 库。UI 库提供了单人语音通话、视频通话,以及多人会议的功能接口。
+
+使用 `EaseCallKit` 库可以快速实现常用的音视频场景,通过信令的交互确认,保证 **用户多端登录时,收到呼叫同时振铃,一端处理后,其他端自动停止**。
+
+`EaseCallKit` 库在 Github 上进行了保存,请参见 [EaseCallKit iOS 端使用指南](https://github.com/easemob/easecallkitui-ios)。
+
+**`EaseCallKit` 在通话过程中,使用环信 ID 加入频道,方便音视频视图中显示用户名。如果用户不使用 `EaseCallKit` 而直接调用声网 API,也可以直接使用数字 UID 加入频道。**
+
+:::tip
+本 UI 库只和移动端 3.8.0 以上版本 Demo 互通。3.8.1 的 UI 库使用声网数字 UID 加入频道,而 3.8.0 使用字符串加入频道,3.8.1 版本不与 3.8.0 互通,Demo 中 EaseCallKit 使用的 token 和 UID 均由你自己生成。若你需要使用声网对应的音视频服务,需单独在声网申请。
+:::
+
+## 跑通 Demo
+
+`EaseCallKit` 集成在环信提供的开源 IM Demo 中,你可以进入 [Github 开源网站](https://github.com/easemob/chat-ios) 下载。
+
+- 安装 SDK 与 `EaseCallKit`
+
+Demo 源码中不涉及 SDK 和 `EaseCallKit`,你可以通过直接导入或通过 CocoaPods 安装。
+
+如果当前系统上没有安装 CocoaPods,需参考 [CocoaPods 安装说明](https://guides.cocoapods.org/using/getting-started.html)进行安装。
+
+使用 CocoaPods 安装需要进入 `podfile` 所在目录,然后在终端执行如下命令:
+
+```
+pod install
+```
+
+- 运行 demo
+
+安装完成 SDK 与 `EaseCallKit` 后,在 Xcode 打开工作空间 `EaseChatDemo.xcworkspace`,连接手机,然后就可以运行了。
+
+## 准备条件
+
+在集成该库前,你需要满足以下条件:
+
+- 分别创建 [环信应用](/product/enable_and_configure_IM.html) 及 [声网应用](https://doc.shengwang.cn/doc/rtc/ios/get-started/enable-service#创建声网项目);
+- 已完成环信 IM 的基本功能,包括登录、好友、群组以及会话等的集成;
+- 上线之前开通声网 token 验证时,用户需要实现自己的 [App Server](https://github.com/easemob/easemob-im-app-server/tree/master/agora-app-server),用于生成 token。利用 App Server 生成 token 的过程参见 [声网 token](https://doc.shengwang.cn/doc/rtc/ios/basic-features/token-authentication)。
+
+## 快速集成
+
+使用 `EaseCallKit` 库完成音视频通话的基本流程如下:
+
+1. 用户调用 `EaseCallKit` 库初始化接口;
+2. 主叫方调用发起通话邀请接口,自动进入通话页面;
+3. 被叫方自动弹出通话请求页面,在 UI 界面选择接听,进入通话;
+4. 结束通话时,点击 UI 界面挂断按钮。
+
+### 导入 EaseCallKit 库
+
+`EaseCallKit` UI 库依赖于 `HyphenateChat`、`AgoraRtcEngine_iOS`、`Masonry` 和 `SDWebImage` 库,导入该 UI 库时需要同步导入工程,依赖库可通过 CocoaPods 导入。
+
+**`EaseCallKit` 是动态库,在 `podfile` 中必须加入 `use_frameworks!`**。
+
+`EaseCallKit` 库可通过手动导入,也可利用 CocoaPods 导入。
+
+#### 使用 CocoaPods 导入 EaseCallKit
+
+- 在 Terminal 里进入项目根目录,并运行 `pod init` 命令。项目文件夹下会生成一个 `Podfile` 文本文件。
+- 打开 `Podfile` 文件,修改文件为如下内容。注意将 `AppName` 替换为你的 app 名称。
+
+```
+use_frameworks!
+target 'AppName' do
+ pod 'HyphenateChat'
+ pod 'Masonry'
+ pod 'AgoraRtcEngine_iOS'
+ pod 'SDWebImage'
+ pod 'EaseCallKit', '~> version'
+end
+```
+
+使用 easecallkit 4.0.0 时,请使用声网音视频库 `AgoraRtcEngine_iOS/RtcBasic` 的 4.1.1 版本。
+
+- 在 Terminal 内运行 `pod update` 命令更新本地库版本。
+- 运行 `pod install` 命令安装 `EaseCallKit` UI 库。成功安装后,Terminal 中会显示 **Pod installation complete!**,此时项目文件夹下会生成一个 `xcworkspace` 文件。
+- 打开新生成的 `xcworkspace` 文件,连接手机,运行 demo。
+
+#### 手动导入 EaseCallKit
+
+- 将在跑通 Demo 阶段下载的 `EaseCallKit.framework` 复制到项目工程目录下;
+- 打开 Xcode,选择 **工程设置** > **General** 菜单,将 `EaseCallKit.framework` 拖拽到工程下,在 `Frameworks`、`libraries` 和 `Embedded Content` 中设置 `EaseCallKit.framework` 为 `Embed & Sign`。
+
+### 添加权限
+
+应用需要音频设备及摄像头权限。在 `info.plist` 文件中,点击 `+` 图标,添加如下信息:
+
+| Key | Type | Value |
+| :------------------------------------- | :----- | :-------------------------------------- |
+| Privacy - Microphone Usage Description | String | 描述信息,如“环信需要使用您的麦克风”。 |
+| Privacy - Camera Usage Description | String | 描述信息,如“环信需要使用您的摄像头” 。 |
+
+如果希望在后台运行,还需要添加后台运行音视频权限,在 `info.plist` 文件中,点击 `+` 图标,添加 `Required background modes`,`Type` 为 `Array`,在 `Array` 下添加元素 `App plays audio or streams audio/video using AirPlay`。
+
+### 初始化
+
+在环信 IM SDK 初始化完成后,同时初始化 `EaseCallKit`,初始化的同时开启回调监听,设置常用配置项。代码如下:
+
+```objectivec
+EaseCallConfig* config = [[EaseCallConfig alloc] init];
+EaseCallUser* usr = [[EaseCallUser alloc] init];
+usr.nickName = @"自定义昵称";
+usr.headImage = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"headImage" ofType:@"png"]];
+config.users = @{@"环信 ID":usr};
+config.agoraAppId=@"声网 AppID";
+[[EaseCallManager sharedManager] initWithConfig:config delegate:self];
+```
+
+可设置的配置项包括以下内容:
+
+```objectivec
+@interface EaseCallConfig : NSObject
+// 默认头像。
+@property (nonatomic) NSURL* defaultHeadImage;
+// 呼叫超时时间,单位为秒。
+@property (nonatomic) UInt32 callTimeOut;
+// 用户信息字典,key 为环信 ID,value 为 `EaseCallUser`。
+@property (nonatomic) NSMutableDictionary* users;
+// 振铃文件。
+@property (nonatomic) NSURL* ringFileUrl;
+// 声网 appId。
+@property (nonatomic) NSString* agoraAppId;
+// 声网 token 验证开关,默认不开启。
+@property (nonatomic) BOOL enableRTCTokenValidate
+@end
+```
+
+### 发起通话邀请
+
+`EaseCallKit` 初始化完成后,可以开始发起音视频通话。
+
+#### 一对一音视频通话
+
+一对一通话分为语音通话与视频通话,发起过程如下:
+
+```objectivec
+// 发起一对一通话。
+// remoteUser 邀请对象的环信 ID。
+// type 通话类型。`EaseCallType1v1Audio` 表示语音通话,`EaseCallType1v1Video` 表示视频通话。
+// ext 通话扩展信息,为用户信息字典。
+[[EaseCallManager sharedManager] startSingleCallWithUId:remoteUser type:aType ext:nil completion:^(NSString * callId, EaseCallError * aError) {
+
+}];
+```
+
+#### 多人音视频通话
+
+你可以从群组成员列表或者好友列表中选择用户,发起多人音视频通话。具体实现可参考 Demo 中的 `ConfInviteUsersViewController`。
+
+```objectivec
+//邀请用户加入多人通话。
+// aInviteUsers 受邀用户的环信 ID 数组。
+// ext 可设置扩展信息,如果从群组发起,可通过 `ext` 设置群组 ID,其他用户也可邀请该群组成员。
+[[EaseCallManager sharedManager] startInviteUsers:aInviteUsers ext:@{@"groupId":aConversationId} completion:^(NSString * callId, EaseCallError * aError) {
+
+}];
+```
+
+发起通话后的 UI 界面如下:
+
+
+
+### 收到邀请
+
+主叫方调用邀请接口后,如果被叫方在线且并未处于通话过程中,将弹出通话页面,被叫用户可选择接听或者拒绝。通话页面如下:
+
+
+
+被叫振铃的同时,会触发以下回调:
+
+```objectivec
+- (void)callDidReceive:(EaseCallType)aType inviter:(NSString*_Nonnull)user ext:(NSDictionary*)aExt
+ {
+
+ }
+```
+
+### 多人通话中间发起邀请
+
+多人通话中,当前用户可以点击通话界面右上角的邀请按钮再次向其他用户发起邀请。这种情况下,会触发 `EaseCallKitListener` 中的 `multiCallDidInvitingWithCurVC` 回调:
+
+```objectivec
+// 多人音视频邀请按钮的回调。
+// vc 当前视图控制器。
+// users 通话中已存在的用户。
+// aExt 通话扩展信息。
+- (void)multiCallDidInvitingWithCurVC:(UIViewController*_Nonnull)vc excludeUsers:(NSArray *_Nullable)users ext:(NSDictionary *)aExt
+ {
+ //若只邀请群组中的用户加入通话,发起通话时在扩展信息里添加 `groupId`。
+ NSString* groupId = nil;
+ if(aExt) {
+ groupId = [aExt objectForKey:@"groupId"];
+ }
+
+ ConfInviteUsersViewController * confVC = nil;
+ if([groupId length] == 0) {
+ confVC = [[ConfInviteUsersViewController alloc] initWithType:ConfInviteTypeUser isCreate:NO excludeUsers:users groupOrChatroomId:nil];
+ }else{
+ confVC = [[ConfInviteUsersViewController alloc] initWithType:ConfInviteTypeGroup isCreate:NO excludeUsers:users groupOrChatroomId:groupId];
+ }
+ [confVC setDoneCompletion:^(NSArray *aInviteUsers) {
+ [[EaseCallManager sharedManager] startInviteUsers:aInviteUsers ext:aExt completion:nil];
+ }];
+ confVC.modalPresentationStyle = UIModalPresentationPopover;
+ [vc presentViewController:confVC animated:NO completion:nil];
+ }
+
+```
+
+通话邀请界面的实现,可以参考 Demo 中的 `ConfInviteUsersViewController` 实现。
+
+### 当前用户成功加入频道回调
+
+自 `EaseCallKit` 3.8.1 新增 `callDidJoinChannel` 方法,在用户加入通话后会收到回调:
+
+```objectivec
+- (void)callDidJoinChannel:(NSString*_Nonnull)aChannelName uid:(NSUInteger)aUid
+ {
+ //此时,可以获取当前频道中已有用户的声网 ID 与环信 ID 的映射表,并将映射表设置到 `EaseCallKit`,同时也可以更新用户的头像和昵称。
+ //[self _fetchUserMapsFromServer:aChannelName];
+ [[EaseCallManager sharedManager] setUsers:users channelName:channelName];
+ }
+
+```
+
+### 对方成功加入频道回调
+
+自 `EaseCallKit` 3.8.1 新增 `remoteUserDidJoinChannel` 方法,在对方用户加入通话后会收到回调。
+
+```objectivec
+-(void)remoteUserDidJoinChannel:( NSString*_Nonnull)aChannelName uid:(NSInteger)aUid username:(NSString*_Nullable)aUserName
+{
+ // 此时,可以获取当前频道中已有用户的声网 RTC UID 与环信 ID 的映射表,并将映射表设置到 `EaseCallKit`,同时也可以更新用户的头像和昵称。
+ //[self _fetchUserMapsFromServer:aChannelName];
+ [[EaseCallManager sharedManager] setUsers:users channelName:channelName];
+}
+```
+
+### 通话结束
+
+在一对一音视频通话中,若其中一方挂断,双方的通话会自动结束,而多人音视频通话中需要主动挂断才能结束通话。通话结束后,会触发 `callDidEnd` 回调:
+
+```objectivec
+// 通话结束回调。
+// aChannelName 通话使用的声网频道名称,用户可以根据频道名称,到声网 Console 的水晶球查询通话质量。
+// aTm 通话时长,单位为秒。
+// aCallType 通话类型。
+- (void)callDidEnd:(NSString*)aChannelName reason:(EaseCallEndReason)aReason time:(int)aTm type:(EaseCallType)aCallType
+ {
+ NSString* msg = @"";
+ switch (aReason) {
+ case EaseCallEndReasonHandleOnOtherDevice:
+ msg = @"已在其他设备处理。";
+ break;
+ case EaseCallEndReasonBusy:
+ msg = @"对方忙。";
+ break;
+ case EaseCallEndReasonRefuse:
+ msg = @"对方拒绝接听。";
+ break;
+ case EaseCallEndReasonCancel:
+ msg = @"您已取消通话。";
+ break;
+ case EaseCallEndReasonRemoteCancel:
+ msg = @"对方取消通话。";
+ break;
+ case EaseCallEndReasonRemoteNoResponse:
+ msg = @"对方无响应。";
+ break;
+ case EaseCallEndReasonNoResponse:
+ msg = @"您未接听。";
+ break;
+ case EaseCallEndReasonHangup:
+ msg = [NSString stringWithFormat:@"通话已结束,通话时长:%d秒",aTm];
+ break;
+ default:
+ break;
+ }
+ if([msg length] > 0)
+ [self showHint:msg];
+ }
+```
+
+## 进阶功能
+
+### 通话异常回调
+
+通话过程中如果有异常或者错误发生,会触发 `callDidOccurError` 回调:
+
+异常包括业务逻辑异常、音视频异常以及 Easemob IM 异常。
+
+```objectivec
+// 通话异常回调。
+// aError 为异常信息,包括了 Easemob IM 异常,RTC 异常,业务异常三种情况。
+- (void)callDidOccurError:(EaseCallError *)aError
+ {
+
+ }
+```
+
+`EaseCallError` 异常包括 IM 异常,RTC 异常以及业务逻辑异常。
+
+```objectivec
+@interface EaseCallError : NSObject
+// 异常类型,包括 Easemob IM 异常、RTC 异常和业务逻辑异常。
+@property (nonatomic) EaseCallErrorType aErrorType;
+// 异常代号。
+@property (nonatomic) NSInteger errCode;
+// 异常描述。
+@property (nonatomic) NSString* errDescription;
+```
+
+### 配置修改
+
+`EaseCallKit` 库初始化之后,可调用该方法修改配置:
+
+```objectivec
+// 以下为修改铃声过程。
+EaseCallConfig* config = [[EaseCallManager sharedManager] getEaseCallConfig];
+NSString* path = [[NSBundle mainBundle] pathForResource:@"huahai128" ofType:@"mp3"];
+config.ringFileUrl = [NSURL fileURLWithPath:path];
+```
+
+### 头像昵称修改
+
+自 `EaseCallKit` 3.8.1 开始,新增了修改头像昵称的接口,用户加入频道后可修改自己和通话中其他人的头像昵称,修改方法如下:
+
+```objectivec
+EaseCallUser* user = [EaseCallUser userWithNickName:info.nickName image:[NSURL URLWithString:info.avatarUrl]];
+[[[EaseCallManager sharedManager] getEaseCallConfig] setUser:username info:user];
+```
+
+## 参考
+
+### 获取声网 token
+
+用户加入音视频通话时,如果需要进行声网 token 鉴权,需要先开启 token 验证开关,开启过程如下:
+
+```objectivec
+EaseCallUser* callUser = [[EaseCallUser alloc] init];
+config.enableRTCTokenValidate = YES;// 开启 RTC Token 验证,默认不开启。
+[[EaseCallManager sharedManager] initWithConfig:config delegate:self];
+```
+
+获取 token 的过程由用户自己完成,开启后在通话时,会收到 `callDidRequestRTCTokenForAppId`回调,用户需要在回调中,实现从用户自己的 App Server 中获取 token(App Server 的实现参见 [生成声网 Token](https://docportal.shengwang.cn/cn/video-call-4.x/token_server_ios_ng),然后调用 `setRTCToken:channelName:` 接口。
+
+```objectivec
+- (void)callDidRequestRTCTokenForAppId:(NSString * _Nonnull)aAppId
+ channelName:(NSString * _Nonnull)aChannelName
+ account:(NSString * _Nonnull)aUserAccount
+ {
+ [[EaseCallManager sharedManager] setRTCToken:@"自己的RTC Token"channelName:aChannelName];
+ }
+
+// 自 EaseCallKit 3.8.1 版本开始,`callDidRequestRTCTokenForAppId` 方法中添加了 `uid` 参数,你可以使用数字 uid 加入声网频道。
+- (void)callDidRequestRTCTokenForAppId:(NSString *)aAppId channelName:(NSString *)aChannelName account:(NSString *)aUserAccount uid:(NSInteger)aAgoraUid
+ {
+ [[EaseCallManager sharedManager] setRTCToken:@"自己的RTC Token" channelName:aChannelName uid:自己的声网uid];
+ }
+```
+
+### 离线推送
+
+为保证被叫用户 App 在后台运行或离线时也能收到通话请求,用户需开启离线推送。关于如何开启离线推送,请参见 [iOS SDK 集成](/document/ios/push/push_apns.html)。开启离线推送后,用户在离线情况下收到呼叫请求时,其手机通知页面会弹出一条通知消息,用户点击该消息可唤醒 App 并进入振铃页面。 关于离线推送场景方案,请参见 [iOS 端设置推送](/document/ios/push/push_overview.html)。
+
+## API 列表
+
+从 API 的角度看,`EaseCallKit` 库的主要包括管理模块 `EaseCallManager` 和回调模块 `EaseCallDelegate`。
+
+管理模块 `EaseCallManager` 的 API 列表如下:
+
+| 方法 | 说明 |
+| :----------------------------------------- | :----------------------------------------------------------- |
+| initWithConfig:delegate | 初始化方法 |
+| startSingleCallWithUId:type:ext:completion | 发起一对一通话。 |
+| startInviteUsers:type:ext:completion: | 邀请用户加入多人通话。 |
+| getEaseCallConfig | 获取 `EaseCallKit` 相关配置。 |
+| setRTCToken:channelName: | 设置声网 Token。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+| setRTCToken:channelName:uid: | 设置声网 Token。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+| setUsers:channelName: | 设置环信 ID 与声网 uid 的映射表。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+
+回调模块 `EaseCallDelegate` 的 API 列表如下:
+
+| 方法 | 说明 |
+| :------------------------------------------------------ | :----------------------------------------------------------- |
+| callDidEnd:reason:time:type: | 通话结束时触发该事件。 |
+| multiCallDidInvitingWithCurVC:excludeUsers:ext: | 多人通话中点击邀请按钮触发该事件。 |
+| callDidReceive:inviter:ext: | 振铃时触发该事件。 |
+| callDidRequestRTCTokenForAppId:channelName:account: | 获取声网 token 回调。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+| callDidRequestRTCTokenForAppId:channelName:account:uid: | 获取声网 token 回调。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+| callDidOccurError: | 通话异常时触发该事件。 |
+| remoteUserDidJoinChannel:uid: | 对方加入频道时触发。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
+| callDidJoinChannel:uid: | 当前用户加入频道时触发。该方法自 `EaseCallKit` 3.8.1 版本添加。 |
diff --git a/docs/callkit/ios/integration.md b/docs/callkit/ios/integration.md
new file mode 100644
index 000000000..4d7d6675f
--- /dev/null
+++ b/docs/callkit/ios/integration.md
@@ -0,0 +1,455 @@
+# CallKit 集成指南
+
+环信 CallKit 是一套基于环信即时通讯 IM(基于 IM 4.16.0 及以上)和声网 RTC 结合开发的音视频 UI 库。使用环信 CallKit 之前,你需要将其集成到你的应用中。如果用户要使用系统的 LiveCommunicationKit,建议设置环信即时通讯 IM 为自动登录。
+
+
+
+
+
+
+## 开发环境要求
+
+- Xcode 16.0 或以上版本
+- 最低支持系统版本:iOS 15.0
+- 已为你的项目设置有效的开发者签名
+- CocoaPods v1.14.3 或以上版本
+
+## 前提条件
+
+在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+1. [注册环信账号](/product/console/account_register.html#注册账号)。
+2. [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+3. [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID 和 [用户 Token](/product/console/operation_user.html#查看用户-token)。
+4. [创建群组](/product/console/operation_group.html#创建群组),获取群组 ID,将用户加入群组。
+5. [开通音视频服务](product_activation.html)。
+
+## 集成步骤
+
+### 步骤 1 安装 CallKit
+
+你可以使用 CocoaPods 安装环信 CallKit 作为 Xcode 项目的依赖项。
+
+CocoaPods 是 iOS 和 macOS 项目的依赖管理工具。它允许您轻松地将第三方库集成到您的项目中,并自动处理依赖关系。安装方法请自行询问AI或者搜索引擎。
+
+使用 `pod init` 命令创建 `podfile` 文件,在 `podfile` 中添加如下依赖
+
+1. 在 `podfile` 中添加如下依赖:
+
+```ruby
+source 'https://github.com/CocoaPods/Specs.git'
+platform :ios, '14.0'
+
+target 'YourTarget' do
+ use_frameworks!
+
+ pod 'EaseCallUIKit'
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |config|
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
+ config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
+ end
+ end
+end
+```
+
+2. 运行 cd 命令到终端下 `podfile` 所在文件夹目录执行以下命令:
+
+```
+pod install
+```
+
+### 步骤 2 初始化 CallKit
+
+CallKit 初始化包括如下步骤:
+
+1. 初始化环信环信即时通讯 IM SDK。CallKit 基于即时通讯 IM 作为信令通道,因此需先初始化 IM SDK。
+ - 填入你的应用的 App Key。
+ - 设置即时通讯 IM SDK 的 `EMOptions`/`ChatSDKOptions` 类中的一些选项。
+ - 如果用户要使用系统的 LiveCommunicationKit,建议设置环信即时通讯 IM 为自动登录 `isAutoLogin` 为 `true`。
+2. 初始化 CallKit。
+ (可选)开启 VoIP 和画中画功能。
+ - 开启 VoIP 功能后会自动开启 LiveCommunicationKit。关于上传 VoIP 服务证书,详见 [APNs 推送文档](/document/ios/push/push_apns.html#上传推送证书)。
+ - 若开启画中画功能,同时需要开启应用后台摄像头采集权限。详见 [视频通话画中画文档](picture_in_picture.html)。若不开启画中画功能,enablePIPOn1V1VideoScene 设置为 false,点击视频通话左上角的缩小按钮,会变成音频悬浮窗。
+
+在整个应用生命周期中,初始化一次即可。
+
+- 已集成 IM SDK,初始化 CallKit 的代码示例如下:
+
+```Swift
+ //已经集成了环信 IM SDK 即已经 import HyphenateChat
+ private func setupCallKit() {
+ let options = EMOptions(appkey: appKey)
+ #if DEBUG
+ options.apnsCertName = "Your_APNS_Developer"
+ options.pushKitCertName = "YourVoipDev"
+ #else
+ options.apnsCertName = "Your_APNS_Product"
+ options.pushKitCertName = "YourVoipPro"
+ #endif
+ EMClient.shared().initializeSDK(with: options)
+ //初始化环信CallKit
+ let config = EaseCallUIKit.CallKitConfig()
+ config.enableVOIP = true//开启voip功能后会自动开启LiveCommunicationKit,需要在develop.apple.com申请证书时勾选。
+ config.enablePIPOn1V1VideoScene = true//开启画中画,同时需要开启应用后台摄像头采集权限。。
+ CallKitManager.shared.setup(config)
+ }
+```
+
+- 未集成 IM SDK,初始化 CallKit 的代码示例如下:
+
+```Swift
+ //没有集成环信 IM SDK,只想使用 CallKit
+ private func setupCallKit() {
+ let options = ChatSDKOptions(appkey: appKey)
+ #if DEBUG
+ options.apnsCertName = "Your_APNS_Developer"
+ options.pushKitCertName = "YourVoipDev"
+ #else
+ options.apnsCertName = "Your_APNS_Product"
+ options.pushKitCertName = "YourVoipPro"
+ #endif
+ ChatClient.shared().initializeSDK(with: options)
+ //初始化环信 CallKit
+ let config = EaseCallUIKit.CallKitConfig()
+ config.enableVOIP = true//开启voip功能后会自动开启LiveCommunicationKit,需要在develop.apple.com申请证书时勾选
+ config.enablePIPOn1V1VideoScene = true//开启画中画,同时需要开启应用后台摄像头采集权限,详见[PictureInPicture.md](./PictureInPicture.md)。
+ CallKitManager.shared.setup(config)
+ }
+```
+
+### 步骤 3 登录
+
+调用即时通讯 IM SDK 的 `login` 方法传入用户 ID 和 Token 登录 IM。
+
+在生产环境中,为了安全考虑,你需要在你的应用服务器集成 [获取 App Token API](/document/server-side/easemob_app_token.html) 和 [获取用户 Token API](/document/server-side/easemob_user_token.html) 实现获取 Token 的业务逻辑,使你的用户从你的应用服务器获取 Token。
+
+```Swift
+ ChatClient.shared().login(withUsername: userId, token: token) { [weak self] userId,error in
+ if let error = error {
+ self?.showCallToast(toast: "Login failed: \(error.errorDescription ?? "")")
+ } else {
+ self?.showCallToast(toast: "Login successful")
+//if !userId.isEmpty { //如有需要透传头像昵称请打开
+// let profile = CallUserProfile()
+// profile.id = userId
+// profile.avatarURL = "https://xxxxx"
+// profile.nickname = "\(userId)昵称"
+// CallKitManager.shared.currentUserInfo = profile
+//}
+ self?.userIdField.isHidden = true
+ self?.tokenField.isHidden = true
+ self?.loginButton.isHidden = true
+ }
+ }
+```
+
+### 步骤 4 配置监听器
+
+你可以调用下面方法来监听 CallKit 中用户相关状态变更的事件和错误。
+
+```swift
+ CallKitManager.shared.addListener(self)//添加监听,均为可选方法
+```
+
+以下是监听事件的示例代码:
+
+```swift
+extension MainViewController: CallServiceListener {
+ // 通话错误
+ func didOccurError(error: CallError) {
+ DispatchQueue.main.async {
+ self.showToast(toast: "Occur error:\(error.errorMessage) on module:\(error.module.rawValue)")
+ }
+ switch error { //Swift error handler
+ case .im(.invalidURL):
+ print("Invalid URL")
+ case .rtc(.invalidToken):
+ print("Invalid Token")
+ case .business(.state):
+ print("State error")
+ case .business(.param):
+ print("Param error")
+ default:
+ // 注意这里要通过 error.error.message 访问
+ print("Other error: \(error.error.message)")
+ }
+// switch error.module {//OC error handler
+// case .im:
+// switch error.getIMError() {
+// case .invalidURL:
+// print("")
+// default:
+// break
+// }
+// case .rtc:
+// switch error.getRTCError() {
+// case .invalidToken:
+// print("")
+// default:
+// break
+// }
+// case .business:
+// switch error.getCallBusinessError() {
+// case .state:
+// print("")
+// case .param:
+// print("")
+// case .signaling:
+// print("")
+// default:
+// break
+// }
+// default:
+// break
+// }
+ }
+ // 通话结束
+ func didUpdateCallEndReason(reason: CallEndReason, info: CallInfo) {
+ if let messageId = info.inviteMessageId {
+ NotificationCenter.default.post(name: Notification.Name("didUpdateCallEndReason"), object: messageId)
+ }
+
+ }
+ // 远端用户加入
+ func remoteUserDidJoined(userId: String, uid: UInt, channelName: String, type: CallType) {
+
+ }
+ // 远端用户离开
+ func remoteUserDidLeft(userId: String, uid: UInt, channelName: String, type: CallType) {
+
+ }
+ // RTC 引擎创建(可用于私有化部署配置)
+ func onRtcEngineCreated(engine: AgoraRtcEngineKit?) {
+
+ }
+
+}
+```
+
+通话结束原因 `CallEndReason` 如下表所示:
+
+| 原因 | 说明 |
+| :--------- | :----- |
+| `CallEndReasonHangup` | 正常挂断 |
+| `CallEndReasonCancel` | 本地用户取消通话 |
+| `CallEndReasonRemoteCancel` | 对方取消通话 |
+| `CallEndReasonRefuse` | 本地用户拒绝接听 |
+| `CallEndReasonRemoteRefuse` | 对方拒绝接听 |
+| `CallEndReasonBusy` | 忙线中 |
+| `CallEndReasonNoResponse` | 本地用户无响应 |
+| `CallEndReasonRemoteNoResponse` | 对方无响应 |
+| `CallEndReasonHandleOnOtherDevice` | 在其他设备接听 |
+| `CallEndReasonRemoteDrop` | 通话中断 |
+
+### 步骤 5 发起通话
+
+#### 发起一对一通话
+
+你可以使用 `call` 方法发起一对一通话,`callType` 设置为 `singleVideo` 为视频通话,`singleAudio` 为音频通话。
+
+```Swift
+@IBAction func callAction(_ sender: Any) {
+ self.view.endEditing(true)
+ guard let input = inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !input.isEmpty else {
+ return
+ }
+ CallKitManager.shared.call(with: input, type: self.callType)
+ }
+```
+
+
+
+
+
+
+#### 发起群组通话
+
+- **创建群组**:要发起群组通话,你需要首先创建群组,在群组中添加用户,详见 [环信控制台文档](/product/console/operation_group.html#创建群组)。
+- **发起群组通话**:指定群组 ID 后,CallKit 会自动拉起群成员选择界面,界面显示群组中的所有成员(群主、管理员、普通成员),用户可以选择要邀请的成员,选中人数会实时显示。为了保证通话质量和性能,CallKit 限制群组通话最多支持 **16 人** 同时参与(包括发起者)。若选择的成员数量超过 16 人时,系统会自动提示 “人数超出最大限制16人” 并阻止发起通话。
+- **通话中邀请他人**:群组通话中,当前用户可以点击通话界面右上角的邀请按钮向其他用户发起邀请。
+
+```Swift
+@IBAction func callAction(_ sender: Any) {
+ self.view.endEditing(true)
+ guard let input = inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !input.isEmpty else {
+ self.showCallToast(toast: "Please enter a valid username or group id")
+ return
+ }
+ CallKitManager.shared.groupCall(groupId: input)
+ }
+```
+
+
+
+
+
+### 步骤 6 接听通话
+
+当接收到通话邀请时,CallKit 会自动触发 `onReceivedCall` 回调:
+1. 弹出通话邀请界面。
+2. 播放来电铃声。
+3. 显示通话邀请通知(当 App 在后台时)。
+
+被叫用户可选择接听、拒绝或挂断通话。
+
+
+
+
+
+
+
+### 步骤 7 离线推送
+
+为保证被叫用户 App 在离线时也能收到通话请求,用户需开启离线推送。关于如何开启离线推送,请参见 [开启 APNs 推送](/document/ios/push/push_notification_mode_dnd.html)。开启离线推送后,用户在离线情况下收到呼叫请求时,其手机通知页面会弹出一条通知消息,用户点击该消息可唤醒 App 并进入振铃弹窗。
+
+关于离线推送场景方案,请参见 [离线推送文档](/document/ios/push/push_overview.html)。
+
+
+
+
+
+## 进阶用法
+
+### 用户信息
+
+默认情况下,音视频通话时,对于用户信息,CallKit 会显示默认头像和用户 ID;对于群信息,CallKit 会根据群组 ID 从 SDK 中拉取群信息来对应显示群组名称和群头像。
+
+如果要在一对一通话界面显示自定义用户头像和昵称,群聊通话显示自定义群头像和群名称,你可以通过 `profileProvider` 实现自定义用户信息。
+
+```Swift
+ CallKitManager.shared.profileProvider = self//Swift
+ //CallKitManager.shared.profileProviderOC = self//OC 与上面profileProvider二者只能设置一个
+ CallKitManager.shared.addListener(self)//添加监听,均为可选方法
+
+//MARK: - CallUserProfileProvider
+//For example using conversations controller,as follows.
+extension ViewController: CallUserProfileProvider {
+ func fetchUserProfiles(profileIds: [String]) async -> [any CallProfileProtocol] {
+ return await withTaskGroup(of: [EaseCallUIKit.CallProfileProtocol].self, returning: [EaseCallUIKit.CallProfileProtocol].self) { group in
+ var resultProfiles: [EaseCallUIKit.CallProfileProtocol] = []
+ group.addTask {
+ var resultProfiles: [EaseCallUIKit.CallProfileProtocol] = []
+ let result = await self.requestUserInfos(profileIds: profileIds)
+ if let infos = result {
+ resultProfiles.append(contentsOf: infos)
+ }
+ return resultProfiles
+ }
+ //Await all task were executed.Return values.
+ for await result in group {
+ resultProfiles.append(contentsOf: result)
+ }
+ return resultProfiles
+ }
+ }
+
+ func fetchGroupProfiles(profileIds: [String]) async -> [any CallProfileProtocol] {
+ return await withTaskGroup(of: [EaseCallUIKit.CallProfileProtocol].self, returning: [EaseCallUIKit.CallProfileProtocol].self) { group in
+ var resultProfiles: [EaseCallUIKit.CallProfileProtocol] = []
+ group.addTask {
+ var resultProfiles: [EaseCallUIKit.CallProfileProtocol] = []
+ let result = await self.requestGroupsInfo(groupIds: profileIds)
+ if let infos = result {
+ resultProfiles.append(contentsOf: infos)
+ }
+ return resultProfiles
+ }
+ //Await all task were executed.Return values.
+ for await result in group {
+ resultProfiles.append(contentsOf: result)
+ }
+ return resultProfiles
+ }
+ }
+
+ private func requestUserInfos(profileIds: [String]) async -> [CallProfileProtocol]? {
+ var unknownIds = [String]()
+ var resultProfiles = [CallProfileProtocol]()
+ for profileId in profileIds {
+ if let profile = CallKitManager.shared.usersCache[profileId] {
+ resultProfiles.append(profile)
+ } else {
+ unknownIds.append(profileId)
+ }
+ }
+ if unknownIds.isEmpty {
+ return resultProfiles
+ }
+ let result = await ChatClient.shared().userInfoManager?.fetchUserInfo(byId: unknownIds)
+ if result?.1 == nil,let infoMap = result?.0 {
+ for (userId,info) in infoMap {
+ let profile = CallUserProfile()
+ let nickname = info.nickname ?? ""
+ profile.id = userId
+ profile.nickname = nickname
+ profile.avatarURL = info.avatarUrl ?? ""
+
+ }
+ return resultProfiles
+ }
+ return []
+ }
+
+ private func requestGroupsInfo(groupIds: [String]) async -> [CallProfileProtocol]? {
+ var resultProfiles = [CallProfileProtocol]()
+ let groups = ChatClient.shared().groupManager?.getJoinedGroups() ?? []
+ for groupId in groupIds {
+ if let group = groups.first(where: { $0.groupId == groupId }) {
+ let profile = CallUserProfile()
+ profile.id = groupId
+ profile.nickname = group.groupName
+ profile.avatarURL = group.settings.ext
+ resultProfiles.append(profile)
+ }
+
+ }
+ return resultProfiles
+ }
+}
+```
+
+### 自定义视频分辨率
+
+环信 CallKit 中默认设置的分辨率为 1280x720。网络连接不稳定时,声网 RTC SDK 会主动降低分辨率或帧率。
+
+若要修改远端视频在本地显示的分辨率,可以在创建声网 RTC 引擎时在 `onRtcEngineCreated` 中进行配置:
+
+```Swift
+func onRtcEngineCreated(engine: AgoraRtcEngineKit?) {
+ let configuration = AgoraVideoEncoderConfiguration()
+ configuration.orientationMode = .fixedPortrait
+ configuration.dimensions = CGSize(width: 1280, height: 720)
+ configuration.frameRate = .fps30
+ engine?.setVideoEncoderConfiguration(configuration)
+ }
+```
+
+更多其他配置可以参考 [声网 RTC 文档](https://doc.shengwang.cn/doc/rtc/ios/basic-features/video-profile#视频参数推荐值)。
+
+### 声网 RTC 私有化部署
+
+如果使用私有化的声网服务,可以在声网 RTC 引擎创建时进行配置:
+
+```Swift
+//添加 CallKitListener 监听后实现下面方法,填写自己的ip地址以及域名
+ func onRtcEngineCreated(engine: AgoraRtcEngineKit?) {
+ let config = AgoraLocalAccessPointConfiguration()
+ config.ipList = ["123.456.789.0"]
+ config.verifyDomainName = "ap.xxx.agora.local"
+ config.mode = .localOnly
+ engine?.setLocalAccessPoint(withConfig: config)
+ }
+```
+
+## 常见问题
+
+1. 当你使用 Xcode 15 创建新工程时,编译时若出现 **Sandbox: rsync.samba(47334) deny(1) file-write-create...** 报错,你需要在 **Target > Build Settings** 中查找 **User Script Sandboxing** 选项,设置为 **NO**。
+
+
+
+
+
+2. 如果 `pod install` 失败报错 RuntimeError **`PBXGroup` attempted to initialize an object with unknown ISA `PBXFileSystemSynchronizedRootGroup` from attributes: `{"isa"=>"PBXFileSystemSynchronizedRootGroup"`**,请尝试将 pod 版本升级为 1.14.3。Xcode 16 及其以下版本打开会报错 **Adjust the project format using a compatible version of Xcode to allow it to be opened by this version of Xcode.**。
diff --git a/docs/callkit/ios/livecommunicationkit.md b/docs/callkit/ios/livecommunicationkit.md
new file mode 100644
index 000000000..7fc88674c
--- /dev/null
+++ b/docs/callkit/ios/livecommunicationkit.md
@@ -0,0 +1,109 @@
+# LiveCommunicationKit
+
+## 概述
+
+环信 CallKit 中的 `LiveCommunicationManager` 是一个用于管理 iOS VoIP 通话的单例管理器类,集成了 Apple 的 PushKit 和 LiveCommunicationKit 框架,提供完整的 VoIP 通话解决方案,包括来电推送、通话管理和音频会话控制。如果用户要使用系统的 LiveCommunicationKit,建议设置环信即时通讯 IM 为自动登录。
+
+
+
+
+
+
+
+## 推荐环境
+
+- iOS 17.4 或以上版本
+- Swift 5.0 或以上版本
+- 必需框架:Foundation、PushKit、AVFAudio 和 LiveCommunicationKit
+
+## 前提条件
+
+- 应用已获得 VoIP 推送权限。
+- 应用已启用 VoIP 后台模式。
+
+## 设置推送证书
+
+环信 CallKit 支持 APNs 推送和 VoIP 推送。若开启了 VoIP 功能,则使用 VoIP 推送。
+
+- APNs 推送:VoIP 功能未开启时,使用 APNs 推送。详见 IM 的 [APNs 离线推送文档](/document/ios/push/push_apns.html)。
+- VoIP 推送:环信 CallKit 集成了 PushKit,你只需要在 IM SDK 初始化时设置 VoIP 推送证书,在 CallKit 初始化时启用 VoIP 功能。关于如何创建 VoIP 推送证书以及上传至 [环信控制台](https://console.easemob.com/user/login),详见 IM 的 [APNs 离线推送文档](/document/ios/push/push_apns.html)。
+
+```Swift
+ private func setupCallKit() {
+ let options = EMOptions(appkey: appKey)
+ #if DEBUG
+ options.apnsCertName = "your_APNS_Developer"
+ options.pushKitCertName = "yourVoipDev"
+ #else
+ options.apnsCertName = "your_APNS_Product"
+ options.pushKitCertName = "yourVoipPro"
+ #endif
+ EMClient.shared().initializeSDK(with: options)
+ //初始化环信 CallKit。
+ let config = EaseCallUIKit.CallKitConfig()
+ config.enableVOIP = true//开启 VoIP 功能后会自动开启 LiveCommunicationKit,需要在 develop.apple.com 申请证书时勾选。
+ config.enablePIPOn1V1VideoScene = true//开启画中画,同时需要开启应用后台摄像头采集权限。
+ CallKitManager.shared.setup(config)
+ }
+```
+
+## 通话管理
+
+环信 CallKit 通过 `ConversationManager` 进行通话管理,包括上报来电通知、管理通话生命周期,例如,接听、挂断、静音等,以及通话超时处理。
+
+你可以创建 `ConversationManager`,进行如下配置:
+- 铃声: `notes_of_the_optimistic`。
+- 图标: 使用应用图标。
+- 限制: 最大会话组数 为 `1`,每组最大会话数 为 `1`。
+
+## 通话流程
+
+### 1. 来电流程
+
+1. 接收 VoIP 推送通知。
+2. 解析推送载荷提取通话信息。
+3. 创建 `ConversationManager`(如不存在)。
+4. 生成或使用现有呼叫 UUID。
+5. 上报新的来电会话。
+6. 更新 `CallKitManager` 状态。
+
+来电流程如下图所示:
+
+
+
+推送通知载荷应包含以下字段:
+
+```json
+{
+ "e": {
+ "callId": "通话ID",
+ "callerNickname": "来电者昵称"
+ },
+ "f": "来电者ID",
+ "m": "消息ID(可选)",
+ "g": "群组ID(可选)"
+}
+```
+
+### 2. 其他流程
+
+
+
+
+
+
+
+
+## 错误处理
+
+| 错误场景 | 描述 |
+| :-------------------- | :-------- |
+| UUID 创建失败 | 自动生成新的呼叫 UUID。 |
+| 通话信息缺失 | 日志记录错误,拒绝接听呼叫。 |
+| 状态不匹配 | 验证通话状态后再执行操作。 |
+| 超时处理 | 无论主叫或被叫超时,通话都自动取消。 |
+
+
+
+
+
diff --git a/docs/callkit/ios/picture_in_picture.md b/docs/callkit/ios/picture_in_picture.md
new file mode 100644
index 000000000..e1811c26a
--- /dev/null
+++ b/docs/callkit/ios/picture_in_picture.md
@@ -0,0 +1,135 @@
+# 视频通话画中画(PiP)
+
+## 功能概述
+
+画中画(Picture-in-Picture,PiP)功能允许用户在视频通话时,将通话界面最小化为悬浮窗口,同时使用其他应用。该功能对于多任务处理场景尤其重要。
+
+对于 PiP,注意 iOS 系统存在如下限制:
+- iOS 15 及以上版本支持自定义 PiP。
+- 需要用户手动触发 PiP,无法自动进入。
+- 音频路由切换需要特殊处理:例如,如果其中群组一个用户关闭了摄像头,开发者需要特殊处理。
+
+
+
+
+
+## PiP 基本配置
+
+1. 开启画中画功能。
+
+ 在 **Capabilities** 中启用 **Background Modes**,勾选 **Audio**, **AirPlay**, 和 **Picture in Picture**。
+
+2. 摄像头后台权限。
+
+ - 默认不允许后台访问摄像头。
+ - 对于 VoIP 应用,在 **Background Modes** 中勾选 **Voice over IP (VoIP)**,支持 LiveCommunicationKit。若不勾选,采用厂商默认系统推送。
+ - 若应用需要后台采集视频流,需要申请多任务相机访问权限(Multitasking Camera Access Entitlement)。iOS 系统版本对多任务相机访问权限的支持详见 [苹果官方文档](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.avfoundation.multitasking-camera-access)。
+
+
+
+## 一对一视频通话 PiP
+
+对于一对一视频通话 PiP,进入 PiP 时,切换到悬浮窗;退出 PiP 时,恢复通话页面全屏 UI,重新加载视频流,恢复交互控件。核心架构如下图所示:
+
+
+
+UI 示意图如下所示:
+
+
+
+
+
+
+## 群组视频通话 PiP 实现方案
+
+CallKit 未实现群组视频通话 PiP,你可以按照本节的推荐方案自行实现。与一对一通话相比,群组视频通话 PiP 的实现更为复杂,需要注意以下几方面:
+- 多流管理:需要智能选择显示内容。避免频繁切换视频流,合理设置切换阈值:例如,3 秒内不反复切换主讲人。
+- 性能优化:降低资源消耗。你可以提供用户手动固定选项:例如,固定主讲人。
+
+推荐采用 **主讲人模式** 作为默认方案,这样可在保证体验的同时,有效控制资源消耗。对于高端设备,可考虑支持宫格模式,但需要严格的性能监控。
+
+### 核心架构
+
+
+
+此外,你还可以实现群组视频通话的特殊需求,例如,多路视频流同时存在、需要智能选择显示内容以及焦点切换等。
+
+### PiP 交互
+
+下图为群组通话中的 PiP 交互示例。在该例中,PiP 窗口中显示主讲人、主讲人画面、群组通话人数以及相关操作按钮。你可以根据自己的业务决定 PiP 窗口采用主讲人模式或者多人模式。
+
+
+
+
+
+### 显示模式
+
+- (推荐)模式一:主讲人模式
+
+```swift
+class GroupPIPManager {
+ var displayMode: DisplayMode = .activeSpeaker
+ var pinnedUserId: String? // 固定显示的用户
+
+ func selectVideoStream() -> VideoStream {
+ switch displayMode {
+ case .activeSpeaker:
+ return getActiveSpeakerStream()
+ case .pinned:
+ return getPinnedUserStream(pinnedUserId)
+ case .recentSpeaker:
+ return getRecentSpeakerStream()
+ }
+ }
+}
+```
+
+- 模式二:宫格模式。该模式对性能要求较高。
+
+```swift
+// 最多显示4路视频
+func setupGridLayout(streams: [VideoStream]) {
+ let maxDisplay = min(streams.count, 4)
+ for i in 0.. Bool
+ //远端视频流
+ public func onRenderVideoFrame(_ videoFrame: AgoraOutputVideoFrame, uid: UInt, channelId: String) -> Bool
+```
+
+### 群组成员状态同步
+
+若从 [视频渲染优化](#视频渲染优化) 一节中的 `onCapture` 和 `onRenderVideoFrame:uid:channelId:` 回调中判断 `callInfo` 中的 `type` 为群组,可结合音频回调 `reportAudioVolumeIndicationOfSpeakers` 进行主讲人渲染。
+
+### PiP 性能优化
+
+目前,群组视频通话页面出现时,不同用户数下使用 `AgoraRtcVideoCanvas` 渲染群组画面,每增加 2 个用户会降低本地渲染一个画质级别,被放大的用户本地渲染画质会变为高质量。
+
+| 优化项 | 全屏模式 | PiP 模式 |
+| :------------------- | :----- | :------------ |
+| 视频分辨率 | 720p/1080p | 360p |
+| 帧率 | 30 fps | 15 fps |
+| 显示人数 | 全部 | 最多 4 人或只显示主讲人|
+
+
diff --git a/docs/callkit/ios/product_activation.md b/docs/callkit/ios/product_activation.md
new file mode 100644
index 000000000..602f23734
--- /dev/null
+++ b/docs/callkit/ios/product_activation.md
@@ -0,0 +1,6 @@
+---
+{
+ pageUri: "/callkit/android/product_activation.html",
+ title: "开通服务"
+}
+---
\ No newline at end of file
diff --git a/docs/callkit/ios/product_overview.md b/docs/callkit/ios/product_overview.md
new file mode 100644
index 000000000..a44c64734
--- /dev/null
+++ b/docs/callkit/ios/product_overview.md
@@ -0,0 +1,66 @@
+# 产品介绍
+
+## CallKit 介绍
+
+环信音视频通话 CallKit 是基于环信即时通讯 IM 和声网实时音视频 RTC 深度整合开发的实时音视频通话框架,实现了一对一及群组音视频通话功能。开发者只需简单集成,即可快速获得稳定流畅的高品质音视频通话能力。环信音视频通话 CallKit 可用于在线互动课堂、视频客服中心、远程会诊系统或视频相亲等场景。
+
+
+
+## CallKit 优势
+
+| 优势 | 描述 |
+| :-------------- | :----- |
+| 三位一体技术整合 | - 环信即时通讯 IM + 声网实时音视频 RTC + UI 组件深度整合
- 双平台服务一键开通,免除多系统对接成本 |
+| 多重唤醒系统 | iOS 平台支持离线推送 和 LiveCommunicationKit 实现来电通知 |
+| 高质量通话品质 | - 声网全球网络:超过 99.99% 服务可用性
- 超低延时:低于 76ms 的端到端延迟
- 抗丢包技术:80% 丢包仍可流畅通话 |
+
+## 主要功能
+
+| 基本功能 | 高级功能 | 功能优势 |
+| :-------------- | :----- | :------- |
+| - 一对一语音/视频通话
- 群组语音/视频通话(16 人及以下):通话中邀请他人
- 自定义铃声:支持主叫、被叫、挂断、超时铃声
- 打开/关闭画中画
- 自定义 UI 界面 | - 高画质/高音质音视频
- 离线推送
- 通话质量检测
- 全球互通
- 弱网卡顿优化
- 视频降噪| - 高质量音视频通话
- 完善的 UI 交互
- 支持多平台互联互通
- 离线推送稳定且多样化 |
+
+## 界面效果展示
+
+### 一对一视频通话
+
+
+
+
+
+
+
+
+### 一对一音频通话
+
+
+
+
+
+
+
+
+### 群组通话
+
+
+
+
+
+
+
+
+
+### 来电通知
+
+
+
+
+
+
+
+
+
+## 使用限制
+
+- 群组音视频通话默认最多支持 16 人。
+- 关于声网 RTC 的使用限制,详见 [声网 RTC 关键性能指标](https://doc.shengwang.cn/doc/rtc/ios/overview/product-overview#%E5%85%B3%E9%94%AE%E6%80%A7%E8%83%BD%E6%8C%87%E6%A0%87) 和 [配额限制](https://doc.shengwang.cn/doc/rtc/ios/overview/product-overview#%E9%85%8D%E9%A2%9D%E9%99%90%E5%88%B6)。
diff --git a/docs/callkit/ios/product_purchase.md b/docs/callkit/ios/product_purchase.md
new file mode 100644
index 000000000..004a3f643
--- /dev/null
+++ b/docs/callkit/ios/product_purchase.md
@@ -0,0 +1,6 @@
+---
+{
+ pageUri: "/callkit/android/product_purchase.html",
+ title: "购买指南"
+}
+---
\ No newline at end of file
diff --git a/docs/callkit/ios/quickstart.md b/docs/callkit/ios/quickstart.md
new file mode 100644
index 000000000..4115b3f44
--- /dev/null
+++ b/docs/callkit/ios/quickstart.md
@@ -0,0 +1,352 @@
+# 快速开始
+
+利用环信 CallKit(基于环信即时通讯 IM SDK V4.16.0 或以上版本),你可以轻松实现一对一通话和群组通话功能。本文介绍如何快速实现发起音视频通话。
+
+## 开发环境要求
+
+- Xcode 16.0 或以上版本
+- 最低支持系统版本:iOS 15.0
+- 已为你的项目设置有效的开发者签名
+- CocoaPods v1.14.3 或以上版本
+
+## 前提条件
+
+在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+1. [注册环信账号](/product/console/account_register.html#注册账号)。
+2. [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+3. [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID 和 [用户 Token](/product/console/operation_user.html#查看用户-token)。
+4. [开通音视频服务](product_activation.html)。为了保障流畅的用户体验,开通服务后,你需等待 15 分钟才能实现发起音视频通话。
+
+## 快速开始
+
+### 步骤 1 创建项目
+
+参考以下步骤在 Xcode 中创建一个 iOS 平台下的 App,项目设置如下:
+
+- **Product Name** 设置为 **EaseCallUIKitQuickStart**。
+- **Organization Identifier** 设置为你的 **identifier**。
+- **User Interface** 选择 **Storyboard**。
+- **Language** 选择你的常用开发语言,推荐 `Swift` 和 `Main.storyboard`。
+- 添加权限:在项目 `info.plist` 中添加权限:
+
+```
+Privacy - Microphone Usage Description //麦克风权限
+Privacy - Camera Usage Description //相机权限
+```
+
+### 步骤 2 安装 CallKit
+
+你可以使用 CocoaPods 安装环信 CallKit 作为 Xcode 项目的依赖项。
+
+1. 在 `podfile` 中添加如下依赖:
+
+```ruby
+source 'https://github.com/CocoaPods/Specs.git'
+platform :ios, '15.0'
+
+target 'YourTarget' do
+ use_frameworks!
+
+ pod 'EaseCallUIKit'
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |config|
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
+ end
+ end
+end
+```
+
+2. 在终端使用 cd 命令到 `podfile` 所在文件夹目录执行以下命令:
+
+```
+pod install
+```
+
+### 步骤 3 初始化 CallKit
+
+你可以在应用程序加载时或使用前初始化 CallKit:
+1. 初始化 IM SDK。CallKit 基于即时通讯 IM 作为信令通道,因此需先初始化 IM SDK。
+ - 填入你的应用的 App Key。
+ - 设置即时通讯 IM SDK 中的一些选项(`ChatSDKOptions` 类),例如,开启 Console 日志和是否自动登录。建议在正式环境中开启自动登录,可参考 [IM Demo 源码](https://github.com/easemob/easemob-demo-ios)。
+2. 初始化 CallKit。
+
+在整个应用生命周期中,初始化一次即可。
+
+```Swift
+import UIKit
+import EaseCallUIKit
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ let option = ChatSDKOptions(appkey: "XXXX#XXX")//首先需要初始化 IM SDK,替换你的 app key
+ option.enableConsoleLog = true//开启日志
+ option.isAutoLogin = false//此处只是示例项目,真实使用时参考环信Demo源码,自动登录更方便
+ ChatClient.shared().initializeSDK(with: option)//初始化SDK
+ CallKitManager.shared.setup()//初始化EaseCallUIKit
+ return true
+ }
+}
+```
+
+### 步骤 4 登录 IM SDK
+
+调用即时通讯 IM SDK 的 `login` 方法传入用户 ID 和 Token 登录 IM。
+
+``` Swift
+ ChatClient.shared().login(withUsername: userId, token: token) { [weak self] userId,error in
+ if let error = error {
+ self?.showCallToast(toast: "Login failed: \(error.errorDescription ?? "")")
+ } else {
+ self?.showCallToast(toast: "Login successful")
+ self?.userIdField.isHidden = true
+ self?.tokenField.isHidden = true
+ self?.loginButton.isHidden = true
+ }
+ }
+```
+
+### 步骤 5 创建快速开始页面
+
+在项目的 `Main.storyboard` 和 `ViewController.swift` 中替换代码,然后运行。
+
+1. 右键点击项目中的 `Main.storyboard`,选择 **Open As** > **Source Code**,替换为如下代码:
+
+::: details `Main.storyboard` 中的替换代码
+
+``` XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+:::
+
+2. 在项目中的 `ViewController.swift`,替换为如下代码:
+
+::: details ViewController.swift 文件中的替换代码:
+
+``` Swift
+import UIKit
+import EaseCallUIKit
+import QuickLook
+
+class ViewController: UIViewController {
+
+ var callType: CallType = .singleAudio
+
+ @IBOutlet var inputField: UITextField!
+
+ @IBOutlet var callButton: UIButton!
+ @IBOutlet weak var loginButton: UIButton!
+ @IBOutlet weak var callTypeSegment: UISegmentedControl!
+ @IBOutlet weak var logButton: UIButton!
+
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do any additional setup after loading the view, typically from a nib.
+ self.callTypeSegment.selectedSegmentIndex = 0
+ self.callTypeSegment.selectedSegmentTintColor = .systemBlue
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ }
+
+
+ override func touchesBegan(_ touches: Set, with event: UIEvent?) {
+ self.view.endEditing(true)
+ }
+
+ @IBAction func chooseCallType(_ sender: Any) {
+ self.callType = CallType(rawValue: UInt(self.callTypeSegment.selectedSegmentIndex)) ?? .singleAudio
+ }
+
+ @IBAction func loginAction(_ sender: Any) {
+ self.view.endEditing(true)
+
+ ChatClient.shared().login(withUsername: "userId", token: "token") { [weak self] userId,error in
+ if let error = error {
+ self?.showCallToast(toast: "Login failed: \(error.errorDescription ?? "")")
+ } else {
+ self?.showCallToast(toast: "Login successful")
+ if !userId.isEmpty {
+ let profile = CallUserProfile()
+ profile.id = userId
+ profile.avatarURL = "https://xxxxx"
+ profile.nickname = "\(userId)昵称"
+ CallKitManager.shared.currentUserInfo = profile
+ }
+ self?.loginButton.isHidden = true
+ }
+ }
+ }
+
+ @IBAction func logAction(_ sender: Any) {
+ let previewController = QLPreviewController()
+ previewController.dataSource = self
+ self.present(previewController, animated: true)
+ }
+
+ @IBAction func callAction(_ sender: Any) {
+ self.view.endEditing(true)
+ guard let input = inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !input.isEmpty else {
+ self.showCallToast(toast: "Please enter a valid username or group id")
+ return
+ }
+ CallKitManager.shared.call(with: input, type: self.callType)
+ }
+}
+
+extension ViewController: QLPreviewControllerDataSource {
+ public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
+ 1
+ }
+
+ public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
+ let fileURL = URL(fileURLWithPath: NSHomeDirectory() + "/Library/Application Support/HyphenateSDK/easemobLog/easemob.log")
+ return fileURL as QLPreviewItem
+ }
+
+
+}
+```
+
+:::
+
+### 步骤 6 发起首次通话
+
+1. 登录:在登录界面输入用户 ID,然后点击 **Login**。
+2. 发起通话:选择 **audio** 或 选择 **video**,输入呼叫用户的用户 ID,点击 **Call**,发起一对一音频或视频通话。
+
+你可以点击 **log** 查看 CallKit 相关日志,搜索 `EaseCallUIKit` 过滤 CallKit 日志。
+
+
+
+
diff --git a/docs/callkit/ios/sample_runthrough.md b/docs/callkit/ios/sample_runthrough.md
new file mode 100644
index 000000000..50fa7cc5b
--- /dev/null
+++ b/docs/callkit/ios/sample_runthrough.md
@@ -0,0 +1,55 @@
+# 运行示例项目
+
+本文帮助你快速集成和运行环信 CallKit(基于环信即时通讯 IM SDK V4.16.0 或以上版本),实现一对一音视频通话和群组音视频通话功能。
+
+## 开发环境要求
+
+- Xcode 16.0 或以上版本
+- 最低支持系统版本:iOS 15.0
+- 已为你的项目设置有效的开发者签名
+- CocoaPods v1.14.3 或以上版本
+
+## 前提条件
+
+在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+1. [注册环信账号](/product/console/account_register.html#注册账号)。
+2. [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+3. [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID 和 [用户 Token](/product/console/operation_user.html#查看用户-token)。
+4. [创建群组](/product/console/operation_group.html#创建群组),获取群组 ID,将用户加入群组。
+5. [开通音视频服务](product_activation.html)。为了保障流畅的用户体验,开通服务后,你需等待 15 分钟才能跑通示例项目。
+
+## 操作步骤
+
+### 步骤 1 配置项目
+
+在 [Github](https://github.com/easemob/easemob-callkit-iOS) 中克隆或下载代码。
+
+```bash
+git clone https://github.com/easemob/easemob-callkit-iOS
+```
+
+### 步骤 2 设置 App Key
+
+在 `PublicDefines.swift` 中填写你的应用的 App Key:
+
+```Swift
+let AppKey: String = <#AppKey#>
+```
+
+### 步骤 3 安装本地依赖
+
+在终端使用 cd 命令到 `podfile` 所在的文件目录下,执行 `pod install` 命令,等待成功后点击 **运行**。
+
+```
+pod install
+```
+
+### 步骤 4 运行项目
+
+1. 登录:在登录界面输入用户 ID 和 [用户 Token](/product/console/operation_user.html#查看用户-token),然后点击 **Login**。
+2. 发起通话:
+ - 一对一通话:选择 **audio** 或 **video**,输入呼叫用户的用户 ID,点击 **Call**。
+ - 群组通话:选择 **group**,输入群组 ID,点击 **Call**。
+你可以点击 **log** 查看 CallKit 相关日志,搜索 `EaseCallUIKit` 过滤 CallKit 日志。
+
+
\ No newline at end of file
diff --git a/docs/callkit/ios/signaling.md b/docs/callkit/ios/signaling.md
new file mode 100644
index 000000000..269fe3226
--- /dev/null
+++ b/docs/callkit/ios/signaling.md
@@ -0,0 +1,97 @@
+
+# 音视频通话信令交互逻辑
+
+## 信令概述
+
+本系统使用即时通讯 IM 消息作为信令载体,通过消息扩展字段(`ext`)传递通话控制信息。
+
+### 核心信令
+
+| 信令类型 | Action 标识 | 发送方 | 接收方 | 描述 | 发送信令消息规则 |
+| :---------------- | :----- | :----- | :----- | :----- | :----- |
+| 通话邀请信令 | `CALL_INVITE` | 主叫 | 被叫 | 发起通话邀请,包含:
- `callId`: 通话唯一标识。
- `channelName`:声网 RTC 频道名称。
- `callType`:`CallTypeAudio` 为音频通话,`CallTypeVideo` 为视频通话,`CallTypeGroup`为群组通话。
- `callerDevId`:主叫设备 ID。
对于群聊通话,该信令还包含以下参数:
- `groupId`:群组 ID。
- `receiverList`:接收通话邀请的目标用户。| 一对一通话:使用普通文本消息,支持离线推送。
群组通话:使用定向文本消息,设置 `chatType = .groupChat`,`receiverList` 指定接收者,支持离线推送 |
+| 响应信令 | `CALL_ALERT` | 被叫 | 主叫 | 通知主叫已收到邀请,包含:
- `callId`:对应的通话 ID。
- `calleeDevId`:被叫设备 ID。 | 使用 CMD 消息,设置 `deliverOnlineOnly = true`,表示仅在线用户能收到,不存储。 |
+| 确认振铃信令 | `CALL_CONFIRM_RING` | 主叫 | 被叫 | 确认被叫可以开始振铃,包含呼叫状态 `callStatus`:
- `1` 为呼叫有效
- `0` 为呼叫无效 | 使用 CMD 消息,设置 `deliverOnlineOnly = false`。 |
+| 应答信令 | `CALL_ANSWER` | 被叫 | 主叫 | 通知主叫是否接听,包含 `callResult` 参数: `accept`:接听`refuse` 拒绝`busy`:忙碌 | 使用 CMD 消息,设置 `deliverOnlineOnly = true`,表示仅在线用户能收到,不存储。 |
+| 确认被叫信令 | `CALL_CONFIRM_CALLEE` | 主叫 | 被叫 | 确认被叫设备和状态,包含:
- `calleeDeviceId`:被叫设备标识
- `callResult`:通知主叫是否接听,详见应答信令中的描述。| 使用 CMD 消息,设置 `deliverOnlineOnly = false`。 |
+| 取消呼叫信令 | `CALL_CANCEL` | 主叫 | 被叫 | 主叫取消呼叫。 | 使用 CMD 消息,设置 `deliverOnlineOnly = false`,便于久不上线的用户收到离线消息做判断。 |
+| 退出通话信令 | `CALL_END` | 任意一方 | 通话双方 | 退出通话。 | 使用 CMD 消息,设置 `deliverOnlineOnly = true`。 |
+
+### 信令特点
+
+| 信令特点 | 描述 |
+| :---------------- | :----- |
+| 信令可靠性 |
- 关键信令(邀请)使用普通文本消息
- 实时信令使用 CMD 消息,降低延迟。 |
+| 状态机管理 |
- 每个信令对应状态转换。
- 非法状态转换直接忽略。 |
+| 超时保护 |
- 多级超时机制。
- 超时后自动清理资源。 |
+| 扩展性设计 |
- `ext` 字段支持自定义扩展。
- 群组呼叫信令支持动态成员,用户可随时加人或退出通话。 |
+
+## 一对一通话信令交互流程
+
+### 完整通话信令流程
+
+
+
+### 主叫取消流程
+
+
+
+### 被叫忙碌流程
+
+
+
+## 群组通话信令交互流程
+
+群组通话信令特点如下:
+
+1. 批量发送: 一条消息发给多个接收方。
+2. 独立响应: 每个被叫独立处理和响应。
+3. 动态邀请: 支持通话中继续邀请新成员。
+
+
+
+## 信令时序控制
+
+| 信令交互 | 预期响应时间 | 超时处理 |
+| :---------------- | :----- | :----------------- |
+| CALL_INVITE → CALL_ALERT | 30 秒内 | |
+| CALL_ALERT → CALL_CONFIRM_RING | 10 秒内 | 主叫快速判断并响应。 |
+| CALL_CONFIRM_RING → CALL_ANSWER | 30 秒内 | 被叫振铃超时,自动结束。 |
+| CALL_ANSWER → CALL_CONFIRM_CALLEE | 10 秒内 | 主叫快速确认被叫设备以及状态。 |
+| 整体呼叫流程 | 30 秒 | 主叫等待超时,自动取消呼叫。被叫超时不接,同样自动取消。 |
+
+## 多设备场景
+
+被叫方使用同一账号登录多个设备,信令中通过主叫或被叫的设备 ID(`callerDevId` 和 `calleeDevId`)区分,只有指定的设备处理信令,其他设备收到后忽略或显示类似 “已在其他设备处理” 等提示。
+
+```swift
+// 判断是否本设备
+if calleeDevId == currentDeviceId {
+ // 本设备处理
+ processCall()
+} else {
+ // 其他设备处理
+ showMessage("已在其他设备处理")
+ dismissUI()
+}
+```
+
+## 异常处理
+
+### 信令丢失处理
+
+| 信令丢失场景 | 处理方式 |
+| :---------------- | :----- |
+| CALL_ALERT 信令丢失 | 30 秒后主叫端超时,用户可以点击重新发起呼叫。 |
+| CALL_CONFIRM_RING 信令丢失 | 被叫等待 10 秒后结束呼叫流程。 |
+| CALL_ANSWER 信令丢失 | 主叫 30 秒超时,发送取消呼叫信令。 |
+| CALL_CONFIRM_CALLEE 信令丢失 | 被叫 10 秒超时,主动结束通话。 |
+
+### 信令冲突处理
+
+| 信令冲突场景 | 处理方式 |
+| :---------------- | :----- |
+| 重复邀请 | 检查 `callId`,相同则忽略。 |
+| 并发呼叫 | 返回忙碌 `busy`,保持当前通话。 |
+| 呼叫状态不一致 | 以最新信令为准,更新本地呼叫状态:空闲、拨号中、振铃中、应答中。 |
+
diff --git a/docs/callkit/web/api_overview.md b/docs/callkit/web/api_overview.md
new file mode 100644
index 000000000..dc72b4df4
--- /dev/null
+++ b/docs/callkit/web/api_overview.md
@@ -0,0 +1,188 @@
+# API 概览
+
+本文档详细介绍 CallKit 组件的所有属性、方法和回调事件。
+
+## 组件属性 (Props)
+
+### 基础配置
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ----------------- | --------------------- | ------------- | ---------- |
+| `className` | `string` | - | 自定义 CSS 类名。 |
+| `style` | `React.CSSProperties` | - | 自定义内联样式。 |
+| `prefix` | `string` | `cui` | CSS 类名前缀。 |
+| `chatClient` | `ChatSDK.Connection` | - | **必须**,环信 IM SDK 实例。 |
+| `layoutMode` | `LayoutMode` | `MULTI_PARTY` | 布局模式:`PREVIEW`、`ONE_TO_ONE` 和 `MULTI_PARTY`。 |
+| `maxVideos` | `number` | `16` | 最大显示视频数量。 |
+| `aspectRatio` | `number` | `1` | 视频窗口宽高比。 |
+| `gap` | `number` | `6` | 视频窗口间隙(像素)。 |
+| `backgroundImage` | `string` | - | 多人通话背景图片 URL。 |
+| `userSelectTitle` | `string` | - | 用户选择弹窗标题。 |
+
+### 铃声配置
+
+| 属性 | 类型 | 默认值 | 描述 |
+| --------------------- | --------- | ------ | ------------------------ |
+| `enableRingtone` | `boolean` | `true` | 是否启用铃声。 |
+| `outgoingRingtoneSrc` | `string` | - | 拨打电话铃声音频文件路径。 |
+| `incomingRingtoneSrc` | `string` | - | 接听电话铃声音频文件路径。 |
+| `ringtoneVolume` | `number` | `0.8` | 铃声音量,范围 0-1。 |
+| `ringtoneLoop` | `boolean` | `true` | 是否循环播放铃声。 |
+
+### 窗口大小和位置
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ----------------- | --------------------------------- | --------------------------- | -------------------- |
+| `resizable` | `boolean` | `false` | 是否允许调整大小。 |
+| `minWidth` | `number` | `400` | 最小宽度(像素)。 |
+| `minHeight` | `number` | `300` | 最小高度(像素)。 |
+| `maxWidth` | `number` | - | 最大宽度(像素)。 |
+| `maxHeight` | `number` | - | 最大高度(像素)。 |
+| `draggable` | `boolean` | `true` | 是否允许拖拽。 |
+| `dragHandle` | `string` | - | 拖拽手柄 CSS 选择器。 |
+| `managedPosition` | `boolean` | `true` | 是否使用内置位置管理。 |
+| `initialPosition` | `{left: number, top: number}` | - | 初始位置。 |
+| `initialSize` | `{width: number, height: number}` | `{width: 748, height: 523}` | 初始大小。 |
+| `minimizedSize` | `{width: number, height: number}` | `{width: 80, height: 64}` | 最小化时的尺寸。 |
+
+### 邀请界面配置
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ------------------------- | ----------------- | ------ | ------------------ |
+| `invitationCustomContent` | `React.ReactNode` | - | 自定义邀请内容。 |
+| `acceptText` | `string` | - | 接听按钮文本。 |
+| `rejectText` | `string` | - | 拒绝按钮文本。 |
+| `showInvitationAvatar` | `boolean` | `true` | 是否显示邀请者头像。 |
+| `showInvitationTimer` | `boolean` | `true` | 是否显示倒计时。 |
+| `autoRejectTime` | `number` | `30` | 自动拒绝时间(秒)。 |
+
+### 信息提供者
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ------------------- | ---------------------------------------------- | ------ | ------------------ |
+| `userInfoProvider` | `(userIds: string[]) => Promise` | - | 用户信息提供者函数。 |
+| `groupInfoProvider` | `(groupIds: string[]) => Promise` | - | 群组信息提供者函数。 |
+
+### 其他配置
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ------------------------- | ---------------- | ------ | ------------------------------ |
+| `speakingVolumeThreshold` | `number` | `60` | 说话指示器音量阈值,范围 1-100。 |
+| `customIcons` | `CallKitIconMap` | - | 自定义图标映射。 |
+| `encoderConfig` | `VideoEncoderConfigurationPreset` | - | 自定义预设的视频编码配置。 |~~~~
+
+
+## 组件方法
+
+通过 `ref` 调用以下方法:
+
+```tsx
+const callKitRef = useRef(null);
+```
+
+### 通话控制方法
+
+| 方法 | 参数 | 返回值 | 描述 |
+| ----------------- | -------------- | ---------------- | ------------ |
+| `startSingleCall` | `{to: string, callType: 'video'\|'audio', msg: string}` | `Promise` | 发起一对一通话。视频通话时摄像头默认开启。 |
+| `startGroupCall` | `{groupId: string, msg: string, ext?: Record}` | `Promise` | 发起群组通话。群组通话时摄像头默认关闭,只创建音频轨道。 |
+| `answerCall` | `result: boolean` | `void` | 接听/拒绝通话。 |
+| `exitCall` | `reason?: string` | `void` | 退出通话。 |
+| `adjustSize` | `newSize: {width: number, height: number}` | `void` | 动态调整窗口尺寸。 |
+
+## 回调事件
+
+### 通话状态回调
+
+| 回调事件 | 参数 | 返回值 | 触发时机 |
+| --------------------- | ---------------------------------------------------------------- | ------ | ------------------------ |
+| `onCallStart` | `videos: VideoWindowProps[]` | `void` | 通话开始时。 |
+| `onEndCallWithReason` | `reason: string, callInfo: CallInfo` | `void` | 通话结束时(包含详细原因)。 |
+| `onReceivedCall` | `callType: 'video'\|'audio'\|'group', userId: string, ext?: any` | `void` | 收到通话邀请时。 |
+| `onCallError` | `error: CallError` | `void` | 通话过程中发生错误时。 |
+
+### 用户状态回调
+
+| 回调事件 | 参数 | 返回值 | 触发时机 |
+| -------------------- | ----------------------------------------------------- | ------ | ------------------ |
+| `onRemoteUserJoined` | `userId: string, callType: 'video'\|'audio'\|'group'` | `void` | 远程用户加入通话时。 |
+| `onRemoteUserLeft` | `userId: string, callType: 'video'\|'audio'\|'group'` | `void` | 远程用户离开通话时。 |
+
+### 邀请处理回调
+
+| 回调事件 | 参数 | 返回值 | 触发时机 |
+| -------------------- | ---------------------------- | ------ | -------------- |
+| `onInvitationAccept` | `invitation: InvitationInfo` | `void` | 用户接受邀请时。 |
+| `onInvitationReject` | `invitation: InvitationInfo` | `void` | 用户拒绝邀请时。 |
+
+### 界面状态回调
+
+| 回调事件 | 参数 | 返回值 | 触发时机 |
+| -------------------- | ------------------------------ | ------ | ---------------- |
+| `onLayoutModeChange` | `layoutMode: 'grid' \| 'main'` | `void` | 布局模式变化时。 |
+| `onMinimizedChange` | `minimized: boolean` | `void` | 最小化状态变化时。 |
+| `onMinimizedToggle` | - | `void` | 最小化切换时。 |
+
+### 窗口操作回调
+
+| 回调事件 | 参数 | 返回值 | 触发时机 |
+| ------------- | -------------------------- | ------ | -------------- |
+| `onResize` | `width: number, height: number, deltaX?: number, deltaY?: number, direction?: string` | `void` | 窗口大小调整时。 |
+| `onDragStart` | `startPosition: {x: number, y: number}` | `void` | 开始拖拽时。 |
+| `onDrag` | `newPosition: {x: number, y: number}, delta: {x: number, y: number}` | `void` | 拖拽过程中。 |
+| `onDragEnd` | `finalPosition: {x: number, y: number}` | `void` | 拖拽结束时。 |
+
+### 技术回调
+
+| 回调事件 | 参数 | 返回值 | 触发时机 |
+| -------------------- | ---------- | ------ | ------------------ |
+| `onRtcEngineCreated` | `rtc: any` | `void` | RTC 引擎创建完成时。 |
+
+## 类型定义
+
+### UserInfo
+
+```tsx
+interface UserInfo {
+ userId: string;
+ nickname?: string;
+ avatarUrl?: string;
+}
+```
+
+### GroupInfo
+
+```tsx
+interface GroupInfo {
+ groupId: string;
+ groupName?: string;
+ groupAvatar?: string;
+}
+```
+
+### 通话错误
+
+通话错误(`CallError`)类型 `errorType` 分为声网 RTC 错误、IM 错误以及 CallKit 本身的错误。
+
+| 通话错误类型 | 描述 |
+| :--------- | :----- |
+| `rtc` | 音视频异常,详见 [声网 RTC 错误码](https://doc.shengwang.cn/doc/rtc/javascript/error-code)。 |
+| `chat` | 即时通讯 IM 异常,详见 [环信即时通讯 IM 错误码](/document/web/error.html)。 |
+| `callkit` | `CallErrorCode` 类中包含三种错误类型:
- `CALL_STATE_ERROR`:通话状态错误
- `CALL_PARAM_ERROR`:通话参数错误
- `CALL_SIGNALING_ERROR`:信令错误 |
+
+## 通话结束原因
+
+通话结束原因 `HANGUP_REASON` 如下表所示:
+
+| 枚举值 | 英文名称 | 描述 | 触发场景 |
+| --------------------- | ---------------------- | ---------------- | -------- |
+| `hangup` | HANGUP | 挂断通话。 | 用户主动挂断正在进行的通话。 |
+| `cancel` | CANCEL | 取消呼叫。 | 发起者在对方接听前取消通话。 |
+| `remoteCancel` | REMOTE_CANCEL | 对方取消呼叫。 | 对方发起者在接听前取消了通话。 |
+| `refuse` | REFUSE | 自己拒绝呼叫。 | 被叫方主动拒绝接听通话。 |
+| `remoteRefuse` | REMOTE_REFUSE | 对方拒绝呼叫。 | 对方被叫方拒绝接听通话。 |
+| `busy` | BUSY | 忙碌。 | 被叫方当前正在通话中,无法接听新通话。 |
+| `noResponse` | NO_RESPONSE | 无响应。 | 自己超时未处理通话邀请。 |
+| `remoteNoResponse` | REMOTE_NO_RESPONSE | 对方无响应。 | 对方超时未接听通话。 |
+| `handleOnOtherDevice` | HANDLE_ON_OTHER_DEVICE | 已在其他设备处理。 | 通话已在其他设备上被接听或处理。 |
+| `abnormalEnd` | ABNORMAL_END | 异常结束。 | 由于网络异常、设备问题等导致通话异常结束。 |
diff --git a/docs/callkit/web/architecture.md b/docs/callkit/web/architecture.md
new file mode 100644
index 000000000..7fb8ccc29
--- /dev/null
+++ b/docs/callkit/web/architecture.md
@@ -0,0 +1,84 @@
+# CallKit 架构文档
+
+## 项目概述
+
+环信 Web CallKit 是一款功能完善的音视频通话组件库,支持一对一通话、群组通话等多种通话场景。该组件库采用清晰的架构分层、完善的类型定义以及灵活的配置机制,为开发者提供强大且易用的通话功能。凭借模块化设计,各组件具备优秀的可维护性和扩展性,结合灵活的布局系统、完整的通话状态管理以及丰富的用户交互功能,该组件库可全面适配各类复杂通话需求。
+
+## 项目结构
+
+```
+module/callkit/
+├── CallKit.tsx # 主组件,核心逻辑入口
+├── components/ # UI 组件目录
+│ ├── VideoPlayer.tsx # 视频播放器组件
+│ ├── InvitationContent.tsx # 邀请内容组件
+│ └── ...
+├── layouts/ # 布局系统目录
+│ ├── FullLayoutManager.tsx # 完整布局管理器
+│ ├── OneToOneFullLayout.tsx # 一对一通话布局
+│ ├── MultiPartyFullLayout.tsx # 多人通话布局
+│ └── PreviewFullLayout.tsx # 预览模式布局
+├── services/ # 服务层目录
+│ ├── CallService.ts # 通话服务核心类
+│ ├── CallError.ts # 错误处理类
+│ └── ...
+├── hooks/ # 自定义 Hook 目录
+│ ├── useCallTimer.ts # 通话计时器 Hook
+│ ├── useInvitationTimers.ts # 邀请定时器 Hook
+│ ├── useContainerSize.ts # 容器尺寸 Hook
+│ ├── useFullscreen.ts # 全屏控制 Hook
+│ ├── useResizable.ts # 尺寸调整 Hook
+│ └── useDraggable.ts # 拖拽控制 Hook
+├── types/ # 类型定义目录
+│ ├── index.ts # 主要类型定义
+│ ├── layout.ts # 布局相关类型
+│ └── ...
+└── styles/ # 样式文件目录
+ └── index.scss # 主样式文件
+```
+
+## 核心架构组件
+
+各组件之间的关系如下图所示:
+
+
+### 核心服务层
+
+| 组件 | 描述 |
+| :------------ | :--- |
+| CallService Class | - 负责管理通话的核心逻辑。
- 处理音视频流的创建、管理和销毁。
- 管理通话状态转换。
- 与 Web IM 和 Agora RTC 进行交互。|
+| CallServiceConfig | - 配置 `CallService` 的各种参数。
- 包含回调函数、连接信息等配置项。|
+| CallInfo Interface | - 定义通话信息的标准格式。
- 包含通话 ID、类型、参与者等关键信息。 |
+
+### CallKit 组件层
+
+| 组件 | 描述 |
+| :------------ | :--- |
+| CallKit forwardRef | - 主要的 React 组件入口。
- 管理组件的整体状态和生命周期。
- 提供对外的方法接口。|
+| 状态管理 | - `videos`: 管理所有视频窗口信息。
- `callStatus`: 管理通话状态。
- `invitation`: 管理邀请相关信息。|
+
+
+### 布局系统
+
+| 组件 | 描述 |
+| :------------ | :--- |
+| - FullLayoutManager
- MemoizedFullLayoutManager | - 统一的布局管理器,支持多种布局模式。
- 使用 React.memo 进行性能优化。
- 根据通话类型自动选择合适的布局。 |
+| 布局类型 | - `OneToOneFullLayout`: 一对一通话布局。
- `MultiPartyFullLayout`: 多人通话布局。
- `PreviewFullLayout`: 预览模式布局。 |
+
+### UI 组件层
+
+| 组件 | 描述 |
+| :------------ | :--- |
+| CallControls Component | - 通话控制按钮(静音、摄像头、扬声器等)。
- 支持自定义图标和样式。 |
+| VideoWindow Rendering | - 视频窗口渲染组件。
- 支持本地和远程视频流。
- 包含用户头像、昵称、状态指示器等。|
+| UserSelect Component | - 用户选择组件,用于群组通话。
- 支持多选和搜索功能。|
+
+## 功能与特性
+
+| 功能/特性 | 描述 |
+| :------------ | :--- |
+| 通话类型支持 | - **一对一视频通话**: 支持本地预览、远程视频显示。
- **一对一音频通话**: 纯音频通话模式。
- **群组通话**: 支持多人音视频通话。 |
+| 布局系统 | - **自适应布局**: 根据参与者数量自动调整布局。
- **多种布局模式**: 支持一对一、多人、预览等不同布局。
- **响应式设计**: 支持窗口大小调整和拖拽。 |
+| 状态管理 | - **完整的状态机**: idle → calling → ringing → connected。
- **实时状态同步**: 与 CallService 保持状态一致。
- **错误处理**: 完善的错误处理和恢复机制。 |
+| 用户交互 | - **控制按钮**: 静音、摄像头、扬声器等。
- **拖拽和调整**: 支持窗口拖拽和尺寸调整。
- **全屏和最小化**: 支持全屏模式和最小化功能。 |
+| 通知系统 | - **邀请通知**: 支持自定义邀请内容和样式
- **超时处理**: 自动处理邀请超时。
- **多语言支持**: 内置国际化支持。 |
diff --git a/docs/callkit/web/common_issue.md b/docs/callkit/web/common_issue.md
new file mode 100644
index 000000000..cda1db33a
--- /dev/null
+++ b/docs/callkit/web/common_issue.md
@@ -0,0 +1,27 @@
+# 常见问题
+
+## 1. 发起通话无反应
+
+- 检查 chatClient:chatClient 为 IM SDK 示例, 需确保 SDK 已经初始化并登录。
+- 用户不存在:确保已在环信控制台创建用户。详见 [控制台文档](/product/console/operation_user.html#创建用户)。
+
+## 2. 通话无法建立
+
+- 对方离线:确保接听方在线且已登录。
+- 网络问题:检查网络连接状况。
+
+## 3. 音视频问题
+
+ - 无声音:检查麦克风权限和音频设备。
+ - 无画面:检查摄像头权限和浏览器兼容性。
+ - 画面卡顿:检查网络带宽。
+
+## 4. 浏览器兼容性
+
+- 不支持 Web RTC:确保使用现代浏览器最新版本。
+- HTTPS 要求:生产环境需要 HTTPS 协议。
+- 浏览器兼容详情,详见 [声网 RTC 文档](https://doc.shengwang.cn/doc/rtc/javascript/overview/browser-compatibility)。
+
+## 5. 好友检查
+
+默认情况下,环信 CallKit 支持陌生人之间进行通话,即无需添加好友即可通话。若在即时通讯 IM 控制台 [开启了好友检查](/product/console/basic_user.html#好友关系检查),会导致非好友不能通过 CallKit 进行一对一通话,群组音视频通话信令也会受影响(邀请使用群定向消息,其他信令均为单聊消息)。建议不开启好友检查,后续 SDK 迭代会优化。
diff --git a/docs/callkit/web/customization.md b/docs/callkit/web/customization.md
new file mode 100644
index 000000000..4264b8d90
--- /dev/null
+++ b/docs/callkit/web/customization.md
@@ -0,0 +1,82 @@
+# 自定义资源
+
+## 布局配置
+
+| 资源 | 描述 |
+| :------------ | :----------------- |
+| `aspectRatio` | 视频窗口宽高比。 |
+| `gap` | 窗口间距。 |
+| `maxVideos` | 最大显示视频数量。 |
+
+## 窗口管理配置
+
+| 资源 | 描述 |
+| :------------------ | :--------------------------- |
+| `resizable` | 窗口是否可调整大小。 |
+| `minWidth` | 窗口最小宽度。 |
+| `minHeight` | 窗口最小高度。 |
+| `maxWidth` | 窗口最大宽度。 |
+| `maxHeight` | 窗口最大高度。 |
+| `onResize` | 窗口尺寸调整时的回调函数。 |
+| `draggable` | 窗口是否可拖拽。 |
+| `onDragStart` | 开始拖拽时的回调函数。 |
+| `onDrag` | 拖拽过程中的回调函数。 |
+| `onDragEnd` | 拖拽结束时的回调函数。 |
+| `managedPosition` | 内置位置管理。 |
+| `initialPosition` | 初始位置。 |
+| `initialSize` | 初始尺寸。 |
+| `minimizedSize` | 群组通话最小化的尺寸。 |
+| `onMinimizedChange` | 最小化状态变化时的回调函数。 |
+
+## 自定义通话背景
+
+| 资源 | 描述 |
+| :---------------- | :------------------ |
+| `backgroundImage` | 通话背景图片的 URL。 |
+
+## 邀请配置
+
+| 资源 | 描述 |
+| :------------------------ | :--------------------- |
+| `invitationCustomContent` | 自定义邀请内容组件。 |
+| `showInvitationAvatar` | 邀请界面是否显示头像。 |
+
+## 呼叫超时时间
+
+| 资源 | 描述 |
+| :--------------- | :----------------------------- |
+| `autoRejectTime` | 呼叫超时自动取消的时间(秒),默认为 30 秒。 |
+
+## 铃声配置
+
+| 资源 | 描述 |
+| :-------------------- | :--------------------- |
+| `outgoingRingtoneSrc` | 呼出铃声的音频源路径。 |
+| `incomingRingtoneSrc` | 来电铃声的音频源路径。 |
+| `enableRingtone` | 是否启用铃声。 |
+| `ringtoneVolume` | 铃声音量(0-1)。 |
+| `ringtoneLoop` | 铃声是否循环播放。 |
+
+## 音量指示器
+
+| 资源 | 描述 |
+| :------------------------ | :--------------------------- |
+| `speakingVolumeThreshold` | 音量阈值:说话检测 0-100,默认为 60。 |
+
+## 自定义图标
+
+| 资源 | 描述 |
+| :---------------------------------- | :----------------------------- |
+| `customIcons.controls.micOn` | 麦克风打开时的自定义图标。 |
+| `customIcons.controls.micOff` | 麦克风关闭时的自定义图标。 |
+| `customIcons.controls.cameraOn` | 摄像头打开时的自定义图标。 |
+| `customIcons.controls.cameraOff` | 摄像头关闭时的自定义图标。 |
+| `customIcons.controls.speakerOn` | 扬声器打开时的自定义图标。 |
+| `customIcons.controls.speakerOff` | 扬声器关闭时的自定义图标。 |
+| `customIcons.controls.hangup` | 挂断按钮的自定义图标。 |
+| `customIcons.controls.accept` | 接听按钮的自定义图标。 |
+| `customIcons.controls.reject` | 拒接按钮的自定义图标。 |
+| `customIcons.header.minimize` | 最小化按钮的自定义图标。 |
+| `customIcons.header.fullscreen` | 全屏按钮的自定义图标。 |
+| `customIcons.header.exitFullscreen` | 退出全屏按钮的自定义图标。 |
+| `customIcons.header.addParticipant` | 群通话邀请人按钮的自定义图标。 |
\ No newline at end of file
diff --git a/docs/callkit/web/design_guide.md b/docs/callkit/web/design_guide.md
new file mode 100644
index 000000000..4334f631b
--- /dev/null
+++ b/docs/callkit/web/design_guide.md
@@ -0,0 +1,6 @@
+---
+{
+ pageUri: "/callkit/android/design_guide.html",
+ title: "设计指南"
+}
+---
\ No newline at end of file
diff --git a/docs/callkit/web/easecallkit.md b/docs/callkit/web/easecallkit.md
new file mode 100644
index 000000000..ecd0e6294
--- /dev/null
+++ b/docs/callkit/web/easecallkit.md
@@ -0,0 +1,223 @@
+# CallKit 使用指南
+
+`CallKit` 是一套基于环信 IM 和声网音视频结合开发的音视频 UI 库,实现了一对一语音和视频通话以及多人音视频通话的功能。通过同一用户 ID 登录多台设备的场景下,当用户处理一台设备上的来电响铃后,其他所有设备都会同时停止响铃。
+
+## 技术原理
+
+使用 `CallKit` 实现实时音视频通讯的基本流程如下:
+
+1. 调用 `init` 对 `CallKit` 进行初始化。
+2. 主叫方调用 `startCall` 发起通话邀请,进行一对一或多人通话。
+3. 被叫方收到 `onInvite` 后,选择接受或拒绝通话邀请。若接受邀请,则进入通话。
+4. 通话结束时,SDK 触发 `onStateChange` 回调。
+
+## 前提条件
+
+集成该库之前,你需要满足以下条件:
+
+- 创建 [环信应用](/product/enable_and_configure_IM.html)及[声网应用](https://doc.shengwang.cn/doc/rtc/javascript/get-started/enable-service#创建声网项目);
+- 实现环信 IM 的基本功能,包括登录、好友、群组以及会话等的集成;
+- 上线前开通声网 Token 验证时,用户需要实现自己的 [App Server](https://github.com/easemob/easemob-im-app-server/tree/master/agora-app-server),用于生成 Token。详见[创建 Token 服务及使用 App Server 生成 Token](https://doc.shengwang.cn/doc/rtc/javascript/basic-features/token-authentication)。
+
+## 项目设置
+
+1. 在终端上运行以下命令安装 `CallKit`:
+
+```
+npm install chat-callkit
+```
+
+2. 导入 `CallKit`:
+
+```
+import Callkit from 'chat-callkit';
+```
+
+## 实现音频和视频通话
+
+本节介绍如何在你的项目中实现音频和视频通话。
+
+### 初始化 `CallKit`
+
+调用 `init` 初始化 `CallKit`。
+
+```javascript
+/**
+ * 初始化 CallKit
+ *
+ * @param appId 声网 App ID。
+ * @param agoraUid 声网用户 ID(UID)。
+ * @param connection IM SDK 连接实例。
+ */
+CallKit.init(appId, agoraUid, connection);
+```
+
+### 发送通话邀请
+
+主叫方调用 `startCall` 发送一对一或多人通话邀请。调用该方法时,需要指定通话类型。
+
+- 一对一通话
+
+一对一通话时,主叫方向被叫方发送短信作为通话邀请。
+
+```javascript
+let options = {
+ /** 通话类型:
+ * 0:一对一音频通话
+ * 1:一对一视频通话
+ * 2:多人视频通话
+ * 3:多人音频通话
+ */
+ callType: 0,
+ chatType: "singleChat",
+ /** IM 用户 ID */
+ to: "userId",
+ /** 通话邀请消息 */
+ message: "Join me on the call",
+ /** 通话频道名称 */
+ channel: "channel",
+ /** 声网 token */
+ accessToken: "Agora token",
+};
+CallKit.startCall(options);
+```
+
+- 多人通话
+
+在多人通话中,主叫方向群组或聊天室发送文本消息,同时向用户发送命令消息加入通话。
+
+```javascript
+let options = {
+ /** 通话类型:
+ * 0:一对一音频通话
+ * 1:一对一视频通话
+ * 2:多人视频通话
+ * 3:多人音频通话
+ */
+ callType: 2,
+ chatType: "groupChat",
+ /** IM 用户 ID */
+ to: ["userId"],
+ /** 通话邀请消息 */
+ message: "Join me on the call",
+ /** 群组 ID */
+ groupId: "groupId",
+ /** 群组名称 */
+ groupName: "group name",
+ /** 声网 token */
+ accessToken: "Agora token",
+ /** 通话频道名称 */
+ channel: "channel",
+};
+CallKit.startCall(options);
+```
+
+下图为发送一对一视频通话邀请后的用户界面示例:
+
+
+
+### 收到通话邀请
+
+通话邀请发送后,如果被叫方在线且可以通话,将通过 `onInvite` 回调收到邀请。你可以弹出一个用户界面,让被叫方在该回调中接受或拒绝邀请。
+
+```javascript
+/**
+ * 处理通话邀请。
+ *
+ * @param result 是否弹出用户界面,接听来电:
+ * - true:是。
+ * - false:否。这种情况下,你无需传入 token 。
+ * @param accessToken 声网 token 。
+ */
+CallKit.answerCall(result, accessToken);
+```
+
+下图为收到一对一视频通话邀请后的用户界面示例:
+
+
+
+### 多人通话中间发起邀请
+
+在多人通话中,多个用户还可以向其他用户发送通话邀请。发送邀请后,SDK 会在发送方的客户端触发 `onAddPerson` 回调。在该回调中,你可以让发送方指定想要邀请加入多人通话的用户,然后调用 `startCall` 发出邀请。
+
+### 监听回调事件
+
+在通话中,你还可以监听以下回调事件:
+
+```javascript
+function Call() {
+ // 处理会话状态变更。
+ const handleCallStateChange = (info) => {
+ switch (info.type) {
+ case "hangup":
+ // 挂断电话。
+ break;
+ case "accept":
+ // 被叫方接受通话邀请。
+ break;
+ case "refuse":
+ // 被叫方拒绝通话邀请。
+ break;
+ case "user-published":
+ // 远端用户在通话中发布媒体流。
+ break;
+ case "user-unpublished":
+ // 远端用户在通话中停止发布媒体流。
+ break;
+ case "user-left":
+ // 远端用户离开通话。
+ break;
+ default:
+ break;
+ }
+ };
+ return ;
+}
+```
+
+### 结束通话
+
+一对一通话中,只要有一方挂断电话,通话即结束。多人通话中,只有本地用户挂断电话,通话才会结束。若本地用户挂断电话,SDK 会触发 `onStateChange` 回调,其中 `info.type` 中的值为 `hangup`。若远端用户挂断电话,SDK 触发 `onStateChange` 回调,其中 `info.type` 的值为 `user-left`。
+
+## 后续步骤
+
+本节介绍你在项目中实现音频和视频通话功能时采取的其他步骤。
+
+### 使用 Video SDK Token 对用户进行身份验证
+
+为了提升通讯安全性,声网建议你在加入通话前通过 Video SDK token 对应用用户进行身份验证。为此,你需要确保[项目的主要证书已启用](https://doc.shengwang.cn/doc/console/general/user-guides/manage_authentication#启用主要证书)。
+
+Token 由声网提供的 token 生成器在应用服务器上生成。获取 token 后,需要在调用 `startCall` 和 `answerCall` 时将 token 传递给 callkit。关于在服务器上如何生成 Token 以及在客户端如何获取和更新 Token,详见[使用 Token 认证用户](https://doc.shengwang.cn/doc/rtc/javascript/basic-features/token-authentication)。
+
+## 参考
+
+本节提供了实现实时音频和视频通信功能时可以参考的其他信息。
+
+### API 列表
+
+`CallKit` 提供以下 API:
+
+- 方法如下表所示:
+
+| 方法 | 描述 |
+| ------------------------- | --------------------------------------------------------------------------------------------------------- |
+| `initWithConfig:delegate` | 初始化 `CallKit`。 |
+| `startCall` | 开始通话。 |
+| `answerCall` | 接听电话。 |
+| `setUserIdMap` | 设置环信 IM 用户 ID 与声网用户 ID(UID)的映射,格式为 `{[uid1]: 'custom name', [uid2]: 'custom name'}`。 |
+
+- 回调如下表所示:
+
+| 事件 | 描述 |
+| --------------- | ---------------------------------- |
+| `onAddPerson` | 当用户邀请其他用户加入通话时触发。 |
+| `onInvite` | 收到通话邀请时触发。 |
+| `onStateChange` | 当通话状态变更时发生。 |
+
+- 属性如下表所示:
+
+| 属性 | 描述 |
+| --------------- | ------------------------ |
+| `contactAvatar` | 一对一通话时显示的头像。 |
+| `groupAvatar` | 多人通话时显示的头像。 |
+| `ringingSource` | 铃声文件。 |
diff --git a/docs/callkit/web/integration.md b/docs/callkit/web/integration.md
new file mode 100644
index 000000000..d35dafe06
--- /dev/null
+++ b/docs/callkit/web/integration.md
@@ -0,0 +1,424 @@
+# CallKit 集成指南
+
+本文档详细介绍如何在你的 React 项目中集成和使用环信 CallKit,实现完整的音视频通话功能。
+
+
+
+
+
+
+## 推荐环境
+
+- Node.js: 18.0 及以上
+- npm: 9.0 及以上 或 yarn: 1.22 及以上
+- React: 18.0 及以上
+- TypeScript: 4.9 及以上
+- Vite: 4.0 及以上
+- IM SDK 4.16.0 及以上或 UIKit 2.0.0 及以上
+- 现代浏览器: Chrome/Firefox/Safari/Edge 最新版本
+
+## 前提条件
+
+在集成 CallKit 之前,你需要完成以下准备工作:
+
+1. 在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+
+- [注册环信账号](/product/console/account_register.html#注册账号)。
+- [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+- [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID。
+- [创建群组](/product/console/operation_group.html#创建群组),获取群组 ID。将用户加入群组。
+- [开通音视频服务](product_activation.html)。
+
+2. 集成环信即时通讯 IM SDK。
+
+确保已集成环信 IM SDK 并完成登录。
+
+## 集成步骤
+
+### 步骤 1 安装与引入 CallKit
+
+#### 1. 安装依赖
+
+```bash
+npm install easemob-chat-uikit
+# 或
+yarn add easemob-chat-uikit
+```
+
+#### 2. 导入样式
+
+```tsx
+import "easemob-chat-uikit/style.css";
+```
+
+#### 3. 引入 CallKit
+
+```tsx
+import { CallKit, Provider, rootStore } from "easemob-chat-uikit";
+import type { CallKitRef } from "easemob-chat-uikit";
+```
+
+### 步骤 2 配置 CallKit 组件
+
+在你的应用根组件中,需要使用 `Provider` 组件包裹整个应用,并在其中使用 `CallKit` 组件:
+
+```tsx
+import React, { useRef } from "react";
+import { Provider, CallKit, rootStore } from "easemob-chat-uikit";
+import type { CallKitRef } from "easemob-chat-uikit";
+import "easemob-chat-uikit/style.css";
+
+const App = () => {
+ const callKitRef = useRef(null);
+
+ // 用户信息提供者
+ const userInfoProvider = async (userIds: string[]) => {
+ return userIds.map((userId) => ({
+ userId,
+ nickname: `用户 ${userId}`,
+ avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${userId}`,
+ }));
+ };
+
+ // 群组信息提供者
+ const groupInfoProvider = async (groupIds: string[]) => {
+ return groupIds.map((groupId) => ({
+ groupId,
+ groupName: `群组 ${groupId}`,
+ groupAvatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=group-${groupId}`,
+ }));
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default App;
+```
+
+CallKit 组件或重要配置的说明如下:
+
+| 组件/属性 | 说明 |
+| :-------------- | :----- |
+| Provider 组件 | - 负责初始化环信 IM SDK 连接,必须包裹在应用的最外层。
- 该组件会自动处理 IM SDK 的初始化和登录。|
+| initConfig 配置 | 包含应用的 App Key、用户 ID 和登录凭证(Token)。 |
+| CallKit 组件 | - 音视频通话组件,会自动处理内部的初始化逻辑。
- 该组件会在内部自动初始化音视频服务,无需手动调用初始化方法。 |
+| chatClient 属性 | 传入 `rootStore.client`,即 Provider 创建的 IM 连接实例。 |
+| 信息提供者 | `userInfoProvider` 和 `groupInfoProvider` 用于获取用户和群组的显示信息。 |
+
+
+### 步骤 3 登录 IM
+
+CallKit 内部依赖 IM SDK 进行信令交互,所以在使用 CallKit 之前需要先登录 IM。登录 IM 有两种方式可以选择:
+
+1. 使用 UIKit:UIKit Provider 组件内集成了 IM SDK,提供 `userId` 和 `token` 属性,内部会自动登录。
+
+```tsx
+import React, { useRef } from "react";
+import { Provider, CallKit, rootStore } from "easemob-chat-uikit";
+import type { CallKitRef } from "easemob-chat-uikit";
+import "easemob-chat-uikit/style.css";
+
+const App = () => {
+ const callKitRef = useRef(null);
+ return (
+
+
+
+ );
+};
+
+export default App;
+```
+
+若手动登录,可以从 `rootStore` 获取 IM SDK 实例,调用 SDK 的 `open` 方法登录。
+
+```tsx
+import React, { useRef, useEffect } from "react";
+import { Provider, CallKit, rootStore } from "easemob-chat-uikit";
+import type { CallKitRef } from "easemob-chat-uikit";
+import "easemob-chat-uikit/style.css";
+
+const App = () => {
+ const callKitRef = useRef(null);
+
+ useEffect(() => {
+ // 手动登录
+ rootStore.client.open({
+ user: "userId",
+ accessToken: "accessToken",
+ });
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default App;
+```
+
+2. 如果不使用 UIKit Provider, 只使用 CallKit 组件,可自行集成 IM SDK 并处理登录。
+
+```tsx
+import React, { useRef } from "react";
+import { CallKit } from "easemob-chat-uikit";
+import type { CallKitRef } from "easemob-chat-uikit";
+import ChatSDK from "easemob-websdk";
+import "easemob-chat-uikit/style.css";
+
+const App = () => {
+ const callKitRef = useRef(null);
+ const [chatClient, setChatClient] = useState(null);
+
+ useEffect(() => {
+ const chat = new ChatSDK.connection({
+ appKey: "your appKey",
+ });
+
+ chat.open({
+ user: "userId",
+ accessToken: "accessToken",
+ });
+ setChatClient(chat);
+ }, []);
+ return (
+
+ );
+};
+
+export default App;
+```
+
+### 步骤 4 配置监听器
+
+CallKit 组件可以设置回调事件,实现监听 CallKit 内部状态和错误事件。
+
+```tsx
+ {}}
+ onEndCallWithReason={(reason) => {}}
+/>
+```
+
+回调事件说明如下表所示:
+
+| 回调事件 | 参数 | 描述 |
+| --------------------- | ----------------------------------------------- | ------------------------------------------------ |
+| `onCallError` | `(error: CallError)` | 通话过程中发生错误时触发,包含错误类型和详细信息 |
+| `onReceivedCall` | `(callType, userId, ext)` | 收到通话邀请时触发 |
+| `onCallStart` | `(videos: VideoWindowProps[])` | 通话开始时触发 |
+| `onEndCallWithReason` | `(reason: string, callInfo: CallInfo)` | 通话结束原因回调 |
+| `onRemoteUserJoined` | `(userId: string, callType)` | 远程用户加入通话时触发 |
+| `onRemoteUserLeft` | `(userId: string, callType)` | 远程用户离开通话时触发 |
+| `onInvitationAccept` | `(invitation: InvitationInfo)` | 用户接受邀请时触发 |
+| `onInvitationReject` | `(invitation: InvitationInfo)` | 用户拒绝邀请时触发 |
+| `onLayoutModeChange` | `(layoutMode: string)` | 布局模式变化时触发 |
+| `onMinimizedChange` | `(minimized: boolean)` | 最小化状态变化时触发 |
+| `onResize` | `(width, height, deltaX?, deltaY?, direction?)` | 窗口大小调整时触发 |
+| `onDragStart` | `(startPosition: {x, y})` | 开始拖拽时触发 |
+| `onDrag` | `(newPosition: {x, y}, delta: {x, y})` | 拖拽过程中触发 |
+| `onDragEnd` | `(finalPosition: {x, y})` | 拖拽结束时触发 |
+| `onRtcEngineCreated` | `(rtc: any)` | RTC 引擎创建完成时触发,可用于自定义配置 |
+| `onAddParticipant` | `()` | 用户点击添加参与者按钮时触发 |
+
+### 步骤 5 发起通话
+
+#### 发起一对一通话
+
+你可以使用 `startSingleCall` 方法发起一对一通话,`callType` 设置为 `video` 为视频通话,`audio` 为音频通话。
+
+```tsx
+const App = () => {
+ const callKitRef = useRef(null);
+ // 一对一视频通话
+ const startVideoCall = () => {
+ callKitRef.current?.startSingleCall({
+ to: "target_user_id",
+ callType: "video",
+ msg: "邀请你进行视频通话",
+ });
+ };
+
+ // 一对一语音通话
+ const startAudioCall = () => {
+ callKitRef.current?.startSingleCall({
+ to: "target_user_id",
+ callType: "audio",
+ msg: "邀请你进行语音通话",
+ });
+ };
+ return (
+
+
+
+ );
+};
+```
+
+
+
+
+
+
+#### 发起群组通话
+
+- **创建群组**:要发起群组通话,你需要首先创建群组,在群组中添加用户,详见 [环信控制台文档](/product/console/operation_group.html#创建群组)。
+- **发起群组通话**:你可以使用 `startGroupCall` 发起群组通话,指定群组 ID,`callType` 设置为 `video` 为视频通话,`audio` 为音频通话,并设置邀请消息 `msg`。CallKit 会自动拉起群成员选择界面,界面显示群组中的所有成员(群主、管理员、普通成员),用户可以选择要邀请的成员,选中人数会实时显示。为了保证通话质量和性能,CallKit 限制群组通话最多支持 **16 人** 同时参与(包括发起者)。
+- **通话中邀请他人**:群组通话中,当前用户可以点击通话界面右上角的邀请按钮向其他用户发起邀请。
+
+```tsx
+// 群组通话
+const startGroupCall = () => {
+ callKitRef.current?.startGroupCall({
+ groupId: "group_id",
+ callType: "video",
+ msg: "邀请加入群组视频通话",
+ });
+};
+```
+
+
+
+
+
+### 步骤 6 接听通话
+
+当接收到通话邀请时,CallKit 会自动触发 `onReceivedCall` 回调:
+1. 弹出通话邀请界面。
+2. 播放来电铃声。
+3. 显示通话邀请通知。
+
+被叫用户可选择接听、拒绝或挂断通话。
+
+
+
+
+
+
+
+## 高阶功能
+
+### 用户信息
+
+- 默认情况下,音视频通话中显示用户 ID 和默认头像,你可以通过 `userInfoProvider` 设置用户昵称和头像。
+- 默认情况下,群组音视频通话中显示群组 ID 和默认群组头像,你可以通过 `groupInfoProvider` 设置群组名称和群组头像。
+
+```tsx
+// 实现用户信息提供者
+const userInfoProvider = async (userIds: string[]) => {
+ // 从你的服务器或本地缓存获取用户信息
+ return userIds.map((userId) => ({
+ userId,
+ nickname: `用户 ${userId}`,
+ avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${userId}`,
+ }));
+};
+// 实现群组信息提供者
+const groupInfoProvider = async (groupIds: string[]) => {
+ // 从你的服务器或本地缓存获取群组信息
+ return groupIds.map((groupId) => ({
+ groupId,
+ groupName: `群组 ${groupId}`,
+ groupAvatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=group-${groupId}`,
+ }));
+};
+```
+
+### 自定义视频分辨率
+
+环信 CallKit 中默认设置的分辨率为 720p。若要修改远端视频在本地显示的分辨率,可以设置 `encoderConfig` 参数。详见 [声网 RTC 文档](https://doc.shengwang.cn/api-ref/rtc/javascript/globals.html#videoencoderconfigurationpreset)。
+
+```tsx
+
+```
+
+### 用户信息缓存
+
+通话过程中,优先使用缓存中的用户信息。若缓存中没有用户信息,你可以去服务器获取。
+
+```tsx
+const userInfoCache = new Map();
+
+const userInfoProvider = async (userIds: string[]) => {
+ const uncachedIds = userIds.filter((id) => !userInfoCache.has(id));
+
+ if (uncachedIds.length > 0) {
+ // 只请求未缓存的用户信息
+ const newUserInfos = await fetchUserInfoFromServer(uncachedIds);
+ newUserInfos.forEach((info) => {
+ userInfoCache.set(info.userId, info);
+ });
+ }
+
+ return userIds.map((id) => userInfoCache.get(id));
+};
+```
+
+### 组件卸载时清理缓存数据
+
+CallKit 组件卸载时需要清理缓存数据。
+
+```tsx
+useEffect(() => {
+ return () => {
+ // 组件卸载时结束通话
+ callKitRef.current?.exitCall();
+ };
+}, []);
+```
diff --git a/docs/callkit/web/product_activation.md b/docs/callkit/web/product_activation.md
new file mode 100644
index 000000000..602f23734
--- /dev/null
+++ b/docs/callkit/web/product_activation.md
@@ -0,0 +1,6 @@
+---
+{
+ pageUri: "/callkit/android/product_activation.html",
+ title: "开通服务"
+}
+---
\ No newline at end of file
diff --git a/docs/callkit/web/product_overview.md b/docs/callkit/web/product_overview.md
new file mode 100644
index 000000000..6eb4bdd9a
--- /dev/null
+++ b/docs/callkit/web/product_overview.md
@@ -0,0 +1,60 @@
+# 产品介绍
+
+环信音视频通话 CallKit 是基于环信即时通讯 IM 和声网实时音视频 RTC 深度整合开发的实时音视频通话框架,实现了一对一及群组音视频通话功能。开发者只需简单集成,即可快速获得稳定流畅的高品质音视频通话能力。环信音视频通话 CallKit 可用于在线互动课堂、视频客服中心、远程会诊系统或视频相亲等场景。
+
+
+
+## CallKit 优势
+
+| 优势 | 描述 |
+| :-------------- | :----- |
+| 三位一体技术整合 | - 环信即时通讯 IM + 声网实时音视频 RTC + UI 组件深度整合
- 双平台服务一键开通,免除多系统对接成本 |
+| 高质量通话品质 | - 声网全球网络:超过 99.99% 服务可用性
- 超低延时:低于 76ms 的端到端延迟
- 抗丢包技术:80% 丢包仍可流畅通话 |
+
+## 功能
+
+| 基本功能 | 高级功能 | 功能优势 |
+| :-------------- | :----- | :------- |
+| - 一对一语音/视频通话
- 群组语音/视频通话(16 人及以下):通话中邀请他人
- 自定义铃声:支持主叫、被叫、挂断、超时铃声
- 自定义 UI 界面 | - 高画质/高音质音视频
- 通话质量检测
- 全球互通
- 弱网卡顿优化
- 视频降噪| - 高质量音视频通话
- 完善的 UI 交互
- 支持多平台互联互通
- 离线推送稳定且多样化 |
+
+## 界面效果展示
+
+### 一对一视频通话
+
+
+
+
+
+
+
+
+### 一对一音频通话
+
+
+
+
+
+
+
+
+### 群组通话
+
+
+
+
+
+
+
+
+
+
+### 来电通知
+
+
+
+
+
+## 使用限制
+
+- 群组音视频通话默认最多支持 16 人。
+- 关于声网 RTC 的使用限制,详见 [声网 RTC 关键性能指标](https://doc.shengwang.cn/doc/rtc/javascript/overview/product-overview#%E5%85%B3%E9%94%AE%E6%8C%87%E6%A0%87) 和 [配额限制](https://doc.shengwang.cn/doc/rtc/javascript/overview/product-overview#%E9%85%8D%E9%A2%9D%E9%99%90%E5%88%B6)。
diff --git a/docs/callkit/web/product_purchase.md b/docs/callkit/web/product_purchase.md
new file mode 100644
index 000000000..004a3f643
--- /dev/null
+++ b/docs/callkit/web/product_purchase.md
@@ -0,0 +1,6 @@
+---
+{
+ pageUri: "/callkit/android/product_purchase.html",
+ title: "购买指南"
+}
+---
\ No newline at end of file
diff --git a/docs/callkit/web/quickstart.md b/docs/callkit/web/quickstart.md
new file mode 100644
index 000000000..ed8145e68
--- /dev/null
+++ b/docs/callkit/web/quickstart.md
@@ -0,0 +1,616 @@
+# 快速开始
+
+利用环信 Web CallKit,你可以轻松实现一对一通话和群组通话功能。本文介绍如何快速实现发起音视频通话。
+
+## 推荐环境
+
+- Node.js: 18.0 或以上版本
+- npm: 9.0 或以上 或 yarn: 1.22 或以上版本
+- React: 18.0 或以上版本
+- TypeScript: 4.9 或以上版本
+- Vite: 4.0 或以上版本
+- 现代浏览器: Chrome/Firefox/Safari/Edge 最新版本
+- IM SDK 4.16.0 或以上/UIKit 2.0.0 或以上版本
+
+## 前提条件
+
+在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+
+1. [注册环信账号](/product/console/account_register.html#注册账号)。
+2. [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+3. [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID。
+4. [开通音视频服务](product_activation.html)。为了保障流畅的用户体验,开通服务后,你需等待 15 分钟才能实现发起音视频通话。
+
+## 快速开始
+
+### 步骤 1 创建项目
+
+本节介绍使用 Vite 创建 React + TypeScript 项目。
+
+1. 使用 Vite 创建新项目:
+
+```bash
+npm create vite@latest callkit-quickstart -- --template react-ts
+cd callkit-quickstart
+```
+
+2. 安装项目依赖:
+
+```bash
+npm install
+```
+
+3. 启动开发服务器验证项目创建成功:
+
+```bash
+npm run dev
+```
+
+4. 打开浏览器访问 `http://localhost:5173`,确认项目正常运行。
+
+### 步骤 2 引入 CallKit
+
+在项目根目录下安装 CallKit 依赖:
+
+```bash
+npm install easemob-chat-uikit
+# 或使用 yarn
+yarn add easemob-chat-uikit
+```
+
+### 步骤 3 创建快速开始页面
+
+1. 替换 `src/App.tsx` 文件内容:
+
+::: details src/App.tsx 文件中的替换代码
+
+```tsx
+import React, { useState, useRef, useEffect } from "react";
+import { Provider, CallKit, rootStore } from "easemob-chat-uikit";
+import type { CallError, CallInfo } from "easemob-chat-uikit";
+import "easemob-chat-uikit/style.css";
+import "./App.css";
+
+interface ConnectionStatus {
+ isConnected: boolean;
+ status: string;
+}
+const appKey = "org#app"; // 修改成你自己的 appKey
+const App: React.FC = () => {
+ // 登录相关状态
+ const [userId, setUserId] = useState("");
+ const [accessToken, setAccessToken] = useState("");
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const [connectionStatus, setConnectionStatus] = useState({
+ isConnected: false,
+ status: "连接状态: 未连接",
+ });
+
+ // 通话相关状态
+ const [targetUserId, setTargetUserId] = useState("");
+ const [groupId, setGroupId] = useState("");
+
+ // CallKit 引用
+ const callKitRef = useRef(null);
+
+ // 处理 URL 参数快速登录
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const userIdFromUrl = urlParams.get("userId");
+ if (userIdFromUrl) setUserId(userIdFromUrl);
+
+ const accessTokenFromUrl = urlParams.get("accessToken");
+ if (accessTokenFromUrl) setAccessToken(accessTokenFromUrl);
+ }, []);
+
+ // 监听连接状态
+ useEffect(() => {
+ // 监听连接状态变化
+ if (rootStore.client) {
+ rootStore.client.addEventHandler("CONNECTION_LISTENER", {
+ onConnected: () => {
+ setConnectionStatus({
+ isConnected: true,
+ status: "连接状态: 已连接",
+ });
+ },
+ onDisconnected: () => {
+ setConnectionStatus({
+ isConnected: false,
+ status: "连接状态: 已断开",
+ });
+ },
+ });
+ }
+
+ return () => {
+ if (rootStore.client) {
+ rootStore.client.removeEventHandler("CONNECTION_LISTENER");
+ }
+ };
+ }, [isLoggedIn]);
+
+ // 登录处理
+ const handleLogin = async () => {
+ if (!userId.trim() || !accessToken.trim()) {
+ alert("用户ID和 accessToken 不能为空!");
+ return;
+ }
+
+ try {
+ // 登录环信 IM
+ await rootStore.client.open({
+ user: userId.trim(),
+ accessToken: accessToken.trim(),
+ });
+
+ setIsLoggedIn(true);
+ setConnectionStatus({
+ isConnected: true,
+ status: "连接状态: 已连接",
+ });
+ alert("登录成功!");
+ } catch (error: any) {
+ alert(`登录失败: ${error.message || error}`);
+ }
+ };
+
+ // 登出处理
+ const handleLogout = async () => {
+ try {
+ await rootStore.client?.close();
+ setIsLoggedIn(false);
+ setConnectionStatus({
+ isConnected: false,
+ status: "连接状态: 已登出",
+ });
+ // 结束所有通话
+ callKitRef.current?.exitCall();
+ alert("登出成功!");
+ } catch (error: any) {
+ alert(`登出失败: ${error.message || error}`);
+ }
+ };
+
+ // 发起一对一视频通话
+ const handleStartVideoCall = async () => {
+ if (!targetUserId.trim()) {
+ alert("对方用户ID不能为空!");
+ return;
+ }
+
+ try {
+ await callKitRef.current?.startSingleCall({
+ to: targetUserId.trim(),
+ callType: "video",
+ msg: "邀请你进行视频通话",
+ });
+ } catch (error: any) {
+ alert(`发起视频通话失败: ${error.message || error}`);
+ }
+ };
+
+ // 发起一对一音频通话
+ const handleStartAudioCall = async () => {
+ if (!targetUserId.trim()) {
+ alert("对方用户ID不能为空!");
+ return;
+ }
+
+ try {
+ await callKitRef.current?.startSingleCall({
+ to: targetUserId.trim(),
+ callType: "audio",
+ msg: "邀请你进行语音通话",
+ });
+ } catch (error: any) {
+ alert(`发起语音通话失败: ${error.message || error}`);
+ }
+ };
+
+ // 发起群组通话
+ const handleStartGroupCall = async () => {
+ if (!groupId.trim()) {
+ alert("群组ID不能为空!");
+ return;
+ }
+
+ try {
+ await callKitRef.current?.startGroupCall({
+ groupId: groupId.trim(),
+ callType: "video",
+ msg: "邀请加入群组视频通话",
+ });
+ } catch (error: any) {
+ alert(`发起群组通话失败: ${error.message || error}`);
+ }
+ };
+
+ // 用户信息提供者
+ const userInfoProvider = async (userIds: string[]) => {
+ return userIds.map((userId) => ({
+ userId,
+ nickname: `用户 ${userId}`,
+ avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${userId}`,
+ }));
+ };
+
+ // 群组信息提供者
+ const groupInfoProvider = async (groupIds: string[]) => {
+ return groupIds.map((groupId) => ({
+ groupId,
+ groupName: `群组 ${groupId}`,
+ groupAvatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${groupId}`,
+ }));
+ };
+
+ return (
+
+
+
+
CallKit 快速开始
+
+ {/* 连接状态指示器 */}
+
+
+
{connectionStatus.status}
+
+
+ {/* 登录区域 */}
+
+
登录信息
+
+ setUserId(e.target.value)}
+ disabled={isLoggedIn}
+ />
+
+
+ setAccessToken(e.target.value)}
+ disabled={isLoggedIn}
+ />
+
+
+
+
+
+
+
+ {/* 通话区域 */}
+
+
通话功能
+
+ setTargetUserId(e.target.value)}
+ disabled={!isLoggedIn}
+ />
+
+
+
+
+
+
+
+ setGroupId(e.target.value)}
+ disabled={!isLoggedIn}
+ />
+
+
+
+
+
+
+
+ {/* CallKit 组件 */}
+ {isLoggedIn && (
+
{
+ console.error("通话错误:", error);
+ alert(`通话错误: ${error.message}`);
+ }}
+ onEndCallWithReason={(reason: string, callInfo: CallInfo) => {
+ console.log("通话结束:", reason, callInfo);
+ }}
+ />
+ )}
+
+
+ );
+};
+
+export default App;
+```
+
+:::
+
+2. 替换 `src/App.css` 文件内容:
+
+::: details src/App.css 文件中的替换代码
+
+```css
+.app-container {
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 20px;
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.main-content {
+ background: #f8f9fa;
+ padding: 30px;
+ border-radius: 12px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+h1 {
+ text-align: center;
+ color: #2c3e50;
+ margin-bottom: 30px;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+h3 {
+ color: #34495e;
+ margin-bottom: 20px;
+ font-size: 18px;
+ font-weight: 500;
+}
+
+/* 状态指示器 */
+.status-section {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 30px;
+ padding: 15px;
+ background: white;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+}
+
+.status-indicator {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ margin-right: 10px;
+}
+
+.status-indicator.connected {
+ background-color: #4caf50;
+}
+
+.status-indicator.disconnected {
+ background-color: #808080;
+}
+
+.status-text {
+ font-size: 14px;
+ font-weight: 500;
+}
+
+/* 登录和通话区域 */
+.login-section,
+.call-section {
+ background: white;
+ padding: 25px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ border: 1px solid #e9ecef;
+}
+
+.input-group {
+ margin-bottom: 15px;
+}
+
+.input-group input {
+ width: 100%;
+ padding: 12px 16px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ font-size: 14px;
+ transition: border-color 0.3s ease;
+ box-sizing: border-box;
+}
+
+.input-group input:focus {
+ outline: none;
+ border-color: #007bff;
+ box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+}
+
+.input-group input:disabled {
+ background-color: #f8f9fa;
+ color: #6c757d;
+ cursor: not-allowed;
+}
+
+.button-group {
+ display: flex;
+ gap: 10px;
+ margin-top: 20px;
+ align-items: center;
+}
+
+.button-group button {
+ flex: 1;
+ padding: 12px 20px;
+ border: none;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.button-group button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+/* 登录按钮 */
+.login-btn {
+ background-color: #28a745;
+ color: white;
+}
+
+.login-btn:hover:not(:disabled) {
+ background-color: #218838;
+}
+
+.logout-btn {
+ background-color: #6c757d;
+ color: white;
+}
+
+.logout-btn:hover:not(:disabled) {
+ background-color: #5a6268;
+}
+
+/* 通话按钮 */
+.call-btn {
+ color: white;
+ font-weight: 600;
+}
+
+.video-btn {
+ background-color: #007bff;
+}
+
+.video-btn:hover:not(:disabled) {
+ background-color: #0056b3;
+}
+
+.audio-btn {
+ background-color: #17a2b8;
+}
+
+.audio-btn:hover:not(:disabled) {
+ background-color: #117a8b;
+}
+
+.group-btn {
+ background-color: #6f42c1;
+}
+
+.group-btn:hover:not(:disabled) {
+ background-color: #5a32a3;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .app-container {
+ padding: 10px;
+ }
+
+ .main-content {
+ padding: 20px;
+ }
+
+ .button-group {
+ flex-direction: column;
+ }
+
+ h1 {
+ font-size: 24px;
+ }
+}
+```
+
+:::
+
+### 步骤 4 配置 App Key
+
+将 `App.jsx` 代码中的 `org#app` 替换成你自己的 App Key。
+
+### 步骤 5 发起首次通话
+
+1. 启动应用:
+
+```bash
+npm run dev
+```
+
+2. 输入用户 ID 和 accessToken,点击 **登录**。等待状态指示器变绿,显示 **已连接**。
+
+ 此外,为了方便测试,应用支持通过将用户 ID 和 accessToken 拼接到 URL 中快速登录:
+
+ ```
+ http://localhost:5173?userId=your_user_id&accessToken=your_accessToken
+ ```
+
+在生产环境中,为了安全考虑,你需要在你的应用服务器集成 [获取 App Token API](/document/server-side/easemob_app_token.html) 和 [获取用户 Token API](/document/server-side/easemob_user_token.html) 实现获取 Token 的业务逻辑,使你的用户从你的应用服务器获取 Token。
+
+3. 输入对方用户 ID,点击 **发起一对一视频通话** 或 **发起一对一语音通话**。
+4. 在浏览器弹出的权限请求中,允许访问摄像头和麦克风。
+5. 在通话中可以控制静音、摄像头、扬声器等,或者点击挂断按钮结束通话。
+
+
+
+## 运行应用
+
+运行应用前,你需要授权摄像头、麦克风、悬浮窗等权限。
+
+1. 在浏览器中访问 `http://localhost:5173`。
+2. 输入 App Key、用户 ID 和 accessToken,点击 **登录** 进行登录,登录成功后状态指示器会变绿。
+3. 在另一个浏览器标签页或设备上打开同样的页面,使用另一个账号登录。
+4. 在主叫浏览器或设备上输入被叫方的用户 ID,点击对应的通话按钮,即可发起音视频通话。
+
+运行应用过程中的常见问题排查如下:
+
+- 连接失败:检查 App Key 是否正确配置。
+- 通话无声音:检查麦克风权限是否已授权。
+- 视频无画面:检查摄像头权限是否已授权。
+- HTTPS 问题:生产环境部署时确保使用 HTTPS 协议。
diff --git a/docs/callkit/web/sample_runthrough.md b/docs/callkit/web/sample_runthrough.md
new file mode 100644
index 000000000..766ddfe16
--- /dev/null
+++ b/docs/callkit/web/sample_runthrough.md
@@ -0,0 +1,73 @@
+# 跑通 Web 示例项目
+
+本文档基于 `call-demo.tsx` 示例,帮助你快速集成和运行环信 Web CallKit,实现一对一音视频通话和群组音视频通话功能。
+
+## 推荐环境
+
+- Node.js: 16.0 或以上版本
+- npm/yarn: 推荐最新版本
+- React: 18.0 或以上版本
+- TypeScript: 4.9 或以上版本
+- 现代浏览器: Chrome/Firefox/Safari/Edge 最新版本
+
+## 前提条件
+
+在 [环信控制台](https://console.easemob.com/user/login) 进行如下操作:
+
+1. [注册环信账号](/product/console/account_register.html#注册账号)。
+2. [创建应用](/product/console/app_create.html),[获取应用的 App Key](/product/console/app_manage.html#获取应用凭证),格式为 `orgname#appname`。
+3. [创建用户](/product/console/operation_user.html#创建用户),获取用户 ID。
+4. [创建群组](/product/console/operation_group.html#创建群组),获取群组 ID,将用户加入群组。
+5. [开通音视频服务](product_activation.html)。为了保障流畅的用户体验,开通服务后,你需等待 15 分钟才能跑通示例项目。
+
+## 操作步骤
+
+### 步骤 1 配置项目
+
+1. 克隆或下载项目。
+
+```bash
+git clone https://github.com/easemob/easemob-uikit-react.git
+cd easemob-uikit-react
+```
+
+2. 安装依赖。
+
+```bash
+npm install
+# 或
+yarn install
+```
+
+3. 启动开发服务器。
+
+```bash
+npm run dev
+# 或
+yarn dev
+```
+
+### 步骤 2 运行项目
+
+打开浏览器访问 `http://localhost:5173/demo/callkit/call-demo.html`,确认项目正常运行。
+
+### 步骤 3 开始通话
+
+1. 填写 App Key、用户 ID 和密码,点击 **登录**,等待登录成功提示。
+
+
+
+2. 输入被叫用户 ID(一对一通话)或群组 ID(群组通话),点击 **完成配置**。
+
+
+
+3. (可选)点击 **选择背景** 在背景选择面板中选择喜欢的通话背景图片。
+
+
+
+4. 点击 **发起视频通话** 或 **发起语音通话** 发起一对一通话,或者点击 **发起群组视频通话**,选择要邀请的成员,发起群组视频通话。
+
+
+
+5. 在浏览器弹出的权限请求中,允许访问摄像头和麦克风。
+6. 在通话中可以控制静音、摄像头、扬声器等,点击 **结束通话** 挂断。
diff --git a/docs/callkit/web/signaling.md b/docs/callkit/web/signaling.md
new file mode 100644
index 000000000..bfd1931e4
--- /dev/null
+++ b/docs/callkit/web/signaling.md
@@ -0,0 +1,7 @@
+---
+{
+ pageUri: "/callkit/ios/signaling.html",
+ title: "音视频通话信令交互逻辑
+"
+}
+---
\ No newline at end of file
diff --git a/docs/product/console/account_center.md b/docs/product/console/account_center.md
index de1102321..5a426cb4a 100644
--- a/docs/product/console/account_center.md
+++ b/docs/product/console/account_center.md
@@ -20,17 +20,17 @@
| 服务中 | 表示当前订单中购买的服务在使用中。同一 App Key 同时仅能有 1 个服务版本的订单状态为 **服务中**。 |
| 已完结 | 表示当前订单中购买的服务已关闭。当客户升级新服务后,原服务版本订单和相关增值服务订单均会自动关闭,变为 **已完结**状态。 |
-2. 点击订单的 **操作** 栏中的 **详情**,可查看订单详情。
+2. 点击订单的 **操作** 栏中的 **详情**,可查看订单详情。下图以即时通讯 IM 的订单为例进行介绍。

## 查看消费账单
-1. 选择 **账单中心** > **消费账单**,点击 **IM扣费账单**,查看你当前的账单记录。
+1. 选择 **账单中心** > **消费账单**,查看你当前的账单记录。

-2. 点击消费账单的 **操作** 栏中的 **账单详情**,查看账单详情。
+2. 点击消费账单的 **操作** 栏中的 **账单详情**,查看账单详情。下图以即时通讯 IM 的实时消息人工审核服务的订单为例进行介绍。

diff --git a/docs/product/console/operation_data.md b/docs/product/console/operation_data.md
index c99425d6e..e64eecb03 100644
--- a/docs/product/console/operation_data.md
+++ b/docs/product/console/operation_data.md
@@ -45,3 +45,17 @@
点击右侧的 **详情** 可查看各类型消息的统计情况。

+
+## 实时音视频服务
+
+成功开通音视频服务后,你可以查看应用的音视频服务用量。
+
+- 选择统计时间范围,查看当前项目在指定时间段内的音视频服务总用量。
+
+
+
+- 选择统计时间范围,查看当前项目的各类产品在指定时间段内的音视频服务用量。
+
+
+
+- 点击 **导出数据** 导出实时音视频服务的用量统计。
diff --git a/docs/product/console/purchase_value_added.md b/docs/product/console/purchase_value_added.md
index b27b0e263..092dd9bf9 100644
--- a/docs/product/console/purchase_value_added.md
+++ b/docs/product/console/purchase_value_added.md
@@ -1,6 +1,6 @@
# 开通增值服务
-即时通讯 IM 的增值服务包括内容审核、即时推送、消息翻译和服务端 API 调用。你可以在 [环信控制台](https://console.easemob.com/user/login) 订阅增值服务。
+即时通讯 IM 的增值服务包括内容审核、即时推送、消息翻译、服务端 API 调用和实时音视频服务。你可以在 [环信控制台](https://console.easemob.com/user/login) 订阅增值服务。
按如下步骤打开 **增值服务** 页面:
@@ -60,6 +60,14 @@

+## 实时音视频服务
+
+使用环信音视频通话 CallKit 之前,你需要开通实时音视频服务。
+
+选择 **实时音视频服务** 页签,点击 **立即开通** 开通实时音视频服务。开通后,你可以订阅 RTC 服务的套餐或加油包。
+
+
+