注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

Demo体验

Demo体验

场景Demo,开箱即用
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

开源项目|使用go语言搭建高效的环信 IM Rest接口(附源码)

项目背景环信 Server SDK 是对环信 IM REST API 的封装, 可以节省服务器端开发者对接环信 API 的时间,只需要配置自己的 App Key 相关信息即可使用。环信目前提供java和PHP版本的Server SDK,此项目使用go语言对环信...
继续阅读 »

项目背景

环信 Server SDK 是对环信 IM REST API 的封装, 可以节省服务器端开发者对接环信 API 的时间,只需要配置自己的 App Key 相关信息即可使用。

环信目前提供java和PHP版本的Server SDK,此项目使用go语言对环信 IM REST API 进行封装,对官方版本进行了补充,有需要的开发者可以直接通过以下地址获取源码。

项目地址

前提条件

  • go语言环境

  • 有效的环信即时通讯 IM 开发者账号和 AppKey、ClientID、ClientSecret、DomainURL
    登录 环信管理后台 到“应用列表” → 点击“查看”即可获取到 App Key、Client ID、ClientSecret,到"即时通讯" → 点击"服务概览"获取到 “Rest api” 的服务器域名。

实现方法

  • go.mod 文件引入: github.com/xiaofengin/easemob-go

AppKey、ClientID、ClientSecret在下图中获取


DomainURL在下图中获取

初始化IM SDK

package main

import (
"context"
"fmt"
IMSDK "github.com/xiaofengin/easemob-go"
)

func main() {
client, err := IMSDK.New("appkey",
"clientId",
"clientSecret",
"domainURL")
if err != nil {
return
}
}

批量注册两个用户

package main

import (
"context"
"fmt"
IMSDK "github.com/xiaofengin/easemob-go"
)

func main() {
client, err := IMSDK.New("appkey",
"clientId",
"clientSecret",
"domainURL")
if err != nil {
return
}
user1 := UserRegisterParam{
Username: "userID_1",
Password: "1",
}
user2 := UserRegisterParam{
Username: "userID_2",
Password: "1",
}
users := []UserRegisterParam{user1, user2}
ret, err := client.UserRegister(context.Background(), &users)
if err != nil {
return
}
fmt.Printf("数据的值:%v\n", ret.Entities)
}

发送一个单聊消息

  • tos 放接收方环信ID(多个)m := CreateTextMsg("hello word", tos) 创建一个消息体
  • 默认发送方ID 是 admin,如果要修改的话 m.From = "指定ID"
  • 也可以给消息添加扩展字段 m.Ext = map[string]interface{}{"key1": "value1", "key2": "value2"}
package main

import (
"context"
"fmt"
IMSDK "github.com/xiaofengin/easemob-go"
)

func main() {
client, err := IMSDK.New("appkey",
"clientId",
"clientSecret",
"domainURL")
if err != nil {
return
}
var tos []string
tos = append(tos, "环信用户ID")
m := CreateTextMsg("hello word", tos)
//m.From = "指定ID"
//m.Ext = map[string]interface{}{"key1": "value1", "key2": "value2"}
ret, err := client.SendChatMessage(context.Background(), m)
if err != nil {
return
}
fmt.Printf("数据的值:%v\n", ret.Data)
}

获取用户token

  • 通过用户ID和密码获取用户token,也可以通过用户ID获取用户token
package main

import (
"context"
"fmt"
IMSDK "github.com/xiaofengin/easemob-go"
)

func main() {
client, err := IMSDK.New("appkey",
"clientId",
"clientSecret",
"domainURL")
if err != nil {
return
}

//通过用户 ID 和密码获取用户 token
//data := TokenParam{
// GrantType: "password",
// Username: "userID",
// Password: "1",
// Ttl: "1024000",
//}

//通过用户 ID 获取用户 token
data := TokenParam{
GrantType: "inherit",
Username: "userID",
AutoCreateUser: true,
Ttl: "1024000",
}
ret, err := client.GetUserToken(context.Background(), &data)
if err != nil {
return
}
fmt.Printf("数据的值:%v\n", ret.AccessToken)
}

SDK功能清单

注意

测试代码中 appkey clientId clientSecret 这三个参数我是写到环境变量里面,
如果 你没有把参数写到环境变量里面,可以直接写死该参数


参考文档:

IMGeek社区支持:https://www.imgeek.net

收起阅读 »

【附源码】推荐8个「IM+AI」场景的开源项目,建议收藏

1、社交泛娱乐——【找搭子】饭搭子、旅游搭子、遛狗搭子…这种新型的“搭子社交”在年轻群体中逐渐流行起来。区别于传统社交,搭子关系,陌生以上,友人未满,这种轻松的浅社交既能获得志趣相投的陪伴,又不用苦心经营彼此的关系。实在是好搭子不问出处,只要有能结伴做的事,就...
继续阅读 »

1、社交泛娱乐——【找搭子】

饭搭子、旅游搭子、遛狗搭子…这种新型的“搭子社交”在年轻群体中逐渐流行起来。区别于传统社交,搭子关系,陌生以上,友人未满,这种轻松的浅社交既能获得志趣相投的陪伴,又不用苦心经营彼此的关系。实在是

好搭子不问出处,只要有能结伴做的事,就可以有那么一“搭”。那么问题来了,如何精准地匹配到自己喜爱的活动以及志趣相投的“搭子”,便成了求搭者们的刚需。

项目介绍

“找搭子”——基于AI Agent解决找搭子最后一公里。它借助环信IM能力,使用对话Agent 充分挖掘用户需求,整合用户信息形成实时活动表单,精准推荐,打破固化标签,增加可信度。为活动发起者、参与者提供了一个双向智能、高效沟通、精准匹配的平台。


(找搭子产品架构)

(找搭子Demo演示)

项目点评:

“找搭子”以其独特的创新视角,展现了信息分发和成员组织的全新可能性。通过利用大模型进行精准匹配,该项目能够根据用户的兴趣爱好,智能地形成特定的用户群体。这种创新的方式不仅极大地提高了用户的参与度和满意度,同时也预示着巨大的商业潜力。在营销、社交和社区建设等领域,其影响力不可忽视。"找搭子"的实现,无疑是对传统模式的一次颠覆性的创新,它为我们打开了一个全新的视角,重新诠释了我们对于用户互动和社区建设的理解。

模型:环信IM+文心一言

项目源码:(Flutter)

2、教育培训——KidChat

项目介绍

「KidChat」是一个儿童故事创作分享社区。通过语音输入故事梗概,由 LLM 生成儿童故事,结合环信IM能力,自动将绘本故事分享到故事广场,让小朋友们一起欣赏。通过 Reaction 互动,并自动为每一个故事生成子区,可在子区内对具体故事进一步讨论,进而形成一个有趣而充满创造力的故事分享社区。

kidchat Demo演示

项目点评

「KidChat」以其卓越的内容生成和讲述能力,成功地满足了面向低龄孩子的“讲故事”场景的需求。该项目利用大模型的强大功能,能够快速生成丰富多样、充满无限可能的故事,并通过语音进行生动有趣的讲解。这无疑满足了低龄儿童家长的刚需,为他们提供了一种新的、高效的儿童教育方式。在实现过程中,KidChat非常富有创意地将环信的Thread和Reaction功能应用到了自己的业务功能实现中,提供了非常好的使用体验。更值得一提的是,该项目的完成度高,用户体验佳,无论是在故事内容的创新性,还是在语音讲解的生动性上,都表现出了极高的专业水准和创新思维。

模型:环信IM+讯飞星火

项目源码:(Flutter)

3、聊天机器人——ChattyAI

项目介绍

「ChattyAI」是一款基于人工智能技术的智能陪聊机器人,旨在提供用户个性化、有趣、愉快的对话体验。通过先进的自然语言处理和机器学习算法,ChattyAI能够理解用户的意图和情感,并以富有情感的方式回应,为用户提供真实感和沟通舒适度。

为增加智能体与用户的互动体验与亲密感,借助环信IM通讯能力,通过智能体向用户定时发送早安、午安、晚安。除此外还可以扩展更丰富多样的主动唤起话题的任务。



项目点评

「ChattyAI」在社交泛娱乐领域实现了人与智能体聊天的全新场景。该项目充分利用大模型在闲聊领域的优势,为用户提供了一种全新的、高质量的交流体验。"ChattyAI"不仅提供了丰富人设的虚拟角色,更在UI设计和交互体验上展现出了卓越的水准。其UI设计优雅且直观,交互体验流畅且自然,使得用户可以轻松地与虚拟角色进行交流。项目的完成度非常高,无论是在技术实现,还是在用户体验上,都展现出了项目团队的专业能力和创新思维。"ChattyAI"的成功实现,为我们在社交泛娱乐领域的探索提供了有力的启示。

模型:环信IM+MINIMAX

项目源码:(Android)

4、AI专家助手

现存市面上大部分的AI主要是面向个人的,而当前的AI助手并不能准确无误的解决大部分人的问题,尤其在一些特定的领域,比如编程,医疗等专业技能要求较高的方向,虽然AI能够给出相应的回复,但是这些回复对于普通人来说,甄别其中的准确性依然存在一定问题。该项目通过一般咨询者的信息,收集不同AI厂商的建议或者帮助信息,能够大大提升相应的工作效率。进而实现专家的效率,而当前社会专家的稀缺才是更大的瓶颈。

模型:环信IM+百川智能+MINIMAX+文心一言

项目源码:

5、ai群管家

此项目提供了群聊、单聊机器人对话服务。通过命令方式激活机器人,单聊bot可实现与多个角色如AI医生、知乎文案生成、AI家教、AI律师、地方美食推荐(自由切换)生成对话,群聊借助AI能力提供了群历史消息总结功能。是超级社群中典型的应用场景。

模型:环信IM+智谱AI

项目源码:

6、PictoChat (AI智绘)

AI智绘是一款创新的移动应用程序(iOS),整合了环信IM(即时通讯)平台和OpenAI的GPT-4.0绘图能力。这款应用专为提升在线交流体验而设计,能够在实时对话中生成和发送图片,极大地增强了交流的趣味性和互动性。

模型:环信IM+ChatGPT

项目源码:

7、百答

百答,一个All in One全能助手,你的AI智囊团。基于环信IM即时通讯解决方案,结合各家大模型能力开发的全能AI助手。互联网+环信IM+大模型,强强联合,多重BUFF叠加,让普通人也拥有撬动地球的力量!恋爱大师,编程助手,周报秘书,抬杠大师,彩虹屁专家,数字女友,礼物攻略等等应有尽有,全能智能 有问必答,堪称bot细分领域第一代卷王。

模型:环信IM+通义千问

项目源码:

8、智慧医疗

此项目是一款智慧医疗应用,患者与AI医生实时互动获得实时的医疗咨询,会话结束时为患者生成咨询档案,同时智能推荐相应的科室。当患者问诊相应科室医生时,医生可调取患者基本信息和与AI医生的历史咨询档案。

智慧医疗的蓬勃发展有望提升整个医疗体系的质量和效率,改善医疗体验,同时为患者提供更为个性化和便捷的医疗服务。我们期待在医疗领域,IM与AI的紧密融合发挥巨大的作用。

模型:环信IM+通义千问

项目源码:

参考资料:

环信集成相关

收起阅读 »

程序员创业:从技术到商业的转变

作为一名程序员,我们通常会聚焦于编程技能和技术能力的提升,这也是我们日常工作的主要职责。但是,随着技术的不断发展和市场的变化,仅仅依靠技术能力已经不足以支撑我们在职场上的发展和求职竞争力了。所以,作为一名有远大理想的程序员,我们应该考虑创业的可能性。 为什么程...
继续阅读 »

作为一名程序员,我们通常会聚焦于编程技能和技术能力的提升,这也是我们日常工作的主要职责。但是,随着技术的不断发展和市场的变化,仅仅依靠技术能力已经不足以支撑我们在职场上的发展和求职竞争力了。所以,作为一名有远大理想的程序员,我们应该考虑创业的可能性。


为什么程序员要创业?


创业其实并非只适用于商学院的毕业生或者有创新理念的企业家。程序员在业内有着相当高的技术储备和市场先知,因此更容易从技术角度前瞻和切入新兴市场,更好地利用技术储备来实现创业梦想。


此外,创业可以释放我们的潜力,同时也可以让我们找到自己的定位和方向。在创业的过程中,我们可能会遇到各种挑战和困难,但这些挑战也将锻炼我们的意志力和决策能力,让我们更好地发挥自己的潜力。


创业需要具备的技能


作为一名技术人员,创业需要具备更多的技能。首先是商业和运营的技能:包括市场分析、用户研究、产品策划、项目管理等。其次是团队管理和沟通能力,在创业的过程中,人才的招聘和管理是核心问题。


另外,还需要具备跨界合作的能力,通过开放性的合作与交流,借助不同团队的技术和资源,完成创业项目。所以我们应该将跨界合作看作是创业过程中的重要选择,选择和加强自己的跨界交流和合作能力,也能为我们的企业注入活力和创新精神。


