shuisheng 2 лет назад
Родитель
Сommit
e63a6b2c1e

+ 4 - 1
.eslintrc.js

@@ -37,7 +37,10 @@ module.exports = {
   parser: 'vue-eslint-parser',
   parserOptions: {
     parser: '@typescript-eslint/parser',
-    ecmaVersion: 2022,
+    ecmaVersion: 'latest',
+    ecmaFeatures: {
+      jsx: true,
+    },
     tsconfigRootDir: __dirname, // https://typescript-eslint.io/docs/linting/typed-linting
     project: ['./tsconfig.json'], // https://typescript-eslint.io/docs/linting/typed-linting
   },

+ 3 - 3
.vscode/settings.json

@@ -2,7 +2,7 @@
   // 指定行尾序列为\n(LF)或者\r\n(CRLF)或者auto
   // "files.eol": "\n",
 
-  "vue.codeActions.savingTimeLimit": 500,
+  "vue.codeActions.savingTimeLimit": 1000,
 
   // 在保存时格式化
   "editor.formatOnSave": true,
@@ -13,8 +13,8 @@
 
   // 保存时进行一些操作
   "editor.codeActionsOnSave": {
-    "source.fixAll.eslint": false, // 运行eslint
-    "source.organizeImports": false // 整理import语句(包括import的成员),以及会删掉未使用的导入,注意:会删掉declare global {import utils from 'billd-utils';}的import utils from 'billd-utils';
+    "source.fixAll.eslint": true, // 运行eslint
+    "source.organizeImports": true // 整理import语句(包括import的成员),以及会删掉未使用的导入,注意:会删掉declare global {import utils from 'billd-utils';}的import utils from 'billd-utils';
     // "source.sortImports": true // 对您的导入进行排序,然而,与organizeImports不同,它不会删除任何未使用的导入,也不会对import里面的成员进行排序
   },
 

+ 2 - 2
package.json

@@ -38,8 +38,6 @@
     "billd-scss": "^0.0.7",
     "billd-utils": "^0.0.12",
     "browser-tool": "^1.0.5",
-    "esbuild-loader": "^3.1.0",
-    "eslint-import-resolver-typescript": "^3.5.5",
     "fabric": "^5.3.0",
     "flv.js": "^1.6.2",
     "js-cookie": "^3.0.5",
@@ -83,7 +81,9 @@
     "css-minimizer-webpack-plugin": "^3.0.0",
     "cz-conventional-changelog": "^3.3.0",
     "cz-customizable": "^7.0.0",
+    "esbuild-loader": "^3.1.0",
     "eslint": "^8.13.0",
+    "eslint-import-resolver-typescript": "^3.5.5",
     "eslint-plugin-import": "^2.28.0",
     "eslint-webpack-plugin": "^3.1.1",
     "file-loader": "^6.2.0",

+ 14 - 58
script/config/webpack.common.ts

@@ -146,6 +146,20 @@ const commonConfig = (isProduction) => {
        */
       publicPath: outputStaticUrl(isProduction),
     },
+    cache: {
+      type: 'memory',
+      // allowCollectingMemory: true, // 它在生产模式中默认为false,并且在开发模式下默认为true。https://webpack.js.org/configuration/cache/#cacheallowcollectingmemory
+      // buildDependencies: {
+      //   // 建议cache.buildDependencies.config: [__filename]在您的 webpack 配置中设置以获取最新配置和所有依赖项。
+      //   config: [
+      //     resolveApp('./script/config/webpack.common.ts'),
+      //     resolveApp('./script/config/webpack.dev.ts'),
+      //     resolveApp('./script/config/webpack.prod.ts'),
+      //     resolveApp('.browserslistrc'), // 防止修改了.browserslistrc文件后,但没修改webpack配置文件,webpack不读取最新更新后的.browserslistrc
+      //     resolveApp('babel.config.js'), // 防止修改了babel.config.js文件后,但没修改webpack配置文件,webpack不读取最新更新后的babel.config.js
+      //   ],
+      // },
+    },
     resolve: {
       // 解析路径
       extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.mjs'], // 解析扩展名,加上.mjs是因为vant,https://github.com/youzan/vant/issues/10738
@@ -189,64 +203,6 @@ const commonConfig = (isProduction) => {
             },
           ],
         },
-        // {
-        //   test: /\.jsx?$/,
-        //   exclude: /node_modules/,
-        //   use: [
-        //     // 'thread-loader',
-        //     {
-        //       loader: 'babel-loader',
-        //       options: {
-        //         cacheDirectory: true,
-        //         cacheCompression: false, // https://github.com/facebook/create-react-app/issues/6846
-        //       },
-        //     },
-        //   ],
-        // },
-        // {
-        //   test: /\.ts$/,
-        //   exclude: /node_modules/,
-        //   use: [
-        //     {
-        //       loader: 'babel-loader',
-        //       options: {
-        //         cacheDirectory: true,
-        //         cacheCompression: false, // https://github.com/facebook/create-react-app/issues/6846
-        //       },
-        //     },
-        //     {
-        //       loader: 'ts-loader',
-        //       options: {
-        //         appendTsSuffixTo: ['\\.vue$'],
-        //         // If you want to speed up compilation significantly you can set this flag. https://www.npmjs.com/package/ts-loader#transpileonly
-        //         transpileOnly: true,
-        //         happyPackMode: false,
-        //       },
-        //     },
-        //   ],
-        // },
-        // {
-        //   test: /\.tsx$/,
-        //   exclude: /node_modules/,
-        //   use: [
-        //     {
-        //       loader: 'babel-loader',
-        //       options: {
-        //         cacheDirectory: true,
-        //         cacheCompression: false, // https://github.com/facebook/create-react-app/issues/6846
-        //       },
-        //     },
-        //     {
-        //       loader: 'ts-loader',
-        //       options: {
-        //         appendTsxSuffixTo: ['\\.vue$'],
-        //         // If you want to speed up compilation significantly you can set this flag. https://www.npmjs.com/package/ts-loader#transpileonly
-        //         transpileOnly: true,
-        //         happyPackMode: false,
-        //       },
-        //     },
-        //   ],
-        // },
         {
           test: /\.css$/,
           oneOf: [

+ 5 - 30
script/config/webpack.dev.ts

@@ -2,6 +2,7 @@ import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
 import portfinder from 'portfinder';
 import { Configuration } from 'webpack';
 import WebpackBar from 'webpackbar';
+import { VueLoaderPlugin } from 'vue-loader';
 
 import { outputStaticUrl, webpackBarEnable } from '../constant';
 import TerminalPrintPlugin from '../TerminalPrintPlugin';
@@ -24,23 +25,9 @@ export default new Promise((resolve) => {
         // https://github.com/webpack/webpack/blob/main/lib/config/defaults.js
         mode: 'development',
         stats: 'none',
-        // cache: {
-        //   type: 'filesystem',
-        //   allowCollectingMemory: true, // 它在生产模式中默认为false,并且在开发模式下默认为true。https://webpack.js.org/configuration/cache/#cacheallowcollectingmemory
-        //   buildDependencies: {
-        //     // 建议cache.buildDependencies.config: [__filename]在您的 webpack 配置中设置以获取最新配置和所有依赖项。
-        //     config: [
-        //       resolveApp('./script/config/webpack.common.ts'),
-        //       resolveApp('./script/config/webpack.dev.ts'),
-        //       resolveApp('./script/config/webpack.prod.ts'),
-        //       resolveApp('.browserslistrc'), // 防止修改了.browserslistrc文件后,但没修改webpack配置文件,webpack不读取最新更新后的.browserslistrc
-        //       resolveApp('babel.config.js'), // 防止修改了babel.config.js文件后,但没修改webpack配置文件,webpack不读取最新更新后的babel.config.js
-        //     ],
-        //   },
-        // },
         // https://webpack.docschina.org/configuration/devtool/
-        // devtool: 'eval-cheap-module-source-map',
-        devtool: 'eval', // eval,具有最高性能的开发构建的推荐选择。
+        devtool: 'eval-cheap-module-source-map',
+        // devtool: 'eval', // eval,具有最高性能的开发构建的推荐选择。
         // 这个infrastructureLogging设置参考了vuecli5,如果不设置,webpack-dev-server会打印一些信息
         infrastructureLogging: {
           level: 'none',
@@ -106,20 +93,7 @@ export default new Promise((resolve) => {
         module: {
           rules: [
             {
-              test: /\.jsx?$/,
-              exclude: /node_modules/,
-              use: [
-                {
-                  loader: 'esbuild-loader',
-                  options: {
-                    loader: 'jsx', // Remove this if you're not using JSX
-                    target: 'esnext', // Syntax to compile to (see options below for possible values)
-                  },
-                },
-              ],
-            },
-            {
-              test: /\.tsx?$/,
+              test: /\.(js|mjs|jsx|ts|tsx)$/,
               exclude: /node_modules/,
               use: [
                 {
@@ -135,6 +109,7 @@ export default new Promise((resolve) => {
         },
         // @ts-ignore
         plugins: [
+          // new VueLoaderPlugin(),
           // 构建进度条
           webpackBarEnable && new WebpackBar(),
           // 终端打印调试地址

+ 42 - 40
script/config/webpack.prod.ts

@@ -11,6 +11,7 @@ import TerserPlugin from 'terser-webpack-plugin';
 // import { version as vueVersion } from 'vue/package.json';
 import { Configuration } from 'webpack';
 import WebpackBar from 'webpackbar';
+import { VueLoaderPlugin } from 'vue-loader';
 
 import { gzipEnable } from '../constant';
 import { chalkINFO } from '../utils/chalkTip';
@@ -135,47 +136,48 @@ const prodConfig: Configuration = {
     //   name: 'runtime'
     // }
   },
-  // module: {
-  //   rules: [
-  //     {
-  //       test: /\.jsx?$/,
-  //       exclude: /node_modules/,
-  //       use: [
-  //         // 'thread-loader',
-  //         {
-  //           loader: 'babel-loader',
-  //           options: {
-  //             cacheDirectory: true,
-  //             cacheCompression: false, // https://github.com/facebook/create-react-app/issues/6846
-  //           },
-  //         },
-  //       ],
-  //     },
-  //     {
-  //       test: /\.tsx?$/,
-  //       exclude: /node_modules/,
-  //       use: [
-  //         {
-  //           loader: 'babel-loader',
-  //           options: {
-  //             cacheDirectory: true,
-  //             cacheCompression: false, // https://github.com/facebook/create-react-app/issues/6846
-  //           },
-  //         },
-  //         {
-  //           loader: 'ts-loader',
-  //           options: {
-  //             appendTsxSuffixTo: ['\\.vue$'],
-  //             // If you want to speed up compilation significantly you can set this flag. https://www.npmjs.com/package/ts-loader#transpileonly
-  //             transpileOnly: true,
-  //             happyPackMode: false,
-  //           },
-  //         },
-  //       ],
-  //     },
-  //   ],
-  // },
+  module: {
+    rules: [
+      {
+        test: /\.jsx?$/,
+        exclude: /node_modules/,
+        use: [
+          // 'thread-loader',
+          {
+            loader: 'babel-loader',
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false, // https://github.com/facebook/create-react-app/issues/6846
+            },
+          },
+        ],
+      },
+      {
+        test: /\.tsx?$/,
+        exclude: /node_modules/,
+        use: [
+          {
+            loader: 'babel-loader',
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false, // https://github.com/facebook/create-react-app/issues/6846
+            },
+          },
+          {
+            loader: 'ts-loader',
+            options: {
+              appendTsxSuffixTo: ['\\.vue$'],
+              // If you want to speed up compilation significantly you can set this flag. https://www.npmjs.com/package/ts-loader#transpileonly
+              transpileOnly: true,
+              happyPackMode: false,
+            },
+          },
+        ],
+      },
+    ],
+  },
   plugins: [
+    // new VueLoaderPlugin(),
     // 构建进度条
     new WebpackBar(),
     // http压缩

+ 1 - 1
src/components/Avatar/index.vue

@@ -19,7 +19,7 @@
 </template>
 
 <script lang="ts" setup>
-const props = defineProps({
+defineProps({
   avatar: { type: String, default: '' },
   size: { type: Number, default: 100 },
   living: { type: Boolean, default: false },

+ 1 - 1
src/components/Dropdown/index.vue

@@ -22,7 +22,7 @@
 <script lang="ts" setup>
 import { isMobile } from 'billd-utils';
 
-const props = defineProps({
+defineProps({
   modelValue: { type: Boolean, default: false },
 });
 const emits = defineEmits(['update:modelValue']);

+ 11 - 0
src/constant.ts

@@ -1,3 +1,5 @@
+import { MediaTypeEnum } from '@/interface';
+
 export const QQ_CLIENT_ID = '101958191';
 export const QQ_OAUTH_URL = 'https://graph.qq.com/oauth2.0';
 export const QQ_REDIRECT_URI = 'https://live.hsslive.cn/oauth/qq_login';
@@ -22,3 +24,12 @@ export const COOKIE_KEY = {
 export const LOCALSTORAGE_KEY = {
   verion: '0.0.1',
 };
+
+export const mediaTypeEnumMap = {
+  [MediaTypeEnum.camera]: '摄像头',
+  [MediaTypeEnum.microphone]: '麦克风',
+  [MediaTypeEnum.screen]: '窗口',
+  [MediaTypeEnum.img]: '图片',
+  [MediaTypeEnum.txt]: '文字',
+  [MediaTypeEnum.media]: '视频',
+};

+ 6 - 2
src/hooks/use-pull.ts

@@ -42,8 +42,13 @@ export function usePull({
   const {
     getSocketId,
     initWs,
+    addTrack,
+    delTrack,
+    canvasVideoStream,
+    lastCoverImg,
     roomLiveing,
     liveRoomInfo,
+    anchorInfo,
     roomNoLive,
     loopHeartbeatTimer,
     localStream,
@@ -55,8 +60,6 @@ export function usePull({
     currentMaxFramerate,
     currentMaxBitrate,
     currentResolutionRatio,
-    addTrack,
-    delTrack,
   } = useWs();
 
   const { flvPlayer, flvVideoEl, startFlvPlay, destroyFlv } = useFlvPlay();
@@ -317,5 +320,6 @@ export function usePull({
     sidebarList,
     danmuStr,
     liveRoomInfo,
+    anchorInfo,
   };
 }

+ 14 - 11
src/hooks/use-push.ts

@@ -181,6 +181,19 @@ export function usePush({
     }
   }
 
+  function connectWs() {
+    initWs({
+      isAnchor: true,
+      roomId: roomId.value,
+      isSRS,
+      isPull: false,
+      currentMaxBitrate: currentMaxBitrate.value,
+      currentMaxFramerate: currentMaxFramerate.value,
+      currentResolutionRatio: currentResolutionRatio.value,
+      roomLiveType: isSRS ? liveTypeEnum.srsPush : liveTypeEnum.webrtcPush,
+    });
+  }
+
   async function startLive() {
     if (!loginTip()) return;
     const flag = await userHasLiveRoom();
@@ -208,17 +221,6 @@ export function usePush({
         }
       }
     }
-    initWs({
-      isAnchor: true,
-      roomId: roomId.value,
-      isSRS,
-      isPull: false,
-      currentMaxBitrate: currentMaxBitrate.value,
-      currentMaxFramerate: currentMaxFramerate.value,
-      currentResolutionRatio: currentResolutionRatio.value,
-      roomLiveType: isSRS ? liveTypeEnum.srsPush : liveTypeEnum.webrtcPush,
-    });
-    return;
   }
 
   /** 结束直播 */
@@ -313,6 +315,7 @@ export function usePush({
     roomName,
     damuList,
     liveUserList,
+    connectWs,
     addTrack,
     delTrack,
   };

+ 12 - 8
src/hooks/use-ws.ts

@@ -10,12 +10,13 @@ import {
   IDanmu,
   IHeartbeat,
   IJoin,
-  ILive,
+  ILiveRoom,
   ILiveUser,
   IMessage,
   IOffer,
   IOtherJoin,
   IUpdateJoinInfo,
+  IUser,
   LiveRoomTypeEnum,
   liveTypeEnum,
 } from '@/interface';
@@ -41,7 +42,8 @@ export const useWs = () => {
   const roomName = ref('');
   const roomNoLive = ref(false);
   const roomLiveing = ref<IJoin['data']>();
-  const liveRoomInfo = ref<ILive>();
+  const liveRoomInfo = ref<ILiveRoom>();
+  const anchorInfo = ref<IUser>();
   const isAnchor = ref(false);
   const roomLiveType = ref(liveTypeEnum.srsFlvPull);
   const joined = ref(false);
@@ -781,7 +783,9 @@ export const useWs = () => {
     // 用户加入房间完成
     ws.socketIo.on(WsMsgTypeEnum.joined, (data: IJoin) => {
       prettierReceiveWebsocket(WsMsgTypeEnum.joined, data);
-      handleHeartbeat(data.data.live?.id || -1);
+      if (data.data.live) {
+        handleHeartbeat(data.data.live.id!);
+      }
       joined.value = true;
       trackInfo.track_audio = data.data.live?.track_audio!;
       trackInfo.track_video = data.data.live?.track_video!;
@@ -789,9 +793,8 @@ export const useWs = () => {
         id: `${getSocketId()}`,
         userInfo: data.user_info,
       });
-      if (!isAnchor.value) {
-        liveRoomInfo.value = data.data;
-      }
+      liveRoomInfo.value = data.data.live_room;
+      anchorInfo.value = data.data.anchor_info;
       // 如果是srs开播,则不需要等有人进来了才new webrtc,只要Websocket连上了就开始new webrtc
       if (isSRS.value) {
         if (!isPull.value) {
@@ -808,12 +811,12 @@ export const useWs = () => {
       prettierReceiveWebsocket(WsMsgTypeEnum.otherJoin, data);
       liveUserList.value.push({
         id: data.data.join_socket_id,
-        userInfo: data.data.liveRoom.user,
+        userInfo: data.data.join_user_info,
       });
       const danmu: IDanmu = {
         msgType: DanmuMsgTypeEnum.otherJoin,
         socket_id: data.data.join_socket_id,
-        userInfo: data.data.liveRoom.user,
+        userInfo: data.data.join_user_info,
         msg: '',
       };
       damuList.value.push(danmu);
@@ -897,6 +900,7 @@ export const useWs = () => {
     lastCoverImg,
     roomLiveing,
     liveRoomInfo,
+    anchorInfo,
     roomNoLive,
     loopHeartbeatTimer,
     localStream,

+ 7 - 3
src/interface.ts

@@ -371,12 +371,15 @@ export interface IMessage {
   };
 }
 
-export type IOtherJoin = {
+export interface IOtherJoin {
   data: {
-    liveRoom: IUserLiveRoom;
+    live_room: ILiveRoom;
+    live_room_user_info: IUser;
+    join_user_info?: IUser;
     join_socket_id: string;
   };
-};
+}
+
 export interface IJoin {
   socket_id: string;
   is_anchor: boolean;
@@ -384,6 +387,7 @@ export interface IJoin {
   data: {
     live?: ILive;
     live_room: ILiveRoom;
+    anchor_info?: IUser;
     // track: { audio: number; video: number };
   };
 }

+ 1 - 1
src/store/app/index.ts

@@ -24,7 +24,7 @@ export type AppRootState = {
     videoEl?: HTMLVideoElement;
     txtInfo?: { txt: string; color: string };
     rect?: { top: number; left: number };
-    scaleInfo?: { scaleX: number; scalcY: number };
+    scaleInfo?: { scaleX: number; scaleY: number };
   }[];
 };
 

+ 93 - 0
src/utils/index.ts

@@ -2,6 +2,99 @@
 
 import { getRangeRandom } from 'billd-utils';
 
+/**
+ * requestFileSystem保存文件,成功返回code:1,失败返回code:2
+ * @param data
+ */
+export function saveFile(data: { file: File; fileName: string }) {
+  return new Promise<{ code: number }>((resolve) => {
+    const { file, fileName } = data;
+    const requestFileSystem =
+      // @ts-ignore
+      window.requestFileSystem || window.webkitRequestFileSystem;
+    if (!requestFileSystem) {
+      console.error('不支持requestFileSystem');
+      resolve({ code: 2 });
+      return;
+    }
+    function onError(err) {
+      console.error('saveFile错误', data.fileName);
+      console.log(err);
+      resolve({ code: 2 });
+    }
+    function onFs(fs) {
+      // 创建文件
+      fs.root.getFile(
+        fileName,
+        { create: true },
+        (fileEntry) => {
+          // 创建文件写入流
+          fileEntry.createWriter(function (fileWriter) {
+            fileWriter.onwriteend = () => {
+              // 完成后关闭文件
+              fileWriter.abort();
+              resolve({ code: 1 });
+            };
+            // 写入文件内容
+            fileWriter.write(file);
+          });
+        },
+        onError
+      );
+    }
+    // Opening a file system with temporary storage
+    requestFileSystem(
+      // @ts-ignore
+      window.PERSISTENT,
+      0,
+      onFs,
+      onError
+    );
+  });
+}
+
+/**
+ * requestFileSystem读取文件,成功返回code:1,失败返回code:2
+ * @param data
+ */
+export function readFile(fileName: string) {
+  return new Promise<{ code: number; file?: File }>((resolve) => {
+    const requestFileSystem =
+      // @ts-ignore
+      window.requestFileSystem || window.webkitRequestFileSystem;
+    if (!requestFileSystem) {
+      console.error('不支持requestFileSystem');
+      resolve({ code: 2 });
+      return;
+    }
+    function onError(err) {
+      console.error('readFile错误', fileName);
+      console.log(err);
+      resolve({ code: 2 });
+    }
+    function onFs(fs) {
+      fs.root.getFile(
+        fileName,
+        {},
+        (fileEntry) => {
+          fileEntry.file(function (file) {
+            resolve({ code: 1, file });
+          }, onError);
+        },
+        onError
+      );
+    }
+    // Opening a file system with temporary storage
+    requestFileSystem(
+      // @ts-ignore
+      window.PERSISTENT,
+      0,
+      onFs,
+      onError
+    );
+  });
+}
+
 export function generateBase64(dom: CanvasImageSource) {
   const canvas = document.createElement('canvas');
   // @ts-ignore

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

@@ -69,7 +69,7 @@ function goRoom(item: ILiveRoom) {
     name: routerName.pull,
     params: { roomId: item.id },
     query: {
-      liveType: liveTypeEnum.srsFlvPull,
+      liveType: liveTypeEnum.srsHlsPull,
     },
   });
 }

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

@@ -386,7 +386,7 @@ function joinRoom(data: { roomId: number; isFlv: boolean }) {
           position: absolute;
           top: 50%;
           left: 50%;
-          z-index: 1;
+          z-index: 20;
           display: none;
           align-items: center;
           align-items: center;

+ 160 - 149
src/views/pull/index.vue

@@ -1,181 +1,182 @@
 <template>
   <div class="pull-wrap">
-    <template v-if="roomNoLive">当前房间没在直播~</template>
-    <template v-else>
-      <div class="left">
-        <div
-          ref="topRef"
-          class="head"
-        >
-          <div class="info">
-            <div
-              class="avatar"
-              :style="{
-                backgroundImage: `url(${roomLiveing?.live?.user?.avatar})`,
-              }"
-            ></div>
-            <div class="detail">
-              <div class="top">{{ roomLiveing?.live?.user?.username }}</div>
-              <div class="bottom">
-                <span>{{ roomLiveing?.live?.live_room?.name }}</span>
-                <span v-if="NODE_ENV === 'development'">
-                  socketId:{{ getSocketId() }}
-                </span>
-              </div>
+    <div class="left">
+      <div
+        ref="topRef"
+        class="head"
+      >
+        <div class="info">
+          <div
+            class="avatar"
+            :style="{
+              backgroundImage: `url(${anchorInfo?.avatar})`,
+            }"
+          ></div>
+          <div class="detail">
+            <div class="top">{{ anchorInfo?.username }}</div>
+            <div class="bottom">
+              <span>{{ liveRoomInfo?.name }}</span>
+              <span v-if="NODE_ENV === 'development'">
+                socketId:{{ getSocketId() }}
+              </span>
             </div>
           </div>
         </div>
+      </div>
+      <div
+        ref="containerRef"
+        class="container"
+      >
+        <div
+          class="no-live"
+          v-if="roomNoLive"
+        >
+          当前房间没在直播~
+        </div>
         <div
-          ref="containerRef"
-          class="container"
+          v-else
+          v-loading="videoLoading"
+          class="video-wrap"
         >
           <div
-            v-loading="videoLoading"
-            class="video-wrap"
-          >
-            <div
-              class="cover"
-              :style="{
-                backgroundImage: `url(${
-                  roomLiveing?.live?.live_room?.cover_img ||
-                  roomLiveing?.live?.user?.avatar
-                })`,
-              }"
-            ></div>
-            <div
-              ref="canvasRef"
-              class="media-list"
-              :class="{ item: appStore.allTrack.length > 1 }"
-            ></div>
-            <VideoControls></VideoControls>
-          </div>
+            class="cover"
+            :style="{
+              backgroundImage: `url(${
+                liveRoomInfo?.cover_img || anchorInfo?.avatar
+              })`,
+            }"
+          ></div>
+          <div
+            ref="canvasRef"
+            class="media-list"
+            :class="{ item: appStore.allTrack.length > 1 }"
+          ></div>
+          <VideoControls></VideoControls>
         </div>
+      </div>
 
+      <div
+        ref="bottomRef"
+        v-loading="giftLoading"
+        class="gift-list"
+      >
         <div
-          ref="bottomRef"
-          v-loading="giftLoading"
-          class="gift-list"
+          v-for="(item, index) in giftGoodsList"
+          :key="index"
+          class="item"
         >
           <div
-            v-for="(item, index) in giftGoodsList"
-            :key="index"
-            class="item"
+            class="ico"
+            :style="{ backgroundImage: `url(${item.cover})` }"
           >
             <div
-              class="ico"
-              :style="{ backgroundImage: `url(${item.cover})` }"
+              v-if="item.badge"
+              class="badge"
+              :style="{ backgroundColor: item.badge_bg }"
             >
-              <div
-                v-if="item.badge"
-                class="badge"
-                :style="{ backgroundColor: item.badge_bg }"
-              >
-                <span class="txt">{{ item.badge }}</span>
-              </div>
-            </div>
-            <div class="name">{{ item.name }}</div>
-            <div class="price">¥{{ item.price }}</div>
-          </div>
-          <div
-            class="item"
-            @click="handleRecharge"
-          >
-            <div class="ico wallet"></div>
-            <div class="name">
-              余额:{{ userStore.userInfo?.wallet?.balance }}
+              <span class="txt">{{ item.badge }}</span>
             </div>
-            <div class="price">立即充值</div>
           </div>
+          <div class="name">{{ item.name }}</div>
+          <div class="price">¥{{ item.price }}</div>
         </div>
-      </div>
-      <div class="right">
-        <div class="tab">
-          <span>在线用户</span>
-          <span> | </span>
-          <span>排行榜</span>
+        <div
+          class="item"
+          @click="handleRecharge"
+        >
+          <div class="ico wallet"></div>
+          <div class="name">余额:{{ userStore.userInfo?.wallet?.balance }}</div>
+          <div class="price">立即充值</div>
         </div>
-        <div class="user-list">
-          <div
-            v-for="(item, index) in liveUserList.filter(
-              (item) => item.id !== getSocketId()
-            )"
-            :key="index"
-            class="item"
-          >
-            <div class="info">
-              <div
-                class="avatar"
-                :style="{ backgroundImage: `url(${item.userInfo?.avatar})` }"
-              ></div>
-              <div class="username">
-                {{ item.userInfo?.username || item.id }}
-              </div>
-            </div>
-          </div>
-          <div
-            v-if="userStore.userInfo"
-            class="item"
-          >
-            <div class="info">
-              <img
-                :src="userStore.userInfo.avatar"
-                class="avatar"
-                alt=""
-              />
-              <div class="username">{{ userStore.userInfo.username }}</div>
+      </div>
+    </div>
+    <div class="right">
+      <div class="tab">
+        <span>在线用户</span>
+        <span> | </span>
+        <span>排行榜</span>
+      </div>
+      <div class="user-list">
+        <div
+          v-for="(item, index) in liveUserList.filter(
+            (item) => item.id !== getSocketId()
+          )"
+          :key="index"
+          class="item"
+        >
+          <div class="info">
+            <div
+              class="avatar"
+              :style="{ backgroundImage: `url(${item.userInfo?.avatar})` }"
+            ></div>
+            <div class="username">
+              {{ item.userInfo?.username || item.id }}
             </div>
           </div>
         </div>
         <div
-          ref="danmuListRef"
-          class="danmu-list"
+          v-if="userStore.userInfo"
+          class="item"
         >
-          <div
-            v-for="(item, index) in damuList"
-            :key="index"
-            class="item"
-          >
-            <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
-              <span class="name">
-                {{ item.userInfo?.username || item.socket_id }}:
-              </span>
-              <span class="msg">{{ item.msg }}</span>
-            </template>
-            <template v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin">
-              <span class="name system">系统通知:</span>
-              <span class="msg">
-                {{ item.userInfo?.username || item.socket_id }}进入直播!
-              </span>
-            </template>
-            <template v-else-if="item.msgType === DanmuMsgTypeEnum.userLeaved">
-              <span class="name system">系统通知:</span>
-              <span class="msg">
-                {{ item.userInfo?.username || item.socket_id }}离开直播!
-              </span>
-            </template>
+          <div class="info">
+            <img
+              :src="userStore.userInfo.avatar"
+              class="avatar"
+              alt=""
+            />
+            <div class="username">{{ userStore.userInfo.username }}</div>
           </div>
         </div>
-        <div class="send-msg">
-          <textarea
-            v-model="danmuStr"
-            class="ipt"
-            @keydown="keydownDanmu"
-          ></textarea>
-          <div
-            class="btn"
-            @click="sendDanmu"
-          >
-            发送
-          </div>
+      </div>
+      <div
+        ref="danmuListRef"
+        class="danmu-list"
+      >
+        <div
+          v-for="(item, index) in damuList"
+          :key="index"
+          class="item"
+        >
+          <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
+            <span class="name">
+              {{ item.userInfo?.username || item.socket_id }}:
+            </span>
+            <span class="msg">{{ item.msg }}</span>
+          </template>
+          <template v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin">
+            <span class="name system">系统通知:</span>
+            <span class="msg">
+              {{ item.userInfo?.username || item.socket_id }}进入直播!
+            </span>
+          </template>
+          <template v-else-if="item.msgType === DanmuMsgTypeEnum.userLeaved">
+            <span class="name system">系统通知:</span>
+            <span class="msg">
+              {{ item.userInfo?.username || item.socket_id }}离开直播!
+            </span>
+          </template>
+        </div>
+      </div>
+      <div class="send-msg">
+        <textarea
+          v-model="danmuStr"
+          class="ipt"
+          @keydown="keydownDanmu"
+        ></textarea>
+        <div
+          class="btn"
+          @click="sendDanmu"
+        >
+          发送
         </div>
       </div>
-      <RechargeCpt v-if="showRecharge"></RechargeCpt>
-    </template>
+    </div>
+    <RechargeCpt v-if="showRecharge"></RechargeCpt>
+    <!-- </template> -->
   </div>
 </template>
 
 <script lang="ts" setup>
-import { NODE_ENV } from 'script/constant';
 import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
@@ -190,6 +191,7 @@ import {
 } from '@/interface';
 import { useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';
+import { NODE_ENV } from 'script/constant';
 
 import RechargeCpt from './recharge/index.vue';
 
@@ -216,15 +218,13 @@ const {
   keydownDanmu,
   sendDanmu,
   addVideo,
-  roomLiveing,
-  autoplayVal,
   videoLoading,
   roomNoLive,
   damuList,
   liveUserList,
-  sidebarList,
   danmuStr,
   liveRoomInfo,
+  anchorInfo,
 } = usePull({
   localVideoRef,
   canvasRef,
@@ -382,12 +382,23 @@ onMounted(() => {
       align-items: center;
       justify-content: space-between;
       height: 562px;
+      background-color: rgba($color: #000000, $alpha: 0.5);
+
+      .no-live {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        z-index: 20;
+        color: white;
+        font-size: 28px;
+        transform: translate(-50%, -50%);
+      }
       .video-wrap {
         position: relative;
         overflow: hidden;
         flex: 1;
         height: 100%;
-        background-color: rgba($color: #000000, $alpha: 0.5);
+
         .cover {
           position: absolute;
           background-position: center center;

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

@@ -45,10 +45,8 @@ import { onMounted, ref } from 'vue';
 
 import { MediaTypeEnum } from '@/interface';
 import { useAppStore } from '@/store/app';
-import { useNetworkStore } from '@/store/network';
 
 const mediaName = ref('');
-const networkStore = useNetworkStore();
 const appStore = useAppStore();
 
 const props = withDefaults(

+ 165 - 290
src/views/pushByCanvas/index.vue

@@ -264,17 +264,24 @@ import {
 } from '@vicons/ionicons5';
 import { fabric } from 'fabric';
 import { UploadFileInfo } from 'naive-ui';
-import { NODE_ENV } from 'script/constant';
 import { markRaw, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 import * as workerTimers from 'worker-timers';
 
+import { mediaTypeEnumMap } from '@/constant';
 import { usePush } from '@/hooks/use-push';
 import { DanmuMsgTypeEnum, MediaTypeEnum, liveTypeEnum } from '@/interface';
 import { AppRootState, useAppStore } from '@/store/app';
 import { useResourceCacheStore } from '@/store/cache';
 import { useUserStore } from '@/store/user';
-import { createVideo, generateBase64, getRandomEnglishString } from '@/utils';
+import {
+  createVideo,
+  generateBase64,
+  getRandomEnglishString,
+  readFile,
+  saveFile,
+} from '@/utils';
+import { NODE_ENV } from 'script/constant';
 
 import MediaModalCpt from './mediaModal/index.vue';
 import OpenMicophoneTipCpt from './openMicophoneTip/index.vue';
@@ -302,9 +309,8 @@ const wrapSize = reactive({
   width: 0,
   height: 0,
 });
-
-const scaleRatio = ref(0);
-const timerId = ref(-1);
+const workerTimerId = ref(-1);
+const requestAnimationFrameId = ref(-1);
 const videoRatio = ref(16 / 9);
 const {
   confirmRoomName,
@@ -329,21 +335,13 @@ const {
   liveUserList,
   addTrack,
   delTrack,
+  connectWs,
 } = usePush({
   localVideoRef,
   remoteVideoRef,
   isSRS,
 });
 
-const mediaTypeEnumMap = {
-  [MediaTypeEnum.camera]: '摄像头',
-  [MediaTypeEnum.microphone]: '麦克风',
-  [MediaTypeEnum.screen]: '窗口',
-  [MediaTypeEnum.img]: '图片',
-  [MediaTypeEnum.txt]: '文字',
-  [MediaTypeEnum.media]: '视频',
-};
-
 watch(
   () => damuList.value.length,
   () => {
@@ -355,23 +353,43 @@ watch(
   }
 );
 
+onMounted(() => {
+  setTimeout(() => {
+    scrollTo(0, 0);
+  }, 100);
+  // initNullAudio();
+  initUserMedia();
+  initCanvas();
+  handleCache();
+  connectWs();
+  document.addEventListener('visibilitychange', onPageVisibility);
+});
+
+onUnmounted(() => {
+  document.removeEventListener('visibilitychange', onPageVisibility);
+  if (workerTimerId.value !== -1) {
+    workerTimers.clearInterval(workerTimerId.value);
+  }
+  clearFrame();
+});
+
 // 处理页面显示/隐藏
 function onPageVisibility() {
   // 注意:此属性在Page Visibility Level 2 规范中被描述为“历史” 。考虑改用该Document.visibilityState 属性。
   // const isHidden = document.hidden;
 
   if (document.visibilityState === 'hidden') {
-    console.log(new Date().toLocaleString(), '页面隐藏了', timerId.value);
+    console.log(new Date().toLocaleString(), '页面隐藏了', workerTimerId.value);
     if (isLiving.value) {
       const delay = 1000 / 60; // 16.666666666666668
-      timerId.value = workerTimers.setInterval(() => {
+      workerTimerId.value = workerTimers.setInterval(() => {
         fabricCanvas.value?.renderAll();
       }, delay);
     }
   } else {
-    console.log(new Date().toLocaleString(), '页面显示了', timerId.value);
+    console.log(new Date().toLocaleString(), '页面显示了', workerTimerId.value);
     if (isLiving.value) {
-      workerTimers.clearInterval(timerId.value);
+      workerTimers.clearInterval(workerTimerId.value);
     }
   }
 }
@@ -404,9 +422,13 @@ function initUserMedia() {
     });
 }
 
+function clearFrame() {
+  window.cancelAnimationFrame(requestAnimationFrameId.value);
+}
+
 function renderFrame() {
   fabricCanvas.value?.renderAll();
-  window.requestAnimationFrame(renderFrame);
+  requestAnimationFrameId.value = window.requestAnimationFrame(renderFrame);
 }
 
 // 处理空音频轨
@@ -577,12 +599,9 @@ function autoCreateVideo({
   return new Promise<{
     canvasDom: fabric.Image;
     videoEl: HTMLVideoElement;
-    clearFrame: any;
     scale: number;
   }>((resolve) => {
-    console.log(videoEl, 888211888888);
     videoEl.onloadedmetadata = () => {
-      console.log('kkkkkkk2kkk', 123);
       const width = stream.getVideoTracks()[0].getSettings().width!;
       const height = stream.getVideoTracks()[0].getSettings().height!;
       const ratio = handleScale({ width, height });
@@ -604,26 +623,12 @@ function autoCreateVideo({
         canvasDom.height,
         canvasDom
       );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
-
+      handleMoving({ canvasDom, id });
+      handleScaling({ canvasDom, id });
       canvasDom.scale(ratio);
       fabricCanvas.value!.add(canvasDom);
-      let timer;
-
-      function clearFrame() {
-        window.cancelAnimationFrame(timer);
-      }
-
-      renderFrame();
 
-      resolve({ canvasDom, scale: ratio, videoEl, clearFrame });
+      resolve({ canvasDom, scale: ratio, videoEl });
     };
   });
 }
@@ -728,7 +733,6 @@ function initCanvas() {
   const wrapWidth = containerRef.value!.getBoundingClientRect().width;
   // const wrapWidth = 1920;
   const ratio = wrapWidth / resolutionWidth;
-  scaleRatio.value = resolutionWidth / wrapWidth;
   const wrapHeight = resolutionHeight * ratio;
   // const wrapHeight = 1080;
   // lower-canvas: 实际的canvas画面,也就是pushCanvasRef
@@ -742,9 +746,34 @@ function initCanvas() {
   wrapSize.width = wrapWidth;
   wrapSize.height = wrapHeight;
   fabricCanvas.value = ins;
+  renderFrame();
   changeCanvasStyle();
 }
 
+function handleScaling({ canvasDom, id }) {
+  canvasDom.on('scaling', () => {
+    appStore.allTrack.forEach((item) => {
+      if (id === item.id) {
+        item.scaleInfo = {
+          scaleX: canvasDom.scaleX || 1,
+          scaleY: canvasDom.scaleY || 1,
+        };
+      }
+    });
+    resourceCacheStore.setList(appStore.allTrack);
+  });
+}
+function handleMoving({ canvasDom, id }) {
+  canvasDom.on('moving', () => {
+    appStore.allTrack.forEach((item) => {
+      if (id === item.id) {
+        item.rect = { top: canvasDom.top || 0, left: canvasDom.left || 0 };
+      }
+    });
+    resourceCacheStore.setList(appStore.allTrack);
+  });
+}
+
 async function handleCache() {
   const res: AppRootState['allTrack'] = [];
   const queue: any[] = [];
@@ -762,141 +791,98 @@ async function handleCache() {
     obj.scaleInfo = item.scaleInfo;
 
     async function handleMediaVideo() {
-      const file = await readFile(item.id);
-      const url = URL.createObjectURL(file);
-      console.log(file, file.name, url);
-      const videoEl = createVideo({});
-      videoEl.src = url;
-      videoEl.muted = item.muted ? item.muted : false;
-      videoEl.style.width = `1px`;
-      videoEl.style.height = `1px`;
-      videoEl.style.position = 'fixed';
-      videoEl.style.bottom = '0';
-      videoEl.style.right = '0';
-      videoEl.style.opacity = '0';
-      videoEl.style.pointerEvents = 'none';
-      document.body.appendChild(videoEl);
-      await new Promise((resolve) => {
-        videoEl.onloadedmetadata = () => {
-          const stream = videoEl
-            // @ts-ignore
-            .captureStream();
-          const width = stream.getVideoTracks()[0].getSettings().width!;
-          const height = stream.getVideoTracks()[0].getSettings().height!;
-          // const ratio = handleScale({ width, height });
-          videoEl.width = width;
-          videoEl.height = height;
+      const { code, file } = await readFile(item.id);
+      if (code === 1 && file) {
+        const url = URL.createObjectURL(file);
+        console.log(file, file.name, url);
+        const videoEl = createVideo({});
+        videoEl.src = url;
+        videoEl.muted = item.muted ? item.muted : false;
+        videoEl.style.width = `1px`;
+        videoEl.style.height = `1px`;
+        videoEl.style.position = 'fixed';
+        videoEl.style.bottom = '0';
+        videoEl.style.right = '0';
+        videoEl.style.opacity = '0';
+        videoEl.style.pointerEvents = 'none';
+        document.body.appendChild(videoEl);
+        await new Promise((resolve) => {
+          videoEl.onloadedmetadata = () => {
+            const stream = videoEl
+              // @ts-ignore
+              .captureStream();
+            const width = stream.getVideoTracks()[0].getSettings().width!;
+            const height = stream.getVideoTracks()[0].getSettings().height!;
+            // const ratio = handleScale({ width, height });
+            videoEl.width = width;
+            videoEl.height = height;
+
+            const canvasDom = markRaw(
+              new fabric.Image(videoEl, {
+                top: item.rect?.top || 0,
+                left: item.rect?.left || 0,
+                width,
+                height,
+              })
+            );
+            handleMoving({ canvasDom, id: item.id });
+            handleScaling({ canvasDom, id: item.id });
+            canvasDom.scale(item.scaleInfo?.scaleX || 1);
+            fabricCanvas.value!.add(canvasDom);
+            obj.videoEl = videoEl;
+            obj.canvasDom = canvasDom;
+            resolve({ videoEl, canvasDom });
+          };
+        });
+        const stream = videoEl
+          // @ts-ignore
+          .captureStream() as MediaStream;
+        obj.stream = stream;
+        obj.streamid = stream.id;
+        obj.track = stream.getVideoTracks()[0];
+        obj.trackid = stream.getVideoTracks()[0].id;
+      } else {
+        console.error('读取文件失败');
+      }
+    }
 
+    async function handleImg() {
+      const { code, file } = await readFile(item.id);
+      if (code === 1 && file) {
+        const imgEl = await new Promise<HTMLImageElement>((resolve) => {
+          const reader = new FileReader();
+          reader.addEventListener(
+            'load',
+            function () {
+              const img = document.createElement('img');
+              img.src = reader.result as string;
+              img.onload = () => {
+                resolve(img);
+              };
+            },
+            false
+          );
+          if (file) {
+            reader.readAsDataURL(file);
+          }
+        });
+        if (fabricCanvas.value) {
           const canvasDom = markRaw(
-            new fabric.Image(videoEl, {
+            new fabric.Image(imgEl, {
               top: item.rect?.top || 0,
               left: item.rect?.left || 0,
-              width,
-              height,
+              width: imgEl.width,
+              height: imgEl.height,
             })
           );
-          canvasDom.on('moving', () => {
-            appStore.allTrack.forEach((iten) => {
-              if (item.id === iten.id) {
-                iten.rect = { top: canvasDom.top!, left: canvasDom.left! };
-              }
-            });
-            resourceCacheStore.setList(appStore.allTrack);
-          });
+          handleMoving({ canvasDom, id: obj.id });
+          handleScaling({ canvasDom, id: obj.id });
           canvasDom.scale(item.scaleInfo?.scaleX || 1);
-          fabricCanvas.value!.add(canvasDom);
-
-          renderFrame();
-          obj.videoEl = videoEl;
+          fabricCanvas.value.add(canvasDom);
           obj.canvasDom = canvasDom;
-          resolve({ videoEl, canvasDom });
-        };
-      });
-      const stream = videoEl
-        // @ts-ignore
-        .captureStream() as MediaStream;
-      obj.stream = stream;
-      obj.streamid = stream.id;
-      obj.track = stream.getVideoTracks()[0];
-      obj.trackid = stream.getVideoTracks()[0].id;
-      // if (stream.getAudioTracks()[0]) {
-      //   console.log('视频有音频', stream.getAudioTracks()[0]);
-      //   mediaVideoTrack.audio = 1;
-      //   const audioTrack: AppRootState['allTrack'][0] = {
-      //     id: mediaVideoTrack.id,
-      //     audio: 1,
-      //     video: 2,
-      //     mediaName: val.mediaName,
-      //     type: MediaTypeEnum.media,
-      //     track: stream.getAudioTracks()[0],
-      //     trackid: stream.getAudioTracks()[0].id,
-      //     stream,
-      //     streamid: stream.id,
-      //     hidden: true,
-      //     muted: false,
-      //   };
-      //   // @ts-ignore
-      //   const res = [...appStore.allTrack, audioTrack];
-      //   appStore.setAllTrack(res);
-      //   resourceCacheStore.setList(res);
-      //   handleMixedAudio();
-      //   // @ts-ignore
-
-      //   addTrack(audioTrack);
-      // }
-    }
-
-    async function handleImg() {
-      const file = await readFile(item.id);
-      const imgEl = await new Promise<HTMLImageElement>((resolve) => {
-        const reader = new FileReader();
-        reader.addEventListener(
-          'load',
-          function () {
-            const img = document.createElement('img');
-            img.src = reader.result as string;
-            img.onload = () => {
-              resolve(img);
-            };
-          },
-          false
-        );
-        if (file) {
-          reader.readAsDataURL(file);
         }
-      });
-      if (fabricCanvas.value) {
-        const canvasDom = markRaw(
-          new fabric.Image(imgEl, {
-            top: item.rect?.top || 0,
-            left: item.rect?.left || 0,
-            width: imgEl.width,
-            height: imgEl.height,
-          })
-        );
-        canvasDom.on('moving', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.on('scaling', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.scaleInfo = {
-                scaleX: canvasDom.scaleX || 1,
-                scalcY: canvasDom.scaleY || 1,
-              };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.scale(item.scaleInfo?.scaleX || 1);
-        fabricCanvas.value.add(canvasDom);
-        obj.canvasDom = canvasDom;
-        renderFrame();
+      } else {
+        console.error('读取文件失败');
       }
     }
     if (item.type === MediaTypeEnum.media && item.video === 1) {
@@ -911,18 +897,11 @@ async function handleCache() {
             fill: item.txtInfo?.color,
           })
         );
-        canvasDom.on('moving', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
+        handleMoving({ canvasDom, id: obj.id });
+        handleScaling({ canvasDom, id: obj.id });
         canvasDom.scale(item.scaleInfo?.scaleX || 1);
         fabricCanvas.value.add(canvasDom);
         obj.canvasDom = canvasDom;
-        renderFrame();
       }
     } else if (item.type === MediaTypeEnum.img) {
       queue.push(handleImg());
@@ -934,21 +913,6 @@ async function handleCache() {
   appStore.setAllTrack(res);
 }
 
-onMounted(() => {
-  setTimeout(() => {
-    scrollTo(0, 0);
-  }, 100);
-  // initNullAudio();
-  initUserMedia();
-  initCanvas();
-  handleCache();
-  document.addEventListener('visibilitychange', onPageVisibility);
-});
-
-onUnmounted(() => {
-  document.removeEventListener('visibilitychange', onPageVisibility);
-});
-
 function selectMediaOk(val: MediaTypeEnum) {
   showMediaModalCpt.value = true;
   showSelectMediaModalCpt.value = false;
@@ -1113,19 +1077,12 @@ async function addMediaOk(val: {
           fill: val.txtInfo?.color,
         })
       );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (txtTrack.id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
+      handleMoving({ canvasDom, id: txtTrack.id });
+      handleScaling({ canvasDom, id: txtTrack.id });
       txtTrack.txtInfo = val.txtInfo;
       // @ts-ignore
       txtTrack.canvasDom = canvasDom;
       fabricCanvas.value.add(canvasDom);
-      renderFrame();
     }
 
     const res = [...appStore.allTrack, txtTrack];
@@ -1153,10 +1110,11 @@ async function addMediaOk(val: {
     };
 
     if (fabricCanvas.value) {
+      if (!val.imgInfo) return;
+      const file = val.imgInfo[0].file!;
+      const { code } = await saveFile({ file, fileName: imgTrack.id });
+      if (code !== 1) return;
       const imgEl = await new Promise<HTMLImageElement>((resolve) => {
-        if (!val.imgInfo) return;
-        const file = val.imgInfo[0].file!;
-        saveFile({ file, fileName: imgTrack.id });
         const reader = new FileReader();
         reader.addEventListener(
           'load',
@@ -1182,21 +1140,14 @@ async function addMediaOk(val: {
           height: imgEl.height,
         })
       );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (imgTrack.id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
+      handleMoving({ canvasDom, id: imgTrack.id });
+      handleScaling({ canvasDom, id: imgTrack.id });
       const ratio = handleScale({ width: imgEl.width, height: imgEl.height });
       // @ts-ignore
       imgTrack.canvasDom = canvasDom;
-      imgTrack.scaleInfo = { scaleX: ratio, scalcY: ratio };
+      imgTrack.scaleInfo = { scaleX: ratio, scaleY: ratio };
       canvasDom.scale(ratio);
       fabricCanvas.value.add(canvasDom);
-      renderFrame();
     }
 
     const res = [...appStore.allTrack, imgTrack];
@@ -1225,7 +1176,8 @@ async function addMediaOk(val: {
     if (fabricCanvas.value) {
       if (!val.mediaInfo) return;
       const file = val.mediaInfo[0].file!;
-      saveFile({ file, fileName: mediaVideoTrack.id });
+      const { code } = await saveFile({ file, fileName: mediaVideoTrack.id });
+      if (code !== 1) return;
       const url = URL.createObjectURL(file);
       console.log(file, file.name, url);
       const videoEl = createVideo({});
@@ -1295,7 +1247,6 @@ async function addMediaOk(val: {
 }
 
 function handleChangeMuted(item: AppRootState['allTrack'][0]) {
-  console.log('handleChangeMuted', item);
   if (item.videoEl) {
     const res = !item.videoEl.muted;
     item.videoEl.muted = res;
@@ -1321,82 +1272,6 @@ function handleDel(item: AppRootState['allTrack'][0]) {
   delTrack(item);
 }
 
-function saveFile(data: { file: File; fileName: string }) {
-  const { file, fileName } = data;
-  const requestFileSystem =
-    // @ts-ignore
-    window.requestFileSystem || window.webkitRequestFileSystem;
-  function onError(err) {
-    console.error('saveFile错误', data.fileName);
-    console.log(err);
-  }
-  function onFs(fs) {
-    // 创建文件
-    fs.root.getFile(
-      fileName,
-      { create: true },
-      function (fileEntry) {
-        // 创建文件写入流
-        fileEntry.createWriter(function (fileWriter) {
-          fileWriter.onwriteend = () => {
-            // 完成后关闭文件
-            fileWriter.abort();
-          };
-          // 写入文件内容
-          fileWriter.write(file);
-        });
-      },
-      () => {
-        console.log('写入文件失败');
-      }
-    );
-  }
-  // Opening a file system with temporary storage
-  requestFileSystem(
-    // @ts-ignore
-    window.PERSISTENT,
-    0,
-    onFs,
-    onError
-  );
-}
-
-function readFile(fileName: string) {
-  return new Promise<File>((resolve) => {
-    const requestFileSystem =
-      // @ts-ignore
-      window.requestFileSystem || window.webkitRequestFileSystem;
-    function onError(err) {
-      console.error('readFile错误', fileName);
-      console.log(err);
-    }
-    function onFs(fs) {
-      fs.root.getFile(
-        fileName,
-        {},
-        function (fileEntry) {
-          fileEntry.file(function (file) {
-            resolve(file);
-            // const url = URL.createObjectURL(file);
-            // const videoEl = createVideo({});
-            // videoEl.src = url;
-            // document.body.appendChild(videoEl);
-          }, onError);
-        },
-        onError
-      );
-    }
-    // Opening a file system with temporary storage
-    requestFileSystem(
-      // @ts-ignore
-      window.PERSISTENT,
-      0,
-      onFs,
-      onError
-    );
-  });
-}
-
 function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
   currentMediaType.value = item.type;
   showMediaModalCpt.value = true;

+ 0 - 1639
src/views/pushByCanvas/index2.vue

@@ -1,1639 +0,0 @@
-<template>
-  <div class="push-wrap">
-    <div
-      ref="topRef"
-      class="left"
-    >
-      <div
-        ref="containerRef"
-        class="container"
-      >
-        <canvas
-          id="pushCanvasRef"
-          ref="pushCanvasRef"
-        ></canvas>
-        <div
-          v-if="appStore.allTrack.filter((item) => !item.hidden).length <= 0"
-          class="add-wrap"
-        >
-          <n-space>
-            <n-button
-              v-for="(item, index) in allMediaTypeList"
-              :key="index"
-              class="item"
-              @click="handleStartMedia(item)"
-            >
-              {{ item.txt }}
-            </n-button>
-          </n-space>
-        </div>
-      </div>
-
-      <div
-        ref="bottomRef"
-        class="room-control"
-      >
-        <div class="info">
-          <div
-            class="avatar"
-            :style="{ backgroundImage: `url(${userStore.userInfo?.avatar})` }"
-          ></div>
-          <div class="detail">
-            <div class="top">
-              <n-input-group>
-                <n-input
-                  v-model:value="roomName"
-                  size="small"
-                  placeholder="输入房间名"
-                  :style="{ width: '50%' }"
-                />
-                <n-button
-                  size="small"
-                  type="primary"
-                  @click="confirmRoomName"
-                >
-                  确定
-                </n-button>
-              </n-input-group>
-            </div>
-            <div class="bottom">
-              <span v-if="NODE_ENV === 'development'">
-                {{ getSocketId() }}
-              </span>
-            </div>
-          </div>
-        </div>
-        <div class="rtc">
-          <div class="item">
-            <div class="txt">码率设置</div>
-            <div class="down">
-              <n-select
-                v-model:value="currentMaxBitrate"
-                :options="maxBitrate"
-              />
-            </div>
-          </div>
-          <div class="item">
-            <div class="txt">帧率设置</div>
-            <div class="down">
-              <n-select
-                v-model:value="currentMaxFramerate"
-                :options="maxFramerate"
-              />
-            </div>
-          </div>
-          <div class="item">
-            <div class="txt">分辨率设置</div>
-            <div class="down">
-              <n-select
-                v-model:value="currentResolutionRatio"
-                :options="resolutionRatio"
-              />
-            </div>
-          </div>
-        </div>
-        <div class="other">
-          <div class="top">
-            <span class="item">
-              <i class="ico"></i>
-              <span>
-                正在观看:
-                {{
-                  liveUserList.filter((item) => item.id !== getSocketId())
-                    .length
-                }}
-              </span>
-            </span>
-          </div>
-          <div class="bottom">
-            <n-button
-              v-if="!isLiving"
-              type="info"
-              size="small"
-              @click="handleStartLive"
-            >
-              开始直播
-            </n-button>
-            <n-button
-              v-else
-              type="error"
-              size="small"
-              @click="endLive"
-            >
-              结束直播
-            </n-button>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <div class="right">
-      <div class="resource-card">
-        <div class="title">素材列表</div>
-        <div class="list">
-          <div
-            v-for="(item, index) in appStore.allTrack.filter(
-              (item) => !item.hidden
-            )"
-            :key="index"
-            class="item"
-          >
-            <span class="name">
-              ({{ mediaTypeEnumMap[item.type] }}){{ item.mediaName }}
-            </span>
-            <div class="control">
-              <div
-                v-if="item.audio === 1"
-                class="control-item"
-                @click="handleChangeMuted(item)"
-              >
-                <n-icon size="16">
-                  <VolumeMuteOutline v-if="item.muted"></VolumeMuteOutline>
-                  <VolumeHighOutline v-else></VolumeHighOutline>
-                </n-icon>
-              </div>
-              <div
-                class="control-item"
-                @click="handleEdit(item)"
-              >
-                <n-icon size="16">
-                  <CreateOutline></CreateOutline>
-                </n-icon>
-              </div>
-              <div
-                class="control-item"
-                @click="handleDel(item)"
-              >
-                <n-icon size="16">
-                  <Close></Close>
-                </n-icon>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="bottom">
-          <n-button
-            size="small"
-            type="primary"
-            @click="showSelectMediaModalCpt = true"
-          >
-            添加素材
-          </n-button>
-        </div>
-      </div>
-      <div class="danmu-card">
-        <div class="title">弹幕互动</div>
-        <div class="list-wrap">
-          <div
-            ref="danmuListRef"
-            class="list"
-          >
-            <div
-              v-for="(item, index) in damuList"
-              :key="index"
-              class="item"
-            >
-              <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
-                <span class="name">
-                  {{ item.userInfo?.username || item.socket_id }}:
-                </span>
-                <span class="msg">{{ item.msg }}</span>
-              </template>
-              <template v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin">
-                <span class="name system">系统通知:</span>
-                <span class="msg">
-                  <span>{{ item.userInfo?.username || item.socket_id }}</span>
-                  <span>进入直播!</span>
-                </span>
-              </template>
-              <template
-                v-else-if="item.msgType === DanmuMsgTypeEnum.userLeaved"
-              >
-                <span class="name system">系统通知:</span>
-                <span class="msg">
-                  <span>{{ item.userInfo?.username || item.socket_id }}</span>
-                  <span>离开直播!</span>
-                </span>
-              </template>
-            </div>
-          </div>
-        </div>
-        <div class="send-msg">
-          <input
-            v-model="danmuStr"
-            class="ipt"
-            @keydown="keydownDanmu"
-          />
-          <n-button
-            type="info"
-            size="small"
-            @click="sendDanmu"
-          >
-            发送
-          </n-button>
-        </div>
-      </div>
-    </div>
-
-    <SelectMediaModalCpt
-      v-if="showSelectMediaModalCpt"
-      :all-media-type-list="allMediaTypeList"
-      @close="showSelectMediaModalCpt = false"
-      @ok="selectMediaOk"
-    ></SelectMediaModalCpt>
-
-    <MediaModalCpt
-      v-if="showMediaModalCpt"
-      :media-type="currentMediaType"
-      @close="showMediaModalCpt = false"
-      @ok="addMediaOk"
-    ></MediaModalCpt>
-    <OpenMicophoneTipCpt
-      v-if="showOpenMicophoneTipCpt"
-      @close="showOpenMicophoneTipCpt = false"
-    ></OpenMicophoneTipCpt>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import {
-  Close,
-  CreateOutline,
-  VolumeHighOutline,
-  VolumeMuteOutline,
-} from '@vicons/ionicons5';
-import { fabric } from 'fabric';
-import { UploadFileInfo } from 'naive-ui';
-import { NODE_ENV } from 'script/constant';
-import { markRaw, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
-import { useRoute } from 'vue-router';
-import * as workerTimers from 'worker-timers';
-
-import { usePush } from '@/hooks/use-push';
-import { DanmuMsgTypeEnum, MediaTypeEnum, liveTypeEnum } from '@/interface';
-import { AppRootState, useAppStore } from '@/store/app';
-import { useResourceCacheStore } from '@/store/cache';
-import { useUserStore } from '@/store/user';
-import { createVideo, generateBase64, getRandomEnglishString } from '@/utils';
-
-import MediaModalCpt from './mediaModal/index.vue';
-import OpenMicophoneTipCpt from './openMicophoneTip/index.vue';
-import SelectMediaModalCpt from './selectMediaModal/index.vue';
-
-const route = useRoute();
-const userStore = useUserStore();
-const appStore = useAppStore();
-const resourceCacheStore = useResourceCacheStore();
-const currentMediaType = ref(MediaTypeEnum.camera);
-const showOpenMicophoneTipCpt = ref(false);
-const showSelectMediaModalCpt = ref(false);
-const showMediaModalCpt = ref(false);
-const topRef = ref<HTMLDivElement>();
-const bottomRef = ref<HTMLDivElement>();
-const danmuListRef = ref<HTMLDivElement>();
-const containerRef = ref<HTMLDivElement>();
-const pushCanvasRef = ref<HTMLCanvasElement>();
-const fabricCanvas = ref<fabric.Canvas>();
-const localVideoRef = ref<HTMLVideoElement>();
-const audioCtx = ref<AudioContext>();
-const remoteVideoRef = ref<HTMLVideoElement[]>([]);
-const isSRS = route.query.liveType === liveTypeEnum.srsPush;
-const wrapSize = reactive({
-  width: 0,
-  height: 0,
-});
-
-const scaleRatio = ref(0);
-const timerId = ref(-1);
-const videoRatio = ref(16 / 9);
-const {
-  confirmRoomName,
-  getSocketId,
-  startLive,
-  endLive,
-  sendDanmu,
-  keydownDanmu,
-  lastCoverImg,
-  canvasVideoStream,
-  isLiving,
-  allMediaTypeList,
-  currentResolutionRatio,
-  currentMaxBitrate,
-  currentMaxFramerate,
-  resolutionRatio,
-  maxBitrate,
-  maxFramerate,
-  danmuStr,
-  roomName,
-  damuList,
-  liveUserList,
-  addTrack,
-  delTrack,
-} = usePush({
-  localVideoRef,
-  remoteVideoRef,
-  isSRS,
-});
-
-const mediaTypeEnumMap = {
-  [MediaTypeEnum.camera]: '摄像头',
-  [MediaTypeEnum.microphone]: '麦克风',
-  [MediaTypeEnum.screen]: '窗口',
-  [MediaTypeEnum.img]: '图片',
-  [MediaTypeEnum.txt]: '文字',
-  [MediaTypeEnum.media]: '视频',
-};
-
-watch(
-  () => damuList.value.length,
-  () => {
-    setTimeout(() => {
-      if (danmuListRef.value) {
-        danmuListRef.value.scrollTop = danmuListRef.value.scrollHeight;
-      }
-    }, 0);
-  }
-);
-
-// 处理页面显示/隐藏
-function onPageVisibility() {
-  // 注意:此属性在Page Visibility Level 2 规范中被描述为“历史” 。考虑改用该Document.visibilityState 属性。
-  // const isHidden = document.hidden;
-
-  if (document.visibilityState === 'hidden') {
-    console.log(new Date().toLocaleString(), '页面隐藏了', timerId.value);
-    if (isLiving.value) {
-      const delay = 1000 / 60; // 16.666666666666668
-      timerId.value = workerTimers.setInterval(() => {
-        fabricCanvas.value?.renderAll();
-      }, delay);
-    }
-  } else {
-    console.log(new Date().toLocaleString(), '页面显示了', timerId.value);
-    if (isLiving.value) {
-      workerTimers.clearInterval(timerId.value);
-    }
-  }
-}
-
-function initUserMedia() {
-  navigator.mediaDevices
-    .getUserMedia({
-      video: true,
-      audio: false,
-    })
-    .then(() => {
-      console.log('初始化获取摄像头成功');
-    })
-    .catch(() => {
-      console.log('初始化获取摄像头失败');
-    })
-    .finally(() => {
-      navigator.mediaDevices
-        .getUserMedia({
-          video: false,
-          audio: true,
-        })
-        .then(() => {
-          console.log('初始化获取麦克风成功');
-        })
-        .catch(() => {
-          console.log('初始化获取麦克风失败');
-          showOpenMicophoneTipCpt.value = true;
-        });
-    });
-}
-
-function renderFrame() {
-  fabricCanvas.value?.renderAll();
-  window.requestAnimationFrame(renderFrame);
-}
-
-// 处理空音频轨
-function initNullAudio() {
-  console.warn('处理空音频轨');
-  // 创建一个AudioContext实例
-  const audioContext = new AudioContext();
-
-  // 创建一个GainNode实例来控制音频的音量
-  const gainNode = audioContext.createGain();
-
-  // 创建一个空的音频缓存
-  const buffer = audioContext.createBuffer(
-    2,
-    audioContext.sampleRate * 3,
-    audioContext.sampleRate
-  );
-
-  // 创建一个用于播放音频的AudioBufferSourceNode
-  const source = audioContext.createBufferSource();
-  source.buffer = buffer;
-
-  // 将源连接到gain node,再连接到输出
-  source.connect(gainNode);
-  gainNode.connect(audioContext.destination);
-  const destination = audioContext.createMediaStreamDestination();
-
-  const audioTrack: AppRootState['allTrack'][0] = {
-    id: getRandomEnglishString(8),
-    audio: 1,
-    video: 2,
-    mediaName: 'webAudio占位',
-    type: MediaTypeEnum.webAudio,
-    track: destination.stream.getAudioTracks()[0],
-    trackid: destination.stream.getAudioTracks()[0].id,
-    stream: destination.stream,
-    streamid: destination.stream.id,
-    hidden: true,
-    muted: false,
-  };
-  const res = [...appStore.allTrack, audioTrack];
-  appStore.setAllTrack(res);
-  const webAudioItem = resourceCacheStore.list.find(
-    (item) => item.type === MediaTypeEnum.webAudio
-  );
-  if (!webAudioItem) {
-    resourceCacheStore.setList([...resourceCacheStore.list, audioTrack]);
-  }
-  const vel = createVideo({});
-  // vel.style.width = `1px`;
-  // vel.style.height = `1px`;
-  vel.style.position = 'fixed';
-  vel.style.bottom = '0';
-  vel.style.right = '0';
-  // vel.style.opacity = '0';
-  // vel.style.pointerEvents = 'none';
-  vel.srcObject = destination.stream;
-  document.body.appendChild(vel);
-}
-
-let streamTmp: MediaStream;
-let vel;
-
-function handleMixedAudio() {
-  const allAudioTrack = appStore.allTrack.filter((item) => item.audio === 1);
-  if (audioCtx.value) {
-    const gainNode = audioCtx.value.createGain();
-    allAudioTrack.forEach((item) => {
-      if (!audioCtx.value || !item.stream) return;
-      const audioInput = audioCtx.value.createMediaStreamSource(item.stream);
-      audioInput.connect(gainNode);
-      console.log('混流', item.stream?.id, item.stream);
-    });
-    if (streamTmp) {
-      const destination = audioCtx.value.createMediaStreamDestination();
-      streamTmp.addTrack(destination.stream.getAudioTracks()[0]);
-      gainNode.connect(destination);
-      const mixedStream = new MediaStream();
-      mixedStream.addTrack(destination.stream.getAudioTracks()[0]);
-      mixedStream.addTrack(canvasVideoStream.value!.getVideoTracks()[0]);
-      canvasVideoStream.value = mixedStream;
-      console.log('替换了21111');
-      return;
-    }
-    const destination = audioCtx.value.createMediaStreamDestination();
-    streamTmp = destination.stream;
-    // @ts-ignore
-    canvasVideoStream.value?.addTrack(destination.stream.getAudioTracks()[0]);
-    gainNode.connect(destination);
-    vel = createVideo({});
-    vel.style.width = `1px`;
-    vel.style.height = `1px`;
-    vel.style.position = 'fixed';
-    vel.style.bottom = '0';
-    vel.style.right = '0';
-    vel.style.opacity = '0';
-    vel.style.pointerEvents = 'none';
-    vel.srcObject = destination.stream;
-    document.body.appendChild(vel);
-  }
-}
-
-function handleStartLive() {
-  if (!audioCtx.value) {
-    audioCtx.value = new AudioContext();
-  }
-  handleMixedAudio();
-  lastCoverImg.value = generateBase64(pushCanvasRef.value!);
-  startLive();
-}
-
-function handleScale({ width, height }: { width: number; height: number }) {
-  const resolutionHeight =
-    currentResolutionRatio.value / window.devicePixelRatio;
-  const resolutionWidth =
-    (currentResolutionRatio.value / window.devicePixelRatio) * videoRatio.value;
-  console.log('当前分辨率', { resolutionWidth, resolutionHeight });
-  let ratio = 1;
-  if (width > resolutionWidth) {
-    const r1 = resolutionWidth / width;
-    ratio = r1;
-  }
-  if (height > resolutionHeight) {
-    const r1 = resolutionHeight / height;
-    if (ratio > r1) {
-      ratio = r1;
-    }
-  }
-  // if (width > wrapSize.width) {
-  //   const r1 = wrapSize.width / width;
-  //   ratio = r1;
-  // }
-  // if (height > wrapSize.height) {
-  //   const r1 = wrapSize.height / height;
-  //   if (ratio > r1) {
-  //     ratio = r1;
-  //   }
-  // }
-
-  return ratio;
-}
-
-function autoCreateVideo({
-  stream,
-  id,
-  rect,
-  muted,
-}: {
-  stream: MediaStream;
-  id: string;
-  rect?: { left: number; top: number };
-  muted?: boolean;
-}) {
-  console.warn('autoCreateVideo', id);
-  const videoEl = createVideo({});
-  if (muted !== undefined) {
-    videoEl.muted = muted;
-  }
-  videoEl.srcObject = stream;
-  videoEl.style.width = `1px`;
-  videoEl.style.height = `1px`;
-  videoEl.style.position = 'fixed';
-  videoEl.style.bottom = '0';
-  videoEl.style.right = '0';
-  videoEl.style.opacity = '0';
-  videoEl.style.pointerEvents = 'none';
-  document.body.appendChild(videoEl);
-  return new Promise<{
-    canvasDom: fabric.Image;
-    videoEl: HTMLVideoElement;
-    clearFrame: any;
-    scale: number;
-  }>((resolve) => {
-    console.log(videoEl, 888211888888);
-    videoEl.onloadedmetadata = () => {
-      console.log('kkkkkkk2kkk', 123);
-      const width = stream.getVideoTracks()[0].getSettings().width!;
-      const height = stream.getVideoTracks()[0].getSettings().height!;
-      const ratio = handleScale({ width, height });
-      videoEl.width = width;
-      videoEl.height = height;
-
-      const canvasDom = markRaw(
-        new fabric.Image(videoEl, {
-          top: rect?.top || 0,
-          left: rect?.left || 0,
-          width,
-          height,
-        })
-      );
-      console.log(
-        '初始化',
-        ratio,
-        canvasDom.width,
-        canvasDom.height,
-        canvasDom
-      );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
-
-      canvasDom.scale(ratio);
-      fabricCanvas.value!.add(canvasDom);
-      let timer;
-
-      function clearFrame() {
-        window.cancelAnimationFrame(timer);
-      }
-
-      renderFrame();
-
-      resolve({ canvasDom, scale: ratio, videoEl, clearFrame });
-    };
-  });
-}
-
-watch(
-  () => currentResolutionRatio.value,
-  (newHeight, oldHeight) => {
-    changeCanvasAttr({ newHeight, oldHeight });
-  }
-);
-
-// 容器宽高,1280*720,即720p
-// canvas容器宽高,2560*1440,即1440p
-
-// ======
-// 容器宽高,960*540,即540p
-// dom宽高,640*480
-// canvas容器宽高,960*540,即540p
-// 将dom绘制到容器里,此时dom的大小就是640*480
-// 需求,不管切换多少分辨率,我要看到的dom都是一样大小,即
-// 960*540时,dom是640*480
-// 1280*720时,dom不能是640*480了,因为这样他就会对比上一个分辨率的dom看起来小了,960/1280=0.75,540/720=0.75,
-// 其实就是分辨率变大了,我们就要将图片也变大,即图片的宽是640/0.75=853.4,高是480/0.75=640
-// 坐标变化,960*540时,dom坐标是100,100
-// 1280*720时,dom的坐标不能再是100,100了,否则对比上一个分辨率看起来偏
-
-function changeCanvasAttr({
-  newHeight,
-  oldHeight,
-}: {
-  newHeight: number;
-  oldHeight: number;
-}) {
-  if (fabricCanvas.value) {
-    const resolutionHeight =
-      currentResolutionRatio.value / window.devicePixelRatio;
-    const resolutionWidth =
-      (currentResolutionRatio.value / window.devicePixelRatio) *
-      videoRatio.value;
-    fabricCanvas.value.setWidth(resolutionWidth);
-    fabricCanvas.value.setHeight(resolutionHeight);
-    // fabricCanvas.value.forEachObject((canvas) => {
-    //   canvas.setCoords();
-    // });
-    appStore.allTrack.forEach((item) => {
-      if (item.canvasDom) {
-        // 分辨率变小了,将图片变小
-        if (newHeight < oldHeight) {
-          const ratio = newHeight / oldHeight;
-          const ratio1 = (item.canvasDom.scaleX || 1) * ratio;
-          const ratio2 = oldHeight / newHeight;
-          console.log(
-            ratio,
-            ratio1,
-            '分辨率变小了,将图片变小-----',
-            item.canvasDom
-          );
-          item.canvasDom.scale(ratio1);
-          item.canvasDom.left = item.canvasDom.left! / ratio2;
-          item.canvasDom.top = item.canvasDom.top! / ratio2;
-        } else {
-          // 分辨率变大了,将图片变大
-          const ratio = newHeight / oldHeight;
-          const ratio1 = (item.canvasDom.scaleX || 1) * ratio;
-          const ratio2 = oldHeight / newHeight;
-          console.log(
-            ratio,
-            ratio1,
-            '分辨率变大了,将图片变大-----',
-            item.canvasDom
-          );
-          item.canvasDom.scale(ratio1);
-          item.canvasDom.left = item.canvasDom.left! / ratio2;
-          item.canvasDom.top = item.canvasDom.top! / ratio2;
-        }
-      }
-    });
-    changeCanvasStyle();
-  }
-}
-
-function changeCanvasStyle() {
-  // @ts-ignore
-  fabricCanvas.value.wrapperEl.style.width = `${wrapSize.width}px`;
-  // @ts-ignore
-  fabricCanvas.value.wrapperEl.style.height = `${wrapSize.height}px`;
-  // @ts-ignore
-  fabricCanvas.value.lowerCanvasEl.style.width = `${wrapSize.width}px`;
-  // @ts-ignore
-  fabricCanvas.value.lowerCanvasEl.style.height = `${wrapSize.height}px`;
-  // @ts-ignore
-  fabricCanvas.value.upperCanvasEl.style.width = `${wrapSize.width}px`;
-  // @ts-ignore
-  fabricCanvas.value.upperCanvasEl.style.height = `${wrapSize.height}px`;
-}
-
-function initCanvas() {
-  const resolutionHeight =
-    currentResolutionRatio.value / window.devicePixelRatio;
-  const resolutionWidth =
-    (currentResolutionRatio.value / window.devicePixelRatio) * videoRatio.value;
-  const wrapWidth = containerRef.value!.getBoundingClientRect().width;
-  // const wrapWidth = 1920;
-  const ratio = wrapWidth / resolutionWidth;
-  scaleRatio.value = resolutionWidth / wrapWidth;
-  const wrapHeight = resolutionHeight * ratio;
-  // const wrapHeight = 1080;
-  // lower-canvas: 实际的canvas画面,也就是pushCanvasRef
-  // upper-canvas: 操作时候的canvas
-  const ins = markRaw(new fabric.Canvas(pushCanvasRef.value!));
-  ins.setWidth(resolutionWidth);
-  ins.setHeight(resolutionHeight);
-  ins.setBackgroundColor('black', () => {
-    console.log('setBackgroundColor回调');
-  });
-  wrapSize.width = wrapWidth;
-  wrapSize.height = wrapHeight;
-  fabricCanvas.value = ins;
-  changeCanvasStyle();
-}
-
-async function handleCache() {
-  const res: AppRootState['allTrack'] = [];
-  const queue: any[] = [];
-  resourceCacheStore.list.forEach((item) => {
-    // @ts-ignore
-    const obj: AppRootState['allTrack'][0] = {};
-    obj.audio = item.audio;
-    obj.video = item.video;
-    obj.id = item.id;
-    obj.type = item.type;
-    obj.hidden = item.hidden;
-    obj.mediaName = item.mediaName;
-    obj.muted = item.muted;
-    obj.rect = item.rect;
-    obj.scaleInfo = item.scaleInfo;
-
-    async function handleMediaVideo() {
-      const file = await readFile(item.id);
-      const url = URL.createObjectURL(file);
-      console.log(file, file.name, url);
-      const videoEl = createVideo({});
-      videoEl.src = url;
-      videoEl.muted = item.muted ? item.muted : false;
-      videoEl.style.width = `1px`;
-      videoEl.style.height = `1px`;
-      videoEl.style.position = 'fixed';
-      videoEl.style.bottom = '0';
-      videoEl.style.right = '0';
-      videoEl.style.opacity = '0';
-      videoEl.style.pointerEvents = 'none';
-      document.body.appendChild(videoEl);
-      await new Promise((resolve) => {
-        videoEl.onloadedmetadata = () => {
-          const stream = videoEl
-            // @ts-ignore
-            .captureStream();
-          const width = stream.getVideoTracks()[0].getSettings().width!;
-          const height = stream.getVideoTracks()[0].getSettings().height!;
-          // const ratio = handleScale({ width, height });
-          videoEl.width = width;
-          videoEl.height = height;
-
-          const canvasDom = markRaw(
-            new fabric.Image(videoEl, {
-              top: item.rect?.top || 0,
-              left: item.rect?.left || 0,
-              width,
-              height,
-            })
-          );
-          canvasDom.on('moving', () => {
-            appStore.allTrack.forEach((iten) => {
-              if (item.id === iten.id) {
-                iten.rect = { top: canvasDom.top!, left: canvasDom.left! };
-              }
-            });
-            resourceCacheStore.setList(appStore.allTrack);
-          });
-          canvasDom.scale(item.scaleInfo?.scaleX || 1);
-          fabricCanvas.value!.add(canvasDom);
-
-          renderFrame();
-          obj.videoEl = videoEl;
-          obj.canvasDom = canvasDom;
-          resolve({ videoEl, canvasDom });
-        };
-      });
-      const stream = videoEl
-        // @ts-ignore
-        .captureStream() as MediaStream;
-      obj.stream = stream;
-      obj.streamid = stream.id;
-      obj.track = stream.getVideoTracks()[0];
-      obj.trackid = stream.getVideoTracks()[0].id;
-      // if (stream.getAudioTracks()[0]) {
-      //   console.log('视频有音频', stream.getAudioTracks()[0]);
-      //   mediaVideoTrack.audio = 1;
-      //   const audioTrack: AppRootState['allTrack'][0] = {
-      //     id: mediaVideoTrack.id,
-      //     audio: 1,
-      //     video: 2,
-      //     mediaName: val.mediaName,
-      //     type: MediaTypeEnum.media,
-      //     track: stream.getAudioTracks()[0],
-      //     trackid: stream.getAudioTracks()[0].id,
-      //     stream,
-      //     streamid: stream.id,
-      //     hidden: true,
-      //     muted: false,
-      //   };
-      //   // @ts-ignore
-      //   const res = [...appStore.allTrack, audioTrack];
-      //   appStore.setAllTrack(res);
-      //   resourceCacheStore.setList(res);
-      //   handleMixedAudio();
-      //   // @ts-ignore
-
-      //   addTrack(audioTrack);
-      // }
-    }
-
-    async function handleImg() {
-      const file = await readFile(item.id);
-      const imgEl = await new Promise<HTMLImageElement>((resolve) => {
-        const reader = new FileReader();
-        reader.addEventListener(
-          'load',
-          function () {
-            const img = document.createElement('img');
-            img.src = reader.result as string;
-            img.onload = () => {
-              resolve(img);
-            };
-          },
-          false
-        );
-        if (file) {
-          reader.readAsDataURL(file);
-        }
-      });
-      if (fabricCanvas.value) {
-        const canvasDom = markRaw(
-          new fabric.Image(imgEl, {
-            top: item.rect?.top || 0,
-            left: item.rect?.left || 0,
-            width: imgEl.width,
-            height: imgEl.height,
-          })
-        );
-        canvasDom.on('moving', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.on('scaling', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.scaleInfo = {
-                scaleX: canvasDom.scaleX || 1,
-                scalcY: canvasDom.scaleY || 1,
-              };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.scale(item.scaleInfo?.scaleX || 1);
-        fabricCanvas.value.add(canvasDom);
-        obj.canvasDom = canvasDom;
-        renderFrame();
-      }
-    }
-    if (item.type === MediaTypeEnum.media && item.video === 1) {
-      queue.push(handleMediaVideo());
-    } else if (item.type === MediaTypeEnum.txt) {
-      obj.txtInfo = item.txtInfo;
-      if (fabricCanvas.value) {
-        const canvasDom = markRaw(
-          new fabric.Text(item.txtInfo?.txt || '', {
-            top: item.rect?.top || 0,
-            left: item.rect?.left || 0,
-            fill: item.txtInfo?.color,
-          })
-        );
-        canvasDom.on('moving', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.scale(item.scaleInfo?.scaleX || 1);
-        fabricCanvas.value.add(canvasDom);
-        obj.canvasDom = canvasDom;
-        renderFrame();
-      }
-    } else if (item.type === MediaTypeEnum.img) {
-      queue.push(handleImg());
-    }
-    res.push(obj);
-  });
-  await Promise.all(queue);
-  canvasVideoStream.value = pushCanvasRef.value!.captureStream();
-  appStore.setAllTrack(res);
-}
-
-onMounted(() => {
-  setTimeout(() => {
-    scrollTo(0, 0);
-  }, 100);
-  // initNullAudio();
-  initUserMedia();
-  initCanvas();
-  handleCache();
-  document.addEventListener('visibilitychange', onPageVisibility);
-});
-
-onUnmounted(() => {
-  document.removeEventListener('visibilitychange', onPageVisibility);
-});
-
-function selectMediaOk(val: MediaTypeEnum) {
-  showMediaModalCpt.value = true;
-  showSelectMediaModalCpt.value = false;
-  currentMediaType.value = val;
-}
-
-async function addMediaOk(val: {
-  type: MediaTypeEnum;
-  deviceId: string;
-  mediaName: string;
-  txtInfo?: { txt: string; color: string };
-  imgInfo?: UploadFileInfo[];
-  mediaInfo?: UploadFileInfo[];
-}) {
-  if (!audioCtx.value) {
-    audioCtx.value = new AudioContext();
-  }
-  showMediaModalCpt.value = false;
-  if (val.type === MediaTypeEnum.screen) {
-    const event = await navigator.mediaDevices.getDisplayMedia({
-      video: {
-        deviceId: val.deviceId,
-        // displaySurface: 'monitor', // browser默认标签页;window默认窗口;monitor默认整个屏幕
-      },
-      audio: true,
-    });
-
-    const videoTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.screen,
-      track: event.getVideoTracks()[0],
-      trackid: event.getVideoTracks()[0].id,
-      stream: event,
-      streamid: event.id,
-      hidden: false,
-      muted: false,
-    };
-
-    const { canvasDom, videoEl } = await autoCreateVideo({
-      stream: event,
-      id: videoTrack.id,
-    });
-    videoTrack.videoEl = videoEl;
-    // @ts-ignore
-    videoTrack.canvasDom = canvasDom;
-
-    const audio = event.getAudioTracks();
-    if (audio.length) {
-      videoTrack.audio = 1;
-      const audioTrack: AppRootState['allTrack'][0] = {
-        id: videoTrack.id,
-        audio: 1,
-        video: 2,
-        mediaName: val.mediaName,
-        type: MediaTypeEnum.screen,
-        track: event.getAudioTracks()[0],
-        trackid: event.getAudioTracks()[0].id,
-        stream: event,
-        streamid: event.id,
-        hidden: true,
-        muted: false,
-      };
-      const res = [...appStore.allTrack, videoTrack, audioTrack];
-      appStore.setAllTrack(res);
-      resourceCacheStore.setList(res);
-      handleMixedAudio();
-      // @ts-ignore
-      addTrack(videoTrack);
-      // @ts-ignore
-      addTrack(audioTrack);
-    } else {
-      const res = [...appStore.allTrack, videoTrack];
-      appStore.setAllTrack(res);
-      resourceCacheStore.setList(res);
-      // @ts-ignore
-      addTrack(videoTrack);
-    }
-
-    console.log('获取窗口成功');
-  } else if (val.type === MediaTypeEnum.camera) {
-    const event = await navigator.mediaDevices.getUserMedia({
-      video: {
-        deviceId: val.deviceId,
-      },
-      audio: false,
-    });
-    const videoTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.camera,
-      track: event.getVideoTracks()[0],
-      trackid: event.getVideoTracks()[0].id,
-      stream: event,
-      streamid: event.id,
-      hidden: false,
-      muted: false,
-    };
-    const { canvasDom, videoEl } = await autoCreateVideo({
-      stream: event,
-      id: videoTrack.id,
-    });
-    videoTrack.videoEl = videoEl;
-    // @ts-ignore
-    videoTrack.canvasDom = canvasDom;
-
-    const res = [...appStore.allTrack, videoTrack];
-    appStore.setAllTrack(res);
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-    addTrack(videoTrack);
-    console.log('获取摄像头成功');
-  } else if (val.type === MediaTypeEnum.microphone) {
-    const event = await navigator.mediaDevices.getUserMedia({
-      video: false,
-      audio: { deviceId: val.deviceId },
-    });
-    const audioTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 1,
-      video: 2,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.microphone,
-      track: event.getAudioTracks()[0],
-      trackid: event.getAudioTracks()[0].id,
-      stream: event,
-      streamid: event.id,
-      hidden: false,
-      muted: false,
-    };
-    const res = [...appStore.allTrack, audioTrack];
-    appStore.setAllTrack(res);
-    resourceCacheStore.setList(res);
-    handleMixedAudio();
-    // @ts-ignore
-    addTrack(audioTrack);
-
-    console.log('获取麦克风成功');
-  } else if (val.type === MediaTypeEnum.txt) {
-    const txtTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.txt,
-      track: undefined,
-      trackid: undefined,
-      stream: undefined,
-      streamid: undefined,
-      hidden: false,
-      muted: false,
-    };
-    if (fabricCanvas.value) {
-      const canvasDom = markRaw(
-        new fabric.Text(val.txtInfo?.txt || '', {
-          top: 0,
-          left: 0,
-          fill: val.txtInfo?.color,
-        })
-      );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (txtTrack.id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
-      txtTrack.txtInfo = val.txtInfo;
-      // @ts-ignore
-      txtTrack.canvasDom = canvasDom;
-      fabricCanvas.value.add(canvasDom);
-      renderFrame();
-    }
-
-    const res = [...appStore.allTrack, txtTrack];
-    // @ts-ignore
-    appStore.setAllTrack(res);
-    // @ts-ignore
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-    addTrack(txtTrack);
-
-    console.log('获取文字成功', fabricCanvas.value);
-  } else if (val.type === MediaTypeEnum.img) {
-    const imgTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.img,
-      track: undefined,
-      trackid: undefined,
-      stream: undefined,
-      streamid: undefined,
-      hidden: false,
-      muted: false,
-    };
-
-    if (fabricCanvas.value) {
-      const imgEl = await new Promise<HTMLImageElement>((resolve) => {
-        if (!val.imgInfo) return;
-        const file = val.imgInfo[0].file!;
-        saveFile({ file, fileName: imgTrack.id });
-        const reader = new FileReader();
-        reader.addEventListener(
-          'load',
-          function () {
-            const img = document.createElement('img');
-            img.src = reader.result as string;
-            img.onload = () => {
-              resolve(img);
-            };
-          },
-          false
-        );
-        if (file) {
-          reader.readAsDataURL(file);
-        }
-      });
-
-      const canvasDom = markRaw(
-        new fabric.Image(imgEl, {
-          top: 0,
-          left: 0,
-          width: imgEl.width,
-          height: imgEl.height,
-        })
-      );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (imgTrack.id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
-      const ratio = handleScale({ width: imgEl.width, height: imgEl.height });
-      // @ts-ignore
-      imgTrack.canvasDom = canvasDom;
-      imgTrack.scaleInfo = { scaleX: ratio, scalcY: ratio };
-      canvasDom.scale(ratio);
-      fabricCanvas.value.add(canvasDom);
-      renderFrame();
-    }
-
-    const res = [...appStore.allTrack, imgTrack];
-    // @ts-ignore
-    appStore.setAllTrack(res);
-    // @ts-ignore
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-    addTrack(imgTrack);
-
-    console.log('获取图片成功', fabricCanvas.value);
-  } else if (val.type === MediaTypeEnum.media) {
-    const mediaVideoTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.media,
-      track: undefined,
-      trackid: undefined,
-      stream: undefined,
-      streamid: undefined,
-      hidden: false,
-      muted: false,
-    };
-    if (fabricCanvas.value) {
-      if (!val.mediaInfo) return;
-      const file = val.mediaInfo[0].file!;
-      saveFile({ file, fileName: mediaVideoTrack.id });
-      const url = URL.createObjectURL(file);
-      console.log(file, file.name, url);
-      const videoEl = createVideo({});
-      videoEl.src = url;
-      videoEl.muted = false;
-      videoEl.style.width = `1px`;
-      videoEl.style.height = `1px`;
-      videoEl.style.position = 'fixed';
-      videoEl.style.bottom = '0';
-      videoEl.style.right = '0';
-      videoEl.style.opacity = '0';
-      videoEl.style.pointerEvents = 'none';
-      document.body.appendChild(videoEl);
-      const videoRes = await new Promise<HTMLVideoElement>((resolve) => {
-        videoEl.onloadedmetadata = () => {
-          resolve(videoEl);
-        };
-      });
-      // @ts-ignore
-      const stream = videoRes.captureStream();
-      const { canvasDom } = await autoCreateVideo({
-        stream,
-        id: mediaVideoTrack.id,
-      });
-      mediaVideoTrack.videoEl = videoEl;
-      // @ts-ignore
-      mediaVideoTrack.canvasDom = canvasDom;
-      if (stream.getAudioTracks()[0]) {
-        console.log('视频有音频', stream.getAudioTracks()[0]);
-        mediaVideoTrack.audio = 1;
-        const audioTrack: AppRootState['allTrack'][0] = {
-          id: mediaVideoTrack.id,
-          audio: 1,
-          video: 2,
-          mediaName: val.mediaName,
-          type: MediaTypeEnum.media,
-          track: stream.getAudioTracks()[0],
-          trackid: stream.getAudioTracks()[0].id,
-          stream,
-          streamid: stream.id,
-          hidden: true,
-          muted: false,
-        };
-        // @ts-ignore
-        const res = [...appStore.allTrack, audioTrack];
-        appStore.setAllTrack(res);
-        resourceCacheStore.setList(res);
-        handleMixedAudio();
-        // @ts-ignore
-
-        addTrack(audioTrack);
-      }
-    }
-    const res = [...appStore.allTrack, mediaVideoTrack];
-    // @ts-ignore
-    appStore.setAllTrack(res);
-    // @ts-ignore
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-
-    addTrack(mediaVideoTrack);
-
-    console.log('获取视频成功', fabricCanvas.value);
-  }
-
-  canvasVideoStream.value = pushCanvasRef.value!.captureStream();
-}
-
-function handleChangeMuted(item: AppRootState['allTrack'][0]) {
-  console.log('handleChangeMuted', item);
-  if (item.videoEl) {
-    const res = !item.videoEl.muted;
-    item.videoEl.muted = res;
-    item.muted = res;
-    resourceCacheStore.setList(appStore.allTrack);
-  }
-}
-
-function handleEdit(item: AppRootState['allTrack'][0]) {
-  console.log('handleEdit', item);
-}
-
-function handleDel(item: AppRootState['allTrack'][0]) {
-  console.log('handleDel', item);
-  if (item.canvasDom !== undefined) {
-    // @ts-ignore
-    fabricCanvas.value?.remove(item.canvasDom);
-    item.videoEl?.remove();
-  }
-  const res = appStore.allTrack.filter((iten) => iten.id !== item.id);
-  appStore.setAllTrack(res);
-  resourceCacheStore.setList(res);
-  delTrack(item);
-}
-
-function saveFile(data: { file: File; fileName: string }) {
-  const { file, fileName } = data;
-  const requestFileSystem =
-    // @ts-ignore
-    window.requestFileSystem || window.webkitRequestFileSystem;
-  function onError(err) {
-    console.error('saveFile错误', data.fileName);
-    console.log(err);
-  }
-  function onFs(fs) {
-    // 创建文件
-    fs.root.getFile(
-      fileName,
-      { create: true },
-      function (fileEntry) {
-        // 创建文件写入流
-        fileEntry.createWriter(function (fileWriter) {
-          fileWriter.onwriteend = () => {
-            // 完成后关闭文件
-            fileWriter.abort();
-          };
-          // 写入文件内容
-          fileWriter.write(file);
-        });
-      },
-      () => {
-        console.log('写入文件失败');
-      }
-    );
-  }
-  // Opening a file system with temporary storage
-  requestFileSystem(
-    // @ts-ignore
-    window.PERSISTENT,
-    0,
-    onFs,
-    onError
-  );
-}
-
-function readFile(fileName: string) {
-  return new Promise<File>((resolve) => {
-    const requestFileSystem =
-      // @ts-ignore
-      window.requestFileSystem || window.webkitRequestFileSystem;
-    function onError(err) {
-      console.error('readFile错误', fileName);
-      console.log(err);
-    }
-    function onFs(fs) {
-      fs.root.getFile(
-        fileName,
-        {},
-        function (fileEntry) {
-          fileEntry.file(function (file) {
-            resolve(file);
-            // const url = URL.createObjectURL(file);
-            // const videoEl = createVideo({});
-            // videoEl.src = url;
-            // document.body.appendChild(videoEl);
-          }, onError);
-        },
-        onError
-      );
-    }
-    // Opening a file system with temporary storage
-    requestFileSystem(
-      // @ts-ignore
-      window.PERSISTENT,
-      0,
-      onFs,
-      onError
-    );
-  });
-}
-
-function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
-  currentMediaType.value = item.type;
-  showMediaModalCpt.value = true;
-}
-</script>
-
-<style lang="scss" scoped>
-.push-wrap {
-  display: flex;
-  justify-content: space-between;
-  margin: 15px auto 0;
-  width: $w-1250;
-  .left {
-    position: relative;
-    display: inline-block;
-    overflow: hidden;
-    box-sizing: border-box;
-    width: $w-960;
-    height: 100%;
-    border-radius: 6px;
-    background-color: white;
-    color: #9499a0;
-    vertical-align: top;
-
-    .container {
-      position: relative;
-      overflow: hidden;
-      height: 100%;
-      background-color: rgba($color: #000000, $alpha: 0.5);
-      line-height: 0;
-
-      :deep(canvas) {
-        // width: 100%;
-      }
-
-      .add-wrap {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: space-around;
-        padding: 0 20px;
-        height: 50px;
-        border-radius: 6px;
-        background-color: white;
-        transform: translate(-50%, -50%);
-      }
-    }
-    .room-control {
-      display: flex;
-      justify-content: space-between;
-      padding: 20px;
-      background-color: papayawhip;
-
-      .info {
-        display: flex;
-        align-items: center;
-
-        .avatar {
-          margin-right: 20px;
-          width: 55px;
-          height: 55px;
-          border-radius: 50%;
-          background-position: center center;
-          background-size: cover;
-          background-repeat: no-repeat;
-        }
-        .detail {
-          display: flex;
-          flex-direction: column;
-          flex-shrink: 0;
-          width: 200px;
-          text-align: initial;
-          .top {
-            margin-bottom: 10px;
-            color: #18191c;
-          }
-          .bottom {
-            font-size: 14px;
-          }
-        }
-      }
-      .rtc {
-        display: flex;
-        align-items: center;
-        flex: 1;
-        font-size: 14px;
-        .item {
-          display: flex;
-          align-items: center;
-          flex: 1;
-          .txt {
-            flex-shrink: 0;
-            width: 80px;
-          }
-          .down {
-            width: 90px;
-
-            user-select: none;
-          }
-        }
-      }
-      .other {
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        font-size: 12px;
-        .top {
-        }
-        .bottom {
-          margin-top: 10px;
-        }
-      }
-    }
-  }
-  .right {
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    box-sizing: border-box;
-    margin-left: 10px;
-    width: $w-250;
-    border-radius: 6px;
-    background-color: white;
-    color: #9499a0;
-
-    .resource-card {
-      position: relative;
-      box-sizing: border-box;
-      margin-bottom: 10px;
-      padding: 10px;
-      width: 100%;
-      height: 290px;
-      border-radius: 6px;
-      background-color: papayawhip;
-      .title {
-        text-align: initial;
-      }
-      .item {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        margin: 5px 0;
-        font-size: 14px;
-        // &:hover {
-        //   .control {
-        //     display: flex;
-        //     align-items: center;
-        //   }
-        // }
-        .control {
-          display: flex;
-          align-items: center;
-          .control-item {
-            cursor: pointer;
-            &:not(:last-child) {
-              margin-right: 6px;
-            }
-          }
-        }
-      }
-      .bottom {
-        position: absolute;
-        bottom: 0;
-        left: 0;
-        padding: 10px;
-      }
-    }
-    .danmu-card {
-      position: relative;
-      flex: 1;
-      box-sizing: border-box;
-      padding: 10px;
-      width: 100%;
-      border-radius: 6px;
-      background-color: papayawhip;
-      text-align: initial;
-      .title {
-        margin-bottom: 10px;
-      }
-      .list {
-        overflow: scroll;
-        height: 360px;
-
-        @extend %hideScrollbar;
-
-        .item {
-          margin-bottom: 10px;
-          font-size: 12px;
-          .name {
-            color: #9499a0;
-          }
-          .msg {
-            color: #61666d;
-          }
-        }
-      }
-
-      .send-msg {
-        position: absolute;
-        bottom: 10px;
-        left: 50%;
-        display: flex;
-        align-items: center;
-        box-sizing: border-box;
-        width: calc(100% - 20px);
-        transform: translateX(-50%);
-        .ipt {
-          display: block;
-          box-sizing: border-box;
-          margin: 0 auto;
-          margin-right: 10px;
-          padding: 10px;
-          width: 80%;
-          height: 30px;
-          outline: none;
-          border: 1px solid hsla(0, 0%, 60%, 0.2);
-          border-radius: 4px;
-          background-color: #f1f2f3;
-          font-size: 14px;
-        }
-      }
-    }
-  }
-}
-
-// 屏幕宽度大于1500的时候
-@media screen and (min-width: $w-1500) {
-  .push-wrap {
-    width: $w-1475;
-    .left {
-      width: $w-1150;
-    }
-    .right {
-      width: $w-300;
-    }
-  }
-}
-</style>

+ 0 - 1639
src/views/pushByCanvas/index3.vue

@@ -1,1639 +0,0 @@
-<template>
-  <div class="push-wrap">
-    <div
-      ref="topRef"
-      class="left"
-    >
-      <div
-        ref="containerRef"
-        class="container"
-      >
-        <canvas
-          id="pushCanvasRef"
-          ref="pushCanvasRef"
-        ></canvas>
-        <div
-          v-if="appStore.allTrack.filter((item) => !item.hidden).length <= 0"
-          class="add-wrap"
-        >
-          <n-space>
-            <n-button
-              v-for="(item, index) in allMediaTypeList"
-              :key="index"
-              class="item"
-              @click="handleStartMedia(item)"
-            >
-              {{ item.txt }}
-            </n-button>
-          </n-space>
-        </div>
-      </div>
-
-      <div
-        ref="bottomRef"
-        class="room-control"
-      >
-        <div class="info">
-          <div
-            class="avatar"
-            :style="{ backgroundImage: `url(${userStore.userInfo?.avatar})` }"
-          ></div>
-          <div class="detail">
-            <div class="top">
-              <n-input-group>
-                <n-input
-                  v-model:value="roomName"
-                  size="small"
-                  placeholder="输入房间名"
-                  :style="{ width: '50%' }"
-                />
-                <n-button
-                  size="small"
-                  type="primary"
-                  @click="confirmRoomName"
-                >
-                  确定
-                </n-button>
-              </n-input-group>
-            </div>
-            <div class="bottom">
-              <span v-if="NODE_ENV === 'development'">
-                {{ getSocketId() }}
-              </span>
-            </div>
-          </div>
-        </div>
-        <div class="rtc">
-          <div class="item">
-            <div class="txt">码率设置</div>
-            <div class="down">
-              <n-select
-                v-model:value="currentMaxBitrate"
-                :options="maxBitrate"
-              />
-            </div>
-          </div>
-          <div class="item">
-            <div class="txt">帧率设置</div>
-            <div class="down">
-              <n-select
-                v-model:value="currentMaxFramerate"
-                :options="maxFramerate"
-              />
-            </div>
-          </div>
-          <div class="item">
-            <div class="txt">分辨率设置</div>
-            <div class="down">
-              <n-select
-                v-model:value="currentResolutionRatio"
-                :options="resolutionRatio"
-              />
-            </div>
-          </div>
-        </div>
-        <div class="other">
-          <div class="top">
-            <span class="item">
-              <i class="ico"></i>
-              <span>
-                正在观看:
-                {{
-                  liveUserList.filter((item) => item.id !== getSocketId())
-                    .length
-                }}
-              </span>
-            </span>
-          </div>
-          <div class="bottom">
-            <n-button
-              v-if="!isLiving"
-              type="info"
-              size="small"
-              @click="handleStartLive"
-            >
-              开始直播
-            </n-button>
-            <n-button
-              v-else
-              type="error"
-              size="small"
-              @click="endLive"
-            >
-              结束直播
-            </n-button>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <div class="right">
-      <div class="resource-card">
-        <div class="title">素材列表</div>
-        <div class="list">
-          <div
-            v-for="(item, index) in appStore.allTrack.filter(
-              (item) => !item.hidden
-            )"
-            :key="index"
-            class="item"
-          >
-            <span class="name">
-              ({{ mediaTypeEnumMap[item.type] }}){{ item.mediaName }}
-            </span>
-            <div class="control">
-              <div
-                v-if="item.audio === 1"
-                class="control-item"
-                @click="handleChangeMuted(item)"
-              >
-                <n-icon size="16">
-                  <VolumeMuteOutline v-if="item.muted"></VolumeMuteOutline>
-                  <VolumeHighOutline v-else></VolumeHighOutline>
-                </n-icon>
-              </div>
-              <div
-                class="control-item"
-                @click="handleEdit(item)"
-              >
-                <n-icon size="16">
-                  <CreateOutline></CreateOutline>
-                </n-icon>
-              </div>
-              <div
-                class="control-item"
-                @click="handleDel(item)"
-              >
-                <n-icon size="16">
-                  <Close></Close>
-                </n-icon>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="bottom">
-          <n-button
-            size="small"
-            type="primary"
-            @click="showSelectMediaModalCpt = true"
-          >
-            添加素材
-          </n-button>
-        </div>
-      </div>
-      <div class="danmu-card">
-        <div class="title">弹幕互动</div>
-        <div class="list-wrap">
-          <div
-            ref="danmuListRef"
-            class="list"
-          >
-            <div
-              v-for="(item, index) in damuList"
-              :key="index"
-              class="item"
-            >
-              <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
-                <span class="name">
-                  {{ item.userInfo?.username || item.socket_id }}:
-                </span>
-                <span class="msg">{{ item.msg }}</span>
-              </template>
-              <template v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin">
-                <span class="name system">系统通知:</span>
-                <span class="msg">
-                  <span>{{ item.userInfo?.username || item.socket_id }}</span>
-                  <span>进入直播!</span>
-                </span>
-              </template>
-              <template
-                v-else-if="item.msgType === DanmuMsgTypeEnum.userLeaved"
-              >
-                <span class="name system">系统通知:</span>
-                <span class="msg">
-                  <span>{{ item.userInfo?.username || item.socket_id }}</span>
-                  <span>离开直播!</span>
-                </span>
-              </template>
-            </div>
-          </div>
-        </div>
-        <div class="send-msg">
-          <input
-            v-model="danmuStr"
-            class="ipt"
-            @keydown="keydownDanmu"
-          />
-          <n-button
-            type="info"
-            size="small"
-            @click="sendDanmu"
-          >
-            发送
-          </n-button>
-        </div>
-      </div>
-    </div>
-
-    <SelectMediaModalCpt
-      v-if="showSelectMediaModalCpt"
-      :all-media-type-list="allMediaTypeList"
-      @close="showSelectMediaModalCpt = false"
-      @ok="selectMediaOk"
-    ></SelectMediaModalCpt>
-
-    <MediaModalCpt
-      v-if="showMediaModalCpt"
-      :media-type="currentMediaType"
-      @close="showMediaModalCpt = false"
-      @ok="addMediaOk"
-    ></MediaModalCpt>
-    <OpenMicophoneTipCpt
-      v-if="showOpenMicophoneTipCpt"
-      @close="showOpenMicophoneTipCpt = false"
-    ></OpenMicophoneTipCpt>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import {
-  Close,
-  CreateOutline,
-  VolumeHighOutline,
-  VolumeMuteOutline,
-} from '@vicons/ionicons5';
-import { fabric } from 'fabric';
-import { UploadFileInfo } from 'naive-ui';
-import { NODE_ENV } from 'script/constant';
-import { markRaw, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
-import { useRoute } from 'vue-router';
-import * as workerTimers from 'worker-timers';
-
-import { usePush } from '@/hooks/use-push';
-import { DanmuMsgTypeEnum, MediaTypeEnum, liveTypeEnum } from '@/interface';
-import { AppRootState, useAppStore } from '@/store/app';
-import { useResourceCacheStore } from '@/store/cache';
-import { useUserStore } from '@/store/user';
-import { createVideo, generateBase64, getRandomEnglishString } from '@/utils';
-
-import MediaModalCpt from './mediaModal/index.vue';
-import OpenMicophoneTipCpt from './openMicophoneTip/index.vue';
-import SelectMediaModalCpt from './selectMediaModal/index.vue';
-
-const route = useRoute();
-const userStore = useUserStore();
-const appStore = useAppStore();
-const resourceCacheStore = useResourceCacheStore();
-const currentMediaType = ref(MediaTypeEnum.camera);
-const showOpenMicophoneTipCpt = ref(false);
-const showSelectMediaModalCpt = ref(false);
-const showMediaModalCpt = ref(false);
-const topRef = ref<HTMLDivElement>();
-const bottomRef = ref<HTMLDivElement>();
-const danmuListRef = ref<HTMLDivElement>();
-const containerRef = ref<HTMLDivElement>();
-const pushCanvasRef = ref<HTMLCanvasElement>();
-const fabricCanvas = ref<fabric.Canvas>();
-const localVideoRef = ref<HTMLVideoElement>();
-const audioCtx = ref<AudioContext>();
-const remoteVideoRef = ref<HTMLVideoElement[]>([]);
-const isSRS = route.query.liveType === liveTypeEnum.srsPush;
-const wrapSize = reactive({
-  width: 0,
-  height: 0,
-});
-
-const scaleRatio = ref(0);
-const timerId = ref(-1);
-const videoRatio = ref(16 / 9);
-const {
-  confirmRoomName,
-  getSocketId,
-  startLive,
-  endLive,
-  sendDanmu,
-  keydownDanmu,
-  lastCoverImg,
-  canvasVideoStream,
-  isLiving,
-  allMediaTypeList,
-  currentResolutionRatio,
-  currentMaxBitrate,
-  currentMaxFramerate,
-  resolutionRatio,
-  maxBitrate,
-  maxFramerate,
-  danmuStr,
-  roomName,
-  damuList,
-  liveUserList,
-  addTrack,
-  delTrack,
-} = usePush({
-  localVideoRef,
-  remoteVideoRef,
-  isSRS,
-});
-
-const mediaTypeEnumMap = {
-  [MediaTypeEnum.camera]: '摄像头',
-  [MediaTypeEnum.microphone]: '麦克风',
-  [MediaTypeEnum.screen]: '窗口',
-  [MediaTypeEnum.img]: '图片',
-  [MediaTypeEnum.txt]: '文字',
-  [MediaTypeEnum.media]: '视频',
-};
-
-watch(
-  () => damuList.value.length,
-  () => {
-    setTimeout(() => {
-      if (danmuListRef.value) {
-        danmuListRef.value.scrollTop = danmuListRef.value.scrollHeight;
-      }
-    }, 0);
-  }
-);
-
-// 处理页面显示/隐藏
-function onPageVisibility() {
-  // 注意:此属性在Page Visibility Level 2 规范中被描述为“历史” 。考虑改用该Document.visibilityState 属性。
-  // const isHidden = document.hidden;
-
-  if (document.visibilityState === 'hidden') {
-    console.log(new Date().toLocaleString(), '页面隐藏了', timerId.value);
-    if (isLiving.value) {
-      const delay = 1000 / 60; // 16.666666666666668
-      timerId.value = workerTimers.setInterval(() => {
-        fabricCanvas.value?.renderAll();
-      }, delay);
-    }
-  } else {
-    console.log(new Date().toLocaleString(), '页面显示了', timerId.value);
-    if (isLiving.value) {
-      workerTimers.clearInterval(timerId.value);
-    }
-  }
-}
-
-function initUserMedia() {
-  navigator.mediaDevices
-    .getUserMedia({
-      video: true,
-      audio: false,
-    })
-    .then(() => {
-      console.log('初始化获取摄像头成功');
-    })
-    .catch(() => {
-      console.log('初始化获取摄像头失败');
-    })
-    .finally(() => {
-      navigator.mediaDevices
-        .getUserMedia({
-          video: false,
-          audio: true,
-        })
-        .then(() => {
-          console.log('初始化获取麦克风成功');
-        })
-        .catch(() => {
-          console.log('初始化获取麦克风失败');
-          showOpenMicophoneTipCpt.value = true;
-        });
-    });
-}
-
-function renderFrame() {
-  fabricCanvas.value?.renderAll();
-  window.requestAnimationFrame(renderFrame);
-}
-
-// 处理空音频轨
-function initNullAudio() {
-  console.warn('处理空音频轨');
-  // 创建一个AudioContext实例
-  const audioContext = new AudioContext();
-
-  // 创建一个GainNode实例来控制音频的音量
-  const gainNode = audioContext.createGain();
-
-  // 创建一个空的音频缓存
-  const buffer = audioContext.createBuffer(
-    2,
-    audioContext.sampleRate * 3,
-    audioContext.sampleRate
-  );
-
-  // 创建一个用于播放音频的AudioBufferSourceNode
-  const source = audioContext.createBufferSource();
-  source.buffer = buffer;
-
-  // 将源连接到gain node,再连接到输出
-  source.connect(gainNode);
-  gainNode.connect(audioContext.destination);
-  const destination = audioContext.createMediaStreamDestination();
-
-  const audioTrack: AppRootState['allTrack'][0] = {
-    id: getRandomEnglishString(8),
-    audio: 1,
-    video: 2,
-    mediaName: 'webAudio占位',
-    type: MediaTypeEnum.webAudio,
-    track: destination.stream.getAudioTracks()[0],
-    trackid: destination.stream.getAudioTracks()[0].id,
-    stream: destination.stream,
-    streamid: destination.stream.id,
-    hidden: true,
-    muted: false,
-  };
-  const res = [...appStore.allTrack, audioTrack];
-  appStore.setAllTrack(res);
-  const webAudioItem = resourceCacheStore.list.find(
-    (item) => item.type === MediaTypeEnum.webAudio
-  );
-  if (!webAudioItem) {
-    resourceCacheStore.setList([...resourceCacheStore.list, audioTrack]);
-  }
-  const vel = createVideo({});
-  // vel.style.width = `1px`;
-  // vel.style.height = `1px`;
-  vel.style.position = 'fixed';
-  vel.style.bottom = '0';
-  vel.style.right = '0';
-  // vel.style.opacity = '0';
-  // vel.style.pointerEvents = 'none';
-  vel.srcObject = destination.stream;
-  document.body.appendChild(vel);
-}
-
-let streamTmp: MediaStream;
-let vel;
-
-function handleMixedAudio() {
-  const allAudioTrack = appStore.allTrack.filter((item) => item.audio === 1);
-  if (audioCtx.value) {
-    const gainNode = audioCtx.value.createGain();
-    allAudioTrack.forEach((item) => {
-      if (!audioCtx.value || !item.stream) return;
-      const audioInput = audioCtx.value.createMediaStreamSource(item.stream);
-      audioInput.connect(gainNode);
-      console.log('混流', item.stream?.id, item.stream);
-    });
-    if (streamTmp) {
-      const destination = audioCtx.value.createMediaStreamDestination();
-      streamTmp.addTrack(destination.stream.getAudioTracks()[0]);
-      gainNode.connect(destination);
-      const mixedStream = new MediaStream();
-      mixedStream.addTrack(destination.stream.getAudioTracks()[0]);
-      mixedStream.addTrack(canvasVideoStream.value!.getVideoTracks()[0]);
-      canvasVideoStream.value = mixedStream;
-      console.log('替换了21111');
-      return;
-    }
-    const destination = audioCtx.value.createMediaStreamDestination();
-    streamTmp = destination.stream;
-    // @ts-ignore
-    canvasVideoStream.value?.addTrack(destination.stream.getAudioTracks()[0]);
-    gainNode.connect(destination);
-    vel = createVideo({});
-    vel.style.width = `1px`;
-    vel.style.height = `1px`;
-    vel.style.position = 'fixed';
-    vel.style.bottom = '0';
-    vel.style.right = '0';
-    vel.style.opacity = '0';
-    vel.style.pointerEvents = 'none';
-    vel.srcObject = destination.stream;
-    document.body.appendChild(vel);
-  }
-}
-
-function handleStartLive() {
-  if (!audioCtx.value) {
-    audioCtx.value = new AudioContext();
-  }
-  handleMixedAudio();
-  lastCoverImg.value = generateBase64(pushCanvasRef.value!);
-  startLive();
-}
-
-function handleScale({ width, height }: { width: number; height: number }) {
-  const resolutionHeight =
-    currentResolutionRatio.value / window.devicePixelRatio;
-  const resolutionWidth =
-    (currentResolutionRatio.value / window.devicePixelRatio) * videoRatio.value;
-  console.log('当前分辨率', { resolutionWidth, resolutionHeight });
-  let ratio = 1;
-  if (width > resolutionWidth) {
-    const r1 = resolutionWidth / width;
-    ratio = r1;
-  }
-  if (height > resolutionHeight) {
-    const r1 = resolutionHeight / height;
-    if (ratio > r1) {
-      ratio = r1;
-    }
-  }
-  // if (width > wrapSize.width) {
-  //   const r1 = wrapSize.width / width;
-  //   ratio = r1;
-  // }
-  // if (height > wrapSize.height) {
-  //   const r1 = wrapSize.height / height;
-  //   if (ratio > r1) {
-  //     ratio = r1;
-  //   }
-  // }
-
-  return ratio;
-}
-
-function autoCreateVideo({
-  stream,
-  id,
-  rect,
-  muted,
-}: {
-  stream: MediaStream;
-  id: string;
-  rect?: { left: number; top: number };
-  muted?: boolean;
-}) {
-  console.warn('autoCreateVideo', id);
-  const videoEl = createVideo({});
-  if (muted !== undefined) {
-    videoEl.muted = muted;
-  }
-  videoEl.srcObject = stream;
-  videoEl.style.width = `1px`;
-  videoEl.style.height = `1px`;
-  videoEl.style.position = 'fixed';
-  videoEl.style.bottom = '0';
-  videoEl.style.right = '0';
-  videoEl.style.opacity = '0';
-  videoEl.style.pointerEvents = 'none';
-  document.body.appendChild(videoEl);
-  return new Promise<{
-    canvasDom: fabric.Image;
-    videoEl: HTMLVideoElement;
-    clearFrame: any;
-    scale: number;
-  }>((resolve) => {
-    console.log(videoEl, 888211888888);
-    videoEl.onloadedmetadata = () => {
-      console.log('kkkkkkk2kkk', 123);
-      const width = stream.getVideoTracks()[0].getSettings().width!;
-      const height = stream.getVideoTracks()[0].getSettings().height!;
-      const ratio = handleScale({ width, height });
-      videoEl.width = width;
-      videoEl.height = height;
-
-      const canvasDom = markRaw(
-        new fabric.Image(videoEl, {
-          top: rect?.top || 0,
-          left: rect?.left || 0,
-          width,
-          height,
-        })
-      );
-      console.log(
-        '初始化',
-        ratio,
-        canvasDom.width,
-        canvasDom.height,
-        canvasDom
-      );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
-
-      canvasDom.scale(ratio);
-      fabricCanvas.value!.add(canvasDom);
-      let timer;
-
-      function clearFrame() {
-        window.cancelAnimationFrame(timer);
-      }
-
-      renderFrame();
-
-      resolve({ canvasDom, scale: ratio, videoEl, clearFrame });
-    };
-  });
-}
-
-watch(
-  () => currentResolutionRatio.value,
-  (newHeight, oldHeight) => {
-    changeCanvasAttr({ newHeight, oldHeight });
-  }
-);
-
-// 容器宽高,1280*720,即720p
-// canvas容器宽高,2560*1440,即1440p
-
-// ======
-// 容器宽高,960*540,即540p
-// dom宽高,640*480
-// canvas容器宽高,960*540,即540p
-// 将dom绘制到容器里,此时dom的大小就是640*480
-// 需求,不管切换多少分辨率,我要看到的dom都是一样大小,即
-// 960*540时,dom是640*480
-// 1280*720时,dom不能是640*480了,因为这样他就会对比上一个分辨率的dom看起来小了,960/1280=0.75,540/720=0.75,
-// 其实就是分辨率变大了,我们就要将图片也变大,即图片的宽是640/0.75=853.4,高是480/0.75=640
-// 坐标变化,960*540时,dom坐标是100,100
-// 1280*720时,dom的坐标不能再是100,100了,否则对比上一个分辨率看起来偏
-
-function changeCanvasAttr({
-  newHeight,
-  oldHeight,
-}: {
-  newHeight: number;
-  oldHeight: number;
-}) {
-  if (fabricCanvas.value) {
-    const resolutionHeight =
-      currentResolutionRatio.value / window.devicePixelRatio;
-    const resolutionWidth =
-      (currentResolutionRatio.value / window.devicePixelRatio) *
-      videoRatio.value;
-    fabricCanvas.value.setWidth(resolutionWidth);
-    fabricCanvas.value.setHeight(resolutionHeight);
-    // fabricCanvas.value.forEachObject((canvas) => {
-    //   canvas.setCoords();
-    // });
-    appStore.allTrack.forEach((item) => {
-      if (item.canvasDom) {
-        // 分辨率变小了,将图片变小
-        if (newHeight < oldHeight) {
-          const ratio = newHeight / oldHeight;
-          const ratio1 = (item.canvasDom.scaleX || 1) * ratio;
-          const ratio2 = oldHeight / newHeight;
-          console.log(
-            ratio,
-            ratio1,
-            '分辨率变小了,将图片变小-----',
-            item.canvasDom
-          );
-          item.canvasDom.scale(ratio1);
-          item.canvasDom.left = item.canvasDom.left! / ratio2;
-          item.canvasDom.top = item.canvasDom.top! / ratio2;
-        } else {
-          // 分辨率变大了,将图片变大
-          const ratio = newHeight / oldHeight;
-          const ratio1 = (item.canvasDom.scaleX || 1) * ratio;
-          const ratio2 = oldHeight / newHeight;
-          console.log(
-            ratio,
-            ratio1,
-            '分辨率变大了,将图片变大-----',
-            item.canvasDom
-          );
-          item.canvasDom.scale(ratio1);
-          item.canvasDom.left = item.canvasDom.left! / ratio2;
-          item.canvasDom.top = item.canvasDom.top! / ratio2;
-        }
-      }
-    });
-    changeCanvasStyle();
-  }
-}
-
-function changeCanvasStyle() {
-  // @ts-ignore
-  fabricCanvas.value.wrapperEl.style.width = `${wrapSize.width}px`;
-  // @ts-ignore
-  fabricCanvas.value.wrapperEl.style.height = `${wrapSize.height}px`;
-  // @ts-ignore
-  fabricCanvas.value.lowerCanvasEl.style.width = `${wrapSize.width}px`;
-  // @ts-ignore
-  fabricCanvas.value.lowerCanvasEl.style.height = `${wrapSize.height}px`;
-  // @ts-ignore
-  fabricCanvas.value.upperCanvasEl.style.width = `${wrapSize.width}px`;
-  // @ts-ignore
-  fabricCanvas.value.upperCanvasEl.style.height = `${wrapSize.height}px`;
-}
-
-function initCanvas() {
-  const resolutionHeight =
-    currentResolutionRatio.value / window.devicePixelRatio;
-  const resolutionWidth =
-    (currentResolutionRatio.value / window.devicePixelRatio) * videoRatio.value;
-  const wrapWidth = containerRef.value!.getBoundingClientRect().width;
-  // const wrapWidth = 1920;
-  const ratio = wrapWidth / resolutionWidth;
-  scaleRatio.value = resolutionWidth / wrapWidth;
-  const wrapHeight = resolutionHeight * ratio;
-  // const wrapHeight = 1080;
-  // lower-canvas: 实际的canvas画面,也就是pushCanvasRef
-  // upper-canvas: 操作时候的canvas
-  const ins = markRaw(new fabric.Canvas(pushCanvasRef.value!));
-  ins.setWidth(resolutionWidth);
-  ins.setHeight(resolutionHeight);
-  ins.setBackgroundColor('black', () => {
-    console.log('setBackgroundColor回调');
-  });
-  wrapSize.width = wrapWidth;
-  wrapSize.height = wrapHeight;
-  fabricCanvas.value = ins;
-  changeCanvasStyle();
-}
-
-async function handleCache() {
-  const res: AppRootState['allTrack'] = [];
-  const queue: any[] = [];
-  resourceCacheStore.list.forEach((item) => {
-    // @ts-ignore
-    const obj: AppRootState['allTrack'][0] = {};
-    obj.audio = item.audio;
-    obj.video = item.video;
-    obj.id = item.id;
-    obj.type = item.type;
-    obj.hidden = item.hidden;
-    obj.mediaName = item.mediaName;
-    obj.muted = item.muted;
-    obj.rect = item.rect;
-    obj.scaleInfo = item.scaleInfo;
-
-    async function handleMediaVideo() {
-      const file = await readFile(item.id);
-      const url = URL.createObjectURL(file);
-      console.log(file, file.name, url);
-      const videoEl = createVideo({});
-      videoEl.src = url;
-      videoEl.muted = item.muted ? item.muted : false;
-      videoEl.style.width = `1px`;
-      videoEl.style.height = `1px`;
-      videoEl.style.position = 'fixed';
-      videoEl.style.bottom = '0';
-      videoEl.style.right = '0';
-      videoEl.style.opacity = '0';
-      videoEl.style.pointerEvents = 'none';
-      document.body.appendChild(videoEl);
-      await new Promise((resolve) => {
-        videoEl.onloadedmetadata = () => {
-          const stream = videoEl
-            // @ts-ignore
-            .captureStream();
-          const width = stream.getVideoTracks()[0].getSettings().width!;
-          const height = stream.getVideoTracks()[0].getSettings().height!;
-          // const ratio = handleScale({ width, height });
-          videoEl.width = width;
-          videoEl.height = height;
-
-          const canvasDom = markRaw(
-            new fabric.Image(videoEl, {
-              top: item.rect?.top || 0,
-              left: item.rect?.left || 0,
-              width,
-              height,
-            })
-          );
-          canvasDom.on('moving', () => {
-            appStore.allTrack.forEach((iten) => {
-              if (item.id === iten.id) {
-                iten.rect = { top: canvasDom.top!, left: canvasDom.left! };
-              }
-            });
-            resourceCacheStore.setList(appStore.allTrack);
-          });
-          canvasDom.scale(item.scaleInfo?.scaleX || 1);
-          fabricCanvas.value!.add(canvasDom);
-
-          renderFrame();
-          obj.videoEl = videoEl;
-          obj.canvasDom = canvasDom;
-          resolve({ videoEl, canvasDom });
-        };
-      });
-      const stream = videoEl
-        // @ts-ignore
-        .captureStream() as MediaStream;
-      obj.stream = stream;
-      obj.streamid = stream.id;
-      obj.track = stream.getVideoTracks()[0];
-      obj.trackid = stream.getVideoTracks()[0].id;
-      // if (stream.getAudioTracks()[0]) {
-      //   console.log('视频有音频', stream.getAudioTracks()[0]);
-      //   mediaVideoTrack.audio = 1;
-      //   const audioTrack: AppRootState['allTrack'][0] = {
-      //     id: mediaVideoTrack.id,
-      //     audio: 1,
-      //     video: 2,
-      //     mediaName: val.mediaName,
-      //     type: MediaTypeEnum.media,
-      //     track: stream.getAudioTracks()[0],
-      //     trackid: stream.getAudioTracks()[0].id,
-      //     stream,
-      //     streamid: stream.id,
-      //     hidden: true,
-      //     muted: false,
-      //   };
-      //   // @ts-ignore
-      //   const res = [...appStore.allTrack, audioTrack];
-      //   appStore.setAllTrack(res);
-      //   resourceCacheStore.setList(res);
-      //   handleMixedAudio();
-      //   // @ts-ignore
-
-      //   addTrack(audioTrack);
-      // }
-    }
-
-    async function handleImg() {
-      const file = await readFile(item.id);
-      const imgEl = await new Promise<HTMLImageElement>((resolve) => {
-        const reader = new FileReader();
-        reader.addEventListener(
-          'load',
-          function () {
-            const img = document.createElement('img');
-            img.src = reader.result as string;
-            img.onload = () => {
-              resolve(img);
-            };
-          },
-          false
-        );
-        if (file) {
-          reader.readAsDataURL(file);
-        }
-      });
-      if (fabricCanvas.value) {
-        const canvasDom = markRaw(
-          new fabric.Image(imgEl, {
-            top: item.rect?.top || 0,
-            left: item.rect?.left || 0,
-            width: imgEl.width,
-            height: imgEl.height,
-          })
-        );
-        canvasDom.on('moving', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.on('scaling', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.scaleInfo = {
-                scaleX: canvasDom.scaleX || 1,
-                scalcY: canvasDom.scaleY || 1,
-              };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.scale(item.scaleInfo?.scaleX || 1);
-        fabricCanvas.value.add(canvasDom);
-        obj.canvasDom = canvasDom;
-        renderFrame();
-      }
-    }
-    if (item.type === MediaTypeEnum.media && item.video === 1) {
-      queue.push(handleMediaVideo());
-    } else if (item.type === MediaTypeEnum.txt) {
-      obj.txtInfo = item.txtInfo;
-      if (fabricCanvas.value) {
-        const canvasDom = markRaw(
-          new fabric.Text(item.txtInfo?.txt || '', {
-            top: item.rect?.top || 0,
-            left: item.rect?.left || 0,
-            fill: item.txtInfo?.color,
-          })
-        );
-        canvasDom.on('moving', () => {
-          appStore.allTrack.forEach((item) => {
-            if (obj.id === item.id) {
-              item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-            }
-          });
-          resourceCacheStore.setList(appStore.allTrack);
-        });
-        canvasDom.scale(item.scaleInfo?.scaleX || 1);
-        fabricCanvas.value.add(canvasDom);
-        obj.canvasDom = canvasDom;
-        renderFrame();
-      }
-    } else if (item.type === MediaTypeEnum.img) {
-      queue.push(handleImg());
-    }
-    res.push(obj);
-  });
-  await Promise.all(queue);
-  canvasVideoStream.value = pushCanvasRef.value!.captureStream();
-  appStore.setAllTrack(res);
-}
-
-onMounted(() => {
-  setTimeout(() => {
-    scrollTo(0, 0);
-  }, 100);
-  // initNullAudio();
-  initUserMedia();
-  initCanvas();
-  handleCache();
-  document.addEventListener('visibilitychange', onPageVisibility);
-});
-
-onUnmounted(() => {
-  document.removeEventListener('visibilitychange', onPageVisibility);
-});
-
-function selectMediaOk(val: MediaTypeEnum) {
-  showMediaModalCpt.value = true;
-  showSelectMediaModalCpt.value = false;
-  currentMediaType.value = val;
-}
-
-async function addMediaOk(val: {
-  type: MediaTypeEnum;
-  deviceId: string;
-  mediaName: string;
-  txtInfo?: { txt: string; color: string };
-  imgInfo?: UploadFileInfo[];
-  mediaInfo?: UploadFileInfo[];
-}) {
-  if (!audioCtx.value) {
-    audioCtx.value = new AudioContext();
-  }
-  showMediaModalCpt.value = false;
-  if (val.type === MediaTypeEnum.screen) {
-    const event = await navigator.mediaDevices.getDisplayMedia({
-      video: {
-        deviceId: val.deviceId,
-        // displaySurface: 'monitor', // browser默认标签页;window默认窗口;monitor默认整个屏幕
-      },
-      audio: true,
-    });
-
-    const videoTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.screen,
-      track: event.getVideoTracks()[0],
-      trackid: event.getVideoTracks()[0].id,
-      stream: event,
-      streamid: event.id,
-      hidden: false,
-      muted: false,
-    };
-
-    const { canvasDom, videoEl } = await autoCreateVideo({
-      stream: event,
-      id: videoTrack.id,
-    });
-    videoTrack.videoEl = videoEl;
-    // @ts-ignore
-    videoTrack.canvasDom = canvasDom;
-
-    const audio = event.getAudioTracks();
-    if (audio.length) {
-      videoTrack.audio = 1;
-      const audioTrack: AppRootState['allTrack'][0] = {
-        id: videoTrack.id,
-        audio: 1,
-        video: 2,
-        mediaName: val.mediaName,
-        type: MediaTypeEnum.screen,
-        track: event.getAudioTracks()[0],
-        trackid: event.getAudioTracks()[0].id,
-        stream: event,
-        streamid: event.id,
-        hidden: true,
-        muted: false,
-      };
-      const res = [...appStore.allTrack, videoTrack, audioTrack];
-      appStore.setAllTrack(res);
-      resourceCacheStore.setList(res);
-      handleMixedAudio();
-      // @ts-ignore
-      addTrack(videoTrack);
-      // @ts-ignore
-      addTrack(audioTrack);
-    } else {
-      const res = [...appStore.allTrack, videoTrack];
-      appStore.setAllTrack(res);
-      resourceCacheStore.setList(res);
-      // @ts-ignore
-      addTrack(videoTrack);
-    }
-
-    console.log('获取窗口成功');
-  } else if (val.type === MediaTypeEnum.camera) {
-    const event = await navigator.mediaDevices.getUserMedia({
-      video: {
-        deviceId: val.deviceId,
-      },
-      audio: false,
-    });
-    const videoTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.camera,
-      track: event.getVideoTracks()[0],
-      trackid: event.getVideoTracks()[0].id,
-      stream: event,
-      streamid: event.id,
-      hidden: false,
-      muted: false,
-    };
-    const { canvasDom, videoEl } = await autoCreateVideo({
-      stream: event,
-      id: videoTrack.id,
-    });
-    videoTrack.videoEl = videoEl;
-    // @ts-ignore
-    videoTrack.canvasDom = canvasDom;
-
-    const res = [...appStore.allTrack, videoTrack];
-    appStore.setAllTrack(res);
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-    addTrack(videoTrack);
-    console.log('获取摄像头成功');
-  } else if (val.type === MediaTypeEnum.microphone) {
-    const event = await navigator.mediaDevices.getUserMedia({
-      video: false,
-      audio: { deviceId: val.deviceId },
-    });
-    const audioTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 1,
-      video: 2,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.microphone,
-      track: event.getAudioTracks()[0],
-      trackid: event.getAudioTracks()[0].id,
-      stream: event,
-      streamid: event.id,
-      hidden: false,
-      muted: false,
-    };
-    const res = [...appStore.allTrack, audioTrack];
-    appStore.setAllTrack(res);
-    resourceCacheStore.setList(res);
-    handleMixedAudio();
-    // @ts-ignore
-    addTrack(audioTrack);
-
-    console.log('获取麦克风成功');
-  } else if (val.type === MediaTypeEnum.txt) {
-    const txtTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.txt,
-      track: undefined,
-      trackid: undefined,
-      stream: undefined,
-      streamid: undefined,
-      hidden: false,
-      muted: false,
-    };
-    if (fabricCanvas.value) {
-      const canvasDom = markRaw(
-        new fabric.Text(val.txtInfo?.txt || '', {
-          top: 0,
-          left: 0,
-          fill: val.txtInfo?.color,
-        })
-      );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (txtTrack.id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
-      txtTrack.txtInfo = val.txtInfo;
-      // @ts-ignore
-      txtTrack.canvasDom = canvasDom;
-      fabricCanvas.value.add(canvasDom);
-      renderFrame();
-    }
-
-    const res = [...appStore.allTrack, txtTrack];
-    // @ts-ignore
-    appStore.setAllTrack(res);
-    // @ts-ignore
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-    addTrack(txtTrack);
-
-    console.log('获取文字成功', fabricCanvas.value);
-  } else if (val.type === MediaTypeEnum.img) {
-    const imgTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.img,
-      track: undefined,
-      trackid: undefined,
-      stream: undefined,
-      streamid: undefined,
-      hidden: false,
-      muted: false,
-    };
-
-    if (fabricCanvas.value) {
-      const imgEl = await new Promise<HTMLImageElement>((resolve) => {
-        if (!val.imgInfo) return;
-        const file = val.imgInfo[0].file!;
-        saveFile({ file, fileName: imgTrack.id });
-        const reader = new FileReader();
-        reader.addEventListener(
-          'load',
-          function () {
-            const img = document.createElement('img');
-            img.src = reader.result as string;
-            img.onload = () => {
-              resolve(img);
-            };
-          },
-          false
-        );
-        if (file) {
-          reader.readAsDataURL(file);
-        }
-      });
-
-      const canvasDom = markRaw(
-        new fabric.Image(imgEl, {
-          top: 0,
-          left: 0,
-          width: imgEl.width,
-          height: imgEl.height,
-        })
-      );
-      canvasDom.on('moving', () => {
-        appStore.allTrack.forEach((item) => {
-          if (imgTrack.id === item.id) {
-            item.rect = { top: canvasDom.top!, left: canvasDom.left! };
-          }
-        });
-        resourceCacheStore.setList(appStore.allTrack);
-      });
-      const ratio = handleScale({ width: imgEl.width, height: imgEl.height });
-      // @ts-ignore
-      imgTrack.canvasDom = canvasDom;
-      imgTrack.scaleInfo = { scaleX: ratio, scalcY: ratio };
-      canvasDom.scale(ratio);
-      fabricCanvas.value.add(canvasDom);
-      renderFrame();
-    }
-
-    const res = [...appStore.allTrack, imgTrack];
-    // @ts-ignore
-    appStore.setAllTrack(res);
-    // @ts-ignore
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-    addTrack(imgTrack);
-
-    console.log('获取图片成功', fabricCanvas.value);
-  } else if (val.type === MediaTypeEnum.media) {
-    const mediaVideoTrack: AppRootState['allTrack'][0] = {
-      id: getRandomEnglishString(8),
-      audio: 2,
-      video: 1,
-      mediaName: val.mediaName,
-      type: MediaTypeEnum.media,
-      track: undefined,
-      trackid: undefined,
-      stream: undefined,
-      streamid: undefined,
-      hidden: false,
-      muted: false,
-    };
-    if (fabricCanvas.value) {
-      if (!val.mediaInfo) return;
-      const file = val.mediaInfo[0].file!;
-      saveFile({ file, fileName: mediaVideoTrack.id });
-      const url = URL.createObjectURL(file);
-      console.log(file, file.name, url);
-      const videoEl = createVideo({});
-      videoEl.src = url;
-      videoEl.muted = false;
-      videoEl.style.width = `1px`;
-      videoEl.style.height = `1px`;
-      videoEl.style.position = 'fixed';
-      videoEl.style.bottom = '0';
-      videoEl.style.right = '0';
-      videoEl.style.opacity = '0';
-      videoEl.style.pointerEvents = 'none';
-      document.body.appendChild(videoEl);
-      const videoRes = await new Promise<HTMLVideoElement>((resolve) => {
-        videoEl.onloadedmetadata = () => {
-          resolve(videoEl);
-        };
-      });
-      // @ts-ignore
-      const stream = videoRes.captureStream();
-      const { canvasDom } = await autoCreateVideo({
-        stream,
-        id: mediaVideoTrack.id,
-      });
-      mediaVideoTrack.videoEl = videoEl;
-      // @ts-ignore
-      mediaVideoTrack.canvasDom = canvasDom;
-      if (stream.getAudioTracks()[0]) {
-        console.log('视频有音频', stream.getAudioTracks()[0]);
-        mediaVideoTrack.audio = 1;
-        const audioTrack: AppRootState['allTrack'][0] = {
-          id: mediaVideoTrack.id,
-          audio: 1,
-          video: 2,
-          mediaName: val.mediaName,
-          type: MediaTypeEnum.media,
-          track: stream.getAudioTracks()[0],
-          trackid: stream.getAudioTracks()[0].id,
-          stream,
-          streamid: stream.id,
-          hidden: true,
-          muted: false,
-        };
-        // @ts-ignore
-        const res = [...appStore.allTrack, audioTrack];
-        appStore.setAllTrack(res);
-        resourceCacheStore.setList(res);
-        handleMixedAudio();
-        // @ts-ignore
-
-        addTrack(audioTrack);
-      }
-    }
-    const res = [...appStore.allTrack, mediaVideoTrack];
-    // @ts-ignore
-    appStore.setAllTrack(res);
-    // @ts-ignore
-    resourceCacheStore.setList(res);
-    // @ts-ignore
-
-    addTrack(mediaVideoTrack);
-
-    console.log('获取视频成功', fabricCanvas.value);
-  }
-
-  canvasVideoStream.value = pushCanvasRef.value!.captureStream();
-}
-
-function handleChangeMuted(item: AppRootState['allTrack'][0]) {
-  console.log('handleChangeMuted', item);
-  if (item.videoEl) {
-    const res = !item.videoEl.muted;
-    item.videoEl.muted = res;
-    item.muted = res;
-    resourceCacheStore.setList(appStore.allTrack);
-  }
-}
-
-function handleEdit(item: AppRootState['allTrack'][0]) {
-  console.log('handleEdit', item);
-}
-
-function handleDel(item: AppRootState['allTrack'][0]) {
-  console.log('handleDel', item);
-  if (item.canvasDom !== undefined) {
-    // @ts-ignore
-    fabricCanvas.value?.remove(item.canvasDom);
-    item.videoEl?.remove();
-  }
-  const res = appStore.allTrack.filter((iten) => iten.id !== item.id);
-  appStore.setAllTrack(res);
-  resourceCacheStore.setList(res);
-  delTrack(item);
-}
-
-function saveFile(data: { file: File; fileName: string }) {
-  const { file, fileName } = data;
-  const requestFileSystem =
-    // @ts-ignore
-    window.requestFileSystem || window.webkitRequestFileSystem;
-  function onError(err) {
-    console.error('saveFile错误', data.fileName);
-    console.log(err);
-  }
-  function onFs(fs) {
-    // 创建文件
-    fs.root.getFile(
-      fileName,
-      { create: true },
-      function (fileEntry) {
-        // 创建文件写入流
-        fileEntry.createWriter(function (fileWriter) {
-          fileWriter.onwriteend = () => {
-            // 完成后关闭文件
-            fileWriter.abort();
-          };
-          // 写入文件内容
-          fileWriter.write(file);
-        });
-      },
-      () => {
-        console.log('写入文件失败');
-      }
-    );
-  }
-  // Opening a file system with temporary storage
-  requestFileSystem(
-    // @ts-ignore
-    window.PERSISTENT,
-    0,
-    onFs,
-    onError
-  );
-}
-
-function readFile(fileName: string) {
-  return new Promise<File>((resolve) => {
-    const requestFileSystem =
-      // @ts-ignore
-      window.requestFileSystem || window.webkitRequestFileSystem;
-    function onError(err) {
-      console.error('readFile错误', fileName);
-      console.log(err);
-    }
-    function onFs(fs) {
-      fs.root.getFile(
-        fileName,
-        {},
-        function (fileEntry) {
-          fileEntry.file(function (file) {
-            resolve(file);
-            // const url = URL.createObjectURL(file);
-            // const videoEl = createVideo({});
-            // videoEl.src = url;
-            // document.body.appendChild(videoEl);
-          }, onError);
-        },
-        onError
-      );
-    }
-    // Opening a file system with temporary storage
-    requestFileSystem(
-      // @ts-ignore
-      window.PERSISTENT,
-      0,
-      onFs,
-      onError
-    );
-  });
-}
-
-function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
-  currentMediaType.value = item.type;
-  showMediaModalCpt.value = true;
-}
-</script>
-
-<style lang="scss" scoped>
-.push-wrap {
-  display: flex;
-  justify-content: space-between;
-  margin: 15px auto 0;
-  width: $w-1250;
-  .left {
-    position: relative;
-    display: inline-block;
-    overflow: hidden;
-    box-sizing: border-box;
-    width: $w-960;
-    height: 100%;
-    border-radius: 6px;
-    background-color: white;
-    color: #9499a0;
-    vertical-align: top;
-
-    .container {
-      position: relative;
-      overflow: hidden;
-      height: 100%;
-      background-color: rgba($color: #000000, $alpha: 0.5);
-      line-height: 0;
-
-      :deep(canvas) {
-        // width: 100%;
-      }
-
-      .add-wrap {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: space-around;
-        padding: 0 20px;
-        height: 50px;
-        border-radius: 6px;
-        background-color: white;
-        transform: translate(-50%, -50%);
-      }
-    }
-    .room-control {
-      display: flex;
-      justify-content: space-between;
-      padding: 20px;
-      background-color: papayawhip;
-
-      .info {
-        display: flex;
-        align-items: center;
-
-        .avatar {
-          margin-right: 20px;
-          width: 55px;
-          height: 55px;
-          border-radius: 50%;
-          background-position: center center;
-          background-size: cover;
-          background-repeat: no-repeat;
-        }
-        .detail {
-          display: flex;
-          flex-direction: column;
-          flex-shrink: 0;
-          width: 200px;
-          text-align: initial;
-          .top {
-            margin-bottom: 10px;
-            color: #18191c;
-          }
-          .bottom {
-            font-size: 14px;
-          }
-        }
-      }
-      .rtc {
-        display: flex;
-        align-items: center;
-        flex: 1;
-        font-size: 14px;
-        .item {
-          display: flex;
-          align-items: center;
-          flex: 1;
-          .txt {
-            flex-shrink: 0;
-            width: 80px;
-          }
-          .down {
-            width: 90px;
-
-            user-select: none;
-          }
-        }
-      }
-      .other {
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        font-size: 12px;
-        .top {
-        }
-        .bottom {
-          margin-top: 10px;
-        }
-      }
-    }
-  }
-  .right {
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    box-sizing: border-box;
-    margin-left: 10px;
-    width: $w-250;
-    border-radius: 6px;
-    background-color: white;
-    color: #9499a0;
-
-    .resource-card {
-      position: relative;
-      box-sizing: border-box;
-      margin-bottom: 10px;
-      padding: 10px;
-      width: 100%;
-      height: 290px;
-      border-radius: 6px;
-      background-color: papayawhip;
-      .title {
-        text-align: initial;
-      }
-      .item {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        margin: 5px 0;
-        font-size: 14px;
-        // &:hover {
-        //   .control {
-        //     display: flex;
-        //     align-items: center;
-        //   }
-        // }
-        .control {
-          display: flex;
-          align-items: center;
-          .control-item {
-            cursor: pointer;
-            &:not(:last-child) {
-              margin-right: 6px;
-            }
-          }
-        }
-      }
-      .bottom {
-        position: absolute;
-        bottom: 0;
-        left: 0;
-        padding: 10px;
-      }
-    }
-    .danmu-card {
-      position: relative;
-      flex: 1;
-      box-sizing: border-box;
-      padding: 10px;
-      width: 100%;
-      border-radius: 6px;
-      background-color: papayawhip;
-      text-align: initial;
-      .title {
-        margin-bottom: 10px;
-      }
-      .list {
-        overflow: scroll;
-        height: 360px;
-
-        @extend %hideScrollbar;
-
-        .item {
-          margin-bottom: 10px;
-          font-size: 12px;
-          .name {
-            color: #9499a0;
-          }
-          .msg {
-            color: #61666d;
-          }
-        }
-      }
-
-      .send-msg {
-        position: absolute;
-        bottom: 10px;
-        left: 50%;
-        display: flex;
-        align-items: center;
-        box-sizing: border-box;
-        width: calc(100% - 20px);
-        transform: translateX(-50%);
-        .ipt {
-          display: block;
-          box-sizing: border-box;
-          margin: 0 auto;
-          margin-right: 10px;
-          padding: 10px;
-          width: 80%;
-          height: 30px;
-          outline: none;
-          border: 1px solid hsla(0, 0%, 60%, 0.2);
-          border-radius: 4px;
-          background-color: #f1f2f3;
-          font-size: 14px;
-        }
-      }
-    }
-  }
-}
-
-// 屏幕宽度大于1500的时候
-@media screen and (min-width: $w-1500) {
-  .push-wrap {
-    width: $w-1475;
-    .left {
-      width: $w-1150;
-    }
-    .right {
-      width: $w-300;
-    }
-  }
-}
-</style>

+ 18 - 12
src/views/pushByCanvas/mediaModal/index.vue

@@ -180,8 +180,9 @@ async function init() {
       type: MediaTypeEnum.microphone,
     };
     mediaName.value = `麦克风-${
-      appStore.allTrack.filter((item) => item.type === MediaTypeEnum.microphone)
-        .length + 1
+      appStore.allTrack
+        .filter((item) => item.type === MediaTypeEnum.microphone)
+        .filter((item) => !item.hidden).length + 1
     }`;
   } else if (props.mediaType === MediaTypeEnum.camera) {
     res.forEach((item) => {
@@ -198,8 +199,9 @@ async function init() {
       type: MediaTypeEnum.camera,
     };
     mediaName.value = `摄像头-${
-      appStore.allTrack.filter((item) => item.type === MediaTypeEnum.camera)
-        .length + 1
+      appStore.allTrack
+        .filter((item) => item.type === MediaTypeEnum.camera)
+        .filter((item) => !item.hidden).length + 1
     }`;
   } else if (props.mediaType === MediaTypeEnum.screen) {
     currentInput.value = {
@@ -207,8 +209,9 @@ async function init() {
       type: MediaTypeEnum.screen,
     };
     mediaName.value = `窗口-${
-      appStore.allTrack.filter((item) => item.type === MediaTypeEnum.screen)
-        .length + 1
+      appStore.allTrack
+        .filter((item) => item.type === MediaTypeEnum.screen)
+        .filter((item) => !item.hidden).length + 1
     }`;
   } else if (props.mediaType === MediaTypeEnum.txt) {
     currentInput.value = {
@@ -217,8 +220,9 @@ async function init() {
     };
     txtInfo.value = { txt: '', color: 'rgba(255,215,0,1)' };
     mediaName.value = `文字-${
-      appStore.allTrack.filter((item) => item.type === MediaTypeEnum.txt)
-        .length + 1
+      appStore.allTrack
+        .filter((item) => item.type === MediaTypeEnum.txt)
+        .filter((item) => !item.hidden).length + 1
     }`;
     setTimeout(() => {
       inputInstRef.value?.focus();
@@ -230,8 +234,9 @@ async function init() {
     };
     imgInfo.value = [];
     mediaName.value = `图片-${
-      appStore.allTrack.filter((item) => item.type === MediaTypeEnum.img)
-        .length + 1
+      appStore.allTrack
+        .filter((item) => item.type === MediaTypeEnum.img)
+        .filter((item) => !item.hidden).length + 1
     }`;
   } else if (props.mediaType === MediaTypeEnum.media) {
     currentInput.value = {
@@ -240,8 +245,9 @@ async function init() {
     };
     mediaInfo.value = [];
     mediaName.value = `视频-${
-      appStore.allTrack.filter((item) => item.type === MediaTypeEnum.media)
-        .length + 1
+      appStore.allTrack
+        .filter((item) => item.type === MediaTypeEnum.media)
+        .filter((item) => !item.hidden).length + 1
     }`;
   }
 }