فهرست منبع

fix: 修复已知问题

shuisheng 2 سال پیش
والد
کامیت
d6156d14e0

+ 1 - 0
package.json

@@ -41,6 +41,7 @@
     "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",
     "pinia": "^2.0.33",

+ 19 - 0
pnpm-lock.yaml

@@ -32,6 +32,9 @@ dependencies:
   js-cookie:
     specifier: ^3.0.5
     version: 3.0.5
+  localforage:
+    specifier: ^1.10.0
+    version: 1.10.0
   mpegts.js:
     specifier: ^1.7.3
     version: 1.7.3
@@ -6619,6 +6622,10 @@ packages:
     engines: {node: '>= 4'}
     dev: true
 
+  /immediate@3.0.6:
+    resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+    dev: false
+
   /immutable@4.3.0:
     resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==}
 
@@ -7169,6 +7176,12 @@ packages:
       type-check: 0.4.0
     dev: true
 
+  /lie@3.1.1:
+    resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
+    dependencies:
+      immediate: 3.0.6
+    dev: false
+
   /lilconfig@2.0.5:
     resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==}
     engines: {node: '>=10'}
@@ -7262,6 +7275,12 @@ packages:
     engines: {node: '>=14'}
     dev: false
 
+  /localforage@1.10.0:
+    resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
+    dependencies:
+      lie: 3.1.1
+    dev: false
+
   /locate-path@2.0.0:
     resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
     engines: {node: '>=4'}

+ 15 - 0
src/App.vue

@@ -6,13 +6,28 @@
 import { isMobile } from 'billd-utils';
 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';
 
+const { appInfo } = useCheckUpdate();
 const userStore = useUserStore();
 
+function handleUpdate() {
+  const old = getLastBuildDateByLs();
+  if (appInfo.value.lastBuildDate !== old) {
+    localStorage.clear();
+  }
+  setLastBuildDateByLs(appInfo.value.lastBuildDate);
+}
+
 onMounted(() => {
+  handleUpdate();
   loginMessage();
   const token = cache.getStorageExp('token');
   if (token) {

+ 1 - 1
src/constant.ts

@@ -22,7 +22,7 @@ export const COOKIE_KEY = {
 
 // 全局的localStorage的key
 export const LOCALSTORAGE_KEY = {
-  verion: '0.0.1',
+  lastBuildDate: 'lastBuildDate',
 };
 
 export const bilibiliCollectiondetail =

+ 11 - 0
src/hooks/use-checkUpdate.ts

@@ -0,0 +1,11 @@
+import { ref } from 'vue';
+
+import { BilldHtmlWebpackPluginLog } from '@/interface';
+
+export const useCheckUpdate = () => {
+  const appInfo = ref<BilldHtmlWebpackPluginLog>(
+    // @ts-ignore
+    process.env.BilldHtmlWebpackPlugin as BilldHtmlWebpackPluginLog
+  );
+  return { appInfo };
+};

+ 4 - 4
src/hooks/use-rtc-params.ts

@@ -103,14 +103,14 @@ export const useRTCParams = () => {
         type: MediaTypeEnum.camera,
         txt: '摄像头',
       },
-      [MediaTypeEnum.screen]: {
-        type: MediaTypeEnum.screen,
-        txt: '窗口',
-      },
       [MediaTypeEnum.microphone]: {
         type: MediaTypeEnum.microphone,
         txt: '麦克风',
       },
+      [MediaTypeEnum.screen]: {
+        type: MediaTypeEnum.screen,
+        txt: '窗口',
+      },
       [MediaTypeEnum.txt]: {
         type: MediaTypeEnum.txt,
         txt: '文字',

+ 9 - 0
src/utils/localStorage/app.ts

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

+ 6 - 0
src/utils/localforage.ts

@@ -0,0 +1,6 @@
+import localforage from 'localforage';
+
+export const myLs = localforage.createInstance({
+  driver: localforage.LOCALSTORAGE,
+  name: 'billdlive',
+});

+ 3 - 1
src/views/home/index.vue

@@ -171,7 +171,8 @@ const currentLiveRoom = ref<ILive>();
 const interactionList = ref(sliderList);
 const remoteVideoRef = ref<HTMLDivElement>();
 
-const { videoLoading, remoteVideo, handleStopDrawing, roomLiving } = usePull();
+const { videoLoading, remoteVideo, handleStopDrawing, roomLiving, handlePlay } =
+  usePull();
 
 watch(
   () => remoteVideo.value,
@@ -194,6 +195,7 @@ function changeLiveRoom(item: ILive) {
   });
   appStore.setLiveRoomInfo(item.live_room!);
   roomLiving.value = true;
+  handlePlay(item.live_room!);
 }
 
 async function getLiveRoomList() {

+ 97 - 38
src/views/push/index.vue

@@ -253,15 +253,19 @@
       v-if="showSelectMediaModalCpt"
       :all-media-type-list="allMediaTypeList"
       @close="showSelectMediaModalCpt = false"
-      @ok="selectMediaOk"
+      @ok="handleShowMediaModalCpt"
     ></SelectMediaModalCpt>
 
     <MediaModalCpt
       v-if="showMediaModalCpt"
+      :is-edit="isEdit"
       :media-type="currentMediaType"
+      :init-data="currentMediaData"
       @close="showMediaModalCpt = false"
-      @ok="addMediaOk"
+      @add-ok="addMediaOk"
+      @edit-ok="editMediaOk"
     ></MediaModalCpt>
+
     <OpenMicophoneTipCpt
       v-if="showOpenMicophoneTipCpt"
       @close="showOpenMicophoneTipCpt = false"
@@ -277,7 +281,6 @@ import {
   VolumeMuteOutline,
 } from '@vicons/ionicons5';
 import { fabric } from 'fabric';
-import { UploadFileInfo } from 'naive-ui';
 import {
   Raw,
   markRaw,
@@ -340,9 +343,11 @@ const {
 } = usePush();
 
 const currentMediaType = ref(MediaTypeEnum.camera);
+const currentMediaData = ref<AppRootState['allTrack'][0]>();
 const showOpenMicophoneTipCpt = ref(false);
 const showSelectMediaModalCpt = ref(false);
 const showMediaModalCpt = ref(false);
+const isEdit = ref(false);
 const topRef = ref<HTMLDivElement>();
 const bottomRef = ref<HTMLDivElement>();
 const danmuListRef = ref<HTMLDivElement>();
@@ -496,12 +501,8 @@ function handleStartLive() {
 }
 
 function handleScale({ width, height }: { width: number; height: number }) {
-  const resolutionHeight =
-    currentResolutionRatio.value * window.devicePixelRatio;
-  const resolutionWidth =
-    currentResolutionRatio.value *
-    window.devicePixelRatio *
-    appStore.videoRatio;
+  const resolutionHeight = currentResolutionRatio.value;
+  const resolutionWidth = currentResolutionRatio.value * appStore.videoRatio;
   let ratio = 1;
   if (width > resolutionWidth) {
     const r1 = resolutionWidth / width;
@@ -802,6 +803,9 @@ async function handleCache() {
           muted: item.muted ? item.muted : false,
           appendChild: true,
         });
+        if (obj.volume !== undefined) {
+          videoEl.volume = obj.volume / 100;
+        }
         videoEl.src = url;
         bodyAppendChildElArr.value.push(videoEl);
         await new Promise((resolve) => {
@@ -885,6 +889,18 @@ async function handleCache() {
       }
     }
 
+    async function handleScreen() {
+      const event = await navigator.mediaDevices.getUserMedia({
+        video: true,
+        audio: { deviceId: obj.deviceId },
+      });
+      const videoEl = createVideo({ appendChild: true, muted: false });
+      videoEl.srcObject = event;
+      if (obj.volume !== undefined) {
+        videoEl.volume = obj.volume / 100;
+      }
+      obj.videoEl = videoEl;
+    }
     async function handleMicrophone() {
       const event = await navigator.mediaDevices.getUserMedia({
         video: false,
@@ -892,6 +908,9 @@ async function handleCache() {
       });
       const videoEl = createVideo({ appendChild: true, muted: false });
       videoEl.srcObject = event;
+      if (obj.volume !== undefined) {
+        videoEl.volume = obj.volume / 100;
+      }
       obj.videoEl = videoEl;
     }
 
@@ -933,6 +952,8 @@ async function handleCache() {
 
     if (item.type === MediaTypeEnum.media && item.video === 1) {
       queue.push(handleMediaVideo());
+    } else if (item.type === MediaTypeEnum.screen) {
+      queue.push(handleScreen());
     } else if (item.type === MediaTypeEnum.camera) {
       queue.push(handleCamera());
     } else if (item.type === MediaTypeEnum.microphone) {
@@ -1003,21 +1024,31 @@ async function handleCache() {
   appStore.setAllTrack(res);
 }
 
-function selectMediaOk(val: MediaTypeEnum) {
+function handleShowMediaModalCpt(val: MediaTypeEnum) {
+  isEdit.value = false;
+  currentMediaData.value = undefined;
   showMediaModalCpt.value = true;
   showSelectMediaModalCpt.value = false;
   currentMediaType.value = val;
 }
 
-function setScaleInfo({ track, canvasDom }) {
+function handleEdit(item: AppRootState['allTrack'][0]) {
+  console.log('handleEdit', item);
+  currentMediaType.value = item.type;
+  currentMediaData.value = item;
+  isEdit.value = true;
+  showMediaModalCpt.value = true;
+}
+
+function setScaleInfo({ track, canvasDom, scale = 1 }) {
   [1, 2, 3, 4].forEach((devicePixelRatio) => {
     track.scaleInfo[devicePixelRatio] = {
-      scaleX: 1 / devicePixelRatio,
-      scaleY: 1 / devicePixelRatio,
+      scaleX: (1 / devicePixelRatio) * scale,
+      scaleY: (1 / devicePixelRatio) * scale,
     };
   });
   if (window.devicePixelRatio !== 1) {
-    const ratio = 1 / window.devicePixelRatio;
+    const ratio = (1 / window.devicePixelRatio) * scale;
     canvasDom.scale(ratio);
     track.scaleInfo[window.devicePixelRatio] = {
       scaleX: ratio,
@@ -1026,16 +1057,7 @@ function setScaleInfo({ track, canvasDom }) {
   }
 }
 
-async function addMediaOk(val: {
-  type: MediaTypeEnum;
-  deviceId: string;
-  mediaName: string;
-  txtInfo?: { txt: string; color: string };
-  timeInfo?: { color: string };
-  stopwatchInfo?: { color: string };
-  imgInfo?: UploadFileInfo[];
-  mediaInfo?: UploadFileInfo[];
-}) {
+async function addMediaOk(val: AppRootState['allTrack'][0]) {
   if (!audioCtx.value) {
     audioCtx.value = new AudioContext();
   }
@@ -1068,7 +1090,8 @@ async function addMediaOk(val: {
       stream: event,
       id: videoTrack.id,
     });
-    setScaleInfo({ canvasDom, track: videoTrack });
+    console.log(scale, 'scalescale');
+    setScaleInfo({ canvasDom, track: videoTrack, scale });
     videoTrack.videoEl = videoEl;
     // @ts-ignore
     videoTrack.canvasDom = canvasDom;
@@ -1135,7 +1158,7 @@ async function addMediaOk(val: {
       stream: event,
       id: videoTrack.id,
     });
-    setScaleInfo({ canvasDom, track: videoTrack });
+    setScaleInfo({ canvasDom, track: videoTrack, scale });
     videoTrack.videoEl = videoEl;
     // @ts-ignore
     videoTrack.canvasDom = canvasDom;
@@ -1361,8 +1384,8 @@ async function addMediaOk(val: {
           height: imgEl.height,
         })
       );
-      const ratio = handleScale({ width: imgEl.width, height: imgEl.height });
-      setScaleInfo({ canvasDom, track: imgTrack });
+      const scale = handleScale({ width: imgEl.width, height: imgEl.height });
+      setScaleInfo({ canvasDom, track: imgTrack, scale });
       handleMoving({ canvasDom, id: imgTrack.id });
       handleScaling({ canvasDom, id: imgTrack.id });
       // @ts-ignore
@@ -1415,7 +1438,7 @@ async function addMediaOk(val: {
         stream,
         id: mediaVideoTrack.id,
       });
-      setScaleInfo({ canvasDom, track: mediaVideoTrack });
+      setScaleInfo({ canvasDom, track: mediaVideoTrack, scale });
       mediaVideoTrack.videoEl = videoEl;
       // @ts-ignore
       mediaVideoTrack.canvasDom = canvasDom;
@@ -1459,15 +1482,55 @@ async function addMediaOk(val: {
 
     console.log('获取视频成功', fabricCanvas.value);
   }
-  // canvasVideoStream.value = pushCanvasRef.value!.captureStream();
+}
+
+function editMediaOk(val: AppRootState['allTrack'][0]) {
+  showMediaModalCpt.value = false;
+  const res = appStore.allTrack.map((item) => {
+    if (item.id === val.id) {
+      item.mediaName = val.mediaName;
+      item.timeInfo = val.timeInfo;
+      item.stopwatchInfo = val.stopwatchInfo;
+      item.txtInfo = val.txtInfo;
+      if (
+        [
+          MediaTypeEnum.txt,
+          MediaTypeEnum.time,
+          MediaTypeEnum.stopwatch,
+        ].includes(val.type)
+      ) {
+        if (item.canvasDom) {
+          // @ts-ignore
+          item.canvasDom.set(
+            'fill',
+            val.txtInfo?.color ||
+              val.timeInfo?.color ||
+              val.stopwatchInfo?.color
+          );
+        }
+      }
+    }
+    return item;
+  });
+  appStore.setAllTrack(res);
+  resourceCacheStore.setList(res);
 }
 
 function updateVolume(item: AppRootState['allTrack'][0], v) {
   console.log(item, item.volume, v);
-  if (item.volume !== undefined) {
-    item.volume = v;
-    item.videoEl!.volume = v / 100;
-  }
+  const res = appStore.allTrack.map((iten) => {
+    if (iten.id === item.id) {
+      if (item.volume !== undefined) {
+        iten.volume = v;
+        if (iten.videoEl) {
+          iten.videoEl.volume = v / 100;
+        }
+      }
+    }
+    return iten;
+  });
+  appStore.setAllTrack(res);
+  resourceCacheStore.setList(res);
 }
 
 function handleChangeMuted(item: AppRootState['allTrack'][0]) {
@@ -1479,10 +1542,6 @@ function handleChangeMuted(item: AppRootState['allTrack'][0]) {
   }
 }
 
-function handleEdit(item: AppRootState['allTrack'][0]) {
-  console.log('handleEdit', item);
-}
-
 function handleDel(item: AppRootState['allTrack'][0]) {
   console.log('handleDel', item);
   if (item.canvasDom !== undefined) {

+ 63 - 24
src/views/push/mediaModal/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="media-wrap">
     <Modal
-      title="添加直播素材"
+      :title="`${isEdit ? '编辑' : '添加'}直播素材`"
       :mask-closable="false"
       @close="emits('close')"
     >
@@ -15,6 +15,7 @@
             <n-select
               v-model:value="currentInput.deviceId"
               :options="inputOptions"
+              :disabled="isEdit"
             />
           </div>
         </div>
@@ -69,7 +70,7 @@
                 accept="image/png, image/jpeg, image/webp"
                 :on-update:file-list="changImg"
               >
-                <n-button>选择文件</n-button>
+                <n-button :disabled="isEdit">选择文件</n-button>
               </n-upload>
             </div>
           </div>
@@ -83,7 +84,7 @@
                 accept="video/mp4, video/quicktime"
                 :on-update:file-list="changMedia"
               >
-                <n-button>选择文件</n-button>
+                <n-button :disabled="isEdit">选择文件</n-button>
               </n-upload>
             </div>
           </div>
@@ -109,7 +110,7 @@ import { InputInst, UploadFileInfo } from 'naive-ui';
 import { onMounted, ref } from 'vue';
 
 import { MediaTypeEnum } from '@/interface';
-import { useAppStore } from '@/store/app';
+import { AppRootState, useAppStore } from '@/store/app';
 
 const inputInstRef = ref<InputInst | null>(null);
 const mediaName = ref('');
@@ -118,12 +119,15 @@ const appStore = useAppStore();
 const props = withDefaults(
   defineProps<{
     mediaType?: MediaTypeEnum;
+    isEdit: boolean;
+    initData?: AppRootState['allTrack'][0];
   }>(),
   {
     mediaType: MediaTypeEnum.camera,
+    isEdit: false,
   }
 );
-const emits = defineEmits(['close', 'ok']);
+const emits = defineEmits(['close', 'addOk', 'editOk']);
 
 const inputOptions = ref<{ label: string; value: string }[]>([]);
 const txtInfo = ref<{ txt: string; color: string }>();
@@ -161,28 +165,42 @@ function handleOk() {
       return;
     }
   }
-  if (props.mediaType === MediaTypeEnum.img) {
-    if (imgInfo.value?.length! !== 1) {
-      window.$message.info('请选择图片!');
-      return;
+  if (!props.isEdit) {
+    if (props.mediaType === MediaTypeEnum.img) {
+      if (imgInfo.value?.length! !== 1) {
+        window.$message.info('请选择图片!');
+        return;
+      }
     }
-  }
-  if (props.mediaType === MediaTypeEnum.media) {
-    if (mediaInfo.value?.length! !== 1) {
-      window.$message.info('请选择视频!');
-      return;
+    if (props.mediaType === MediaTypeEnum.media) {
+      if (mediaInfo.value?.length! !== 1) {
+        window.$message.info('请选择视频!');
+        return;
+      }
     }
   }
-
-  emits('ok', {
-    ...currentInput.value,
-    mediaName: mediaName.value,
-    txtInfo: txtInfo.value,
-    imgInfo: imgInfo.value,
-    mediaInfo: mediaInfo.value,
-    timeInfo: timeInfo.value,
-    stopwatchInfo: stopwatchInfo.value,
-  });
+  if (props.isEdit) {
+    emits('editOk', {
+      ...props.initData,
+      ...currentInput.value,
+      mediaName: mediaName.value,
+      txtInfo: txtInfo.value,
+      imgInfo: imgInfo.value,
+      mediaInfo: mediaInfo.value,
+      timeInfo: timeInfo.value,
+      stopwatchInfo: stopwatchInfo.value,
+    });
+  } else {
+    emits('addOk', {
+      ...currentInput.value,
+      mediaName: mediaName.value,
+      txtInfo: txtInfo.value,
+      imgInfo: imgInfo.value,
+      mediaInfo: mediaInfo.value,
+      timeInfo: timeInfo.value,
+      stopwatchInfo: stopwatchInfo.value,
+    });
+  }
 }
 
 async function init() {
@@ -294,6 +312,27 @@ async function init() {
         .filter((item) => !item.hidden).length + 1
     }`;
   }
+  if (props.initData) {
+    if (
+      [MediaTypeEnum.camera, MediaTypeEnum.microphone].includes(
+        props.initData.type
+      )
+    ) {
+      currentInput.value = {
+        deviceId: props.initData.deviceId!,
+        type: props.initData.type,
+      };
+    }
+    mediaName.value = props.initData.mediaName;
+    timeInfo.value = props.initData.timeInfo;
+    txtInfo.value = props.initData.txtInfo;
+    stopwatchInfo.value = props.initData.stopwatchInfo;
+    console.log(
+      props.initData.deviceId,
+      props.initData.type,
+      props.initData.mediaName
+    );
+  }
 }
 </script>