Android 開發實踐:實現17人視頻l聊天應用

自從2016年,鼓吹“互聯網寒冬”的論調甚囂塵上,2017年亦有愈演愈烈之勢。但連麥直播、在線抓娃娃、直播問答、遠程狼人殺等類型的項目卻異軍突起,成了投資人的風口,創業者的藍海和用戶的必裝App,而這些方向的項目都有一個共同的特點——都依賴視頻通話和全互動直播技術。

目前有很多第三方平臺提供實時音視頻通訊服務,讓Android、iOS開發者們不用去考慮網絡延時、設備兼容等方面的問題。我們這次將試著通過聲網的SDK實現一個多人視頻通話應用。

環境

聲網Agora.io SDK的兼容性良好,對硬件設備和軟件系統的要求不高,開發環境和測試環境滿足以下條件即可:

  • Android SDK API Level >= 16
  • Android Studio 2.0 或以上版本
  • 支持語音和視頻功能的真機
  • App 要求 Android 4.1 或以上設備

以下是我試用聲網Agora.io SDK的開發環境和測試環境:

  • 開發環境
  • Windows 10 家庭中文版
  • Java Version SE 8
  • Android Studio 3.2 Canary 4

測試環境

  • Samsung Nexus (Android 4.4.2 API 19)
  • Mi Note 3 (Android 7.1.1 API 25)

集成

步驟一:首先點此下載完整的SDK和官方demo

步驟二:既然我們要把聲網Agora.io集成到自己的項目裡,所以不必運行sample,我們自己新建一個HelloAgora項目,注意一定要支持C++哦。

步驟三:把libs文件夾裡的arm64-v8a、、armeabi-v7a以及x86文件夾複製粘貼到app module的libs裡。如果有NDK開發的必要,則把libs->include文件夾裡的兩個.h頭文件複製粘貼到合適位置。

步驟四:首先在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'

如果用複製粘貼jar的方式,那麼此處添加如下代碼:

compile fileTree(dir: '../../../libs', include: ['*.jar'])

如果有自定義NDK的必要,可以繼續在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一下,聲網Agora.io的SDK就集成到項目中來了。

權限

SDK集成完畢後,為了保證SDK能正常運行,我們需要在AndroidManisfest.xml 文件中聲明以下權限:


<uses-permission>


<uses-permission>

<uses-permission>

<uses-permission>

<uses-permission>

<uses-permission>

<uses-permission>

這些權限都是Android開發過程中的常見權限,有經驗的程序員都會感覺眼熟,WRITE_EXTERNAL_STORAGE等敏感權限適配Android 6.0以後版本的問題並非本文關注重點,在此不做贅述。

混淆代碼

集成SDK並聲明瞭權限後,就該考慮混淆的問題了,我們需要在project的proguard-rules.pro文件裡添加以下代碼:

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

經過以上過程後,我們已經完成了聲網Agora.io SDK的快速集成,邁出了走向連麥直播、在線抓娃娃、直播問答、遠程狼人殺等風口的第一步。在接下來的文章裡,我將繼續分享APP ID鑑權、Token鑑權、一對一視頻聊天、創建群聊room、分屏、窗口切換和前後攝像頭切換等內容。

鑑權

APP ID鑑權

所謂APP ID,就是在 Agora創建每個項目都有的一個唯一標識。App ID 可以明確你的項目及組織身份,並在 joinChannel 方法中作為參數,連接到 Agora 實時網絡中,實現實時通信或直播功能。不同的App ID在Agora實時網絡中的通話是完全隔離的;Agora 提供的頻道信息、計費、管理服務也都是基於 App ID。

申請APP ID的操作很簡便,只要在Agora官網https://dashboard.agora.io/projects右側欄目的“項目”中點擊“添加新項目”,只需輸入項目名就可生成APP ID,全過程如下圖所示:

Android 開發實踐:實現17人視頻l聊天應用

找到,把“”替換為圖中的馬賽克里的字符串。

<string>

以上就是APP ID鑑權的全過程。

