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


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

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

    前台服务代码如下:

    1. package com.notify.test.service;
    2. import android.app.Notification;
    3. import android.app.NotificationChannel;
    4. import android.app.NotificationManager;
    5. import android.app.PendingIntent;
    6. import android.app.Service;
    7. import android.content.Context;
    8. import android.content.Intent;
    9. import android.os.Build;
    10. import android.os.IBinder;
    11. import com.notify.test.R;
    12. import androidx.annotation.Nullable;
    13. /**
    14. * desc:解决声网音视频锁屏后听不到声音的问题
    15. * (可以配合Application.ActivityLifecycleCallbacks使用)
    16. *
    17. * Created by booyoung
    18. * on 2023/9/8 14:46
    19. */
    20. public class KeepAppLifeService extends Service {
    21. @Nullable
    22. @Override
    23. public IBinder onBind(Intent intent) {
    24. return null;
    25. }
    26. private final String notificationId = "app_keep_live";
    27. private final String notificationName = "audio_and_video_call";
    28. @Override
    29. public void onCreate() {
    30. super.onCreate();
    31. NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    32. //创建NotificationChannel
    33. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    34. NotificationChannel channel = new NotificationChannel(notificationId, notificationName, NotificationManager.IMPORTANCE_HIGH);
    35. //不震动
    36. channel.enableVibration(false);
    37. //静音
    38. channel.setSound(null, null);
    39. notificationManager.createNotificationChannel(channel);
    40. }
    41. startForeground(1, getNotification());
    42. }
    43. @Override
    44. public int onStartCommand(Intent intent, int flags, int startId) {
    45. return super.onStartCommand(intent, flags, startId);
    46. }
    47. @Override
    48. public void onDestroy() {
    49. super.onDestroy();
    50. }
    51. @Override
    52. public void onTaskRemoved(Intent rootIntent) {
    53. super.onTaskRemoved(rootIntent);
    54. //stop service
    55. this.stopSelf();
    56. }
    57. /**
    58. * 获取通知(Android8.0后需要)
    59. * @return
    60. */
    61. private Notification getNotification() {
    62. Notification.Builder builder = new Notification.Builder(this)
    63. .setSmallIcon(R.mipmap.logo)
    64. .setOngoing(true)
    65. .setContentTitle("App名称")
    66. .setContentIntent(getIntent())
    67. .setContentText("音视频通话中,轻击以继续");
    68. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    69. builder.setChannelId(notificationId);
    70. }
    71. return builder.build();
    72. }
    73. /**
    74. * 点击后,直接打开app
    75. * @return
    76. */
    77. private PendingIntent getIntent() {
    78. //获取启动Activity
    79. Intent msgIntent = getApplicationContext().getPackageManager().getLaunchIntentForPackage(getPackageName());
    80. PendingIntent pendingIntent = PendingIntent.getActivity(
    81. getApplicationContext(),
    82. 1,
    83. msgIntent,
    84. PendingIntent.FLAG_UPDATE_CURRENT);
    85. return pendingIntent;
    86. }
    87. }

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

    1. <service android:name=".service.KeepAppLifeService"
    2. android:enabled="true"
    3. android:exported="false"
    4. android:stopWithTask="true" />

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

    1. public void addCallkitListener() {
    2. callKitListener = new EaseCallKitListener() {
    3. @Override
    4. public void onInviteUsers(Context context, String userId[], JSONObject ext) {
    5. }
    6. @Override
    7. public void onEndCallWithReason(EaseCallType callType, String channelName, EaseCallEndReason reason, long callTime) {
    8. EMLog.d(TAG, "onEndCallWithReason" + (callType != null ? callType.name() : " callType is null ") + " reason:" + reason + " time:" + callTime);
    9. SimpleDateFormat formatter = new SimpleDateFormat("mm:ss");
    10. formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
    11. String callString = "通话时长";
    12. callString += formatter.format(callTime);
    13. Toast.makeText(MainActivity.this, callString, Toast.LENGTH_SHORT).show();
    14. //关闭任务栏通知
    15. stopBarNotify();
    16. }
    17. @Override
    18. public void onGenerateToken(String userId, String channelName, String appKey, EaseCallKitTokenCallback callback) {
    19. EMLog.d(TAG, "onGenerateToken userId:" + userId + " channelName:" + channelName + " appKey:" + appKey);
    20. //获取声网Token
    21. getAgoraToken(userId, channelName, callback);
    22. //创建服务开启任务栏通知(此处为了模拟,最好将openBarNotify()方法放在获取成功声网token后调用)
    23. openBarNotify();
    24. }
    25. @Override
    26. public void onReceivedCall(EaseCallType callType, String fromUserId, JSONObject ext) {
    27. EMLog.d(TAG, "onRecivedCall" + callType.name() + " fromUserId:" + fromUserId);
    28. }
    29. @Override
    30. public void onCallError(EaseCallKit.EaseCallError type, int errorCode, String description) {
    31. EMLog.d(TAG, "onCallError");
    32. }
    33. @Override
    34. public void onInViteCallMessageSent() {
    35. // LiveDataBus.get().with(DemoConstant.MESSAGE_CHANGE_CHANGE).postValue(new EaseEvent(DemoConstant.MESSAGE_CHANGE_CHANGE, EaseEvent.TYPE.MESSAGE));
    36. }
    37. @Override
    38. public void onRemoteUserJoinChannel(String channelName, String userName, int uid, EaseGetUserAccountCallback callback) {
    39. }
    40. };
    41. EaseCallKit.getInstance().setCallKitListener(callKitListener);
    42. }
    43. private void openBarNotify(){
    44. keepAppIntent = new Intent(this, KeepAppLifeService.class);
    45. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    46. //android8.0以上通过startForegroundService启动service
    47. startForegroundService(keepAppIntent);
    48. } else {
    49. startService(keepAppIntent);
    50. }
    51. }
    52. private void stopBarNotify(){
    53. if (keepAppIntent != null) {
    54. stopService(keepAppIntent);
    55. }
    56. }

    二、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代理方法

    1. - (void)applicationDidEnterBackground:(UIApplication *)application{
    2. //环信已实现了进入后台的处理逻辑,如果要自己处理,可以参考下边注释代码
    3. [[EMClient sharedClient] applicationDidEnterBackground:application];
    4. }
    5. #if Ease_UIKIT
    6. // - (void)applicationDidEnterBackground:(NSNotification *)notification {
    7. // if (!self.config.shouldRemoveExpiredDataWhenEnterBackground) {
    8. // return;
    9. // }
    10. // Class UIApplicationClass = NSClassFromString(@"UIApplication");
    11. // if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
    12. // return;
    13. // }
    14. // UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
    15. // __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    16. // // Clean up any unfinished task business by marking where you
    17. // // stopped or ending the task outright.
    18. // [application endBackgroundTask:bgTask];
    19. // bgTask = UIBackgroundTaskInvalid;
    20. // }];
    21. //
    22. // // Start the long-running task and return immediately.
    23. // [self deleteOldFilesWithCompletionBlock:^{
    24. // [application endBackgroundTask:bgTask];
    25. // bgTask = UIBackgroundTaskInvalid;
    26. // }];
    27. // }
    28. #endif

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

  • 相关阅读:
    DBeaver使用
    ZooKeeper 核心知识总结
    大厂面试之算法篇
    专项技能训练五《云计算网络技术与应用》实训6-2:ryu控制器安装
    SpringMVC第六阶段:数据在域中的保存(02)
    Falcon 登陆 Hugging Face 生态
    食品级接触材料的检测标准有哪些?
    【PAT甲级 - C++题解】1149 Dangerous Goods Packaging
    Go 之烧脑的接口
    Pointnet++的改进
  • 原文地址:https://blog.csdn.net/c_furong/article/details/130584370