Aufgrund der Projektanforderungen müssen wir die Aufnahmefunktion auf der Webseite implementieren. Zu Beginn wurden zwei Lösungen gefunden: eine über iframe und die andere über die getUserMedia-API von HTML5. Da unsere Aufnahmefunktion nicht mit dem IE-Browser kompatibel sein muss, haben wir nicht gezögert, getUserMedia von HTML5 zu wählen, um sie zu implementieren. Die Grundidee besteht darin, es mit der offiziellen API-Dokumentation und einigen online gefundenen Lösungen zu kombinieren, um eine Lösung zu erstellen, die den Anforderungen des Projekts entspricht. Da wir aber sicherstellen müssen, dass die Aufnahmefunktion sowohl am Pad als auch am PC gleichzeitig eingeschaltet werden kann, gibt es auch einige Fallstricke. Das Folgende ist eine Prozesswiederherstellung.
Schritt 1Weil die neue API navigator.mediaDevices.getUserMedia übergibt und ein Versprechen zurückgibt.
Die alte API ist navigator.getUserMedia, daher wurde Kompatibilität hergestellt. Der Code lautet wie folgt:
// Alte Browser implementieren mediaDevices möglicherweise überhaupt nicht, daher können wir zuerst ein leeres Objekt festlegen if (navigator.mediaDevices === undefiniert) { navigator.mediaDevices = {};}// Einige Browser unterstützen mediaDevices teilweise. Wir können getUserMedia// nicht direkt für das Objekt festlegen, da dadurch möglicherweise vorhandene Eigenschaften überschrieben werden. Hier fügen wir das getUserMedia-Attribut nur hinzu, wenn es nicht vorhanden ist. if (navigator.mediaDevices.getUserMedia === undefiniert) { let getUserMedia = navigator.webkitGetUserMedia ||. navigator.msGetUserMedia = function(constraints) { //. Erstens, wenn es getUserMedia gibt, holen Sie es sich // Einige Browser implementieren es überhaupt nicht – dann geben Sie einen Fehler an die Ablehnung des Versprechens zurück, um eine einheitliche Schnittstelle beizubehalten if (!getUserMedia) { return Promise.reject(new Error(' getUserMedia is nicht in diesem Browser implementiert')); // Ansonsten ein Promise für die alte navigator.getUserMedia-Methode umschließen. abweisen) { getUserMedia.call(navigator, Constraints, Resolve });Schritt 2
Dies ist eine Methode, die im Internet existiert und einen HZRecorder kapselt. Grundsätzlich verweisen wir auf diese Methode. Die Aufzeichnungsschnittstelle kann durch Aufrufen von HZRecorder.get aufgerufen werden. Diese Methode übergibt eine Rückruffunktion. Nach der neuen HZRecorder-Funktion wird die Rückruffunktion ausgeführt und ein materialisiertes HZRecorder-Objekt übergeben. Funktionen wie Starten der Aufnahme, Anhalten, Stoppen und Abspielen können über die Methoden dieses Objekts implementiert werden.
var HZRecorder = function (stream, config) { config.sampleBits = config.sampleBits ||. ; //Abtastrate (1/6 44100) //Erstelle ein Audioumgebungsobjekt audioContext = window.AudioContext ||. var context = new audioContext(); //Geben Sie den Ton in dieses Objekt ein var audioInput(); .connect(volume); //Erstelle einen Cache zum Zwischenspeichern von Sound var bufferSize = 4096 //Erstelle einen Sound-Cache-Knoten, createScriptProcessor-Methode // Der zweite und dritte Parameter beziehen sich darauf, dass sowohl die Eingabe als auch die Ausgabe binaural erfolgt. var recorder = context.createScriptProcessor(bufferSize, 2, 2); var audioData = { size: 0 //Aufzeichnungsdateilänge, Puffer: [] //Aufzeichnungscache, inputSampleRate: //Eingabeabtastrate, inputSampleBits: 16 //Abtastziffern 8, 16 eingeben, AusgabeSampleRate: config.sampleRate //Abtastrate ausgeben, oututSampleBits: config.sampleBits //Beispielbits 8, 16 ausgeben, input: function (data) { this.buffer.push(new Float32Array(data) }, compress: function () { / /Komprimierung zusammenführen//Vardaten zusammenführen = 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; } //Compression var compress = parseInt(this.inputSampleRate); var length = data.length / compress; var result = new Float32Array(length); var index = 0, j = 0; while (index < length) { result[index] = data[j]; index++; } return result; , encodeWAV: function () { var sampleRate = Math.min(this.inputSampleRate); var sampleBits = Math.min(this.inputSampleBits); .compress(); var dataLength = bytes.length * (sampleBits / 8); ArrayBuffer(44 + dataLength); var data = new DataView(buffer); var channelCount = 0; var writeString = function (str) { for (var i = 0; i < str. length; i++) { data.setUint8(offset + i, str.charCodeAt(i)); // Ressourcenaustauschdatei-ID writeString('RIFF'); offset += 4; //Die Gesamtzahl der Bytes von der nächsten Adresse bis zum Ende der Datei, also die Dateigröße -8 data.setUint32(offset, 36 + dataLength, true); /WAV-Datei-Flag writeString(' WAVE'); // Wellenform-Format-Flag writeString('fmt '); data.setUint32(offset, 16, true); // Formatkategorie (PCM-Format-Abtastdaten) data.setUint16(offset, 1, true); // Anzahl der Kanäle data.setUint16 ( offset,channelCount, true); offset += 2; // Abtastrate, die Anzahl der Samples pro Sekunde, stellt die Wiedergabegeschwindigkeit jedes Kanals dar data.setUint32(offset, sampleRate, true); offset += 4; //Wellenform-Datenübertragungsrate (durchschnittliche Bytes pro Sekunde) Mono × Datenbits pro Sekunde × Datenbits pro Sample/8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true ); offset += 4; // Die Anzahl der Bytes, die gleichzeitig durch die schnelle Datenanpassung belegt werden, beträgt Mono × die Anzahl der Datenbits pro Abtastung/8 data.setUint16(offset,channelCount * (sampleBits / 8), true); // Anzahl der Datenbits pro Probe data.setUint16(offset, sampleBits, true); // Datenbezeichner writeString('data'); += 4; //Gesamtzahl der abgetasteten Daten, d. h. Gesamtdatengröße -44 data.setUint32(offset, dataLength, true); //Abgetastete Daten schreiben 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 { for (var i = 0; i < bytes.length; i++, offset += 2) { var s = Math.max(-1 , Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); } return new Blob([data], { type: 'audio/wav' } }; connect(context.destination); //Stop this.stop = function () { recorder.disconnect(); //End this.end = function() { context.close(); }; //Continue this.again = function() { recorder.connect(context.destination) //Die Audiodatei abrufen this.getBlob = function () { this.stop(); ; }; //Playback this.play = function (audio) { audio.src = window.URL.createObjectURL(this.getBlob()); { var fd = new FormData(); fd.append('audioData', this.getBlob()); var xhr = new XMLHttpRequest(); { xhr.upload.addEventListener('progress', function (e ) { callback('uploading', e); xhr.addEventListener('load', function (e) { callback('ok', e); }, false); xhr.addEventListener('error', function (e) { callback('error', e); }, false); (e) { callback('cancel', e}, false } xhr.open('POST', url }); //Audio-Sammlung recorder.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); //record(e.inputBuffer.getChannelData(0)); .throwError = function (message) { throw new function () { this.toString = function () { return message };}; //Ob die Aufnahme unterstützt wird HZRecorder.canRecording = (navigator.getUserMedia != null); //Den Rekorder abrufen HZRecorder.get = function (callback, config) { if (callback) { navigator.mediaDevices .getUserMedia({ audio: true }) .then(function(stream) { let rec = new HZRecorder(stream, config); callback(rec); }) .catch(function(error) { HZRecorder.throwError('Aufzeichnung nicht möglich, bitte überprüfen Sie den Gerätestatus'); });
Die oben genannten können bereits die meisten Anforderungen erfüllen. Aber wir müssen mit der Pad-Seite kompatibel sein. Wir haben mehrere Probleme mit unserem Pad, die gelöst werden müssen.
Nachfolgend finden Sie Lösungen für beide Probleme.
Schritt 3Das Folgende ist eine Lösung für mich, das Aufnahmeformat als mp3 und window.URL.createObjectURL zu implementieren, um Blob-Daten zu übergeben und einen Fehler auf der Pad-Seite zu melden.
1. Ändern Sie den audioData-Objektcode in HZRecorder. Und stellen Sie eine js-Datei lamejs.js von einer großartigen Person im Internet vor
const lame = new lamejs();let audioData = { SamplesMono: null, maxSamples: 1152, mp3Encoder: new lame.Mp3Encoder(1, context.sampleRate || 44100, config.bitRate || 128), dataBuffer: [], size : 0, // Puffer für die Länge der Aufzeichnungsdatei: [], // Aufzeichnungspuffer inputSampleRate: context.sampleRate, // Abtastrate eingeben inputSampleBits: 16, // Beispielziffern eingeben 8, 16 outputSampleRate: config.sampleRate, // Abtastrate ausgeben outputSampleBits: config.sampleBits, // Beispielziffern ausgeben 8, 16 ConvertBuffer: function(arrayBuffer) { let data = new Float32Array(arrayBuffer); let out = new Int16Array(arrayBuffer.length); this.floatTo16BitPCM(data, out); return out }, floatTo16BitPCM: function(input, output) { for (let i = 0; i < input. length; i++) { let s = Math.max(-1, Math.min(1, input[i])); = s < 0 ? s * 0x8000 : s * 0x7fff; } , appendToBuffer: function(mp3Buf) { this.dataBuffer.push(new Int8Array(mp3Buf)); .convertBuffer(arrayBuffer); let verbleibend = this.samplesMono.length; (let i = 0; verbleibend >= 0; i += this.maxSamples) { let left = this.samplesMono.subarray(i, i + this.maxSamples); let mp3buf = this.mp3Encoder.encodeBuffer(left this); .appendToBuffer(mp3buf); verbleibend -= this.maxSamples } , finish: function() { this.appendToBuffer(this.mp3Encoder.flush()); return new Blob(this.dataBuffer, { type: 'audio/mp3' } }, input: function(data) { this.buffer.push(new Float32Array( data)); this.size += data.length }, compress: function() { // Komprimierung zusammenführen // let data = new Float32Array(this.size); let offset = 0; for (let i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset); [i].length; } // Komprimierung let compress = parseInt(this.inputSampleRate / this.outputSampleRate, 10); let length = data.length / compress; Float32Array(length); let j = 0; while (index] = data[j]; return result; { let sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); let sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits); let dataLength = bytes.length * (sampleBits / 8); let data = new DataView(buffer); 1; //mono let offset = 0; let writeString = function(str) { for (let i = 0; i < str.length; i++) { data.setUint8(offset + i, str.charCodeAt(i)); // Ressourcenaustauschdatei-Offset += 4; Dateinummer, also Dateigröße - 8 data.setUint32(offset, 36 + dataLength, true); // WAV-Datei-Flag offset += 4; // Wave-Format-Flag writeString('fmt'); // Filterbytes, im Allgemeinen 0x10 = 16 data.setUint32(offset, 16, true); // Formatkategorie (PCM-Formularabtastung). data) data.setUint16(offset, 1, true); // Kanalnummer data.setUint16(offset,channelCount, true); += 2; // Abtastrate, die Anzahl der Samples pro Sekunde, die die Wiedergabegeschwindigkeit jedes Kanals angibt data.setUint32(offset, sampleRate, true); // Wellenform-Datenübertragungsrate (durchschnittliche Anzahl von Bytes). pro Sekunde) Mono × Datenbits pro Sekunde × Datenbits pro Sample / 8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); Schnelle Datenanpassung Anzahl der von einem Sample-Monokanal belegten Bytes data.setUint16(offset, sampleBits, true); // Datenbezeichner writeString('data'); abgetastete Daten, d. h. Gesamtdatengröße - 44 data.setUint32(offset, dataLength, true); offset += 4; // Beispieldaten schreiben if (sampleBits === 8) { for (let i = 0; i < bytes.length; i++, offset++) { const s = Math.max(-1, Math.min(1, bytes[i])); sei val = s < 0 : s * 0x7fff; val = parseInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true); } } else { for (let i = 0; i < bytes.length; i++ , offset += 2) { const s = Math.max(-1, Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true); return new Blob([data], { type: 'audio/wav' }};
2. Ändern Sie die Aufrufmethode der Audiosammlung von HZRecord.
// Audiosammlung recorder.onaudioprocess = function(e) { audioData.encode(e.inputBuffer.getChannelData(0));};
3. Die getBlob-Methode von HZRecord.
this.getBlob = function() { this.stop(); return audioData.finish();};
4. Die Abspielmethode von HZRecord. Blob in base64url konvertieren.
this.play = function(func) { readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(data, callback) { let fileReader = new FileReader(); fileReader.onload = function(e) { callback(e .target.result); }; fileReader.readAsDataURL(data);}
Bisher wurden die beiden oben genannten Probleme gelöst.
Schritt 4Hier stellen wir hauptsächlich vor, wie man während der Aufnahme dynamische Effekte erzeugt. Eine unserer Animationsanforderungen ist:
Entsprechend dem einströmenden Volumen wird ein Kreisbogen dynamisch erweitert.
//Erstellen Sie einen Analyseknoten und erhalten Sie Audiozeit- und -frequenzdaten constanalysator = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const wrapEle = $this.refs['wrap'] ; let ctx = wrapEle.getContext('2d');const width = wrapEle.width;const height = wrapEle.height;const center = { x: width / 2, y: height / 2}; function drawArc(ctx, color, x, y, radius, beginAngle, endAngle) { ctx.beginPath(); ; ctx.strokeStyle = color; ctx.arc(x, y, radius, (Math.PI * beginAngle) / 180, (Math.PI * endAngle) / 180); ctx.Stroke();}(function drawSpectrum() { analyser.getByteFrequencyData(inputAnalyser); // Frequenzbereichsdaten abrufen ctx.clearRect(0, 0, width, height); // Linien zeichnen für (let i = 0; i < 1; i++) { let value = inputAnalyser[i] / 3; <===Daten abrufen let Colors = []; if (Wert <= 16) { Colors = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '# e4e4e4']; } else if (value <= 32) { Colors = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4']; } else { farben = ['#f5A631', '#f5A631', '#f5A631', '#f5A631 ', '#f5A631', '#f5A631']; } drawArc(ctx, farben[0], center.x, center.y, 52 + 16, -30, 30); drawArc(ctx, farben[1], center.x, center.y , 52 + 16, 150, 210); drawArc(ctx, farben[2], center.x, center.y, 52 + 32, -22,5, 22,5); drawArc(ctx, Farben[3], Mitte.x, Mitte.y, 52 + 32, 157,5, 202,5); drawArc(ctx, Farben[4], Mitte.x, Mitte.y, 52 + 48, -13, 13); drawArc(ctx, farben[5], center.x, center.y, 52 + 48, 167, 193); } // Den nächsten Frame anfordern requestAnimationFrame(drawSpectrum);})();Ende des Schicksals
Zu diesem Zeitpunkt wurde eine vollständige Lösung für die HTML5-Aufzeichnungsfunktion fertiggestellt. Wenn etwas hinzugefügt werden muss, hinterlassen Sie bitte eine Nachricht, wenn etwas Unangemessenes vorliegt.
PS: Lamejs können auf diesen Github verweisen
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass er für das Studium aller hilfreich ist. Ich hoffe auch, dass jeder das VeVb Wulin Network unterstützt.