儘管App ID鑑權在最大程度上方便了開發者使用 Agora 的服務。但App ID 鑑權的安全性不佳,一旦有別有用心的人非法獲取了你的 App ID,他就可以在 Agora 提供的SDK中使用你的App ID。如果你的項目對安全性要求高,或者增加用戶權限設置的話,建議採用Token鑑權。

Token鑑權

在通信和直播場景中存在著多個角色,而每種角色又對應著一些默認權限。比如在直播場景中,主播可以發佈流、訂閱流、邀請嘉賓;觀眾可以訂閱流、申請連麥;管理員則可以踢人或禁言。

Token鑑權的步驟比APP ID鑑權稍微複雜一些,在上文項目列表中查看 App ID 的地方,啟用該項目的 App Certificate:

首先,點擊激活項目右上方的 編輯 按鈕。

Android 開發實踐:實現17人視頻l聊天應用

將你的 App Certificate 保存在服務器端,且對任何客戶端均不可見。當項目的 App Certificate 被啟用後,你必須使用 Token。例如: 在啟用 App Certificate 前,你可以使用 App ID 加入頻道。但啟用了 App Certificate 後,你就必須使用 Token 加入頻道。後臺如何用App Certificate生成Token本文不做贅述。

初始化Agora

RtcEngine 類包含應用程序調用的主要方法,調用 RtcEngine 的接口最好在同一個線程進行,不建議在不同的線程同時調用。

目前 Agora Native SDK 只支持一個 RtcEngine 實例,每個應用程序僅創建一個 RtcEngine 對象 。 RtcEngine 類的所有接口函數,如無特殊說明,都是異步調用,對接口的調用建議在同一個線程進行。所有返回值為 int 型的 API,如無特殊說明,返回值 0 為調用成功,返回值小於 0 為調用失敗。

IRtcEngineEventHandler接口類用於SDK嚮應用程序發送回調事件通知,應用程序通過繼承該接口類的方法獲取 SDK 的事件通知。

接口類的所有方法都有缺省(空)實現,應用程序可以根據需要只繼承關心的事件。在回調方法中,應用程序不應該做耗時或者調用可能會引起阻塞的 API(如 SendMessage),否則可能影響 SDK 的運行。

private RtcEngine mRtcEngine;
/**
* Tutorial Step 1
* 初始化Agora,創建 RtcEngine 對象
*/
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初始化失敗了,檢查一下是哪兒出錯了\n" + 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() {
//設置遠端視頻顯示屬性
setupRemoteVideo(uid);
}
});
}
@Override
public void onUserOffline(int uid, int reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//其他用戶離開當前頻道回調
onRemoteUserLeft();
}
});
}
@Override
public void onUserMuteVideo(final int uid, final boolean muted) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//其他用戶已停發/已重發視頻流回調
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()方法用於打開視頻模式。可以在加入頻道前或者通話中調用,在加入頻道前調用,則自動開啟視頻模式,在通話中調用則由音頻模式切換為視頻模式。調用 disableVideo() 方法可關閉視頻模式。

setVideoProfile()方法設置視頻編碼屬性(Profile)。每個屬性對應一套視頻參數,如分辨率、幀率、碼率等。 當設備的攝像頭不支持指定的分辨率時,SDK 會自動選擇一個合適的攝像頭分辨率,但是編碼分辨率仍然用 setVideoProfile() 指定的。

該方法僅設置編碼器編出的碼流屬性,可能跟最終顯示的屬性不一致,例如編碼碼流分辨率為 640x480,碼流的旋轉屬性為 90 度,則顯示出來的分辨率為豎屏模式。

/**
* Tutorial Step 2
* 打開視頻模式,並設置本地視頻屬性
*/
private void setupVideoProfile() {
//打開視頻模式
mRtcEngine.enableVideo();
//設置本地視頻屬性
mRtcEngine.setVideoProfile(Constants.VIDEO_PROFILE_360P, false);
}

設置本地視頻顯示屬性

setupLocalVideo( VideoCanvas local )方法用於設置本地視頻顯示信息。應用程序通過調用此接口綁定本地視頻流的顯示視窗(view),並設置視頻顯示模式。 在應用程序開發中,通常在初始化後調用該方法進行本地視頻設置,然後再加入頻道。退出頻道後,綁定仍然有效,如果需要解除綁定,可以調用 setupLocalVideo(null) 。

