Agora SDK 在Android中的使用(在線視頻通話)

首先聲明本文是Agora SDK入門的小白文章


一.集成

1.註冊賬號創建項目

其中最重要的要數 App ID 了


2.下載Agora SDK


二、學會看示例代碼(可跳過)

1.整體瞭解項目結構(1v1的視頻通信示例)

以前看一個Android項目先看AndroidManifest.xml,我更喜歡先把文件夾內的結構樹打印出來
打印文件夾內的結構樹可詳見:雜篇-從整理文件發起的雜談[-File-]

|---app
    |---.gitignore
    |---build.gradle
    |---libs
        |---PLACEHOLDER
    |---proguard-rules.pro
    |---src
        |---main
            |---AndroidManifest.xml
            |---java
                |---io
                    |---agora
                        |---tutorials1v1vcall
                            |---VideoChatViewActivity.java
            |---jniLibs
                |---arm64-v8a
                    |---PLACEHOLDER
                |---armeabi-v7a
                    |---PLACEHOLDER
                |---x86
                    |---PLACEHOLDER
            |---res
                |---drawable-xxxhdpi
                    |---btn_end_call.png
                    |---btn_mute.png
                    |---btn_switch_camera.png
                    |---btn_video.png
                    |---btn_voice.png
                    |---ic_launcher.png
                |---layout
                    |---activity_video_chat_view.xml
                |---values
                    |---colors.xml
                    |---dimens.xml
                    |---strings.xml
                    |---styles.xml
|---build.gradle
|---gradle
    |---wrapper
        |---gradle-wrapper.jar
        |---gradle-wrapper.properties
|---gradle.properties
|---gradlew
|---gradlew.bat
|---images
    |---ActivityViewChat.png
|---LICENSE.md
|---README.md
|---README.zh.md
|---settings.gradle

2.查看最項目的settings.gradlebuild.gradle(最外層)

如果你想導入AS中查看,可以看一下com.android.tools.build:gradle的版本修改一下

---->[settings.gradle]----------------看一下項目包含的模塊------------
include ':app'

---->[build.gradle]----------------看一下項目的一些信息------------
buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'
    }
}
allprojects {
    repositories {
        jcenter()
        google()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

3.查看模塊下的build.gradle

---->[app/build.gradle]----------------看一下項目的具體信息------------
apply plugin: 'com.android.application'
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "io.agora.tutorials1v1vcall"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {//這裏是jniLibs的目錄
        main {
            jniLibs.srcDirs = ['../../../libs']
        }
    }
}

dependencies {//這裏是依賴
    implementation fileTree(dir: '../../../libs', include: ['*.jar']) // DO NOT CHANGE, CI may needs it when packaging
    implementation 'com.android.support:appcompat-v7:26.1.0'
}

4.查看AndroidManifest.xml,得到入口Activity

可見示例的入口是VideoChatViewActivity,並看一下權限

<activity
    android:name=".VideoChatViewActivity"
    android:screenOrientation="sensorPortrait"
    android:theme="@style/FullScreenVideoTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

|--- 權限 ------------
<!--網絡權限-->
<uses-permission android:name="android.permission.INTERNET"/>
<!--錄音權限-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--更改錄音設置-->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<!--網絡狀態權限-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--相機權限-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--藍牙權限-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!--SD卡寫權限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

三、創建IChat項目


1、配置項目

項目的配置如圖,將依賴包以及.so文件放在對應位置

爲了方便些,將res文件夾的資源拷貝一下


2、配置APP ID


3.視頻通話Activity的分析

一共也就200多行,還包括一大坨權限申請的代碼,這裏權限申請的代碼單獨拎出來,就當複習一下。

3.1:權限申請(非要點,可忽略)

---->[成員變量]----------------------------------
private static final int PERMISSION_REQ_ID = 22;
//WRITE_EXTERNAL_STORAGE 權限只是爲了保存日誌到SD卡
private static final String[] REQUESTED_PERMISSIONS = {
        Manifest.permission.RECORD_AUDIO,//錄音權限
        Manifest.permission.CAMERA,//相機權限
        Manifest.permission.WRITE_EXTERNAL_STORAGE//SD卡寫權限
};

if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
        checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID) &&
        checkSelfPermission(REQUESTED_PERMISSIONS[2], PERMISSION_REQ_ID)) {
    //執行到此處說明已有權限成功     
    initAgoraEngineAndJoinChannel();
}

