声网实现视频会议(二)
实现视频通话
本节介绍如何实现视频通话。视频通话的 API 调用时序见下图:
1. 创建用户界面
根据场景需要,为你的项目创建视频通话的用户界面。若已有界面,可以直接查看导入类。
我们推荐你添加如下 UI 元素来实现一个视频通话,
- 本地视频窗口
- 远端视频窗口
- 结束通话按钮
你也可以参考 Agora-Android-Tutorial-1to1 示例项目的 activity_video_chat_view.xml 文件中的代码。
创建 UI 示例
2. 导入类
在项目的 Activity 文件中添加如下行:
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
3. 获取设备权限
调用 checkSelfPermission
方法,在开启 Activity 时检查并获取 Android 移动设备的摄像头和麦克风使用权限。
// Java private static final int PERMISSION_REQ_ID = 22; // App 运行时确认麦克风和摄像头设备的使用权限。 private static final String[] REQUESTED_PERMISSIONS = { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_chat_view); // 获取权限后,初始化 RtcEngine,并加入频道。 if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) && checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) { initEngineAndJoinChannel(); } } private void initEngineAndJoinChannel() { initializeEngine(); setupLocalVideo(); joinChannel(); } private boolean checkSelfPermission(String permission, int requestCode) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode); return false; } return true; } // Kotlin // app 运行时确认麦克风和摄像头的使用权限。 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_voice_chat_view) // 获取权限后,初始化 RtcEngine,并加入频道。 if (checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO) && checkSelfPermission(Manifest.permission.CAMERA, PERMISSION_REQ_ID_CAMERA)) { initAgoraEngineAndJoinChannel() } } private fun initAgoraEngineAndJoinChannel() { initializeAgoraEngine() setupLocalVideo() joinChannel() } private fun checkSelfPermission(permission: String, requestCode: Int): Boolean { Log.i(LOG_TAG, "checkSelfPermission $permission $requestCode") if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode) return false } return true }
4. 初始化 RtcEngine
在调用其他 Agora API 前,需要创建并初始化 RtcEngine 对象。
将获取到的 App ID 添加到 string.xml
文件中的 agora_app_id
一栏。调用 create
方法,传入获取到的 App ID,即可初始化 RtcEngine。
你还根据场景需要,在初始化时注册想要监听的回调事件,如本地用户加入频道,及远端用户加入频道等。注意不要在这些回调中进行 UI 操作。
// Java private RtcEngine mRtcEngine; private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() { @Override // 注册 onJoinChannelSuccess 回调。 // 本地用户成功加入频道时,会触发该回调。 public void onJoinChannelSuccess(String channel, final int uid, int elapsed) { runOnUiThread(new Runnable() { @Override public void run() { Log.i("agora","Join channel success, uid: " + (uid & 0xFFFFFFFFL)); } }); } @Override // 注册 onUserJoined 回调。 // 远端用户成功加入频道时,会触发该回调。 // 可以在该回调中调用 setupRemoteVideo 方法设置远端视图。 public void onUserJoined(final int uid, int elapsed) { runOnUiThread(new Runnable() { @Override public void run() { Log.i("agora","Remote user joined, uid: " + (uid & 0xFFFFFFFFL)); setupRemoteVideo(uid); } }); } @Override // 注册 onUserOffline 回调。 // 远端用户离开频道或掉线时,会触发该回调。 public void onUserOffline(final int uid, int reason) { runOnUiThread(new Runnable() { @Override public void run() { Log.i("agora","User offline, uid: " + (uid & 0xFFFFFFFFL)); onRemoteUserLeft(); } }); } }; ... // 初始化 RtcEngine 对象。 private void initializeEngine() { try { mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString(e)); throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e)); } } // Kotlin private var mRtcEngine: RtcEngine? = null private val mRtcEventHandler = object : IRtcEngineEventHandler() { // 注册 onUserJoined 回调。 // 远端用户成功加入频道时,会触发该回调。 // 可以在该回调用调用 setupRemoteVideo 方法设置远端视图。 override fun onUserJoined(uid: Int, elapsed: Int) { runOnUiThread { setupRemoteVideo(uid) } } // 注册 onUserOffline 回调。远端用户离开频道后,会触发该回调。 override fun onUserOffline(uid: Int, reason: Int) { runOnUiThread { onRemoteUserLeft() } } } ... // 调用 Agora SDK 的方法初始化 RtcEngine。 private fun initializeAgoraEngine() { try { mRtcEngine = RtcEngine.create(baseContext, getString(R.string.agora_app_id), mRtcEventHandler) } catch (e: Exception) { Log.e(LOG_TAG, Log.getStackTraceString(e)) throw RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e)) } }
5. 设置本地视图
成功初始化 RtcEngine 对象后,需要在加入频道前设置本地视图,以便在通话中看到本地图像。参考以下步骤设置本地视图:
- 调用
enableVideo
方法启用视频模块。 - 调用
createRendererView
方法创建一个 SurfaceView 对象。 - 调用
setupLocalVideo
方法设置本地视图。
// Java private void setupLocalVideo() { // 启用视频模块。 mRtcEngine.enableVideo(); // 创建 SurfaceView 对象。 private FrameLayout mLocalContainer; private SurfaceView mLocalView; mLocalView = RtcEngine.CreateRendererView(getBaseContext()); mLocalView.setZOrderMediaOverlay(true); mLocalContainer.addView(mLocalView); // 设置本地视图。 VideoCanvas localVideoCanvas = new VideoCanvas(mLocalView, VideoCanvas.RENDER_MODE_HIDDEN, 0); mRtcEngine.setupLocalVideo(localVideoCanvas); } // Kotlin private fun setupLocalVideo() { // 启用视频模块。 mRtcEngine!!.enableVideo() val container = findViewById(R.id.local_video_view_container) as FrameLayout // 创建 SurfaceView。 val surfaceView = RtcEngine.createRendererView(baseContext) surfaceView.setZorderMediaOverlay(true) container.addView(surfaceView) // 设置本地视图。 mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0)) }
6. 加入频道
完成初始化和设置本地视图后(视频通话场景),你就可以调用 joinChannel
方法加入频道。你需要在该方法中传入如下参数:
-
token
:传入用于鉴权的 Token,可设为如下一个值:- 临时 Token。你可以在控制台里生成一个临时 Token,详见获取临时 Token。加入频道时,请确保填入的频道名和生成临时 Token 时填入的频道名一致。
- 在你的服务器端生成的 Token。在安全要求高的场景下,我们推荐你使用此种方式生成的 Token,详见从服务端生成 Token。加入频道时,请确保填入的频道名和 uid 与生成 Token 时填入的频道名和 uid 一致。
- 若项目已启用 App 证书,请使用 Token。
- 请勿将
token
设为 ""。
-
channelName:传入能标识频道的频道 ID。App ID 相同、频道 ID 相同的用户会进入同一个频道。
-
uid: 本地用户的 ID。数据类型为整型,且频道内每个用户的 uid 必须是唯一的。若将 uid 设为 0,则 SDK 会自动分配一个 uid,并在
onJoinChannelSuccess
回调中报告。用户成功加入频道后,会默认订阅频道内其他所有用户的音频流和视频流,因此产生用量并影响计费。如果想取消订阅,可以通过调用相应的mute
方法实现。
更多的参数设置注意事项请参考 joinChannel
接口中的参数描述。
// Java private void joinChannel() { // 调用 joinChannel 方法 加入频道。 mRtcEngine.joinChannel(YOUR_TOKEN, "demoChannel1", "Extra Optional Data", 0); } // Kotlin private fun joinChannel() { // 调用 joinChannel 方法加入频道。 mRtcEngine!!.joinChannel(token, "demoChannel1", "Extra Optional Data", 0) }
7. 设置远端视图
视频通话中,通常你也需要看到其他用户。在加入频道后,可通过调用 setupRemoteVideo
方法设置远端用户的视图。远端视图和本地视图的区别就是需要设置远端用户的 UID。
远端用户成功加入频道后,SDK 会触发 onUserJoined
回调,该回调中会包含这个远端用户的 uid 信息。在该回调中调用 setupRemoteVideo
方法,传入获取到的 uid,设置远端用户的视图。
// Java @Override // 监听 onUserJoined 回调。 // 远端用户加入频道时,会触发该回调。 // 可以在该回调中调用 setupRemoteVideo 方法设置远端视图。 public void onUserJoined(final int uid, int elapsed) { runOnUiThread(new Runnable() { @Override public void run() { Log.i("agora","Remote user joined, uid: " + (uid & 0xFFFFFFFFL)); setupRemoteVideo(uid); } }); } private void setupRemoteVideo(int uid) { // 创建一个 SurfaceView 对象。 private RelativeLayout mRemoteContainer; private SurfaceView mRemoteView; mRemoteView = RtcEngine.CreateRendererView(getBaseContext()); mRemoteContainer.addView(mRemoteView); // 设置远端视图。 mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid)); } // Kotlin // 注册 onUserJoined 回调。 // 远端用户加入频道时,会触发该回调。 // 可以在该回调用调用 setupRemoteVideo 方法设置远端视图。 override fun onUserJoined(uid: Int, elapsed: Int) { runOnUiThread { setupRemoteVideo(uid) } } private fun setupRemoteVideo(uid: Int) { val container = findViewById(R.id.remote_video_view_container) as FrameLayout if (container.childCount >= 1) { return } // 创建一个 SurfaceView 对象。 val surfaceView = RtcEngine.CreateRendererView(baseContext) container.addView(surfaceView) // 设置远端视图。 mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid)) }
8. 更多步骤
你还可以在通话中参考如下代码实现更多功能及场景。
停止发送本地音频流切换前后摄像头
9. 离开频道
根据场景需要,如结束通话、关闭 App 或 App 切换至后台时,调用 leaveChannel
离开当前通话频道。
// Java @Override protected void onDestroy() { super.onDestroy(); if (!mCallEnd) { leaveChannel(); } RtcEngine.destroy(); } private void leaveChannel() { // 离开当前频道。 mRtcEngine.leaveChannel(); } // Kotlin override fun onDestroy() { super.onDestroy() leaveChannel() RtcEngine.destroy() mRtcEngine = null } private fun leaveChannel() { // 离开当前频道。 mRtcEngine!!.leaveChannel() }
示例代码
你可以在 Agora-Android-Tutorial-1to1 示例项目的 VideoChatViewActivity.java 文件中查看完整的源码和代码逻辑。
运行项目
在 Android 设备中运行该项目。当成功开始视频通话时,你可以同时看到本地和远端的视图。