shuisheng 2 лет назад
Родитель
Сommit
a76410a8b7

+ 10 - 15
src/hooks/use-pull.ts

@@ -3,7 +3,7 @@ import { Ref, nextTick, onUnmounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
-import { useWs } from '@/hooks/use-ws';
+import { useSrsWs } from '@/hooks/use-srs-ws';
 import { DanmuMsgTypeEnum, IDanmu, IMessage, liveTypeEnum } from '@/interface';
 import { WsMsgTypeEnum } from '@/network/webSocket';
 import { useAppStore } from '@/store/app';
@@ -39,24 +39,22 @@ export function usePull({
   const videoElArr = ref<HTMLVideoElement[]>([]);
   const remoteVideo = ref<HTMLElement[]>([]);
   const {
-    getSocketId,
-    initWs,
+    mySocketId,
+    initSrsWs,
     roomLiving,
     liveRoomInfo,
     anchorInfo,
     roomNoLive,
-    loopHeartbeatTimer,
     localStream,
     liveUserList,
     damuList,
-  } = useWs();
+  } = useSrsWs();
 
   const { flvPlayer, flvVideoEl, startFlvPlay } = useFlvPlay();
   const { hlsVideoEl, startHlsPlay } = useHlsPlay();
   const stopDrawingArr = ref<any[]>([]);
 
   onUnmounted(() => {
-    clearInterval(loopHeartbeatTimer.value);
     handleStopDrawing();
   });
 
@@ -220,12 +218,9 @@ export function usePull({
     if (autoplayVal.value) {
       videoLoading.value = true;
     }
-    initWs({
+    initSrsWs({
       roomId: roomId.value,
-      isSRS,
       isAnchor: false,
-      isPull: true,
-      roomLiveType: roomLiveType.value,
     });
   }
 
@@ -241,12 +236,12 @@ export function usePull({
   }
 
   function addVideo() {
-    sidebarList.value.push({ socketId: getSocketId() });
+    sidebarList.value.push({ socketId: mySocketId.value });
     nextTick(() => {
       liveUserList.value.forEach((item) => {
         const socketId = item.id;
-        if (socketId === getSocketId()) {
-          localVideoRef.value[getSocketId()].srcObject = localStream.value;
+        if (socketId === mySocketId.value) {
+          localVideoRef.value[mySocketId.value].srcObject = localStream.value;
         }
       });
     });
@@ -268,7 +263,7 @@ export function usePull({
     const instance = networkStore.wsMap.get(roomId.value);
     if (!instance) return;
     const danmu: IDanmu = {
-      socket_id: getSocketId(),
+      socket_id: mySocketId.value,
       userInfo: userStore.userInfo,
       msgType: DanmuMsgTypeEnum.danmu,
       msg: danmuStr.value,
@@ -290,7 +285,7 @@ export function usePull({
     initPull,
     closeWs,
     closeRtc,
-    getSocketId,
+    mySocketId,
     keydownDanmu,
     sendDanmu,
     addVideo,

+ 48 - 64
src/hooks/use-push.ts

@@ -10,8 +10,8 @@ import {
   DanmuMsgTypeEnum,
   ILiveRoom,
   IMessage,
+  LiveRoomTypeEnum,
   MediaTypeEnum,
-  liveTypeEnum,
 } from '@/interface';
 import { WsMsgTypeEnum } from '@/network/webSocket';
 import { useAppStore } from '@/store/app';
@@ -20,10 +20,11 @@ import { useUserStore } from '@/store/user';
 import { createVideo, generateBase64 } from '@/utils';
 
 import { loginTip } from './use-login';
+import { useSrsWs } from './use-srs-ws';
 import { useTip } from './use-tip';
-import { useWs } from './use-ws';
 
 export function usePush({ isSRS }: { isSRS: boolean }) {
+  console.log('usePushusePush', isSRS);
   const route = useRoute();
   const router = useRouter();
   const appStore = useAppStore();
@@ -37,63 +38,57 @@ export function usePush({ isSRS }: { isSRS: boolean }) {
   const liveRoomInfo = ref<ILiveRoom>();
   const videoElArr = ref<HTMLVideoElement[]>([]);
 
-  const allMediaTypeList: {
-    [index: string]: { type: MediaTypeEnum; txt: string };
-  } = {
-    [MediaTypeEnum.camera]: {
-      type: MediaTypeEnum.camera,
-      txt: '摄像头',
-    },
-    [MediaTypeEnum.screen]: {
-      type: MediaTypeEnum.screen,
-      txt: '窗口',
-    },
-    [MediaTypeEnum.microphone]: {
-      type: MediaTypeEnum.microphone,
-      txt: '麦克风',
-    },
-    [MediaTypeEnum.txt]: {
-      type: MediaTypeEnum.txt,
-      txt: '文字',
-    },
-    [MediaTypeEnum.img]: {
-      type: MediaTypeEnum.img,
-      txt: '图片',
-    },
-    [MediaTypeEnum.media]: {
-      type: MediaTypeEnum.media,
-      txt: '视频',
-    },
-    [MediaTypeEnum.time]: {
-      type: MediaTypeEnum.time,
-      txt: '时间',
-    },
-    [MediaTypeEnum.stopwatch]: {
-      type: MediaTypeEnum.stopwatch,
-      txt: '秒表',
-    },
-  };
+  const allMediaTypeList: Record<string, { type: MediaTypeEnum; txt: string }> =
+    {
+      [MediaTypeEnum.camera]: {
+        type: MediaTypeEnum.camera,
+        txt: '摄像头',
+      },
+      [MediaTypeEnum.screen]: {
+        type: MediaTypeEnum.screen,
+        txt: '窗口',
+      },
+      [MediaTypeEnum.microphone]: {
+        type: MediaTypeEnum.microphone,
+        txt: '麦克风',
+      },
+      [MediaTypeEnum.txt]: {
+        type: MediaTypeEnum.txt,
+        txt: '文字',
+      },
+      [MediaTypeEnum.img]: {
+        type: MediaTypeEnum.img,
+        txt: '图片',
+      },
+      [MediaTypeEnum.media]: {
+        type: MediaTypeEnum.media,
+        txt: '视频',
+      },
+      [MediaTypeEnum.time]: {
+        type: MediaTypeEnum.time,
+        txt: '时间',
+      },
+      [MediaTypeEnum.stopwatch]: {
+        type: MediaTypeEnum.stopwatch,
+        txt: '秒表',
+      },
+    };
 
   const {
-    getSocketId,
-    initWs,
+    mySocketId,
+    initSrsWs,
     canvasVideoStream,
     lastCoverImg,
-    loopHeartbeatTimer,
     localStream,
     liveUserList,
     damuList,
-    maxBitrate,
-    maxFramerate,
-    resolutionRatio,
     currentMaxFramerate,
     currentMaxBitrate,
     currentResolutionRatio,
     addTrack,
     delTrack,
-    sendStartLive,
-    startNewWebRtc,
-  } = useWs();
+    handleStartLive,
+  } = useSrsWs();
 
   watch(
     () => localStream.value,
@@ -148,7 +143,6 @@ export function usePush({ isSRS }: { isSRS: boolean }) {
   });
 
   onUnmounted(() => {
-    clearInterval(loopHeartbeatTimer.value);
     closeWs();
     closeRtc();
   });
@@ -189,19 +183,17 @@ export function usePush({ isSRS }: { isSRS: boolean }) {
   }
 
   function connectWs() {
-    initWs({
+    initSrsWs({
       isAnchor: true,
       roomId: roomId.value,
-      isSRS,
-      isPull: false,
       currentMaxBitrate: currentMaxBitrate.value,
       currentMaxFramerate: currentMaxFramerate.value,
       currentResolutionRatio: currentResolutionRatio.value,
-      roomLiveType: isSRS ? liveTypeEnum.srsPush : liveTypeEnum.webrtcPush,
     });
   }
 
-  async function startLive() {
+  async function startLive(type: LiveRoomTypeEnum) {
+    console.log('startLivestartLive', type);
     if (!loginTip()) return;
     const flag = await userHasLiveRoom();
     if (!flag) {
@@ -228,18 +220,13 @@ export function usePush({ isSRS }: { isSRS: boolean }) {
         }
       }
     }
-    sendStartLive({ coverImg: lastCoverImg.value, name: roomName.value });
-    startNewWebRtc({
-      videoEl: document.createElement('video'),
-      receiver: 'srs',
-    });
+    handleStartLive({ coverImg: lastCoverImg.value, name: roomName.value });
   }
 
   /** 结束直播 */
   function endLive() {
     isLiving.value = false;
     localStream.value = undefined;
-    clearInterval(loopHeartbeatTimer.value);
     const instance = networkStore.wsMap.get(roomId.value);
     if (instance) {
       instance.send({
@@ -296,7 +283,7 @@ export function usePush({ isSRS }: { isSRS: boolean }) {
       data: messageData,
     });
     damuList.value.push({
-      socket_id: getSocketId(),
+      socket_id: mySocketId.value,
       msgType: DanmuMsgTypeEnum.danmu,
       msg: danmuStr.value,
       userInfo: userStore.userInfo,
@@ -306,7 +293,7 @@ export function usePush({ isSRS }: { isSRS: boolean }) {
 
   return {
     confirmRoomName,
-    getSocketId,
+    mySocketId,
     startLive,
     endLive,
     sendDanmu,
@@ -319,9 +306,6 @@ export function usePush({ isSRS }: { isSRS: boolean }) {
     currentResolutionRatio,
     currentMaxBitrate,
     currentMaxFramerate,
-    resolutionRatio,
-    maxBitrate,
-    maxFramerate,
     danmuStr,
     roomName,
     damuList,

+ 100 - 0
src/hooks/use-rtc-params.ts

@@ -0,0 +1,100 @@
+import { ref } from 'vue';
+
+export const useRTCParams = () => {
+  const maxBitrate = ref([
+    {
+      label: '1',
+      value: 1,
+    },
+    {
+      label: '10',
+      value: 10,
+    },
+    {
+      label: '1000',
+      value: 1000,
+    },
+    {
+      label: '2000',
+      value: 2000,
+    },
+    {
+      label: '3000',
+      value: 3000,
+      disabled: true,
+    },
+    {
+      label: '4000',
+      value: 4000,
+      disabled: true,
+    },
+    {
+      label: '5000',
+      value: 5000,
+      disabled: true,
+    },
+    {
+      label: '6000',
+      value: 6000,
+      disabled: true,
+    },
+    {
+      label: '7000',
+      value: 7000,
+      disabled: true,
+    },
+    {
+      label: '8000',
+      value: 8000,
+      disabled: true,
+    },
+  ]);
+  const maxFramerate = ref([
+    {
+      label: '1帧',
+      value: 1,
+    },
+    {
+      label: '10帧',
+      value: 10,
+    },
+    {
+      label: '24帧',
+      value: 24,
+    },
+    {
+      label: '30帧',
+      value: 30,
+    },
+    {
+      label: '60帧',
+      value: 60,
+      disabled: true,
+    },
+  ]);
+  const resolutionRatio = ref([
+    {
+      label: '360P',
+      value: 360,
+    },
+    {
+      label: '540P',
+      value: 540,
+    },
+    {
+      label: '720P',
+      value: 720,
+    },
+    {
+      label: '1080P',
+      value: 1080,
+    },
+    {
+      label: '1440P',
+      value: 1440,
+      disabled: true,
+    },
+  ]);
+
+  return { maxBitrate, maxFramerate, resolutionRatio };
+};

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

@@ -0,0 +1,499 @@
+import { getRandomString } from 'billd-utils';
+import { computed, onUnmounted, ref, watch } from 'vue';
+
+import { fetchRtcV1Publish } from '@/api/srs';
+import { WEBSOCKET_URL } from '@/constant';
+import {
+  DanmuMsgTypeEnum,
+  IDanmu,
+  ILiveRoom,
+  ILiveUser,
+  IUser,
+  LiveRoomTypeEnum,
+} from '@/interface';
+import {
+  WSGetRoomAllUserType,
+  WsGetLiveUserType,
+  WsHeartbeatType,
+  WsJoinType,
+  WsLeavedType,
+  WsMessageType,
+  WsOtherJoinType,
+  WsRoomLivingType,
+  WsStartLiveType,
+  WsUpdateJoinInfoType,
+} from '@/interface-ws';
+import { WebRTCClass } from '@/network/webRTC';
+import {
+  WebSocketClass,
+  WsConnectStatusEnum,
+  WsMsgTypeEnum,
+  prettierReceiveWsMsg,
+} from '@/network/webSocket';
+import { AppRootState, useAppStore } from '@/store/app';
+import { useNetworkStore } from '@/store/network';
+import { useUserStore } from '@/store/user';
+import { createVideo } from '@/utils';
+
+import { useRTCParams } from './use-rtc-params';
+
+export const useSrsWs = () => {
+  const appStore = useAppStore();
+  const userStore = useUserStore();
+  const networkStore = useNetworkStore();
+  const loopHeartbeatTimer = ref();
+  const liveUserList = ref<ILiveUser[]>([]);
+  const roomId = ref('');
+  const roomNoLive = ref(false);
+  const roomLiving = ref(false);
+  const liveRoomInfo = ref<ILiveRoom>();
+  const anchorInfo = ref<IUser>();
+  const isAnchor = ref(false);
+  const localStream = ref<MediaStream>();
+  const canvasVideoStream = ref<MediaStream>();
+  const lastCoverImg = ref('');
+  const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+  const currentMaxBitrate = ref(maxBitrate.value[2].value);
+  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
+  const currentMaxFramerate = ref(maxFramerate.value[2].value);
+
+  const damuList = ref<IDanmu[]>([]);
+
+  watch(
+    () => appStore.allTrack,
+    (newTrack, oldTrack) => {
+      console.log('appStore.allTrack变了', newTrack, oldTrack);
+      const mixedStream = new MediaStream();
+      newTrack.forEach((item) => {
+        if (item.track) {
+          mixedStream.addTrack(item.track);
+        }
+      });
+      console.log('新的allTrack音频轨', mixedStream.getAudioTracks());
+      console.log('新的allTrack视频轨', mixedStream.getVideoTracks());
+      console.log('旧的allTrack音频轨', localStream.value?.getAudioTracks());
+      console.log('旧的allTrack视频轨', localStream.value?.getVideoTracks());
+      localStream.value = mixedStream;
+    },
+    { deep: true }
+  );
+
+  onUnmounted(() => {
+    clearInterval(loopHeartbeatTimer.value);
+  });
+
+  watch(
+    () => currentResolutionRatio.value,
+    (newVal) => {
+      if (canvasVideoStream.value) {
+        canvasVideoStream.value.getVideoTracks().forEach((track) => {
+          track.applyConstraints({
+            frameRate: { max: currentMaxFramerate.value },
+            height: newVal,
+          });
+        });
+      } else {
+        appStore.allTrack.forEach((info) => {
+          info.track?.applyConstraints({
+            frameRate: { max: currentMaxFramerate.value },
+            height: newVal,
+          });
+        });
+      }
+
+      networkStore.rtcMap.forEach(async (rtc) => {
+        const res = await rtc.setResolutionRatio(newVal);
+        if (res === 1) {
+          window.$message.success('切换分辨率成功!');
+        } else {
+          window.$message.success('切换分辨率失败!');
+        }
+      });
+    }
+  );
+
+  watch(
+    () => currentMaxFramerate.value,
+    (newVal) => {
+      console.log(currentMaxFramerate.value, 'currentMaxFramerate.value');
+      if (canvasVideoStream.value) {
+        canvasVideoStream.value.getVideoTracks().forEach((track) => {
+          track.applyConstraints({
+            frameRate: { max: newVal },
+            height: currentResolutionRatio.value,
+          });
+        });
+      } else {
+        appStore.allTrack.forEach((info) => {
+          info.track?.applyConstraints({
+            frameRate: { max: newVal },
+            height: currentResolutionRatio.value,
+          });
+        });
+      }
+
+      networkStore.rtcMap.forEach(async (rtc) => {
+        const res = await rtc.setMaxFramerate(newVal);
+        if (res === 1) {
+          window.$message.success('切换帧率成功!');
+        } else {
+          window.$message.success('切换帧率失败!');
+        }
+      });
+    }
+  );
+
+  watch(
+    () => currentMaxBitrate.value,
+    (newVal) => {
+      networkStore.rtcMap.forEach(async (rtc) => {
+        const res = await rtc.setMaxBitrate(newVal);
+        if (res === 1) {
+          window.$message.success('切换码率成功!');
+        } else {
+          window.$message.success('切换码率失败!');
+        }
+      });
+    }
+  );
+
+  function addTrack(addTrackInfo: { track; stream }) {
+    if (isAnchor.value) {
+      networkStore.rtcMap.forEach((rtc) => {
+        const sender = rtc.peerConnection
+          ?.getSenders()
+          .find((sender) => sender.track?.id === addTrackInfo.track?.id);
+        if (!sender) {
+          console.log('pc添加track-开播后中途添加', addTrackInfo.track?.id);
+          rtc.peerConnection
+            ?.getSenders()
+            ?.find((sender) => sender.track?.kind === 'audio')
+            ?.replaceTrack(canvasVideoStream.value!.getAudioTracks()[0]);
+          const vel = createVideo({});
+          vel.srcObject = canvasVideoStream.value!;
+        }
+      });
+    }
+    const mixedStream = new MediaStream();
+    appStore.allTrack.forEach((item) => {
+      if (item.track) {
+        mixedStream.addTrack(item.track);
+      }
+    });
+    console.log('addTrack后结果的音频轨', mixedStream.getAudioTracks());
+    console.log('addTrack后结果的视频轨', mixedStream.getVideoTracks());
+    localStream.value = mixedStream;
+  }
+
+  function delTrack(delTrackInfo: AppRootState['allTrack'][0]) {
+    if (isAnchor.value) {
+      networkStore.rtcMap.forEach((rtc) => {
+        const sender = rtc.peerConnection
+          ?.getSenders()
+          .find((sender) => sender.track?.id === delTrackInfo.track?.id);
+        if (sender) {
+          console.log('删除track', delTrackInfo, sender);
+          rtc.peerConnection?.removeTrack(sender);
+        }
+      });
+    }
+    const mixedStream = new MediaStream();
+    appStore.allTrack.forEach((item) => {
+      if (item.track) {
+        mixedStream.addTrack(item.track);
+      }
+    });
+    console.log('delTrack后结果的音频轨', mixedStream.getAudioTracks());
+    console.log('delTrack后结果的视频轨', mixedStream.getVideoTracks());
+    localStream.value = mixedStream;
+  }
+
+  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']>({
+        msgType: WsMsgTypeEnum.heartbeat,
+        data: {
+          socket_id: socketId,
+        },
+      });
+    }, 1000 * 5);
+  }
+
+  async function sendOffer({ receiver }: { receiver: string }) {
+    console.log('开始sendOffer');
+    const ws = networkStore.wsMap.get(roomId.value);
+    if (!ws) return;
+    const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
+    if (!rtc) return;
+    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!}?token=${myLiveRoom.key!}&type=${
+        LiveRoomTypeEnum.user_srs
+      }`,
+      tid: getRandomString(10),
+    });
+    networkStore.wsMap.get(roomId.value)?.send<WsUpdateJoinInfoType['data']>({
+      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 }) {
+    networkStore.wsMap.get(roomId.value)?.send<WsStartLiveType['data']>({
+      msgType: WsMsgTypeEnum.startLive,
+      data: {
+        cover_img: coverImg,
+        name,
+        type: LiveRoomTypeEnum.user_srs,
+      },
+    });
+    startNewSrsWebRtc({
+      videoEl: document.createElement('video'),
+      receiver: 'srs',
+    });
+  }
+
+  function sendJoin() {
+    const instance = networkStore.wsMap.get(roomId.value);
+    if (!instance) return;
+    instance.send<WsJoinType['data']>({
+      msgType: WsMsgTypeEnum.join,
+      data: {
+        socket_id: mySocketId.value,
+        live_room: {
+          id: Number(roomId.value),
+        },
+        user_info: userStore.userInfo,
+      },
+    });
+  }
+
+  /** 原生的webrtc时,receiver必传 */
+  function startNewSrsWebRtc({
+    receiver,
+    videoEl,
+  }: {
+    receiver: string;
+    videoEl: HTMLVideoElement;
+  }) {
+    console.warn('SRS开始new WebRTCClass', `${roomId.value}___${receiver!}`);
+    const rtc = new WebRTCClass({
+      maxBitrate: currentMaxBitrate.value,
+      maxFramerate: currentMaxFramerate.value,
+      resolutionRatio: currentResolutionRatio.value,
+      roomId: `${roomId.value}___${receiver!}`,
+      videoEl,
+      isSRS: true,
+      receiver,
+    });
+    if (canvasVideoStream.value) {
+      localStream.value = canvasVideoStream.value;
+      rtc.localStream = canvasVideoStream.value;
+      canvasVideoStream.value.getTracks().forEach((track) => {
+        console.log('pc添加track-srs', track.kind, track.id);
+        rtc.peerConnection?.addTrack(track, localStream.value!);
+      });
+    }
+
+    sendOffer({
+      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, () => {
+      prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws);
+      if (!ws) return;
+      ws.status = WsConnectStatusEnum.disconnect;
+      ws.update();
+    });
+
+    // 主播正在直播
+    ws.socketIo.on(WsMsgTypeEnum.roomLiving, (data: WsRoomLivingType) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
+      roomLiving.value = true;
+      roomNoLive.value = false;
+    });
+
+    // 主播不在直播
+    ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data);
+      roomNoLive.value = true;
+      roomLiving.value = false;
+    });
+
+    // 当前所有在线用户
+    ws.socketIo.on(
+      WsMsgTypeEnum.liveUser,
+      (data: WSGetRoomAllUserType['data']) => {
+        prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data);
+        const res = data.liveUser.map((item) => {
+          return {
+            id: item.id,
+            // userInfo: item.id,
+          };
+        });
+        liveUserList.value = res;
+      }
+    );
+
+    // 收到用户发送消息
+    ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.message, data);
+      damuList.value.push({
+        socket_id: data.socket_id,
+        msgType: DanmuMsgTypeEnum.danmu,
+        msg: data.data.msg,
+        userInfo: data.user_info,
+      });
+    });
+
+    // 用户加入房间完成
+    ws.socketIo.on(WsMsgTypeEnum.joined, (data: WsJoinType['data']) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
+      liveUserList.value.push({
+        id: data.socket_id,
+        userInfo: data.user_info,
+      });
+      liveRoomInfo.value = data.live_room;
+      anchorInfo.value = data.anchor_info;
+      ws.send<WsGetLiveUserType['data']>({
+        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 danmu: IDanmu = {
+        msgType: DanmuMsgTypeEnum.otherJoin,
+        socket_id: data.join_socket_id,
+        userInfo: data.join_user_info,
+        msg: '',
+      };
+      damuList.value.push(danmu);
+      ws.send<WsGetLiveUserType['data']>({
+        msgType: WsMsgTypeEnum.getLiveUser,
+        data: {
+          live_room_id: data.live_room.id!,
+        },
+      });
+    });
+
+    // 用户离开房间
+    ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
+    });
+
+    // 用户离开房间完成
+    ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
+      prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
+      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,
+        userInfo: data.user_info,
+        msg: '',
+      });
+    });
+  }
+
+  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 {
+    initSrsWs,
+    addTrack,
+    delTrack,
+    handleStartLive,
+    mySocketId,
+    canvasVideoStream,
+    lastCoverImg,
+    roomLiving,
+    liveRoomInfo,
+    anchorInfo,
+    roomNoLive,
+    localStream,
+    liveUserList,
+    damuList,
+    currentMaxFramerate,
+    currentMaxBitrate,
+    currentResolutionRatio,
+  };
+};

+ 0 - 936
src/hooks/use-ws.ts

@@ -1,936 +0,0 @@
-import { getRandomString } from 'billd-utils';
-import { onUnmounted, reactive, ref, watch } from 'vue';
-
-import { fetchRtcV1Play, fetchRtcV1Publish } from '@/api/srs';
-import { WEBSOCKET_URL } from '@/constant';
-import {
-  DanmuMsgTypeEnum,
-  IDanmu,
-  ILiveRoom,
-  ILiveUser,
-  IUser,
-  LiveRoomTypeEnum,
-  liveTypeEnum,
-} from '@/interface';
-import {
-  WSGetRoomAllUserType,
-  WsAnswerType,
-  WsCandidateType,
-  WsGetLiveUserType,
-  WsHeartbeatType,
-  WsJoinType,
-  WsLeavedType,
-  WsMessageType,
-  WsOfferType,
-  WsOtherJoinType,
-  WsRoomLivingType,
-  WsStartLiveType,
-  WsUpdateJoinInfoType,
-} from '@/interface-ws';
-import { WebRTCClass } from '@/network/webRTC';
-import {
-  WebSocketClass,
-  WsConnectStatusEnum,
-  WsMsgTypeEnum,
-  prettierReceiveWsMsg,
-} from '@/network/webSocket';
-import { AppRootState, useAppStore } from '@/store/app';
-import { useNetworkStore } from '@/store/network';
-import { useUserStore } from '@/store/user';
-import { createVideo } from '@/utils';
-
-export const useWs = () => {
-  const appStore = useAppStore();
-  const userStore = useUserStore();
-  const networkStore = useNetworkStore();
-  const loopHeartbeatTimer = ref();
-  const liveUserList = ref<ILiveUser[]>([]);
-  const roomId = ref('');
-  const roomName = ref('');
-  const roomNoLive = ref(false);
-  const roomLiving = ref(false);
-  const liveRoomInfo = ref<ILiveRoom>();
-  const anchorInfo = ref<IUser>();
-  const isAnchor = ref(false);
-  const roomLiveType = ref(liveTypeEnum.srsFlvPull);
-  const joined = ref(false);
-  const isSRS = ref(false);
-  const isPull = ref(false);
-  const trackInfo = reactive({ track_audio: 1, track_video: 1 });
-  const localVideo = ref<HTMLVideoElement>(document.createElement('video'));
-  const localStream = ref<MediaStream>();
-  const canvasVideoStream = ref<MediaStream>();
-  const lastCoverImg = ref('');
-  const maxBitrate = ref([
-    {
-      label: '1',
-      value: 1,
-    },
-    {
-      label: '10',
-      value: 10,
-    },
-    {
-      label: '1000',
-      value: 1000,
-    },
-    {
-      label: '2000',
-      value: 2000,
-    },
-    {
-      label: '3000',
-      value: 3000,
-    },
-    {
-      label: '4000',
-      value: 4000,
-      disabled: true,
-    },
-    {
-      label: '5000',
-      value: 5000,
-      disabled: true,
-    },
-    {
-      label: '6000',
-      value: 6000,
-      disabled: true,
-    },
-    {
-      label: '7000',
-      value: 7000,
-      disabled: true,
-    },
-    {
-      label: '8000',
-      value: 8000,
-      disabled: true,
-    },
-  ]);
-  const maxFramerate = ref([
-    {
-      label: '1帧',
-      value: 1,
-    },
-    {
-      label: '10帧',
-      value: 10,
-    },
-    {
-      label: '24帧',
-      value: 24,
-    },
-    {
-      label: '30帧',
-      value: 30,
-    },
-    {
-      label: '60帧',
-      value: 60,
-      disabled: true,
-    },
-  ]);
-  const resolutionRatio = ref([
-    {
-      label: '360P',
-      value: 360,
-    },
-    {
-      label: '540P',
-      value: 540,
-    },
-    {
-      label: '720P',
-      value: 720,
-    },
-    {
-      label: '1080P',
-      value: 1080,
-    },
-    {
-      label: '1440P',
-      value: 1440,
-      disabled: true,
-    },
-  ]);
-  const currentMaxBitrate = ref(maxBitrate.value[2].value);
-  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
-  const currentMaxFramerate = ref(maxFramerate.value[2].value);
-
-  const damuList = ref<IDanmu[]>([]);
-
-  watch(
-    () => appStore.allTrack,
-    (newTrack, oldTrack) => {
-      console.log('appStore.allTrack变了', newTrack, oldTrack);
-      const mixedStream = new MediaStream();
-      newTrack.forEach((item) => {
-        if (item.track) {
-          mixedStream.addTrack(item.track);
-        }
-      });
-      console.log('新的allTrack音频轨', mixedStream.getAudioTracks());
-      console.log('新的allTrack视频轨', mixedStream.getVideoTracks());
-      console.log('旧的allTrack音频轨', localStream.value?.getAudioTracks());
-      console.log('旧的allTrack视频轨', localStream.value?.getVideoTracks());
-      localStream.value = mixedStream;
-      // if (isSRS.value) {
-      //   if (!isPull.value) {
-      //     networkStore.rtcMap.forEach((rtc) => {
-      //       rtc.close();
-      //     });
-      //     startNewWebRtc({
-      //       receiver: 'srs',
-      //       videoEl: localVideo.value,
-      //     });
-      //   }
-      // }
-    },
-    { deep: true }
-  );
-  onUnmounted(() => {
-    clearInterval(loopHeartbeatTimer.value);
-  });
-
-  watch(
-    () => currentResolutionRatio.value,
-    (newVal) => {
-      if (canvasVideoStream.value) {
-        canvasVideoStream.value.getVideoTracks().forEach((track) => {
-          track.applyConstraints({
-            frameRate: { max: currentMaxFramerate.value },
-            height: newVal,
-          });
-        });
-      } else {
-        appStore.allTrack.forEach((info) => {
-          info.track?.applyConstraints({
-            frameRate: { max: currentMaxFramerate.value },
-            height: newVal,
-          });
-        });
-      }
-
-      networkStore.rtcMap.forEach(async (rtc) => {
-        const res = await rtc.setResolutionRatio(newVal);
-        if (res === 1) {
-          window.$message.success('切换分辨率成功!');
-        } else {
-          window.$message.success('切换分辨率失败!');
-        }
-      });
-    }
-  );
-
-  watch(
-    () => currentMaxFramerate.value,
-    (newVal) => {
-      console.log(currentMaxFramerate.value, 'currentMaxFramerate.value');
-      if (canvasVideoStream.value) {
-        canvasVideoStream.value.getVideoTracks().forEach((track) => {
-          track.applyConstraints({
-            frameRate: { max: newVal },
-            height: currentResolutionRatio.value,
-          });
-        });
-      } else {
-        appStore.allTrack.forEach((info) => {
-          info.track?.applyConstraints({
-            frameRate: { max: newVal },
-            height: currentResolutionRatio.value,
-          });
-        });
-      }
-
-      networkStore.rtcMap.forEach(async (rtc) => {
-        const res = await rtc.setMaxFramerate(newVal);
-        if (res === 1) {
-          window.$message.success('切换帧率成功!');
-        } else {
-          window.$message.success('切换帧率失败!');
-        }
-      });
-    }
-  );
-
-  watch(
-    () => currentMaxBitrate.value,
-    (newVal) => {
-      networkStore.rtcMap.forEach(async (rtc) => {
-        const res = await rtc.setMaxBitrate(newVal);
-        if (res === 1) {
-          window.$message.success('切换码率成功!');
-        } else {
-          window.$message.success('切换码率失败!');
-        }
-      });
-    }
-  );
-
-  function addTrack(addTrackInfo: { track; stream }) {
-    if (isAnchor.value) {
-      networkStore.rtcMap.forEach((rtc) => {
-        const sender = rtc.peerConnection
-          ?.getSenders()
-          .find((sender) => sender.track?.id === addTrackInfo.track?.id);
-        if (!sender) {
-          console.log('pc添加track-开播后中途添加', addTrackInfo.track?.id);
-          // vel.srcObject = destination.stream;
-          // canvasVideoStream.value!.getAudioTracks()[0] =
-          //   destination.stream.getAudioTracks()[0];
-
-          // rtc.peerConnection?.addTrack(addTrackInfo.track, addTrackInfo.stream);
-          rtc.peerConnection
-            ?.getSenders()
-            ?.find((sender) => sender.track?.kind === 'audio')
-            ?.replaceTrack(canvasVideoStream.value!.getAudioTracks()[0]);
-          const vel = createVideo({});
-          vel.srcObject = canvasVideoStream.value!;
-          // document.body.appendChild(vel);
-          console.log(
-            rtc.peerConnection
-              ?.getSenders()
-              ?.find((sender) => sender.track?.kind === 'audio'),
-            8888,
-            rtc.peerConnection?.getSenders()
-          );
-        }
-      });
-    }
-    const mixedStream = new MediaStream();
-    appStore.allTrack.forEach((item) => {
-      if (item.track) {
-        mixedStream.addTrack(item.track);
-      }
-    });
-    console.log('addTrack后结果的音频轨', mixedStream.getAudioTracks());
-    console.log('addTrack后结果的视频轨', mixedStream.getVideoTracks());
-    localStream.value = mixedStream;
-    // srs不需要更新,因为更新了之后,跟着就关闭当前rtc然后重新new一个新的rtc了
-    if (!isSRS.value) {
-      let resUrl = '';
-      const rtmpUrl = userStore.userInfo?.live_rooms?.[0].rtmp_url!;
-      if (rtmpUrl.indexOf('type=') === -1) {
-        resUrl += `${rtmpUrl}&type=${
-          isSRS.value ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc
-        }`;
-      } else {
-        resUrl = rtmpUrl.replace(
-          /type=([0-9]+)/,
-          `type=${
-            isSRS.value
-              ? LiveRoomTypeEnum.user_srs
-              : LiveRoomTypeEnum.user_wertc
-          }`
-        );
-      }
-      const data: WsUpdateJoinInfoType['data'] = {
-        live_room_id: Number(roomId.value),
-        track: {
-          audio: appStore.getTrackInfo().audio > 0 ? 1 : 2,
-          video: appStore.getTrackInfo().video > 0 ? 1 : 2,
-        },
-        rtmp_url: resUrl,
-      };
-      networkStore.wsMap.get(roomId.value)?.send({
-        msgType: WsMsgTypeEnum.updateJoinInfo,
-        data,
-      });
-    }
-  }
-
-  function delTrack(delTrackInfo: AppRootState['allTrack'][0]) {
-    if (isAnchor.value) {
-      networkStore.rtcMap.forEach((rtc) => {
-        const sender = rtc.peerConnection
-          ?.getSenders()
-          .find((sender) => sender.track?.id === delTrackInfo.track?.id);
-        if (sender) {
-          console.log('删除track', delTrackInfo, sender);
-          rtc.peerConnection?.removeTrack(sender);
-        }
-      });
-    }
-    const mixedStream = new MediaStream();
-    appStore.allTrack.forEach((item) => {
-      if (item.track) {
-        mixedStream.addTrack(item.track);
-      }
-    });
-    console.log('delTrack后结果的音频轨', mixedStream.getAudioTracks());
-    console.log('delTrack后结果的视频轨', mixedStream.getVideoTracks());
-    localStream.value = mixedStream;
-    if (!isSRS.value) {
-      let resUrl = '';
-      const rtmpUrl = userStore.userInfo?.live_rooms?.[0].rtmp_url!;
-      if (rtmpUrl.indexOf('type=') === -1) {
-        resUrl += `${rtmpUrl}&type=${
-          isSRS.value ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc
-        }`;
-      } else {
-        resUrl = rtmpUrl.replace(
-          /type=([0-9]+)/,
-          `type=${
-            isSRS.value
-              ? LiveRoomTypeEnum.user_srs
-              : LiveRoomTypeEnum.user_wertc
-          }`
-        );
-      }
-      const data: WsUpdateJoinInfoType['data'] = {
-        live_room_id: Number(roomId.value),
-        track: {
-          audio: appStore.getTrackInfo().audio > 0 ? 1 : 2,
-          video: appStore.getTrackInfo().video > 0 ? 1 : 2,
-        },
-        rtmp_url: resUrl,
-      };
-      networkStore.wsMap.get(roomId.value)?.send({
-        msgType: WsMsgTypeEnum.updateJoinInfo,
-        data,
-      });
-    }
-  }
-
-  function getSocketId() {
-    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']>({
-        msgType: WsMsgTypeEnum.heartbeat,
-        data: {
-          socket_id: socketId,
-        },
-      });
-    }, 1000 * 5);
-  }
-
-  async function sendOffer({
-    sender,
-    receiver,
-  }: {
-    sender: string;
-    receiver: string;
-  }) {
-    console.log('开始sendOffer');
-    const ws = networkStore.wsMap.get(roomId.value);
-    if (!ws) return;
-    const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
-    if (!rtc) return;
-    if (!isSRS.value) {
-      const sdp = await rtc.createOffer();
-      await rtc.setLocalDescription(sdp!);
-      ws.send({
-        msgType: WsMsgTypeEnum.offer,
-        data: {
-          sdp,
-          sender,
-          receiver,
-          live_room_id: roomId.value,
-        },
-      });
-    } else {
-      const sdp = await rtc.createOffer();
-      await rtc.setLocalDescription(sdp!);
-      let res;
-
-      if (isPull.value) {
-        res = await fetchRtcV1Play({
-          api: `/rtc/v1/play/`,
-          clientip: null,
-          sdp: sdp!.sdp!,
-          streamurl: liveRoomInfo.value!.rtmp_url!.replace('rtmp', 'webrtc'),
-          tid: getRandomString(10),
-        });
-      } else {
-        res = await fetchRtcV1Publish({
-          api: `/rtc/v1/publish/`,
-          clientip: null,
-          sdp: sdp!.sdp!,
-          streamurl: userStore.userInfo!.live_rooms![0]!.rtmp_url!.replace(
-            'rtmp',
-            'webrtc'
-          ),
-          tid: getRandomString(10),
-        });
-        const data: WsUpdateJoinInfoType['data'] = {
-          live_room_id: Number(roomId.value),
-          track: {
-            audio: appStore.getTrackInfo().audio > 0 ? 1 : 2,
-            video: appStore.getTrackInfo().video > 0 ? 1 : 2,
-          },
-        };
-        networkStore.wsMap.get(roomId.value)?.send({
-          msgType: WsMsgTypeEnum.updateJoinInfo,
-          data,
-        });
-      }
-      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 sendStartLive({ coverImg, name }) {
-    networkStore.wsMap.get(roomId.value)?.send<WsStartLiveType['data']>({
-      msgType: WsMsgTypeEnum.startLive,
-      data: {
-        cover_img: coverImg,
-        name,
-      },
-    });
-  }
-
-  function sendJoin() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (!instance) return;
-    let resUrl = '';
-    const rtmpUrl = userStore.userInfo?.live_rooms?.[0].rtmp_url;
-    // 如果是用户看直播,发送join时不需要rtmpUrl;只有房主直播的时候需要带rtmpUrl
-    if (rtmpUrl) {
-      if (rtmpUrl.indexOf('type=') === -1) {
-        resUrl += `${rtmpUrl}&type=${
-          isSRS.value ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc
-        }`;
-      } else {
-        resUrl = rtmpUrl.replace(
-          /type=([0-9]+)/,
-          `type=${
-            isSRS.value
-              ? LiveRoomTypeEnum.user_srs
-              : LiveRoomTypeEnum.user_wertc
-          }`
-        );
-      }
-    }
-    instance.send<WsJoinType['data']>({
-      msgType: WsMsgTypeEnum.join,
-      data: {
-        socket_id: getSocketId(),
-        live_room: {
-          id: Number(roomId.value),
-          name: roomName.value,
-          cover_img: lastCoverImg.value,
-          type: isSRS.value
-            ? LiveRoomTypeEnum.user_srs
-            : LiveRoomTypeEnum.user_wertc,
-          rtmp_url: resUrl,
-        },
-      },
-    });
-  }
-
-  function handleNegotiationneeded(data: { roomId: string; isSRS: boolean }) {
-    console.warn(`${data.roomId},开始监听pc的negotiationneeded`);
-    const rtc = networkStore.getRtcMap(data.roomId);
-    if (!rtc) return;
-    console.warn(`监听pc的negotiationneeded`);
-    rtc.peerConnection?.addEventListener('negotiationneeded', (event) => {
-      console.warn(`${data.roomId},pc收到negotiationneeded`, event);
-      sendOffer({
-        sender: getSocketId(),
-        receiver: rtc.receiver,
-      });
-    });
-  }
-
-  /** 原生的webrtc时,receiver必传 */
-  function startNewWebRtc({
-    receiver,
-    videoEl,
-  }: {
-    receiver: string;
-    videoEl: HTMLVideoElement;
-  }) {
-    let rtc: WebRTCClass;
-    if (isSRS.value) {
-      console.warn('SRS开始new WebRTCClass', `${roomId.value}___${receiver!}`);
-      rtc = new WebRTCClass({
-        maxBitrate: isPull.value ? -1 : currentMaxBitrate.value,
-        maxFramerate: isPull.value ? -1 : currentMaxFramerate.value,
-        resolutionRatio: isPull.value ? -1 : currentResolutionRatio.value,
-        roomId: `${roomId.value}___${receiver!}`,
-        videoEl,
-        isSRS: true,
-        receiver,
-      });
-      if (isPull.value) {
-        if (trackInfo.track_video === 1) {
-          rtc.peerConnection?.addTransceiver('video', {
-            direction: 'recvonly',
-          });
-        }
-        if (trackInfo.track_audio === 1) {
-          rtc.peerConnection?.addTransceiver('audio', {
-            direction: 'recvonly',
-          });
-        }
-      }
-      // handleNegotiationneeded({
-      //   roomId: `${roomId.value}___${receiver}`,
-      //   isSRS: true,
-      // });
-      if (canvasVideoStream.value) {
-        localStream.value = canvasVideoStream.value;
-      }
-      rtc.localStream = localStream.value;
-      localStream.value?.getTracks().forEach((track) => {
-        console.warn(
-          'srs startNewWebRtc,pc插入track',
-          track.id,
-          localStream.value?.id
-        );
-        console.log('pc添加track-srs', track.kind, track.id);
-        rtc.peerConnection?.addTrack(track, localStream.value!);
-      });
-
-      sendOffer({
-        sender: getSocketId(),
-        receiver,
-      });
-    } else {
-      console.warn('开始new WebRTCClass', `${roomId.value}___${receiver!}`);
-      rtc = new WebRTCClass({
-        maxBitrate: isPull.value ? -1 : currentMaxBitrate.value,
-        maxFramerate: isPull.value ? -1 : currentMaxFramerate.value,
-        resolutionRatio: isPull.value ? -1 : currentResolutionRatio.value,
-        roomId: `${roomId.value}___${receiver!}`,
-        videoEl,
-        isSRS: false,
-        receiver,
-      });
-      if (isAnchor.value) {
-        handleNegotiationneeded({
-          roomId: `${roomId.value}___${receiver}`,
-          isSRS: false,
-        });
-        rtc.localStream = localStream.value;
-        localStream.value?.getTracks().forEach((track) => {
-          // rtc.peerConnection?.addTransceiver(track, {
-          //   streams: [localStream.value!],
-          //   direction: 'sendonly',
-          // });
-          console.log('pc添加track-原生');
-          rtc.peerConnection?.addTrack(track, localStream.value!);
-        });
-      }
-    }
-    return rtc;
-  }
-
-  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, () => {
-      prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws);
-      if (!ws) return;
-      ws.status = WsConnectStatusEnum.disconnect;
-      ws.update();
-    });
-
-    // 收到offer
-    ws.socketIo.on(WsMsgTypeEnum.offer, async (data: WsOfferType) => {
-      prettierReceiveWsMsg(
-        WsMsgTypeEnum.offer,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS.value) return;
-      if (!ws) return;
-      if (data.data.receiver === getSocketId()) {
-        console.log('收到offer,这个offer是发给我的');
-        if (!isAnchor.value) {
-          // 如果是用户进来看直播
-          let rtc = networkStore.getRtcMap(
-            `${roomId.value}___${data.data.sender}`
-          );
-          if (!rtc) {
-            rtc = await startNewWebRtc({
-              receiver: data.data.sender,
-              videoEl: localVideo.value,
-            });
-          }
-          await rtc.setRemoteDescription(data.data.sdp);
-          const sdp = await rtc.createAnswer();
-          await rtc.setLocalDescription(sdp!);
-          const answerData: WsAnswerType['data'] = {
-            sdp,
-            sender: getSocketId(),
-            receiver: data.data.sender,
-            live_room_id: data.data.live_room_id,
-          };
-          ws.send({
-            msgType: WsMsgTypeEnum.answer,
-            data: answerData,
-          });
-        }
-      } else {
-        console.log('收到offer,但是这个offer不是发给我的');
-      }
-    });
-
-    // 收到answer
-    ws.socketIo.on(WsMsgTypeEnum.answer, async (data: WsOfferType) => {
-      prettierReceiveWsMsg(
-        WsMsgTypeEnum.answer,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS.value) return;
-      if (!ws) return;
-      const rtc = networkStore.getRtcMap(`${roomId.value}___${data.socket_id}`);
-      if (!rtc) return;
-      rtc.update();
-      if (data.data.receiver === getSocketId()) {
-        console.log('收到answer,这个answer是发给我的');
-        await rtc.setRemoteDescription(data.data.sdp);
-      } else {
-        console.log('收到answer,但这个answer不是发给我的');
-      }
-    });
-
-    // 收到candidate
-    ws.socketIo.on(WsMsgTypeEnum.candidate, (data: WsCandidateType['data']) => {
-      prettierReceiveWsMsg(
-        WsMsgTypeEnum.candidate,
-        `发送者:${data.sender},接收者:${data.receiver}`,
-        data
-      );
-      if (isSRS.value) return;
-      if (!ws) return;
-      const rtc = networkStore.getRtcMap(`${roomId.value}___${data.sender}`);
-      if (!rtc) return;
-      if (data.sender !== getSocketId()) {
-        console.log('不是我发的candidate');
-        rtc.peerConnection
-          ?.addIceCandidate(data.candidate)
-          .then(() => {
-            console.log('candidate成功');
-          })
-          .catch((err) => {
-            console.error('candidate失败', err);
-          });
-      } else {
-        console.log('是我发的candidate');
-      }
-    });
-
-    // 主播正在直播
-    ws.socketIo.on(WsMsgTypeEnum.roomLiving, (data: WsRoomLivingType) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
-      roomLiving.value = true;
-      roomNoLive.value = false;
-      // 如果是srs开播,则不需要等有人进来了才new webrtc,只要Websocket连上了就开始new webrtc
-      if (isSRS.value) {
-        if (isPull.value) {
-          if (roomLiveType.value === liveTypeEnum.srsWebrtcPull) {
-            startNewWebRtc({
-              receiver: 'srs',
-              videoEl: localVideo.value,
-            });
-          }
-        }
-      }
-    });
-
-    // 主播不在直播
-    ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data);
-      roomNoLive.value = true;
-      roomLiving.value = false;
-    });
-
-    // 当前所有在线用户
-    ws.socketIo.on(
-      WsMsgTypeEnum.liveUser,
-      (data: WSGetRoomAllUserType['data']) => {
-        prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data);
-        const res = data.liveUser.map((item) => {
-          return {
-            id: item.id,
-            // userInfo: item.id,
-          };
-        });
-        liveUserList.value = res;
-      }
-    );
-
-    // 收到用户发送消息
-    ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.message, data);
-      if (!ws) return;
-      damuList.value.push({
-        socket_id: data.socket_id,
-        msgType: DanmuMsgTypeEnum.danmu,
-        msg: data.data.msg,
-        userInfo: data.user_info,
-      });
-    });
-
-    // 用户加入房间完成
-    ws.socketIo.on(WsMsgTypeEnum.joined, (data: WsJoinType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.joined, data);
-      joined.value = true;
-      trackInfo.track_audio = 1;
-      trackInfo.track_video = 1;
-      liveUserList.value.push({
-        id: data.socket_id,
-        userInfo: data.user_info,
-      });
-      liveRoomInfo.value = data.live_room;
-      anchorInfo.value = data.anchor_info;
-      ws.send<WsGetLiveUserType['data']>({
-        msgType: WsMsgTypeEnum.getLiveUser,
-        data: {
-          live_room_id: data.live_room.id!,
-        },
-      });
-      // 如果是srs开播,则不需要等有人进来了才new webrtc,只要Websocket连上了就开始new webrtc
-      // if (isSRS.value) {
-      //   if (!isPull.value) {
-      //     startNewWebRtc({
-      //       receiver: 'srs',
-      //       videoEl: localVideo.value,
-      //     });
-      //   }
-      // }
-    });
-
-    // 其他用户加入房间
-    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 danmu: IDanmu = {
-        msgType: DanmuMsgTypeEnum.otherJoin,
-        socket_id: data.join_socket_id,
-        userInfo: data.join_user_info,
-        msg: '',
-      };
-      damuList.value.push(danmu);
-      ws.send<WsGetLiveUserType['data']>({
-        msgType: WsMsgTypeEnum.getLiveUser,
-        data: {
-          live_room_id: data.live_room.id!,
-        },
-      });
-      // 如果是srs开播,且进来的用户不是srs-webrtc-pull,则不能再new webrtc了
-      if (isSRS.value) return;
-      if (joined.value) {
-        // startNewWebRtc({
-        //   receiver: data.join_socket_id,
-        //   videoEl: localVideo.value,
-        // });
-      }
-    });
-
-    // 用户离开房间
-    ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.leave, data);
-      if (!ws) return;
-      ws.send({
-        msgType: WsMsgTypeEnum.leave,
-        data: { roomId: ws.roomId },
-      });
-    });
-
-    // 用户离开房间完成
-    ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => {
-      prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data);
-      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,
-        userInfo: data.user_info,
-        msg: '',
-      });
-    });
-  }
-
-  function initWs(data: {
-    isAnchor: boolean;
-    roomId: string;
-    isSRS: boolean;
-    isPull: boolean;
-    currentResolutionRatio?: number;
-    currentMaxFramerate?: number;
-    currentMaxBitrate?: number;
-    roomLiveType: liveTypeEnum;
-  }) {
-    roomId.value = data.roomId;
-    isAnchor.value = data.isAnchor;
-    roomLiveType.value = data.roomLiveType;
-    if (data.currentMaxBitrate) {
-      currentMaxBitrate.value = data.currentMaxBitrate;
-    }
-    if (data.currentMaxFramerate) {
-      currentMaxFramerate.value = data.currentMaxFramerate;
-    }
-    if (data.currentResolutionRatio) {
-      currentResolutionRatio.value = data.currentResolutionRatio;
-    }
-    isSRS.value = data.isSRS;
-    isPull.value = data.isPull;
-    new WebSocketClass({
-      roomId: roomId.value,
-      url: WEBSOCKET_URL,
-      isAnchor: data.isAnchor,
-    });
-    initReceive();
-  }
-
-  return {
-    getSocketId,
-    initWs,
-    addTrack,
-    delTrack,
-    startNewWebRtc,
-    sendStartLive,
-    canvasVideoStream,
-    lastCoverImg,
-    roomLiving,
-    liveRoomInfo,
-    anchorInfo,
-    roomNoLive,
-    loopHeartbeatTimer,
-    localStream,
-    liveUserList,
-    damuList,
-    maxBitrate,
-    maxFramerate,
-    resolutionRatio,
-    currentMaxFramerate,
-    currentMaxBitrate,
-    currentResolutionRatio,
-  };
-};

+ 8 - 2
src/interface-ws.ts

@@ -1,4 +1,9 @@
-import { DanmuMsgTypeEnum, ILiveRoom, IUser } from './interface';
+import {
+  DanmuMsgTypeEnum,
+  ILiveRoom,
+  IUser,
+  LiveRoomTypeEnum,
+} from './interface';
 
 export interface IWsFormat<T> {
   /** 用户socket_id */
@@ -44,6 +49,7 @@ export type WsOtherJoinType = IWsFormat<{
 export type WsStartLiveType = IWsFormat<{
   cover_img: string;
   name: string;
+  type: LiveRoomTypeEnum;
 }>;
 
 export type WsJoinType = IWsFormat<{
@@ -58,7 +64,7 @@ export type WsLeavedType = IWsFormat<{
   user_info?: IUser;
 }>;
 
-export type IRoomLiving = IWsFormat<{
+export type WsRoomNoLiveType = IWsFormat<{
   live_room: ILiveRoom;
 }>;
 

+ 15 - 47
src/network/webRTC.ts

@@ -277,41 +277,9 @@ export class WebRTCClass {
     if (!this.peerConnection) return;
     this.prettierLog('createOffer开始', 'warn');
     try {
-      const description = await this.peerConnection.createOffer({
-        iceRestart: true,
-      });
-      this.prettierLog('createOffer成功', 'warn', description);
-      // const sdpStr = description.sdp;
-
-      // const sdpObj = SDPTransform.parse(sdpStr);
-
-      // // Get all m-lines
-      // const mlines = sdpObj.media;
-
-      // // Map to store unique m-lines
-      // const mLineMap = new Map();
-
-      // mlines.forEach((mLine) => {
-      //   const key = `${mLine.type}_${mLine.port}`;
-      //   if (!mLineMap.has(key)) {
-      //     mLineMap.set(key, mLine);
-      //   }
-      // });
-
-      // // Clear media array and only keep m-lines from map
-      // sdpObj.media = [];
-
-      // mLineMap.forEach((mLine) => {
-      //   sdpObj.media.push(mLine);
-      // });
-
-      // // Write new SDP string
-      // const newSdpStr = SDPTransform.write(sdpObj);
-      // console.log('old', description.sdp);
-      // console.log('newSdpStr', newSdpStr);
-      // Use new SDP string ...
-      // description.sdp = newSdpStr;
-      return description;
+      const sdp = await this.peerConnection.createOffer();
+      this.prettierLog('createOffer成功', 'warn', sdp);
+      return sdp;
     } catch (error) {
       this.prettierLog('createOffer失败', 'error');
       console.log(error);
@@ -323,9 +291,9 @@ export class WebRTCClass {
     if (!this.peerConnection) return;
     this.prettierLog('createAnswer开始', 'warn');
     try {
-      const description = await this.peerConnection.createAnswer();
-      this.prettierLog('createAnswer成功', 'warn', description);
-      return description;
+      const sdp = await this.peerConnection.createAnswer();
+      this.prettierLog('createAnswer成功', 'warn', sdp);
+      return sdp;
     } catch (error) {
       this.prettierLog('createAnswer失败', 'error');
       console.log(error);
@@ -333,29 +301,29 @@ export class WebRTCClass {
   };
 
   // 设置本地描述
-  setLocalDescription = async (desc: RTCLocalSessionDescriptionInit) => {
+  setLocalDescription = async (sdp: RTCLocalSessionDescriptionInit) => {
     if (!this.peerConnection) return;
     this.prettierLog('setLocalDescription开始', 'warn');
     try {
-      await this.peerConnection.setLocalDescription(desc);
-      this.prettierLog('setLocalDescription成功', 'warn', desc);
+      await this.peerConnection.setLocalDescription(sdp);
+      this.prettierLog('setLocalDescription成功', 'warn', sdp);
     } catch (error) {
       this.prettierLog('setLocalDescription失败', 'error');
-      console.error('setLocalDescription', desc);
+      console.error('setLocalDescription', sdp);
       console.error(error);
     }
   };
 
   // 设置远端描述
-  setRemoteDescription = async (desc: RTCSessionDescriptionInit) => {
+  setRemoteDescription = async (sdp: RTCSessionDescriptionInit) => {
     if (!this.peerConnection) return;
     this.prettierLog(`setRemoteDescription开始`, 'warn');
     try {
-      await this.peerConnection.setRemoteDescription(desc);
-      this.prettierLog('setRemoteDescription成功', 'warn', desc);
+      await this.peerConnection.setRemoteDescription(sdp);
+      this.prettierLog('setRemoteDescription成功', 'warn', sdp);
     } catch (error) {
       this.prettierLog('setRemoteDescription失败', 'error');
-      console.error('setRemoteDescription', desc);
+      console.error('setRemoteDescription', sdp);
       console.error(error);
     }
   };
@@ -395,7 +363,7 @@ export class WebRTCClass {
         const roomId = this.roomId.split('___')[0];
         const receiver = this.roomId.split('___')[1];
         networkStore.wsMap.get(roomId)?.send<WsCandidateType['data']>({
-          msgType: WsMsgTypeEnum.candidate,
+          msgType: WsMsgTypeEnum.srsCandidate,
           data: {
             candidate: event.candidate,
             sender: networkStore.wsMap.get(roomId)?.socketIo?.id || '',

+ 7 - 8
src/network/webSocket.ts

@@ -42,18 +42,17 @@ export enum WsMsgTypeEnum {
   roomLiving = 'roomLiving',
   /** 房间不在直播 */
   roomNoLive = 'roomNoLive',
-  /** sendBlob */
-  sendBlob = 'sendBlob',
-  /** getLiveUser */
   getLiveUser = 'getLiveUser',
-  /** updateJoinInfo */
   updateJoinInfo = 'updateJoinInfo',
-  /** heartbeat */
   heartbeat = 'heartbeat',
-  offer = 'offer',
-  answer = 'answer',
-  candidate = 'candidate',
   startLive = 'startLive',
+
+  srsOffer = 'srsOffer',
+  srsAnswer = 'srsAnswer',
+  srsCandidate = 'srsCandidate',
+  webrtcOffer = 'webrtcOffer',
+  webrtcAnswer = 'webrtcAnswer',
+  webrtcCandidate = 'webrtcCandidate',
 }
 
 export function prettierReceiveWsMsg(...arg) {

+ 3 - 3
src/views/pull/index.vue

@@ -17,7 +17,7 @@
             <div class="bottom">
               <span>{{ liveRoomInfo?.name }}</span>
               <span v-if="NODE_ENV === 'development'">
-                socketId:{{ getSocketId() }}
+                socketId:{{ mySocketId }}
               </span>
             </div>
           </div>
@@ -106,7 +106,7 @@
       <div class="user-list">
         <div
           v-for="(item, index) in liveUserList.filter(
-            (item) => item.id !== getSocketId()
+            (item) => item.id !== mySocketId
           )"
           :key="index"
           class="item"
@@ -223,7 +223,7 @@ const {
   initPull,
   closeWs,
   closeRtc,
-  getSocketId,
+  mySocketId,
   keydownDanmu,
   sendDanmu,
   addVideo,

+ 15 - 35
src/views/push/index.vue

@@ -58,7 +58,7 @@
             </div>
             <div class="bottom">
               <span v-if="NODE_ENV === 'development'">
-                {{ getSocketId() }}
+                {{ mySocketId }}
               </span>
             </div>
           </div>
@@ -99,8 +99,7 @@
               <span>
                 正在观看:
                 {{
-                  liveUserList.filter((item) => item.id !== getSocketId())
-                    .length
+                  liveUserList.filter((item) => item.id !== mySocketId).length
                 }}
               </span>
             </span>
@@ -278,7 +277,13 @@ import * as workerTimers from 'worker-timers';
 
 import { mediaTypeEnumMap } from '@/constant';
 import { usePush } from '@/hooks/use-push';
-import { DanmuMsgTypeEnum, MediaTypeEnum, liveTypeEnum } from '@/interface';
+import { useRTCParams } from '@/hooks/use-rtc-params';
+import {
+  DanmuMsgTypeEnum,
+  LiveRoomTypeEnum,
+  MediaTypeEnum,
+  liveTypeEnum,
+} from '@/interface';
 import { AppRootState, useAppStore } from '@/store/app';
 import { useResourceCacheStore } from '@/store/cache';
 import { useUserStore } from '@/store/user';
@@ -313,16 +318,18 @@ const audioCtx = ref<AudioContext>();
 const timeCanvasDom = ref<Raw<fabric.Text>[]>([]);
 const stopwatchCanvasDom = ref<Raw<fabric.Text>[]>([]);
 const isSRS = route.query.liveType === liveTypeEnum.srsPush;
+console.log(route.query.liveType, liveTypeEnum.srsPush, 22222121);
 const wrapSize = reactive({
   width: 0,
   height: 0,
 });
 const workerTimerId = ref(-1);
-const requestAnimationFrameId = ref(-1);
 const videoRatio = ref(16 / 9);
+const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+
 const {
   confirmRoomName,
-  getSocketId,
+  mySocketId,
   startLive,
   endLive,
   sendDanmu,
@@ -334,9 +341,6 @@ const {
   currentResolutionRatio,
   currentMaxBitrate,
   currentMaxFramerate,
-  resolutionRatio,
-  maxBitrate,
-  maxFramerate,
   danmuStr,
   roomName,
   damuList,
@@ -367,40 +371,15 @@ onMounted(() => {
   initUserMedia();
   initCanvas();
   handleCache();
-  document.addEventListener('visibilitychange', onPageVisibility);
 });
 
 onUnmounted(() => {
   bodyAppendChildElArr.value.forEach((el) => {
     el.remove();
   });
-  document.removeEventListener('visibilitychange', onPageVisibility);
-  if (workerTimerId.value !== -1) {
-    workerTimers.clearInterval(workerTimerId.value);
-  }
   clearFrame();
 });
 
-// 处理页面显示/隐藏
-function onPageVisibility() {
-  // 注意:此属性在Page Visibility Level 2 规范中被描述为“历史” 。考虑改用该Document.visibilityState 属性。
-  // const isHidden = document.hidden;
-  if (document.visibilityState === 'hidden') {
-    console.log(new Date().toLocaleString(), '页面隐藏了', workerTimerId.value);
-    if (isLiving.value) {
-      const delay = 1000 / 60; // 16.666666666666668
-      workerTimerId.value = workerTimers.setInterval(() => {
-        renderAll();
-      }, delay);
-    }
-  } else {
-    console.log(new Date().toLocaleString(), '页面显示了', workerTimerId.value);
-    if (isLiving.value) {
-      workerTimers.clearInterval(workerTimerId.value);
-    }
-  }
-}
-
 function initUserMedia() {
   navigator.mediaDevices
     .getUserMedia({
@@ -492,6 +471,7 @@ function renderFrame() {
   workerTimerId.value = workerTimers.setInterval(() => {
     renderAll();
   }, delay);
+  console.log('workerTimerId.value', workerTimerId.value);
 }
 
 // 处理空音频轨
@@ -600,7 +580,7 @@ function handleStartLive() {
   }
   handleMixedAudio();
   lastCoverImg.value = generateBase64(pushCanvasRef.value!);
-  startLive();
+  startLive(isSRS ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc);
 }
 
 function handleScale({ width, height }: { width: number; height: number }) {

+ 2 - 4
src/views/push2/index.vue

@@ -256,7 +256,7 @@
 
 <script lang="ts" setup>
 import { getRandomString } from 'billd-utils';
-import { NODE_ENV } from 'script/constant';
+
 import { onMounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
@@ -264,6 +264,7 @@ import { usePush } from '@/hooks/use-push';
 import { DanmuMsgTypeEnum, MediaTypeEnum, liveTypeEnum } from '@/interface';
 import { AppRootState, useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';
+import { NODE_ENV } from 'script/constant';
 
 import MediaModalCpt from './mediaModal/index.vue';
 import SelectMediaModalCpt from './selectMediaModal/index.vue';
@@ -294,9 +295,6 @@ const {
   currentResolutionRatio,
   currentMaxBitrate,
   currentMaxFramerate,
-  resolutionRatio,
-  maxBitrate,
-  maxFramerate,
   danmuStr,
   roomName,
   damuList,