当前位置: 首页 > news >正文

手机视频聊天分享

在人际互动的手机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中&#xff0c;增加语音视频聊天功能是一个常见的需求。而现在&#xff0c;更进一步&#xff0c;在某些场景下&#xff0c;我们需要能将自己的手机屏幕分享给他人&#xff0c;或者是观看他人的手机屏幕。那么&#xff0c;这些常见的功能是如何实现的了&…...

神经网络小记-优化器

优化器是深度学习中用于优化神经网络模型的一类算法&#xff0c;其主要作用是根据模型的损失函数来调整模型的参数&#xff0c;使得模型能够更好地拟合训练数据&#xff0c;提高模型的性能和泛化能力。优化器在训练过程中通过不断更新模型的参数&#xff0c;使模型逐步接近最优…...

200+行代码写一个简易的Qt界面贪吃蛇

照例先演示一下&#xff1a; 一个简单的Qt贪吃蛇&#xff0c;所有的图片都是我自己画的&#xff08;得意&#xff09;。 大致的运行逻辑和之前那个200行写一个C小黑窗贪吃蛇差不多&#xff0c;因此在写这个项目的时候&#xff0c;大多情况是在想怎么通过Qt给展现出来。 背景图…...

redis中使用bloomfilter的白名单功能解决缓存穿透问题

一 缓存预热 1.1 缓存预热 将需要的数据提前缓存到缓存redis中&#xff0c;可以在服务启动时候&#xff0c;或者在使用前一天完成数据的同步等操作。保证后续能够正常使用。 1.2 缓存穿透 在redis中&#xff0c;查询redis缓存数据没有内容&#xff0c;接着查询mysql数据库&…...

Spring Boot 2.7.8以后mysql-connector-java与mysql-connector-j

错误信息 如果升级到Spring Boot 2.7.8&#xff0c;可以看到因为找不到mysql-connector-java依赖而出现错误。 配置&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>&l…...

03|「如何写好一个 Prompt」

前言 Prompt 文章目录 前言一、通用模板和范式1. 组成2. 要求1&#xff09;文字描述2&#xff09;注意标点符号 一、通用模板和范式 1. 组成 指令&#xff08;角色&#xff09; 生成主体 额外要求 指令&#xff1a;模型具体完成的任务描述。例如&#xff0c;翻译一段文字&…...

关于提示词 Prompt

Prompt原则 原则1 提供清晰明确的指示 注意在提示词中添加正确的分割符号 prompt """ 请给出下面文本的摘要&#xff1a; <你的文本> """可以指定输出格式&#xff0c;如&#xff1a;Json、HTML提示词中可以提供少量实例&#xff0c;…...

【Linux多线程】线程的互斥与同步(附抢票案例代码+讲解)

线程的互斥与同步 &#x1f4ab; 概念引入⭐️临界资源&#xff08;Critical Resource&#xff09;&#xff1a;&#x1f31f;临界区&#xff08;Critical Section&#xff09;&#xff1a;✨互斥&#xff08;Mutex&#xff09;&#xff1a; ⚡️结合代码看互斥☄️ 代码逻辑&a…...

ajax概述

目录 1.什么是ajax 2.ja原生ajax 3.jQuery框架的ajax 4.综合案例 1.什么是ajax Ajax 即"Asynchronous Javascript And XML"&#xff08;异步 JavaScript 和 XML&#xff09;&#xff0c;是指一种创建交互式网页应用的网页开发技术。Ajax 异步 JavaScript 和 XML&…...

小白带你学习linux的mysql服务(主从mysql服务和读写分离三十一)

目录 二、MySQL Replication优点&#xff1a; 三、MySQL复制类型 1、异步复制&#xff08;Asynchronous repication&#xff09; 2、全同步复制&#xff08;Fully synchronous replication&#xff09; 3、半同步复制&#xff08;Semisynchronous replication&#xff09;…...

【低代码专题方案】iPaaS运维方案,助力企业集成平台智能化高效运维

