Ver código fonte

fix: 重构ing

shuisheng 1 ano atrás
pai
commit
1ad034362a

+ 2 - 2
doc/备忘.md

@@ -3,9 +3,9 @@
 - srs直播
   > todo
 - webrtc直播
-  > 主播给除自己以外的所有人发offer
+  > 在ohterJoin的时候,主播给除自己以外的所有人发offer
 - webrtc会议
-  > 观众进入直播间,观众给除自己以外的所有人发offer
+  > 在roomLiving的时候,主播给除自己以外的所有人发offer
 - 打pk
   > 主播1和主播2先webrtc视频通话,再由任意一方转推srs。
 

+ 2 - 2
src/components/VideoControls/index.vue

@@ -158,13 +158,13 @@ function changePlay() {
 function changeLiveLine(item) {
   if (
     item === LiveLineEnum.rtc &&
-    appStore.liveRoomInfo?.type !== LiveRoomTypeEnum.user_wertc
+    appStore.liveRoomInfo?.type !== LiveRoomTypeEnum.user_wertc_live
   ) {
     window.$message.info('不支持该线路!');
     return;
   }
   if (
-    appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc &&
+    appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc_live &&
     item !== LiveLineEnum.rtc
   ) {
     window.$message.info('不支持该线路!');

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

@@ -43,7 +43,7 @@ export function usePull(roomId: string) {
   const {
     isPull,
     mySocketId,
-    initSrsWs,
+    initWs,
     roomLiving,
     anchorInfo,
     liveUserList,
@@ -191,7 +191,7 @@ export function usePull(roomId: string) {
           handleHlsPlay(data.hls_url!);
         }
         break;
-      case LiveRoomTypeEnum.user_wertc:
+      case LiveRoomTypeEnum.user_wertc_live:
         appStore.setLiveLine(LiveLineEnum.rtc);
         break;
     }
@@ -296,7 +296,7 @@ export function usePull(roomId: string) {
   watch(
     () => networkStore.rtcMap,
     (newVal) => {
-      if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc) {
+      if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc_live) {
         newVal.forEach((item) => {
           videoLoading.value = false;
           // if (videoWrapRef.value) {
@@ -330,7 +330,7 @@ export function usePull(roomId: string) {
         console.log('localStream变了');
         console.log('音频轨:', val?.getAudioTracks());
         console.log('视频轨:', val?.getVideoTracks());
-        if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc) {
+        if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc_live) {
           videoElArr.value.forEach((dom) => {
             dom.remove();
           });
@@ -400,7 +400,7 @@ export function usePull(roomId: string) {
     if (autoplayVal.value) {
       videoLoading.value = true;
     }
-    initSrsWs({
+    initWs({
       roomId,
       isAnchor: false,
     });

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

@@ -42,7 +42,7 @@ export function usePush() {
   const {
     roomLiving,
     isPull,
-    initSrsWs,
+    initWs,
     handleStartLive,
     handleSendGetLiveUser,
     mySocketId,
@@ -195,7 +195,7 @@ export function usePush() {
   }
 
   function connectWs() {
-    initSrsWs({
+    initWs({
       isAnchor: true,
       roomId: roomId.value,
       currentMaxBitrate: currentMaxBitrate.value,

+ 103 - 270
src/hooks/use-websocket.ts

@@ -6,6 +6,10 @@ import { useRoute } from 'vue-router';
 import { fetchVerifyPkKey } from '@/api/liveRoom';
 import { fetchRtcV1Publish } from '@/api/srs';
 import { SRS_CB_URL_PARAMS, THEME_COLOR, WEBSOCKET_URL } from '@/constant';
+import { useRTCParams } from '@/hooks/use-rtcParams';
+import { useTip } from '@/hooks/use-tip';
+import { useWebRtcManyToManyMeeting } from '@/hooks/webrtc/manyToManyMeeting';
+import { useWebRtcOneToManyLive } from '@/hooks/webrtc/oneToManyLive';
 import {
   DanmuMsgTypeEnum,
   ILiveUser,
@@ -39,10 +43,7 @@ import {
   WsStartLiveType,
   WsUpdateJoinInfoType,
 } from '@/types/websocket';
-import { createVideo } from '@/utils';
-
-import { useRTCParams } from './use-rtcParams';
-import { useTip } from './use-tip';
+import { createNullVideo, createVideo, handleUserMedia } from '@/utils';
 
 export const useWebsocket = () => {
   const route = useRoute();
@@ -52,6 +53,10 @@ export const useWebsocket = () => {
   const dialog = useDialog();
 
   const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+  const { updateWebRtcOneToManyLiveConfig, webRtcOneToManyLive } =
+    useWebRtcOneToManyLive();
+  const { updateWebRtcManyToManyMeetingConfig, webRtcManyToManyMeeting } =
+    useWebRtcManyToManyMeeting();
 
   const connectStatus = ref();
   const loopHeartbeatTimer = ref();
@@ -65,6 +70,7 @@ export const useWebsocket = () => {
   const anchorInfo = ref<IUser>();
   const anchorSocketId = ref('');
   const canvasVideoStream = ref<MediaStream>();
+  const meetingMyStream = ref<MediaStream>();
   const lastCoverImg = ref('');
   const currentMaxBitrate = ref(maxBitrate.value[3].value);
   const currentMaxFramerate = ref(maxFramerate.value[2].value);
@@ -77,19 +83,6 @@ export const useWebsocket = () => {
     clearInterval(loopGetLiveUserTimer.value);
   });
 
-  function createNullVideo() {
-    const videoEl = document.createElement('video');
-    videoEl.autoplay = true;
-    videoEl.muted = true;
-    videoEl.playsInline = true;
-    videoEl.loop = true;
-    videoEl.setAttribute('webkit-playsinline', 'true');
-    videoEl.setAttribute('x5-video-player-type', 'h5');
-    videoEl.setAttribute('x5-video-player-fullscreen', 'true');
-    videoEl.setAttribute('x5-video-orientation', 'portraint');
-    return videoEl;
-  }
-
   watch(
     () => appStore.pkStream,
     (newval) => {
@@ -135,6 +128,7 @@ export const useWebsocket = () => {
         data: {
           socket_id: socketId,
           live_room_id: Number(roomId.value),
+          roomLiving: isAnchor.value && roomLiving.value,
         },
       });
     }, 1000 * 5);
@@ -207,205 +201,6 @@ export const useWebsocket = () => {
     });
   }
 
-  async function handleUserMedia({ video, audio }) {
-    try {
-      const event = await navigator.mediaDevices.getUserMedia({
-        video,
-        audio,
-      });
-      return event;
-    } catch (error) {
-      console.log(error);
-    }
-  }
-
-  const nativeWebRtc = {
-    newWebrtc: ({
-      isAnchor,
-      sender,
-      receiver,
-      videoEl,
-    }: {
-      isAnchor: boolean;
-      sender: string;
-      receiver: string;
-      videoEl: HTMLVideoElement;
-    }) => {
-      return new WebRTCClass({
-        maxBitrate: currentMaxBitrate.value,
-        maxFramerate: currentMaxFramerate.value,
-        resolutionRatio: currentResolutionRatio.value,
-        isSRS: false,
-        roomId: roomId.value,
-        isAnchor,
-        videoEl,
-        sender,
-        receiver,
-      });
-    },
-    /**
-     * 房主发offer给用户
-     */
-    sendOffer: async ({
-      sender,
-      receiver,
-    }: {
-      sender: string;
-      receiver: string;
-    }) => {
-      console.log('开始nativeWebRtc的sendOffer', { sender, receiver });
-      try {
-        const ws = networkStore.wsMap.get(roomId.value);
-        if (!ws) return;
-        if (networkStore.rtcMap.get(receiver)) {
-          return;
-        }
-        const rtc = nativeWebRtc.newWebrtc({
-          isAnchor: true,
-          sender,
-          receiver,
-          videoEl: createNullVideo(),
-        });
-        if (
-          appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc_meeting
-        ) {
-          dialog.warning({
-            title: '提示',
-            content: '是否加入会议',
-            positiveText: '确认',
-            onPositiveClick() {
-              async function main() {
-                const stream = await handleUserMedia({
-                  video: true,
-                  audio: true,
-                });
-                if (rtc?.peerConnection) {
-                  stream?.getTracks().forEach((track) => {
-                    console.log('加入会议插入track', track.kind, track);
-                    rtc.peerConnection?.addTrack(track, stream);
-                  });
-                }
-                const offerSdp = await rtc.createOffer();
-                if (!offerSdp) {
-                  console.error('nativeWebRtc的offerSdp为空');
-                  return;
-                }
-                await rtc.setLocalDescription(offerSdp!);
-                networkStore.wsMap
-                  .get(roomId.value)
-                  ?.send<WsOfferType['data']>({
-                    requestId: getRandomString(8),
-                    msgType: WsMsgTypeEnum.nativeWebRtcOffer,
-                    data: {
-                      live_room: appStore.liveRoomInfo!,
-                      live_room_id: appStore.liveRoomInfo!.id!,
-                      sender,
-                      receiver,
-                      sdp: offerSdp,
-                    },
-                  });
-              }
-              return main();
-            },
-          });
-        } else if (
-          appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc
-        ) {
-          canvasVideoStream.value?.getTracks().forEach((track) => {
-            if (canvasVideoStream.value) {
-              console.log(
-                'nativeWebRtc的canvasVideoStream插入track',
-                track.kind,
-                track
-              );
-              rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
-            }
-          });
-          const offerSdp = await rtc.createOffer();
-          if (!offerSdp) {
-            console.error('nativeWebRtc的offerSdp为空');
-            return;
-          }
-          await rtc.setLocalDescription(offerSdp!);
-          networkStore.wsMap.get(roomId.value)?.send<WsOfferType['data']>({
-            requestId: getRandomString(8),
-            msgType: WsMsgTypeEnum.nativeWebRtcOffer,
-            data: {
-              live_room: appStore.liveRoomInfo!,
-              live_room_id: appStore.liveRoomInfo!.id!,
-              sender,
-              receiver,
-              sdp: offerSdp,
-            },
-          });
-        }
-      } catch (error) {
-        console.error('nativeWebRtc的sendOffer错误');
-      }
-    },
-    /**
-     * 原生webrtc视频通话
-     * 用户收到房主的offer,用户回复房主answer
-     */
-    sendAnswer: async ({
-      isPk,
-      sdp,
-      sender,
-      receiver,
-    }: {
-      isPk: boolean;
-      sdp: RTCSessionDescriptionInit;
-      sender: string;
-      receiver: string;
-    }) => {
-      console.log('开始nativeWebRtc的sendAnswer', { sender, receiver, sdp });
-      try {
-        const ws = networkStore.wsMap.get(roomId.value);
-        if (!ws) return;
-        const rtc = networkStore.rtcMap.get(receiver);
-        if (rtc) {
-          await rtc.setRemoteDescription(sdp);
-          if (isPk) {
-            if (!isAnchor.value) {
-              const stream = await handleUserMedia({
-                video: true,
-                audio: true,
-              });
-              if (rtc?.peerConnection) {
-                rtc.peerConnection.onnegotiationneeded = (event) => {
-                  console.log('onnegotiationneeded', event);
-                };
-                stream?.getTracks().forEach((track) => {
-                  rtc.peerConnection?.addTrack(track, stream);
-                });
-              }
-            }
-          }
-          const answerSdp = await rtc.createAnswer();
-          if (!answerSdp) {
-            console.error('nativeWebRtc的answerSdp为空');
-            return;
-          }
-          await rtc.setLocalDescription(answerSdp);
-          networkStore.wsMap.get(roomId.value)?.send<WsAnswerType['data']>({
-            requestId: getRandomString(8),
-            msgType: WsMsgTypeEnum.nativeWebRtcAnswer,
-            data: {
-              live_room_id: Number(roomId.value),
-              sender,
-              receiver,
-              sdp: answerSdp,
-            },
-          });
-        } else {
-          console.error('rtc不存在');
-        }
-      } catch (error) {
-        console.error('nativeWebRtc的sendAnswer错误');
-      }
-    },
-  };
-
   const srsWebRtc = {
     newWebrtc: ({
       roomId,
@@ -417,7 +212,6 @@ export const useWebsocket = () => {
       videoEl: HTMLVideoElement;
     }) => {
       return new WebRTCClass({
-        isAnchor: true,
         maxBitrate: currentMaxBitrate.value,
         maxFramerate: currentMaxFramerate.value,
         resolutionRatio: currentResolutionRatio.value,
@@ -551,7 +345,6 @@ export const useWebsocket = () => {
           console.warn('是发给我的srsOffer');
           const videoEl = createNullVideo();
           const rtc = new WebRTCClass({
-            isAnchor: true,
             maxBitrate: currentMaxBitrate.value,
             maxFramerate: currentMaxFramerate.value,
             resolutionRatio: currentResolutionRatio.value,
@@ -630,26 +423,25 @@ export const useWebsocket = () => {
                 positiveText: '确认',
                 onPositiveClick() {
                   async function main() {
-                    if (data.receiver === mySocketId.value) {
-                      console.warn('是发给我的nativeWebRtcOffer');
-                      await nativeWebRtc.newWebrtc({
-                        isAnchor: true,
-                        // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
-                        // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
-                        sender: mySocketId.value,
-                        receiver: data.sender,
-                        videoEl: createNullVideo(),
-                      });
-                      nativeWebRtc.sendAnswer({
-                        isPk: true,
-                        sender: mySocketId.value,
-                        // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
-                        receiver: data.sender,
-                        sdp: data.sdp,
-                      });
-                    } else {
-                      console.error('不是发给我的nativeWebRtcOffer');
-                    }
+                    // if (data.receiver === mySocketId.value) {
+                    //   console.warn('是发给我的nativeWebRtcOffer');
+                    //   await nativeWebRtc.newWebrtc({
+                    //     // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
+                    //     // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
+                    //     sender: mySocketId.value,
+                    //     receiver: data.sender,
+                    //     videoEl: createNullVideo(),
+                    //   });
+                    //   nativeWebRtc.sendAnswer({
+                    //     isPk: true,
+                    //     sender: mySocketId.value,
+                    //     // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
+                    //     receiver: data.sender,
+                    //     sdp: data.sdp,
+                    //   });
+                    // } else {
+                    //   console.error('不是发给我的nativeWebRtcOffer');
+                    // }
                   }
                   return main();
                 },
@@ -657,41 +449,54 @@ export const useWebsocket = () => {
             } else {
               window.$message.error('验证pkKey错误!');
             }
-          } else {
-            if (data.receiver === mySocketId.value) {
-              console.warn('是发给我的nativeWebRtcOffer');
-              await nativeWebRtc.newWebrtc({
-                isAnchor: true,
-                // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
-                // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
-                sender: mySocketId.value,
-                receiver: data.sender,
-                videoEl: createNullVideo(),
-              });
-              nativeWebRtc.sendAnswer({
-                isPk: true,
-                sender: mySocketId.value,
-                // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
-                receiver: data.sender,
-                sdp: data.sdp,
-              });
-            } else {
-              console.error('不是发给我的nativeWebRtcOffer');
+          }
+        } else if (data.live_room.type === LiveRoomTypeEnum.user_wertc_live) {
+          if (data.receiver === mySocketId.value) {
+            console.warn('是发给我的nativeWebRtcOffer');
+            if (networkStore.rtcMap.get(data.sender)) {
+              return;
             }
+            updateWebRtcOneToManyLiveConfig({
+              roomId: roomId.value,
+              canvasVideoStream: canvasVideoStream.value,
+            });
+            webRtcOneToManyLive.newWebRtc({
+              // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
+              // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
+              sender: mySocketId.value,
+              receiver: data.sender,
+              videoEl: createNullVideo(),
+            });
+            await webRtcOneToManyLive.sendAnswer({
+              sender: mySocketId.value,
+              // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
+              receiver: data.sender,
+              sdp: data.sdp,
+            });
+          } else {
+            console.error('不是发给我的nativeWebRtcOffer');
           }
-        } else {
+        } else if (
+          data.live_room.type === LiveRoomTypeEnum.user_wertc_meeting
+        ) {
           if (data.receiver === mySocketId.value) {
             console.warn('是发给我的nativeWebRtcOffer');
-            await nativeWebRtc.newWebrtc({
-              isAnchor: true,
+            updateWebRtcManyToManyMeetingConfig({
+              roomId: roomId.value,
+              meetingMyStream: meetingMyStream.value,
+              meetingAnchorStream: canvasVideoStream.value,
+            });
+            if (networkStore.rtcMap.get(data.sender)) {
+              return;
+            }
+            webRtcManyToManyMeeting.newWebRtc({
               // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
               // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
               sender: mySocketId.value,
               receiver: data.sender,
               videoEl: createNullVideo(),
             });
-            await nativeWebRtc.sendAnswer({
-              isPk: false,
+            await webRtcManyToManyMeeting.sendAnswer({
               sender: mySocketId.value,
               // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己
               receiver: data.sender,
@@ -739,7 +544,7 @@ export const useWebsocket = () => {
     // 主播正在直播
     ws.socketIo.on(
       WsMsgTypeEnum.roomLiving,
-      (data: WsRoomLivingType['data']) => {
+      async (data: WsRoomLivingType['data']) => {
         prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data);
         roomLiving.value = true;
         if (data.anchor_socket_id) {
@@ -748,9 +553,26 @@ export const useWebsocket = () => {
         if (route.name === routerName.pull) {
           // 当前是拉流页面
           if (data.live_room?.type === LiveRoomTypeEnum.user_wertc_meeting) {
+            const stream = await handleUserMedia({
+              video: true,
+              audio: true,
+            });
+            meetingMyStream.value = stream;
+            updateWebRtcManyToManyMeetingConfig({
+              roomId: roomId.value,
+              meetingMyStream: stream,
+            });
             data.socket_list?.forEach((item) => {
               if (item !== mySocketId.value) {
-                nativeWebRtc.sendOffer({
+                if (networkStore.rtcMap.get(item)) {
+                  return;
+                }
+                webRtcManyToManyMeeting.newWebRtc({
+                  sender: mySocketId.value,
+                  receiver: item,
+                  videoEl: createNullVideo(),
+                });
+                webRtcManyToManyMeeting.sendOffer({
                   sender: mySocketId.value,
                   receiver: item,
                 });
@@ -887,11 +709,22 @@ export const useWebsocket = () => {
         ) {
           return;
         }
-        if (data.live_room.type === LiveRoomTypeEnum.user_wertc) {
-          isSRS.value = false;
+        if (data.live_room.type === LiveRoomTypeEnum.user_wertc_live) {
+          updateWebRtcOneToManyLiveConfig({
+            roomId: roomId.value,
+            canvasVideoStream: canvasVideoStream.value,
+          });
           data.socket_list.forEach((item) => {
             if (item !== mySocketId.value) {
-              nativeWebRtc.sendOffer({
+              if (networkStore.rtcMap.get(item)) {
+                return;
+              }
+              webRtcOneToManyLive.newWebRtc({
+                sender: mySocketId.value,
+                receiver: item,
+                videoEl: createNullVideo(),
+              });
+              webRtcOneToManyLive.sendOffer({
                 sender: mySocketId.value,
                 receiver: item,
               });
@@ -927,7 +760,7 @@ export const useWebsocket = () => {
     });
   }
 
-  function initSrsWs(data: {
+  function initWs(data: {
     isAnchor: boolean;
     roomId: string;
     currentResolutionRatio?: number;
@@ -955,7 +788,7 @@ export const useWebsocket = () => {
 
   return {
     isPull,
-    initSrsWs,
+    initWs,
     handleStartLive,
     handleSendGetLiveUser,
     mySocketId,

+ 160 - 0
src/hooks/webrtc/manyToManyMeeting.ts

@@ -0,0 +1,160 @@
+import { getRandomString } from 'billd-utils';
+import { ref } from 'vue';
+
+import { useRTCParams } from '@/hooks/use-rtcParams';
+import { WebRTCClass } from '@/network/webRTC';
+import { useAppStore } from '@/store/app';
+import { useNetworkStore } from '@/store/network';
+import { WsAnswerType, WsMsgTypeEnum, WsOfferType } from '@/types/websocket';
+
+export const useWebRtcManyToManyMeeting = () => {
+  const appStore = useAppStore();
+  const networkStore = useNetworkStore();
+
+  const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+  const currentMaxBitrate = ref(maxBitrate.value[3].value);
+  const currentMaxFramerate = ref(maxFramerate.value[2].value);
+  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
+  const roomId = ref('');
+  const meetingMyStream = ref<MediaStream>();
+  const meetingAnchorStream = ref<MediaStream>();
+
+  function updateWebRtcManyToManyMeetingConfig(data: {
+    roomId;
+    meetingMyStream;
+    meetingAnchorStream?;
+  }) {
+    roomId.value = data.roomId;
+    meetingMyStream.value = data.meetingMyStream;
+    meetingAnchorStream.value = data.meetingAnchorStream;
+  }
+
+  const webRtcManyToManyMeeting = {
+    newWebRtc: (data: {
+      sender: string;
+      receiver: string;
+      videoEl: HTMLVideoElement;
+    }) => {
+      return new WebRTCClass({
+        maxBitrate: currentMaxBitrate.value,
+        maxFramerate: currentMaxFramerate.value,
+        resolutionRatio: currentResolutionRatio.value,
+        isSRS: false,
+        roomId: roomId.value,
+        videoEl: data.videoEl,
+        sender: data.sender,
+        receiver: data.receiver,
+      });
+    },
+    /**
+     * 主播发offer给观众
+     */
+    sendOffer: async ({
+      sender,
+      receiver,
+    }: {
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('开始webRtcManyToManyMeeting的sendOffer', {
+        sender,
+        receiver,
+      });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = networkStore.rtcMap.get(receiver);
+        if (rtc) {
+          meetingMyStream.value?.getTracks().forEach((track) => {
+            if (meetingMyStream.value) {
+              console.log(
+                'webRtcManyToManyMeeting的meetingMyStream插入track',
+                track.kind,
+                track
+              );
+              rtc.peerConnection?.addTrack(track, meetingMyStream.value);
+            }
+          });
+          const offerSdp = await rtc.createOffer();
+          if (!offerSdp) {
+            console.error('webRtcManyToManyMeeting的offerSdp为空');
+            return;
+          }
+          await rtc.setLocalDescription(offerSdp!);
+          networkStore.wsMap.get(roomId.value)?.send<WsOfferType['data']>({
+            requestId: getRandomString(8),
+            msgType: WsMsgTypeEnum.nativeWebRtcOffer,
+            data: {
+              live_room: appStore.liveRoomInfo!,
+              live_room_id: appStore.liveRoomInfo!.id!,
+              sender,
+              receiver,
+              sdp: offerSdp,
+            },
+          });
+        } else {
+          console.error('rtc不存在');
+        }
+      } catch (error) {
+        console.error('webRtcManyToManyMeeting的sendOffer错误');
+      }
+    },
+    /**
+     * 观众收到主播的offer,观众回复主播answer
+     */
+    sendAnswer: async ({
+      sdp,
+      sender,
+      receiver,
+    }: {
+      sdp: RTCSessionDescriptionInit;
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('开始webRtcManyToManyMeeting的sendAnswer', {
+        sender,
+        receiver,
+      });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = networkStore.rtcMap.get(receiver);
+        if (rtc) {
+          await rtc.setRemoteDescription(sdp);
+          meetingAnchorStream.value?.getTracks().forEach((track) => {
+            if (meetingAnchorStream.value) {
+              console.log(
+                'webRtcManyToManyMeeting的meetingAnchorStream插入track',
+                track.kind,
+                track
+              );
+              rtc.peerConnection?.addTrack(track, meetingAnchorStream.value);
+            }
+          });
+          const answerSdp = await rtc.createAnswer();
+          if (!answerSdp) {
+            console.error('webRtcManyToManyMeeting的answerSdp为空');
+            return;
+          }
+          await rtc.setLocalDescription(answerSdp);
+          networkStore.wsMap.get(roomId.value)?.send<WsAnswerType['data']>({
+            requestId: getRandomString(8),
+            msgType: WsMsgTypeEnum.nativeWebRtcAnswer,
+            data: {
+              live_room_id: Number(roomId.value),
+              sender,
+              receiver,
+              sdp: answerSdp,
+            },
+          });
+        } else {
+          console.error('rtc不存在');
+        }
+      } catch (error) {
+        console.error('webRtcManyToManyMeeting的sendAnswer错误');
+      }
+    },
+  };
+
+  return { updateWebRtcManyToManyMeetingConfig, webRtcManyToManyMeeting };
+};

+ 147 - 0
src/hooks/webrtc/oneToManyLive.ts

@@ -0,0 +1,147 @@
+import { getRandomString } from 'billd-utils';
+import { ref } from 'vue';
+
+import { useRTCParams } from '@/hooks/use-rtcParams';
+import { WebRTCClass } from '@/network/webRTC';
+import { useAppStore } from '@/store/app';
+import { useNetworkStore } from '@/store/network';
+import { WsAnswerType, WsMsgTypeEnum, WsOfferType } from '@/types/websocket';
+
+export const useWebRtcOneToManyLive = () => {
+  const appStore = useAppStore();
+  const networkStore = useNetworkStore();
+
+  const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+  const currentMaxBitrate = ref(maxBitrate.value[3].value);
+  const currentMaxFramerate = ref(maxFramerate.value[2].value);
+  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
+  const roomId = ref('');
+  const canvasVideoStream = ref<MediaStream>();
+
+  function updateWebRtcOneToManyLiveConfig(data: {
+    roomId;
+    canvasVideoStream;
+  }) {
+    roomId.value = data.roomId;
+    canvasVideoStream.value = data.canvasVideoStream;
+  }
+
+  const webRtcOneToManyLive = {
+    newWebRtc: (data: {
+      sender: string;
+      receiver: string;
+      videoEl: HTMLVideoElement;
+    }) => {
+      return new WebRTCClass({
+        maxBitrate: currentMaxBitrate.value,
+        maxFramerate: currentMaxFramerate.value,
+        resolutionRatio: currentResolutionRatio.value,
+        isSRS: false,
+        roomId: roomId.value,
+        videoEl: data.videoEl,
+        sender: data.sender,
+        receiver: data.receiver,
+      });
+    },
+    /**
+     * 主播发offer给观众
+     */
+    sendOffer: async ({
+      sender,
+      receiver,
+    }: {
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('开始webRtcOneToManyLive的sendOffer', {
+        sender,
+        receiver,
+      });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = networkStore.rtcMap.get(receiver);
+        if (rtc) {
+          canvasVideoStream.value?.getTracks().forEach((track) => {
+            if (canvasVideoStream.value) {
+              console.log(
+                'webRtcOneToManyLive的canvasVideoStream插入track',
+                track.kind,
+                track
+              );
+              rtc.peerConnection?.addTrack(track, canvasVideoStream.value);
+            }
+          });
+          const offerSdp = await rtc.createOffer();
+          if (!offerSdp) {
+            console.error('webRtcOneToManyLive的offerSdp为空');
+            return;
+          }
+          await rtc.setLocalDescription(offerSdp!);
+          networkStore.wsMap.get(roomId.value)?.send<WsOfferType['data']>({
+            requestId: getRandomString(8),
+            msgType: WsMsgTypeEnum.nativeWebRtcOffer,
+            data: {
+              live_room: appStore.liveRoomInfo!,
+              live_room_id: appStore.liveRoomInfo!.id!,
+              sender,
+              receiver,
+              sdp: offerSdp,
+            },
+          });
+        } else {
+          console.error('rtc不存在');
+        }
+      } catch (error) {
+        console.error('webRtcOneToManyLive的sendOffer错误');
+      }
+    },
+    /**
+     * 观众收到主播的offer,观众回复主播answer
+     */
+    sendAnswer: async ({
+      sdp,
+      sender,
+      receiver,
+    }: {
+      sdp: RTCSessionDescriptionInit;
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('开始webRtcOneToManyLive的sendAnswer', {
+        sender,
+        receiver,
+      });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = networkStore.rtcMap.get(receiver);
+        if (rtc) {
+          await rtc.setRemoteDescription(sdp);
+          const answerSdp = await rtc.createAnswer();
+          if (!answerSdp) {
+            console.error('webRtcOneToManyLive的answerSdp为空');
+            return;
+          }
+          await rtc.setLocalDescription(answerSdp);
+          networkStore.wsMap.get(roomId.value)?.send<WsAnswerType['data']>({
+            requestId: getRandomString(8),
+            msgType: WsMsgTypeEnum.nativeWebRtcAnswer,
+            data: {
+              live_room_id: Number(roomId.value),
+              sender,
+              receiver,
+              sdp: answerSdp,
+            },
+          });
+        } else {
+          console.error('rtc不存在');
+        }
+      } catch (error) {
+        console.error('webRtcOneToManyLive的sendAnswer错误');
+      }
+    },
+  };
+
+  return { updateWebRtcOneToManyLiveConfig, webRtcOneToManyLive };
+};

+ 3 - 1
src/layout/pc/head/index.vue

@@ -253,7 +253,9 @@
               </a>
               <a
                 class="item"
-                @click.prevent="handleStartLive(LiveRoomTypeEnum.user_wertc)"
+                @click.prevent="
+                  handleStartLive(LiveRoomTypeEnum.user_wertc_live)
+                "
               >
                 <div class="txt">{{ t('layout.webrtcLive') }}</div>
               </a>

+ 0 - 4
src/network/webRTC.ts

@@ -65,7 +65,6 @@ export async function handleMaxFramerate(data: {
 }
 
 export class WebRTCClass {
-  isAnchor = false;
   roomId = '-1';
   sender = '';
   receiver = '';
@@ -86,7 +85,6 @@ export class WebRTCClass {
   isSRS: boolean;
 
   constructor(data: {
-    isAnchor: boolean;
     roomId: string;
     videoEl: HTMLVideoElement;
     maxBitrate?: number;
@@ -97,7 +95,6 @@ export class WebRTCClass {
     receiver: string;
     localStream?: MediaStream;
   }) {
-    this.isAnchor = data.isAnchor;
     this.roomId = data.roomId;
     this.videoEl = data.videoEl;
     // document.body.appendChild(this.videoEl);
@@ -391,7 +388,6 @@ export class WebRTCClass {
       this.prettierLog({ msg: 'pc收到icecandidate', type: 'warn' });
       if (event.candidate) {
         const networkStore = useNetworkStore();
-        console.log('准备发送candidate', event.candidate.candidate);
         networkStore.wsMap.get(this.roomId)?.send<WsCandidateType['data']>({
           requestId: getRandomString(8),
           msgType: this.isSRS

+ 1 - 1
src/network/webSocket.ts

@@ -18,7 +18,7 @@ export function prettierSendWsMsg(data: {
   data;
 }) {
   console.warn(
-    `【websocket】发送消息 requestId:${data.requestId},msgType:${data.msgType}`,
+    `【websocket】发送消息 msgType:${data.msgType},requestId:${data.requestId}`,
     data
   );
 }

+ 1 - 1
src/types/ILiveRoom.ts

@@ -18,7 +18,7 @@ export enum LiveRoomTypeEnum {
   /** 主播使用obs/ffmpeg推流 */
   user_obs,
   /** 主播使用webrtc推流,直播 */
-  user_wertc,
+  user_wertc_live,
   /** 主播使用webrtc推流,会议 */
   user_wertc_meeting,
   /** 主播使用msr推流 */

+ 1 - 0
src/types/websocket.ts

@@ -203,6 +203,7 @@ export type WsLeavedType = IWsFormat<{
 export type WsHeartbeatType = IWsFormat<{
   socket_id: string;
   live_room_id: number;
+  roomLiving?: boolean;
 }>;
 
 /** msr直播发送blob */

+ 25 - 0
src/utils/index.ts

@@ -2,6 +2,31 @@
 import { computeBox, getRangeRandom } from 'billd-utils';
 import sparkMD5 from 'spark-md5';
 
+export function createNullVideo() {
+  const videoEl = document.createElement('video');
+  videoEl.autoplay = true;
+  videoEl.muted = true;
+  videoEl.playsInline = true;
+  videoEl.loop = true;
+  videoEl.setAttribute('webkit-playsinline', 'true');
+  videoEl.setAttribute('x5-video-player-type', 'h5');
+  videoEl.setAttribute('x5-video-player-fullscreen', 'true');
+  videoEl.setAttribute('x5-video-orientation', 'portraint');
+  return videoEl;
+}
+
+export async function handleUserMedia({ video, audio }) {
+  try {
+    const event = await navigator.mediaDevices.getUserMedia({
+      video,
+      audio,
+    });
+    return event;
+  } catch (error) {
+    console.log(error);
+  }
+}
+
 export function formatMoney(money?: number) {
   if (!money) {
     return '0.00';

+ 1 - 1
src/views/h5/room/index.vue

@@ -345,7 +345,7 @@ async function getLiveRoomInfo() {
     const res = await fetchFindLiveRoom(roomId.value);
     if (res.code === 200) {
       appStore.setLiveRoomInfo(res.data);
-      if (res.data.type === LiveRoomTypeEnum.user_wertc) {
+      if (res.data.type === LiveRoomTypeEnum.user_wertc_live) {
         autoplayVal.value = true;
         showPlayBtn.value = false;
       } else {

+ 1 - 1
src/views/home/index.vue

@@ -310,7 +310,7 @@ function playLive(item: ILive) {
 
 function changeLiveRoom(item: ILive) {
   if (item.id === currentLiveRoom.value?.id) return;
-  if (item.live_room?.type !== LiveRoomTypeEnum.user_wertc) {
+  if (item.live_room?.type !== LiveRoomTypeEnum.user_wertc_live) {
     appStore.setLiveLine(LiveLineEnum.hls);
   }
   appStore.setPlay(true);

+ 10 - 13
src/views/pull/index.vue

@@ -430,7 +430,12 @@ import { useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
 import { WsDisableSpeakingType, WsMsgTypeEnum } from '@/types/websocket';
-import { formatMoney, formatTimeHour, videoFullBox } from '@/utils';
+import {
+  formatMoney,
+  formatTimeHour,
+  handleUserMedia,
+  videoFullBox,
+} from '@/utils';
 import { NODE_ENV } from 'script/constant';
 
 import RechargeCpt from './recharge/index.vue';
@@ -582,18 +587,6 @@ async function handleHistoryMsg() {
   }
 }
 
-async function handleUserMedia({ video, audio }) {
-  try {
-    const event = await navigator.mediaDevices.getUserMedia({
-      video,
-      audio,
-    });
-    return event;
-  } catch (error) {
-    console.log(error);
-  }
-}
-
 async function handlePk() {
   const stream = await handleUserMedia({ video: true, audio: true });
   const rtc = networkStore.rtcMap.get(`${roomId.value}`)!;
@@ -611,6 +604,10 @@ async function handlePk() {
 watch(
   () => networkStore.rtcMap,
   (newVal) => {
+    if (newVal.size) {
+      roomLiving.value = true;
+      videoLoading.value = false;
+    }
     newVal.forEach((item) => {
       const rect = videoWrapRef.value?.getBoundingClientRect();
       if (rect) {

+ 4 - 12
src/views/push/index.vue

@@ -408,6 +408,7 @@ import {
   formatDownTime,
   generateBase64,
   getRandomEnglishString,
+  handleUserMedia,
   readFile,
   saveFile,
 } from '@/utils';
@@ -547,6 +548,9 @@ watch(
   (newVal) => {
     console.log('rtcMap变了');
     newVal.forEach((item) => {
+      if (appStore.allTrack.find((v) => v.mediaName === item.receiver)) {
+        return;
+      }
       addMediaOk({
         id: getRandomEnglishString(6),
         openEye: true,
@@ -1222,18 +1226,6 @@ function handleMoving({
   });
 }
 
-async function handleUserMedia({ video, audio }) {
-  try {
-    const event = await navigator.mediaDevices.getUserMedia({
-      video,
-      audio,
-    });
-    return event;
-  } catch (error) {
-    console.log(error);
-  }
-}
-
 async function handleDisplayMedia({ video, audio }) {
   try {
     const event = await navigator.mediaDevices.getDisplayMedia({