【Android App】实战项目之仿微信的视频通话(附源码和演示 超详细必看)

news2024/9/22 5:14:13

需要源码请点赞关注收藏后评论区留言私信~~~

虽然手机出现许多年了,它具备的功能也越来越丰富,但是最基本的通话功能几乎没有变化。从前使用固定电话的时候,通话就是听声音;如今使用最新的智能手机,通话仍旧是听声音。 只闻其声不见其人的状况持续了好多年,既然手机自带的通话功能不支持视频画面,只好通过App自身实现了,比如微信就支持视频通话功能。通话双方一边对话,一边在手机屏幕上看着对方,感觉就像面对面交谈那般亲切。

一、需求描述

视频通话的请求方点击视频通话菜单项,接收方会自动打开等待通话界面。

接收方点击接听按钮,表示同意视频通话,之后双方的微信都切到接通了的视频通话界面。

任何一方点击挂断按钮,都将结束视频通话过程。

二、功能分析

视频童话不但要实时传输语音,还要实时传输画面,这对即时性要求很高,从用户界面到后台服务,视频通话主要集成了以下技术

(1)模糊位图:等待接听界面的背景可使用对方的模糊头像。

(2)音频管理器:按下音量加减键可以调节通话音量。

(3)Socket通信:与拨号事件有关的信令管理,需要采取Socket通信与后端服务器交互。

(4)移动数据格式JSON:客户端与服务器之间传输信令,需要把信令内容封装为JSON格式。 (5)实时音视频:开源库WebRTC适用于一对一的视频传输。

下面介绍代码模块之间的关系

(1)ContactListActivity.java:这是联系人的列表界面。

可以分解为下列三类操作

1:分别侦听好友上线和好友下线时间,在好友上线时将他加入联系人列表,在好友下线时将他从联系人列表移除
2:点击某位好友的头像,确认将要与其视频通话后打开视频通话等待界面

3:未在视频通话时需要侦听好友通话事件  一旦收到某位好友的通话请求就立即跳到等待接听界面 

(2)ContactVideoActivity.java:这是视频通话的预览界面,发起方与接收方通用。 

(3)服务端HttpServer模块中的VideoChatServer.java:处理Socket通信后端的信令消息传输。

视频通话的发起方与接收方的通话处理有所不同 主要区别如下

1:发起方发起通话请求之后需要侦听对方的接听事件,只有对方接受请求同意接听才能调用createOffer方法为其创建音视频供应

2:接收方只要按下接听按钮就表示同意通话请求,那么在收到对方的媒体能力时就应该调用createAnswer方法为其创建音视频答复 

三、效果分析 

联系人列表如下

 

 

四、代码 

部分代码如下 全部代码请点赞关注收藏后评论区留言私信~~~

package com.example.live;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.example.live.adapter.EntityListAdapter;
import com.example.live.bean.EntityInfo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.socket.client.Socket;

