소스 검색

feat: 阶段保存

shuisheng 2 년 전
부모
커밋
3e0aa94e85
9개의 변경된 파일668개의 추가작업 그리고 693개의 파일을 삭제
  1. 1 1
      README.md
  2. 0 1
      package.json
  3. 26 483
      pnpm-lock.yaml
  4. 10 105
      src/hooks/use-pull.ts
  5. 55 51
      src/hooks/use-push.ts
  6. 544 0
      src/hooks/use-ws.ts
  7. 0 2
      src/showBilldVersion.ts
  8. 1 0
      src/store/app/index.ts
  9. 31 50
      src/views/push/index.vue

+ 1 - 1
README.md

@@ -132,7 +132,7 @@ pnpm i
 > 更新 billd 相关依赖:
 
 ```bash
-pnpm i billd-utils@latest billd-scss@latest billd-html-webpack-plugin@latest billd-deploy@latest
+pnpm i billd-utils@latest billd-scss@latest billd-html-webpack-plugin@latest
 ```
 
 - 运行

+ 0 - 1
package.json

@@ -34,7 +34,6 @@
   "dependencies": {
     "@vicons/ionicons5": "^0.12.0",
     "axios": "^1.2.1",
-    "billd-deploy": "^1.0.16",
     "billd-html-webpack-plugin": "^1.0.1",
     "billd-scss": "^0.0.7",
     "billd-utils": "^0.0.12",

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 26 - 483
pnpm-lock.yaml


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

@@ -1,4 +1,5 @@
 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';
 
@@ -18,7 +19,6 @@ import {
   IOtherJoin,
   IUpdateJoinInfo,
   LiveRoomTypeEnum,
-  MediaTypeEnum,
   liveTypeEnum,
 } from '@/interface';
 import { WebRTCClass, audioElArr } from '@/network/webRTC';
@@ -52,11 +52,13 @@ export function usePull({
   videoEl.muted = true;
   videoEl.playsInline = true;
   videoEl.autoplay = true;
-  // videoEl.controls = true; // 调试用
   videoEl.setAttribute('webkit-playsinline', 'true');
-  // videoEl.oncontextmenu = (e) => {
-  //   e.preventDefault();
-  // };
+  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);
@@ -101,29 +103,6 @@ export function usePull({
   const hooksRtcMap = ref(new Set());
   const sender = ref();
 
-  const allMediaTypeList = {
-    [MediaTypeEnum.camera]: {
-      type: MediaTypeEnum.camera,
-      txt: '摄像头',
-    },
-    [MediaTypeEnum.screen]: {
-      type: MediaTypeEnum.screen,
-      txt: '窗口',
-    },
-  };
-
-  const currMediaTypeList = ref<
-    {
-      type: MediaTypeEnum;
-      txt: String;
-    }[]
-  >([]);
-
-  const currMediaType = ref<{
-    type: MediaTypeEnum;
-    txt: String;
-  }>();
-
   const { flvVideoEl, startFlvPlay } = useFlvPlay();
   const { hlsVideoEl, startHlsPlay } = useHlsPlay();
 
@@ -131,44 +110,6 @@ export function usePull({
     clearInterval(heartbeatTimer.value);
   });
 
-  /** 摄像头 */
-  async function startGetUserMedia() {
-    if (!localStream.value) {
-      // WARN navigator.mediaDevices在localhost和https才能用,http://192.168.1.103:8000局域网用不了
-      const event = await navigator.mediaDevices.getUserMedia({
-        video: true,
-        audio: true,
-      });
-      const audio = event.getAudioTracks();
-      const video = event.getVideoTracks();
-      track.audio = audio.length ? 1 : 2;
-      track.video = video.length ? 1 : 2;
-      console.log('getUserMedia成功', event, audio, video);
-      currMediaType.value = allMediaTypeList[MediaTypeEnum.camera];
-      currMediaTypeList.value.push(allMediaTypeList[MediaTypeEnum.camera]);
-      localStream.value = event;
-    }
-  }
-
-  /** 窗口 */
-  async function startGetDisplayMedia() {
-    if (!localStream.value) {
-      // WARN navigator.mediaDevices.getDisplayMedia在localhost和https才能用,http://192.168.1.103:8000局域网用不了
-      const event = await navigator.mediaDevices.getDisplayMedia({
-        video: true,
-        audio: true,
-      });
-      const audio = event.getAudioTracks();
-      const video = event.getVideoTracks();
-      track.audio = audio.length ? 1 : 2;
-      track.video = video.length ? 1 : 2;
-      console.log('getDisplayMedia成功', event, audio, video);
-      currMediaType.value = allMediaTypeList[MediaTypeEnum.screen];
-      currMediaTypeList.value.push(allMediaTypeList[MediaTypeEnum.screen]);
-      localStream.value = event;
-    }
-  }
-
   watch(
     () => appStore.allTrack,
     () => {
@@ -191,7 +132,6 @@ export function usePull({
   watch(
     () => appStore.muted,
     (val) => {
-      console.log(val, audioElArr, 2222);
       remoteVideoRef.value.muted = val;
       audioElArr.forEach((el) => {
         console.log(el, el.muted);
@@ -235,7 +175,7 @@ export function usePull({
       isAnchor: false,
     });
     ws.update();
-    initReceive();
+    initWsReceive();
 
     remoteVideoRef.value?.addEventListener('loadstart', () => {
       console.warn('视频流-loadstart');
@@ -301,20 +241,6 @@ export function usePull({
     });
   }
 
-  function addTrack() {
-    if (!localStream.value) return;
-    liveUserList.value.forEach((item) => {
-      console.log(item, item.id, 1111113223431);
-      if (item.id !== getSocketId()) {
-        localStream.value.getTracks().forEach((track) => {
-          const rtc = networkStore.getRtcMap(`${roomId.value}___${item.id}`);
-          console.log('pull-addTrack', `${roomId.value}___${item.id}`);
-          rtc?.addTrack(localStream.value);
-        });
-      }
-    });
-  }
-
   async function sendOffer({
     sender,
     receiver,
@@ -343,22 +269,6 @@ export function usePull({
     });
   }
 
-  async function batchSendOffer(socketId: string) {
-    console.log('batchSendOffer', socketId);
-    await nextTick(async () => {
-      if (!offerSended.value.has(socketId) && socketId !== getSocketId()) {
-        hooksRtcMap.value.add(await startNewWebRtc({ receiver: socketId }));
-        await addTrack();
-        console.log('执行sendOffer', {
-          sender: getSocketId(),
-          receiver: socketId,
-        });
-        sendOffer({ sender: getSocketId(), receiver: socketId });
-        // offerSended.value.add(socketId);
-      }
-    });
-  }
-
   function addVideo() {
     sidebarList.value.push({ socketId: getSocketId() });
     console.log(sidebarList.value, 111, getSocketId());
@@ -377,7 +287,6 @@ export function usePull({
               videoEl: localVideoRef.value[socketId],
             })
           );
-          await addTrack();
           console.log('执行sendOffer', {
             sender: getSocketId(),
             receiver: socketId,
@@ -474,7 +383,7 @@ export function usePull({
     danmuStr.value = '';
   }
 
-  function initReceive() {
+  function initWsReceive() {
     const instance = networkStore.wsMap.get(roomId.value);
     if (!instance?.socketIo) return;
     // websocket连接成功
@@ -731,7 +640,6 @@ export function usePull({
         id: data.data.join_socket_id,
         userInfo: data.data.liveRoom.user,
       });
-      batchSendOffer(data.data.join_socket_id);
     });
 
     // 用户离开房间
@@ -750,6 +658,7 @@ export function usePull({
       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
@@ -772,10 +681,6 @@ export function usePull({
     getSocketId,
     keydownDanmu,
     sendDanmu,
-    batchSendOffer,
-    startGetUserMedia,
-    startGetDisplayMedia,
-    addTrack,
     addVideo,
     autoplayVal,
     videoLoading,

+ 55 - 51
src/hooks/use-push.ts

@@ -1,13 +1,5 @@
 import { getRandomString, windowReload } from 'billd-utils';
-import {
-  Ref,
-  nextTick,
-  onMounted,
-  onUnmounted,
-  reactive,
-  ref,
-  watch,
-} from 'vue';
+import { Ref, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 
 import { fetchRtcV1Publish } from '@/api/srs';
@@ -44,15 +36,16 @@ import { useUserStore } from '@/store/user';
 
 import { loginTip } from './use-login';
 import { useTip } from './use-tip';
+import { useWs } from './use-ws';
 
 export function usePush({
   localVideoRef,
   remoteVideoRef,
   isSRS,
 }: {
-  localVideoRef: Ref<HTMLVideoElement | undefined>;
+  localVideoRef: Ref<HTMLVideoElement>;
   remoteVideoRef: Ref<HTMLVideoElement[]>;
-  isSRS?: boolean;
+  isSRS: boolean;
 }) {
   const route = useRoute();
   const router = useRouter();
@@ -69,6 +62,7 @@ export function usePush({
   const offerSended = ref(new Set());
   const webRTC = ref<WebRTCClass>();
   const srsSdp = ref('');
+  const { initWs } = useWs();
   const maxBitrate = ref([
     {
       label: '1000',
@@ -157,12 +151,7 @@ export function usePush({
   ]);
   const currentMaxFramerate = ref(maxFramerate.value[1].value);
 
-  const track = reactive({
-    audio: 1,
-    video: 1,
-  });
   const streamurl = ref('');
-
   const damuList = ref<IDanmu[]>([]);
   const liveUserList = ref<ILiveUser[]>([]);
 
@@ -191,39 +180,44 @@ export function usePush({
   >([]);
 
   watch(
-    () => localStream.value,
-    (newStream) => {
-      console.log('localStream变了');
-      console.log('新的视频流', newStream?.getVideoTracks());
-      console.log('新的音频流', newStream?.getAudioTracks());
-      if (!localVideoRef.value || !newStream) return;
-      localVideoRef.value.srcObject = newStream;
-      if (isSRS) {
-        if (isLiving.value) {
-          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);
-            }
-          });
-        });
-      }
+    () => 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 }
   );
@@ -382,6 +376,15 @@ export function usePush({
       window.$message.warning('请选择一个素材!');
       return;
     }
+    initWs({
+      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,
@@ -435,6 +438,7 @@ export function usePush({
     receiver: string;
     videoEl?: HTMLVideoElement;
   }) {
+    console.log('xxx', localStream.value);
     let rtc: WebRTCClass;
     if (isSRS) {
       console.warn('SRS开始new WebRTCClass', `${roomId.value}___${receiver!}`);
@@ -463,7 +467,7 @@ export function usePush({
       });
       webRTC.value = rtc;
     } else {
-      console.warn('开始new WebRTCClass', `${roomId.value}___${receiver!}`);
+      console.warn('-开始new WebRTCClass', `${roomId.value}___${receiver!}`);
       rtc = new WebRTCClass({
         maxBitrate: currentMaxBitrate.value,
         maxFramerate: currentMaxFramerate.value,
@@ -789,7 +793,6 @@ export function usePush({
         msg: '',
       };
       damuList.value.push(danmu);
-      if (isSRS) return;
       if (joined.value) {
         startNewWebRtc({
           receiver: data.data.join_socket_id,
@@ -814,6 +817,7 @@ export function usePush({
       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
       );

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

@@ -0,0 +1,544 @@
+import { getRandomString } from 'billd-utils';
+import { ref, watch } from 'vue';
+
+import { fetchRtcV1Publish } from '@/api/srs';
+import { WEBSOCKET_URL } from '@/constant';
+import {
+  DanmuMsgTypeEnum,
+  ICandidate,
+  IDanmu,
+  IHeartbeat,
+  IJoin,
+  ILiveUser,
+  IMessage,
+  IOffer,
+  IOtherJoin,
+  LiveRoomTypeEnum,
+} from '@/interface';
+import { WebRTCClass } 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';
+
+export const useWs = () => {
+  const appStore = useAppStore();
+  const userStore = useUserStore();
+  const networkStore = useNetworkStore();
+  const heartbeatTimer = ref();
+  const liveUserList = ref<ILiveUser[]>([]);
+  const roomId = ref('');
+  const roomName = ref('');
+  const isLiving = ref(false);
+  const joined = ref(false);
+  const isSRS = ref(false);
+  const localVideo = ref<HTMLVideoElement>(document.createElement('video'));
+  const localStream = ref<MediaStream>();
+  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 damuList = ref<IDanmu[]>([]);
+
+  watch(
+    () => appStore.allTrack,
+    (trackInfo) => {
+      console.log('appStore.allTrack变了');
+      const mixedStream = new MediaStream();
+      trackInfo.forEach((item) => {
+        mixedStream.addTrack(item.track);
+      });
+      localStream.value = mixedStream;
+      localVideo.value.srcObject = mixedStream;
+    },
+    { deep: true }
+  );
+
+  function getSocketId() {
+    return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
+  }
+
+  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);
+  }
+  async function sendOffer({
+    sender,
+    receiver,
+  }: {
+    sender: string;
+    receiver: string;
+  }) {
+    console.log('开始sendOffer');
+    const ws = networkStore.wsMap.get(roomId.value);
+    if (!ws) return;
+    const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
+    if (!rtc) return;
+    if (!isSRS.value) {
+      const sdp = await rtc.createOffer();
+      await rtc.setLocalDescription(sdp!);
+      ws.send({
+        msgType: WsMsgTypeEnum.offer,
+        data: {
+          sdp,
+          sender,
+          receiver,
+          live_room_id: roomId.value,
+        },
+      });
+    } else {
+      const sdp = await rtc.createOffer();
+      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),
+      });
+      await rtc.setRemoteDescription(
+        new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
+      );
+    }
+  }
+
+  function handleCoverImg() {
+    const canvas = document.createElement('canvas');
+    const { width, height } = localVideo.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(localVideo.value, 0, 0, coverWidth, coverHeight);
+    // webp比png的体积小非常多!因此coverWidth就可以不用压缩太夸张
+    const dataURL = canvas.toDataURL('image/webp');
+    return dataURL;
+  }
+
+  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.value
+          ? 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,
+    });
+  }
+
+  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);
+      sendOffer({
+        sender: getSocketId(),
+        receiver: rtc.receiver,
+      });
+    });
+  }
+
+  /** 原生的webrtc时,receiver必传 */
+  function startNewWebRtc({
+    receiver,
+    videoEl,
+  }: {
+    receiver: string;
+    videoEl: HTMLVideoElement;
+  }) {
+    console.log('xxx', localStream.value);
+    let rtc: WebRTCClass;
+    if (isSRS.value) {
+      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!);
+      });
+    } 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!);
+      });
+    }
+    return rtc;
+  }
+
+  function initReceive() {
+    const ws = networkStore.wsMap.get(roomId.value);
+    if (!ws?.socketIo) return;
+    // websocket连接成功
+    ws.socketIo.on(WsConnectStatusEnum.connect, () => {
+      prettierReceiveWebsocket(WsConnectStatusEnum.connect);
+      if (!ws) return;
+      ws.status = WsConnectStatusEnum.connect;
+      ws.update();
+      sendJoin();
+    });
+
+    // websocket连接断开
+    ws.socketIo.on(WsConnectStatusEnum.disconnect, () => {
+      prettierReceiveWebsocket(WsConnectStatusEnum.disconnect, ws);
+      if (!ws) return;
+      ws.status = WsConnectStatusEnum.disconnect;
+      ws.update();
+    });
+
+    // 收到offer
+    ws.socketIo.on(WsMsgTypeEnum.offer, (data: IOffer) => {
+      prettierReceiveWebsocket(
+        WsMsgTypeEnum.offer,
+        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
+        data
+      );
+      if (isSRS.value) return;
+      if (!ws) return;
+      if (data.data.receiver === getSocketId()) {
+        console.log('收到offer,这个offer是发给我的');
+        // 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,
+        //     });
+        //   }
+        // });
+      } else {
+        console.log('收到offer,但是这个offer不是发给我的');
+      }
+    });
+
+    // 收到answer
+    ws.socketIo.on(WsMsgTypeEnum.answer, async (data: IOffer) => {
+      prettierReceiveWebsocket(
+        WsMsgTypeEnum.answer,
+        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
+        data
+      );
+      if (isSRS.value) return;
+      if (!ws) return;
+      const rtc = networkStore.getRtcMap(`${roomId.value}___${data.socket_id}`);
+      if (!rtc) return;
+      rtc.update();
+      if (data.data.receiver === getSocketId()) {
+        console.log('收到answer,这个answer是发给我的');
+        await rtc.setRemoteDescription(data.data.sdp);
+      } else {
+        console.log('收到answer,但这个answer不是发给我的');
+      }
+    });
+
+    // 收到candidate
+    ws.socketIo.on(WsMsgTypeEnum.candidate, (data: ICandidate) => {
+      prettierReceiveWebsocket(
+        WsMsgTypeEnum.candidate,
+        `发送者:${data.data.sender},接收者:${data.data.receiver}`,
+        data
+      );
+      if (isSRS.value) return;
+      if (!ws) return;
+      const rtc = networkStore.getRtcMap(`${roomId.value}___${data.socket_id}`);
+      if (!rtc) return;
+      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');
+      }
+    });
+
+    // 管理员正在直播
+    ws.socketIo.on(WsMsgTypeEnum.roomLiveing, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.roomLiveing, data);
+    });
+
+    // 当前所有在线用户
+    ws.socketIo.on(WsMsgTypeEnum.liveUser, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.liveUser, data);
+    });
+
+    // 收到用户发送消息
+    ws.socketIo.on(WsMsgTypeEnum.message, (data: IMessage) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.message, data);
+      if (!ws) return;
+      damuList.value.push({
+        socket_id: data.socket_id,
+        msgType: DanmuMsgTypeEnum.danmu,
+        msg: data.data.msg,
+        userInfo: data.user_info,
+      });
+    });
+
+    // 用户加入房间完成
+    ws.socketIo.on(WsMsgTypeEnum.joined, (data: 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.value) {
+        startNewWebRtc({ receiver: getSocketId(), videoEl: localVideo.value });
+      }
+    });
+
+    // 其他用户加入房间
+    ws.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 (isSRS.value) return;
+      if (joined.value) {
+        startNewWebRtc({
+          receiver: data.data.join_socket_id,
+          videoEl: localVideo.value,
+        });
+      }
+    });
+
+    // 用户离开房间
+    ws.socketIo.on(WsMsgTypeEnum.leave, (data) => {
+      prettierReceiveWebsocket(WsMsgTypeEnum.leave, data);
+      if (!ws) return;
+      ws.send({
+        msgType: WsMsgTypeEnum.leave,
+        data: { roomId: ws.roomId },
+      });
+    });
+
+    // 用户离开房间完成
+    ws.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 initWs(data: {
+    roomId: string;
+    isSRS: boolean;
+    currentResolutionRatio: number;
+    currentMaxFramerate: number;
+    currentMaxBitrate: number;
+    localVideo: HTMLVideoElement;
+  }) {
+    roomId.value = data.roomId;
+    localVideo.value = data.localVideo;
+    currentMaxBitrate.value = data.currentMaxBitrate;
+    currentMaxFramerate.value = data.currentMaxFramerate;
+    currentResolutionRatio.value = data.currentResolutionRatio;
+    isSRS.value = data.isSRS;
+    console.warn('开始new WebSocketClass', data);
+    new WebSocketClass({
+      roomId: roomId.value,
+      url: WEBSOCKET_URL,
+      isAnchor: true,
+    });
+    isLiving.value = true;
+    initReceive();
+  }
+
+  return { initWs };
+};

+ 0 - 2
src/showBilldVersion.ts

@@ -1,4 +1,3 @@
-import BilldDeploy from 'billd-deploy/package.json';
 import BilldHtmlWebpackPlugin from 'billd-html-webpack-plugin/package.json';
 import BilldScss from 'billd-scss/package.json';
 import BilldUtils from 'billd-utils/package.json';
@@ -6,6 +5,5 @@ import BilldUtils from 'billd-utils/package.json';
 console.table({
   'billd-utils': BilldUtils.version,
   'billd-scss': BilldScss.version,
-  'billd-deploy': BilldDeploy.version,
   'billd-html-webpack-plugin': BilldHtmlWebpackPlugin.version,
 });

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

@@ -11,6 +11,7 @@ export type AppRootState = {
     audio: number;
     /** 1开启;2关闭 */
     video: number;
+    id: string;
     mediaName: string;
     type: MediaTypeEnum;
     track: MediaStreamTrack;

+ 31 - 50
src/views/push/index.vue

@@ -21,7 +21,7 @@
             x5-video-player-fullscreen="true"
             x5-video-orientation="portraint"
             muted
-            controls
+            :controls="NODE_ENV === 'development' ? true : false"
           ></video>
           <div
             v-if="!appStore.allTrack || appStore.allTrack.length <= 0"
@@ -174,6 +174,12 @@
               ({{ item.audio === 1 ? '音频' : ''
               }}{{ item.video === 1 ? '视频' : '' }}){{ item.mediaName }}
             </span>
+            <div
+              class="del"
+              @click="delTrack(item)"
+            >
+              x
+            </div>
           </div>
         </div>
         <div class="bottom">
@@ -250,12 +256,14 @@
 </template>
 
 <script lang="ts" setup>
+import { getRandomString } from 'billd-utils';
+import { NODE_ENV } from 'script/constant';
 import { onMounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { usePush } from '@/hooks/use-push';
 import { DanmuMsgTypeEnum, MediaTypeEnum, liveTypeEnum } from '@/interface';
-import { useAppStore } from '@/store/app';
+import { AppRootState, useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';
 
 import MediaModalCpt from './mediaModal/index.vue';
@@ -313,11 +321,9 @@ watch(
 );
 
 onMounted(() => {
-  if (localVideoRef.value) {
-    // localVideoRef.value.oncontextmenu = (e) => {
-    //   e.preventDefault();
-    // };
-  }
+  localVideoRef.value!.oncontextmenu = (e) => {
+    e.preventDefault();
+  };
   if (topRef.value && bottomRef.value && containerRef.value) {
     const res =
       bottomRef.value.getBoundingClientRect().top -
@@ -342,24 +348,11 @@ async function selectMediaOk(val: {
       },
       audio: true,
     });
-    if (localStream.value) {
-      const mixedStream = new MediaStream();
-      localStream.value
-        ?.getVideoTracks()
-        .forEach((track) => mixedStream.addTrack(track));
-      localStream.value
-        ?.getAudioTracks()
-        .forEach((track) => mixedStream.addTrack(track));
-      event.getVideoTracks().forEach((track) => mixedStream.addTrack(track));
-      event.getAudioTracks().forEach((track) => mixedStream.addTrack(track));
-      localStream.value = mixedStream;
-    } else {
-      localStream.value = event;
-    }
     const audio = event.getAudioTracks();
     appStore.setAllTrack([
       ...appStore.allTrack,
       {
+        id: getRandomString(8),
         audio: audio.length > 0 ? 1 : 2,
         video: 1,
         mediaName: val.mediaName,
@@ -378,23 +371,10 @@ async function selectMediaOk(val: {
       },
       audio: false,
     });
-    if (localStream.value) {
-      const mixedStream = new MediaStream();
-      localStream.value
-        ?.getVideoTracks()
-        .forEach((track) => mixedStream.addTrack(track));
-      localStream.value
-        ?.getAudioTracks()
-        .forEach((track) => mixedStream.addTrack(track));
-      event.getVideoTracks().forEach((track) => mixedStream.addTrack(track));
-      event.getAudioTracks().forEach((track) => mixedStream.addTrack(track));
-      localStream.value = mixedStream;
-    } else {
-      localStream.value = event;
-    }
     appStore.setAllTrack([
       ...appStore.allTrack,
       {
+        id: getRandomString(8),
         audio: 2,
         video: 1,
         mediaName: val.mediaName,
@@ -409,24 +389,10 @@ async function selectMediaOk(val: {
       video: false,
       audio: { deviceId: val.deviceId },
     });
-    if (localStream.value) {
-      const mixedStream = new MediaStream();
-      localStream.value
-        ?.getVideoTracks()
-        .forEach((track) => mixedStream.addTrack(track));
-      localStream.value
-        ?.getAudioTracks()
-        .forEach((track) => mixedStream.addTrack(track));
-      event.getVideoTracks().forEach((track) => mixedStream.addTrack(track));
-      event.getAudioTracks().forEach((track) => mixedStream.addTrack(track));
-      localStream.value = mixedStream;
-    } else {
-      localStream.value = event;
-    }
-    console.log(localStream.value, event);
     appStore.setAllTrack([
       ...appStore.allTrack,
       {
+        id: getRandomString(8),
         audio: 1,
         video: 2,
         mediaName: val.mediaName,
@@ -439,6 +405,12 @@ async function selectMediaOk(val: {
   }
 }
 
+function delTrack(item: AppRootState['allTrack'][0]) {
+  console.log(item);
+  const res = appStore.allTrack.filter((iten) => iten.id !== item.id);
+  appStore.setAllTrack(res);
+}
+
 function handleAddMedia() {
   currentMediaType.value = MediaTypeEnum.microphone;
   showMediaModalCpt.value = true;
@@ -620,6 +592,15 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
         justify-content: space-between;
         margin: 5px 0;
         font-size: 12px;
+        &:hover {
+          .del {
+            display: block;
+          }
+        }
+        .del {
+          display: none;
+          cursor: pointer;
+        }
       }
       .bottom {
         position: absolute;

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.