Browse Source

feat(组件): trtc处理完成

gemercheung 2 năm trước cách đây
mục cha
commit
cd65f06921

+ 4 - 1
src/App.vue

@@ -13,9 +13,11 @@ import ChatRoom from "/@/components/chatRoom/index.vue";
 import Title from "/@/components/basic/title.vue";
 import FloorSwitch from "/@/components/basic/FloorSwitch.vue";
 import browser from "/@/utils/browser";
+import { useRtcStore } from "./store/modules/rtc";
 
 const sceneStore = useSceneStore();
 const appStore = useAppStore();
+const rtcStore = useRtcStore();
 const dataLoaded = ref(false);
 const scene$ = ref<Nullable<HTMLElement>>(null);
 const refMiniMap = ref<Nullable<string>>(null);
@@ -23,6 +25,7 @@ const flying = computed(() => appStore.flying);
 const player = computed(() => appStore.player);
 const metadata = computed(() => sceneStore.metadata);
 const mode = computed(() => appStore.mode);
+const isJoined = computed(() => rtcStore.isJoined);
 const controls = computed(() => {
   return metadata.value.controls;
 });
@@ -128,7 +131,7 @@ const changeMode = (name: string) => {
 <template>
   <LoadingLogo :thumb="true" />
   <!-- 引导页 -->
-  <Title />
+  <Title v-if="!isJoined" />
   <Guideline />
   <div class="ui-view-layout" :class="{ show: show }">
     <div class="scene" ref="scene$"></div>

+ 8 - 2
src/components/chatRoom/controls/join.ts

@@ -1,7 +1,8 @@
+import consolaGlobalInstance from "consola";
 import { getApp } from "/@/hooks/userApp";
 import { useRoom } from "/@/hooks/useRoom";
 import { useSocket } from "/@/hooks/userSocket";
-import { useRtcStore } from "/@/store/modules/rtc";
+import { UserInfoType, useRtcStore } from "/@/store/modules/rtc";
 const { enterRoom } = useRoom()
 
 // 自已join的方法
@@ -11,7 +12,12 @@ export function handleJoin(data: any) {
     enterRoom();
     const rtcStore = useRtcStore();
     rtcStore.setIsJoined(true)
-    // 
+    consolaGlobalInstance.info({
+        message: data,
+        tag: "socket:enter"
+    })
+    const members = data?.members as any as UserInfoType[]
+    members && rtcStore.setMemberList(members)
     if (!rtcStore.isLeader) {
         setTimeout(() => {
             socket.emit("action", {

+ 48 - 11
src/components/chatRoom/index.vue

@@ -7,7 +7,7 @@
       :currentUser="currentUser"
     ></chat>
     <!-- 当前人数 start -->
-    <div class="member_number" >
+    <div class="member_number">
       <div class="members"></div>
       <span>{{ members.length }}观看</span>
     </div>
@@ -45,8 +45,8 @@
         <!-- @click="openMember" -->
         <div v-if="isNativeLeader" class="members" @click="openMember"></div>
         <template v-if="isNativeLeader">
-          <div class="mic_on" v-if="unref(disableMic)"></div>
-          <div class="mic_no" v-if="!unref(disableMic)"></div>
+          <div class="mic_on" v-if="!audioMuted" @click="handleMute"></div>
+          <div class="mic_no" v-if="audioMuted" @click="handleNoMute"></div>
         </template>
 
         <div style="font-size: 0.65rem" v-if="isNativeLeader">
@@ -86,11 +86,27 @@
       @close-member="closeMember"
     />
     <!-- 用户列表 end -->
-
-    <!-- 场景列表 start -->
-    <SceneList v-if="showScenes" @close="" @changeScene="" />
-    <!-- 场景列表 end -->
   </div>
+
+  <!-- <teleport :to="`#app`">
+    <div v-if="showShare" @click="showShare = false" class="sharetip">
+      <img
+        @click.stop
+        :style="`right:${isMP ? '16%' : '6%'}`"
+        :src="require('@/assets/images/icon/img_scene_share.png')"
+        alt=""
+      />
+    </div>
+  </teleport> -->
+  <!-- 场景列表 start -->
+  <SceneList v-if="showScenes" @close="showScenes = false" @changeScene="" />
+  <!-- 场景列表 end -->
+  <!-- trtc相关 start -->
+  <div class="local" id="local" v-if="isJoined"></div>
+  <template v-for="item in remoteStreams" :key="item.getId()">
+    <div class="remote" :id="item.getId()"></div>
+  </template>
+  <!-- trtc相关  end -->
 </template>
 
 <script lang="ts" setup>
@@ -115,10 +131,12 @@ import type { SocketParams, RoleType } from "/@/store/modules/rtc";
 import chat from "./chat.vue";
 import memberList from "./memberList.vue";
 import SceneList from "./sceneList.vue";
-
+import { useRoom } from "/@/hooks/useRoom";
 import consola from "consola";
 
+// hook
 const { isDrawing, setDraw, setCloseDraw } = useDraw();
+const { initialRoom } = useRoom();
 
 const rtcStore = useRtcStore();
 const isNativeLeader = computed(() => rtcStore.role == "leader");
@@ -136,6 +154,12 @@ const chatList = computed(() => rtcStore.chatList);
 const currentUser = computed(() => rtcStore.userId);
 const members = computed(() => rtcStore.memberList);
 const showScenes = ref(false);
+const audioMuted = computed(() => rtcStore.audioMuted);
+const remoteStreams = computed(() => rtcStore.remoteStreams);
+
+watchEffect(() => {
+  console.log("当前audioMuted", audioMuted.value);
+});
 
 //设置socket 参数
 const initParams: SocketParams = {
@@ -160,12 +184,12 @@ rtcStore.setSocketParams(initParams);
 
 const socket = createSocket();
 initSocketEvent(socket);
-const { createRTCSocket } = useRtcSdk();
+const { createRTCSocket, localStream } = useRtcSdk();
 
 (async () => {
   await createRTCSocket();
 })();
-
+initialRoom();
 onMounted(async () => {
   const app = await useApp();
   // app.Connect.follow.start({ follow: !unref(isNativeLeader) });
@@ -183,7 +207,7 @@ onUnmounted(async () => {
   app.Connect.paint.off("data", leaderPaint);
 });
 
-/* method */
+/* method 方法*/
 
 const leaderSync = (data) => {
   if (unref(isNativeLeader) && unref(isJoined)) {
@@ -274,6 +298,19 @@ const closeMember = () => {
     showMember.value = false;
   }, 200);
 };
+
+const handleNoMute = () => {
+  const { localStream } = useRtcSdk();
+  console.log("localStream", localStream);
+  rtcStore.unmute();
+  localStream.unmuteAudio();
+};
+const handleMute = () => {
+  const { localStream } = useRtcSdk();
+  console.log("localStream", localStream);
+  rtcStore.mute();
+  localStream.muteVideo();
+};
 </script>
 
 <style scoped lang="scss">

+ 1 - 0
src/components/chatRoom/sceneList.vue

@@ -32,6 +32,7 @@ import Icon from "/@/components/basic/icon/index.vue";
 import { useRoom } from "/@/hooks/useRoom";
 // import { currentScene } from "@/store/room";
 const { currentScene, sceneList } = useRoom();
+
 console.log("sceneList", unref(sceneList));
 
 const emit = defineEmits(["close", "changeScene"]);

+ 35 - 38
src/components/custom/main-shop.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="panel" :class="{show}">
+  <div class="panel" :class="{ show }">
     <span class="icon" @click="playing ? pause() : play()" v-if="existsGuide">
       <Icon type="play" />
     </span>
@@ -10,50 +10,50 @@
       <Icon type="arrows@2x" />
     </span>
   </div>
-  <SceneList 
-    v-if="showScenes" 
-    @close="showScenes = false" 
+  <SceneList
+    v-if="showScenes"
+    @close="showScenes = false"
     @changeScene="changeScene"
   />
 </template>
 
-<script setup>
-import SceneList from './scene-list.vue'
+<script setup lang="ts">
+import SceneList from "./scene-list.vue";
 //import { useMusicPlayer } from "@/utils/sound";
-import { changeScene } from '@/store/room'
-import Icon from '@/components/icon/index.vue'
-import { ref, onUnmounted } from 'vue'
-import { getApp } from '@/app'
-
-const show = ref(false)
-const app = getApp()
-const playing = ref(false)
-const existsGuide = ref(false)
-const showScenes = ref(false)
-
-app.use('TourPlayer').then(player => {
-  console.log('===>', player)
-  player.on('play', ({ partId, frameId }) => (playing.value = true))
-  player.on('pause', ({ partId, frameId }) => (playing.value = false))
-  player.on('end', () => {
-    playing.value = false
-  })
-})
+import { changeScene } from "@/store/room";
+import Icon from "@/components/icon/index.vue";
+import { ref, onUnmounted } from "vue";
+import { getApp } from "@/app";
+
+const show = ref(false);
+const app = getApp();
+const playing = ref(false);
+const existsGuide = ref(false);
+const showScenes = ref(false);
+
+app.use("TourPlayer").then((player) => {
+  console.log("===>", player);
+  player.on("play", ({ partId, frameId }) => (playing.value = true));
+  player.on("pause", ({ partId, frameId }) => (playing.value = false));
+  player.on("end", () => {
+    playing.value = false;
+  });
+});
 
 // 需要双向绑定时,重新设置数据
-app.TourManager.on('loaded', tours => {
-  existsGuide.value = !!tours.length
-})
+app.TourManager.on("loaded", (tours) => {
+  existsGuide.value = !!tours.length;
+});
 
 const play = async () => {
-  const player = await app.TourManager.player
-  player.play()
-}
+  const player = await app.TourManager.player;
+  player.play();
+};
 
 const pause = async () => {
-  const player = await app.TourManager.player
-  player.pause()
-}
+  const player = await app.TourManager.player;
+  player.pause();
+};
 
 // const timeout = setTimeout(() => {
 //   const a = useMusicPlayer()
@@ -61,8 +61,6 @@ const pause = async () => {
 // }, 2000)
 
 // onUnmounted(() => clearTimeout(timeout))
-
-
 </script>
 
 <style lang="scss" scoped>
@@ -82,7 +80,6 @@ const pause = async () => {
   // width: 110px;
   transform: translateX(calc(-100% + 30px));
 
-
   &.show {
     transform: translateX(0);
   }
@@ -98,7 +95,7 @@ const pause = async () => {
   color: #fff;
 
   &.active {
-    color: #ED5D18;
+    color: #ed5d18;
   }
 }
 

+ 412 - 15
src/hooks/useTRTC.ts

@@ -1,14 +1,31 @@
 
 import consola from "consola";
+import { watch, nextTick, onMounted, onUnmounted, ref } from 'vue'
 import TRTC from "trtc-js-sdk";
+import type { LocalStream, Client, RemoteStream } from "trtc-js-sdk";
 import { useRtcStore } from "/@/store/modules/rtc";
 import Dialog from '/@/components/basic/dialog'
+
+let localClient: Client;
+let localStream: LocalStream;
+const invitedRemoteStreams = ref<RemoteStream[]>([]);
+const muteAudioLeader = ref(false);
+const muteVideoLeader = ref(false);
+
+
 export const updateDevice = async () => {
     console.log("updateDevice");
+    const rtcStore = useRtcStore()
     const microphoneItems = await TRTC.getMicrophones();
     microphoneItems.forEach((item) => {
-        console.log('item', item)
+        item['value'] = item.deviceId;
     });
+    if (microphoneItems?.length) {
+        rtcStore.setAudioDeviceId(microphoneItems[0].deviceId)
+    } else {
+        rtcStore.setAudioDeviceId('')
+    }
+
 }
 
 
@@ -21,24 +38,39 @@ const checkoutIsExistAudioInput = async () => {
 //     createRTCSocket: () => Promise<void>
 // }
 
+async function createLocalStream() {
+    try {
+        const rtcStore = useRtcStore()
+        localStream = TRTC.createStream({
+            userId: rtcStore.userId,
+            audio: true,
+            video: false,
+            microphoneId: rtcStore.audioDeviceId,
+        });
+
+        await localStream.initialize();
+
+        if (rtcStore.audioMuted) {
+            localStream.muteAudio();
+        }
+    } catch (error) {
+        console.log(error, "createStream");
+    }
+}
+
 async function createRTCSocket(): Promise<void> {
     try {
-        console.log('Dialog', Dialog)
         const rtcStore = useRtcStore()
         await checkoutIsExistAudioInput();
-        const microphoneItems = await TRTC.getMicrophones();
-        // debugger
-        if (microphoneItems?.length) {
-            const localStream = TRTC.createStream({
-                userId: rtcStore.userId,
-                audio: true,
-                video: false,
-                // microphoneId: store.getters["rtc/audioDeviceId"],
-                microphoneId: rtcStore.audioDeviceId
-            });
-            console.log('localStream', localStream)
 
-        }
+        await updateDevice()
+        // const microphoneItems = await TRTC.getMicrophones();
+
+        // if (microphoneItems?.length) {
+
+        // }
+        await handleJoin();
+
     } catch (error) {
         consola.error({
             tag: 'createRTCSocket',
@@ -46,10 +78,375 @@ async function createRTCSocket(): Promise<void> {
         })
     }
 }
+async function handleJoin() {
+    try {
+        // let res = await apis.getSign({ userId: store.getters["rtc/userId"] });
+        const rtcStore = useRtcStore()
+        localClient = TRTC.createClient({
+            mode: "rtc",
+            sdkAppId: parseInt(rtcStore.sdkAppId, 10),
+            userId: rtcStore.userId,
+            userSig: rtcStore.genUserSig,
+            useStringRoomId: true,
+            enableAutoPlayDialog: false,
+        });
+        installEventHandlers();
+
+        await localClient.join({ roomId: rtcStore.roomId });
+        // store.commit("rtc/setIsJoined", true);
+        rtcStore.setIsJoined(true)
+        // inviteLink.value = store.commit("rtc/createShareLink");
+    } catch (error) {
+        console.error(error, "error-----------");
+    }
+
+    await createLocalStream();
+    await handlePublish();
+    localStream
+        .play("local")
+        .then(() => {
+            consola.info({
+                message: "音采源",
+                tag: 'rtc:audio'
+            })
+        })
+        .catch((e) => {
+            console.log(localStream);
+        });
+
+    localStream.on("error", (error) => {
+        if (error.getCode() === 0x4043) {
+            // 自动播放受限导致播放失败,此时引导用户点击页面。
+            // 在点击事件的回调函数中,执行 stream.resume();
+            Dialog.confirm({
+                showCloseIcon: false,
+                okText: "确定",
+                content:
+                    "<span style='font-size: 16px; line-height: 1.5;'>在用户与网页产生交互(例如点击、触摸页面等)之前,网页将被禁止播放带有声音的媒体。点击恢复播放<span/>",
+                title: "隐私条款:",
+                single: true,
+                func: (state) => {
+                    if (state == "ok") {
+                        localStream.resume();
+                    }
+                },
+            });
+        }
+    });
+}
+
+async function handlePublish() {
+    const rtcStore = useRtcStore()
+
+    if (!rtcStore.isJoined) {
+        return;
+    }
+    if (rtcStore.isPublished) {
+        return;
+    }
+    if (!rtcStore.isLeader) {
+        return;
+    }
+    try {
+        await localClient.publish(localStream);
+        // store.commit("rtc/setIsPublished", true);
+        rtcStore.setIsPublished(true)
+    } catch (error) {
+        console.error(error, "---------------handlePublish--------------------");
+    }
+}
+
+// async function handleStartShare() {
+//     shareClient = new ShareClient({
+//         sdkAppId: parseInt(store.getters["rtc/sdkAppId"], 10),
+//         userId: `share${store.getters["rtc/userId"]}`,
+//         roomId: store.getters["rtc/roomId"],
+//         secretKey: store.getters["rtc/secretKey"],
+//         useStringRoomId: true,
+//     });
+//     try {
+//         await shareClient.join();
+//         await shareClient.publish();
+//         console.log("Start share screen success");
+//         store.isShared = true;
+//     } catch (error) {
+//         console.error(`Start share error: ${error.message_}`);
+//     }
+// }
+
+async function handleUnpublish() {
+    const rtcStore = useRtcStore()
+    if (!rtcStore.isJoined) {
+        return;
+    }
+    if (!rtcStore.isPublished) {
+        return;
+    }
+    try {
+        await localClient.unpublish(localStream);
+        // store.commit("rtc/setIsPublished", false);
+        rtcStore.setIsPublished(false)
+    } catch (error) {
+        console.error(error, "-----------handleUnpublish--------------");
+    }
+}
+
+async function handleLeave() {
+    const rtcStore = useRtcStore()
+    if (rtcStore.isPublished) {
+        await handleUnpublish();
+    }
+    try {
+        uninstallEventHandlers();
+        await localClient.leave();
+        localClient.destroy();
+        // localClient = null;
+        invitedRemoteStreams.value.forEach((item) => {
+            item.stop();
+        });
+        invitedRemoteStreams.value = [];
+        rtcStore.setVideoDeviceId('')
+        rtcStore.setAudioDeviceId('')
+        // store.commit("rtc/setVideoDeviceId", "");
+        // store.commit("rtc/setAudioDeviceId", "");
+
+        if (localStream) {
+            localStream.stop();
+            localStream.close();
+            // localStream = null;
+            console.log("有执行到这里-------------");
+        }
+    } catch (error) {
+        console.error(error, "-----------handleLeave--------------");
+    }
+}
+
+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`);
+    if (event.userId.indexOf("leader") > -1) {
+        muteVideoLeader.value = true;
+    }
+}
+
+function handleMuteAudio(event) {
+    if (event.userId.indexOf("leader") > -1) {
+        muteAudioLeader.value = true;
+    }
+    console.log(event, `[] mute audio`);
+}
+
+function handleUnmuteVideo(event) {
+    console.log(`[${event.userId}] unmute video`);
+    if (event.userId.indexOf("leader") > -1) {
+        muteVideoLeader.value = false;
+    }
+}
+
+function handleUnmuteAudio(event) {
+    console.log(`[${event.userId}] unmute audio`);
+    if (event.userId.indexOf("leader") > -1) {
+        muteAudioLeader.value = false;
+    }
+}
+
+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();
+    const rtcStore = useRtcStore()
+
+    console.log(remoteStream, "-------------remoteStream");
+
+    if (remoteStream.getUserId() === rtcStore.userId) {
+        // don't need to screen shared by us
+        localClient.unsubscribe(remoteStream).catch((error) => {
+            console.info(`unsubscribe failed: ${error.message_}`);
+        });
+    } else {
+        console.log(
+            `remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`
+        );
+        localClient.subscribe(remoteStream).catch((error) => {
+            console.info(`subscribe failed: ${error.message_}`);
+        });
+    }
+}
+
+async function handleStreamSubscribed(event) {
+    const remoteStream = event.stream;
+    const rtcStore = useRtcStore()
+    const remoteUserId = remoteStream.getUserId()
+    const remoteId = remoteStream.getId()
+    if (remoteUserId == rtcStore.userId) {
+        return;
+    }
+
+    if (!rtcStore.isUserInRemoteStream(remoteUserId)) {
+        rtcStore.pushRemoteStreams(remoteStream)
+        // debugger
+    }
+    await nextTick();
+    setTimeout(async () => {
+        try {
+            await remoteStream
+                .play(remoteId)
+            consola.info({
+                message: "客音源",
+                tag: 'rtc:audio'
+            })
+
+        } catch (error) {
+          
+            Dialog.confirm({
+                showCloseIcon: false,
+                okText: "确定",
+                content:
+                    "<span style='font-size: 16px; line-height: 1.5;'>在用户与网页产生交互(例如点击、触摸页面等)之前,网页将被禁止播放带有声音的媒体。点击恢复播放<span/>",
+                title: "隐私条款:",
+                single: true,
+                func: (state) => {
+                    if (state == "ok") {
+                        remoteStream.resume();
+                    }
+                },
+            });
+        }
+
+    }, 200);
+    // console.info(
+    //     remoteStream.userId_,
+    //     rtcStore,
+    //     "handleStreamSubscribedhandleStreamSubscribed.value"
+    // );
+
+    // if (
+    //     !invitedRemoteStreams.value.some(
+    //         (item) => item.userId_ == remoteStream.userId_
+    //     )
+    // ) {
+    //     debugger
+    //     invitedRemoteStreams.value.push(remoteStream);
+    // }
+
+    // console.log(invitedRemoteStreams.value, "invitedRemoteStreams.value");
+
+    // await nextTick();
+    // setTimeout(() => {
+    //     console.log(remoteStream.userId_, "remoteStream.getId()");
+    //     remoteStream
+    //         .play(remoteStream.userId_)
+    //         .then(() => {
+    //             console.log(`RemoteStream play success`, 88888888888888888888);
+    //         })
+    //         .catch((error) => {
+    //             console.log(`RemoteStream play failed:  error: ${error.message_}`);
+    //         });
+    // }, 100);
+
+    // 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()}`
+    );
+}
+
+let switchDevice = async ({ videoId, audioId }) => {
+    const rtcStore = useRtcStore()
+    if (!rtcStore.isJoined) {
+        return;
+    }
+    if (videoId) {
+        try {
+            await localStream.switchDevice("video", videoId);
+        } catch (error) { }
+    }
+    if (audioId) {
+        try {
+            await localStream.switchDevice("audio", audioId);
+        } catch (error) { }
+    }
+};
+
+
 
 export function useRtcSdk() {
     return {
-        createRTCSocket
+        createRTCSocket,
+        handleJoin,
+        handleLeave,
+        localStream,
+        client: localClient
     }
 }
 

+ 36 - 4
src/store/modules/rtc.ts

@@ -1,5 +1,7 @@
 import { defineStore } from 'pinia';
 import consola from 'consola'
+import { genTestUserSig } from '/@/utils/generateTestUserSig'
+import type { RemoteStream } from "trtc-js-sdk";
 interface RtcState {
     socket: Nullable<SocketIOClient.Socket>,
     showDaoGou: boolean,
@@ -17,13 +19,14 @@ interface RtcState {
     isJoined: boolean,
     isPublished: boolean,
     isShared: boolean,
-    remoteStreams: any[],
-    invitedRemoteStreams: any[],
+    remoteStreams: RemoteStream[],
+    // invitedRemoteStreams: any[],
     avatar: Nullable<string>,
     nickname: Nullable<string>,
     mode: string,
     chatList: ChatContentType[],
     memberList: UserInfoType[],
+    audioMuted: boolean
 }
 
 interface DeviceListParams {
@@ -86,11 +89,12 @@ export const useRtcStore = defineStore({
         isPublished: false,
         isShared: false,
         remoteStreams: [],
-        invitedRemoteStreams: [],
+        // invitedRemoteStreams: [],
         avatar: null,
         mode: '',
         chatList: [],
-        memberList: []
+        memberList: [],
+        audioMuted: true
     }),
     getters: {
         isLeader(): boolean {
@@ -98,6 +102,19 @@ export const useRtcStore = defineStore({
         },
         isMe() {
             return (userId: string) => this.userId === userId
+        },
+        genUserSig() {
+            const { userSig } = genTestUserSig({
+                sdkAppId: parseInt(this.sdkAppId, 10),
+                userId: this.userId,
+                secretKey: this.secretKey,
+            });
+            return userSig
+        },
+        isUserInRemoteStream() {
+            return (userId: string) => {
+                return this.remoteStreams.some(item => item.getUserId() === userId)
+            }
         }
     },
     actions: {
@@ -172,6 +189,21 @@ export const useRtcStore = defineStore({
             }, []);
             this.memberList = memberList
         },
+        mute() {
+            this.audioMuted = true
+        },
+        unmute() {
+            this.audioMuted = false
+        },
+        pushRemoteStreams(stream: RemoteStream) {
+            this.remoteStreams.push(stream)
+        },
+        removeRemoteStreams(id: string) {
+            const existStreamIndex = this.remoteStreams.findIndex(stream => stream.getId() === id)
+            if (existStreamIndex > -1) {
+                this.remoteStreams.splice(existStreamIndex, 1)
+            }
+        }
     }
 
 })

+ 64 - 0
src/utils/generateTestUserSig.ts

@@ -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 (window as any).LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME);
+  const userSig = generator.genTestUserSig(userId);
+  return {
+    sdkAppId: SDKAPPID,
+    userSig: userSig
+  };
+}