use-ws.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. import { getRandomString } from 'billd-utils';
  2. import { onUnmounted, reactive, ref, watch } from 'vue';
  3. import { fetchRtcV1Play, fetchRtcV1Publish } from '@/api/srs';
  4. import { WEBSOCKET_URL } from '@/constant';
  5. import {
  6. DanmuMsgTypeEnum,
  7. IDanmu,
  8. ILiveRoom,
  9. ILiveUser,
  10. IUser,
  11. LiveRoomTypeEnum,
  12. liveTypeEnum,
  13. } from '@/interface';
  14. import {
  15. WSGetRoomAllUserType,
  16. WsAnswerType,
  17. WsCandidateType,
  18. WsGetLiveUserType,
  19. WsHeartbeatType,
  20. WsJoinType,
  21. WsLeavedType,
  22. WsMessageType,
  23. WsOfferType,
  24. WsOtherJoinType,
  25. WsRoomLivingType,
  26. WsUpdateJoinInfoType,
  27. } from '@/interface-ws';
  28. import { WebRTCClass } from '@/network/webRTC';
  29. import {
  30. WebSocketClass,
  31. WsConnectStatusEnum,
  32. WsMsgTypeEnum,
  33. prettierReceiveWsMsg,
  34. } from '@/network/webSocket';
  35. import { AppRootState, useAppStore } from '@/store/app';
  36. import { useNetworkStore } from '@/store/network';
  37. import { useUserStore } from '@/store/user';
  38. import { createVideo } from '@/utils';
  39. export const useWs = () => {
  40. const appStore = useAppStore();
  41. const userStore = useUserStore();
  42. const networkStore = useNetworkStore();
  43. const loopHeartbeatTimer = ref();
  44. const liveUserList = ref<ILiveUser[]>([]);
  45. const roomId = ref('');
  46. const roomName = ref('');
  47. const roomNoLive = ref(false);
  48. const roomLiving = ref(false);
  49. const liveRoomInfo = ref<ILiveRoom>();
  50. const anchorInfo = ref<IUser>();
  51. const isAnchor = ref(false);
  52. const roomLiveType = ref(liveTypeEnum.srsFlvPull);
  53. const joined = ref(false);
  54. const isSRS = ref(false);
  55. const isPull = ref(false);
  56. const trackInfo = reactive({ track_audio: 1, track_video: 1 });
  57. const localVideo = ref<HTMLVideoElement>(document.createElement('video'));
  58. const localStream = ref<MediaStream>();
  59. const canvasVideoStream = ref<MediaStream>();
  60. const lastCoverImg = ref('');
  61. const maxBitrate = ref([
  62. {
  63. label: '1',
  64. value: 1,
  65. },
  66. {
  67. label: '10',
  68. value: 10,
  69. },
  70. {
  71. label: '1000',
  72. value: 1000,
  73. },
  74. {
  75. label: '2000',
  76. value: 2000,
  77. },
  78. {
  79. label: '3000',
  80. value: 3000,
  81. },
  82. {
  83. label: '4000',
  84. value: 4000,
  85. },
  86. {
  87. label: '5000',
  88. value: 5000,
  89. },
  90. {
  91. label: '6000',
  92. value: 6000,
  93. },
  94. {
  95. label: '7000',
  96. value: 7000,
  97. },
  98. {
  99. label: '8000',
  100. value: 8000,
  101. },
  102. {
  103. label: '9000',
  104. value: 9000,
  105. },
  106. {
  107. label: '10000',
  108. value: 10000,
  109. },
  110. ]);
  111. const maxFramerate = ref([
  112. {
  113. label: '1帧',
  114. value: 1,
  115. },
  116. {
  117. label: '10帧',
  118. value: 10,
  119. },
  120. {
  121. label: '20帧',
  122. value: 20,
  123. },
  124. {
  125. label: '30帧',
  126. value: 30,
  127. },
  128. {
  129. label: '60帧',
  130. value: 60,
  131. },
  132. ]);
  133. const resolutionRatio = ref([
  134. {
  135. label: '360P',
  136. value: 360,
  137. },
  138. {
  139. label: '540P',
  140. value: 540,
  141. },
  142. {
  143. label: '720P',
  144. value: 720,
  145. },
  146. {
  147. label: '1080P',
  148. value: 1080,
  149. },
  150. {
  151. label: '1440P',
  152. value: 1440,
  153. },
  154. ]);
  155. const currentMaxBitrate = ref(maxBitrate.value[2].value);
  156. const currentResolutionRatio = ref(resolutionRatio.value[3].value);
  157. const currentMaxFramerate = ref(maxFramerate.value[2].value);
  158. const damuList = ref<IDanmu[]>([]);
  159. watch(
  160. () => appStore.allTrack,
  161. (newTrack, oldTrack) => {
  162. console.log('appStore.allTrack变了', newTrack, oldTrack);
  163. const mixedStream = new MediaStream();
  164. newTrack.forEach((item) => {
  165. if (item.track) {
  166. mixedStream.addTrack(item.track);
  167. }
  168. });
  169. console.log('新的allTrack音频轨', mixedStream.getAudioTracks());
  170. console.log('新的allTrack视频轨', mixedStream.getVideoTracks());
  171. console.log('旧的allTrack音频轨', localStream.value?.getAudioTracks());
  172. console.log('旧的allTrack视频轨', localStream.value?.getVideoTracks());
  173. localStream.value = mixedStream;
  174. // if (isSRS.value) {
  175. // if (!isPull.value) {
  176. // networkStore.rtcMap.forEach((rtc) => {
  177. // rtc.close();
  178. // });
  179. // startNewWebRtc({
  180. // receiver: 'srs',
  181. // videoEl: localVideo.value,
  182. // });
  183. // }
  184. // }
  185. },
  186. { deep: true }
  187. );
  188. onUnmounted(() => {
  189. clearInterval(loopHeartbeatTimer.value);
  190. });
  191. watch(
  192. () => currentResolutionRatio.value,
  193. (newVal) => {
  194. if (canvasVideoStream.value) {
  195. canvasVideoStream.value.getVideoTracks().forEach((track) => {
  196. track.applyConstraints({
  197. frameRate: { max: currentMaxFramerate.value },
  198. height: newVal,
  199. });
  200. });
  201. } else {
  202. appStore.allTrack.forEach((info) => {
  203. info.track?.applyConstraints({
  204. frameRate: { max: currentMaxFramerate.value },
  205. height: newVal,
  206. });
  207. });
  208. }
  209. networkStore.rtcMap.forEach(async (rtc) => {
  210. const res = await rtc.setResolutionRatio(newVal);
  211. if (res === 1) {
  212. window.$message.success('切换分辨率成功!');
  213. } else {
  214. window.$message.success('切换分辨率失败!');
  215. }
  216. });
  217. }
  218. );
  219. watch(
  220. () => currentMaxFramerate.value,
  221. (newVal) => {
  222. console.log(currentMaxFramerate.value, 'currentMaxFramerate.value');
  223. if (canvasVideoStream.value) {
  224. canvasVideoStream.value.getVideoTracks().forEach((track) => {
  225. track.applyConstraints({
  226. frameRate: { max: newVal },
  227. height: currentResolutionRatio.value,
  228. });
  229. });
  230. } else {
  231. appStore.allTrack.forEach((info) => {
  232. info.track?.applyConstraints({
  233. frameRate: { max: newVal },
  234. height: currentResolutionRatio.value,
  235. });
  236. });
  237. }
  238. networkStore.rtcMap.forEach(async (rtc) => {
  239. const res = await rtc.setMaxFramerate(newVal);
  240. if (res === 1) {
  241. window.$message.success('切换帧率成功!');
  242. } else {
  243. window.$message.success('切换帧率失败!');
  244. }
  245. });
  246. }
  247. );
  248. watch(
  249. () => currentMaxBitrate.value,
  250. (newVal) => {
  251. networkStore.rtcMap.forEach(async (rtc) => {
  252. const res = await rtc.setMaxBitrate(newVal);
  253. if (res === 1) {
  254. window.$message.success('切换码率成功!');
  255. } else {
  256. window.$message.success('切换码率失败!');
  257. }
  258. });
  259. }
  260. );
  261. function addTrack(addTrackInfo: { track; stream }) {
  262. if (isAnchor.value) {
  263. networkStore.rtcMap.forEach((rtc) => {
  264. const sender = rtc.peerConnection
  265. ?.getSenders()
  266. .find((sender) => sender.track?.id === addTrackInfo.track?.id);
  267. if (!sender) {
  268. console.log('pc添加track-开播后中途添加', addTrackInfo.track?.id);
  269. // vel.srcObject = destination.stream;
  270. // canvasVideoStream.value!.getAudioTracks()[0] =
  271. // destination.stream.getAudioTracks()[0];
  272. // rtc.peerConnection?.addTrack(addTrackInfo.track, addTrackInfo.stream);
  273. rtc.peerConnection
  274. ?.getSenders()
  275. ?.find((sender) => sender.track?.kind === 'audio')
  276. ?.replaceTrack(canvasVideoStream.value!.getAudioTracks()[0]);
  277. const vel = createVideo({});
  278. vel.srcObject = canvasVideoStream.value!;
  279. // document.body.appendChild(vel);
  280. console.log(
  281. rtc.peerConnection
  282. ?.getSenders()
  283. ?.find((sender) => sender.track?.kind === 'audio'),
  284. 8888,
  285. rtc.peerConnection?.getSenders()
  286. );
  287. }
  288. });
  289. }
  290. const mixedStream = new MediaStream();
  291. appStore.allTrack.forEach((item) => {
  292. if (item.track) {
  293. mixedStream.addTrack(item.track);
  294. }
  295. });
  296. console.log('addTrack后结果的音频轨', mixedStream.getAudioTracks());
  297. console.log('addTrack后结果的视频轨', mixedStream.getVideoTracks());
  298. localStream.value = mixedStream;
  299. // srs不需要更新,因为更新了之后,跟着就关闭当前rtc然后重新new一个新的rtc了
  300. if (!isSRS.value) {
  301. let resUrl = '';
  302. const rtmpUrl = userStore.userInfo?.live_rooms?.[0].rtmp_url!;
  303. if (rtmpUrl.indexOf('type=') === -1) {
  304. resUrl += `${rtmpUrl}&type=${
  305. isSRS.value ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc
  306. }`;
  307. } else {
  308. resUrl = rtmpUrl.replace(
  309. /type=([0-9]+)/,
  310. `type=${
  311. isSRS.value
  312. ? LiveRoomTypeEnum.user_srs
  313. : LiveRoomTypeEnum.user_wertc
  314. }`
  315. );
  316. }
  317. const data: WsUpdateJoinInfoType['data'] = {
  318. live_room_id: Number(roomId.value),
  319. track: {
  320. audio: appStore.getTrackInfo().audio > 0 ? 1 : 2,
  321. video: appStore.getTrackInfo().video > 0 ? 1 : 2,
  322. },
  323. rtmp_url: resUrl,
  324. };
  325. networkStore.wsMap.get(roomId.value)?.send({
  326. msgType: WsMsgTypeEnum.updateJoinInfo,
  327. data,
  328. });
  329. }
  330. }
  331. function delTrack(delTrackInfo: AppRootState['allTrack'][0]) {
  332. if (isAnchor.value) {
  333. networkStore.rtcMap.forEach((rtc) => {
  334. const sender = rtc.peerConnection
  335. ?.getSenders()
  336. .find((sender) => sender.track?.id === delTrackInfo.track?.id);
  337. if (sender) {
  338. console.log('删除track', delTrackInfo, sender);
  339. rtc.peerConnection?.removeTrack(sender);
  340. }
  341. });
  342. }
  343. const mixedStream = new MediaStream();
  344. appStore.allTrack.forEach((item) => {
  345. if (item.track) {
  346. mixedStream.addTrack(item.track);
  347. }
  348. });
  349. console.log('delTrack后结果的音频轨', mixedStream.getAudioTracks());
  350. console.log('delTrack后结果的视频轨', mixedStream.getVideoTracks());
  351. localStream.value = mixedStream;
  352. if (!isSRS.value) {
  353. let resUrl = '';
  354. const rtmpUrl = userStore.userInfo?.live_rooms?.[0].rtmp_url!;
  355. if (rtmpUrl.indexOf('type=') === -1) {
  356. resUrl += `${rtmpUrl}&type=${
  357. isSRS.value ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc
  358. }`;
  359. } else {
  360. resUrl = rtmpUrl.replace(
  361. /type=([0-9]+)/,
  362. `type=${
  363. isSRS.value
  364. ? LiveRoomTypeEnum.user_srs
  365. : LiveRoomTypeEnum.user_wertc
  366. }`
  367. );
  368. }
  369. const data: WsUpdateJoinInfoType['data'] = {
  370. live_room_id: Number(roomId.value),
  371. track: {
  372. audio: appStore.getTrackInfo().audio > 0 ? 1 : 2,
  373. video: appStore.getTrackInfo().video > 0 ? 1 : 2,
  374. },
  375. rtmp_url: resUrl,
  376. };
  377. networkStore.wsMap.get(roomId.value)?.send({
  378. msgType: WsMsgTypeEnum.updateJoinInfo,
  379. data,
  380. });
  381. }
  382. }
  383. function getSocketId() {
  384. return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
  385. }
  386. function handleHeartbeat(socketId: string) {
  387. loopHeartbeatTimer.value = setInterval(() => {
  388. const ws = networkStore.wsMap.get(roomId.value);
  389. if (!ws) return;
  390. ws.send<WsHeartbeatType['data']>({
  391. msgType: WsMsgTypeEnum.heartbeat,
  392. data: {
  393. socket_id: socketId,
  394. },
  395. });
  396. }, 1000 * 5);
  397. }
  398. async function sendOffer({
  399. sender,
  400. receiver,
  401. }: {
  402. sender: string;
  403. receiver: string;
  404. }) {
  405. console.log('开始sendOffer');
  406. const ws = networkStore.wsMap.get(roomId.value);
  407. if (!ws) return;
  408. const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
  409. if (!rtc) return;
  410. if (!isSRS.value) {
  411. const sdp = await rtc.createOffer();
  412. await rtc.setLocalDescription(sdp!);
  413. ws.send({
  414. msgType: WsMsgTypeEnum.offer,
  415. data: {
  416. sdp,
  417. sender,
  418. receiver,
  419. live_room_id: roomId.value,
  420. },
  421. });
  422. } else {
  423. const sdp = await rtc.createOffer();
  424. await rtc.setLocalDescription(sdp!);
  425. let res;
  426. if (isPull.value) {
  427. res = await fetchRtcV1Play({
  428. api: `/rtc/v1/play/`,
  429. clientip: null,
  430. sdp: sdp!.sdp!,
  431. streamurl: liveRoomInfo.value!.rtmp_url!.replace('rtmp', 'webrtc'),
  432. tid: getRandomString(10),
  433. });
  434. } else {
  435. res = await fetchRtcV1Publish({
  436. api: `/rtc/v1/publish/`,
  437. clientip: null,
  438. sdp: sdp!.sdp!,
  439. streamurl: userStore.userInfo!.live_rooms![0]!.rtmp_url!.replace(
  440. 'rtmp',
  441. 'webrtc'
  442. ),
  443. tid: getRandomString(10),
  444. });
  445. const data: WsUpdateJoinInfoType['data'] = {
  446. live_room_id: Number(roomId.value),
  447. track: {
  448. audio: appStore.getTrackInfo().audio > 0 ? 1 : 2,
  449. video: appStore.getTrackInfo().video > 0 ? 1 : 2,
  450. },
  451. };
  452. networkStore.wsMap.get(roomId.value)?.send({
  453. msgType: WsMsgTypeEnum.updateJoinInfo,
  454. data,
  455. });
  456. }
  457. if (res.data.code !== 0) {
  458. console.error('/rtc/v1/publish/拿不到sdp');
  459. window.$message.error('/rtc/v1/publish/拿不到sdp');
  460. return;
  461. }
  462. await rtc.setRemoteDescription(
  463. new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
  464. );
  465. }
  466. }
  467. function sendStartLive() {
  468. networkStore.wsMap.get(roomId.value)?.send({
  469. msgType: WsMsgTypeEnum.startLive,
  470. });
  471. }
  472. function sendJoin() {
  473. const instance = networkStore.wsMap.get(roomId.value);
  474. if (!instance) return;
  475. let resUrl = '';
  476. const rtmpUrl = userStore.userInfo?.live_rooms?.[0].rtmp_url;
  477. // 如果是用户看直播,发送join时不需要rtmpUrl;只有房主直播的时候需要带rtmpUrl
  478. if (rtmpUrl) {
  479. if (rtmpUrl.indexOf('type=') === -1) {
  480. resUrl += `${rtmpUrl}&type=${
  481. isSRS.value ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc
  482. }`;
  483. } else {
  484. resUrl = rtmpUrl.replace(
  485. /type=([0-9]+)/,
  486. `type=${
  487. isSRS.value
  488. ? LiveRoomTypeEnum.user_srs
  489. : LiveRoomTypeEnum.user_wertc
  490. }`
  491. );
  492. }
  493. }
  494. instance.send<WsJoinType['data']>({
  495. msgType: WsMsgTypeEnum.join,
  496. data: {
  497. socket_id: getSocketId(),
  498. live_room: {
  499. id: Number(roomId.value),
  500. name: roomName.value,
  501. cover_img: lastCoverImg.value,
  502. type: isSRS.value
  503. ? LiveRoomTypeEnum.user_srs
  504. : LiveRoomTypeEnum.user_wertc,
  505. rtmp_url: resUrl,
  506. },
  507. },
  508. });
  509. }
  510. function handleNegotiationneeded(data: { roomId: string; isSRS: boolean }) {
  511. console.warn(`${data.roomId},开始监听pc的negotiationneeded`);
  512. const rtc = networkStore.getRtcMap(data.roomId);
  513. if (!rtc) return;
  514. console.warn(`监听pc的negotiationneeded`);
  515. rtc.peerConnection?.addEventListener('negotiationneeded', (event) => {
  516. console.warn(`${data.roomId},pc收到negotiationneeded`, event);
  517. sendOffer({
  518. sender: getSocketId(),
  519. receiver: rtc.receiver,
  520. });
  521. });
  522. }
  523. /** 原生的webrtc时,receiver必传 */
  524. function startNewWebRtc({
  525. receiver,
  526. videoEl,
  527. }: {
  528. receiver: string;
  529. videoEl: HTMLVideoElement;
  530. }) {
  531. let rtc: WebRTCClass;
  532. if (isSRS.value) {
  533. console.warn('SRS开始new WebRTCClass', `${roomId.value}___${receiver!}`);
  534. rtc = new WebRTCClass({
  535. maxBitrate: isPull.value ? -1 : currentMaxBitrate.value,
  536. maxFramerate: isPull.value ? -1 : currentMaxFramerate.value,
  537. resolutionRatio: isPull.value ? -1 : currentResolutionRatio.value,
  538. roomId: `${roomId.value}___${receiver!}`,
  539. videoEl,
  540. isSRS: true,
  541. receiver,
  542. });
  543. if (isPull.value) {
  544. if (trackInfo.track_video === 1) {
  545. rtc.peerConnection?.addTransceiver('video', {
  546. direction: 'recvonly',
  547. });
  548. }
  549. if (trackInfo.track_audio === 1) {
  550. rtc.peerConnection?.addTransceiver('audio', {
  551. direction: 'recvonly',
  552. });
  553. }
  554. }
  555. // handleNegotiationneeded({
  556. // roomId: `${roomId.value}___${receiver}`,
  557. // isSRS: true,
  558. // });
  559. if (canvasVideoStream.value) {
  560. localStream.value = canvasVideoStream.value;
  561. }
  562. rtc.localStream = localStream.value;
  563. localStream.value?.getTracks().forEach((track) => {
  564. console.warn(
  565. 'srs startNewWebRtc,pc插入track',
  566. track.id,
  567. localStream.value?.id
  568. );
  569. console.log('pc添加track-srs', track.kind, track.id);
  570. rtc.peerConnection?.addTrack(track, localStream.value!);
  571. });
  572. sendOffer({
  573. sender: getSocketId(),
  574. receiver,
  575. });
  576. } else {
  577. console.warn('开始new WebRTCClass', `${roomId.value}___${receiver!}`);
  578. rtc = new WebRTCClass({
  579. maxBitrate: isPull.value ? -1 : currentMaxBitrate.value,
  580. maxFramerate: isPull.value ? -1 : currentMaxFramerate.value,
  581. resolutionRatio: isPull.value ? -1 : currentResolutionRatio.value,
  582. roomId: `${roomId.value}___${receiver!}`,
  583. videoEl,
  584. isSRS: false,
  585. receiver,
  586. });
  587. if (isAnchor.value) {
  588. handleNegotiationneeded({
  589. roomId: `${roomId.value}___${receiver}`,
  590. isSRS: false,
  591. });
  592. rtc.localStream = localStream.value;
  593. localStream.value?.getTracks().forEach((track) => {
  594. // rtc.peerConnection?.addTransceiver(track, {
  595. // streams: [localStream.value!],
  596. // direction: 'sendonly',
  597. // });
  598. console.log('pc添加track-原生');
  599. rtc.peerConnection?.addTrack(track, localStream.value!);
  600. });
  601. }
  602. }
  603. return rtc;
  604. }
  605. function initReceive() {
  606. const ws = networkStore.wsMap.get(roomId.value);
  607. if (!ws?.socketIo) return;
  608. // websocket连接成功
  609. ws.socketIo.on(WsConnectStatusEnum.connect, () => {
  610. prettierReceiveWsMsg(WsConnectStatusEnum.connect, ws.socketIo);
  611. handleHeartbeat(ws.socketIo!.id);
  612. if (!ws) return;
  613. ws.status = WsConnectStatusEnum.connect;
  614. ws.update();
  615. sendJoin();
  616. });
  617. // websocket连接断开
  618. ws.socketIo.on(WsConnectStatusEnum.disconnect, () => {
  619. prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws);
  620. if (!ws) return;
  621. ws.status = WsConnectStatusEnum.disconnect;
  622. ws.update();
  623. });
  624. // 收到offer
  625. ws.socketIo.on(WsMsgTypeEnum.offer, async (data: WsOfferType) => {
  626. prettierReceiveWsMsg(
  627. WsMsgTypeEnum.offer,
  628. `发送者:${data.data.sender},接收者:${data.data.receiver}`,
  629. data
  630. );
  631. if (isSRS.value) return;
  632. if (!ws) return;
  633. if (data.data.receiver === getSocketId()) {
  634. console.log('收到offer,这个offer是发给我的');
  635. if (!isAnchor.value) {
  636. // 如果是用户进来看直播
  637. let rtc = networkStore.getRtcMap(
  638. `${roomId.value}___${data.data.sender}`
  639. );
  640. if (!rtc) {
  641. rtc = await startNewWebRtc({
  642. receiver: data.data.sender,
  643. videoEl: localVideo.value,
  644. });
  645. }
  646. await rtc.setRemoteDescription(data.data.sdp);
  647. const sdp = await rtc.createAnswer();
  648. await rtc.setLocalDescription(sdp!);
  649. const answerData: WsAnswerType['data'] = {
  650. sdp,
  651. sender: getSocketId(),
  652. receiver: data.data.sender,
  653. live_room_id: data.data.live_room_id,
  654. };
  655. ws.send({
  656. msgType: WsMsgTypeEnum.answer,
  657. data: answerData,
  658. });
  659. }
  660. } else {
  661. console.log('收到offer,但是这个offer不是发给我的');
  662. }
  663. });
  664. // 收到answer
  665. ws.socketIo.on(WsMsgTypeEnum.answer, async (data: WsOfferType) => {
  666. prettierReceiveWsMsg(
  667. WsMsgTypeEnum.answer,
  668. `发送者:${data.data.sender},接收者:${data.data.receiver}`,
  669. data
  670. );
  671. if (isSRS.value) return;
  672. if (!ws) return;
  673. const rtc = networkStore.getRtcMap(`${roomId.value}___${data.socket_id}`);
  674. if (!rtc) return;
  675. rtc.update();
  676. if (data.data.receiver === getSocketId()) {
  677. console.log('收到answer,这个answer是发给我的');
  678. await rtc.setRemoteDescription(data.data.sdp);
  679. } else {
  680. console.log('收到answer,但这个answer不是发给我的');
  681. }
  682. });
  683. // 收到candidate
  684. ws.socketIo.on(WsMsgTypeEnum.candidate, (data: WsCandidateType['data']) => {
  685. prettierReceiveWsMsg(
  686. WsMsgTypeEnum.candidate,
  687. `发送者:${data.sender},接收者:${data.receiver}`,
  688. data
  689. );
  690. if (isSRS.value) return;
  691. if (!ws) return;
  692. const rtc = networkStore.getRtcMap(`${roomId.value}___${data.sender}`);
  693. if (!rtc) return;
  694. if (data.sender !== getSocketId()) {
  695. console.log('不是我发的candidate');
  696. const candidate = new RTCIceCandidate({
  697. sdpMid: data.sdpMid,
  698. sdpMLineIndex: data.sdpMLineIndex,
  699. candidate: data.candidate,
  700. });
  701. rtc.peerConnection
  702. ?.addIceCandidate(candidate)
  703. .then(() => {
  704. console.log('candidate成功');
  705. })
  706. .catch((err) => {
  707. console.error('candidate失败', err);
  708. });
  709. } else {
  710. console.log('是我发的candidate');
  711. }
  712. });
  713. // 管理员正在直播
  714. ws.socketIo.on(WsMsgTypeEnum.roomLiving, (data: WsRoomLivingType) => {
  715. prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
  716. roomLiving.value = true;
  717. roomNoLive.value = false;
  718. // 如果是srs开播,则不需要等有人进来了才new webrtc,只要Websocket连上了就开始new webrtc
  719. if (isSRS.value) {
  720. if (isPull.value) {
  721. if (roomLiveType.value === liveTypeEnum.srsWebrtcPull) {
  722. startNewWebRtc({
  723. receiver: 'srs',
  724. videoEl: localVideo.value,
  725. });
  726. }
  727. }
  728. }
  729. });
  730. // 管理员不在直播
  731. ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
  732. prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data);
  733. roomNoLive.value = true;
  734. roomLiving.value = false;
  735. });
  736. // 当前所有在线用户
  737. ws.socketIo.on(
  738. WsMsgTypeEnum.liveUser,
  739. (data: WSGetRoomAllUserType['data']) => {
  740. prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data);
  741. const res = data.liveUser.map((item) => {
  742. return {
  743. id: item.id,
  744. // userInfo: item.id,
  745. };
  746. });
  747. liveUserList.value = res;
  748. }
  749. );
  750. // 收到用户发送消息
  751. ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => {
  752. prettierReceiveWsMsg(WsMsgTypeEnum.message, data);
  753. if (!ws) return;
  754. damuList.value.push({
  755. socket_id: data.socket_id,
  756. msgType: DanmuMsgTypeEnum.danmu,
  757. msg: data.data.msg,
  758. userInfo: data.user_info,
  759. });
  760. });
  761. // 用户加入房间完成
  762. ws.socketIo.on(WsMsgTypeEnum.joined, (data: WsJoinType['data']) => {
  763. prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
  764. joined.value = true;
  765. trackInfo.track_audio = 1;
  766. trackInfo.track_video = 1;
  767. liveUserList.value.push({
  768. id: data.socket_id,
  769. userInfo: data.user_info,
  770. });
  771. liveRoomInfo.value = data.live_room;
  772. anchorInfo.value = data.anchor_info;
  773. ws.send<WsGetLiveUserType['data']>({
  774. msgType: WsMsgTypeEnum.getLiveUser,
  775. data: {
  776. live_room_id: data.live_room.id!,
  777. },
  778. });
  779. // 如果是srs开播,则不需要等有人进来了才new webrtc,只要Websocket连上了就开始new webrtc
  780. // if (isSRS.value) {
  781. // if (!isPull.value) {
  782. // startNewWebRtc({
  783. // receiver: 'srs',
  784. // videoEl: localVideo.value,
  785. // });
  786. // }
  787. // }
  788. });
  789. // 其他用户加入房间
  790. ws.socketIo.on(WsMsgTypeEnum.otherJoin, (data: WsOtherJoinType['data']) => {
  791. prettierReceiveWsMsg(WsMsgTypeEnum.otherJoin, data);
  792. liveUserList.value.push({
  793. id: data.join_socket_id,
  794. userInfo: data.join_user_info,
  795. });
  796. const danmu: IDanmu = {
  797. msgType: DanmuMsgTypeEnum.otherJoin,
  798. socket_id: data.join_socket_id,
  799. userInfo: data.join_user_info,
  800. msg: '',
  801. };
  802. damuList.value.push(danmu);
  803. ws.send<WsGetLiveUserType['data']>({
  804. msgType: WsMsgTypeEnum.getLiveUser,
  805. data: {
  806. live_room_id: data.live_room.id!,
  807. },
  808. });
  809. // 如果是srs开播,且进来的用户不是srs-webrtc-pull,则不能再new webrtc了
  810. if (isSRS.value) return;
  811. if (joined.value) {
  812. // startNewWebRtc({
  813. // receiver: data.join_socket_id,
  814. // videoEl: localVideo.value,
  815. // });
  816. }
  817. });
  818. // 用户离开房间
  819. ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
  820. prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
  821. if (!ws) return;
  822. ws.send({
  823. msgType: WsMsgTypeEnum.leave,
  824. data: { roomId: ws.roomId },
  825. });
  826. });
  827. // 用户离开房间完成
  828. ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
  829. prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
  830. networkStore.rtcMap
  831. .get(`${roomId.value}___${data.socket_id as string}`)
  832. ?.close();
  833. networkStore.removeRtc(`${roomId.value}___${data.socket_id as string}`);
  834. const res = liveUserList.value.filter(
  835. (item) => item.id !== data.socket_id
  836. );
  837. liveUserList.value = res;
  838. damuList.value.push({
  839. socket_id: data.socket_id,
  840. msgType: DanmuMsgTypeEnum.userLeaved,
  841. userInfo: data.user_info,
  842. msg: '',
  843. });
  844. });
  845. }
  846. function initWs(data: {
  847. isAnchor: boolean;
  848. roomId: string;
  849. isSRS: boolean;
  850. isPull: boolean;
  851. currentResolutionRatio?: number;
  852. currentMaxFramerate?: number;
  853. currentMaxBitrate?: number;
  854. roomLiveType: liveTypeEnum;
  855. }) {
  856. roomId.value = data.roomId;
  857. isAnchor.value = data.isAnchor;
  858. roomLiveType.value = data.roomLiveType;
  859. if (data.currentMaxBitrate) {
  860. currentMaxBitrate.value = data.currentMaxBitrate;
  861. }
  862. if (data.currentMaxFramerate) {
  863. currentMaxFramerate.value = data.currentMaxFramerate;
  864. }
  865. if (data.currentResolutionRatio) {
  866. currentResolutionRatio.value = data.currentResolutionRatio;
  867. }
  868. isSRS.value = data.isSRS;
  869. isPull.value = data.isPull;
  870. new WebSocketClass({
  871. roomId: roomId.value,
  872. url: WEBSOCKET_URL,
  873. isAnchor: data.isAnchor,
  874. });
  875. initReceive();
  876. }
  877. return {
  878. getSocketId,
  879. initWs,
  880. addTrack,
  881. delTrack,
  882. startNewWebRtc,
  883. sendStartLive,
  884. canvasVideoStream,
  885. lastCoverImg,
  886. roomLiving,
  887. liveRoomInfo,
  888. anchorInfo,
  889. roomNoLive,
  890. loopHeartbeatTimer,
  891. localStream,
  892. liveUserList,
  893. damuList,
  894. maxBitrate,
  895. maxFramerate,
  896. resolutionRatio,
  897. currentMaxFramerate,
  898. currentMaxBitrate,
  899. currentResolutionRatio,
  900. };
  901. };