浏览代码

fix: 整理

shuisheng 2 年之前
父节点
当前提交
4ac16e7685

+ 90 - 552
src/hooks/use-pull.ts

@@ -1,33 +1,17 @@
-import { getRandomString, judgeDevice } from 'billd-utils';
 import { NODE_ENV } from 'script/constant';
-import { Ref, nextTick, onUnmounted, reactive, ref, watch } from 'vue';
+import { Ref, nextTick, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
-import { fetchRtcV1Play } from '@/api/srs';
-import { WEBSOCKET_URL } from '@/constant';
 import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
+import { useWs } from '@/hooks/use-ws';
 import {
   DanmuMsgTypeEnum,
-  IAnswer,
-  ICandidate,
   IDanmu,
-  IJoin,
-  ILive,
-  ILiveUser,
   IMessage,
-  IOffer,
-  IOtherJoin,
   IUpdateJoinInfo,
-  LiveRoomTypeEnum,
   liveTypeEnum,
 } from '@/interface';
-import { WebRTCClass, audioElArr } from '@/network/webRTC';
-import {
-  WebSocketClass,
-  WsConnectStatusEnum,
-  WsMsgTypeEnum,
-  prettierReceiveWebsocket,
-} from '@/network/webSocket';
+import { WsMsgTypeEnum } from '@/network/webSocket';
 import { useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
@@ -41,114 +25,110 @@ export function usePull({
 }: {
   localVideoRef: Ref<HTMLVideoElement[]>;
   canvasRef: Ref<Element | undefined>;
-  isSRS?: boolean;
+  isSRS: boolean;
   liveType: liveTypeEnum;
 }) {
   const route = useRoute();
   const appStore = useAppStore();
   const userStore = useUserStore();
   const networkStore = useNetworkStore();
-  const videoEl = document.createElement('video');
-  videoEl.muted = true;
-  videoEl.playsInline = true;
-  videoEl.autoplay = true;
-  videoEl.setAttribute('webkit-playsinline', 'true');
-  videoEl.oncontextmenu = (e) => {
-    e.preventDefault();
-  };
-  if (NODE_ENV === 'development') {
-    videoEl.controls = true;
-  }
-  const remoteVideoRef = ref(videoEl);
-  const heartbeatTimer = ref();
   const roomId = ref(route.params.roomId as string);
   const roomLiveType = ref<liveTypeEnum>(liveType);
-  const roomName = ref('');
-  const roomSocketId = ref('');
-  const userName = ref('');
-  const userAvatar = ref('');
-  const streamurl = ref('');
   const flvurl = ref('');
   const hlsurl = ref('');
-  const coverImg = ref('');
   const danmuStr = ref('');
-  const balance = ref('0.00');
-  const currentLiveRoom = ref<ILive>();
-  const damuList = ref<IDanmu[]>([]);
-  const liveUserList = ref<ILiveUser[]>([]);
   const autoplayVal = ref(false);
   const videoLoading = ref(false);
-  const isDone = ref(false);
-  const roomNoLive = ref(false);
-  const localStream = ref();
   const sidebarList = ref<
     {
       socketId: string;
     }[]
   >([]);
 
-  const track = reactive({
-    audio: 1,
-    video: 1,
-  });
-
-  const giftList = ref([
-    { name: '鲜花', ico: '', price: '免费' },
-    { name: '肥宅水', ico: '', price: '2元' },
-    { name: '小鸡腿', ico: '', price: '3元' },
-    { name: '大鸡腿', ico: '', price: '5元' },
-    { name: '一杯咖啡', ico: '', price: '10元' },
-  ]);
-  const offerSended = ref(new Set());
-  const hooksRtcMap = ref(new Set());
-  const sender = ref();
+  const {
+    getSocketId,
+    initWs,
+    liveRoomInfo,
+    roomNoLive,
+    heartbeatTimer,
+    localStream,
+    liveUserList,
+    damuList,
+    maxBitrate,
+    maxFramerate,
+    resolutionRatio,
+    currentMaxFramerate,
+    currentMaxBitrate,
+    currentResolutionRatio,
+    addTrack,
+    delTrack,
+  } = useWs();
 
   const { flvVideoEl, startFlvPlay } = useFlvPlay();
   const { hlsVideoEl, startHlsPlay } = useHlsPlay();
 
-  onUnmounted(() => {
-    clearInterval(heartbeatTimer.value);
-  });
-
   watch(
-    () => appStore.allTrack,
-    () => {
-      // networkStore.rtcMap.forEach((rtc) => {
-      //   if (appStore.getTrackInfo().audio > 0) {
-      //     rtc!.peerConnection?.addTransceiver('audio', {
-      //       direction: 'recvonly',
-      //     });
-      //   }
-      //   if (appStore.getTrackInfo().video > 0) {
-      //     rtc!.peerConnection?.addTransceiver('video', {
-      //       direction: 'recvonly',
-      //     });
-      //   }
-      // });
+    () => localStream,
+    async (stream) => {
+      if (stream.value) {
+        if (roomLiveType.value === liveTypeEnum.srsFlvPull) {
+          if (!autoplayVal.value) return;
+          const { width, height } = await startFlvPlay({
+            flvurl: flvurl.value,
+          });
+          videoToCanvas({
+            videoEl: flvVideoEl.value!,
+            targetEl: canvasRef.value!,
+            width,
+            height,
+          });
+          videoLoading.value = false;
+        } else if (roomLiveType.value === liveTypeEnum.srsHlsPull) {
+          if (!autoplayVal.value) return;
+          const { width, height } = await startHlsPlay({
+            hlsurl: hlsurl.value,
+          });
+          videoToCanvas({
+            videoEl: hlsVideoEl.value!,
+            targetEl: canvasRef.value!,
+            width,
+            height,
+          });
+          videoLoading.value = false;
+        } else if (roomLiveType.value === liveTypeEnum.webrtcPull) {
+          const videoEl = document.createElement('video');
+          videoEl.muted = true;
+          videoEl.playsInline = true;
+          videoEl.autoplay = true;
+          videoEl.setAttribute('webkit-playsinline', 'true');
+          videoEl.oncontextmenu = (e) => {
+            e.preventDefault();
+          };
+          if (NODE_ENV === 'development') {
+            videoEl.controls = true;
+          }
+          videoEl.srcObject = stream.value;
+          canvasRef.value?.childNodes?.forEach((item) => {
+            item.remove();
+          });
+          canvasRef.value?.appendChild(videoEl);
+          videoLoading.value = false;
+        } else {
+          canvasRef.value?.childNodes?.forEach((item) => {
+            item.remove();
+          });
+        }
+      }
     },
     { deep: true }
   );
 
-  watch(
-    () => appStore.muted,
-    (val) => {
-      remoteVideoRef.value.muted = val;
-      audioElArr.forEach((el) => {
-        console.log(el, el.muted);
-        el.muted = val;
-      });
-    }
-  );
-
   watch(
     [
       () => userStore.userInfo,
       () => networkStore.wsMap.get(roomId.value)?.socketIo?.connected,
     ],
     ([userInfo, connected]) => {
-      if (userInfo) {
-        balance.value = userInfo.wallet?.balance || '0.00';
-      }
       if (userInfo && connected) {
         const instance = networkStore.wsMap.get(roomId.value);
         if (!instance) return;
@@ -168,45 +148,7 @@ export function usePull({
     if (autoplayVal.value) {
       videoLoading.value = true;
     }
-    console.warn('开始new WebSocketClass');
-    const ws = new WebSocketClass({
-      roomId: roomId.value,
-      url: WEBSOCKET_URL,
-      isAnchor: false,
-    });
-    ws.update();
-    initWsReceive();
-
-    remoteVideoRef.value?.addEventListener('loadstart', () => {
-      console.warn('视频流-loadstart');
-      const rtc = networkStore.getRtcMap(roomId.value);
-      if (!rtc) return;
-      rtc.update();
-    });
-
-    remoteVideoRef.value?.addEventListener('loadedmetadata', () => {
-      console.warn('视频流-loadedmetadata');
-      if (
-        roomLiveType.value === liveTypeEnum.webrtcPull ||
-        roomLiveType.value === liveTypeEnum.srsWebrtcPull
-      ) {
-        canvasRef.value?.appendChild(remoteVideoRef.value);
-      }
-      videoLoading.value = false;
-      const rtc = networkStore.getRtcMap(roomId.value);
-      if (!rtc) return;
-      rtc.update();
-    });
-  }
-
-  function handleHeartbeat() {
-    heartbeatTimer.value = setInterval(() => {
-      const instance = networkStore.wsMap.get(roomId.value);
-      if (!instance) return;
-      instance.send({
-        msgType: WsMsgTypeEnum.heartbeat,
-      });
-    }, 1000 * 5);
+    initWs({ roomId: roomId.value, isSRS, isAnchor: false });
   }
 
   function closeWs() {
@@ -220,135 +162,33 @@ export function usePull({
     });
   }
 
-  function getSocketId() {
-    return networkStore.wsMap.get(roomId.value!)?.socketIo?.id || '-1';
-  }
-
-  function sendJoin() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (!instance) return;
-    const joinData: IJoin['data'] = {
-      live_room: {
-        id: Number(roomId.value),
-        name: roomName.value,
-        type: isSRS ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc,
-      },
-      track,
-    };
-    instance.send({
-      msgType: WsMsgTypeEnum.join,
-      data: joinData,
-    });
-  }
-
-  async function sendOffer({
-    sender,
-    receiver,
-  }: {
-    sender: string;
-    receiver: string;
-  }) {
-    console.log(isDone.value);
-    if (isDone.value) return;
-    const instance = networkStore.wsMap.get(roomId.value);
-    console.log(instance, roomId.value);
-    if (!instance) return;
-    const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
-    if (!rtc) return;
-    const sdp = await rtc.createOffer();
-    await rtc.setLocalDescription(sdp!);
-    const offerData = {
-      sdp,
-      sender,
-      receiver,
-      live_room_id: roomId.value,
-    };
-    instance.send({
-      msgType: WsMsgTypeEnum.offer,
-      data: offerData,
-    });
-  }
-
   function addVideo() {
     sidebarList.value.push({ socketId: getSocketId() });
-    console.log(sidebarList.value, 111, getSocketId());
     nextTick(() => {
-      console.log(liveUserList.value, 222);
       liveUserList.value.forEach(async (item) => {
         const socketId = item.id;
         console.log(item, 333);
         if (socketId === getSocketId()) {
           localVideoRef.value[getSocketId()].srcObject = localStream.value;
         }
-        if (!offerSended.value.has(socketId)) {
-          hooksRtcMap.value.add(
-            await startNewWebRtc({
-              receiver: socketId,
-              videoEl: localVideoRef.value[socketId],
-            })
-          );
-          console.log('执行sendOffer', {
-            sender: getSocketId(),
-            receiver: socketId,
-          });
-          sendOffer({ sender: getSocketId(), receiver: socketId });
-          offerSended.value.add(socketId);
-        }
+        // if (!offerSended.value.has(socketId)) {
+        //   hooksRtcMap.value.add(
+        //     await startNewWebRtc({
+        //       receiver: socketId,
+        //       videoEl: localVideoRef.value[socketId],
+        //     })
+        //   );
+        //   console.log('执行sendOffer', {
+        //     sender: getSocketId(),
+        //     receiver: socketId,
+        //   });
+        //   sendOffer({ sender: getSocketId(), receiver: socketId });
+        //   offerSended.value.add(socketId);
+        // }
       });
     });
   }
 
-  /** 原生的webrtc时,receiver必传 */
-  async function startNewWebRtc({
-    receiver,
-    videoEl = remoteVideoRef.value!,
-  }: {
-    receiver: string;
-    videoEl?: HTMLVideoElement;
-  }) {
-    let rtc: WebRTCClass;
-    if (isSRS) {
-      if (!autoplayVal.value) return;
-      console.warn('开始new SRSWebRTCClass', getSocketId());
-      rtc = new WebRTCClass({
-        roomId: `${roomId.value}___${getSocketId()}`,
-        videoEl,
-        isSRS: true,
-        direction: 'recvonly',
-        receiver,
-      });
-      rtc.update();
-      try {
-        const offer = await rtc.createOffer();
-        if (!offer) return;
-        await rtc.setLocalDescription(offer);
-        const res = await fetchRtcV1Play({
-          api: `/rtc/v1/play/`,
-          clientip: null,
-          sdp: offer.sdp!,
-          streamurl: streamurl.value,
-          tid: getRandomString(10),
-        });
-        await rtc.setRemoteDescription(
-          new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
-        );
-      } catch (error) {
-        console.log(error);
-      }
-    } else {
-      if (!autoplayVal.value) return;
-      console.warn('开始new WebRTCClass');
-      rtc = new WebRTCClass({
-        roomId: `${roomId.value}___${receiver!}`,
-        videoEl,
-        isSRS: false,
-        direction: 'recvonly',
-        receiver,
-      });
-    }
-    return rtc;
-  }
-
   function keydownDanmu(event: KeyboardEvent) {
     const key = event.key.toLowerCase();
     if (key === 'enter') {
@@ -383,297 +223,6 @@ export function usePull({
     danmuStr.value = '';
   }
 
-  function initWsReceive() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (!instance?.socketIo) return;
-    // websocket连接成功
-    instance.socketIo.on(WsConnectStatusEnum.connect, () => {
-      prettierReceiveWebsocket(WsConnectStatusEnum.connect);
-      handleHeartbeat();
-      if (!instance) return;
-      instance.status = WsConnectStatusEnum.connect;
-      instance.update();
-      sendJoin();
-    });
-
-    // websocket连接断开
-    instance.socketIo.on(WsConnectStatusEnum.disconnect, () => {
-      prettierReceiveWebsocket(WsConnectStatusEnum.disconnect);
-      if (!instance) return;
-      instance.status = WsConnectStatusEnum.disconnect;
-      instance.update();
-    });
-
-    // 用户加入房间
-    instance.socketIo.on(
-      WsMsgTypeEnum.joined,
-      async (data: { data: ILive }) => {
-        prettierReceiveWebsocket(WsMsgTypeEnum.joined, data);
-        roomSocketId.value = data.data.socket_id!;
-        roomName.value = data.data.live_room?.name!;
-        userName.value = data.data.user?.username!;
-        userAvatar.value = data.data.user?.avatar!;
-        track.audio = data.data.track_audio!;
-        track.video = data.data.track_video!;
-        coverImg.value = data.data.live_room?.cover_img!;
-        flvurl.value = data.data.live_room?.flv_url!;
-        hlsurl.value = data.data.live_room?.hls_url!;
-        streamurl.value = data.data.live_room!.rtmp_url!.replace(
-          'rtmp',
-          'webrtc'
-        );
-        currentLiveRoom.value = data.data;
-        if (roomLiveType.value === liveTypeEnum.srsWebrtcPull) {
-          instance.send({ msgType: WsMsgTypeEnum.getLiveUser });
-        } else if (roomLiveType.value === liveTypeEnum.srsFlvPull) {
-          if (!autoplayVal.value) return;
-          const { width, height } = await startFlvPlay({
-            flvurl: flvurl.value,
-          });
-          videoToCanvas({
-            videoEl: flvVideoEl.value!,
-            targetEl: canvasRef.value!,
-            width,
-            height,
-            // width: flvPlayer.value?.mediaInfo.width!,
-            // height: flvPlayer.value?.mediaInfo.height!,
-          });
-          videoLoading.value = false;
-        } else if (roomLiveType.value === liveTypeEnum.srsHlsPull) {
-          if (!autoplayVal.value) return;
-          const { width, height } = await startHlsPlay({
-            hlsurl: hlsurl.value,
-          });
-          videoToCanvas({
-            videoEl: hlsVideoEl.value!,
-            targetEl: canvasRef.value!,
-            width,
-            height,
-          });
-          videoLoading.value = false;
-        } else if (
-          data.data.live_room?.type === LiveRoomTypeEnum.user_obs ||
-          data.data.live_room?.type === LiveRoomTypeEnum.system
-        ) {
-          if (!autoplayVal.value) return;
-          if (judgeDevice().isIphone) {
-            const { width, height } = await startHlsPlay({
-              hlsurl: flvurl.value,
-            });
-            videoToCanvas({
-              videoEl: hlsVideoEl.value!,
-              targetEl: canvasRef.value!,
-              width,
-              height,
-            });
-          } else {
-            const { width, height } = await startFlvPlay({
-              flvurl: flvurl.value,
-            });
-            videoToCanvas({
-              videoEl: flvVideoEl.value!,
-              targetEl: canvasRef.value!,
-              width,
-              height,
-            });
-          }
-          videoLoading.value = false;
-        }
-        instance.send({
-          msgType: WsMsgTypeEnum.getLiveUser,
-        });
-      }
-    );
-
-    // 收到offer
-    instance.socketIo.on(WsMsgTypeEnum.offer, async (data: IOffer) => {
-      prettierReceiveWebsocket(
-        WsMsgTypeEnum.offer,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS) return;
-      if (!instance) return;
-      if (data.data.receiver === getSocketId()) {
-        if (!data.is_anchor) {
-          sidebarList.value.push({ socketId: data.data.sender });
-        }
-        await nextTick(async () => {
-          console.log('收到offer,这个offer是发给我的', data);
-          sender.value = data.data.sender;
-          let rtc = networkStore.getRtcMap(
-            `${roomId.value}___${data.data.sender}`
-          );
-          if (!rtc) {
-            rtc = await startNewWebRtc({
-              receiver: data.data.sender,
-              videoEl: data.is_anchor
-                ? remoteVideoRef.value
-                : localVideoRef.value[data.data.sender],
-            });
-          }
-          // const rtc = await startNewWebRtc({
-          //   receiver: data.data.sender,
-          //   videoEl: data.is_anchor
-          //     ? remoteVideoRef.value
-          //     : localVideoRef.value[data.data.sender],
-          // });
-          if (rtc) {
-            await rtc.setRemoteDescription(data.data.sdp);
-            const sdp = await rtc.createAnswer();
-            await rtc.setLocalDescription(sdp!);
-            const answerData: IAnswer = {
-              sdp,
-              sender: getSocketId(),
-              receiver: data.data.sender,
-              live_room_id: data.data.live_room_id,
-            };
-            instance.send({
-              msgType: WsMsgTypeEnum.answer,
-              data: answerData,
-            });
-          }
-        });
-      } else {
-        console.log('收到offer,但是这个offer不是发给我的');
-      }
-    });
-
-    // 收到answer
-    instance.socketIo.on(WsMsgTypeEnum.answer, async (data: IOffer) => {
-      prettierReceiveWebsocket(
-        WsMsgTypeEnum.answer,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS) return;
-      if (!instance) 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
-    instance.socketIo.on(WsMsgTypeEnum.candidate, (data: ICandidate) => {
-      prettierReceiveWebsocket(
-        WsMsgTypeEnum.candidate,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS) return;
-      if (!instance) return;
-      const rtc = networkStore.getRtcMap(`${roomId.value}___${data.socket_id}`);
-      if (!rtc) return;
-      if (data.data.receiver === getSocketId()) {
-        console.log('是发给我的candidate');
-        const candidate = new RTCIceCandidate({
-          sdpMid: data.data.sdpMid,
-          sdpMLineIndex: data.data.sdpMLineIndex,
-          candidate: data.data.candidate,
-        });
-        rtc.peerConnection
-          ?.addIceCandidate(candidate)
-          .then(() => {
-            console.log('candidate成功');
-          })
-          .catch((err) => {
-            console.error('candidate失败', err);
-          });
-      } else {
-        console.log('不是发给我的candidate');
-      }
-    });
-
-    // 管理员正在直播
-    instance.socketIo.on(WsMsgTypeEnum.roomLiveing, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.roomLiveing, data);
-      if (isSRS && roomLiveType.value !== liveTypeEnum.srsFlvPull) {
-        startNewWebRtc({ receiver: getSocketId() });
-      }
-    });
-
-    // 管理员不在直播
-    instance.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.roomNoLive, data);
-      roomNoLive.value = true;
-      closeRtc();
-    });
-
-    // 当前所有在线用户
-    instance.socketIo.on(WsMsgTypeEnum.liveUser, (data: ILiveUser[]) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.liveUser, data);
-      if (!instance) return;
-      liveUserList.value = data;
-      // batchSendOffer();
-    });
-
-    // 收到用户发送消息
-    instance.socketIo.on(WsMsgTypeEnum.message, (data: IMessage) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.message, data);
-      if (!instance) return;
-      const danmu: IDanmu = {
-        msgType: DanmuMsgTypeEnum.danmu,
-        socket_id: data.socket_id,
-        userInfo: data.user_info,
-        msg: data.data.msg,
-      };
-      damuList.value.push(danmu);
-    });
-
-    // 其他用户加入房间
-    instance.socketIo.on(WsMsgTypeEnum.otherJoin, (data: IOtherJoin) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.otherJoin, data);
-      const danmu: IDanmu = {
-        msgType: DanmuMsgTypeEnum.otherJoin,
-        socket_id: data.data.join_socket_id,
-        userInfo: data.data.liveRoom.user,
-        msg: '',
-      };
-      damuList.value.push(danmu);
-      liveUserList.value.push({
-        id: data.data.join_socket_id,
-        userInfo: data.data.liveRoom.user,
-      });
-    });
-
-    // 用户离开房间
-    instance.socketIo.on(WsMsgTypeEnum.leave, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.leave, data);
-      if (!instance) return;
-      instance.send({
-        msgType: WsMsgTypeEnum.leave,
-        data: { roomId: instance.roomId },
-      });
-    });
-
-    // 用户离开房间完成
-    instance.socketIo.on(WsMsgTypeEnum.leaved, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.leaved, data);
-      networkStore.rtcMap
-        .get(`${roomId.value}___${data.socketId as string}`)
-        ?.close();
-      networkStore.removeRtc(`${roomId.value}___${data.socketId as string}`);
-      if (!instance) return;
-      const res = liveUserList.value.filter(
-        (item) => item.id !== data.socketId
-      );
-      liveUserList.value = res;
-      const danmu: IDanmu = {
-        msgType: DanmuMsgTypeEnum.userLeaved,
-        socket_id: data.socketId,
-        userInfo: data.data.userInfo,
-        msg: '',
-      };
-      damuList.value.push(danmu);
-    });
-  }
-
   return {
     initPull,
     closeWs,
@@ -684,22 +233,11 @@ export function usePull({
     addVideo,
     autoplayVal,
     videoLoading,
-    balance,
-    roomLiveType,
-    roomSocketId,
-    roomName,
-    userName,
-    userAvatar,
-    currentLiveRoom,
-    hlsurl,
-    coverImg,
     roomNoLive,
     damuList,
-    giftList,
     liveUserList,
-    danmuStr,
-    localStream,
-    sender,
     sidebarList,
+    danmuStr,
+    liveRoomInfo,
   };
 }

+ 703 - 0
src/hooks/use-pull22.ts

@@ -0,0 +1,703 @@
+import { getRandomString, judgeDevice } from 'billd-utils';
+import { NODE_ENV } from 'script/constant';
+import { Ref, nextTick, onUnmounted, reactive, ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
+
+import { fetchRtcV1Play } from '@/api/srs';
+import { WEBSOCKET_URL } from '@/constant';
+import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
+import {
+  DanmuMsgTypeEnum,
+  IAnswer,
+  ICandidate,
+  IDanmu,
+  IJoin,
+  ILive,
+  ILiveUser,
+  IMessage,
+  IOffer,
+  IOtherJoin,
+  IUpdateJoinInfo,
+  LiveRoomTypeEnum,
+  liveTypeEnum,
+} from '@/interface';
+import { WebRTCClass, audioElArr } from '@/network/webRTC';
+import {
+  WebSocketClass,
+  WsConnectStatusEnum,
+  WsMsgTypeEnum,
+  prettierReceiveWebsocket,
+} from '@/network/webSocket';
+import { useAppStore } from '@/store/app';
+import { useNetworkStore } from '@/store/network';
+import { useUserStore } from '@/store/user';
+import { videoToCanvas } from '@/utils';
+
+export function usePull({
+  localVideoRef,
+  canvasRef,
+  isSRS,
+  liveType,
+}: {
+  localVideoRef: Ref<HTMLVideoElement[]>;
+  canvasRef: Ref<Element | undefined>;
+  isSRS?: boolean;
+  liveType: liveTypeEnum;
+}) {
+  const route = useRoute();
+  const appStore = useAppStore();
+  const userStore = useUserStore();
+  const networkStore = useNetworkStore();
+  const videoEl = document.createElement('video');
+  videoEl.muted = true;
+  videoEl.playsInline = true;
+  videoEl.autoplay = true;
+  videoEl.setAttribute('webkit-playsinline', 'true');
+  videoEl.oncontextmenu = (e) => {
+    e.preventDefault();
+  };
+  if (NODE_ENV === 'development') {
+    videoEl.controls = true;
+  }
+  const remoteVideoRef = ref(videoEl);
+  const heartbeatTimer = ref();
+  const roomId = ref(route.params.roomId as string);
+  const roomLiveType = ref<liveTypeEnum>(liveType);
+  const roomName = ref('');
+  const roomSocketId = ref('');
+  const userName = ref('');
+  const userAvatar = ref('');
+  const streamurl = ref('');
+  const flvurl = ref('');
+  const hlsurl = ref('');
+  const coverImg = ref('');
+  const danmuStr = ref('');
+  const balance = ref('0.00');
+  const currentLiveRoom = ref<ILive>();
+  const damuList = ref<IDanmu[]>([]);
+  const liveUserList = ref<ILiveUser[]>([]);
+  const autoplayVal = ref(false);
+  const videoLoading = ref(false);
+  const isDone = ref(false);
+  const roomNoLive = ref(false);
+  const localStream = ref();
+  const sidebarList = ref<
+    {
+      socketId: string;
+    }[]
+  >([]);
+
+  const track = reactive({
+    audio: 1,
+    video: 1,
+  });
+
+  const giftList = ref([
+    { name: '鲜花', ico: '', price: '免费' },
+    { name: '肥宅水', ico: '', price: '2元' },
+    { name: '小鸡腿', ico: '', price: '3元' },
+    { name: '大鸡腿', ico: '', price: '5元' },
+    { name: '一杯咖啡', ico: '', price: '10元' },
+  ]);
+  const offerSended = ref(new Set());
+  const hooksRtcMap = ref(new Set());
+  const sender = ref();
+
+  const { flvVideoEl, startFlvPlay } = useFlvPlay();
+  const { hlsVideoEl, startHlsPlay } = useHlsPlay();
+
+  onUnmounted(() => {
+    clearInterval(heartbeatTimer.value);
+  });
+
+  watch(
+    () => appStore.allTrack,
+    () => {
+      // networkStore.rtcMap.forEach((rtc) => {
+      //   if (appStore.getTrackInfo().audio > 0) {
+      //     rtc!.peerConnection?.addTransceiver('audio', {
+      //       direction: 'recvonly',
+      //     });
+      //   }
+      //   if (appStore.getTrackInfo().video > 0) {
+      //     rtc!.peerConnection?.addTransceiver('video', {
+      //       direction: 'recvonly',
+      //     });
+      //   }
+      // });
+    },
+    { deep: true }
+  );
+
+  watch(
+    () => appStore.muted,
+    (val) => {
+      remoteVideoRef.value.muted = val;
+      audioElArr.forEach((el) => {
+        console.log(el, el.muted);
+        el.muted = val;
+      });
+    }
+  );
+
+  watch(
+    [
+      () => userStore.userInfo,
+      () => networkStore.wsMap.get(roomId.value)?.socketIo?.connected,
+    ],
+    ([userInfo, connected]) => {
+      if (userInfo) {
+        balance.value = userInfo.wallet?.balance || '0.00';
+      }
+      if (userInfo && connected) {
+        const instance = networkStore.wsMap.get(roomId.value);
+        if (!instance) return;
+        const data: IUpdateJoinInfo['data'] = {
+          live_room_id: Number(roomId.value),
+        };
+        instance.send({
+          msgType: WsMsgTypeEnum.updateJoinInfo,
+          data,
+        });
+      }
+    }
+  );
+
+  function initPull(autolay = true) {
+    autoplayVal.value = autolay;
+    if (autoplayVal.value) {
+      videoLoading.value = true;
+    }
+    console.warn('开始new WebSocketClass');
+    const ws = new WebSocketClass({
+      roomId: roomId.value,
+      url: WEBSOCKET_URL,
+      isAnchor: false,
+    });
+    ws.update();
+    initWsReceive();
+
+    remoteVideoRef.value?.addEventListener('loadstart', () => {
+      console.warn('视频流-loadstart');
+      const rtc = networkStore.getRtcMap(roomId.value);
+      if (!rtc) return;
+      rtc.update();
+    });
+
+    remoteVideoRef.value?.addEventListener('loadedmetadata', () => {
+      console.warn('视频流-loadedmetadata');
+      if (
+        roomLiveType.value === liveTypeEnum.webrtcPull ||
+        roomLiveType.value === liveTypeEnum.srsWebrtcPull
+      ) {
+        canvasRef.value?.appendChild(remoteVideoRef.value);
+      }
+      videoLoading.value = false;
+      const rtc = networkStore.getRtcMap(roomId.value);
+      if (!rtc) return;
+      rtc.update();
+    });
+  }
+
+  function handleHeartbeat() {
+    heartbeatTimer.value = setInterval(() => {
+      const instance = networkStore.wsMap.get(roomId.value);
+      if (!instance) return;
+      instance.send({
+        msgType: WsMsgTypeEnum.heartbeat,
+      });
+    }, 1000 * 5);
+  }
+
+  function closeWs() {
+    const instance = networkStore.wsMap.get(roomId.value);
+    instance?.close();
+  }
+
+  function closeRtc() {
+    networkStore.rtcMap.forEach((rtc) => {
+      rtc.close();
+    });
+  }
+
+  function getSocketId() {
+    return networkStore.wsMap.get(roomId.value!)?.socketIo?.id || '-1';
+  }
+
+  function sendJoin() {
+    const instance = networkStore.wsMap.get(roomId.value);
+    if (!instance) return;
+    const joinData: IJoin['data'] = {
+      live_room: {
+        id: Number(roomId.value),
+        name: roomName.value,
+        type: isSRS ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc,
+      },
+      track,
+    };
+    instance.send({
+      msgType: WsMsgTypeEnum.join,
+      data: joinData,
+    });
+  }
+
+  async function sendOffer({
+    sender,
+    receiver,
+  }: {
+    sender: string;
+    receiver: string;
+  }) {
+    console.log(isDone.value);
+    if (isDone.value) return;
+    const instance = networkStore.wsMap.get(roomId.value);
+    console.log(instance, roomId.value);
+    if (!instance) return;
+    const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
+    if (!rtc) return;
+    const sdp = await rtc.createOffer();
+    await rtc.setLocalDescription(sdp!);
+    const offerData = {
+      sdp,
+      sender,
+      receiver,
+      live_room_id: roomId.value,
+    };
+    instance.send({
+      msgType: WsMsgTypeEnum.offer,
+      data: offerData,
+    });
+  }
+
+  function addVideo() {
+    sidebarList.value.push({ socketId: getSocketId() });
+    nextTick(() => {
+      liveUserList.value.forEach(async (item) => {
+        const socketId = item.id;
+        console.log(item, 333);
+        if (socketId === getSocketId()) {
+          localVideoRef.value[getSocketId()].srcObject = localStream.value;
+        }
+        if (!offerSended.value.has(socketId)) {
+          hooksRtcMap.value.add(
+            await startNewWebRtc({
+              receiver: socketId,
+              videoEl: localVideoRef.value[socketId],
+            })
+          );
+          console.log('执行sendOffer', {
+            sender: getSocketId(),
+            receiver: socketId,
+          });
+          sendOffer({ sender: getSocketId(), receiver: socketId });
+          offerSended.value.add(socketId);
+        }
+      });
+    });
+  }
+
+  /** 原生的webrtc时,receiver必传 */
+  async function startNewWebRtc({
+    receiver,
+    videoEl = remoteVideoRef.value!,
+  }: {
+    receiver: string;
+    videoEl?: HTMLVideoElement;
+  }) {
+    let rtc: WebRTCClass;
+    if (isSRS) {
+      if (!autoplayVal.value) return;
+      console.warn('开始new SRSWebRTCClass', getSocketId());
+      rtc = new WebRTCClass({
+        roomId: `${roomId.value}___${getSocketId()}`,
+        videoEl,
+        isSRS: true,
+        direction: 'recvonly',
+        receiver,
+      });
+      rtc.update();
+      try {
+        const offer = await rtc.createOffer();
+        if (!offer) return;
+        await rtc.setLocalDescription(offer);
+        const res = await fetchRtcV1Play({
+          api: `/rtc/v1/play/`,
+          clientip: null,
+          sdp: offer.sdp!,
+          streamurl: streamurl.value,
+          tid: getRandomString(10),
+        });
+        await rtc.setRemoteDescription(
+          new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
+        );
+      } catch (error) {
+        console.log(error);
+      }
+    } else {
+      if (!autoplayVal.value) return;
+      console.warn('开始new WebRTCClass');
+      rtc = new WebRTCClass({
+        roomId: `${roomId.value}___${receiver!}`,
+        videoEl,
+        isSRS: false,
+        direction: 'recvonly',
+        receiver,
+      });
+    }
+    return rtc;
+  }
+
+  function keydownDanmu(event: KeyboardEvent) {
+    const key = event.key.toLowerCase();
+    if (key === 'enter') {
+      event.preventDefault();
+      sendDanmu();
+    }
+  }
+
+  function sendDanmu() {
+    if (!danmuStr.value.trim().length) {
+      window.$message.warning('请输入弹幕内容!');
+      return;
+    }
+    const instance = networkStore.wsMap.get(roomId.value);
+    if (!instance) return;
+    const danmu: IDanmu = {
+      socket_id: getSocketId(),
+      userInfo: userStore.userInfo,
+      msgType: DanmuMsgTypeEnum.danmu,
+      msg: danmuStr.value,
+    };
+    const messageData: IMessage['data'] = {
+      msg: danmuStr.value,
+      msgType: DanmuMsgTypeEnum.danmu,
+      live_room_id: Number(roomId.value),
+    };
+    instance.send({
+      msgType: WsMsgTypeEnum.message,
+      data: messageData,
+    });
+    damuList.value.push(danmu);
+    danmuStr.value = '';
+  }
+
+  function initWsReceive() {
+    const instance = networkStore.wsMap.get(roomId.value);
+    if (!instance?.socketIo) return;
+    // websocket连接成功
+    instance.socketIo.on(WsConnectStatusEnum.connect, () => {
+      prettierReceiveWebsocket(WsConnectStatusEnum.connect);
+      handleHeartbeat();
+      if (!instance) return;
+      instance.status = WsConnectStatusEnum.connect;
+      instance.update();
+      sendJoin();
+    });
+
+    // websocket连接断开
+    instance.socketIo.on(WsConnectStatusEnum.disconnect, () => {
+      prettierReceiveWebsocket(WsConnectStatusEnum.disconnect);
+      if (!instance) return;
+      instance.status = WsConnectStatusEnum.disconnect;
+      instance.update();
+    });
+
+    // 用户加入房间
+    instance.socketIo.on(
+      WsMsgTypeEnum.joined,
+      async (data: { data: ILive }) => {
+        prettierReceiveWebsocket(WsMsgTypeEnum.joined, data);
+        roomSocketId.value = data.data.socket_id!;
+        roomName.value = data.data.live_room?.name!;
+        userName.value = data.data.user?.username!;
+        userAvatar.value = data.data.user?.avatar!;
+        track.audio = data.data.track_audio!;
+        track.video = data.data.track_video!;
+        coverImg.value = data.data.live_room?.cover_img!;
+        flvurl.value = data.data.live_room?.flv_url!;
+        hlsurl.value = data.data.live_room?.hls_url!;
+        streamurl.value = data.data.live_room!.rtmp_url!.replace(
+          'rtmp',
+          'webrtc'
+        );
+        currentLiveRoom.value = data.data;
+        if (roomLiveType.value === liveTypeEnum.srsWebrtcPull) {
+          instance.send({ msgType: WsMsgTypeEnum.getLiveUser });
+        } else if (roomLiveType.value === liveTypeEnum.srsFlvPull) {
+          if (!autoplayVal.value) return;
+          const { width, height } = await startFlvPlay({
+            flvurl: flvurl.value,
+          });
+          videoToCanvas({
+            videoEl: flvVideoEl.value!,
+            targetEl: canvasRef.value!,
+            width,
+            height,
+            // width: flvPlayer.value?.mediaInfo.width!,
+            // height: flvPlayer.value?.mediaInfo.height!,
+          });
+          videoLoading.value = false;
+        } else if (roomLiveType.value === liveTypeEnum.srsHlsPull) {
+          if (!autoplayVal.value) return;
+          const { width, height } = await startHlsPlay({
+            hlsurl: hlsurl.value,
+          });
+          videoToCanvas({
+            videoEl: hlsVideoEl.value!,
+            targetEl: canvasRef.value!,
+            width,
+            height,
+          });
+          videoLoading.value = false;
+        } else if (
+          data.data.live_room?.type === LiveRoomTypeEnum.user_obs ||
+          data.data.live_room?.type === LiveRoomTypeEnum.system
+        ) {
+          if (!autoplayVal.value) return;
+          if (judgeDevice().isIphone) {
+            const { width, height } = await startHlsPlay({
+              hlsurl: flvurl.value,
+            });
+            videoToCanvas({
+              videoEl: hlsVideoEl.value!,
+              targetEl: canvasRef.value!,
+              width,
+              height,
+            });
+          } else {
+            const { width, height } = await startFlvPlay({
+              flvurl: flvurl.value,
+            });
+            videoToCanvas({
+              videoEl: flvVideoEl.value!,
+              targetEl: canvasRef.value!,
+              width,
+              height,
+            });
+          }
+          videoLoading.value = false;
+        }
+        instance.send({
+          msgType: WsMsgTypeEnum.getLiveUser,
+        });
+      }
+    );
+
+    // 收到offer
+    instance.socketIo.on(WsMsgTypeEnum.offer, async (data: IOffer) => {
+      prettierReceiveWebsocket(
+        WsMsgTypeEnum.offer,
+        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
+        data
+      );
+      if (isSRS) return;
+      if (!instance) return;
+      if (data.data.receiver === getSocketId()) {
+        if (!data.is_anchor) {
+          sidebarList.value.push({ socketId: data.data.sender });
+        }
+        await nextTick(async () => {
+          console.log('收到offer,这个offer是发给我的', data);
+          sender.value = data.data.sender;
+          let rtc = networkStore.getRtcMap(
+            `${roomId.value}___${data.data.sender}`
+          );
+          if (!rtc) {
+            rtc = await startNewWebRtc({
+              receiver: data.data.sender,
+              videoEl: data.is_anchor
+                ? remoteVideoRef.value
+                : localVideoRef.value[data.data.sender],
+            });
+          }
+          // const rtc = await startNewWebRtc({
+          //   receiver: data.data.sender,
+          //   videoEl: data.is_anchor
+          //     ? remoteVideoRef.value
+          //     : localVideoRef.value[data.data.sender],
+          // });
+          if (rtc) {
+            await rtc.setRemoteDescription(data.data.sdp);
+            const sdp = await rtc.createAnswer();
+            await rtc.setLocalDescription(sdp!);
+            const answerData: IAnswer = {
+              sdp,
+              sender: getSocketId(),
+              receiver: data.data.sender,
+              live_room_id: data.data.live_room_id,
+            };
+            instance.send({
+              msgType: WsMsgTypeEnum.answer,
+              data: answerData,
+            });
+          }
+        });
+      } else {
+        console.log('收到offer,但是这个offer不是发给我的');
+      }
+    });
+
+    // 收到answer
+    instance.socketIo.on(WsMsgTypeEnum.answer, async (data: IOffer) => {
+      prettierReceiveWebsocket(
+        WsMsgTypeEnum.answer,
+        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
+        data
+      );
+      if (isSRS) return;
+      if (!instance) 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
+    instance.socketIo.on(WsMsgTypeEnum.candidate, (data: ICandidate) => {
+      prettierReceiveWebsocket(
+        WsMsgTypeEnum.candidate,
+        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
+        data
+      );
+      if (isSRS) return;
+      if (!instance) return;
+      const rtc = networkStore.getRtcMap(`${roomId.value}___${data.socket_id}`);
+      if (!rtc) return;
+      if (data.data.receiver === getSocketId()) {
+        console.log('是发给我的candidate');
+        const candidate = new RTCIceCandidate({
+          sdpMid: data.data.sdpMid,
+          sdpMLineIndex: data.data.sdpMLineIndex,
+          candidate: data.data.candidate,
+        });
+        rtc.peerConnection
+          ?.addIceCandidate(candidate)
+          .then(() => {
+            console.log('candidate成功');
+          })
+          .catch((err) => {
+            console.error('candidate失败', err);
+          });
+      } else {
+        console.log('不是发给我的candidate');
+      }
+    });
+
+    // 管理员正在直播
+    instance.socketIo.on(WsMsgTypeEnum.roomLiveing, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.roomLiveing, data);
+      if (isSRS && roomLiveType.value !== liveTypeEnum.srsFlvPull) {
+        startNewWebRtc({ receiver: getSocketId() });
+      }
+    });
+
+    // 管理员不在直播
+    instance.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.roomNoLive, data);
+      roomNoLive.value = true;
+      closeRtc();
+    });
+
+    // 当前所有在线用户
+    instance.socketIo.on(WsMsgTypeEnum.liveUser, (data: ILiveUser[]) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.liveUser, data);
+      if (!instance) return;
+      liveUserList.value = data;
+      // batchSendOffer();
+    });
+
+    // 收到用户发送消息
+    instance.socketIo.on(WsMsgTypeEnum.message, (data: IMessage) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.message, data);
+      if (!instance) return;
+      const danmu: IDanmu = {
+        msgType: DanmuMsgTypeEnum.danmu,
+        socket_id: data.socket_id,
+        userInfo: data.user_info,
+        msg: data.data.msg,
+      };
+      damuList.value.push(danmu);
+    });
+
+    // 其他用户加入房间
+    instance.socketIo.on(WsMsgTypeEnum.otherJoin, (data: IOtherJoin) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.otherJoin, data);
+      const danmu: IDanmu = {
+        msgType: DanmuMsgTypeEnum.otherJoin,
+        socket_id: data.data.join_socket_id,
+        userInfo: data.data.liveRoom.user,
+        msg: '',
+      };
+      damuList.value.push(danmu);
+      liveUserList.value.push({
+        id: data.data.join_socket_id,
+        userInfo: data.data.liveRoom.user,
+      });
+    });
+
+    // 用户离开房间
+    instance.socketIo.on(WsMsgTypeEnum.leave, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.leave, data);
+      if (!instance) return;
+      instance.send({
+        msgType: WsMsgTypeEnum.leave,
+        data: { roomId: instance.roomId },
+      });
+    });
+
+    // 用户离开房间完成
+    instance.socketIo.on(WsMsgTypeEnum.leaved, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.leaved, data);
+      networkStore.rtcMap
+        .get(`${roomId.value}___${data.socketId as string}`)
+        ?.close();
+      networkStore.removeRtc(`${roomId.value}___${data.socketId as string}`);
+      if (!instance) return;
+      const res = liveUserList.value.filter(
+        (item) => item.id !== data.socketId
+      );
+      liveUserList.value = res;
+      const danmu: IDanmu = {
+        msgType: DanmuMsgTypeEnum.userLeaved,
+        socket_id: data.socketId,
+        userInfo: data.data.userInfo,
+        msg: '',
+      };
+      damuList.value.push(danmu);
+    });
+  }
+
+  return {
+    initPull,
+    closeWs,
+    closeRtc,
+    getSocketId,
+    keydownDanmu,
+    sendDanmu,
+    addVideo,
+    autoplayVal,
+    videoLoading,
+    balance,
+    roomLiveType,
+    roomSocketId,
+    roomName,
+    userName,
+    userAvatar,
+    currentLiveRoom,
+    hlsurl,
+    coverImg,
+    roomNoLive,
+    damuList,
+    giftList,
+    liveUserList,
+    danmuStr,
+    localStream,
+    sender,
+    sidebarList,
+  };
+}

