Commit a807bfcf by hewei

Merge branch '1.4_rtc' into '1.4'

1.4 rtc

See merge request !3
parents dd30d6d8 9c7c59d3
package io.geekidea.springbootplus.test;
import cn.hutool.core.codec.Base64;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 读取系统文件目录 生成apns字符串
*/
public class ApnsTest {
@Test
public void test() throws Exception {
File file = new File("/Users/giaogiao/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/f31cd9e6d7da1d15c57c40575f5c85db/Message/MessageTemp/e1180914825140f051a87348bda5cbb5/File/hipro_test_push.p12");
InputStream certificate = new FileInputStream(file);
String encode = Base64.encode(certificate);
}
}
......@@ -6,6 +6,8 @@ import com.turo.pushy.apns.PushType;
import com.wecloud.im.ws.sender.IosPush;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
......@@ -29,17 +31,22 @@ public class IosApnsBase64Test {
// * @param sound rtc= "call.caf"; 否则为default
Map<String, Object> customProperty = new HashMap<String, Object>(10);
String apnsCertificatePath = "frogsell_push_dev.p12";
String deviceToken = "27c93ca84bbf17d9ff8eb05df0576ac49822db2ae1c02aa0afea83b5c3861276";
// String apnsCertificatePath = "frogsell_push_dev.p12";
String deviceToken = "5b761f954efe7493de0bc751942e1a8355853771b66a512f5687ca05e7335e99";
String alertTitle = "你好333";
String alertBody = "hi333";
int badge = 1;
String topicBundleId = "com.jdw.frogsell";
String topicBundleId = "com.xteng.Hibro";
boolean contentAvailable = false;
InputStream certificate = IosPush.getApnsCertificate(apnsCertificatePath);
String encode = Base64.encode(certificate);
// InputStream certificate = IosPush.getApnsCertificate(apnsCertificatePath);
// String encode = Base64.encode(certificate);
File file = new File("/Users/giaogiao/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/f31cd9e6d7da1d15c57c40575f5c85db/Message/MessageTemp/e1180914825140f051a87348bda5cbb5/File/hipro_test_push.p12");
InputStream in = new FileInputStream(file);
String encode = Base64.encode(in);
// 解码
byte[] decode = Base64.decode(encode);
......
package com.wecloud.im.controller;
import com.wecloud.im.param.CreateRtcChannelResult;
import com.wecloud.im.param.add.CreateRtcChannelParam;
import com.wecloud.im.param.rtc.CandidateForwardParam;
import com.wecloud.im.param.rtc.CreateRtcChannelParam;
import com.wecloud.im.param.rtc.CreateRtcChannelResult;
import com.wecloud.im.param.rtc.JoinRtcChannelParam;
import com.wecloud.im.param.rtc.LeaveRtcChannelParam;
import com.wecloud.im.param.rtc.RejectRtcChannelParam;
import com.wecloud.im.param.rtc.SdpForwardParam;
import com.wecloud.rtc.service.RtcService;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.common.controller.BaseController;
......@@ -35,37 +40,41 @@ public class ImRtcController extends BaseController {
@PostMapping("/createAndCall")
@ApiOperation(value = "创建频道,并邀请客户端加入", notes = "创建频道,并邀请客户端加入")
public ApiResult<CreateRtcChannelResult> createAndCall(@RequestBody CreateRtcChannelParam createRtcChannelParam) throws Exception {
CreateRtcChannelResult createRtcChannelResult = rtcService.create(createRtcChannelParam);
return ApiResult.ok(createRtcChannelResult);
return rtcService.createAndCall(createRtcChannelParam);
}
public ApiResult<Boolean> join() {
return ApiResult.result(true);
@PostMapping("/join")
@ApiOperation(value = "同意进入频道", notes = "")
public ApiResult<Boolean> join(@RequestBody JoinRtcChannelParam joinRtcChannelParam) {
return rtcService.join(joinRtcChannelParam);
}
public ApiResult<Boolean> reject() {
return ApiResult.result(true);
@PostMapping("/reject")
@ApiOperation(value = "拒接进入频道", notes = "")
public ApiResult<Boolean> reject(@RequestBody RejectRtcChannelParam rejectRtcChannelParam) {
return rtcService.reject(rejectRtcChannelParam);
}
public ApiResult<Boolean> leave() {
return ApiResult.result(true);
@PostMapping("/leave")
@ApiOperation(value = "主动挂断(离开频道)", notes = "")
public ApiResult<Boolean> leave(@RequestBody LeaveRtcChannelParam leaveRtcChannelParam) {
return rtcService.leave(leaveRtcChannelParam);
}
public ApiResult<Boolean> sdpForword() {
return ApiResult.result(true);
@PostMapping("/sdpForward")
@ApiOperation(value = "SDP数据转发", notes = "")
public ApiResult<Boolean> sdpForward(@RequestBody SdpForwardParam sdpForwardParam) {
return rtcService.sdpForward(sdpForwardParam);
}
public ApiResult<Boolean> candidateForword() {
return ApiResult.result(true);
@PostMapping("/candidateForward")
@ApiOperation(value = "candidate候选者数据转发", notes = "")
public ApiResult<Boolean> candidateForward(@RequestBody CandidateForwardParam candidateForwardParam) {
return rtcService.candidateForward(candidateForwardParam);
}
......
......@@ -32,7 +32,7 @@ public class SignController extends BaseController {
* 根据客户方生成签名字符串 验证通过则下发token
*/
@PostMapping("/get")
@ApiOperation(value = "获取sign(仅试使用)", notes = "生成签名测试,在生产环境中,此步骤需要第三方应用的服务端进行生成")
@ApiOperation(value = "获取sign(仅提供测试调试使用)", notes = "生成签名测试,在生产环境中,此步骤需要第三方应用的服务端进行生成")
public String get(@RequestBody GetSignParam getSignParam) throws Exception {
return new MD5().digestHex(getSignParam.getTimestamp() + getSignParam.getClientId() + getSignParam.getAppKey() + getSignParam.getAppSecret());
......
......@@ -44,7 +44,7 @@ public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {
* allIdleTime—状态为IdleState的IdleStateEvent。在一定时间内不读不写会触发ALL_IDLE。指定0禁用。
* unit—readerIdleTime、writeIdleTime和allIdleTime的时间单位
*/
pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
}
}
......@@ -4,12 +4,14 @@ import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.wecloud.im.ws.model.WsConstants;
import com.wecloud.im.ws.receive.ReadWsData;
import com.wecloud.im.ws.service.MangerChannelService;
import com.wecloud.rtc.service.RtcService;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
......@@ -35,6 +37,9 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
@Resource
private ReadWsData readWsData;
@Autowired
private RtcService rtcService;
@Resource
private MangerChannelService mangerChannelService;
......@@ -170,9 +175,16 @@ public class WsReadHandler extends SimpleChannelInboundHandler<TextWebSocketFram
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
String appKey = ctx.channel().attr(MangerChannelService.APP_KEY).get();
String clientId = ctx.channel().attr(MangerChannelService.CLIENT_ID).get();
String userIdByChannel = mangerChannelService.getInfoByChannel(ctx);
log.info("uid:" + userIdByChannel + "," + "handlerRemoved" + ",channelId:" + ctx.channel().id().asLongText());
// 关掉连接
ctx.close();
// rtc清空缓存
rtcService.clientOffline(appKey, clientId);
}
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "CandidateForwardParam")
public class CandidateForwardParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
/**
* 转发的候选者数据
*/
private String candidateData;
}
package com.wecloud.im.param.add;
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
......@@ -27,12 +27,9 @@ public class CreateRtcChannelParam implements Serializable {
private String type;
@ApiModelProperty(value = "绑定的会话id,可选", required = false)
private String toConversation;
private Long conversationId;
@ApiModelProperty(value = "接收方展示的系统推送内容,可", required = false, example = "{" +
" \"title\":\"xxx正在邀请你视频通话\"," +
" \"subTitle\":\"点击接听\"" +
" }")
@ApiModelProperty(value = "接收方展示的系统推送内容,可", required = false)
private String push;
@ApiModelProperty(value = "是否需要给对方发系统通知", required = true)
......
package com.wecloud.im.param;
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
......
package com.wecloud.im.param.add;
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
......@@ -7,13 +7,13 @@ import lombok.Data;
import java.io.Serializable;
/**
* 创建频道请求参数
* 加入频道请求参数
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "CreateRtcChannelParam")
@ApiModel(value = "JoinRtcChannelParam")
public class JoinRtcChannelParam implements Serializable {
private static final long serialVersionUID = 1L;
......
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 退出频道 请求
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "LeaveRtcChannelParam")
public class LeaveRtcChannelParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 拒接加入频道 请求
*
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "RejectRtcChannelParam")
public class RejectRtcChannelParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
}
package com.wecloud.im.param.rtc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author wei
* @since 2021-04-29
*/
@Data
@ApiModel(value = "SdpForwardParam")
public class SdpForwardParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("频道id")
private Long channelId;
@ApiModelProperty("sdp转发的数据")
private String sdpData;
@ApiModelProperty("sdp类型: Offer或Answer")
private String sdpType;
}
......@@ -16,7 +16,7 @@ import com.wecloud.im.service.ImConversationMembersService;
import com.wecloud.im.service.ImInboxService;
import com.wecloud.im.service.ImMessageService;
import com.wecloud.im.ws.enums.WsResponseCmdEnum;
import com.wecloud.im.ws.model.ResponseModel;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.service.WriteDataService;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
......@@ -205,7 +205,7 @@ public class ImInboxServiceImpl extends BaseServiceImpl<ImInboxMapper, ImInbox>
continue;
}
// 向接收方推送
ResponseModel<ImMessageOnlineSend> responseModel = new ResponseModel<>();
WsResponseModel<ImMessageOnlineSend> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.ONLINE_EVENT_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
......
......@@ -8,7 +8,7 @@ package com.wecloud.im.ws.enums;
public enum WsResponseCmdEnum {
/**
* 下发单人在线RTC事件
* 下发在线RTC事件
*/
SINGLE_RTC_MSG(4),
......@@ -34,20 +34,6 @@ public enum WsResponseCmdEnum {
this.cmdCode = uriCode;
}
/**
* 根据uriCode获取
*
* @param uriCode
* @return
*/
public static WsResponseCmdEnum getByCode(int uriCode) {
for (WsResponseCmdEnum wsResponsePathEnum : values()) {
if (wsResponsePathEnum.getCmdCode() == uriCode) {
return wsResponsePathEnum;
}
}
return null;
}
public int getCmdCode() {
return cmdCode;
......
package com.wecloud.im.ws.enums;
/**
* @Description webrtc响应类型
* @Author hewei hwei1233@163.com
* @Date 2019-12-05
*/
public enum WsRtcResponseSubCmdEnum {
// --- 服务端响应
/**
* 接收到RTC邀请
*/
RTC_CALL(1),
/**
* 用户状态更新事件(用户加入频道)
*/
CLIENT_JOIN(2),
/**
* 用户状态更新事件(用户退出频道)
*/
CLIENT_LEAVE(3),
/**
* 用户状态更新事件(用户拒接邀请,不同意进入频道)
*/
CLIENT_REJECT(4),
/**
* SDP数据转发
*/
SDP_FORWARD(5),
/**
* candidate候选者数据转发
*/
CANDIDATE_FORWARD(6);
private final int cmdCode;
WsRtcResponseSubCmdEnum(int cmdCode) {
this.cmdCode = cmdCode;
}
public int getCmdCode() {
return cmdCode;
}
}
......@@ -12,7 +12,7 @@ import java.io.Serializable;
*/
@Data
@Accessors(chain = true)
public class ResponseModel<T> implements Serializable {
public class WsResponseModel<T> implements Serializable {
/**
* 枚举类WsResponseCmdEnum 请求uri的编码
......
package com.wecloud.im.ws.service;
import com.wecloud.im.ws.model.ResponseModel;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.model.request.ReceiveModel;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
......@@ -47,7 +47,7 @@ public interface WriteDataService {
*
* @param responseModel
*/
void write(ResponseModel responseModel, String toAppKey, String toClientId);
void write(WsResponseModel responseModel, String toAppKey, String toClientId);
}
......@@ -310,22 +310,22 @@ public class MangerChannelServiceImpl implements MangerChannelService {
NioSocketChannel nioSocketChannel = get(toAppKey, toClientId);
if (null == nioSocketChannel) {
// userCache.offline(toAppKey + toClientId);
if (log.isDebugEnabled()) {
// if (log.isDebugEnabled()) {
log.info("writeData连接为空:" + toAppKey + toClientId + "," + msg);
}
// }
return false;
}
// 判断连接是否断开
if (nioSocketChannel.isShutdown()) {
if (log.isDebugEnabled()) {
// if (log.isDebugEnabled()) {
log.info("writeData连接断开:" + toAppKey + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText());
}
// }
return false;
}
if (log.isDebugEnabled()) {
// if (log.isDebugEnabled()) {
log.info("writeData:" + toAppKey + "," + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText());
}
// }
ChannelFuture channelFuture = nioSocketChannel.writeAndFlush(new TextWebSocketFrame(msg));
channelFuture.addListener(
......
......@@ -3,7 +3,7 @@ package com.wecloud.im.ws.service.impl;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.wecloud.im.ws.model.ResponseModel;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.model.WsConstants;
import com.wecloud.im.ws.model.request.ReceiveModel;
import com.wecloud.im.ws.service.MangerChannelService;
......@@ -62,7 +62,7 @@ public class WriteDataServiceImpl implements WriteDataService {
@Override
public void dataAndStatus(ReceiveModel receiveModel, ApiCode apiCode, Object data, String toAppKey, String toClientId) {
ApiResult<Boolean> apiResult = ApiResult.result(apiCode);
ResponseModel responseModel = new ResponseModel();
WsResponseModel responseModel = new WsResponseModel();
responseModel.setMsg(apiResult.getMessage());
responseModel.setCmd(receiveModel.getCmd());
responseModel.setReqId(receiveModel.getReqId());
......@@ -72,7 +72,7 @@ public class WriteDataServiceImpl implements WriteDataService {
}
@Override
public void write(ResponseModel responseModel, String toAppKey, String toClientId) {
public void write(WsResponseModel responseModel, String toAppKey, String toClientId) {
WRITE_TASK_THREAD_POOL_EXECUTOR.execute(
() -> {
......
......@@ -19,7 +19,7 @@ import com.wecloud.im.service.ImMessageService;
import com.wecloud.im.ws.annotation.CmdTypeAnnotation;
import com.wecloud.im.ws.enums.WsRequestCmdEnum;
import com.wecloud.im.ws.enums.WsResponseCmdEnum;
import com.wecloud.im.ws.model.ResponseModel;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.model.request.ReceiveModel;
import com.wecloud.im.ws.sender.SystemPush;
import com.wecloud.im.ws.service.WriteDataService;
......@@ -172,7 +172,7 @@ public class ImChatConcrete extends ImCmdAbstract {
}
// 向接收方推送
ResponseModel<ImMessageOnlineSend> responseModel = new ResponseModel<>();
WsResponseModel<ImMessageOnlineSend> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.ONLINE_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
......@@ -186,7 +186,7 @@ public class ImChatConcrete extends ImCmdAbstract {
}
// 响应发送方消息id等信息
ResponseModel<HashMap<String, Long>> responseModel = new ResponseModel<>();
WsResponseModel<HashMap<String, Long>> responseModel = new WsResponseModel<>();
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCmd(WsResponseCmdEnum.RES.getCmdCode());
responseModel.setCode(result.getCode());
......@@ -233,7 +233,7 @@ public class ImChatConcrete extends ImCmdAbstract {
log.info("被对方拉黑了");
// 响应发送方
ResponseModel<HashMap<String, Long>> responseModel = new ResponseModel<>();
WsResponseModel<HashMap<String, Long>> responseModel = new WsResponseModel<>();
ApiResult<Boolean> result = ApiResult.result(ApiCode.IS_BE_BLACK);
responseModel.setCmd(WsResponseCmdEnum.RES.getCmdCode());
responseModel.setCode(result.getCode());
......@@ -248,7 +248,7 @@ public class ImChatConcrete extends ImCmdAbstract {
if (black) {
log.info("你把对方拉黑了");
// 响应发送方
ResponseModel<HashMap<String, Long>> responseModel = new ResponseModel<>();
WsResponseModel<HashMap<String, Long>> responseModel = new WsResponseModel<>();
ApiResult<Boolean> result = ApiResult.result(ApiCode.IS_TO_BLACK);
responseModel.setCmd(WsResponseCmdEnum.RES.getCmdCode());
responseModel.setCode(result.getCode());
......
......@@ -14,7 +14,7 @@ import com.wecloud.im.ws.model.request.ReceiveModel;
import com.wecloud.im.ws.sender.SystemPush;
import com.wecloud.im.ws.service.WriteDataService;
import com.wecloud.im.ws.strategy.ImCmdAbstract;
import com.wecloud.rtc.SubCmd;
import com.wecloud.rtc.entity.RtcSubCmd;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -59,31 +59,31 @@ public class SingleRtcConcrete extends ImCmdAbstract {
public void process(ReceiveModel receiveModel, ChannelHandlerContext ctx, String data, String appKey, String clientId) throws JsonProcessingException {
// 指令判空
if (receiveModel.getData().get(SubCmd.SUB_CMD) == null) {
if (receiveModel.getData().get(RtcSubCmd.SUB_CMD) == null) {
return;
}
String cmd = receiveModel.getData().get(SubCmd.SUB_CMD).toString();
String cmd = receiveModel.getData().get(RtcSubCmd.SUB_CMD).toString();
switch (cmd) {
//创建频道
case SubCmd.CREATE:
break;
//加入频道
case SubCmd.JOIN:
break;
//拒绝加入频道
case SubCmd.REJECT:
break;
//SDP数据转发
case SubCmd.SDP:
break;
//主动挂断(离开频道)
case SubCmd.LEAVE:
break;
// //创建频道
// case RtcSubCmd.CREATE:
// break;
//
// //加入频道
// case RtcSubCmd.JOIN:
// break;
//
// //拒绝加入频道
// case RtcSubCmd.REJECT:
// break;
//
// //SDP数据转发
// case RtcSubCmd.SDP:
// break;
//
// //主动挂断(离开频道)
// case RtcSubCmd.LEAVE:
// break;
}
......
package com.wecloud.rtc;
import java.io.Serializable;
public class SubCmd implements Serializable {
/**
* subCmd子类型指令码
*/
public static final String SUB_CMD = "subCmd";
/**
* 创建频道
*/
public static final String CREATE = "create";
/**
* 加入频道
*/
public static final String JOIN = "join";
/**
* 拒绝加入频道
*/
public static final String REJECT = "reject";
/**
* 主动挂断(离开频道)
*/
public static final String LEAVE = "leave";
// --- 服务端响应
/**
* 接收到RTC邀请
*/
public static final String RTC_CALL = "rtcCall";
/**
* 用户状态更新事件(用户加入,用户退出,用户拒接邀请)
*/
public static final String CLIENT_EVENT = "clientEvent";
/**
* 流状态更新(切换音频 切换视频)
*/
public static final String TYPE_EVENT = "typeEvent";
/**
* 状态更新(网络断开,挂断)
*/
public static final String STATUS_EVENT = "statusEvent";
/**
* SDP数据转发
*/
public static final String SDP = "SDP";
/**
* 忙线
*/
public static final String BUSY = "busy";
}
package com.wecloud.rtc.entity;
import java.io.Serializable;
public class RtcSubCmd implements Serializable {
/**
* subCmd子类型指令码
*/
public static final String SUB_CMD = "subCmd";
}
package com.wecloud.rtc.entity;
package com.wecloud.rtc.entity.redis;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -12,8 +12,11 @@ import java.io.Serializable;
public class RtcChannelInfo implements Serializable {
@ApiModelProperty("当前房主")
String owner;
private String owner;
private String appKey;
// private String appId;
@ApiModelProperty("创建时间")
Long createTimestamp;
private Long createTimestamp;
}
package com.wecloud.rtc.entity;
package com.wecloud.rtc.entity.redis;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
......@@ -12,11 +12,13 @@ import java.io.Serializable;
public class RtcJoinUser implements Serializable {
@ApiModelProperty("客户端")
String clientId;
private String clientId;
@ApiModelProperty("加入时间")
Long createTimestamp;
private Long createTimestamp;
String sdp;
private String sdpData;
private String sdpType;
}
package com.wecloud.rtc;
package com.wecloud.rtc.entity.redis;
import java.io.Serializable;
public class RtcRedisKey implements Serializable {
/**
* 维护频道信息
* 维护频道信息 (kv)
*/
public static final String RTC_CHANNEL_INFO = "rci:%s";
public static final String RTC_CHANNEL_INFO = "r:ci:%s";
/**
* 维护所有用户当前在线的频道ID
* 维护用户当前在线的频道ID ( kv)
* user_join_channel = ujc
* rcu:clientA = 10001
* rcu:clientB = 10001
* rcu:clientC = 10002
* rcu:clientD = 10003
*/
public static final String USER_JOIN_CHANNEL = "ujc:%s";
public static final String USER_JOIN_CHANNEL = "r:ujc:%s";
/**
* 维护频道中存在的用户
* <p>
* redis Key (set 集合):
* 维护频道中存在的用户 (set 集合):
* rtc_channel_users = rcu
* rcu:10001 = clientA , clientB
* rcu:10002 = clientC
* rcu:10003 = clientD
*/
public static final String RTC_CHANNEL_USERS = "rcu:%s";
public static final String RTC_CHANNEL_USERS = "r:cu:%s";
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcCallResponse extends RtcSubDataBase implements Serializable {
private String type;
private Long conversationId;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcCandidateForwardResponse extends RtcSubDataBase implements Serializable {
/**
* 转发的候选者数据
*/
private String candidateData;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcClientJoinResponse extends RtcSubDataBase implements Serializable {
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcClientLeaveResponse extends RtcSubDataBase implements Serializable {
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcClientRejectResponse extends RtcSubDataBase implements Serializable {
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import java.io.Serializable;
/**
* webRtc websocket下发数据封装类
*
* @param <T>
*/
@Data
public class RtcResponseBase<T> implements Serializable {
/**
* 子指令
*/
private Integer subCmd;
/**
* 根据不同子指令 不同的实体
*/
private T subData;
/**
* 自定义拓展字段
*/
private String attrs;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class RtcSdpForwardResponse extends RtcSubDataBase implements Serializable {
/**
* channelId : 1234263457652
* clientId : 7657567
* sdpData : xxxxxxxxxxxxxxxx
* sdpType : Offer/Answer
*/
private String sdpData;
private String sdpType;
}
package com.wecloud.rtc.entity.response;
import lombok.Data;
import java.io.Serializable;
@Data
public class RtcSubDataBase implements Serializable {
private Long channelId;
private String clientId;
private Long timestamp;
}
package com.wecloud.rtc.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.wecloud.rtc.entity.redis.RtcChannelInfo;
import java.util.List;
......@@ -9,7 +10,32 @@ import java.util.List;
*/
public interface MangerRtcCacheService {
boolean isEmpty(String appKey, String clientId, Long rtcChannelId);
/**
* 移除频道信息
*
* @param rtcChannelId
* @return
*/
boolean delChannelInfo(Long rtcChannelId);
/**
* 频道中客户端是否为空
*
* @param rtcChannelId
* @return
*/
boolean channelIsEmpty(Long rtcChannelId);
/**
* 获取频道信息
*
* @param rtcChannelId
* @return
* @throws JsonProcessingException
*/
RtcChannelInfo getRtcChannelInfo(Long rtcChannelId) throws JsonProcessingException;
/**
* 创建一个频道
......@@ -28,17 +54,17 @@ public interface MangerRtcCacheService {
/**
* 退出频道
*/
void remove(String appKey, String clientId, Long rtcChannelId);
void leave(String appKey, String clientId, Long rtcChannelId);
/**
* 根据频道ID获取频道内所有client
*/
List<String> getClientListByRtcChannelId(Long rtcChannelId);
// /**
// * 根据客户端ID获取该客户端加入的频道ID
// */
// Long getRtcChannelIdListByClientId(String appKey, String clientId);
/**
* 根据客户端ID获取该客户端加入的频道ID
*/
Long getRtcChannelIdListByClientId(String appKey, String clientId);
/**
* 获取客户端忙线/空闲状态
......
package com.wecloud.rtc.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.wecloud.im.param.CreateRtcChannelResult;
import com.wecloud.im.param.add.CreateRtcChannelParam;
import com.wecloud.im.param.rtc.CandidateForwardParam;
import com.wecloud.im.param.rtc.CreateRtcChannelParam;
import com.wecloud.im.param.rtc.CreateRtcChannelResult;
import com.wecloud.im.param.rtc.JoinRtcChannelParam;
import com.wecloud.im.param.rtc.LeaveRtcChannelParam;
import com.wecloud.im.param.rtc.RejectRtcChannelParam;
import com.wecloud.im.param.rtc.SdpForwardParam;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
/**
* 管理rtc频道
*/
public interface RtcService {
/**
* 客户端离线
*/
void clientOffline(String appKey, String clientId);
/**
* 创建一个频道
* 创建一个频道,并向接收方发送系统推送
*/
CreateRtcChannelResult create(CreateRtcChannelParam createRtcChannelParam) throws JsonProcessingException;
ApiResult<CreateRtcChannelResult> createAndCall(CreateRtcChannelParam createRtcChannelParam) throws JsonProcessingException;
/**
* 加入频道
*/
void join();
ApiResult<Boolean> join(JoinRtcChannelParam joinRtcChannelParam);
/**
* 拒接加入频道
*/
void reject();
ApiResult<Boolean> reject(RejectRtcChannelParam rejectRtcChannelParam);
/**
* 退出频道
*/
void leave();
ApiResult<Boolean> leave(LeaveRtcChannelParam leaveRtcChannelParam);
void sdpForword();
/**
* SDP数据转发
*
* @param sdpForwardParam
* @return
*/
ApiResult<Boolean> sdpForward(SdpForwardParam sdpForwardParam);
void candidateForword();
/**
* candidate候选者数据转发
*
* @param candidateForwardParam
* @return
*/
ApiResult<Boolean> candidateForward(CandidateForwardParam candidateForwardParam);
}
package com.wecloud.rtc.service;
import com.wecloud.rtc.entity.response.RtcCallResponse;
import com.wecloud.rtc.entity.response.RtcCandidateForwardResponse;
import com.wecloud.rtc.entity.response.RtcClientJoinResponse;
import com.wecloud.rtc.entity.response.RtcClientLeaveResponse;
import com.wecloud.rtc.entity.response.RtcClientRejectResponse;
import com.wecloud.rtc.entity.response.RtcSdpForwardResponse;
/**
* WebRtc webSocket下发指令数据
*/
public interface WsRtcWrite {
/**
* 接收到RTC邀请
*/
void rtcCall(RtcCallResponse rtcCallResponse, String toAppKey, String toClientId);
/**
* 用户状态更新事件(用户加入频道)
*/
void clientJoin(RtcClientJoinResponse rtcClientJoinResponse, String toAppKey, String toClientId);
/**
* 用户状态更新事件(用户退出频道)
*/
void clientLeave(RtcClientLeaveResponse rtcClientLeaveResponse, String toAppKey, String toClientId);
/**
* 用户状态更新事件(用户拒接邀请;不同意进入频道)
*/
void clientReject(RtcClientRejectResponse rtcClientRejectResponse, String toAppKey, String toClientId);
/**
* SDP数据转发
*/
void sdpForward(RtcSdpForwardResponse rtcSdpForwardResponse, String toAppKey, String toClientId);
/**
* candidate候选者数据转发
*/
void candidateForward(RtcCandidateForwardResponse rtcCandidateForwardResponse, String toAppKey, String toClientId);
}
......@@ -3,9 +3,10 @@ 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.entity.redis.RtcChannelInfo;
import com.wecloud.rtc.entity.redis.RtcRedisKey;
import com.wecloud.rtc.service.MangerRtcCacheService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -22,29 +23,54 @@ public class MangerRtcCacheServiceImpl implements MangerRtcCacheService {
@Override
public boolean isEmpty(String appKey, String clientId, Long rtcChannelId) {
public boolean delChannelInfo(Long rtcChannelId) {
String channelKey = String.format(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelId);
redisUtils.delKey(channelKey);
return true;
}
@Override
public boolean channelIsEmpty(Long rtcChannelId) {
List<String> clientListByRtcChannelId = getClientListByRtcChannelId(rtcChannelId);
// 移除自己
clientListByRtcChannelId.remove(appKey + clientId);
// // 移除自己
// clientListByRtcChannelId.remove(appKey + clientId);
return clientListByRtcChannelId.isEmpty();
}
@Override
public RtcChannelInfo getRtcChannelInfo(Long rtcChannelId) throws JsonProcessingException {
// 频道信息
String channelKey = String.format(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelId);
String value = redisUtils.getKey(channelKey);
if (StringUtils.isBlank(value)) {
return null;
}
JsonMapper jsonMapper = new JsonMapper();
return jsonMapper.readValue(value, RtcChannelInfo.class);
}
@Override
public void create(String appKey, String clientId, Long rtcChannelId) throws JsonProcessingException {
// --- 频道信息
RtcChannelInfo rtcChannelInfo = new RtcChannelInfo();
rtcChannelInfo.setAppKey(appKey);
// rtcChannelInfo.setAppId("");
//当前房主
rtcChannelInfo.setOwner(appKey + clientId);
rtcChannelInfo.setOwner(clientId);
//创建时间
rtcChannelInfo.setCreateTimestamp(new Date().getTime());
String rtcChannelInfoJson = new JsonMapper().writeValueAsString(rtcChannelInfo);
// --- 保存频道信息
redisUtils.setKey(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelInfoJson);
String channelKey = String.format(RtcRedisKey.RTC_CHANNEL_INFO, rtcChannelId);
redisUtils.setKey(channelKey, rtcChannelInfoJson);
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
......@@ -52,7 +78,7 @@ public class MangerRtcCacheServiceImpl implements MangerRtcCacheService {
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.addForSet(rtcChannelUsers, appKey + clientId);
redisUtils.addForSet(rtcChannelUsers, clientId);
}
......@@ -65,12 +91,12 @@ public class MangerRtcCacheServiceImpl implements MangerRtcCacheService {
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.addForSet(rtcChannelUsers, appKey + clientId);
redisUtils.addForSet(rtcChannelUsers, clientId);
}
@Override
public void remove(String appKey, String clientId, Long rtcChannelId) {
public void leave(String appKey, String clientId, Long rtcChannelId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
......@@ -78,7 +104,7 @@ public class MangerRtcCacheServiceImpl implements MangerRtcCacheService {
//频道中存在的用户
String rtcChannelUsers = String.format(RtcRedisKey.RTC_CHANNEL_USERS, rtcChannelId);
redisUtils.removeForSet(rtcChannelUsers, appKey + clientId);
redisUtils.removeForSet(rtcChannelUsers, clientId);
}
@Override
......@@ -94,15 +120,17 @@ public class MangerRtcCacheServiceImpl implements MangerRtcCacheService {
}
// @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 Long getRtcChannelIdListByClientId(String appKey, String clientId) {
//用户当前在线的频道ID
String userJoinChannelKey = String.format(RtcRedisKey.USER_JOIN_CHANNEL, appKey + clientId);
String key = redisUtils.getKey(userJoinChannelKey);
if (StringUtils.isBlank(key)) {
return null;
}
return Long.valueOf(key);
}
@Override
public boolean getBusyStatus(String appKey, String clientId) {
......
package com.wecloud.rtc.service.impl;
import com.wecloud.im.ws.enums.WsResponseCmdEnum;
import com.wecloud.im.ws.enums.WsRtcResponseSubCmdEnum;
import com.wecloud.im.ws.model.WsResponseModel;
import com.wecloud.im.ws.service.WriteDataService;
import com.wecloud.rtc.entity.response.RtcCallResponse;
import com.wecloud.rtc.entity.response.RtcCandidateForwardResponse;
import com.wecloud.rtc.entity.response.RtcClientJoinResponse;
import com.wecloud.rtc.entity.response.RtcClientLeaveResponse;
import com.wecloud.rtc.entity.response.RtcClientRejectResponse;
import com.wecloud.rtc.entity.response.RtcResponseBase;
import com.wecloud.rtc.entity.response.RtcSdpForwardResponse;
import com.wecloud.rtc.service.WsRtcWrite;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class WsRtcWriteImpl implements WsRtcWrite {
// private static final JsonMapper JSON_MAPPER = new JsonMapper();
@Autowired
private WriteDataService writeDataService;
@Override
public void rtcCall(RtcCallResponse rtcCallResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcCallResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.RTC_CALL.getCmdCode());
rtcResponseBase.setSubData(rtcCallResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcCallResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void clientJoin(RtcClientJoinResponse rtcClientJoinResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcClientJoinResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CLIENT_JOIN.getCmdCode());
rtcResponseBase.setSubData(rtcClientJoinResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcClientJoinResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void clientLeave(RtcClientLeaveResponse rtcClientLeaveResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcClientLeaveResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CLIENT_LEAVE.getCmdCode());
rtcResponseBase.setSubData(rtcClientLeaveResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcClientLeaveResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void clientReject(RtcClientRejectResponse rtcClientRejectResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcClientRejectResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CLIENT_REJECT.getCmdCode());
rtcResponseBase.setSubData(rtcClientRejectResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcClientRejectResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void sdpForward(RtcSdpForwardResponse rtcSdpForwardResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcSdpForwardResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.SDP_FORWARD.getCmdCode());
rtcResponseBase.setSubData(rtcSdpForwardResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcSdpForwardResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
@Override
public void candidateForward(RtcCandidateForwardResponse rtcCandidateForwardResponse, String toAppKey, String toClientId) {
RtcResponseBase<RtcCandidateForwardResponse> rtcResponseBase = new RtcResponseBase<>();
rtcResponseBase.setSubCmd(WsRtcResponseSubCmdEnum.CANDIDATE_FORWARD.getCmdCode());
rtcResponseBase.setSubData(rtcCandidateForwardResponse);
// rtcResponseBase.setAttrs(rtcCallResponse.get);
// 向接收方推送
WsResponseModel<RtcResponseBase<RtcCandidateForwardResponse>> responseModel = new WsResponseModel<>();
responseModel.setCmd(WsResponseCmdEnum.SINGLE_RTC_MSG.getCmdCode());
ApiResult<Boolean> result = ApiResult.result(ApiCode.SUCCESS);
responseModel.setCode(result.getCode());
responseModel.setMsg(result.getMessage());
responseModel.setData(rtcResponseBase);
responseModel.setReqId(null);
writeDataService.write(responseModel, toAppKey, toClientId);
}
}
......@@ -14,22 +14,41 @@ spring-boot-plus:
response-log-format: false
#spring:
# datasource:
# url: jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
# username: web
# password: axT8knPN5hAP
#
# # Redis配置
# redis:
# database: 0
# host: 127.0.0.1
# password: JH86uc53r8Ca
# port: 6379
# cloud:
# nacos:
# discovery:
# server-addr: localhost:8848
# 国内IM测试外网
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: web
password: axT8knPN5hAP
username: root
password: temple123456
# Redis配置
redis:
database: 0
host: 127.0.0.1
password: JH86uc53r8Ca
password: temple123456
port: 6379
cloud:
nacos:
discovery:
server-addr: localhost:8848
# 打印SQL语句和结果集,本地开发环境可开启,线上注释掉
mybatis-plus:
configuration:
......
......@@ -8,4 +8,4 @@ ps aux|grep bootstrap-2.0|awk '{print $2}'|xargs kill -9
# 启动新jar,
nohup java -jar bootstrap-2.0.jar &>/dev/null &
echo "success"
echo "run success"
#! /bin/shell
# 国内IM集成版测试环境
# sudo 超级权限启动, 否则无法创建log日志文件夹报错
# 停止服务
ps aux|grep bootstrap-2.0|awk '{print $2}'|xargs kill -9
#/data0/java_projects_jenkins/
# 启动新jar,
#nohup java -jar bootstrap-2.0.jar &>/dev/null &
nohup java -jar \
-Dspring.datasource.url="jdbc:mysql://127.0.0.1:3306/wecloud_im?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true" \
-Dspring.datasource.username=root \
-Dspring.datasource.password=temple123456 \
-Dspring.redis.database=0 \
-Dspring.redis.host=127.0.0.1 \
-Dspring.redis.password=temple123456 \
-Dspring.redis.port=6379 \
bootstrap-2.0-test.jar &>/dev/null &
echo "run success"
# wecloud-RTC音视频客户端信令对接文档
# wecloud-RTC音视频客户端信令对接文档
......@@ -19,42 +19,54 @@
4. 一个频道可以由通话发起者绑定到会话ID,"挂断","未接听"等状态会同步到会话, 未绑定将不同步到会话(可选)
5. 连接websocket时带上client类型, 如web,安卓,ios
client需要监听频道内 状态更新(房间断开,挂断)、用户状态更新(用户加入, 用户退出)、流状态更新(切换音频 切换视频)
6.如果接收方未在线时收到音视频通话邀请,在规定时间内发起方还在等待中,发起方重新连接可以收到音视频通话的离线信令
```
## 流程图
### 不绑定到会话 示例一
![双人WebRTC流程图-不绑定会话](https://tva1.sinaimg.cn/large/008i3skNly1gvftfayr8ij60u016wtcx02.jpg)
![image-20211018180059906](https://tva1.sinaimg.cn/large/008i3skNly1gvjmbmw0wcj60u016877z02.jpg)
## 指令码说明
## subCmd指令码说明
#### **subCmd**子类型指令码
##### 客户端**请求**指令列表:
**create**:创建频道
**join**: 加入频道
**SDP**:SDP数据转发
**reject**:拒绝加入频道
**leave**:主动挂断(离开频道)
枚举类:
```
/**
* 接收到RTC邀请
*/
RTC_CALL(1),
##### 服务端**响应**指令列表:
/**
* 用户状态更新事件(用户加入频道)
*/
CLIENT_JOIN(2),
**rtcCall**:接收到RTC邀请
**clientEvent** : 用户状态更新事件(用户加入,用户退出,用户拒接邀请)
**typeEvent**:流状态更新(切换音频 切换视频)
**statusEvent**:状态更新(网络断开,挂断)
**SDP**:SDP数据转发
**busy**:忙线
/**
* 用户状态更新事件(用户退出频道)
*/
CLIENT_LEAVE(3),
/**
* 用户状态更新事件(用户拒接邀请,不同意进入频道)
*/
CLIENT_REJECT(4),
/**
* SDP数据转发
*/
SDP_FORWARD(5),
/**
* candidate候选者数据转发
*/
CANDIDATE_FORWARD(6);
```
## 创建频道 发起RTC音视频通话 (http)
......@@ -70,14 +82,14 @@ client需要监听频道内 状态更新(房间断开,挂断)、用户状态更
{
"cmd":4,
"data":{
"subCmd":"rtcCall",
"subCmd":"1",
"subData":{
"type":"video",
"toConversation":null,
"conversationId":null,
"channelId":1234263457652,
"sender":"client_1010"
"timestamp":113123123,
"clientId":"client_1010"
},
"diyParam自定义字段":"aaaa自已定义字段的值",
"attrs":{
"a":"示例: 用户自定义的一些键值对",
"b":"存储用户自定义的一些键值对"
......@@ -92,12 +104,12 @@ client需要监听频道内 状态更新(房间断开,挂断)、用户状态更
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| toConversation | Long | 否 | 绑定的会话id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| conversationId | Long | 否 | 绑定的会话id |
| subCmd | String | 否 | 子类型指令 |
| sender | String | 否 | 发起通话的客户端ID |
| clientId | String | 否 | 发起通话的客户端ID |
| channelId | Long | 否 | 由服务端创建的频道id |
| type | String | 否 | 类型: "video" 或 "voice" |
| timestamp | Timestamp | 否 | 频道创建时间戳 |
## 同意加入频道(http)
......@@ -112,10 +124,9 @@ client需要监听频道内 状态更新(房间断开,挂断)、用户状态更
{
"cmd":4,
"data":{
"subCmd":"clientEvent",
"subCmd":"2",
"subData":{
"channelId":1234263457652,
"type":"join",
"clientId":7657567,
}
}
......@@ -124,32 +135,8 @@ client需要监听频道内 状态更新(房间断开,挂断)、用户状态更
## 拒绝加入频道 http
client接收方向服务端请求数据:
```json
{
"reqId":"123",
"cmd":3,
"data":{
"subCmd":"reject",
"diyParam自定义字段":"aaaa自已定义字段的值",
"channelId":1234263457652,
"attrs":{}
}
}
```
**说明:**
**参数描述**
见http接口文档
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| toConversation | Long | 否 | 会话id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子类型 |
## 有Client拒绝加入频道(ws下发)
......@@ -159,17 +146,18 @@ client接收方向服务端请求数据:
{
"cmd":4,
"data":{
"subCmd":"clientEvent",
"subCmd":4,
"subData":{
"channelId":1234263457652,
"type":"reject",
"clientId":7657567,
}
}
}
```
## 流媒体描述信息SDP转发
## SDP转发
流媒体描述信息
(服务端仅负责转发)(candidate,anser,offer)
......@@ -183,52 +171,89 @@ client接收方向服务端请求数据:
{
"cmd":4,
"data":{
"subCmd":"sdp",
"subCmd":5,
"subData":{
"channelId":1234263457652,
"clientId":7657567,
"sdpData":"xxxxxxxxxxxxxxxx",
"diyParam自定义字段":"aaaa自已定义字段的值",
"sdpType":"Offer/Answer"
},
"attrs":{}
}
}
}
```
## 主动挂断(离开频道) http
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| channelId | Long | 否 | 频道id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子指令 |
| sdpData | String | 否 | sdp转发的数据 |
| sdpType | String | 否 | sdp类型: Offer或Answer |
client主动离开频道
见http接口文档
## 有client离开频道(ws下发)
## Candidate转发
服务端向频道内其他client响应数据:
候选人信息
(服务端仅负责转发)(candidate,anser,offer)
### client上传Candidate (http接口)
见http接口文档
### client接收Candidate (ws下发)
```json
{
"cmd":4,
"data":{
"subCmd":"clientEvent",
"subCmd":6,
"subData":{
"channelId":1234263457652,
"type":"leave",
"clientId":7657567,
}
"candidateData":"xxxxxxxxxxxxxxxx",
},
"attrs":{}
}
}
```
## 查询忙线状态(对方正在通话中) http
**参数描述**
| 字段名 | 字段类型 | 是否可空 | 说明 |
| ---- | -------- | -- | ------ |
| cmd | String | 否 | 指令码 |
| attrs | Object | 是 | 自定义拓展字段 |
| channelId | Long | 否 | 频道id |
| diyParam自定义字段 | Object | 是 | 自定义拓展字段 |
| subCmd | String | 否 | 子指令 |
| candidateData | String | 否 | 转发的候选者数据 |
服务端响应给发起方
## 主动挂断(离开频道) http
见http接口文档
## 有client离开频道(ws下发)
服务端向频道内其他client响应数据:
```json
{
"reqId":"555111-ad-afd12",
"cmd":5,
"cmd":4,
"data":{
"subCmd":"busy",
"subCmd":3,
"subData":{
"channelId":1234263457652,
"clientId":7657567
......@@ -237,6 +262,11 @@ client主动离开频道
}
```
## 查询忙线状态(对方正在通话中) http
见http接口文档
## 断线重连
重新join进频道即可重连
......@@ -247,7 +277,7 @@ client主动离开频道
## 视频/音频切换
## 查询对方是否离开
## 查询对方是否离开频道
# wecloud-im 视频通话云对接文档
# wecloud-im 视频通话云对接文档
[TOC]
## 产品概述
集成视频 SDK,实现高清流畅视频通话。
视频通话 SDK 可实现一对一视频通话,同时具备纯语音通话和视频通话功能。
**即将推出多对多视频通话**
## 使用场景
##### 视频聊天
支持 1 对 1 视频通话,适用于视频聊天、视频客服、远程医疗、金融双录、远程定损等场景
##### 在线教育
视频面对面教学,真实还原线下教学场景,支持 1v1 教学
##### 视频客服
支持 1v1 专属 VIP 视频客服,助力用户服务升级,提供更优质的服务体验
**即将推出多对多视频通话**
## 文档描述
此文档为一对一音视频通话技术对接文档
由于视频通话基于wecloud-im聊天服务,**对接视频通话前,需要先对接wecloud-im聊天服务**
## 核心概念说明
### 频道
两个client加入到同一个"频道"进行音视频通话
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