Bladeren bron

feat: 优化

shuisheng 1 jaar geleden
bovenliggende
commit
2585761028

+ 1 - 1
src/constant.ts

@@ -22,7 +22,7 @@ export const appBuildInfo =
 
 export const WEBSOCKET_URL =
   process.env.NODE_ENV === 'development'
-    ? `ws://localhost:4300`
+    ? `ws://192.168.1.127:4300` // `ws://localhost:4300`
     : `wss://srs-pull.${prodDomain}`;
 
 export const AXIOS_BASEURL =

+ 12 - 3
src/hooks/use-pull.ts

@@ -34,6 +34,7 @@ export function usePull(roomId: string) {
   const hlsurl = ref('');
   const videoWrapRef = ref<HTMLDivElement>();
   const videoResolution = ref();
+  const isRemoteDesk = ref(false);
   const videoElArr = ref<HTMLVideoElement[]>([]);
   const remoteVideo = ref<HTMLElement[]>([]);
   const {
@@ -152,6 +153,7 @@ export function usePull(roomId: string) {
         videoLoading.value = false;
       }
       if (
+        isRemoteDesk.value ||
         appStore.liveRoomInfo?.type === LiveRoomTypeEnum.wertc_meeting_one ||
         appStore.liveRoomInfo?.type === LiveRoomTypeEnum.wertc_live ||
         appStore.liveRoomInfo?.type === LiveRoomTypeEnum.pk ||
@@ -273,7 +275,8 @@ export function usePull(roomId: string) {
             handlePlay(liveRoomInfo!);
           }
         }
-      } else {
+      }
+      if (!roomLiving.value) {
         closeRtc();
         handleStopDrawing();
       }
@@ -359,12 +362,18 @@ export function usePull(roomId: string) {
     }
   );
 
-  function initPull(autolay = true) {
-    autoplayVal.value = autolay;
+  function initPull(data: { autolay?: boolean; isRemoteDesk?: boolean }) {
+    if (data.autolay === undefined) {
+      autoplayVal.value = true;
+    } else {
+      autoplayVal.value = data.autolay;
+    }
     if (autoplayVal.value) {
       videoLoading.value = true;
     }
+    isRemoteDesk.value = !!data.isRemoteDesk;
     initWs({
+      isRemoteDesk: data.isRemoteDesk,
       roomId,
       isAnchor: false,
     });

+ 1 - 0
src/hooks/use-push.ts

@@ -192,6 +192,7 @@ export function usePush() {
 
   function connectWs() {
     initWs({
+      isRemoteDesk: false,
       isAnchor: true,
       roomId: roomId.value,
       currentMaxBitrate: currentMaxBitrate.value,

+ 60 - 1
src/hooks/use-websocket.ts

@@ -39,8 +39,10 @@ import {
   WsMsgTypeEnum,
   WsOfferType,
   WsOtherJoinType,
+  WsRemoteDeskMoveMsgType,
   WsRoomLivingType,
   WsStartLiveType,
+  WsStartRemoteDesk,
   WsUpdateJoinInfoType,
 } from '@/types/websocket';
 import { createNullVideo, handleUserMedia } from '@/utils';
@@ -49,6 +51,8 @@ import {
   prettierReceiveWsMsg,
 } from '@/utils/network/webSocket';
 
+import { useWebRtcRemoteDesk } from './webrtc/remoteDesk';
+
 export const useWebsocket = () => {
   const route = useRoute();
   const appStore = useAppStore();
@@ -56,6 +60,8 @@ export const useWebsocket = () => {
   const networkStore = useNetworkStore();
 
   const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+  const { updateWebRtcRemoteDeskConfig, webRtcRemoteDesk } =
+    useWebRtcRemoteDesk();
   const { updateWebRtcMeetingPkConfig, webRtcMeetingPk } = useWebRtcMeetingPk();
   const { updateWebRtcSrsConfig, webRtcSrs } = useWebRtcSrs();
   const { updateWebRtcTencentcloudCssConfig, webRtcTencentcloudCss } =
@@ -71,6 +77,7 @@ export const useWebsocket = () => {
   const roomId = ref('');
   const roomLiving = ref(false);
   const isAnchor = ref(false);
+  const isRemoteDesk = ref(false);
   const anchorInfo = ref<IUser>();
   const anchorSocketId = ref('');
   const canvasVideoStream = ref<MediaStream>();
@@ -234,6 +241,7 @@ export const useWebsocket = () => {
       requestId: getRandomString(8),
       msgType: WsMsgTypeEnum.join,
       data: {
+        isRemoteDesk: isRemoteDesk.value,
         socket_id: mySocketId.value,
         live_room_id: Number(roomId.value),
         user_info: userStore.userInfo,
@@ -247,7 +255,7 @@ export const useWebsocket = () => {
     // websocket连接成功
     ws.socketIo.on(WsConnectStatusEnum.connect, () => {
       prettierReceiveWsMsg(WsConnectStatusEnum.connect, ws.socketIo);
-      handleHeartbeat(ws.socketIo!.id);
+      handleHeartbeat(ws.socketIo!.id!);
       if (!ws) return;
       connectStatus.value = WsConnectStatusEnum.connect;
       ws.status = WsConnectStatusEnum.connect;
@@ -299,6 +307,15 @@ export const useWebsocket = () => {
       }).catch(() => {});
     });
 
+    // 收到startRemoteDesk
+    ws.socketIo.on(WsMsgTypeEnum.startRemoteDesk, (data: WsStartRemoteDesk) => {
+      console.log('收到startRemoteDesk', data);
+      if (data.data.receiver === mySocketId.value) {
+        appStore.remoteDesk.startRemoteDesk = true;
+        appStore.remoteDesk.sender = data.data.sender;
+      }
+    });
+
     // 收到srsOffer
     ws.socketIo.on(WsMsgTypeEnum.srsOffer, (data: WsOfferType['data']) => {
       console.log('收到srsOffer', data);
@@ -329,6 +346,36 @@ export const useWebsocket = () => {
       WsMsgTypeEnum.nativeWebRtcOffer,
       async (data: WsOfferType['data']) => {
         console.log('收到nativeWebRtcOffer', data);
+        if (data.isRemoteDesk) {
+          if (data.receiver === mySocketId.value) {
+            console.warn('是发给我的nativeWebRtcOffer');
+            if (networkStore.rtcMap.get(data.sender)) {
+              return;
+            }
+            updateWebRtcRemoteDeskConfig({
+              roomId: roomId.value,
+              userStream: userStream.value,
+              anchorStream: canvasVideoStream.value,
+            });
+            webRtcRemoteDesk.newWebRtc({
+              // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者;
+              // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆
+              sender: mySocketId.value,
+              receiver: data.sender,
+              videoEl: createNullVideo(),
+            });
+            await webRtcRemoteDesk.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');
+          }
+          return;
+        }
+        console.log('kkkkkkkkkkk');
         if (
           data.live_room.type === LiveRoomTypeEnum.pk ||
           data.live_room.type === LiveRoomTypeEnum.tencent_css_pk
@@ -476,6 +523,14 @@ export const useWebsocket = () => {
       }
     );
 
+    // 收到remoteDeskMoveMsg
+    ws.socketIo.on(
+      WsMsgTypeEnum.remoteDeskMoveMsg,
+      (data: WsRemoteDeskMoveMsgType['data']) => {
+        console.log('收到remoteDeskMoveMsg', data);
+      }
+    );
+
     // 主播正在直播
     ws.socketIo.on(
       WsMsgTypeEnum.roomLiving,
@@ -740,12 +795,16 @@ export const useWebsocket = () => {
   function initWs(data: {
     isAnchor: boolean;
     roomId: string;
+    isRemoteDesk?: boolean;
     currentResolutionRatio?: number;
     currentMaxFramerate?: number;
     currentMaxBitrate?: number;
   }) {
     roomId.value = data.roomId;
     isAnchor.value = data.isAnchor;
+    if (data.isRemoteDesk) {
+      isRemoteDesk.value = data.isRemoteDesk;
+    }
     if (data.currentMaxBitrate) {
       currentMaxBitrate.value = data.currentMaxBitrate;
     }

+ 156 - 0
src/hooks/webrtc/remoteDesk.ts

@@ -0,0 +1,156 @@
+import { getRandomString } from 'billd-utils';
+import { ref } from 'vue';
+
+import { useRTCParams } from '@/hooks/use-rtcParams';
+import { useAppStore } from '@/store/app';
+import { useNetworkStore } from '@/store/network';
+import { WsAnswerType, WsMsgTypeEnum, WsOfferType } from '@/types/websocket';
+import { WebRTCClass } from '@/utils/network/webRTC';
+
+export const useWebRtcRemoteDesk = () => {
+  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 anchorStream = ref<MediaStream>();
+  const userStream = ref<MediaStream>();
+
+  function updateWebRtcRemoteDeskConfig(data: {
+    roomId;
+    anchorStream;
+    userStream?;
+  }) {
+    roomId.value = data.roomId;
+    anchorStream.value = data.anchorStream;
+    userStream.value = data.userStream;
+  }
+
+  const webRtcRemoteDesk = {
+    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('remoteDesk的sendOffer', {
+        sender,
+        receiver,
+      });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = networkStore.rtcMap.get(receiver);
+        if (rtc) {
+          anchorStream.value?.getTracks().forEach((track) => {
+            if (anchorStream.value) {
+              console.log('remoteDesk的sendOffer插入track', track.kind, track);
+              rtc.peerConnection?.addTrack(track, anchorStream.value);
+            }
+          });
+          const offerSdp = await rtc.createOffer();
+          if (!offerSdp) {
+            console.error('remoteDesk的offerSdp为空');
+            return;
+          }
+          await rtc.setLocalDescription(offerSdp!);
+          networkStore.wsMap.get(roomId.value)?.send<WsOfferType['data']>({
+            requestId: getRandomString(8),
+            msgType: WsMsgTypeEnum.nativeWebRtcOffer,
+            data: {
+              isRemoteDesk: true,
+              live_room: appStore.liveRoomInfo!,
+              // @ts-ignore
+              live_room_id: roomId.value,
+              sender,
+              receiver,
+              sdp: offerSdp,
+            },
+          });
+        } else {
+          console.error('rtc不存在');
+        }
+      } catch (error) {
+        console.error('remoteDesk的sendOffer错误');
+        console.log(error);
+      }
+    },
+    /**
+     * 观众收到主播的offer,观众回复主播answer
+     */
+    sendAnswer: async ({
+      sdp,
+      sender,
+      receiver,
+    }: {
+      sdp: RTCSessionDescriptionInit;
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('remoteDesk的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);
+          userStream.value?.getTracks().forEach((track) => {
+            if (userStream.value) {
+              console.log('remoteDesk的sendAnswer插入track');
+              rtc.peerConnection?.addTrack(track, userStream.value);
+            }
+          });
+          const answerSdp = await rtc.createAnswer();
+          if (!answerSdp) {
+            console.error('remoteDesk的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('remoteDesk的sendAnswer错误');
+        console.log(error);
+      }
+    },
+  };
+
+  return { updateWebRtcRemoteDeskConfig, webRtcRemoteDesk };
+};

+ 11 - 2
src/layout/pc/head/index.vue

@@ -29,7 +29,7 @@
           >
             {{ t('layout.area') }}
           </a>
-          <a
+          <!-- <a
             class="item"
             :class="{
               active: router.currentRoute.value.name === routerName.shop,
@@ -37,7 +37,7 @@
             @click.prevent="router.push({ name: routerName.shop })"
           >
             {{ t('layout.shop') }}
-          </a>
+          </a> -->
           <a
             class="item"
             :href="COMMON_URL.admin"
@@ -56,6 +56,15 @@
               <div class="txt">new</div>
             </div>
           </a>
+          <a
+            class="item"
+            @click.prevent="router.push({ name: routerName.remoteDesktop })"
+          >
+            {{ t('layout.remoteDesktop') }}
+            <div class="badge">
+              <div class="txt">beta</div>
+            </div>
+          </a>
         </div>
       </div>
 

+ 1 - 0
src/locales/en/layout.ts

@@ -13,6 +13,7 @@ export default nameSpaceWrap('layout', {
   signin: 'Signin',
   deploy: 'Private Deploy',
   videoTools: 'Video Tool',
+  remoteDesktop: 'Remote Desktop',
   startLive: 'Start Live',
   login: 'Login',
   logout: 'Logout',

+ 1 - 0
src/locales/zh/layout.ts

@@ -13,6 +13,7 @@ export default nameSpaceWrap('layout', {
   signin: '签到',
   deploy: '私有化部署',
   videoTools: '视频工具',
+  remoteDesktop: '远程桌面',
   startLive: '我要开播',
   login: '登录',
   logout: '退出',

+ 6 - 0
src/router/index.ts

@@ -35,6 +35,7 @@ export const routerName = {
   order: 'order',
   wallet: 'wallet',
   shop: 'shop',
+  remoteDesktop: 'remoteDesktop',
   link: 'link',
   ad: 'ad',
   faq: 'faq',
@@ -121,6 +122,11 @@ export const defaultRoutes: RouteRecordRaw[] = [
         path: '/shop',
         component: () => import('@/views/shop/index.vue'),
       },
+      {
+        name: routerName.remoteDesktop,
+        path: '/remoteDesktop',
+        component: () => import('@/views/remoteDesktop/index.vue'),
+      },
       {
         name: routerName.profile,
         path: '/profile/:userId',

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

@@ -6,6 +6,10 @@ import { mobileRouterName } from '@/router';
 import { ILiveRoom } from '@/types/ILiveRoom';
 
 export type AppRootState = {
+  remoteDesk: {
+    sender: string;
+    startRemoteDesk: boolean;
+  };
   playing: boolean;
   videoRatio: number;
   normalVolume: number;
@@ -65,6 +69,10 @@ export type AppRootState = {
 export const useAppStore = defineStore('app', {
   state: (): AppRootState => {
     return {
+      remoteDesk: {
+        startRemoteDesk: false,
+        sender: '',
+      },
       playing: false,
       videoRatio: 16 / 9,
       videoControls: {

+ 21 - 0
src/types/websocket.ts

@@ -67,6 +67,13 @@ export enum WsMsgTypeEnum {
   srsAnswer = 'srsAnswer',
   srsCandidate = 'srsCandidate',
 
+  startRemoteDesk = 'startRemoteDesk',
+  remoteDeskMoveMsg = 'remoteDeskMoveMsg',
+
+  remoteDeskOffer = 'remoteDeskOffer',
+  remoteDeskAnswer = 'remoteDeskAnswer',
+  remoteDeskCandidate = 'remoteDeskCandidate',
+
   nativeWebRtcOffer = 'nativeWebRtcOffer',
   nativeWebRtcAnswer = 'nativeWebRtcAnswer',
   nativeWebRtcCandidate = 'nativeWebRtcCandidate',
@@ -121,6 +128,12 @@ export type WsRoomNoLiveType = IWsFormat<{
   live_room: ILiveRoom;
 }>;
 
+export type WsRemoteDeskMoveMsgType = IWsFormat<{
+  roomId: string;
+  sender: string;
+  receiver: string;
+}>;
+
 export interface IDanmu {
   msgType: DanmuMsgTypeEnum;
   msgIsFile: WsMessageMsgIsFileEnum;
@@ -196,6 +209,7 @@ export type WsJoinType = IWsFormat<{
   live_room?: ILiveRoom;
   anchor_info?: IUser;
   user_info?: IUser;
+  isRemoteDesk?: boolean;
   socket_list?: string[];
 }>;
 
@@ -223,12 +237,19 @@ export type WsMsrBlobType = IWsFormat<{
   max_delay: number;
 }>;
 
+export type WsStartRemoteDesk = IWsFormat<{
+  sender: string;
+  receiver: string;
+  roomId: string;
+}>;
+
 export type WsOfferType = IWsFormat<{
   live_room: ILiveRoom;
   sdp: any;
   sender: string;
   receiver: string;
   live_room_id: number;
+  isRemoteDesk?: boolean;
 }>;
 
 export type WsAnswerType = IWsFormat<{

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

@@ -328,7 +328,7 @@ async function getLiveRoomInfo() {
       } else {
         showPlayBtn.value = true;
       }
-      initPull(autoplayVal.value);
+      initPull({ autolay: autoplayVal.value });
     }
   } catch (error) {
     console.error(error);

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

@@ -509,7 +509,7 @@ onMounted(() => {
     height.value = res;
   }
   getBg();
-  initPull();
+  initPull({});
   getGiftRecord();
   getGiftGroupList();
   handleSendGetLiveUser(Number(roomId.value));

+ 93 - 0
src/views/remoteDesktop/index.vue

@@ -0,0 +1,93 @@
+<template>
+  <div>
+    <h1>当前是控制方</h1>
+    <h1>https://github.com/galaxy-s10/billd-desk</h1>
+    <div>
+      房间id:{{ roomId }},我的id:{{ mySocketId }},<n-button
+        @click="copyToClipBoard(mySocketId)"
+      >
+        复制
+      </n-button>
+    </div>
+    <n-input-group>
+      <n-input-group-label>被控id</n-input-group-label>
+      <n-input
+        :style="{ width: '200px' }"
+        placeholder="请输入被控id"
+        v-model:value="receiverId"
+      />
+      <n-button
+        type="primary"
+        @click="handleRemote"
+      >
+        开始远程
+      </n-button>
+      <n-button @click="handleMoveMouse">移动鼠标</n-button>
+    </n-input-group>
+    <div
+      class="wrap"
+      ref="remoteVideoRef"
+    ></div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { copyToClipBoard, getRandomString } from 'billd-utils';
+import { computed, onMounted, ref } from 'vue';
+
+import { usePull } from '@/hooks/use-pull';
+import { useNetworkStore } from '@/store/network';
+import {
+  WsMsgTypeEnum,
+  WsRemoteDeskMoveMsgType,
+  WsStartRemoteDesk,
+} from '@/types/websocket';
+
+const num = '123456';
+const networkStore = useNetworkStore();
+const { videoWrapRef, initPull } = usePull(num);
+const roomId = ref(num);
+const receiverId = ref('');
+const remoteVideoRef = ref<HTMLDivElement>();
+
+const mySocketId = computed(() => {
+  return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1';
+});
+
+onMounted(() => {
+  initPull({
+    isRemoteDesk: true,
+  });
+  videoWrapRef.value = remoteVideoRef.value;
+});
+
+function handleMoveMouse() {
+  networkStore.wsMap.get(roomId.value)?.send<WsRemoteDeskMoveMsgType['data']>({
+    requestId: getRandomString(8),
+    msgType: WsMsgTypeEnum.remoteDeskMoveMsg,
+    data: {
+      roomId: roomId.value,
+      sender: mySocketId.value,
+      receiver: receiverId.value,
+    },
+  });
+}
+
+function handleRemote() {
+  networkStore.wsMap.get(roomId.value)?.send<WsStartRemoteDesk['data']>({
+    requestId: getRandomString(8),
+    msgType: WsMsgTypeEnum.startRemoteDesk,
+    data: {
+      roomId: roomId.value,
+      sender: mySocketId.value,
+      receiver: receiverId.value,
+    },
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+.wrap {
+  display: inline-block;
+}
+</style>