tremble 3 年 前
コミット
54fddd7e66

+ 11 - 0
package-lock.json

@@ -12,6 +12,7 @@
         "clipboard": "^2.0.8",
         "core-js": "^3.8.3",
         "socket.io": "^4.5.1",
+        "trtc-js-sdk": "^4.13.0",
         "vue": "^3.2.36",
         "vuex": "^4.0.2"
       },
@@ -9047,6 +9048,11 @@
       "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
       "dev": true
     },
+    "node_modules/trtc-js-sdk": {
+      "version": "4.13.0",
+      "resolved": "https://registry.npmmirror.com/trtc-js-sdk/-/trtc-js-sdk-4.13.0.tgz",
+      "integrity": "sha512-Ks1bmm4O/7w3oGI1g6XNXDoT9Uyl5ibvxRuu1aXTgY/NDR/Ji8wKxNMYXMk0oKAPszPINOSv6ztAYy+qt89dQA=="
+    },
     "node_modules/tslib": {
       "version": "2.4.0",
       "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz",
@@ -16987,6 +16993,11 @@
       "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
       "dev": true
     },
+    "trtc-js-sdk": {
+      "version": "4.13.0",
+      "resolved": "https://registry.npmmirror.com/trtc-js-sdk/-/trtc-js-sdk-4.13.0.tgz",
+      "integrity": "sha512-Ks1bmm4O/7w3oGI1g6XNXDoT9Uyl5ibvxRuu1aXTgY/NDR/Ji8wKxNMYXMk0oKAPszPINOSv6ztAYy+qt89dQA=="
+    },
     "tslib": {
       "version": "2.4.0",
       "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz",

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "clipboard": "^2.0.8",
     "core-js": "^3.8.3",
     "socket.io": "^4.5.1",
+    "trtc-js-sdk": "^4.13.0",
     "vue": "^3.2.36",
     "vuex": "^4.0.2"
   },

+ 1 - 0
public/index.html

@@ -24,6 +24,7 @@
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/vconsole.js"></script>
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/swiper/swiper-bundle.min.js"></script>
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/socket.io.min.js"></script>
+        <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/lib-generate-test-usersig.min.js"></script>
 
 
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/jweixin-1.6.0.js"></script>

ファイルの差分が大きいため隠しています
+ 2 - 0
public/viewer/static/lib/lib-generate-test-usersig.min.js


+ 3 - 0
src/assets/images/icon/camera-mute.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-camera-video-off-fill" viewBox="0 0 16 16">
+  <path fill-rule="evenodd" d="M10.961 12.365a1.99 1.99 0 0 0 .522-1.103l3.11 1.382A1 1 0 0 0 16 11.731V4.269a1 1 0 0 0-1.406-.913l-3.111 1.382A2 2 0 0 0 9.5 3H4.272l6.69 9.365zm-10.114-9A2.001 2.001 0 0 0 0 5v6a2 2 0 0 0 2 2h5.728L.847 3.366zm9.746 11.925-10-14 .814-.58 10 14-.814.58z"/>
+</svg>

+ 3 - 0
src/assets/images/icon/camera.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-camera-video-fill" viewBox="0 0 16 16">
+  <path fill-rule="evenodd" d="M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.462a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5z"/>
+</svg>

+ 3 - 0
src/assets/images/icon/clippy.svg

@@ -0,0 +1,3 @@
+<svg height="1024" width="896" xmlns="http://www.w3.org/2000/svg">
+  <path d="M128 768h256v64H128v-64z m320-384H128v64h320v-64z m128 192V448L384 640l192 192V704h320V576H576z m-288-64H128v64h160v-64zM128 704h160v-64H128v64z m576 64h64v128c-1 18-7 33-19 45s-27 18-45 19H64c-35 0-64-29-64-64V192c0-35 29-64 64-64h192C256 57 313 0 384 0s128 57 128 128h192c35 0 64 29 64 64v320h-64V320H64v576h640V768zM128 256h512c0-35-29-64-64-64h-64c-35 0-64-29-64-64s-29-64-64-64-64 29-64 64-29 64-64 64h-64c-35 0-64 29-64 64z" />
+</svg>

