草庐IT

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

showswoller 2023-04-05 原文

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

虽然手机出现许多年了,它具备的功能也越来越丰富,但是最基本的通话功能几乎没有变化。从前使用固定电话的时候,通话就是听声音;如今使用最新的智能手机,通话仍旧是听声音。 只闻其声不见其人的状况持续了好多年,既然手机自带的通话功能不支持视频画面,只好通过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();
    }
}

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

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

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  3. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  4. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  5. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  6. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  9. ruby - 如何在 Ruby 字符串中插入项目符号字符? - 2

    我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195

  10. ruby - 在 Rails 项目中测试本地版本的 gem - 2

    我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行​​bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正

随机推荐