/**
 * 檢查權限的方法
 *
 * @param permission  權限
 * @param requestCode 請求碼
 * @return 是否擁有權限
 */
public boolean checkSelfPermission(String permission, int requestCode) {
    Log.i(LOG_TAG, "checkSelfPermission " + permission + " " + 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) {
    Log.i(LOG_TAG, "onRequestPermissionsResult " + grantResults[0] + " " + requestCode);
    switch (requestCode) {
        case 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();
                break;
            }
            //執行到此處說明用戶已允許權限
            initAgoraEngineAndJoinChannel();
            break;
        }
    }
}

4.初始化Agora引擎和連接頻道

/**
 * 初始化Agora引擎和連接頻道
 */
private void initAgoraEngineAndJoinChannel() {
    initializeAgoraEngine();//初始化Agora引擎
    setupVideoProfile();//設置視頻信息
    setupLocalVideo();//設置本地的視頻窗
    joinChannel();//連接頻道
}

/**
 * 初始化Agora引擎
 */
private void initializeAgoraEngine() {
    try {
        mRtcEngine = RtcEngine.create(//實例化Rtc引擎
            getBaseContext(),//傳入Context 
            getString(R.string.agora_app_id), //傳入APP ID
            mRtcEventHandler);//RTC事件處理器
    } catch (Exception e) {//發生異常時捕獲異常
        Log.e(LOG_TAG, Log.getStackTraceString(e));
        throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
    }
}

/**
 * 設置視頻信息
 */
private void setupVideoProfile() {
    mRtcEngine.enableVideo();//啓用視屏
    mRtcEngine.setVideoEncoderConfiguration(//視頻解碼配置
            new VideoEncoderConfiguration(//實例化對象
                    VideoEncoderConfiguration.VD_120x120,//尺寸
                    VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,//幀率
                    VideoEncoderConfiguration.STANDARD_BITRATE,//比特率
                    VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));//旋轉模式
}
|---關於VideoEncoderConfiguration對象---->[VideoEncoderConfiguration構造方法]------------------
 public VideoEncoderConfiguration(
 VideoEncoderConfiguration.VideoDimensions dimensions, //尺寸
 VideoEncoderConfiguration.FRAME_RATE frameRate,//幀率 
 int bitrate, //比特率
 VideoEncoderConfiguration.ORIENTATION_MODE orientationMode)//旋轉模式

/**
 * 設置本地視頻窗
 */
private void setupLocalVideo() {
    FrameLayout container = findViewById(R.id.local_video_view_container);//FrameLayout視圖
    SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());//創建SurfaceView
    surfaceView.setZOrderMediaOverlay(true);
    container.addView(surfaceView);
    mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
}

 /**
  * 連接到頻道
  */
 private void joinChannel() {
     mRtcEngine.joinChannel(null, "demoChannel1", "Extra Optional Data", 0);
     // 如果你不指定 uid(第四參), 我們會爲你生成一個 uid
 }

5.RTC事件處理器:IRtcEngineEventHandler

IRtcEngineEventHandler是一個抽象類,定義了非常多的抽象方法還有一些靜態內部類

private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
    @Override//已完成遠端視頻首幀解碼回調。
    public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) {
        runOnUiThread(() -> setupRemoteVideo(uid));
    }
    @Override//遠端用戶(通信模式)/主播(直播模式)離開當前頻道回調。
    public void onUserOffline(int uid, int reason) {
        runOnUiThread(() -> onRemoteUserLeft());
    }
    @Override//遠端用戶靜音回調。
    public void onUserMuteVideo(final int uid, final boolean muted) {
        runOnUiThread(() -> onRemoteUserVideoMuted(uid, muted));
    }
};