/**
* Tutorial Step 3
* 設置本地視頻顯示屬性
*/
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));
}

加入一個頻道

joinChannel(String token,String channelName,String optionalInfo,int optionalUid )方法讓用戶加入通話頻道,在同一個頻道內的用戶可以互相通話,多個用戶加入同一個頻道,可以群聊。 使用不同 App ID 的應用程序是不能互通的。如果已在通話中,用戶必須調用 leaveChannel() 退出當前通話,才能進入下一個頻道。

/**
* Tutorial Step 4
* 加入一個頻道
*/
private void joinChannel() {
//如果不指定UID,Agroa將自動生成並分配一個UID
mRtcEngine.joinChannel(null, "demoChannel1", "Extra Optional Data", 0);
}

設置遠端視頻顯示屬性

setupRemoteVideo( VideoCanvas remote)方法用於綁定遠程用戶和顯示視圖,即設定 uid 指定的用戶用哪個視圖顯示。調用該接口時需要指定遠程視頻的 uid,一般可以在進頻道前提前設置好。

如果應用程序不能事先知道對方的 uid,可以在 APP 收到 onUserJoined 事件時設置。如果啟用了視頻錄製功能,視頻錄製服務會做為一個啞客戶端加入頻道,因此其他客戶端也會收到它的 onUserJoined 事件,APP 不應給它綁定視圖(因為它不會發送視頻流),如果 APP 不能識別啞客戶端,可以在 onFirstRemoteVideoDecoded 事件時再綁定視圖。解除某個用戶的綁定視圖可以把 view 設置為空。退出頻道後,SDK 會把遠程用戶的綁定關係清除掉。

/**
* Tutorial Step 5
* 設置遠端視頻顯示屬性
*/
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);
}

離開當前頻道

leaveChannel()方法用於離開頻道,即掛斷或退出通話。

當調用 joinChannel() API 方法後,必須調用 leaveChannel() 結束通話,否則無法開始下一次通話。 不管當前是否在通話中,都可以調用 leaveChannel(),沒有副作用。該方法會把會話相關的所有資源釋放掉。該方法是異步操作,調用返回時並沒有真正退出頻道。在真正退出頻道後,SDK 會觸發 onLeaveChannel 回調。

/**
* Tutorial Step 6
* 離開當前頻道
*/
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)設置是否打開閃光燈、setCameraAutoFocusFaceModeEnabled(boolean enabled)設置是否開啟人臉對焦功能等等。

/**
* Tutorial Step 7
* 切換前置/後置攝像頭
*/
public void onSwitchCameraClicked(View view) {
mRtcEngine.switchCamera();
}

將自己靜音

muteLocalAudioStream(boolean muted)方法用於靜音/取消靜音。該方法可以允許/禁止往網絡發送本地音頻流。但該方法並沒有禁用麥克風,不影響錄音狀態。

