Devido às necessidades do projeto, precisamos implementar a função de gravação no lado web. No início foram encontradas duas soluções, uma era através do iframe e a outra era a api getUserMedia do html5. Como nossa função de gravação não precisa ser compatível com o navegador IE, não hesitamos em escolher o getUserMedia fornecido pela html5 para implementá-la. A ideia básica é combiná-lo com a documentação oficial da API e algumas soluções encontradas online para fazer uma solução que atenda às necessidades do projeto. Mas como temos que garantir que a função de gravação possa ser ativada no pad e no PC ao mesmo tempo, também existem algumas armadilhas. A seguir está uma restauração do processo.
Passo 1Porque a nova API passa navigator.mediaDevices.getUserMedia e retorna uma promessa.
A API antiga é navigator.getUserMedia, então a compatibilidade foi feita. O código é o seguinte:
// Navegadores antigos podem não implementar mediaDevices, então podemos definir um objeto vazio primeiro if (navigator.mediaDevices === undefined) { navigator.mediaDevices = {};}// Alguns navegadores suportam parcialmente mediaDevices. Não podemos definir getUserMedia// diretamente no objeto porque isso pode substituir as propriedades existentes. Aqui só adicionaremos o atributo getUserMedia se ele não existir. if (navigator.mediaDevices.getUserMedia === indefinido) { let getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || Primeiro, se houver getUserMedia, obtenha-o // Alguns navegadores não o implementam - então retorne um erro à rejeição da promessa para manter uma interface unificada if (!getUserMedia) { return Promise.reject(new Error(' getUserMedia is não implementado neste navegador')); // Caso contrário, envolva uma Promise para o método navigator.getUserMedia antigo return new Promise(function(resolve, rejeitar) { getUserMedia.call(navegador, restrições, resolução, rejeição });Etapa 2
Este é um método que existe na Internet, encapsulando um HZRecorder. Basicamente referenciando este método. A interface de gravação pode ser acessada chamando HZRecorder.get. Este método passa uma função de retorno de chamada. Após o novo HZRecorder, a função de retorno de chamada é executada e um objeto HZRecorder materializado é passado. Funções como iniciar a gravação, pausar, parar e reproduzir podem ser implementadas através dos métodos deste objeto.
var HZRecorder = function (stream, config) { config = config || config.sampleBits = config.sampleBits || Bits de amostragem 8, 16 config.sampleRate = config.sampleRate || ; //Taxa de amostragem (1/6 44100) //Cria um objeto de ambiente de áudio audioContext = window.AudioContext || window.webkitAudioContext; var context = new audioContext(); //Inserir som neste objeto var audioInput = context.createMediaStreamSource(stream); .connect(volume); //Cria um cache para armazenar som var bufferSize = 4096; O segundo e o terceiro parâmetros referem-se ao fato de a entrada e a saída serem binaural. var recorder = context.createScriptProcessor(bufferSize, 2, 2); var audioData = { size: 0 //Comprimento do arquivo de gravação, buffer: [] //Cache de gravação, inputSampleRate: context.sampleRate //Taxa de amostragem de entrada, inputSampleBits: 16 //Inserir dígitos de amostragem 8, 16, outputSampleRate: config.sampleRate //Taxa de amostragem de saída, oututSampleBits: config.sampleBits //Saída de bits de amostra 8, 16, entrada: function (data) { this.buffer.push(new Float32Array(data)); this.size += data.length }, compress: function () { / /Mesclar compactação//Mesclar 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 } //Compressão var compression = parseInt(this.inputSampleRate / this.outputSampleRate); / compressão; var resultado = new Float32Array(comprimento var índice = 0, j = 0 enquanto (índice <comprimento) { resultado[índice] = dados[j]; index++; } retornar resultado } , encodeWAV: function () { var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate = this). .compress(); var dataLength = bytes.length * (sampleBits / 8); ArrayBuffer(44 + dataLength); var data = new DataView(buffer); i++) { data.setUint8(offset + i, str.charCodeAt(i)); // Identificador do arquivo de troca de recursos writeString('RIFF'); offset += 4; //O número total de bytes do próximo endereço até o final do arquivo, ou seja, o tamanho do arquivo -8 data.setUint32(offset, 36 + dataLength, true offset += 4); / Sinalizador de arquivo WAV writeString(' WAVE'); offset += 4; // Sinalizador de formato de forma de onda writeString('fmt '); data.setUint32(offset, 16, true offset += 4; // Categoria de formato (dados de amostragem no formato PCM) data.setUint16(offset, 1, true offset += 2); (offset, channelCount, true); offset += 2; // Taxa de amostragem, o número de amostras por segundo, representa a velocidade de reprodução de cada canal data.setUint32(offset, sampleRate, true); offset += 4; //Taxa de transferência de dados de forma de onda (bytes médios por segundo) Mono × bits de dados por segundo × bits de dados por amostra/8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true ); offset += 4; // O número de bytes ocupados pela amostragem de número de ajuste rápido de dados de uma vez é mono × o número de bits de dados por amostra/8 data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2; // Número de bits de dados por amostra data.setUint16(offset, sampleBits, true); += 4; //Número total de dados amostrados, ou seja, tamanho total dos dados -44 data.setUint32(offset, dataLength, true); //Escrever dados amostrados if (sampleBits, true); === 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(deslocamento, s < 0 ? s * 0x8000 : s * 0x7FFF, true } } return new Blob([data], { type: 'audio/wav' } } }); //Começa a gravar this.start = function () { audioInput.connect(recorder). connect(context.destination }); //Parar this.stop = function () { recorder.disconnect(); }; //Continue this.again = function() { recorder.connect(context.destination }); //Obter o arquivo de áudio this.getBlob = function () { this.stop(); ; }; //Reprodução this.play = function (audio) { audio.src = window.URL.createObjectURL(this.getBlob() }); { var fd = new FormData(); ) { retorno de chamada ('carregando', e }, falso); xhr.addEventListener ('carregar', função (e) { callback('ok', e }, falso); xhr.addEventListener('erro', função (e) { retorno de chamada('erro', e); }, falso); (e) { retorno de chamada ('cancelar', e }, falso } xhr.open('POST', xhr.send(fd }); //Coleta de áudio recorder.onaudioprocess = function (e) { audioData.input(e.inputBuffer.getChannelData(0)); //record(e.inputBuffer.getChannelData(0) }); .throwError = função (mensagem) { lançar nova função () { this.toString = função () { retornar mensagem };}; //Se a gravação é suportada HZRecorder.canRecording = (navigator.getUserMedia != null); //Obter o gravador 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('Não foi possível gravar, verifique o status do dispositivo'); } }});
O acima já pode atender à maioria das necessidades. Mas precisamos ser compatíveis com o lado do pad. Temos vários problemas com nosso bloco que precisam ser resolvidos.
Abaixo estão soluções para ambos os problemas.
Etapa 3A seguir está uma solução para implementar o formato de gravação como mp3 e window.URL.createObjectURL para passar dados de blob e relatar um erro no lado do pad.
1. Modifique o código do objeto audioData em HZRecorder. E apresente um arquivo js lamejs.js de uma grande pessoa na Internet
const lame = new lamejs();let audioData = { samplesMono: null, maxSamples: 1152, mp3Encoder: new lame.Mp3Encoder (1, context.sampleRate || 44100, config.bitRate || 128), dataBuffer: [], tamanho : 0, // Gravando buffer de comprimento do arquivo: [], // Buffer de gravação inputSampleRate: context.sampleRate, // Taxa de amostragem de entrada inputSampleBits: 16, // Entrada de dígitos de amostra 8, 16 outputSampleRate: config.sampleRate, // Taxa de amostragem de saída outputSampleBits: config.sampleBits, // Saída de dígitos de amostra 8, 16 convertBuffer: function(arrayBuffer) { deixe dados = novo Float32Array(arrayBuffer); let out = new Int16Array(arrayBuffer.length); this.floatTo16BitPCM(dados, saída }, floatTo16BitPCM: function(input, output) { for (deixe i = 0; i <entrada. comprimento; i++) { deixe s = Math.max(-1, Math.min(1, entrada[i])); = s < 0 ? s * 0x8000 : s * 0x7fff; } }, anexarToBuffer: function(mp3Buf) { this.dataBuffer.push(new Int8Array(mp3Buf) }, codificar: function(arrayBuffer) { this.samplesMono = this. .convertBuffer(arrayBuffer); deixe restante = this.samplesMono.length; (deixe i = 0; restante >= 0; i += this.maxSamples) { deixe left = this.samplesMono.subarray(i, i + this.maxSamples); .appendToBuffer(mp3buf); restante -= this.maxSamples; this.appendToBuffer(this.mp3Encoder.flush()); return new Blob(this.dataBuffer, { tipo: 'audio/mp3' } }, entrada: function(data) { this.buffer.push(new Float32Array( data)); this.size += data.length }, compress: function() { // Mesclar compactação // Mesclar let data = new Float32Array(this.size); let offset = 0 for (let i = 0; i < this.buffer.length; i++) { data.set(this.buffer[i], offset offset += this.buffer); [i].length; } // Compressão let length = parseInt(this.inputSampleRate / this.outputSampleRate, 10); Float32Array(comprimento deixe índice = 0; deixe j = 0; enquanto (índice <comprimento) { resultado[índice] = dados[j]; { deixe sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); this.oututSampleBits); let bytes = this.compress(); let dataLength = bytes.length * (sampleBits / 8); 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 do arquivo de troca de recursos writeString('RIFF'); número do arquivo, ou seja, tamanho do arquivo - 8 data.setUint32(offset, 36 + dataLength, true); offset += 4; // sinalizador de arquivo WAV writeString('WAVE'); // Sinalizador de formato de onda writeString('fmt '); offset += 4; // Filtrar bytes, geralmente 0x10 = 16 data.setUint32(offset, 16, true offset += 4); data) data.setUint16(offset, 1, true); offset += 2; // Número do canal data.setUint16(offset, channelCount, true); += 2; // Taxa de amostragem, o número de amostras por segundo, indicando a velocidade de reprodução de cada canal data.setUint32(offset, sampleRate, true); // Taxa de transmissão de dados da forma de onda (número médio de bytes); por segundo) Mono × bits de dados por segundo × bits de dados por amostra / 8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true offset += 4; Número de bytes de ajuste rápido de dados ocupados por um canal mono de amostra Number data.setUint16(offset, sampleBits, true); // Identificador de dados writeString('data'); dados amostrados, ou seja, tamanho total dos dados - 44 data.setUint32(offset, dataLength, true); offset += 4; // Escreve dados de amostra if (sampleBits === 8) { for (let i = 0; i < bytes.length; i++, offset++) { const s = Math.max(-1, Math.min(1, bytes[i])); 0x7fff; val = parseInt(255 / (65535 / (val + 32768)), 10); data.setInt8(offset, val, true); , deslocamento += 2) { const s = Math.max(-1, Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true } } return new Blob([dados], { tipo: 'audio/wav' } }});
2. Modifique o método de chamada da coleção de áudio do HZRecord.
// Coleta de áudio recorder.onaudioprocess = function(e) { audioData.encode(e.inputBuffer.getChannelData(0));};
3. Método getBlob do HZRecord.
this.getBlob = function() { this.stop(); return audioData.finish();};
4. Método de reprodução do HZRecord. Converter blob em base64url.
this.play = function(func) { readBlobAsDataURL(this.getBlob(), func);};function readBlobAsDataURL(dados, retorno de chamada) { let fileReader = new fileReader.onload = function(e) { callback(e) .target.result); };fileReader.readAsDataURL(dados);}
Até agora, os dois problemas acima foram resolvidos.
Etapa 4Aqui apresentamos principalmente como criar efeitos dinâmicos durante a gravação. Um dos nossos requisitos de animação é:
De acordo com o volume recebido, um arco circular é expandido dinamicamente.
//Crie um nó analisador e obtenha dados de tempo e frequência de áudio const analyzer = context.createAnalyser();audioInput.connect(analyser);const inputAnalyser = new Uint8Array(1);const wrapEle = $this.refs['wrap'] ; deixe ctx = wrapEle.getContext('2d');const largura = wrapEle.width;const altura = wrapEle.height;const center = { x: largura / 2, y: altura / 2}; função drawArc (ctx, cor, x, y, raio, startAngle, endAngle) { ctx.beginPath(); ;ctx.strokeStyle = cor; (Math.PI * endAngle) / 180); ctx.stroke();}(function drawSpectrum() { analyser.getByteFrequencyData(inputAnalyser); // Obtém dados do domínio de frequência ctx.clearRect(0, 0, width, height); // Desenha linhas para (let i = 0; i < 1; i++) { let value = inputAnalyser[i] / 3; <===Obter dados let cores = []; if (valor <= 16) { cores = ['#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4', '#e4e4e4', '# e4e4e4']; } else if (valor <= 32) { cores = ['#f5A631', '#f5A631', '#f5A631', '#f5A631', '#e4e4e4', '#e4e4e4'] } else { cores = ['#f5A631', '#f5A631', '#f5A631', '#f5A631 ', '#f5A631', '#f5A631']; } drawArc(ctx, cores[0], center.x, center.y, 52 + 16, -30, 30); , 52 + 16, 150, 210); drawArc(ctx, cores[2], centro.x, centro.y, 52 + 32, -22,5, 22,5); drawArc(ctx, cores[3], centro.x, centro.y, 52 + 32, 157,5, 202,5); + 48, -13, 13); drawArc(ctx, cores[5], centro.x, centro.y, 52 + 48, 167, 193); // Solicita o próximo quadro requestAnimationFrame(drawSpectrum);})();Fim do destino
Neste ponto, uma solução completa de função de gravação HTML5 foi concluída. Se houver algo que precise ser adicionado, deixe uma mensagem se houver algo irracional.
ps: lamejs pode consultar este github
O texto acima é todo o conteúdo deste artigo. Espero que seja útil para o estudo de todos. Também espero que todos apoiem a Rede VeVb Wulin.