/**
 * 根據uid設置遠端視頻
 * @param uid 唯一標識符
 */
private void setupRemoteVideo(int uid) {
    FrameLayout container = findViewById(R.id.remote_video_view_container);
    if (container.getChildCount() >= 1) {
        return;
    }
    SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());
    container.addView(surfaceView);
    mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, u
    surfaceView.setTag(uid); // 用uid爲surfaceView打標籤
    View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); // 隱藏文字UI
    tipMsg.setVisibility(View.GONE);
}

/**
 * 遠端用戶掛斷
 */
private void onRemoteUserLeft() {
    FrameLayout container = findViewById(R.id.remote_video_view_container);
    container.removeAllViews();
    View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); // 顯示文字UI
    tipMsg.setVisibility(View.VISIBLE);
}

/**
 * 遠端用戶靜音
 * @param uid 標識符
 * @param muted 是否靜音
 */
private void onRemoteUserVideoMuted(int uid, boolean muted) {
    FrameLayout container = findViewById(R.id.remote_video_view_container);
    SurfaceView surfaceView = (SurfaceView) container.getChildAt(0);
    Object tag = surfaceView.getTag();
    if (tag != null && (Integer) tag == uid) {
        surfaceView.setVisibility(muted ? View.GONE : View.VISIBLE);
    }
}

6.幾個點擊事件

/**
 * 是否屏蔽視頻
 * @param view
 */
public void onLocalVideoMuteClicked(View view) {
    ImageView iv = (ImageView) view;
    if (iv.isSelected()) {
        iv.setSelected(false);
        iv.clearColorFilter();
    } else {
        iv.setSelected(true);
        iv.setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY);
    }
    mRtcEngine.muteLocalVideoStream(iv.isSelected());//核心的一句API,
    FrameLayout container = findViewById(R.id.local_video_view_container);
    SurfaceView surfaceView = (SurfaceView) container.getChildAt(0);
    surfaceView.setZOrderMediaOverlay(!iv.isSelected());
    surfaceView.setVisibility(iv.isSelected() ? View.GONE : View.VISIBLE);
}

/**
 * 是否靜音
 * @param view
 */
public void onLocalAudioMuteClicked(View view) {
    ImageView iv = (ImageView) view;
    if (iv.isSelected()) {
        iv.setSelected(false);
        iv.clearColorFilter();
    } else {
        iv.setSelected(true);
        iv.setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY);
    }
    mRtcEngine.muteLocalAudioStream(iv.isSelected());//核心的一句API,
}

/**
 * 切換攝像頭
 * @param view
 */
public void onSwitchCameraClicked(View view) {
    mRtcEngine.switchCamera();
}

/**
 * 關閉
 * @param view
 */
public void onEncCallClicked(View view) {
    finish();
}

 @Override
 protected void onDestroy() {
     super.onDestroy();
     leaveChannel();//離開頻道
     RtcEngine.destroy();//引擎銷燬
     mRtcEngine = null;//引擎置空
 }

 /**
 * 離開頻道
 */
private void leaveChannel() {
    mRtcEngine.leaveChannel();
}

最後

小編整理了一些關於這方面的深入講解的視頻,如需要的話可以加羣免費領取。

本人Java開發4年Android開發5年,定期分享Android高級技術及經驗分享,歡迎大家關注~(分享內容包括不限於高級UI、性能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術;希望能幫助到大家,也節省大家在網上搜索資料的時間來學習,也可以分享動態給身邊好友一起學習!)

最後文末放上一個技術交流羣:Android IOC架構設計

羣內有許多技術大牛,有任何問題,歡迎廣大網友一起來交流,羣內還不定期免費分享高階Android學習視頻資料和麪試資料包~

再推薦一篇文章:“寒冬未過”,阿里P9架構分享Android必備技術點,讓你offer拿到手軟!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章