/**
* 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)方法用於暫停發送本地視頻流,但該方法並沒有禁用攝像頭,不影響本地視頻流獲取。

/**
* 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);
}

完成一對一視頻通話

拿兩部手機安裝編譯好的App,如果能看見兩個自己,說明你成功了。至此我們已經實現了一對一的視頻通話。接下來,我們來進一步實現多人視頻通話功能,主要需要解決兩個問題:

  1. 隨著加入人數和他們的手機攝像頭分辨率的變化,顯示不同的UI,即所謂的“分屏”
  2. 點擊分屏中的小窗,可以放大顯示該聊天窗

分屏

根據前期技術調研,分屏顯示最好的方式是採用瀑布流結合動態聊天窗實現,這樣比較方便的能夠適應UI的變化。所謂瀑布流,就是目前比較流行的一種列表佈局,會在界面上呈現參差不齊的多欄佈局。我們先實現一個瀑布流:

瀑布流的實現方式很多,本文采用結合 GridLayoutManager的RecyclerView 來實現。我們首先自定義一個 RecyclerView,命名為 GridVideoViewContainer。核心代碼如下:

int count = uids.size();
if (count <= 2) {
// 只有本地視頻或聊天室內只有另外一個人
this.setLayoutManager(new LinearLayoutManager(activity.getApplicationContext(), orientation, false));
} else if (count > 2) {

// 多人聊天室
int itemSpanCount = getNearestSqrt(count);
this.setLayoutManager(new GridLayoutManager(activity.getApplicationContext(), itemSpanCount, orientation, false));
}

根據上面的代碼可以看出,在聊天室裡只有自己的本地視頻或者只有另外一個人的時候,採用 LinearLayoutManager,這樣的佈局其實與前文的一對一聊天類似;而在真正意義的多人聊天室裡,則採用 GridLayoutManager 實現瀑布流,其中 itemSpanCount 就是瀑布流的列數。

有了一個可用的瀑布流之後,下面我們就可以實現動態聊天窗了:

動態聊天窗的要點在於 item 的大小由視頻的寬高比決定,因此 Adapter 及其對應的 layout 就該注意不要寫死尺寸。在 Adapter 裡控制 item 具體尺寸的代碼如下:

if (force || mItemWidth == 0 || mItemHeight == 0) {
WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(outMetrics);
int count = uids.size();
int DividerX = 1;
int DividerY = 1;
if (count == 2) {
DividerY = 2;
} else if (count >= 3) {
DividerX = getNearestSqrt(count);
DividerY = (int) Math.ceil(count * 1.f / DividerX);
}
int width = outMetrics.widthPixels;
int height = outMetrics.heightPixels;
if (width > height) {
mItemWidth = width / DividerY;
mItemHeight = height / DividerX;
} else {
mItemWidth = width / DividerX;

mItemHeight = height / DividerY;
}
}

以上代碼根據視頻的數量確定了列數和行數,然後根據列數和屏幕寬度確定了視頻的寬度,接著根據視頻的寬高比和視頻寬度確定了視頻高度。同時也考慮了手機的橫豎屏情況(就是if (width > height)這行代碼)。

該 Adapter 對應的 layout 的代碼如下:

<relativelayout> xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/user_control_mask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<imageview> android:id="@+id/default_avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
android:class="lazy" data-original="@drawable/icon_default_avatar"
android:contentDescription="DEFAULT_AVATAR" />
<imageview> android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/video_indicator_bottom_margin"
android:contentDescription="VIDEO_INDICATOR" />
<linearlayout> android:id="@+id/video_info_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="24dp"
android:layout_marginStart="15dp"
android:layout_marginLeft="15dp"
android:visibility="gone"
android:orientation="vertical">
<textview> android:id="@+id/video_info_metadata"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"

style="@style/NotificationUIText" />
/<textview>/<linearlayout>
/<imageview>/<imageview>/<relativelayout>

我們可以看到,layout 中有關尺寸的屬性都 是wrap_content,這就使得 item 大小隨視頻寬高比變化成為可能。

把分屏的佈局寫好之後,我們就可以在每一個 item 上播放聊天視頻了。

播放聊天視頻

在 Agora SDK 中一個遠程視頻的顯示只和該用戶的 UID 有關,所以使用的數據源只需要簡單定義為包含 UID 和對應的 SurfaceView 即可,就像這樣:

 private final HashMap<integer> mUidsList = new HashMap<>();
```
每當有人加入了我們的聊天頻道,都會觸發`onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed)`方法,第一個 uid 就是他們的 UID;接下來我們要為每個 item 新建一個 SurfaceView 併為其創建渲染視圖,最後將它們加入剛才創建好的mUidsList裡並調用`setupRemoteVideo( VideoCanvas remote )`方法播放這個聊天視頻。這個過程的完整代碼如下:
```
@Override
public void onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed) {
doRenderRemoteUi(uid);
}
private void doRenderRemoteUi(final int uid) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (isFinishing()) {
return;

}
if (mUidsList.containsKey(uid)) {
return;
}
SurfaceView surfaceV = RtcEngine.CreateRendererView(getApplicationContext());
mUidsList.put(uid, surfaceV);
boolean useDefaultLayout = mLayoutType == LAYOUT_TYPE_DEFAULT;
surfaceV.setZOrderOnTop(true);
surfaceV.setZOrderMediaOverlay(true);
rtcEngine().setupRemoteVideo(new VideoCanvas(surfaceV, VideoCanvas.RENDER_MODE_HIDDEN, uid));
if (useDefaultLayout) {
log.debug("doRenderRemoteUi LAYOUT_TYPE_DEFAULT " + (uid & 0xFFFFFFFFL));
switchToDefaultVideoView();
} else {
int bigBgUid = mSmallVideoViewAdapter == null ? uid : mSmallVideoViewAdapter.getExceptedUid();
log.debug("doRenderRemoteUi LAYOUT_TYPE_SMALL " + (uid & 0xFFFFFFFFL) + " " + (bigBgUid & 0xFFFFFFFFL));
switchToSmallVideoView(bigBgUid);
}
}
});
}
/<integer>

以上代碼與前文中播放一對一視頻的代碼如出一撤,但是細心的讀者可能已經發現我們並沒有將生成的 SurfaceView 放在界面裡,這正是與一對一視頻的不同之處:我們要在一個抽象的 VideoViewAdapter 類裡將 SurfaceView 放出來,關鍵代碼如下:

SurfaceView target = user.mView;
VideoViewAdapterUtil.stripView(target);
holderView.addView(target, 0, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

一般 Android 工程師看見 holderView 就明白這是 ViewHolder 的 layout 的根 layout 了,而 user 是哪兒來的,詳見文末的代碼,文中不做贅述。

這樣在多人聊天的時候我們就能使用分屏的方式播放用戶聊天視頻了,如果想放大某一個用戶的視頻該怎麼辦呢?

全屏和小窗

當用戶雙擊某一個 item 的時候,他希望對應的視頻能夠全屏顯示,而其他的視頻則變成小窗口,那麼我們先定義一個雙擊事件接口:

public interface VideoViewEventListener {
void onItemDoubleClick(View v, Object item);
}
具體實現方式如下:
mGridVideoViewContainer.setItemEventHandler(new VideoViewEventListener() {
@Override
public void onItemDoubleClick(View v, Object item) {
log.debug("onItemDoubleClick " + v + " " + item + " " + mLayoutType);
if (mUidsList.size() < 2) {
return;
}
UserStatusData user = (UserStatusData) item;
int uid = (user.mUid == 0) ? config().mUid : user.mUid;
if (mLayoutType == LAYOUT_TYPE_DEFAULT && mUidsList.size() != 1) {
switchToSmallVideoView(uid);
} else {
switchToDefaultVideoView();
}
}
});

將被選中的視頻全屏播放的方法很容易理解,我們只看生成小窗列表的方法:

private void switchToSmallVideoView(int bigBgUid) {
HashMap<integer> slice = new HashMap<>(1);
slice.put(bigBgUid, mUidsList.get(bigBgUid));
Iterator<surfaceview> iterator = mUidsList.values().iterator();
while (iterator.hasNext()) {
SurfaceView s = iterator.next();
s.setZOrderOnTop(true);
s.setZOrderMediaOverlay(true);
}

mUidsList.get(bigBgUid).setZOrderOnTop(false);
mUidsList.get(bigBgUid).setZOrderMediaOverlay(false);
mGridVideoViewContainer.initViewContainer(this, bigBgUid, slice, mIsLandscape);
bindToSmallVideoView(bigBgUid);
mLayoutType = LAYOUT_TYPE_SMALL;
requestRemoteStreamType(mUidsList.size());
}
/<surfaceview>/<integer>

小窗列表要注意移除全屏的那個 UID,此外一切都和正常瀑布流視圖相同,包括雙擊小窗的item將其全屏播放。

到了這裡我們就已經使用 Agora SDK 完成了一個有基本功能的簡單多人聊天 demo,要產品化還有很多的東西要做,在這裡先做一個簡單的總結吧!

總結

聲網Agora SDK已經僅覆蓋了主流的操作系統,集成效率也比較高,而且還支持包括聊天,會議,直播等功能在內的多個模式的視頻通話。SDK 中 API 設計基本能夠滿足大部分的開發需要,而且隱藏了底層開發,只需要提供 SurfaceView 和 UID 即可播放視頻,這樣對於 App 層的開發者來說十分友好。非常適合有視頻聊天開發需求的開發者。


分享到:


相關文章: