Browse Source

feat:多人视频-开发中

master
xsx 2 years ago
parent
commit
c26c478afb
  1. 7
      im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java
  2. 13
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java
  3. 19
      im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java
  4. 102
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcGroupServiceImpl.java
  5. 44
      im-platform/src/main/java/com/bx/implatform/util/UserStateUtils.java
  6. 28
      im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupInfoVO.java
  7. 12
      im-uniapp/pages/chat/chat-box.vue
  8. 2
      im-uniapp/pages/chat/chat-group-video.vue

7
im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java

@ -2,9 +2,10 @@ package com.bx.implatform.contant;
public final class RedisKey {
private RedisKey() {
}
/**
* 用户状态 无值:空闲 1:正在忙
*/
public static final String IM_USER_STATE = "im:user:state";
/**
* 已读群聊消息位置(已读最大id)
*/

13
im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java

@ -4,6 +4,7 @@ import com.bx.implatform.dto.*;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IWebrtcGroupService;
import com.bx.implatform.vo.WebrtcGroupInfoVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
@ -108,4 +109,16 @@ public class WebrtcGroupController {
return ResultUtils.success();
}
@ApiOperation(httpMethod = "GET", value = "获取通话信息")
@GetMapping("/info")
public Result<WebrtcGroupInfoVO> info(@RequestParam Long groupId) {
return ResultUtils.success(webrtcGroupService.info(groupId));
}
@ApiOperation(httpMethod = "POST", value = "获取通话信息")
@PostMapping("/heartbeat")
public Result heartbeat(@RequestParam Long groupId) {
webrtcGroupService.heartbeat(groupId);
return ResultUtils.success();
}
}

19
im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java