public class ContactListActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
    private final static String TAG = "ContactListActivity";
    private EntityListAdapter mAdapter; // 联系人的列表适配器
    private Map<String, EntityInfo> mContactMap = new HashMap<>(); // 联系人的名称映射
    private List<EntityInfo> mContactList = new ArrayList<>(); // 联系人列表
    private Socket mSocket; // 声明一个套接字对象
    private String mSelfName; // 我的昵称

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact_list);
        initView(); // 初始化视图
        initSocket(); // 初始化套接字
    }

    // 初始化视图
    private void initView() {
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("联系人列表");
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        ListView lv_contact = findViewById(R.id.lv_contact);
        mAdapter = new EntityListAdapter(this, mContactList);
        lv_contact.setAdapter(mAdapter);
        lv_contact.setOnItemClickListener(this);
    }

    // 初始化套接字
    private void initSocket() {
        mSelfName = getIntent().getStringExtra("self_name");
        Log.d(TAG , "initSocket "+mSelfName);
        mSocket = MainApplication.getInstance().getSocket();
        mSocket.connect(); // 建立Socket连接
        // 开始监听好友上线事件
        mSocket.on("friend_online", (args) -> {
            String friend_name = (String) args[0];
            if (friend_name != null) {
                // 把刚上线的好友加入联系人列表
                mContactMap.put(friend_name, new EntityInfo(friend_name, "好友"));
                mContactList.clear();
                mContactList.addAll(mContactMap.values());
                runOnUiThread(() -> mAdapter.notifyDataSetChanged());
            }
        });
        // 开始监听好友下线事件
        mSocket.on("friend_offline", (args) -> {
            String friend_name = (String) args[0];
            if (friend_name != null) {
                mContactMap.remove(friend_name); // 从联系人列表移除已下线的好友
                mContactList.clear();
                mContactList.addAll(mContactMap.values());
                runOnUiThread(() -> mAdapter.notifyDataSetChanged());
            }
        });
        // 开始监听好友通话事件
        mSocket.on("friend_converse", (args) -> {
            String friend_name = (String) args[0];
            // 接收到好友的通话请求,于是跳到视频通话页面
            Intent intent = new Intent(this, ContactVideoActivity.class);
            intent.putExtra("self_name", mSelfName); // 我的昵称
            intent.putExtra("friend_name", friend_name); // 好友昵称
            intent.putExtra("is_offer", false); // 是否为发起方
            startActivity(intent);
        });
        mSocket.emit("self_online", mSelfName); // 通知服务器“我已上线”
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mSocket.connected()) { // 已经连上Socket服务器
            mSocket.emit("self_offline", mSelfName); // 通知服务器“我已下线”
            mSocket.off("friend_online"); // 取消监听好友上线事件
            mSocket.off("friend_offline"); // 取消监听好友下线事件
            mSocket.off("friend_converse"); // 取消监听好友通话事件
            mSocket.disconnect(); // 断开Socket连接
        }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        EntityInfo friend = mContactList.get(position);
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(String.format("你是否要跟%s视频通话?", friend.name));
        builder.setPositiveButton("是", (dialog, which) -> {
            // 想跟好友通话,就打开视频通话页面
            Intent intent = new Intent(this, ContactVideoActivity.class);
            intent.putExtra("self_name", mSelfName); // 我的昵称
            intent.putExtra("friend_name", friend.name); // 好友昵称
            intent.putExtra("is_offer", true); // 是否为发起方
            startActivity(intent);
        });
        builder.setNegativeButton("否", null);
        builder.create().show();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Toast.makeText(this, "视频通话已结束", Toast.LENGTH_SHORT).show();
    }
}

创作不易 觉得有帮助请点赞关注收藏~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/57836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无刷三相直流电机电动工具驱动方案设计

电动工具是一种工具&#xff0c;其致动通过附加的动力源和机构比仅其他手工劳动与使用手工工具&#xff0c;电动工具用于工业、建筑、花园、做饭、清洁等家务劳动&#xff0c;以及在房子周围用于驱动&#xff08;紧固件&#xff09;、钻孔、切割、成型、打磨、研磨、布线、抛光…

【微服务】Java agent 使用详解

一、前言 于一个即将上线的应用来说&#xff0c;系统监控是必不可少的&#xff0c;为什么需要监控呢&#xff1f;应用是跑在服务器上的&#xff0c;应用在运行过程中会发生各自意想不到的问题&#xff0c;像大家熟知的OOM&#xff0c;mysql故障&#xff0c;服务器宕机&#xff…

cubeIDE开发,基于已有的STM32CubeMX (.ioc)创建工程文件

一、STM32Cube 生态系统 可以在其官网查看&#xff0c;支持中文。 STM32Cube - Discover the STM32Cube Ecosystem - STMicroelectronics ​ 截取官网的STM32Cube家族的软件工具描述&#xff1a; 【1】STM32CubeMX, 面向任意STM32设备的配置工具。这款简单易用的图形用户界面为…

ubuntu根目录清理

0.防范于未然&#xff08;就像给window电脑清理垃圾&#xff09; 清理ubuntu用不上的东西的常用命令 # 系统自带清理命令 sudo apt-get autoclean sudo apt-get clean sudo apt-get autoremove# 查看目录占用空间 sudo du -cks * | sort -rn | head -10 sudo du --max-depth1…

[附源码]计算机毕业设计JAVA血库管理系统

[附源码]计算机毕业设计JAVA血库管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Ma…

Effective C++条款24:若所有参数皆需类型转换,请为此采用non-member函数

Effective C条款24&#xff1a;若所有参数皆需类型转换&#xff0c;请为此采用non-member函数&#xff08;Declare non-member functions when type conversions should apply to all parameters&#xff09;条款24&#xff1a;若所有参数皆需类型转换&#xff0c;请为此采用no…

Swift基础语法 - 枚举

枚举的基本用法 定义&#xff1a;枚举简单的说也是一种数据类型&#xff0c;只不过是这种数据类型只包含自定义的特定数据&#xff0c;它是一组有共同特性的数据的集合。 enum Direction {case northcase southcase eastcase west }enum Direction {case north,south,east,we…

