text: //Recorder from https://gist.github.com/meziantou/edb7217fddfbb70e899e
  
  Shiny.addCustomMessageHandler('recordAudio', function (params) {
  
  var start = document.getElementById(params.start);
  var stop = document.getElementById(params.stop);
  
  var leftchannel = [];
  var rightchannel = [];
  var recorder = null;
  var recordingLength = 0;
  var volume = null;
  var mediaStream = null;
  var sampleRate = 44100;
  var context = null;
  var blob = null;
  
          start.addEventListener('click', function () {
              // Initialize recorder
              navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
              navigator.getUserMedia(
              {
                  audio: true
              },
              function (e) {
                  console.log('user consent');
  
                  // creates the audio context
                  window.AudioContext = window.AudioContext || window.webkitAudioContext;
                  context = new AudioContext();
  
                  // creates an audio node from the microphone incoming stream
                  mediaStream = context.createMediaStreamSource(e);
  
                  // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createScriptProcessor
                  // bufferSize: the onaudioprocess event is called when the buffer is full
                  var bufferSize = 2048;
                  var numberOfInputChannels = 2;
                  var numberOfOutputChannels = 2;
                  if (context.createScriptProcessor) {
                      recorder = context.createScriptProcessor(bufferSize, numberOfInputChannels, numberOfOutputChannels);
                  } else {
                      recorder = context.createJavaScriptNode(bufferSize, numberOfInputChannels, numberOfOutputChannels);
                  }
  
                  recorder.onaudioprocess = function (e) {
                      leftchannel.push(new Float32Array(e.inputBuffer.getChannelData(0)));
                      rightchannel.push(new Float32Array(e.inputBuffer.getChannelData(1)));
                      recordingLength += bufferSize;
                  }
  
                  // we connect the recorder
                  mediaStream.connect(recorder);
                  recorder.connect(context.destination);
              },
                          function (e) {
                              console.error(e);
                          });
          });
  
          stop.addEventListener('click', function () {
  
              // stop recording
              recorder.disconnect(context.destination);
              mediaStream.disconnect(recorder);
  
              // we flat the left and right channels down
              // Float32Array[] => Float32Array
              var leftBuffer = flattenArray(leftchannel, recordingLength);
              var rightBuffer = flattenArray(rightchannel, recordingLength);
              // we interleave both channels together, remove for mono
  
              // we create our wav file
              var buffer = new ArrayBuffer(44 + leftBuffer.length * 2);
              var view = new DataView(buffer);
  
              // RIFF chunk descriptor
              writeUTFBytes(view, 0, 'RIFF');
              view.setUint32(4, 44 + leftBuffer.length * 2, true);
              writeUTFBytes(view, 8, 'WAVE');
              // FMT sub-chunk
              writeUTFBytes(view, 12, 'fmt ');
              view.setUint32(16, 16, true); // chunkSize
              view.setUint16(20, 1, true); // wFormatTag
              view.setUint16(22, 1, true); // wChannels: stereo (2 channels), change to 1 for mono
              view.setUint32(24, sampleRate, true); // dwSamplesPerSec
              view.setUint32(28, sampleRate * 2, true); // dwAvgBytesPerSec, change to *2 for mono
              view.setUint16(32, 4, true); // wBlockAlign
              view.setUint16(34, 16, true); // wBitsPerSample
              // data sub-chunk
              writeUTFBytes(view, 36, 'data');
              view.setUint32(40, leftBuffer.length * 2, true);
  
              // write the PCM samples
              var index = 44;
              var volume = 1;
              for (var i = 0; i < leftBuffer.length; i++) {
                  view.setInt16(index, leftBuffer[i] * (0x7FFF * volume), true);
                  index += 2;
              }
  
              // our final blob
              blob = new Blob([view], { type: 'audio/wav' });
              Shiny.setInputValue(params.output, blob)
          });
          
          function flattenArray(channelBuffer, recordingLength) {
              var result = new Float32Array(recordingLength);
              var offset = 0;
              for (var i = 0; i < channelBuffer.length; i++) {
                  var buffer = channelBuffer[i];
                  result.set(buffer, offset);
                  offset += buffer.length;
              }
              return result;
          }
          
          function writeUTFBytes(view, offset, string) {
              for (var i = 0; i < string.length; i++) {
                  view.setUint8(offset + i, string.charCodeAt(i));
              }
          }
  })