@ -1,36 +1,32 @@
package com.bx.implatform.service;
import com.bx.implatform.dto.*;
import com.bx.implatform.vo.WebrtcGroupInfoVO;
public interface IWebrtcGroupService {
/**
* 发起通话
* @param dto
*/
void setup(WebrtcGroupSetupDTO dto);
/**
* 接受通话
* @groupId 群id
*/
void accept(Long groupId);
/**
* 拒绝通话
* @groupId 群id
*/
void reject(Long groupId);
/**
* 通话失败,如设备不支持用户忙等(此接口为系统自动调用,无需用户操作所以不抛异常)
* @dto dto
*/
void failed(WebrtcGroupFailedDTO dto);
/**
* 主动加入通话
* @groupId 群id
*/
void join(Long groupId);
@ -51,29 +47,32 @@ public interface IWebrtcGroupService {
/**
* 推送offer信息给对方
* @dto dto
*/
void offer(WebrtcGroupOfferDTO dto);
/**
* 推送answer信息给对方
* @dto dto
*/
void answer(WebrtcGroupAnswerDTO dto);
/**
* 推送candidate信息给对方
* @dto dto
*/
void candidate(WebrtcGroupCandidateDTO dto);
/**
* 用户进行了设备操作如果关闭摄像头
* @dto dto
*/
void device(WebrtcGroupDeviceDTO dto);
/**
* 查询通话信息
*/
WebrtcGroupInfoVO info(Long groupId);
/**
* 心跳保持, 用户每15s上传一次心跳
*/
void heartbeat(Long groupId);
}

102
im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcGroupServiceImpl.java

@ -17,14 +17,17 @@ import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.session.WebrtcGroupSession;
import com.bx.implatform.session.WebrtcUserInfo;
import com.bx.implatform.util.UserStateUtils;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.WebrtcGroupFailedVO;
import com.bx.implatform.vo.WebrtcGroupInfoVO;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.lang.reflect.Member;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -44,6 +47,7 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
private final IGroupMemberService groupMemberService;
private final RedisTemplate<String, Object> redisTemplate;
private final IMClient imClient;
private final UserStateUtils userStateUtils;
/**
* 最多支持8路视频
*/
@ -61,14 +65,22 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
if (!groupMemberService.isInGroup(dto.getGroupId(), userIds)) {
throw new GlobalException("存在不在群聊中的用户");
}
// 离线用户处理
// 有效用户
List<WebrtcUserInfo> userInfos = new LinkedList<>();
// 离线用户
List<Long> offlineUserIds = new LinkedList<>();
// 忙线用户
List<Long> busyUserIds = new LinkedList<>();
for (WebrtcUserInfo userInfo : dto.getUserInfos()) {
if (imClient.isOnline(userInfo.getId())) {
userInfos.add(userInfo);
} else {
if (!imClient.isOnline(userInfo.getId())) {
//userInfos.add(userInfo);
offlineUserIds.add(userInfo.getId());
} else if (userStateUtils.isBusy(userInfo.getId())) {
busyUserIds.add(userInfo.getId());
} else {
userInfos.add(userInfo);
// 设置用户忙线状态
userStateUtils.setBusy(userInfo.getId());
}
}
// 创建通话session
@ -85,6 +97,13 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
vo.setReason("用户不在线");
sendMessage2(MessageType.RTC_GROUP_FAILED, dto.getGroupId(), userInfo, JSON.toJSONString(vo));
}
if (!busyUserIds.isEmpty()) {
WebrtcGroupFailedVO vo = new WebrtcGroupFailedVO();
vo.setUserIds(busyUserIds);
vo.setReason("用户正忙");
IMUserInfo reciver = new IMUserInfo(userSession.getUserId(), userSession.getTerminal());
sendMessage2(MessageType.RTC_GROUP_FAILED, dto.getGroupId(), reciver, JSON.toJSONString(vo));
}
// 向被邀请的用户广播消息,发起呼叫
List<Long> recvIds = getRecvIds(dto.getUserInfos());
sendMessage1(MessageType.RTC_GROUP_SETUP, dto.getGroupId(), recvIds, JSON.toJSONString(userInfos));
@ -132,6 +151,8 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
.collect(Collectors.toList());
webrtcSession.setUserInfos(userInfos);
saveWebrtcSession(groupId, webrtcSession);
// 进入空闲状态
userStateUtils.setFree(userSession.getUserId());
// 广播消息给的所有用户
List<Long> recvIds = getRecvIds(userInfos);
sendMessage1(MessageType.RTC_GROUP_REJECT, groupId, recvIds, "");
@ -156,6 +177,8 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
.collect(Collectors.toList());
webrtcSession.setUserInfos(userInfos);
saveWebrtcSession(dto.getGroupId(), webrtcSession);
// 进入空闲状态
userStateUtils.setFree(userSession.getUserId());
// 广播信令
WebrtcGroupFailedVO vo = new WebrtcGroupFailedVO();
vo.setUserIds(Arrays.asList(userSession.getUserId()));
@ -172,7 +195,7 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
WebrtcGroupSession webrtcSession = getWebrtcSession(groupId);
// 校验
GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userSession.getUserId());
if (Objects.isNull(member)) {
if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException("您不在群里中");
}
// 防止重复进入
@ -190,6 +213,8 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
}
webrtcSession.getInChatUsers().add(new IMUserInfo(userSession.getUserId(), userSession.getTerminal()));
saveWebrtcSession(groupId, webrtcSession);
// 进入忙线状态
userStateUtils.setBusy(userSession.getUserId());
// 广播信令
List<Long> recvIds = getRecvIds(webrtcSession.getUserInfos());
sendMessage1(MessageType.RTC_GROUP_JOIN, groupId, recvIds, JSON.toJSONString(userInfo));
@ -207,6 +232,8 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
List<Long> userIds = getRecvIds(userInfos);
// 离线用户id
List<Long> offlineUserIds = new LinkedList<>();
// 忙线用户
List<Long> busyUserIds = new LinkedList<>();
// 新加入的用户
List<WebrtcUserInfo> newUserInfos = new LinkedList<>();
for (WebrtcUserInfo userInfo : dto.getUserInfos()) {
@ -214,10 +241,14 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
// 防止重复进入
continue;
}
if (imClient.isOnline(userInfo.getId())) {
newUserInfos.add(userInfo);
} else {
if (!imClient.isOnline(userInfo.getId())) {
offlineUserIds.add(userInfo.getId());
} else if (userStateUtils.isBusy(userInfo.getId())) {
busyUserIds.add(userInfo.getId());
} else {
// 进入忙线状态
userStateUtils.setBusy(userInfo.getId());
newUserInfos.add(userInfo);
}
}
// 更新会话信息
@ -231,6 +262,13 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
IMUserInfo reciver = new IMUserInfo(userSession.getUserId(), userSession.getTerminal());
sendMessage2(MessageType.RTC_GROUP_FAILED, dto.getGroupId(), reciver, JSON.toJSONString(vo));
}
if (!busyUserIds.isEmpty()) {
WebrtcGroupFailedVO vo = new WebrtcGroupFailedVO();
vo.setUserIds(busyUserIds);
vo.setReason("用户正在忙");
IMUserInfo reciver = new IMUserInfo(userSession.getUserId(), userSession.getTerminal());
sendMessage2(MessageType.RTC_GROUP_FAILED, dto.getGroupId(), reciver, JSON.toJSONString(vo));
}
// 向被邀请的发起呼叫
List<Long> newUserIds = getRecvIds(newUserInfos);
sendMessage1(MessageType.RTC_GROUP_SETUP, dto.getGroupId(), newUserIds, JSON.toJSONString(userInfos));
@ -251,6 +289,8 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
// 移除rtc session
String key = buildWebrtcSessionKey(groupId);
redisTemplate.delete(key);
// 进入空闲状态
webrtcSession.getUserInfos().forEach(user -> userStateUtils.setFree(user.getId()));
// 广播消息给的所有用户
List<Long> recvIds = getRecvIds(webrtcSession.getUserInfos());
sendMessage1(MessageType.RTC_GROUP_CANCEL, groupId, recvIds, "");
@ -269,11 +309,14 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
List<WebrtcUserInfo> userInfos =
webrtcSession.getUserInfos().stream().filter(user -> !user.getId().equals(userSession.getUserId()))
.collect(Collectors.toList());
// 如果群聊中没有人已经接受了通话,则直接取消整个通话
if (inChatUsers.isEmpty() || userInfos.isEmpty()) {
// 移除rtc session
String key = buildWebrtcSessionKey(groupId);
redisTemplate.delete(key);
// 进入空闲状态
webrtcSession.getUserInfos().forEach(user -> userStateUtils.setFree(user.getId()));
// 广播给还在呼叫中的用户,取消通话
List<Long> recvIds = getRecvIds(webrtcSession.getUserInfos());
sendMessage1(MessageType.RTC_GROUP_CANCEL, groupId, recvIds, "");
@ -283,6 +326,8 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
webrtcSession.setInChatUsers(inChatUsers);
webrtcSession.setUserInfos(userInfos);
saveWebrtcSession(groupId, webrtcSession);
// 进入空闲状态
userStateUtils.setFree(userSession.getUserId());
// 广播信令
List<Long> recvIds = getRecvIds(userInfos);
sendMessage1(MessageType.RTC_GROUP_QUIT, groupId, recvIds, "");
@ -359,6 +404,44 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
dto.getIsCamera());
}
@Override
public WebrtcGroupInfoVO info(Long groupId) {
WebrtcGroupInfoVO vo = new WebrtcGroupInfoVO();
String key = buildWebrtcSessionKey(groupId);
WebrtcGroupSession webrtcSession = (WebrtcGroupSession)redisTemplate.opsForValue().get(key);
if (Objects.isNull(webrtcSession)) {
// 群聊当前没有通话
vo.setIsChating(false);
} else {
// 群聊正在通话中
vo.setIsChating(true);
vo.setUserInfos(webrtcSession.getUserInfos());
Long hostId = webrtcSession.getHost().getId();
WebrtcUserInfo host = findUserInfo(webrtcSession,hostId);
if (Objects.isNull(host)) {
// 如果发起人已经退出了通话,则从数据库查询发起人数据
GroupMember member = groupMemberService.findByGroupAndUserId(groupId,hostId);
host = new WebrtcUserInfo();
host.setId(hostId);
host.setNickName(member.getAliasName());
host.setHeadImage(member.getHeadImage());
host.setIsCamera(false);
}
vo.setHost(host);
}
return vo;
}
@Override
public void heartbeat(Long groupId) {
UserSession userSession = SessionContext.getSession();
// 给通话session续命
String key = buildWebrtcSessionKey(groupId);
redisTemplate.expire(key,30,TimeUnit.SECONDS);
// 用户忙线状态续命
userStateUtils.expire(userSession.getUserId());
}
private WebrtcGroupSession getWebrtcSession(Long groupId) {
String key = buildWebrtcSessionKey(groupId);
WebrtcGroupSession webrtcSession = (WebrtcGroupSession)redisTemplate.opsForValue().get(key);
@ -370,7 +453,7 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
private void saveWebrtcSession(Long groupId, WebrtcGroupSession webrtcSession) {
String key = buildWebrtcSessionKey(groupId);
redisTemplate.opsForValue().set(key, webrtcSession, 2, TimeUnit.HOURS);
redisTemplate.opsForValue().set(key, webrtcSession, 30, TimeUnit.SECONDS);
}
private String buildWebrtcSessionKey(Long groupId) {
@ -407,7 +490,6 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService {
private Boolean isExist(WebrtcGroupSession webrtcSession, Long userId) {
return webrtcSession.getUserInfos().stream().anyMatch(user -> user.getId().equals(userId));
}
private void sendMessage1(MessageType messageType, Long groupId, List<Long> recvIds, String content) {

44
im-platform/src/main/java/com/bx/implatform/util/UserStateUtils.java

@ -0,0 +1,44 @@
package com.bx.implatform.util;
import cn.hutool.core.util.StrUtil;
import com.bx.implatform.contant.RedisKey;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author: 谢绍许
* @date: 2024-06-10
* @version: 1.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class UserStateUtils {
private final RedisTemplate<String, Object> redisTemplate;
public void setBusy(Long userId){
String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId);
redisTemplate.opsForValue().set(key,1,30, TimeUnit.SECONDS);
}
public void expire(Long userId){
String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId);
redisTemplate.expire(key,30, TimeUnit.SECONDS);
}
public void setFree(Long userId){
String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId);
redisTemplate.delete(key);
}
public Boolean isBusy(Long userId){
String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId);
return redisTemplate.hasKey(key);
}
}