如何创业?


从技术到商业的转变,从最初想法的诞生到成熟的企业的创立,都需要一个创业的路线图。以下是一些需要注意的事项:




  1. 研究市场:了解市场趋势,分析需求,制定产品策略。可以去参加行业论坛,争取到专业意见和帮助。




  2. 制定商业计划:包括产品方案、市场营销、项目管理、团队建设等。制定一个系统的商业计划是投资者和团队成员对创业企业的认可。




  3. 招募团队:由于我们一般不是经验丰富的企业家,团队的选择尤为重要。要找的不仅要是技能和经验匹配的团队,更要找能一起携手完成创业项目的合作者。




  4. 行动计划:从实现规划步入到实战行动是创业项目的关键。按部就班地完成阶段性任务,控制实施进度和途中变化,在完成一个阶段后可以重新评估计划。




  5. 完成任务并分析:最后,团队成员需要根据企业进展,完整阶段性的目标,做自己的工作。及时完成考核任务并一起分享数据分析、事件解决和项目总结等信息,为项目下一阶段做出准确预测。




结语


创业是一条充满挑战性和机遇的路线,也是在我们的技术和业务的进一步升级中一条非常良好的通道。越来越多的技术人员意识到了自己的潜力,开始考虑自己创业的可能性。只要学会逐步掌握创业所需的技能和知识,并制订出详细的创业路线图,大可放手去尝试,才能最终实现自己心中的创业梦想。


作者:郝学胜
链接:https://juejin.cn/post/7240465997002047547
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
收起阅读 »

实战:一天开发一款内置游戏直播的国产版Discord应用【附源码】(下)

上篇:https://www.imgeek.net/article/825362923声网RTC接入, 直播与语音实现接入在views/Channel/components文件夹下新增一个组件StreamHandler, 该组件为后续我们处理游戏房间的组件, ...
继续阅读 »

上篇:https://www.imgeek.net/article/825362923

声网RTC接入, 直播与语音实现

接入

views/Channel/components文件夹下新增一个组件StreamHandler, 该组件为后续我们处理游戏房间的组件, 先初步编写声网接入逻辑

// views/Channel/components/StreamHandler/index.js

const options = {
appId:
process.env.REACT_APP_AGORA_APPID || "default id",
channel: process.env.REACT_APP_AGORA_CHANNEL || "test",
token:
process.env.REACT_APP_AGORA_TOKEN ||
"default token",
uid: process.env.REACT_APP_AGORA_UID || "default uid",
};

const StreamHandler = (props) => {
// 组件参数: 用户信息, 当前频道所有消息, 当前频道id, 是否开启本地语音
const { userInfo, messageInfo, channelId, enableLocalVoice = false } = props;

const [rtcClient, setRtcClient] = useState(null);
// 声网client连接完成
const [connectStatus, setConnectStatus] = useState(false);

// RTC相关逻辑
useEffect(() => {
AgoraRTC.setLogLevel(3);
const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
// TODO: use right channel
client
.join(options.appId, options.channel, options.token, userInfo?.username)
.then(() => {
setConnectStatus(true);
console.log("[Stream] join channel success");
})
.catch((e) => {
console.log(e);
});

setRtcClient(client);
return () => {
// 销毁时, 自动退出RTC频道
client.leave();
setRtcClient(null);
};
}, []);

return (
<>
{!connectStatus && <Spin tip="Loading" size="large" />}
</>
);
}


// 我们需要全局状态中的userinfo, 映射一下到当前组件的props中
const mapStateToProps = ({ app }) => {
return {
userInfo: app.userInfo,
};
};
export default memo(connect(mapStateToProps)(StreamHandler));

然后回到Channel中, 在之前的renderStreamChannel函数中添加上StreamHandler组件

// view/Channel/index.js
const [enableVoice, setEnableVoice] = useState(false);
const toggleVoice = () => {
setEnableVoice((enable) => {
return !enable;
});
}

// 保留了输入窗口, 可以在它的菜单栏中添加游戏频道独有的一些逻辑,
// 这里我加入了开关本地语音的逻辑, 拓展Input的细节可以参照完整版代码
const renderStreamChannel = () => {
return (
<>
<div className={s.messageRowWrap}>
<StreamHandler messageInfo={messageInfo} channelId={channelId} enableLocalVoice={enableVoice} />
</div>
<div className={s.iptWrap}>
<Input chatType={CHAT_TYPE.groupChat} fromId={channelId} extraMenuItems={renderStreamMenu()} />
</div>
</>
);
}

const renderStreamMenu = () => {
return [
{
key: "voice",
label: (
<div
className="circleDropItem"
onClick={toggleVoice}
>
<Icon
name="person_wave_slash"
size="24px"
iconClass="circleDropMenuIcon"
/>
<span className="circleDropMenuOp">
{enableVoice ? "关闭语音" : "开启语音"}
</span>
</div>
),
}
];
}

此时我们创建一个video-开题的游戏频道, 应该可以看到命令行中输出了RTC连接成功信息. [Stream] join channel success

音视频推流

接下来我们继续做实质的RTC推流逻辑, 及用户上下播的入口. 但在那之前, 先简单过一下声网RTC中的一些概念.

参考以下步骤实现音视频通话的逻辑:

  1. 调用 createClient 方法创建 AgoraRTCClient 对象。
  2. 调用 join 方法加入一个 RTC 频道,你需要在该方法中传入 App ID 、用户 ID、Token、频道名称。
  3. 先调用 createMicrophoneAudioTrack 通过麦克风采集的音频创建本地音频轨道对象,调用 createCameraVideoTrack 通过摄像头采集的视频创建本地视频轨道对象;然后调用 publish 方法,将这些本地音视频轨道对象当作参数即可将音视频发布到频道中。
  4. 当一个远端用户加入频道并发布音视频轨道时:
  5. 监听 client.on(“user-published”) 事件。当 SDK 触发该事件时,在这个事件回调函数的参数中你可以获取远端用户 AgoraRTCRemoteUser 对象 。
  6. 调用 subscribe 方法订阅远端用户 AgoraRTCRemoteUser 对象,获取远端用户的远端音频轨道 RemoteAudioTrack 和远端视频轨道 RemoteVideoTrack 对象。

在这里插入图片描述

(以上内容来自声网官方文档)

在上面的接入中, 我们已经完成了创建对象并加入频道两步.
在RTC中, 可以传输音频和视频信号, 由于单个RTC客户端要传输不同种类的数据, 每个单独的音视频源被分成不同的track(由于它们都是实时不断产生的, 我们称作流), 随后通过publish方法, 将我们本地的信号源交付给RTC客户端传输.
随后通过user-published事件的回调来在其他用户发布信号源时进行处理, 首先需要subscribe该用户来获取后续数据, 随后根据不同类型的信号流做处理.
离开时需要关闭本地当前的信号源, 并退出RTC客户端.
最后通过user-unpublished事件监听其他用户退出, 移除它们对应的信号流.

逻辑理清楚后代码就很容易看懂了

// views/Channel/components/StreamHandler/index.js
const StreamHandler = (props) => {
...
// 本地视频元素
const localVideoEle = useRef(null);
// 远程视频元素
const canvasEle = useRef(null);
const [rtcClient, setRtcClient] = useState(null);
const [connectStatus, setConnectStatus] = useState(false);
// 当前直播的用户
const [remoteUser, setRemoteUser] = useState(null);
// 远程音视频track
const [remoteVoices, setRemoteVoices] = useState([]);
const [remoteVideo, setRemoteVideo] = useState(null);

// RTC相关逻辑
useEffect(() => {
...
// client.join 后

// 监听新用户加入
client.on("user-published", async (user, mediaType) => {
// auto subscribe when users coming
await client.subscribe(user, mediaType);
console.log("[Stream] subscribe success on user ", user);
if (mediaType === "video") {
// 获取直播流
if (remoteUser && remoteUser.uid !== user.uid) {
// 只能有一个用户推视频流
console.error(
"already in a call, can not subscribe another user ",
user
);
return;
}
// 播放并记录下视频流
const remoteVideoTrack = user.videoTrack;
remoteVideoTrack.play(localVideoEle.current);
setRemoteVideo(remoteVideoTrack);
// can only have one remote video user
setRemoteUser(user);
}
if (mediaType === "audio") {
// 获取音频流
const remoteAudioTrack = user.audioTrack;
// 去重
if (remoteVoices.findIndex((item) => item.uid === user.uid) == -1) {
remoteAudioTrack.play();
// 添加到数组中
setRemoteVoices([
...remoteVoices,
{ audio: remoteAudioTrack, uid: user.uid },
]);
}
}
});

client.on("user-unpublished", (user) => {
// 用户离开, 去除流信息
console.log("[Stream] user-unpublished", user);
removeUserStream(user);
});
setRtcClient(client);
return () => {
client.leave();
setRtcClient(null);
};
}, []);

const removeUserStream = (user) => {
if (remoteUser && remoteUser.uid === user.uid) {
setRemoteUser(null);
setRemoteVideo(null);
}
setRemoteVoices(remoteVoices.filter((voice) => voice.uid !== user.uid));
};
}

接着我们根据之前提到的自定义消息判断当前在播状态, 以最后一条自定义消息为准.

// views/Channel/components/StreamHandler/index.js
const StreamHandler = (props) => {
const { userInfo, messageInfo, channelId, enableLocalVoice = false } = props;

// 第一条 stream 消息, 用于判断直播状态
const firstStreamMessage = useMemo(() => {
return messageInfo?.list?.find(
(item) => item.type === "custom" && item?.ext?.type === "stream"
);
}, [messageInfo]);

// 是否有直播
const hasRemoteStream =
firstStreamMessage?.ext?.status === CMD_START_STREAM &&
firstStreamMessage?.ext?.user !== userInfo?.username;
// 本地直播状态
const [localStreaming, setLocalStreaming] = useState(
firstStreamMessage?.ext?.status === CMD_START_STREAM &&
firstStreamMessage?.ext?.user === userInfo?.username
);

// 本地直播流状态
const toggleLocalGameStream = () => {
if (hasRemoteStream) {
return;
}
setLocalStreaming(!localStreaming);
};
// 根据直播状态选择渲染
return (
<>
{!connectStatus && <Spin tip="Loading" size="large" />}
{hasRemoteStream ? (
<RemoteStreamHandler
remoteUser={firstStreamMessage?.ext?.user}
localVideoRef={localVideoEle}
channelId={channelId}
userInfo={userInfo}
rtcClient={rtcClient}
/>
) : (
<LocalStreamHandler
localStreaming={localStreaming}
canvasRef={canvasEle}
toggleLocalGameStream={toggleLocalGameStream}
rtcClient={rtcClient}
userInfo={userInfo}
channelId={channelId}
/>
)}
</>
);
}

我们根据hasRemoteStream分成两种逻辑RemoteStreamHandlerLocalStreamHandler(可以先用div+文字的空实现占位), 首先我们来看本地游戏的逻辑

// view/Channel/components/StreamHandler/local_stream.js
const LocalStreamHandler = (props) => {

const {
toggleLocalGameStream,
canvasRef,
localStreaming,
rtcClient,
userInfo,
channelId,
} = props;

const [localVideoStream, setLocalVideoStream] = useState(false);
const localPlayerContainerRef = useRef(null);

// 开启本地视频流
useEffect(() => {
if (!localPlayerContainerRef.current) return;
const f = async () => {
// 暂时使用视频代替游戏流
let lgs = await AgoraRTC.createCameraVideoTrack();
lgs.play(localPlayerContainerRef.current);
setLocalGameStream(lgs);
}
f();
}, [localPlayerContainerRef])

const renderLocalStream = () => {
return (
<div style={{ height: "100%" }} ref={localPlayerContainerRef}>
</div>
)
}

// 控制上下播
const renderFloatButtons = () => {
return (
<FloatButton.Group
icon={<DesktopOutlined />}
trigger="click"
style={{ left: "380px" }}
>
<FloatButton
onClick={toggleLocalGameStream}
icon={
localStreaming ? <VideoCameraFilled /> : <VideoCameraOutlined />
}
tooltip={<div>{localStreaming ? "停止直播" : "开始直播"}</div>}
/>
</FloatButton.Group>
);
};

// 渲染: 悬浮窗和本地流
return (
<>
<div style={{ height: "100%" }}>
{renderFloatButtons()}
{renderLocalStream()}
</div>
</>
);
}

现在我们进入直播房间已经可以看到本地摄像头的内容了, 但我们还没有将视频流投放到RTC中, 且上播逻辑也没有处理

