shuisheng %!s(int64=2) %!d(string=hai) anos
pai
achega
89ad0ca400

+ 1 - 1
package.json

@@ -123,4 +123,4 @@
     "webpackbar": "^5.0.2",
     "webpackbar": "^5.0.2",
     "windicss-webpack-plugin": "^1.7.7"
     "windicss-webpack-plugin": "^1.7.7"
   }
   }
-}
+}

+ 1 - 0
src/constant.ts

@@ -191,6 +191,7 @@ export const mediaTypeEnumMap = {
   [MediaTypeEnum.media]: '视频',
   [MediaTypeEnum.media]: '视频',
   [MediaTypeEnum.time]: '时间',
   [MediaTypeEnum.time]: '时间',
   [MediaTypeEnum.stopwatch]: '秒表',
   [MediaTypeEnum.stopwatch]: '秒表',
+  [MediaTypeEnum.pk]: '打pk',
 };
 };
 
 
 export const sliderList = [
 export const sliderList = [

+ 2 - 2
src/hooks/use-pull.ts

@@ -3,7 +3,7 @@ import { onUnmounted, ref, watch } from 'vue';
 
 
 import { commentAuthTip, loginTip } from '@/hooks/use-login';
 import { commentAuthTip, loginTip } from '@/hooks/use-login';
 import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
 import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
-import { useSrsWs } from '@/hooks/use-srs-ws';
+import { useWebsocket } from '@/hooks/use-websocket';
 import {
 import {
   DanmuMsgTypeEnum,
   DanmuMsgTypeEnum,
   IDanmu,
   IDanmu,
@@ -47,7 +47,7 @@ export function usePull(roomId: string) {
     anchorInfo,
     anchorInfo,
     liveUserList,
     liveUserList,
     damuList,
     damuList,
-  } = useSrsWs();
+  } = useWebsocket();
   isPull.value = true;
   isPull.value = true;
   const { flvVideoEl, flvIsPlaying, startFlvPlay, destroyFlv } = useFlvPlay();
   const { flvVideoEl, flvIsPlaying, startFlvPlay, destroyFlv } = useFlvPlay();
   const { hlsVideoEl, hlsIsPlaying, startHlsPlay, destroyHls } = useHlsPlay();
   const { hlsVideoEl, hlsIsPlaying, startHlsPlay, destroyHls } = useHlsPlay();

+ 34 - 54
src/hooks/use-push.ts

@@ -15,8 +15,8 @@ import { useUserStore } from '@/store/user';
 import { createVideo, generateBase64 } from '@/utils';
 import { createVideo, generateBase64 } from '@/utils';
 
 
 import { commentAuthTip, loginTip } from './use-login';
 import { commentAuthTip, loginTip } from './use-login';
-import { useSrsWs } from './use-srs-ws';
 import { useTip } from './use-tip';
 import { useTip } from './use-tip';
+import { useWebsocket } from './use-websocket';
 
 
 export function usePush() {
 export function usePush() {
   const route = useRoute();
   const route = useRoute();
@@ -46,9 +46,31 @@ export function usePush() {
     currentMaxFramerate,
     currentMaxFramerate,
     currentMaxBitrate,
     currentMaxBitrate,
     currentResolutionRatio,
     currentResolutionRatio,
-  } = useSrsWs();
+  } = useWebsocket();
+
   isPull.value = false;
   isPull.value = false;
 
 
+  onMounted(() => {
+    if (!loginTip()) return;
+  });
+
+  onUnmounted(() => {
+    closeWs();
+    closeRtc();
+  });
+
+  function closeWs() {
+    const instance = networkStore.wsMap.get(roomId.value);
+    instance?.close();
+  }
+
+  function closeRtc() {
+    networkStore.rtcMap.forEach((rtc) => {
+      rtc.close();
+      networkStore.removeRtc(rtc.roomId);
+    });
+  }
+
   watch(
   watch(
     () => appStore.allTrack,
     () => appStore.allTrack,
     (newTrack) => {
     (newTrack) => {
@@ -82,26 +104,6 @@ export function usePush() {
     }
     }
   );
   );
 
 
-  // watch(
-  //   () => currentMaxFramerate.value,
-  //   () => {
-  //     handleMaxFramerate({
-  //       stream: canvasVideoStream.value!,
-  //       height: currentResolutionRatio.value,
-  //       frameRate: currentMaxFramerate.value,
-  //     });
-  //     console.log(currentMaxFramerate.value, 'kkkkkk');
-  //     // networkStore.rtcMap.forEach(async (rtc) => {
-  //     //   const res = await rtc.setMaxFramerate(newVal);
-  //     //   if (res === 1) {
-  //     //     window.$message.success('切换帧率成功!');
-  //     //   } else {
-  //     //     window.$message.success('切换帧率失败!');
-  //     //   }
-  //     // });
-  //   }
-  // );
-
   watch(
   watch(
     () => currentMaxBitrate.value,
     () => currentMaxBitrate.value,
     (newVal) => {
     (newVal) => {
@@ -147,7 +149,7 @@ export function usePush() {
     () => userStore.userInfo,
     () => userStore.userInfo,
     async (newVal) => {
     async (newVal) => {
       if (newVal) {
       if (newVal) {
-        const res = await userHasLiveRoom();
+        const res = await handleUserHasLiveRoom();
         if (!res) {
         if (!res) {
           await useTip('你还没有直播间,是否立即开通?');
           await useTip('你还没有直播间,是否立即开通?');
           await handleCreateUserLiveRoom();
           await handleCreateUserLiveRoom();
@@ -161,33 +163,13 @@ export function usePush() {
     { immediate: true }
     { immediate: true }
   );
   );
 
 
-  onMounted(() => {
-    roomId.value = route.query.roomId as string;
-    if (!loginTip()) return;
-  });
-
-  onUnmounted(() => {
-    closeWs();
-    closeRtc();
-  });
-
-  function closeWs() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    instance?.close();
-  }
-
-  function closeRtc() {
-    networkStore.rtcMap.forEach((rtc) => {
-      rtc.close();
-      networkStore.removeRtc(rtc.roomId);
-    });
-  }
-
-  async function userHasLiveRoom() {
+  async function handleUserHasLiveRoom() {
     const res = await fetchUserHasLiveRoom(userStore.userInfo?.id!);
     const res = await fetchUserHasLiveRoom(userStore.userInfo?.id!);
     if (res.code === 200 && res.data) {
     if (res.code === 200 && res.data) {
       liveRoomInfo.value = res.data.live_room;
       liveRoomInfo.value = res.data.live_room;
-      router.push({ query: { ...route.query, roomId: roomId.value } });
+      router.push({
+        query: { ...route.query, roomId: liveRoomInfo.value?.id },
+      });
       return true;
       return true;
     }
     }
     return false;
     return false;
@@ -217,10 +199,9 @@ export function usePush() {
     });
     });
   }
   }
 
 
-  async function startLive({ type, receiver, chunkDelay }) {
-    console.log('startLive', { type, receiver, chunkDelay });
+  async function startLive({ type, msrDelay }) {
     if (!loginTip()) return;
     if (!loginTip()) return;
-    const flag = await userHasLiveRoom();
+    const flag = await handleUserHasLiveRoom();
     if (!flag) {
     if (!flag) {
       await useTip('你还没有直播间,是否立即开通?');
       await useTip('你还没有直播间,是否立即开通?');
       await handleCreateUserLiveRoom();
       await handleCreateUserLiveRoom();
@@ -257,8 +238,7 @@ export function usePush() {
       coverImg: lastCoverImg.value,
       coverImg: lastCoverImg.value,
       name: roomName.value,
       name: roomName.value,
       type,
       type,
-      receiver,
-      chunkDelay,
+      msrDelay,
     });
     });
   }
   }
 
 
@@ -297,8 +277,8 @@ export function usePush() {
       window.$message.warning('请输入房间名!');
       window.$message.warning('请输入房间名!');
       return false;
       return false;
     }
     }
-    if (roomName.value.length < 3 || roomName.value.length > 30) {
-      window.$message.warning('房间名要求3-30个字符!');
+    if (roomName.value.length < 3 || roomName.value.length > 20) {
+      window.$message.warning('房间名要求3-20个字符!');
       return false;
       return false;
     }
     }
     return true;
     return true;

+ 0 - 567
src/hooks/use-rtc-ws.ts

@@ -1,567 +0,0 @@
-import { getRandomString } from 'billd-utils';
-import { computed, onUnmounted, ref } from 'vue';
-
-import { fetchRtcV1Publish } from '@/api/srs';
-import { SRS_CB_URL_PARAMS, WEBSOCKET_URL } from '@/constant';
-import {
-  DanmuMsgTypeEnum,
-  IDanmu,
-  ILiveUser,
-  IUser,
-  LiveRoomTypeEnum,
-} from '@/interface';
-import {
-  WSGetRoomAllUserType,
-  WsAnswerType,
-  WsCandidateType,
-  WsConnectStatusEnum,
-  WsDisableSpeakingType,
-  WsGetLiveUserType,
-  WsHeartbeatType,
-  WsJoinType,
-  WsLeavedType,
-  WsMessageType,
-  WsMsgTypeEnum,
-  WsOfferType,
-  WsOtherJoinType,
-  WsRoomLivingType,
-  WsStartLiveType,
-  WsUpdateJoinInfoType,
-} from '@/interface-ws';
-import { WebRTCClass } from '@/network/webRTC';
-import { WebSocketClass, prettierReceiveWsMsg } from '@/network/webSocket';
-import { useAppStore } from '@/store/app';
-import { useNetworkStore } from '@/store/network';
-import { useUserStore } from '@/store/user';
-import { createVideo, formatDownTime } from '@/utils';
-
-import { useRTCParams } from './use-rtcParams';
-
-export const useSrsWs = () => {
-  const appStore = useAppStore();
-  const userStore = useUserStore();
-  const networkStore = useNetworkStore();
-  const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
-
-  const loopHeartbeatTimer = ref();
-  const liveUserList = ref<ILiveUser[]>([]);
-  const roomId = ref('');
-  const isPull = ref(false);
-  const roomLiving = ref(false);
-  const isAnchor = ref(false);
-  const isSRS = ref(false);
-  const anchorInfo = ref<IUser>();
-  const anchorSocketId = ref('');
-  const canvasVideoStream = ref<MediaStream>();
-  const lastCoverImg = ref('');
-  const currentMaxBitrate = ref(maxBitrate.value[3].value);
-  const currentMaxFramerate = ref(maxFramerate.value[2].value);
-  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
-  const timerObj = ref({});
-  const damuList = ref<IDanmu[]>([]);
-
-  onUnmounted(() => {
-    clearInterval(loopHeartbeatTimer.value);
-  });
-
-  const mySocketId = computed(() => {
-    return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
-  });
-
-  function handleHeartbeat(socketId: string) {
-    loopHeartbeatTimer.value = setInterval(() => {
-      const ws = networkStore.wsMap.get(roomId.value);
-      if (!ws) return;
-      ws.send<WsHeartbeatType['data']>({
-        requestId: getRandomString(8),
-        msgType: WsMsgTypeEnum.heartbeat,
-        data: {
-          socket_id: socketId,
-        },
-      });
-    }, 1000 * 5);
-  }
-
-  async function handleSendOffer({ receiver }: { receiver: string }) {
-    console.log('开始handleSendOffer', receiver);
-    const ws = networkStore.wsMap.get(roomId.value);
-    if (!ws) return;
-    const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
-    if (!rtc) return;
-    canvasVideoStream.value?.getTracks().forEach((track) => {
-      if (rtc && canvasVideoStream.value) {
-        console.log('11canvasVideoStream插入track', track.kind, track);
-        rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
-      }
-    });
-    const sdp = await rtc.createOffer();
-    await rtc.setLocalDescription(sdp!);
-    const myLiveRoom = userStore.userInfo!.live_rooms![0];
-    const res = await fetchRtcV1Publish({
-      api: `/rtc/v1/publish/`,
-      clientip: null,
-      sdp: sdp!.sdp!,
-      streamurl: `${myLiveRoom.rtmp_url!}?${
-        SRS_CB_URL_PARAMS.publishKey
-      }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
-        LiveRoomTypeEnum.user_srs
-      }`,
-      tid: getRandomString(10),
-    });
-    networkStore.wsMap.get(roomId.value)?.send<WsUpdateJoinInfoType['data']>({
-      requestId: getRandomString(8),
-      msgType: WsMsgTypeEnum.updateJoinInfo,
-      data: {
-        live_room_id: Number(roomId.value),
-        track: {
-          audio: 1,
-          video: 1,
-        },
-      },
-    });
-    if (res.data.code !== 0) {
-      console.error('/rtc/v1/publish/拿不到sdp');
-      window.$message.error('/rtc/v1/publish/拿不到sdp');
-      return;
-    }
-    await rtc.setRemoteDescription(
-      new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
-    );
-  }
-
-  function handleStartLive({
-    coverImg,
-    name,
-    type,
-    receiver,
-    chunkDelay,
-  }: {
-    coverImg?: string;
-    name?: string;
-    type: LiveRoomTypeEnum;
-    receiver: string;
-    videoEl?: HTMLVideoElement;
-    chunkDelay: number;
-  }) {
-    console.log('handleStartLivehandleStartLive', receiver);
-    networkStore.wsMap.get(roomId.value)?.send<WsStartLiveType['data']>({
-      requestId: getRandomString(8),
-      msgType: WsMsgTypeEnum.startLive,
-      data: {
-        cover_img: coverImg!,
-        name: name!,
-        type,
-        chunkDelay,
-      },
-    });
-    if (type === LiveRoomTypeEnum.user_msr) {
-      return;
-    }
-    isSRS.value = true;
-    if (type !== LiveRoomTypeEnum.user_wertc) {
-      startNewWebRtc({
-        videoEl: createVideo({}),
-        receiver,
-      });
-    }
-  }
-
-  function sendJoin() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (!instance) return;
-    instance.send<WsJoinType['data']>({
-      requestId: getRandomString(8),
-      msgType: WsMsgTypeEnum.join,
-      data: {
-        socket_id: mySocketId.value,
-        live_room: {
-          id: Number(roomId.value),
-        },
-        user_info: userStore.userInfo,
-      },
-    });
-  }
-
-  /** 原生的webrtc时,receiver必传 */
-  function startNewWebRtc({
-    receiver,
-    videoEl,
-  }: {
-    receiver: string;
-    videoEl: HTMLVideoElement;
-  }) {
-    console.warn(
-      '22开始new WebRTCClass',
-      receiver,
-      `${roomId.value}___${receiver!}`,
-      isSRS.value,
-      canvasVideoStream.value
-    );
-    new WebRTCClass({
-      maxBitrate: currentMaxBitrate.value,
-      maxFramerate: currentMaxFramerate.value,
-      resolutionRatio: currentResolutionRatio.value,
-      roomId: `${roomId.value}___${receiver!}`,
-      videoEl,
-      isSRS: true,
-      receiver,
-      localStream: canvasVideoStream.value,
-    });
-    // isSRS.value = true;
-    handleSendOffer({
-      receiver,
-    });
-  }
-
-  function initReceive() {
-    const ws = networkStore.wsMap.get(roomId.value);
-    if (!ws?.socketIo) return;
-    // websocket连接成功
-    ws.socketIo.on(WsConnectStatusEnum.connect, () => {
-      prettierReceiveWsMsg(WsConnectStatusEnum.connect, ws.socketIo);
-      handleHeartbeat(ws.socketIo!.id);
-      if (!ws) return;
-      ws.status = WsConnectStatusEnum.connect;
-      ws.update();
-      sendJoin();
-    });
-
-    // websocket连接断开
-    ws.socketIo.on(WsConnectStatusEnum.disconnect, (err) => {
-      prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws);
-      console.log('websocket连接断开', err);
-      if (!ws) return;
-      ws.status = WsConnectStatusEnum.disconnect;
-      ws.update();
-    });
-
-    // 收到offer
-    ws.socketIo.on(WsMsgTypeEnum.offer, async (data: WsOfferType['data']) => {
-      console.log('收到offer', data);
-      if (data.receiver === mySocketId.value) {
-        console.warn('是发给我的offer');
-        console.warn(
-          '33开始new WebRTCClass',
-          `${roomId.value}___${data.sender}`
-        );
-        const videoEl = createVideo({ appendChild: true });
-        const rtc = new WebRTCClass({
-          maxBitrate: currentMaxBitrate.value,
-          maxFramerate: currentMaxFramerate.value,
-          resolutionRatio: currentResolutionRatio.value,
-          roomId: `${roomId.value}___${data.sender}`,
-          videoEl,
-          isSRS: true,
-          receiver: data.receiver,
-        });
-        isSRS.value = true;
-        await rtc.setRemoteDescription(data.sdp);
-        const answer = await rtc.createAnswer();
-        if (answer) {
-          await rtc.setLocalDescription(answer);
-          ws.send<WsAnswerType['data']>({
-            requestId: getRandomString(8),
-            msgType: WsMsgTypeEnum.answer,
-            data: {
-              live_room_id: Number(roomId.value),
-              sdp: answer,
-              receiver: data.sender,
-              sender: mySocketId.value,
-            },
-          });
-        } else {
-          console.error('没有answer');
-        }
-      } else {
-        console.error('不是发给我的offer');
-      }
-    });
-
-    // 收到answer
-    ws.socketIo.on(WsMsgTypeEnum.answer, (data: WsAnswerType['data']) => {
-      console.log('收到answer', data);
-      if (data.receiver === mySocketId.value) {
-        console.warn('是发给我的answer', `${roomId.value}___${data.receiver}`);
-        const rtc = networkStore.getRtcMap(`${roomId.value}___${data.sender}`)!;
-        rtc.setRemoteDescription(data.sdp);
-      } else {
-        console.error('不是发给我的answer');
-      }
-    });
-
-    // 收到candidate
-    ws.socketIo.on(WsMsgTypeEnum.candidate, (data: WsCandidateType['data']) => {
-      console.log('收到candidate', data);
-      if (data.receiver === mySocketId.value) {
-        console.warn('是发给我的candidate');
-        const rtc = networkStore.getRtcMap(`${roomId.value}___${data.sender}`)!;
-        rtc.addIceCandidate(data.candidate);
-      } else {
-        console.error('不是发给我的candidate');
-      }
-    });
-
-    // 主播正在直播
-    ws.socketIo.on(
-      WsMsgTypeEnum.roomLiving,
-      (data: WsRoomLivingType['data']) => {
-        prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
-        roomLiving.value = true;
-        if (data.anchor_socket_id) {
-          anchorSocketId.value = data.anchor_socket_id;
-        }
-        isSRS.value = true;
-        if (data.live_room.type === LiveRoomTypeEnum.user_wertc) {
-          isSRS.value = false;
-          startNewWebRtc({
-            videoEl: createVideo({}),
-            receiver: data.anchor_socket_id,
-          });
-        }
-      }
-    );
-
-    // 主播不在直播
-    ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data);
-      roomLiving.value = false;
-    });
-
-    // 当前所有在线用户
-    ws.socketIo.on(
-      WsMsgTypeEnum.liveUser,
-      (data: WSGetRoomAllUserType['data']) => {
-        console.log('当前所有在线用户当前所有在线用户', data.liveUser.length);
-        prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data);
-        liveUserList.value = data.liveUser;
-      }
-    );
-
-    // 收到用户发送消息
-    ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.message, data);
-      damuList.value.push({
-        request_id: data.request_id,
-        socket_id: data.socket_id,
-        msgType: DanmuMsgTypeEnum.danmu,
-        msg: data.data.msg,
-        userInfo: data.user_info,
-        msgIsFile: data.data.msgIsFile,
-        sendMsgTime: data.data.sendMsgTime,
-      });
-    });
-
-    // 收到disableSpeaking
-    ws.socketIo.on(
-      WsMsgTypeEnum.disableSpeaking,
-      (data: WsDisableSpeakingType['data']) => {
-        prettierReceiveWsMsg(WsMsgTypeEnum.disableSpeaking, data);
-        if (data.is_disable_speaking) {
-          window.$message.error('你已被禁言!');
-          appStore.disableSpeaking.set(data.live_room_id, {
-            exp: data.disable_expired_at,
-            label: formatDownTime({
-              startTime: +new Date(),
-              endTime: data.disable_expired_at,
-            }),
-          });
-          clearTimeout(timerObj.value[data.live_room_id]);
-          timerObj.value[data.live_room_id] = setInterval(() => {
-            if (
-              data.disable_expired_at &&
-              +new Date() > data.disable_expired_at
-            ) {
-              clearTimeout(timerObj.value[data.live_room_id]);
-            }
-            appStore.disableSpeaking.set(data.live_room_id, {
-              exp: data.disable_expired_at!,
-              label: formatDownTime({
-                startTime: +new Date(),
-                endTime: data.disable_expired_at!,
-              }),
-            });
-          }, 1000);
-          damuList.value = damuList.value.filter(
-            (v) => v.request_id !== data.request_id
-          );
-        }
-        if (data.user_id !== userStore.userInfo?.id && data.disable_ok) {
-          window.$message.success('禁言成功!');
-        }
-        if (
-          data.user_id !== userStore.userInfo?.id &&
-          data.restore_disable_ok
-        ) {
-          window.$message.success('解除禁言成功!');
-        }
-        if (
-          data.user_id === userStore.userInfo?.id &&
-          data.restore_disable_ok
-        ) {
-          window.$message.success('禁言接触了!');
-          clearTimeout(timerObj.value[data.live_room_id]);
-          appStore.disableSpeaking.delete(data.live_room_id);
-        }
-      }
-    );
-
-    // 用户加入房间完成
-    ws.socketIo.on(WsMsgTypeEnum.joined, (data: WsJoinType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
-      // liveUserList.value.push({
-      //   id: data.socket_id,
-      //   userInfo: data.user_info,
-      // });
-      appStore.setLiveRoomInfo(data.live_room);
-      anchorInfo.value = data.anchor_info;
-      ws.send<WsGetLiveUserType['data']>({
-        requestId: getRandomString(8),
-        msgType: WsMsgTypeEnum.getLiveUser,
-        data: {
-          live_room_id: data.live_room.id!,
-        },
-      });
-    });
-
-    // 其他用户加入房间
-    ws.socketIo.on(WsMsgTypeEnum.otherJoin, (data: WsOtherJoinType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.otherJoin, data);
-      // liveUserList.value.push({
-      //   id: data.join_socket_id,
-      //   userInfo: data.join_user_info,
-      // });
-      const requestId = getRandomString(8);
-      const danmu: IDanmu = {
-        request_id: requestId,
-        msgType: DanmuMsgTypeEnum.otherJoin,
-        socket_id: data.join_socket_id,
-        userInfo: data.join_user_info,
-        msgIsFile: false,
-        msg: '',
-        sendMsgTime: +new Date(),
-      };
-      damuList.value.push(danmu);
-      ws.send<WsGetLiveUserType['data']>({
-        requestId,
-        msgType: WsMsgTypeEnum.getLiveUser,
-        data: {
-          live_room_id: data.live_room.id!,
-        },
-      });
-      if (!isPull.value && !isSRS.value) {
-        if (!roomLiving.value) return;
-        // liveUserList.value.forEach(async (item) => {
-        //   const receiver = item.id;
-        //   if (
-        //     receiver === mySocketId.value ||
-        //     networkStore.getRtcMap(`${roomId.value}___${receiver!}`)
-        //   )
-        //     return;
-        //   console.warn(
-        //     '11开始new WebRTCClass',
-        //     `${roomId.value}___${receiver!}`
-        //   );
-        //   const rtc = new WebRTCClass({
-        //     maxBitrate: currentMaxBitrate.value,
-        //     maxFramerate: currentMaxFramerate.value,
-        //     resolutionRatio: currentResolutionRatio.value,
-        //     roomId: `${roomId.value}___${receiver!}`,
-        //     videoEl: createVideo({}),
-        //     isSRS: false,
-        //     receiver,
-        //   });
-        //   networkStore.updateRtcMap(`${roomId.value}___${receiver!}`, rtc);
-        //   canvasVideoStream.value?.getTracks().forEach((track) => {
-        //     if (rtc && canvasVideoStream.value) {
-        //       console.log('22canvasVideoStream插入track', track.kind, track);
-        //       rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
-        //     }
-        //   });
-        //   const ws = networkStore.wsMap.get(roomId.value)!;
-        //   const offer = await rtc.createOffer();
-        //   await rtc.setLocalDescription(offer!);
-        //   ws.send<WsOfferType['data']>({
-        //     msgType: WsMsgTypeEnum.offer,
-        //     data: {
-        //       sdp: offer,
-        //       live_room_id: Number(roomId.value),
-        //       sender: mySocketId.value,
-        //       receiver,
-        //     },
-        //   });
-        // });
-      }
-    });
-
-    // 用户离开房间
-    ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
-    });
-
-    // 用户离开房间完成
-    ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
-      if (anchorSocketId.value === data.socket_id) {
-        roomLiving.value = false;
-      }
-      networkStore.rtcMap
-        .get(`${roomId.value}___${data.socket_id as string}`)
-        ?.close();
-      networkStore.removeRtc(`${roomId.value}___${data.socket_id as string}`);
-      // const res = liveUserList.value.filter(
-      //   (item) => item.id !== data.socket_id
-      // );
-      // liveUserList.value = res;
-      damuList.value.push({
-        socket_id: data.socket_id,
-        msgType: DanmuMsgTypeEnum.userLeaved,
-        msgIsFile: false,
-        userInfo: data.user_info,
-        msg: '',
-        sendMsgTime: +new Date(),
-      });
-    });
-  }
-
-  function initSrsWs(data: {
-    isAnchor: boolean;
-    roomId: string;
-    currentResolutionRatio?: number;
-    currentMaxFramerate?: number;
-    currentMaxBitrate?: number;
-  }) {
-    roomId.value = data.roomId;
-    isAnchor.value = data.isAnchor;
-    if (data.currentMaxBitrate) {
-      currentMaxBitrate.value = data.currentMaxBitrate;
-    }
-    if (data.currentMaxFramerate) {
-      currentMaxFramerate.value = data.currentMaxFramerate;
-    }
-    if (data.currentResolutionRatio) {
-      currentResolutionRatio.value = data.currentResolutionRatio;
-    }
-    new WebSocketClass({
-      roomId: roomId.value,
-      url: WEBSOCKET_URL,
-      isAnchor: data.isAnchor,
-    });
-    initReceive();
-  }
-
-  return {
-    isPull,
-    initSrsWs,
-    handleStartLive,
-    mySocketId,
-    canvasVideoStream,
-    lastCoverImg,
-    roomLiving,
-    anchorInfo,
-    liveUserList,
-    damuList,
-    currentMaxFramerate,
-    currentMaxBitrate,
-    currentResolutionRatio,
-  };
-};

+ 0 - 567
src/hooks/use-srs-ws.ts

@@ -1,567 +0,0 @@
-import { getRandomString } from 'billd-utils';
-import { computed, onUnmounted, ref } from 'vue';
-
-import { fetchRtcV1Publish } from '@/api/srs';
-import { SRS_CB_URL_PARAMS, WEBSOCKET_URL } from '@/constant';
-import {
-  DanmuMsgTypeEnum,
-  IDanmu,
-  ILiveUser,
-  IUser,
-  LiveRoomTypeEnum,
-} from '@/interface';
-import {
-  WSGetRoomAllUserType,
-  WsAnswerType,
-  WsCandidateType,
-  WsConnectStatusEnum,
-  WsDisableSpeakingType,
-  WsGetLiveUserType,
-  WsHeartbeatType,
-  WsJoinType,
-  WsLeavedType,
-  WsMessageType,
-  WsMsgTypeEnum,
-  WsOfferType,
-  WsOtherJoinType,
-  WsRoomLivingType,
-  WsStartLiveType,
-  WsUpdateJoinInfoType,
-} from '@/interface-ws';
-import { WebRTCClass } from '@/network/webRTC';
-import { WebSocketClass, prettierReceiveWsMsg } from '@/network/webSocket';
-import { useAppStore } from '@/store/app';
-import { useNetworkStore } from '@/store/network';
-import { useUserStore } from '@/store/user';
-import { createVideo, formatDownTime } from '@/utils';
-
-import { useRTCParams } from './use-rtcParams';
-
-export const useSrsWs = () => {
-  const appStore = useAppStore();
-  const userStore = useUserStore();
-  const networkStore = useNetworkStore();
-  const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
-
-  const loopHeartbeatTimer = ref();
-  const liveUserList = ref<ILiveUser[]>([]);
-  const roomId = ref('');
-  const isPull = ref(false);
-  const roomLiving = ref(false);
-  const isAnchor = ref(false);
-  const isSRS = ref(false);
-  const anchorInfo = ref<IUser>();
-  const anchorSocketId = ref('');
-  const canvasVideoStream = ref<MediaStream>();
-  const lastCoverImg = ref('');
-  const currentMaxBitrate = ref(maxBitrate.value[3].value);
-  const currentMaxFramerate = ref(maxFramerate.value[2].value);
-  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
-  const timerObj = ref({});
-  const damuList = ref<IDanmu[]>([]);
-
-  onUnmounted(() => {
-    clearInterval(loopHeartbeatTimer.value);
-  });
-
-  const mySocketId = computed(() => {
-    return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
-  });
-
-  function handleHeartbeat(socketId: string) {
-    loopHeartbeatTimer.value = setInterval(() => {
-      const ws = networkStore.wsMap.get(roomId.value);
-      if (!ws) return;
-      ws.send<WsHeartbeatType['data']>({
-        requestId: getRandomString(8),
-        msgType: WsMsgTypeEnum.heartbeat,
-        data: {
-          socket_id: socketId,
-        },
-      });
-    }, 1000 * 5);
-  }
-
-  async function handleSendOffer({ receiver }: { receiver: string }) {
-    console.log('开始handleSendOffer', receiver);
-    const ws = networkStore.wsMap.get(roomId.value);
-    if (!ws) return;
-    const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
-    if (!rtc) return;
-    canvasVideoStream.value?.getTracks().forEach((track) => {
-      if (rtc && canvasVideoStream.value) {
-        console.log('11canvasVideoStream插入track', track.kind, track);
-        rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
-      }
-    });
-    const sdp = await rtc.createOffer();
-    await rtc.setLocalDescription(sdp!);
-    const myLiveRoom = userStore.userInfo!.live_rooms![0];
-    const res = await fetchRtcV1Publish({
-      api: `/rtc/v1/publish/`,
-      clientip: null,
-      sdp: sdp!.sdp!,
-      streamurl: `${myLiveRoom.rtmp_url!}?${
-        SRS_CB_URL_PARAMS.publishKey
-      }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
-        LiveRoomTypeEnum.user_srs
-      }`,
-      tid: getRandomString(10),
-    });
-    networkStore.wsMap.get(roomId.value)?.send<WsUpdateJoinInfoType['data']>({
-      requestId: getRandomString(8),
-      msgType: WsMsgTypeEnum.updateJoinInfo,
-      data: {
-        live_room_id: Number(roomId.value),
-        track: {
-          audio: 1,
-          video: 1,
-        },
-      },
-    });
-    if (res.data.code !== 0) {
-      console.error('/rtc/v1/publish/拿不到sdp');
-      window.$message.error('/rtc/v1/publish/拿不到sdp');
-      return;
-    }
-    await rtc.setRemoteDescription(
-      new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
-    );
-  }
-
-  function handleStartLive({
-    coverImg,
-    name,
-    type,
-    receiver,
-    chunkDelay,
-  }: {
-    coverImg?: string;
-    name?: string;
-    type: LiveRoomTypeEnum;
-    receiver: string;
-    videoEl?: HTMLVideoElement;
-    chunkDelay: number;
-  }) {
-    console.log('handleStartLivehandleStartLive', receiver);
-    networkStore.wsMap.get(roomId.value)?.send<WsStartLiveType['data']>({
-      requestId: getRandomString(8),
-      msgType: WsMsgTypeEnum.startLive,
-      data: {
-        cover_img: coverImg!,
-        name: name!,
-        type,
-        chunkDelay,
-      },
-    });
-    if (type === LiveRoomTypeEnum.user_msr) {
-      return;
-    }
-    isSRS.value = true;
-    if (type !== LiveRoomTypeEnum.user_wertc) {
-      startNewWebRtc({
-        videoEl: createVideo({}),
-        receiver,
-      });
-    }
-  }
-
-  function sendJoin() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (!instance) return;
-    instance.send<WsJoinType['data']>({
-      requestId: getRandomString(8),
-      msgType: WsMsgTypeEnum.join,
-      data: {
-        socket_id: mySocketId.value,
-        live_room: {
-          id: Number(roomId.value),
-        },
-        user_info: userStore.userInfo,
-      },
-    });
-  }
-
-  /** 原生的webrtc时,receiver必传 */
-  function startNewWebRtc({
-    receiver,
-    videoEl,
-  }: {
-    receiver: string;
-    videoEl: HTMLVideoElement;
-  }) {
-    console.warn(
-      '22开始new WebRTCClass',
-      receiver,
-      `${roomId.value}___${receiver!}`,
-      isSRS.value,
-      canvasVideoStream.value
-    );
-    new WebRTCClass({
-      maxBitrate: currentMaxBitrate.value,
-      maxFramerate: currentMaxFramerate.value,
-      resolutionRatio: currentResolutionRatio.value,
-      roomId: `${roomId.value}___${receiver!}`,
-      videoEl,
-      isSRS: true,
-      receiver,
-      localStream: canvasVideoStream.value,
-    });
-    // isSRS.value = true;
-    handleSendOffer({
-      receiver,
-    });
-  }
-
-  function initReceive() {
-    const ws = networkStore.wsMap.get(roomId.value);
-    if (!ws?.socketIo) return;
-    // websocket连接成功
-    ws.socketIo.on(WsConnectStatusEnum.connect, () => {
-      prettierReceiveWsMsg(WsConnectStatusEnum.connect, ws.socketIo);
-      handleHeartbeat(ws.socketIo!.id);
-      if (!ws) return;
-      ws.status = WsConnectStatusEnum.connect;
-      ws.update();
-      sendJoin();
-    });
-
-    // websocket连接断开
-    ws.socketIo.on(WsConnectStatusEnum.disconnect, (err) => {
-      prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws);
-      console.log('websocket连接断开', err);
-      if (!ws) return;
-      ws.status = WsConnectStatusEnum.disconnect;
-      ws.update();
-    });
-
-    // 收到offer
-    ws.socketIo.on(WsMsgTypeEnum.offer, async (data: WsOfferType['data']) => {
-      console.log('收到offer', data);
-      if (data.receiver === mySocketId.value) {
-        console.warn('是发给我的offer');
-        console.warn(
-          '33开始new WebRTCClass',
-          `${roomId.value}___${data.sender}`
-        );
-        const videoEl = createVideo({ appendChild: true });
-        const rtc = new WebRTCClass({
-          maxBitrate: currentMaxBitrate.value,
-          maxFramerate: currentMaxFramerate.value,
-          resolutionRatio: currentResolutionRatio.value,
-          roomId: `${roomId.value}___${data.sender}`,
-          videoEl,
-          isSRS: true,
-          receiver: data.receiver,
-        });
-        isSRS.value = true;
-        await rtc.setRemoteDescription(data.sdp);
-        const answer = await rtc.createAnswer();
-        if (answer) {
-          await rtc.setLocalDescription(answer);
-          ws.send<WsAnswerType['data']>({
-            requestId: getRandomString(8),
-            msgType: WsMsgTypeEnum.answer,
-            data: {
-              live_room_id: Number(roomId.value),
-              sdp: answer,
-              receiver: data.sender,
-              sender: mySocketId.value,
-            },
-          });
-        } else {
-          console.error('没有answer');
-        }
-      } else {
-        console.error('不是发给我的offer');
-      }
-    });
-
-    // 收到answer
-    ws.socketIo.on(WsMsgTypeEnum.answer, (data: WsAnswerType['data']) => {
-      console.log('收到answer', data);
-      if (data.receiver === mySocketId.value) {
-        console.warn('是发给我的answer', `${roomId.value}___${data.receiver}`);
-        const rtc = networkStore.getRtcMap(`${roomId.value}___${data.sender}`)!;
-        rtc.setRemoteDescription(data.sdp);
-      } else {
-        console.error('不是发给我的answer');
-      }
-    });
-
-    // 收到candidate
-    ws.socketIo.on(WsMsgTypeEnum.candidate, (data: WsCandidateType['data']) => {
-      console.log('收到candidate', data);
-      if (data.receiver === mySocketId.value) {
-        console.warn('是发给我的candidate');
-        const rtc = networkStore.getRtcMap(`${roomId.value}___${data.sender}`)!;
-        rtc.addIceCandidate(data.candidate);
-      } else {
-        console.error('不是发给我的candidate');
-      }
-    });
-
-    // 主播正在直播
-    ws.socketIo.on(
-      WsMsgTypeEnum.roomLiving,
-      (data: WsRoomLivingType['data']) => {
-        prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
-        roomLiving.value = true;
-        if (data.anchor_socket_id) {
-          anchorSocketId.value = data.anchor_socket_id;
-        }
-        isSRS.value = true;
-        if (data.live_room.type === LiveRoomTypeEnum.user_wertc) {
-          isSRS.value = false;
-          startNewWebRtc({
-            videoEl: createVideo({}),
-            receiver: data.anchor_socket_id,
-          });
-        }
-      }
-    );
-
-    // 主播不在直播
-    ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data);
-      roomLiving.value = false;
-    });
-
-    // 当前所有在线用户
-    ws.socketIo.on(
-      WsMsgTypeEnum.liveUser,
-      (data: WSGetRoomAllUserType['data']) => {
-        console.log('当前所有在线用户当前所有在线用户', data.liveUser.length);
-        prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data);
-        liveUserList.value = data.liveUser;
-      }
-    );
-
-    // 收到用户发送消息
-    ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.message, data);
-      damuList.value.push({
-        request_id: data.request_id,
-        socket_id: data.socket_id,
-        msgType: DanmuMsgTypeEnum.danmu,
-        msg: data.data.msg,
-        userInfo: data.user_info,
-        msgIsFile: data.data.msgIsFile,
-        sendMsgTime: data.data.sendMsgTime,
-      });
-    });
-
-    // 收到disableSpeaking
-    ws.socketIo.on(
-      WsMsgTypeEnum.disableSpeaking,
-      (data: WsDisableSpeakingType['data']) => {
-        prettierReceiveWsMsg(WsMsgTypeEnum.disableSpeaking, data);
-        if (data.is_disable_speaking) {
-          window.$message.error('你已被禁言!');
-          appStore.disableSpeaking.set(data.live_room_id, {
-            exp: data.disable_expired_at,
-            label: formatDownTime({
-              startTime: +new Date(),
-              endTime: data.disable_expired_at,
-            }),
-          });
-          clearTimeout(timerObj.value[data.live_room_id]);
-          timerObj.value[data.live_room_id] = setInterval(() => {
-            if (
-              data.disable_expired_at &&
-              +new Date() > data.disable_expired_at
-            ) {
-              clearTimeout(timerObj.value[data.live_room_id]);
-            }
-            appStore.disableSpeaking.set(data.live_room_id, {
-              exp: data.disable_expired_at!,
-              label: formatDownTime({
-                startTime: +new Date(),
-                endTime: data.disable_expired_at!,
-              }),
-            });
-          }, 1000);
-          damuList.value = damuList.value.filter(
-            (v) => v.request_id !== data.request_id
-          );
-        }
-        if (data.user_id !== userStore.userInfo?.id && data.disable_ok) {
-          window.$message.success('禁言成功!');
-        }
-        if (
-          data.user_id !== userStore.userInfo?.id &&
-          data.restore_disable_ok
-        ) {
-          window.$message.success('解除禁言成功!');
-        }
-        if (
-          data.user_id === userStore.userInfo?.id &&
-          data.restore_disable_ok
-        ) {
-          window.$message.success('禁言接触了!');
-          clearTimeout(timerObj.value[data.live_room_id]);
-          appStore.disableSpeaking.delete(data.live_room_id);
-        }
-      }
-    );
-
-    // 用户加入房间完成
-    ws.socketIo.on(WsMsgTypeEnum.joined, (data: WsJoinType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
-      // liveUserList.value.push({
-      //   id: data.socket_id,
-      //   userInfo: data.user_info,
-      // });
-      appStore.setLiveRoomInfo(data.live_room);
-      anchorInfo.value = data.anchor_info;
-      ws.send<WsGetLiveUserType['data']>({
-        requestId: getRandomString(8),
-        msgType: WsMsgTypeEnum.getLiveUser,
-        data: {
-          live_room_id: data.live_room.id!,
-        },
-      });
-    });
-
-    // 其他用户加入房间
-    ws.socketIo.on(WsMsgTypeEnum.otherJoin, (data: WsOtherJoinType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.otherJoin, data);
-      // liveUserList.value.push({
-      //   id: data.join_socket_id,
-      //   userInfo: data.join_user_info,
-      // });
-      const requestId = getRandomString(8);
-      const danmu: IDanmu = {
-        request_id: requestId,
-        msgType: DanmuMsgTypeEnum.otherJoin,
-        socket_id: data.join_socket_id,
-        userInfo: data.join_user_info,
-        msgIsFile: false,
-        msg: '',
-        sendMsgTime: +new Date(),
-      };
-      damuList.value.push(danmu);
-      ws.send<WsGetLiveUserType['data']>({
-        requestId,
-        msgType: WsMsgTypeEnum.getLiveUser,
-        data: {
-          live_room_id: data.live_room.id!,
-        },
-      });
-      if (!isPull.value && !isSRS.value) {
-        if (!roomLiving.value) return;
-        // liveUserList.value.forEach(async (item) => {
-        //   const receiver = item.id;
-        //   if (
-        //     receiver === mySocketId.value ||
-        //     networkStore.getRtcMap(`${roomId.value}___${receiver!}`)
-        //   )
-        //     return;
-        //   console.warn(
-        //     '11开始new WebRTCClass',
-        //     `${roomId.value}___${receiver!}`
-        //   );
-        //   const rtc = new WebRTCClass({
-        //     maxBitrate: currentMaxBitrate.value,
-        //     maxFramerate: currentMaxFramerate.value,
-        //     resolutionRatio: currentResolutionRatio.value,
-        //     roomId: `${roomId.value}___${receiver!}`,
-        //     videoEl: createVideo({}),
-        //     isSRS: false,
-        //     receiver,
-        //   });
-        //   networkStore.updateRtcMap(`${roomId.value}___${receiver!}`, rtc);
-        //   canvasVideoStream.value?.getTracks().forEach((track) => {
-        //     if (rtc && canvasVideoStream.value) {
-        //       console.log('22canvasVideoStream插入track', track.kind, track);
-        //       rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
-        //     }
-        //   });
-        //   const ws = networkStore.wsMap.get(roomId.value)!;
-        //   const offer = await rtc.createOffer();
-        //   await rtc.setLocalDescription(offer!);
-        //   ws.send<WsOfferType['data']>({
-        //     msgType: WsMsgTypeEnum.offer,
-        //     data: {
-        //       sdp: offer,
-        //       live_room_id: Number(roomId.value),
-        //       sender: mySocketId.value,
-        //       receiver,
-        //     },
-        //   });
-        // });
-      }
-    });
-
-    // 用户离开房间
-    ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
-    });
-
-    // 用户离开房间完成
-    ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
-      if (anchorSocketId.value === data.socket_id) {
-        roomLiving.value = false;
-      }
-      networkStore.rtcMap
-        .get(`${roomId.value}___${data.socket_id as string}`)
-        ?.close();
-      networkStore.removeRtc(`${roomId.value}___${data.socket_id as string}`);
-      // const res = liveUserList.value.filter(
-      //   (item) => item.id !== data.socket_id
-      // );
-      // liveUserList.value = res;
-      damuList.value.push({
-        socket_id: data.socket_id,
-        msgType: DanmuMsgTypeEnum.userLeaved,
-        msgIsFile: false,
-        userInfo: data.user_info,
-        msg: '',
-        sendMsgTime: +new Date(),
-      });
-    });
-  }
-
-  function initSrsWs(data: {
-    isAnchor: boolean;
-    roomId: string;
-    currentResolutionRatio?: number;
-    currentMaxFramerate?: number;
-    currentMaxBitrate?: number;
-  }) {
-    roomId.value = data.roomId;
-    isAnchor.value = data.isAnchor;
-    if (data.currentMaxBitrate) {
-      currentMaxBitrate.value = data.currentMaxBitrate;
-    }
-    if (data.currentMaxFramerate) {
-      currentMaxFramerate.value = data.currentMaxFramerate;
-    }
-    if (data.currentResolutionRatio) {
-      currentResolutionRatio.value = data.currentResolutionRatio;
-    }
-    new WebSocketClass({
-      roomId: roomId.value,
-      url: WEBSOCKET_URL,
-      isAnchor: data.isAnchor,
-    });
-    initReceive();
-  }
-
-  return {
-    isPull,
-    initSrsWs,
-    handleStartLive,
-    mySocketId,
-    canvasVideoStream,
-    lastCoverImg,
-    roomLiving,
-    anchorInfo,
-    liveUserList,
-    damuList,
-    currentMaxFramerate,
-    currentMaxBitrate,
-    currentResolutionRatio,
-  };
-};

+ 746 - 0
src/hooks/use-websocket.ts

@@ -0,0 +1,746 @@
+import { getRandomString } from 'billd-utils';
+import { computed, onUnmounted, ref, watch } from 'vue';
+
+import { fetchRtcV1Publish } from '@/api/srs';
+import { SRS_CB_URL_PARAMS, WEBSOCKET_URL } from '@/constant';
+import {
+  DanmuMsgTypeEnum,
+  IDanmu,
+  ILiveUser,
+  IUser,
+  LiveRoomTypeEnum,
+} from '@/interface';
+import {
+  WSGetRoomAllUserType,
+  WsAnswerType,
+  WsCandidateType,
+  WsConnectStatusEnum,
+  WsDisableSpeakingType,
+  WsGetLiveUserType,
+  WsHeartbeatType,
+  WsJoinType,
+  WsLeavedType,
+  WsMessageType,
+  WsMsgTypeEnum,
+  WsOfferType,
+  WsOtherJoinType,
+  WsRoomLivingType,
+  WsStartLiveType,
+} from '@/interface-ws';
+import { WebRTCClass } from '@/network/webRTC';
+import { WebSocketClass, prettierReceiveWsMsg } from '@/network/webSocket';
+import { useAppStore } from '@/store/app';
+import { useNetworkStore } from '@/store/network';
+import { useUserStore } from '@/store/user';
+import { createVideo, formatDownTime } from '@/utils';
+
+import { useRTCParams } from './use-rtcParams';
+
+export const useWebsocket = () => {
+  const appStore = useAppStore();
+  const userStore = useUserStore();
+  const networkStore = useNetworkStore();
+  const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+
+  const loopHeartbeatTimer = ref();
+  const liveUserList = ref<ILiveUser[]>([]);
+  const roomId = ref('');
+  const isPull = ref(false);
+  const roomLiving = ref(false);
+  const isAnchor = ref(false);
+  const isSRS = ref(false);
+  const anchorInfo = ref<IUser>();
+  const anchorSocketId = ref('');
+  const canvasVideoStream = ref<MediaStream>();
+  const lastCoverImg = ref('');
+  const currentMaxBitrate = ref(maxBitrate.value[3].value);
+  const currentMaxFramerate = ref(maxFramerate.value[2].value);
+  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
+  const timerObj = ref({});
+  const damuList = ref<IDanmu[]>([]);
+
+  onUnmounted(() => {
+    clearInterval(loopHeartbeatTimer.value);
+  });
+
+  watch(
+    () => appStore.pkStream,
+    (newval) => {
+      console.log('转推到srs', newval);
+      if (newval && isAnchor.value) {
+        srsWebRtc.sendOffer({
+          isPk: true,
+          sender: mySocketId.value,
+        });
+      }
+    }
+  );
+
+  const mySocketId = computed(() => {
+    return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
+  });
+
+  function handleHeartbeat(socketId: string) {
+    loopHeartbeatTimer.value = setInterval(() => {
+      const ws = networkStore.wsMap.get(roomId.value);
+      if (!ws) return;
+      ws.send<WsHeartbeatType['data']>({
+        requestId: getRandomString(8),
+        msgType: WsMsgTypeEnum.heartbeat,
+        data: {
+          socket_id: socketId,
+        },
+      });
+    }, 1000 * 5);
+  }
+
+  function handleStartLive({
+    coverImg,
+    name,
+    type,
+    msrDelay,
+  }: {
+    coverImg?: string;
+    name?: string;
+    type: LiveRoomTypeEnum;
+    videoEl?: HTMLVideoElement;
+    msrDelay: number;
+  }) {
+    networkStore.wsMap.get(roomId.value)?.send<WsStartLiveType['data']>({
+      requestId: getRandomString(8),
+      msgType: WsMsgTypeEnum.startLive,
+      data: {
+        cover_img: coverImg!,
+        name: name!,
+        type,
+        msrDelay,
+      },
+    });
+    if (type === LiveRoomTypeEnum.user_msr) {
+      return;
+    }
+    isSRS.value = true;
+    if (
+      ![LiveRoomTypeEnum.user_wertc, LiveRoomTypeEnum.user_pk].includes(type)
+    ) {
+      srsWebRtc.sendOffer({
+        isPk: false,
+        sender: mySocketId.value,
+      });
+    }
+  }
+
+  function sendJoin() {
+    const instance = networkStore.wsMap.get(roomId.value);
+    if (!instance) return;
+    instance.send<WsJoinType['data']>({
+      requestId: getRandomString(8),
+      msgType: WsMsgTypeEnum.join,
+      data: {
+        socket_id: mySocketId.value,
+        live_room: {
+          id: Number(roomId.value),
+        },
+        user_info: userStore.userInfo,
+      },
+    });
+  }
+
+  async function handleUserMedia({ video, audio }) {
+    try {
+      const event = await navigator.mediaDevices.getUserMedia({
+        video,
+        audio,
+      });
+      return event;
+    } catch (error) {
+      console.log(error);
+    }
+  }
+
+  const nativeWebRtc = {
+    newWebrtc: ({
+      isAnchor,
+      sender,
+      receiver,
+      videoEl,
+    }: {
+      isAnchor: boolean;
+      sender: string;
+      receiver: string;
+      videoEl: HTMLVideoElement;
+    }) => {
+      return new WebRTCClass({
+        maxBitrate: currentMaxBitrate.value,
+        maxFramerate: currentMaxFramerate.value,
+        resolutionRatio: currentResolutionRatio.value,
+        isSRS: false,
+        roomId: `${roomId.value}`,
+        isAnchor,
+        videoEl,
+        sender,
+        receiver,
+        localStream: canvasVideoStream.value,
+      });
+    },
+    /**
+     * 原生webrtc视频通话
+     * 视频发起方是房主,房主发offer给用户
+     */
+    sendOffer: async ({
+      sender,
+      receiver,
+    }: {
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('开始nativeWebRtc的sendOffer', { sender, receiver });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = nativeWebRtc.newWebrtc({
+          isAnchor: true,
+          sender,
+          receiver,
+          videoEl: createVideo({
+            appendChild: true,
+          }),
+        });
+        canvasVideoStream.value?.getTracks().forEach((track) => {
+          if (rtc && canvasVideoStream.value) {
+            console.log(
+              'nativeWebRtc的canvasVideoStream插入track',
+              track.kind,
+              track
+            );
+            rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
+          }
+        });
+        const offerSdp = await rtc.createOffer();
+        if (!offerSdp) {
+          console.error('nativeWebRtc的offerSdp为空');
+          return;
+        }
+        await rtc.setLocalDescription(offerSdp!);
+        networkStore.wsMap.get(roomId.value)?.send<WsOfferType['data']>({
+          requestId: getRandomString(8),
+          msgType: WsMsgTypeEnum.nativeWebRtcOffer,
+          data: {
+            live_room_id: Number(roomId.value),
+            sender,
+            receiver,
+            sdp: offerSdp,
+          },
+        });
+      } catch (error) {
+        console.error('nativeWebRtc的sendOffer错误');
+      }
+    },
+    /**
+     * 原生webrtc视频通话
+     * 用户收到房主的offer,用户回复房主answer
+     */
+    sendAnswer: async ({
+      sdp,
+      sender,
+      receiver,
+    }: {
+      sdp: RTCSessionDescriptionInit;
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('开始nativeWebRtc的sendAnswer', { sender, receiver, sdp });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = nativeWebRtc.newWebrtc({
+          isAnchor: false,
+          sender,
+          receiver,
+          videoEl: createVideo({ appendChild: true }),
+        });
+        const stream = await handleUserMedia({ video: true, audio: true });
+        if (rtc?.peerConnection) {
+          rtc.peerConnection.onnegotiationneeded = (event) => {
+            console.log('onnegotiationneeded', event);
+          };
+          stream?.getTracks().forEach((track) => {
+            console.log(rtc, stream, track);
+            rtc.peerConnection?.addTrack(track, stream);
+          });
+        }
+        await rtc.setRemoteDescription(sdp);
+        const answerSdp = await rtc.createAnswer();
+        if (!answerSdp) {
+          console.error('nativeWebRtc的answerSdp为空');
+          return;
+        }
+        await rtc.setLocalDescription(answerSdp);
+        networkStore.wsMap.get(roomId.value)?.send<WsAnswerType['data']>({
+          requestId: getRandomString(8),
+          msgType: WsMsgTypeEnum.nativeWebRtcAnswer,
+          data: {
+            live_room_id: Number(roomId.value),
+            sender: receiver,
+            receiver: sender,
+            sdp: answerSdp,
+          },
+        });
+      } catch (error) {
+        console.error('nativeWebRtc的sendAnswer错误');
+      }
+    },
+  };
+
+  const srsWebRtc = {
+    newWebrtc: ({
+      roomId,
+      sender,
+      videoEl,
+    }: {
+      roomId: string;
+      sender: string;
+      videoEl: HTMLVideoElement;
+    }) => {
+      return new WebRTCClass({
+        isAnchor: true,
+        maxBitrate: currentMaxBitrate.value,
+        maxFramerate: currentMaxFramerate.value,
+        resolutionRatio: currentResolutionRatio.value,
+        roomId,
+        videoEl,
+        isSRS: true,
+        sender,
+        receiver: 'srs',
+        localStream: canvasVideoStream.value,
+      });
+    },
+    /**
+     * srs的webrtc推流视频通话
+     * 视频发起方是房主,房主发offer给srs
+     */
+    sendOffer: async ({ isPk, sender }: { isPk: boolean; sender: string }) => {
+      console.log('开始srsWebRtc的sendOffer', { sender });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = srsWebRtc.newWebrtc({
+          roomId: `${isPk ? `pk-${roomId.value}` : `${roomId.value}`}`,
+          sender,
+          videoEl: createVideo({ appendChild: true }),
+        });
+        canvasVideoStream.value?.getTracks().forEach((track) => {
+          if (rtc && canvasVideoStream.value) {
+            console.log(
+              'srsWebRtc的canvasVideoStream插入track',
+              track.kind,
+              track
+            );
+            rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
+          }
+        });
+        const offerSdp = await rtc.createOffer();
+        if (!offerSdp) {
+          console.error('srsWebRtc的offerSdp为空');
+          return;
+        }
+        await rtc.setLocalDescription(offerSdp!);
+        const myLiveRoom = userStore.userInfo!.live_rooms![0];
+        const answerRes = await fetchRtcV1Publish({
+          api: `/rtc/v1/publish/`,
+          clientip: null,
+          sdp: offerSdp!.sdp!,
+          streamurl: `${myLiveRoom.rtmp_url!}?${
+            SRS_CB_URL_PARAMS.publishKey
+          }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
+            LiveRoomTypeEnum.user_srs
+          }`,
+          tid: getRandomString(10),
+        });
+        if (answerRes.data.code !== 0) {
+          console.error('/rtc/v1/publish/拿不到sdp');
+          window.$message.error('/rtc/v1/publish/拿不到sdp');
+          return;
+        }
+        await rtc.setRemoteDescription(
+          new RTCSessionDescription({ type: 'answer', sdp: answerRes.data.sdp })
+        );
+      } catch (error) {
+        console.error('srsWebRtc的sendOffer错误');
+      }
+    },
+  };
+
+  function initReceive() {
+    const ws = networkStore.wsMap.get(roomId.value);
+    if (!ws?.socketIo) return;
+    // websocket连接成功
+    ws.socketIo.on(WsConnectStatusEnum.connect, () => {
+      prettierReceiveWsMsg(WsConnectStatusEnum.connect, ws.socketIo);
+      handleHeartbeat(ws.socketIo!.id);
+      if (!ws) return;
+      ws.status = WsConnectStatusEnum.connect;
+      ws.update();
+      sendJoin();
+    });
+
+    // websocket连接断开
+    ws.socketIo.on(WsConnectStatusEnum.disconnect, (err) => {
+      prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws);
+      console.log('websocket连接断开', err);
+      if (!ws) return;
+      ws.status = WsConnectStatusEnum.disconnect;
+      ws.update();
+    });
+
+    // 收到srsOffer
+    ws.socketIo.on(
+      WsMsgTypeEnum.srsOffer,
+      async (data: WsOfferType['data']) => {
+        console.log('收到srsOffer', data);
+        if (data.receiver === mySocketId.value) {
+          console.warn('是发给我的srsOffer');
+          const videoEl = createVideo({ appendChild: true });
+          const rtc = new WebRTCClass({
+            isAnchor: true,
+            maxBitrate: currentMaxBitrate.value,
+            maxFramerate: currentMaxFramerate.value,
+            resolutionRatio: currentResolutionRatio.value,
+            roomId: `${roomId.value}`,
+            videoEl,
+            isSRS: true,
+            sender: data.sender,
+            receiver: data.receiver,
+          });
+          isSRS.value = true;
+          await rtc.setRemoteDescription(data.sdp);
+          const answerSdp = await rtc.createAnswer();
+          if (answerSdp) {
+            await rtc.setLocalDescription(answerSdp);
+            ws.send<WsAnswerType['data']>({
+              requestId: getRandomString(8),
+              msgType: WsMsgTypeEnum.srsAnswer,
+              data: {
+                live_room_id: Number(roomId.value),
+                sdp: answerSdp,
+                receiver: data.sender,
+                sender: mySocketId.value,
+              },
+            });
+          } else {
+            console.error('srsOffer的answerSdp为空');
+          }
+        } else {
+          console.error('不是发给我的srsOffer');
+        }
+      }
+    );
+
+    // 收到srsAnswer
+    ws.socketIo.on(WsMsgTypeEnum.srsAnswer, (data: WsAnswerType['data']) => {
+      console.log('收到srsAnswer', data);
+      if (data.receiver === mySocketId.value) {
+        console.warn('是发给我的srsAnswer');
+        const rtc = networkStore.getRtcMap(`${roomId.value}`)!;
+        rtc.setRemoteDescription(data.sdp);
+      } else {
+        console.error('不是发给我的srsAnswer');
+      }
+    });
+
+    // 收到srsCandidate
+    ws.socketIo.on(
+      WsMsgTypeEnum.srsCandidate,
+      (data: WsCandidateType['data']) => {
+        console.log('收到srsCandidate', data);
+        if (data.receiver === mySocketId.value) {
+          console.warn('是发给我的srsCandidate');
+          const rtc = networkStore.getRtcMap(`${roomId.value}`)!;
+          rtc.addIceCandidate(data.candidate);
+        } else {
+          console.error('不是发给我的srsCandidate');
+        }
+      }
+    );
+
+    // 收到nativeWebRtcOffer
+    ws.socketIo.on(
+      WsMsgTypeEnum.nativeWebRtcOffer,
+      async (data: WsOfferType['data']) => {
+        console.log('收到nativeWebRtcOffer', data);
+        if (data.receiver === mySocketId.value) {
+          console.warn('是发给我的nativeWebRtcOffer');
+          await nativeWebRtc.sendAnswer({
+            sender: data.sender,
+            receiver: data.receiver,
+            sdp: data.sdp,
+          });
+        } else {
+          console.error('不是发给我的nativeWebRtcOffer');
+        }
+      }
+    );
+
+    // 收到nativeWebRtcAnswer
+    ws.socketIo.on(
+      WsMsgTypeEnum.nativeWebRtcAnswer,
+      async (data: WsAnswerType['data']) => {
+        console.log('收到nativeWebRtcAnswer', data);
+        if (data.receiver === mySocketId.value) {
+          console.warn('是发给我的nativeWebRtcAnswer');
+          const rtc = networkStore.getRtcMap(`${roomId.value}`)!;
+          await rtc.setRemoteDescription(data.sdp);
+        } else {
+          console.error('不是发给我的nativeWebRtcAnswer');
+        }
+      }
+    );
+
+    // 收到nativeWebRtcCandidate
+    ws.socketIo.on(
+      WsMsgTypeEnum.nativeWebRtcCandidate,
+      (data: WsCandidateType['data']) => {
+        console.log('收到nativeWebRtcCandidate', data);
+        if (data.receiver === mySocketId.value) {
+          console.warn('是发给我的nativeWebRtcCandidate');
+          const rtc = networkStore.getRtcMap(`${roomId.value}`)!;
+          rtc.addIceCandidate(data.candidate);
+        } else {
+          console.error('不是发给我的nativeWebRtcCandidate');
+        }
+      }
+    );
+
+    // 主播正在直播
+    ws.socketIo.on(
+      WsMsgTypeEnum.roomLiving,
+      (data: WsRoomLivingType['data']) => {
+        prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
+        roomLiving.value = true;
+        if (data.anchor_socket_id) {
+          anchorSocketId.value = data.anchor_socket_id;
+        }
+        isSRS.value = true;
+        if (
+          [LiveRoomTypeEnum.user_wertc, LiveRoomTypeEnum.user_pk].includes(
+            data.live_room.type!
+          )
+        ) {
+          isSRS.value = false;
+        }
+      }
+    );
+
+    // 主播不在直播
+    ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data);
+      roomLiving.value = false;
+    });
+
+    // 当前所有在线用户
+    ws.socketIo.on(
+      WsMsgTypeEnum.liveUser,
+      (data: WSGetRoomAllUserType['data']) => {
+        console.log('当前所有在线用户', data.liveUser);
+        prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data);
+        liveUserList.value = data.liveUser;
+      }
+    );
+
+    // 收到用户发送消息
+    ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.message, data);
+      damuList.value.push({
+        request_id: data.request_id,
+        socket_id: data.socket_id,
+        msgType: DanmuMsgTypeEnum.danmu,
+        msg: data.data.msg,
+        userInfo: data.user_info,
+        msgIsFile: data.data.msgIsFile,
+        sendMsgTime: data.data.sendMsgTime,
+      });
+    });
+
+    // 收到disableSpeaking
+    ws.socketIo.on(
+      WsMsgTypeEnum.disableSpeaking,
+      (data: WsDisableSpeakingType['data']) => {
+        prettierReceiveWsMsg(WsMsgTypeEnum.disableSpeaking, data);
+        if (data.is_disable_speaking) {
+          window.$message.error('你已被禁言!');
+          appStore.disableSpeaking.set(data.live_room_id, {
+            exp: data.disable_expired_at,
+            label: formatDownTime({
+              startTime: +new Date(),
+              endTime: data.disable_expired_at,
+            }),
+          });
+          clearTimeout(timerObj.value[data.live_room_id]);
+          timerObj.value[data.live_room_id] = setInterval(() => {
+            if (
+              data.disable_expired_at &&
+              +new Date() > data.disable_expired_at
+            ) {
+              clearTimeout(timerObj.value[data.live_room_id]);
+            }
+            appStore.disableSpeaking.set(data.live_room_id, {
+              exp: data.disable_expired_at!,
+              label: formatDownTime({
+                startTime: +new Date(),
+                endTime: data.disable_expired_at!,
+              }),
+            });
+          }, 1000);
+          damuList.value = damuList.value.filter(
+            (v) => v.request_id !== data.request_id
+          );
+        }
+        if (data.user_id !== userStore.userInfo?.id && data.disable_ok) {
+          window.$message.success('禁言成功!');
+        }
+        if (
+          data.user_id !== userStore.userInfo?.id &&
+          data.restore_disable_ok
+        ) {
+          window.$message.success('解除禁言成功!');
+        }
+        if (
+          data.user_id === userStore.userInfo?.id &&
+          data.restore_disable_ok
+        ) {
+          window.$message.success('禁言接触了!');
+          clearTimeout(timerObj.value[data.live_room_id]);
+          appStore.disableSpeaking.delete(data.live_room_id);
+        }
+      }
+    );
+
+    // 用户加入房间完成
+    ws.socketIo.on(WsMsgTypeEnum.joined, (data: WsJoinType['data']) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
+      // liveUserList.value.push({
+      //   id: data.socket_id,
+      //   userInfo: data.user_info,
+      // });
+      appStore.setLiveRoomInfo(data.live_room);
+      anchorInfo.value = data.anchor_info;
+      ws.send<WsGetLiveUserType['data']>({
+        requestId: getRandomString(8),
+        msgType: WsMsgTypeEnum.getLiveUser,
+        data: {
+          live_room_id: data.live_room.id!,
+        },
+      });
+    });
+
+    // 其他用户加入房间
+    ws.socketIo.on(WsMsgTypeEnum.otherJoin, (data: WsOtherJoinType['data']) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.otherJoin, data);
+      // liveUserList.value.push({
+      //   id: data.join_socket_id,
+      //   userInfo: data.join_user_info,
+      // });
+      const requestId = getRandomString(8);
+      const danmu: IDanmu = {
+        request_id: requestId,
+        msgType: DanmuMsgTypeEnum.otherJoin,
+        socket_id: data.join_socket_id,
+        userInfo: data.join_user_info,
+        msgIsFile: false,
+        msg: '',
+        sendMsgTime: +new Date(),
+      };
+      damuList.value.push(danmu);
+      ws.send<WsGetLiveUserType['data']>({
+        requestId,
+        msgType: WsMsgTypeEnum.getLiveUser,
+        data: {
+          live_room_id: data.live_room.id!,
+        },
+      });
+      if (!isPull.value && !isSRS.value) {
+        if (!roomLiving.value) return;
+      }
+      if (
+        [LiveRoomTypeEnum.user_wertc, LiveRoomTypeEnum.user_pk].includes(
+          data.live_room.type!
+        )
+      ) {
+        isSRS.value = false;
+        nativeWebRtc.sendOffer({
+          sender: mySocketId.value,
+          receiver: data.join_socket_id,
+        });
+      }
+    });
+
+    // 用户离开房间
+    ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
+    });
+
+    // 用户离开房间完成
+    ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
+      if (anchorSocketId.value === data.socket_id) {
+        roomLiving.value = false;
+      }
+      networkStore.rtcMap.get(`${roomId.value}`)?.close();
+      networkStore.removeRtc(`${roomId.value}`);
+      // const res = liveUserList.value.filter(
+      //   (item) => item.id !== data.socket_id
+      // );
+      // liveUserList.value = res;
+      damuList.value.push({
+        socket_id: data.socket_id,
+        msgType: DanmuMsgTypeEnum.userLeaved,
+        msgIsFile: false,
+        userInfo: data.user_info,
+        msg: '',
+        sendMsgTime: +new Date(),
+      });
+    });
+  }
+
+  function initSrsWs(data: {
+    isAnchor: boolean;
+    roomId: string;
+    currentResolutionRatio?: number;
+    currentMaxFramerate?: number;
+    currentMaxBitrate?: number;
+  }) {
+    roomId.value = data.roomId;
+    isAnchor.value = data.isAnchor;
+    if (data.currentMaxBitrate) {
+      currentMaxBitrate.value = data.currentMaxBitrate;
+    }
+    if (data.currentMaxFramerate) {
+      currentMaxFramerate.value = data.currentMaxFramerate;
+    }
+    if (data.currentResolutionRatio) {
+      currentResolutionRatio.value = data.currentResolutionRatio;
+    }
+    new WebSocketClass({
+      roomId: roomId.value,
+      url: WEBSOCKET_URL,
+      isAnchor: data.isAnchor,
+    });
+    initReceive();
+  }
+
+  return {
+    isPull,
+    initSrsWs,
+    handleStartLive,
+    mySocketId,
+    canvasVideoStream,
+    lastCoverImg,
+    roomLiving,
+    anchorInfo,
+    liveUserList,
+    damuList,
+    currentMaxFramerate,
+    currentMaxBitrate,
+    currentResolutionRatio,
+  };
+};

+ 6 - 4
src/interface-ws.ts

@@ -54,9 +54,9 @@ export enum WsMsgTypeEnum {
   /** 主播踢掉用户 */
   /** 主播踢掉用户 */
   kick = 'kick',
   kick = 'kick',
 
 
-  offer = 'offer',
-  answer = 'answer',
-  candidate = 'candidate',
+  srsOffer = 'srsOffer',
+  srsAnswer = 'srsAnswer',
+  srsCandidate = 'srsCandidate',
 
 
   nativeWebRtcOffer = 'nativeWebRtcOffer',
   nativeWebRtcOffer = 'nativeWebRtcOffer',
   nativeWebRtcAnswer = 'nativeWebRtcAnswer',
   nativeWebRtcAnswer = 'nativeWebRtcAnswer',
@@ -153,7 +153,8 @@ export type WsStartLiveType = IWsFormat<{
   cover_img: string;
   cover_img: string;
   name: string;
   name: string;
   type: LiveRoomTypeEnum;
   type: LiveRoomTypeEnum;
-  chunkDelay: number;
+  /** 单位:毫秒 */
+  msrDelay: number;
 }>;
 }>;
 
 
 /** 用户加入直播间 */
 /** 用户加入直播间 */
@@ -180,6 +181,7 @@ export type WsMsrBlobType = IWsFormat<{
   live_room_id: number;
   live_room_id: number;
   blob: any;
   blob: any;
   blob_id: string;
   blob_id: string;
+  /** 单位:毫秒 */
   delay: number;
   delay: number;
 }>;
 }>;
 
 

+ 3 - 0
src/interface.ts

@@ -300,6 +300,8 @@ export enum LiveRoomTypeEnum {
   user_obs,
   user_obs,
   /** 主播使用msr直播 */
   /** 主播使用msr直播 */
   user_msr,
   user_msr,
+  /** 主播打pk */
+  user_pk,
 }
 }
 
 
 export interface BilldHtmlWebpackPluginLog {
 export interface BilldHtmlWebpackPluginLog {
@@ -468,6 +470,7 @@ export enum MediaTypeEnum {
   time,
   time,
   stopwatch,
   stopwatch,
   webAudio,
   webAudio,
+  pk,
 }
 }
 
 
 export enum DanmuMsgTypeEnum {
 export enum DanmuMsgTypeEnum {

+ 6 - 0
src/layout/pc/head/index.vue

@@ -269,6 +269,12 @@
               >
               >
                 <div class="txt">msr开播</div>
                 <div class="txt">msr开播</div>
               </a>
               </a>
+              <a
+                class="item"
+                @click.prevent="handleStartLive(LiveRoomTypeEnum.user_pk)"
+              >
+                <div class="txt">打PK</div>
+              </a>
             </div>
             </div>
           </template>
           </template>
         </Dropdown>
         </Dropdown>

+ 26 - 8
src/network/webRTC.ts

@@ -64,7 +64,9 @@ export async function handleMaxFramerate(data: {
 }
 }
 
 
 export class WebRTCClass {
 export class WebRTCClass {
+  isAnchor = false;
   roomId = '-1';
   roomId = '-1';
+  sender = '';
   receiver = '';
   receiver = '';
 
 
   videoEl: HTMLVideoElement;
   videoEl: HTMLVideoElement;
@@ -83,17 +85,22 @@ export class WebRTCClass {
   isSRS: boolean;
   isSRS: boolean;
 
 
   constructor(data: {
   constructor(data: {
+    isAnchor: boolean;
     roomId: string;
     roomId: string;
     videoEl: HTMLVideoElement;
     videoEl: HTMLVideoElement;
     maxBitrate?: number;
     maxBitrate?: number;
     maxFramerate?: number;
     maxFramerate?: number;
     resolutionRatio?: number;
     resolutionRatio?: number;
     isSRS: boolean;
     isSRS: boolean;
+    sender: string;
     receiver: string;
     receiver: string;
     localStream?: MediaStream;
     localStream?: MediaStream;
   }) {
   }) {
+    this.isAnchor = data.isAnchor;
     this.roomId = data.roomId;
     this.roomId = data.roomId;
     this.videoEl = data.videoEl;
     this.videoEl = data.videoEl;
+    // document.body.appendChild(this.videoEl);
+    this.sender = data.sender;
     this.receiver = data.receiver;
     this.receiver = data.receiver;
     this.localStream = data.localStream;
     this.localStream = data.localStream;
     if (data.maxBitrate) {
     if (data.maxBitrate) {
@@ -205,10 +212,11 @@ export class WebRTCClass {
         });
         });
       }
       }
     });
     });
-    if (addTrack.length) {
-      appStore.setAllTrack([...appStore.allTrack, ...addTrack]);
-    }
+    // if (addTrack.length) {
+    //   appStore.setAllTrack([...appStore.allTrack, ...addTrack]);
+    // }
     this.localStream = stream;
     this.localStream = stream;
+    appStore.pkStream = stream;
   };
   };
 
 
   /** 设置分辨率 */
   /** 设置分辨率 */
@@ -394,15 +402,21 @@ export class WebRTCClass {
       if (event.candidate) {
       if (event.candidate) {
         const networkStore = useNetworkStore();
         const networkStore = useNetworkStore();
         console.log('准备发送candidate', event.candidate.candidate);
         console.log('准备发送candidate', event.candidate.candidate);
-        const roomId = this.roomId.split('___')[0];
-        const receiver = this.roomId.split('___')[1];
+        const roomId = this.roomId;
+        console.log({
+          roomId,
+          sender: this.sender,
+          receiver: this.receiver,
+        });
         networkStore.wsMap.get(roomId)?.send<WsCandidateType['data']>({
         networkStore.wsMap.get(roomId)?.send<WsCandidateType['data']>({
           requestId: getRandomString(8),
           requestId: getRandomString(8),
-          msgType: WsMsgTypeEnum.candidate,
+          msgType: this.isSRS
+            ? WsMsgTypeEnum.srsCandidate
+            : WsMsgTypeEnum.nativeWebRtcCandidate,
           data: {
           data: {
             candidate: event.candidate,
             candidate: event.candidate,
-            sender: networkStore.wsMap.get(roomId)?.socketIo?.id || '',
-            receiver,
+            sender: this.isAnchor ? this.sender : this.receiver,
+            receiver: this.isAnchor ? this.receiver : this.sender,
             live_room_id: Number(roomId),
             live_room_id: Number(roomId),
           },
           },
         });
         });
@@ -494,6 +508,10 @@ export class WebRTCClass {
         }
         }
       }
       }
     );
     );
