הלקוח שלך לא יכול לענות לשיחה בזמן שהטלפון נעול. או שיש הד שמפריע לשני הצדדים. לפעמים השיחה נופלת כשמגיע SMS. באגים אלה הם תוצאות אופייניות של VoIP משולב בצורה גרועה. אנחנו פותרים אותם ברמת הארכיטקטורה, תוך ניצול ניסיון בטלפורפואה, לוגיסטיקה ותקשורת ארגונית.
יישום שיחות VoIP באפליקציה לנייד אינו משימה אחת — אלא כמה שכבות טכניות מחוברות: פרוטוקול איתות, הובלת מדיה, ניהול סשן שמע, ואינטגרציה עם ממשקי API של שיחות מערכת. דילוג או פישוט של כל אחת מהשכבות האלה פירושו שיחה שעובדת בהדגמה אבל נשברת בייצור: הד, אובדן שמע בהודעת התראה נכנסת, חוסר יכולת לענות לשיחה ממסך הנעילה.
איך CallKit עובד
ב-iOS, VoIP ללא CallKit אפשרי טכנית, אבל האפליקציה לא תקבל סשן שמע לשיחה נכנסת כשהמסך נעול, המערכת לא תציג את מסך השיחה הנכנסת המערכתי, ומאז iOS 13, אפליקציות ללא CallKit לא יקבלו הודעות PushKit ל-VoIP. CallKit הוא framework מערכתי שמציג את מסך השיחה הנכנסת המערכתי, מנהל סשנים של שמע, תומך ב-Bluetooth/AirPods, ומציג שיחות בהיסטוריית השיחות.
import CallKit
class CallManager: NSObject {
let provider: CXProvider
let callController = CXCallController()
init() {
let config = CXProviderConfiguration()
config.supportsVideo = false
config.maximumCallsPerCallGroup = 1
config.supportedHandleTypes = [.phoneNumber, .emailAddress]
provider = CXProvider(configuration: config)
super.init()
provider.setDelegate(self, queue: nil)
}
func reportIncomingCall(uuid: UUID, callerName: String) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: callerName)
update.hasVideo = false
provider.reportNewIncomingCall(with: uuid, update: update) { error in
// начинаем отвечать на звонок после разрешения системы
}
}
}PushKit לשיחות נכנסות ברקע. בניגוד ל-APNs רגיל, PushKit מעיר את האפליקציה מיד עם עדיפות גבוהה. אבל מאז iOS 13, אפל דורשת לקרוא מיד ל-import CallKit class CallManager: NSObject { let provider: CXProvider let callController = CXCallController() init() { let config = CXProviderConfiguration() config.supportsVideo = false config.maximumCallsPerCallGroup = 1 config.supportedHandleTypes = [.phoneNumber, .emailAddress] provider = CXProvider(configuration: config) super.init() provider.setDelegate(self, queue: nil) } func reportIncomingCall(uuid: UUID, callerName: String) { let update = CXCallUpdate() update.remoteHandle = CXHandle(type: .generic, value: callerName) update.hasVideo = false provider.reportNewIncomingCall(with: uuid, update: update) { error in // начинаем отвечать на звонок после разрешения системы } } } עם קבלת push של VoIP — אחרת האפליקציה קורסת. אין בקשת רשת לפני CallKit.
אנדרואיד: ConnectionService ו-Telecom API
המקבילה האנדרואידית ל-CallKit היא ConnectionService מהחבילה reportNewIncomingCall. היא מאפשרת לאפליקציה להפוך ל"חשבון טלפון" במערכת, להציג שיחות על מסך הנעילה, ולנהל ניתוב שמע. לשיחות נכנסות ברקע — push של FCM עם android.telecom. באנדרואיד 14+, שירותי רקע מוגבלים, אבל priority: high מסוג ForegroundService (נוסף באנדרואיד 14) פותר בדיוק את המשימה הזו — ללא הגבלות הפעלה לשיחות נכנסות.
ניהול סשן שמע דרך phoneCall:
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager.isSpeakerphoneOn = false
// при завершении:
audioManager.mode = AudioManager.MODE_NORMALאוזניות Bluetooth הן כאב ראש נפרד. פרופיל AudioManager, חיבור val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.mode = AudioManager.MODE_IN_COMMUNICATION audioManager.isSpeakerphoneOn = false // при завершении: audioManager.mode = AudioManager.MODE_NORMAL לקול (לא A2DP!), BluetoothHeadset עבור SCO. ללא ניהול SCO מפורש, השמע עובר דרך הרמקול גם כשמחוברת אוזניית Bluetooth.
למה שרת TURN חשוב
אם אתה משתמש ב-WebRTC לבד (ולא דרך Twilio/Vonage), אתה צריך שרת TURN למעבר NAT. בלעדיו, שיחות עובדות רק בתוך רשת אחת — באג הדגמה קלאסי שנשבר כשהלקוח נמצא מאחורי NAT ארגוני. שרת TURN גם עוזר עם NAT סימטרי שבו STUN רגיל לא עובד.
BroadcastReceiver הוא שרת TURN בקוד פתוח, שניתן לפרוס על VPS תוך כמה שעות. הגדרת ICE:
// Android WebRTC SDK
val iceServers = listOf(
PeerConnection.IceServer.builder("stun:stun.example.com:3478").createIceServer(),
PeerConnection.IceServer.builder("turn:turn.example.com:3478")
.setUsername("user")
.setPassword("password")
.createIceServer()
)קודקים: Opus לשמע (קצב סיביות אדפטיבי 6–510 kbps, עובד היטב עם עד 20% אובדן מנות). ערכת ה-SDK של WebRTC כוללת אותו כברירת מחדל.
פרטי בחירת קודק שמע
Opus — קודק אדפטיבי עם קצב סיביות מ-6 עד 510 kbps. תומך בסטריאו ושמע רב-ערוצי. לתאימות עם טלפוני SIP, ייתכן שיהיה צורך ב-G.711 (PCMA/PCMU) או G.722. בבחירת קודק, שקול תמיכת לקוח, רוחב פס ודרישות איכות.השוואת גישות: SDK מנוהל מול WebRTC
| פרמטר | SDK מנוהל (Twilio, Agora) | WebRTC + TURN |
|---|---|---|
| מורכבות פיתוח | נמוכה | בינונית |
| עלות לדקה | ~$0.004–0.01 | ~$0.001 (TURN עצמי) |
| שליטה | מוגבלת | מלאה |
| חביון | תלוי ב-SDK | מינימלי (<100 ms) |
| משאבים נדרשים | אין | שרת TURN |
SDKs מנוהלים פשוטים יותר, אבל WebRTC יכול לחסוך עד 80% בעלויות שיחות עם תשתית TURN עצמית. בנפח גבוה (מעל 10,000 דקות בחודש), ההבדל הופך למשמעותי.
טעויות נפוצות ופתרונות
| טעות | פתרון |
|---|---|
| הד | הגדר ACTION_SCO_AUDIO_STATE_UPDATED בשתי הפלטפורמות + AEC (ביטול הד אקוסטי) |
| שיחה לא מגיעה למסך הנעילה | שלב CallKit/ConnectionService |
| חביון >300 ms | הגדר P2P ICE, TURN רק כגיבוי |
| הפרעה ב-SMS | טפל ב-coturn (iOS) וב-// Android WebRTC SDK val iceServers = listOf( PeerConnection.IceServer.builder("stun:stun.example.com:3478").createIceServer(), PeerConnection.IceServer.builder("turn:turn.example.com:3478") .setUsername("user") .setPassword("password") .createIceServer() ) (אנדרואיד) |
| שמע Bluetooth לא עובר | ניהול SCO מפורש דרך MODE_IN_COMMUNICATION |
מה כלול בעבודה
- ניתוח דרישות ובחירת מחסנית אופטימלית (WebRTC, Twilio, Agora, או SIP מותאם).
- עיצוב ערוץ איתות (WebSocket, SIP, או פרוטוקול מותאם).
- פריסת שרת TURN (coturn) והגדרת מועמדי ICE.
- אינטגרציה של CallKit (iOS) ו-ConnectionService (אנדרואיד) עם PushKit/FCM.
- בדיקות על 20+ מכשירים אמיתיים עם גרסאות מערכת הפעלה ותנאי רשת שונים.
- אופטימיזציה של איכות שמע: AEC, jitter buffer אדפטיבי, הסתרת אובדן מנות.
- תיעוד והדרכת צוות הלקוח.
- תמיכה לאחר השקה (ניטור, תיקוני באגים, שיפורים).
תהליך פיתוח שיחות VoIP
- ניתוח דרישות ובחירת מחסנית (SDK מנוהל מול WebRTC).
- עיצוב ערוץ איתות ומדיה.
- פריסת שרת TURN והגדרת ICE.
- אינטגרציה של CallKit/ConnectionService ו-PushKit/FCM.
- בדיקות על מכשירים אמיתיים עם תנאי רשת שונים.
- אופטימיזציה של איכות שמע והסרת פגמים.
- פריסה וניטור.
צור קשר כדי לדון בארכיטקטורת פתרון ה-VoIP שלך. הזמן פיתוח ואנחנו מבטיחים שיחות יציבות ללא הד או נפילות. קבל ייעוץ מהמהנדס שלנו — אנחנו מוכנים לעזור.







