use-websocket.ts 29 KB

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