Browse Source

修正无法反复调用麦克风

gavin.chen 2 years ago
parent
commit
c7dd422903
2 changed files with 517 additions and 387 deletions
  1. 1 0
      .gitignore
  2. 516 387
      src/flutter_sound_recorder.js

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+.history/

+ 516 - 387
src/flutter_sound_recorder.js

@@ -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()"
+    );
+  }
 }
-