声网Android端集成与一对一音视频功能实现
发布日期:2021-05-07 10:20:50 浏览次数:16 分类:精选文章

本文共 10440 字,大约阅读时间需要 34 分钟。

该sdk集成的前提条件

(现在一般都符合的)

  1. Android Studio 3.0 或以上版本
  2. Android SDK API 等级 16 或以上
  3. 支持 Android 4.1 或以上版本的移动设备
  4. 有效的 Agora 账户 和 App ID

集成SDK

集成方式一:使用JCenter自动集成

(该方法也是目前Android端项目使用的方式)

在项目的/app/build.gradle文件中,添加如下行:

dependencies { //… implementation 'io.agora.rtc:full-sdk:3.1.3'}

具体的版本号可以查看官网文档的。

集成方式二:手动复制sdk文件

  1. 前往 SDK 下载页面,获取最新版的 Agora 视频 SDK,然后解压。
  2. 将 SDK 包内 libs 路径下的如下文件,拷贝到你的项目路径下:
    在这里插入图片描述

添加项目权限

根据场景需要,在 /app/src/main/AndroidManifest.xml 文件中添加如下行,获取相应的设备权限:

// 如果你的场景中涉及读取外部存储,需添加如下权限:
// 如果你使用的是 Android 10.0 及以上设备,还需要添加如下权限:
...

如果你的 targetSdkVersion ≥ 29,还需要在 AndroidManifest.xml 文件的 区域添加如下行:

...

防止代码混淆

在 app/proguard-rules.pro 文件中添加如下行,防止混淆 Agora SDK 的代码:

-keep class io.agora.**{*;}

音视频功能实现

大致步骤如下:

  1. 创建用户界面
  2. 获取设备权限
  3. 初始化RtcEngine
  4. 设置本地视图
  5. 加入频道
  6. 设置远端视图
  7. 更多步骤
  8. 离开频道

具体实现可以直接看

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

  1. RtcEngine 类包含应用程序调用的主要方法。
  2. IRtcEngineEventHandler 类用于向应用程序发送回调通知。
  3. RtcChannel 类在指定频道中实现实时音视频功能。通过创建多个 RtcChannel 对象,用户可以同时加入多个频道。
  4. IRtcChannelEventHandler 类监听和报告指定频道的事件和数据。
  5. IAudioEffectManager 类提供管理音效文件的方法。

其中:

IRtcEngineEventHandler 接口类用于SDK向 App 发送回调事件通知,App 通过继承该接口类的方法获取 SDK 的事件通知。 接口类的所有方法都有缺省(空)实现, App 可以根据需要只继承关心的事件。在回调方法中,App 不应该做耗时或者调用可能会引起阻塞的 API(如 SendMessage),否则可能影响 SDK 的运行。

上一篇:HMS推送Android端集成
下一篇:Android 键盘快捷键

发表评论

最新留言

逛到本站,mark一下
[***.202.152.39]2025年04月09日 10时07分14秒