AndroidAv_zh - aopacloud/aopa-rtc GitHub Wiki
本文介绍如何集成奥帕实时互动 SDK,通过少量代码从 0 开始实现一个简单的实时互动 App,适用于互动直播和视频通话场景。
首先,你需要了解以下有关音视频实时互动的基础概念:
- 奥帕实时互动 SDK:由奥帕开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
- 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
- 观众:可以在频道内订阅音视频,不具备发布音视频权限。
下图展示在 App 中实现音视频互动的基本工作流程:
- 所有用户调用
joinChannel
方法加入频道,并根据需要设置用户角色:- 互动直播:如果用户需要在频道中发流,则设为主播;如果用户只需要收流,则设为观众。
- 视频通话:将所有的用户角色都为主播。
- 加入频道后,不同角色的用户具备不同的行为:
- 所有用户默认都可以接收频道中的音视频流。
- 主播可以在频道内发布音视频流。
- 观众如果需要发流,可在频道内调用
setClientRole
方法修改用户角色,使其具备发流权限。
在实现功能以前,请按照以下要求准备开发环境:
- Android Studio 4.1 以上版本。
- Android API 级别 16 或以上。
- 两台运行 Android 4.1 或以上版本的移动设备。
- 可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考[应对防火墙限制]以正常使用奥帕服务。
- 一个有效的奥帕账号以及奥帕项目。请参考[开通服务]从奥帕控制台获得以下信息:
- App ID:奥帕随机生成的字符串,用于识别你的项目。
- 临时 Token:Token 也称为动态密钥,在客户端加入频道时对用户鉴权。临时 Token 的有效期为 24 小时。
本小节介绍如何创建项目并为项目添加体验实时互动所需的权限。
-
(可选) 创建新项目。详见 Create a project。
-
打开 Android Studio,选择 New Project。
-
选择 Phone and Tablet > Empty Views Activity,点击 Next。
-
设置项目名称和存储路径,选择语言为 Java,点击 Finish 创建 Android 项目。
注意
创建项目后,Android Studio 会自动开始同步 gradle,稍等片刻至同步成功后再进行下一步操作。
-
-
添加网络及设备权限。
打开
/app/src/main/AndroidManifest.xml
文件,在</application>
后面添加如下权限:XML
<!--必要权限--> <uses-permission android:name="android.permission.INTERNET"/> <!--可选权限--> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <!-- 对于 Android 12.0 及以上且集成 v4.1.0 以下 SDK 的设备,还需要添加以下权限 --> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> <!-- 对于 Android 12.0 及以上设备,还需要添加以下权限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
-
防止代码混淆。
打开
/app/proguard-rules.pro
文件,添加如下行以防止奥帕 SDK 的代码被混淆:Java
-keep class io.aopa.**{*;}
你可以选用以下任一方式集成奥帕实时互动 SDK。
- 通过 Maven Central 集成
- 手动集成
-
打开项目根目录下的
settings.gradle
文件,添加 Maven Central 依赖 (如果已有可忽略):Java
repositories { ... mavenCentral() ...}
注意
如果你的 Android 项目设置了 dependencyResolutionManagement,添加 Maven Central 依赖的方式可能存在差异。
-
打开
/app/build.gradle
文件,在dependencies
中添加奥帕 RTC SDK 的依赖。你可以从[发版说明])中查询 SDK 的最新版本,并将x.y.z
替换为具体的版本号。Java
... dependencies { ... // x.y.z 替换为具体的 SDK 版本号,如:4.0.0 或 4.1.0-1 implementation 'io.aopa.rtc:full-sdk:x.y.z'}
-
在[下载]页面下载最新版本的 Android 实时互动 SDK,并解压。
-
打开解压文件,将以下文件或子文件夹复制到你的项目路径中。
文件或子文件夹 项目路径 aopa-rtc-sdk.aar
文件/app/libs/
arm64-v8a
文件夹/app/src/main/jniLibs/
armeabi-v7a
文件夹/app/src/main/jniLibs/
-
在 Android Studio 的左侧导航栏上选择
Project Files/app/libs/aopa-rtc-sdk.jar
文件,右键单击,在下拉菜单中选择add as a library
。
根据实时音视频互动的场景需要,为你的项目创建两个视图框,分别用于展示本地视频和远端视频。如下图所示:
复制以下代码到 /app/src/main/res/layout/activity_main.xml
文件中替换原有内容,即可快速创建场景所需的用户界面。
创建用户界面示例代码
XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/RoomIDTextView"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_marginLeft="50dp"
android:layout_marginTop="40dp"
android:text="房间ID"
android:textColor="@android:color/black" />
<EditText
android:id="@+id/RoomIdEditText"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignTop="@+id/RoomIDTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/RoomIDTextView"
android:gravity="left"
android:inputType="text"
android:singleLine="true"
android:text="room123"
android:textColor="@android:color/black"
android:textSize="15dp" />
<TextView
android:id="@+id/AppIDTextView"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_below="@+id/RoomIDTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="APP ID"
android:textColor="@android:color/black" />
<Spinner
android:id="@+id/AppIdSpinner"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignTop="@+id/AppIDTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/AppIDTextView"
android:gravity="center"
android:textSize="15dp" />
<TextView
android:id="@+id/UserIdTextView"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_below="@+id/AppIDTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="用户ID"
android:textColor="@android:color/black" />
<EditText
android:id="@+id/UserIdEditText"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignTop="@+id/UserIdTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/UserIdTextView"
android:gravity="left"
android:inputType="text"
android:singleLine="true"
android:text=""
android:textColor="@android:color/black"
android:textSize="15dp" />
<TextView
android:id="@+id/ServerAddrTextView"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_below="@+id/UserIdTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="服务环境"
android:textColor="@android:color/black"
android:visibility="visible"
tools:visibility="visible" />
<Spinner
android:id="@+id/ServerSpinner"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignTop="@+id/ServerAddrTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/ServerAddrTextView"
android:gravity="center"
android:textSize="15dp"
android:visibility="visible" />
<TextView
android:id="@+id/AudioQualityTextView"
android:textColor="@android:color/black"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_below="@+id/ServerAddrTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="音质" />
<Spinner
android:id="@+id/AudioQualitySpinner"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignTop="@+id/AudioQualityTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/AudioQualityTextView"
android:gravity="center"
android:textSize="15dp" />
<TextView
android:id="@+id/ScenarioTextView"
android:textColor="@android:color/black"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_below="@+id/AudioQualityTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="场景" />
<Spinner
android:id="@+id/ScenarioSpinner"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_toRightOf="@+id/ScenarioTextView"
android:layout_alignTop="@+id/ScenarioTextView"
android:layout_alignParentRight="true"
android:layout_marginRight="50dp"
android:layout_marginTop="-6dp"
android:textSize="15dp"
android:gravity="center" />
<TextView
android:id="@+id/RoleTextView"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_below="@+id/ScenarioTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="角色"
android:textColor="@android:color/black" />
<Spinner
android:id="@+id/RoleSpinner"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignTop="@+id/RoleTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/RoleTextView"
android:gravity="center"
android:textSize="15dp" />
<TextView
android:id="@+id/ResolutionTextView"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_below="@+id/RoleTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="采集分辨率"
android:textColor="@android:color/black" />
<Spinner
android:id="@+id/ResolutionSpinner"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignTop="@+id/ResolutionTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/ResolutionTextView"
android:gravity="center"
android:textSize="15dp" />
<TextView
android:id="@+id/CustomServerTextView"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_below="@+id/ResolutionSpinner"
android:layout_marginLeft="50dp"
android:layout_marginTop="4dp"
android:text="自定义服务"
android:textColor="@android:color/black" />
<EditText
android:id="@+id/CustomServerEditText"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignTop="@+id/CustomServerTextView"
android:layout_alignParentRight="true"
android:layout_marginTop="-6dp"
android:layout_marginRight="50dp"
android:layout_toRightOf="@+id/CustomServerTextView"
android:gravity="left"
android:inputType="text"
android:singleLine="true"
android:text=""
android:textColor="@android:color/black"
android:textSize="15dp"
android:tooltipText="输入服务器IP"
android:visibility="invisible"
tools:visibility="visible" />
<CheckBox
android:id="@+id/TokenCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_below="@+id/CustomServerTextView"
android:layout_marginLeft="50dp"
android:layout_marginTop="10dp"
android:text="Token校验" />
<CheckBox
android:id="@+id/StatsCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_alignTop="@+id/TokenCheckBox"
android:layout_marginLeft="10dp"
android:layout_marginTop="0dp"
android:layout_toRightOf="@+id/TokenCheckBox"
android:text="统计数据" />
<CheckBox
android:id="@+id/SpeakerCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_below="@+id/TokenCheckBox"
android:layout_marginLeft="50dp"
android:layout_marginTop="10dp"
android:text="扬声器输出" />
<CheckBox
android:id="@+id/QuicCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_alignTop="@+id/SpeakerCheckBox"
android:layout_marginLeft="10dp"
android:layout_marginTop="0dp"
android:layout_toRightOf="@+id/InearCheckBox"
android:text="QUIC" />
<CheckBox
android:id="@+id/InearCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_below="@+id/SpeakerCheckBox"
android:layout_marginLeft="50dp"
android:layout_marginTop="10dp"
android:text="耳返" />
<CheckBox
android:id="@+id/ReverbCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_alignTop="@+id/InearCheckBox"
android:layout_marginLeft="10dp"
android:layout_marginTop="0dp"
android:layout_toRightOf="@+id/InearCheckBox"
android:text="混响" />
<CheckBox
android:id="@+id/AAACheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_below="@+id/InearCheckBox"
android:layout_marginLeft="50dp"
android:layout_marginTop="5dp"
android:text="3A处理" />
<CheckBox
android:id="@+id/VideoCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_below="@+id/InearCheckBox"
android:layout_alignLeft="@+id/ReverbCheckBox"
android:layout_marginTop="5dp"
android:text="开启视频" />
<CheckBox
android:id="@+id/MulticastCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_below="@+id/AAACheckBox"
android:layout_marginLeft="50dp"
android:layout_marginTop="5dp"
android:text="大小视频" />
<CheckBox
android:id="@+id/DetachCheckBox"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_below="@+id/VideoCheckBox"
android:layout_alignLeft="@+id/VideoCheckBox"
android:layout_marginTop="5dp"
android:text="推拉流分离" />
<Button
android:id="@+id/RoomBnt"
android:layout_width="120dp"
android:layout_height="48dp"
android:layout_below="@id/MulticastCheckBox"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:onClick="onJoinClick"
android:text="多人视频" />
<Button
android:id="@+id/SingleBnt"
android:layout_width="120dp"
android:layout_height="48dp"
android:layout_below="@id/RoomBnt"
android:layout_centerHorizontal="true"
android:layout_marginTop="5dp"
android:onClick="onSingleClick"
android:text="单人视频" />
<TextView
android:id="@+id/VersionText"
android:layout_width="60dp"
android:layout_height="20dp"
android:singleLine="true"
android:text=""
android:textSize="14dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>
本小节介绍如何实现一个实时音视频互动 App。你可以先复制完整的示例代码到你的项目中,快速体验实时音视频互动的基础功能,再按照实现步骤了解核心 API 调用。
下图展示了使用奥帕 RTC SDK 实现音视频互动的基本流程:
下面列出了一段实现实时互动基本流程的完整代码以供参考。复制以下代码到 /app/src/main/java/com/example/<projectname>/MainActivity.java
文件中替换 package com.example.<projectname>
后的全部内容,即可快速体验实时互动基础功能。
信息
在 appId
、token
和 channelName
字段中传入你在控制台获取到的 App ID、临时 Token,以及生成临时 Token 时填入的频道名。
实现实时音视频互动示例代码
Java
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.SurfaceView;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import org.banban.rtc.ChannelMediaOptions;
import org.banban.rtc.Constants;
import org.banban.rtc.IRtcEngineEventHandler;
import org.banban.rtc.RtcEngine;
import org.banban.rtc.RtcEngineConfig;
import org.banban.rtc.video.VideoCanvas;
public class MainActivity extends AppCompatActivity {
// 填写声网控制台中获取的 App ID
private String appId = "<#Your App ID#>";
// 填写频道名
private String channelName = "<#Your channel name#>";
// 填写声网控制台中生成的临时 Token
private String token = "<#Your Token#>";
private RtcEngine mRtcEngine;
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
// 成功加入频道回调
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
super.onJoinChannelSuccess(channel, uid, elapsed);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "Join channel success", Toast.LENGTH_SHORT).show();
});
}
// 远端用户或主播加入当前频道回调
@Override
public void onUserJoined(int uid, int elapsed) {
runOnUiThread(() -> {
// 当远端用户加入频道后,显示指定 uid 的远端视频流
setupRemoteVideo(uid);
});
}
// 远端用户或主播离开当前频道回调
@Override
public void onUserOffline(int uid, int reason) {
super.onUserOffline(uid, reason);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "User offline: " + uid, Toast.LENGTH_SHORT).show();
});
}
};
private void initializeAndJoinChannel() {
try {
// 创建 RtcEngineConfig 对象,并进行配置
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getBaseContext();
config.mAppId = appId;
config.mEventHandler = mRtcEventHandler;
// 创建并初始化 RtcEngine
mRtcEngine = RtcEngine.create(config);
} catch (Exception e) {
throw new RuntimeException("Check the error.");
}
// 启用视频模块
mRtcEngine.enableVideo();
// 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象
FrameLayout container = findViewById(R.id.local_video_view_container);
SurfaceView surfaceView = new SurfaceView (getBaseContext());
container.addView(surfaceView);
// 将 SurfaceView 对象传入声网实时互动 SDK,设置本地视图
mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
// 开启本地预览
mRtcEngine.startPreview();
// 创建 ChannelMediaOptions 对象,并进行配置
ChannelMediaOptions options = new ChannelMediaOptions();
// 设置用户角色为 BROADCASTER (主播) 或 AUDIENCE (观众)
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// 设置频道场景为 BROADCASTING (直播场景)
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// 发布麦克风采集的音频
options.publishMicrophoneTrack = true;
// 发布摄像头采集的视频
options.publishCameraTrack = true;
// 自动订阅所有音频流
options.autoSubscribeAudio = true;
// 自动订阅所有视频流
options.autoSubscribeVideo = true;
// 使用临时 Token 和频道名加入频道,uid 为 0 表示引擎内部随机生成用户名
// 成功后会触发 onJoinChannelSuccess 回调
mRtcEngine.joinChannel(token, channelName, 0, options);
}
private void setupRemoteVideo(int uid) {
FrameLayout container = findViewById(R.id.remote_video_view_container);
SurfaceView surfaceView = new SurfaceView (getBaseContext());
surfaceView.setZOrderMediaOverlay(true);
container.addView(surfaceView);
// 将 SurfaceView 对象传入声网实时互动 SDK,设置远端视图
mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
}
private static final int PERMISSION_REQ_ID = 22;
private String[] getRequiredPermissions(){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
return new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.BLUETOOTH_CONNECT
};
} else {
return new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
};
}
}
private boolean checkPermissions() {
for (String permission : getRequiredPermissions()) {
int permissionCheck = ContextCompat.checkSelfPermission(this, permission);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (checkPermissions()) {
initializeAndJoinChannel();
} else {
ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (checkPermissions()) {
initializeAndJoinChannel();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mRtcEngine != null) {
mRtcEngine.stopPreview();
mRtcEngine.leaveChannel();
mRtcEngine = null;
RtcEngine.destroy();
}
}
}
本小节介绍如何导入 Android 相关的类并获取 Android 设备的摄像头、录音等权限。
-
导入 Android 相关的类
Java
import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.SurfaceView; import android.widget.FrameLayout; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat;
-
获取 Android 权限
启动应用程序时,检查是否已在 App 中授予了实现实时互动所需的权限。
Java
private static final int PERMISSION_REQ_ID = 22; private String[] getRequiredPermissions(){ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { return new String[]{ Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE, Manifest.permission.BLUETOOTH_CONNECT }; } else { return new String[]{ Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA }; } } private boolean checkPermissions() { for (String permission : getRequiredPermissions()) { int permissionCheck = ContextCompat.checkSelfPermission(this, permission); if (permissionCheck != PackageManager.PERMISSION_GRANTED) { return false; } } return true; }
导入奥帕 RTC SDK 相关的类和接口:
Java
import org.banban.rtc.ChannelMediaOptions;
import org.banban.rtc.Constants;
import org.banban.rtc.IRtcEngineEventHandler;
import org.banban.rtc.RtcEngine;
import org.banban.rtc.RtcEngineConfig;
import org.banban.rtc.video.VideoCanvas;
传入从奥帕控制台获取的 App ID、临时 Token,以及生成临时 Token 时填入的频道名,用于后续初始化引擎和加入频道。
Java
// 填写奥帕控制台中获取的 App ID
private String appId = "<#Your App ID#>";
// 填写频道名
private String channelName = "<#Your channel name#>";
// 填写奥帕控制台中生成的临时 Token
private String token = "<#Your Token#>";
调用 create
方法初始化 RtcEngine
。
注意
在初始化 SDK 前,需确保终端用户已经充分了解并同意相关的隐私政策。
Java
private RtcEngine mRtcEngine;
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
...};
// 创建 RtcEngineConfig 对象,并进行配置
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getBaseContext();
config.mAppId = appId;
config.mEventHandler = mRtcEventHandler;
// 创建并初始化 RtcEngine
mRtcEngine = RtcEngine.create(config);
按照以下步骤启用视频模块:
- 调用
enableVideo
方法,启用视频模块。 - 调用
setupLocalVideo
方法初始化本地视图,同时设置本地的视频显示属性。 - 调用
startPreview
方法,开启本地视频预览。
Java
// 启用视频模块
mRtcEngine.enableVideo();
// 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象
FrameLayout container = findViewById(R.id.local_video_view_container);
SurfaceView surfaceView = new SurfaceView (getBaseContext());
container.addView(surfaceView);
// 将 SurfaceView 对象传入奥帕实时互动 SDK,设置本地视图
mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
// 开启本地预览
mRtcEngine.startPreview();
调用 joinChannel
加入频道。在 ChannelMediaOptions
中进行如下配置:
- 设置频道场景为
BROADCASTING
(直播场景) 并设置用户角色设置为BROADCASTER
(主播) 或AUDIENCE
(观众)。 - 将
publishMicrophoneTrack
和publishCameraTrack
设置为true
,发布麦克风采集的音频和摄像头采集的视频。 - 将
autoSubscribeAudio
和autoSubscribeVideo
设置为true
,自动订阅所有音视频流。
Java
// 创建 ChannelMediaOptions 对象,并进行配置
ChannelMediaOptions options = new ChannelMediaOptions();
// 设置用户角色为 BROADCASTER (主播) 或 AUDIENCE (观众)
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// 设置频道场景为 BROADCASTING (直播场景)
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// 发布麦克风采集的音频
options.publishMicrophoneTrack = true;
// 发布摄像头采集的视频
options.publishCameraTrack = true;
// 自动订阅所有音频流
options.autoSubscribeAudio = true;
// 自动订阅所有视频流
options.autoSubscribeVideo = true;
// 使用临时 Token 和频道名加入频道,uid 为 0 表示引擎内部随机生成用户名
// 成功后会触发 onJoinChannelSuccess 回调
mRtcEngine.joinChannel(token, channelName, 0, options);
调用 setupRemoteVideo
方法初始化远端用户视图,同时设置远端用户的视图在本地显示属性。你可以通过 onUserJoined
回调获取远端用户的 uid
。
Java
private void setupRemoteVideo(int uid) {
FrameLayout container = findViewById(R.id.remote_video_view_container);
SurfaceView surfaceView = new SurfaceView (getBaseContext());
surfaceView.setZOrderMediaOverlay(true);
container.addView(surfaceView);
// 将 SurfaceView 对象传入声网实时互动 SDK,设置远端视图
mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
}
根据使用场景,定义必要的回调。以下示例代码展示如何实现 onJoinChannelSuccess
、 onUserJoined
和 onUserOffline
回调。
Java
// 成功加入频道回调
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
super.onJoinChannelSuccess(channel, uid, elapsed);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "Join channel success", Toast.LENGTH_SHORT).show();
});
}
// 远端用户或主播加入当前频道回调
@Override
public void onUserJoined(int uid, int elapsed) {
runOnUiThread(() -> {
// 当远端用户加入频道后,显示指定 uid 的远端视频流
setupRemoteVideo(uid);
});
}
// 远端用户或主播离开当前频道回调
@Override
public void onUserOffline(int uid, int reason) {
super.onUserOffline(uid, reason);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "User offline: " + uid, Toast.LENGTH_SHORT).show();
});
}
在 onCreate
中调用一系列方法加载界面布局、检查 App 是否获取实时互动所需权限,并加入频道开始音视频互动。
Java
// 成功加入频道回调
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
super.onJoinChannelSuccess(channel, uid, elapsed);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "Join channel success", Toast.LENGTH_SHORT).show();
});
}
// 远端用户或主播加入当前频道回调
@Override
public void onUserJoined(int uid, int elapsed) {
runOnUiThread(() -> {
// 当远端用户加入频道后,显示指定 uid 的远端视频流
setupRemoteVideo(uid);
});
}
// 远端用户或主播离开当前频道回调
@Override
public void onUserOffline(int uid, int reason) {
super.onUserOffline(uid, reason);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "User offline: " + uid, Toast.LENGTH_SHORT).show();
});
}
按照以下步骤结束音视频互动:
-
调用
stopPreview
停止视频预览。 -
调用
leaveChannel
离开当前频道,释放所有会话相关的资源。 -
调用
destroy
销毁引擎,并释放奥帕 SDK 中使用的所有资源。警告
调用
destroy
后,你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能,你必须重新创建一个新的引擎。详见初始化引擎。Java
@Override protected void onDestroy() { super.onDestroy(); if (mRtcEngine != null) { // 停止本地视频预览 mRtcEngine.stopPreview(); // 离开频道 mRtcEngine.leaveChannel(); mRtcEngine = null; // 销毁引擎 RtcEngine.destroy(); } }
按照以下步骤测试直播 App:
-
开启 Android 设备的开发者选项,打开 USB 调试,通过 USB 连接线将 Android 设备接入电脑,并在 Android 设备选项中勾选你的 Android 设备。
-
在 Android Studio 中,点击 (Sync Project with Gradle Files) 进行 Gradle 同步。
-
待同步成功后,点击 (Run 'app') 开始编译。片刻后,App 便会安装到你的 Android 设备上。
-
启动 App,授予录音和摄像头权限,如果你将用户角色设置为主播,便会在本地视图中看到自己。
-
使用第二台 Android 设备,重复以上步骤,在该设备上安装 App、打开 App 加入频道,观察测试结果:
- 如果两台设备均作为主播加入频道,则可以看到对方并且听到对方的声音。
- 如果两台设备分别作为主播和观众加入,则主播可以在本地视频窗口看到自己;观众可以在远端视频窗口看到主播、并听到主播的声音。
在完成音视频互动后,你可以阅读以下文档进一步了解:
- 本文的示例使用了临时 Token 加入频道。在测试或生产环境中,为保证通信安全,奥帕推荐从服务器中获取 Token,详情请参考[使用 Token 鉴权]。
- 如果你想要实现极速直播场景,可以在实时音视频互动的基础上,通过修改观众端的延时级别为低延时 (
AUDIENCE_LATENCY_LEVEL_LOW_LATENCY
) 实现。详见[实现极速直播]
本节提供了额外的信息供参考。
奥帕提供了开源的实时音视频互动示例项目供你参考,你可以前往下载或查看其中的源代码。
- GitHub: AopaRtcAndroid