// view/Channel/components/StreamHandler/local_stream.js
useEffect(() => {
// 发布直播推流
if (!localStreaming || !rtcClient || !localVideoStream) {
return;
}
console.log("height", canvasRef.current.height);
console.log("publishing local stream", localVideoStream);
// 将流publish到rtc中
rtcClient.publish(localVideoStream).then(() => {
// 频道中发布一条消息, 表示开始直播
sendStreamMessage(
{
user: userInfo?.username,
status: CMD_START_STREAM,
},
channelId
).then(() => {
message.success({
content: "start streaming",
});
});
});
return () => {
// 用户退出的清理工作,
// unpublish流(远程), 停止播放流(本地), 发送直播关闭消息(频道)
if (localVideoStream) {
rtcClient.unpublish(localVideoStream);
localVideoStream.stop();
sendStreamMessage(
{
user: userInfo?.username,
status: CMD_END_STREAM,
},
channelId
);
message.info({
content: "stop streaming",
});
}
};
}, [rtcClient, localStreaming, canvasRef, userInfo, channelId, localVideoStream]);

为了测试直播效果, 我们需要登录第二个账号(使用浏览器的匿名/开其他的浏览器, 此时cookie没有共享, 可以多账号登录), 进入相同频道, 开启直播, 此时第一个账号应该会自动刷新状态(如果没有则手动切换一下频道), 进入到RemoteStreamHandler, 说明我们直播的逻辑已经完成.

本地语音的逻辑也是类似的, 这里就不再重复.

接下来是远程流的渲染逻辑, 它的逻辑相对简单, 观看者可以选择开始/停止观看直播流

// view/Channel/components/StreamHandler/remote_stream.js
const RemoteStreamHandler = (props) => {

const {
remoteUser,
localVideoRef,
toggleRemoteVideo,
channelId,
userInfo,
rtcClient,
} = props;

// 这里加一个强制t人的开关, 由于debug
const enableForceStop = true;
const forceStopStream = () => {
sendStreamMessage(
{
user: userInfo?.username,
status: CMD_END_STREAM,
},
channelId
);
};
const renderRemoteStream = () => {
return (
<div style={{ height: "100%" }}>
<div
id="remote-player"
style={{
width: "100%",
height: "90%",
border: "1px solid #fff",
}}
ref={localVideoRef}
/>
<div
style={{
display: "flex",
justifyContent: "center",
marginTop: "10px",
}}
>
<span style={{ color: "#0ECD0A" }}>{remoteUser}</span>
&nbsp; is playing{" "}
</div>
</div>
);
};
const renderFloatButtons = () => {
return (
<FloatButton.Group
icon={<DesktopOutlined />}
trigger="click"
style={{ left: "380px" }}
>
<FloatButton
onClick={toggleRemoteVideo}
icon={<VideoCameraAddOutlined />}
tooltip={<div>观看/停止观看直播</div>}
/>
{enableForceStop && (
<FloatButton
onClick={forceStopStream}
icon={<VideoCameraAddOutlined />}
tooltip={<div>强制停止直播</div>}
/>
)}
</FloatButton.Group>
);
};
return (
<>
<div style={{ height: "100%" }}>
{renderFloatButtons()}
{renderRemoteStream()}
</div>
</>
);
}

开关远程流的代码在StreamHander中, 作为参数传给RemoteStream

// views/Channel/components/StreamHandler/index.js
const toggleRemoteVideo = () => {
if (!hasRemoteStream) {
return;
}
console.log("[Stream] set remote video to ", !enableRemoteVideo);
// 当前是关闭状态,需要打开
// 开关远程音频的逻辑也与此类型.
if (enableRemoteVideo) {
remoteVideo?.stop();
} else {
remoteVideo?.play(localVideoEle.current);
}
setEnableRemoteVideo(!enableRemoteVideo);
};

ok, 现在我们已经实现了基于声网RTC, 在环信超级社区集成视频直播的功能.

直播替换为游戏流

接下来我们来将直播流升级一下, 替换成模拟器包, 为了方便测试, 我们直接使用打包好的版本(https://github.com/a71698422/web-0.1.1), pkg包解压后直接放置到项目根目录,

RustNESEmulator 是一个基于Rust语言的NES模拟器, 我们在web平台可以使用它编译好的wasm版本

并将mario.nes文件放到src/assets目录下, 这是初代马里奥游戏的ROM文件(你也可以使用你喜欢的nes游戏, 如果遇到问题, 欢迎到RustNESEmulator中提issue)

加入前端的模拟器适配代码

// views/Channel/components/StreamHandler
// from tetanes.

import * as wasm from "@/pkg";
class State {
constructor() {
this.sample_rate = 44100;
this.buffer_size = 1024;
this.nes = null;
this.animation_id = null;
this.empty_buffers = [];
this.audio_ctx = null;
this.gain_node = null;
this.next_start_time = 0;
this.last_tick = 0;
this.mute = false;
this.setup_audio();
console.log("[NES]: create state");
}

load_rom(rom) {
this.nes = wasm.WebNes.new(rom, "canvas", this.sample_rate);
this.run();
}

toggleMute() {
this.mute = !this.mute;
}

setup_audio() {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) {
console.error("Browser does not support audio");
return;
}
this.audio_ctx = new AudioContext();
this.gain_node = this.audio_ctx.createGain();
this.gain_node.gain.setValueAtTime(1, 0);
}

run() {
const now = performance.now();
this.animation_id = requestAnimationFrame(this.run.bind(this));
if (now - this.last_tick > 16) {
this.nes.do_frame();
this.queue_audio();
this.last_tick = now;
}
}

get_audio_buffer() {
if (!this.audio_ctx) {
throw new Error("AudioContext not created");
}

if (this.empty_buffers.length) {
return this.empty_buffers.pop();
} else {
return this.audio_ctx.createBuffer(1, this.buffer_size, this.sample_rate);
}
}

queue_audio() {
if (!this.audio_ctx || !this.gain_node) {
throw new Error("Audio not set up correctly");
}

this.gain_node.gain.setValueAtTime(1, this.audio_ctx.currentTime);

const audioBuffer = this.get_audio_buffer();
this.nes.audio_callback(this.buffer_size, audioBuffer.getChannelData(0));
if (this.mute) {
return;
}
const source = this.audio_ctx.createBufferSource();
source.buffer = audioBuffer;
source.connect(this.gain_node).connect(this.audio_ctx.destination);
source.onended = () => {
this.empty_buffers.push(audioBuffer);
};
const latency = 0.032;
const audio_ctxTime = this.audio_ctx.currentTime + latency;
const start = Math.max(this.next_start_time, audio_ctxTime);
source.start(start);
this.next_start_time = start + this.buffer_size / this.sample_rate;
}
// ...
}

export default State;

改造local_stream

// view/Channel/components/StreamHandler/local_stream.js

import mario_url from "@/assets/mario.nes";
import * as wasm_emulator from "@/pkg";
import State from "./state";

const LocalStreamHandler = (props) => {
// 模拟器 state
const stateRef = useRef(new State());

// 注意要将原来的代码注释掉
/*
const [localVideoStream, setLocalVideoStream] = useState(false);
const localPlayerContainerRef = useRef(null);

// 开启本地视频流
useEffect(() => {
if (!localPlayerContainerRef.current) return;
const f = async () => {
// 暂时使用视频代替游戏流
let lgs = await AgoraRTC.createCameraVideoTrack();
lgs.play(localPlayerContainerRef.current);
setLocalGameStream(lgs);
}
f();
}, [localPlayerContainerRef])

// 推流的函数也暂时注释
useEffet...
*/


useEffect(() => {
// 本地游戏
if (!canvasRef) {
return;
}
// 开启键盘监听等全局事件
wasm_emulator.wasm_main();
fetch(mario_url, {
headers: { "Content-Type": "application/octet-stream" },
})
.then((response) => response.arrayBuffer())
.then((data) => {
let mario = new Uint8Array(data);
// 加载 rom数据
stateRef.current.load_rom(mario);
});
}, [canvasRef]);

// 更新本地流渲染
const renderLocalStream = () => {
return (
<div style={{ height: "100%" }}>
<canvas
id="canvas"
style={{ width: 600, height: 500 }}
width="600"
height="500"
ref={canvasRef}
/>
</div>
);
};
}

这一步完成后, 我们就可以在本地试玩马里奥游戏了, 键盘绑定为

A      = J
B = K
Select = RShift
Start = Return
Up = W
Down = S
Left = A
Right = D

将推本地视频流改为游戏流

  useEffect(() => {
// 发布直播推流
if (!localStreaming || !rtcClient) {
return;
}
// 只修改了流获取部分
// canvas的captureStream接口支持获取视频流
// 我们用这个视频流构造一个声网的自定义视频流
let stream = canvasRef.current.captureStream(30);
let localVideoStream = AgoraRTC.createCustomVideoTrack({
mediaStreamTrack: stream.getVideoTracks()[0],
});
console.log("height", canvasRef.current.height);
console.log("publishing local stream", localVideoStream);
rtcClient.publish(localVideoStream).then(() => {
sendStreamMessage(
{
user: userInfo?.username,
status: CMD_START_STREAM,
},
channelId
).then(() => {
message.success({
content: "start streaming",
});
});
});
return () => {
if (localVideoStream) {
rtcClient.unpublish(localVideoStream);
localVideoStream.stop();
sendStreamMessage(
{
user: userInfo?.username,
status: CMD_END_STREAM,
},
channelId
);
message.info({
content: "stop streaming",
});
}
};
}, [rtcClient, localStreaming, canvasRef, userInfo, channelId]);

最后总结一下房间的流程图
在这里插入图片描述

至此该项目的完整流程就算结束啦,如果有哪些步骤细节不太明确, 可以参照完整版项目
环信超级社区项目
注册环信
模拟器直播项目github源码获取

收起阅读 »

实战:一天开发一款内置游戏直播的国产版Discord应用【附源码】(上)

游戏直播是Discord产品的核心功能之一,本教程教大家如何1天内开发一款内置游戏直播的国产版Discord应用,用户不仅可以通过IM聊天,也可以进行语聊,看游戏直播,甚至自己进行游戏直播,无任何实时音视频底层技术的Web开发者同样适用,效果如下: 开整!St...
继续阅读 »

游戏直播是Discord产品的核心功能之一,本教程教大家如何1天内开发一款内置游戏直播的国产版Discord应用,用户不仅可以通过IM聊天,也可以进行语聊,看游戏直播,甚至自己进行游戏直播,无任何实时音视频底层技术的Web开发者同样适用,效果如下:



开整!

Step1 初始化

本项目基于环信超级社区的实例项目, 所以我们先从Circle-Demo-Web这个仓库开启做初始化

  1. 克隆项目 git clone https://github.com/easemob/Circle-Demo-Web.git
  2. 安装依赖 npm install
  3. 设置appKey src/utils/WebIM.js 中设置appKey,AppKey为环信后台项目对应的key,注册环信,https://console.easemob.com/user/register,登录console后台获取Appkey
  4. 运行项目 npm run start

运行后, 登录完毕效果如下,
在这里插入图片描述

与discord设计逻辑相似, 左边功能区有

  • 个人信息页
  • 好友会话页
  • 当前加入的频道
  • 创建新频道
  • 加入服务器

超级社区的逻辑为

社区(Server)、频道(Channel) 和子区(Thread) 三层结构

社区为一个独立的结构, 不同社区直接相互隔离, 社区包含不同的频道, 代表了不同的话题, 用户在频道中聊天, 而针对一条单独信息产生的回复为子区.

我们本次的项目主要集中在频道部分, 需要加入一个服务器后, 创建一个测试社区, 保证你具有管理员权限.

Step2 协议设置

在这里插入图片描述

我们的目标是尽量利用现有api扩展功能, 有几个问题需要解决

  1. 如何区分普通频道和游戏频道?
  2. 如何区分当前频道是否有玩家直播, 如果有直播如何获取玩家信息, 第二玩家的状态?
  3. 多人聊天的状态?

如何区分普通频道和游戏频道

这里直接简单采用频道前缀做特殊区分, 创建频道前缀带video-的识别为游戏频道, 同时将渲染内容做替换

// views/Channel/index.js


const isVideoChannel = useMemo(() => {
return currentChannelInfo?.name?.startsWith("");
}, [currentChannelInfo]);

const renderTextChannel = () => {
// 原来的渲染逻辑
return (
<>
<MessageList
messageInfo
={messageInfo}
channelId
={channelId}
handleOperation
={handleOperation}
className
={s.messageWrap}
/>
<div className={s.iptWrap}>
<Input chatType={CHAT_TYPE.groupChat} fromId={channelId} />
</div>
</>
);
}

const renderStreamChannel = () => {
// 先填充一个占位符
return (
<>This is a Stream Channel<>
);
}

return (
...
<div className={s.contentWrap}>
{isVideoChannel ? renderStreamChannel() : renderTextChannel()}
</div>
...
);

如果需要区分图标, 可以搜索channelNameWrap, 分别在channelItemChannel/components/Header中添加一个css类, 通过这个类设置图标图片

如何区分当前频道是否有玩家直播, 如果有直播如何获取玩家信息, 第二玩家的状态?

我们可以复用在频道中发送消息的机制, 直播开始, 结束都可以当做一条特殊的消息发送, 只不过这条消息不承载用户的信息, 而是表达用户上下播的行为

当然这个机制存在一定实时性的问题, 不过大致是可行的.

首先我们来看一条普通的消息是如何发送的

  // components/input