28
im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupInfoVO.java

@ -0,0 +1,28 @@
package com.bx.implatform.vo;
import com.bx.implatform.session.WebrtcUserInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author: 谢绍许
* @date: 2024-06-09
* @version: 1.0
*/
@Data
@ApiModel("群通话信息VO")
public class WebrtcGroupInfoVO {
@ApiModelProperty(value = "是否在通话中")
private Boolean isChating;
@ApiModelProperty(value = "通话发起人")
WebrtcUserInfo host;
@ApiModelProperty(value = "通话用户列表")
private List<WebrtcUserInfo> userInfos;
}

12
im-uniapp/pages/chat/chat-box.vue

@ -110,6 +110,7 @@
<!-- 群语音通话时选择成员 -->
<group-member-selector ref="selBox" :members="groupMembers"
@complete="onSelectMember"></group-member-selector>
<group-rtc-join ref="rtcJoin" :groupId="group.id"></group-rtc-join>
</view>
</template>
@ -191,9 +192,20 @@
})
},
onGroupVideo() {
this.$http({
url: "/webrtc/group/info?groupId="+this.group.id,
method: 'GET'
}).then((rtcInfo)=>{
if(rtcInfo.isChating){
//
this.$refs.rtcJoin.open(rtcInfo);
}else {
//
let ids = [this.mine.id];
this.$refs.selBox.init(ids, ids);
this.$refs.selBox.open();
}
})
},
onSelectMember(ids) {
let users = [];

2
im-uniapp/pages/chat/chat-group-video.vue

@ -76,10 +76,10 @@
this.url += "&isHost=" + this.isHost;
this.url += "&loginInfo=" + JSON.stringify(uni.getStorageSync("loginInfo"));
this.url += "&userInfos=" + JSON.stringify(this.userInfos);
console.log(this.url)
},
},
onBackPress() {
console.log("onBackPress")
this.sendMessageToWebView("NAV_BACK", {})
},
onLoad(options) {

Loading…
Cancel
Save