+    // negotiationneeded
+    // this.peerConnection.addEventListener('negotiationneeded', (event: any) => {
+    //   console.log(this.roomId, 'pc收到negotiationneeded', event);
+    // });
   };
   };
 
 
   // 创建对等连接
   // 创建对等连接

+ 1 - 0
src/store/app/index.ts

@@ -39,6 +39,7 @@ export type AppRootState = {
   liveRoomInfo?: ILiveRoom;
   liveRoomInfo?: ILiveRoom;
   showLoginModal: boolean;
   showLoginModal: boolean;
   disableSpeaking: Map<number, { exp: number; label: string }>;
   disableSpeaking: Map<number, { exp: number; label: string }>;
+  pkStream?: MediaStream;
 };
 };
 
 
 export const useAppStore = defineStore('app', {
 export const useAppStore = defineStore('app', {

+ 6 - 1
src/utils/index.ts

@@ -299,13 +299,15 @@ export const createVideo = ({
   autoplay = true,
   autoplay = true,
   appendChild = false,
   appendChild = false,
   show = false,
   show = false,
+  controls = false,
+  size = 100,
 }) => {
 }) => {
   const videoEl = document.createElement('video');
   const videoEl = document.createElement('video');
   videoEl.autoplay = autoplay;
   videoEl.autoplay = autoplay;
   videoEl.muted = muted;
   videoEl.muted = muted;
   videoEl.playsInline = true;
   videoEl.playsInline = true;
   videoEl.loop = true;
   videoEl.loop = true;
-  videoEl.controls = false;
+  videoEl.controls = controls;
   videoEl.setAttribute('webkit-playsinline', 'true');
   videoEl.setAttribute('webkit-playsinline', 'true');
   videoEl.setAttribute('x5-video-player-type', 'h5');
   videoEl.setAttribute('x5-video-player-type', 'h5');
   videoEl.setAttribute('x5-video-player-fullscreen', 'true');
   videoEl.setAttribute('x5-video-player-fullscreen', 'true');
@@ -319,6 +321,9 @@ export const createVideo = ({
       videoEl.style.height = `1px`;
       videoEl.style.height = `1px`;
       videoEl.style.opacity = '0';
       videoEl.style.opacity = '0';
       videoEl.style.pointerEvents = 'none';
       videoEl.style.pointerEvents = 'none';
+    } else {
+      videoEl.style.width = `${size}px`;
+      videoEl.style.height = `${size}px`;
     }
     }
     videoEl.style.position = 'fixed';
     videoEl.style.position = 'fixed';
     videoEl.style.bottom = '0';
     videoEl.style.bottom = '0';

+ 3 - 1
src/views/privatizationDeployment/index.vue

@@ -38,7 +38,9 @@
         <div class="hr"></div>
         <div class="hr"></div>
         <div class="item">
         <div class="item">
           <h2>联系方式</h2>
           <h2>联系方式</h2>
-          <p>微信号: shuisheng9905,加微信请备注: 私有化部署+用途</p>
+          <p>添加时请备注: 私有化部署+用途</p>
+          <p>微信号: shuisheng9905</p>
+          <p>qq号: 2274751790</p>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>

+ 117 - 17
src/views/push/index.vue

@@ -429,6 +429,7 @@ const startTime = ref(+new Date());
 // const startTime = ref(1692807352565); // 1693027352565
 // const startTime = ref(1692807352565); // 1693027352565
 const msgLoading = ref(false);
 const msgLoading = ref(false);
 const uploadRef = ref<HTMLInputElement>();
 const uploadRef = ref<HTMLInputElement>();
+const nullAudioStream = ref<MediaStream>();
 
 
 const timeCanvasDom = ref<Raw<fabric.Text>[]>([]);
 const timeCanvasDom = ref<Raw<fabric.Text>[]>([]);
 const stopwatchCanvasDom = ref<Raw<fabric.Text>[]>([]);
 const stopwatchCanvasDom = ref<Raw<fabric.Text>[]>([]);
@@ -442,7 +443,7 @@ const liveType = Number(route.query.liveType);
 const recorder = ref<MediaRecorder>();
 const recorder = ref<MediaRecorder>();
 const sendBlobTimer = ref();
 const sendBlobTimer = ref();
 const bolbId = ref(0);
 const bolbId = ref(0);
-const chunkDelay = ref(1000 * 2);
+const msrDelay = ref(1000 * 2);
 
 
 watch(
 watch(
   () => roomLiving.value,
   () => roomLiving.value,
@@ -476,6 +477,29 @@ watch(
   }
   }
 );
 );
 
 
+watch(
+  () => appStore.pkStream,
+  (newval) => {
+    console.log('pkStream', newval);
+    if (newval) {
+      addMediaOk({
+        id: getRandomEnglishString(8),
+        audio: 2,
+        video: 1,
+        mediaName: 'pkStream',
+        type: MediaTypeEnum.pk,
+        track: newval.getVideoTracks()[0],
+        trackid: newval.getVideoTracks()[0].id,
+        stream: newval,
+        streamid: newval.id,
+        hidden: false,
+        muted: false,
+        scaleInfo: {},
+      });
+    }
+  }
+);
+
 watch(
 watch(
   () => damuList.value.length,
   () => damuList.value.length,
   () => {
   () => {
@@ -496,7 +520,7 @@ function handleSendBlob(event: BlobEvent) {
   sendBlob({
   sendBlob({
     blob: event.data,
     blob: event.data,
     blobId: `${bolbId.value}`,
     blobId: `${bolbId.value}`,
-    delay: chunkDelay.value,
+    delay: msrDelay.value,
   });
   });
 }
 }
 
 
@@ -600,7 +624,7 @@ function handleMsr(stream: MediaStream) {
   sendBlobTimer.value = setInterval(function () {
   sendBlobTimer.value = setInterval(function () {
     recorder.value?.stop();
     recorder.value?.stop();
     recorder.value?.start();
     recorder.value?.start();
-  }, chunkDelay.value);
+  }, msrDelay.value);
 }
 }
 
 
 onMounted(() => {
 onMounted(() => {
@@ -658,9 +682,55 @@ function renderFrame() {
   }, delay);
   }, delay);
 }
 }
 
 
+function handleNullAudio() {
+  // 创建AudioContext对象
+  const audioContext = new window.AudioContext();
+
+  // 创建输入和输出节点
+  const source = audioContext.createBufferSource();
+  const destination = audioContext.createMediaStreamDestination();
+
+  // 连接输入和输出节点
+  source.connect(destination);
+
+  // 播放空白音频
+  source.start();
+
+  // 获取音频流
+  const stream = destination.stream;
+
+  // 检查是否已经获取到音频流
+  if (stream) {
+    console.log('已创建空的直播音频流');
+    const video = createVideo({
+      appendChild: true,
+    });
+    video.srcObject = stream;
+    nullAudioStream.value = stream;
+  } else {
+    console.error('无法创建空的直播音频流');
+  }
+}
+
 function handleMixedAudio() {
 function handleMixedAudio() {
-  // console.log('handleMixedAudio');
   const allAudioTrack = appStore.allTrack.filter((item) => item.audio === 1);
   const allAudioTrack = appStore.allTrack.filter((item) => item.audio === 1);
+  const nullAudio = nullAudioStream.value?.getAudioTracks()[0];
+  if (nullAudio) {
+    allAudioTrack.push({
+      id: getRandomEnglishString(8),
+      audio: 2,
+      video: 1,
+      mediaName: '占位空音频',
+      type: MediaTypeEnum.webAudio,
+      track: nullAudio,
+      trackid: nullAudio.id,
+      stream: nullAudioStream.value,
+      streamid: nullAudioStream.value?.id,
+      hidden: false,
+      muted: false,
+      scaleInfo: {},
+    });
+  }
   const audioCtx = new AudioContext();
   const audioCtx = new AudioContext();
   if (canvasVideoStream.value?.getAudioTracks()[0]) {
   if (canvasVideoStream.value?.getAudioTracks()[0]) {
     canvasVideoStream.value.removeTrack(
     canvasVideoStream.value.removeTrack(
@@ -685,7 +755,9 @@ function handleMixedAudio() {
   if (webaudioVideo.value) {
   if (webaudioVideo.value) {
     webaudioVideo.value.remove();
     webaudioVideo.value.remove();
   }
   }
-  webaudioVideo.value = createVideo({ appendChild: true, show: false });
+  webaudioVideo.value = createVideo({
+    appendChild: true,
+  });
   bodyAppendChildElArr.value.push(webaudioVideo.value);
   bodyAppendChildElArr.value.push(webaudioVideo.value);
   webaudioVideo.value.className = 'web-audio-video';
   webaudioVideo.value.className = 'web-audio-video';
   webaudioVideo.value!.srcObject = destination.stream;
   webaudioVideo.value!.srcObject = destination.stream;
@@ -715,12 +787,12 @@ function handleStartLive() {
     window.$message.warning('至少选择一个素材');
     window.$message.warning('至少选择一个素材');
     return;
     return;
   }
   }
+  handleNullAudio();
   handleMixedAudio();
   handleMixedAudio();
   lastCoverImg.value = generateBase64(pushCanvasRef.value!);
   lastCoverImg.value = generateBase64(pushCanvasRef.value!);
   startLive({
   startLive({
     type: liveType,
     type: liveType,
-    receiver: mySocketId.value,
-    chunkDelay: chunkDelay.value,
+    msrDelay: msrDelay.value,
   });
   });
   if (liveType === LiveRoomTypeEnum.user_msr) {
   if (liveType === LiveRoomTypeEnum.user_msr) {
     const stream = pushCanvasRef.value!.captureStream();
     const stream = pushCanvasRef.value!.captureStream();
@@ -1015,6 +1087,7 @@ async function handleUserMedia({ video, audio }) {
     console.log(error);
     console.log(error);
   }
   }
 }
 }
+
 async function handleDisplayMedia({ video, audio }) {
 async function handleDisplayMedia({ video, audio }) {
   try {
   try {
     const event = await navigator.mediaDevices.getDisplayMedia({
     const event = await navigator.mediaDevices.getDisplayMedia({
@@ -1415,9 +1488,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
       const res = [...appStore.allTrack, videoTrack];
       const res = [...appStore.allTrack, videoTrack];
       appStore.setAllTrack(res);
       appStore.setAllTrack(res);
       cacheStore.setResourceList(res);
       cacheStore.setResourceList(res);
-      // @ts-ignore
     }
     }
-
     console.log('获取窗口成功');
     console.log('获取窗口成功');
   } else if (val.type === MediaTypeEnum.camera) {
   } else if (val.type === MediaTypeEnum.camera) {
     const event = await handleUserMedia({
     const event = await handleUserMedia({
@@ -1454,8 +1525,43 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     const res = [...appStore.allTrack, videoTrack];
     const res = [...appStore.allTrack, videoTrack];
     appStore.setAllTrack(res);
     appStore.setAllTrack(res);
     cacheStore.setResourceList(res);
     cacheStore.setResourceList(res);
-    // @ts-ignore
     console.log('获取摄像头成功');
     console.log('获取摄像头成功');
+  } else if (val.type === MediaTypeEnum.pk) {
+    const event = await handleUserMedia({
+      video: {
+        deviceId: val.deviceId,
+      },
+      audio: false,
+    });
+    if (!event) return;
+    const videoTrack: AppRootState['allTrack'][0] = {
+      id: getRandomEnglishString(8),
+      deviceId: val.deviceId,
+      audio: 2,
+      video: 1,
+      mediaName: val.mediaName,
+      type: MediaTypeEnum.pk,
+      track: event.getVideoTracks()[0],
+      trackid: event.getVideoTracks()[0].id,
+      stream: event,
+      streamid: event.id,
+      hidden: false,
+      muted: false,
+      scaleInfo: {},
+    };
+    const { canvasDom, videoEl, scale } = await autoCreateVideo({
+      stream: event,
+      id: videoTrack.id,
+    });
+    setScaleInfo({ canvasDom, track: videoTrack, scale });
+    videoTrack.videoEl = videoEl;
+    // @ts-ignore
+    videoTrack.canvasDom = canvasDom;
+
+    const res = [...appStore.allTrack, videoTrack];
+    appStore.setAllTrack(res);
+    cacheStore.setResourceList(res);
+    console.log('获取pk成功');
   } else if (val.type === MediaTypeEnum.microphone) {
   } else if (val.type === MediaTypeEnum.microphone) {
     const event = await handleUserMedia({
     const event = await handleUserMedia({
       video: false,
       video: false,
@@ -1487,7 +1593,6 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     appStore.setAllTrack(res);
     appStore.setAllTrack(res);
     cacheStore.setResourceList(res);
     cacheStore.setResourceList(res);
     handleMixedAudio();
     handleMixedAudio();
-
     console.log('获取麦克风成功');
     console.log('获取麦克风成功');
   } else if (val.type === MediaTypeEnum.txt) {
   } else if (val.type === MediaTypeEnum.txt) {
     const txtTrack: AppRootState['allTrack'][0] = {
     const txtTrack: AppRootState['allTrack'][0] = {
@@ -1534,7 +1639,6 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     appStore.setAllTrack(res);
     appStore.setAllTrack(res);
     // @ts-ignore
     // @ts-ignore
     cacheStore.setResourceList(res);
     cacheStore.setResourceList(res);
-
     console.log('获取文字成功', fabricCanvas.value);
     console.log('获取文字成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.time) {
   } else if (val.type === MediaTypeEnum.time) {
     const timeTrack: AppRootState['allTrack'][0] = {
     const timeTrack: AppRootState['allTrack'][0] = {
@@ -1573,7 +1677,6 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     appStore.setAllTrack(res);
     appStore.setAllTrack(res);
     // @ts-ignore
     // @ts-ignore
     cacheStore.setResourceList(res);
     cacheStore.setResourceList(res);
-
     console.log('获取时间成功', fabricCanvas.value);
     console.log('获取时间成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.stopwatch) {
   } else if (val.type === MediaTypeEnum.stopwatch) {
     const stopwatchTrack: AppRootState['allTrack'][0] = {
     const stopwatchTrack: AppRootState['allTrack'][0] = {
@@ -1613,7 +1716,6 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     appStore.setAllTrack(res);
     appStore.setAllTrack(res);
     // @ts-ignore
     // @ts-ignore
     cacheStore.setResourceList(res);
     cacheStore.setResourceList(res);
-
     console.log('获取秒表成功', fabricCanvas.value);
     console.log('获取秒表成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.img) {
   } else if (val.type === MediaTypeEnum.img) {
     const imgTrack: AppRootState['allTrack'][0] = {
     const imgTrack: AppRootState['allTrack'][0] = {
@@ -1675,7 +1777,6 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     appStore.setAllTrack(res);
     appStore.setAllTrack(res);
     // @ts-ignore
     // @ts-ignore
     cacheStore.setResourceList(res);
     cacheStore.setResourceList(res);
-
     console.log('获取图片成功', fabricCanvas.value);
     console.log('获取图片成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.media) {
   } else if (val.type === MediaTypeEnum.media) {
     const mediaVideoTrack: AppRootState['allTrack'][0] = {
     const mediaVideoTrack: AppRootState['allTrack'][0] = {
@@ -1746,7 +1847,6 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     appStore.setAllTrack(res);
     appStore.setAllTrack(res);
     // @ts-ignore
     // @ts-ignore
     cacheStore.setResourceList(res);
     cacheStore.setResourceList(res);
-
     console.log('获取视频成功', fabricCanvas.value);
     console.log('获取视频成功', fabricCanvas.value);
   }
   }
 }
 }
@@ -2023,7 +2123,7 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
       }
       }
       .list {
       .list {
         overflow: scroll;
         overflow: scroll;
-        height: 300px;
+        height: 266px;
 
 
         @extend %customScrollbar;
         @extend %customScrollbar;