WebAssembly002 FFmpegWasmLocalServer项目

项目介绍

  • https://github.com/incubated-geek-cc/FFmpegWasmLocalServer.git可将音频或视频文件转换为其他可选的多媒体格式,并导出转码的结果
$ bash run.sh 
FFmpeg App is listening on port 3000!

运行效果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

相关依赖

Error: Cannot find module ‘express’

  • npm install express
$npm install express
npm WARN old lockfile 
npm WARN old lockfile The package-lock.json file was created with an old version of npm,
npm WARN old lockfile so supplemental metadata must be fetched from the registry.
npm WARN old lockfile 
npm WARN old lockfile This is a one-time fix-up, please be patient...
npm WARN old lockfile 
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE   package: 'FFmpegWasmLocalServer@1.0.0',
npm WARN EBADENGINE   required: { node: '12.13.0' },
npm WARN EBADENGINE   current: { node: 'v16.20.0', npm: '8.19.4' }
npm WARN EBADENGINE }
npm WARN deprecated consolidate@0.16.0: Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelogadded 70 packages, and audited 71 packages in 2m7 packages are looking for fundingrun `npm fund` for detailsfound 0 vulnerabilities

启动服务

// 引入 Express 库
const express = require('express');
// 创建一个 Express 应用程序实例
const app = express();
// 设置应用程序的端口号,使用环境变量 PORT 或默认值 3000
const PORT = process.env.PORT || 3000;// 引入文件系统和路径处理模块
const fs = require('fs');
const path = require('path');
// 引入 Consolidate 模块用于模板引擎支持
const engine = require("consolidate");
// 引入 compression 模块用于启用响应压缩
const compression = require('compression');// 使用 compression 中间件,对所有响应进行压缩
app.use(compression());// 中间件,启用 SharedBuffer
app.use(function(req, res, next) {// 设置响应头,启用 SharedBufferres.header("Cross-Origin-Embedder-Policy", "require-corp");res.header("Cross-Origin-Opener-Policy", "same-origin");// 继续执行下一个中间件或路由处理函数next();
});// 静态文件中间件,将 public 目录设置为静态文件目录
app.use(express.static(path.join(__dirname, "public")))
// 设置视图目录为 views
.set("views", path.join(__dirname, "views"))
// 使用 Mustache 模板引擎
.engine("html", engine.mustache)
// 设置视图引擎为 Mustache
.set("view engine", "html")// 处理根路径的 GET 请求,渲染 index.html 页面
.get("/", (req, res) => res.render("index.html"))
// 处理 /index.html 路径的 GET 请求,同样渲染 index.html 页面
.get("/index.html", (req, res) => res.render("index.html"))// 监听指定的端口号,当应用程序启动时打印日志
.listen(PORT, () => {console.log(`FFmpeg App is listening on port ${PORT}!`);
});

https服务:

  • 非https访问可能存在如下问题:The Cross-Origin-Opener-Policy header has been ignored, because the URL’s origin was untrustworthy. It was defined either in the final response or a redirect. Please deliver the response using the HTTPS protocol. You can also use the ‘localhost’ origin instead. See https://www.w3.org/TR/powerful-features/#potentially-trustworthy-origin and https://html.spec.whatwg.org/#the-cross-origin-opener-policy-header.
const express = require('express');
const https = require('https');
const fs = require('fs');
const compression = require('compression');
const engine = require('consolidate');const app = express();
const PORT = process.env.PORT || 3000;// 1. 使用 Let's Encrypt 获取 SSL/TLS 证书,并将证书文件放置在项目中
// or just openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt
const options = {key: fs.readFileSync('path/to/server.key'),cert: fs.readFileSync('path/to/server.crt'),
};// 2. 配置 Express 应用程序以使用 HTTPS
const server = https.createServer(options, app);// 3. 添加 HTTP 到 HTTPS 的重定向中间件
app.use(function (req, res, next) {if (!req.secure) {return res.redirect('https://' + req.headers.host + req.url);}next();
});// 4. 添加其他中间件和路由
app.use(compression());
app.use(function (req, res, next) {res.header('Cross-Origin-Embedder-Policy', 'require-corp');res.header('Cross-Origin-Opener-Policy', 'same-origin');next();
});app.use(express.static(__dirname + '/public')).set('views', __dirname + '/views').engine('html', engine.mustache).set('view engine', 'html').get('/', (req, res) => res.render('index.html')).get('/index.html', (req, res) => res.render('index.html'));// 启动 Express 应用程序
server.listen(PORT, () => {console.log(`FFmpeg App is listening on port ${PORT} with HTTPS!`);
});

index.html

<html lang='en' class='notranslate' translate='no'><head><!-- 设置页面元数据 --><meta name='google' content='notranslate' />      <meta charset='UTF-8'>      <meta name='description' content='An Offline Multimedia File Conversion Tool.'>      <meta name='keywords' content='ffmpeg,wasm API,audio-conversion'>      <meta name="author" content="Charmaine Chui" />      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">      <meta name='viewport' content='width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' />     <meta http-equiv='Content-Language' content='en' /><title>Media Transcoder | Built With FFmpeg for Audio & Video Files</title><meta name='msapplication-TileColor' content='#ffffff' />      <meta name='theme-color' content='#ffffff' />      <meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />      <meta name='apple-mobile-web-app-capable' content='yes' />      <meta name='mobile-web-app-capable' content='yes' />      <meta name='HandheldFriendly' content='True' />      <meta name='MobileOptimized' content='320' /><!-- 设置网站图标 --><link rel="apple-touch-icon" sizes="76x76" href="img/favicon-76.png">      <link rel="apple-touch-icon" sizes="120x120" href="img/favicon-120.png">      <link rel="apple-touch-icon" sizes="152x152" href="img/favicon-152.png">      <link rel="icon" sizes="196x196" href="img/favicon-196.png">      <link rel="icon" type="image/x-icon" href="img/favicon.ico"><!-- 引入样式表 --><link href='css/bootstrap-4.5.2.min.css' rel='stylesheet' type='text/css' /><link href='css/offcanvas.css' rel='stylesheet' type='text/css' /><link href='css/custom.css' rel='stylesheet' type='text/css' /></head><!-- 在无法运行JavaScript的情况下显示提示信息 --><noscript>You need to enable JavaScript to run this app.</noscript><body><!-- 网站导航栏 --><nav id='site-header' class="navbar navbar-expand-sm bg-light navbar-light border-bottom fixed-top pt-0 pb-0 text-muted small"><!-- 网站标志 --><!-- 网站信息和链接 --></nav><!-- 主体内容区域 --><div class='container-full h-100 p-1'><div class='row no-gutters'><!-- 第一个列:选择输出文件格式 --><div class='col-sm-4 p-1'><!-- 卡片组件 --><div class="card rounded-0"><div class="card-header p-1"><span class='symbol'>❶</span> Select media format of output file</div><div class="card-body p-1"><!-- 表格组件 --><table class='table table-bordered small mb-0 w-100'><thead><tr><td colspan='2'><!-- 输入框和下拉列表 --></td></tr></thead><!-- 输出文件详细信息 --><!-- 重置按钮 --><button id='resetAllBtn' type='button' class='btn btn-sm btn-outline-danger rounded-circle navBtn float-right text-center symbol'>↺</button></td></tr></tbody></table></div></div></div><!-- 第二个列:上传媒体文件 --><!-- 上传文件按钮 --><button id='uploadMediaBtn' type='button' class='btn btn-sm btn-light border border-primary text-primary rounded-0'><span class='emoji'>📂</span> <small>Upload File</small><input id='uploadMedia' type='file' accept='audio/*,video/*' /></button></thead><tbody><!-- 输入文件详细信息 --></tbody><tr><td colspan='2' valign='middle'><!-- 保存按钮 --><button id='saveOutput' type='button' class='btn btn-sm btn-outline-success rounded-circle navBtn float-right text-center symbol'>💾</button><span class='symbol float-right mr-2 text-success'>𝙴𝚡𝚙𝚘𝚛𝚝 𝙿𝚛𝚘𝚌𝚎𝚜𝚜𝚎𝚍 𝙾𝚞𝚝𝚙𝚞𝚝 ▷</span></td></tr></table></div></div></div><!-- 第三个列:服务器的 Cross-Origin Isolated 状态 --><!-- 支持的文件格式信息 --><!-- 媒体文件预览区域 --><div id='mediaWrapper' class='text-center'></div></div></div></div></div><!-- 底部输出日志区域 --><div class='row no-gutters'></div><!-- 引入JavaScript文件 --><script src='js/polyfill.js'></script><script src='js/ie10-viewport-bug-workaround.js'></script><script src='js/bootstrap-native-v4.js'></script><script src="js/ffmpeg/ffmpeg.min.js"></script><script src="js/mimeTypes.js"></script><script src="js/custom.js"></script></body>
</html>
脚本标签作用
<script src='js/polyfill.js'></script>JavaScript特性的兼容性支持,确保在旧版本的浏览器正常运行
<script src='js/ie10-viewport-bug-workaround.js'></script>解决在 Internet Explorer 10 (IE10) 浏览器中的一些视口(viewport)相关的问题
<script src='js/bootstrap-native-v4.js'></script>引入 Bootstrap 框架的 JavaScript 部分,提供页面布局、样式和交互的基本功能。
<script src="js/ffmpeg/ffmpeg.min.js"></script>引入 FFmpeg 库
<script src="js/mimeTypes.js"></script>定义和处理不同媒体类型(MIME类型)的脚本
<script src="js/custom.js"></script>自定义的 JavaScript 代码

custom.js

// 检查文档是否完全加载,如果是则立即执行回调,否则等待DOMContentLoaded事件
if (document.readyState === "complete" || document.readyState !== "loading" && !document.documentElement.doScroll) {callback();
} else {// 在DOMContentLoaded事件触发时执行document.addEventListener('DOMContentLoaded', async () => {console.log('DOMContentLoaded');// 获取所有类名为 'card' 的元素const cards = document.querySelectorAll('.card');let maxHeight;// 计算所有 'card' 元素的最大高度for (let card of cards) {if (typeof maxHeight === 'undefined' || card.clientHeight > maxHeight) {maxHeight = card.clientHeight;}}// 设置所有 'card' 元素的高度和溢出样式for (let card of cards) {card['style']['height'] = `${maxHeight}px`;card['style']['overflow-y'] = 'auto';}// 设置 logsOutput 元素的高度const logsOutput = document.getElementById('logsOutput');logsOutput['style']['height'] = `calc(100vh - 50px - 0.25rem - 0.25rem - 0.25rem - 0.25rem - 0.25rem - ${maxHeight}px)`;// 显示当前年份const yearDisplay = document.getElementById('yearDisplay');yearDisplay.innerHTML = new Date().getFullYear();// 获取 outputLogs 元素const outputLogs = document.getElementById('outputLogs');// 获取当前日期时间的字符串表示function getCurrentDatetimeStamp() {const d = new Date();let datestamp = d.getFullYear() + '-' + ((d.getMonth() + 1 < 10) ? ('0' + (d.getMonth() + 1)) : (d.getMonth() + 1)) + '-' + ((d.getDate() < 10) ? ('0' + d.getDate()) : (d.getDate()));let timestamp = ((d.getHours() < 10) ? ('0' + d.getHours()) : (d.getHours())) + ':' + ((d.getMinutes() < 10) ? ('0' + d.getMinutes()) : (d.getMinutes())) + ':' + ((d.getSeconds() < 10) ? ('0' + d.getSeconds()) : (d.getSeconds()));let datetimeStr = datestamp + ' ' + timestamp;return datetimeStr;}// 日志类型常量const infoNote = 'ɪɴғᴏ ';const errNote = 'ᴇʀʀᴏʀ';// 添加数据日志到页面function appendDataLog(logMsg) {if (typeof logMsg === 'string') {let logType = infoNote;let textClass = 'text-light bg-dark';// 根据日志内容判断日志类型,并设置样式if (logMsg.toLowerCase().includes('fail')) {logType = errNote;textClass = 'text-light bg-danger';logMsg = `${logMsg}`;} else if (logMsg.indexOf('[fferr] size= ') === 0 || logMsg.indexOf('[fferr] frame= ') === 0) {textClass = 'text-white bg-primary'; // 重要的操作需求logMsg = `${logMsg}`;} else if (logMsg.indexOf('[fferr]') === 0 && logMsg.includes(':') && !logMsg.toLowerCase().includes('config')) {textClass = 'text-primary bg-light'; // 文件信息logMsg = `${logMsg}`;} else if (logMsg.indexOf('[info]') === 0) {textClass = 'text-dark'; // 比填充更好logMsg = `${logMsg}`;} else if (logMsg.indexOf('[fferr]') === 0) {textClass = 'text-secondary'; // 填充日志logMsg = `${logMsg}`;} else if (logMsg.indexOf('[ffout]') === 0) {textClass = 'text-white bg-success'; // 重要通知,处理结束logMsg = `${logMsg}`;} else {logMsg = `${logMsg}`;}// 插入日志到页面outputLogs.insertAdjacentHTML('beforeend', '<p class="mb-0 small"><span class="unicode text-dark mr-1">' + logType + '</span><span class="text-white bg-dark"><span class="symbol">【</span>' + getCurrentDatetimeStamp() + '<span class="symbol">】</span></span> <span class="' + textClass + '"> ' + logMsg.trim() + ' </span></p>');// 滚动到日志底部let scrollTopVal = outputLogs.scrollHeight - outputLogs.clientHeight;outputLogs.scroll(0, scrollTopVal);}}// 添加错误日志到页面function appendErrorLog(errMsg) {if (typeof errMsg === 'string') {outputLogs.insertAdjacentHTML('beforeend', '<p class="mb-0 small"><span class="unicode text-dark mr-1">' + errNote + '</span><span class="text-white bg-dark"><span class="symbol">【</span>' + getCurrentDatetimeStamp() + '<span class="symbol">】</span></span> <span class="text-light bg-danger"> ' + errMsg.trim() + ' </span></p>');// 滚动到日志底部let scrollTopVal = outputLogs.scrollHeight - outputLogs.clientHeight;outputLogs.scroll(0, scrollTopVal);}}// 重写 console.log 和 console.error,将输出信息显示在页面中的 outputLogs 元素中console.logs = console.log.bind(console);console.log = function () {console.logs.apply(console, arguments);if (Array.from(arguments).length === 1 && typeof (Array.from(arguments)[0]) === 'string') {appendDataLog(Array.from(arguments)[0]);}};console.errors = console.error.bind(console);console.error = function () {console.errors.apply(console, arguments);if (Array.from(arguments).length === 1 && typeof Array.from(arguments) === 'object') {appendErrorLog(Array.from(arguments)[0].path[0].error.message);}};

检查跨域隔离是否生效

        // 检查是否为跨域隔离const isCrossOriginIsolated = document.getElementById('isCrossOriginIsolated');if (crossOriginIsolated) {isCrossOriginIsolated.innerHTML = '🟢'; // 绿色} else {isCrossOriginIsolated.innerHTML = '🔴'; // 红色}

关键元素

        // 上传文件相关元素const uploadMediaBtn = document.getElementById('uploadMediaBtn');const uploadMedia = document.getElementById('uploadMedia');// 文件信息展示元素const fileNameDisplay = document.getElementById('FileName');const fileTypeDisplay = document.getElementById('FileType');const fileSizeDisplay = document.getElementById('FileSize');// 输出文件信息展示元素const outputFileExtension = document.getElementById('outputFileExtension');const FileExtDisplay = document.getElementById('FileExt');const MimeTypeDisplay = document.getElementById('MimeType');const MimeDescriptionDisplay = document.getElementById('MimeDescription');// 重置和保存按钮const resetAllBtn = document.getElementById('resetAllBtn');const saveOutputBtn = document.getElementById('saveOutput');saveOutputBtn.disabled = true;

相关函数

        // 触发事件,重置按钮点击时调用function triggerEvent(el, type) {let e = (('createEvent' in document) ? document.createEvent('HTMLEvents') : document.createEventObject());if ('createEvent' in document) {e.initEvent(type, false, true);el.dispatchEvent(e);} else {e.eventType = type;el.fireEvent('on' + e.eventType, e);}}// Uint8Array 转为 Base64,上传文件时调用const convertBitArrtoB64 = (bitArr) => (btoa(bitArr.reduce((data, byte) => data + String.fromCharCode(byte), '')));// 读取文件为 Array Buffer,上传文件时调用function readFileAsArrayBuffer(file) {return new Promise((resolve, reject) => {let fileredr = new FileReader();fileredr.onload = () => resolve(fileredr.result);fileredr.onerror = () => reject(fileredr);fileredr.readAsArrayBuffer(file);});}

选中事件

        let isSelected = false;let counter = 0;// 填充文件类型下拉框for (let mimeTypeObj of mimeTypes) {let fileExt = mimeTypeObj['Extension'];let fileDescription = mimeTypeObj['Description'];let fileMimeType = mimeTypeObj['MIME_Types'][0];let conversionWorks = mimeTypeObj['Works'];let oOption = document.createElement('option');oOption.value = fileMimeType;oOption.text = `${fileDescription} [${fileExt}]`;if (!isSelected) {oOption.setAttribute('selected', true);MimeTypeDisplay.innerHTML = fileMimeType;FileExtDisplay.innerHTML = fileExt;MimeDescriptionDisplay.innerHTML = fileDescription;isSelected = true;}outputFileExtension.add(oOption, counter++);}// 延时处理,确保页面加载完成await new Promise((resolve, reject) => setTimeout(resolve, 50));// 文件类型下拉框选择变化事件outputFileExtension.addEventListener('change', async (e) => {let allOptions = e.currentTarget.options;let optionSelectedIndex = e.currentTarget.selectedIndex;let mimeType = allOptions[optionSelectedIndex].value;let fileExtStr = ((e.currentTarget.options[optionSelectedIndex].textContent).split('[')[1]);fileExtStr = fileExtStr.replaceAll(']', '');let mimeDescriptionStr = ((e.currentTarget.options[optionSelectedIndex].textContent).split('[')[0]);mimeDescriptionStr = mimeDescriptionStr.trim();MimeTypeDisplay.innerHTML = mimeType;FileExtDisplay.innerHTML = fileExtStr;MimeDescriptionDisplay.innerHTML = mimeDescriptionStr;});// HTML5 兼容的媒体类型const HTML5MediaTypes = {'.mp4': true,'.mp3': true,'.wav': true,'.ogg': true};const mediaWrapper = document.getElementById('mediaWrapper');const displayedHeightVal = 150;// 加载媒体文件const loadMedia = (url, type) => new Promise((resolve, reject) => {var mediaObj = document.createElement(type);mediaObj.addEventListener('canplay', () => resolve(mediaObj));mediaObj.addEventListener('error', (err) => reject(err));mediaObj.src = url;});// 渲染处理后的输出async function renderProcessedOutput(encodedData, mediaType, outputFileExt) {if (typeof HTML5MediaTypes[outputFileExt.toLowerCase()] !== 'undefined') {try {let loadedMediaObj = await loadMedia(encodedData, mediaType);loadedMediaObj.setAttribute('controls', '');await new Promise((resolve, reject) => setTimeout(resolve, 50));if (mediaType == 'video') {let mediaObjHeight = loadedMediaObj.videoHeight;let mediaObjWidth = loadedMediaObj.videoWidth;let scaleRatio = parseFloat(displayedHeightVal / mediaObjHeight);let displayedHeight = scaleRatio * mediaObjHeight;let displayedWidth = scaleRatio * mediaObjWidth;loadedMediaObj['style']['height'] = `${displayedHeight}px`;loadedMediaObj['style']['width'] = `${displayedWidth}px`;loadedMediaObj['style']['margin'] = '0 auto';await new Promise((resolve, reject) => setTimeout(resolve, 50));}mediaWrapper.appendChild(loadedMediaObj);} catch (errMsg) {console.error(errMsg);}} else {let fillerDIV = document.createElement('div');fillerDIV.className = 'border';fillerDIV['style']['height'] = `${displayedHeightVal}px`;fillerDIV['style']['width'] = `${displayedHeightVal}px`;fillerDIV['style']['margin'] = '0 auto';fillerDIV.innerHTML = 'Content is not HTML5 compatible for display.';mediaWrapper.appendChild(fillerDIV);}return Promise.resolve('Conversion Success!');}

上传文件并转换函数(关键运算)

        // 上传文件改变事件uploadMedia.addEventListener('change', async (evt) => {outputFileExtension.disabled = true;uploadMediaBtn.disabled = true;const outputFileMimeType = MimeTypeDisplay.innerHTML;const outputFileExt = FileExtDisplay.innerHTML;const file = evt.target.files[0];if (!file) return;let fileName = file.name;let fileType = file.type;let fileSizeInKB = parseInt(file.size / 1024);let fileSizeInMB = ((file.size / 1024) / 1024).toFixed(2);fileNameDisplay.innerHTML = fileName;fileTypeDisplay.innerHTML = fileType;fileSizeDisplay.innerHTML = `${fileSizeInKB} <strong class="symbol">𝚔𝙱</strong> <span class="symbol">≈</span> ${fileSizeInMB} <strong class="symbol">𝙼𝙱</strong>`;// 使用指定路径创建 FFmpeg 实例appendDataLog('Initialising FFmpeg.');const ffmpeg = FFmpeg.createFFmpeg({corePath: new URL('js/ffmpeg/ffmpeg-core.js', document.location).href,workerPath: new URL('js/ffmpeg/ffmpeg-core.worker.js', document.location).href,wasmPath: new URL('js/ffmpeg/ffmpeg-core.wasm', document.location).href,log: true});await ffmpeg.load();appendDataLog('FFmpeg has loaded.');// 将文件读取为数组缓冲区,然后将数组缓冲区转换为 Uint8ArrayappendDataLog('Reading input file.');let arrBuffer = await readFileAsArrayBuffer(file);let uInt8Array = new Uint8Array(arrBuffer);appendDataLog('Writing to input file.');ffmpeg.FS('writeFile', fileName, uInt8Array);// https://emscripten.org/docs/api_reference/Filesystem-API.html            // https://segmentfault.com/a/1190000039308144            // ffmpeg.FS("writeFile",  "input.avi",  new Uint8Array(sourceBuffer, 0, sourceBuffer.byteLength));appendDataLog('Transcoding input file to output file.');await ffmpeg.run('-i', fileName, `output${outputFileExt}`);appendDataLog('Retrieving output file from virtual files system.');const data = ffmpeg.FS('readFile', `output${outputFileExt}`); // Uint8Array let b64Str = convertBitArrtoB64(data);let encodedData = `data:${outputFileMimeType};base64,${b64Str}`;appendDataLog('File conversion has been successfully completed.');saveOutputBtn.disabled = false;saveOutputBtn.value = encodedData;let mediaType = 'audio';if (!outputFileMimeType.includes(mediaType)) {mediaType = 'video';}let status = await renderProcessedOutput(encodedData, mediaType, outputFileExt);appendDataLog(status);ffmpeg.FS('unlink', `output${outputFileExt}`);await new Promise((resolve, reject) => setTimeout(resolve, 50));ffmpeg.exit();});

保存输出

        // 保存输出按钮点击事件saveOutputBtn.addEventListener('click', async () => {let dwnlnk = document.createElement('a');let fileName = fileNameDisplay.innerHTML;let outputFileExt = FileExtDisplay.innerHTML;let saveFilename = fileName.substr(0, fileName.lastIndexOf('.'));dwnlnk.download = `${saveFilename}${outputFileExt}`;dwnlnk.href = saveOutputBtn.value;dwnlnk.click();});

重置所有按钮点击事件

        // 重置所有按钮点击事件function resetAll() {if (mediaWrapper.children.length > 0) {mediaWrapper.removeChild(mediaWrapper.children[0]);}outputFileExtension.disabled = false;outputFileExtension.selectedIndex = 0;triggerEvent(outputFileExtension, 'change');uploadMediaBtn.disabled = false;uploadMedia.value = '';fileNameDisplay.innerHTML = '<span class="symbol">…</span>';fileTypeDisplay.innerHTML = '<span class="symbol">…</span>';fileSizeDisplay.innerHTML = '<span class="symbol">…</span>';outputLogs.innerHTML = '';saveOutputBtn.value = '';saveOutputBtn.disabled = true;}// 重置所有按钮点击事件绑定resetAllBtn.addEventListener('click', async () => {resetAll();});});
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/671955.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

由vscode自动升级导致的“终端可以ssh服务器,但是vscode无法连接服务器”

问题描述 简单来说就是&#xff0c;ssh配置没动&#xff0c;前两天还可以用vscode连接服务器&#xff0c;今天突然就连不上了&#xff0c;但是用本地终端ssh可以顺利连接。 连接情况 我的ssh配置如下&#xff1a; Host gpu3HostName aaaUser zwx现在直接在终端中进行ssh&am…

ElastAlert 错误日志告警

文章目录 前言一、ElastAlert 概览1.1 简介1.2 ElastAlert 特性 二、ElastAlert 下载部署2.1 安装 Python3 环境2.2 下载 ElastAlert2.3 部署 ElastAlert 三、接入平台3.1 对外接口层3.2 服务层 前言 ElastAlert 是 Yelp 公司基于 python 开发的 ELK 日志告警插件&#xff0c;…

Stata学习(1)

一、五大窗口 Command窗口&#xff1a;实现人机交互 来导入一个自带数据&#xff1a; sysuse是导入系统自带的数据&#xff0c;auto导入该数据的名称&#xff0c;后面的clear是清除之前的数据 结果窗口&#xff1a;展示计算结果、查找功能 在Edit的find可以实现查找功能&#…

Nacos安装,服务注册,负载均衡配置,权重配置以及环境隔离

1. 安装 首先从官网下载 nacos 安装包&#xff0c;注意是下载 nacos-server Nacos官网 | Nacos 官方社区 | Nacos 下载 | Nacos 下载完毕后&#xff0c;解压找到文件夹bin&#xff0c;文本打开startup.cmd 修改配置如下 然后双击 startup.cmd 启动 nacos服务&#xff0c;默认…

【经验分享】如何高效管理Jmeter的接口测试用例?

Test Fragment 测试片段元素是控制器上的一个种特殊的线程组&#xff0c;它在测试树上与线程组处于一个层级。它与线程组有所不同&#xff0c;它不被执行&#xff0c;当它是一个模块控制器或者是被控制器所引用时才会被执行 添加Test Fragment的步骤&#xff1a; 目前接口的组…

微信小程序的图片色彩分析,窃取主色调,调色板

1、在微信小程序中创建包管理器 package.json npm init -y 2、安装 Mini App Color Thief 包 npm i --save miniapp-color-thief 3、构建 npm 4、wxml <canvas canvas-id"myCanvas"></canvas> <button bindtap"chooseImage">chooseIm…

MATLAB语音去噪系统

目录 一、背景 二、GUI页面 三、程序 3.1 LMS滤波程序 3.2 GUI程序 四、附录 一、背景 本文介绍了一种最佳的自适应滤波器结构&#xff0c;该结构采用最小均方差&#xff08;LMS&#xff09;作为判据&#xff0c;通过不断迭代自适应结构来调整得到最佳滤波器…

前端常用代码整理(不断更新中)— js,jquery篇

1.随机函数代码 function getRandom(min, max) {return Math.floor(Math.random() * (max - min 1)) min}2.倒计时代码 let now new Date()// 2. 得到指定时间的时间戳let last new Date(这里写想要达到的时间)// 3. &#xff08;计算剩余的毫秒数&#xff09; / 1000 剩余…

【Linux】缓冲区与缓冲区的刷新策略

目录 1.缓冲区基础 1.1缓冲区的刷新策略 1.1.1三种刷新策略 1.1.2.两种强制刷新策略 2.用户级语言层缓冲区 2.1.默认在显示器输出 2.2.重定向到文件输出 2.3.write调用没有显示两份的原因 3.模拟实现文件缓冲区 3.1 myFileBuffer.h 3.2 myFileBuffer.c 4.系统内核缓…

Linux操作系统运维-Docker的基础知识梳理总结

Linux操作系统运维-Docker的基础知识梳理总结 docker用来解决不同开发人员软件调试时环境不统一的问题&#xff0c;保证了程序调试时运行环境的一致性。docker的设计理念便是一处镜像&#xff0c;处处运行&#xff0c;即通过产生用户软件&#xff0c;运行环境及其运行配置的统一…

回归预测 | Matlab实现ABC-BP人工蜂群算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现ABC-BP人工蜂群算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现ABC-BP人工蜂群算法优化BP神经网络多变量回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现ABC-BP人工蜂群算法优化BP神经网络多变量回归预测&#x…

Linux下centos操作系统安装Mysql8.0过程及踩坑填补

我自己有一台服务器&#xff0c;之前安装的是MySQL5.5&#xff0c;现在我想升级为MySQL8.0&#xff0c;于是我干了以下操作,既有踩坑又有干货&#xff1a; 1.先卸载MySQL&#xff1b; 2.删除跟MySQL相关文件&#xff1b; 3.安装新的MySQL8.0版本&#xff08;这里踩了一个坑&…

Spring- FactoryBean接口中的getObject()方法

目录 一、Spring框架介绍 二、FactoryBean接口是什么 三、getObject()方法如何使用 一、Spring框架介绍 Spring框架是一个轻量级的、非侵入式的Java企业级应用开发框架&#xff0c;以IoC&#xff08;控制反转&#xff09;和AOP&#xff08;面向切面编程&#xff09;为核心思…

springboot集成easypoi导出多sheet页

pom文件 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.1.0</version> </dependency> 导出模板&#xff1a; 后端代码示例&#xff1a; /*** 导出加油卡进便利店大额审批列…

云计算 - 弹性计算技术全解与实践

一、引言 在过去的十年里&#xff0c;云计算从一个前沿概念发展为企业和开发者的必备工具。传统的计算模型通常局限于单一的、物理的位置和有限的资源&#xff0c;而云计算则通过分布式的资源和服务&#xff0c;为计算能力带来了前所未有的"弹性"。 弹性&#xff1a;…

axios get 请求 url 转码 空格转成+,导致请求失败(前端解决)

问题 GET 请求参数&#xff1a; URL-encoded 后&#xff1a; 浏览器将空格转成了&#xff0c;导致服务报错&#xff0c;返回 400。 解决 在请求拦截器中&#xff0c;对 params 进行处理。 axios.interceptors.request.use((config) > {let url config.url;if (config…

ES监控方法以及核心指标

文章目录 1. 监控指标采集1.1 部署elasticsearch_exporter1.2 prometheus采集elasticsearch_exporter的暴露指标1.3 promethues配置告警规则或者配置grafana大盘 2. 核心告警指标2.1 es核心指标2.2 es容量模型建议 3. 参考文章 探讨es的监控数据采集方式以及需要关注的核心指标…

python-自动化篇-办公-一键将word中的表格提取到excel文件中

文章目录 代码 工作中&#xff0c;经常需要将Word文档中的表格粘贴到Excel文件中&#xff0c;以便汇总及分析。一个一个复制粘贴&#xff0c;非常不方便&#xff0c;还是Python自动化操作&#xff0c;省心省力。要求如下图所示&#xff0c;即将word中的所有表格&#xff0c;转存…

闲聊电脑(6)装个 Windows(二)

闲聊电脑&#xff08;6&#xff09;装个 Windows&#xff08;二&#xff09; 夜深人静&#xff0c;万籁俱寂&#xff0c;老郭趴在电脑桌上打盹&#xff0c;桌子上的小黄鸭和桌子旁的冰箱又开始窃窃私语…… 小黄鸭&#xff1a;冰箱大哥&#xff0c;上次说的镜像文件到底长啥样…

VMware虚拟机安装openEuler系统(一)(2024)

目录 一、下载ISO镜像 二、开始创建虚拟机 通过实践是学习openEuler开源Linux系统的最佳方式。因此我们首先得搭建一个openEuler实战环境&#xff0c;文章是在Windows系统上使用VMware Workstation虚拟化软件&#xff0c;安装和学习openEuler开源Linux操作系统。 使用虚拟机…