コールのライフサイクル

ライフサイクルコールバックを使用して、通話の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 エヌシーシーオー この動作は会話単位で有効です。通話を終了しても、その参加者の「足」だけが共有ルームから削除されるだけです。他の参加者は、各自で退出するまで接続されたままとなります。