//发消息
const sendMessage = useCallback(() => {
if (!text) return;
getTarget().then((target) => {
let msg = createMsg({
chatType,
type: "txt",
to: target,
msg: convertToMessage(ref.current.innerHTML),
isChatThread: props.isThread
});
setText("");
deliverMsg(msg).then(() => {
if (msg.isChatThread) {
setThreadMessage({
message: { ...msg, from: WebIM.conn.user },
fromId: target
});
} else {
insertChatMessage({
chatType,
fromId: target,
messageInfo: {
list: [{ ...msg, from: WebIM.conn.user }]
}
});
scrollBottom();
}
});
});
}, [text, props, getTarget, chatType, setThreadMessage, insertChatMessage]);

去除掉与输入框逻辑耦合的部分, 可以分为两步, createMsg创建消息, deliverMsg发送消息, 这两个功能都是环信SDK功能的封装, 经过查阅文档, 它支持发送自定义消息.
在utils中新建一个stream.js文件来封装直播的逻辑

// utils/stream.js
const sendStreamMessage = (content, channelId) => {
let msg = createMsg({
chatType: CHAT_TYPE.groupChat,
type: "custom",
to: channelId,
ext: {
type: "stream",
...content,
},
});
return deliverMsg(msg)
.then(() => {
console.log("发送成功");
})
.catch(console.error);
};

它接收content表示我们的额外信息, 用户名和上下播状态, channelId区分不同的channel, 对它的调用可以如下

// 定义在 utils/stream.js 中
const CMD_START_STREAM = "start";
const CMD_END_STREAM = "end";

// 上播
sendStreamMessage(
{
user: userInfo?.username,
status: CMD_START_STREAM,
},
channelId
);
// 下播
sendStreamMessage(
{
user: userInfo?.username,
status: CMD_END_STREAM,
},
channelId
);

第二玩家的状态可以类比第一个玩家用额外的自定义消息实现, 这里不做重复.

关于自定义消息, 原本它的作用是邀请用户加入频道, 你可以在components/CustomMsg中找到, 我们要额外识别一下直播消息(可以渲染在消息列表里, 也可以直接屏蔽掉).

// components/CustomMsg/index.js
const isStream = message?.ext?.type === "stream";


// 屏蔽
const renderStream = () => {
return (<>)
}
if (isStream) {
return renderStream();
} else {
...
}

多人聊天的状态?

我们引入声网RTC sdk, 每个进入直播房间的用户都对应维护一个声网客户端,
通过on事件感知远端视频/音频流.

根据文档 进行如下操作,

  1. 注册声网开发者, 并在后台创建一个测试项目
  2. 项目根目录创建.env文件, 存放api token等信息
# channel, uid 暂时设置为固定
REACT_APP_AGORA_APPID = your app id
REACT_APP_AGORA_CHANNEL = test
REACT_APP_AGORA_TOKEN = your token
REACT_APP_AGORA_UID = 123xxx
  1. 添加声网sdk依赖 npm install agora-rtc-sdk-ng

我们在下一章中编写接入逻辑

声网RTC接入, 直播与语音实现

收起阅读 »

【附源码】国内首届Discord场景创意编程开源项目

以下开源项目是由环信联合华为举办的《国内首届Discord场景创意编程赛》作品,附源码,一键即用。一、 模拟器游戏直播-新新人类新新人类模拟器游戏直播基于环信超级社区Demo构建,增加以“video-x”命名的新型Channel,用户可在本机操作/控制当前游戏...
继续阅读 »

以下开源项目是由环信联合华为举办的《国内首届Discord场景创意编程赛》作品,附源码,一键即用。


一、 模拟器游戏直播-新新人类

新新人类模拟器游戏直播基于环信超级社区Demo构建,增加以“video-x”命名的新型Channel,用户可在本机操作/控制当前游戏界面,并通过集成声网RTC SDK, 在聊天频道中实现连麦聊天, 一对多直播。其中直播流来自于NES模拟器画面, 用户可以观看房主游玩经典NES的游戏画面. 并进一步与房主联机, 实现2p游戏。


模拟器游戏直播-项目预览

该项目不仅集成了环信超级社区SDK,声网的RTC功能,也同时集成了第三方小游戏。而这正是超级社区,也就是Discord产品的精髓之一。用户不仅可以通过IM聊天,也可以进行语聊,看游戏直播,甚至自己进行游戏直播。这些都是当下Discord这款产品中使用率最高的功能。这个作品不仅让人眼前一亮,也展示出作者对Discord和超级社区场景深入的理解,令人印象深刻。

源码:https://github.com/easemob/EasemobCircle-2022-Innovation-Challenge/tree/master/Innovation-Challenge/%E6%96%B0%E6%96%B0%E4%BA%BA%E7%B1%BB-%E6%A8%A1%E6%8B%9F%E5%99%A8%E7%9B%B4%E6%92%AD


二、 代码搬运工-CT超级社区

CT超级社区基于环信超级社区Demo,在实时聊天场景基本功能中,丰富了聊天内容英译汉翻译功能。同时增加了频道插件功能,通过将封装部分API成SDK,部分功能可通过插件的形式去实现,通过丰富的插件功能提高用户互动性,提升社区体验。目前实现的插件有:投票、社区签到、打卡分享、代码分享、频道内置机器人、外置拓展机器人。

CT超级社区-项目预览

该项目集成了多个超级社区场景下的高使用频率功能,投票以及打卡签到、机器人等插件有助于提升社区活跃度,鼓励社区内用户发起讨论,独到之处也为开发者们提供了分享代码的功能插件,为社区提供了更丰富的互动元素,高度契合了超级社区场景化需求。

源码:https://github.com/easemob/EasemobCircle-2022-Innovation-Challenge/tree/master/Innovation-Challenge/%E4%BB%A3%E7%A0%81%E6%90%AC%E8%BF%90%E5%B7%A5-CT%E7%A4%BE%E5%8C%BA


三、小雪花-有趣点儿圈子

“有趣点儿圈子”基于环信超级社区构建,在于支持万人场景下的沟通交流娱乐。多种分类频道(通知频道、直播频道),满足于用户畅游。用户等级VIP信息,专属聊天图标。内置扔骰子游戏,石头剪刀布游戏,红包功能,随机打卡功能。还有可以陪你的机器人功能,萌萌的它,可以每日单词、笑话、天气提醒...,更有功能强大的ChatGPT AI对话。

有趣点儿圈子-项目预览


该项目集成了多种群内小游戏以及红包功能,同时支持了不同群成员的等级属性。作为一个社交类产品,这些功能都极大提升了一个社区的活力和丰富程度。值得一提的是虽然ChatGPT功能并没有在此项目中完全跑通,但这种创新精神和将新兴功能接入超级社区的想象力仍然值得鼓励。期待后期继续完善,为广大开发者分享更加卓越的场景应用。

源码:https://github.com/easemob/EasemobCircle-2022-Innovation-Challenge/tree/master/Innovation-Challenge/%E5%B0%8F%E9%9B%AA%E8%8A%B1-%E6%9C%89%E8%B6%A3%E7%82%B9%E5%84%BF%E5%9C%88%E5%AD%90

以上开源作品中使用到的SDK:

 ●注册环信:https://console.easemob.com/user/register

●超级社区介绍:https://www.easemob.com/product/im/circle

●超级社区SDK 集成文档::https://docs-im.easemob.com/ccim/circle/overview

●超级社区Demo体验:https://www.easemob.com/download/demo#discord

●技术支持社区:https://www.imgeek.org

收起阅读 »

类Discord应用『环信超级社区1.0』项目介绍【附源码】

2021年马斯克让Clubhouse火爆出圈,2022年Discord以1.5亿月活150亿美元估值的数据让全球的开发者们看到了泛娱乐领域新的机会,环信作为泛娱乐行业的基础设施服务商,一直致力于给开发者提供更稳定的SDK,更丰富更易用的API,更垂直的场景解决...
继续阅读 »

2021年马斯克让Clubhouse火爆出圈,2022年Discord以1.5亿月活150亿美元估值的数据让全球的开发者们看到了泛娱乐领域新的机会,环信作为泛娱乐行业的基础设施服务商,一直致力于给开发者提供更稳定的SDK,更丰富更易用的API,更垂直的场景解决方案。近日环信重磅推出了“环信超级社区DEMO”,这是一款类Discord产品的开源项目,在此基础上二开,可以快速搭建国内版Discord产品,帮您节省60%的开发难度!


项目介绍

环信超级社区是一款基于环信IM+声网RTC打造的类Discord实时社区应用,用户可创建/管理自己的兴趣社区,设置/管理频道(群组),支持陌生人/好友单聊、社区成员无上限,可创建的频道数无上限,用户加入的频道数无上限,真正实现万人实时群聊,语音聊天等。


功能架构


核心优势

1、IM提供高并发的通讯管道,支持亿级用户并发

▲万人群组互动

▲群组数量无上限

▲自定义加群权限设置

▲支持群资料和属性

▲提供群组/聊天室完善的群聊管理功能

▲提供管理员列表、成员列表、禁言列表、黑名单等服务

▲聊天室功能与直播功能进行对接实现直播聊天室

▲可以根据客户需要进行灵活配置,包括关系、数量、能力

2、百万人大群组承载

环信群组分片技术:将1个群中百万成员分片在100个万人群里


3、消息爆炸问题

解决方案:通过notice减少消息的Qps,进群后再拉取下发消息


4、环信SD-GMN,构建低延迟网络,实现全球加速

▲五大数据中心覆盖全球200+个国家和地区;

▲集团自建上万台服务器,部署全球300多个补充加速节点,实现低延迟;

▲FPA加速与AWS加速智能切换,确保通信质量和高可用能力;

▲典型时延:北美,30-40毫秒;欧洲20-30毫秒;东南亚,日韩30-40毫秒;中东70毫秒;北非45毫秒;澳洲50毫秒;最远的南美和南非,90毫秒;

▲持续改进,不断优化…

5、内容过滤能力

环信内容审核系统,低成本,高效率,个性化,高准确


适合场景

兴趣社交、游戏社交、区块链、媒体、粉丝社区、品牌社区等等。


项目源码

https://github.com/easemob/easemob_supercommunity


APK下载

链接: https://pan.baidu.com/s/1HUL_CUYTvUr3mT29WRcoaQ 提取码: zq1x 


超级社区2.0

继超级社区1.0以后,环信推出了超级社区2.0(Circle),这是一款基于环信 IM 打造的类 Discord 实时社区应用场景方案,支持社区(Server)、频道(Channel) 和子区(Thread) 三层结构。一个 App 下可以有多个社区,同时支持陌生人/好友单聊。用户可创建和管理自己的社区,在社区中设置和管理频道将一个话题下的子话题进行分区,在频道中根据感兴趣的某条消息发起子区讨论,实现万人实时群聊,满足超大规模用户的顺畅沟通需求。旨在一站式帮助客户快速开发和构建稳定超大规模用户即时通讯的"类Discord超级社区",作为构建实时交互社区的第一选择,环信超级社区自发布以来很好地满足了类 Discord 实时社区业务场景的客户需求,并支持开发者结合业务需要灵活自定义产品形态,目前已经广泛服务于国内头部出海企业以及海外东南亚和印度企业。

环信超级社区2.0介绍:https://www.easemob.com/product/im/circle

环信超级社区2.0体验:https://www.easemob.com/download/demo#discord


收起阅读 »

开源项目|使用声网&环信 SDK 构建元宇宙应用 MetaTown 最佳实践

大家好!我们是美特兄弟三人组!前阵参加了【声网&环信 RTE2022 创新编程挑战赛】,整个大赛历时47天,除了对声网和环信的 SDK 有了很多的体验,还做了很多奇思妙想的结合。在此次大赛中我们团队基于声网&环信 SDK 构建了一个元宇宙应用 ...
继续阅读 »

大家好!我们是美特兄弟三人组!前阵参加了【声网&环信 RTE2022 创新编程挑战赛】,整个大赛历时47天,除了对声网和环信的 SDK 有了很多的体验,还做了很多奇思妙想的结合。在此次大赛中我们团队基于声网&环信 SDK 构建了一个元宇宙应用 MetaTown,获得了环信专项奖,开心之余把这个项目介绍给大家,抛砖引玉,感兴趣的兄弟可以加入进来一起在元宇宙中闯荡江湖~

一、关于MetaTown

3f49489dc452a849d29829903a504934.jpg

金钱是被铸造出来的自由——陀思妥耶夫斯基

在三次元的现实世界,你是否为了搞钱而终日奔波?忍受996甚至007的非人待遇?是否正经历着创业人的凛冬?疫情等因素带来的本轮经济下行落实在每个人身上都是真真切切的,对于经历了40年经济暴增的国人来讲更是史无前例的。

在荷包日瘪萎靡不振的日子里,精神慰藉尤为重要,元宇宙正是当下最时髦的,何不创造一个可以躺着赚钱的元宇宙小镇?不为别的,在有虚拟工作的前提下每天我的虚拟角色的金币都会涨,想一想岂不是有一点小欢愉?还能在这个小镇结交一些志同道合沉迷于搞钱的友友们!

