互动直播 Android 端开发指南

SDK 概述

网易云通信 SDK 为移动提供完善的音视频直播与实时互动开发框架,屏蔽其内部复杂细节,对外提供较为简洁的 API 接口,方便第三方应用快速集成互动直播功能,主要有:

  • 支持主播和观众实时互动连麦,合并直播;
  • 支持视频连麦和音频连麦;
  • 支持互动直播过程中动态切换视频清晰度、码率和编解码器;
  • 支持互动直播过程中动态切换推流地址。

开发准备

网易云通信 Android SDK 支持两种方式集成SDK。

1. 通过 Gradle 集成SDK (推荐)

2. 通过类库配置集成SDK

网易云通信 Android SDK 2.5.0 以上强烈推荐通过 Gradle 集成 SDK。

注意: 1. SDK 最低要求 Android 4.0, 其中互动直播最低要求 Android 4.1。 2. 从 3.2 版本开始 jni 库支持 64位 系统

通过Gradle集成SDK

首先,在整个工程的 build.gradle 文件中,配置repositories,使用 jcenter 或者 maven ,二选一即可,如下:

allprojects {
repositories {
jcenter() // 或者 mavenCentral()
}
}

第二步,在主工程的 build.gradle 文件中,添加 dependencies。根据自己项目的需求,添加不同的依赖即可。注意:版本号必须一致,这里以3.3.0版本为例:


android {
defaultConfig {
ndk {
//设置支持的SO库架构
abiFilters "armeabi-v7a", "x86","arm64-v8a","x86_64"
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
// 添加依赖。注意,版本号必须一致。
// 基础功能 (必需)
compile 'com.netease.nimlib:basesdk:3.3.0'
// 音视频需要
compile 'com.netease.nimlib:avchat:3.3.0'
}

再次注意:依赖包的版本号必须一致。

通过类库配置集成SDK

首先到下载页面下载 Android SDK。开发者可以根据实际需求,配置类库。

以下介绍以 Android SDK v2.5及以上版本为例,Android SDK v2.5以下的配置,请咨询技术支持。

SDK 包的libs文件夹中,包含了网易云通信的 jar 文件,各 jni 库文件夹以及 SDK 依赖的第三方库。

实现音视频通话功能,需要将这些文件拷贝到你的工程的 libs 目录下,即可完成配置。列表如下:

libs
├── arm64-v8a
│   ├── libnrtc_engine.so (音视频需要)
│   └── libnrtc_network.so (音视频需要)
├── armeabi-v7a
│   ├── libnrtc_engine.so
│   └── libnrtc_network.so
├── x86
│   ├── libnrtc_engine.so
│   └── libnrtc_network.so
├── x86_64
│   ├── libnrtc_engine.so
│   └── libnrtc_network.so
│
├── nim-basesdk-3.3.0.jar (基础功能)
├── nim-avchat-3.3.0.jar (音视频需要)
├── nrtc-sdk.jar(音视频需要)

以上文件列表中,jar文件版本号可能会不同,子目录中的文件是 SDK 所依赖的各个 CPU 架构的 so 库。

如果你使用的 IDE 是 Android Studio,要将 jni 库按照 IDEA 工程目录的结构,放置在对应的目录中(一般为 src/main/jniLibs)。或者在 build.gradle 中配置好 jniLibs 的 sourceSets(可参考 demo 的 build.gradle)。

权限与组件

AndroidManifest.xml 中加入以下配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xxx">

<!-- 权限声明 -->
<!-- 访问网络状态-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 控制呼吸灯,振动器等,用于新消息提醒 -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- 外置存储存取权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<!-- 多媒体相关 -->
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

<!-- 如果需要实时音视频通话模块,下面的权限也是必须的。否则,可以不加 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

<!-- SDK 权限申明, 第三方 APP 接入时,请将 com.netease.nim.demo 替换为自己的包名 -->
<!-- 和下面的 uses-permission 一起加入到你的 AndroidManifest 文件中。 -->
<permission
android:name="com.netease.nim.demo.permission.RECEIVE_MSG"
android:protectionLevel="signature"/>
<!-- 接收 SDK 消息广播权限, 第三方 APP 接入时,请将 com.netease.nim.demo 替换为自己的包名 -->
<uses-permission android:name="com.netease.nim.demo.permission.RECEIVE_MSG"/>

<application
...>
<!-- APP key, 可以在这里设置,也可以在 SDKOptions 中提供。
如果 SDKOptions 中提供了,取 SDKOptions 中的值。 -->
<meta-data
android:name="com.netease.nim.appKey"
android:value="key_of_your_app" />

<!-- 声明网易云通信后台服务,如需保持后台推送,使用独立进程效果会更好。 -->
<service
android:name="com.netease.nimlib.service.NimService"
android:process=":core"/>

<!-- 运行后台辅助服务 -->
<service
android:name="com.netease.nimlib.service.NimService$Aux"
android:process=":core"/>

<!-- 声明网易云通信后台辅助服务 -->
<service
android:name="com.netease.nimlib.job.NIMJobService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":core"/>

<!-- 网易云通信SDK的监视系统启动和网络变化的广播接收器,用户开机自启动以及网络变化时候重新登录,
保持和 NimService 同一进程 -->
<receiver android:name="com.netease.nimlib.service.NimReceiver"
android:process=":core"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>

<!-- 网易云通信进程间通信 Receiver -->
<receiver android:name="com.netease.nimlib.service.ResponseReceiver"/>

<!-- 网易云通信进程间通信service -->
<service android:name="com.netease.nimlib.service.ResponseService"/>

</application>
</manifest>

混淆配置

如果你的 apk 最终会经过代码混淆,请在 proguard 配置文件中加入以下代码:

-dontwarn com.netease.**
-dontwarn io.netty.**
-keep class com.netease.** {*;}
#如果 netty 使用的官方版本,它中间用到了反射,因此需要 keep。如果使用的是我们提供的版本,则不需要 keep
-keep class io.netty.** {*;}

#如果你使用全文检索插件,需要加入
-dontwarn org.apache.lucene.**
-keep class org.apache.lucene.** {*;}

总体接口介绍

网易云通信 SDK 提供了两类接口供开发者调用:一类是第三方 APP 主动发起请求,第二类是第三方 APP 作为观察者监听事件和变化。第一类接口名均以 Service 结尾,例如 AuthService ,第二类接口名均以 ServiceObserver 结尾,例如 AuthServiceObserver,个别太长的类名则可能直接以 Observer 结尾,比如 SystemMessageObserver

可以通过 NIMClientgetService 接口获取到各个服务实例,例如:通过 NIMClient.getService(AuthService.class) 获取到 AuthService 服务实例,通过 NIMClient.getService(AuthServiceObserver.class) 获取到 AuthServiceObserver 观察者接口。

SDK 由于需要保持后台运行,典型场景下会在独立进程中运行,第一类接口基本上都是从主进程发起调用,然后在后台进程执行,最后再将结果返回给主进程。因此,如无特殊说明,所有接口均为异步调用,开发者无需担心调用 SDK 接口阻塞 UI 的问题。如果调用的接口需要一些后期操作,包括结果回调,取消调用等,此类接口会返回一个 InvocationFuture 对象。如果开发者关心调用的结果,可在此返回的接口中设置回调函数。 接口回调接口为 RequestCallback ,有3个接口需要实现:onSuccess, onFailed, onException,分别对应成功,失败,以及出现异常。另外还提供了一个更简洁的接口 RequestCallbackWrapper,作为 RequestCallback 的包裹,封装了上面的 3 个接口,然后将他们合并成一个 onResult 接口,在参数上做不同结果的区分。

还有一类接口,耗时会很长,可能需要传输大量数据,或者要发起网络连接,比如上传下载,登录等。对于这类接口,返回值会是一个 AbortableFuture 对象,该接口继承自 InvocationFuture,增加了一个 abort() 方法,可取消之前的请求。

观察者接口的方法名都是以 observe 开头,并包含一个 register 参数,该值为true时,为注册观察者,为false时,注销观察者。开发者要在不需要观察者时,主动注销,以免造成资源泄露。

注意:除了 NIMClient.init 接口外,其他 SDK 暴露的接口都只能在 UI 进程调用。如果 APP 包含远程 service,该 APP 的 Application 的 onCreate 会多次调用。因此,如果需要在 onCreate 中调用除 init 接口外的其他接口,应先判断当前所属进程,并只有在当前是 UI 进程时才调用。判断代码如下:

public static boolean inMainProcess(Context context) {
String packageName = context.getPackageName();
String processName = SystemUtil.getProcessName(context);
return packageName.equals(processName);
}

/**
  * 获取当前进程名
  * @param context
  * @return 进程名
  */
public static final String getProcessName(Context context) {
String processName = null;

// ActivityManager
ActivityManager am = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE));

while (true) {
for (ActivityManager.RunningAppProcessInfo info : am.getRunningAppProcesses()) {
if (info.pid == android.os.Process.myPid()) {
processName = info.processName;
break;
}
}

// go home
if (!TextUtils.isEmpty(processName)) {
return processName;
}

// take a rest and again
try {
Thread.sleep(100L);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}

SDK 提供的接口主要按照业务进行分类,大致说明如下:

  • AuthService:用户认证服务接口,提供登录注销接口。
  • AuthServiceObserve: 用户状态观察者接口。
  • MsgService: 消息服务接口,用于发送消息,管理消息记录等。同时还提供了发送自定义通知的接口。
  • MsgServiceObserve: 接收消息,消息状态变化等观察者接口。
  • TeamService: 群组服务接口,用于发送群组消息,管理群组和群成员资料等。
  • TeamServiceObserve: 群组和群成员资料变化观察者。
  • SystemMessageService: 管理系统通知接口。
  • SystemMessageObserve: 系统通知观察者。
  • FriendService: 好友关系托管接口,目前支持添加、删除好友、获取好友列表、黑名单、设置消息提醒。
  • FriendServiceObserve: 好友关系变更、黑名单变更通知观察者。
  • UserService: 用户资料托管接口,提供获取用户资料、修改个人资料等。
  • UserServiceObserve: 用户资料托管接口,提供获取用户资料、修改个人资料等。
  • AVChatManager: 语音视频通话接口。
  • NosService: 网易云存储服务,提供文件上传和下载。
  • MixPushService 第三方推送接口,提供第三方推送服务。

SDK 数据缓存目录结构

当收到多媒体消息后,SDK 会负责下载这些多媒体文件,同时 SDK 还要记录一些关键的 log,因此 SDK 需要一个数据缓存目录。 该目录可以在 SDK 初始化时通过 SDKOptions#sdkStorageRootPath 进行设置。 如果不设置,则默认为“/{外卡根目录}/{app_package_name}/nim/”,其中外卡根目录获取方式为 Environment.getExternalStorageDirectory().getPath()。 如果你的 APP 需要清除缓存功能,可扫描该目录下的文件,按照你们的规则清理即可。 在 SDK 初始化完成后可以通过 NimClient#getSdkStorageDirPath 获取 SDK 数据缓存目录。

SDK数据缓存目录下面包含如下子目录:

  • log: SDK日志包含一个文件:nim_sdk.log,大小一般不超过 8M。音视频通话的日志包含3个文件:avchat_n.log, nrtc_engine.log, nrtc_net.log。 默认路径为:/{外卡根目录}/{app_package_name}/nim/log/,如果在 SDKOptions 中配置过 sdkStorageRootPath,那么日志路径为“{sdkStorageRootPath}/log/”。
  • file: 文件消息文件
  • image: 图片消息文件
  • audio:语音消息文件
  • video:视频消息文件
  • thumb:图片/视频缩略图文件

初始化 SDK

在你的程序的 Application 的 onCreate 中,加入网易云通信 SDK 的初始化代码:

public class NimApplication extends Application {

public void onCreate() {
// ... your codes

// SDK初始化(启动后台服务,若已经存在用户登录信息, SDK 将完成自动登录)
NIMClient.init(this, loginInfo(), options());

// ... your codes
if (inMainProcess()) {
// 注意:以下操作必须在主进程中进行
// 1、UI相关初始化操作
// 2、相关Service调用
}
}

// 如果返回值为 null,则全部使用默认参数。
private SDKOptions options() {
SDKOptions options = new SDKOptions();

// 如果将新消息通知提醒托管给 SDK 完成,需要添加以下配置。否则无需设置。
StatusBarNotificationConfig config = new StatusBarNotificationConfig();
config.notificationEntrance = WelcomeActivity.class; // 点击通知栏跳转到该Activity
config.notificationSmallIconId = R.drawable.ic_stat_notify_msg;
// 呼吸灯配置
config.ledARGB = Color.GREEN;
config.ledOnMs = 1000;
config.ledOffMs = 1500;
// 通知铃声的uri字符串
config.notificationSound = "android.resource://com.netease.nim.demo/raw/msg";
options.statusBarNotificationConfig = config;

// 配置保存图片,文件,log 等数据的目录
// 如果 options 中没有设置这个值,SDK 会使用下面代码示例中的位置作为 SDK 的数据目录。
// 该目录目前包含 log, file, image, audio, video, thumb 这6个目录。
// 如果第三方 APP 需要缓存清理功能, 清理这个目录下面个子目录的内容即可。
String sdkPath = Environment.getExternalStorageDirectory() + "/" + getPackageName() + "/nim";
options.sdkStorageRootPath = sdkPath;

// 配置是否需要预下载附件缩略图,默认为 true
options.preloadAttach = true;

// 配置附件缩略图的尺寸大小。表示向服务器请求缩略图文件的大小
// 该值一般应根据屏幕尺寸来确定, 默认值为 Screen.width / 2
options.thumbnailSize = ${Screen.width} / 2;

// 用户资料提供者, 目前主要用于提供用户资料,用于新消息通知栏中显示消息来源的头像和昵称
options.userInfoProvider = new UserInfoProvider() {
@Override
public UserInfo getUserInfo(String account) {
return null;
}

@Override
public int getDefaultIconResId() {
return R.drawable.avatar_def;
}

@Override
public Bitmap getTeamIcon(String tid) {
return null;
}

@Override
public Bitmap getAvatarForMessageNotifier(String account) {
return null;
}

@Override
public String getDisplayNameForMessageNotifier(String account, String sessionId,
SessionTypeEnum sessionType) {
return null;
}
};
return options;
}

// 如果已经存在用户登录信息,返回LoginInfo,否则返回null即可
private LoginInfo loginInfo() {
return null;
}
}

特别提醒:SDK 的初始化方法必须在主进程中调用,在非主进程中初始化无效。请在主进程中调用 SDK XXXService 提供的方法,在主进程中注册 XXXServiceObserver 的观察者(有事件变更,会回调给主进程的主线程)。如果你的模块运行在非主进程,请自行实现主进程与非主进程的通信(Binder/AIDL/BroadcastReceiver等IPC)将主进程回调或监听返回的数据传递给非主进程。

网易云通信Andorid SDK断网重连机制及登录返回码说明

登录与登出

登录集成必读

手动登录

一般 APP 在首次登录、切换帐号登录、注销重登时需要手动登录,开发者需要调用 AuthService 提供的 login 接口主动发起登录请求。该接口返回类型为 AbortableFuture,允许用户在后面取消登录操作。如果服务器一直没有响应,30 秒后 RequestCallback 的 onFailed 会被调用,参数为 408 (网络连接超时)。

public class LoginActivity extends Activity {
public void doLogin() {
LoginInfo info = new LoginInfo(); // config...
RequestCallback<LoginInfo> callback =
new RequestCallback<LoginInfo>() {
// 可以在此保存LoginInfo到本地,下次启动APP做自动登录用
};
NIMClient.getService(AuthService.class).login(info)
.setCallback(callback);
}
}

登录成功后,可以将用户登录信息 LoginInfo 信息保存到本地,下次启动APP时,读取本地保存的 LoginInfo 进行自动登录。

说明:在手动登录过程中,如果网络断开或者与网易云通信服务器建立连接失败,会返回登录失败(错误码 415),在线状态切换为 NET_BROKEN; 如果连接建立成功,SDK 发出登录请求后网易云通信服务器一直没有响应,那么 30s 后将导致登录超时,那么会返回登录失败(错误码 408),在线状态切换为 UNLOGIN。

注意:从SDK 2.2.0版本开始, LoginInfo 中添加了可选属性 AppKey,支持在登录的时候设置 AppKey;如果不填,则优先使用 SDKOptions 中配置的 AppKey;如果也没有,则使用 AndroidManifest.xml 中配置的 AppKey(默认方式)。建议使用默认方式。

特别提醒: 登录成功之前,调用服务器相关请求接口(由于与网易云通信服务器连接尚未建立成功,会导致发包超时)会报408错误;调用本地数据库相关接口(手动登录的情况下数据库未打开),会报1000错误,建议用户在登录成功之后,再进行相关接口调用。

自动登录

如果上次登录已经存在用户登录信息,那么在初始化 SDK 时传入 LoginInfo,SDK 后台会自动登录,并在登录发起前即打开相关账号的数据库,供上层调用。开发者此时无需再手动调用登录接口,可以跳过登录界面直接进入主界面。

进入主界面后,可以通过监听用户在线状态(每次注册用户在线状态监听都会立即回调通知当前的用户在线状态),或者主动获取当前用户在线状态,来判断自动登录是否成功。

在初始化 SDK 时自动登录示例:

public class NimApplication extends Application {

public void onCreate() {
// ... your codes

// SDK初始化(启动后台服务,若已经存在用户登录信息,SDK 将完成自动登录)
NIMClient.init(this, loginInfo(), options());

// ... your codes
}

private LoginInfo loginInfo() {
// 从本地读取上次登录成功时保存的用户登录信息
String account = Preferences.getUserAccount();
String token = Preferences.getUserToken();

if (!TextUtils.isEmpty(account) && !TextUtils.isEmpty(token)) {
DemoCache.setAccount(account.toLowerCase());
return new LoginInfo(account, token);
} else {
return null;
}
}
}

说明:在自动登录过程中,如果没有网络或者网络断开或者与网易云通信服务器建立连接失败,会上报在线状态 NET_BROKEN,表示当前网络不可用,当网络恢复的时候,会触发断网自动重连;如果连接建立成功但登录超时,会上报在线状态 UNLOGIN,并触发自动重连,无需上层手动调用登录接口。

特别提醒: 在自动登录成功前,调用服务器相关请求接口(由于与网易云通信服务器连接尚未建立成功,会导致发包超时)会报408错误。但可以调用本地数据库相关接口获取本地数据(自动登录的情况下会自动打开相关账号的数据库)。自动登录过程中也会有用户在线状态回调。

监听用户在线状态

登录成功后,SDK 会负责维护与服务器的长连接以及断线重连等工作。当用户在线状态发生改变时,会发出通知。此外,自动登录过程中也会有状态回调。开发者可以通过加入以下代码监听用户在线状态改变:

NIMClient.getService(AuthServiceObserver.class).observeOnlineStatus(
new Observer<StatusCode> () {
public void onEvent(StatusCode status) {
Log.i("tag", "User status changed to: " + status);
if (code.wontAutoLogin()) {
// 被踢出、账号被禁用、密码错误等情况,自动登录失败,需要返回到登录界面进行重新登录操作
}
}
}, true);

被踢出的情况说明:

  1. 当用户在线时被踢出,会立刻收到被踢出的状态变更通知;
  2. 当用户离线后在其他设备成功登录,又在本设备重新自动登录时,也会收到被踢出的状态变更通知。

开发者也可以主动获取当前用户在线状态:

StatusCode status = NIMClient.getStatus();

数据同步状态通知

登录成功后,SDK 会立即同步数据(用户资料、用户关系、群资料、离线消息、漫游消息等),同步开始和同步完成都会发出通知。

注册登录同步状态通知:

NIMClient.getService(AuthServiceObserver.class).observeLoginSyncDataStatus(new Observer<LoginSyncStatus>() {
@Override
public void onEvent(LoginSyncStatus status) {
if (status == LoginSyncStatus.BEGIN_SYNC) {
LogUtil.i(TAG, "login sync data begin");
} else if (status == LoginSyncStatus.SYNC_COMPLETED) {
LogUtil.i(TAG, "login sync data completed");
}
}
}, register);

同步开始时,SDK 数据库中的数据可能还是旧数据(如果是首次登录,那么 SDK 数据库中还没有数据,重新登录时 SDK 数据库中还是上一次退出时保存的数据)。

同步完成时, SDK 数据库已完成更新。

在同步过程中,SDK 数据的更新会通过相应的 XXXServiceObserver 接口发出数据变更通知。

一般来说, APP 开发者在登录完成后可以开始构建数据缓存:

  • 登录完成后立即从 SDK 读取数据构建缓存,此时加载到的可能是旧数据;
  • 在 Application 的 onCreate 中注册 XXXServiceObserver 来监听数据变化,那么在同步过程中, APP 会收到数据更新通知,此时直接更新缓存。当同步完成时,缓存也就构建完成了。

多端登录

登录成功后,可以注册多端登录状态观察者。当有其他端登录或者注销时,会通过此接口通知到UI。登录成功后,如果有其他端登录着(在线),也会发出通知。返回的 OnlineClient 能够获取当前同时在线的客户端类型和操作系统。

NIMClient.getService(AuthServiceObserver.class).observeOtherClients(new Observer<List<OnlineClient>>() { ... }, true);

如果需要主动踢掉当前同时在线的其他端, 需要传入 OnlineClient

NIMClient.getService(AuthService.class).kickOtherClient(onlineClient).setCallback(new RequestCallback<Void>() { ... });

当被其他端踢掉,可以通过在线状态观察者来接收监听消息:

NIMClient.getService(AuthServiceObserver.class).observeOnlineStatus(
new Observer<StatusCode> () {
public void onEvent(StatusCode status) {
// 判断在线状态,如果为被其他端踢掉,做登出操作
}
}, true);

网易云通信内置多端登录互踢策略为:移动端( Android 、 iOS )互踢,桌面端( PC 、 Web )互踢,移动端和桌面端共存( 可以采用上述 kickOtherClient 主动踢下共存的其他端)。

如果当前的互踢策略无法满足业务需求的话,可以联系我们取消内置互踢,根据多端登录的回调和当前的设备列表,判断本设备是否需要被踢出。如果需要踢出,直接调用登出接口并在界面上给出相关提示即可。

登出

如果用户手动登出,不再接收消息和提醒,开发者可以调用 logout 方法,该方法没有回调。

注意: 登出操作,不要放在 Activity(Fragment) 的 onDestroy 方法中。

NIMClient.getService(AuthService.class).logout();

离线查看数据

对于一些弱IM场景,需要在登录成功前或者未登录状态下访问指定账号的数据(聊天记录、好友资料等)。 SDK 提供两种方案:

  1. 使用自动登录。在登录成功前,可以访问 SDK 服务来读取本地数据(但不能发送数据)。
  2. 使用 AuthService#openLocalCache 接口打开本地数据,这是个同步方法,打开后即可读取 SDK 数据库中的记录。可以通过注销来切换账号查看本地数据。
NIMClient.getService(AuthService.class).openLocalCache(account);

断线重连机制

SDK 提供三种断线重连的策略(重新建立与网易云通信服务器的连接并重新登录):

1. 当网络由连通变为断开时,SDK 会启动立即上报网络断开的状态,并启动重连定时器,采用特定的策略并根据当前网络状态进行重连(如果 APP 处于后台,重连时间间隔会较长)。

2. SDK会监听设备的网络连接状况,当监听到手机断网重连上网络的通知后,会立即进行重连并登录。

3. 应用长时间处于后台(后台进程可能活着但网络连接被系统切断)后切回到前台(恢复网络连通),SDK 监测到当前处于未登录状态,会在短时间内进行重连。

互动直播

网易云通信 SDK 提供了完整的互动直播推流和音视频连麦功能,主要有以下功能:

  • 支持主播推流直播
  • 支持主播和观众一对一实时连麦,并且合并推流
  • 支持两种连麦方式:纯语音连麦和视频连麦
  • 支持互动直播过程中动态设置清晰度,码率和编解码器
  • 支持互动直播过程中动态更换推流地址

注意: 互动直播需要 Android 4.1 及以上版本的系统,同时推荐主播使用中高端 Android 机器。

互动直播概念

  • 房间 互动直播房间与音视频多人会议的房间概念一致,以房间名称为唯一标识。互动直播房间需要先创建成功后才能加入,当所有用户都离开房间后,可以复用该房间名重新创建。
  • 主播 互动直播房间的主用户,推流地址的指定者,直播的主画面源。需要首先加入房间,连麦者才能加入。
  • 连麦用户 互动直播房间的次用户,直播辅画面源,与主播加入同一房间,即能实现实时连麦互动。需要主播在房间时才能加入。
  • 观众 互动直播中除了主播和连麦者,观看直播画面的其他用户。可以通过停止播放直播并加入互动房间转变为连麦者。

互动直播与多人音视频关系: 互动直播基于音视频多人会议开发,通过将多人会议中用户的音视频数据处理后推送给视频流服务器实现直播和实时连麦。 在功能的提供上,互动直播复用多人音视频接口,增加互动开关、推流地址指定与切换、直播角色指定等扩展设置

互动直播接入流程

SDK提供简单的互动推流和连麦接口,只需要创建并加入互动房间即可以实现直播推流;连麦者以相同的房间名加入即可以实现实时连麦互动。

创建互动直播房间

通过一个房间名 roomName 来创建互动直播房间。

AVChatManager.getInstance().createRoom(roomName, extraMessage, new AVChatCallback<AVChatChannelInfo>() {}

加入互动直播房间

通过一个房间名 roomName 来加入一个已经创建好的互动直播房间。

AVChatManager.getInstance().joinRoom(roomName, callType, config, new AVChatCallback<AVChatData>() {}
  • 主播加入房间需要打开config#enableLive 开关,同时还需要指定config#setLiveUrl 推流地址。当收到AVChatStateObserver#onCallEstablished 后,主播就开始推流, 观众即可使用拉流播放器观看主播直播。 如果主播开始加入房间的时候不想开启推流,则可以仅需指定config#setLiveUrl 推流地址,后续通过实时开启互动直播接口 AVChatManager#startLive 进行动态的控制。
  • 连麦观众加入房间需要打开config#enableLive 开关,同时不能指定config#setLiveUrl 推流地址。当连麦观众成功加入房间后,其他观众就可以观看到双人连麦互动直播。
  • 连麦画中画混屏模式设置 config#setLivePIPMode , 目前支持多种混屏模式设置,可以参考 AVChatLivePIPMode混屏模式图文介绍
  • 互动直播服务器录制设置 config#enableLiveServerRecord , 需要后台开通相关业务。

实时动态开启互动直播

加入房间的时候不开启互动直播, 中途动态的调用此接口开启互动直播。 如果想作为主播开启互动直播,那么在加入房间的时候就必须指定 config#liveUrl 推流地址。 成功调用开启互动直播后,会有相应的回调通知 AVChatStateObserver#onStartLiveResult ,通过通知的返回码来最终确认互动直播是否开启成功。

AVChatManager.getInstance().startLive();
/**
  * 调用 {@link AVChatManager#startLive()} 后的结果返回
  *
  * @param code 相关错误码
  * @see com.netease.nimlib.sdk.avchat.constant.AVChatResCode.LiveCode
*/
void onStartLiveResult(int code);

实时动态关闭互动直播

主播和连麦观众可以实时动态的关闭直播推流。 成功调用关闭互动直播后,会有相应的回调通知 AVChatStateObserver#onStopLiveResult ,通过通知的返回码来最终确认互动直播是否关闭成功。

AVChatManager.getInstance().stopLive();
/**
  * 调用 {@link AVChatManager#stopLive()} 后的结果返回
  *
  * @param code 相关错误码
  * @see com.netease.nimlib.sdk.avchat.constant.AVChatResCode.LiveCode
*/
void onStopLiveResult(int code);

实时更新推流地址

在互动直播的过程中可以实时的更新推流地址。如果加入房间时就没有指定推流地址,那么后续也不能实时更新推流地址。

AVChatManager.getInstance().setParameters(AVChatParameters params);

AVChatParameters 参数:

  • KEY_SESSION_LIVE_URL 推流地址。

互动直播音视频控制

互动直播基本继承了多人音视频的控制操作,主要介绍下重要的几个操作:

  • 动态切换清晰度
AVChatManager.getInstance().setParameters(AVChatParameters params);

AVChatParameters 参数: KEY_VIDEO_QUALITY

  • 实时设置视频最大码率
AVChatManager.getInstance().setParameters(AVChatParameters params);

AVChatParameters 参数: KEY_VIDEO_MAX_BITRATE

  • 实时设置视频编解码模式
AVChatManager.getInstance().setParameters(AVChatParameters params);

AVChatParameters 参数: KEY_VIDEO_ENCODER_MODEKEY_VIDEO_DECODER_MODE

  • 静音操作
AVChatManager.getInstance().muteLocalAudio(true);
  • 切换摄像头
AVChatManager.getInstance().switchCamera;

连麦开始与结束通知

  • 加入房间以后,收到其他用户加入的通知 AVChatStateObserver#onUserJoined ,表示连麦开始。
  • 房间里的用户收到其他用户离开的通知 AVChatStateObserver#onUserLeave ,表示连麦结束,此时连麦者应该主动退出房间,主播不要退出房间。

离开互动直播房间

离开一个已经加入的互动直播房间。

AVChatManager.getInstance().leaveRoom(new AVChatCallback<Void>() {}

直播状态监听

实现 AVChatStateObserver 监听直播过程中状态变化。SDK 自动进行互动直播服务器连接,将主播和连麦者的状态变化返回相应信息供上层应用使用。

public class BaseActivity implements AVChatStateObserver {
AVChatManager.getInstance().observeAVChatState(this, register);
}

当前互动直播服务器连接回调

首先返回服务器连接是否成功的回调 onJoinedChannel,并通过返回的 result code 做相应的处理。

参数 code 返回加入频道是否成功。常见错误码参考 JoinChannelCode 参数 filePath fileName 在开启服务器录制的情况下返回录制文件的保存路径。

@Override
public void onJoinedChannel(int code, String filePath, String fileName) { }

加入当前互动直播频道用户帐号回调

其他用户互动直播服务器连接成功后,会回调 onUserJoined,可以获取当前通话的用户帐号。

@Override
public void onUserJoined(String account) {}

当前用户离开频道回调

互动直播过程中,若主播或者连麦者离开,则会回调 onUserLeave

// @param event   -1,用户超时离开  0,正常退出
@Override
public void onUserLeave(String account, int event) {}

自己成功离开频道回调

@Override
public void onLeaveChannel() {}

版本协议不兼容回调

若互动直播时主播和连麦者的软件版本不兼容,则会回调 onProtocolIncompatible

// @param status 0 自己版本过低  1 对方版本过低
@Override
public void onProtocolIncompatible(int status) {}

服务器断开回调

互动直播过程中,服务器断开,会回调 onDisconnectServer

@Override
public void onDisconnectServer() {}

当前互动直播网络状况回调

互动直播过程中网络状态发生变化,会回调 onNetworkQuality

// @param value 0~3 ,the less the better; 0 : best; 3 : worst
@Override
public void onNetworkQuality(String account, int value) {}

互动直播连接成功建立回调

互动直播连接建立,会回调 onCallEstablished。音频切换到音频直播的界面,并开始计时等处理。视频则通过为用户设置对应画布并添加到相应的 layout 上显示图像。

@Override
public void onCallEstablished() {
if (state == AVChatTypeEnum.AUDIO.getValue()) {
aVChatUIManager.onCallStateChange(CallStateEnum.AUDIO);
} else {
aVChatUIManager.initSmallSurfaceView();
aVChatUIManager.onCallStateChange(CallStateEnum.VIDEO);
}
isCallEstablished = true;
}

互动直播音视频设备状态通知

当音视频设备状态发生改变时,会回调 onDeviceEvent

@Override
public void onDeviceEvent(String account, int code, String desc) {}

截图结果回调

用户执行截图后会回调 onTakeSnapshotResult

@Override
public void onTakeSnapshotResult(String account, boolean success, String file) {}

本地网络类型发生改变回调

本地客户端网络类型发生改变时回调,会通知当前网络类型。

@Override
public void onConnectionTypeChanged(int netType) {}

音视频录制回调

当主播或者连麦者录制音视频结束时回调,会通知录制的用户id和录制文件路径。

@Override
void onAVRecordingCompletion(String account, String filePath) {}

当主播或者连麦者录制语音结束时回调,会通知录制文件路径。

@Override
void onAudioRecordingCompletion(String filePath) {}

当存储空间不足时的警告回调,存储空间低于20M时开始出现警告,出现警告时请及时关闭所有的录制服务,当存储空间低于10M时会自动关闭所有的录制。

@Override
void onLowStorageSpaceWarning(long availableSize) {}

用户第一帧画面通知

当主播或者连麦者第一帧视频画面绘制前通知。

@Override
public void onFirstVideoFrameAvailable(String account) {}

用户第一帧画面绘制后通知

当主播或者连麦者第一帧视频画面绘制后通知。

@Override
public void onFirstVideoFrameRendered(String user) {}

用户视频画面分辨率改变通知

当主播或者连麦者视频画面的分辨率改变时通知。

@Override
public void onVideoFrameResolutionChanged(String user, int width, int height, int rotate) {}

用户视频帧率汇报

实时汇报主播或者连麦者的视频绘制帧率。

@Override
public void onVideoFpsReported(String account, int fps) {}

采集视频数据回调

当用户开始外部视频处理后,采集到的视频数据通过回调通知。 用户可以对视频数据做相应的美颜等不同的处理。 需要通过setParameters开启视频数据处理。

@Override
public boolean onVideoFrameFilter(AVChatVideoFrame frame) {}

采集语音数据回调

当用户开始外部语音处理后,采集到的语音数据通过回调通知。 用户可以对语音数据做相应的变声等不同的处理。需要通过setParameters开启语音数据处理。

@Override
public boolean onAudioFrameFilter(AVChatAudioFrame frame) {}

语音播放设备变化通知

当用户切换扬声器或者耳机的插拔等操作时, 语音的播放设备都会发生变化通知。 语音设备参考 AVChatAudioDevice

@Override
public void onAudioOutputDeviceChanged(int device) {}

语音正在说话用户声音强度通知

正在说话用户的语音强度回调,包括自己和其他用户的声音强度。如果一个用户没有说话,或者说话声音小没有被参加到混音,那么这个用户的信息不会在回调中出现。

@Override
void onReportSpeaker(Map<String, Integer> speakers, int mixedEnergy) {}

伴音事件通知

当伴音出错或者结束时,通过此回调进行通知

@Override
void onAudioMixingEvent(int event) {}

通话中的设备控制

直播进行中,可以进行设备静音,扬声器,摄像头切换,开关摄像头和切换通话模式的设置。

通话中实时设置参数

在通话过程中, 可以实时设置部分参数。用户可以通过这些参数进行软硬件编解码切换,清晰度切换等。

AVChatParameters params = new AVChatParameters();
params.setBoolean(AVChatParameters.KEY_VIDEO_FPS_REPORTED, false);
AVChatManager.getInstance().setParameters(params);

直播中实时获取参数

在直播过程中, 可以实时获取部分参数。

AVChatParameters params = new AVChatParameters();
params.requestKey(AVChatParameters.KEY_VIDEO_FPS_REPORTED);
AVChatManager.getInstance().getParameters(params);

设置视频绘制画布

目前支持两种画布方式,使用内置的 AVChatVideoRender 或者自定义实现 AVChatExternalVideoRender

当设置 AVChatVideoRender 为用户的视频画布时,同时还可以制定画布是否镜像处理,以及相应的缩放模式。

当设置 AVChatExternalVideoRender 为用户的视频画布时,镜像和缩放方式会忽略,用户需要自己去绘制 I420 原始视频数据。

对于交换用户画布的操作,需要先把当前用户的画布解除,通过此接口传入null即可解除,然后再设置新的画布。

如果需要启动开启会话后立即预览本地视频数据,在加入直播前调用 setupLocalVideoRender 即可。

// 设置本地用户视频画布
AVChatManager.getInstance().setupLocalVideoRender(IVideoRender render, boolean mirror, int scalingType);
// 设置远端用户视频画布
AVChatManager.getInstance().setupRemoteVideoRender(String account, IVideoRender render, boolean mirror, int scalingType);

设置本地语音流静音

将设置本地发送语音是否静音。

AVChatManager.getInstance().muteLocalAudio(true);

设置远端用户语音流静音

将设置是否播放其他用户的语音数据。

AVChatManager.getInstance().muteRemoteAudio(account, true);

设置本地视频流静音

设置本地视频数据是否发送。

AVChatManager.getInstance().muteLocalVideo(true);

设置远端用户视频流静音

将设置是否绘制远端用户的视频数据。

AVChatManager.getInstance().muteRemoteVideo(account, true);

设置扬声器

// 设置扬声器是否开启
AVChatManager.getInstance().setSpeaker(!AVChatManager.getInstance().speakerEnabled());

切换摄像头

// 切换摄像头(主要用于前置和后置摄像头切换)
AVChatManager.getInstance().switchCamera();

客户端本地录制音视频数据

直播进行中,可以录制用户的音频和视频数据, 文件将以MP4格式保存在客户端本地, 也可以录制所有用户的语音数据,录音文件格式为wav,文件保存在客户端本地。程序卸载时录制的本地文件也会随程序一并删除。

客户端本地开始音视频录制接口,通过返回值判断是否调用成功。录制account的音频和视频文件,前后摄像头切换时录制文件可能存在多个。

// 开始录制用户的音视频数据
AVChatManager.getInstance().startAVRecording(account);

客户端本地停止音视频录制接口, 停止录制后将会通过回调函数返回结果。

// 停止录制用户的音视频数据
AVChatManager.getInstance().stopAVRecording(account);

客户端本地开始录音接口,包含所有用户的语音数据,录音文件格式为wav,文件保存在客户端本地。

// 开始录音
AVChatManager.getInstance().startAudioRecording();

客户端本地停止录音接口,停止录制后将会通过回调函数返回结果。

// 停止录音
AVChatManager.getInstance().stopAudioRecording();

录制结束后会通过网络会话状态通知告知。

/**
  * 用户音视频数据录制结束
  *
  * @param account 用户账号
  * @param filePath 录制文件路径,当发生视频清晰度等情况时会存在多个MP4文件
*/
void onAVRecordingCompletion(String account, String filePath);
/**
  * 语音录制结束
  *
  * @param filePath 录制语音文件路径
*/
void onAudioRecordingCompletion(String filePath);
/**
  * 存储空间不足警告,存储空间低于20M时开始出现警告,出现警告时请及时关闭所有的录制服务,当存储空间低于10M时会自动关闭所有的录制功能
  *
  * @param availableSize 可用空间
*/
void onLowStorageSpaceWarning(long availableSize);

视频画面截图

截取指定用户的视频画面, 截图结果将会通过 onTakeSnapshotResult 回调通知。

AVChatManager.getInstance().takeSnapshot(account);

多人模式观众角色设置

是否打开观众角色, 设置观众角色后所有的语音和视频数据的采集和发送会关闭,仅允许接收和播放远端其他用户的数据。

AVChatManager.getInstance().enableAudienceRole(true);

本地语音伴音

在通话过程中,可以指定本地音频文件来和麦克风采集的音频流进行混音或者替换。

开启本地语音伴音,可以通过参数指定是否循环,替换本地语音,以及初始音量[0.0f - 1.0f]。 伴音的状态通知 onDeviceEvent,事件类型为 AUDIO_MIXING_ERROR 以及 AUDIO_MIXING_FINISHED.

AVChatManager.getInstance().startAudioMixing(filePath, loopback, replace, cycle, volume);

暂停本地语音伴音

AVChatManager.getInstance().pauseAudioMixing();

恢复本地语音伴音,伴音文件将从暂停时位置开始播放

AVChatManager.getInstance().resumeAudioMixing();

停止本地语音伴音

AVChatManager.getInstance().stopAudioMixing();

本地语音伴音音量,通过参数 KEY_AUDIO_MIXING_STREAM_VOLUME 来调整伴音音量,音量范围[0.0f - 1.0f].

AVChatManager.getInstance().setParameters(param);

互动直播可选参数

互动直播可选参数分为直播前可设置参数以及直播过程中可设置参数。 直播前参数设置主要为 AVChatOptionalConfig ,直播过程中参数设置为 AVChatParameters

直播前可选设置参数

主要介绍各种参数的含义以及默认值,在加入会话时进行设置。

  • AVChatOptionalConfig#enableCallProximity(true), 是否打开语音通话时距离感应器。
  • AVChatOptionalConfig#enableVideoRotate(true), 是否打开视频图像根据设备角度自动旋转。
  • AVChatOptionalConfig#enableAudienceRole(true), 多人通话是否观众角色进入。
  • AVChatOptionalConfig#enableServerRecordAudio(false), 是否打开服务器录制音频,服务器录制需要开通相关业务。
  • AVChatOptionalConfig#enableServerRecordVideo(false), 是否打开服务器录制视频,服务器录制需要开通相关业务。
  • AVChatOptionalConfig#setDefaultFrontCamera(true), 是否默认采用前置摄像头通话。
  • AVChatOptionalConfig#setVideoQuality(QUALITY_DEFAULT), 设置期望的视频清晰度。
  • AVChatOptionalConfig#enableVideoFpsReported(true), 是否允许进行帧率汇报。
  • AVChatOptionalConfig#setVideoMaxBitrate(0), 设置视频最大码率,默认采用预先配置。
  • AVChatOptionalConfig#setVideoFrameRate(15), 设置期望的视频帧率。
  • AVChatOptionalConfig#setAudioEffectNSMode(PLATFORM_BUILTIN), 设置优先使用的降噪模块。
  • AVChatOptionalConfig#setAudioEffectAECMode(PLATFORM_BUILTIN), 设置优先使用的回音消除模块。
  • AVChatOptionalConfig#setDefaultDeviceRotation(0), 设置设备默认的旋转角度。
  • AVChatOptionalConfig#setDeviceRotationFixedOffset(0), 设置设备需要修正的旋转角度。
  • AVChatOptionalConfig#setVideoEncoderMode(MEDIA_CODEC_AUTO), 设置采用的视频编码器。
  • AVChatOptionalConfig#enableLive(false), 是否允许互动直播。
  • AVChatOptionalConfig#setLiveUrl(null), 设置互动直播的推流地址。
  • AVChatOptionalConfig#setLivePIPMode(PIP_FLOATING_RIGHT_VERTICAL), 设置互动直播时进行连麦混屏模式。
  • AVChatOptionalConfig#setVideoDecoderMode(MEDIA_CODEC_AUTO), 设置采用的视频解码器。
  • AVChatOptionalConfig#enableAudioHighQuality(false), 设置是否采用高清语音模式。
  • AVChatOptionalConfig#enableAudioDtx(true), 设置是否采用DTX编码发送。
  • AVChatOptionalConfig#enableLiveServerRecord(false), 是否打开互动直播服务器录制, 需要开通相关业务。

直播时可选设置参数

在通话过程中进行设置。

AVChatManager.getInstance().setParameters(param);;
  • AVChatParameters#KEY_VIDEO_ENCODER_MODE, 视频编码器。
  • AVChatParameters#KEY_VIDEO_DECODER_MODE, 视频解码器。
  • AVChatParameters#KEY_VIDEO_CROP_BEFORE_SEND, 视频发送前是否裁剪。
  • AVChatParameters#KEY_VIDEO_ROTATE_BEFORE_RENDING, 视频绘制时是否自动旋转。
  • AVChatParameters#KEY_VIDEO_FPS_REPORTED, 是否汇报帧率。
  • AVChatParameters#KEY_VIDEO_MAX_BITRATE, 设置视频最大码率。
  • AVChatParameters#KEY_VIDEO_QUALITY, 设置视频清晰度。
  • AVChatParameters#KEY_KEY_SESSION_LIVE_URL, 设置互动直播推流地址。
  • AVChatParameters#KEY_VIDEO_FRAME_FILTER, 是否需要外部额外处理视频数据,美颜等。
  • AVChatParameters#KEY_AUDIO_FRAME_FILTER, 是否需要外部额外处理音频数据,变声等。
  • AVChatParameters#KEY_AUDIO_REPORT_SPEAKER, 是否需要汇报正在说话用户声音强度。
  • AVChatParameters#KEY_AUDIO_MIXING_STREAM_VOLUME, 调整伴音音量。

互动直播其它接口

权限检查

在Android 6.0 及以上系统中提供网络通话权限检查,需要保证所有权限获取后再进行网络通话。

//返回缺失的权限
AVChatManager.getInstance().checkPermission(context);

网络探测

在通话前或者通话中进行网络探测,用于检测用户当前网络的接入质量。

开启网络探测,返回本次任务的ID

AVChatNetDetector.startNetDetect(callback);

结束网络探测,传入探测任务ID

AVChatNetDetector.stopNetDetect(id);

网络探测结果通知

AVChatNetDetectCallback#onDetectResult( ... );

网络探测情况分级表

在结果信息中,lossRate、rttAverage、rttMeanDeviation这三个值最能反应当前客户端的实际网络情况。由这三个值可以计算出当前的网络状况指数:

网络状况指数 = (lossRate/20)*50% +(rttAverage/1200)*25% +(rttMeanDeviation/150)*25%

经过我们的反复测试,现提供三个网络状况指数节点

| 网络状况指数节点 | lossRate(%)| rttAverage(ms) |rttMeanDeviation(ms) |网络状况指数| | :--------: | :--------:| :--: |:--:|:--:| | A | 3 | 500 |50|0.2625| | B | 10 | 800 |80|0.55| | C | 20 | 1200 |150|1|

备注:

  1. 当网络状况指数≤0.2625时,网络状况非常好,音视频通话流畅;
  2. 当0.2625<网络状况指数≤0.55时,网络状况好,音视频通话偶有卡顿;
  3. 当0.55<网络状况指数≤1时,网络状况差,音频通话流畅;
  4. 当网络状况指数>1时,网络状况非常差,音频通话偶有卡顿。