LiveRoomServiceImpl.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. package com.ym.service.Impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5. import com.microsvc.toolkit.middleware.live.LivePluginContext;
  6. import com.microsvc.toolkit.middleware.live.impl.TencentCloudLivePlugin;
  7. import com.microsvc.toolkit.middleware.live.message.RTCRequest;
  8. import com.microsvc.toolkit.middleware.live.message.RTCRoom;
  9. import com.ym.http.HttpHelper;
  10. import com.ym.mec.biz.dal.entity.ImLiveRoomVideo;
  11. import com.ym.mec.biz.service.ImLiveRoomVideoService;
  12. import com.ym.mec.common.entity.ImRoomMessage;
  13. import com.ym.mec.common.exception.BizException;
  14. import com.ym.mec.im.IMHelper;
  15. import com.ym.mec.thirdparty.storage.StoragePluginContext;
  16. import com.ym.pojo.IMApiResultInfo;
  17. import com.ym.pojo.IMUserOnlineInfo;
  18. import com.ym.pojo.RecordConfig;
  19. import com.ym.pojo.RecordNotify;
  20. import com.ym.service.LiveRoomService;
  21. import lombok.extern.slf4j.Slf4j;
  22. import org.apache.commons.lang.StringUtils;
  23. import org.joda.time.DateTime;
  24. import org.redisson.api.RBucket;
  25. import org.redisson.api.RedissonClient;
  26. import org.springframework.beans.factory.annotation.Autowired;
  27. import org.springframework.stereotype.Service;
  28. import java.net.HttpURLConnection;
  29. import java.util.*;
  30. import java.util.concurrent.TimeUnit;
  31. /**
  32. * @author hgw
  33. * Created by 2022-02-21
  34. */
  35. @Slf4j
  36. @Service
  37. public class LiveRoomServiceImpl implements LiveRoomService {
  38. @Autowired
  39. private IMHelper imHelper;
  40. @Autowired
  41. private HttpHelper httpHelper;
  42. @Autowired
  43. private RedissonClient redissonClient;
  44. @Autowired
  45. private ImLiveRoomVideoService imLiveRoomVideoService;
  46. @Autowired
  47. private StoragePluginContext storagePluginContext;
  48. @Autowired
  49. private LivePluginContext livePluginContext;
  50. /**
  51. * 创建房间-聊天室
  52. *
  53. * @param roomId 房间Uid
  54. * @param roomName 房间名称
  55. */
  56. public IMApiResultInfo createLiveRoom(String roomId, String roomName) {
  57. IMApiResultInfo resultInfo;
  58. try {
  59. resultInfo = imHelper.createChatRoom(roomId, roomName);
  60. } catch (Exception e) {
  61. log.error("create chatRoom error >>>", e.getCause());
  62. throw new BizException("创建聊天室失败!");
  63. }
  64. if (!resultInfo.isSuccess()) {
  65. log.error("create chatRoom error: {}", resultInfo.getErrorMessage());
  66. throw new BizException("创建聊天室失败!");
  67. }
  68. log.info("create chatRoom success: {}", roomId);
  69. return resultInfo;
  70. }
  71. /**
  72. * 销毁房间-聊天室
  73. *
  74. * @param roomId 房间Uid
  75. */
  76. public IMApiResultInfo destroyLiveRoom(String roomId) {
  77. //删除服务器房间
  78. List<String> deleteRoomIds = new ArrayList<String>() {{
  79. add(roomId);
  80. }};
  81. IMApiResultInfo resultInfo;
  82. try {
  83. resultInfo = imHelper.deleteChrm(deleteRoomIds);
  84. } catch (Exception e) {
  85. throw new BizException("关闭聊天室失败!");
  86. }
  87. if (!resultInfo.isSuccess()) {
  88. log.error("destroy chatRoom error: {}", resultInfo.getErrorMessage());
  89. throw new BizException("关闭聊天室失败!");
  90. }
  91. return resultInfo;
  92. }
  93. /**
  94. * 发送消息
  95. *
  96. * @param message
  97. */
  98. public IMApiResultInfo publishRoomMessage(ImRoomMessage message) {
  99. log.info("publishRoomMessage message : {}", JSONObject.toJSONString(message));
  100. IMApiResultInfo resultInfo;
  101. try {
  102. resultInfo = imHelper.publishRoomMessage(message.getFromUserId(), message.getToChatroomId(), message);
  103. } catch (Exception e) {
  104. throw new BizException("消息发送失败" + e.getMessage());
  105. }
  106. if (!resultInfo.isSuccess()) {
  107. log.error("publishRoomMessage chatRoom error: {}", resultInfo.getErrorMessage());
  108. throw new BizException("消息发送失败!");
  109. }
  110. return resultInfo;
  111. }
  112. @Override
  113. public void startRecord(String roomId, String videoResolution) throws Exception {
  114. log.error("开始录制直播:roomId : {} ", roomId);
  115. JSONObject paramJson = new JSONObject();
  116. paramJson.put("sessionId", getRoomSessionId(roomId));
  117. RecordConfig recordConfig = new RecordConfig();
  118. Optional.ofNullable(videoResolution)
  119. .ifPresent(recordConfig::setVideoResolution);
  120. paramJson.put("config", recordConfig);
  121. String body = paramJson.toJSONString();
  122. HttpURLConnection conn = httpHelper.createIMRtcPostHttpConnection("/rtc/record/start.json", "application/json", roomId);
  123. httpHelper.setBodyParameter(body, conn);
  124. String returnResult = httpHelper.returnResult(conn, body);
  125. JSONObject resultObject = JSONObject.parseObject(returnResult);
  126. String code = resultObject.getString("code");
  127. if (!"200".equals(code)) {
  128. log.error("直播视频录制失败:resultInfo : {} ", returnResult);
  129. }
  130. String recordId = resultObject.getString("recordId");
  131. ImLiveRoomVideo video = imLiveRoomVideoService.getOne(new QueryWrapper<ImLiveRoomVideo>().eq("room_uid_", roomId).eq("record_id_", recordId).eq("type", 0));
  132. if (Objects.nonNull(video)) {
  133. return;
  134. }
  135. imLiveRoomVideoService.save(initImLiveRoomVideo(roomId, recordId, new Date()));
  136. }
  137. private ImLiveRoomVideo initImLiveRoomVideo(String roomId, String recordId, Date now) {
  138. ImLiveRoomVideo video = new ImLiveRoomVideo();
  139. video.setRoomUid(roomId);
  140. video.setRecordId(recordId);
  141. video.setStartTime(now);
  142. video.setType(0);
  143. video.setCreatedTime(now);
  144. return video;
  145. }
  146. @Override
  147. public void stopRecord(String roomId) throws Exception {
  148. JSONObject paramJson = new JSONObject();
  149. paramJson.put("sessionId", getRoomSessionId(roomId));
  150. String body = paramJson.toJSONString();
  151. HttpURLConnection conn = httpHelper.createIMRtcPostHttpConnection("/rtc/record/stop.json", "application/json", roomId);
  152. httpHelper.setBodyParameter(body, conn);
  153. redissonClient.getBucket("sessionId:" + roomId).delete();
  154. log.info("结束录制直播 roomId :{},{}", roomId, httpHelper.returnResult(conn, body));
  155. }
  156. @Override
  157. public void recordSync(RecordNotify recordNotify) {
  158. if (recordNotify.getCode().equals(200)) {
  159. if (Objects.nonNull(recordNotify.getType()) && recordNotify.getType() == 4) {
  160. //云端录制文件地址
  161. String fileUrl = storagePluginContext.getPublicUrl(recordNotify.getOutput().getFileUrl(),"live-rewind");
  162. String roomId = recordNotify.getRoomId();
  163. //写入数据库
  164. try {
  165. Date now = new Date();
  166. //获取最后一次录制视频
  167. ImLiveRoomVideo video = imLiveRoomVideoService.getDao().getLastRecord(roomId, recordNotify.getRecordId());
  168. if (Objects.isNull(video)) {
  169. log.error("获取录制视频失败:roomId : {} ,recordId:{}", roomId, recordNotify.getRecordId());
  170. return;
  171. }
  172. if (StringUtils.isNotEmpty(video.getUrl())) {
  173. //保存切片
  174. ImLiveRoomVideo imLiveRoomVideo = initImLiveRoomVideo(roomId, recordNotify.getRecordId(), now);
  175. imLiveRoomVideo.setStartTime(video.getEndTime());
  176. imLiveRoomVideo.setEndTime(now);
  177. imLiveRoomVideo.setUrl(fileUrl);
  178. imLiveRoomVideo.setType(2);
  179. imLiveRoomVideoService.save(imLiveRoomVideo);
  180. return;
  181. } else {
  182. video.setEndTime(now);
  183. video.setType(2);
  184. video.setUrl(fileUrl);
  185. video.setCreatedTime(now);
  186. imLiveRoomVideoService.updateById(video);
  187. }
  188. } catch (Exception e) {
  189. log.error("recordSync >>>> error : {}", e.getMessage());
  190. }
  191. }
  192. }
  193. }
  194. /**
  195. * 查询用户是否在聊天室
  196. *
  197. * @param chatroomId 要查询的聊天室 ID(必传)
  198. * @param userId 要查询的用户 ID(必传)
  199. * @return true 在聊天室,false 不在聊天室
  200. * <p>触发融云退出聊天室机制将用户踢出
  201. * <p>聊天室中用户在离线 30 秒后有新消息产生时或离线后聊天室中产生 30 条消息时会被自动退出聊天室
  202. * <p>此状态需要聊天室中有新消息时才会进行同步
  203. */
  204. public boolean userExistInRoom(String chatroomId, String userId) {
  205. log.info("userExistInRoom chatroomId : {} userId : {}", chatroomId, userId);
  206. IMApiResultInfo resultInfo;
  207. try {
  208. resultInfo = imHelper.isInChartRoom(chatroomId, userId);
  209. } catch (Exception e) {
  210. throw new BizException("查询失败" + e.getMessage());
  211. }
  212. if (!resultInfo.isSuccess()) {
  213. log.error("userExistInRoom chatroomId : {} userId : {}", chatroomId, userId);
  214. throw new BizException("查询失败!");
  215. }
  216. return resultInfo.isSuccess() && resultInfo.getIsInChrm();
  217. }
  218. /**
  219. * 查询用户是否在线
  220. * 注意:断网时,用户状态返回可能不准确
  221. *
  222. * @param userId 要查询的用户 ID(必传)
  223. * @return true 在线,false 不在线
  224. */
  225. public boolean checkOnline(String userId) {
  226. log.info("checkOnline userId : {}", userId);
  227. IMUserOnlineInfo resultInfo;
  228. try {
  229. resultInfo = imHelper.checkOnline(userId);
  230. } catch (Exception e) {
  231. return false;
  232. }
  233. if (!resultInfo.isSuccess()) {
  234. log.error("checkOnline userId : {}", userId);
  235. return false;
  236. }
  237. log.info("checkOnline userId : {} resultInfo code:{} status: {}", userId, resultInfo.getCode(), resultInfo.getStatus());
  238. return Objects.equals("1", resultInfo.getStatus());
  239. }
  240. /**
  241. * 添加禁言成员-默认禁言120分钟
  242. *
  243. * @param roomUid 房间uid
  244. * @param userId 用户id
  245. */
  246. public boolean addUserUnableSpeak(String roomUid, String userId) {
  247. log.info("addUserUnableToSpeak chatroomId : {} userId : {}", roomUid, userId);
  248. IMApiResultInfo resultInfo;
  249. try {
  250. resultInfo = imHelper.addUserUnableSpeak(roomUid, userId, "120");
  251. } catch (Exception e) {
  252. log.error("addUserUnableToSpeak chatroomId error: {} userId : {}", roomUid, userId);
  253. return false;
  254. }
  255. if (!resultInfo.isSuccess()) {
  256. log.error("addUserUnableToSpeak chatroomId : {} userId : {}", roomUid, userId);
  257. return false;
  258. }
  259. return true;
  260. }
  261. /**
  262. * 移除禁言成员
  263. *
  264. * @param roomUid 房间uid
  265. * @param userId 用户id
  266. */
  267. public boolean removeUserUnableSpeak(String roomUid, String userId) {
  268. log.info("removeUserUnableSpeak chatroomId : {} userId : {}", roomUid, userId);
  269. IMApiResultInfo resultInfo;
  270. try {
  271. resultInfo = imHelper.removeUserUnableSpeak(roomUid, userId);
  272. } catch (Exception e) {
  273. log.error("removeUserUnableSpeak chatroomId error: {} userId : {}", roomUid, userId);
  274. return false;
  275. }
  276. if (!resultInfo.isSuccess()) {
  277. log.error("removeUserUnableSpeak chatroomId : {} userId : {}", roomUid, userId);
  278. return false;
  279. }
  280. return true;
  281. }
  282. public String getRoomSessionId(String roomId) {
  283. RBucket<String> bucket = redissonClient.getBucket("sessionId:" + roomId);
  284. if (bucket.isExists()) {
  285. return bucket.get();
  286. }
  287. HttpURLConnection conn;
  288. JSONObject resultObject;
  289. try {
  290. JSONObject jsonObject = new JSONObject();
  291. jsonObject.put("roomId", roomId);
  292. conn = httpHelper.createIMRtcPostHttpConnection("/rtc/room/query.json", "application/json", null);
  293. httpHelper.setBodyParameter(jsonObject.toJSONString(), conn);
  294. String returnResult = httpHelper.returnResult(conn, jsonObject.toJSONString());
  295. resultObject = JSONObject.parseObject(returnResult);
  296. } catch (Exception e) {
  297. throw new BizException("获取sessionId失败", e.getCause());
  298. }
  299. String sessionId;
  300. if ("200".equals(resultObject.getString("code"))) {
  301. sessionId = resultObject.getString("sessionId");
  302. bucket.set(sessionId, 1, TimeUnit.HOURS);
  303. log.info("getRoomSessionId roomId : {} sessionId : {}", roomId, sessionId);
  304. } else {
  305. log.error("获取sessionId失败 roomId : {} returnResult:{}", roomId, resultObject);
  306. throw new BizException("获取sessionId失败");
  307. }
  308. return sessionId;
  309. }
  310. /**
  311. * 创建tencent云直播录制记录
  312. *
  313. * @param streamId 推流Id
  314. */
  315. @Override
  316. public void strartTencentLiveVideoRecord(String streamId) {
  317. // 直播间ROOM_UID
  318. String roomId = streamId.split("_")[0];
  319. // 创建直播录制
  320. RTCRequest.RecordStart recordStart = RTCRequest.RecordStart.builder()
  321. .streamName(streamId)
  322. .extra("")
  323. .endTime(DateTime.now().plusHours(23).getMillis())
  324. .build();
  325. // 创建录制任务失败,重试3次后,发送IM消息通知主播老师
  326. int maxRetry = 0;
  327. // 录制任务ID
  328. String taskId = "";
  329. do {
  330. try {
  331. // 创建录制任务
  332. RTCRoom.RecordResp resp = livePluginContext.getPluginService(TencentCloudLivePlugin.PLUGIN_NAME)
  333. .rtcRoomRecordStart(recordStart);
  334. taskId = resp.getRecordId();
  335. if (StringUtils.isNotBlank(taskId)) {
  336. maxRetry = 3;
  337. }
  338. log.info("createTencentLiveRoomVideoRecord resp={}", JSON.toJSONString(resp));
  339. } catch (Exception e) {
  340. log.error("创建直播录制失败", e);
  341. }
  342. } while (maxRetry++ < 3);
  343. // 生成录制记录
  344. ImLiveRoomVideo video = imLiveRoomVideoService.getOne(new QueryWrapper<ImLiveRoomVideo>()
  345. .eq("room_uid_", roomId)
  346. .eq("record_id_", taskId)
  347. .eq("type", 0));
  348. if (Objects.nonNull(video)) {
  349. return;
  350. }
  351. imLiveRoomVideoService.save(initImLiveRoomVideo(roomId, taskId, new Date()));
  352. }
  353. /**
  354. * 关闭tencent云直播录制记录
  355. *
  356. * @param streamId 推流Id
  357. */
  358. @Override
  359. public void stopTencentLiveVideoRecord(String streamId) {
  360. // 关闭录制任务失败,重试3次后,发送IM消息通知主播老师
  361. int maxRetry = 0;
  362. do {
  363. try {
  364. // 关闭直播录制
  365. RTCRoom.RecordResp ret = livePluginContext.getPluginService(TencentCloudLivePlugin.PLUGIN_NAME)
  366. .rtcRoomRecordStop(streamId);
  367. if (StringUtils.isNotBlank(ret.getRequestId())) {
  368. // 重试最大次数
  369. maxRetry = 3;
  370. }
  371. } catch (Exception e) {
  372. log.error("关闭直播录制失败", e);
  373. }
  374. } while (maxRetry++ < 3);
  375. }
  376. }