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 @@ + + + + + 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 的整体架构采用模块化设计,各个模块职责清晰,便于维护和扩展。 + +![img](/images/callkit/android/architecture.png) + +### 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 界面如下: + +![img](/images/android/sendcall.png) + +### 被叫收到通话邀请 + +主叫方发起邀请后,如果被叫方在线且当前不在通话中,会弹出邀请通话界面,被叫可以选择接听或者拒绝。 + +被叫收到邀请后会触发 `EaseCallKitListener` 中的 `onRevivedCall` 回调: + +```java +/** + * 收到通话邀请回调。 + * @param callType 通话类型。 + * @param userId 邀请方的用户 ID。 + * @param ext 自定义扩展字段,描述通话扩展信息。 + */ +void onRevivedCall(EaseCallType callType, String userId,String ext){} +``` + +收到通话邀请后的界面如下: + +![img](/images/android/called.jpeg) + +### 多人通话中邀请 + +多人通话中,当前用户可以点击通话界面右上角的邀请按钮再次向其他用户发起邀请。这种情况下,会触发 `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. 点击 **立即开通**。 + +![img](/images/callkit/product/rtc_activation.png) + +## 开通微信中集成音视频通话服务 + +你可在微信中集成声网 RTC 的 MiniAppSDK,实现轻量级实时音视频功能。**注意该服务开通后不能关闭。** + +1. 登录 [环信控制台](https://console.easemob.com/user/login)。 +2. 选择页面上方的 **应用管理**。在弹出的应用列表页面,单击你的应用的 **操作** 栏中的 **管理**。 +3. 在左侧导航栏,选择 **功能配置** > **增值服务** > **实时音视频**。 +4. 点击 **立即开通**。 + +![img](/images/callkit/product/rtc_plus_activation.png) + + + + + 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. 点击 **升级套餐**。 + +![img](/images/callkit/product/rtc_package_upgrade.png) + +5. 选择 RTC 套餐版本,点击 **立即购买**。 + +![img](/images/callkit/product/rtc_package_purchase.png) + +6. 确认账单信息,点击 **下一步**。在弹出的 **付款确认** 对话框中,确认付款信息后,阅读并同意《环信云服务购买协议》,点击 **立即支付**。 + +![img](/images/callkit/product/rtc_billing_confirm.png) + +7. 购买套餐后,可在 **实时音视频服务** 页面查看你的套餐包容量。 + +![img](/images/callkit/product/rtc_package_view.png) + +## 订阅/升级加油包 + +加油包支持按时长购买,购买后立即生效,并可抵扣本月 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. 点击 **服务总览** 栏中的 **购买加油包**。 + +![img](/images/callkit/product/rtc_plus_purchase.png) + +5. 确认加油包信息,点击 **下一步**。在弹出的 **付款确认** 对话框中,确认付款信息后,阅读并同意《环信云服务购买协议》,点击 **立即支付**。 + +![img](/images/callkit/product/rtc_plus_confirm.png) + +6. 购买套餐包后,你可以查看当前音视频服务的总量。 + +![img](/images/callkit/product/rtc_capacity_total.png) + +## 续费套餐包 + +所有版本默认自动续订。如需取消续订,请将套餐切换为免费版。 + +## 套餐降级 + +套餐包每自然月仅可降级一次,请谨慎操作,如需撤回或修改,请联系商务。 + +**套餐降级次月 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +::: + +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); +``` + +下图为发送一对一视频通话邀请后的用户界面示例: + +![img](/images/web/callkit_single_invite.png) + +### 收到通话邀请 + +通话邀请发送后,如果被叫方在线且可以通话,将通过 `onInvite` 回调收到邀请。你可以弹出一个用户界面,让被叫方在该回调中接受或拒绝邀请。 + +```javascript +/** + * 处理通话邀请。 + * + * @param result 是否弹出用户界面,接听来电: + * - true:是。 + * - false:否。这种情况下,你无需传入 token 。 + * @param accessToken 声网 token 。 + */ +CallKit.answerCall(result, accessToken); +``` + +下图为收到一对一视频通话邀请后的用户界面示例: + +![img](/images/web/callkit_single_receive.png) + +### 多人通话中间发起邀请 + +在多人通话中,多个用户还可以向其他用户发送通话邀请。发送邀请后,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 的订单为例进行介绍。 ![img](/images/console/billing_order_detail.png) ## 查看消费账单 -1. 选择 **账单中心** > **消费账单**,点击 **IM扣费账单**,查看你当前的账单记录。 +1. 选择 **账单中心** > **消费账单**,查看你当前的账单记录。 ![img](/images/console/billing_billing_list.png) -2. 点击消费账单的 **操作** 栏中的 **账单详情**,查看账单详情。 +2. 点击消费账单的 **操作** 栏中的 **账单详情**,查看账单详情。下图以即时通讯 IM 的实时消息人工审核服务的订单为例进行介绍。 ![img](/images/console/billing_bill_detail.png) 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 @@ 点击右侧的 **详情** 可查看各类型消息的统计情况。 ![img](/images/console/operation_data_hot_statistics.png) + +## 实时音视频服务 + +成功开通音视频服务后,你可以查看应用的音视频服务用量。 + +- 选择统计时间范围,查看当前项目在指定时间段内的音视频服务总用量。 + +![img](/images/console/operation_data_rtc_usage_all.png) + +- 选择统计时间范围,查看当前项目的各类产品在指定时间段内的音视频服务用量。 + +![img](/images/console/operation_data_rtc_usage_specific.png) + +- 点击 **导出数据** 导出实时音视频服务的用量统计。 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 @@ ![img](/images/console/server_api_raiselimit.png) +## 实时音视频服务 + +使用环信音视频通话 CallKit 之前,你需要开通实时音视频服务。 + +选择 **实时音视频服务** 页签,点击 **立即开通** 开通实时音视频服务。开通后,你可以订阅 RTC 服务的套餐或加油包。 + +![img](/images/callkit/product/rtc_activation.png) +