Karena kebutuhan proyek, kita perlu mengimplementasikan fungsi perekaman di sisi web. Pada awalnya ditemukan dua solusi, satu melalui iframe, dan yang lainnya adalah api getUserMedia dari html5. Karena fungsi perekaman kami tidak harus kompatibel dengan browser IE, kami tidak ragu memilih getUserMedia yang disediakan oleh html5 untuk mengimplementasikannya. Ide dasarnya adalah menggabungkannya dengan dokumentasi api resmi dan beberapa solusi yang ditemukan online untuk membuat solusi yang sesuai dengan kebutuhan proyek. Namun karena kami harus memastikan bahwa fungsi perekaman dapat diaktifkan pada pad dan PC secara bersamaan, terdapat beberapa kendala. Berikut ini adalah proses restorasi.
Langkah 1Karena api baru melewati navigator.mediaDevices.getUserMedia dan mengembalikan janji.
API lama adalah navigator.getUserMedia, jadi kompatibilitas dibuat. Kodenya adalah sebagai berikut:
// Browser lama mungkin tidak mengimplementasikan mediaDevices sama sekali, jadi kita bisa menyetel objek kosong terlebih dahulu if (navigator.mediaDevices === undefinisi) { navigator.mediaDevices = {};}// Beberapa browser mendukung sebagian mediaDevices. Kita tidak bisa langsung menyetel getUserMedia// pada objek karena hal ini dapat menimpa properti yang sudah ada. Disini kita hanya akan menambahkan atribut getUserMedia jika tidak ada. if (navigator.mediaDevices.getUserMedia === tidak ditentukan) { biarkan getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||. navigator.mozGetUserMedia ||. Pertama, jika ada getUserMedia, dapatkan // Beberapa browser tidak mengimplementasikannya sama sekali - lalu kembalikan kesalahan ke penolakan janji untuk mempertahankan antarmuka terpadu if (!getUserMedia) { return Promise.reject(new Error(' getUserMedia tidak diterapkan di browser ini')); } // Jika tidak, bungkus Promise untuk metode navigator lama.getUserMedia return new Promise(function(resolve, tolak) { getUserMedia.call(navigator, batasan, tekad, tolak });Langkah 2
Ini adalah metode yang ada di Internet, merangkum HZRecorder. Pada dasarnya merujuk pada metode ini. Antarmuka perekaman dapat dipanggil dengan memanggil HZRecorder.get. Metode ini meneruskan fungsi panggilan balik. Setelah HZRecorder baru, fungsi panggilan balik dijalankan dan objek HZRecorder yang terwujud diteruskan. Fungsi seperti mulai merekam, menjeda, menghentikan, dan memutar dapat diimplementasikan melalui metode objek ini.
var HZRecorder = fungsi (stream, config) { config = config ||. {}; config.sampleBits = config.sampleBits || ; //Laju pengambilan sampel (1/6 44100) //Membuat objek lingkungan audio audioContext = window.AudioContext ||. window.webkitAudioContext; var konteks = new audioContext(); //Masukkan suara ke objek ini var audioInput = konteks.createMediaStreamSource(stream); .connect(volume); //Buat cache untuk menyimpan cache suara var bufferSize = 4096; //Buat node cache suara, metode createScriptProcessor // Parameter kedua dan ketiga mengacu pada input dan output yang bersifat binaural. var perekam = konteks.createScriptProcessor(bufferSize, 2, 2); var audioData = { ukuran: 0 //Merekam panjang file, buffer: [] //Merekam cache, inputSampleRate: konteks.sampleRate //Input laju pengambilan sampel, inputSampleBits: 16 //Masukkan digit pengambilan sampel 8, 16, outputSampleRate: config.sampleRate //Laju pengambilan sampel keluaran, oututSampleBits: config.sampleBits //Output sampel bit 8, 16, input: function (data) { this.buffer.push(new Float32Array(data)); this.size += data.length }, kompres: function () { / /Gabungkan kompresi//Gabungkan var data = new Float32Array(this.size); var offset = 0; for (var i = 0; i < this.buffer.length; i++) { data.set(ini.buffer[i], offset); offset += this.buffer[i].length; } //Kompresi var kompresi = parseInt(ini.inputSampleRate / this.outputSampleRate); / kompresi; var hasil = new Float32Array(panjang); var indeks = 0, j = 0; while (indeks < panjang) { hasil[indeks] = data[j]; indeks++; } mengembalikan hasil; } , encodeWAV: function () { var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits); .compress(); var dataLength = byte.length * (sampleBits / 8); ArrayBuffer(44 + dataLength); var data = new DataView(buffer); var channelCount = 1; //Mono var offset = 0; var writeString = function (str) { untuk (var i = 0; i < str.panjang; i++) { data.setUint8(offset + i, str.charCodeAt(i) } } // Pengidentifikasi file pertukaran sumber daya writeString('RIFF'); offset += 4; //Jumlah total byte dari alamat berikutnya hingga akhir file, yaitu ukuran file -8 data.setUint32(offset, 36 + dataLength, true); / Bendera file WAV writeString(' WAVE'); offset += 4; // Bendera format gelombang writeString('fmt '); // Filter byte, umumnya 0x10 = 16 data.setUint32(offset, 16, true); offset += 4; // Kategori format (data pengambilan sampel format PCM) data.setUint16(offset, 1, offset += 2); ( offset, channelCount, true); offset += 2; // Kecepatan pengambilan sampel, jumlah sampel per detik, mewakili kecepatan pemutaran setiap saluran data.setUint32(offset, sampleRate, true); offset += 4; //Kecepatan transfer data bentuk gelombang (rata-rata byte per detik) Mono × bit data per detik × bit data per sampel/8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); ); offset += 4; // Jumlah byte yang ditempati oleh sampel nomor penyesuaian data cepat pada satu waktu adalah mono × jumlah bit data per sampel/8 data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2; // Jumlah bit data per sampel data.setUint16(offset, sampleBits, true); // Pengidentifikasi data writeString('data'); += 4; //Jumlah total data sampel, yaitu total ukuran data -44 data.setUint32(offset, dataLength, true); //Tulis data sampel if (sampleBits === 8) { for (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 / (val + 32768))); data.setInt8(offset, val, true); else { untuk (var i = 0; i < byte.panjang; i++, offset += 2) { var s = Math.max(-1 , Matematika.min(1, byte[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); } } return new Blob([data], { ketik: 'audio/wav' }); //Mulai merekam ini.mulai = fungsi () { audioInput.connect(perekam); connect(context.destination); //Hentikan ini.stop = function() { recorder.disconnect() }; }; //Lanjutkan ini.lagi = function() { recorder.connect(context.destination }; //Dapatkan file audio this.getBlob = function () { this.stop(); ; }; //Putar ulang this.play = fungsi (audio) { audio.src = window.URL.createObjectURL(this.getBlob() }; { var fd = new FormData(); fd.append('audioData', this.getBlob()); var xhr = new XMLHttpRequest(); ) { panggilan balik('mengunggah', e }, salah); panggilan balik('ok', e); }, false); xhr.addEventListener('kesalahan', fungsi (e) { panggilan balik('kesalahan', e); }, xhr.addEventListener('batalkan', fungsi (e) { panggilan balik('batal', e }, false } xhr.open('POST', url); xhr.kirim(fd }; //Perekam koleksi audio.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); //record(e.inputBuffer.getChannelData(0) }; //melemparkan pengecualian HZRecorder .throwError = fungsi (pesan) { melempar fungsi baru () { this.toString = function () { mengembalikan pesan }; }; //Apakah perekaman didukung HZRecorder.canRecording = (navigator.getUserMedia != null); //Dapatkan perekam HZRecorder.get = function (callback, config) { if (callback) { navigator.mediaDevices .getUserMedia({ audio: true }) .then(function(stream) { biarkan rec = new HZRecorder(stream, config); callback(rec); }) .catch(function(error) { HZRecorder.throwError('Tidak dapat merekam, harap periksa status perangkat'); } }};
Hal di atas sudah dapat memenuhi sebagian besar kebutuhan. Tapi kita harus kompatibel dengan sisi pad. Kami memiliki beberapa masalah dengan pad kami yang harus diselesaikan.
Di bawah ini adalah solusi untuk kedua masalah tersebut.
Langkah 3Berikut ini adalah solusi bagi saya untuk mengimplementasikan format rekaman sebagai mp3 dan window.URL.createObjectURL untuk meneruskan data blob dan melaporkan kesalahan di sisi pad.
1. Ubah kode objek audioData di HZRecorder. Dan perkenalkan file js lamejs.js dari orang hebat di Internet
const lumpuh = lamejs baru();biarkan audioData = { sampelMono: null, maxSamples: 1152, mp3Encoder: lame baru.Mp3Encoder(1, konteks.sampleRate || 44100, config.bitRate || 128), dataBuffer: [], ukuran : 0, // Buffer panjang file rekaman: [], // Merekam buffer inputSampleRate: konteks.sampleRate, // Input sampling rate inputSampleBits: 16, // Input digit sampling 8, 16 outputSampleRate: config.sampleRate, // Output sampling rate outputSampleBits: config.sampleBits, // Output sampling digit 8, 16 convertBuffer: function(arrayBuffer) { biarkan data = baru Float32Array(arrayBuffer); biarkan keluar = new Int16Array(arrayBuffer.length); this.floatTo16BitPCM(data, keluar); }, floatTo16BitPCM: function(input, output) { untuk (biarkan i = 0; i < masukan. panjang; i++) { misalkan s = Matematika.maks(-1, Matematika.min(1, masukan[i]) keluaran[i] = s < 0 ? s * 0x8000 : s * 0x7fff; } }, appendToBuffer: function(mp3Buf) { this.dataBuffer.push(new Int8Array(mp3Buf)); .convertBuffer(arrayBuffer); biarkan tersisa = this.samplesMono.length; (biarkan i = 0; sisa >= 0; i += this.maxSamples) { biarkan kiri = this.samplesMono.subarray(i, i + this.maxSamples); biarkan mp3buf = this.mp3Encoder.encodeBuffer(kiri ini); .appendToBuffer(mp3buf); tersisa -= this.maxSamples; this.appendToBuffer(this.mp3Encoder.flush()); mengembalikan Blob baru(this.dataBuffer, { ketik: 'audio/mp3' } }, masukan: fungsi(data) { this.buffer.push(new Float32Array( data)); this.size += data.length }, kompres: function() { // Gabungkan kompresi // Gabungkan data = baru Float32Array(ini.ukuran); biarkan offset = 0; untuk (biarkan i = 0; i < this.buffer.length; i++) { data.set(ini.buffer[i], offset += this.buffer); [i].length; } // Kompresi biarkan kompresi = parseInt(this.inputSampleRate / this.outputSampleRate, 10); biarkan panjang = data.length / kompresi; Float32Array(panjang); biarkan indeks = 0; biarkan j = 0; sementara (indeks < panjang) { hasil[indeks] = data[j]; j += indeks++; { biarkan sampleRate = Math.min(ini.inputSampleRate, this.outputSampleRate); this.oututSampleBits); biarkan byte = this.compress(); biarkan dataLength = bytes.length * (sampleBits / 8); biarkan buffer = new ArrayBuffer(44 + dataLength); 1; //mono biarkan offset = 0; biarkan writeString = function(str) { untuk (biarkan i = 0; i < str.length; i++) { data.setUint8(offset + i, str.charCodeAt(i)); // Pengidentifikasi file pertukaran sumber daya writeString('RIFF'); // Total byte dari alamat berikutnya hingga akhir nomor file, yaitu ukuran file - 8 data.setUint32(offset, 36 + dataLength, offset += 4; // flag file WAV writeString('WAVE'); // Bendera format gelombang writeString('fmt '); offset += 4; // Filter byte, biasanya 0x10 = 16 data.setUint32(offset, 16, offset += 4; data) data.setUint16(offset, 1, true); offset += 2; // Nomor saluran data.setUint16(offset, channelCount, offset += 2; // Kecepatan pengambilan sampel, jumlah sampel per detik, menunjukkan kecepatan pemutaran setiap saluran data.setUint32(offset, sampleRate, true); // Kecepatan transmisi data bentuk gelombang (rata-rata byte per detik ) Mono × bit data per detik × bit data per sampel / 8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), offset += 4; Penyesuaian data cepat jumlah byte yang ditempati oleh satu sampel saluran mono Nomor data.setUint16(offset, sampleBits, offset += 2; // Pengidentifikasi data writeString('data'); // Jumlah total data sampel, yaitu total ukuran data - 44 data.setUint32(offset, dataLength, true); offset += 4; // Tulis data sampel if (sampleBits === 8) { for (misalkan i = 0; i < bytes.length; i++, offset++) { const s = Matematika.max(-1, Matematika.min(1, byte[i])); misalkan val = s < 0 ? s * 0x8000 : s * 0x7fff; val = parseInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true); , offset += 2) { const s = Math.max(-1, Math.min(1, byte[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
2. Ubah metode pemanggilan koleksi audio HZRecord.
// Perekam koleksi audio.onaudioprocess = function(e) { audioData.encode(e.inputBuffer.getChannelData(0));};
3. Metode getBlob HZRecord.
this.getBlob = function() { this.stop(); mengembalikan audioData.finish();};
4. Metode pemutaran HZRecord. Ubah gumpalan menjadi base64url.
this.play = function(func) { readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(data, callback) { biarkan fileReader = new FileReader(); .target.hasil }; fileReader.readAsDataURL(data);}
Sejauh ini, kedua permasalahan di atas telah teratasi.
Langkah 4Di sini kami terutama memperkenalkan cara membuat efek dinamis selama perekaman. Salah satu persyaratan animasi kami adalah:
Berdasarkan volume yang masuk, busur lingkaran mengembang secara dinamis.
//Buat simpul penganalisis dan dapatkan data waktu dan frekuensi audio const analisar = konteks.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const wrapEle = $this.refs['wrap'] ; biarkan ctx = wrapEle.getContext('2d');const lebar = wrapEle.width;const tinggi = wrapEle.height;const center = { x: lebar / 2, y: tinggi / 2}; fungsi drawArc(ctx, warna, x, y, radius, BeginAngle, endAngle) { ctx.beginPath(); ; ctx.strokeStyle = warna; ctx.arc(x, y, radius, (Matematika.PI * BeginAngle) / 180, (Math.PI * endAngle) / 180); ctx.stroke();}(function drawSpectrum() { analyser.getByteFrequencyData(inputAnalyser); // Dapatkan data domain frekuensi ctx.clearRect(0, 0, lebar, tinggi); // Menggambar garis untuk (misalkan i = 0; i < 1; i++) { let value = inputAnalyser[i] / 3; <===Dapatkan data biarkan warna = []; if (nilai <= 16) { warna = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '# e4e4e4']; } else if (nilai <= 32) { warna = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4']; } else { warna = ['#f5A631', '#f5A631', '#f5A631', '#f5A631 ', '#f5A631', '#f5A631']; } drawArc(ctx, warna[0], tengah.x, tengah.y, 52 + 16, -30, 30); drawArc(ctx, warna[1], tengah.x, tengah.y , 52 + 16, 150, 210); drawArc(ctx, warna[2], tengah.x, tengah.y, 52 + 32, -22.5, 22.5); drawArc(ctx, warna[3], tengah.x, tengah.y, 52 + 32, 157.5, 202.5); drawArc(ctx, warna[4], tengah.x, tengah.y, 52 + 48, -13, 13); drawArc(ctx, warna[5], tengah.x, tengah.y, 52 + 48, 167, 193); } // Minta bingkai berikutnya requestAnimationFrame(drawSpectrum);})();Akhir dari takdir
Pada titik ini, solusi lengkap fungsi perekaman HTML5 telah selesai. Jika ada yang perlu ditambahkan, silakan tinggalkan pesan jika ada yang tidak masuk akal.
ps: lamejs bisa merujuk ke github ini
Di atas adalah keseluruhan isi artikel ini, saya harap dapat bermanfaat untuk pembelajaran semua orang. Saya juga berharap semua orang mendukung VeVb Wulin Network.