在一些有人際互動的手機APP中,增加語音視訊聊天功能是一個常見的需求。而現在,更進一步,在某些場景下,我們需要能將自己的手機螢幕分享給他人,或者是觀看他人的手機螢幕。那麼,這些常見的功能是如何實現的了?
我為此專門寫了一個安卓版的Demo,並將原始碼放出來供大家參考,希望對大家有所幫助。
(1)每個登入的使用者都可向其他任意線上使用者傳送視訊聊天請求。
(2)當收到來自其他線上使用者的視訊聊天邀請時,可接受或拒絕對方的請求。
(3)當接受其他線上使用者的視訊聊天邀請時,就啟動視訊聊天。
(1)每個登入的使用者都可向其他任意線上使用者傳送螢幕分享請求;當對方未響應時,可主動取消螢幕分享請求。
(2)當收到來自其他線上使用者請求螢幕分享時,可接受或拒絕對方的請求。
(3)當傳送方收到其他線上使用者同意螢幕分享時,即可觀看其螢幕
(4)被控端和主控端都可主動斷開螢幕分享。
Android Studio 4.0
JAVA
Netty 、OMCS
類似視訊聊天或螢幕分享這樣的功能,一般是C/S架構的。在這種應用中,伺服器端相對簡單,其主要是在使用者端之間轉發訊息。本Demo提供了一個非常簡易的C#伺服器端(開發環境:VS 2022),直接執行起來即可。下面我們將主要介紹安卓端的實現。
大家可以從文末下載安卓端的原始碼,在閱讀本文時對照原始碼,就會更清楚些。
首先,我們先要確定使用者端之間相互通訊的訊息型別。
public class InformationTypes {
/// <summary>
/// 視訊請求 0
/// </summary>
public static final int VideoRequest = 0;
/// <summary>
/// 回覆視訊請求的結果 1
/// </summary>
public static final int VideoResult = 1;
/// <summary>
/// 通知對方 結束通話 視訊連線 2
/// </summary>
public static final int CloseVideo = 2;
/// <summary>
/// 通知好友 網路原因,導致 視訊中斷 3
/// </summary>
public static final int NetReasonCloseVideo = 3;
/// <summary>
/// 通知對方(忙線中) 結束通話 視訊連線 4
/// </summary>
public static final int BusyLine = 4;
/// <summary>
/// 螢幕分享請求 5
/// </summary>
public static final int DesktopRequest = 5;
/// <summary>
/// 回覆螢幕分享請求的結果 6
/// </summary>
public static final int DesktopResult = 6;
/// <summary>
/// 主動取消螢幕分享請求
/// </summary>
public static final int CancelDesktop = 7;
/// <summary>
/// 對方(主人端)主動斷開螢幕分享
/// </summary>
public static final int OwnerCloseDesktop = 8;
/// <summary>
/// 客人端斷開螢幕分享
/// </summary>
public static final int GuestCloseDesktop = 9;
}
這裡我們定義了為了實現第一部分「功能介紹」中的功能,所需要用到的訊息型別。
在安卓上進行視訊聊天和螢幕分享,APP需要向安卓系統申請3個許可權:麥克風、攝像頭、螢幕錄製。
private void getPermission() {
List<PermissionItem> permissionItems = new ArrayList<PermissionItem>();
permissionItems.add(new PermissionItem(Manifest.permission.CAMERA, "相機", R.drawable.permission_ic_camera));
permissionItems.add(new PermissionItem(Manifest.permission.RECORD_AUDIO, "麥克風", R.drawable.permission_ic_micro_phone));
permissionItems.add(new PermissionItem(Manifest.permission.WRITE_EXTERNAL_STORAGE, "儲存", R.drawable.permission_ic_storage));
permissionItems.add(new PermissionItem(Manifest.permission.READ_EXTERNAL_STORAGE, "", 0));
try {
HiPermission.create(LoginActivity.this)
.title("歡迎存取" + getString(R.string.app_name))
.permissions(permissionItems)
.checkMutiPermission(new PermissionCallback() {
String TAG = getString(R.string.app_name);
@Override
public void onClose() {
Log.i(TAG, "onClose");
}
@Override
public void onFinish() {
Log.i(TAG, "onFinish");
}
@Override
public void onDeny(String permission, int position) {
Log.i(TAG, "onDeny- permission:" + permission + " position:" + position);
}
@Override
public void onGuarantee(String permission, int position) {
Log.i(TAG, "onGuarantee");
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
當安卓手機首次進入該Demo時, 將彈窗提示獲取裝置許可權:
注:若禁止了這兩個許可權,後續就無法進行正常的視訊聊天了!
MultimediaManagerFactory.GetSingleton().setDesktopRecordActivity(MainActivity.this);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
MultimediaManagerFactory.GetSingleton().setDesktopRecordActivityResult(requestCode,resultCode,data);
}
當收到其他線上使用者的螢幕分享請求並回復同意時,將彈窗獲取螢幕許可權:
注:若禁止該許可權,後續對方就無法看到分享者的螢幕了。
當發起視訊聊天時,將顯示視訊聊天視窗,並開啟手機攝像頭預覽畫面,然後向對方傳送視訊通話請求:
CameraSurfaceView2 myView = null;
MultimediaManagerFactory.GetSingleton().getAudioMessageController().dispose();
AndroidUtil.OpenSpeaker(this);
try {
MultimediaManagerFactory.GetSingleton().openCamera();
} catch (Exception e) {
e.printStackTrace();
}
this.tv_nick = (TextView) findViewById(R.id.tv_nick);
myView = (CameraSurfaceView2) findViewById(R.id.local_surface);
myView.setSurfaceEventLister(new CameraSurfaceView2.SurfaceEventLister() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
setShowPreviewHolder(surfaceHolder);
}
});
myView.setZOrderOnTop(true);
MultimediaManagerFactory.GetSingleton().setCameraDeviceIndex(1);//設定為前置攝像頭
//設定攝像頭開啟成功回撥函數
MultimediaManagerFactory.GetSingleton().setCameraOpenCallBack(this);
if (StringHelper.isNullOrEmpty(userId)) {
isSender = true;
//我向對方發起視訊
userId = getIntent().getStringExtra(TalkingID);
if (StringHelper.isNullOrEmpty(userId)) {
tv_nick.setText("未知requestID");
} else {
ll_to_callLayout.setVisibility(View.VISIBLE);
coming_callLayout.setVisibility(View.GONE);
hangup.setVisibility(View.VISIBLE);
MainActivity.getInstance().sendMediaCommunicate(userId, CommunicateType.Request);
tv_tips.setText("正在等待對方接受邀請");
}
}
執行起來的UI截圖如下所示:
當收到對方的視訊聊天邀請時,將進入視訊預覽頁面,顯示視訊邀請。
當點選「接聽」或「結束通話」按鈕時,就會傳送視訊聊天回覆訊息:
//接聽
answer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
MainActivity.getInstance().stopRingForCalling();
coming_callLayout.setVisibility(View.GONE);
ll_to_callLayout.setVisibility(View.VISIBLE);
openConnector();
MainActivity.getInstance().sendMediaCommunicate(userId, CommunicateType.Agree);
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
//拒絕
refuse.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
MainActivity.getInstance().sendMediaCommunicate(userId, CommunicateType.Reject);
MainActivity.getInstance().stopRingForCalling();
finish();
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
當對方回覆同意時,自己和對方將相互連線到對方的麥克風和攝像頭。
private void openConnector() {
try {
if (thread2 != null) {
thread2.interrupt();
}
hangup.setVisibility(View.VISIBLE);
switch_camera_layout.setVisibility(View.VISIBLE);
ll_top_container.setVisibility(View.INVISIBLE);
thread2 = new Thread(new Runnable() {
Override
public void run() {
//在這裡關閉不能重新連線
cameraConnector = new CameraConnector();
cameraConnector.setOtherVideoPlayerSurfaceView(otherView);
cameraConnector.setConnectorEventListener(new IConnectorEventListener() {
@Override
public void connectEnded(ConnectResult connectResult) {
final String connectFailStr = MainActivity.getConnectFailStr(connectResult);
if (!StringHelper.isNullOrEmpty(connectFailStr)) {
mHandler.post(new Runnable() {
@Override
public void run() {
tv_camera_failure_cause.setText("攝像頭:" + connectFailStr);
}
});
}
boolean isMobilePhone = cameraConnector.getOwnerMachineType() == MachineType.Android || cameraConnector.getOwnerMachineType() == MachineType.IOS;
cameraConnector.setVideoUniformScale(true, isMobilePhone); //false 表示小的那邊留黑邊,true表示裁剪大的那一邊
}
@Override
public void disconnected(ConnectorDisconnectedType connectorDisconnectedType) {
}
});
cameraConnector.beginConnect(loginID);
microphoneConnector = new MicrophoneConnector();
microphoneConnector.setConnectorEventListener(new IConnectorEventListener() {
@Override
public void connectEnded(final ConnectResult connectResult) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (connectResult == ConnectResult.Succeed) {
startTimer(SystemClock.elapsedRealtime());
} else {
String connectFailStr = MainActivity.getConnectFailStr(connectResult);
tv_mic_failure_cause.setText("麥克風:" + connectFailStr);
}
}
});
}
@Override
public void disconnected(ConnectorDisconnectedType connectorDisconnectedType) {
}
});
microphoneConnector.beginConnect(loginID);
}
});
thread2.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
當攝像頭和麥克風都連線成功後,就可以正常視訊聊天了。
螢幕分享功能的業務邏輯與視訊聊天功能的業務邏輯是相似的,這裡就不再贅述了,大家可以自行參看原始碼。
關於Demo的原始碼介紹就這麼多了,接下來我們看如何將Demo執行起來。
解壓 VideoChatMini.rar 後,進入解壓目錄,依次進入 VideoChatMini.Server -> bin -> debug 。
雙擊 Oraycn.Demos.VideoChatMini.Server.exe ,即可啟動視訊聊天伺服器端。伺服器端執行介面如下所示:
解壓安卓端原始碼壓縮包 VideoChatMini.Android.rar,解壓後,使用 Android Studio 開啟並編譯,將生成的apk傳送到手機安裝。
我們可以用兩部手機,啟動並登入兩個安卓使用者端,登入的賬號密碼可以隨便填。安卓端登入成功後,出現如下介面:
我們在 「對方ID」 輸入框中填上對方的登入賬號,就可以發起視訊聊天邀請了。對應的介面截圖在前面已經貼出來了。
對方同意視訊邀請後,兩個人就開啟視訊聊天了,執行效果如下所示:
Android 端:VideoChatMini.Android.rar
伺服器端 + PC 端:VideoChatMini.rar
在這裡,我也給出了PC端的原始碼,PC端專案對應的目錄是 VideoChatMini.ClientWPF。伺服器端和PC端都是 C# 開發的(開發環境是 VS2022),PC端UI使用的是WPF。
PC端和安卓端是可以互通的,也就是可以相互視訊通話,以及觀看螢幕/桌面。
希望這篇文章會對你有所幫助,謝謝。