c1b918850b4200f52dd18a77f5787a35.jpg

自然这些虚拟财富目前还是无法转化为真正的成就感的(变为现实财富),但现在市面上开始有人吹web3.0了!坐等币圈大佬入局,我们有信心打造一款能让一部分人先富起来的元宇宙小镇!

---------------------------------------

MetaTown 是基于声网 RTC 和环信 IM 打造的模拟城市生活的元宇宙社交类 App。

初来乍到的玩家首次进入 MetaTown 先选择不同的职业,在这座城市首先要考虑的是如何赚钱,或做一名程序员上下班打卡,或自己创业开个酒吧/书店或去银行投资理财。除此以外,还要注意身体健康,可能哪天会随机生病需要去医院,支付挂号费咨询不同科室的医生。注意,没有核酸证明有可能看不了病嗷~不打工没钱也看不了病嗷~

c0b33d573cd7c33d59ffa94ff25aa85c.jpg

这个小镇的所有公共场所,均可以随时发起与陌生人私聊,因共同兴趣结缘,充分体验在MetaTown小镇 搞钱 闯荡的日子!

项目 GitHub 地址:

https://github.com/AgoraIO-Community/RTE-2022-Innovation-Challenge/tree/main/Application-Challenge/%E9%A1%B9%E7%9B%AE243-metatown-metatown

二、MetaTown 核心技术

MetaTown 使用当下最流行的声网实时音视频以及环信即时通讯 SDK,具体场景如:医院场景中一对一咨询医生,进行远程实时问诊。社交场景中与好友实时音视频沟通,聊天。


1、环信即时通讯

MetaTown 在6大交互场景中运用了环信的即时通讯 IM(Instant Messaging),给 IM 赋予了新的场景活力,支持陌生人私聊,群聊及超大型聊天室。

2-1)会话列表 项目中 IM 会话列表如下图:

92bfd6a17ed1c1ed32497b4726563362.jpg


会话列表关键代码:

publicvoidshow(BaseActivity activity){
NiceDialog.init().setLayoutId(R.layout.dialog_message)
.setConvertListener(new ViewConvertListener() {
@Override
protectedvoidconvertView(ViewHolder holder, BaseNiceDialog dialog){
RecyclerView rv = holder.getView(R.id.rv);
List easeConversationInfos = initData();
rv.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
DialogMsgAdapter dialogMsgAdapter = new DialogMsgAdapter(easeConversationInfos);
rv.setAdapter(dialogMsgAdapter);
dialogMsgAdapter.setOnItemClickListener(new DialogMsgAdapter.OnItemClickListener() {
@Override
publicvoidonItemClick(int pos,String name){
SoundUtil.getInstance().playBtnSound();
dialog.dismissAllowingStateLoss();
EMConversation item = (EMConversation) easeConversationInfos.get(pos).getInfo();
ChatDialog.getInstance().show(activity,item.conversationId(), name);
}
});
}
})
.setAnimStyle(R.style.EndAnimation)
.setOutCancel(true)
.setShowEnd(true)
.show(activity.getSupportFragmentManager());
}


2-2)IM 聊天

项目中 IM 聊天如下图:

776dc85621da53df8dd76d40e98ba06d.jpg


发送消息关键代码:

@Override
public void sendMessage(EMMessage message) {
if(message == null) {
if(isActive()) {
runOnUI(() -> mView.sendMessageFail("message is null!"));
}
return;
}
addMessageAttributes(message);
if (chatType == EaseConstant.CHATTYPE_GROUP){
message.setChatType(EMMessage.ChatType.GroupChat);
}else if(chatType == EaseConstant.CHATTYPE_CHATROOM){
message.setChatType(EMMessage.ChatType.ChatRoom);
}
...
EMClient.getInstance().chatManager().sendMessage(message);
if(isActive()) {
runOnUI(()-> mView.sendMessageFinish(message));
}
}


接受消息关键代码:

public void onMessageReceived(List messages) {
super.onMessageReceived(messages);
LiveDataBus.get().with(Constants.RECEIVE_MSG, LiveEvent.class).postValue(new LiveEvent());
for (EMMessage message : messages) {
// in background, do not refresh UI, notify it in notification bar
if(!MetaTownApp.getInstance().getLifecycleCallbacks().isFront()){
getNotifier().notify(message);
}
//notify new message
getNotifier().vibrateAndPlayTone(message);
}
}


2、声网音视频

MetaTown 运用了声网的实时音视频功能。

1)集成声网 SDK

1-1)添加声网音视频依赖在 app module 的 build.gradle 文件的 dependencies 代码块中添加如下代码:

implementation 'io.agora.rtc:full-rtc-basic:3.6.2'


然后在app module的build.gradle文件的android->defaultConfig代码块中添加如下代码

ndk {
abiFilters "arm64-v8a"
}
// 设置支持的SO库架构(开发者可以根据需要,选择一个或多个平台的so)


1-2)添加必要权限 为了保证 SDK 能正常运行,我们需要在 AndroidManisfest.xml 文件中声明以下权限:















1-3)APP 在签名打包时防止出现混淆的问题需要在 proguard-rules.pro 文件里添加以下代码:

-keep classio.agora.**{*;}


2)创建并初始化 RtcEngine

创建并初始化 RtcEngine
private void initializeEngine() {
try {
EaseCallKitConfig config = EaseCallKit.getInstance().getCallKitConfig();
if(config != null){
agoraAppId = config.getAgoraAppId();
}
mRtcEngine = RtcEngine.create(getBaseContext(), agoraAppId, mRtcEventHandler);


//因为有小程序 设置为直播模式 角色设置为主播
mRtcEngine.setChannelProfile(CHANNEL_PROFILE_LIVE_BROADCASTING);
mRtcEngine.setClientRole(CLIENT_ROLE_BROADCASTER);


EaseCallFloatWindow.getInstance().setRtcEngine(getApplicationContext(), mRtcEngine);
//设置小窗口悬浮类型
EaseCallFloatWindow.getInstance().setCallType(EaseCallType.CONFERENCE_CALL);
} catch (Exception e) {
EMLog.e(TAG, Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}


3)设置视频模式

privatevoidsetupVideoConfig(){
mRtcEngine.enableVideo();
mRtcEngine.muteLocalVideoStream(true);
mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_1280x720,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));

//启动谁在说话检测
int res = mRtcEngine.enableAudioVolumeIndication(500,3,false);
}


4)设置本地视频显示属性
4-1)setupLocalVideo( VideoCanvas local ) 方法用于设置本地视频显示信息。应用程序通过调用此接口绑定本地视频流的显示视窗(view),并设置视频显示模式。在应用程序开发中,通常在初始化后调用该方法进行本地视频设置,然后再加入频道。

privatevoidsetupLocalVideo(){
if(isFloatWindowShowing()) {
return;
}
localMemberView = createCallMemberView();
UserInfo info = new UserInfo();
info.userAccount = EMClient.getInstance().getCurrentUser();
info.uid = 0;
localMemberView.setUserInfo(info);
localMemberView.setVideoOff(true);
localMemberView.setCameraDirectionFront(isCameraFront);
callConferenceViewGroup.addView(localMemberView);
setUserJoinChannelInfo(EMClient.getInstance().getCurrentUser(),0);
mUidsList.put(0, localMemberView);
mRtcEngine.setupLocalVideo(new VideoCanvas(localMemberView.getSurfaceView(), VideoCanvas.RENDER_MODE_HIDDEN, 0));
}


4-2)joinChannel(String token,String channelName,String optionalInfo,int optionalUid ) 方法让用户加入通话频道,在同一个频道内的用户可以互相通话,多个用户加入同一个频道,可以群聊。使用不同 App ID 的应用程序是不能互通的。如果已在通话中,用户必须调用 leaveChannel() 退出当前通话,才能进入下一个频道。

privatevoidjoinChannel(){
EaseCallKitConfig callKitConfig = EaseCallKit.getInstance().getCallKitConfig();
if(listener != null && callKitConfig != null && callKitConfig.isEnableRTCToken()){
listener.onGenerateToken(EMClient.getInstance().getCurrentUser(),channelName, EMClient.getInstance().getOptions().getAppKey(), new EaseCallKitTokenCallback(){
@Override
publicvoidonSetToken(String token,int uId){
EMLog.d(TAG,"onSetToken token:" + token + " uid: " +uId);
//获取到Token uid加入频道
mRtcEngine.joinChannel(token, channelName,null,uId);
//自己信息加入uIdMap
uIdMap.put(uId,new EaseUserAccount(uId,EMClient.getInstance().getCurrentUser()));
}

@Override
public void onGetTokenError(int error, String errorMsg) {
EMLog.e(TAG,"onGenerateToken error :" + error + " errorMsg:" + errorMsg);
//获取Token失败,退出呼叫
exitChannel();
}
});
}
}


完成以上配置后就可以发起呼叫了,其它一些摄像头控制,声音控制可以参考声网官网的API,这里不再赘述。

3、场景原画

3c3fcbba0f0756f9d04cc6a30cbe9c0b.jpg


92e0417362811512e9a30009f161e4de.png


1)人物行走可分为踏步、水平移动两种动作,分别通过踏步动画和控制人物及背景 scrollview 移动实现。

2)关键点在于人物向左走过半屏继续向左行走,或向右走过半屏继续向右走的情况,以向右走为例,如果人物未超过屏幕中线,则控制人物向右移动;如果超出屏幕中线继续向右移动,则将人物固定在中线位置,背景向左滑动;如果背景向左已滑动至尽头,则保持背景不动,人物继续向右移动;如果人物移动至右边缘,则只控制人物原地踏步,背景和人物均不水平移动。

动画关键代码:

if (isToRight) {
ivPerson.setRotationY(180f);
}
isToRight = false;

RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivPerson.getLayoutParams();

if (sv != null) {
if (layoutParams.leftMargin > DisplayUtil.getHeight(MetaTownApp.getApplication()) || !sv.canScrollHorizontally(-1)) {
layoutParams.leftMargin -= STEP;
layoutParams.leftMargin = Math.max(layoutParams.leftMargin, 50);
ivPerson.setLayoutParams(layoutParams);
} else {
sv.smoothScrollBy(-STEP, 0);
}
} else {
layoutParams.leftMargin -= STEP;
layoutParams.leftMargin = Math.max(layoutParams.leftMargin, 50);
ivPerson.setLayoutParams(layoutParams);
}

mAnimationDrawable.start();

if (!isToRight) {
ivPerson.setRotationY(0f);
}
isToRight = true;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivPerson.getLayoutParams();
if (sv != null) {
if (layoutParams.leftMargin < DisplayUtil.getHeight(MetaTownApp.getApplication()) || !sv.canScrollHorizontally(1)) {
layoutParams.leftMargin += STEP;
layoutParams.leftMargin = Math.min(layoutParams.leftMargin, DisplayUtil.getWidth(MetaTownApp.getApplication()) - 100);
ivPerson.setLayoutParams(layoutParams);
} else {
sv.smoothScrollBy(STEP, 0);
}
} else {
layoutParams.leftMargin += STEP;
layoutParams.leftMargin = Math.min(layoutParams.leftMargin, DisplayUtil.getWidth(MetaTownApp.getApplication()) - 100);
ivPerson.setLayoutParams(layoutParams);
}

mAnimationDrawable.start();


写字楼打工

bc4f986b39c3dc97594148931d278342.png

(程序员上下班赚钱)

银行投资理财

83d4122ff7bff56c6fdb76df312d7829.png

(银行投资理财)

再说说2.0版本后续计划,有时间有兴趣的小伙伴欢迎留言加入我们兴趣小组一起搞事情~


1)把现有的几个场景补齐(II期)

2)开发新场景,丰富搞钱路数

3)等一位币圈大佬掉到碗里。

以上是 MetaTown 作品在 RTE2022 编程挑战赛期间的实践分享,更多开源项目可以访问环信开源项目频道:https://www.imgeek.org/code/

备注“开源项目”加入环信开发者开源项目交流群

b2307c9a08e932ab1d16bdece9758a2f.jpg


收起阅读 »

最近很火的反调试,你知道它是什么吗?

