Ver código fonte

feat: 优化

shuisheng 2 anos atrás
pai
commit
0ef58f2fbc
3 arquivos alterados com 268 adições e 210 exclusões
  1. 2 11
      src/network/webRtc.ts
  2. 4 9
      src/network/websocket.ts
  3. 262 190
      src/views/home/index.vue

+ 2 - 11
src/network/webRtc.ts

@@ -91,7 +91,7 @@ export class WebRTCClass {
   };
 
   rtcStatus = {
-    joinRes: false, // true代表成功,false代表失败
+    joined: false, // true代表成功,false代表失败
     icecandidate: false, // true代表成功,false代表失败
     createOffer: false, // true代表成功,false代表失败
     setLocalDescription: false, // true代表成功,false代表失败
@@ -311,7 +311,7 @@ export class WebRTCClass {
   createOffer = async () => {
     console.log('开始createOffer');
     if (!this.peerConnection) return;
-    if (this.rtcStatus.createOffer) return;
+    if (this.rtcStatus.createOffer) return this.localDescription;
     try {
       const description = await this.peerConnection.createOffer({
         offerToReceiveAudio: false,
@@ -460,17 +460,8 @@ export class WebRTCClass {
     console.warn('开始监听track');
     this.peerConnection.addEventListener('track', (event: any) => {
       console.log('pc收到track事件', event);
-      // setTimeout(() => {
-      // const video = document.createElement('video');
-      // video.srcObject = event.streams[0];
-      // video.autoplay = true;
-      // video.controls = true;
-      // video.playsInline = true;
-      // document.body.appendChild(video);
-      // this.addStream(event.streams[0]);
       document.querySelector<HTMLVideoElement>('#localVideo')!.srcObject =
         event.streams[0];
-      // }, 1000);
     });
 
     // connectionstatechange

+ 4 - 9
src/network/websocket.ts

@@ -24,14 +24,16 @@ export const wsConnectStatus = {
 export const wsMsgType = {
   /** 用户进入聊天 */
   join: 'join',
-  /** 用户进入聊天 */
+  /** 用户进入聊天完成 */
   joined: 'joined',
   /** 用户进入聊天 */
   otherJoin: 'otherJoin',
   /** 用户退出聊天 */
   leave: 'leave',
-  /** 用户退出聊天 */
+  /** 用户退出聊天完成 */
   leaved: 'leaved',
+  /** 当前所有在线用户 */
+  liveUser: 'liveUser',
   /** 人满了 */
   full: 'full',
   /** 用户发送消息 */
@@ -46,13 +48,6 @@ export enum statusEnum {
   disconnect = 'disconnect',
 }
 
-// export const state = reactive({
-//   socketIo: null,
-//   status: statusEnum.connect,
-//   url: 'ws://localhost:3300',
-//   roomId: '-1',
-// });
-
 export class WebSocketClass {
   socketIo: Socket | null = null;
   status: statusEnum = statusEnum.disconnect;

+ 262 - 190
src/views/home/index.vue

@@ -1,72 +1,88 @@
 <template>
   <div class="home-wrap">
     <div>
-      需求:
-      <div>1.房主的话,直接调用摄像头然后展示画面</div>
-      <div>
-        1.用户a进来了,默认不显示用户a的画面,用户a直接new
-        rtc,然后将自己的sdp房主,房主给他的rtc添加音视频轨,让用户a能看到房主的画面
-      </div>
-    </div>
-    <br />
-    <div class="content">
-      <div class="left">
-        房间号:<input
-          v-model="roomId"
-          type="text"
-          placeholder="输入房间号"
-        />
-        <button
-          ref="joinRef"
-          class="join-btn"
-          @click="join"
-        >
-          进入
-        </button>
-        <button
-          ref="leaveRef"
-          class="join-btn"
-          disabled
-          @click="leave"
-        >
-          退出
-        </button>
-        <div>socketId:{{ getSocketId() }}</div>
-        <div>ws状态:{{ networkStore.wsMap.get(roomId!)?.status }}</div>
-        <div>rtcStatus:{{ networkStore.rtcMap.get(roomId!)?.rtcStatus }}</div>
-        <button @click="startAction">开始直播</button>
-        <div>
-          <video
-            id="localVideo"
-            ref="localVideoRef"
-            autoplay
-            webkit-playsinline="true"
-            playsinline
-            x-webkit-airplay="allow"
-            x5-video-player-type="h5"
-            x5-video-player-fullscreen="true"
-            x5-video-orientation="portraint"
-            :muted="muted"
-            controls
-          ></video>
-        </div>
-      </div>
-      <div class="right">
-        <div>当前房间用户:</div>
-        <ul>
-          <li
-            v-for="item in userList"
-            :key="item"
+      <div class="content">
+        <div class="left">
+          房间号:<input
+            ref="roomIdRef"
+            v-model="roomId"
+            type="text"
+            placeholder="输入房间号"
+          />
+          <button
+            ref="joinRef"
+            class="join-btn"
+            @click="join"
           >
-            {{ item }}
+            进入
+          </button>
+          <button
+            ref="leaveRef"
+            class="join-btn"
+            disabled
+            @click="leave"
+          >
+            退出
+          </button>
+          <div>socketId:{{ getSocketId() }}</div>
+          <div>ws状态:{{ networkStore.wsMap.get(roomId!)?.status }}</div>
+          <div>
+            rtcStatus:{{ networkStore.rtcMap.get(roomId!)?.rtcStatus }}
+          </div>
+          <div v-if="id === '1234'">
+            选择类型:
             <button
+              :style="{
+                'margin-right': '10px',
+                background: currType === 1 ? 'skyblue' : 'inherit',
+              }"
+              @click="currType = 1"
+            >
+              摄像头
+            </button>
+            <button
+              :style="{
+                background: currType === 2 ? 'skyblue' : 'inherit',
+              }"
+              @click="currType = 2"
+            >
+              录屏
+            </button>
+          </div>
+
+          <div>
+            <video
+              id="localVideo"
+              ref="localVideoRef"
+              autoplay
+              webkit-playsinline="true"
+              playsinline
+              x-webkit-airplay="allow"
+              x5-video-player-type="h5"
+              x5-video-player-fullscreen="true"
+              x5-video-orientation="portraint"
+              :muted="muted"
+            ></video>
+          </div>
+        </div>
+
+        <div class="right">
+          <div>当前在线用户:</div>
+          <ul>
+            <li
+              v-for="(item, index) in userList"
+              :key="index"
+            >
+              {{ item.id }}
+              <!-- <button
               v-if="item !== getSocketId()"
               @click="sendOffer"
             >
               开始视频
-            </button>
-          </li>
-        </ul>
+            </button> -->
+            </li>
+          </ul>
+        </div>
       </div>
     </div>
   </div>
@@ -74,6 +90,7 @@
 
 <script lang="ts" setup>
 import { onMounted, ref } from 'vue';
+import { useRoute } from 'vue-router';
 
 import { WebRTCClass } from '@/network/webRtc';
 import {
@@ -86,19 +103,21 @@ import { useNetworkStore } from '@/store/network';
 
 const networkStore = useNetworkStore();
 
+const roomIdRef = ref<HTMLInputElement>();
 const joinRef = ref<HTMLButtonElement>();
 const leaveRef = ref<HTMLButtonElement>();
-const roomId = ref<string>('123456');
-const instance = ref<WebSocketClass>();
-const userList = ref<string[]>([]);
+const roomId = ref<string>('19990507');
+const websocketInstant = ref<WebSocketClass>();
+const userList = ref<{ id: string; rooms: string[] }[]>([]);
 const muted = ref(true);
 const localVideoRef = ref<HTMLVideoElement>();
 const localStream = ref();
-
+const currType = ref(1); // 1:摄像头,2:录屏
 function getSocketId() {
   return networkStore.wsMap.get(roomId.value!)?.socketIo?.id;
 }
-
+const id = ref('');
+const route = useRoute();
 interface IOffer {
   socketId: string;
   roomId: string;
@@ -113,125 +132,112 @@ interface ICandidate {
 }
 
 onMounted(() => {
+  console.log(route.query, '----');
+  id.value = route.query.id as string;
+  websocketInstant.value = new WebSocketClass({
+    roomId: roomId.value,
+    url:
+      process.env.NODE_ENV === 'development'
+        ? 'ws://localhost:4300'
+        : 'wss://live.hsslive.cn',
+  });
+  websocketInstant.value.update();
+  initReceive();
+
   localVideoRef.value?.addEventListener('loadstart', () => {
     console.warn('视频流-loadstart');
+    const rtc = networkStore.rtcMap.get(roomId.value);
+    if (!rtc) return;
+    rtc.rtcStatus.loadstart = true;
+    rtc.update();
   });
   localVideoRef.value?.addEventListener('loadedmetadata', () => {
     console.warn('视频流-loadedmetadata');
+    const rtc = networkStore.rtcMap.get(roomId.value);
+    if (!rtc) return;
+    rtc.rtcStatus.loadedmetadata = true;
+    rtc.update();
   });
 });
 
-// Handles start button action: creates local MediaStream.
-function startAction() {
-  // WARN navigator.mediaDevices在localhost和https才能用,http://192.168.1.103:8000局域网用不了
-  navigator.mediaDevices
-    .getUserMedia({ video: true, audio: true })
-    .then((event) => {
-      console.log('getUserMedia成功', event);
-      if (!localVideoRef.value) return;
-      localVideoRef.value.srcObject = event;
-      localStream.value = event;
-      // event.getTracks().forEach((track) => {
-      //   console.log('tracktrack', track, event);
-      //   networkStore.rtcMap.get(roomId.value)?.myAddTrack(track, event);
-      // });
-    })
-    .catch((err) => {
-      console.log('getUserMedia失败', err);
-    });
-}
-async function sendOffer() {
-  if (!instance.value) return;
-  const localDesc = await networkStore.rtcMap.get(roomId.value)?.createOffer();
-  console.warn('发送offer');
-  instance.value.socketIo?.emit(wsMsgType.offer, {
-    socketId: getSocketId(),
-    roomId: roomId.value,
-    sdp: localDesc,
-  });
-}
-async function sendAnswer(sdp) {
-  if (!instance.value) return;
-  console.warn('发送answer');
-  const createOffer = await networkStore.rtcMap.get(roomId.value)?.rtcStatus
-    .createOffer;
-  if (!createOffer) {
-    await networkStore.rtcMap.get(roomId.value)?.createOffer();
-  }
-  instance.value.socketIo?.emit(wsMsgType.answer, {
-    socketId: getSocketId(),
-    roomId: roomId.value,
-    sdp,
-  });
-}
-
 function join() {
   console.log('join的房间号', roomId.value);
   if (!roomId.value) {
     console.error('房间号不能为空!');
+    alert('房间号不能为空!');
     return;
   }
-  instance.value = new WebSocketClass({
-    roomId: roomId.value,
-    url: 'ws://localhost:4300',
+  if (joinRef.value && leaveRef.value && roomIdRef.value) {
+    roomIdRef.value.disabled = true;
+    joinRef.value.disabled = true;
+    leaveRef.value.disabled = false;
+  }
+  const instance = networkStore.wsMap.get(roomId.value);
+  if (!instance?.socketIo) return;
+  instance.socketIo.emit(wsMsgType.join, {
+    roomId: instance.roomId,
   });
+  if (id.value === '1234') {
+    if (currType.value === 1) {
+      startMediaDevices();
+    } else if (currType.value === 2) {
+      startGetDisplayMedia();
+    }
+  }
+}
 
-  if (!instance.value.socketIo) return;
+function initReceive() {
+  const instance = websocketInstant.value;
+  if (!instance?.socketIo) return;
   // websocket连接成功
-  instance.value.socketIo.on(wsConnectStatus.connect, () => {
-    if (!instance.value) return;
-    console.log('【websocket】websocket连接成功', instance.value.socketIo?.id);
-    if (!instance.value) return;
-    instance.value.status = statusEnum.connect;
-    if (joinRef.value && leaveRef.value) {
-      joinRef.value.disabled = true;
-      leaveRef.value.disabled = false;
-    }
-    instance.value.update();
-    instance.value.socketIo?.emit(wsMsgType.join, {
-      roomId: instance.value.roomId,
-    });
+  instance.socketIo.on(wsConnectStatus.connect, () => {
+    console.log('【websocket】websocket连接成功', instance.socketIo?.id);
+    if (!instance) return;
+    instance.status = statusEnum.connect;
+    instance.update();
+  });
+
+  // 当前所有在线用户
+  instance.socketIo.on(wsMsgType.liveUser, (data) => {
+    console.log('【websocket】当前所有在线用户');
+    if (!instance) return;
+    userList.value = data;
   });
 
   // websocket连接断开
-  instance.value.socketIo.on(wsConnectStatus.disconnect, () => {
-    console.log('【websocket】websocket连接断开', instance.value);
-    if (!instance.value) return;
-    instance.value.status = statusEnum.disconnect;
-    instance.value.update();
+  instance.socketIo.on(wsConnectStatus.disconnect, () => {
+    console.log('【websocket】websocket连接断开', instance);
+    if (!instance) return;
+    instance.status = statusEnum.disconnect;
+    instance.update();
   });
 
   // 收到offer
-  instance.value.socketIo.on(wsMsgType.offer, async (data: IOffer) => {
+  instance.socketIo.on(wsMsgType.offer, async (data: IOffer) => {
     console.warn('【websocket】收到offer', data);
-    if (!instance.value) return;
+    if (!instance) return;
     const rtc = networkStore.rtcMap.get(roomId.value);
     if (data.socketId !== getSocketId()) {
-      console.log('收到offer,并且这个offer不是我发的');
-      // const createOffer = await rtc?.rtcStatus.createOffer;
-      // if (!createOffer) {
-      //   console.log('没有创建offer成功');
-      rtc?.setRemoteDescription(data.sdp);
-      const sdp = await rtc?.createAnswer();
-      console.log(sdp, 'lllll');
-      instance.value.socketIo?.emit(wsMsgType.answer, {
-        socketId: getSocketId(),
-        roomId: roomId.value,
-        sdp,
-      });
-      // }
-      // setTimeout(() => {
-      //   rtc?.setRemoteDescription(data.sdp);
-      // }, 500);
+      console.log('收到offer,并且这个offer不是我发的', data);
+
+      setTimeout(async () => {
+        await rtc?.setRemoteDescription(data.sdp);
+        const sdp = await rtc?.createAnswer();
+        instance.socketIo?.emit(wsMsgType.answer, {
+          socketId: getSocketId(),
+          roomId: roomId.value,
+          sdp,
+        });
+      }, 500);
     } else {
       console.log('收到offer,并且这个offer是我发的');
     }
   });
 
   // 收到answer
-  instance.value.socketIo.on(wsMsgType.answer, async (data: IOffer) => {
+  instance.socketIo.on(wsMsgType.answer, async (data: IOffer) => {
     console.warn('【websocket】收到answer', data);
-    if (!instance.value) return;
+    if (!instance) return;
     if (!networkStore.rtcMap.get(roomId.value)?.rtcStatus.createOffer) return;
     if (data.socketId !== getSocketId()) {
       console.log('不是我发的answer');
@@ -246,9 +252,9 @@ function join() {
   });
 
   // 收到candidate
-  instance.value.socketIo.on(wsMsgType.candidate, (data: ICandidate) => {
+  instance.socketIo.on(wsMsgType.candidate, (data: ICandidate) => {
     console.warn('【websocket】收到candidate', data);
-    if (!instance.value) return;
+    if (!instance) return;
     const rtc = networkStore.rtcMap.get(roomId.value);
     if (!rtc) return;
     if (data.socketId !== getSocketId()) {
@@ -259,11 +265,12 @@ function join() {
           sdpMLineIndex: data.sdpMLineIndex,
           candidate: data.candidate,
         });
-        console.log('addIceCandidateaddIceCandidate');
         rtc.peerConnection
           ?.addIceCandidate(candidate)
           .then(() => {
             console.log('candidate成功');
+            rtc.rtcStatus.icecandidate = true;
+            rtc.update();
           })
           .catch((err) => {
             console.error('candidate失败', err);
@@ -275,70 +282,134 @@ function join() {
   });
 
   // 用户加入房间
-  instance.value.socketIo.on(wsMsgType.join, (data) => {
+  instance.socketIo.on(wsMsgType.join, (data) => {
     console.log('【websocket】用户加入房间', data);
-    if (!instance.value) return;
+    if (!instance) return;
   });
 
   // 用户加入房间
-  instance.value.socketIo.on(wsMsgType.joined, (data) => {
+  instance.socketIo.on(wsMsgType.joined, (data) => {
     console.log('【websocket】用户加入房间完成', data);
-    if (!instance.value) return;
-    userList.value.push(instance.value.socketIo?.id!);
+    if (!instance) return;
     console.warn('开始new WebRTCClass');
     const rtc = new WebRTCClass({ roomId: roomId.value });
-    networkStore.updateRtcMap(roomId.value, rtc);
-    // const localDesc = await rtc.createOffer();
-    // console.log(localDesc);
-    // instance.value.socketIo?.emit(wsMsgType.offer, {
-    //   sdp: localDesc,
-    // });
-    if (userList.value.length > 1) {
-      sendOffer();
-    }
+    rtc.rtcStatus.joined = true;
+    rtc.update();
+    // if (userList.value.length > 1) {
+    //   sendOffer();
+    // }
   });
 
   // 其他用户加入房间
-  instance.value.socketIo.on(wsMsgType.otherJoin, (data) => {
+  instance.socketIo.on(wsMsgType.otherJoin, (data) => {
     console.log('【websocket】其他用户加入房间', data);
-    if (!instance.value) return;
-    userList.value.push(data.socketId);
+    if (!instance) return;
+    console.log('加轨');
     // sendAnswer(networkStore.rtcMap.get(roomId.value)?.localDescription);
-    console.log(localStream.value.getTracks(), 888);
-    localStream.value.getTracks().forEach((track) => {
-      console.log('tracktrack', track, localStream.value);
-      networkStore.rtcMap
-        .get(roomId.value)
-        ?.myAddTrack(track, localStream.value);
-    });
+    // localStream.value.getTracks().forEach((track) => {
+    //   networkStore.rtcMap
+    //     .get(roomId.value)
+    //     ?.myAddTrack(track, localStream.value);
+    // });
     sendOffer();
   });
 
   // 用户离开房间
-  instance.value.socketIo.on(wsMsgType.leave, (data) => {
+  instance.socketIo.on(wsMsgType.leave, (data) => {
     console.log('【websocket】用户离开房间', data);
-    if (!instance.value) return;
-    instance.value.socketIo?.emit(wsMsgType.leave, {
-      roomId: instance.value.roomId,
+    if (!instance) return;
+    instance.socketIo?.emit(wsMsgType.leave, {
+      roomId: instance.roomId,
     });
   });
 
   // 用户离开房间完成
-  instance.value.socketIo.on(wsMsgType.leaved, (data) => {
+  instance.socketIo.on(wsMsgType.leaved, (data) => {
     console.log('【websocket】用户离开房间完成', data);
-    if (!instance.value) return;
-    instance.value.close();
+    if (!instance) return;
+    instance.close();
   });
 }
 
+function startMediaDevices() {
+  currType.value = 1;
+  // WARN navigator.mediaDevices在localhost和https才能用,http://192.168.1.103:8000局域网用不了
+  navigator.mediaDevices
+    .getUserMedia({ video: true, audio: true })
+    .then((event) => {
+      console.log('getUserMedia成功', event);
+      if (!localVideoRef.value) return;
+      localVideoRef.value.srcObject = event;
+      localStream.value = event;
+      localStream.value.getTracks().forEach((track) => {
+        networkStore.rtcMap
+          .get(roomId.value)
+          ?.myAddTrack(track, localStream.value);
+      });
+    })
+    .catch((err) => {
+      console.log('getUserMedia失败', err);
+    });
+}
+
+function startGetDisplayMedia() {
+  currType.value = 2;
+  // WARN navigator.mediaDevices.getDisplayMedia在localhost和https才能用,http://192.168.1.103:8000局域网用不了
+  navigator.mediaDevices
+    .getDisplayMedia({ video: true, audio: true })
+    .then((event) => {
+      console.log('getDisplayMedia成功', event);
+      if (!localVideoRef.value) return;
+      localVideoRef.value.srcObject = event;
+      localStream.value = event;
+      localStream.value.getTracks().forEach((track) => {
+        networkStore.rtcMap
+          .get(roomId.value)
+          ?.myAddTrack(track, localStream.value);
+      });
+    })
+    .catch((err) => {
+      console.log('getDisplayMedia失败', err);
+    });
+}
+
+async function sendOffer() {
+  if (!websocketInstant.value) return;
+  const localDesc = await networkStore.rtcMap.get(roomId.value)?.createOffer();
+  console.log('sendOffer', localDesc);
+
+  const data = {
+    socketId: getSocketId(),
+    roomId: roomId.value,
+    sdp: localDesc,
+  };
+  console.warn('【websocket】发送offer', data);
+  websocketInstant.value.socketIo?.emit(wsMsgType.offer, data);
+}
+// async function sendAnswer(sdp) {
+//   if (!websocketInstant.value) return;
+//   console.warn('发送answer');
+//   const createOffer = await networkStore.rtcMap.get(roomId.value)?.rtcStatus
+//     .createOffer;
+//   if (!createOffer) {
+//     await networkStore.rtcMap.get(roomId.value)?.createOffer();
+//   }
+//   websocketInstant.value.socketIo?.emit(wsMsgType.answer, {
+//     socketId: getSocketId(),
+//     roomId: roomId.value,
+//     sdp,
+//   });
+// }
+
 function leave() {
-  if (joinRef.value && leaveRef.value) {
+  if (joinRef.value && leaveRef.value && roomIdRef.value) {
+    roomIdRef.value.disabled = false;
     joinRef.value.disabled = false;
     leaveRef.value.disabled = true;
   }
-  if (!instance.value) return;
-  instance.value.socketIo?.emit(wsMsgType.leave, {
-    roomId: instance.value.roomId,
+  if (!websocketInstant.value) return;
+  websocketInstant.value.socketIo?.emit(wsMsgType.leave, {
+    roomId: websocketInstant.value.roomId,
   });
 }
 </script>
@@ -347,7 +418,8 @@ function leave() {
 .home-wrap {
   padding: 10px;
   video {
-    width: 500px;
+    width: 800px;
+    background-color: skyblue;
   }
   .content {
     display: flex;