shuisheng 2 rokov pred
rodič
commit
2157ad7ea8

+ 3 - 3
package.json

@@ -34,16 +34,16 @@
   "dependencies": {
     "@vicons/ionicons5": "^0.12.0",
     "axios": "^1.2.1",
-    "billd-html-webpack-plugin": "^1.0.4",
+    "billd-html-webpack-plugin": "^1.0.5",
     "billd-scss": "^0.0.7",
-    "billd-utils": "^0.0.12",
+    "billd-utils": "^0.0.15",
     "browser-tool": "^1.0.5",
     "fabric": "^5.3.0",
     "flv.js": "^1.6.2",
     "js-cookie": "^3.0.5",
     "localforage": "^1.10.0",
     "mpegts.js": "^1.7.3",
-    "naive-ui": "^2.34.3",
+    "naive-ui": "^2.34.4",
     "pinia": "^2.0.33",
     "pinia-plugin-persistedstate": "^3.2.0",
     "qrcode": "^1.5.3",

+ 22 - 23
pnpm-lock.yaml

@@ -12,14 +12,14 @@ dependencies:
     specifier: ^1.2.1
     version: 1.3.4
   billd-html-webpack-plugin:
-    specifier: ^1.0.4
-    version: 1.0.4(@swc/core@1.3.84)(@types/node@18.15.3)(esbuild@0.15.18)(sass@1.59.3)(terser@5.16.6)(webpack-cli@4.10.0)
+    specifier: ^1.0.5
+    version: 1.0.5(@swc/core@1.3.84)(@types/node@18.15.3)(esbuild@0.15.18)(sass@1.59.3)(terser@5.16.6)(webpack-cli@4.10.0)
   billd-scss:
     specifier: ^0.0.7
     version: 0.0.7
   billd-utils:
-    specifier: ^0.0.12
-    version: 0.0.12(typescript@5.1.6)
+    specifier: ^0.0.15
+    version: 0.0.15(typescript@5.1.6)
   browser-tool:
     specifier: ^1.0.5
     version: 1.0.5
@@ -39,8 +39,8 @@ dependencies:
     specifier: ^1.7.3
     version: 1.7.3
   naive-ui:
-    specifier: ^2.34.3
-    version: 2.34.3(vue@3.3.4)
+    specifier: ^2.34.4
+    version: 2.34.4(vue@3.3.4)
   pinia:
     specifier: ^2.0.33
     version: 2.0.33(typescript@5.1.6)(vue@3.3.4)
@@ -1320,7 +1320,6 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       regenerator-runtime: 0.13.11
-    dev: false
 
   /@babel/template@7.20.7:
     resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==}
@@ -2914,7 +2913,7 @@ packages:
     peerDependencies:
       video.js: ^7 || ^8
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       '@videojs/vhs-utils': 4.0.0
       aes-decrypter: 4.0.1
       global: 4.4.0
@@ -2928,7 +2927,7 @@ packages:
     resolution: {integrity: sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==}
     engines: {node: '>=8', npm: '>=5'}
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       global: 4.4.0
       url-toolkit: 2.2.5
     dev: false
@@ -2937,7 +2936,7 @@ packages:
     resolution: {integrity: sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==}
     engines: {node: '>=8', npm: '>=5'}
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       global: 4.4.0
       url-toolkit: 2.2.5
     dev: false
@@ -2945,7 +2944,7 @@ packages:
   /@videojs/xhr@2.6.0:
     resolution: {integrity: sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==}
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       global: 4.4.0
       is-function: 1.0.2
     dev: false
@@ -3305,7 +3304,7 @@ packages:
   /aes-decrypter@4.0.1:
     resolution: {integrity: sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==}
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       '@videojs/vhs-utils': 3.0.5
       global: 4.4.0
       pkcs7: 1.0.4
@@ -3643,8 +3642,8 @@ packages:
     resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
     dev: true
 
-  /billd-html-webpack-plugin@1.0.4(@swc/core@1.3.84)(@types/node@18.15.3)(esbuild@0.15.18)(sass@1.59.3)(terser@5.16.6)(webpack-cli@4.10.0):
-    resolution: {integrity: sha512-AMfNC14Ynu7Cs4+xsRFAROkHqDlhKD23KBdMSxueCAmW8/GxBOqUPrjO9r7nBhbL0MUP2lB0nOT5tPDHuUlSeQ==}
+  /billd-html-webpack-plugin@1.0.5(@swc/core@1.3.84)(@types/node@18.15.3)(esbuild@0.15.18)(sass@1.59.3)(terser@5.16.6)(webpack-cli@4.10.0):
+    resolution: {integrity: sha512-SYT/tR7+LcnCc3kADU7Lt56HDPKCBOo0R1QNj4ky78aGqH4BqOUMqbe66uIjqa4MqFHsWYjkOarO7JY4M/KpYw==}
     requiresBuild: true
     dependencies:
       vite: 4.2.0(@types/node@18.15.3)(sass@1.59.3)(terser@5.16.6)
@@ -3667,8 +3666,8 @@ packages:
     requiresBuild: true
     dev: false
 
-  /billd-utils@0.0.12(typescript@5.1.6):
-    resolution: {integrity: sha512-bHB/gZk6I9fGKHQiu+QXpqBiCXF72kYi4/xDI8RZwdVsjFd6QqsItlx1MpX4j25GJnhsG2vbrg0tLQ48zXHQoQ==}
+  /billd-utils@0.0.15(typescript@5.1.6):
+    resolution: {integrity: sha512-IkazhLt62GRKANDf9ZRzdMWhKxC6qoL6O+SYdpoF/ul6NW46yaLTnOjEGUsrycXyJvcaL+U+7YinKhnuL8skAQ==}
     requiresBuild: true
     dependencies:
       '@babel/core': 7.21.3
@@ -4739,7 +4738,7 @@ packages:
     resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
     engines: {node: '>=0.11'}
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
     dev: false
 
   /dateformat@3.0.3:
@@ -7470,7 +7469,7 @@ packages:
   /m3u8-parser@6.2.0:
     resolution: {integrity: sha512-qlC00JTxYOxawcqg+RB8jbyNwL3foY/nCY61kyWP+RCuJE9APLeqB/nSlTjb4Mg0yRmyERgjswpdQxMvkeoDrg==}
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       '@videojs/vhs-utils': 3.0.5
       global: 4.4.0
     dev: false
@@ -7712,7 +7711,7 @@ packages:
     resolution: {integrity: sha512-uZ/db5wQdlQn1L+OD49YXBhPI9UGeK1SeQE4D5EoaJIhf0WM9X3HDj8d+9PjoG06CgCvGZw3YW/wsHku+CH3yA==}
     hasBin: true
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       '@videojs/vhs-utils': 3.0.5
       '@xmldom/xmldom': 0.8.8
       global: 4.4.0
@@ -7762,12 +7761,12 @@ packages:
     engines: {node: '>=8', npm: '>=5'}
     hasBin: true
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
       global: 4.4.0
     dev: false
 
-  /naive-ui@2.34.3(vue@3.3.4):
-    resolution: {integrity: sha512-fUMr0dzb/iGsOTWgoblPVobY5X5dihQ1eam5dA+H74oyLYAvgX4pL96xQFPBLIYqvyRFBAsN85kHN5pLqdtpxA==}
+  /naive-ui@2.34.4(vue@3.3.4):
+    resolution: {integrity: sha512-aPG8PDfhSzIzn/jSC9y3Jb3Pe2wHJ7F0cFV1EWlbImSrZECeUmoc+fIcOSWbizoztkKfaUAeKwYdMl09MKkj1g==}
     peerDependencies:
       vue: ^3.0.0
     dependencies:
@@ -9219,7 +9218,7 @@ packages:
   /regenerator-transform@0.15.1:
     resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==}
     dependencies:
-      '@babel/runtime': 7.21.0
+      '@babel/runtime': 7.22.6
 
   /regexp.prototype.flags@1.4.3:
     resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}

+ 5 - 8
src/App.vue

@@ -9,27 +9,24 @@ import { onMounted } from 'vue';
 import { useCheckUpdate } from '@/hooks/use-checkUpdate';
 import { loginMessage } from '@/hooks/use-login';
 import { useUserStore } from '@/store/user';
-import cache from '@/utils/cache';
-import {
-  getLastBuildDateByLs,
-  setLastBuildDateByLs,
-} from '@/utils/localStorage/app';
+import { getLastBuildDate, setLastBuildDate } from '@/utils/localStorage/app';
+import { getToken } from '@/utils/localStorage/user';
 
 const { appInfo } = useCheckUpdate();
 const userStore = useUserStore();
 
 function handleUpdate() {
-  const old = getLastBuildDateByLs();
+  const old = getLastBuildDate();
   if (appInfo.value.lastBuildDate !== old) {
     localStorage.clear();
   }
-  setLastBuildDateByLs(appInfo.value.lastBuildDate);
+  setLastBuildDate(appInfo.value.lastBuildDate);
 }
 
 onMounted(() => {
   handleUpdate();
   loginMessage();
-  const token = cache.getStorageExp('token');
+  const token = getToken();
   if (token) {
     userStore.setToken(token);
     userStore.getUserInfo();

+ 8 - 9
src/components/Modal/index.vue

@@ -31,15 +31,14 @@
 import { useSlots } from 'vue';
 
 const slots = useSlots();
-const props = defineProps({
-  modelValue: Boolean,
-  hiddenClose: Boolean,
-  maskClosable: Boolean,
-  title: {
-    type: String,
-    default: '',
-  },
-});
+const props = withDefaults(
+  defineProps<{
+    hiddenClose?: Boolean;
+    maskClosable?: Boolean;
+    title?: string;
+  }>(),
+  {}
+);
 
 function maskClose() {
   if (props.maskClosable) {

+ 110 - 16
src/components/VideoControls/index.vue

@@ -2,20 +2,60 @@
   <div class="video-controls-wrap">
     <div class="left">
       <div
-        class="item"
-        @click="appStore.setMuted(!appStore.muted)"
+        class="play"
+        @click="changePlay"
       >
-        <n-icon
-          size="25"
-          color="white"
-        >
-          <VolumeMuteOutline v-if="appStore.muted"></VolumeMuteOutline>
-          <VolumeHighOutline v-else></VolumeHighOutline>
+        <n-icon size="25">
+          <Pause v-if="appStore.play"></Pause>
+          <Play v-else></Play>
+        </n-icon>
+      </div>
+      <div
+        class="refresh"
+        @click="emits('refresh')"
+      >
+        <n-icon size="25">
+          <RefreshSharp></RefreshSharp>
         </n-icon>
       </div>
+      <div
+        class="muted"
+        @click="changeMuted"
+      >
+        <n-popover
+          placement="top"
+          trigger="hover"
+          :flip="false"
+          :style="{ padding: '4px 4px 24px 4px' }"
+          :show-arrow="false"
+        >
+          <template #trigger>
+            <n-icon size="25">
+              <VolumeMuteOutline v-if="cacheStore.muted"></VolumeMuteOutline>
+              <VolumeHighOutline v-else></VolumeHighOutline>
+            </n-icon>
+          </template>
+          <div class="slider">
+            <div class="txt">{{ cacheStore.volume }}</div>
+            <n-slider
+              :value="cacheStore.volume"
+              :step="1"
+              vertical
+              :tooltip="false"
+              @update-value="changeVolume"
+            />
+          </div>
+        </n-popover>
+      </div>
     </div>
 
     <div class="right">
+      <div
+        class="resolution"
+        v-if="resolution"
+      >
+        {{ resolution }}
+      </div>
       <div class="line">
         <span
           class="txt"
@@ -42,8 +82,9 @@
         <span
           class="txt"
           @click="showSpeed = !showSpeed"
-          >倍速</span
         >
+          倍速
+        </span>
         <div
           class="list"
           :class="{ show: showSpeed }"
@@ -60,12 +101,29 @@
 </template>
 
 <script lang="ts" setup>
-import { VolumeHighOutline, VolumeMuteOutline } from '@vicons/ionicons5';
+import {
+  Pause,
+  Play,
+  RefreshSharp,
+  VolumeHighOutline,
+  VolumeMuteOutline,
+} from '@vicons/ionicons5';
 import { ref } from 'vue';
 
 import { LiveLineEnum, LiveRoomTypeEnum } from '@/interface';
 import { useAppStore } from '@/store/app';
+import { usePiniaCacheStore } from '@/store/cache';
+
+withDefaults(
+  defineProps<{
+    resolution?: string;
+  }>(),
+  {}
+);
 
+const emits = defineEmits(['refresh']);
+
+const cacheStore = usePiniaCacheStore();
 const appStore = useAppStore();
 const showLine = ref(false);
 const showSpeed = ref(false);
@@ -73,6 +131,17 @@ const showSpeed = ref(false);
 function handleTip() {
   window.$message.info('敬请期待~');
 }
+
+function changeMuted() {
+  cacheStore.setMuted(!cacheStore.muted);
+}
+function changeVolume(v) {
+  cacheStore.setVolume(v);
+}
+function changePlay() {
+  appStore.setPlay(!appStore.play);
+}
+
 function changeLiveLine(item) {
   if (
     item === LiveLineEnum.rtc &&
@@ -93,6 +162,17 @@ function changeLiveLine(item) {
 </script>
 
 <style lang="scss" scoped>
+.slider {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
+  width: 24px;
+  height: 80px;
+  text-align: center;
+  .txt {
+    font-size: 12px;
+  }
+}
 .video-controls-wrap {
   position: absolute;
   bottom: 0;
@@ -114,18 +194,29 @@ function changeLiveLine(item) {
   text-align: initial;
 
   user-select: none;
-  .item {
-    cursor: pointer;
-  }
   .left {
+    display: flex;
+    align-items: center;
+    .muted,
+    .refresh,
+    .play {
+      display: flex;
+      align-items: center;
+      margin-right: 10px;
+      cursor: pointer;
+    }
   }
   .right {
     display: flex;
     align-items: center;
-    .speed,
-    .line {
+    .resolution {
+      cursor: no-drop;
+    }
+    .resolution,
+    .line,
+    .speed {
       position: relative;
-      margin-left: 15px;
+      margin-right: 15px;
       &:hover {
         .list {
           display: block;
@@ -160,6 +251,9 @@ function changeLiveLine(item) {
         }
       }
     }
+    .speed {
+      margin-right: 0;
+    }
   }
 }
 </style>

+ 2 - 1
src/constant.ts

@@ -21,8 +21,9 @@ export const COOKIE_KEY = {
 };
 
 // 全局的localStorage的key
-export const LOCALSTORAGE_KEY = {
+export const lsKey = {
   lastBuildDate: 'lastBuildDate',
+  token: 'token',
 };
 
 export const bilibiliCollectiondetail =

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

@@ -7,8 +7,8 @@ import { QQ_CLIENT_ID, QQ_OAUTH_URL, QQ_REDIRECT_URI } from '@/constant';
 import LoginModalCpt from '@/hooks/loginModal/index.vue';
 import { PlatformEnum } from '@/interface';
 import { useUserStore } from '@/store/user';
-import cache from '@/utils/cache';
 import { clearLoginInfo, setLoginInfo } from '@/utils/cookie';
+import { getToken } from '@/utils/localStorage/user';
 
 const app = createApp(LoginModalCpt);
 const container = document.createElement('div');
@@ -49,7 +49,7 @@ export async function handleLogin(e) {
 }
 
 export function loginTip(show = false) {
-  const token = cache.getStorageExp('token');
+  const token = getToken();
   instance.show = show;
   if (!token) {
     window.$message.warning('请先登录!');

+ 79 - 16
src/hooks/use-play.ts

@@ -5,6 +5,7 @@ import Player from 'video.js/dist/types/player';
 import { onMounted, onUnmounted, ref, watch } from 'vue';
 
 import { useAppStore } from '@/store/app';
+import { usePiniaCacheStore } from '@/store/cache';
 import { createVideo } from '@/utils';
 
 export * as flvJs from 'flv.js';
@@ -13,6 +14,7 @@ export function useFlvPlay() {
   // const flvPlayer = ref<flvJs.Player>();
   const flvPlayer = ref<mpegts.Player>();
   const flvVideoEl = ref<HTMLVideoElement>();
+  const cacheStore = usePiniaCacheStore();
   const appStore = useAppStore();
   const retryMax = ref(120);
   const retry = ref(0);
@@ -32,22 +34,51 @@ export function useFlvPlay() {
     flvVideoEl.value?.remove();
   }
 
-  watch(
-    () => appStore.muted,
-    (newVal) => {
-      setMuted(newVal);
-    }
-  );
-
   function setMuted(val) {
+    if (flvVideoEl.value) {
+      flvVideoEl.value.muted = val;
+    }
     if (flvPlayer.value) {
       flvPlayer.value.muted = val;
     }
+  }
+  function setVolume(val: number) {
     if (flvVideoEl.value) {
-      flvVideoEl.value.muted = val;
+      flvVideoEl.value.volume = val / 100;
+    }
+    if (flvPlayer.value) {
+      flvPlayer.value.volume = val / 100;
+    }
+  }
+  function setPlay(val: boolean) {
+    if (val) {
+      flvVideoEl.value?.play();
+      flvPlayer.value?.play();
+    } else {
+      flvVideoEl.value?.pause();
+      flvPlayer.value?.pause();
     }
   }
 
+  watch(
+    () => cacheStore.muted,
+    (newVal) => {
+      setMuted(newVal);
+    }
+  );
+  watch(
+    () => cacheStore.volume,
+    (newVal) => {
+      setVolume(newVal);
+    }
+  );
+  watch(
+    () => appStore.play,
+    (newVal) => {
+      setPlay(newVal);
+    }
+  );
+
   function startFlvPlay(data: { flvurl: string }) {
     console.log('startFlvPlay', data.flvurl);
     return new Promise((resolve) => {
@@ -66,7 +97,7 @@ export function useFlvPlay() {
           videoEl.addEventListener('playing', () => {
             console.log('flv-playing');
             retry.value = 0;
-            setMuted(appStore.muted);
+            setMuted(cacheStore.muted);
             flvVideoEl.value = videoEl;
             resolve('');
           });
@@ -95,7 +126,7 @@ export function useFlvPlay() {
             console.log('mpegts消息:mpegts.Events.MEDIA_INFO');
           });
           try {
-            console.log(`开始播放flv,muted:${appStore.muted}`);
+            console.log(`开始播放flv,muted:${cacheStore.muted}`);
             flvPlayer.value.play();
           } catch (err) {
             console.error('flv播放失败');
@@ -115,6 +146,7 @@ export function useFlvPlay() {
 export function useHlsPlay() {
   const hlsPlayer = ref<Player>();
   const hlsVideoEl = ref<HTMLVideoElement>();
+  const cacheStore = usePiniaCacheStore();
   const appStore = useAppStore();
   const retryMax = ref(120);
   const retry = ref(0);
@@ -134,8 +166,7 @@ export function useHlsPlay() {
     hlsVideoEl.value?.remove();
   }
 
-  function setMuted(val) {
-    console.log('setMuted', val);
+  function setMuted(val: boolean) {
     if (hlsVideoEl.value) {
       hlsVideoEl.value.muted = val;
     }
@@ -143,20 +174,52 @@ export function useHlsPlay() {
       hlsPlayer.value.muted(val);
     }
   }
+  function setVolume(val: number) {
+    if (hlsVideoEl.value) {
+      hlsVideoEl.value.volume = val / 100;
+    }
+    if (hlsPlayer.value) {
+      hlsPlayer.value.volume(val / 100);
+    }
+  }
+  function setPlay(val: boolean) {
+    if (val) {
+      hlsVideoEl.value?.play();
+      hlsPlayer.value?.play();
+    } else {
+      hlsVideoEl.value?.pause();
+      hlsPlayer.value?.pause();
+    }
+  }
 
   watch(
-    () => appStore.muted,
+    () => cacheStore.muted,
     (newVal) => {
       setMuted(newVal);
     }
   );
+  watch(
+    () => cacheStore.volume,
+    (newVal) => {
+      setVolume(newVal);
+    }
+  );
+  watch(
+    () => appStore.play,
+    (newVal) => {
+      setPlay(newVal);
+    }
+  );
 
   function startHlsPlay(data: { hlsurl: string }) {
     return new Promise((resolve) => {
       function main() {
         console.log('startHlsPlay', data.hlsurl);
         destroyHls();
-        const videoEl = createVideo({ muted: appStore.muted, autoplay: true });
+        const videoEl = createVideo({
+          muted: cacheStore.muted,
+          autoplay: true,
+        });
         hlsPlayer.value = videoJs(
           videoEl,
           {
@@ -169,7 +232,7 @@ export function useHlsPlay() {
           },
           function () {
             try {
-              console.log(`开始播放hls,muted:${appStore.muted}`);
+              console.log(`开始播放hls,muted:${cacheStore.muted}`);
               hlsPlayer.value?.play();
             } catch (err) {
               console.error('hls播放失败');
@@ -198,7 +261,7 @@ export function useHlsPlay() {
         });
         hlsPlayer.value?.on('playing', () => {
           console.log('hls-playing');
-          setMuted(appStore.muted);
+          setMuted(cacheStore.muted);
           retry.value = 0;
           // console.log(hlsPlayer.value?.videoHeight()); // 获取到的是正确的!
           const childNodes = hlsPlayer.value?.el().childNodes;

+ 45 - 4
src/hooks/use-pull.ts

@@ -13,6 +13,7 @@ import {
 } from '@/interface';
 import { WsMsgTypeEnum } from '@/network/webSocket';
 import { useAppStore } from '@/store/app';
+import { usePiniaCacheStore } from '@/store/cache';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
 import { createVideo, videoToCanvas } from '@/utils';
@@ -21,6 +22,7 @@ export function usePull() {
   const route = useRoute();
   const userStore = useUserStore();
   const networkStore = useNetworkStore();
+  const cacheStore = usePiniaCacheStore();
   const appStore = useAppStore();
   const roomId = ref(route.params.roomId as string);
   const localStream = ref<MediaStream>();
@@ -29,6 +31,7 @@ export function usePull() {
   const videoLoading = ref(false);
   const flvurl = ref('');
   const hlsurl = ref('');
+  const videoHeight = ref();
   const sidebarList = ref<
     {
       socketId: string;
@@ -68,6 +71,9 @@ export function usePull() {
     stopDrawingArr.value.forEach((cb) => cb());
     const { canvas, stopDrawing } = videoToCanvas({
       videoEl: hlsVideoEl.value!,
+      resize: ({ w, h }) => {
+        videoHeight.value = `${w}x${h}`;
+      },
     });
     stopDrawingArr.value.push(stopDrawing);
     remoteVideo.value.push(canvas);
@@ -89,6 +95,9 @@ export function usePull() {
     stopDrawingArr.value.forEach((cb) => cb());
     const { canvas, stopDrawing } = videoToCanvas({
       videoEl: flvVideoEl.value!,
+      resize: ({ w, h }) => {
+        videoHeight.value = `${w}x${h}`;
+      },
     });
     stopDrawingArr.value.push(stopDrawing);
     remoteVideo.value.push(canvas);
@@ -106,7 +115,6 @@ export function usePull() {
   }
 
   function handlePlay(data: ILiveRoom) {
-    console.warn('handlePlay', data.type);
     roomLiving.value = true;
     flvurl.value = data.flv_url!;
     hlsurl.value = data.hls_url!;
@@ -160,12 +168,41 @@ export function usePull() {
     }
   );
   watch(
-    () => appStore.muted,
+    () => cacheStore.muted,
     (newVal) => {
-      console.log('muted变了', newVal);
       videoElArr.value.forEach((el) => {
         el.muted = newVal;
       });
+      if (!newVal) {
+        cacheStore.setVolume(cacheStore.volume || appStore.normalVolume);
+      } else {
+        cacheStore.setVolume(0);
+      }
+    }
+  );
+  watch(
+    () => cacheStore.volume,
+    (newVal) => {
+      videoElArr.value.forEach((el) => {
+        el.volume = newVal / 100;
+      });
+      if (!newVal) {
+        cacheStore.setMuted(true);
+      } else {
+        cacheStore.setMuted(false);
+      }
+    }
+  );
+  watch(
+    () => appStore.play,
+    (newVal) => {
+      videoElArr.value.forEach((el) => {
+        if (newVal) {
+          el.play();
+        } else {
+          el.pause();
+        }
+      });
     }
   );
 
@@ -177,6 +214,9 @@ export function usePull() {
           videoLoading.value = false;
           const { canvas } = videoToCanvas({
             videoEl: item.videoEl,
+            resize: ({ w, h }) => {
+              videoHeight.value = `${w}x${h}`;
+            },
           });
           videoElArr.value.push(item.videoEl);
           remoteVideo.value.push(canvas);
@@ -336,10 +376,11 @@ export function usePull() {
     initPull,
     closeWs,
     closeRtc,
-    mySocketId,
     keydownDanmu,
     sendDanmu,
     addVideo,
+    mySocketId,
+    videoHeight,
     remoteVideo,
     roomLiving,
     autoplayVal,

+ 4 - 2
src/hooks/use-srs-ws.ts

@@ -51,6 +51,7 @@ export const useSrsWs = () => {
   const isPull = ref(false);
   const roomLiving = ref(false);
   const isAnchor = ref(false);
+  const isSRS = ref(false);
   const anchorInfo = ref<IUser>();
   const anchorSocketId = ref('');
   const canvasVideoStream = ref<MediaStream>();
@@ -192,7 +193,7 @@ export const useSrsWs = () => {
       isSRS: true,
       receiver,
     });
-
+    isSRS.value = true;
     handleSendOffer({
       receiver,
     });
@@ -236,6 +237,7 @@ export const useSrsWs = () => {
           isSRS: true,
           receiver: data.receiver,
         });
+        isSRS.value = true;
         await rtc.setRemoteDescription(data.sdp);
         const answer = await rtc.createAnswer();
         if (answer) {
@@ -358,7 +360,7 @@ export const useSrsWs = () => {
           live_room_id: data.live_room.id!,
         },
       });
-      if (!isPull.value) {
+      if (!isPull.value && !isSRS.value) {
         if (!roomLiving.value) return;
         liveUserList.value.forEach(async (item) => {
           const receiver = item.id;

+ 6 - 4
src/store/app/index.ts

@@ -5,8 +5,9 @@ import { ILiveRoom, LiveLineEnum, MediaTypeEnum } from '@/interface';
 import { mobileRouterName } from '@/router';
 
 export type AppRootState = {
-  muted: boolean;
+  play: boolean;
   videoRatio: number;
+  normalVolume: number;
   navList: { routeName: string; name: string }[];
   allTrack: {
     /** 1开启;2关闭 */
@@ -41,8 +42,9 @@ export type AppRootState = {
 export const useAppStore = defineStore('app', {
   state: (): AppRootState => {
     return {
-      muted: true,
+      play: true,
       videoRatio: 16 / 9,
+      normalVolume: 70,
       navList: [
         { routeName: mobileRouterName.h5, name: '频道' },
         { routeName: mobileRouterName.h5Rank, name: '排行' },
@@ -60,8 +62,8 @@ export const useAppStore = defineStore('app', {
     setLiveLine(res: AppRootState['liveLine']) {
       this.liveLine = res;
     },
-    setMuted(res: AppRootState['muted']) {
-      this.muted = res;
+    setPlay(res: AppRootState['play']) {
+      this.play = res;
     },
     setAllTrack(res: AppRootState['allTrack']) {
       this.allTrack = res;

+ 18 - 8
src/store/cache/index.ts

@@ -2,22 +2,32 @@ import { defineStore } from 'pinia';
 
 import { AppRootState } from '@/store/app';
 
-export type ResourceCacheRootState = {
-  list: AppRootState['allTrack'];
+export type PiniaCacheRootState = {
+  muted: boolean;
+  volume: number;
+  'resource-list': AppRootState['allTrack'];
 };
 
-export const useResourceCacheStore = defineStore('resource-cache', {
+export const usePiniaCacheStore = defineStore('pinia-cache', {
   persist: {
-    key: 'resource-cache',
+    key: 'pinia-cache',
   },
-  state: (): ResourceCacheRootState => {
+  state: (): PiniaCacheRootState => {
     return {
-      list: [],
+      muted: true,
+      volume: 70,
+      'resource-list': [],
     };
   },
   actions: {
-    setList(res: ResourceCacheRootState['list']) {
-      this.list = res;
+    setResourceList(res: PiniaCacheRootState['resource-list']) {
+      this['resource-list'] = res;
+    },
+    setMuted(res: PiniaCacheRootState['muted']) {
+      this.muted = res;
+    },
+    setVolume(res: PiniaCacheRootState['volume']) {
+      this.volume = res;
     },
   },
 });

+ 3 - 3
src/store/user/index.ts

@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
 
 import { fetchUserInfo } from '@/api/user';
 import { IRole, IUser } from '@/interface';
-import cache from '@/utils/cache';
+import { clearToken, setToken } from '@/utils/localStorage/user';
 
 type RootState = {
   userInfo?: IUser;
@@ -23,14 +23,14 @@ export const useUserStore = defineStore('user', {
       this.userInfo = res;
     },
     setToken(res) {
-      cache.setStorageExp('token', res, 24);
+      setToken(res);
       this.token = res;
     },
     setRoles(res) {
       this.roles = res;
     },
     logout() {
-      cache.clearStorage('token');
+      clearToken();
       this.token = undefined;
       this.userInfo = undefined;
       this.roles = [];

+ 6 - 1
src/utils/index.ts

@@ -220,7 +220,10 @@ export const createVideo = ({
   return videoEl;
 };
 
-export function videoToCanvas(data: { videoEl: HTMLVideoElement }) {
+export function videoToCanvas(data: {
+  videoEl: HTMLVideoElement;
+  resize?: (data: { w: number; h: number }) => void;
+}) {
   const { videoEl } = data;
   if (!videoEl) {
     throw new Error('videoEl不能为空!');
@@ -234,7 +237,9 @@ export function videoToCanvas(data: { videoEl: HTMLVideoElement }) {
   function handleResize() {
     w = videoEl.videoWidth;
     h = videoEl.videoHeight;
+    data.resize?.({ w, h });
   }
+  data.resize?.({ w, h });
   videoEl.addEventListener('resize', handleResize);
   function drawCanvas() {
     canvas.width = w;

+ 5 - 5
src/utils/localStorage/app.ts

@@ -1,9 +1,9 @@
-import { LOCALSTORAGE_KEY } from '@/constant';
+import { lsKey } from '@/constant';
 import cache from '@/utils/cache';
 
-export const getLastBuildDateByLs = () => {
-  return cache.getStorage(LOCALSTORAGE_KEY.lastBuildDate);
+export const getLastBuildDate = () => {
+  return cache.getStorage<string>(lsKey.lastBuildDate);
 };
-export const setLastBuildDateByLs = (val: string) => {
-  return cache.setStorage(LOCALSTORAGE_KEY.lastBuildDate, val);
+export const setLastBuildDate = (val: string) => {
+  return cache.setStorage(lsKey.lastBuildDate, val);
 };

+ 12 - 0
src/utils/localStorage/user.ts

@@ -0,0 +1,12 @@
+import { lsKey } from '@/constant';
+import cache from '@/utils/cache';
+
+export const getToken = () => {
+  return cache.getStorage<string>(lsKey.token);
+};
+export const setToken = (val: string) => {
+  return cache.setStorage(lsKey.token, val);
+};
+export const clearToken = () => {
+  return cache.clearStorage(lsKey.token);
+};

+ 2 - 2
src/utils/request.ts

@@ -1,7 +1,7 @@
 import axios, { Axios, AxiosRequestConfig } from 'axios';
 
 import { useUserStore } from '@/store/user';
-import cache from '@/utils/cache';
+import { getToken } from '@/utils/localStorage/user';
 
 export interface MyAxiosPromise<T = any>
   extends Promise<{
@@ -28,7 +28,7 @@ class MyAxios {
     // 请求拦截器
     this.instance.interceptors.request.use(
       (cfg) => {
-        const token = cache.getStorageExp('token');
+        const token = getToken();
         if (token) {
           // eslint-disable-next-line
           cfg.headers.Authorization = `Bearer ${token}`;

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

@@ -115,8 +115,10 @@ import { usePull } from '@/hooks/use-pull';
 import { DanmuMsgTypeEnum, LiveRoomTypeEnum } from '@/interface';
 import router, { mobileRouterName } from '@/router';
 import { useAppStore } from '@/store/app';
+import { usePiniaCacheStore } from '@/store/cache';
 
 const route = useRoute();
+const cacheStore = usePiniaCacheStore();
 const appStore = useAppStore();
 
 const bottomRef = ref<HTMLDivElement>();
@@ -188,7 +190,7 @@ async function getLiveRoomInfo() {
 }
 
 function startPull() {
-  appStore.setMuted(false);
+  cacheStore.setMuted(false);
   showPlayBtn.value = false;
   handlePlay(appStore.liveRoomInfo!);
 }

+ 23 - 5
src/views/home/index.vue

@@ -37,7 +37,11 @@
             ></div>
           </div>
           <template v-if="currentLiveRoom">
-            <VideoControls></VideoControls>
+            <VideoControls
+              @click.stop
+              :resolution="videoHeight"
+              @refresh="handleRefresh"
+            ></VideoControls>
             <div
               class="join-btn"
               :class="{
@@ -171,8 +175,14 @@ const currentLiveRoom = ref<ILive>();
 const interactionList = ref(sliderList);
 const remoteVideoRef = ref<HTMLDivElement>();
 
-const { videoLoading, remoteVideo, handleStopDrawing, roomLiving, handlePlay } =
-  usePull();
+const {
+  videoLoading,
+  remoteVideo,
+  roomLiving,
+  videoHeight,
+  handleStopDrawing,
+  handlePlay,
+} = usePull();
 
 watch(
   () => remoteVideo.value,
@@ -186,9 +196,12 @@ watch(
   }
 );
 
-function changeLiveRoom(item: ILive) {
+function handleRefresh() {
+  playLive(currentLiveRoom.value!);
+}
+
+function playLive(item: ILive) {
   handleStopDrawing();
-  if (item.id === currentLiveRoom.value?.id) return;
   currentLiveRoom.value = item;
   canvasRef.value?.childNodes?.forEach((item) => {
     item.remove();
@@ -198,6 +211,11 @@ function changeLiveRoom(item: ILive) {
   handlePlay(item.live_room!);
 }
 
+function changeLiveRoom(item: ILive) {
+  if (item.id === currentLiveRoom.value?.id) return;
+  playLive(item);
+}
+
 async function getLiveRoomList() {
   try {
     const res = await fetchLiveList({

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

@@ -52,7 +52,7 @@
             class="media-list"
             :class="{ item: appStore.allTrack.length > 1 }"
           ></div>
-          <VideoControls></VideoControls>
+          <VideoControls :resolution="videoHeight"></VideoControls>
         </div>
       </div>
 
@@ -211,6 +211,7 @@ const {
   mySocketId,
   keydownDanmu,
   sendDanmu,
+  videoHeight,
   videoLoading,
   remoteVideo,
   roomLiving,

+ 27 - 28
src/views/push/index.vue

@@ -300,7 +300,7 @@ import { usePush } from '@/hooks/use-push';
 import { useRTCParams } from '@/hooks/use-rtc-params';
 import { DanmuMsgTypeEnum, MediaTypeEnum } from '@/interface';
 import { AppRootState, useAppStore } from '@/store/app';
-import { useResourceCacheStore } from '@/store/cache';
+import { usePiniaCacheStore } from '@/store/cache';
 import { useNetworkStore } from '@/store/network';
 import { useUserStore } from '@/store/user';
 import {
@@ -321,7 +321,7 @@ const route = useRoute();
 const userStore = useUserStore();
 const appStore = useAppStore();
 const networkStore = useNetworkStore();
-const resourceCacheStore = useResourceCacheStore();
+const cacheStore = usePiniaCacheStore();
 const { maxBitrate, maxFramerate, resolutionRatio, allMediaTypeList } =
   useRTCParams();
 
@@ -357,9 +357,7 @@ const containerRef = ref<HTMLDivElement>();
 const pushCanvasRef = ref<HTMLCanvasElement>();
 const webaudioVideo = ref<HTMLVideoElement>();
 const fabricCanvas = ref<fabric.Canvas>();
-const audioCtx = ref<AudioContext>();
 const startTime = ref(+new Date());
-const normalVolume = ref(70);
 // const startTime = ref(1692807352565); // 1693027352565
 
 const timeCanvasDom = ref<Raw<fabric.Text>[]>([]);
@@ -755,7 +753,7 @@ function handleScaling({ canvasDom, id }) {
         });
       }
     });
-    resourceCacheStore.setList(appStore.allTrack);
+    cacheStore.setResourceList(appStore.allTrack);
   });
 }
 function handleMoving({
@@ -781,14 +779,14 @@ function handleMoving({
         };
       }
     });
-    resourceCacheStore.setList(appStore.allTrack);
+    cacheStore.setResourceList(appStore.allTrack);
   });
 }
 
 async function handleCache() {
   const res: AppRootState['allTrack'] = [];
   const queue: any[] = [];
-  resourceCacheStore.list.forEach((item) => {
+  cacheStore['resource-list'].forEach((item) => {
     // @ts-ignore
     const obj: AppRootState['allTrack'][0] = {};
     obj.audio = item.audio;
@@ -1116,7 +1114,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     const audio = event.getAudioTracks();
     if (audio.length) {
       videoTrack.audio = 1;
-      videoTrack.volume = normalVolume.value;
+      videoTrack.volume = appStore.normalVolume;
       const audioTrack: AppRootState['allTrack'][0] = {
         id: videoTrack.id,
         audio: 1,
@@ -1134,12 +1132,12 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
       };
       const res = [...appStore.allTrack, videoTrack, audioTrack];
       appStore.setAllTrack(res);
-      resourceCacheStore.setList(res);
+      cacheStore.setResourceList(res);
       handleMixedAudio();
     } else {
       const res = [...appStore.allTrack, videoTrack];
       appStore.setAllTrack(res);
-      resourceCacheStore.setList(res);
+      cacheStore.setResourceList(res);
       // @ts-ignore
     }
 
@@ -1177,7 +1175,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
 
     const res = [...appStore.allTrack, videoTrack];
     appStore.setAllTrack(res);
-    resourceCacheStore.setList(res);
+    cacheStore.setResourceList(res);
     // @ts-ignore
     console.log('获取摄像头成功');
   } else if (val.type === MediaTypeEnum.microphone) {
@@ -1208,7 +1206,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     microphoneVideoTrack.videoEl = videoEl;
     const res = [...appStore.allTrack, microphoneVideoTrack];
     appStore.setAllTrack(res);
-    resourceCacheStore.setList(res);
+    cacheStore.setResourceList(res);
     handleMixedAudio();
 
     console.log('获取麦克风成功');
@@ -1256,7 +1254,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     // @ts-ignore
     appStore.setAllTrack(res);
     // @ts-ignore
-    resourceCacheStore.setList(res);
+    cacheStore.setResourceList(res);
 
     console.log('获取文字成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.time) {
@@ -1295,7 +1293,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     // @ts-ignore
     appStore.setAllTrack(res);
     // @ts-ignore
-    resourceCacheStore.setList(res);
+    cacheStore.setResourceList(res);
 
     console.log('获取时间成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.stopwatch) {
@@ -1335,7 +1333,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     // @ts-ignore
     appStore.setAllTrack(res);
     // @ts-ignore
-    resourceCacheStore.setList(res);
+    cacheStore.setResourceList(res);
 
     console.log('获取秒表成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.img) {
@@ -1397,7 +1395,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     // @ts-ignore
     appStore.setAllTrack(res);
     // @ts-ignore
-    resourceCacheStore.setList(res);
+    cacheStore.setResourceList(res);
 
     console.log('获取图片成功', fabricCanvas.value);
   } else if (val.type === MediaTypeEnum.media) {
@@ -1442,7 +1440,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
       if (stream.getAudioTracks()[0]) {
         console.log('视频有音频', stream.getAudioTracks()[0]);
         mediaVideoTrack.audio = 1;
-        mediaVideoTrack.volume = normalVolume.value;
+        mediaVideoTrack.volume = appStore.normalVolume;
         const audioTrack: AppRootState['allTrack'][0] = {
           id: mediaVideoTrack.id,
           audio: 1,
@@ -1460,7 +1458,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
         };
         const res = [...appStore.allTrack, audioTrack];
         appStore.setAllTrack(res);
-        resourceCacheStore.setList(res);
+        cacheStore.setResourceList(res);
         handleMixedAudio();
       }
     }
@@ -1468,7 +1466,7 @@ async function addMediaOk(val: AppRootState['allTrack'][0]) {
     // @ts-ignore
     appStore.setAllTrack(res);
     // @ts-ignore
-    resourceCacheStore.setList(res);
+    cacheStore.setResourceList(res);
 
     console.log('获取视频成功', fabricCanvas.value);
   }
@@ -1509,30 +1507,28 @@ function editMediaOk(val: AppRootState['allTrack'][0]) {
     return item;
   });
   appStore.setAllTrack(res);
-  resourceCacheStore.setList(res);
+  cacheStore.setResourceList(res);
 }
 
 function handleChangeMuted(item: AppRootState['allTrack'][0]) {
   if (item.videoEl) {
     const res = !item.videoEl.muted;
     item.videoEl.muted = res;
-    item.videoEl.volume = res ? 0 : normalVolume.value / 100;
-    item.volume = res ? 0 : normalVolume.value;
+    item.videoEl.volume = res ? 0 : appStore.normalVolume / 100;
+    item.volume = res ? 0 : appStore.normalVolume;
     item.muted = res;
-    resourceCacheStore.setList(appStore.allTrack);
+    cacheStore.setResourceList(appStore.allTrack);
     handleMixedAudio();
   }
 }
 
 function handleChangeVolume(item: AppRootState['allTrack'][0], v) {
-  console.log(item, item.volume, v);
   const res = appStore.allTrack.map((iten) => {
     if (iten.id === item.id) {
       if (item.volume !== undefined) {
         iten.volume = v;
         iten.muted = v === 0;
         if (iten.videoEl) {
-          console.log('kkkewk', iten.muted, v / 100);
           iten.videoEl.volume = v / 100;
           iten.videoEl.muted = v === 0;
         }
@@ -1541,7 +1537,7 @@ function handleChangeVolume(item: AppRootState['allTrack'][0], v) {
     return iten;
   });
   appStore.setAllTrack(res);
-  resourceCacheStore.setList(res);
+  cacheStore.setResourceList(res);
   handleMixedAudio();
 }
 
@@ -1550,17 +1546,20 @@ function handleDel(item: AppRootState['allTrack'][0]) {
   if (item.canvasDom !== undefined) {
     fabricCanvas.value?.remove(item.canvasDom);
     item.videoEl?.remove();
+    item.stream?.getTracks().forEach((track) => {
+      track.stop();
+      item.stream?.removeTrack(track);
+    });
   }
   bodyAppendChildElArr.value.forEach((el) => {
     const videoid = el.getAttribute('videoid');
     if (item.id === videoid) {
-      console.log('移除');
       el.remove();
     }
   });
   const res = appStore.allTrack.filter((iten) => iten.id !== item.id);
   appStore.setAllTrack(res);
-  resourceCacheStore.setList(res);
+  cacheStore.setResourceList(res);
   handleMixedAudio();
 }
 

+ 3 - 2
src/views/push/mediaModal/index.vue

@@ -67,7 +67,7 @@
             <div class="value">
               <n-upload
                 :max="1"
-                accept="image/png, image/jpeg, image/webp"
+                :accept="'image/png, image/jpeg, image/webp'"
                 :on-update:file-list="changImg"
               >
                 <n-button :disabled="isEdit">选择文件</n-button>
@@ -81,7 +81,7 @@
             <div class="value">
               <n-upload
                 :max="1"
-                accept="video/mp4, video/quicktime"
+                :accept="'video/mp4, video/quicktime'"
                 :on-update:file-list="changMedia"
               >
                 <n-button :disabled="isEdit">选择文件</n-button>
@@ -125,6 +125,7 @@ const props = withDefaults(
   {
     mediaType: MediaTypeEnum.camera,
     isEdit: false,
+    initData: undefined,
   }
 );
 const emits = defineEmits(['close', 'addOk', 'editOk']);