手机视频聊天分享

在人际互动的手机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;
}

这里我们定义了为了实现第一部分"功能介绍"中的功能,所需要用到的消息类型。

  1. 获取安卓系统权限

在安卓上进行视频聊天和屏幕分享,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)屏幕录制权限

复制代码
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("正在等待对方接受邀请");
  }
}
  1. 回复对方视频请求

当收到对方的视频聊天邀请时,将进入视频预览页面,显示视频邀请。

当点击"接听"或"挂断"按钮时,就会发送视频聊天回复消息:

复制代码
//接听
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();
    }
  }
});
  1. 相互连接对方的摄像头、麦克风

当对方回复同意时,自己和对方将相互连接到对方的麦克风和摄像头。

复制代码
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();
  }
}

当摄像头和麦克风都连接成功后,就可以正常视频聊天了。

相关推荐
wanhengidc4 小时前
什么是站群服务器
运维·服务器·网络·游戏·智能手机
wanhengidc11 小时前
云手机玩游戏卡顿的原因都有哪些
智能手机·玩游戏
杂化轨道VSEPR11 小时前
一个手机的奇幻之旅(手机在基站间的切换)
5g·智能手机·信息与通信
vxtkjzxt88812 小时前
手机群控软件实现多设备监控与测试的技术解析
智能手机
龙信科技12 小时前
【国内电子数据取证厂商龙信科技】手机在纯净模式下如何安装客户端软件
科技·智能手机
Digitally12 小时前
如何轻松删除 realme 手机中的联系人
智能手机
SPFFC1893803305313 小时前
AI玩具排线专业生产与全球营销策略
人工智能·学习·智能手机·显示器·智能手表·平板·游戏机
Wency(王斯-CUEB)1 天前
苹果IOS26【手机空间场景模型已完成0%】+【无法载入照片】
智能手机
AORO20251 天前
2025全新三防平板科普:5G-A+卫星通信+国产化
网络·5g·智能手机·电脑·制造·信息与通信
wanhengidc1 天前
云手机长期使用会消耗很多流量吗
网络·游戏·智能手机·架构·云计算