作者:聲網(wǎng)Agora用戶,資深A(yù)ndroid開發(fā)者吳東洋。
本系列文章分享了基于Agora SDK 2.1實(shí)現(xiàn)多人視頻通話的實(shí)踐經(jīng)驗(yàn)。
自從2016年,鼓吹“互聯(lián)網(wǎng)寒冬”的論調(diào)甚囂塵上,2017年亦有愈演愈烈之勢(shì)。但連麥直播、在線抓娃娃、直播問答、遠(yuǎn)程狼人殺等類型的項(xiàng)目卻異軍突起,成了投資人的風(fēng)口,創(chuàng)業(yè)者的藍(lán)海和用戶的必裝App,而這些方向的項(xiàng)目都有一個(gè)共同的特點(diǎn)——都依賴視頻通話和全互動(dòng)直播技術(shù)。
聲網(wǎng)Agora.io的SDK讓App和網(wǎng)站都可以實(shí)現(xiàn)高質(zhì)量的音頻通話、視頻通話、全互動(dòng)直播。我試著通過該SDK實(shí)現(xiàn)一個(gè)多人視頻通話應(yīng)用。本文先分享集成的部分。
環(huán)境聲網(wǎng)Agora.io SDK的兼容性良好,對(duì)硬件設(shè)備和軟件系統(tǒng)的要求不高,開發(fā)環(huán)境和測試環(huán)境滿足以下條件即可:
Android SDK API Level >= 16
Android Studio 2.0 或以上版本
支持語音和視頻功能的真機(jī)
App 要求 Android 4.1 或以上設(shè)備
以下是我試用聲網(wǎng)Agora.io SDK的開發(fā)環(huán)境和測試環(huán)境:
開發(fā)環(huán)境
Windows 10 家庭中文版
Java Version SE 8
Android Studio 3.2 Canary 4
測試環(huán)境
Samsung Nexus (Android 4.4.2 API 19)
Mi Note 3 (Android 7.1.1 API 25)
集成步驟一:首先點(diǎn)此下載完整的SDK和官方demo
步驟二:既然我們要把聲網(wǎng)Agora.io集成到自己的項(xiàng)目里,所以不必運(yùn)行sample,我們自己新建一個(gè)HelloAgora項(xiàng)目,注意一定要支持C++哦。
步驟三:把libs文件夾里的arm64-v8a、、armeabi-v7a以及x86文件夾復(fù)制粘貼到app module的libs里。如果有NDK開發(fā)的必要,則把libs->include文件夾里的兩個(gè).h頭文件復(fù)制粘貼到合適位置。
步驟四:首先在app module的build.gradle文件的android代碼塊中添加如下代碼:
sourceSets { main { jniLibs.srcDirs = ["../../../libs"] } }
然后在app module的build.gradle文件的android->defaultConfig代碼塊中添加如下代碼:
ndk { abiFilters "armeabi-v7a", "x86" }
接下來在app module的build.gradle文件的dependencies代碼塊中添加如下代碼:
compile "io.agora.rtc:full-sdk:2.0.0"
如果用復(fù)制粘貼jar的方式,那么此處添加如下代碼:
compile fileTree(dir: "../../../libs", include: ["*.jar"])
如果有自定義NDK的必要,可以繼續(xù)在app module的build.gradle文件的android代碼塊中添加如下代碼:
externalNativeBuild { ndkBuild { path "src/main/cpp/Android.mk" } }
然后在app module的build.gradle文件的android->defaultConfig代碼塊中添加如下代碼:
externalNativeBuild { ndkBuild { arguments "NDK_APPLICATION_MK:=src/main/cpp/Application.mk" } }
最后sync一下,聲網(wǎng)Agora.io的SDK就集成到項(xiàng)目中來了。
權(quán)限SDK集成完畢后,為了保證SDK能正常運(yùn)行,我們需要在AndroidManisfest.xml 文件中聲明以下權(quán)限:
這些權(quán)限都是Android開發(fā)過程中的常見權(quán)限,有經(jīng)驗(yàn)的程序員都會(huì)感覺眼熟,WRITE_EXTERNAL_STORAGE等敏感權(quán)限適配Android 6.0以后版本的問題并非本文關(guān)注重點(diǎn),在此不做贅述。
混淆代碼集成SDK并聲明了權(quán)限后,就該考慮混淆的問題了,我們需要在project的proguard-rules.pro文件里添加以下代碼:
-keep class io.agora.**{*;}
經(jīng)過以上過程后,我們已經(jīng)完成了聲網(wǎng)Agora.io SDK的快速集成,邁出了走向連麥直播、在線抓娃娃、直播問答、遠(yuǎn)程狼人殺等風(fēng)口的第一步。在接下來的文章里,我將繼續(xù)分享APP ID鑒權(quán)、Token鑒權(quán)、一對(duì)一視頻聊天、創(chuàng)建群聊room、分屏、窗口切換和前后攝像頭切換等內(nèi)容。
鑒權(quán)APP ID鑒權(quán)
所謂APP ID,就是在 Agora創(chuàng)建每個(gè)項(xiàng)目都有的一個(gè)唯一標(biāo)識(shí)。App ID 可以明確你的項(xiàng)目及組織身份,并在 joinChannel 方法中作為參數(shù),連接到 Agora 實(shí)時(shí)網(wǎng)絡(luò)中,實(shí)現(xiàn)實(shí)時(shí)通信或直播功能。不同的App ID在Agora實(shí)時(shí)網(wǎng)絡(luò)中的通話是完全隔離的;Agora 提供的頻道信息、計(jì)費(fèi)、管理服務(wù)也都是基于 App ID。
申請(qǐng)APP ID的操作很簡便,只要在Agora官網(wǎng)https://dashboard.agora.io/pr...“項(xiàng)目”中點(diǎn)擊“添加新項(xiàng)目”,只需輸入項(xiàng)目名就可生成APP ID,全過程如下圖所示:
找到,把“<#YOUR APP ID#>”替換為圖中的馬賽克里的字符串。
<#YOUR APP ID#>
以上就是APP ID鑒權(quán)的全過程。
盡管App ID鑒權(quán)在最大程度上方便了開發(fā)者使用 Agora 的服務(wù)。但App ID 鑒權(quán)的安全性不佳,一旦有別有用心的人非法獲取了你的 App ID,他就可以在 Agora 提供的SDK中使用你的App ID。如果你的項(xiàng)目對(duì)安全性要求高,或者增加用戶權(quán)限設(shè)置的話,建議采用Token鑒權(quán)。
Token鑒權(quán)
在通信和直播場景中存在著多個(gè)角色,而每種角色又對(duì)應(yīng)著一些默認(rèn)權(quán)限。比如在直播場景中,主播可以發(fā)布流、訂閱流、邀請(qǐng)嘉賓;觀眾可以訂閱流、申請(qǐng)連麥;管理員則可以踢人或禁言。
Token鑒權(quán)的步驟比APP ID鑒權(quán)稍微復(fù)雜一些,在上文項(xiàng)目列表中查看 App ID 的地方,啟用該項(xiàng)目的 App Certificate:
首先,點(diǎn)擊激活項(xiàng)目右上方的 編輯 按鈕。
將你的 App Certificate 保存在服務(wù)器端,且對(duì)任何客戶端均不可見。當(dāng)項(xiàng)目的 App Certificate 被啟用后,你必須使用 Token。例如: 在啟用 App Certificate 前,你可以使用 App ID 加入頻道。但啟用了 App Certificate 后,你就必須使用 Token 加入頻道。后臺(tái)如何用App Certificate生成Token本文不做贅述。
初始化Agora
RtcEngine 類包含應(yīng)用程序調(diào)用的主要方法,調(diào)用 RtcEngine 的接口最好在同一個(gè)線程進(jìn)行,不建議在不同的線程同時(shí)調(diào)用。
目前 Agora Native SDK 只支持一個(gè) RtcEngine 實(shí)例,每個(gè)應(yīng)用程序僅創(chuàng)建一個(gè) RtcEngine 對(duì)象 。 RtcEngine 類的所有接口函數(shù),如無特殊說明,都是異步調(diào)用,對(duì)接口的調(diào)用建議在同一個(gè)線程進(jìn)行。所有返回值為 int 型的 API,如無特殊說明,返回值 0 為調(diào)用成功,返回值小于 0 為調(diào)用失敗。
IRtcEngineEventHandler接口類用于SDK向應(yīng)用程序發(fā)送回調(diào)事件通知,應(yīng)用程序通過繼承該接口類的方法獲取 SDK 的事件通知。
接口類的所有方法都有缺省(空)實(shí)現(xiàn),應(yīng)用程序可以根據(jù)需要只繼承關(guān)心的事件。在回調(diào)方法中,應(yīng)用程序不應(yīng)該做耗時(shí)或者調(diào)用可能會(huì)引起阻塞的 API(如 SendMessage),否則可能影響 SDK 的運(yùn)行。
private RtcEngine mRtcEngine; /** * Tutorial Step 1 * 初始化Agora,創(chuàng)建 RtcEngine 對(duì)象 */ private void initializeAgoraEngine() { try { mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler); } catch (Exception e) { Log.e(LOG_TAG, Log.getStackTraceString(e)); throw new RuntimeException("Agora初始化失敗了,檢查一下是哪兒出錯(cuò)了 " + Log.getStackTraceString(e)); } } private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() { @Override public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) { runOnUiThread(new Runnable() { @Override public void run() { //設(shè)置遠(yuǎn)端視頻顯示屬性 setupRemoteVideo(uid); } }); } @Override public void onUserOffline(int uid, int reason) { runOnUiThread(new Runnable() { @Override public void run() { //其他用戶離開當(dāng)前頻道回調(diào) onRemoteUserLeft(); } }); } @Override public void onUserMuteVideo(final int uid, final boolean muted) { runOnUiThread(new Runnable() { @Override public void run() { //其他用戶已停發(fā)/已重發(fā)視頻流回調(diào) onRemoteUserVideoMuted(uid, muted); } }); } }; private void onRemoteUserLeft() { FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container); container.removeAllViews(); //文案可隨意定制 View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); tipMsg.setVisibility(View.VISIBLE); } private void onRemoteUserVideoMuted(int uid, boolean muted) { FrameLayout container = (FrameLayout) 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); } }
打開視頻模式
enableVideo()方法用于打開視頻模式。可以在加入頻道前或者通話中調(diào)用,在加入頻道前調(diào)用,則自動(dòng)開啟視頻模式,在通話中調(diào)用則由音頻模式切換為視頻模式。調(diào)用 disableVideo() 方法可關(guān)閉視頻模式。
setVideoProfile()方法設(shè)置視頻編碼屬性(Profile)。每個(gè)屬性對(duì)應(yīng)一套視頻參數(shù),如分辨率、幀率、碼率等。 當(dāng)設(shè)備的攝像頭不支持指定的分辨率時(shí),SDK 會(huì)自動(dòng)選擇一個(gè)合適的攝像頭分辨率,但是編碼分辨率仍然用 setVideoProfile() 指定的。
該方法僅設(shè)置編碼器編出的碼流屬性,可能跟最終顯示的屬性不一致,例如編碼碼流分辨率為 640x480,碼流的旋轉(zhuǎn)屬性為 90 度,則顯示出來的分辨率為豎屏模式。
/** * Tutorial Step 2 * 打開視頻模式,并設(shè)置本地視頻屬性 */ private void setupVideoProfile() { //打開視頻模式 mRtcEngine.enableVideo(); //設(shè)置本地視頻屬性 mRtcEngine.setVideoProfile(Constants.VIDEO_PROFILE_360P, false); }
設(shè)置本地視頻顯示屬性
setupLocalVideo( VideoCanvas local )方法用于設(shè)置本地視頻顯示信息。應(yīng)用程序通過調(diào)用此接口綁定本地視頻流的顯示視窗(view),并設(shè)置視頻顯示模式。 在應(yīng)用程序開發(fā)中,通常在初始化后調(diào)用該方法進(jìn)行本地視頻設(shè)置,然后再加入頻道。退出頻道后,綁定仍然有效,如果需要解除綁定,可以調(diào)用 setupLocalVideo(null) 。
/** * Tutorial Step 3 * 設(shè)置本地視頻顯示屬性 */ private void setupLocalVideo() { FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container); SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext()); surfaceView.setZOrderMediaOverlay(true); container.addView(surfaceView); mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_ADAPTIVE, 0)); }
加入一個(gè)頻道
joinChannel(String token,String channelName,String optionalInfo,int optionalUid )方法讓用戶加入通話頻道,在同一個(gè)頻道內(nèi)的用戶可以互相通話,多個(gè)用戶加入同一個(gè)頻道,可以群聊。 使用不同 App ID 的應(yīng)用程序是不能互通的。如果已在通話中,用戶必須調(diào)用 leaveChannel() 退出當(dāng)前通話,才能進(jìn)入下一個(gè)頻道。
/** * Tutorial Step 4 * 加入一個(gè)頻道 */ private void joinChannel() { //如果不指定UID,Agroa將自動(dòng)生成并分配一個(gè)UID mRtcEngine.joinChannel(null, "demoChannel1", "Extra Optional Data", 0); }
設(shè)置遠(yuǎn)端視頻顯示屬性
setupRemoteVideo( VideoCanvas remote)方法用于綁定遠(yuǎn)程用戶和顯示視圖,即設(shè)定 uid 指定的用戶用哪個(gè)視圖顯示。調(diào)用該接口時(shí)需要指定遠(yuǎn)程視頻的 uid,一般可以在進(jìn)頻道前提前設(shè)置好。
如果應(yīng)用程序不能事先知道對(duì)方的 uid,可以在 APP 收到 onUserJoined 事件時(shí)設(shè)置。如果啟用了視頻錄制功能,視頻錄制服務(wù)會(huì)做為一個(gè)啞客戶端加入頻道,因此其他客戶端也會(huì)收到它的 onUserJoined 事件,APP 不應(yīng)給它綁定視圖(因?yàn)樗粫?huì)發(fā)送視頻流),如果 APP 不能識(shí)別啞客戶端,可以在 onFirstRemoteVideoDecoded 事件時(shí)再綁定視圖。解除某個(gè)用戶的綁定視圖可以把 view 設(shè)置為空。退出頻道后,SDK 會(huì)把遠(yuǎn)程用戶的綁定關(guān)系清除掉。
/** * Tutorial Step 5 * 設(shè)置遠(yuǎn)端視頻顯示屬性 */ private void setupRemoteVideo(int uid) { FrameLayout container = (FrameLayout) 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_ADAPTIVE, uid)); surfaceView.setTag(uid); //文案可隨意定制 View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); tipMsg.setVisibility(View.GONE); }
離開當(dāng)前頻道
leaveChannel()方法用于離開頻道,即掛斷或退出通話。
當(dāng)調(diào)用 joinChannel() API 方法后,必須調(diào)用 leaveChannel() 結(jié)束通話,否則無法開始下一次通話。 不管當(dāng)前是否在通話中,都可以調(diào)用 leaveChannel(),沒有副作用。該方法會(huì)把會(huì)話相關(guān)的所有資源釋放掉。該方法是異步操作,調(diào)用返回時(shí)并沒有真正退出頻道。在真正退出頻道后,SDK 會(huì)觸發(fā) onLeaveChannel 回調(diào)。
/** * Tutorial Step 6 * 離開當(dāng)前頻道 */ private void leaveChannel() { mRtcEngine.leaveChannel(); } public void onEncCallClicked(View view) { finish(); } @Override protected void onDestroy() { super.onDestroy(); leaveChannel(); RtcEngine.destroy(); mRtcEngine = null; }
管理攝像頭
switchCamera()方法用于在前置/后置攝像頭間切換。除此以外Agora還提供了一下管理攝像頭的方法:例如setCameraTorchOn(boolean isOn)設(shè)置是否打開閃光燈、setCameraAutoFocusFaceModeEnabled(boolean enabled)設(shè)置是否開啟人臉對(duì)焦功能等等。
/** * Tutorial Step 7 * 切換前置/后置攝像頭 */ public void onSwitchCameraClicked(View view) { mRtcEngine.switchCamera(); }
將自己靜音
muteLocalAudioStream(boolean muted)方法用于靜音/取消靜音。該方法可以允許/禁止往網(wǎng)絡(luò)發(fā)送本地音頻流。但該方法并沒有禁用麥克風(fēng),不影響錄音狀態(tài)。
/** * Tutorial Step 8 * 將自己靜音 */ 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()); }
暫停本地視頻流
muteLocalVideoStream(boolean muted)方法用于暫停發(fā)送本地視頻流,但該方法并沒有禁用攝像頭,不影響本地視頻流獲取。
/** * Tutorial Step 9 * 暫停本地視頻流 */ 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()); FrameLayout container = (FrameLayout) 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); }
運(yùn)行效果
拿兩部手機(jī)安裝編譯好的App,如果能看見兩個(gè)自己,說明你成功了。
通過本文的學(xué)習(xí),我們已經(jīng)掌握了利用Agora SDK進(jìn)行一對(duì)一聊天的技巧,接下來的文章中,我將繼續(xù)介紹如何實(shí)現(xiàn)多人聊天室。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/69029.html
摘要:我們先實(shí)現(xiàn)一個(gè)瀑布流瀑布流的實(shí)現(xiàn)方式很多,本文采用結(jié)合的來實(shí)現(xiàn)。有了一個(gè)可用的瀑布流之后,下面我們就可以實(shí)現(xiàn)動(dòng)態(tài)聊天窗了動(dòng)態(tài)聊天窗的要點(diǎn)在于的大小由視頻的寬高比決定,因此及其對(duì)應(yīng)的就該注意不要寫死尺寸。 作者:聲網(wǎng)用戶,資深A(yù)ndroid工程師吳東洋本系列文章分享了基于Agora SDK 2.1實(shí)現(xiàn)多人視頻通話的實(shí)踐經(jīng)驗(yàn)。 在上一篇《Android 多人視頻聊天應(yīng)用的開發(fā)(一)一對(duì)一聊...
閱讀 2064·2023-04-25 22:58
閱讀 1408·2021-09-22 15:20
閱讀 2694·2019-08-30 15:56
閱讀 1986·2019-08-30 15:54
閱讀 2101·2019-08-29 12:31
閱讀 2728·2019-08-26 13:37
閱讀 592·2019-08-26 13:25
閱讀 2098·2019-08-26 11:58