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

news2025/5/25 11:43:19

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

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

前台服务代码如下:

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哈

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

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

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

- (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!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1010564.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在ubuntu安装vncserver,可以打开远程桌面

1.网上查找vncserver的资料 2.在ubuntu20.04上面使用 https://www.5axxw.com/questions/simple/ywnpl5 参考了这个和其他很多 我直接运行vncserver&#xff0c;没有成功&#xff01; 当我使用这个命令时 xtigervncviewer -SecurityTypes VncAuth -passwd /home/yx/.vnc/passw…

构建高效的接口自动化测试框架思路

在选择接口测试自动化框架时&#xff0c;需要根据团队的技术栈和项目需求来综合考虑。对于测试团队来说&#xff0c;使用Python相关的测试框架更为便捷。无论选择哪种框架&#xff0c;重要的是确保 框架功能完备&#xff0c;易于维护和扩展&#xff0c;提高测试效率和准确性。今…

Biome-BGC生态系统模型与Python融合技术教程

详情点击公众号链接&#xff1a;Biome-BGC生态系统模型与Python融合技术教程 前言 Biome-BGC是利用站点描述数据、气象数据和植被生理生态参数&#xff0c;模拟日尺度碳、水和氮通量的有效模型&#xff0c;其研究的空间尺度可以从点尺度扩展到陆地生态系统。 在Biome-BGC模型…

crm、scrm、ocrm、acrm、ccrm等等分别是什么?有什么区别?

crm、SCRM、OCRM、ACRM、CCRM等等分别是什么&#xff1f;crm&#xff0c;scrm&#xff0c;ocrm&#xff0c;acrm&#xff0c;ccrm有什么区别&#xff1f;又有什么联系&#xff1f;这些系统各自之间都有哪些优势和缺点呢&#xff1f;今天将带领大家深入浅出的系统了解crm&#x…

idea 创建java web项目 run后出现404现象

1、创建新项目 创建的新项目只是单纯的java项目&#xff0c;如图 2、添加lib库文件&#xff0c;里面存放jar包&#xff0c;并导入库配置 这里要注意&#xff0c;需要先添加lib库文件再去配置模块和工件否则会出现404现象 3、打开模块设置&#xff0c;设置项目配置 将本…

c语言练习58:⾃定义类型:结构体

⾃定义类型&#xff1a;结构体 结构体的概念 结构是⼀些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 结构体是一个种自定义的数据类型&#xff0c;它可以由很多个默认数据类型组成。它主要用于描述复杂场景下的变量。 例如&#xff0c;想…

go语言基础--杂谈

常量运行期间&#xff0c;不可以改变的值 const PI float64 3.14字面常量 所谓字面常量&#xff0c;是值程序中硬编码的常量 123 //整型类型常量 156.78 //浮点类型的常量 true //布尔类型的常量 “abc”//字符串类型的常量//const PI float64 3.14 const PI 3.14 // //P…

在Kubernetes集群中部署 dolphindcheduler-3.1.8

温故知新 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 部署&#x1f4d7;安装helm&#x1f4d7;安装dolphindcheduler&#xff08;使用k8s的部署用户操作&#xff09;&#x1f4d5;通过命令验证&#x1f4d5;通过Ku…

OpenCV(四十二):Harris角点检测

1.Harris角点介绍 什么是角点&#xff1f; 角点指的是两条边的交点&#xff0c;图中红色圈起来的点就是角点。 Harris角点检测原理&#xff1a;首先定义一个矩形区域&#xff0c;然后将这个矩形区域放置在我的图像中&#xff0c;求取这个区域内所有的像素值之和&#xff0c;之…

【C语言】每日一题(半月斩)——day2

目录 一.选择题 1、以下程序段的输出结果是( ) 2、若有以下程序&#xff0c;则运行后的输出结果是&#xff08; &#xff09; 3、如下函数的 f(1) 的值为&#xff08; &#xff09; 4、下面3段程序代码的效果一样吗( ) 5、对于下面的说法&#xff0c;正确的是&#xf…

2024CFA一级notes百度网盘下载

024CFA一级notes百度网盘下载 2024CFA一级notes2024年CFA考纲已经正式发布&#xff0c;相比与老考纲&#xff0c;新考纲变化实在不算小。 面对2024年CFA新考纲的变化&#xff0c;我们在第一时间对2024年考试的新趋势和新变化&#xff0c;进行深度解读。具体总结如下&#xff…

ddtrace 系列篇之 dd-trace-java 项目编译

dd-trace-java 是 Datadog 开源的 java APM 框架&#xff0c;本文主要讲解如何编译 dd-trace-java 项目。 环境准备 JDK 编译环境(三个都要&#xff1a;jdk8\jdk11\jdk17) Gradle 8 Maven 3.9 (需要 15G 以上的存储空间存放依赖) Git >2 (低于会出现一想不到的异常&#xf…

短视频搭建矩阵源码--短视频矩阵源码搭建

短视频矩阵系统是一种通过将短视频内容进行分类、管理和展示的系统。它可以将用户上传或者选择的短视频按照不同的特定标签进行分类和管理&#xff0c;用户可以根据自己的兴趣和需求选择观看不同类别的短视频。 短视频矩阵源码的开发部署其实并不难&#xff0c;主要依托于抖音平…

springboot集成excel导入导出

1、引入依赖 <dependency><groupId>com.pig4cloud.excel</groupId><artifactId>excel-spring-boot-starter</artifactId><version>1.2.7</version> </dependency> 2、导出 ResponseExcel(name "测试列表") Post…

记录:移动设备软件开发(activity组件)

目录 前言Android简介和发展Android应用的基本组件介绍Activity组件Activity简介Activity的状态和生命周期 小结 前言 移动设备软件开发是指为智能手机、平板电脑等移动设备设计和开发应用程序的过程。移动设备软件开发涉及多种技术、平台和工具&#xff0c;例如Android、iOS、…

R语言CalibrationCurves包绘制带可信区间的校准曲线

校准曲线图表示的是预测值和实际值的差距&#xff0c;作为预测模型的重要部分&#xff0c;目前很多函数能绘制校准曲线。 一般分为两种&#xff0c;一种是通过Hosmer-Lemeshow检验&#xff0c;把P值分为10等分&#xff0c;求出每等分的预测值和实际值的差距。一种是绘制连续的校…

C#: 未能加载文件或程序集“xxx“

导入数据时&#xff0c;发生了异常&#xff0c;错误日志如下&#xff1a; 2023-09-11 09:20:49,304 [125] FATAL [(null)] - NPOI.POIXMLException ---> System.Reflection.TargetInvocationException: 调用的目标发生了异常。 ---> System.IO.FileLoadException: 未能加…

OpenCV之GOTURN目标追踪

&#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 目录 前言 一、goturn简介 二、预处理 三、模型加载 四、执行推理 五、解…

干洗店洗鞋店小程序家政保洁带商城一体化系统开发

干洗店洗鞋店小程序家政保洁带商城一体化系统开发&#xff1b; 一、核心功能介绍 1.(支持上门取送、送货到店、寄存网点、智能衣柜四种下单方式) 用户下单-上门取货拍照-送达门店工厂-入厂拍照-清洗完成拍照-上挂物品柜-物品送回 2.骑手端(接单上门、取货拍照&#xff0c;入库拍…

Can‘t load the model for ‘stabilityai/sd-vae-ft-mse‘

Can’t load the model for ‘stabilityai/sd-vae-ft-mse’. If you were trying to load it from ‘https://huggingface.co/models’, make sure you don’t have a local directory with the same name. Otherwise, make sure ‘stabilityai/sd-vae-ft-mse’ is the correct…