|
@@ -16,14 +16,16 @@
|
|
|
* along with Flutter-Sound. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
|
|
|
|
-const RECORDER_VERSION = '8.2.0'
|
|
|
+// const RECORDER_VERSION = '8.2.1'
|
|
|
+const RECORDER_VERSION = "8.2.0";
|
|
|
|
|
|
const IS_RECORDER_PAUSED = 1;
|
|
|
const IS_RECORDER_RECORDING = 2;
|
|
|
const IS_RECORDER_STOPPED = 0;
|
|
|
|
|
|
-function newRecorderInstance(aCallback, callbackTable) { return new FlutterSoundRecorder(aCallback, callbackTable); }
|
|
|
-
|
|
|
+function newRecorderInstance(aCallback, callbackTable) {
|
|
|
+ return new FlutterSoundRecorder(aCallback, callbackTable);
|
|
|
+}
|
|
|
|
|
|
const CB_updateRecorderProgress = 0;
|
|
|
const CB_recordingData = 1;
|
|
@@ -36,394 +38,521 @@ const CB_closeRecorderCompleted = 7;
|
|
|
const CB_recorder_log = 8;
|
|
|
|
|
|
class FlutterSoundRecorder {
|
|
|
- static newInstance(aCallback, callbackTable) { return new FlutterSoundRecorder(aCallback, callbackTable); }
|
|
|
-
|
|
|
- constructor(aCallback, callbackTable) {
|
|
|
- this.callback = aCallback;
|
|
|
- this.callbackTable = callbackTable;
|
|
|
- this.subscriptionDuration = 0;
|
|
|
- this.timerId = null;
|
|
|
- this.deltaTime = 0;
|
|
|
- this.currentRecordPath = '';
|
|
|
- this.localObjects = [];
|
|
|
- this.instanceNo = instanceNumber;
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'Instance Number : ' + this.instanceNo.toString())
|
|
|
- ++instanceNumber;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- initializeFlautoRecorder(focus, category, mode, audioFlags, device) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'initializeFlautoRecorder');
|
|
|
- this.callbackTable[CB_openRecorderCompleted](this.callback, IS_RECORDER_STOPPED, true);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- deleteObjects() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:---> deleteObjects ');
|
|
|
- for (var url in this.localObjects) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'deleteRecord : ' + url);
|
|
|
- this.deleteRecord(url);
|
|
|
- }
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:<--- deleteObjects ');
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- releaseFlautoRecorder() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:---> releaseFlautoRecorder');
|
|
|
- this.stop();
|
|
|
- this.deleteObjects();
|
|
|
- this.localObjects = [];
|
|
|
-
|
|
|
- this.callbackTable[CB_closeRecorderCompleted](this.callback, IS_RECORDER_STOPPED, true);
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:<--- releaseFlautoRecorder');
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- setAudioFocus(focus, category, mode, audioFlags, device) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'setAudioFocus');
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- isEncoderSupported(codec) {
|
|
|
- /*
|
|
|
+ static newInstance(aCallback, callbackTable) {
|
|
|
+ return new FlutterSoundRecorder(aCallback, callbackTable);
|
|
|
+ }
|
|
|
+
|
|
|
+ constructor(aCallback, callbackTable) {
|
|
|
+ this.callback = aCallback;
|
|
|
+ this.callbackTable = callbackTable;
|
|
|
+ this.subscriptionDuration = 0;
|
|
|
+ this.timerId = null;
|
|
|
+ this.deltaTime = 0;
|
|
|
+ this.currentRecordPath = "";
|
|
|
+ this.localObjects = [];
|
|
|
+ this.instanceNo = instanceNumber;
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "Instance Number : " + this.instanceNo.toString()
|
|
|
+ );
|
|
|
+ ++instanceNumber;
|
|
|
+ }
|
|
|
+
|
|
|
+ initializeFlautoRecorder(focus, category, mode, audioFlags, device) {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "initializeFlautoRecorder"
|
|
|
+ );
|
|
|
+ this.callbackTable[CB_openRecorderCompleted](
|
|
|
+ this.callback,
|
|
|
+ IS_RECORDER_STOPPED,
|
|
|
+ true
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ deleteObjects() {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:---> deleteObjects "
|
|
|
+ );
|
|
|
+ for (var url in this.localObjects) {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "deleteRecord : " + url
|
|
|
+ );
|
|
|
+ this.deleteRecord(url);
|
|
|
+ }
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:<--- deleteObjects "
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ releaseFlautoRecorder() {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:---> releaseFlautoRecorder"
|
|
|
+ );
|
|
|
+ this.stop();
|
|
|
+ this.deleteObjects();
|
|
|
+ this.localObjects = [];
|
|
|
+
|
|
|
+ this.callbackTable[CB_closeRecorderCompleted](
|
|
|
+ this.callback,
|
|
|
+ IS_RECORDER_STOPPED,
|
|
|
+ true
|
|
|
+ );
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:<--- releaseFlautoRecorder"
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ setAudioFocus(focus, category, mode, audioFlags, device) {
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "setAudioFocus");
|
|
|
+ }
|
|
|
+
|
|
|
+ isEncoderSupported(codec) {
|
|
|
+ /*
|
|
|
for (var i in mime_types)
|
|
|
{
|
|
|
}
|
|
|
*/
|
|
|
- var r = MediaRecorder.isTypeSupported(mime_types[codec]);
|
|
|
- if (r)
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'mime_types[codec] encoder is supported');
|
|
|
- else
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'mime_types[codec] encoder is NOT supported');
|
|
|
- return r;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- setSubscriptionDuration(duration) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'setSubscriptionDuration');
|
|
|
- this.subscriptionDuration = duration;
|
|
|
- if (this.mediaRecorder != null)
|
|
|
- this.startTimer();
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- _deleteRecord(aPath,) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'deleteRecord: ' + aPath);
|
|
|
- if ((aPath == null) || (aPath == '')) {
|
|
|
- path = lasturl;
|
|
|
- } else {
|
|
|
- var path = aPath;
|
|
|
- }
|
|
|
- var myStorage;
|
|
|
- if (path.substring(0, 1) == '/') {
|
|
|
- myStorage = window.localStorage;
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'localStorage');
|
|
|
- } else {
|
|
|
- myStorage = window.sessionStorage;
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'sessionStorage');
|
|
|
- }
|
|
|
- var oldUrl = myStorage.getItem(path);
|
|
|
- if (oldUrl != null && oldUrl != '') {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'Deleting object : ' + oldUrl.toString());
|
|
|
- URL.revokeObjectURL(oldUrl);
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- deleteRecord(path) {
|
|
|
- this._deleteRecord(path);
|
|
|
-
|
|
|
- var found = this.localObjects.findIndex(element => element == path);
|
|
|
- if (found != null && found >= 0) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, "Found : " + found);
|
|
|
- this.localObjects[found] = null;
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- setRecordURL(aPath, newUrl) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'setRecordUrl: ' + aPath + ' <- ' + newUrl);
|
|
|
- var path = aPath;
|
|
|
- var myStorage;
|
|
|
- if ((path == null) || (path == '')) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- if (path.substring(0, 1) == '/') {
|
|
|
- myStorage = window.localStorage;
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'localStorage');
|
|
|
- } else {
|
|
|
- myStorage = window.sessionStorage;
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'sessionStorage');
|
|
|
- }
|
|
|
- var oldUrl = myStorage.getItem(path);
|
|
|
- if (oldUrl != null && oldUrl != '') {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'Deleting object ' + ' : ' + oldUrl.toString());
|
|
|
- URL.revokeObjectURL(oldUrl);
|
|
|
- } else {
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- lastUrl = aPath;
|
|
|
-
|
|
|
- myStorage.setItem(path, newUrl);
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, '<--- setRecordURL ( ' + path + ' ) : ' + newUrl);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- getRecordURL(aPath,) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, '---> getRecordURL : ' + aPath);
|
|
|
- var r = getRecordURL(aPath);
|
|
|
- if (r == null)
|
|
|
- r = ''; // stopRecorder does not like a null
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, '<--- getRecordURL :' + r);
|
|
|
- return r;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- async startRecorder(path, sampleRate, numChannels, bitRate, codec, toStream, audioSource) {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'startRecorder');
|
|
|
- var me = this;
|
|
|
- this.currentRecordPath = path;
|
|
|
- var chunks = [];
|
|
|
- var mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
|
|
|
- me.mediaStream = mediaStream;
|
|
|
- var options =
|
|
|
- {
|
|
|
- audioBitsPerSecond: 160000,
|
|
|
- mimeType: mime_types[codec]
|
|
|
- }
|
|
|
- me.audioCtx = new AudioContext();
|
|
|
- var mediaRecorder = new MediaRecorder(mediaStream,options);
|
|
|
- me.mediaRecorder = mediaRecorder;
|
|
|
- if (toStream) // not yet implemented !
|
|
|
- mediaRecorder.start(30); // 30 milliseconds for a chunk
|
|
|
- else
|
|
|
- mediaRecorder.start();
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, "recorder started : " + mediaRecorder.state);
|
|
|
- mediaRecorder.ondataavailable = function (e) {
|
|
|
- if (e.data) {
|
|
|
- if (toStream) // not yet implemented !
|
|
|
- {
|
|
|
- me.callbackTable[CB_recordingData](me.callback, e.data);
|
|
|
-
|
|
|
- }
|
|
|
- if (path != null && path != '') {
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, 'On data available : ' + e.data.constructor.name);
|
|
|
- const fileReader = new FileReader();
|
|
|
- fileReader.onload = function() {
|
|
|
- // 读取完成后的回调函数
|
|
|
- const audioContext = new AudioContext();
|
|
|
- audioContext.decodeAudioData(fileReader.result, function(audioBuffer) {
|
|
|
- chunks.push(audioBuffer);
|
|
|
- var dataBuffer = [];
|
|
|
- var audioBufferData = audioBuffer.getChannelData(0);
|
|
|
- const data = new Float32Array(audioBufferData);
|
|
|
- const out = new Int16Array(audioBufferData.length);
|
|
|
- for (let i = 0; i < data.length; i++) {
|
|
|
- const s = Math.max(-1, Math.min(1, data[i]));
|
|
|
- out[i] = (s < 0 ? s * 0x8000 : s * 0x7FFF);
|
|
|
- }
|
|
|
- this.samplesMono = out;
|
|
|
- this.maxSamples = 1152;
|
|
|
- let remaining = this.samplesMono.length;
|
|
|
-
|
|
|
- var mp3Encoder = new lamejs.Mp3Encoder(1,44100,128);
|
|
|
- for (let i = 0; remaining >= 0; i += this.maxSamples) {
|
|
|
- const left = this.samplesMono.subarray(i, i + this.maxSamples);
|
|
|
- const mp3buffer = mp3Encoder.encodeBuffer(left);
|
|
|
- dataBuffer.push(new Int8Array(mp3buffer));
|
|
|
- remaining -= this.maxSamples;
|
|
|
- }
|
|
|
- var blob = new Blob(dataBuffer, { type: 'audio/mp3' });
|
|
|
- var url = URL.createObjectURL(blob);
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, 'Instance Number : ' + me.instanceNo.toString())
|
|
|
- me.setRecordURL(path, url);
|
|
|
- var found = me.localObjects.findIndex(element => element == path);
|
|
|
- if (found != null && found >= 0) {
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, "Found : " + found);
|
|
|
- me.localObjects[found] = path;
|
|
|
- } else {
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, "NOT FOUND! : " + path);
|
|
|
- me.localObjects.push(path);
|
|
|
- }
|
|
|
- chunks = null;///[];
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, 'recorder stopped');
|
|
|
- me.mediaRecorder = null;
|
|
|
- me.callbackTable[CB_stopRecorderCompleted](me.callback, IS_RECORDER_STOPPED, true, me.getRecordURL(path));
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '<--- mediaRecorder onstop');
|
|
|
- });
|
|
|
- }
|
|
|
- fileReader.readAsArrayBuffer(e.data);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- mediaRecorder.onstart = function (e) {
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '---> mediaRecorder OnStart');
|
|
|
- me.deltaTime = 0;
|
|
|
- me.startTimer();
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '<---mediaRecorder OnStart : ' + me.mediaRecorder.state);
|
|
|
- me.callbackTable[CB_startRecorderCompleted](me.callback, IS_RECORDER_RECORDING, true);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- mediaRecorder.onerror = function (e) {
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, "mediaRecorder OnError : " + e.error);
|
|
|
- me.stopRecorder()
|
|
|
- }
|
|
|
-
|
|
|
- mediaRecorder.onpause = function (e) {
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '---> mediaRecorder onpause');
|
|
|
- me.callbackTable[CB_pauseRecorderCompleted](me.callback, IS_RECORDER_PAUSED, true);
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '<--- mediaRecorder onpause');
|
|
|
- }
|
|
|
-
|
|
|
- mediaRecorder.onresume = function (e) {
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '---> mediaRecorder onresume');
|
|
|
- me.callbackTable[CB_resumeRecorderCompleted](me.callback, IS_RECORDER_RECORDING, true);
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '<--- mediaRecorder onresume');
|
|
|
- }
|
|
|
-
|
|
|
- mediaRecorder.onstop = async function (e) {
|
|
|
-
|
|
|
- me.callbackTable[CB_recorder_log](me.callback, DBG, '---> mediaRecorder onstop');
|
|
|
- var blob = new Blob(chunks, { 'type': mime_types[codec] });
|
|
|
- //const audioContext = new AudioContext();
|
|
|
- // var reader = new FileReader();
|
|
|
- // reader.onload = async function()
|
|
|
- // {
|
|
|
- // var buf = new Uint8Array(this.result);
|
|
|
- // const audioBuffer = await audioContext.decodeAudioData(buf.buffer);
|
|
|
- // // Import the lamejs library
|
|
|
- // var audioBufferData = audioBuffer.getChannelData(0);
|
|
|
- // const data = new Float32Array(audioBufferData);
|
|
|
- // const out = new Int16Array(audioBufferData.length);
|
|
|
- // for (let i = 0; i < data.length; i++) {
|
|
|
- // const s = Math.max(-1, Math.min(1, data[i]));
|
|
|
- // out[i] = (s < 0 ? s * 0x8000 : s * 0x7FFF);
|
|
|
- // }
|
|
|
- // this.samplesMono = out;
|
|
|
- // this.maxSamples = 1152;
|
|
|
- // let remaining = this.samplesMono.length;
|
|
|
-
|
|
|
- // var mp3Encoder = new lamejs.Mp3Encoder(1,44100,128);
|
|
|
- // for (let i = 0; remaining >= 0; i += this.maxSamples) {
|
|
|
- // const left = this.samplesMono.subarray(i, i + this.maxSamples);
|
|
|
- // const mp3buffer = mp3Encoder.encodeBuffer(left);
|
|
|
- // this.dataBuffer.push(mp3buffer);
|
|
|
- // remaining -= this.maxSamples;
|
|
|
- // }
|
|
|
- // blob = new Blob(this.dataBuffer, { type: 'audio/mp3' });
|
|
|
- // };
|
|
|
- // reader.readAsArrayBuffer(blob);
|
|
|
- var url = URL.createObjectURL(blob);
|
|
|
- //me.callbackTable[CB_recorder_log](me.callback, DBG, 'Instance Number : ' + me.instanceNo.toString())
|
|
|
-
|
|
|
- me.setRecordURL(path, url);
|
|
|
-
|
|
|
- // var found = me.localObjects.findIndex(element => element == path);
|
|
|
- // if (found != null && found >= 0) {
|
|
|
- // me.callbackTable[CB_recorder_log](me.callback, DBG, "Found : " + found);
|
|
|
- // me.localObjects[found] = path;
|
|
|
- // } else {
|
|
|
- // me.callbackTable[CB_recorder_log](me.callback, DBG, "NOT FOUND! : " + path);
|
|
|
- // me.localObjects.push(path);
|
|
|
- // }
|
|
|
- // chunks = null;///[];
|
|
|
- // me.callbackTable[CB_recorder_log](me.callback, DBG, 'recorder stopped');
|
|
|
- // me.mediaRecorder = null;
|
|
|
- // me.callbackTable[CB_stopRecorderCompleted](me.callback, IS_RECORDER_STOPPED, true, me.getRecordURL(path));
|
|
|
- // me.callbackTable[CB_recorder_log](me.callback, DBG, '<--- mediaRecorder onstop');
|
|
|
- }
|
|
|
- //});
|
|
|
- }
|
|
|
-
|
|
|
- stop() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:---> stop()');
|
|
|
- this.stopTimer();
|
|
|
- if (this.mediaRecorder != null) {
|
|
|
- this.mediaRecorder.stop();
|
|
|
- this.mediaRecorder = null;
|
|
|
- }
|
|
|
- if (this.mediaStream != null) {
|
|
|
- this.mediaStream.getTracks().forEach(track => track.stop());
|
|
|
- this.mediaStream = null;
|
|
|
- }
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:<--- stop()');
|
|
|
- }
|
|
|
-
|
|
|
- stopRecorder() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:---> stopRecorder');
|
|
|
- this.stopTimer();
|
|
|
- if (this.mediaRecorder != null) {
|
|
|
- this.mediaRecorder.stop();
|
|
|
- } else {
|
|
|
- this.callbackTable[CB_stopRecorderCompleted](this.callback, IS_RECORDER_STOPPED, /*false*/true, this.getRecordURL(this.currentRecordPath));
|
|
|
- }
|
|
|
- this.mediaRecorder = null;
|
|
|
-
|
|
|
- if (this.mediaStream != null) {
|
|
|
- this.mediaStream.getTracks().forEach(track => track.stop());
|
|
|
- this.mediaStream = null;
|
|
|
- }
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, "JS:<--- stopRecorder");
|
|
|
+ var r = MediaRecorder.isTypeSupported(mime_types[codec]);
|
|
|
+ if (r)
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "mime_types[codec] encoder is supported"
|
|
|
+ );
|
|
|
+ else
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "mime_types[codec] encoder is NOT supported"
|
|
|
+ );
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ setSubscriptionDuration(duration) {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "setSubscriptionDuration"
|
|
|
+ );
|
|
|
+ this.subscriptionDuration = duration;
|
|
|
+ if (this.mediaRecorder != null) this.startTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ _deleteRecord(aPath) {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "deleteRecord: " + aPath
|
|
|
+ );
|
|
|
+ if (aPath == null || aPath == "") {
|
|
|
+ path = lasturl;
|
|
|
+ } else {
|
|
|
+ var path = aPath;
|
|
|
+ }
|
|
|
+ var myStorage;
|
|
|
+ if (path.substring(0, 1) == "/") {
|
|
|
+ myStorage = window.localStorage;
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "localStorage");
|
|
|
+ } else {
|
|
|
+ myStorage = window.sessionStorage;
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "sessionStorage");
|
|
|
+ }
|
|
|
+ var oldUrl = myStorage.getItem(path);
|
|
|
+ if (oldUrl != null && oldUrl != "") {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "Deleting object : " + oldUrl.toString()
|
|
|
+ );
|
|
|
+ URL.revokeObjectURL(oldUrl);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ deleteRecord(path) {
|
|
|
+ this._deleteRecord(path);
|
|
|
+
|
|
|
+ var found = this.localObjects.findIndex((element) => element == path);
|
|
|
+ if (found != null && found >= 0) {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "Found : " + found
|
|
|
+ );
|
|
|
+ this.localObjects[found] = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ setRecordURL(aPath, newUrl) {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "setRecordUrl: " + aPath + " <- " + newUrl
|
|
|
+ );
|
|
|
+ var path = aPath;
|
|
|
+ var myStorage;
|
|
|
+ if (path == null || path == "") {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (path.substring(0, 1) == "/") {
|
|
|
+ myStorage = window.localStorage;
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "localStorage");
|
|
|
+ } else {
|
|
|
+ myStorage = window.sessionStorage;
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "sessionStorage");
|
|
|
+ }
|
|
|
+ var oldUrl = myStorage.getItem(path);
|
|
|
+ if (oldUrl != null && oldUrl != "") {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "Deleting object " + " : " + oldUrl.toString()
|
|
|
+ );
|
|
|
+ URL.revokeObjectURL(oldUrl);
|
|
|
+ } else {
|
|
|
+ }
|
|
|
+
|
|
|
+ lastUrl = aPath;
|
|
|
+
|
|
|
+ myStorage.setItem(path, newUrl);
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "<--- setRecordURL ( " + path + " ) : " + newUrl
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ getRecordURL(aPath) {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "---> getRecordURL : " + aPath
|
|
|
+ );
|
|
|
+ var r = getRecordURL(aPath);
|
|
|
+ if (r == null) r = ""; // stopRecorder does not like a null
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "<--- getRecordURL :" + r
|
|
|
+ );
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ async startRecorder(
|
|
|
+ path,
|
|
|
+ sampleRate,
|
|
|
+ numChannels,
|
|
|
+ bitRate,
|
|
|
+ codec,
|
|
|
+ toStream,
|
|
|
+ audioSource
|
|
|
+ ) {
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "startRecorder");
|
|
|
+ var me = this;
|
|
|
+ this.currentRecordPath = path;
|
|
|
+ var chunks = [];
|
|
|
+ var mediaStream;
|
|
|
+ /// 判断 path 是否包含 @,如果包含则表示指定了麦克风设备
|
|
|
+ if (path.indexOf("@") > 0) {
|
|
|
+ var deviceId = path.split("@")[1].split(".")[0];
|
|
|
+ mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
|
+ audio: { deviceId: { ideal: deviceId } },
|
|
|
+ video: false,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
|
+ audio: true,
|
|
|
+ video: false,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ me.mediaStream = mediaStream;
|
|
|
+ const audioContext = new AudioContext();
|
|
|
+ const _audioSource = audioContext.createMediaStreamSource(mediaStream);
|
|
|
+ const analyser = audioContext.createAnalyser();
|
|
|
+ // todo: review if this values are right (set to mimic a behaviour closest to that of Android)
|
|
|
+ analyser.fftSize = 512;
|
|
|
+ analyser.minDecibels = -110;
|
|
|
+ analyser.maxDecibels = 0;
|
|
|
+ analyser.smoothingTimeConstant = 0.4;
|
|
|
+ _audioSource.connect(analyser);
|
|
|
+ const volumes = new Uint8Array(analyser.frequencyBinCount);
|
|
|
+
|
|
|
+ this.getVolumeLevel = () => {
|
|
|
+ analyser.getByteFrequencyData(volumes);
|
|
|
+ let volumeSum = 0;
|
|
|
+ for (const volume of volumes) volumeSum += volume;
|
|
|
+ return volumeSum / volumes.length;
|
|
|
+ };
|
|
|
+ var options = {
|
|
|
+ audioBitsPerSecond: bitRate,
|
|
|
+ mimeType: mime_types[codec],
|
|
|
+ };
|
|
|
+
|
|
|
+ var mediaRecorder = new MediaRecorder(mediaStream, options);
|
|
|
+ me.mediaRecorder = mediaRecorder;
|
|
|
+ if (toStream)
|
|
|
+ // not yet implemented !
|
|
|
+ mediaRecorder.start(30); // 30 milliseconds for a chunk
|
|
|
+ else mediaRecorder.start();
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "recorder started : " + mediaRecorder.state
|
|
|
+ );
|
|
|
+
|
|
|
+ mediaRecorder.ondataavailable = function (e) {
|
|
|
+ if (e.data) {
|
|
|
+ if (toStream) {
|
|
|
+ // not yet implemented !
|
|
|
+ me.callbackTable[CB_recordingData](me.callback, e.data);
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
- pauseRecorder() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'pauseRecorder');
|
|
|
- this.mediaRecorder.pause();
|
|
|
- this.stopTimer();
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, "recorder paused : " + this.mediaRecorder.state);
|
|
|
-
|
|
|
+ if (path != null && path != "") {
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "On data available : " + e.data.constructor.name
|
|
|
+ );
|
|
|
+ chunks.push(e.data);
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
- resumeRecorder() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'resumeRecorder');
|
|
|
- this.mediaRecorder.resume();
|
|
|
- this.startTimer();
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, "recorder resumed : " + this.mediaRecorder.state);
|
|
|
- }
|
|
|
-
|
|
|
- startTimer() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'startTimer()');
|
|
|
- this.stopTimer();
|
|
|
- var me = this;
|
|
|
-
|
|
|
- if (this.subscriptionDuration > 0) {
|
|
|
- this.countDownDate = new Date().getTime();
|
|
|
- this.timerId = setInterval
|
|
|
- (
|
|
|
- function () {
|
|
|
- var now = new Date().getTime();
|
|
|
- var distance = now - me.countDownDate;
|
|
|
- me.callbackTable[CB_updateRecorderProgress](me.callback, me.deltaTime + distance, 0);
|
|
|
-
|
|
|
- },
|
|
|
- this.subscriptionDuration
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- stopTimer() {
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:---> stopTimer()');
|
|
|
- if (this.timerId != null) {
|
|
|
- clearInterval(this.timerId);
|
|
|
- var now = new Date().getTime();
|
|
|
- var distance = now - this.countDownDate;
|
|
|
- this.deltaTime += distance;
|
|
|
- this.timerId = null;
|
|
|
- }
|
|
|
- this.callbackTable[CB_recorder_log](this.callback, DBG, 'JS:<--- stopTimer()');
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ mediaRecorder.onstart = function (e) {
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "---> mediaRecorder OnStart"
|
|
|
+ );
|
|
|
+ me.deltaTime = 0;
|
|
|
+ me.startTimer();
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "<---mediaRecorder OnStart : " + me.mediaRecorder.state
|
|
|
+ );
|
|
|
+ me.callbackTable[CB_startRecorderCompleted](
|
|
|
+ me.callback,
|
|
|
+ IS_RECORDER_RECORDING,
|
|
|
+ true
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ mediaRecorder.onerror = function (e) {
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "mediaRecorder OnError : " + e.error
|
|
|
+ );
|
|
|
+ me.stopRecorder();
|
|
|
+ };
|
|
|
+
|
|
|
+ mediaRecorder.onpause = function (e) {
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "---> mediaRecorder onpause"
|
|
|
+ );
|
|
|
+ me.callbackTable[CB_pauseRecorderCompleted](
|
|
|
+ me.callback,
|
|
|
+ IS_RECORDER_PAUSED,
|
|
|
+ true
|
|
|
+ );
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "<--- mediaRecorder onpause"
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ mediaRecorder.onresume = function (e) {
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "---> mediaRecorder onresume"
|
|
|
+ );
|
|
|
+ me.callbackTable[CB_resumeRecorderCompleted](
|
|
|
+ me.callback,
|
|
|
+ IS_RECORDER_RECORDING,
|
|
|
+ true
|
|
|
+ );
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "<--- mediaRecorder onresume"
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ mediaRecorder.onstop = function (e) {
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "---> mediaRecorder onstop"
|
|
|
+ );
|
|
|
+ var blob;
|
|
|
+ if (path.indexOf("@") > 0) {
|
|
|
+ blob = new Blob(chunks, { type: mime_types[codec] });
|
|
|
+ } else {
|
|
|
+ encoder = new WavAudioEncoder(16000, 2);
|
|
|
+ encoder.encode(chunks);
|
|
|
+ blob = encoder.finish();
|
|
|
+ }
|
|
|
+ var url = URL.createObjectURL(blob);
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "Instance Number : " + me.instanceNo.toString()
|
|
|
+ );
|
|
|
+
|
|
|
+ me.setRecordURL(path, url);
|
|
|
+
|
|
|
+ var found = me.localObjects.findIndex((element) => element == path);
|
|
|
+ if (found != null && found >= 0) {
|
|
|
+ me.callbackTable[CB_recorder_log](me.callback, DBG, "Found : " + found);
|
|
|
+ me.localObjects[found] = path;
|
|
|
+ } else {
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "NOT FOUND! : " + path
|
|
|
+ );
|
|
|
+ me.localObjects.push(path);
|
|
|
+ }
|
|
|
+ chunks = null; ///[];
|
|
|
+ me.callbackTable[CB_recorder_log](me.callback, DBG, "recorder stopped");
|
|
|
+ me.mediaRecorder = null;
|
|
|
+ me.callbackTable[CB_stopRecorderCompleted](
|
|
|
+ me.callback,
|
|
|
+ IS_RECORDER_STOPPED,
|
|
|
+ true,
|
|
|
+ me.getRecordURL(path)
|
|
|
+ );
|
|
|
+ me.callbackTable[CB_recorder_log](
|
|
|
+ me.callback,
|
|
|
+ DBG,
|
|
|
+ "<--- mediaRecorder onstop"
|
|
|
+ );
|
|
|
+ };
|
|
|
+ //});
|
|
|
+ }
|
|
|
+
|
|
|
+ stop() {
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "JS:---> stop()");
|
|
|
+ this.stopTimer();
|
|
|
+ if (this.mediaRecorder != null) {
|
|
|
+ this.mediaRecorder.stop();
|
|
|
+ this.mediaRecorder = null;
|
|
|
+ }
|
|
|
+ if (this.mediaStream != null) {
|
|
|
+ this.mediaStream.getTracks().forEach((track) => track.stop());
|
|
|
+ this.mediaStream = null;
|
|
|
+ }
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "JS:<--- stop()");
|
|
|
+ }
|
|
|
+
|
|
|
+ stopRecorder() {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:---> stopRecorder"
|
|
|
+ );
|
|
|
+ this.stopTimer();
|
|
|
+ if (this.mediaRecorder != null) {
|
|
|
+ this.mediaRecorder.stop();
|
|
|
+ } else {
|
|
|
+ this.callbackTable[CB_stopRecorderCompleted](
|
|
|
+ this.callback,
|
|
|
+ IS_RECORDER_STOPPED,
|
|
|
+ /*false*/ true,
|
|
|
+ this.getRecordURL(this.currentRecordPath)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ this.mediaRecorder = null;
|
|
|
+
|
|
|
+ if (this.mediaStream != null) {
|
|
|
+ this.mediaStream.getTracks().forEach((track) => track.stop());
|
|
|
+ this.mediaStream = null;
|
|
|
+ }
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:<--- stopRecorder"
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ pauseRecorder() {
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "pauseRecorder");
|
|
|
+ this.mediaRecorder.pause();
|
|
|
+ this.stopTimer();
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "recorder paused : " + this.mediaRecorder.state
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ resumeRecorder() {
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "resumeRecorder");
|
|
|
+ this.mediaRecorder.resume();
|
|
|
+ this.startTimer();
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "recorder resumed : " + this.mediaRecorder.state
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ startTimer() {
|
|
|
+ this.callbackTable[CB_recorder_log](this.callback, DBG, "startTimer()");
|
|
|
+ this.stopTimer();
|
|
|
+ var me = this;
|
|
|
+
|
|
|
+ if (this.subscriptionDuration > 0) {
|
|
|
+ this.countDownDate = new Date().getTime();
|
|
|
+ this.timerId = setInterval(function () {
|
|
|
+ var volumeLevel = me.getVolumeLevel();
|
|
|
+ var now = new Date().getTime();
|
|
|
+ var distance = now - me.countDownDate;
|
|
|
+ me.callbackTable[CB_updateRecorderProgress](
|
|
|
+ me.callback,
|
|
|
+ me.deltaTime + distance,
|
|
|
+ volumeLevel
|
|
|
+ );
|
|
|
+ }, this.subscriptionDuration);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ stopTimer() {
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:---> stopTimer()"
|
|
|
+ );
|
|
|
+ if (this.timerId != null) {
|
|
|
+ clearInterval(this.timerId);
|
|
|
+ var now = new Date().getTime();
|
|
|
+ var distance = now - this.countDownDate;
|
|
|
+ this.deltaTime += distance;
|
|
|
+ this.timerId = null;
|
|
|
+ }
|
|
|
+ this.callbackTable[CB_recorder_log](
|
|
|
+ this.callback,
|
|
|
+ DBG,
|
|
|
+ "JS:<--- stopTimer()"
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
-
|