+ 35 - 700
src/hooks/use-push.ts

@@ -1,35 +1,13 @@
-import { getRandomString, windowReload } from 'billd-utils';
-import { Ref, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
+import { windowReload } from 'billd-utils';
+import { Ref, onMounted, onUnmounted, ref, watch } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 
-import { fetchRtcV1Publish } from '@/api/srs';
 import {
   fetchCreateUserLiveRoom,
   fetchUserHasLiveRoom,
 } from '@/api/userLiveRoom';
-import { WEBSOCKET_URL } from '@/constant';
-import {
-  DanmuMsgTypeEnum,
-  IAnswer,
-  ICandidate,
-  IDanmu,
-  IHeartbeat,
-  IJoin,
-  ILiveUser,
-  IMessage,
-  IOffer,
-  IOtherJoin,
-  IUpdateJoinInfo,
-  LiveRoomTypeEnum,
-  MediaTypeEnum,
-} from '@/interface';
-import { WebRTCClass } from '@/network/webRTC';
-import {
-  WebSocketClass,
-  WsConnectStatusEnum,
-  WsMsgTypeEnum,
-  prettierReceiveWebsocket,
-} from '@/network/webSocket';
+import { DanmuMsgTypeEnum, IMessage, MediaTypeEnum } from '@/interface';
+import { WsMsgTypeEnum } from '@/network/webSocket';
 import { useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
@@ -43,7 +21,7 @@ export function usePush({
   remoteVideoRef,
   isSRS,
 }: {
-  localVideoRef: Ref<HTMLVideoElement>;
+  localVideoRef: Ref<HTMLVideoElement | undefined>;
   remoteVideoRef: Ref<HTMLVideoElement[]>;
   isSRS: boolean;
 }) {
@@ -52,108 +30,11 @@ export function usePush({
   const appStore = useAppStore();
   const userStore = useUserStore();
   const networkStore = useNetworkStore();
-  const heartbeatTimer = ref();
-  const roomId = ref('-1');
+
+  const roomId = ref('');
   const roomName = ref('');
   const danmuStr = ref('');
   const isLiving = ref(false);
-  const joined = ref(false);
-  const localStream = ref<MediaStream>();
-  const offerSended = ref(new Set());
-  const webRTC = ref<WebRTCClass>();
-  const srsSdp = ref('');
-  const { initWs } = useWs();
-  const maxBitrate = ref([
-    {
-      label: '1000',
-      value: 1000,
-    },
-    {
-      label: '2000',
-      value: 2000,
-    },
-    {
-      label: '3000',
-      value: 3000,
-    },
-    {
-      label: '4000',
-      value: 4000,
-    },
-    {
-      label: '5000',
-      value: 5000,
-    },
-    {
-      label: '6000',
-      value: 6000,
-    },
-    {
-      label: '7000',
-      value: 7000,
-    },
-    {
-      label: '8000',
-      value: 8000,
-    },
-    {
-      label: '9000',
-      value: 9000,
-    },
-    {
-      label: '10000',
-      value: 10000,
-    },
-  ]);
-  const currentMaxBitrate = ref(maxBitrate.value[0].value);
-
-  const resolutionRatio = ref([
-    {
-      label: '360P',
-      value: 360,
-    },
-    {
-      label: '720P',
-      value: 720,
-    },
-    {
-      label: '1080P',
-      value: 1080,
-    },
-    {
-      label: '1440P',
-      value: 1440,
-    },
-  ]);
-  const currentResolutionRatio = ref(resolutionRatio.value[2].value);
-
-  const maxFramerate = ref([
-    {
-      label: '10帧',
-      value: 10,
-    },
-    {
-      label: '20帧',
-      value: 20,
-    },
-    {
-      label: '24帧',
-      value: 24,
-    },
-    {
-      label: '30帧',
-      value: 30,
-    },
-    {
-      label: '60帧',
-      value: 60,
-    },
-  ]);
-  const currentMaxFramerate = ref(maxFramerate.value[1].value);
-
-  const streamurl = ref('');
-  const damuList = ref<IDanmu[]>([]);
-  const liveUserList = ref<ILiveUser[]>([]);
 
   const allMediaTypeList: {
     [index: string]: { type: MediaTypeEnum; txt: string };
@@ -171,129 +52,34 @@ export function usePush({
       txt: '麦克风',
     },
   };
-  const userSelectMediaList = ref<
-    {
-      mediaName: string;
-      audio: boolean;
-      video: boolean;
-    }[]
-  >([]);
-
-  watch(
-    () => appStore.allTrack,
-    (trackInfo) => {
-      console.log('appStore.allTrack变了');
-      const mixedStream = new MediaStream();
-      trackInfo.forEach((item) => {
-        mixedStream.addTrack(item.track);
-      });
-      localStream.value = mixedStream;
-      localVideoRef.value.srcObject = mixedStream;
-      // if (!localVideoRef.value || !newStream) return;
-      // localVideoRef.value.srcObject = newStream;
-      // if (isSRS) {
-      //   if (isLiving.value) {
-      //     console.log('当前是srs,关闭');
-      //     networkStore.getRtcMap(`${roomId.value}___${getSocketId()}`)?.close();
-      //     networkStore.removeRtc(`${roomId.value}___${getSocketId()}`);
-      //     startNewWebRtc({
-      //       receiver: getSocketId(),
-      //       videoEl: localVideoRef.value,
-      //     });
-      //   }
-      // } else {
-      //   networkStore.rtcMap.forEach((rtc) => {
-      //     newStream?.getTracks().forEach((track) => {
-      //       const sender = rtc.peerConnection
-      //         ?.getSenders()
-      //         .find((s) => s.track?.id === track.id);
-      //       if (!sender) {
-      //         console.warn('localStream变了,pc插入track');
-      //         // rtc.peerConnection?.addTransceiver(track, {
-      //         //   streams: [newStream],
-      //         //   direction: 'sendonly',
-      //         // });
-      //         rtc.peerConnection?.addTrack(track, newStream);
-      //       }
-      //     });
-      //   });
-      // }
-    },
-    { deep: true }
-  );
-
-  watch(
-    () => appStore.allTrack,
-    () => {
-      console.log('allTrack变了');
-      const data: IUpdateJoinInfo['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,
-      });
-    },
-    {
-      deep: true,
-    }
-  );
-
-  watch(
-    () => appStore.muted,
-    (newVal) => {
-      console.log(newVal);
-      // const rtc = networkStore.getRtcMap(`${roomId.value}___${item.id}`);
-    }
-  );
-
-  watch(
-    () => currentMaxFramerate.value,
-    async (newVal) => {
-      if (!webRTC.value) {
-        return;
-      }
-      const res = await webRTC.value.setMaxFramerate(newVal);
-      if (res === 1) {
-        window.$message.success('切换帧率成功!');
-      } else {
-        window.$message.success('切换帧率失败!');
-      }
-    }
-  );
 
-  watch(
-    () => currentMaxBitrate.value,
-    async (newVal) => {
-      if (!webRTC.value) {
-        return;
-      }
-      const res = await webRTC.value.setMaxBitrate(newVal);
-      if (res === 1) {
-        window.$message.success('切换码率成功!');
-      } else {
-        window.$message.success('切换码率失败!');
-      }
-    }
-  );
+  const {
+    getSocketId,
+    initWs,
+    heartbeatTimer,
+    localStream,
+    liveUserList,
+    damuList,
+    maxBitrate,
+    maxFramerate,
+    resolutionRatio,
+    currentMaxFramerate,
+    currentMaxBitrate,
+    currentResolutionRatio,
+    addTrack,
+    delTrack,
+  } = useWs();
 
   watch(
-    () => currentResolutionRatio.value,
-    async (newVal) => {
-      if (!webRTC.value) {
-        return;
-      }
-      const res = await webRTC.value.setResolutionRatio(newVal);
-      if (res === 1) {
-        window.$message.success('切换分辨率成功!');
+    () => localStream,
+    (stream) => {
+      if (stream.value) {
+        localVideoRef.value!.srcObject = stream.value;
       } else {
-        window.$message.success('切换分辨率失败!');
+        localVideoRef.value!.srcObject = null;
       }
-    }
+    },
+    { deep: true }
   );
 
   watch(
@@ -304,12 +90,6 @@ export function usePush({
         if (!res) {
           await useTip('你还没有直播间,是否立即开通?');
           await handleCreateUserLiveRoom();
-        } else {
-          const rtmpUrl = newVal.live_rooms![0]!.rtmp_url!.replace(
-            'rtmp',
-            'webrtc'
-          );
-          streamurl.value = rtmpUrl;
         }
       }
     },
@@ -322,7 +102,6 @@ export function usePush({
   });
 
   onUnmounted(() => {
-    clearInterval(heartbeatTimer.value);
     closeWs();
     closeRtc();
   });
@@ -342,7 +121,7 @@ export function usePush({
     const res = await fetchUserHasLiveRoom(userStore.userInfo?.id!);
     if (res.code === 200 && res.data) {
       roomName.value = res.data.live_room?.name || '';
-      roomId.value = `${res.data.live_room?.id || -1}`;
+      roomId.value = `${res.data.live_room?.id || ''}`;
       router.push({ query: { ...route.query, roomId: roomId.value } });
       return true;
     }
@@ -376,32 +155,22 @@ export function usePush({
       window.$message.warning('请选择一个素材!');
       return;
     }
+    isLiving.value = true;
     initWs({
+      isAnchor: true,
       roomId: roomId.value,
       isSRS,
-      localVideo: localVideoRef.value,
       currentMaxBitrate: currentMaxBitrate.value,
       currentMaxFramerate: currentMaxFramerate.value,
       currentResolutionRatio: currentResolutionRatio.value,
     });
     return;
-    const instance = new WebSocketClass({
-      roomId: roomId.value,
-      url: WEBSOCKET_URL,
-      isAnchor: true,
-    });
-    isLiving.value = true;
-    instance.update();
-    initReceive();
   }
 
   /** 结束直播 */
   function endLive() {
     isLiving.value = false;
-    userSelectMediaList.value = [];
     localStream.value = undefined;
-
-    localVideoRef.value!.srcObject = null;
     clearInterval(heartbeatTimer.value);
     const instance = networkStore.wsMap.get(roomId.value);
     if (instance) {
@@ -416,420 +185,6 @@ export function usePush({
     }, 500);
   }
 
-  function handleNegotiationneeded(data: { roomId: string; isSRS: boolean }) {
-    console.warn(`${data.roomId},开始监听pc的negotiationneeded`);
-    const rtc = networkStore.getRtcMap(data.roomId);
-    if (!rtc) return;
-    rtc.peerConnection?.addEventListener('negotiationneeded', (event) => {
-      console.warn(`${data.roomId},pc收到negotiationneeded`, event);
-      sendOffer({
-        sender: getSocketId(),
-        receiver: rtc.receiver,
-        isSRS: data.isSRS,
-      });
-    });
-  }
-
-  /** 原生的webrtc时,receiver必传 */
-  function startNewWebRtc({
-    receiver,
-    videoEl = localVideoRef.value!,
-  }: {
-    receiver: string;
-    videoEl?: HTMLVideoElement;
-  }) {
-    console.log('xxx', localStream.value);
-    let rtc: WebRTCClass;
-    if (isSRS) {
-      console.warn('SRS开始new WebRTCClass', `${roomId.value}___${receiver!}`);
-      rtc = new WebRTCClass({
-        maxBitrate: currentMaxBitrate.value,
-        maxFramerate: currentMaxFramerate.value,
-        resolutionRatio: currentResolutionRatio.value,
-        roomId: `${roomId.value}___${getSocketId()}`,
-        videoEl,
-        isSRS: true,
-        direction: 'sendonly',
-        receiver,
-      });
-      handleNegotiationneeded({
-        roomId: `${roomId.value}___${receiver}`,
-        isSRS: true,
-      });
-      rtc.localStream = localStream.value;
-      localStream.value?.getTracks().forEach((track) => {
-        console.warn('srs startNewWebRtc,pc插入track');
-        // rtc.peerConnection?.addTransceiver(track, {
-        //   streams: [localStream.value!],
-        //   direction: 'sendonly',
-        // });
-        rtc.peerConnection?.addTrack(track, localStream.value!);
-      });
-      webRTC.value = rtc;
-    } else {
-      console.warn('-开始new WebRTCClass', `${roomId.value}___${receiver!}`);
-      rtc = new WebRTCClass({
-        maxBitrate: currentMaxBitrate.value,
-        maxFramerate: currentMaxFramerate.value,
-        resolutionRatio: currentResolutionRatio.value,
-        roomId: `${roomId.value}___${receiver!}`,
-        videoEl,
-        isSRS: false,
-        direction: 'sendonly',
-        receiver,
-      });
-      handleNegotiationneeded({
-        roomId: `${roomId.value}___${receiver}`,
-        isSRS: false,
-      });
-      rtc.localStream = localStream.value;
-      localStream.value?.getTracks().forEach((track) => {
-        console.warn('startNewWebRtc,pc插入track');
-        // rtc.peerConnection?.addTransceiver(track, {
-        //   streams: [localStream.value!],
-        //   direction: 'sendonly',
-        // });
-        rtc.peerConnection?.addTrack(track, localStream.value!);
-      });
-      webRTC.value = rtc;
-    }
-    return rtc;
-  }
-
-  function handleCoverImg() {
-    const canvas = document.createElement('canvas');
-    const { width, height } = localVideoRef.value!.getBoundingClientRect();
-    const rate = width / height;
-    const coverWidth = width * 0.5;
-    const coverHeight = coverWidth / rate;
-    canvas.width = coverWidth;
-    canvas.height = coverHeight;
-    canvas
-      .getContext('2d')!
-      .drawImage(localVideoRef.value!, 0, 0, coverWidth, coverHeight);
-    // webp比png的体积小非常多!因此coverWidth就可以不用压缩太夸张
-    const dataURL = canvas.toDataURL('image/webp');
-    return dataURL;
-  }
-
-  function handleHeartbeat(liveId: number) {
-    heartbeatTimer.value = setInterval(() => {
-      const instance = networkStore.wsMap.get(roomId.value);
-      if (!instance) return;
-      const heartbeatData: IHeartbeat['data'] = {
-        live_id: liveId,
-        live_room_id: Number(roomId.value),
-      };
-      instance.send({
-        msgType: WsMsgTypeEnum.heartbeat,
-        data: heartbeatData,
-      });
-    }, 1000 * 5);
-  }
-
-  function addTrack() {
-    if (!localStream.value) return;
-    liveUserList.value.forEach((item) => {
-      if (item.id !== getSocketId()) {
-        localStream.value?.getTracks().forEach((track) => {
-          const rtc = networkStore.getRtcMap(`${roomId.value}___${item.id}`);
-          console.log('4444444444');
-          rtc?.addTrack(localStream.value!);
-        });
-      }
-    });
-  }
-
-  function sendJoin() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (!instance) return;
-    const joinData: IJoin['data'] = {
-      live_room: {
-        id: Number(roomId.value),
-        name: roomName.value,
-        cover_img: handleCoverImg(),
-        type: isSRS ? LiveRoomTypeEnum.user_srs : LiveRoomTypeEnum.user_wertc,
-      },
-      track: {
-        audio: appStore.getTrackInfo().audio > 0 ? 1 : 2,
-        video: appStore.getTrackInfo().video > 0 ? 1 : 2,
-      },
-    };
-    instance.send({
-      msgType: WsMsgTypeEnum.join,
-      data: joinData,
-    });
-  }
-
-  async function sendOffer({
-    sender,
-    receiver,
-    isSRS,
-  }: {
-    sender: string;
-    receiver: string;
-    isSRS: boolean;
-  }) {
-    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) {
-      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();
-      console.log(sdp, 22);
-      await rtc.setLocalDescription(sdp!);
-      const 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),
-      });
-      srsSdp.value = res.data.sdp;
-      await rtc.setRemoteDescription(
-        new RTCSessionDescription({ type: 'answer', sdp: srsSdp.value })
-      );
-    }
-  }
-
-  function batchSendOffer() {
-    console.log('开始batchSendOffer');
-    liveUserList.value.forEach(async (item) => {
-      const socketId = item.id;
-      if (!offerSended.value.has(socketId) && socketId !== getSocketId()) {
-        let rtc = networkStore.getRtcMap(`${roomId.value}___${socketId}`);
-        if (!rtc) {
-          rtc = await startNewWebRtc({
-            receiver: socketId,
-            videoEl: localVideoRef.value,
-          });
-        }
-        // await addTrack();
-        console.log('执行sendOffer', {
-          sender: getSocketId(),
-          receiver: socketId,
-        });
-
-        sendOffer({ sender: getSocketId(), receiver: socketId, isSRS: false });
-      }
-    });
-  }
-
-  function getSocketId() {
-    return networkStore.wsMap.get(roomId.value!)?.socketIo?.id || '-1';
-  }
-
-  function initReceive() {
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (!instance?.socketIo) return;
-    // websocket连接成功
-    instance.socketIo.on(WsConnectStatusEnum.connect, () => {
-      prettierReceiveWebsocket(WsConnectStatusEnum.connect);
-      if (!instance) return;
-      instance.status = WsConnectStatusEnum.connect;
-      instance.update();
-      sendJoin();
-    });
-
-    // websocket连接断开
-    instance.socketIo.on(WsConnectStatusEnum.disconnect, () => {
-      prettierReceiveWebsocket(WsConnectStatusEnum.disconnect, instance);
-      if (!instance) return;
-      instance.status = WsConnectStatusEnum.disconnect;
-      instance.update();
-    });
-
-    // 收到offer
-    instance.socketIo.on(WsMsgTypeEnum.offer, (data: IOffer) => {
-      prettierReceiveWebsocket(
-        WsMsgTypeEnum.offer,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS) return;
-      if (!instance) return;
-      if (data.data.receiver === getSocketId()) {
-        console.log('收到offer,这个offer是发给我的');
-        nextTick(async () => {
-          const rtc = await startNewWebRtc({
-            receiver: data.data.sender,
-            videoEl: remoteVideoRef.value[data.data.sender],
-          });
-          if (rtc) {
-            await rtc.setRemoteDescription(data.data.sdp);
-            const sdp = await rtc.createAnswer();
-            await rtc.setLocalDescription(sdp!);
-            const answerData: IAnswer = {
-              sdp,
-              sender: getSocketId(),
-              receiver: data.data.sender,
-              live_room_id: data.data.live_room_id,
-            };
-            instance.send({
-              msgType: WsMsgTypeEnum.answer,
-              data: answerData,
-            });
-          }
-        });
-      } else {
-        console.log('收到offer,但是这个offer不是发给我的');
-      }
-    });
-
-    // 收到answer
-    instance.socketIo.on(WsMsgTypeEnum.answer, async (data: IOffer) => {
-      prettierReceiveWebsocket(
-        WsMsgTypeEnum.answer,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS) return;
-      if (!instance) 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
-    instance.socketIo.on(WsMsgTypeEnum.candidate, (data: ICandidate) => {
-      prettierReceiveWebsocket(
-        WsMsgTypeEnum.candidate,
-        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
-        data
-      );
-      if (isSRS) return;
-      if (!instance) return;
-      const rtc = networkStore.getRtcMap(`${roomId.value}___${data.socket_id}`);
-      if (!rtc) return;
-      if (data.socket_id !== getSocketId()) {
-        console.log('不是我发的candidate');
-        const candidate = new RTCIceCandidate({
-          sdpMid: data.data.sdpMid,
-          sdpMLineIndex: data.data.sdpMLineIndex,
-          candidate: data.data.candidate,
-        });
-        rtc.peerConnection
-          ?.addIceCandidate(candidate)
-          .then(() => {
-            console.log('candidate成功');
-          })
-          .catch((err) => {
-            console.error('candidate失败', err);
-          });
-      } else {
-        console.log('是我发的candidate');
-      }
-    });
-
-    // 管理员正在直播
-    instance.socketIo.on(WsMsgTypeEnum.roomLiveing, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.roomLiveing, data);
-    });
-
-    // 当前所有在线用户
-    instance.socketIo.on(WsMsgTypeEnum.liveUser, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.liveUser, data);
-    });
-
-    // 收到用户发送消息
-    instance.socketIo.on(WsMsgTypeEnum.message, (data: IMessage) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.message, data);
-      if (!instance) return;
-      damuList.value.push({
-        socket_id: data.socket_id,
-        msgType: DanmuMsgTypeEnum.danmu,
-        msg: data.data.msg,
-        userInfo: data.user_info,
-      });
-    });
-
-    // 用户加入房间完成
-    instance.socketIo.on(WsMsgTypeEnum.joined, (data: IJoin) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.joined, data);
-      handleHeartbeat(data.data.live_id || -1);
-      joined.value = true;
-      liveUserList.value.push({
-        id: `${getSocketId()}`,
-        userInfo: data.user_info,
-      });
-      if (isSRS) {
-        startNewWebRtc({ receiver: getSocketId() });
-      }
-    });
-
-    // 其他用户加入房间
-    instance.socketIo.on(WsMsgTypeEnum.otherJoin, (data: IOtherJoin) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.otherJoin, data);
-      liveUserList.value.push({
-        id: data.data.join_socket_id,
-        userInfo: data.data.liveRoom.user,
-      });
-      const danmu: IDanmu = {
-        msgType: DanmuMsgTypeEnum.otherJoin,
-        socket_id: data.data.join_socket_id,
-        userInfo: data.data.liveRoom.user,
-        msg: '',
-      };
-      damuList.value.push(danmu);
-      if (joined.value) {
-        startNewWebRtc({
-          receiver: data.data.join_socket_id,
-          videoEl: localVideoRef.value,
-        });
-      }
-    });
-
-    // 用户离开房间
-    instance.socketIo.on(WsMsgTypeEnum.leave, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.leave, data);
-      if (!instance) return;
-      instance.send({
-        msgType: WsMsgTypeEnum.leave,
-        data: { roomId: instance.roomId },
-      });
-    });
-
-    // 用户离开房间完成
-    instance.socketIo.on(WsMsgTypeEnum.leaved, (data) => {
-      prettierReceiveWebsocket(WsMsgTypeEnum.leaved, data);
-      networkStore.rtcMap
-        .get(`${roomId.value}___${data.socketId as string}`)
-        ?.close();
-      networkStore.removeRtc(`${roomId.value}___${data.socketId as string}`);
-      const res = liveUserList.value.filter(
-        (item) => item.id !== data.socketId
-      );
-      liveUserList.value = res;
-      damuList.value.push({
-        socket_id: data.socketId,
-        msgType: DanmuMsgTypeEnum.userLeaved,
-        msg: '',
-      });
-    });
-  }
-
   function roomNameIsOk() {
     if (!roomName.value.length) {
       window.$message.warning('请输入房间名!');
@@ -882,28 +237,7 @@ export function usePush({
     danmuStr.value = '';
   }
 
-  function initPush() {
-    localVideoRef.value?.addEventListener('loadstart', () => {
-      console.warn('视频流-loadstart');
-      const rtc = networkStore.getRtcMap(roomId.value);
-      if (!rtc) return;
-      rtc.update();
-    });
-
-    localVideoRef.value?.addEventListener('loadedmetadata', () => {
-      console.warn('视频流-loadedmetadata');
-      const rtc = networkStore.getRtcMap(roomId.value);
-      if (!rtc) return;
-      rtc.update();
-      if (isSRS) return;
-      if (joined.value) {
-        // batchSendOffer();
-      }
-    });
-  }
-
   return {
-    initPush,
     confirmRoomName,
     getSocketId,
     startLive,
@@ -923,6 +257,7 @@ export function usePush({
     roomName,
     damuList,
     liveUserList,
-    userSelectMediaList,
+    addTrack,
+    delTrack,
   };
 }

+ 195 - 45
src/hooks/use-ws.ts

@@ -5,14 +5,17 @@ import { fetchRtcV1Publish } from '@/api/srs';
 import { WEBSOCKET_URL } from '@/constant';
 import {
   DanmuMsgTypeEnum,
+  IAnswer,
   ICandidate,
   IDanmu,
   IHeartbeat,
   IJoin,
+  ILive,
   ILiveUser,
   IMessage,
   IOffer,
   IOtherJoin,
+  IUpdateJoinInfo,
   LiveRoomTypeEnum,
 } from '@/interface';
 import { WebRTCClass } from '@/network/webRTC';
@@ -22,7 +25,7 @@ import {
   WsMsgTypeEnum,
   prettierReceiveWebsocket,
 } from '@/network/webSocket';
-import { useAppStore } from '@/store/app';
+import { AppRootState, useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
 
@@ -34,7 +37,9 @@ export const useWs = () => {
   const liveUserList = ref<ILiveUser[]>([]);
   const roomId = ref('');
   const roomName = ref('');
-  const isLiving = ref(false);
+  const roomNoLive = ref(false);
+  const liveRoomInfo = ref<ILive>();
+  const isAnchor = ref(false);
   const joined = ref(false);
   const isSRS = ref(false);
   const localVideo = ref<HTMLVideoElement>(document.createElement('video'));
@@ -127,20 +132,138 @@ export const useWs = () => {
 
   const damuList = ref<IDanmu[]>([]);
 
+  watch(
+    () => appStore.muted,
+    (newVal) => {
+      console.log(newVal);
+    }
+  );
+
   watch(
     () => appStore.allTrack,
-    (trackInfo) => {
+    (newTrack, oldTrack) => {
       console.log('appStore.allTrack变了');
       const mixedStream = new MediaStream();
-      trackInfo.forEach((item) => {
+      newTrack.forEach((item) => {
         mixedStream.addTrack(item.track);
       });
+      // oldTrack.forEach((item) => {
+      //   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;
-      localVideo.value.srcObject = mixedStream;
     },
     { deep: true }
   );
 
+  watch(
+    () => currentMaxFramerate.value,
+    (newVal) => {
+      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('切换码率失败!');
+        }
+      });
+    }
+  );
+
+  watch(
+    () => currentResolutionRatio.value,
+    (newVal) => {
+      networkStore.rtcMap.forEach(async (rtc) => {
+        const res = await rtc.setResolutionRatio(newVal);
+        if (res === 1) {
+          window.$message.success('切换分辨率成功!');
+        } else {
+          window.$message.success('切换分辨率失败!');
+        }
+      });
+    }
+  );
+
+  function addTrack(addTrackInfo: AppRootState['allTrack'][0]) {
+    if (isAnchor.value) {
+      networkStore.rtcMap.forEach((rtc) => {
+        const sender = rtc.peerConnection
+          ?.getSenders()
+          .find((sender) => sender.track === addTrackInfo.track);
+        if (!sender) {
+          rtc.peerConnection?.addTrack(addTrackInfo.track, addTrackInfo.stream);
+        }
+      });
+    }
+    const mixedStream = new MediaStream();
+    appStore.allTrack.forEach((item) => {
+      mixedStream.addTrack(item.track);
+    });
+    console.log('addTrack后结果的音频轨', mixedStream.getAudioTracks());
+    console.log('addTrack后结果的视频轨', mixedStream.getVideoTracks());
+    localStream.value = mixedStream;
+    const data: IUpdateJoinInfo['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,
+    });
+  }
+  function delTrack(delTrackInfo: AppRootState['allTrack'][0]) {
+    if (isAnchor.value) {
+      networkStore.rtcMap.forEach((rtc) => {
+        const sender = rtc.peerConnection
+          ?.getSenders()
+          .find((sender) => sender.track === delTrackInfo.track);
+        if (sender) {
+          console.log('删除track', sender);
+          rtc.peerConnection?.removeTrack(sender);
+        }
+      });
+    }
+    const mixedStream = new MediaStream();
+    appStore.allTrack.forEach((item) => {
+      console.log('xxxx', item.track);
+      mixedStream.addTrack(item.track);
+    });
+    console.log('delTrack后结果的音频轨', mixedStream.getAudioTracks());
+    console.log('delTrack后结果的视频轨', mixedStream.getVideoTracks());
+    localStream.value = mixedStream;
+    const data: IUpdateJoinInfo['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,
+    });
+  }
+
   function getSocketId() {
     return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
   }
@@ -185,7 +308,6 @@ export const useWs = () => {
       });
     } else {
       const sdp = await rtc.createOffer();
-      console.log(sdp, 22);
       await rtc.setLocalDescription(sdp!);
       const res = await fetchRtcV1Publish({
         api: `/rtc/v1/publish/`,
@@ -245,7 +367,6 @@ export const useWs = () => {
   function handleNegotiationneeded(data: { roomId: string; isSRS: boolean }) {
     console.warn(`${data.roomId},开始监听pc的negotiationneeded`);
     const rtc = networkStore.getRtcMap(data.roomId);
-    console.log(rtc, 111);
     if (!rtc) return;
     rtc.peerConnection?.addEventListener('negotiationneeded', (event) => {
       console.warn(`${data.roomId},pc收到negotiationneeded`, event);
@@ -264,7 +385,6 @@ export const useWs = () => {
     receiver: string;
     videoEl: HTMLVideoElement;
   }) {
-    console.log('xxx', localStream.value);
     let rtc: WebRTCClass;
     if (isSRS.value) {
       console.warn('SRS开始new WebRTCClass', `${roomId.value}___${receiver!}`);
@@ -341,7 +461,7 @@ export const useWs = () => {
     });
 
     // 收到offer
-    ws.socketIo.on(WsMsgTypeEnum.offer, (data: IOffer) => {
+    ws.socketIo.on(WsMsgTypeEnum.offer, async (data: IOffer) => {
       prettierReceiveWebsocket(
         WsMsgTypeEnum.offer,
         `发送者:${data.data.sender},接收者:${data.data.receiver}`,
@@ -351,27 +471,31 @@ export const useWs = () => {
       if (!ws) return;
       if (data.data.receiver === getSocketId()) {
         console.log('收到offer,这个offer是发给我的');
-        // nextTick(async () => {
-        //   const rtc = await startNewWebRtc({
-        //     receiver: data.data.sender,
-        //     videoEl: remoteVideoRef.value[data.data.sender],
-        //   });
-        //   if (rtc) {
-        //     await rtc.setRemoteDescription(data.data.sdp);
-        //     const sdp = await rtc.createAnswer();
-        //     await rtc.setLocalDescription(sdp!);
-        //     const answerData: IAnswer = {
-        //       sdp,
-        //       sender: getSocketId(),
-        //       receiver: data.data.sender,
-        //       live_room_id: data.data.live_room_id,
-        //     };
-        //     ws.send({
-        //       msgType: WsMsgTypeEnum.answer,
-        //       data: answerData,
-        //     });
-        //   }
-        // });
+        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: IAnswer = {
+            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不是发给我的');
       }
@@ -433,6 +557,12 @@ export const useWs = () => {
       prettierReceiveWebsocket(WsMsgTypeEnum.roomLiveing, data);
     });
 
+    // 管理员不在直播
+    ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.roomNoLive, data);
+      roomNoLive.value = true;
+    });
+
     // 当前所有在线用户
     ws.socketIo.on(WsMsgTypeEnum.liveUser, (data) => {
       prettierReceiveWebsocket(WsMsgTypeEnum.liveUser, data);
@@ -459,8 +589,8 @@ export const useWs = () => {
         id: `${getSocketId()}`,
         userInfo: data.user_info,
       });
-      if (isSRS.value) {
-        startNewWebRtc({ receiver: getSocketId(), videoEl: localVideo.value });
+      if (!isAnchor.value) {
+        liveRoomInfo.value = data.data;
       }
     });
 
@@ -478,7 +608,6 @@ export const useWs = () => {
         msg: '',
       };
       damuList.value.push(danmu);
-      if (isSRS.value) return;
       if (joined.value) {
         startNewWebRtc({
           receiver: data.data.join_socket_id,
@@ -517,28 +646,49 @@ export const useWs = () => {
   }
 
   function initWs(data: {
+    isAnchor: boolean;
     roomId: string;
     isSRS: boolean;
-    currentResolutionRatio: number;
-    currentMaxFramerate: number;
-    currentMaxBitrate: number;
-    localVideo: HTMLVideoElement;
+    currentResolutionRatio?: number;
+    currentMaxFramerate?: number;
+    currentMaxBitrate?: number;
   }) {
     roomId.value = data.roomId;
-    localVideo.value = data.localVideo;
-    currentMaxBitrate.value = data.currentMaxBitrate;
-    currentMaxFramerate.value = data.currentMaxFramerate;
-    currentResolutionRatio.value = data.currentResolutionRatio;
+    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;
+    }
     isSRS.value = data.isSRS;
-    console.warn('开始new WebSocketClass', data);
     new WebSocketClass({
       roomId: roomId.value,
       url: WEBSOCKET_URL,
-      isAnchor: true,
+      isAnchor: data.isAnchor,
     });
-    isLiving.value = true;
     initReceive();
   }
 
-  return { initWs };
+  return {
+    getSocketId,
+    initWs,
+    addTrack,
+    delTrack,
+    liveRoomInfo,
+    roomNoLive,
+    heartbeatTimer,
+    localStream,
+    liveUserList,
+    damuList,
+    maxBitrate,
+    maxFramerate,
+    resolutionRatio,
+    currentMaxFramerate,
+    currentMaxBitrate,
+    currentResolutionRatio,
+  };
 };

+ 64 - 39
src/network/webRTC.ts

@@ -1,7 +1,8 @@
+import { getRandomString } from 'billd-utils';
 import browserTool from 'browser-tool';
-import { NODE_ENV } from 'script/constant';
 
-import { ICandidate } from '@/interface';
+import { ICandidate, MediaTypeEnum } from '@/interface';
+import { AppRootState, useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 
 import { WsMsgTypeEnum } from './webSocket';
@@ -69,11 +70,6 @@ export class WebRTCClass {
     console.warn('new webrtc参数:', data);
     this.browser = browserTool();
     this.createPeerConnection();
-    // setInterval(() => {
-    //   const getAudioTracks = this.localStream?.getAudioTracks().length;
-    //   const getVideoTracks = this.localStream?.getVideoTracks().length;
-    //   console.log(getAudioTracks, getVideoTracks, '----');
-    // }, 1000);
   }
 
   prettierLog = (msg: string, type?: 'log' | 'warn' | 'error', ...args) => {
@@ -87,33 +83,62 @@ export class WebRTCClass {
 
   addTrack = (stream: MediaStream, isCb?: boolean) => {
     console.log('开始addTrack,是否是pc的track回调', isCb);
-    console.log('收到新stream', stream);
-    console.log('收到新stream的视频轨', stream.getVideoTracks());
-    console.log('收到新stream的音频轨', stream.getAudioTracks());
-    console.log('原本旧stream的视频轨', this.localStream?.getVideoTracks());
-    console.log('原本旧stream的音频轨', this.localStream?.getAudioTracks());
-    const mixedStream = new MediaStream();
-    this.localStream
-      ?.getVideoTracks()
-      .forEach((track) => mixedStream.addTrack(track));
-    this.localStream
-      ?.getAudioTracks()
-      .forEach((track) => mixedStream.addTrack(track));
-    stream.getVideoTracks().forEach((track) => mixedStream.addTrack(track));
-    stream.getAudioTracks().forEach((track) => mixedStream.addTrack(track));
-    console.log('混流stream', stream);
-    console.log('混流stream的视频流', mixedStream.getVideoTracks());
-    console.log('混流stream的音频流', mixedStream.getAudioTracks());
-    // const sender = this.peerConnection
-    //   ?.getSenders()
-    //   .find((sender) => sender.track !== track);
-    // console.log('getSenders', this.peerConnection?.getSenders());
-    // console.log('sender', sender);
-    if (NODE_ENV === 'development') {
-      this.videoEl.controls = true;
-    }
-    this.videoEl.srcObject = mixedStream;
-    this.localStream = mixedStream;
+    console.log('收到新track', stream);
+    console.log('收到新track的视频轨', stream.getVideoTracks());
+    console.log('收到新track的音频轨', stream.getAudioTracks());
+    console.log('原本旧track的视频轨', this.localStream?.getVideoTracks());
+    console.log('原本旧track的音频轨', this.localStream?.getAudioTracks());
+
+    const appStore = useAppStore();
+    const allTrack: AppRootState['allTrack'] = [];
+
+    this.localStream?.getVideoTracks().forEach((track) => {
+      allTrack.push({
+        id: getRandomString(8),
+        track,
+        stream,
+        audio: 2,
+        video: 1,
+        type: MediaTypeEnum.screen,
+        mediaName: '',
+      });
+    });
+    this.localStream?.getAudioTracks().forEach((track) => {
+      allTrack.push({
+        id: getRandomString(8),
+        track,
+        stream,
+        audio: 1,
+        video: 2,
+        type: MediaTypeEnum.microphone,
+        mediaName: '',
+      });
+    });
+    stream.getVideoTracks().forEach((track) =>
+      allTrack.push({
+        id: getRandomString(8),
+        track,
+        stream,
+        audio: 2,
+        video: 1,
+        type: MediaTypeEnum.screen,
+        mediaName: '',
+      })
+    );
+    stream.getAudioTracks().forEach((track) =>
+      allTrack.push({
+        id: getRandomString(8),
+        track,
+        stream,
+        audio: 1,
+        video: 2,
+        type: MediaTypeEnum.microphone,
+        mediaName: '',
+      })
+    );
+    appStore.setAllTrack(allTrack);
+    this.localStream = stream;
+
     if (this.maxBitrate !== -1) {
       this.setMaxBitrate(this.maxBitrate);
     }
@@ -265,11 +290,11 @@ export class WebRTCClass {
     // 废弃:https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream
     console.warn(`${this.roomId},开始监听pc的addstream`);
     this.peerConnection.addEventListener('addstream', (event: any) => {
-      console.warn(`${this.roomId},pc收到addstream事件`, event);
-      console.log('addstream事件的stream', event.stream);
-      console.log('addstream事件的视频轨', event.stream.getVideoTracks());
-      console.log('addstream事件的音频轨', event.stream.getAudioTracks());
-      this.addTrack(event.stream, true);
+      // console.warn(`${this.roomId},pc收到addstream事件`, event);
+      // console.log('addstream事件的stream', event.stream);
+      // console.log('addstream事件的视频轨', event.stream.getVideoTracks());
+      // console.log('addstream事件的音频轨', event.stream.getAudioTracks());
+      // this.addTrack(event.stream, true);
     });
 
     console.warn(`${this.roomId},开始监听pc的track`);

+ 1 - 0
src/network/webSocket.ts

@@ -70,6 +70,7 @@ export class WebSocketClass {
       alert('当前环境不支持WebSocket!');
       return;
     }
+    console.warn('开始new WebSocketClass', data);
     this.roomId = data.roomId;
     this.isAnchor = data.isAnchor;
     this.url = data.url;

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

@@ -41,7 +41,9 @@ export const useAppStore = defineStore('app', {
     isOnlyAudio() {
       let videoTracks = 0;
       this.allTrack.forEach((item) => {
-        videoTracks += item.stream.getVideoTracks().length;
+        if (item.video === 1) {
+          videoTracks += 1;
+        }
       });
       return videoTracks <= 0;
     },

+ 21 - 32
src/views/pull/index.vue

@@ -11,14 +11,16 @@
             <div
               class="avatar"
               :style="{
-                backgroundImage: `url(${userAvatar})`,
+                backgroundImage: `url(${liveRoomInfo?.user?.avatar})`,
               }"
             ></div>
             <div class="detail">
-              <div class="top">{{ userName || '-' }}</div>
+              <div class="top">{{ liveRoomInfo?.user?.username }}</div>
               <div class="bottom">
-                <span>{{ roomName }}</span>
-                <span>socketId:{{ getSocketId() }}</span>
+                <span>{{ liveRoomInfo?.live_room?.name }}</span>
+                <span v-if="NODE_ENV === 'development'">
+                  socketId:{{ getSocketId() }}
+                </span>
               </div>
             </div>
           </div>
@@ -35,7 +37,8 @@
               class="cover"
               :style="{
                 backgroundImage: `url(${
-                  coverImg || currentLiveRoom?.user?.avatar
+                  liveRoomInfo?.live_room?.cover_img ||
+                  liveRoomInfo?.user?.avatar
                 })`,
               }"
             ></div>
@@ -113,7 +116,9 @@
             @click="handleRecharge"
           >
             <div class="ico wallet"></div>
-            <div class="name">余额:{{ balance }}</div>
+            <div class="name">
+              余额:{{ userStore.userInfo?.wallet?.balance }}
+            </div>
             <div class="price">立即充值</div>
           </div>
         </div>
@@ -205,6 +210,7 @@
 </template>
 
 <script lang="ts" setup>
+import { NODE_ENV } from 'script/constant';
 import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
@@ -242,28 +248,15 @@ const {
   getSocketId,
   keydownDanmu,
   sendDanmu,
-  batchSendOffer,
-  startGetUserMedia,
-  startGetDisplayMedia,
-  addTrack,
   addVideo,
   autoplayVal,
   videoLoading,
-  balance,
-  roomName,
-  userName,
-  userAvatar,
-  currentLiveRoom,
-  hlsurl,
-  coverImg,
   roomNoLive,
   damuList,
-  giftList,
   liveUserList,
-  danmuStr,
-  localStream,
-  sender,
   sidebarList,
+  danmuStr,
+  liveRoomInfo,
 } = usePull({
   localVideoRef,
   canvasRef,
@@ -297,16 +290,11 @@ function handleRecharge() {
 function handleJoin() {
   showJoin.value = !showJoin.value;
   nextTick(async () => {
-    await startGetUserMedia();
+    // await startGetUserMedia();
     addVideo();
   });
 }
 
-onUnmounted(() => {
-  closeWs();
-  closeRtc();
-});
-
 watch(
   () => damuList.value.length,
   () => {
@@ -318,6 +306,11 @@ watch(
   }
 );
 
+onUnmounted(() => {
+  closeWs();
+  closeRtc();
+});
+
 onMounted(() => {
   getGoodsList();
   if (
@@ -336,11 +329,7 @@ onMounted(() => {
         topRef.value.getBoundingClientRect().height);
     containerRef.value.style.height = `${res}px`;
   }
-  if (route.query.liveType === liveTypeEnum.srsHlsPull) {
-    initPull();
-  } else {
-    initPull();
-  }
+  initPull();
 });
 </script>
 

+ 73 - 54
src/views/push/index.vue

@@ -22,6 +22,7 @@
             x5-video-orientation="portraint"
             muted
             :controls="NODE_ENV === 'development' ? true : false"
+            @contextmenu.prevent
           ></video>
           <div
             v-if="!appStore.allTrack || appStore.allTrack.length <= 0"
@@ -150,7 +151,7 @@
             </n-button>
             <n-button
               v-else
-              type="info"
+              type="error"
               size="small"
               @click="endLive"
             >
@@ -171,12 +172,11 @@
             class="item"
           >
             <span class="name">
-              ({{ item.audio === 1 ? '音频' : ''
-              }}{{ item.video === 1 ? '视频' : '' }}){{ item.mediaName }}
+              ({{ item.audio === 1 ? '音频' : '视频' }}){{ item.mediaName }}
             </span>
             <div
               class="del"
-              @click="delTrack(item)"
+              @click="handleDelTrack(item)"
             >
               x
             </div>
@@ -186,9 +186,9 @@
           <n-button
             size="small"
             type="primary"
-            @click="handleAddMedia"
+            @click="showSelectMediaModalCpt = true"
           >
-            添加音频
+            添加素材
           </n-button>
         </div>
       </div>
@@ -246,11 +246,18 @@
       </div>
     </div>
 
+    <SelectMediaModalCpt
+      v-if="showSelectMediaModalCpt"
+      :all-media-type-list="allMediaTypeList"
+      @close="showSelectMediaModalCpt = false"
+      @ok="selectMediaOk"
+    ></SelectMediaModalCpt>
+
     <MediaModalCpt
       v-if="showMediaModalCpt"
       :media-type="currentMediaType"
       @close="showMediaModalCpt = false"
-      @ok="selectMediaOk"
+      @ok="addMediaOk"
     ></MediaModalCpt>
   </div>
 </template>
@@ -267,11 +274,13 @@ import { AppRootState, useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';
 
 import MediaModalCpt from './mediaModal/index.vue';
+import SelectMediaModalCpt from './selectMediaModal/index.vue';
 
 const route = useRoute();
 const userStore = useUserStore();
 const appStore = useAppStore();
 const currentMediaType = ref(MediaTypeEnum.camera);
+const showSelectMediaModalCpt = ref(false);
 const showMediaModalCpt = ref(false);
 const liveType = route.query.liveType;
 const topRef = ref<HTMLDivElement>();
@@ -282,7 +291,6 @@ const localVideoRef = ref<HTMLVideoElement>();
 const remoteVideoRef = ref<HTMLVideoElement[]>([]);
 
 const {
-  initPush,
   confirmRoomName,
   getSocketId,
   startLive,
@@ -302,7 +310,8 @@ const {
   roomName,
   damuList,
   liveUserList,
-  userSelectMediaList,
+  addTrack,
+  delTrack,
 } = usePush({
   localVideoRef,
   remoteVideoRef,
@@ -321,19 +330,21 @@ watch(
 );
 
 onMounted(() => {
-  localVideoRef.value!.oncontextmenu = (e) => {
-    e.preventDefault();
-  };
   if (topRef.value && bottomRef.value && containerRef.value) {
     const res =
       bottomRef.value.getBoundingClientRect().top -
       topRef.value.getBoundingClientRect().top;
     containerRef.value.style.height = `${res}px`;
   }
-  initPush();
 });
 
-async function selectMediaOk(val: {
+function selectMediaOk(val: MediaTypeEnum) {
+  showMediaModalCpt.value = true;
+  showSelectMediaModalCpt.value = false;
+  currentMediaType.value = val;
+}
+
+async function addMediaOk(val: {
   type: MediaTypeEnum;
   deviceId: string;
   mediaName: string;
@@ -349,18 +360,33 @@ async function selectMediaOk(val: {
       audio: true,
     });
     const audio = event.getAudioTracks();
-    appStore.setAllTrack([
-      ...appStore.allTrack,
-      {
+    const videoTrack = {
+      id: getRandomString(8),
+      audio: 2,
+      video: 1,
+      mediaName: val.mediaName,
+      type: MediaTypeEnum.screen,
+      track: event.getVideoTracks()[0],
+      stream: event,
+    };
+    if (audio.length) {
+      const autioTrack = {
         id: getRandomString(8),
-        audio: audio.length > 0 ? 1 : 2,
-        video: 1,
+        audio: 1,
+        video: 2,
         mediaName: val.mediaName,
         type: MediaTypeEnum.screen,
-        track: event.getVideoTracks()[0],
+        track: event.getAudioTracks()[0],
         stream: event,
-      },
-    ]);
+      };
+      appStore.setAllTrack([...appStore.allTrack, videoTrack, autioTrack]);
+      addTrack(videoTrack);
+      addTrack(autioTrack);
+    } else {
+      appStore.setAllTrack([...appStore.allTrack, videoTrack]);
+      addTrack(videoTrack);
+    }
+
     console.log('获取窗口成功');
   } else if (val.type === MediaTypeEnum.camera) {
     const event = await navigator.mediaDevices.getUserMedia({
@@ -371,53 +397,46 @@ async function selectMediaOk(val: {
       },
       audio: false,
     });
-    appStore.setAllTrack([
-      ...appStore.allTrack,
-      {
-        id: getRandomString(8),
-        audio: 2,
-        video: 1,
-        mediaName: val.mediaName,
-        type: MediaTypeEnum.camera,
-        track: event.getVideoTracks()[0],
-        stream: event,
-      },
-    ]);
+    const track = {
+      id: getRandomString(8),
+      audio: 2,
+      video: 1,
+      mediaName: val.mediaName,
+      type: MediaTypeEnum.camera,
+      track: event.getVideoTracks()[0],
+      stream: event,
+    };
+    appStore.setAllTrack([...appStore.allTrack, track]);
+    addTrack(track);
     console.log('获取摄像头成功');
   } else if (val.type === MediaTypeEnum.microphone) {
     const event = await navigator.mediaDevices.getUserMedia({
       video: false,
       audio: { deviceId: val.deviceId },
     });
-    appStore.setAllTrack([
-      ...appStore.allTrack,
-      {
-        id: getRandomString(8),
-        audio: 1,
-        video: 2,
-        mediaName: val.mediaName,
-        type: MediaTypeEnum.microphone,
-        track: event.getAudioTracks()[0],
-        stream: event,
-      },
-    ]);
+    const track = {
+      id: getRandomString(8),
+      audio: 1,
+      video: 2,
+      mediaName: val.mediaName,
+      type: MediaTypeEnum.microphone,
+      track: event.getAudioTracks()[0],
+      stream: event,
+    };
+    appStore.setAllTrack([...appStore.allTrack, track]);
+    addTrack(track);
     console.log('获取麦克风成功');
   }
 }
 
-function delTrack(item: AppRootState['allTrack'][0]) {
-  console.log(item);
+function handleDelTrack(item: AppRootState['allTrack'][0]) {
+  console.log('handleDelTrack', item);
   const res = appStore.allTrack.filter((iten) => iten.id !== item.id);
   appStore.setAllTrack(res);
-}
-
-function handleAddMedia() {
-  currentMediaType.value = MediaTypeEnum.microphone;
-  showMediaModalCpt.value = true;
+  delTrack(item);
 }
 
 function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
-  console.log(item);
   currentMediaType.value = item.type;
   showMediaModalCpt.value = true;
 }

+ 54 - 0
src/views/push/selectMediaModal/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="select-media-wrap">
+    <Modal
+      title="选择直播素材"
+      :mask-closable="false"
+      @close="emits('close')"
+    >
+      <div class="container">
+        <n-space justify="center">
+          <n-button
+            v-for="(item, index) in allMediaTypeList"
+            :key="index"
+            class="item"
+            @click="emits('ok', item.type)"
+          >
+            {{ item.txt }}
+          </n-button>
+        </n-space>
+      </div>
+      <template #footer></template>
+    </Modal>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineEmits, defineProps, onMounted, withDefaults } from 'vue';
+
+import { MediaTypeEnum } from '@/interface';
+
+const props = withDefaults(
+  defineProps<{
+    allMediaTypeList: {
+      [index: string]: {
+        type: MediaTypeEnum;
+        txt: string;
+      };
+    };
+  }>(),
+  {}
+);
+const emits = defineEmits(['close', 'ok']);
+
+onMounted(() => {});
+</script>
+
+<style lang="scss" scoped>
+.select-media-wrap {
+  text-align: initial;
+
+  .container {
+    padding-top: 10px;
+  }
+}
+</style>