前言我们日常开发中,永远离不开debug调试,断点技术一直是我们排查bug的有力手段之一!随着网络安全意识的逐步提高,对app安全的要求就越来越高,反调试 (有朋友不太了解这个概念,这里我解释一下,就是通过调试技术,比如我们可以反编译某个apk,即使apk是r...
继续阅读 »

前言

我们日常开发中,永远离不开debug调试,断点技术一直是我们排查bug的有力手段之一!随着网络安全意识的逐步提高,对app安全的要求就越来越高,反调试 (有朋友不太了解这个概念,这里我解释一下,就是通过调试技术,比如我们可以反编译某个apk,即使apk是release包,同样也可以进行反编译后调试,比如最新版本的jadx)的技术也渐渐深入我们开发者的眼帘,那么我们来具体看看,android中,同时也是linux内核中,是怎么处理调试程序的!

执行跟踪

无论是断点还是其他debug手段,其实都可以总结为一个技术手段,就是执行跟踪,含义就是一个程序监视另一个程序的技术,被跟踪的程序通过一步步执行,知道收到一个信号或者系统调用停止!

在linux内核中,就是通过ptrace系统调用进行的执行跟踪

#include  
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

随着我们对linux的了解,那么就离不开对权限的讨论!一个程序跟踪另一个程序这种东西,按照linux风格,肯定是具有某种权限才可以执行!这个权限就是设置了CAP_SYS_PTRACE 权限的进程,就可以跟踪系统中除了init进程(linux第一个进程)外的任何进程!当然!就算一个进程没有CAP_SYS_PTRACE权限,也可以跟踪一个与被监视进程有相同属组的进程,比如父进程可以通过ptrace跟踪子进程!执行跟踪还有一个非常重要的特点,就是两个进程不能同时跟踪一个进程

我们再回到ptrace函数调用,可以看到第一个参数是一个枚举值,其实就是发出ptrace的当前行为,它有以下可选命令(仅部分举例):

其他的参数含义如下: pid参数标识目标进程,addr参数表明执行peek(读操作)和poke(写操作)操作的地址,data参数则对于poke操作,指明存放数据的地址,对于peek操作,指明获取数据的地址。

ptrace设计探讨

我们了解了linux提供的系统api,那么我们还是从设计者角度出发,我们想要跟踪一个进程的话,我们需要干点什么?来来来,我们来想一下,可能就会有以下几个问题吧

  1. 被跟踪进程与跟踪进程怎么建立联系

  2. 如果使程序停止在我们想要停止的点(比如断点)

  3. 跟踪进程与被跟踪进程怎么进行数据交换,又或者我们怎么样看到被跟踪进程中当前的数据

下面我们逐步去探讨一下这几个问题吧!(以PTRACE_ATTACH 作为例子)首先对于问题1,我们怎么建立起被跟踪进程与跟踪进程之间的联系呢?linux中进程存在父子关系,兄弟关系对吧!这些进程就可以通过相对便捷的方式进行通信,同时linux也有定义了特殊的信号提供给父子进程的通信。看到这里,相信大家能够猜到ptrace究竟干了啥!就是通过调用ptrace系统调用,把被跟踪进程(第二个参数pid)的进程描述符号中的p_pptr字段指向了跟踪进程!毕竟linux判断进程的描述,就靠着进程描述符,想要建立父子关系,修改进程描述符即可,就这么简单!这里补充一下部分描述符号:


那么好!我们建立进程之间的联系了,那么当执行跟踪终止的时候,我们就可以调用ptrace 第一个参数为PTRACE_DETACH 命令,把p_pptr恢复到原来的数据即可!(那么有人会问,原来的父进程描述符保存在哪里了,嘿嘿,在p_opptr中,也就是他的祖先中,这里我们不深入讨论)

接下来我们来讨论一下问题2和问题3,怎么使程序停止呢?(这里我们讨论常用的做法,以linux内核2.4版本为准,请注意细微的区别)其实就是被监控进程在读取指令前,就会执行被嵌入的监控代码,如果我想要停止在代码的某一行,这个时候cpu会执行一条“陷阱指令”也称为Debug指令(这里可以采用架构相关的指令或者架构无关的指令实现,比如SIGTRAP或者其他规定信号),一般来说,这条指令作用只是为了使程序停止,然后发出一个SIGCHLD信号给父进程(不了解信号的知识可以看看这篇),嘿嘿,那么这个父进程是谁呢?没错,就是我们刚刚改写的监控进程,这样一来,我们的监控进程就能够收到被监控进程的消息,此时就可以继续调用其他的ptrace调用(第一个参数指定为其他需要的枚举值),查看当前寄存器或者其他的数据


这么说下来可能会有人还是不太懂,我们举个例子,我们的单步调试是怎么样做的: 还是上面的步骤,子进程发送一个SIGCHLD给父进程,此时身为父进程的监控线程就可以再调用ptrace(PTRACE_SINGLESTEP, *, *, * )方法给子进程的下一条指令设置陷阱指令,进行单步调试,此时控制权又会给到子进程,子进程执行完一个指令,就会又发出SIGCHLD给父进程,如此循环下去!

反调试

最近隐私合规与app安全性能被各大app所重视,对于app安全性能来说,反调试肯定是最重要的一环!看到上面的这些介绍,我们应该也明白了ptrace的作用,下面我们介绍一下几种常见的反调试方案:

  1. ptrace占位:利用ptrace的机制,我们知道一个进程只能被一个监控进程所监控,所以我们可以提前初始化一个进程,用这个进程对我们自身app的进程调用一次ptrace即可

  2. 轮询进程状态:可以通过轮训的手段,查看进程当前的进程信息:proc/pid/status

Name: test\
Umask: 0022\
State: D (disk sleep)-----------------------表示此时线程处于sleeping,并且是uninterruptible状态的wait。

Tgid: 157-----------------------------------线程组的主pid\
Ngid: 0\
Pid: 159------------------------------------线程自身的pid\
PPid: 1-------------------------------------线程组是由init进程创建的。\
TracerPid: 0\                             **这里是关键**
Uid: 0 0 0 0\
Gid: 0 0 0 0\
FDSize: 256---------------------------------表示到目前为止进程使用过的描述符总数。\
Groups: 0 10 \
VmPeak: 1393220 kB--------------------------虚拟内存峰值大小。\
VmSize: 1390372 kB--------------------------当前使用中的虚拟内存,小于VmPeak。\
VmLck: 0 kB\
VmPin: 0 kB\
VmHWM: 47940 kB-----------------------------RSS峰值。\
VmRSS: 47940 kB-----------------------------RSS实际使用量=RSSAnon+RssFile+RssShmem。\
RssAnon: 38700 kB\
RssFile: 9240 kB\
RssShmem: 0 kB\
VmData: 366648 kB--------------------------进程数据段共366648KB。\
VmStk: 132 kB------------------------------进程栈一共132KB。\
VmExe: 84 kB-------------------------------进程text段大小84KB。\
VmLib: 11488 kB----------------------------进程lib占用11488KB内存。\
VmPTE: 1220 kB\
VmPMD: 0 kB\
VmSwap: 0 kB\
Threads: 40-------------------------------进程中一个40个线程。\
SigQ: 0/3142------------------------------进程信号队列最大3142,当前没有pending

如果TracerPid不为0,那么就存在被监控的进程,此时如果该进程不是我们所信任的进程,就调用我们指定好的程序重启即可!读取这个proc/pid/status文件涉及到的相关处理可以自行google,这里就不再重复列举啦!

总结

看到这里,我们也能够明白debug在linux内核中的处理流程啦!最后,点个赞再走呗!


作者:Pika
来源:juejin.cn/post/7132438417970823176

收起阅读 »

二次元恋爱社交开源项目---mua【附客户端、服务端源码】

Mua是由环信MVP开发者精心打造的开源项目,提供Demo体验和示例源码,支持开发者结合业务需要灵活自定义产品形态。Mua是一个二次元恋爱互动社交APP,有类似项目需求的创业者或想拥有甜蜜恋爱过程的开发者们都可以来44,下面介绍下这个恋爱升温神器的功能 打开A...
继续阅读 »

Mua是由环信MVP开发者精心打造的开源项目,提供Demo体验和示例源码,支持开发者结合业务需要灵活自定义产品形态。

Mua是一个二次元恋爱互动社交APP,有类似项目需求的创业者或想拥有甜蜜恋爱过程的开发者们都可以来44,下面介绍下这个恋爱升温神器的功能

APP----码,码   ,


Mua 

线

Mua

1MuaIM

 

 

 



2IM
Mua使IM


3
5Appdddd



4

tips.




5

AppApp+100




6





7

& 便&&





8Mua

Mua3

饿



9

cmd

 




MuaAPP,


Mua

⬇️Demo  


Android

mua端、服务端
https://github.com/easemob/mua


收起阅读 »

【开源项目】音视频LowCode平台——AgoraFlow

此开源项目由热心网友@Lu-Derek 开发AgoraFlow是一款音视频 Low Code Web 共享编辑器。将音视频相关功能进行模块化集成,提供一个图形化界面,让开发者可以用做 PPT 的形式来完成想要实现的功能。Q:AgoraFlow是一个怎么样的项目...
继续阅读 »

此开源项目由热心网友@Lu-Derek 开发

AgoraFlow是一款音视频 Low Code Web 共享编辑器。将音视频相关功能进行模块化集成,提供一个图形化界面,让开发者可以用做 PPT 的形式来完成想要实现的功能。

Q:AgoraFlow是一个怎么样的项目?

A:AgoraFlow是一个基于Agora+环信MQTT服务,在Low Code方向上的一次尝试。除了Agora Vue Web SDK和环信之外,还用到了Node-RED,一个IBM的物联网框架。

这个项目允许用户通过拖拽的方式安排音视频流在不同设备上的采集-发布-订阅-播放行为。在积木式的搭建工作流后,程序会根据逻辑在指定设备部署应用。


Q:为什么要做这样一个项目?

A:作为程序员,总会耳濡目染一些行业热点。最近Low Code比较火,所以想看看Low Code和音视频结合会产生什么样的火花。其实大家对于Low Code的产品形态还没有一个准确的定位,所以我想利用这次编程赛阐述我的理解。


Q:对于这个项目的技术选型有什么想说的?

我觉得大家都应该利用自己的【工作技能】,接触一些【工作内容】以外的东西。这是我在这个项目中使用了平时并不常用的技术栈、选型比较奇怪的原因。我使用了声网尚在Beta中的产品【Agora
Vue
SDK】、使用了偏物联网行业的技术栈【Node-RED】和【MQTT】,按照我的理解做了这样一个不完整但是有趣的产品。对我来说,这是一次尝试,一次娱乐,而非工作。



【项目介绍】

AgoraFlow

基于声网+环信MQTT的音视频LowCode平台

https://agoraflow.wrtc.dev/


安装/设置:
npm install

访问 http://localhost:1880  即可编辑音视频上下行

运行:

目前支持四台设备:
device1:https://agoraflow.wrtc.dev/devices/index.html?deviceName=device1
device2:https://agoraflow.wrtc.dev/devices/index.html?deviceName=device2
device3:https://agoraflow.wrtc.dev/devices/index.html?deviceName=device3
device4:https://agoraflow.wrtc.dev/devices/index.html?deviceName=device4

使用四代设备分别打开网页,网页的上下行受low code平台控制


Github地址:

https://hub.fastgit.org/AgoraIO-Community/RTE-2021-Innovation-Challenge/tree/master/Application-Challenge/%E3%80%90%E7%BA%A2%E9%B2%A4%E9%B1%BC%E4%B8%8E%E7%BB%BF%E9%B2%A4%E9%B1%BC%E4%B8%8E%E9%A9%B4%E3%80%91AgoraFlow

作者联系邮箱:scaret.in@gmail.com

收起阅读 »

【开源项目】集成环信IM开发的一款社交app---共享影院

项目介绍该项目旨在嵌入当今已经较为成熟的视频播放行业,让用户可以创建一个观影房,与远端的其他用户进行视频通讯并且同时观看同一视频。做到相隔万里,依然可以零距离互动,感受视频所带来的乐趣。项目结构share-cinema: 共享影院前端源码video-backe...
继续阅读 »

项目介绍

该项目旨在嵌入当今已经较为成熟的视频播放行业,让用户可以创建一个观影房,与远端的其他用户进行视频通讯并且同时观看同一视频。做到相隔万里,依然可以零距离互动,感受视频所带来的乐趣。

项目结构

  • share-cinema: 共享影院前端源码
  • video-backend: 共享影院后端源码
  • 演示视频.mp4: 共享影院功能演示视频


相关技术

  • Agora Video SDK :实现高清、稳定、流畅的及时通讯
  • 环信IM SDK :实现安全、简单的文字聊天
  • Socket.io :实现聊天室中同步播放、暂停以及拖动进度条
  • Celery + Flower : 实现后台的用户画像及模型更新
  • 图计算 :生成用户推荐模型


作品背景

《共享影院》项目的设计灵感来自当下非常流行的一种视频形式:Reaction Video(反应视频)。反应视频,顾名思义,就是记录下人们对事情做出反应的视频。在表现形式上,画面由两个部分组成,包括观看的视频资源,以及观看者本人的反应。这有点像观看体育比赛时电视台邀请的实况解说。
2013年,美剧《权利的游戏》第三季热播,大量的油管网友录制自己或朋友看剧时的激烈反应,引发了全球观众的集体共鸣,反应视频由此走入大众视野。目前,这类反应视频已经在多个视频平台成为了一类发展成熟且庞大的分支。Youtube上最火的Reaction类频道,目前已累积90亿次播放量,收获了1970万订阅。
Reaction Video之所以会如此成功,有以下两部分的因素:

  • 认同感:对于观众来说,他们希望在看视频的时候可以找到与自己有相同关注点的人,也期待着他人在看视频时会不会产生与自己相近的反应。
  • 分享感:对于反应视频的制作者来说,他们希望与大家分享自己在看视频时的喜怒哀乐。

这两种心理因素同样适应于如今非常流行的弹幕文化,但是其中仍然存在一些缺点。对于反应视频的制作者,或者发送弹幕的人来说,他们发表了自己的观点,收获了分享的满足感,却很难得到及时的反馈;对于观众而言,他们看到了弹幕或视频制作者的反应,从中找到了认同感,却难以与其分享自己的感受。
同时我们注意到,从以前“看完视频写评论”到“看视频时发弹幕”、“直播互动聊天”等新型视频形式,随着现在人们生活节奏的加快,人们越来越需要在相同的时间内获得更多的信息。
因此,我们提出《共享影院》这个项目,从“视频+音视频通讯+文字聊天”的形式上,将认同感与分享感合二为一,为看视频的用户提供及时地、双向地、新颖地视频娱乐体验。

创新性

  • 2020年,网易云音乐推出了《一起听音乐》,可以与好友一起同步听歌曲。
  • 2020年,BiliBili推出了《一起看》功能,提供了同步观影,语音消息等功能。
  • 2021年,抖音推出了《一起看》功能,提供和好友一起刷短视频的功能。
  • 我们的《共享影院》不但提供了与好友一起看视频的功能、实现了聊天室内的同步观影,而且与这些产品不同的是,首次结合了视频通讯,将Reaction Video的理念加入进来。不仅可以让已经观看过视频的人,将感兴趣的视频推荐给好友,再次观看进而与好友更加深入的交流;还可以在赛事直播以及新品发布时,与好友第一时间面对面见证历史时刻。此外,我们还引入了“陌生人匹配”,在孤独的时候为用户推荐最符合用户画像的“熟悉的陌生人”。
一起看时进行音视频通讯
一起看时进行文字聊天
观看精彩影视作品
观看赛事直播与发布会
陌生人匹配


潜在商业价值

反应类视频所带来的市场需求

  • 给用户带来认同感与分享感
  • 为用户提供一个私密的共享空间
  • 彼此之间的互动体验

文化推广,促进营销

该项目为用户之间提供了更多的讨论机会,这种“一起观看”的形式并不只停留在用户的首次观影,许多用户会为了与他人分享而将同一作品进行多次观看。同时,用户邀请和分享给好友视频资源,这对视频本身的内容营销有着颇多利好。可以快速带动视频内容的宣传,同时增加点播数,创造更多收益。

会员制度

部分电视剧、电影、综艺,需要开通会员才能观看。共同观影要求所有用户都满足权限才能进行观看。因此,在视频得到推广的同时,视频平台也将从中获益。

快速接入 《共享影院》的核心内容简单,可变性强

可以快速与已经成熟的视频平台对接,迅速投入商业化使用。视频资源不但可以是影视剧集,还可以是实况球赛、网络直播、新品发布会等各式各样的视频类型,上升空间大。
趣味性

借助音视频通讯来吸引用户观看视频。使用Agora Video SDK可以快速提升音视频通讯技术,提供美颜、变声、AR Face等多种玩法,增加视频生活的趣味性,提高用户黏性。


运行说明

前端

  • 《共享影院》项目前端由vue.cli 4.x搭建,启动前请按以下步骤执行
  • 安装依赖

npm install

  • 本地启动

npm run serve

运行后通过 https://localhost:8020/ 进行访问

后端

  • 《共享影院》项目后端由python3+ Flask框架搭建,运行前请按以下步骤操作
  • 请在requirements.txt所在目录下执行
  • 请在环信IM管理控制台手动创建一个名为superadmin的管理员用户,用于在后台创建聊天室
  • 请在环信IM管理控制台手动将用户注册方式修改为开放注册,用于实现用户注册

pip install -r requirements.txt

  • 添加声网RTC所需的相关配置


在app.py同一路径下创建config.py文件

文件中添加agora_token相关信息

# 声网SDK配置

appid = ""

appsecret = ""#

环信IM配置

url = "http://a1.easemob.com/"

orgname = ""

appname = ""

clientid = ""

clientsecret = ""

  • 启动后端


python app.py


操作指南

  • 前后端均正常运行之后,使用https://localhost:8020进入主页
  • 用户A选择主页中的任一视频,将自动创建观影房,观影房将有一个独立的房间号
  • 用户B从主页左上角的输入框输入用户A的房间号,将进入用户A的房间
  • 此时双方在观影房内的播放、暂停、拖动进度条均保持同步
  • 观影房中右侧的三个按钮分别是:禁止麦克风、退出房间、禁止摄像头

注意事项

  • 部分后台数据与模型储存在服务器上,为方便用户浏览项目中的全部功能,我们提供了本地运行版本,部分数据已经进行了模拟。
  • 视频资源因空间较大,我们只上传了部分视频资源,方便演示播放功能。
  • 项目中的图片以及视频资源均来源于网络。


收起阅读 »

一套包含了社区匹配聊天以及语音互动直播相关的社交系统模板项目

一套包含了社区匹配聊天语音以及直播相关的社交系统模板项目,包括服务器端以及 Android 客户端背景及选型在实现社交相关项目时,少不了 IM 及时聊天功能,这里选择了自己比较熟悉的环信三方 SDK,环信 IM...
继续阅读 »

社交模板项目

一套包含了社区匹配聊天语音以及直播相关的社交系统模板项目,包括服务器端以及 Android 客户端

项目资源均来自于互联网,如果有侵权请联系我

背景及选型

一直以来都是标榜自己是一个喜欢开源的程序猿,一直想做一款能够被大家认同的开源项目,也是想提供给广大的新手程序猿一个比较完整系统的社交系统以供参考,因此有了这一套社交系统模板项目, 当前模板项目主要功能可以看下边的 功能与TODO

在实现社交相关项目时,少不了 IM 及时聊天功能,这里选择了自己比较熟悉的环信三方 SDK,环信 IMSDK 能够比较方便的实现自定义扩展功能,比如会话扩展,消息扩展等,消息效果可以看下方 项目截图

通话方面这里选择了声网提供的服务,看了下他们提供的功能还是比较多的,这里主要用到了语音通话,以及变声效果处理,感觉集成还是比较方便的,之前没用过的情况下,其实两天就搞定了 1V1 通话和多人互动通话的功能,他们还提供了更多场景使用,比如教育,直播等,更多功能大家可以搜索他们官网查看,通话效果可以看下方 项目截图

开发环境

项目基本属于在Android开发环境下开发,全局使用Kotlin语言,项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒,10.x的文件选择等;

  • 开发系统:Mac OS 11.1
  • 开发工具:Android Studio 4.2
  • 打包工具:Gradle 4.2.0
  • 开发语言:Kotlin 1.4.32

项目模块儿

  • app是项目主模块儿,这部分主要包含了项目的业务逻辑,比如匹配、内容发布,信息展示等
  • vmcommon是项目基础模块儿,这部分主要包含了项目的基类封装,依赖管理,包括网络请求,图片加载等工具类
  • vmim聊天与通话模块儿,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,只需要将vmimmodule导入到自己的项目中就行了,具体使用方式参见项目app模块儿;

功能与TODO

IM部分功能

  •  链接监听
  •  登录注册
  •  会话功能
    •  置顶
    •  标为未读
    •  删除与清空
    •  草稿功能
  •  聊天功能
    • [x]消息类型
      •  文本消息
      •  图片消息
        •  查看大图
        •  保存图片
    •  消息处理
      •  删除
      •  撤回
      •  复制(仅文本可复制)
    •  昵称头像处理(通过回调实现)
    •  头像点击(回调到 App 层)
    •  语音实时通话功能
      •  1V1音频通话
      •  静音、扬声器播放
      •  音效变声
  •  解忧茶室
    •  创建房间
    •  发送消息
      •  文本消息
      •  鼓励消息
    •  上下麦处理
    •  音效变声(彩蛋功能)

App部分功能

  •  登录注册(包括业务逻辑和 IM 逻辑)
  •  首页
    •  自己的状态
    •  拉取其他人的状态信息
    •  心情匹配
    •  解忧聊天室
  •  聊天(这里直接加载 IM 模块儿)
  •  发现
    •  发布内容
    •  喜欢操作
    •  详情展示
      •  喜欢操作
      •  评论获取
      •  添加评论
  •  我的
    •  个人信息展示
    •  上传头像、封面
    •  设置昵称、签名、职业、地址、生日、性别等
    •  邮箱绑定
    •  个人发布与喜欢内容展示
  •  设置
    •  个人信息设置
    •  深色模式适配
    •  通知设置
    •  资源加载设置
    •  关于
      •  检查更新
      •  问题反馈
    •  环境切换
    •  退出

发布功能

  •  多渠道打包
  •  签名配置
  •  开发与线上环境配置
  •  敏感信息保护

配置运行

  1. 首先复制config.default.gradleconfig.gradle
  2. 配置下config.gradle内相关字段
  3. 正式打包需要自己生成签名文件,然后修改下config.gradlesignings签名信息
  4. 需配合服务器端一起使用,修改上边config.gradle配置文件的baseDebugUrlbaseReleaseUrl

参与贡献

如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与 😁

  1. Fork本仓库
  2. 新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
  3. 提交代码
  4. 新建Pull Request
  5. 等待Review & Merge

其他

下载体验 

这就是一个使用当前模板运营的一个项目

项目截图

这里简单截取了几个界面,更多功能自己去发现吧

matchHome matchExplore matchMsg matchMine matchChat matchChatFast matchCall matchAbout matchInfo matchAbout

交流

QQ群: 901211985 个人QQ: 1565176197

QQ 交流群 个人 QQ
收起阅读 »

【开源项目】利用环信IM开发的一款兴趣社交APP——相约国粹

项目背景 相约国粹是一个集结国内名著有声书,国粹欣赏以及外国著名音乐为主题 供用户欣赏 蕴含了古典文化的传播,教育,欣赏等 我们将每一种资源(比如论语)都单独开设群聊用户们可以根据喜欢的资源将志同道合的朋友欢聚一堂进行线上畅聊讨论运行说明&...
继续阅读 »

项目背景

 相约国粹是一个集结国内名著有声书,国粹欣赏以及外国著名音乐为主题 

供用户欣赏 蕴含了古典文化的传播,教育,欣赏等 我们将每一种资源(比如论语)都单独开设群聊

用户们可以根据喜欢的资源将志同道合的朋友欢聚一堂进行线上畅聊讨论

运行说明 

1.终端进入对应目录进行 pod install 

2.资源只做展示,项目中已去除资源获取地址

功能介绍 

1.音视频资源的展示,播放与下载 

2.通过资源匹配响应的群聊 

3.可以进行1对1聊天,语音,视频


github地址:https://github.com/xiyupingxuan/RTE-2021-Innovation-Challenge/tree/master/Application-Challenge/


收起阅读 »

【开源项目】声网Agora+环信IM实现的社交APP---CircleLive

CircleLive分享,遇见,Live分享自己絮语,心情,碎碎念,在Live中遇见共鸣。技术支持1、Agora互动直播SDK多人音视频互动2、Agora云信令实时消息通信,Live过程的消息分发3、环信IM 支持,建立好有关系,会话主要功能1、发布和浏览心情...
继续阅读 »

CircleLive
分享,遇见,Live

分享自己絮语,心情,碎碎念,在Live中遇见共鸣。

技术支持
1、Agora互动直播SDK
多人音视频互动

2、Agora云信令
实时消息通信,Live过程的消息分发

3、环信
IM 支持,建立好有关系,会话

主要功能
1、发布和浏览心情动态
2、Live
  -发布Live
  -订阅共鸣Live
  -多人音视频Live
  -Live心情滤镜
3、IM
  -加好友(自动创建会话)
  -基本IM(文字,语言,图片,视频,表情)


视频Demo




安装包
Apk下载:链接: https://pan.baidu.com/s/111xZY2ANYCNZORt0ydLg9Q 密码: voil


视频和APK链接如果失效,请email我 stonelavender@hotmail.com

测试账号
也可以新注册

欢天喜地
你好
灰色头像
密码均为:123456

收起阅读 »

【开源项目】用环信IM实现的公益APP--宝贝回家Baby back home

项目背景有时,只一瞬间没回头,生命中的最重要就消失不见。 这是电影《亲爱的》中一句最经典的台词,看完整个电影,就会明白失去孩子的父母有多无助,拐卖孩子的人贩子有多可恨。 当今社会通过网络平台,短视频,404页面等寻亲信息曝光,很多热心群众都自发参与帮助走失儿童...
继续阅读 »

项目背景
有时,只一瞬间没回头,生命中的最重要就消失不见。 这是电影《亲爱的》中一句最经典的台词,看完整个电影,就会明白失去孩子的父母有多无助,拐卖孩子的人贩子有多可恨。 当今社会通过网络平台,短视频,404页面等寻亲信息曝光,很多热心群众都自发参与帮助走失儿童寻找家人。但还没有发现一个功能实用,寻亲信息集中的移动端公益平台。 本项目是一个为丢失家人的家庭提供发布寻人信息和搜集线索的移动端平台,借助网络传播的力量,通过环信IM一对一实时消息,声网音视频的高效连接,帮助走失的宝贝尽快回归家庭。

项目说明
本项目使用OC开发
接入声网SDK AgoraRtcEngine_iOS
接入环信SDK EaseCallKit

运行说明
本项目使用iOS设备运行
在注册页面注册账号后就能正常登录

开发环境
Xcode 12.1

运行环境
iOS 11.0

功能介绍
0, 注册登录
本项目登录注册功能全部采用环信sdk提供的登录注册功能

1, 首页
首页主要是展示寻人信息,点击列表可以进入查看详情并私信发布人,利用环信一对一实时通讯让有线索的人更高效顺畅的与寻人者建立通讯。

2, 寻找
寻找页可以按条件筛选丢失人员的信息,除内容关键词搜索外,还可按地区,按发布时间,按性别分类筛选,快速查找到符合的信息。

3,消息
可以接收到有线索人的私信,点击头像添加好友,进行聊天。 界面右上角 “+” 号可以搜索添加好友,左上角点击进入联系人界面。

4,我的
个人信息管理。可更换头像,修改昵称,查看我发布的信息,以及退出登录

5,发布
发布寻人信息,输入被寻人相关信息,丢失原因,时间,地址,姓名,性别,联系方式等。发布后还可以将本条信息转发至qq群,微信群,微信好友,借助网友的力量一起寻找。 因没有服务器,使用的本地数据库。发布后可以在首页查看


项目截图:





iOS端源码下载地址:https://hub.fastgit.org/AgoraIO-Community/RTE-2021-Innovation-Challenge/tree/master/Application-Challenge/%E3%80%90%E5%BC%A0%E5%AE%87%E3%80%91%E5%AE%9D%E8%B4%9D%E5%9B%9E%E5%AE%B6/Baby%20back%20home


欢迎添加环信冬冬微信,联系该项目作者

收起阅读 »

【开源项目】用环信IM实现的一款教学助手

教学助手开发环境:Tools : Android Studio 4.1.2os : windows 10code : kotlin配置文件:appId 目录 com.kangaroo.studentedu.app.appIdappCe(证书) 目录 com.k...
继续阅读 »

教学助手

开发环境:

  • Tools : Android Studio 4.1.2
  • os : windows 10
  • code : kotlin

配置文件:

  • appId 目录 com.kangaroo.studentedu.app.appId
  • appCe(证书) 目录 com.kangaroo.studentedu.app.appCe

运行环境:

  • os : Android 5.0 +

项目包含内容:

  • Android project
  • 安装包

第三方:

  • 声网灵动课堂 sdk
  • 声网直播 sdk
  • 环信IM sdk

项目背景

疫情期间在线教育火了,各种直播教育软件都开始推广。教育软件开始了火热。就拿我公司来说,我公司是数据化教育行业的一员。在疫情期间帮助学校进行了作业发布,作业批改的业务,帮助学校提升了疫情期间的教学质量。 就拿普通中小学来举例: 互动白板功能非常重要,老师上课会用互动白板功能,老师录课也会用互动白板功能。除了上课和录课,老师还可以通过设备来下发电子考题,考试题目。考试完毕后还可以统计到本堂课的上课质量。

  • 拿一些特殊课堂举例: 艺术课,体育课,音乐课等,这些课堂有时可能不需要白板这样的功能,互动直播功能又变得比较适合。
  • 拿一些校外辅导举例: 校外辅导的校长需要了解当前学校老师教学情况的数据,了解学生上课的数据,了解学校招生的数据。通过这些数据来提升学校的应受。

运行说明

本项目登录功能全部采用环信sdk提供的登录功能呢,支持单设备登录,互踢功能。 由于本项目没有后台,很多功能和数据都是在本地做的处理

安装包:安装包

一个app提供两种身份登录(学生,老师),两种权限(学生,老师)


老师分为2种类型

  1. 普通老师(带有白板功能)
  2. 体育老师等艺术类老师(带有直播功能)

学生端


主功能界面


学生主要有4种功能

签到

学生地理位置的签到,老师会收到学生签到的通知,那么进一步老师会在考勤上记录学生的情况

课堂点评

学生会对老师当堂课程进行点评,打分,可以发送图片内容。点评的数据,在数据统计里展示,学校管理员,或校长,会直接看到,那么校长会知道教学质量

写作业

老师端会给当天课程进行布置作业,布置一些图片作业,或者文字作业。学生要写作业

数据统计

学生的数据主要针对各科的数据进行统计,直观的看自己的平均发展。查漏补缺,提升自己薄弱的方面



课程表

课程表会展示 管理员在后台给老师和学生排的课程,下方课程便是今天学生该上的课程(直播课,或白板课)。

通讯录

教学互动,老师和学生可以直接交流。对今天不会的课程进行答疑

消息列表

消息列表

我的

退出登录,等基本展示

老师端




主功能界面

老师主要有6种功能

学员考勤

勾选今日到校的学生进行考勤管理。

课堂点评

老师可以查看学生对自己的评价,提升自我教学质量

布置作业

老师端会给当天课程进行布置作业,布置一些图片作业,或者文字作业。学生要写作业

批改作业

老师会对发上的作业,进行及时批改。

我的班级

可以查看当前班级,查看学生数据

数据统计

老师关心的班级男女比例,出勤率,课堂评价,作业提交率等,根据数据来对自己的教学质量更改

课程表

课程表会展示 管理员在后台给老师和学生排的课程,下方课程便是今天老师要教的(直播课,或白板课)。

通讯录

教学互动,老师和学生可以直接交流。对今天不会的课程进行答疑

消息列表

消息列表

我的

退出登录,等基本展示


github地址:https://github.com/smartbackme/RTE-2021-Innovation-Challenge/tree/master/Application-Challenge/%5B%E5%8F%B2%E5%A4%A7%E4%BC%9F%5D%20%E6%95%99%E5%AD%A6%E5%8A%A9%E6%89%8B

安装包下载地址:com.kangaroo.studentedu-release-v1.0.0-20210528152329L.apk


欢迎添加环信冬冬微信,联系该项目作者

收起阅读 »

【开源项目】使用环信IM开发的一款仿微信APP

项目背景:为了让更多的小伙伴们能够使用环信快速开发出一款自己的社交通讯APP,现进行开源 产品功能:易用IM是一款仿微信APP,包含以下主要功能:1. 单聊,群聊,群聊天中可发随机红包2. 通讯录:管理好友和群组3. 朋友圈:展示自己和好友发的全部可见的动态,...
继续阅读 »

项目背景

为了让更多的小伙伴们能够使用环信快速开发出一款自己的社交通讯APP,现进行开源

 

产品功能:

易用IM是一款仿微信APP,包含以下主要功能:

1. 单聊群聊,群聊天中可发随机红包

2. 通讯录:管理好友和群组

3. 朋友圈展示自己和好友发的全部可见的动态,可点赞、评论、回复和收藏

4. 支付宝充值余额、提现

5. 余额充值提现功能

6. 表情商店:后台维护表情包,用户可一键添加到自己的聊天中

 

软件架构

1. 使用ThinkPHP3.2.3框架开发

2. 数据库mysql5.7

3. IM功能集成环信即时通讯

4. 集成极光推送、阿里云OSS

5. 百度地图



资源地址:

服务端 https://gitee.com/491290710/EasyIM_Service.git

安卓端 https://gitee.com/491290710/EasyIM_Android.git

IOS端 https://gitee.com/491290710/EasyIM_IOS.git 

 

安装教程

1. 服务器建议使用centos7+,运行环境使用lnmp1.5-1.6一键安装

2. 第三方开发参数请在Application/Common/Conf/config.php中进行配置

3. WEB端代码在layim目录中,访问方式为 您的域名/layim

4. 推荐使用阿里云服务器ECS,优惠购买请点击

https://partner.aliyun.com/shop/20690101/newusers?marketer=286

 

使用说明:

1. WEB端体验地址 http://weixin.pro2.liuniukeji.net/layim

2. 可自行注册账号,注册时验证码输入 654321

 

 

项目截图:

 

 

 

 

 

安卓端下载地址:



本开源项目仅做个人学习使用如需商业合作,请联系:

电话: 18660911357

微信  liuniukeji-js

公司官网: https://www.liuniukeji.com/index/easemob

收起阅读 »

【开源项目】使用环信SDK搭建在线教学场景(含三端源码下载)

2021年在线教育行业如火如荼,所谓人人为我,我为人人,为了方便教育行业的小伙伴们更好地使用环信SDK,我搭建了一个在线教学开源项目“环环教育”,一期覆盖1对1互动教学、在线互动小班课两种核心教学场景,实现了iOS、Android和Web三端应用。此开源项目演...
继续阅读 »

2021年在线教育行业如火如荼,所谓人人为我,我为人人,为了方便教育行业的小伙伴们更好地使用环信SDK,我搭建了一个在线教学开源项目“环环教育”,一期覆盖1对1互动教学、在线互动小班课两种核心教学场景,实现了iOS、Android和Web三端应用。此开源项目演示了环信IM SDK的部分API使用示例,以帮助开发者更好地理解和运用环信IM SDK。


 

开源项目简介:


环环教育demo,覆盖1对1互动教学、在线互动小班课两种核心教学场景,具备即时消息互动、实时音视频互动、互动白板、屏幕共享等丰富功能。Demo生动展示了如何用IM、音视频、互动白板SDK共同搭建在线互动教学场景。同时demo覆盖了教师端和学生端,并开发实现了iOS、Android、Web三端应用。现在正式开源给小伙伴们,详细介绍请往下看。

 

核心界面展示

1对1互动教学主界面


 移动端

 


Web端

 

 

在线互动小班课主界面


移动端

 

 



教师web端

 

 

核心功能

  • 1对1互动教学
  • 在线互动小班课(1名老师+多位学生互动)
  • 即时消息互动(聊天室)
  • 实时音视频互动(音视频)
  • 互动白板
  • 白板权限控制
  • 屏幕共享
  • 学生列表(小班课老师端特有)

 

资源下载

Github源码下载:(含iOS、Android、Web以及服务器端

https://github.com/easemob/learn-easemob

 

Demo下载体验:

Web端:https://cgame.bjictc.com/#/

iOS端:


识别二维码下载

 

 

Android端:

下载链接:https://download-sdk.oss-cn-beijing.aliyuncs.com/downloads/IMDemo/easemob_education_demo.apk


识别二维码下载

 

tips:同一房间名称+同一身份退出后不要重复多次进入,建议体验时退出后使用不同房间名。

收起阅读 »

【开源项目】使用环信SDK搭建在线教学场景(含三端源码下载)

 引言:2021年在线教育行业如火如荼,所谓人人为我,我为人人,为了方便教育行业的小伙伴们更好地使用环信SDK,IMGEEK论坛生态开发者@巍巍 发布了使用环信SDK搭建的在线教学开源项目“环环教育”,一期覆盖1对1互动教学、在线互动小班课...
继续阅读 »

 引言:2021年在线教育行业如火如荼,所谓人人为我,我为人人,为了方便教育行业的小伙伴们更好地使用环信SDK,IMGEEK论坛生态开发者@巍巍 发布了使用环信SDK搭建的在线教学开源项目“环环教育”,一期覆盖1对1互动教学、在线互动小班课两种核心教学场景,实现了iOS、Android和Web三端应用。此开源项目演示了环信IM SDK的部分API使用示例,以帮助开发者更好地理解和运用环信IM SDK。

 


 

开源项目简介:


环环教育demo,覆盖1对1互动教学、在线互动小班课两种核心教学场景,具备即时消息互动、实时音视频互动、互动白板、屏幕共享等丰富功能。Demo生动展示了如何用IM、音视频、互动白板SDK共同搭建在线互动教学场景。同时demo覆盖了教师端和学生端,并开发实现了iOS、Android、Web三端应用。现在正式开源给小伙伴们,详细介绍请往下看。

 

核心界面展示

1对1互动教学主界面



 移动端

 


Web端

 

 

在线互动小班课主界面

 


移动端

 

 



教师web端

 

 

 

核心功能

  • 1对1互动教学
  • 在线互动小班课(1名老师+多位学生互动)
  • 即时消息互动(聊天室)
  • 实时音视频互动(音视频)
  • 互动白板
  • 白板权限控制
  • 屏幕共享
  • 学生列表(小班课老师端特有)

 

资源下载

Github源码下载:(含iOS、Android、Web以及服务器端

https://github.com/easemob/learn-easemob

 

Demo下载体验:

Web端:https://cgame.bjictc.com/#/

iOS端:


识别二维码下载

 

 

Android端:

下载链接:https://download-sdk.oss-cn-beijing.aliyuncs.com/downloads/IMDemo/easemob_education_demo.apk


识别二维码下载

 

tips:同一房间名称+同一身份退出后不要重复多次进入,建议体验时退出后使用不同房间名。 

收起阅读 »