Debido a las necesidades del proyecto, necesitamos implementar la función de grabación en el lado web. Al principio, se encontraron dos soluciones, una a través de iframe y la otra era la API getUserMedia de html5. Dado que nuestra función de grabación no necesita ser compatible con el navegador IE, no dudamos en elegir getUserMedia proporcionado por html5 para implementarla. La idea básica es combinarlo con la documentación oficial de la API y algunas soluciones que se encuentran en línea para crear una solución que se adapte a las necesidades del proyecto. Pero como tenemos que asegurarnos de que la función de grabación se pueda activar tanto en el pad como en la PC al mismo tiempo, también existen algunos inconvenientes. El siguiente es un proceso de restauración.
Paso 1Porque la nueva API pasa navigator.mediaDevices.getUserMedia y devuelve una promesa.
La API anterior es navigator.getUserMedia, por lo que se hizo compatibilidad. El código es el siguiente:
// Es posible que los navegadores antiguos no implementen mediaDevices en absoluto, por lo que primero podemos configurar un objeto vacío if (navigator.mediaDevices === undefinido) { navigator.mediaDevices = {};}// Algunos navegadores admiten parcialmente mediaDevices. No podemos configurar getUserMedia// directamente en el objeto porque esto puede sobrescribir las propiedades existentes. Aquí solo agregaremos el atributo getUserMedia si no existe. if (navigator.mediaDevices.getUserMedia === indefinido) { let getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || Primero, si existe getUserMedia, consígalo // Algunos navegadores no lo implementan en absoluto; luego devuelven un error al rechazar la promesa para mantener una interfaz unificada if (!getUserMedia) { return Promise.reject(new Error(' getUserMedia is no implementado en este navegador')); } // De lo contrario, envuelva una Promesa para el antiguo método navigator.getUserMedia return new Promise(function(resolve, rechazar) { getUserMedia.call(navegador, restricciones, resolver, rechazar });Paso 2
Este es un método que existe en Internet y que encapsula un HZRecorder. Básicamente haciendo referencia a este método. La interfaz de grabación se puede llamar llamando a HZRecorder.get. Este método pasa una función de devolución de llamada. Después del nuevo HZRecorder, se ejecuta la función de devolución de llamada y se pasa un objeto HZRecorder materializado. Funciones como iniciar grabación, pausar, detener y reproducir se pueden implementar a través de los métodos de este objeto.
var HZRecorder = function (stream, config) { config = config || {}; config.sampleBits = config.sampleBits || Bits de muestreo 8, 16 config.sampleRate = config.sampleRate (44100/6) ; //Frecuencia de muestreo (1/6 44100) //Crear un objeto de entorno de audio audioContext = window.AudioContext || window.webkitAudioContext; var context = new audioContext(); //Ingresa sonido en este objeto var audioInput = context.createMediaStreamSource(stream); //Establece el nodo de volumen var volume = context.createGain(); .connect(volume); //Crea un caché para almacenar en caché el sonido var bufferSize = 4096 //Crea un nodo de caché de sonido, método createScriptProcessor // El segundo y tercer parámetro se refieren a que tanto la entrada como la salida son binaurales. var recorder = context.createScriptProcessor(bufferSize, 2, 2); var audioData = { size: 0 //Longitud del archivo de grabación, buffer: [] //Caché de grabación, inputSampleRate: context.sampleRate //Frecuencia de muestreo de entrada, inputSampleBits: 16 //Ingrese los dígitos de muestreo 8, 16, outputSampleRate: config.sampleRate //Frecuencia de muestreo de salida, oututSampleBits: config.sampleBits // Bits de muestra de salida 8, 16, entrada: función (datos) { this.buffer.push(new Float32Array(data)); this.size += data.length }, comprimir: función () { / /Fusionar compresión//Fusionar datos var = new Float32Array(this.size offset = 0 for (var i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset += this.buffer[i].length; } //Compresión var compresión = parseInt(this.inputSampleRate / this.outputSampleRate length = data.length); / compresión; var resultado = new Float32Array(longitud); var índice = 0, j = 0; mientras (índice < longitud) { resultado[índice] = datos[j]; index++; } devolver resultado; }, encodeWAV: function () { var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits var bytes = this); .compress(); var dataLength = bytes.length * (sampleBits / var buffer = nuevo); ArrayBuffer(44 + dataLength); var data = new DataView(buffer); var channelCount = 1; //Mono var offset = 0; var writeString = function (str) { for (var i = 0; i < str. length; i++) { data.setUint8(offset + i, str.charCodeAt(i)); } } // Identificador del archivo de intercambio de recursos writeString('RIFF'); offset += 4; // El número total de bytes desde la siguiente dirección hasta el final del archivo, es decir, el tamaño del archivo -8 data.setUint32(offset, 36 + dataLength, true); /Indicador de archivo WAV writeString(' WAVE'); desplazamiento += 4; // Indicador de formato de forma de onda writeString('fmt '); // Filtrar bytes, generalmente 0x10 = 16; data.setUint32(offset, 16, true); offset += 4; // Categoría de formato (datos de muestreo en formato PCM) data.setUint16(offset, 1, true); (offset, channelCount, true); offset += 2; // Frecuencia de muestreo, el número de muestras por segundo, representa la velocidad de reproducción de cada canal data.setUint32(offset, sampleRate, true); offset += 4; //Velocidad de transferencia de datos de forma de onda (bytes promedio por segundo) Mono × bits de datos por segundo × bits de datos por muestra/8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); ); offset += 4; // El número de bytes ocupados por el muestreo de números de ajuste rápido de datos a la vez es mono × el número de bits de datos por muestra/8 data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2; // Número de bits de datos por muestra data.setUint16(offset, sampleBits, true); += 4; // Número total de datos muestreados, es decir, tamaño total de datos -44 data.setUint32(offset, dataLength, true); //Escribe datos muestreados 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; valor = parseInt(255 / (65535 / (valor + 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(desplazamiento, s < 0 ? s * 0x8000 : s * 0x7FFF, verdadero); } } return new Blob([data], { type: 'audio/wav' } } }; //Comienza a grabar this.start = function () { audioInput.connect(recorder). connect(context.destination); } // Detener this.stop = function () { recorder.disconnect() }; }; //Continuar this.again = function() { recorder.connect(context.destination }; //Obtener el archivo de audio this.getBlob = function () { this.stop(); ; }; //Reproducir this.play = function (audio) { audio.src = window.URL.createObjectURL(this.getBlob() }; { var fd = new FormData(); fd.append('audioData', this.getBlob()); var xhr = new XMLHttpRequest(); if (devolución de llamada) { xhr.upload.addEventListener('progreso', función (e) ) { callback('cargando', e }, falso); xhr.addEventListener('cargar', función (e) { callback('ok', e); }, falso); xhr.addEventListener('error', función (e) { callback('error', e); }, falso); (e) { callback('cancelar', e }, falso } xhr.open('POST', xhr.send(fd); //Recolección de audio recorder.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); //record(e.inputBuffer.getChannelData(0) }; .throwError = función (mensaje) { lanzar nueva función () { this.toString = función () { devolver mensaje };}; //Si se admite la grabación HZRecorder.canRecording = (navigator.getUserMedia != null); //Obtener la grabadora 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('No se puede grabar, verifique el estado del dispositivo'); } }});
Lo anterior ya puede satisfacer la mayoría de las necesidades. Pero necesitamos ser compatibles con el lado de la almohadilla. Tenemos varios problemas con nuestra plataforma que deben resolverse.
A continuación se presentan soluciones a ambos problemas.
Paso 3La siguiente es una solución para implementar el formato de grabación como mp3 y window.URL.createObjectURL para pasar datos de blobs e informar un error en el lado del pad.
1. Modifique el código del objeto audioData en HZRecorder. E introduzca un archivo js lamejs.js de una gran persona en Internet.
const lame = new lamejs();let audioData = { samplesMono: null, maxSamples: 1152, mp3Encoder: new lame.Mp3Encoder(1, context.sampleRate || 44100, config.bitRate || 128), dataBuffer: [], tamaño : 0, // Búfer de longitud del archivo de grabación: [], // Búfer de grabación inputSampleRate: context.sampleRate, // Frecuencia de muestreo de entrada inputSampleBits: 16, // Ingreso de dígitos de muestra 8, 16 outputSampleRate: config.sampleRate, // Frecuencia de muestreo de salida outputSampleBits: config.sampleBits, // Salida de dígitos de muestra 8, 16 convertBuffer: función (arrayBuffer) {dejar datos = nuevo Float32Array(arrayBuffer); dejar salir = new Int16Array(arrayBuffer.length); this.floatTo16BitPCM(datos, salida); floatTo16BitPCM: función(entrada, salida) {para (dejar i = 0; i < entrada. longitud; i++) { let s = Math.max(-1, Math.min(1, entrada[i])); = s < 0? s * 0x8000: s * 0x7fff; } }, appendToBuffer: function(mp3Buf) { this.dataBuffer.push(new Int8Array(mp3Buf) }, codificar: function(arrayBuffer) { this.samplesMono = this); .convertBuffer(arrayBuffer); deja restante = this.samplesMono.length para; (let i = 0; restante >= 0; i += this.maxSamples) { let left = this.samplesMono.subarray(i, i + this.maxSamples) let mp3buf = this.mp3Encoder.encodeBuffer(left); .appendToBuffer(mp3buf); restante -= this.maxSamples } }, finalizar: función() { this.appendToBuffer(this.mp3Encoder.flush()); devuelve nuevo Blob(this.dataBuffer, { tipo: 'audio/mp3' } }, entrada: función(datos) { this.buffer.push(new Float32Array( datos)); this.size += data.length }, comprimir: function() { // Fusionar compresión // Fusionar let data = nuevo Float32Array(this.size); let offset = 0; for (let i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset += this.buffer); [i].length; } // Compresión let compresión = parseInt(this.inputSampleRate / this.outputSampleRate, 10); let longitud = data.length / compresión; Float32Array(longitud); let index = 0; let j = 0; while (índice <longitud) {resultado[índice] = datos[j]; j += compresión }; { let sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); let sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits); let bytes = this.compress(); let dataLength = bytes.length * (sampleBits / 8); let channelCount (44 + dataLength); 1; //mono let offset = 0; let writeString = function(str) { for (let i = 0; i < str.length; i++) { data.setUint8(offset + i, str.charCodeAt(i)); } }; // Identificador de archivo de intercambio de recursos writeString('RIFF'); // Total de bytes desde la siguiente dirección hasta el final del número de archivo, es decir, tamaño del archivo - 8 data.setUint32(offset, 36 + dataLength, true); offset += 4; // indicador de archivo WAV writeString('WAVE'); // Indicador de formato de onda writeString('fmt '); offset += 4; // Filtrar bytes, generalmente 0x10 = 16 data.setUint32(offset, 16, true += 4; datos) data.setUint16(offset, 1, true); offset += 2 // Número de canal data.setUint16(offset, channelCount, true); += 2; // Frecuencia de muestreo, el número de muestras por segundo, que indica la velocidad de reproducción de cada canal data.setUint32(offset, sampleRate, true); // Velocidad de transmisión de datos de forma de onda (número promedio de bytes). por segundo) Mono × bits de datos por segundo × bits de datos por muestra / 8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true += 4; Número de bytes de ajuste rápido de datos ocupados por un canal mono de muestra data.setUint16(offset, sampleBits, true); // Identificador de datos writeString('data' offset += 4; datos muestreados, es decir, tamaño total de datos - 44 data.setUint32(offset, dataLength, true); offset += 4; // Escribe datos de muestra if (sampleBits === 8) { for (let i = 0; i < bytes.length; i++, offset++) { const s = Math.max(-1, Math.min(1, bytes[i])); deja que val = s < 0 s * 0x8000 : s * 0x7fff; val = parseInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true); , desplazamiento += 2) { const s = Math.max(-1, Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, verdadero); } } return new Blob([datos], { tipo: 'audio/wav' } }});
2. Modifique el método de llamada de la colección de audio de HZRecord.
// grabadora de colección de audio.onaudioprocess = function(e) { audioData.encode(e.inputBuffer.getChannelData(0));};
3. Método getBlob de HZRecord.
this.getBlob = function() { this.stop(); return audioData.finish();};
4. Método de reproducción de HZRecord. Convierta blob a base64url.
this.play = función(func) { readBlobAsDataURL(this.getBlob(), func);};función readBlobAsDataURL(datos, devolución de llamada) { let fileReader = new FileReader(); .objetivo.resultado); }; fileReader.readAsDataURL(datos);}
Hasta ahora, los dos problemas anteriores se han resuelto.
Paso 4Aquí presentamos principalmente cómo crear efectos dinámicos durante la grabación. Uno de nuestros requisitos de animación es:
Según el volumen entrante, un arco circular se expande dinámicamente.
//Crea un nodo analizador y obtiene datos de tiempo y frecuencia de audio const analyser = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const wrapEle = $this.refs['wrap'] ; let ctx = wrapEle.getContext('2d');ancho constante = wrapEle.width;alto constante =; wrapEle.height;const center = { x: ancho / 2, y: alto / 2}; función drawArc(ctx, color, x, y, radio, beginAngle, endAngle) { ctx.beginPath(); ; ctx.strokeStyle = color; ctx.arc(x, y, radio, (Math.PI * BeginAngle) / 180, (Math.PI * endAngle) / 180); ctx.stroke();}(function drawSpectrum() { analyser.getByteFrequencyData(inputAnalyser); // Obtener datos del dominio de frecuencia ctx.clearRect(0, 0, width, height); // Dibujar líneas para (let i = 0; i < 1; i++) { let value = inputAnalyser[i] / 3; <===Obtener datos let colores = []; if (valor <= 16) { colores = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '# e4e4e4']; } else if (valor <= 32) { colores = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4'] } más {colores = ['#f5A631', '#f5A631', '#f5A631', '#f5A631 ', '#f5A631', '#f5A631']; } drawArc(ctx, colores[0], centro.x, centro.y, 52 + 16, -30, 30); drawArc(ctx, colores[1], centro.x, centro.y); , 52 + 16, 150, 210); drawArc(ctx, colores[2], centro.x, centro.y, 52 + 32, -22,5, 22,5); dibujarArc(ctx, colores[3], centro.x, centro.y, 52 + 32, 157,5, 202,5); + 48, -13, 13); dibujarArc(ctx, colores[5], centro.x, centro.y, 52 + 48, 167, 193); } // Solicita el siguiente cuadro requestAnimationFrame(drawSpectrum);})();fin del destino
Hasta ahora, se ha completado una solución completa de función de grabación html5. Si hay algo que deba agregarse, deje un mensaje si hay algo que no sea razonable.
PD: lamejs puede consultar este github
Lo anterior es el contenido completo de este artículo. Espero que sea útil para el estudio de todos. También espero que todos apoyen VeVb Wulin Network.