use-websocket.ts 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001
  1. import { copyToClipBoard, getRandomString } from 'billd-utils';
  2. import { NButton } from 'naive-ui';
  3. import { computed, h, onUnmounted, ref, watch } from 'vue';
  4. import { useRoute } from 'vue-router';
  5. import { fetchVerifyPkKey } from '@/api/liveRoom';
  6. import { THEME_COLOR, WEBSOCKET_URL } from '@/constant';
  7. import { useRTCParams } from '@/hooks/use-rtcParams';
  8. import { useTip } from '@/hooks/use-tip';
  9. import { useWebRtcLive } from '@/hooks/webrtc/live';
  10. import { useWebRtcMeetingOne } from '@/hooks/webrtc/meetingOne';
  11. import { useWebRtcMeetingPk } from '@/hooks/webrtc/meetingPk';
  12. import { useWebRtcSrs } from '@/hooks/webrtc/srs';
  13. import { useWebRtcTencentcloudCss } from '@/hooks/webrtc/tencentcloudCss';
  14. import {
  15. DanmuMsgTypeEnum,
  16. ILiveUser,
  17. WsMessageContentTypeEnum,
  18. } from '@/interface';
  19. import router, { routerName } from '@/router';
  20. import { useAppStore } from '@/store/app';
  21. import { useNetworkStore } from '@/store/network';
  22. import { useUserStore } from '@/store/user';
  23. import { LiveRoomTypeEnum } from '@/types/ILiveRoom';
  24. import { IUser } from '@/types/IUser';
  25. import {
  26. WSGetRoomAllUserType,
  27. WSLivePkKeyType,
  28. WsAnswerType,
  29. WsBatchSendOffer,
  30. WsCandidateType,
  31. WsConnectStatusEnum,
  32. WsDisableSpeakingType,
  33. WsHeartbeatType,
  34. WsJoinType,
  35. WsLeavedType,
  36. WsMessageType,
  37. WsMsgTypeEnum,
  38. WsOfferType,
  39. WsOtherJoinType,
  40. WsRemoteDeskBehaviorType,
  41. WsRoomLivingType,
  42. WsStartLiveType,
  43. WsStartRemoteDesk,
  44. WsUpdateJoinInfoType,
  45. } from '@/types/websocket';
  46. import {
  47. createNullVideo,
  48. handleUserMedia,
  49. setAudioTrackContentHints,
  50. setVideoTrackContentHints,
  51. } from '@/utils';
  52. import {
  53. WebSocketClass,
  54. prettierReceiveWsMsg,
  55. } from '@/utils/network/webSocket';
  56. import { useForwardAll } from './webrtc/forwardAll';
  57. import { useForwardBilibili } from './webrtc/forwardBilibili';
  58. import { useForwardHuya } from './webrtc/forwardHuya';
  59. import { useWebRtcRemoteDesk } from './webrtc/remoteDesk';
  60. export const useWebsocket = () => {
  61. const route = useRoute();
  62. const appStore = useAppStore();
  63. const userStore = useUserStore();
  64. const networkStore = useNetworkStore();
  65. const {
  66. maxBitrate,
  67. maxFramerate,
  68. resolutionRatio,
  69. videoContentHint,
  70. audioContentHint,
  71. } = useRTCParams();
  72. const { updateWebRtcRemoteDeskConfig, webRtcRemoteDesk } =
  73. useWebRtcRemoteDesk();
  74. const { updateWebRtcMeetingPkConfig, webRtcMeetingPk } = useWebRtcMeetingPk();
  75. const { updateWebRtcSrsConfig, webRtcSrs } = useWebRtcSrs();
  76. const { updateForwardBilibiliConfig, forwardBilibili } = useForwardBilibili();
  77. const { updateForwardAllConfig, forwardAll } = useForwardAll();
  78. const { updateForwardHuyaConfig, forwardHuya } = useForwardHuya();
  79. const { updateWebRtcTencentcloudCssConfig, webRtcTencentcloudCss } =
  80. useWebRtcTencentcloudCss();
  81. const { updateWebRtcLiveConfig, webRtcLive } = useWebRtcLive();
  82. const { updateWebRtcMeetingOneConfig, webRtcMeetingOne } =
  83. useWebRtcMeetingOne();
  84. const connectStatus = ref<WsConnectStatusEnum>();
  85. const loopHeartbeatTimer = ref();
  86. const loopGetLiveUserTimer = ref();
  87. const liveUserList = ref<ILiveUser[]>([]);
  88. const roomId = ref('');
  89. const roomLiving = ref(false);
  90. const isAnchor = ref(false);
  91. const isRemoteDesk = ref(false);
  92. const isBilibili = ref(false);
  93. const anchorInfo = ref<IUser>();
  94. const anchorSocketId = ref('');
  95. const canvasVideoStream = ref<MediaStream>();
  96. const userStream = ref<MediaStream>();
  97. const lastCoverImg = ref('');
  98. const currentMaxBitrate = ref(maxBitrate.value[3].value);
  99. const currentMaxFramerate = ref(maxFramerate.value[2].value);
  100. const currentResolutionRatio = ref(resolutionRatio.value[3].value);
  101. const currentVideoContentHint = ref(videoContentHint.value[3].value);
  102. const currentAudioContentHint = ref(audioContentHint.value[0].value);
  103. const timerObj = ref({});
  104. const damuList = ref<WsMessageType[]>([]);
  105. onUnmounted(() => {
  106. clearInterval(loopHeartbeatTimer.value);
  107. clearInterval(loopGetLiveUserTimer.value);
  108. });
  109. watch(
  110. [() => userStore.userInfo?.id, () => connectStatus.value],
  111. ([userInfo, status]) => {
  112. if (userInfo && status === WsConnectStatusEnum.connect) {
  113. const ws = networkStore.wsMap.get(roomId.value);
  114. if (!ws) return;
  115. ws.send<WsUpdateJoinInfoType['data']>({
  116. requestId: getRandomString(8),
  117. msgType: WsMsgTypeEnum.updateJoinInfo,
  118. data: {
  119. live_room_id: Number(roomId.value),
  120. },
  121. });
  122. }
  123. },
  124. { immediate: true }
  125. );
  126. const mySocketId = computed(() => {
  127. return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
  128. });
  129. function handleHeartbeat() {
  130. loopHeartbeatTimer.value = setInterval(() => {
  131. const ws = networkStore.wsMap.get(roomId.value);
  132. if (!ws) return;
  133. ws.send<WsHeartbeatType['data']>({
  134. requestId: getRandomString(8),
  135. msgType: WsMsgTypeEnum.heartbeat,
  136. data: {
  137. live_room_id: Number(roomId.value),
  138. roomLiving: isAnchor.value && roomLiving.value,
  139. },
  140. });
  141. }, 1000 * 5);
  142. }
  143. function handleStartLive({
  144. name,
  145. type,
  146. msrDelay,
  147. msrMaxDelay,
  148. }: {
  149. name?: string;
  150. type: LiveRoomTypeEnum;
  151. videoEl?: HTMLVideoElement;
  152. msrDelay: number;
  153. msrMaxDelay: number;
  154. }) {
  155. if (appStore.liveRoomInfo) {
  156. appStore.liveRoomInfo.type = type;
  157. }
  158. networkStore.wsMap.get(roomId.value)?.send<WsStartLiveType['data']>({
  159. requestId: getRandomString(8),
  160. msgType: WsMsgTypeEnum.startLive,
  161. data: {
  162. name: name!,
  163. type,
  164. msrDelay,
  165. msrMaxDelay,
  166. },
  167. });
  168. if (canvasVideoStream.value) {
  169. setVideoTrackContentHints(
  170. canvasVideoStream.value,
  171. // @ts-ignore
  172. currentVideoContentHint.value
  173. );
  174. setAudioTrackContentHints(
  175. canvasVideoStream.value,
  176. // @ts-ignore
  177. currentAudioContentHint.value
  178. );
  179. }
  180. if (type === LiveRoomTypeEnum.srs) {
  181. updateWebRtcSrsConfig({
  182. isPk: false,
  183. roomId: roomId.value,
  184. canvasVideoStream: canvasVideoStream.value,
  185. });
  186. webRtcSrs.newWebRtc({
  187. sender: mySocketId.value,
  188. receiver: 'srs',
  189. videoEl: createNullVideo(),
  190. });
  191. webRtcSrs.sendOffer({
  192. sender: mySocketId.value,
  193. receiver: 'srs',
  194. });
  195. } else if (type === LiveRoomTypeEnum.forward_bilibili) {
  196. updateForwardBilibiliConfig({
  197. isPk: false,
  198. roomId: roomId.value,
  199. canvasVideoStream: canvasVideoStream.value,
  200. });
  201. forwardBilibili.newWebRtc({
  202. sender: mySocketId.value,
  203. receiver: 'srs',
  204. videoEl: createNullVideo(),
  205. });
  206. forwardBilibili.sendOffer({
  207. sender: mySocketId.value,
  208. receiver: 'srs',
  209. });
  210. } else if (type === LiveRoomTypeEnum.forward_huya) {
  211. updateForwardHuyaConfig({
  212. isPk: false,
  213. roomId: roomId.value,
  214. canvasVideoStream: canvasVideoStream.value,
  215. });
  216. forwardHuya.newWebRtc({
  217. sender: mySocketId.value,
  218. receiver: 'srs',
  219. videoEl: createNullVideo(),
  220. });
  221. forwardHuya.sendOffer({
  222. sender: mySocketId.value,
  223. receiver: 'srs',
  224. });
  225. } else if (type === LiveRoomTypeEnum.forward_all) {
  226. updateForwardAllConfig({
  227. isPk: false,
  228. roomId: roomId.value,
  229. canvasVideoStream: canvasVideoStream.value,
  230. });
  231. forwardAll.newWebRtc({
  232. sender: mySocketId.value,
  233. receiver: 'srs',
  234. videoEl: createNullVideo(),
  235. });
  236. forwardAll.sendOffer({
  237. sender: mySocketId.value,
  238. receiver: 'srs',
  239. });
  240. } else if (type === LiveRoomTypeEnum.tencent_css) {
  241. updateWebRtcTencentcloudCssConfig({
  242. isPk: false,
  243. roomId: roomId.value,
  244. canvasVideoStream: canvasVideoStream.value,
  245. });
  246. webRtcTencentcloudCss.newWebRtc({
  247. sender: mySocketId.value,
  248. receiver: 'tencentcloud_css',
  249. videoEl: createNullVideo(),
  250. });
  251. webRtcTencentcloudCss.sendOffer({
  252. sender: mySocketId.value,
  253. receiver: 'tencentcloud_css',
  254. });
  255. } else if (type === LiveRoomTypeEnum.pk) {
  256. updateWebRtcSrsConfig({
  257. isPk: true,
  258. roomId: roomId.value,
  259. canvasVideoStream: canvasVideoStream.value,
  260. });
  261. webRtcSrs.newWebRtc({
  262. sender: mySocketId.value,
  263. receiver: 'srs',
  264. videoEl: createNullVideo(),
  265. });
  266. webRtcSrs.sendOffer({
  267. sender: mySocketId.value,
  268. receiver: 'srs',
  269. });
  270. } else if (type === LiveRoomTypeEnum.tencent_css_pk) {
  271. updateWebRtcTencentcloudCssConfig({
  272. isPk: true,
  273. roomId: roomId.value,
  274. canvasVideoStream: canvasVideoStream.value,
  275. });
  276. webRtcTencentcloudCss.newWebRtc({
  277. sender: mySocketId.value,
  278. receiver: 'tencentcloud_css',
  279. videoEl: createNullVideo(),
  280. });
  281. webRtcTencentcloudCss.sendOffer({
  282. sender: mySocketId.value,
  283. receiver: 'tencentcloud_css',
  284. });
  285. }
  286. }
  287. function sendJoin() {
  288. const instance = networkStore.wsMap.get(roomId.value);
  289. if (!instance) return;
  290. instance.send<WsJoinType['data']>({
  291. requestId: getRandomString(8),
  292. msgType: WsMsgTypeEnum.join,
  293. data: {
  294. isBilibili: isBilibili.value,
  295. isRemoteDesk: isRemoteDesk.value,
  296. socket_id: mySocketId.value,
  297. live_room_id: Number(roomId.value),
  298. user_info: userStore.userInfo,
  299. },
  300. });
  301. }
  302. function initReceive() {
  303. const ws = networkStore.wsMap.get(roomId.value);
  304. if (!ws?.socketIo) return;
  305. // websocket连接成功
  306. ws.socketIo.on(WsConnectStatusEnum.connect, () => {
  307. prettierReceiveWsMsg(WsConnectStatusEnum.connect, ws.socketIo);
  308. handleHeartbeat();
  309. if (!ws) return;
  310. connectStatus.value = WsConnectStatusEnum.connect;
  311. ws.status = WsConnectStatusEnum.connect;
  312. ws.update();
  313. sendJoin();
  314. });
  315. // websocket连接断开
  316. ws.socketIo.on(WsConnectStatusEnum.disconnect, (err) => {
  317. prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws);
  318. console.log('websocket连接断开', err);
  319. if (!ws) return;
  320. ws.status = WsConnectStatusEnum.disconnect;
  321. ws.update();
  322. });
  323. // 收到livePkKey
  324. ws.socketIo.on(WsMsgTypeEnum.livePkKey, (data: WSLivePkKeyType['data']) => {
  325. console.log('收到livePkKey', data);
  326. const url = router.resolve({
  327. name: routerName.pull,
  328. params: { roomId: data.live_room_id },
  329. query: {
  330. pkKey: data.key,
  331. },
  332. });
  333. const pkurl = `${window.location.origin}${url.href}`;
  334. useTip({
  335. title: '邀请主播加入PK',
  336. width: '360px',
  337. hiddenCancel: true,
  338. content: h('div', [
  339. h('div', { style: { marginBottom: '5px' } }, `${pkurl}`),
  340. h(
  341. NButton,
  342. {
  343. size: 'small',
  344. type: 'primary',
  345. color: THEME_COLOR,
  346. onClick: () => {
  347. copyToClipBoard(pkurl);
  348. window.$message.success('复制成功!');
  349. },
  350. },
  351. () => '复制链接' // 用箭头函数返回性能更好。
  352. ),
  353. h('div', { style: { marginTop: '5px' } }, '注意,有效期:5分钟'),
  354. ]),
  355. }).catch(() => {});
  356. });
  357. // 收到startRemoteDesk
  358. ws.socketIo.on(WsMsgTypeEnum.startRemoteDesk, (data: WsStartRemoteDesk) => {
  359. console.log('收到startRemoteDesk', data);
  360. if (data.data.receiver === mySocketId.value) {
  361. appStore.remoteDesk.startRemoteDesk = true;
  362. appStore.remoteDesk.sender = data.data.sender;
  363. }
  364. });
  365. // 收到srsOffer
  366. ws.socketIo.on(WsMsgTypeEnum.srsOffer, (data: WsOfferType['data']) => {
  367. console.log('收到srsOffer', data);
  368. });
  369. // 收到srsAnswer
  370. ws.socketIo.on(WsMsgTypeEnum.srsAnswer, (data: WsAnswerType['data']) => {
  371. console.log('收到srsAnswer', data);
  372. });
  373. // 收到srsCandidate
  374. ws.socketIo.on(
  375. WsMsgTypeEnum.srsCandidate,
  376. (data: WsCandidateType['data']) => {
  377. console.log('收到srsCandidate', data);
  378. if (data.receiver === mySocketId.value) {
  379. console.warn('是发给我的srsCandidate');
  380. const rtc = networkStore.rtcMap.get(data.sender);
  381. rtc?.addIceCandidate(data.candidate);
  382. } else {
  383. console.error('不是发给我的srsCandidate');
  384. }
  385. }
  386. );
  387. // 收到nativeWebRtcOffer
  388. ws.socketIo.on(
  389. WsMsgTypeEnum.nativeWebRtcOffer,
  390. async (data: WsOfferType['data']) => {
  391. console.log('收到nativeWebRtcOffer', data);
  392. if (data.isRemoteDesk) {
  393. if (data.receiver === mySocketId.value) {
  394. console.warn('是发给我的nativeWebRtcOffer-isRemoteDesk');
  395. if (networkStore.rtcMap.get(data.sender)) {
  396. return;
  397. }
  398. updateWebRtcRemoteDeskConfig({
  399. roomId: roomId.value,
  400. userStream: userStream.value,
  401. anchorStream: canvasVideoStream.value,
  402. });
  403. webRtcRemoteDesk.newWebRtc({
  404. // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
  405. // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
  406. sender: mySocketId.value,
  407. receiver: data.sender,
  408. videoEl: createNullVideo(),
  409. });
  410. await webRtcRemoteDesk.sendAnswer({
  411. sender: mySocketId.value,
  412. // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
  413. receiver: data.sender,
  414. sdp: data.sdp,
  415. });
  416. } else {
  417. console.error('不是发给我的nativeWebRtcOffer-isRemoteDesk');
  418. }
  419. return;
  420. }
  421. if (
  422. data.live_room.type === LiveRoomTypeEnum.pk ||
  423. data.live_room.type === LiveRoomTypeEnum.tencent_css_pk
  424. ) {
  425. if (!route.query.pkKey) {
  426. return;
  427. }
  428. if (data.receiver === mySocketId.value) {
  429. console.warn('是发给我的nativeWebRtcOffer-pk-tencent_css_pk');
  430. updateWebRtcMeetingPkConfig({
  431. roomId: roomId.value,
  432. anchorStream: canvasVideoStream.value,
  433. // userStream: userStream.value,
  434. });
  435. webRtcMeetingPk.newWebRtc({
  436. // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
  437. // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
  438. sender: mySocketId.value,
  439. receiver: data.sender,
  440. videoEl: createNullVideo(),
  441. });
  442. webRtcMeetingPk.addTrack({
  443. stream: userStream.value,
  444. receiver: data.sender,
  445. });
  446. await webRtcMeetingPk.sendAnswer({
  447. sender: mySocketId.value,
  448. // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
  449. receiver: data.sender,
  450. sdp: data.sdp,
  451. });
  452. } else {
  453. console.error('不是发给我的nativeWebRtcOffer');
  454. }
  455. } else if (data.live_room.type === LiveRoomTypeEnum.wertc_live) {
  456. if (data.receiver === mySocketId.value) {
  457. console.warn('是发给我的nativeWebRtcOffer-wertc_live');
  458. if (networkStore.rtcMap.get(data.sender)) {
  459. return;
  460. }
  461. updateWebRtcLiveConfig({
  462. roomId: roomId.value,
  463. canvasVideoStream: canvasVideoStream.value,
  464. });
  465. webRtcLive.newWebRtc({
  466. // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
  467. // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
  468. sender: mySocketId.value,
  469. receiver: data.sender,
  470. videoEl: createNullVideo(),
  471. });
  472. await webRtcLive.sendAnswer({
  473. sender: mySocketId.value,
  474. // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
  475. receiver: data.sender,
  476. sdp: data.sdp,
  477. });
  478. } else {
  479. console.error('不是发给我的nativeWebRtcOffer');
  480. }
  481. } else if (data.live_room.type === LiveRoomTypeEnum.wertc_meeting_one) {
  482. if (data.receiver === mySocketId.value) {
  483. console.warn('是发给我的nativeWebRtcOffer-wertc_meeting_one');
  484. updateWebRtcMeetingOneConfig({
  485. roomId: roomId.value,
  486. // userStream: userStream.value,
  487. anchorStream: canvasVideoStream.value,
  488. });
  489. if (networkStore.rtcMap.get(data.sender)) {
  490. return;
  491. }
  492. webRtcMeetingOne.newWebRtc({
  493. // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
  494. // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
  495. sender: mySocketId.value,
  496. receiver: data.sender,
  497. videoEl: createNullVideo(),
  498. });
  499. webRtcMeetingOne.addTrack({
  500. stream: userStream.value,
  501. receiver: data.sender,
  502. });
  503. await webRtcMeetingOne.sendAnswer({
  504. sender: mySocketId.value,
  505. // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
  506. receiver: data.sender,
  507. sdp: data.sdp,
  508. });
  509. } else {
  510. console.error('不是发给我的nativeWebRtcOffer');
  511. }
  512. }
  513. }
  514. );
  515. // 收到nativeWebRtcAnswer
  516. ws.socketIo.on(
  517. WsMsgTypeEnum.nativeWebRtcAnswer,
  518. async (data: WsAnswerType['data']) => {
  519. console.log('收到nativeWebRtcAnswer', data);
  520. if (data.receiver === mySocketId.value) {
  521. console.warn('是发给我的nativeWebRtcAnswer');
  522. const rtc = networkStore.rtcMap.get(data.sender);
  523. if (rtc) {
  524. await rtc.setRemoteDescription(data.sdp);
  525. }
  526. } else {
  527. console.error('不是发给我的nativeWebRtcAnswer');
  528. }
  529. }
  530. );
  531. // 收到nativeWebRtcCandidate
  532. ws.socketIo.on(
  533. WsMsgTypeEnum.nativeWebRtcCandidate,
  534. (data: WsCandidateType['data']) => {
  535. console.log('收到nativeWebRtcCandidate', data);
  536. if (data.receiver === mySocketId.value) {
  537. console.warn('是发给我的nativeWebRtcCandidate');
  538. const rtc = networkStore.rtcMap.get(data.sender);
  539. rtc?.addIceCandidate(data.candidate);
  540. } else {
  541. console.error('不是发给我的nativeWebRtcCandidate');
  542. }
  543. }
  544. );
  545. // 收到remoteDeskBehavior
  546. ws.socketIo.on(
  547. WsMsgTypeEnum.remoteDeskBehavior,
  548. (data: WsRemoteDeskBehaviorType['data']) => {
  549. console.log('收到remoteDeskBehavior', data);
  550. }
  551. );
  552. // 主播正在直播
  553. ws.socketIo.on(
  554. WsMsgTypeEnum.roomLiving,
  555. async (data: WsRoomLivingType['data']) => {
  556. prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
  557. roomLiving.value = true;
  558. if (data.anchor_socket_id) {
  559. anchorSocketId.value = data.anchor_socket_id;
  560. }
  561. if (
  562. route.name === routerName.pull ||
  563. route.name === routerName.h5Room
  564. ) {
  565. // 当前是拉流页面
  566. if (data.live_room?.type === LiveRoomTypeEnum.wertc_meeting_one) {
  567. await handleMeeting();
  568. } else if (data.live_room?.type === LiveRoomTypeEnum.pk) {
  569. await handlePk();
  570. }
  571. } else if (route.name === routerName.push) {
  572. // 当前是推流页面
  573. }
  574. }
  575. );
  576. // 主播不在直播
  577. ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
  578. prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data);
  579. roomLiving.value = false;
  580. });
  581. // 当前所有在线用户
  582. ws.socketIo.on(
  583. WsMsgTypeEnum.liveUser,
  584. (data: WSGetRoomAllUserType['data']) => {
  585. prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data);
  586. liveUserList.value = data.liveUser;
  587. }
  588. );
  589. // 收到用户发送消息
  590. ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => {
  591. prettierReceiveWsMsg(WsMsgTypeEnum.message, data);
  592. damuList.value.push(data);
  593. });
  594. // 收到disableSpeaking
  595. ws.socketIo.on(
  596. WsMsgTypeEnum.disableSpeaking,
  597. (data: WsDisableSpeakingType['data']) => {
  598. prettierReceiveWsMsg(WsMsgTypeEnum.disableSpeaking, data);
  599. // if (data.is_disable_speaking) {
  600. // window.$message.error('你已被禁言!');
  601. // appStore.disableSpeaking.set(data.live_room_id, {
  602. // exp: data.disable_expired_at,
  603. // label: formatDownTime({
  604. // startTime: +new Date(),
  605. // endTime: data.disable_expired_at,
  606. // }),
  607. // });
  608. // clearTimeout(timerObj.value[data.live_room_id]);
  609. // timerObj.value[data.live_room_id] = setInterval(() => {
  610. // if (
  611. // data.disable_expired_at &&
  612. // +new Date() > data.disable_expired_at
  613. // ) {
  614. // clearTimeout(timerObj.value[data.live_room_id]);
  615. // }
  616. // appStore.disableSpeaking.set(data.live_room_id, {
  617. // exp: data.disable_expired_at!,
  618. // label: formatDownTime({
  619. // startTime: +new Date(),
  620. // endTime: data.disable_expired_at!,
  621. // }),
  622. // });
  623. // }, 1000);
  624. // damuList.value = damuList.value.filter(
  625. // (v) => v.request_id !== data.request_id
  626. // );
  627. // }
  628. if (data.user_id !== userStore.userInfo?.id && data.disable_ok) {
  629. window.$message.success('禁言成功!');
  630. }
  631. if (
  632. data.user_id !== userStore.userInfo?.id &&
  633. data.restore_disable_ok
  634. ) {
  635. window.$message.success('解除禁言成功!');
  636. }
  637. if (
  638. data.user_id === userStore.userInfo?.id &&
  639. data.restore_disable_ok
  640. ) {
  641. window.$message.success('禁言接触了!');
  642. clearTimeout(timerObj.value[data.live_room_id]);
  643. appStore.disableSpeaking.delete(data.live_room_id);
  644. }
  645. }
  646. );
  647. async function handleMeeting() {
  648. await useTip({
  649. content: '是否加入会议?',
  650. });
  651. const stream = await handleUserMedia({
  652. video: true,
  653. audio: true,
  654. });
  655. userStream.value = stream;
  656. networkStore.wsMap.get(roomId.value)?.send<WsBatchSendOffer['data']>({
  657. requestId: getRandomString(8),
  658. msgType: WsMsgTypeEnum.batchSendOffer,
  659. data: {
  660. roomId: roomId.value,
  661. },
  662. });
  663. }
  664. async function handlePk() {
  665. if (!route.query.pkKey) {
  666. return;
  667. }
  668. const res = await fetchVerifyPkKey({
  669. liveRoomId: Number(roomId.value),
  670. key: route.query.pkKey,
  671. });
  672. if (res.code === 200 && res.data === true) {
  673. await useTip({
  674. content: '是否加入PK?',
  675. });
  676. const stream = await handleUserMedia({
  677. video: true,
  678. audio: true,
  679. });
  680. userStream.value = stream;
  681. networkStore.wsMap.get(roomId.value)?.send<WsBatchSendOffer['data']>({
  682. requestId: getRandomString(8),
  683. msgType: WsMsgTypeEnum.batchSendOffer,
  684. data: {
  685. roomId: roomId.value,
  686. },
  687. });
  688. } else {
  689. await useTip({
  690. content: '加入PK失败,验证pkKey错误!',
  691. hiddenCancel: true,
  692. hiddenClose: true,
  693. });
  694. }
  695. }
  696. // 用户加入房间完成
  697. ws.socketIo.on(WsMsgTypeEnum.joined, async (data: WsJoinType['data']) => {
  698. prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
  699. appStore.setLiveRoomInfo(data.live_room);
  700. anchorInfo.value = data.anchor_info;
  701. if (route.name === routerName.pull || route.name === routerName.h5Room) {
  702. // 当前是拉流页面
  703. if (
  704. roomLiving.value &&
  705. data.live_room?.type === LiveRoomTypeEnum.wertc_meeting_one
  706. ) {
  707. await handleMeeting();
  708. } else if (
  709. roomLiving.value &&
  710. data.live_room?.type === LiveRoomTypeEnum.pk
  711. ) {
  712. await handlePk();
  713. }
  714. }
  715. });
  716. // batchSendOffer
  717. ws.socketIo.on(
  718. WsMsgTypeEnum.batchSendOffer,
  719. (data: WsBatchSendOffer['data']) => {
  720. if (
  721. appStore.liveRoomInfo?.type === LiveRoomTypeEnum.wertc_meeting_one
  722. ) {
  723. data.socket_list?.forEach((item) => {
  724. if (item !== mySocketId.value) {
  725. if (networkStore.rtcMap.get(item)) {
  726. return;
  727. }
  728. webRtcMeetingOne.newWebRtc({
  729. sender: mySocketId.value,
  730. receiver: item,
  731. videoEl: createNullVideo(),
  732. });
  733. webRtcMeetingOne.sendOffer({
  734. sender: mySocketId.value,
  735. receiver: item,
  736. });
  737. }
  738. });
  739. } else if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.pk) {
  740. data.socket_list?.forEach((item) => {
  741. if (item !== mySocketId.value) {
  742. if (networkStore.rtcMap.get(item)) {
  743. return;
  744. }
  745. webRtcMeetingPk.newWebRtc({
  746. sender: mySocketId.value,
  747. receiver: item,
  748. videoEl: createNullVideo(),
  749. });
  750. webRtcMeetingPk.sendOffer({
  751. sender: mySocketId.value,
  752. receiver: item,
  753. });
  754. }
  755. });
  756. }
  757. }
  758. );
  759. // 其他用户加入房间
  760. ws.socketIo.on(WsMsgTypeEnum.otherJoin, (data: WsOtherJoinType['data']) => {
  761. prettierReceiveWsMsg(WsMsgTypeEnum.otherJoin, data);
  762. const danmu: WsMessageType = {
  763. request_id: '',
  764. socket_id: '',
  765. time: +new Date(),
  766. user_agent: navigator.userAgent,
  767. data: {
  768. live_room_id: data.live_room.id!,
  769. msg_id: -1,
  770. content: '',
  771. content_type: WsMessageContentTypeEnum.txt,
  772. msg_type: DanmuMsgTypeEnum.otherJoin,
  773. },
  774. };
  775. damuList.value.push(danmu);
  776. if (route.name === routerName.push) {
  777. // 当前是推流页面
  778. if (!isAnchor.value) {
  779. console.error('不是主播');
  780. return;
  781. }
  782. if (!roomLiving.value) {
  783. console.error('主播没点开始直播');
  784. return;
  785. }
  786. if (userStore.userInfo?.id === data.join_user_info?.id) {
  787. console.error('自己进入直播间,退出');
  788. return;
  789. }
  790. if (
  791. [
  792. LiveRoomTypeEnum.system,
  793. LiveRoomTypeEnum.srs,
  794. LiveRoomTypeEnum.obs,
  795. ].includes(data.live_room.type!)
  796. ) {
  797. return;
  798. }
  799. if (data.live_room.type === LiveRoomTypeEnum.wertc_live) {
  800. updateWebRtcLiveConfig({
  801. roomId: roomId.value,
  802. canvasVideoStream: canvasVideoStream.value,
  803. });
  804. data.socket_list.forEach((item) => {
  805. if (item !== mySocketId.value) {
  806. if (networkStore.rtcMap.get(item)) {
  807. return;
  808. }
  809. webRtcLive.newWebRtc({
  810. sender: mySocketId.value,
  811. receiver: item,
  812. videoEl: createNullVideo(),
  813. });
  814. webRtcLive.sendOffer({
  815. sender: mySocketId.value,
  816. receiver: item,
  817. });
  818. }
  819. });
  820. } else if (data.live_room.type === LiveRoomTypeEnum.wertc_meeting_one) {
  821. updateWebRtcMeetingOneConfig({
  822. roomId: roomId.value,
  823. anchorStream: canvasVideoStream.value,
  824. });
  825. // data.socket_list?.forEach((item) => {
  826. // if (item !== mySocketId.value) {
  827. // if (networkStore.rtcMap.get(item)) {
  828. // return;
  829. // }
  830. // webRtcMeetingOne.newWebRtc({
  831. // sender: mySocketId.value,
  832. // receiver: item,
  833. // videoEl: createNullVideo(),
  834. // });
  835. // webRtcMeetingOne.sendOffer({
  836. // sender: mySocketId.value,
  837. // receiver: item,
  838. // });
  839. // }
  840. // });
  841. } else if (data.live_room.type === LiveRoomTypeEnum.pk) {
  842. updateWebRtcMeetingPkConfig({
  843. roomId: roomId.value,
  844. anchorStream: canvasVideoStream.value,
  845. });
  846. // data.socket_list?.forEach((item) => {
  847. // if (item !== mySocketId.value) {
  848. // if (networkStore.rtcMap.get(item)) {
  849. // return;
  850. // }
  851. // webRtcMeetingPk.newWebRtc({
  852. // sender: mySocketId.value,
  853. // receiver: item,
  854. // videoEl: createNullVideo(),
  855. // });
  856. // webRtcMeetingPk.sendOffer({
  857. // sender: mySocketId.value,
  858. // receiver: item,
  859. // });
  860. // }
  861. // });
  862. } else if (data.live_room.type === LiveRoomTypeEnum.tencent_css_pk) {
  863. updateWebRtcMeetingPkConfig({
  864. roomId: roomId.value,
  865. anchorStream: canvasVideoStream.value,
  866. });
  867. data.socket_list?.forEach((item) => {
  868. if (item !== mySocketId.value) {
  869. if (networkStore.rtcMap.get(item)) {
  870. return;
  871. }
  872. webRtcMeetingPk.newWebRtc({
  873. sender: mySocketId.value,
  874. receiver: item,
  875. videoEl: createNullVideo(),
  876. });
  877. webRtcMeetingPk.sendOffer({
  878. sender: mySocketId.value,
  879. receiver: item,
  880. });
  881. }
  882. });
  883. }
  884. } else {
  885. // 当前不是推流页面
  886. }
  887. });
  888. // 用户离开房间
  889. ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
  890. prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
  891. });
  892. // 用户离开房间完成
  893. ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
  894. prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
  895. if (anchorSocketId.value === data.socket_id) {
  896. roomLiving.value = false;
  897. }
  898. networkStore.removeRtc(data.socket_id);
  899. damuList.value.push({
  900. request_id: '',
  901. socket_id: '',
  902. time: +new Date(),
  903. user_agent: navigator.userAgent,
  904. data: {
  905. live_room_id: Number(roomId.value),
  906. msg_id: -1,
  907. content: '',
  908. content_type: WsMessageContentTypeEnum.txt,
  909. msg_type: DanmuMsgTypeEnum.userLeaved,
  910. },
  911. });
  912. });
  913. }
  914. function initWs(data: {
  915. isAnchor: boolean;
  916. roomId: string;
  917. isBilibili?: boolean;
  918. isRemoteDesk?: boolean;
  919. currentResolutionRatio?: number;
  920. currentMaxFramerate?: number;
  921. currentMaxBitrate?: number;
  922. }) {
  923. roomId.value = data.roomId;
  924. isAnchor.value = data.isAnchor;
  925. if (data.isRemoteDesk !== undefined) {
  926. isRemoteDesk.value = data.isRemoteDesk;
  927. }
  928. if (data.isBilibili !== undefined) {
  929. isBilibili.value = data.isBilibili;
  930. }
  931. if (data.currentMaxBitrate !== undefined) {
  932. currentMaxBitrate.value = data.currentMaxBitrate;
  933. }
  934. if (data.currentMaxFramerate !== undefined) {
  935. currentMaxFramerate.value = data.currentMaxFramerate;
  936. }
  937. if (data.currentResolutionRatio !== undefined) {
  938. currentResolutionRatio.value = data.currentResolutionRatio;
  939. }
  940. new WebSocketClass({
  941. roomId: roomId.value,
  942. url: WEBSOCKET_URL,
  943. isAnchor: data.isAnchor,
  944. });
  945. initReceive();
  946. }
  947. return {
  948. initWs,
  949. handleStartLive,
  950. isBilibili,
  951. connectStatus,
  952. mySocketId,
  953. canvasVideoStream,
  954. lastCoverImg,
  955. roomLiving,
  956. anchorInfo,
  957. liveUserList,
  958. damuList,
  959. currentMaxFramerate,
  960. currentMaxBitrate,
  961. currentResolutionRatio,
  962. currentAudioContentHint,
  963. currentVideoContentHint,
  964. };
  965. };