+ 4 - 0
src/assets/images/icon/mic-mute.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-mic-mute-fill" viewBox="0 0 16 16">
+  <path d="M13 8c0 .564-.094 1.107-.266 1.613l-.814-.814A4.02 4.02 0 0 0 12 8V7a.5.5 0 0 1 1 0v1zm-5 4c.818 0 1.578-.245 2.212-.667l.718.719a4.973 4.973 0 0 1-2.43.923V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 1 0v1a4 4 0 0 0 4 4zm3-9v4.879L5.158 2.037A3.001 3.001 0 0 1 11 3z"/>
+  <path d="M9.486 10.607 5 6.12V8a3 3 0 0 0 4.486 2.607zm-7.84-9.253 12 12 .708-.708-12-12-.708.708z"/>
+</svg>

+ 4 - 0
src/assets/images/icon/mic.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-mic-fill" viewBox="0 0 16 16">
+  <path d="M5 3a3 3 0 0 1 6 0v5a3 3 0 0 1-6 0V3z"/>
+  <path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/>
+</svg>

+ 1 - 1
src/components/Controls/Panel/Main.vue

@@ -32,7 +32,7 @@
 
         <li>
           <ui-icon
-            type="guided_shopping"
+            type="shopping"
             @click.stop="
               onClickMenu({
                 icon: 'shopping',

+ 8 - 2
src/components/RTC/PageRtcLive.vue

@@ -5,6 +5,8 @@
       <span>{{ user_list.length }}观看</span>
     </div>
     <chat ref="chat$" v-show="chatShow" :chatList="chatList" :user_info="user_info"></chat>
+
+    <Trtccom v-if="showTest" />
     <div class="videoBox userVideo" id="userVideo" v-show="!hideVideoTop">
       <img v-if="!userVideoShow" :src="require('@/assets/images/rtcLive/avatar_small@2x.png')" alt="" />
       <img v-if="!userVideoShow" class="loadingTip" :src="require('@/assets/images/rtcLive/loading@2x.png')" alt="" />
@@ -121,6 +123,7 @@ import common from "@/utils/common";
 
 import { mapGetters } from "vuex";
 import chat from "./chat/chat.vue";
+import Trtccom from "./Trtccom.vue";
 import browser from "@/utils/browser";
 
 const emit = defineEmits(["openDialog"]);
@@ -170,6 +173,7 @@ const showMember = ref(false);
 const animateActive = ref(false);
 
 const socket = computed(() => store.getters["rtc/socket"]);
+const showTest = ref(browser.getURLParam("test"));
 
 const myVideoShow = ref(false);
 const userVideoShow = ref(false);
@@ -177,6 +181,8 @@ const user_info = ref({});
 const user_list = ref([]);
 const mode = ref(browser.getURLParam("mode"));
 const role = ref(browser.getURLParam("role"));
+
+
 const isJoined = ref(false);
 
 const paint = reactive({});
@@ -362,8 +368,8 @@ const onMemberLeave = (res) => {
     return tempArr;
   }, []);
   if (res.user.Role == "leader") {
-    Dialog.toast({ content: `房间解散` });
-    emit("closeSocket");
+    Dialog.toast({ content: `主持人离开了房间` });
+    // emit("closeSocket");
   }
 };
 

+ 328 - 0
src/components/RTC/Trtccom.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="trtccom" v-if="show">
+    <Device @switchDevice="switchDevice" v-if="show" />
+    <div class="local" id="local" v-if="isJoined">
+      <div class="tag">
+        <div :class="audioMuted ? 'muteAudio' : 'unmuteAudio'" @click="muteAudio"></div>
+        <div :class="videoMuted ? 'muteVideo' : 'unmuteVideo'" @click="muteVideo"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import TRTC, { Client, LocalStream } from "trtc-js-sdk";
+import { Dialog } from "@/global_components/";
+import { ref, computed, watch } from "vue";
+import Device from "./trtc/Device";
+import { useStore } from "vuex";
+const store = useStore();
+
+const show = ref(false);
+const isJoined = computed(() => store.getters["rtc/isJoined"]);
+const isPublished = computed(() => store.getters["rtc/isPublished"]);
+
+let localClient = "";
+let localStream = "";
+let shareClient = "";
+const audioMuted = ref(false);
+const videoMuted = ref(false);
+
+TRTC.checkSystemRequirements().then((checkResult) => {
+  console.log(checkResult.result, "checkResult.result");
+  if (!checkResult.result) {
+    Dialog.toast({ content: `您的設備不支持音視頻通訊`, type: "error" });
+  } else {
+    show.value = true;
+  }
+});
+
+async function createLocalStream() {
+  try {
+    localStream = TRTC.createStream({
+      userId: store.getters["rtc/userId"],
+      audio: true,
+      video: true,
+      cameraId: store.getters["rtc/videoDeviceId"],
+      microphoneId: store.getters["rtc/audioDeviceId"],
+    });
+    localStream.setVideoProfile("480p");
+
+    await localStream.initialize();
+
+    localStream
+      .play("local")
+      .then(() => {
+        // addLocalControlView();
+      })
+      .catch((e) => {});
+  } catch (error) {}
+}
+
+async function handleJoin() {
+  if (!store.commit("rtc/getInitParamsStates")) {
+    return;
+  }
+  const userSig = store.commit("rtc/getUserSig");
+
+  try {
+    localClient = TRTC.createClient({
+      mode: "rtc",
+      sdkAppId: parseInt(store.getters["rtc/sdkAppId"], 10),
+      userId: store.getters["rtc/userId"],
+      userSig,
+    });
+    installEventHandlers();
+    await localClient.join({ roomId: parseInt(store.getters["rtc/roomId"], 10) });
+    store.commit("rtc/setIsJoined", true);
+    inviteLink.value = store.commit("rtc/createShareLink");
+  } catch (error) {}
+
+  await createLocalStream();
+  await handlePublish();
+}
+
+async function handlePublish() {
+  if (!isJoined.value) {
+    return;
+  }
+  if (isPublished.value) {
+    return;
+  }
+
+  try {
+    await localClient.publish(localStream);
+    store.commit("rtc/setIsPublished", true);
+  } catch (error) {}
+}
+
+async function handleUnpublish() {
+  if (!isJoined.value) {
+    return;
+  }
+  if (!isPublished.value) {
+    return;
+  }
+  try {
+    await localClient.unpublish(localStream);
+    store.commit("rtc/setIsPublished", false);
+  } catch (error) {}
+}
+
+async function handleLeave() {
+  if (!isJoined.value) {
+    return;
+  }
+  if (isPublished.value) {
+    await handleUnpublish();
+  }
+  try {
+    uninstallEventHandlers();
+    await localClient.leave();
+    store.commit("rtc/setIsJoined", false);
+    if (localStream) {
+      localStream.stop();
+      localStream.close();
+      localStream = null;
+    }
+  } catch (error) {}
+}
+
+function installEventHandlers() {
+  if (!localClient) {
+    return;
+  }
+  localClient.on("error", handleError);
+  localClient.on("client-banned", handleBanned);
+  localClient.on("peer-join", handlePeerJoin);
+  localClient.on("peer-leave", handlePeerLeave);
+  localClient.on("stream-added", handleStreamAdded);
+  localClient.on("stream-subscribed", handleStreamSubscribed);
+  localClient.on("stream-removed", handleStreamRemoved);
+  localClient.on("stream-updated", handleStreamUpdated);
+  localClient.on("mute-video", handleMuteVideo);
+  localClient.on("mute-audio", handleMuteAudio);
+  localClient.on("unmute-video", handleUnmuteVideo);
+  localClient.on("unmute-audio", handleUnmuteAudio);
+}
+
+function uninstallEventHandlers() {
+  if (!localClient) {
+    return;
+  }
+  localClient.off("error", handleError);
+  localClient.off("error", handleError);
+  localClient.off("client-banned", handleBanned);
+  localClient.off("peer-join", handlePeerJoin);
+  localClient.off("peer-leave", handlePeerLeave);
+  localClient.off("stream-added", handleStreamAdded);
+  localClient.off("stream-subscribed", handleStreamSubscribed);
+  localClient.off("stream-removed", handleStreamRemoved);
+  localClient.off("stream-updated", handleStreamUpdated);
+  localClient.off("mute-video", handleMuteVideo);
+  localClient.off("mute-audio", handleMuteAudio);
+  localClient.off("unmute-video", handleUnmuteVideo);
+  localClient.off("unmute-audio", handleUnmuteAudio);
+}
+
+function handleMuteVideo(event) {
+  console.log(`[${event.userId}] mute video`);
+}
+
+function handleMuteAudio(event) {
+  console.log(`[${event.userId}] mute audio`);
+}
+
+function handleUnmuteVideo(event) {
+  console.log(`[${event.userId}] unmute video`);
+}
+
+function handleUnmuteAudio(event) {
+  console.log(`[${event.userId}] unmute audio`);
+}
+
+function handleError(error) {
+  console.log(`LocalClient error: ${error.message_}`);
+}
+
+function handleBanned(error) {
+  console.log(`Client has been banned for ${error.message_}`);
+}
+
+function handlePeerJoin(event) {
+  const { userId } = event;
+  if (userId !== "local-screen") {
+    console.log(`Peer Client [${userId}] joined`);
+  }
+}
+
+function handlePeerLeave(event) {
+  const { userId } = event;
+  if (userId !== "local-screen") {
+    console.log(`[${userId}] leave`);
+  }
+}
+
+function handleStreamAdded(event) {
+  const remoteStream = event.stream;
+  const id = remoteStream.getId();
+  const userId = remoteStream.getUserId();
+
+  if (remoteStream.getUserId() === `share_${store.userId}`) {
+    // don't need to screen shared by us
+    localClient.unsubscribe(remoteStream).catch((error) => {
+      console.error(`unsubscribe failed: ${error.message_}`);
+    });
+  } else {
+    console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
+    localClient.subscribe(remoteStream).catch((error) => {
+      console.error(`subscribe failed: ${error.message_}`);
+    });
+  }
+}
+
+function handleStreamSubscribed(event) {
+  const remoteStream = event.stream;
+  const userId = remoteStream.getUserId();
+  console.log(`RemoteStream subscribed: [${userId}]`);
+}
+
+function handleStreamRemoved(event) {
+  const remoteStream = event.stream;
+  const userId = remoteStream.getUserId();
+  console.log(`RemoteStream removed: [${userId}]`);
+}
+
+function handleStreamUpdated(event) {
+  const remoteStream = event.stream;
+  const userId = remoteStream.getUserId();
+  console.log(`RemoteStream updated: [${userId}] audio:${remoteStream.hasAudio()} video:${remoteStream.hasVideo()}`);
+}
+
+const muteAudio = () => {
+  if (!audioMuted.value) {
+    localStream.muteAudio();
+    audioMuted.value = true;
+  } else {
+    localStream.unmuteAudio();
+    audioMuted.value = false;
+  }
+};
+
+const muteVideo = () => {
+  if (!videoMuted.value) {
+    localStream.muteVideo();
+    videoMuted.value = true;
+  } else {
+    localStream.unmuteVideo();
+    videoMuted.value = false;
+  }
+};
+
+let switchDevice = async ({ videoId, audioId }) => {
+  if (!isJoined.value) {
+    return;
+  }
+  if (videoId) {
+    try {
+      await localStream.switchDevice("video", videoId);
+    } catch (error) {}
+  }
+  if (audioId) {
+    try {
+      await localStream.switchDevice("audio", audioId);
+    } catch (error) {}
+  }
+};
+
+watch(show, () => {
+  if (show.value) {
+    handleJoin();
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.trtccom {
+  .local {
+    width: 120px;
+    height: 120px;
+    position: fixed;
+    z-index: 9999;
+    left: 10px;
+    top: 10px;
+    .muteAudio {
+      background: url(~@/assets/images/icon/mic-mute.svg) center center no-repeat;
+    }
+
+    .unmuteAudio {
+      background: url(~@/assets/images/icon/mic.svg) center center no-repeat;
+    }
+
+    .muteVideo {
+      background: url(~@/assets/images/icon/camera-mute.svg) center center no-repeat;
+    }
+
+    .unmuteVideo {
+      background: url(~@/assets/images/icon/camera.svg) center center no-repeat;
+    }
+
+    .tag {
+      position: absolute;
+      bottom: 0;
+      width: 100%;
+      height: 25px;
+      z-index: 999;
+      background: rgba(0, 0, 0, 0.3);
+      display: flex;
+      padding: 0 4px;
+      flex-direction: row-reverse;
+      > div {
+        height: 25px;
+        width: 25px;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 64 - 0
src/components/RTC/trtc/Device.vue

@@ -0,0 +1,64 @@
+<template>
+  <div></div>
+</template>
+
+<script setup>
+import { defineEmits, computed } from "vue";
+import TRTC from "trtc-js-sdk";
+import { useStore } from "vuex";
+import { Dialog } from "@/global_components/";
+
+const store = useStore();
+
+const videoDeviceId = computed(() => store.getters["rtc/videoDeviceId"]);
+const audioDeviceId = computed(() => store.getters["rtc/audioDeviceId"]);
+
+const emit = defineEmits(["switchDevice"]);
+
+const updateDevice = async () => {
+  console.log("updateDevice");
+  const cameraItems = await TRTC.getCameras();
+  cameraItems.forEach((item) => {
+    item.value = item.deviceId;
+  });
+  const microphoneItems = await TRTC.getMicrophones();
+  microphoneItems.forEach((item) => {
+    item.value = item.deviceId;
+  });
+
+  store.commit("rtc/setDeviceList", {
+    cameraList: cameraItems,
+    microphoneList: microphoneItems,
+  });
+
+  if (!videoDeviceId.value) {
+    store.commit("rtc/setVideoDeviceId", cameraItems[0].deviceId);
+  }
+
+  if (!audioDeviceId.value) {
+    store.commit("rtc/setAudioDeviceId", microphoneItems[0].deviceId);
+  }
+};
+
+navigator.mediaDevices
+  .getUserMedia({ audio: true, video: true })
+  .then((stream) => {
+    stream.getTracks().forEach((track) => {
+      track.stop();
+    });
+    updateDevice();
+  })
+  .catch((error) => {
+    console.log(error,'error');
+    Dialog.toast({ content: `请授权您的麦克风和摄像头权限`, type: "error" });
+  });
+
+navigator.mediaDevices.ondevicechange = updateDevice;
+
+const handleDeviceChange = () => {
+  emit("switchDevice", {
+    videoId: store.videoDeviceId,
+    audioId: store.audioDeviceId,
+  });
+};
+</script>

+ 74 - 2
src/store/modules/rtc.js

@@ -1,12 +1,28 @@
 
 
+import { genTestUserSig } from '@/utils/generateTestUserSig';
 
 export default {
     namespaced: true,
     state() {
         return {
             socket: null,
-            showdaogou: false
+            showdaogou: false,
+            sdkAppId: '1400709402',
+            userId: '',
+            roomId: '',
+            secretKey: 'def391b02e6423a6db15eea3d9a0c131f2abac921204246bbe3f36fcea7d111d',
+            userSig: 'eJw1jtEKgjAYhd9l1yH-1tpM6CqCLKULrcw7YVN-JFkqrRG9e6J1eb7DxzlvkkaJp18GO00CAdwHWEzsqTsSEOYBmXOvmsIYVCSgHEDCmgObG1S6HbDESaBLYFT6QMXfw2rEzvZJ7XjGq*YQ3dyprZNY7ONtWFmbHeXjkubdudhdVRlufuKA9-ESFStfsnFPfL6RQTHD',
+            audioDeviceId: '',
+            videoDeviceId: '',
+            cameraList: [],
+            microphoneList: [],
+            logs: [],
+            isJoined: false,
+            isPublished: false,
+            isShared: false,
+            remoteStreams: [],
+            invitedRemoteStreams: [],
         }
     },
     getters: {
@@ -16,6 +32,20 @@ export default {
         showdaogou: state => {
             return state.showdaogou
         },
+        sdkAppId: state => state.sdkAppId,
+        userId: state => state.userId,
+        roomId: state => state.roomId,
+        secretKey: state => state.secretKey,
+        audioDeviceId: state => state.audioDeviceId,
+        videoDeviceId: state => state.videoDeviceId,
+        cameraList: state => state.cameraList,
+        microphoneList: state => state.microphoneList,
+        logs: state => state.logs,
+        isJoined: state => state.isJoined,
+        isPublished: state => state.isPublished,
+        isShared: state => state.isShared,
+        remoteStreams: state => state.remoteStreams,
+        invitedRemoteStreams: state => state.invitedRemoteStreams
     },
     mutations: {
         setSocket(state, payload) {
@@ -23,7 +53,49 @@ export default {
         },
         setShowdaogou(state, payload) {
             state.showdaogou = payload
-        }
+        },
+
+        setDeviceList(state, payload) {
+            state.cameraList=payload.cameraItems
+            state.microphoneList=payload.microphoneItems
+        },
+
+        setVideoDeviceId(state, payload) {
+            state.videoDeviceId=payload
+        },
+
+        setAudioDeviceId(state, payload) {
+            state.audioDeviceId=payload
+        },
+
+        setIsJoined(state, payload){
+            state.isJoined = payload
+        },
+        setIsPublished(state, payload){
+            state.isPublished = payload
+        },
+        getInitParamsStates(state, payload) {
+            return !!(state.sdkAppId && state.secretKey && state.roomId && state.userId);
+        },
+        getUserSig(state, payload) {
+            return state.userSig || genTestUserSig({
+                sdkAppId: parseInt(state.sdkAppId, 10),
+                userId: state.userId,
+                secretKey: state.secretKey,
+            }).userSig;
+        },
+        createShareLink(state, payload) {
+            const userId = `Guest_${Math.floor(Math.random() * 1000000)}`;
+            const { userSig } = genTestUserSig({
+                sdkAppId: parseInt(state.sdkAppId, 10),
+                userId,
+                secretKey: state.secretKey,
+            });
+            const { origin } = window.location;
+            const { pathname } = window.location;
+            return `${origin}${pathname}#/invite?userSig=${userSig}&&SDKAppId=${state.sdkAppId}&&userId=${userId}&&roomId=${state.roomId}`;
+        },
+        
     },
     actions: {
 

+ 64 - 0
src/utils/generateTestUserSig.js

@@ -0,0 +1,64 @@
+/* eslint-disable */
+
+/*
+ * Module:   GenerateTestUserSig
+ *
+ * Function: 用于生成测试用的 UserSig,UserSig 是腾讯云为其云服务设计的一种安全保护签名。
+ *           其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
+ *
+ * Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
+ *
+ *            本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
+ *            这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
+ *            一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
+ *
+ *            正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
+ *            由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
+ *
+ * Reference:https://cloud.tencent.com/document/product/647/17275#Server
+ */
+
+
+export function genTestUserSig({ sdkAppId, userId, secretKey }) {
+  /**
+   * 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。
+   *
+   * 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId,
+   * 它是腾讯云用于区分客户的唯一标识。
+   */
+  const SDKAPPID = sdkAppId;
+
+  /**
+   * 签名过期时间,建议不要设置的过短
+   * <p>
+   * 时间单位:秒
+   * 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
+   */
+  const EXPIRETIME = 604800;
+
+  /**
+   * 计算签名用的加密密钥,获取步骤如下:
+   *
+   * step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ),如果还没有应用就创建一个,
+   * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。
+   * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中
+   *
+   * 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
+   * 文档:https://cloud.tencent.com/document/product/647/17275#Server
+   */
+  const SECRETKEY = secretKey;
+
+  // a soft reminder to guide developer to configure sdkAppId/secretKey
+  if (SDKAPPID == undefined || SECRETKEY === '') {
+    alert(
+      '请先配置好您的账号信息: SDKAPPID 及 SECRETKEY ' +
+      '\r\n\r\nPlease configure your SDKAPPID/SECRETKEY in js/debug/GenerateTestUserSig.js'
+    );
+  }
+  const generator = new LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME);
+  const userSig = generator.genTestUserSig(userId);
+  return {
+    sdkAppId: SDKAPPID,
+    userSig: userSig
+  };
+}

+ 1 - 1
vue.config.js

@@ -11,7 +11,7 @@ module.exports = defineConfig({
   },
   devServer: {
     // port: 443,
-    // https: true,
+    https: true,
     // disableHostCheck: true,
     headers: {
       "Cache-Control": "no-store",