数理统计笔记10:回归分析

引言 数理统计笔记的第10篇介绍了回归分析&#xff0c;从相关关系开始介绍&#xff0c;然后介绍回归分析&#xff0c;主要介绍了一元回归模型和多元回归模型&#xff0c;并对其中的原理和检验进行了叙述&#xff0c;最后简单介绍了一下可以化为线性回归模型的非线性回归模型。 …

【Gradle-5】Gradle常用命令与参数

1、前言 Gradle的命令有很多&#xff0c;熟悉常用命令之后&#xff0c;在日常开发中&#xff0c;不仅可以提升效率&#xff0c;也可以辅助我们快速定位并解决编译问题&#xff1b;而且某些情况下命令行(CLI)与按钮执行的编译结果是不一样的&#xff0c;比如构建时要传参(-P)&a…

大学生环保主题网页制作 环境网页设计模板 学生静态网页作业成品 dreamweaver保护地球环境HTML网站制作

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

【电商项目实战】上传头像(详细篇)

&#x1f341;博客主页&#xff1a;&#x1f449;不会压弯的小飞侠 ✨欢迎关注&#xff1a;&#x1f449;点赞&#x1f44d;收藏⭐留言✒ ✨系列专栏&#xff1a;&#x1f449;SpringBoot电商项目实战 ✨学习社区&#xff1a; &#x1f449;不会压弯的小飞侠 ✨知足上进&#x…

【Spring源码系列】Bean生命周期-实例化前

这里写目录标题前言一、实例化前 - InstantiationAwareBeanPostProcessor介绍InstantiationAwareBeanPostProcessor实例化前作用InstantiationAwareBeanPostProcessor实例化前代码案例二、实例化前 - 源码分析声明关键点源代码解读前言 在Bean的生命周期中&#xff0c;‘实例化…

Python还是很迷茫的小伙伴进来,教你用图秒懂Python

哈喽&#xff0c;大家好呀&#xff01;今天为大家带来12张图解python&#xff0c;让你们轻松学会了解python。 1.Python 解释器&#xff1a; Python数据结构&#xff1a;变量与运算符&#xff1a;Python 流程控制&#xff1a;Python 文件处理&#xff1a;python 输入输出&…

(三)Vue之模板语法

文章目录插值语法指令语法Vue学习目录上一篇&#xff1a;&#xff08;二&#xff09;初识Vue 下一篇&#xff1a;&#xff08;四&#xff09;Vue之数据绑定 Vue模板语法有2大类&#xff1a; 1.插值语法2.指令语法 插值语法 功能&#xff1a;用于解析标签体内容。 写法&…

lombok入门

目录 lombok概述 lombok安装 Getter、Setter ToString EqualsAndHashCode NotNull 生成构造方法相关注解 Data、Builder Log Cleanup、SneakyThrows lombok概述 以前的Java项目中&#xff0c;充斥着太多不友好的代码&#xff1a;POJO的getter/setter/toString/构造方…

Python迭代器和生成器

在Python中&#xff0c;很多对象都是可以通过for语句来直接遍历的&#xff0c;例如list、string、dict等等&#xff0c;这些对象都可以被称为可迭代对象。至于说哪些对象是可以被迭代访问的&#xff0c;就要了解一下迭代器相关的知识了。 迭代器 迭代器对象要求支持迭代器协议…

cpu设计和实现(总结篇)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 学习cpu&#xff0c;主要还是因为自己对它的原理和实现还有很多不明白、不清楚的地方&#xff0c;本着追根溯源的精神&#xff0c;正好借助于veril…

项目接入腾讯云短信服务SMS实现向用户发送手机验证码

1、自述 早在18年的时候&#xff0c;我就在项目中使用过阿里云的短信服务&#xff0c;现在我上阿里云短信控制台看&#xff0c;还能看到当时创建的短信签名&#xff0c;如下图所示。 出于某种原因&#xff0c;我现在想重新申请一个新的签名&#xff0c;却审批失败了&#xf…

HashMap和Hashtable的详细区别

HashMap和Hashtable的详细区别 一、简述&#xff1a; 1.安全性 Hashtable是线程安全&#xff0c;HashMap是非线程安全。HashMap的性能会高于Hashtable&#xff0c;我们平时使用时若无特殊需求建议使用HashMap&#xff0c;在多线程环境下若使用HashMap需要使用Collections.sy…

MyBatisPlus的使用入门

一、简介 官网&#xff1a;http://mp.baomidou.com/ 参考教程&#xff1a;http://mp.baomidou.com/guide/ MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 二、…