手机视频聊天分享
在人际互动的手机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);@Overridepublic void onClose() {Log.i(TAG, "onClose");}@Overridepublic void onFinish() {Log.i(TAG, "onFinish");}@Overridepublic void onDeny(String permission, int position) {Log.i(TAG, "onDeny- permission:" + permission + " position:" + position);}@Overridepublic 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() {@Overridepublic 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("正在等待对方接受邀请");}
}
4. 回复对方视频请求
当收到对方的视频聊天邀请时,将进入视频预览页面,显示视频邀请。
当点击“接听”或“挂断”按钮时,就会发送视频聊天回复消息:
//接听
answer.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {@Overridepublic 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() {Overridepublic void run() {//在这里关闭不能重新连接cameraConnector = new CameraConnector();cameraConnector.setOtherVideoPlayerSurfaceView(otherView);cameraConnector.setConnectorEventListener(new IConnectorEventListener() {@Overridepublic void connectEnded(ConnectResult connectResult) {final String connectFailStr = MainActivity.getConnectFailStr(connectResult);if (!StringHelper.isNullOrEmpty(connectFailStr)) {mHandler.post(new Runnable() {@Overridepublic void run() {tv_camera_failure_cause.setText("摄像头:" + connectFailStr);}});}boolean isMobilePhone = cameraConnector.getOwnerMachineType() == MachineType.Android || cameraConnector.getOwnerMachineType() == MachineType.IOS;cameraConnector.setVideoUniformScale(true, isMobilePhone); //false 表示小的那边留黑边,true表示裁剪大的那一边}@Overridepublic void disconnected(ConnectorDisconnectedType connectorDisconnectedType) {}});cameraConnector.beginConnect(loginID);microphoneConnector = new MicrophoneConnector();microphoneConnector.setConnectorEventListener(new IConnectorEventListener() {@Overridepublic void connectEnded(final ConnectResult connectResult) {mHandler.post(new Runnable() {@Overridepublic void run() {if (connectResult == ConnectResult.Succeed) {startTimer(SystemClock.elapsedRealtime());} else {String connectFailStr = MainActivity.getConnectFailStr(connectResult);tv_mic_failure_cause.setText("麦克风:" + connectFailStr);}}});}@Overridepublic void disconnected(ConnectorDisconnectedType connectorDisconnectedType) {}});microphoneConnector.beginConnect(loginID);}});thread2.start();} catch (Exception ex) {ex.printStackTrace();}
}
当摄像头和麦克风都连接成功后,就可以正常视频聊天了。
相关文章:
手机视频聊天分享
在人际互动的手机APP中,增加语音视频聊天功能是一个常见的需求。而现在,更进一步,在某些场景下,我们需要能将自己的手机屏幕分享给他人,或者是观看他人的手机屏幕。那么,这些常见的功能是如何实现的了&…...
神经网络小记-优化器
优化器是深度学习中用于优化神经网络模型的一类算法,其主要作用是根据模型的损失函数来调整模型的参数,使得模型能够更好地拟合训练数据,提高模型的性能和泛化能力。优化器在训练过程中通过不断更新模型的参数,使模型逐步接近最优…...
200+行代码写一个简易的Qt界面贪吃蛇
照例先演示一下: 一个简单的Qt贪吃蛇,所有的图片都是我自己画的(得意)。 大致的运行逻辑和之前那个200行写一个C小黑窗贪吃蛇差不多,因此在写这个项目的时候,大多情况是在想怎么通过Qt给展现出来。 背景图…...
redis中使用bloomfilter的白名单功能解决缓存穿透问题
一 缓存预热 1.1 缓存预热 将需要的数据提前缓存到缓存redis中,可以在服务启动时候,或者在使用前一天完成数据的同步等操作。保证后续能够正常使用。 1.2 缓存穿透 在redis中,查询redis缓存数据没有内容,接着查询mysql数据库&…...
Spring Boot 2.7.8以后mysql-connector-java与mysql-connector-j
错误信息 如果升级到Spring Boot 2.7.8,可以看到因为找不到mysql-connector-java依赖而出现错误。 配置: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>&l…...
03|「如何写好一个 Prompt」
前言 Prompt 文章目录 前言一、通用模板和范式1. 组成2. 要求1)文字描述2)注意标点符号 一、通用模板和范式 1. 组成 指令(角色) 生成主体 额外要求 指令:模型具体完成的任务描述。例如,翻译一段文字&…...
关于提示词 Prompt
Prompt原则 原则1 提供清晰明确的指示 注意在提示词中添加正确的分割符号 prompt """ 请给出下面文本的摘要: <你的文本> """可以指定输出格式,如:Json、HTML提示词中可以提供少量实例,…...
【Linux多线程】线程的互斥与同步(附抢票案例代码+讲解)
线程的互斥与同步 💫 概念引入⭐️临界资源(Critical Resource):🌟临界区(Critical Section):✨互斥(Mutex): ⚡️结合代码看互斥☄️ 代码逻辑&a…...
ajax概述
目录 1.什么是ajax 2.ja原生ajax 3.jQuery框架的ajax 4.综合案例 1.什么是ajax Ajax 即"Asynchronous Javascript And XML"(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。Ajax 异步 JavaScript 和 XML&…...
小白带你学习linux的mysql服务(主从mysql服务和读写分离三十一)
目录 二、MySQL Replication优点: 三、MySQL复制类型 1、异步复制(Asynchronous repication) 2、全同步复制(Fully synchronous replication) 3、半同步复制(Semisynchronous replication)…...
【低代码专题方案】iPaaS运维方案,助力企业集成平台智能化高效运维
01 场景背景 随着IT行业的发展和各家企业IT建设的需要,信息系统移动化、社交化、大数据、系统互联、数据打通等需求不断增多,企业集成平台占据各个企业领域,成为各业务系统数据传输的中枢。 集成平台承接的业务系统越多,集成平台…...
Android SDK 上手指南||第一章 环境需求||第二章 IDE:Eclipse速览
第一章 环境需求 这是我们系列教程的第一篇,让我们来安装Android的开发环境并且把Android SDK运行起来! 介绍 欢迎来到Android SDK入门指南系列文章,如果你想开始开发Android App,这个系列将从头开始教你所须的技能。我们假定你…...
Amazon Linux上使用ec2-user来设置开机自启动的shell脚本
要在Amazon Linux上使用ec2-user来设置开机自启动的shell脚本,可以按照以下步骤操作: 1. 确保您拥有要设置自启动的shell脚本。假设脚本的路径是/home/ec2-user/myscript.sh。 2. 使用以下命令打开/etc/rc.d/rc.local文件: shell sudo nano /…...
【Spring】Spring 下载及其 jar 包
根据 【动力节点】最新Spring框架教程,全网首套Spring6教程,跟老杜从零学spring入门到高级 以及老杜的原版笔记 https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》 进行整理, 文档密码:mg9b…...
蓝桥杯2023年第十四届省赛-飞机降落
题目描述 N 架飞机准备降落到某个只有一条跑道的机场。其中第 i 架飞机在 Ti 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 Di 个单位时间,即它最早 可以于 Ti 时刻开始降落,最晚可以于 Ti Di 时刻开始降落。降落过程需要 Li个单位时…...
STM32 串口实验(学习一)
本章将实现如下功能:STM32通过串口和上位机对话,STM32在收到上位机发过来的字符串后,原原本本返回给上位机。 STM32 串口简介 串口作为MCU的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。现在基本…...
多臂治疗规则的 Qini 曲线(Stefan Wager)
英文题目: Qini Curves for Multi-Armed Treatment Rules 中文题目:多臂治疗规则的 Qini 曲线 单位:Stefan Wager 论文链接: 代码:GitHub - grf-labs/maq: Treatment rule evaluation via the multi-armed Qini …...
NOSQL之Redis配置及优化
目录 一、关系型数据库 二、非关系型数据库 三、关系型数据库和非关系型数据库区别 1、数据存储方式不同 2、扩展方式不同 3、对事务性的支持不同 四、Redis简介 五、Redis优点 (1)具有极高的数据读写速度 (2)支持丰富的…...
植物一区HR | 植物生理组+转录组:揭示豆科植物响应干旱胁迫机制
PlantArray 植物高通量生理学表型监测系统 是一套以植物生理学为基础的高精度,高通量,自动化表型监测系统,集合实验设置、数据分析、决策工具于一身,能够高通量实时动态监测并进行全天候生理及环境参数采集,是进行植物…...
TCP粘包问题
TCP粘包问题 TCP粘包问题造成TCP粘包的原因发送方原因接收方原因 如何处理TCP粘包发送方接收方应用层 为什么UDP没有粘包问题 TCP粘包问题 TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
React核心概念:State是什么?如何用useState管理组件自己的数据?
系列回顾: 在上一篇《React入门第一步》中,我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目,并修改了App.jsx组件,让页面显示出我们想要的文字。但是,那个页面是“死”的,它只是静态…...
Java设计模式:责任链模式
一、什么是责任链模式? 责任链模式(Chain of Responsibility Pattern) 是一种 行为型设计模式,它通过将请求沿着一条处理链传递,直到某个对象处理它为止。这种模式的核心思想是 解耦请求的发送者和接收者,…...
数据挖掘是什么?数据挖掘技术有哪些?
目录 一、数据挖掘是什么 二、常见的数据挖掘技术 1. 关联规则挖掘 2. 分类算法 3. 聚类分析 4. 回归分析 三、数据挖掘的应用领域 1. 商业领域 2. 医疗领域 3. 金融领域 4. 其他领域 四、数据挖掘面临的挑战和未来趋势 1. 面临的挑战 2. 未来趋势 五、总结 数据…...
