
声网Android端集成与一对一音视频功能实现
坑二:
发布日期:2021-05-07 10:20:50
浏览次数:16
分类:精选文章
本文共 10440 字,大约阅读时间需要 34 分钟。
该sdk集成的前提条件
(现在一般都符合的)
- Android Studio 3.0 或以上版本
- Android SDK API 等级 16 或以上
- 支持 Android 4.1 或以上版本的移动设备
- 有效的 Agora 账户 和 App ID
集成SDK
集成方式一:使用JCenter自动集成
(该方法也是目前Android端项目使用的方式)
在项目的/app/build.gradle文件中,添加如下行:
dependencies { //… implementation 'io.agora.rtc:full-sdk:3.1.3'}
具体的版本号可以查看官网文档的。
集成方式二:手动复制sdk文件
- 前往 SDK 下载页面,获取最新版的 Agora 视频 SDK,然后解压。
- 将 SDK 包内 libs 路径下的如下文件,拷贝到你的项目路径下:
添加项目权限
根据场景需要,在 /app/src/main/AndroidManifest.xml 文件中添加如下行,获取相应的设备权限:
// 如果你的场景中涉及读取外部存储,需添加如下权限: // 如果你使用的是 Android 10.0 及以上设备,还需要添加如下权限: ...
如果你的 targetSdkVersion ≥ 29,还需要在 AndroidManifest.xml 文件的 区域添加如下行:
...
防止代码混淆
在 app/proguard-rules.pro 文件中添加如下行,防止混淆 Agora SDK 的代码:
-keep class io.agora.**{*;}
音视频功能实现
大致步骤如下:
- 创建用户界面
- 获取设备权限
- 初始化RtcEngine
- 设置本地视图
- 加入频道
- 设置远端视图
- 更多步骤
- 离开频道
具体实现可以直接看
import android.Manifest;import android.content.pm.PackageManager;import android.os.Bundle;import android.text.TextUtils;import android.util.Log;import android.view.SurfaceView;import android.view.View;import android.widget.FrameLayout;import android.widget.ImageView;import android.widget.RelativeLayout;import android.widget.Toast;import androidx.annotation.NonNull;import androidx.appcompat.app.AppCompatActivity;import androidx.core.app.ActivityCompat;import androidx.core.content.ContextCompat;import io.agora.rtc.IRtcEngineEventHandler;import io.agora.rtc.RtcEngine;import io.agora.rtc.video.VideoCanvas;import io.agora.rtc.video.VideoEncoderConfiguration;public class VideoChatViewActivity extends AppCompatActivity { private static final String TAG = VideoChatViewActivity.class.getSimpleName(); private static final int PERMISSION_REQ_ID = 22; private static final String[] REQUESTED_PERMISSIONS = { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE }; private RtcEngine mRtcEngine; private boolean mCallEnd; private boolean mMuted; private FrameLayout mLocalContainer; private RelativeLayout mRemoteContainer; private SurfaceView mLocalView; private SurfaceView mRemoteView; private ImageView mCallBtn; private ImageView mMuteBtn; private ImageView mSwitchCameraBtn; private LoggerRecyclerView mLogView; private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() { @Override public void onJoinChannelSuccess(String channel, final int uid, int elapsed) { runOnUiThread(new Runnable() { @Override public void run() { mLogView.logI("Join channel success, uid: " + (uid & 0xFFFFFFFFL)); } }); } @Override public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) { runOnUiThread(new Runnable() { @Override public void run() { mLogView.logI("First remote video decoded, uid: " + (uid & 0xFFFFFFFFL)); setupRemoteVideo(uid); } }); } @Override public void onUserOffline(final int uid, int reason) { runOnUiThread(new Runnable() { @Override public void run() { mLogView.logI("User offline, uid: " + (uid & 0xFFFFFFFFL)); onRemoteUserLeft(); } }); } }; private void setupRemoteVideo(int uid) { int count = mRemoteContainer.getChildCount(); View view = null; for (int i = 0; i < count; i++) { View v = mRemoteContainer.getChildAt(i); if (v.getTag() instanceof Integer && ((int) v.getTag()) == uid) { view = v; } } if (view != null) { return; } mRemoteView = RtcEngine.CreateRendererView(getBaseContext()); mRemoteContainer.addView(mRemoteView); mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid)); mRemoteView.setTag(uid); } private void onRemoteUserLeft() { removeRemoteVideo(); } private void removeRemoteVideo() { if (mRemoteView != null) { mRemoteContainer.removeView(mRemoteView); } mRemoteView = null; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_chat_view); initUI(); if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) && checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID) && checkSelfPermission(REQUESTED_PERMISSIONS[2], PERMISSION_REQ_ID)) { initEngineAndJoinChannel(); } } private void initUI() { mLocalContainer = findViewById(R.id.local_video_view_container); mRemoteContainer = findViewById(R.id.remote_video_view_container); mCallBtn = findViewById(R.id.btn_call); mMuteBtn = findViewById(R.id.btn_mute); mSwitchCameraBtn = findViewById(R.id.btn_switch_camera); mLogView = findViewById(R.id.log_recycler_view); // Sample logs are optional. showSampleLogs(); } private void showSampleLogs() { mLogView.logI("Welcome to Agora 1v1 video call"); mLogView.logW("You will see custom logs here"); mLogView.logE("You can also use this to show errors"); } 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; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQ_ID) { if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED || grantResults[2] != PackageManager.PERMISSION_GRANTED) { showLongToast("Need permissions " + Manifest.permission.RECORD_AUDIO + "/" + Manifest.permission.CAMERA + "/" + Manifest.permission.WRITE_EXTERNAL_STORAGE); finish(); return; } initEngineAndJoinChannel(); } } private void showLongToast(final String msg) { this.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show(); } }); } private void initEngineAndJoinChannel() { initializeEngine(); setupVideoConfig(); setupLocalVideo(); joinChannel(); } 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)); } } private void setupVideoConfig() { mRtcEngine.enableVideo(); mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration( VideoEncoderConfiguration.VD_640x360, VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15, VideoEncoderConfiguration.STANDARD_BITRATE, VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT)); } private void setupLocalVideo() { mLocalView = RtcEngine.CreateRendererView(getBaseContext()); mLocalView.setZOrderMediaOverlay(true); mLocalContainer.addView(mLocalView); mRtcEngine.setupLocalVideo(new VideoCanvas(mLocalView, VideoCanvas.RENDER_MODE_HIDDEN, 0)); } private void joinChannel() { String token = getString(R.string.agora_access_token); if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) { token = null; } mRtcEngine.joinChannel(token, "demochannel", "Extra Optional Data", 1); } @Override protected void onDestroy() { super.onDestroy(); if (!mCallEnd) { leaveChannel(); } RtcEngine.destroy(); } private void leaveChannel() { mRtcEngine.leaveChannel(); } public void onLocalAudioMuteClicked(View view) { mMuted = !mMuted; mRtcEngine.muteLocalAudioStream(mMuted); int res = mMuted ? R.drawable.btn_mute : R.drawable.btn_unmute; mMuteBtn.setImageResource(res); } public void onSwitchCameraClicked(View view) { mRtcEngine.switchCamera(); } public void onCallClicked(View view) { if (mCallEnd) { startCall(); mCallEnd = false; mCallBtn.setImageResource(R.drawable.btn_endcall); } else { endCall(); mCallEnd = true; mCallBtn.setImageResource(R.drawable.btn_startcall); } showButtons(!mCallEnd); } private void startCall() { setupLocalVideo(); joinChannel(); } private void endCall() { removeLocalVideo(); removeRemoteVideo(); leaveChannel(); } private void removeLocalVideo() { if (mLocalView != null) { mLocalContainer.removeView(mLocalView); } mLocalView = null; } private void showButtons(boolean show) { int visibility = show ? View.VISIBLE : View.GONE; mMuteBtn.setVisibility(visibility); mSwitchCameraBtn.setVisibility(visibility); }}
踩坑:
坑一:不能为null
几个值都要有值,不然相机是黑屏的。
public abstract int joinChannel(String token, String channelName, String optionalInfo, int optionalUid);
还有app_id:agora_app_id;


常用API
- RtcEngine 类包含应用程序调用的主要方法。
- IRtcEngineEventHandler 类用于向应用程序发送回调通知。
- RtcChannel 类在指定频道中实现实时音视频功能。通过创建多个 RtcChannel 对象,用户可以同时加入多个频道。
- IRtcChannelEventHandler 类监听和报告指定频道的事件和数据。
- IAudioEffectManager 类提供管理音效文件的方法。
其中:
IRtcEngineEventHandler 接口类用于SDK向 App 发送回调事件通知,App 通过继承该接口类的方法获取 SDK 的事件通知。 接口类的所有方法都有缺省(空)实现, App 可以根据需要只继承关心的事件。在回调方法中,App 不应该做耗时或者调用可能会引起阻塞的 API(如 SendMessage),否则可能影响 SDK 的运行。发表评论
最新留言
逛到本站,mark一下
[***.202.152.39]2025年04月09日 10时07分14秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
ILI9341几个重要的命令
2019-03-05
AD如何对原理图进行注释
2019-03-05
力扣:地图分析(多源bfs)
2019-03-05
NC15136: 迷宫
2019-03-05
动态点击a标签
2019-03-05
@RequestBody和@RequestParam
2019-03-05
oracle创建序列语法
2019-03-05
springboot通过控制层跳转页面404
2019-03-05
idea2020 没有 tomcat server
2019-03-05
jq动态修改元素的onclick属性的值
2019-03-05
为什么讨厌所谓仿生AI的说法
2019-03-05
ORACLE 客户端工具
2019-03-05
Elasticsearch下载慢?分享百度云下载-ELK
2019-03-05
云服务器springboot jar项目开启jmx remote监控-解决无法连接的问题
2019-03-05
文件上传-FileUpload
2019-03-05
快速排序
2019-03-05
Pyinstaller打包的exe文件过大的解决方法
2019-03-05
Linux的软链接跟Windows快捷方式一样?
2019-03-05
更改github的默认语言类型
2019-03-05