如何實現安卓屏幕分享及視頻聊天?(源碼)

在一些有人際互動的手機APP中,增加語音視頻聊天功能是一個常見的需求。而現在,更進一步,在某些場景下,我們需要能將自己的手機屏幕分享給他人,或者是觀看他人的手機屏幕。那麼,這些常見的功能是如何實現的了?
我爲此專門寫了一個安卓版的Demo,並將源碼放出來供大家參考,希望對大家有所幫助。

一.功能介紹

1. 視頻聊天

(1)每個登錄的用戶都可向其他任意在線用戶發送視頻聊天請求。

(2)當收到來自其他在線用戶的視頻聊天邀請時,可接受或拒絕對方的請求。

(3)當接受其他在線用戶的視頻聊天邀請時,就啓動視頻聊天。

2.屏幕分享

(1)每個登錄的用戶都可向其他任意在線用戶發送屏幕分享請求;當對方未響應時,可主動取消屏幕分享請求。

(2)當收到來自其他在線用戶請求屏幕分享時,可接受或拒絕對方的請求。

(3)當發送方收到其他在線用戶同意屏幕分享時,即可觀看其屏幕

(4)被控端和主控端都可主動斷開屏幕分享。

二.開發環境

1.開發工具:

Android Studio 4.0

2.開發語言:

JAVA

3.主要框架:

Netty 、OMCS

三.具體實現

類似視頻聊天或屏幕分享這樣的功能,一般是C/S架構的。在這種應用中,服務端相對簡單,其主要是在客戶端之間轉發消息。本Demo提供了一個非常簡易的C#服務端(開發環境:VS 2022),直接運行起來即可。下面我們將主要介紹安卓端的實現。
大家可以從文末下載安卓端的源碼,在閱讀本文時對照源碼,就會更清楚些。
首先,我們先要確定客戶端之間相互通信的消息類型。

1.自定義消息類型 InformationTypes

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;
}

這裏我們定義了爲了實現第一部分“功能介紹”中的功能,所需要用到的消息類型。

2. 獲取安卓系統權限

在安卓上進行視頻聊天和屏幕分享,APP需要向安卓系統申請3個權限:麥克風、攝像頭、屏幕錄製。

(1)獲取相機、麥克風、存儲權限

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時, 將彈窗提示獲取設備權限:

注:若禁止了這兩個權限,後續就無法進行正常的視頻聊天了!

(2)屏幕錄製權限

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

當收到其他在線用戶的屏幕分享請求並回復同意時,將彈窗獲取屏幕權限:

注:若禁止該權限,後續對方就無法看到分享者的屏幕了。

3. 發送視頻聊天請求

當發起視頻聊天時,將顯示視頻聊天窗口,並打開手機攝像頭預覽畫面,然後向對方發送視頻通話請求:

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截圖如下所示:

4. 回覆對方視頻請求

當收到對方的視頻聊天邀請時,將進入視頻預覽頁面,顯示視頻邀請。

當點擊“接聽”或“掛斷”按鈕時,就會發送視頻聊天回覆消息:

//接聽
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();
    }
  }
});

5. 相互連接對方的攝像頭、麥克風

當對方回覆同意時,自己和對方將相互連接到對方的麥克風和攝像頭。

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();
  }
}

當攝像頭和麥克風都連接成功後,就可以正常視頻聊天了。

6. 屏幕分享功能實現

屏幕分享功能的業務邏輯與視頻聊天功能的業務邏輯是相似的,這裏就不再贅述了,大家可以自行參看源碼。

四.部署運行

關於Demo的源碼介紹就這麼多了,接下來我們看如何將Demo運行起來。

1. 啓動服務端

解壓 VideoChatMini.rar 後,進入解壓目錄,依次進入 VideoChatMini.Server -> bin -> debug 。
雙擊 Oraycn.Demos.VideoChatMini.Server.exe ,即可啓動視頻聊天服務端。服務端運行界面如下所示:

2. 運行安卓端

解壓安卓端源碼壓縮包 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端和安卓端是可以互通的,也就是可以相互視頻通話,以及觀看屏幕/桌面。

希望這篇文章會對你有所幫助,謝謝。

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