WebAssembly002 FFmpegWasmLocalServer项目

这篇具有很好参考价值的文章主要介绍了WebAssembly002 FFmpegWasmLocalServer项目。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

项目介绍

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

运行效果

WebAssembly002 FFmpegWasmLocalServer项目,硬件和移动端,笔记

WebAssembly002 FFmpegWasmLocalServer项目,硬件和移动端,笔记

WebAssembly002 FFmpegWasmLocalServer项目,硬件和移动端,笔记文章来源地址https://www.toymoban.com/news/detail-833740.html

相关依赖

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 changelog

added 70 packages, and audited 71 packages in 2m

7 packages are looking for funding
  run `npm fund` for details

found 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) {
  // 设置响应头,启用 SharedBuffer
  res.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.');
			// 将文件读取为数组缓冲区,然后将数组缓冲区转换为 Uint8Array
            appendDataLog('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项目的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ROS小车研究笔记1/31/2023 小车硬件结构及键盘移动控制节点

    1 小车硬件结构 1 中控设备 上方的单片机用于控制电机运动,搭载wifi模块和电量显示屏。下方为树莓派,安装了ROS系统和Ubuntu系统,用于整个小车控制。显示屏和树莓派相连 2 传感器系统 激光雷达及转换器。激光雷达和转换器相连,再由转换器连接树莓派以控制激光雷达 摄

    2024年02月09日
    浏览(65)
  • Debain11 + Qt 6.5.2 WebAssembly环境搭建笔记

    1,安装Qt 6.5.2 WebAssembly 我使用的MaintenanceTool安装Qt的,下载地址Download Qt OSS: Get Qt Online Installer. 在安装时勾选WebAssembly。 2,安装WebAssembly开发环境依赖。 sudo apt install llvm clang nodejs (这是我这边需要安装的,不同的环境可能需要安装的不同)。 3,Qt for WebAssembly,依赖 https://github.c

    2024年02月16日
    浏览(36)
  • webassembly003 whisper.cpp的项目结构CMakeLists.txt

    注:带星号的为非重要部分 POSIX:可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ) Core ML 是一个 Apple 框架,允许开发人员轻松集成 机器学习 (ML) 模型到应用中。Core ML 可在 iOS、iPadOS、 watchOS、macOS 和 Apple tvOS。Core ML 引入了公共文件格式 (.mlmodel)

    2024年01月18日
    浏览(52)
  • Emscripten + CMakeLists.txt 将 C++ 项目编译成 WebAssembly(.wasm)/js,并编译 Html 测试

    背景:Web 端需要使用已有的 C++ 库(使用 CMake 编译),需要将 C++ 项目编译成 WebAssembly(.wasm) 供 js 调用。 上篇文章《Mac 上安装 Emscripten》 已讲解如何安装配置 Emscripten 环境。 本篇文章主要讲解如何将基于 CMakeLists 配置的 C++ 项目编译成 WebAssembly 库来供 Web 前端使用。编译结

    2024年02月06日
    浏览(36)
  • ToBeWritten之IoT Web、移动应用、设备硬件、无线电通信、IoV威胁建模

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 转移发布平台通知:将不再在CSDN博客发布新文章,敬请移步知识星球 感谢大家一直以来对我CSDN博客的关注和支持,但

    2024年02月01日
    浏览(45)
  • 基于X86六轮差速移动机器人运动控制器设计与实现(一)软件与硬件架构

    本文研究的六轮差速移动机器人 (Six-Wheeled Differential Mobile Robot , SWDMR) 为了满足资源站到资源站点对点的物资运输,对机器人的跨越障碍能力 有较高的要求。对比传统的四轮移动机器人,六轮移动机器人能够提供更强的驱动 力,而且六轮与四轮相比整车分散到单个车轮的负

    2024年02月12日
    浏览(56)
  • 【硬件设计】硬件学习笔记一--元器件的介绍与选型

    写在前面:本篇笔记来自王工的硬件工程师培训课程,想要学硬件的同学可以去腾讯课堂直接搜索,以下是我对知识点的总结归纳,硬件的学习还是建议大家多去看元器件手册,多动手实操。 1.1 电阻的分类 常用贴片电阻有三种基本类型:金属膜电阻、薄膜贴片电阻及厚膜贴片

    2024年02月11日
    浏览(44)
  • 【硬件学习笔记】防反接保护电路

    原理:输入接法正确时,电路正常运行;输入反接时,因二极管的存在会阻碍电流形成回路,从而达到断路的效果,避免损坏后级器件。 优点:简洁方便,元器件少,效果强劲; 不足:二极管有压降,当输入电流较大时,会有很大的损耗,例如:流过5A电流,损耗=0.55V✖5A

    2023年04月09日
    浏览(38)
  • 硬件学习笔记(器件篇)—— 电感(四)

    电感电流是电感选型中最重要的参数之一。在阅读不同厂家的电感手册时,我们会发现,各个厂家的标注方式各不相同,有的厂家手册只有一个额定电流。有的厂家会标注两个,一个是饱和电流,一个是温升电流。有的厂家会标注饱和电流和温升电流的典型值和最大值。 饱和

    2024年02月13日
    浏览(35)
  • 硬件学习笔记(器件篇)—— 电感(六)

    我们在使用电感的时候经常会遇到电感发烫的问题,这就涉及到电感的损耗。 电感的损耗有哪些? 电感的损耗如何计算? 电感的损耗分为两大部分,一部分为线圈损耗,一部分为磁芯损耗。 DCR DCR就是指电感线圈的直流电阻,这个参数一般厂家会在规格书中给出,DCR是电感

    2024年02月03日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包