use-websocket.ts 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  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. WsMessageMsgIsFileEnum,
  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. IDanmu,
  27. WSGetRoomAllUserType,
  28. WSLivePkKeyType,
  29. WsAnswerType,
  30. WsBatchSendOffer,
  31. WsCandidateType,
  32. WsConnectStatusEnum,
  33. WsDisableSpeakingType,
  34. WsHeartbeatType,
  35. WsJoinType,
  36. WsLeavedType,
  37. WsMessageType,
  38. WsMsgTypeEnum,
  39. WsOfferType,
  40. WsOtherJoinType,
  41. WsRemoteDeskBehaviorType,
  42. WsRoomLivingType,
  43. WsStartLiveType,
  44. WsStartRemoteDesk,
  45. WsUpdateJoinInfoType,
  46. } from '@/types/websocket';
  47. import {
  48. createNullVideo,
  49. handleUserMedia,
  50. setAudioTrackContentHints,
  51. setVideoTrackContentHints,
  52. } from '@/utils';
  53. import {
  54. WebSocketClass,
  55. prettierReceiveWsMsg,
  56. } from '@/utils/network/webSocket';
  57. import { useForwardAll } from './webrtc/forwardAll';
  58. import { useForwardBilibili } from './webrtc/forwardBilibili';
  59. import { useForwardHuya } from './webrtc/forwardHuya';
  60. import { useWebRtcRemoteDesk } from './webrtc/remoteDesk';
  61. export const useWebsocket = () => {
  62. const route = useRoute();
  63. const appStore = useAppStore();
  64. const userStore = useUserStore();
  65. const networkStore = useNetworkStore();
  66. const {
  67. maxBitrate,
  68. maxFramerate,
  69. resolutionRatio,
  70. videoContentHint,
  71. audioContentHint,
  72. } = useRTCParams();
  73. const { updateWebRtcRemoteDeskConfig, webRtcRemoteDesk } =
  74. useWebRtcRemoteDesk();
  75. const { updateWebRtcMeetingPkConfig, webRtcMeetingPk } = useWebRtcMeetingPk();
  76. const { updateWebRtcSrsConfig, webRtcSrs } = useWebRtcSrs();
  77. const { updateForwardBilibiliConfig, forwardBilibili } = useForwardBilibili();
  78. const { updateForwardAllConfig, forwardAll } = useForwardAll();
  79. const { updateForwardHuyaConfig, forwardHuya } = useForwardHuya();
  80. const { updateWebRtcTencentcloudCssConfig, webRtcTencentcloudCss } =
  81. useWebRtcTencentcloudCss();
  82. const { updateWebRtcLiveConfig, webRtcLive } = useWebRtcLive();
  83. const { updateWebRtcMeetingOneConfig, webRtcMeetingOne } =
  84. useWebRtcMeetingOne();
  85. const connectStatus = ref<WsConnectStatusEnum>();
  86. const loopHeartbeatTimer = ref();
  87. const loopGetLiveUserTimer = ref();
  88. const liveUserList = ref<ILiveUser[]>([]);
  89. const roomId = ref('');
  90. const roomLiving = ref(false);
  91. const isAnchor = ref(false);
  92. const isRemoteDesk = 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<IDanmu[]>([]);
  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(socketId: string) {
  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. socket_id: socketId,
  138. live_room_id: Number(roomId.value),
  139. roomLiving: isAnchor.value && roomLiving.value,
  140. },
  141. });
  142. }, 1000 * 5);
  143. }
  144. function handleStartLive({
  145. name,
  146. type,
  147. msrDelay,
  148. msrMaxDelay,
  149. }: {
  150. name?: string;
  151. type: LiveRoomTypeEnum;
  152. videoEl?: HTMLVideoElement;
  153. msrDelay: number;
  154. msrMaxDelay: number;
  155. }) {
  156. if (appStore.liveRoomInfo) {
  157. appStore.liveRoomInfo.type = type;
  158. }
  159. networkStore.wsMap.get(roomId.value)?.send<WsStartLiveType['data']>({
  160. requestId: getRandomString(8),
  161. msgType: WsMsgTypeEnum.startLive,
  162. data: {
  163. name: name!,
  164. type,
  165. msrDelay,
  166. msrMaxDelay,
  167. },
  168. });
  169. if (canvasVideoStream.value) {
  170. setVideoTrackContentHints(
  171. canvasVideoStream.value,
  172. // @ts-ignore
  173. currentVideoContentHint.value
  174. );
  175. setAudioTrackContentHints(
  176. canvasVideoStream.value,
  177. // @ts-ignore
  178. currentAudioContentHint.value
  179. );
  180. }
  181. if (type === LiveRoomTypeEnum.srs) {
  182. updateWebRtcSrsConfig({
  183. isPk: false,
  184. roomId: roomId.value,
  185. canvasVideoStream: canvasVideoStream.value,
  186. });
  187. webRtcSrs.newWebRtc({
  188. sender: mySocketId.value,
  189. receiver: 'srs',
  190. videoEl: createNullVideo(),
  191. });
  192. webRtcSrs.sendOffer({
  193. sender: mySocketId.value,
  194. receiver: 'srs',
  195. });
  196. } else if (type === LiveRoomTypeEnum.forward_bilibili) {
  197. updateForwardBilibiliConfig({
  198. isPk: false,
  199. roomId: roomId.value,
  200. canvasVideoStream: canvasVideoStream.value,
  201. });
  202. forwardBilibili.newWebRtc({
  203. sender: mySocketId.value,
  204. receiver: 'srs',
  205. videoEl: createNullVideo(),
  206. });
  207. forwardBilibili.sendOffer({
  208. sender: mySocketId.value,
  209. receiver: 'srs',
  210. });
  211. } else if (type === LiveRoomTypeEnum.forward_huya) {
  212. updateForwardHuyaConfig({
  213. isPk: false,
  214. roomId: roomId.value,
  215. canvasVideoStream: canvasVideoStream.value,
  216. });
  217. forwardHuya.newWebRtc({
  218. sender: mySocketId.value,
  219. receiver: 'srs',
  220. videoEl: createNullVideo(),
  221. });
  222. forwardHuya.sendOffer({
  223. sender: mySocketId.value,
  224. receiver: 'srs',
  225. });
  226. } else if (type === LiveRoomTypeEnum.forward_all) {
  227. updateForwardAllConfig({
  228. isPk: false,
  229. roomId: roomId.value,
  230. canvasVideoStream: canvasVideoStream.value,
  231. });
  232. forwardAll.newWebRtc({
  233. sender: mySocketId.value,
  234. receiver: 'srs',
  235. videoEl: createNullVideo(),
  236. });
  237. forwardAll.sendOffer({
  238. sender: mySocketId.value,
  239. receiver: 'srs',
  240. });
  241. } else if (type === LiveRoomTypeEnum.tencent_css) {
  242. updateWebRtcTencentcloudCssConfig({
  243. isPk: false,
  244. roomId: roomId.value,
  245. canvasVideoStream: canvasVideoStream.value,
  246. });
  247. webRtcTencentcloudCss.newWebRtc({
  248. sender: mySocketId.value,
  249. receiver: 'tencentcloud_css',
  250. videoEl: createNullVideo(),
  251. });
  252. webRtcTencentcloudCss.sendOffer({
  253. sender: mySocketId.value,
  254. receiver: 'tencentcloud_css',
  255. });
  256. } else if (type === LiveRoomTypeEnum.pk) {
  257. updateWebRtcSrsConfig({
  258. isPk: true,
  259. roomId: roomId.value,
  260. canvasVideoStream: canvasVideoStream.value,
  261. });
  262. webRtcSrs.newWebRtc({
  263. sender: mySocketId.value,
  264. receiver: 'srs',
  265. videoEl: createNullVideo(),
  266. });
  267. webRtcSrs.sendOffer({
  268. sender: mySocketId.value,
  269. receiver: 'srs',
  270. });
  271. } else if (type === LiveRoomTypeEnum.tencent_css_pk) {
  272. updateWebRtcTencentcloudCssConfig({
  273. isPk: true,
  274. roomId: roomId.value,
  275. canvasVideoStream: canvasVideoStream.value,
  276. });
  277. webRtcTencentcloudCss.newWebRtc({
  278. sender: mySocketId.value,
  279. receiver: 'tencentcloud_css',
  280. videoEl: createNullVideo(),
  281. });
  282. webRtcTencentcloudCss.sendOffer({
  283. sender: mySocketId.value,
  284. receiver: 'tencentcloud_css',
  285. });
  286. }
  287. }
  288. function sendJoin() {
  289. const instance = networkStore.wsMap.get(roomId.value);
  290. if (!instance) return;
  291. instance.send<WsJoinType['data']>({
  292. requestId: getRandomString(8),
  293. msgType: WsMsgTypeEnum.join,
  294. data: {
  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(ws.socketIo!.id!);
  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({
  593. user_agent: data.data.user_agent,
  594. live_room_id: data.data.live_room_id,
  595. request_id: data.request_id,
  596. socket_id: data.socket_id,
  597. msgType: data.data.msgType,
  598. msg: data.data.msg,
  599. userInfo: data.user_info,
  600. msgIsFile: data.data.msgIsFile,
  601. send_msg_time: data.data.send_msg_time,
  602. });
  603. });
  604. // 收到disableSpeaking
  605. ws.socketIo.on(
  606. WsMsgTypeEnum.disableSpeaking,
  607. (data: WsDisableSpeakingType['data']) => {
  608. prettierReceiveWsMsg(WsMsgTypeEnum.disableSpeaking, data);
  609. // if (data.is_disable_speaking) {
  610. // window.$message.error('你已被禁言!');
  611. // appStore.disableSpeaking.set(data.live_room_id, {
  612. // exp: data.disable_expired_at,
  613. // label: formatDownTime({
  614. // startTime: +new Date(),
  615. // endTime: data.disable_expired_at,
  616. // }),
  617. // });
  618. // clearTimeout(timerObj.value[data.live_room_id]);
  619. // timerObj.value[data.live_room_id] = setInterval(() => {
  620. // if (
  621. // data.disable_expired_at &&
  622. // +new Date() > data.disable_expired_at
  623. // ) {
  624. // clearTimeout(timerObj.value[data.live_room_id]);
  625. // }
  626. // appStore.disableSpeaking.set(data.live_room_id, {
  627. // exp: data.disable_expired_at!,
  628. // label: formatDownTime({
  629. // startTime: +new Date(),
  630. // endTime: data.disable_expired_at!,
  631. // }),
  632. // });
  633. // }, 1000);
  634. // damuList.value = damuList.value.filter(
  635. // (v) => v.request_id !== data.request_id
  636. // );
  637. // }
  638. if (data.user_id !== userStore.userInfo?.id && data.disable_ok) {
  639. window.$message.success('禁言成功!');
  640. }
  641. if (
  642. data.user_id !== userStore.userInfo?.id &&
  643. data.restore_disable_ok
  644. ) {
  645. window.$message.success('解除禁言成功!');
  646. }
  647. if (
  648. data.user_id === userStore.userInfo?.id &&
  649. data.restore_disable_ok
  650. ) {
  651. window.$message.success('禁言接触了!');
  652. clearTimeout(timerObj.value[data.live_room_id]);
  653. appStore.disableSpeaking.delete(data.live_room_id);
  654. }
  655. }
  656. );
  657. async function handleMeeting() {
  658. await useTip({
  659. content: '是否加入会议?',
  660. });
  661. const stream = await handleUserMedia({
  662. video: true,
  663. audio: true,
  664. });
  665. console.log(stream?.getAudioTracks());
  666. console.log(stream?.getVideoTracks(), 1111);
  667. userStream.value = stream;
  668. networkStore.wsMap.get(roomId.value)?.send<WsBatchSendOffer['data']>({
  669. requestId: getRandomString(8),
  670. msgType: WsMsgTypeEnum.batchSendOffer,
  671. data: {
  672. roomId: roomId.value,
  673. },
  674. });
  675. }
  676. async function handlePk() {
  677. if (!route.query.pkKey) {
  678. return;
  679. }
  680. const res = await fetchVerifyPkKey({
  681. liveRoomId: Number(roomId.value),
  682. key: route.query.pkKey,
  683. });
  684. if (res.code === 200 && res.data === true) {
  685. await useTip({
  686. content: '是否加入PK?',
  687. });
  688. const stream = await handleUserMedia({
  689. video: true,
  690. audio: true,
  691. });
  692. userStream.value = stream;
  693. networkStore.wsMap.get(roomId.value)?.send<WsBatchSendOffer['data']>({
  694. requestId: getRandomString(8),
  695. msgType: WsMsgTypeEnum.batchSendOffer,
  696. data: {
  697. roomId: roomId.value,
  698. },
  699. });
  700. } else {
  701. await useTip({
  702. content: '加入PK失败,验证pkKey错误!',
  703. hiddenCancel: true,
  704. hiddenClose: true,
  705. });
  706. }
  707. }
  708. // 用户加入房间完成
  709. ws.socketIo.on(WsMsgTypeEnum.joined, async (data: WsJoinType['data']) => {
  710. prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
  711. appStore.setLiveRoomInfo(data.live_room);
  712. anchorInfo.value = data.anchor_info;
  713. if (route.name === routerName.pull || route.name === routerName.h5Room) {
  714. // 当前是拉流页面
  715. if (
  716. roomLiving.value &&
  717. data.live_room?.type === LiveRoomTypeEnum.wertc_meeting_one
  718. ) {
  719. await handleMeeting();
  720. } else if (
  721. roomLiving.value &&
  722. data.live_room?.type === LiveRoomTypeEnum.pk
  723. ) {
  724. await handlePk();
  725. }
  726. }
  727. });
  728. // batchSendOffer
  729. ws.socketIo.on(
  730. WsMsgTypeEnum.batchSendOffer,
  731. (data: WsBatchSendOffer['data']) => {
  732. if (
  733. appStore.liveRoomInfo?.type === LiveRoomTypeEnum.wertc_meeting_one
  734. ) {
  735. data.socket_list?.forEach((item) => {
  736. if (item !== mySocketId.value) {
  737. if (networkStore.rtcMap.get(item)) {
  738. return;
  739. }
  740. webRtcMeetingOne.newWebRtc({
  741. sender: mySocketId.value,
  742. receiver: item,
  743. videoEl: createNullVideo(),
  744. });
  745. webRtcMeetingOne.sendOffer({
  746. sender: mySocketId.value,
  747. receiver: item,
  748. });
  749. }
  750. });
  751. } else if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.pk) {
  752. data.socket_list?.forEach((item) => {
  753. if (item !== mySocketId.value) {
  754. if (networkStore.rtcMap.get(item)) {
  755. return;
  756. }
  757. webRtcMeetingPk.newWebRtc({
  758. sender: mySocketId.value,
  759. receiver: item,
  760. videoEl: createNullVideo(),
  761. });
  762. webRtcMeetingPk.sendOffer({
  763. sender: mySocketId.value,
  764. receiver: item,
  765. });
  766. }
  767. });
  768. }
  769. }
  770. );
  771. // 其他用户加入房间
  772. ws.socketIo.on(WsMsgTypeEnum.otherJoin, (data: WsOtherJoinType['data']) => {
  773. prettierReceiveWsMsg(WsMsgTypeEnum.otherJoin, data);
  774. const requestId = getRandomString(8);
  775. const danmu: IDanmu = {
  776. live_room_id: data.live_room.id!,
  777. request_id: requestId,
  778. msgType: DanmuMsgTypeEnum.otherJoin,
  779. socket_id: data.join_socket_id,
  780. userInfo: data.join_user_info,
  781. msgIsFile: WsMessageMsgIsFileEnum.no,
  782. msg: '',
  783. send_msg_time: +new Date(),
  784. };
  785. damuList.value.push(danmu);
  786. if (route.name === routerName.push) {
  787. // 当前是推流页面
  788. if (!isAnchor.value) {
  789. console.error('不是主播');
  790. return;
  791. }
  792. if (!roomLiving.value) {
  793. console.error('主播没点开始直播');
  794. return;
  795. }
  796. if (userStore.userInfo?.id === data.join_user_info?.id) {
  797. console.error('自己进入直播间,退出');
  798. return;
  799. }
  800. if (
  801. [
  802. LiveRoomTypeEnum.system,
  803. LiveRoomTypeEnum.srs,
  804. LiveRoomTypeEnum.obs,
  805. ].includes(data.live_room.type!)
  806. ) {
  807. return;
  808. }
  809. if (data.live_room.type === LiveRoomTypeEnum.wertc_live) {
  810. updateWebRtcLiveConfig({
  811. roomId: roomId.value,
  812. canvasVideoStream: canvasVideoStream.value,
  813. });
  814. data.socket_list.forEach((item) => {
  815. if (item !== mySocketId.value) {
  816. if (networkStore.rtcMap.get(item)) {
  817. return;
  818. }
  819. webRtcLive.newWebRtc({
  820. sender: mySocketId.value,
  821. receiver: item,
  822. videoEl: createNullVideo(),
  823. });
  824. webRtcLive.sendOffer({
  825. sender: mySocketId.value,
  826. receiver: item,
  827. });
  828. }
  829. });
  830. } else if (data.live_room.type === LiveRoomTypeEnum.wertc_meeting_one) {
  831. updateWebRtcMeetingOneConfig({
  832. roomId: roomId.value,
  833. anchorStream: canvasVideoStream.value,
  834. });
  835. // data.socket_list?.forEach((item) => {
  836. // if (item !== mySocketId.value) {
  837. // if (networkStore.rtcMap.get(item)) {
  838. // return;
  839. // }
  840. // webRtcMeetingOne.newWebRtc({
  841. // sender: mySocketId.value,
  842. // receiver: item,
  843. // videoEl: createNullVideo(),
  844. // });
  845. // webRtcMeetingOne.sendOffer({
  846. // sender: mySocketId.value,
  847. // receiver: item,
  848. // });
  849. // }
  850. // });
  851. } else if (data.live_room.type === LiveRoomTypeEnum.pk) {
  852. updateWebRtcMeetingPkConfig({
  853. roomId: roomId.value,
  854. anchorStream: canvasVideoStream.value,
  855. });
  856. // data.socket_list?.forEach((item) => {
  857. // if (item !== mySocketId.value) {
  858. // if (networkStore.rtcMap.get(item)) {
  859. // return;
  860. // }
  861. // webRtcMeetingPk.newWebRtc({
  862. // sender: mySocketId.value,
  863. // receiver: item,
  864. // videoEl: createNullVideo(),
  865. // });
  866. // webRtcMeetingPk.sendOffer({
  867. // sender: mySocketId.value,
  868. // receiver: item,
  869. // });
  870. // }
  871. // });
  872. } else if (data.live_room.type === LiveRoomTypeEnum.tencent_css_pk) {
  873. updateWebRtcMeetingPkConfig({
  874. roomId: roomId.value,
  875. anchorStream: canvasVideoStream.value,
  876. });
  877. data.socket_list?.forEach((item) => {
  878. if (item !== mySocketId.value) {
  879. if (networkStore.rtcMap.get(item)) {
  880. return;
  881. }
  882. webRtcMeetingPk.newWebRtc({
  883. sender: mySocketId.value,
  884. receiver: item,
  885. videoEl: createNullVideo(),
  886. });
  887. webRtcMeetingPk.sendOffer({
  888. sender: mySocketId.value,
  889. receiver: item,
  890. });
  891. }
  892. });
  893. }
  894. } else {
  895. // 当前不是推流页面
  896. }
  897. });
  898. // 用户离开房间
  899. ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
  900. prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
  901. });
  902. // 用户离开房间完成
  903. ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
  904. prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
  905. if (anchorSocketId.value === data.socket_id) {
  906. roomLiving.value = false;
  907. }
  908. networkStore.removeRtc(data.socket_id);
  909. damuList.value.push({
  910. live_room_id: Number(roomId.value),
  911. socket_id: data.socket_id,
  912. msgType: DanmuMsgTypeEnum.userLeaved,
  913. msgIsFile: WsMessageMsgIsFileEnum.no,
  914. userInfo: data.user_info,
  915. msg: '',
  916. send_msg_time: +new Date(),
  917. });
  918. });
  919. }
  920. function initWs(data: {
  921. isAnchor: boolean;
  922. roomId: string;
  923. isRemoteDesk?: boolean;
  924. currentResolutionRatio?: number;
  925. currentMaxFramerate?: number;
  926. currentMaxBitrate?: number;
  927. }) {
  928. roomId.value = data.roomId;
  929. isAnchor.value = data.isAnchor;
  930. if (data.isRemoteDesk) {
  931. isRemoteDesk.value = data.isRemoteDesk;
  932. }
  933. if (data.currentMaxBitrate) {
  934. currentMaxBitrate.value = data.currentMaxBitrate;
  935. }
  936. if (data.currentMaxFramerate) {
  937. currentMaxFramerate.value = data.currentMaxFramerate;
  938. }
  939. if (data.currentResolutionRatio) {
  940. currentResolutionRatio.value = data.currentResolutionRatio;
  941. }
  942. new WebSocketClass({
  943. roomId: roomId.value,
  944. url: WEBSOCKET_URL,
  945. isAnchor: data.isAnchor,
  946. });
  947. initReceive();
  948. }
  949. return {
  950. initWs,
  951. handleStartLive,
  952. connectStatus,
  953. mySocketId,
  954. canvasVideoStream,
  955. lastCoverImg,
  956. roomLiving,
  957. anchorInfo,
  958. liveUserList,
  959. damuList,
  960. currentMaxFramerate,
  961. currentMaxBitrate,
  962. currentResolutionRatio,
  963. currentAudioContentHint,
  964. currentVideoContentHint,
  965. };
  966. };