Last active
March 15, 2019 18:48
-
-
Save mperezguendulain/3ef390647b274916618cfe0521a9f6bd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable, EventEmitter } from '@angular/core'; | |
import * as RecordRTC from 'recordrtc'; | |
import * as Lamejs from 'lamejs'; | |
navigator.getUserMedia = | |
navigator['mediaDevices'] && navigator['mediaDevices'].getUserMedia ? | |
navigator['mediaDevices'].getUserMedia : ( navigator['getUserMedia'] || navigator['webkitGetUserMedia'] || navigator['mozGetUserMedia'] || navigator['msGetUserMedia'] ); | |
enum Time { | |
Segundo = 1000, | |
Minuto = 60 * Time.Segundo, | |
Hora = 60 * Time.Minuto | |
} | |
@Injectable() | |
export class Audio2RecordService { | |
private currentAudioStream: MediaStream = null;; | |
private recordRTC: any = null; | |
private recStoppedEvent: EventEmitter<any> = new EventEmitter<string>(); | |
private microphone: any = null; | |
private audioContext: any = null; | |
private mp3encoder; | |
private mp3Data = []; | |
private lameOptions = { | |
channels: 1, //1 for mono or 2 for stereo | |
sampleRate: 44100, //44.1khz (normal mp3 samplerate) | |
kbps: 128 //encode 128kbps mp3 | |
}; | |
// Processor buffer size | |
private readonly BUFFER_SIZE = [256, 512, 1024, 2048, 4096, 8192, 16384]; | |
// MP3 bit ratenumberOfAudioChannels: 1 | |
private readonly BIT_RATE = [64, 80, 96, 112, 128, 160, 192, 224, 256, 320]; | |
constructor() { | |
this.mp3encoder = new Lamejs.Mp3Encoder(this.lameOptions.channels, this.lameOptions.sampleRate, this.lameOptions.kbps); | |
} | |
/** | |
* Inicia el proceso de grabación. | |
* | |
* @param maxRecDuration Tiempo maximo de grabación | |
*/ | |
startRecording( maxRecDuration?: number ): Promise<void> { | |
this.mp3Data = []; | |
return new Promise( | |
( resolve, reject ) => { | |
this.initMicrophone() | |
.then( | |
audioStream => { | |
this.currentAudioStream = audioStream; | |
let options = { | |
recorderType: RecordRTC.StereoAudioRecorder, | |
type: 'audio', | |
mimeType: 'audio/wav', | |
numberOfAudioChannels: 1, | |
bufferSize: this.BUFFER_SIZE[4], | |
bitsPerSecond: this.BIT_RATE[4], | |
sampleRate: 44100, | |
timeSlice: 1 * Time.Segundo, | |
ondataavailable: this.processAudioSlice.bind(this) | |
}; | |
this.recordRTC = RecordRTC(audioStream, options); | |
// Auto stop recording after maxRecDuration seconds | |
if ( maxRecDuration ) | |
this.recordRTC.setRecordingDuration(maxRecDuration).onRecordingStopped(this.emitRecStoppedEvent.bind(this)); | |
this.recordRTC.startRecording(); | |
resolve(); | |
}, error => { | |
reject(error); | |
} | |
); | |
} | |
); | |
} | |
/** | |
* Para la grabación. | |
*/ | |
stopRecording(): Promise<Blob> { | |
return new Promise( | |
(resolve, reject) => { | |
this.recordRTC.stopRecording( | |
recURL => { | |
this.stopRecordingProcess(false); | |
let mp3Blob = this.getMP3File() | |
resolve(mp3Blob); | |
} | |
); | |
} | |
); | |
} | |
/** | |
* Emite una señal indicando que ha alcanzado el tiempo maximo de grabación. | |
* | |
* @param recURL | |
*/ | |
emitRecStoppedEvent(recURL: string){ | |
this.recStoppedEvent.emit(recURL); | |
} | |
/** | |
* Cancela la grabación. | |
*/ | |
cancelRecording() { | |
this.recordRTC.stopRecording( | |
recURL => this.stopRecordingProcess(false) | |
); | |
} | |
/** | |
* Inicia el proceso de paro de la grabación. | |
*/ | |
stopRecordingProcess(finish) { | |
this.microphone.disconnect(); | |
this.recordRTC.clearRecordedData(); | |
this.recordRTC = null; | |
if (!finish && this.microphone) | |
this.endMicrophone(); | |
} | |
/** | |
* Finaliza el microfono. | |
*/ | |
endMicrophone() { | |
this.currentAudioStream.getAudioTracks().forEach( track => track.stop() ); | |
this.currentAudioStream = null; | |
this.microphone = null; | |
this.audioContext.close(); | |
this.audioContext = null; | |
} | |
/** | |
* Codifica un archivo binario a mp3 | |
* | |
* @param chunkBlob Pedazo de archivo binario a codificar | |
*/ | |
processAudioSlice(chunkBlob: Blob) { | |
this.convBlobToBufferArray(chunkBlob) | |
.then( | |
arrayBuffer => this.encodeBufferChunkToMP3(arrayBuffer) | |
); | |
} | |
/** | |
* Codifica un pedazo de grabación a MP3. | |
* | |
* @param audioData Información a codificar | |
*/ | |
encodeBufferChunkToMP3(audioData: ArrayBuffer) { | |
let samples = new Int16Array(audioData); | |
let remaining = samples.length; | |
let sampleBlockSize = 1152; | |
for (let i = 0; i < samples.length; i += sampleBlockSize) { | |
let sampleChunk = samples.subarray(i, i + sampleBlockSize); | |
var mp3buf = this.mp3encoder.encodeBuffer(sampleChunk); | |
if (mp3buf.length > 0) { | |
this.mp3Data.push(new Int8Array(mp3buf)); | |
} | |
} | |
} | |
/** | |
* Obtiene un archivo MP3 | |
*/ | |
getMP3File(): Blob { | |
let mp3buf = this.mp3encoder.flush(); //finish writing mp3 | |
if (mp3buf.length > 0) | |
this.mp3Data.push(new Int8Array(mp3buf)); | |
let mp3Blob = new Blob(this.mp3Data, {type: 'audio/mp3'}); | |
window.open(window.URL.createObjectURL(mp3Blob)); | |
return mp3Blob; | |
} | |
/** | |
* Convierte un objeto Blob a ArrayBuffer | |
* | |
* @param blob Información binaria | |
*/ | |
convBlobToBufferArray(blob: Blob): Promise<ArrayBuffer> { | |
return new Promise( | |
( resolve, reject ) => { | |
var fileReader = new FileReader(); | |
fileReader.onload = ( event: any ) => { | |
let arrayBuffer = event.target.result; | |
resolve(arrayBuffer); | |
}; | |
fileReader.readAsArrayBuffer(blob); | |
} | |
) | |
} | |
/** | |
* Inicializa el microfono. | |
*/ | |
initMicrophone(): Promise<any> { | |
return new Promise( | |
(resolve, reject) => { | |
if (this.microphone == null) { | |
var AudioContext = window['AudioContext'] || window['webkitAudioContext']; | |
this.audioContext = new AudioContext(); | |
if (this.audioContext.createScriptProcessor == null) | |
this.audioContext.createScriptProcessor = this.audioContext['createJavaScriptNode']; | |
if (navigator['mediaDevices'] && navigator['mediaDevices'].getUserMedia) { | |
navigator.getUserMedia.call(navigator['mediaDevices'], { audio: true }) | |
.then( | |
stream => { | |
this.microphone = this.audioContext.createMediaStreamSource(stream); | |
resolve(stream); | |
} | |
).catch( | |
error => { | |
console.error("Error: No se puede iniciar el microfono"); | |
reject(error); | |
} | |
); | |
} else { | |
navigator.getUserMedia( | |
{ audio: true }, | |
stream => { | |
this.microphone = this.audioContext.createMediaStreamSource(stream); | |
resolve(stream); | |
}, | |
error => { | |
console.error("Error: No se puede iniciar el microfono"); | |
reject(error); | |
} | |
); | |
} | |
} | |
} | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Angular service that records microphone audio with the help of: https://github.com/muaz-khan/RecordRTC and encodes to mp3 with the help of: https://github.com/zhuker/lamejs
I have the following problem: Every second you hear a click on the encoded audio..
Can you help me please? I have only one instance of Mp3Encoder