เนื่องจากความต้องการของโครงการ เราจึงจำเป็นต้องใช้ฟังก์ชันการบันทึกบนเว็บ ในตอนแรก พบวิธีแก้ปัญหาสองวิธี วิธีหนึ่งคือผ่าน iframe และอีกวิธีคือ getUserMedia api ของ html5 เนื่องจากฟังก์ชันการบันทึกของเราไม่จำเป็นต้องเข้ากันได้กับเบราว์เซอร์ IE เราจึงไม่ลังเลที่จะเลือก getUserMedia ที่จัดทำโดย html5 เพื่อใช้งาน แนวคิดพื้นฐานคือการรวมเข้ากับเอกสาร API อย่างเป็นทางการและโซลูชันบางอย่างที่พบทางออนไลน์เพื่อสร้างโซลูชันที่เหมาะสมกับความต้องการของโครงการ แต่เนื่องจากเราต้องแน่ใจว่าฟังก์ชั่นการบันทึกสามารถเปิดได้ทั้งบนแพดและพีซีในเวลาเดียวกัน จึงมีข้อผิดพลาดบางประการเช่นกัน ต่อไปนี้คือการฟื้นฟูกระบวนการ
ขั้นตอนที่ 1เนื่องจาก API ใหม่ผ่าน navigator.mediaDevices.getUserMedia และส่งคืนสัญญา
API เก่าคือ navigator.getUserMedia ดังนั้นจึงมีความเข้ากันได้ รหัสมีดังนี้:
// เบราว์เซอร์เก่าอาจไม่ติดตั้ง mediaDevices เลย ดังนั้นเราสามารถตั้งค่าอ็อบเจ็กต์ว่างก่อนได้หาก (navigator.mediaDevices === undefinition) { navigator.mediaDevices = {};}// เบราว์เซอร์บางตัวรองรับ mediaDevices บางส่วน เราไม่สามารถตั้งค่า getUserMedia// บนออบเจ็กต์ได้โดยตรง เนื่องจากอาจเขียนทับคุณสมบัติที่มีอยู่ ที่นี่เราจะเพิ่มแอตทริบิวต์ getUserMedia หากไม่มีอยู่เท่านั้น if (navigator.mediaDevices.getUserMedia === ไม่ได้กำหนด) { ให้ getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||. navigator.mozGetUserMedia ||. ขั้นแรก หากมี getUserMedia ให้รับมัน // เบราว์เซอร์บางตัวไม่ได้ใช้งานเลย - จากนั้นส่งคืนข้อผิดพลาดไปยังการปฏิเสธของสัญญาเพื่อรักษาอินเทอร์เฟซแบบรวมถ้า (!getUserMedia) { return Promise.reject(new Error(' getUserMedia is ไม่ได้นำมาใช้ในเบราว์เซอร์นี้')); } // มิฉะนั้นให้ห่อสัญญาสำหรับเมธอด navigator.getUserMedia เก่าส่งคืนสัญญาใหม่ (ฟังก์ชั่น (แก้ไข ปฏิเสธ) { getUserMedia.call(เนวิเกเตอร์, ข้อ จำกัด, แก้ไข, ปฏิเสธ });ขั้นตอนที่ 2
นี่เป็นวิธีการที่มีอยู่ในอินเทอร์เน็ต โดยห่อหุ้ม HZRecorder โดยพื้นฐานแล้วอ้างอิงวิธีนี้ อินเทอร์เฟซการบันทึกสามารถเรียกใช้ได้โดยการเรียก HZRecorder.get เมธอดนี้จะส่งผ่านในฟังก์ชันการโทรกลับ หลังจาก HZRecorder ใหม่ ฟังก์ชันการโทรกลับจะถูกดำเนินการและส่งผ่านออบเจ็กต์ HZRecorder ที่เป็นรูปธรรม ฟังก์ชั่นต่างๆ เช่น การเริ่มต้นการบันทึก การหยุดชั่วคราว การหยุด และการเล่น สามารถนำไปใช้ได้ผ่านวิธีการของวัตถุนี้
var HZRecorder = ฟังก์ชั่น (สตรีม config) { config = config ||. {}; config.sampleBits ||. ; // อัตราการสุ่มตัวอย่าง (1/6 44100) // สร้างวัตถุสภาพแวดล้อมเสียง audioContext = window.AudioContext ||. window.webkitAudioContext; var context = new audioContext(); // ป้อนข้อมูลเสียงลงในวัตถุนี้ var audioInput = context.createMediaStreamSource(stream); // ตั้งค่าระดับเสียงของโหนด var = context.createGain(); .connect(volume); //สร้างแคชเพื่อแคชเสียง var bufferSize = 4096; //สร้างโหนดแคชเสียง, วิธี createScriptProcessor // พารามิเตอร์ตัวที่สองและสามอ้างอิงถึงทั้งอินพุตและเอาท์พุตแบบสองทาง var recorder = context.createScriptProcessor (bufferSize, 2, 2); var audioData = { ขนาด: 0 // การบันทึกความยาวไฟล์, บัฟเฟอร์: [] // การบันทึกแคช, inputSampleRate: context.sampleRate // อัตราการสุ่มตัวอย่างอินพุต, inputSampleBits: 16 //ป้อนตัวเลขการสุ่มตัวอย่าง 8, 16, outputSampleRate: config.sampleRate //อัตราการสุ่มตัวอย่างเอาต์พุต, oututSampleBits: config.sampleBits //บิตตัวอย่างเอาต์พุต 8, 16, อินพุต: ฟังก์ชั่น (ข้อมูล) { this.buffer.push(new Float32Array(data)); this.size += data.length }, บีบอัด: function () { / /ผสานการบีบอัด//ผสาน var data = new Float32Array(this.size); var offset = 0; for (var i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset); offset += this.buffer[i].length; } //การบีบอัด var การบีบอัด = parseInt(this.inputSampleRate / this.outputSampleRate); / การบีบอัด; var result = new Float32Array(length); var index = 0, j = 0; while (index < length) { result[index] = data[j]; index++; } ส่งคืนผลลัพธ์; } , encodeWAV: function () { var SampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); var SampleBits = Math.min(this.inputSampleBits, this.oututSampleBits); .compress(); var dataLength = bytes.length * (sampleBits / 8); var buffer = ใหม่ ArrayBuffer (44 + dataLength); var data = ใหม่ DataView (buffer); var channelCount = 1; // Mono var offset = 0; i++) { data.setUint8(offset + i, str.charCodeAt(i)); // ตัวระบุไฟล์แลกเปลี่ยนทรัพยากร writeString('RIFF'); offset += 4; // จำนวนไบต์ทั้งหมดจากที่อยู่ถัดไปจนถึงจุดสิ้นสุดของไฟล์ นั่นคือขนาดไฟล์ -8 data.setUint32(offset, 36 + dataLength, true); /WAV file flag writeString('WAVE'); offset += 4; // ตั้งค่าสถานะรูปคลื่น writeString('fmt'); offset += 4; // กรองไบต์, โดยทั่วไป 0x10 = 16 data.setUint32(offset, 16, true); offset += 4; // รูปแบบหมวดหมู่ (ข้อมูลการสุ่มตัวอย่างรูปแบบ PCM) data.setUint16 (offset, 1, true); // จำนวนช่องสัญญาณ data.setUint16 ( offset, channelCount, true); offset += 2; // อัตราการสุ่มตัวอย่าง, จำนวนตัวอย่างต่อวินาที, แสดงถึงความเร็วการเล่นของแต่ละช่อง data.setUint32 (offset, SampleRate, จริง); offset += 4; // อัตราการถ่ายโอนข้อมูลรูปคลื่น (ไบต์เฉลี่ยต่อวินาที) โมโน × บิตข้อมูลต่อวินาที × บิตข้อมูลต่อตัวอย่าง/8 data.setUint32(offset, channelCount * SampleRate * (sampleBits / 8), จริง ); offset += 4; // จำนวนไบต์ที่ถูกครอบครองโดยการสุ่มตัวอย่างตัวเลขการปรับข้อมูลอย่างรวดเร็วในครั้งเดียวคือโมโน × จำนวนบิตข้อมูลต่อตัวอย่าง/8 data.setUint16(offset, channelCount * (sampleBits / 8), จริง); // จำนวนบิตข้อมูลต่อตัวอย่าง data.setUint16 (offset, exampleBits, จริง); // ตัวระบุข้อมูล writeString ('data'); += 4; // จำนวนข้อมูลตัวอย่างทั้งหมด นั่นคือ ขนาดข้อมูลทั้งหมด -44 data.setUint32(offset, dataLength, true); offset += 4; // เขียนข้อมูลตัวอย่าง if (sampleBits === 8) { สำหรับ (var i = 0; i < bytes.length; i++, offset++) { var s = Math.max(-1, Math.min(1, bytes[i])); s < 0 ? s * 0x8000 : s * 0x7FFF; val = parseInt(255 / (65535 / (วาล + 32768))); data.setInt8(offset, val, true); } } else { สำหรับ (var i = 0; i < bytes.length; i++, offset += 2) { var s = Math.max(-1 , Math.min(1, ไบต์[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, จริง); } } ส่งคืน Blob ใหม่ ([data], { type: 'audio/wav' }); // เริ่มการบันทึก this.start = function () { audioInput.connect (recorder. เชื่อมต่อ (context.destination); }; //Stop this.stop = function () { recorder.disconnect(); }; //End this.end = function() { context.close(); }; //ดำเนินการต่อ this.again = function() { recorder.connect(context.destination); }; // รับไฟล์เสียง this.getBlob = function () { this.stop(); ; }; //เล่น this.play = ฟังก์ชั่น (เสียง) { audio.src = window.URL.createObjectURL(this.getBlob()); }; { var fd = new FormData(); fd.append('audioData', this.getBlob()); var xhr = new XMLHttpRequest(); { xhr.upload.addEventListener('progress', ฟังก์ชัน (เช่น ) { โทรกลับ ('อัปโหลด', e }, false); xhr.addEventListener('load', function (e) { โทรกลับ ('ตกลง', e); }, false); xhr.addEventListener('error', function (e) { callback('error', e); }, false); (จ) { โทรกลับ('ยกเลิก', e); } xhr.open('POST', url); xhr.send(fd) //Audio collection recorder.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); //record(e.inputBuffer.getChannelData(0)); //โยนข้อยกเว้น .throwError = function (ข้อความ) { โยนฟังก์ชันใหม่ () { this.toString = function () { ข้อความส่งคืน };}; // ไม่ว่าจะรองรับการบันทึก HZRecorder.canRecording = (navigator.getUserMedia != null); // รับฟังก์ชัน HZRecorder.get = (โทรกลับ, config) { ถ้า (โทรกลับ) { navigator.mediaDevices .getUserMedia ({ เสียง: จริง }) .then(function(stream) { la rec = new HZRecorder(stream, config); callback(rec); }) .catch(function(error) { HZRecorder.throwError('ไม่สามารถบันทึกได้ โปรดตรวจสอบสถานะอุปกรณ์'); }); window.HZRecorder = HZRecorder;
สิ่งที่กล่าวมาข้างต้นสามารถตอบสนองความต้องการส่วนใหญ่ได้แล้ว แต่เราต้องเข้ากันได้กับแผ่นรองด้านข้าง เรามีปัญหาหลายประการกับแพดของเราที่ต้องได้รับการแก้ไข
ด้านล่างนี้เป็นวิธีแก้ไขปัญหาทั้งสองอย่าง
ขั้นตอนที่ 3ต่อไปนี้เป็นวิธีแก้ไขปัญหาสำหรับฉันในการใช้รูปแบบการบันทึกเป็น mp3 และ window.URL.createObjectURL เพื่อส่งผ่านข้อมูล blob และรายงานข้อผิดพลาดที่ด้านข้างของแผ่น
1. แก้ไขโค้ดออบเจ็กต์ audioData ใน HZRecorder และแนะนำไฟล์ js lamejs.js จากบุคคลสำคัญทางอินเตอร์เน็ต
const lame = new lamejs();let audioData = { exampleMono: null, maxSamples: 1152, mp3Encoder: new lame.Mp3Encoder(1, context.sampleRate || 44100, config.bitRate || 128), dataBuffer: [], ขนาด : 0, // บัฟเฟอร์ความยาวไฟล์บันทึก: [], // บัฟเฟอร์การบันทึก inputSampleRate: context.sampleRate, // อัตราการสุ่มตัวอย่างอินพุต inputSampleBits: 16, // อินพุตตัวอย่างหลัก 8, 16 outputSampleRate: config.sampleRate, // อัตราตัวอย่างเอาต์พุต outputSampleBits: config.sampleBits, // เอาต์พุตตัวอย่างหลัก 8, 16 ConvertBuffer: function(arrayBuffer) { ให้ data = new Float32Array(arrayBuffer); ให้ออก = ใหม่ Int16Array(arrayBuffer.length); this.floatTo16BitPCM(data, out); }, floatTo16BitPCM: function(input, output) { for (let i = 0; i < input. ความยาว; i++) { ให้ s = Math.max(-1, Math.min(1, input[i])); = s < 0 ? s * 0x8000 : s * 0x7fff; } }, appendToBuffer: function(mp3Buf) { this.dataBuffer.push(new Int8Array(mp3Buf) }, เข้ารหัส: function(arrayBuffer) { this.samplesMono = this .convertBuffer(arrayBuffer); ให้เหลือ = this.samplesMono.length; (ให้ i = 0; เหลือ >= 0; i += this.maxSamples) { ให้เหลือ = this.samplesMono.subarray(i, i + this.maxSamples); ให้ mp3buf = this.mp3Encoder.encodeBuffer(left); .appendToBuffer(mp3buf); เหลือ -= this.maxSamples; } } เสร็จสิ้น: function() { this.appendToBuffer(this.mp3Encoder.flush()); ส่งคืน Blob ใหม่ (this.dataBuffer, { ประเภท: 'audio/mp3' }); อินพุต: ฟังก์ชั่น (ข้อมูล) { this.buffer.push(new Float32Array( data)); this.size += data.length; }, บีบอัด: function() { // ผสานการบีบอัด // ผสานข้อมูล = ใหม่ Float32Array(this.size); ให้ offset = 0; for (let i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset); [i].length; } // การบีบอัด ให้การบีบอัด = parseInt(this.inputSampleRate / this.outputSampleRate, 10); ให้ length = data.length / การบีบอัด; Float32Array (ความยาว) ให้ดัชนี = 0; ให้ j = 0; ในขณะที่ (ดัชนี <ความยาว) { ผล [ดัชนี] = ข้อมูล [j]; j += ดัชนี } }, ฟังก์ชัน () { ให้ SampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); ให้ SampleBits = Math.min(this.inputSampleBits, this.oututSampleBits); ให้ bytes = this.compress(); ให้ dataLength = bytes.length * (sampleBits / 8); ให้ buffer = new ArrayBuffer(44 + dataLength); 1; //mono ให้ offset = 0; ให้ writeString = function(str) { สำหรับ (ให้ i = 0; i < str.length; i++) { data.setUint8(offset + i, str.charCodeAt(i)); // ตัวระบุไฟล์แลกเปลี่ยนทรัพยากร ('RIFF'); // ไบต์ทั้งหมดจากที่อยู่ถัดไปถึงจุดสิ้นสุดของ หมายเลขไฟล์ นั่นคือขนาดไฟล์ - 8 data.setUint32(offset, 36 + dataLength, true); offset += 4; // การตั้งค่าสถานะไฟล์ WAV ('WAVE'); // รูปแบบคลื่น writeString('fmt '); // กรองไบต์ โดยทั่วไป 0x10 = 16 data.setUint32(offset, 16, true); // หมวดหมู่รูปแบบ (การสุ่มตัวอย่างแบบฟอร์ม PCM ข้อมูล) data.setUint16 (ออฟเซ็ต, 1, จริง); // หมายเลขช่อง data.setUint16 (ออฟเซ็ต, channelCount, จริง); += 2; // อัตราการสุ่มตัวอย่าง, จำนวนตัวอย่างต่อวินาที, ระบุความเร็วการเล่นของแต่ละช่องข้อมูล setUint32(offset, SampleRate, true); offset += 4; // อัตราการส่งข้อมูลรูปคลื่น (จำนวนไบต์เฉลี่ย ต่อวินาที ) โมโน × บิตข้อมูลต่อวินาที × บิตข้อมูลต่อตัวอย่าง / 8 data.setUint32(offset, channelCount * SampleRate * (sampleBits / 8), offset += 4; จำนวนไบต์ในการปรับข้อมูลอย่างรวดเร็วซึ่งครอบครองโดยหนึ่งช่องสัญญาณโมโนตัวอย่าง data.setUint16 (offset, exampleBits, true); offset += 2; // Data identifier writeString ('data'); // จำนวนรวมของ ข้อมูลตัวอย่างนั่นคือขนาดข้อมูลทั้งหมด - 44 data.setUint32 (offset, dataLength, true); offset += 4; // เขียนข้อมูลตัวอย่าง if (sampleBits === 8) { for (let i = 0; i < bytes.length; i++, offset++) { const s = Math.max(-1, Math.min(1, ไบต์[i])); ให้ val = s < 0 ? s * 0x8000 : s * 0x7fff; val = parseInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true); } } else { สำหรับ (ให้ i = 0; i < bytes.length; i++ , offset += 2) { const s = Math.max(-1, Math.min(1, ไบต์ [i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true); } } ส่งคืน Blob ใหม่ ([data], { ประเภท: 'audio/wav' }});
2. แก้ไขวิธีการโทรของคอลเลกชันเสียงของ HZRecord
// คอลเลกชันเสียง recorder.onaudioprocess = function(e) { audioData.encode(e.inputBuffer.getChannelData(0));};
3. วิธี getBlob ของ HZRecord
this.getBlob = function() { this.stop(); return audioData.finish();};
4. วิธีการเล่นของ HZRecord แปลงหยดเป็น base64url
this.play = function(func) { readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(data, callback) { la fileReader = new FileReader(); fileReader.onload = function(e) { โทรกลับ(e .target.result); };
จนถึงตอนนี้ปัญหาทั้งสองข้างต้นได้รับการแก้ไขแล้ว
ขั้นตอนที่ 4ในที่นี้เราจะแนะนำวิธีสร้างเอฟเฟกต์ไดนามิกระหว่างการบันทึกเป็นหลัก ข้อกำหนดด้านแอนิเมชั่นประการหนึ่งของเราคือ:
ตามปริมาตรที่เข้ามา ส่วนโค้งแบบวงกลมจะถูกขยายแบบไดนามิก
//สร้างโหนดตัววิเคราะห์และรับข้อมูลเวลาและความถี่ของเสียง const analyser = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const wrapEle = $this.refs['wrap'] ; ให้ ctx = wrapEle.getContext('2d');const width = wrapEle.width;const height = wrapEle.height;const center = { x: width / 2, y: height / 2}; ฟังก์ชั่น DrawArc(ctx, สี, x, y, รัศมี, beginningAngle, endAngle) { ctx.beginPath(); ; ctx. strokeStyle = สี; ctx.arc(x, y, รัศมี, (Math.PI * beginningAngle) / 180, (Math.PI * endAngle) / 180); ctx. stroke();}(function DrawSpectrum() { analyser.getByteFrequencyData(inputAnalyser); // รับข้อมูลโดเมนความถี่ ctx.clearRect(0, 0, ความกว้าง, ความสูง); // ลากเส้นสำหรับ (ให้ i = 0; i < 1; i++) { ให้ค่า = inputAnalyser[i] / 3; <===รับข้อมูล ให้สี = []; ถ้า (ค่า <= 16) { สี = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '# e4e4e4']; } อื่น ๆ ถ้า (ค่า <= 32) { สี = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4']; } else { สี = ['#f5A631', '#f5A631', '#f5A631', '#f5A631 ', '#f5A631', '#f5A631']; } DrawArc(ctx, สี[0], center.x, center.y, 52 + 16, -30, 30); , 52 + 16, 150, 210); DrawArc(ctx, สี[2], center.x, center.y, 52 + 32, -22.5, 22.5); DrawArc(ctx, สี[3], center.x, center.y, 52 + 32, 157.5, 202.5); DrawArc(ctx, สี[4], center.x, center.y, 52 + 48, -13, 13); DrawArc(ctx, สี[5], center.x, center.y, 52 + 48, 167, 193); } // ขอเฟรมถัดไป requestAnimationFrame(drawSpectrum);})();สิ้นสุดชะตากรรม
ณ จุดนี้ โซลูชันฟังก์ชันการบันทึก html5 ที่สมบูรณ์ได้เสร็จสมบูรณ์แล้ว หากมีสิ่งใดที่ต้องเพิ่มกรุณาฝากข้อความหากมีสิ่งใดที่ไม่สมเหตุสมผล
PS: lamejs สามารถอ้างถึง GitHub นี้
ข้างต้นคือเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการศึกษาของทุกคน ฉันหวังว่าทุกคนจะสนับสนุน VeVb Wulin Network