当前位置: 首页 > news >正文

关于前端文件处理-实战篇

1.前沿
总结工作遇到的处理文件的一些情况,简单入门文件
2.背景
某天阳光明媚的早上,app的同事问我:我这边有个上传图片的接口调不通,后端说你们前端接口调通了,那个前端input type=file 读取到的数据叫什么?
一脸懵逼的我心里想数据是叫什么?二进制流?blob?buffer?file对象?
通过回忆以前写过一篇关于二进制流、blob、buffer、File对象、Base64之间的转化,知道了答案是File对象
我:喂,那个app同事,读取到的数据格式叫File对象
app同事:好的,File对象??
3.扩展前端处理文件的场景
对于需要了解,基础的同学传送门,
这里扩展一下实战的api,FileReader对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

 // 以原始二进制方式读取,读取结果可直接转成整数数组
fileReader.readAsArrayBuffer(this.files[0]);

在这里插入图片描述
可以看到,它对前端开发人员也是透明的,不能够直接读取里面的内容,但可以通过ArrayBuffer.length得到长度,还能转成整型数组,就能知道文件的原始二进制内容了:

let buffer = this.result;
// 依次每字节8位读取,放到一个整数数组
let view = new Uint8Array(buffer);
console.log(view);

小磐,在这里提出两个问题
1.二进制在前端怎么使用?
2.二进制在前端怎么接受?

