You need to enable JavaScript to run this app.
导航
通话打断和恢复
最近更新时间:2024.06.24 11:28:07首次发布时间:2022.05.06 14:47:11

用户在进行实时音视频通话的过程中,可能会被系统电话,第三方应用,或其他系统行为打断音视频采集,大部分情况下,RTC 将在打断结束时,自动恢复音视频采集,详见下表。你需要根据实际业务场景,考虑在不同打断事件中,是否同步音视频的采集状态,以及如何维护远端音视频订阅状态。
例如,在 1 v 1 通话场景中,本端用户在 RTC 通话接听了系统电话,预期 RTC 的音频和视频均暂停采集,远端音视频流暂停播放。但此时只有音频会暂停,视频仍然正常采集和发布。因此,你需要在收到暂停状态回调时,调用相应接口,停止视频采集、取消远端音视频流订阅。并在收到自动恢复回调后,重新开启视频采集、订阅远端音视频流。

音视频采集打断和自动恢复

在不同的打断事件中,RTC SDK 的音视频采集状态如下表。

打断事件AndroidiOSWindows/macOS
系统电话
  • 音频:暂停,通话结束后自动恢复
  • 视频:正常
  • 音频:暂停,通话结束后自动恢复
  • 视频:被打断
  • 不适用
    第三方应用 (占用音视频采集设备)音视频:暂停,占用结束后自动恢复音视频:暂停,占用结束后自动恢复由操作系统决定,例如设备被抢占导致 RTC 采集不到数据,将回调相应状态
    锁屏参见 锁屏后继续采集音视频音视频:正常音视频:正常
    PC 合盖(系统未休眠)不适用不适用
  • 音频:正常
  • 视频:正常,但合盖后摄像头采集不到人像,用户也看不到远端画面
  • iOS 应用处于侧拉、分屏或者画中画模式
  • iOS 系统性能不足(如设备过热)
  • iOS 应用切换到后台
  • 不适用视频:被打断,将回调设备采集状态,打断结束后自动恢复不适用

    默认情况下,SDK 会监听 Android 系统电话事件,在系统通话期间关闭音频模块。确保 app 中没有包含拦截系统电话事件( CALL_STATE_IDLECALL_STATE_OFFHOOK )的业务逻辑,否则 RTC 将无法感知系统电话,可能造成用户通话时仍继续发送音视频流。

    管理采集打断期间的音视频采集和订阅

    不同打断事件中,你需要关注音视频采集打断和自动恢复状态,以便同时控制视频采集、维护远端音视频流的订阅关系。
    通过监听 onAudioDeviceStateChangedonVideoDeviceStateChanged 回调,了解采集设备被打断和自动恢复的情况。

    自 3.38.1 开始,RTC SDK 新增以下枚举值,反映当前打断状态。

    • InterruptionBegan : 被打断。Android 目前无法监听音频采集被第三方应用打断/恢复的事件,可以监听被打断后的静音采集错误

    • InterruptionEnded : 已恢复

    1. 在 RTC 通话被打断时,调用 pauseAllSubscribedStream 暂停接收所有远端用户。 按照上方表格,指定暂停接收的媒体流类型为音频或视频。调用 stopVideoCapture 同时暂停视频采集。

    2. 在 RTC 通话从打断中恢复时,调用 resumeAllSubscribedStream 恢复订阅所有远端用户。监听以下回调,了解音视频的采集状态。调用 startVideoCapture 同时开启视频采集。

      1. onAudioDeviceStateChangedonVideoDeviceStateChanged

        • state: RuntimeError

        • error: DeviceNoCallback

      2. onAudioDeviceWarningonVideoDeviceWarning

        • warning: CaptureSilence

    当从以上回调监听到采集异常时,你可以尝试通过再次调用 startVideoCapturestartAudioCapture 来进行恢复。

    Android 9 及以上实现锁屏后继续采集音视频

    由于 Android 9 以上版本的 系统限制 ,部分 Android 手机在锁屏后采集音视频失败,解锁后音频采集自动恢复。如果你希望锁屏后仍能采集音频或视频的,建议在按照以下步骤,在合适时机启用一个前台 service。

    1. AndroidManifest.xml 中引入前台服务权限和 service。

      <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
      
      <service
          android:name="com.ss.demo.service.RoomKeepLifeService"
      <!-- 将 microphone 修改为 `camera` 实现在锁屏后继续采集视频。-->
          android:foregroundServiceType="microphone"
          tools:node="merge" />
      
    2. 继承系统 Service,并重载相关的函数,实现 service 的启动和停止。

      package com.ss.demo.service;
      
      import android.app.Notification;
      import android.app.NotificationChannel;
      import android.app.NotificationManager;
      import android.app.PendingIntent;
      import android.app.Service;
      import android.content.Intent;
      import android.content.pm.ServiceInfo;
      import android.os.Build;
      import android.os.IBinder;
      import android.support.annotation.Nullable;
      import android.support.v4.app.NotificationCompat;
      
      import com.ss.video.rtc.demo.ui.chat.ChatActivity;
      
      public class RoomKeepLifeService extends Service {
          public static final String CHANNEL_ID = "RoomKeepLifeServiceChannel";
          private static final String COMMAND = "command";
          private static final String COMMAND_START = "start";
          private static final String COMMAND_STOP = "stop";
      
          @Nullable
          @Override
          public IBinder onBind(Intent intent) {
              return null;
          }
      
          @Override
          public void onCreate() {
              super.onCreate();
              startAsForeground(new Intent());
          }
      
          @Override
          public int onStartCommand(Intent intent, int flags, int startId) {
              if(intent != null){
                  switch (intent.getStringExtra(COMMAND)){
                      case COMMAND_START:
                          startAsForeground(intent);
                          break;
                      case COMMAND_STOP:
                          stopInternal();
                          break;
                      default:
                          break;
                  }
              }
      
              return super.onStartCommand(intent, flags, startId);
          }
      
          public void stopInternal() {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                  stopForeground(true);
                  stopSelf();
              }
          }
      
          private void startAsForeground(Intent intent) {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                  createNotificationChannel();
                  Intent notificationIntent = new Intent(this, ChatActivity.class);
                  PendingIntent pendingIntent = PendingIntent.getActivity(this,
                          0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
      
                  Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                          .setContentTitle("房间正在进行中...")
                          .setSmallIcon(com.ss.video.rtc.common.R.drawable.icon_default_avatar)
                          .setContentIntent(pendingIntent)
                          .setShowWhen(false)
                          .build();
      
                  startForeground(110, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
              }
          }
      
          private void createNotificationChannel() {
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                  NotificationChannel serviceChannel = new NotificationChannel(
                          CHANNEL_ID,
                          "Foreground Service Channel",
                          NotificationManager.IMPORTANCE_DEFAULT
                  );
      
                  NotificationManager manager = getSystemService(NotificationManager.class);
                  if (manager != null) {
                      manager.createNotificationChannel(serviceChannel);
                  }
              }
          }
      }
      
    3. 根据业务需要,在合适的时机启动和停止 service。以下示例以进房启动,退房停止为例。

      public void joinRoom() {
          // -- 启动服务 -- //
          startRoomKeepLifeService();
          
          // -- 进房相关操作 -- //
          // ...
      }
      
      public void leaveRoom() {
          // -- 退房相关操作 -- //
          // ...
          
          // -- 停止服务 -- //
          stopRoomKeepLifeService();
      }
      
      private void startRoomKeepLifeService() {
          if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
          // EnvUtilities.getAppContext() 为获取 context 的方法示例,你需要根据项目实际情况进行替换
              Intent serviceIntent = new Intent(EnvUtilities.getAppContext(), RoomKeepLifeService.class);
              serviceIntent.putExtra("command", "start");
              ContextCompat.startForegroundService(EnvUtilities.getAppContext(), serviceIntent);
          }
      }
      
      private void stopRoomKeepLifeService(){
          if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
          // EnvUtilities.getAppContext() 为获取 context 的方法示例,你需要根据项目实际情况进行替换
              Intent serviceIntent = new Intent(EnvUtilities.getAppContext(), RoomKeepLifeService.class);
              serviceIntent.putExtra("command", "stop");
              ContextCompat.startForegroundService(EnvUtilities.getAppContext(), serviceIntent);
          }
      }
      

    API 参考