解决Agora声网音视频在后台没有声音的问题

前言:本文会介绍 Android 与 iOS 两个平台的处理方式

一、Android高版本在应用退到后台时,系统为了省电会限制应用的后台活动,因此我们需要开启一个前台服务,在前台服务中发送常驻任务栏通知,以此来保证App 退到后台时不会被限制活动.

前台服务代码如下:

java 复制代码
package com.notify.test.service;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;

import com.notify.test.R;

import androidx.annotation.Nullable;

/**
 * desc:解决声网音视频锁屏后听不到声音的问题
 * (可以配合Application.ActivityLifecycleCallbacks使用)
 *
 * Created by booyoung
 * on 2023/9/8 14:46
 */
public class KeepAppLifeService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private final String notificationId = "app_keep_live";
    private final String notificationName = "audio_and_video_call";

    @Override
    public void onCreate() {
        super.onCreate();
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //创建NotificationChannel
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(notificationId, notificationName, NotificationManager.IMPORTANCE_HIGH);
            //不震动
            channel.enableVibration(false);
            //静音
            channel.setSound(null, null);
            notificationManager.createNotificationChannel(channel);
        }
        startForeground(1, getNotification());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        //stop service
        this.stopSelf();
    }

    /**
     * 获取通知(Android8.0后需要)
     * @return
     */
    private Notification getNotification() {
        Notification.Builder builder = new Notification.Builder(this)
                .setSmallIcon(R.mipmap.logo)
                .setOngoing(true)
                .setContentTitle("App名称")
                .setContentIntent(getIntent())
                .setContentText("音视频通话中,轻击以继续");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            builder.setChannelId(notificationId);
        }
        return builder.build();
    }

    /**
     * 点击后,直接打开app
     * @return
     */
    private PendingIntent getIntent() {
        //获取启动Activity
        Intent msgIntent = getApplicationContext().getPackageManager().getLaunchIntentForPackage(getPackageName());
        PendingIntent pendingIntent = PendingIntent.getActivity(
                getApplicationContext(),
                1,
                msgIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        return pendingIntent;
    }
}

不要忘记在AndroidManifest.xml中声明Service哈

XML 复制代码
 <service android:name=".service.KeepAppLifeService"
      android:enabled="true"
      android:exported="false"
      android:stopWithTask="true" />

然后接下来就需要在声网音视频接通与挂断分别开启与关闭前台服务了,此处回调使用了EaseCallKit的写法,如果没使用EaseCallKit UI库的可以自己在EaseVideoCallActivity中的接通与挂断回调开启与关闭前台服务

java 复制代码
  public void addCallkitListener() {
        callKitListener = new EaseCallKitListener() {
            @Override
            public void onInviteUsers(Context context, String userId[], JSONObject ext) {
            }

            @Override
            public void onEndCallWithReason(EaseCallType callType, String channelName, EaseCallEndReason reason, long callTime) {
                EMLog.d(TAG, "onEndCallWithReason" + (callType != null ? callType.name() : " callType is null ") + " reason:" + reason + " time:" + callTime);
                SimpleDateFormat formatter = new SimpleDateFormat("mm:ss");
                formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
                String callString = "通话时长";
                callString += formatter.format(callTime);

                Toast.makeText(MainActivity.this, callString, Toast.LENGTH_SHORT).show();
                //关闭任务栏通知
                stopBarNotify();
            }

            @Override
            public void onGenerateToken(String userId, String channelName, String appKey, EaseCallKitTokenCallback callback) {
                EMLog.d(TAG, "onGenerateToken userId:" + userId + " channelName:" + channelName + " appKey:" + appKey);
                //获取声网Token
                getAgoraToken(userId, channelName, callback);
               //创建服务开启任务栏通知(此处为了模拟,最好将openBarNotify()方法放在获取成功声网token后调用)
                openBarNotify();
            }

            @Override
            public void onReceivedCall(EaseCallType callType, String fromUserId, JSONObject ext) {
                EMLog.d(TAG, "onRecivedCall" + callType.name() + " fromUserId:" + fromUserId);
            }

            @Override
            public void onCallError(EaseCallKit.EaseCallError type, int errorCode, String description) {
                EMLog.d(TAG, "onCallError");
            }

            @Override
            public void onInViteCallMessageSent() {
//                LiveDataBus.get().with(DemoConstant.MESSAGE_CHANGE_CHANGE).postValue(new EaseEvent(DemoConstant.MESSAGE_CHANGE_CHANGE, EaseEvent.TYPE.MESSAGE));
            }

            @Override
            public void onRemoteUserJoinChannel(String channelName, String userName, int uid, EaseGetUserAccountCallback callback) {

            }
        };
        EaseCallKit.getInstance().setCallKitListener(callKitListener);
    }



  private void openBarNotify(){
        keepAppIntent = new Intent(this, KeepAppLifeService.class);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //android8.0以上通过startForegroundService启动service
            startForegroundService(keepAppIntent);
        } else {
            startService(keepAppIntent);
        }
    }

    private void stopBarNotify(){
        if (keepAppIntent != null) {
            stopService(keepAppIntent);
        }
    }

二、iOS想在后台时播放声音,需要在添加App plays audio or streams audio/video using AirPlay权限

1.Info.plist里找到选项Required background modes 添加App plays audio or streams audio/video using AirPlay

2.在Signing&Capabilities -> Background Modes -> 勾选 Audio,AirPlay, and Picture in Picture

3.在AppDelegate.m中实现applicationDidEnterBackground代理方法

objectivec 复制代码
- (void)applicationDidEnterBackground:(UIApplication *)application{
    //环信已实现了进入后台的处理逻辑,如果要自己处理,可以参考下边注释代码
    [[EMClient sharedClient] applicationDidEnterBackground:application];
}

#if Ease_UIKIT
//    - (void)applicationDidEnterBackground:(NSNotification *)notification {
//        if (!self.config.shouldRemoveExpiredDataWhenEnterBackground) {
//            return;
//        }
//        Class UIApplicationClass = NSClassFromString(@"UIApplication");
//        if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
//            return;
//        }
//        UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
//        __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
//            // Clean up any unfinished task business by marking where you
//            // stopped or ending the task outright.
//            [application endBackgroundTask:bgTask];
//            bgTask = UIBackgroundTaskInvalid;
//        }];
//
//        // Start the long-running task and return immediately.
//        [self deleteOldFilesWithCompletionBlock:^{
//            [application endBackgroundTask:bgTask];
//            bgTask = UIBackgroundTaskInvalid;
//        }];
//    }
#endif

4.因为App plays audio or streams audio/video using AirPlay权限只能是音乐播放类与具有音视频通话场景的App使用,所以审核的时候需要在备注描述清楚使用该场景的方式.如果审核失败,可以录制视频在附件上传,然后等待苹果重新审核即可.如果录制的视频没有问题,那就坐等着审核通过了,good luck!

相关推荐
车轮滚滚__14 分钟前
uniapp对接unipush 1.0 ios/android
笔记
Code&Ocean14 分钟前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/15 分钟前
Laya ios接入goole广告,开始接入 2
ios
云边有个稻草人3 小时前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
拭心11 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
冷眼看人间恩怨11 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
darkdragonking12 小时前
FLV视频封装格式详解
音视频
带电的小王13 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡13 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道14 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频