基本使用:

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>FileReader</title><!-- <link rel="stylesheet" type="text/css" href="style.css"> --></head><body><div class="container"><label for="select-input">选择要上传的关键词txt文件</label><input type="file" id="select-input" value="上传关键词2" /><div id="content"></div><img id="picture" src="" /></div><script>document.getElementById("select-input").addEventListener("change",(e) => {//获取选择的文件对象let file = e.target.files[0];// 检测浏览器对FileReader的支持if (window.FileReader) {// 创建FileReader对象(文件对象)const reader = new FileReader();/*----------    6种事件模型    ---------*/// 开始读取时:reader.onloadstart = function (e) {console.log("开始读取", e);};// 正在读取:reader.onprogress = function (e) {console.log("正在读取", e);};// 读取出错时:reader.onerror = function (e) {console.log("读取出错", e);};// 读取中断时:reader.onabort = function (e) {console.log("读取中断", e);};// 读取成功时:reader.onload = function (e) {keyList = e.target.result.split(",");console.log("读取成功", keyList);if (/image\/\w+/.test(file.type)) {document.getElementById("picture").src = e.target.result;} else {// 输出文件document.getElementById("content").innerText = e.target.result;}};// 读取完成,无论成功失败:reader.onloadend = function (e) {console.log("读取完成,无论成功失败", e);};/*-------  4种文件读功能(方法、函数)  ------*//*	reader.readAsText(file,"utf-8");reader.readAsBinaryString(file);  	// 将文件读取为二进制编码,buffer就是二进制reader.readAsDataURL(file);  		// 将文件读取为DataURLreader.readAsText(none);  			// 终端读取操作 			*/if (/image\/\w+/.test(file.type)) {reader.readAsDataURL(file);} else {// 输出文件reader.readAsBinaryString(file, "utf-8");}} else {alert("Not supported by your browser!");}},false);</script></body>
</html>

3.1下载文件流

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件流下载示例</title><!-- 引入axios库 --><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><style>body {font-family: Arial, sans-serif;display: flex;flex-direction: column;align-items: center;padding: 2rem;}.download-btn {padding: 0.8rem 1.5rem;background-color: #42b983;color: white;border: none;border-radius: 4px;cursor: pointer;font-size: 16px;transition: background-color 0.3s;}.download-btn:hover {background-color: #359e6d;}.status {margin-top: 1rem;color: #666;}</style>
</head>
<body><h2>文件下载示例</h2><button class="download-btn" id="downloadBtn">下载Excel文件</button><div class="status" id="status"></div><script>// 获取DOM元素const downloadBtn = document.getElementById('downloadBtn');const statusElement = document.getElementById('status');// 下载按钮点击事件downloadBtn.addEventListener('click', () => {downloadFile();});/*** 下载文件流的函数*/function downloadFile() {// 显示加载状态statusElement.textContent = '正在下载文件...';downloadBtn.disabled = true;// 模拟请求参数const param = {// 这里可以根据实际需求填写请求参数startDate: '2023-01-01',endDate: '2023-12-31',type: 'report'};// 实际接口地址,请替换为你的后端接口const _urls = 'https://example.com/api/export-excel';axios({method: "post",data: param,responseType: "blob",  // 关键:指定响应类型为bloburl: _urls,headers: {// 可以根据需要添加请求头,如认证信息等// 'Authorization': 'Bearer your_token_here'}}).then((response) => {// 注意:axios返回的响应对象中,数据在response.data里const blob = new Blob([response.data], { type: "application/vnd.ms-excel" });// 创建下载链接const link = document.createElement("a");const objectUrl = window.URL.createObjectURL(blob);link.href = objectUrl;// 设置下载文件名link.download = `数据报表_${new Date().toLocaleDateString().replace(/\//g, '-')}.xlsx`;// 触发下载document.body.appendChild(link);link.click();// 清理资源setTimeout(() => {document.body.removeChild(link);window.URL.revokeObjectURL(objectUrl);}, 100);// 更新状态statusElement.textContent = '文件下载成功!';}).catch((error) => {console.error('下载失败:', error);statusElement.textContent = '文件下载失败,请重试!';}).finally(() => {// 恢复按钮状态downloadBtn.disabled = false;});}</script>
</body>
</html>

3.2下载canvas为图片

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Canvas下载为图片示例</title><style>body {font-family: Arial, sans-serif;display: flex;flex-direction: column;align-items: center;padding: 2rem;gap: 1.5rem;}.container {border: 2px dashed #ccc;border-radius: 8px;padding: 1rem;}canvas {border: 1px solid #666;background-color: #f9f9f9;}.controls {display: flex;gap: 1rem;}button {padding: 0.6rem 1.2rem;border: none;border-radius: 4px;cursor: pointer;font-size: 14px;transition: all 0.3s;}.draw-btn {background-color: #4285f4;color: white;}.download-btn {background-color: #34a853;color: white;}button:hover {opacity: 0.9;transform: translateY(-2px);}.info {color: #666;font-size: 14px;}</style>
</head>
<body><h2>Canvas下载为图片示例</h2><div class="container"><!-- Canvas元素 --><canvas id="myCanvas" width="600" height="400"></canvas></div><div class="controls"><button class="draw-btn" id="drawBtn">在Canvas上绘制内容</button><button class="download-btn" id="downloadBtn">下载为图片</button></div><div class="info" id="info"></div><script>// 获取DOM元素const canvas = document.getElementById('myCanvas');const ctx = canvas.getContext('2d');const drawBtn = document.getElementById('drawBtn');const downloadBtn = document.getElementById('downloadBtn');const infoElement = document.getElementById('info');// 初始状态提示infoElement.textContent = '点击"在Canvas上绘制内容"按钮生成示例图形';// 绘制示例内容drawBtn.addEventListener('click', () => {// 清空Canvasctx.clearRect(0, 0, canvas.width, canvas.height);// 绘制背景ctx.fillStyle = '#f0f8ff';ctx.fillRect(0, 0, canvas.width, canvas.height);// 绘制矩形ctx.fillStyle = '#4285f4';ctx.fillRect(50, 50, 200, 150);// 绘制圆形ctx.fillStyle = '#ea4335';ctx.beginPath();ctx.arc(400, 125, 75, 0, Math.PI * 2);ctx.fill();// 绘制文本ctx.fillStyle = '#333';ctx.font = '24px Arial';ctx.textAlign = 'center';ctx.fillText('Canvas 示例', canvas.width / 2, 320);// 绘制线条ctx.strokeStyle = '#fbbc05';ctx.lineWidth = 5;ctx.beginPath();ctx.moveTo(100, 350);ctx.lineTo(500, 350);ctx.stroke();infoElement.textContent = '已绘制示例图形,可点击下载按钮保存为图片';});// 下载Canvas为图片downloadBtn.addEventListener('click', () => {try {// 将Canvas内容转换为图片URL (支持png/jpeg/webp等格式)// 注意:toDataURL方法受同源策略限制,且Canvas如果被污染(包含跨域图片)会报错const imageUrl = canvas.toDataURL('image/png'); // 也可以使用 'image/jpeg'// 创建下载链接const link = document.createElement('a');link.href = imageUrl;// 设置下载文件名(包含时间戳避免重复)const timestamp = new Date().getTime();link.download = `canvas-export-${timestamp}.png`;// 触发下载document.body.appendChild(link);link.click();// 清理document.body.removeChild(link);URL.revokeObjectURL(imageUrl);infoElement.textContent = '图片下载成功!';} catch (error) {console.error('下载失败:', error);infoElement.textContent = '下载失败:' + error.message;}});</script>
</body>
</html>

3.3获取视频的第一帧,并且转化为图片上传。

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>视频第一帧捕获示例</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 2rem auto;padding: 0 1rem;}.container {display: flex;flex-direction: column;gap: 1.5rem;}.preview-area {display: flex;gap: 1rem;flex-wrap: wrap;}.preview-box {flex: 1;min-width: 300px;}video, img {width: 100%;border: 1px solid #ddd;border-radius: 4px;max-height: 300px;object-fit: contain;}button {padding: 0.8rem 1.5rem;background-color: #4285f4;color: white;border: none;border-radius: 4px;cursor: pointer;font-size: 16px;}button:hover {background-color: #3367d6;}.status {padding: 1rem;border-radius: 4px;margin-top: 1rem;}.success {background-color: #e6f4ea;color: #137333;}.error {background-color: #fce8e6;color: #c5221f;}</style>
</head>
<body><div class="container"><h2>视频第一帧捕获与上传</h2><button id="captureBtn">获取视频第一帧</button><div class="preview-area"><div class="preview-box"><h3>视频预览</h3><div id="videoContainer"></div></div><div class="preview-box"><h3>第一帧图片</h3><div id="frameContainer"></div></div></div><div id="status" class="status"></div></div><script>const captureBtn = document.getElementById('captureBtn');const videoContainer = document.getElementById('videoContainer');const frameContainer = document.getElementById('frameContainer');const statusElement = document.getElementById('status');// 使用提供的视频地址const videoUrl = 'https://d7246990-e472-4105-aa90-f0d2a9f73dae.mdnplay.dev/shared-assets/videos/flower.webm';captureBtn.addEventListener('click', captureFirstFrame);function captureFirstFrame() {// 清空之前的内容videoContainer.innerHTML = '';frameContainer.innerHTML = '';statusElement.textContent = '';statusElement.className = 'status';try {// 创建视频元素const video = document.createElement('video');video.crossOrigin = 'anonymous'; // 处理跨域video.controls = true;video.autoplay = true;video.muted = true; // 静音以允许自动播放video.src = videoUrl;// 添加到页面预览videoContainer.appendChild(video);// 视频加载完成后捕获第一帧video.addEventListener('loadeddata', function() {statusElement.textContent = '视频加载完成,正在捕获第一帧...';statusElement.className = 'status';// 创建canvas绘制第一帧const canvas = document.createElement('canvas');canvas.width = video.videoWidth;canvas.height = video.videoHeight;const ctx = canvas.getContext('2d');ctx.drawImage(video, 0, 0, canvas.width, canvas.height);// 显示捕获的帧const frameImg = document.createElement('img');frameImg.src = canvas.toDataURL('image/png');frameContainer.appendChild(frameImg);// 转换为文件并模拟上传const fileName = `flower-frame-${new Date().getTime()}.png`;dataURLtoFile(canvas.toDataURL('image/png'), fileName);});// 视频加载错误处理video.addEventListener('error', function() {statusElement.textContent = '视频加载失败,请检查网络或视频地址';statusElement.className = 'status error';});} catch (error) {console.error('捕获失败:', error);statusElement.textContent = `捕获失败: ${error.message}`;statusElement.className = 'status error';}}// 将dataURL转换为File对象function dataURLtoFile(dataurl, filename) {try {const arr = dataurl.split(',');const mime = arr[0].match(/:(.*?);/)[1];const bstr = atob(arr[1]);const n = bstr.length;const u8arr = new Uint8Array(n);for (let i = 0; i < n; i++) {u8arr[i] = bstr.charCodeAt(i);}const file = new File([u8arr], filename, { type: mime });statusElement.textContent = '第一帧捕获成功,准备上传...';// 模拟上传过程simulateUpload(file);} catch (error) {console.error('转换失败:', error);statusElement.textContent = `转换失败: ${error.message}`;statusElement.className = 'status error';}}// 模拟文件上传function simulateUpload(file) {// 实际项目中这里会是真实的上传接口调用setTimeout(() => {statusElement.textContent = `上传成功!文件名: ${file.name},大小: ${(file.size/1024).toFixed(2)}KB`;statusElement.className = 'status success';}, 1500);}// 真实上传示例function uploadFile(file) {const formData = new FormData();formData.append('frame', file);fetch('/your-upload-api', {method: 'POST',body: formData}).then(response => response.json()).then(data => {statusElement.textContent = '上传成功,服务器已接收';statusElement.className = 'status success';}).catch(error => {statusElement.textContent = '上传失败: ' + error.message;statusElement.className = 'status error';});}</script>
</body>
</html>

解决获取时候的报错信息
3.3.1.toDataUrl方法画布转base64失败
在这里插入图片描述

视频节点未设置允许跨域,导致画布资源导出失败
video.setAttribute(‘crossOrigin’, ‘anonymous’);
3.3.2.资源跨域问题
在这里插入图片描述

这是资源问题,在要在资源那边设置,我更改为自己公司的oss地址视频就可以了

3.4vue项目后端接口返回文件流,接口报错时前端获取不到错误信息解决方法和文件流处理

exportFiles () { // 导出案卷包let params = {casesId: this.caseId,taskId: this.taskId	}downloadCases(params).then(res => {let data = res.datalet _self = thislet fileReader = new FileReader();fileReader.onload = function() {try {let jsonData = JSON.parse(this.result);  // 说明是普通对象数据,后台转换失败if (jsonData.code === 400) { // 接口返回的错误信息// alert(jsonData.msg)_self.$alarm.showWarning(jsonData.msg) // 弹出的提示信息}} catch (err) {   // 解析成对象失败,说明是正常的文件流const blob = new Blob([res.data], { type: 'application/zip' }) // 如类型为excel,type为:'application/vnd.ms-excel'const fileName = res.headers.filenameconst url = window.URL.createObjectURL(blob)const link = document.createElement('a')link.style.display = 'none'link.href = urllink.setAttribute('download', fileName)document.body.appendChild(link)link.click()document.body.removeChild(link) // 点击后移除,防止生成很多个隐藏a标签URL.revokeObjectURL(link.href);} };fileReader.readAsText(data)  // 注意别落掉此代码,可以将 Blob 或者 File 对象转根据特殊的编码格式转化为内容(字符串形式)}).catch(err => {console.log(err)})},

3.5ArrayBuffer操作二进制数组
参考:传送门
ArrayBuffer对象、TypedArray视图和DataView视图,它们都是以数组的语法处理二进制数据,所以统称为二进制数组。

这个接口的原始设计目的,与 WebGL 项目有关。所谓 WebGL,就是指浏览器与显卡之间的通信接口,为了满足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。

允许开发者以数组下标的形式,直接操作内存,大大增强了 JavaScript 处理二进制数据的能力,使得开发者有可能通过 JavaScript 与操作系统的原生接口进行二进制通信。比如:浏览器与显卡之间的通信接口

ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写,视图的作用是以指定格式解读二进制数据。

const buf = new ArrayBuffer(32);
const dataView = new DataView(buf);
dataView.getUint8(0) // 0

ArrayBuffer 与字符串的互相转换

/*** Convert ArrayBuffer/TypedArray to String via TextDecoder** @see https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder*/
function ab2str(input: ArrayBuffer | Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array,outputEncoding: string = 'utf8',
): string {const decoder = new TextDecoder(outputEncoding)return decoder.decode(input)
}/*** Convert String to ArrayBuffer via TextEncoder** @see https://developer.mozilla.org/zh-CN/docs/Web/API/TextEncoder*/
function str2ab(input: string): ArrayBuffer {const view = str2Uint8Array(input)return view.buffer
}/** Convert String to Uint8Array */
function str2Uint8Array(input: string): Uint8Array {const encoder = new TextEncoder()const view = encoder.encode(input)return view
}

3.6 base-64 转blob

function dataURLtoBlob(base64Buf: string): Blob {const arr = base64Buf.split(',');const typeItem = arr[0];const mime = typeItem.match(/:(.*?);/)![1];const bstr = atob(arr[1]);let n = bstr.length;const u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}return new Blob([u8arr], { type: mime });
} 

大概的意思就是:

  1. 用atob对base-64 编码的字符串进行解码
  2. 新增一个8 位无符号整型类数组,设置长度为解码后的字符长度
  3. charCodeAt获取字符对应的UTF-16 码元,并存进去Uint8Array

备注:Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0。
charCodeAt() 方法返回一个整数,表示给定索引处的 UTF-16 码元,其值介于 0 和 65535 之间。

3.7大文件分片下载:

// script
function downloadRange(url, start, end, i) {return new Promise((resolve, reject) => {const req = new XMLHttpRequest();req.open('GET', url, true);req.setRequestHeader('range', `bytes=${start}-${end}`);req.responseType = 'blob';req.onload = function (oEvent) {req.response.arrayBuffer().then((res) => {resolve({i,buffer: res,});});};req.send();});}// 合并bufferfunction concatenate(resultConstructor, arrays) {let totalLength = 0;for (let arr of arrays) {totalLength += arr.length;}let result = new resultConstructor(totalLength);let offset = 0;for (let arr of arrays) {result.set(arr, offset);offset += arr.length;}return result;}download2.onclick = () => {axios({url,method: 'head',}).then((res) => {// 获取长度来进行分割块console.time('并发下载');const size = Number(res.headers['content-length']);const length = parseInt(size / m);const arr = [];for (let i = 0; i < length; i++) {let start = i * m;let end = i == length - 1 ? size - 1 : (i + 1) * m - 1;arr.push(downloadRange(url, start, end, i));}Promise.all(arr).then((res) => {const arrBufferList = res.sort((item) => item.i - item.i).map((item) => new Uint8Array(item.buffer));const allBuffer = concatenate(Uint8Array, arrBufferList);const blob = new Blob([allBuffer], { type: 'image/jpeg' });const blobUrl = URL.createObjectURL(blob);const aTag = document.createElement('a');aTag.download = '360_0388.jpg';aTag.href = blobUrl;aTag.click();URL.revokeObjectURL(blobUrl);console.timeEnd('并发下载');});});};

3.82. 如何拼接两个音频文件
由以上整理的转换图得出途径
fetch请求音频资源 -> ArrayBuffer -> TypedArray -> 拼接成一个 TypedArray -> ArrayBuffer -> Blob -> Object URL

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>音频文件拼接示例</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 2rem auto;padding: 0 1rem;}.container {display: flex;flex-direction: column;gap: 1.5rem;}.input-group {margin-bottom: 1rem;}label {display: block;margin-bottom: 0.5rem;font-weight: bold;}input, button {width: 100%;padding: 0.8rem;border: 1px solid #ddd;border-radius: 4px;box-sizing: border-box;}button {background-color: #4285f4;color: white;border: none;cursor: pointer;font-size: 16px;transition: background-color 0.3s;}button:hover {background-color: #3367d6;}.audio-player {margin-top: 1rem;width: 100%;}.status {padding: 1rem;border-radius: 4px;margin-top: 1rem;}.success {background-color: #e6f4ea;color: #137333;}.error {background-color: #fce8e6;color: #c5221f;}.info {background-color: #e8f0fe;color: #1967d2;}</style>
</head>
<body><div class="container"><h2>音频文件拼接工具</h2><div class="input-group"><label for="audioUrl1">第一个音频URL</label><input type="text" id="audioUrl1" value="https://c9a115da-8327-437a-a080-470a5b029c4e.mdnplay.dev/shared-assets/audio/t-rex-roar.mp3" placeholder="输入第一个音频文件的URL"></div><div class="input-group"><label for="audioUrl2">第二个音频URL</label><input type="text" id="audioUrl2" value="https://c9a115da-8327-437a-a080-470a5b029c4e.mdnplay.dev/shared-assets/audio/t-rex-roar.mp3" placeholder="输入第二个音频文件的URL"></div><button id="concatBtn">拼接音频文件</button><div id="status" class="status info">请点击拼接按钮,将两个T-Rex roar音频拼接在一起</div><div id="originalAudios"><h3>原始音频(两个相同的音频)</h3><div><p>音频1:</p><audio id="originalAudio1" class="audio-player" controls></audio></div><div style="margin-top: 1rem;"><p>音频2:</p><audio id="originalAudio2" class="audio-player" controls></audio></div></div><div id="combinedAudioSection"><h3>拼接后的音频(时长应为原始的2倍)</h3><audio id="combinedAudio" class="audio-player" controls></audio><button id="downloadBtn" style="margin-top: 1rem; display: none;">下载拼接后的音频</button></div></div><script>// 获取DOM元素const audioUrl1Input = document.getElementById('audioUrl1');const audioUrl2Input = document.getElementById('audioUrl2');const concatBtn = document.getElementById('concatBtn');const statusElement = document.getElementById('status');const originalAudio1 = document.getElementById('originalAudio1');const originalAudio2 = document.getElementById('originalAudio2');const combinedAudio = document.getElementById('combinedAudio');const downloadBtn = document.getElementById('downloadBtn');let combinedBlobUrl = null;// 绑定拼接按钮事件concatBtn.addEventListener('click', concatAudioFiles);// 绑定下载按钮事件downloadBtn.addEventListener('click', downloadCombinedAudio);/*** 拼接两个音频文件*/async function concatAudioFiles() {const url1 = audioUrl1Input.value.trim();const url2 = audioUrl2Input.value.trim();// 验证输入if (!url1 || !url2) {showStatus('请输入两个有效的音频URL', 'error');return;}showStatus('正在加载音频文件...', 'info');try {// 1. Fetch请求获取音频资源,转换为ArrayBuffer// 对同一个URL使用两次fetch确保获取完整数据const [buffer1, buffer2] = await Promise.all([fetchAudioAsArrayBuffer(url1),fetchAudioAsArrayBuffer(url2)]);// 显示原始音频originalAudio1.src = url1;originalAudio2.src = url2;// 2. 将ArrayBuffer转换为TypedArray (Uint8Array)const audio1 = new Uint8Array(buffer1);const audio2 = new Uint8Array(buffer2);showStatus('正在拼接音频文件...', 'info');// 3. 拼接两个TypedArrayconst combined = concatenateTypedArrays(audio1, audio2);// 4. 将拼接后的TypedArray转换回ArrayBufferconst combinedBuffer = combined.buffer;// 5. 创建Blob对象const mimeType = 'audio/mpeg'; // 此URL的音频为MP3格式const combinedBlob = new Blob([combinedBuffer], { type: mimeType });// 6. 创建Object URLcombinedBlobUrl = URL.createObjectURL(combinedBlob);// 设置拼接后音频的源combinedAudio.src = combinedBlobUrl;showStatus('音频文件拼接成功!拼接后的音频时长应为原始的2倍', 'success');downloadBtn.style.display = 'block';} catch (error) {console.error('音频拼接失败:', error);showStatus(`拼接失败: ${error.message}`, 'error');}}/*** 使用fetch获取音频并转换为ArrayBuffer*/async function fetchAudioAsArrayBuffer(url) {try {const response = await fetch(url);if (!response.ok) {throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);}return response.arrayBuffer();} catch (error) {throw new Error(`无法加载音频: ${error.message}`);}}/*** 拼接两个TypedArray (Uint8Array)*/function concatenateTypedArrays(a, b) {const result = new Uint8Array(a.length + b.length);result.set(a, 0);         // 将第一个数组放在开始位置result.set(b, a.length);  // 将第二个数组放在第一个数组后面return result;}/*** 下载拼接后的音频*/function downloadCombinedAudio() {if (!combinedBlobUrl) return;const a = document.createElement('a');a.href = combinedBlobUrl;a.download = 'combined-t-rex-roar.mp3';document.body.appendChild(a);a.click();document.body.removeChild(a);}/*** 显示状态信息*/function showStatus(message, type = 'info') {statusElement.textContent = message;statusElement.className = 'status';statusElement.classList.add(type);}// 页面卸载时清理资源window.addEventListener('beforeunload', () => {if (combinedBlobUrl) {URL.revokeObjectURL(combinedBlobUrl);}});</script>
</body>
</html>
http://www.dtcms.com/a/540208.html

相关文章:

  • <script setup lang=“ts“>+uniapp实现轮播(swiper)效果
  • 网站建设与设计方案现在什么网页游戏最好玩最火
  • SOME/IP 序列化问题
  • 【Rust编程:从新手到大师】 Rust 所有权与内存安全
  • wordpress如何添加背景音乐seo点评类网站
  • Flink Keyed State 详解之二
  • AI IN ALL王炸霸屏|战神数科与腾讯字节等深度践行AI
  • 【技术干货】在Stimulsoft中使用Google Sheets作为数据源创建报表与仪表盘
  • PCIe协议之唤醒篇之 WAKE# 信号
  • 搜狗做网站怎么样做静态网站有什么用
  • 潍坊网站建设公司哪家好大庆+网站建设
  • 推理成本吞噬AI未来,云计算如何平衡速度与成本的难题?
  • 基于VaR模型的ETF日内动态止损策略实现与理论验证
  • Linux云计算基础篇(28)-Samba文件服务
  • 学习经验分享【42】数学建模大赛参赛经历
  • 5.3 大数据方法论与实践指南-存储成本优化(省钱)
  • 运营商网站服务密码搜索引擎优化seo信息
  • 【案例实战】鸿蒙元服务开发实战:从云原生到移动端,包大小压缩 96% 启动提速 75% 的轻量化设计
  • 网站开发人员介绍网络营销研究现状文献综述
  • html5制作网站一个网站建立团队大概要多少钱
  • AceContainer类中用于初始化任务执行系统的核心方法--AceContainer::InitializeTask
  • Ubuntu部署 Kubernetes1.23
  • 悟空 AI CRM 的回访功能:深化客户关系,驱动业务增长
  • Qt的.pro文件中INSTALLS的作用和用法
  • 我的项目该选LoRa还是RF超短波全数字加密传输?
  • vue3 实现记事本手机版01
  • 03_全连接神经网络
  • 生成式AI重塑教学生态:理论基础、核心特征与伦理边界
  • html5手机网站调用微信分享wordpress缩略图加载慢
  • 动环监控:数据中心机房的“智慧守护者”