コールのライフサイクル
ライフサイクルコールバックを使用して、通話のUIをリアルタイムの通話進行状況と同期させます。以下の図は、発信と着信という2つの主な視点を示しています。
発信(発信者視点)
アプリが呼び出すと serverCall(), 呼び出し先側は、によって報告されるステータス更新を経て処理が進みます onLegStatusUpdate.
着信(着信側の視点)
アプリが通話招待を受信すると、そのローカル通話は招待処理を経て、アクティブな通話へと移行します。
コールバックのソース
onLegStatusUpdate呼び出し先のレッグの進行状況を報告します:Ringing,Answered,Completed. これを使用して、発信時のUIを更新します(例:「呼び出し中...」と表示した後、「接続されました」と表示するなど)。onCallInvite/onCallInviteCancel受信招待のライフサイクルを処理します。詳細については、以下を参照してください。 着信コールを受ける キャンセル理由の詳細については。onCallHangup発信・着信を問わず、何らかの理由で通話が終了した際に発火します。必ずこれを最終的なクリーンアップのトリガーとして使用してください。onCallMediaDisconnect/onCallMediaReconnecting/onCallMediaReconnectionメディア層の中断を報告します。詳しくは 再接続 詳細はこちら。
ステータスの更新
onLegStatusUpdate 着信側の状態の推移(例:着信中 → 応答 → 通話完了)を提供します。これは、着信側の状態をUIに反映させたい発信通話において、特に有用です。
client.on('legStatusUpdate', (callId, legId, legStatus) => {
if (legStatus === LegStatus.Answered) {
console.log(`Call ${callId} was answered by ${legId}`);
} else if (legStatus === LegStatus.Completed) {
console.log(`Call ${callId} was completed`);
} else if (legStatus === LegStatus.Ringing) {
console.log(`Call ${callId} is ringing`);
}
});
client.setOnLegStatusUpdate { call, legId, status ->
when(status) {
LegStatus.ringing -> println("Leg $legId for call $call is ringing")
LegStatus.answered -> println("Leg $legId for call $call was answered")
LegStatus.completed -> println("Leg $legId for call $call was completed")
}
}
client.setOnLegStatusUpdate((call, legId, status) -> {
switch (status) {
case ringing:
System.out.println("Leg " + legId + " for call " + call + " is ringing");
break;
case answered:
System.out.println("Leg " + legId + " for call " + call + " was answered");
break;
case completed:
System.out.println("Leg " + legId + " for call " + call + " was completed");
break;
}
return null;
});
class CallStatusExampleDelegate: NSObject, VGVoiceClientDelegate {
func voiceClient(_ client: VGVoiceClient, didReceiveLegStatusUpdateForCall callId: VGCallId, withLegId legId: VGCallId, andStatus status: VGLegStatus) {
switch(status){
case .ringing:
print("Leg \(legId) is ringing")
case .answered:
print("Leg \(legId) has answered")
case .completed:
print("Leg \(legId) has completed")
default:
print("Leg has entered the Unknown")
}
}
func voiceClient(_ client: VGVoiceClient, didReceiveInviteForCall callId: VGCallId, from caller: String, with type: VGVoiceChannelType) {}
func voiceClient(_ client: VGVoiceClient, didReceiveHangupForCall callId: VGCallId, withQuality callQuality: VGRTCQuality, reason: VGHangupReason) {}
func voiceClient(_ client: VGVoiceClient, didReceiveInviteCancelForCall callId: VGCallId, with reason: VGVoiceInviteCancelReason) {}
func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {}
}
- (void) voiceClient:(VGVoiceClient *)client didReceiveLegStatusUpdateForCall:(VGCallId)callId withLegId:(NSString *)legId andStatus:(VGLegStatus)status {
switch (status) {
case VGLegStatusRinging:
NSLog(@"Leg %@ is ringing", legId);
break;
case VGLegStatusAnswered:
NSLog(@"Leg %@ has answered", legId);
break;
case VGLegStatusCompleted:
NSLog(@"Leg %@ has completed", legId);
break;
case VGLegStatusUnknown:
NSLog(@"Leg has entered the Unknown");
break;
}
}
発信・通話終了
用途 hangup 通話を終了するには。
const callId = 'callId';
await client.hangup(callId);
// Callback
client.hangup("callId") {
it?.let { err ->
// Handle Error hanging up call
println("Error hanging up call: $err")
}
}
// Coroutine
try {
client.hangup("callId")
} catch (e: Error) {
// Handle Error hanging up call
}
client.hangup("callId", err -> {
if (err != null) {
// Handle Error hanging up call
System.out.println("Error hanging up call: " + err);
}
return null;
});
// Callback
client.hangup("someCallId") { error in
guard let error = error else {
print("Call hangup successful")
return
}
print("Failed to hangup \(error.localizedDescription)")
}
// Async Await
Task.detached {
do {
// Simple Case
try await client.hangup("someCallId")
// Fully Parameter list
try await client.hangup("someCallId", reason: "reason-text", code: "reason-code")
}
catch {
print("Failed to say hangup' \(error.localizedDescription)")
}
}
[client hangup:@"someCallid" callback:^(NSError * _Nullable error) {
if(error){
NSLog(@"Failed to hangup: %@", error);
}
}];
通話が切断された場合のエラー処理
hangup() 通話がすでに終了している場合、セッションの有効期限が切れている場合、またはネットワークに問題がある場合、この呼び出しは失敗する可能性があります。どのような場合でも、UIが「終了」状態に確実に遷移するように、常にエラー処理を行ってください:
try {
await client.hangup(callId);
} catch (error) {
console.error("Hangup error:", error.type, error.message);
} finally {
// Always clean up — if hangup failed locally, the server will
// eventually terminate the call via mediaTimeout
showCallEndedUI();
}
client.hangup(callId) { error ->
if (error != null) {
// Hangup request failed — but the call may still end via server-side timeout.
// Clean up local UI anyway to avoid a stuck call screen.
println("Hangup error: ${error.message}")
}
// Always transition to end-of-call UI
showCallEndedUI()
}
client.hangup(callId) { error in
if let error = error {
print("Hangup error: \(error.localizedDescription)")
}
self.showCallEndedUI()
}
たとえ……であっても hangup() 失敗した場合は、必ずローカルの通話状態とUIをクリーンアップしてください。クライアント側での通話終了がサーバーに届かなかった場合、サーバーはタイムアウトにより独自に通話を終了します。待機中 onCallHangup 二次的な確認を行うことは、良い慣行です。
ハングアップイベントの受信
用途 onCallHangup 最終的なコールの終了処理とUIのクリーンアップを行うため。
client.on(
'callHangup',
(callId: string, callQuality: RTCQuality, reason: HangupReason) => {
if (reason == 'LOCAL_HANGUP') {
console.log(`Call ${callId} was hung up locally`);
} else if (reason == 'REMOTE_HANGUP') {
console.log(`Call ${callId} was hung up remotely`);
} else if (reason == 'REMOTE_REJECT') {
console.log(`Call ${callId} was rejected remotely`);
} else if (reason == 'REMOTE_NO_ANSWER_TIMEOUT') {
console.log(`Call ${callId} timed out`);
} else if (reason == 'MEDIA_TIMEOUT') {
console.log(`Call ${callId} timed out`);
} else {
exhaustiveCheck(reason);
}
}
);
client.setOnCallHangupListener { call, callQuality, reason ->
println("Received $reason callQuality: $callQuality for Call $call")
}
client.setOnCallHangupListener((call, legId, reason) -> {
System.out.println("Leg " + legId + " for call " + call + " was hung up for reason " + reason);
return null;
});
class HangupExampleDelegate: NSObject, VGVoiceClientDelegate {
func voiceClient(_ client: VGVoiceClient, didReceiveHangupForCall callId: VGCallId, withQuality callQuality: VGRTCQuality, reason: VGHangupReason) {
print("call: \(callId) has finished")
}
func voiceClient(_ client: VGVoiceClient, didReceiveInviteForCall callId: VGCallId, from caller: String, with type: VGVoiceChannelType) {}
func voiceClient(_ client: VGVoiceClient, didReceiveInviteCancelForCall callId: VGCallId, with reason: VGVoiceInviteCancelReason) {}
func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {}
}
- (void)voiceClient:(VGVoiceClient *)client didReceiveHangupForCall:(VGCallId)callId withQuality:(VGRTCQuality *)callQuality andReason:(VGHangupReason)reason {
NSLog(@"Call %@ has finished with reason: %ld and quality: %@", callId, reason, callQuality);
}
ライフサイクルイベントのデバッグ
コールイベントが期待どおりに到着しない場合は、開発中に詳細ログを有効にして、ライフサイクルの遷移を追跡してください。詳しくは ログ記録と診断 カスタムロガーやトピックフィルタリングなど、すべての設定オプションについては、こちらをご覧ください。
NCCOのハングアップ動作
connectエヌシーシーオー 標準的な2者間通話と同様に動作します。一方の参加者が電話を切ると、ポイントツーポイント接続が終了し、両方の回線が切断されます。conversationエヌシーシーオー この動作は会話単位で有効です。通話を終了しても、その参加者の「足」だけが共有ルームから削除されるだけです。他の参加者は、各自で退出するまで接続されたままとなります。