01 场景背景 随着IT行业的发展和各家企业IT建设的需要&#xff0c;信息系统移动化、社交化、大数据、系统互联、数据打通等需求不断增多&#xff0c;企业集成平台占据各个企业领域&#xff0c;成为各业务系统数据传输的中枢。 集成平台承接的业务系统越多&#xff0c;集成平台…...

Android SDK 上手指南||第一章 环境需求||第二章 IDE:Eclipse速览

第一章 环境需求 这是我们系列教程的第一篇&#xff0c;让我们来安装Android的开发环境并且把Android SDK运行起来&#xff01; 介绍 欢迎来到Android SDK入门指南系列文章&#xff0c;如果你想开始开发Android App&#xff0c;这个系列将从头开始教你所须的技能。我们假定你…...

Amazon Linux上使用ec2-user来设置开机自启动的shell脚本

要在Amazon Linux上使用ec2-user来设置开机自启动的shell脚本&#xff0c;可以按照以下步骤操作&#xff1a; 1. 确保您拥有要设置自启动的shell脚本。假设脚本的路径是/home/ec2-user/myscript.sh。 2. 使用以下命令打开/etc/rc.d/rc.local文件&#xff1a; shell sudo nano /…...

【Spring】Spring 下载及其 jar 包

根据 【动力节点】最新Spring框架教程&#xff0c;全网首套Spring6教程&#xff0c;跟老杜从零学spring入门到高级 以及老杜的原版笔记 https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》 进行整理&#xff0c; 文档密码&#xff1a;mg9b…...

蓝桥杯2023年第十四届省赛-飞机降落

题目描述 N 架飞机准备降落到某个只有一条跑道的机场。其中第 i 架飞机在 Ti 时刻到达机场上空&#xff0c;到达时它的剩余油料还可以继续盘旋 Di 个单位时间&#xff0c;即它最早 可以于 Ti 时刻开始降落&#xff0c;最晚可以于 Ti Di 时刻开始降落。降落过程需要 Li个单位时…...

STM32 串口实验(学习一)

本章将实现如下功能&#xff1a;STM32通过串口和上位机对话&#xff0c;STM32在收到上位机发过来的字符串后&#xff0c;原原本本返回给上位机。 STM32 串口简介 串口作为MCU的重要外部接口&#xff0c;同时也是软件开发重要的调试手段&#xff0c;其重要性不言而喻。现在基本…...

多臂治疗规则的 Qini 曲线(Stefan Wager)

英文题目&#xff1a; Qini Curves for Multi-Armed Treatment Rules 中文题目&#xff1a;多臂治疗规则的 Qini 曲线 单位&#xff1a;Stefan Wager 论文链接&#xff1a; 代码&#xff1a;GitHub - grf-labs/maq: Treatment rule evaluation via the multi-armed Qini …...

NOSQL之Redis配置及优化

目录 一、关系型数据库 二、非关系型数据库 三、关系型数据库和非关系型数据库区别 1、数据存储方式不同 2、扩展方式不同 3、对事务性的支持不同 四、Redis简介 五、Redis优点 &#xff08;1&#xff09;具有极高的数据读写速度 &#xff08;2&#xff09;支持丰富的…...

植物一区HR | 植物生理组+转录组:揭示豆科植物响应干旱胁迫机制

PlantArray 植物高通量生理学表型监测系统 是一套以植物生理学为基础的高精度&#xff0c;高通量&#xff0c;自动化表型监测系统&#xff0c;集合实验设置、数据分析、决策工具于一身&#xff0c;能够高通量实时动态监测并进行全天候生理及环境参数采集&#xff0c;是进行植物…...

TCP粘包问题

TCP粘包问题 TCP粘包问题造成TCP粘包的原因发送方原因接收方原因 如何处理TCP粘包发送方接收方应用层 为什么UDP没有粘包问题 TCP粘包问题 TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包&#xff0c;从接收缓冲区来看&#xff0c;后一包数据的头紧接着前一包数据…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...