Commit 54fb1017 by giaogiao

redis维护rtc频道

parent 2761ba3a
...@@ -4,41 +4,33 @@ import java.io.Serializable; ...@@ -4,41 +4,33 @@ import java.io.Serializable;
public class RtcRedisKey implements Serializable { public class RtcRedisKey implements Serializable {
/**
* 维护频道信息
*/
public static final String RTC_CHANNEL_INFO = "rci:%s";
/** /**
* 维护所有用户当前在线的频道ID * 维护所有用户当前在线的频道ID
* new Map<String,Long> map
* map.put("clientA",10001)
* map.put("clientB",10001)
* map.put("clientC",10002)
* map.put("clientD",10003)
* <p>
* redis Key:
* user_join_channel = ujc * user_join_channel = ujc
* rcu:clientA:10001 * rcu:clientA = 10001
* rcu:clientB:10001 * rcu:clientB = 10001
* rcu:clientC:10002 * rcu:clientC = 10002
* rcu:clientD:10003 * rcu:clientD = 10003
*/ */
public static final String USER_JOIN_CHANNEL = "ujc:%s:%s"; public static final String USER_JOIN_CHANNEL = "ujc:%s";
/** /**
* 维护频道中存在的用户 * 维护频道中存在的用户
* Map<Long,List<String>> map
* <p>
* new List<String> list
* list.add("clientA")
* list.add("clientB")
* <p>
* map.put(10001,list)
* <p> * <p>
* redis Key: * redis Key (set 集合):
* rtc_channel_users = rcu * rtc_channel_users = rcu
* key = rcu:10001:clientA * rcu:10001 = clientA , clientB
* key = rcu:10001:clientB * rcu:10002 = clientC
* key = rcu:10002:clientC * rcu:10003 = clientD
* key = rcu:10003:clientD
*/ */
public static final String RTC_CHANNEL_USERS = "rcu:%s:%s"; public static final String RTC_CHANNEL_USERS = "rcu:%s";
} }
package com.wecloud.rtc.service; package com.wecloud.rtc.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.List; import java.util.List;
/** /**
* 管理rtc频道 * 管理rtc频道
*/ */
public interface MangerRtcChannelService { public interface MangerRtcCacheService {
boolean isEmpty(String appKey, String clientId, Long rtcChannelId);
/** /**
* 创建一个频道 * 创建一个频道
*
* @param appKey
* @param clientId
* @param rtcChannelId 雪花算法生成频道id
*/ */
Long create(String appKey, String clientId, Long rtcChannelId); void create(String appKey, String clientId, Long rtcChannelId) throws JsonProcessingException;
/** /**
* 加入频道 * 加入频道
...@@ -27,13 +35,14 @@ public interface MangerRtcChannelService { ...@@ -27,13 +35,14 @@ public interface MangerRtcChannelService {
*/ */
List<String> getClientListByRtcChannelId(Long rtcChannelId); List<String> getClientListByRtcChannelId(Long rtcChannelId);
/** // /**
* 根据客户端ID获取该客户端加入的频道ID // * 根据客户端ID获取该客户端加入的频道ID
*/ // */
Long getRtcChannelIdListByClientId(String appKey, String clientId); // Long getRtcChannelIdListByClientId(String appKey, String clientId);
/** /**
* 获取客户端忙线/空闲状态 * 获取客户端忙线/空闲状态
*
* @param appKey * @param appKey
* @param clientId * @param clientId
* @return true:忙线,false空闲 * @return true:忙线,false空闲
......
package com.wecloud.rtc.service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.ws.utils.RedisUtils;
import com.wecloud.rtc.entity.RtcChannelInfo;
import com.wecloud.rtc.RtcRedisKey;
import com.wecloud.rtc.service.MangerRtcCacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
@Service
public class MangerRtcCacheServiceImpl implements MangerRtcCacheService {
@Autowired
private RedisUtils redisUtils;
@Override
public boolean isEmpty(String appKey, String clientId, Long rtcChannelId) {
List<String> clientListByRtcChannelId = getClientListByRtcChannelId(rtcChannelId);
// 移除自己
clientListByRtcChannelId.remove(appKey + clientId);
return clientListByRtcChannelId.isEmpty();
}
@Override
public void create(String appKey, String clientId, Long rtcChannelId) throws JsonProcessingException {
// --- 频道信息
RtcChannelInfo rtcChannelInfo = new RtcChannelInfo();
//当前房主
rtcChannelInfo.setOwner(appKey + clientId);
//创建时间
rtcChannelInfo.setCreateTimestamp(new Date().getTime());
String rtcChannelInfoJson = new JsonMapper().writeValueAsString(rtcChannelInfo);
// --- 保存频道信息
redisUtils.setKey(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelInfoJson);
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
redisUtils.setKey(userJoinChannelKey, rtcChannelId.toString());
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.addForSet(rtcChannelUsers, appKey + clientId);
}
@Override
public void join(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
redisUtils.setKey(userJoinChannelKey, rtcChannelId.toString());
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.addForSet(rtcChannelUsers, appKey + clientId);
}
@Override
public void remove(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
redisUtils.delKey(userJoinChannelKey);
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.removeForSet(rtcChannelUsers, appKey + clientId);
}
@Override
public List<String> getClientListByRtcChannelId(Long rtcChannelId) {
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
Set<String> forSetMembers = redisUtils.getForSetMembers(rtcChannelUsers);
return new ArrayList<>(forSetMembers);
}
// @Override
// public Long getRtcChannelIdListByClientId(String appKey, String clientId) {
//
// //用户当前在线的频道ID
// String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
// String key = redisUtils.getKey(userJoinChannelKey);
//
// return Long.valueOf(key);
// }
@Override
public boolean getBusyStatus(String appKey, String clientId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
String key = redisUtils.getKey(userJoinChannelKey);
return key != null && !key.isEmpty();
}
}
package com.wecloud.rtc.service.impl;
import com.wecloud.im.ws.utils.RedisUtils;
import com.wecloud.rtc.RtcRedisKey;
import com.wecloud.rtc.service.MangerRtcChannelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Service
public class MangerRtcChannelServiceImpl implements MangerRtcChannelService {
@Autowired
private RedisUtils redisUtils;
@Override
public Long create(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId, rtcChannelId);
//频道中存在的用户
String rtcChannelUsersKey = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId, appKey + clientId);
redisUtils.setKey(userJoinChannelKey, "");
redisUtils.setKey(rtcChannelUsersKey, "");
return null;
}
@Override
public void join(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId, rtcChannelId);
//频道中存在的用户
String rtcChannelUsersKey = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId, appKey + clientId);
redisUtils.setKey(userJoinChannelKey, "");
redisUtils.setKey(rtcChannelUsersKey, "");
}
@Override
public void remove(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId, rtcChannelId);
//频道中存在的用户
String rtcChannelUsersKey = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId, appKey + clientId);
redisUtils.delKey(userJoinChannelKey);
redisUtils.delKey(rtcChannelUsersKey);
}
@Override
public List<String> getClientListByRtcChannelId(Long rtcChannelId) {
String rtcChannelUsersKey = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId, "*");
Set<String> keys = redisUtils.keys(rtcChannelUsersKey);
List<String> clientList = new ArrayList<>();
for (String next:keys){
String s = next.split(":")[2];
clientList.add(s);
}
return clientList;
}
@Override
public Long getRtcChannelIdListByClientId(String appKey, String clientId) {
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId, "*");
Set<String> keys = redisUtils.keys(userJoinChannelKey);
String next = keys.iterator().next();
return Long.valueOf(next.split(":")[2]);
}
@Override
public boolean getBusyStatus(String appKey, String clientId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId, "*");
Set<String> keys = redisUtils.keys(userJoinChannelKey);
if (keys == null || keys.isEmpty()) {
return false;
} else {
return true;
}
}
}
# wecloud-RTC音视频客户端信令对接文档
# wecloud-RTC音视频客户端信令对接文档
## 文档描述
此文档为单人RTC音视频通讯技术对接文档
由于RTC基于wecloud-im即时通讯服务,**对接RTC前,需要先对接wecloud-im服务**
## 核心概念说明
### 频道与会话
频道与会话的概念不一样
```
1. 引入"频道RtcChannel"概念, 可以不基于会话发起音视频通话
2. 目前频道只支持两个client,进行通话的两端必须先加入到同一个"频道",所有的指令都在频道内进行转发
3. 允许不在同个会话中的两个client加入到同个频道进行通话 (可配置是否两个client必须在同一个会话才能发起音视频通话)
4. 一个频道可以由通话发起者绑定到会话ID,"挂断","未接听"等状态会同步到会话, 未绑定将不同步到会话(可选)
5. 连接websocket时带上client类型, 如web,安卓,ios
client需要监听频道内 状态更新(房间断开,挂断)、用户状态更新(用户加入, 用户退出)、流状态更新(切换音频 切换视频)
```
## 流程图
![单人WebRTC发起流程图](https://tva1.sinaimg.cn/large/008i3skNgy1guxhnn6x64j60jr0xgmz302.jpg)
## 指令码说明
#### **subCmd**子类型指令码
##### 客户端**请求**指令列表:
**create**:创建频道
**join**: 加入频道
**SDP**:SDP数据转发
**reject**:拒绝加入频道
**leave**:主动挂断(离开频道)
##### 服务端**响应**指令列表:
**rtcCall**:接收到RTC邀请
**clientEvent** : 用户状态更新事件(用户加入,用户退出,用户拒接邀请)
**typeEvent**:流状态更新(切换音频 切换视频)
**statusEvent**:状态更新(网络断开,挂断)
**SDP**:SDP数据转发
**busy**:忙线
## 创建频道 发起RTC音视频通话
### 1.client发起方 发起请求:
**示例一: 必传参数:**
```json
{
"reqId":"555111-ad-afd12",
"cmd":3,
"data":{
"toClient":["client_3030"],
"subCmd":"create",
"type":"video"
}
}
```
**示例二: 全部可选参数:**
```json
{
"reqId":"555111-ad-afd12",
"cmd":3,
"data":{
"diyParam自定义字段":"aaaa自已定义字段的值",
"toConversation":null,
"toClient":["client_3030"],
"subCmd":"create",
"type":"video",
"push":{
"title":"xxx正在邀请你视频通话",
"subTitle":"点击接听"
},
"attrs":{
"a":"示例: 用户自定义的一些键值对A",
"b":"示例: 存储用户自定义的一些键值对B"
}
}
}
```
**说明:** toConversation会话ID可以为空, 为空时通话记录将不会记录到会话当中, 如"未接听", "已拒绝"等事件通知不会写入到会话的聊天记录中.
**请求参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| toConversation | Long | 是 | 会话id,可为空 |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| push | String | 是 | 接收方展示的系统推送内容 |
| subCmd | String | 否 | 子类型指令 |
| toClient | Array[String] | 否 | 被邀请的客户端ID |
| type | String | 否 | 类型: "video" 或 "voice" |
### 2.服务端向client发起方响应数据:
```json
{
"reqId":"555111-ad-afd12",
"cmd":5,
"data":{
"channelId":1234263457652
}
}
```
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| channelId | Long | 否 | 由服务端创建的频道id |
## 接收方收到RTC音视频通话邀请
服务端向client接收方下发数据:
```json
{
"cmd":4,
"data":{
"subCmd":"rtcCall",
"subData":{
"type":"video",
"toConversation":null,
"channelId":1234263457652,
"sender":"client_1010"
},
"diyParam自定义字段":"aaaa自已定义字段的值",
"attrs":{
"a":"示例: 用户自定义的一些键值对",
"b":"存储用户自定义的一些键值对"
}
}
}
```
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| toConversation | Long | 否 | 绑定的会话id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子类型指令 |
| sender | String | 否 | 发起通话的客户端ID |
| channelId | Long | 否 | 由服务端创建的频道id |
| type | String | 否 | 类型: "video" 或 "voice" |
## 同意加入频道
client接收方向服务端请求数据:
```json
{
"reqId":"123",
"cmd":3,
"data":{
"channelId":1234263457652,
"subCmd":"join"
}
}
```
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子类型指令 |
## 通知:有client加入频道
服务端向频道内其他client响应数据:
```json
{
"cmd":4,
"data":{
"subCmd":"clientEvent",
"subData":{
"channelId":1234263457652,
"type":"join",
"clientId":7657567,
}
}
}
```
## 拒绝加入频道
client接收方向服务端请求数据:
```json
{
"reqId":"123",
"cmd":3,
"data":{
"subCmd":"reject",
"diyParam自定义字段":"aaaa自已定义字段的值",
"channelId":1234263457652,
"attrs":{}
}
}
```
**说明:**
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| toConversation | Long | 否 | 会话id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子类型 |
## 通知:有Client拒绝加入频道
服务端向频道内其他client响应数据:
```json
{
"cmd":4,
"data":{
"subCmd":"clientEvent",
"subData":{
"channelId":1234263457652,
"type":"reject",
"clientId":7657567,
}
}
}
```
## 流媒体描述信息SDP转发
(服务端仅负责转发)(ice_candidate,anser,offer)
### client发送SDP
```json
{
"reqId":"123",
"cmd":3,
"data":{
"subCmd":"sdp",
"channelId":1234263457652,
"sdpData":"xxxxxxxxxxxxxxxx",
"diyParam自定义字段":"aaaa自已定义字段的值",
"attrs":{}
}
}
```
### client接收SDP
```json
{
"cmd":4,
"data":{
"subCmd":"sdp",
"subData":{
"channelId":1234263457652,
"clientId":7657567,
"sdpData":"xxxxxxxxxxxxxxxx",
"diyParam自定义字段":"aaaa自已定义字段的值",
"attrs":{}
}
}
}
```
## 主动挂断(离开频道)
client主动离开频道
向服务端请求:
```json
{
"reqId":"123",
"cmd":3,
"data":{
"subCmd":"leave",
"channelId":1234263457652
}
}
```
## 通知:有client离开频道
服务端向频道内其他client响应数据:
```json
{
"cmd":4,
"data":{
"subCmd":"clientEvent",
"subData":{
"channelId":1234263457652,
"type":"leave",
"clientId":7657567,
}
}
}
```
## 对方忙线(对方正在通话中)
服务端响应给发起方
```json
{
"reqId":"555111-ad-afd12",
"cmd":5,
"data":{
"subCmd":"busy",
"subData":{
"channelId":1234263457652,
"clientId":7657567
}
}
}
```
## 断线重连
重新join进频道即可重连
## 对方是否还挂起
## 视频/音频切换
## 查询对方是否离开
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment