WebAssembly002 FFmpegWasmLocalServer项目

2024-02-04 15:04

本文主要是介绍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();});});
}

这篇关于WebAssembly002 FFmpegWasmLocalServer项目的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/677904

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧

SpringBoot项目是如何启动

启动步骤 概念 运行main方法,初始化SpringApplication 从spring.factories读取listener ApplicationContentInitializer运行run方法读取环境变量,配置信息创建SpringApplication上下文预初始化上下文,将启动类作为配置类进行读取调用 refresh 加载 IOC容器,加载所有的自动配置类,创建容器在这个过程

Maven创建项目中的groupId, artifactId, 和 version的意思

文章目录 groupIdartifactIdversionname groupId 定义:groupId 是 Maven 项目坐标的第一个部分,它通常表示项目的组织或公司的域名反转写法。例如,如果你为公司 example.com 开发软件,groupId 可能是 com.example。作用:groupId 被用来组织和分组相关的 Maven artifacts,这样可以避免

2. 下载rknn-toolkit2项目

官网链接: https://github.com/airockchip/rknn-toolkit2 安装好git:[[1. Git的安装]] 下载项目: git clone https://github.com/airockchip/rknn-toolkit2.git 或者直接去github下载压缩文件,解压即可。

9.8javaweb项目总结

1.主界面用户信息显示 登录成功后,将用户信息存储在记录在 localStorage中,然后进入界面之前通过js来渲染主界面 存储用户信息 将用户信息渲染在主界面上,并且头像设置跳转,到个人资料界面 这里数据库中还没有设置相关信息 2.模糊查找 检测输入框是否有变更,有的话调用方法,进行查找 发送检测请求,然后接收的时候设置最多显示四个类似的搜索结果

maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令

maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令 在日常的工作中由于各种原因,会出现这样一种情况,某些项目并没有打包至mvnrepository。如果采用原始直接打包放到lib目录的方式进行处理,便对项目的管理带来一些不必要的麻烦。例如版本升级后需要重新打包并,替换原有jar包等等一些额外的工作量和麻烦。为了避免这些不必要的麻烦,通常我们

html css jquery选项卡 代码练习小项目

在学习 html 和 css jquery 结合使用的时候 做好是能尝试做一些简单的小功能,来提高自己的 逻辑能力,熟悉代码的编写语法 下面分享一段代码 使用html css jquery选项卡 代码练习 <div class="box"><dl class="tab"><dd class="active">手机</dd><dd>家电</dd><dd>服装</dd><dd>数码</dd><dd