├── .gitattributes ├── .gitignore ├── README.md ├── bower.json ├── bower_components └── Recorderjs │ ├── .bower.json │ ├── README.md │ ├── example_simple_exportwav.html │ ├── recorder.js │ └── recorderWorker.js ├── demo ├── Recorderjs │ ├── recorder.js │ └── recorderWorker.js ├── form │ ├── DirectoryManager.php │ ├── Session.php │ └── audioProcess.php ├── index.html ├── js │ └── init.js ├── jsHtml5AudioRecorder.js └── medias │ └── .htaccess ├── index.html ├── jsHtml5AudioRecorder.js ├── jsHtml5AudioRecorder.min.js └── nbproject ├── project.properties └── project.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | # Files that might appear on external disk 36 | .Spotlight-V100 37 | .Trashes 38 | /nbproject/private/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Js Html5 Audio Recorder 2 | ======================= 3 | 4 | JsHtml5AudioRecorder is a native html5 object that helps you record live audio stream from your browser. 5 | it provides echo cancellation and clear record. 6 | It works perfectly on Google Chrome, Firefox and Opera. 7 | 8 | 9 | 1) How to install 10 | --------------------- 11 | 12 | bower install js-html5-audio-recorder 13 | 14 | 15 | 2) How to use it? 16 | ----------------- 17 | 18 | //Instantiate the object 19 | var jsAudioRecorder = new jsHtml5AudioRecorder(); 20 | 21 | //Set Parameters 22 | jsAudioRecorder.Recorder = Recorder; //External library that effectively record audio stream 23 | 24 | jsAudioRecorder.mediaPath = '/medias/Temp/'; //Path where to store audio files 25 | jsAudioRecorder.audioExtension = 'wav'; //Only wav format is supported 26 | jsAudioRecorder.audioTagId = 'myAudio'; 27 | jsAudioRecorder.showStreamOnFinish = false; //Show audio player on finish? 28 | 29 | jsAudioRecorder.phpFile = '/form/audioProcess.php'; //Php file that will proceed to audio file 30 | 31 | //Initialize it 32 | jsAudioRecorder.init(); 33 | 34 | //Start recording 35 | jsAudioRecorder.startRecording(); 36 | 37 | 38 | function startCapture() { 39 | jsPhotoBooth.startCapture(); 40 | stopRecording(); 41 | } 42 | 43 | /** 44 | * You can use "save", "saveAndDownload" or "saveAndStream", "downloadAndStream" parameters 45 | */ 46 | function stopRecording() { 47 | //For demo 48 | jsPhotoBooth.stopCapture('downloadAndStream'); 49 | 50 | //Use this in production 51 | //jsPhotoBooth.stopCapture('saveAndStream'); 52 | } 53 | 54 | 55 | 3) Live Demonstration 56 | --------------------- 57 | 58 | http://edouardkombo.github.io/jsHtml5AudioRecorder/demo/ 59 | 60 | 61 | Contributing 62 | ------------- 63 | 64 | If you do contribute, please make sure it conforms to the PSR coding standard. The easiest way to contribute is to work on a checkout of the repository, or your own fork, rather than an installed version. 65 | 66 | Want to learn more? Visit my blog http://creativcoders.wordpress.com 67 | 68 | 69 | Issues 70 | ------ 71 | 72 | Bug reports and feature requests can be submitted on the [Github issues tracker](https://github.com/edouardkombo/jsHtml5AudioRecorder/issues). 73 | 74 | For further informations, contact me directly at edouard.kombo@gmail.com. 75 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsHtml5AudioRecorder", 3 | "main": "jsHtml5AudioRecorder.js", 4 | "version": "master", 5 | "authors": [ 6 | "Edouard Kombo " 7 | ], 8 | "description": "Record live audio stream in html5 with echo cancellation, save file on your server, download it directly from browser or stream it", 9 | "keywords": [ 10 | "html5", 11 | "audio", 12 | "recorder", 13 | "html5", 14 | "live", 15 | "audio", 16 | "stream", 17 | "jsHtml5AudioRecorder" 18 | ], 19 | "license": "MIT", 20 | "homepage": "https://github.com/edouardkombo/jsHtml5AudioRecorder", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | "dependencies": { 29 | "Recorderjs": "*" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bower_components/Recorderjs/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Recorderjs", 3 | "homepage": "https://github.com/mattdiamond/Recorderjs", 4 | "_release": "0a3bb87cc9", 5 | "_resolution": { 6 | "type": "branch", 7 | "branch": "master", 8 | "commit": "0a3bb87cc92d0980bbb21e9a22834283811d5e64" 9 | }, 10 | "_source": "git://github.com/mattdiamond/Recorderjs.git", 11 | "_target": "*", 12 | "_originalSource": "Recorderjs", 13 | "_direct": true 14 | } -------------------------------------------------------------------------------- /bower_components/Recorderjs/README.md: -------------------------------------------------------------------------------- 1 | # Recorder.js 2 | 3 | ## A plugin for recording/exporting the output of Web Audio API nodes 4 | 5 | ### Syntax 6 | #### Constructor 7 | var rec = new Recorder(source [, config]) 8 | 9 | Creates a recorder instance. 10 | 11 | - **source** - The node whose output you wish to capture 12 | - **config** - (*optional*) A configuration object (see **config** section below) 13 | 14 | --------- 15 | #### Config 16 | 17 | - **workerPath** - Path to recorder.js worker script. Defaults to 'js/recorderjs/recorderWorker.js' 18 | - **bufferLen** - The length of the buffer that the internal JavaScriptNode uses to capture the audio. Can be tweaked if experiencing performance issues. Defaults to 4096. 19 | - **callback** - A default callback to be used with `exportWAV`. 20 | - **type** - The type of the Blob generated by `exportWAV`. Defaults to 'audio/wav'. 21 | 22 | --------- 23 | #### Instance Methods 24 | 25 | rec.record() 26 | rec.stop() 27 | 28 | Pretty self-explanatory... **record** will begin capturing audio and **stop** will cease capturing audio. Subsequent calls to **record** will add to the current recording. 29 | 30 | rec.clear() 31 | 32 | This will clear the recording. 33 | 34 | rec.exportWAV([callback][, type]) 35 | 36 | This will generate a Blob object containing the recording in WAV format. The callback will be called with the Blob as its sole argument. If a callback is not specified, the default callback (as defined in the config) will be used. If no default has been set, an error will be thrown. 37 | 38 | In addition, you may specify the type of Blob to be returned (defaults to 'audio/wav'). 39 | 40 | rec.getBuffer([callback]) 41 | 42 | This will pass the recorded stereo buffer (as an array of two Float32Arrays, for the separate left and right channels) to the callback. It can be played back by creating a new source buffer and setting these buffers as the separate channel data: 43 | 44 | function getBufferCallback( buffers ) { 45 | var newSource = audioContext.createBufferSource(); 46 | var newBuffer = audioContext.createBuffer( 2, buffers[0].length, audioContext.sampleRate ); 47 | newBuffer.getChannelData(0).set(buffers[0]); 48 | newBuffer.getChannelData(1).set(buffers[1]); 49 | newSource.buffer = newBuffer; 50 | 51 | newSource.connect( audioContext.destination ); 52 | newSource.start(0); 53 | } 54 | 55 | This sample code will play back the stereo buffer. 56 | 57 | 58 | rec.configure(config) 59 | 60 | This will set the configuration for Recorder by passing in a config object. 61 | 62 | #### Utility Methods (static) 63 | 64 | Recorder.forceDownload(blob[, filename]) 65 | 66 | This method will force a download using the new anchor link *download* attribute. Filename defaults to 'output.wav'. 67 | 68 | ## License (MIT) 69 | 70 | Copyright © 2013 Matt Diamond 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 73 | 74 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 75 | 76 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /bower_components/Recorderjs/example_simple_exportwav.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Live input record and playback 7 | 11 | 12 | 13 | 14 |

Recorder.js simple WAV export example

15 | 16 |

Make sure you are using a recent version of Google Chrome, at the moment this only works with Google Chrome Canary.

17 |

Also before you enable microphone input either plug in headphones or turn the volume down if you want to avoid ear splitting feedback!

18 | 19 | 20 | 21 | 22 |

Recordings

23 | 24 | 25 |

Log

26 |

 27 | 
 28 |   
103 | 
104 |   
105 | 
106 | 


--------------------------------------------------------------------------------
/bower_components/Recorderjs/recorder.js:
--------------------------------------------------------------------------------
 1 | (function(window){
 2 | 
 3 |   var WORKER_PATH = 'recorderWorker.js';
 4 | 
 5 |   var Recorder = function(source, cfg){
 6 |     var config = cfg || {};
 7 |     var bufferLen = config.bufferLen || 4096;
 8 |     this.context = source.context;
 9 |     this.node = (this.context.createScriptProcessor ||
10 |                  this.context.createJavaScriptNode).call(this.context,
11 |                                                          bufferLen, 2, 2);
12 |     var worker = new Worker(config.workerPath || WORKER_PATH);
13 |     worker.postMessage({
14 |       command: 'init',
15 |       config: {
16 |         sampleRate: this.context.sampleRate
17 |       }
18 |     });
19 |     var recording = false,
20 |       currCallback;
21 | 
22 |     this.node.onaudioprocess = function(e){
23 |       if (!recording) return;
24 |       worker.postMessage({
25 |         command: 'record',
26 |         buffer: [
27 |           e.inputBuffer.getChannelData(0),
28 |           e.inputBuffer.getChannelData(1)
29 |         ]
30 |       });
31 |     }
32 | 
33 |     this.configure = function(cfg){
34 |       for (var prop in cfg){
35 |         if (cfg.hasOwnProperty(prop)){
36 |           config[prop] = cfg[prop];
37 |         }
38 |       }
39 |     }
40 | 
41 |     this.record = function(){
42 |       recording = true;
43 |     }
44 | 
45 |     this.stop = function(){
46 |       recording = false;
47 |     }
48 | 
49 |     this.clear = function(){
50 |       worker.postMessage({ command: 'clear' });
51 |     }
52 | 
53 |     this.getBuffer = function(cb) {
54 |       currCallback = cb || config.callback;
55 |       worker.postMessage({ command: 'getBuffer' })
56 |     }
57 | 
58 |     this.exportWAV = function(cb, type){
59 |       currCallback = cb || config.callback;
60 |       type = type || config.type || 'audio/wav';
61 |       if (!currCallback) throw new Error('Callback not set');
62 |       worker.postMessage({
63 |         command: 'exportWAV',
64 |         type: type
65 |       });
66 |     }
67 | 
68 |     worker.onmessage = function(e){
69 |       var blob = e.data;
70 |       currCallback(blob);
71 |     }
72 | 
73 |     source.connect(this.node);
74 |     this.node.connect(this.context.destination);    //this should not be necessary
75 |   };
76 | 
77 |   Recorder.forceDownload = function(blob, filename){
78 |     var url = (window.URL || window.webkitURL).createObjectURL(blob);
79 |     var link = window.document.createElement('a');
80 |     link.href = url;
81 |     link.download = filename || 'output.wav';
82 |     var click = document.createEvent("Event");
83 |     click.initEvent("click", true, true);
84 |     link.dispatchEvent(click);
85 |   }
86 | 
87 |   window.Recorder = Recorder;
88 | 
89 | })(window);
90 | 


--------------------------------------------------------------------------------
/bower_components/Recorderjs/recorderWorker.js:
--------------------------------------------------------------------------------
  1 | var recLength = 0,
  2 |   recBuffersL = [],
  3 |   recBuffersR = [],
  4 |   sampleRate;
  5 | 
  6 | this.onmessage = function(e){
  7 |   switch(e.data.command){
  8 |     case 'init':
  9 |       init(e.data.config);
 10 |       break;
 11 |     case 'record':
 12 |       record(e.data.buffer);
 13 |       break;
 14 |     case 'exportWAV':
 15 |       exportWAV(e.data.type);
 16 |       break;
 17 |     case 'getBuffer':
 18 |       getBuffer();
 19 |       break;
 20 |     case 'clear':
 21 |       clear();
 22 |       break;
 23 |   }
 24 | };
 25 | 
 26 | function init(config){
 27 |   sampleRate = config.sampleRate;
 28 | }
 29 | 
 30 | function record(inputBuffer){
 31 |   recBuffersL.push(inputBuffer[0]);
 32 |   recBuffersR.push(inputBuffer[1]);
 33 |   recLength += inputBuffer[0].length;
 34 | }
 35 | 
 36 | function exportWAV(type){
 37 |   var bufferL = mergeBuffers(recBuffersL, recLength);
 38 |   var bufferR = mergeBuffers(recBuffersR, recLength);
 39 |   var interleaved = interleave(bufferL, bufferR);
 40 |   var dataview = encodeWAV(interleaved);
 41 |   var audioBlob = new Blob([dataview], { type: type });
 42 | 
 43 |   this.postMessage(audioBlob);
 44 | }
 45 | 
 46 | function getBuffer() {
 47 |   var buffers = [];
 48 |   buffers.push( mergeBuffers(recBuffersL, recLength) );
 49 |   buffers.push( mergeBuffers(recBuffersR, recLength) );
 50 |   this.postMessage(buffers);
 51 | }
 52 | 
 53 | function clear(){
 54 |   recLength = 0;
 55 |   recBuffersL = [];
 56 |   recBuffersR = [];
 57 | }
 58 | 
 59 | function mergeBuffers(recBuffers, recLength){
 60 |   var result = new Float32Array(recLength);
 61 |   var offset = 0;
 62 |   for (var i = 0; i < recBuffers.length; i++){
 63 |     result.set(recBuffers[i], offset);
 64 |     offset += recBuffers[i].length;
 65 |   }
 66 |   return result;
 67 | }
 68 | 
 69 | function interleave(inputL, inputR){
 70 |   var length = inputL.length + inputR.length;
 71 |   var result = new Float32Array(length);
 72 | 
 73 |   var index = 0,
 74 |     inputIndex = 0;
 75 | 
 76 |   while (index < length){
 77 |     result[index++] = inputL[inputIndex];
 78 |     result[index++] = inputR[inputIndex];
 79 |     inputIndex++;
 80 |   }
 81 |   return result;
 82 | }
 83 | 
 84 | function floatTo16BitPCM(output, offset, input){
 85 |   for (var i = 0; i < input.length; i++, offset+=2){
 86 |     var s = Math.max(-1, Math.min(1, input[i]));
 87 |     output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
 88 |   }
 89 | }
 90 | 
 91 | function writeString(view, offset, string){
 92 |   for (var i = 0; i < string.length; i++){
 93 |     view.setUint8(offset + i, string.charCodeAt(i));
 94 |   }
 95 | }
 96 | 
 97 | function encodeWAV(samples){
 98 |   var buffer = new ArrayBuffer(44 + samples.length * 2);
 99 |   var view = new DataView(buffer);
100 | 
101 |   /* RIFF identifier */
102 |   writeString(view, 0, 'RIFF');
103 |   /* RIFF chunk length */
104 |   view.setUint32(4, 36 + samples.length * 2, true);
105 |   /* RIFF type */
106 |   writeString(view, 8, 'WAVE');
107 |   /* format chunk identifier */
108 |   writeString(view, 12, 'fmt ');
109 |   /* format chunk length */
110 |   view.setUint32(16, 16, true);
111 |   /* sample format (raw) */
112 |   view.setUint16(20, 1, true);
113 |   /* channel count */
114 |   view.setUint16(22, 2, true);
115 |   /* sample rate */
116 |   view.setUint32(24, sampleRate, true);
117 |   /* byte rate (sample rate * block align) */
118 |   view.setUint32(28, sampleRate * 4, true);
119 |   /* block align (channel count * bytes per sample) */
120 |   view.setUint16(32, 4, true);
121 |   /* bits per sample */
122 |   view.setUint16(34, 16, true);
123 |   /* data chunk identifier */
124 |   writeString(view, 36, 'data');
125 |   /* data chunk length */
126 |   view.setUint32(40, samples.length * 2, true);
127 | 
128 |   floatTo16BitPCM(view, 44, samples);
129 | 
130 |   return view;
131 | }
132 | 


--------------------------------------------------------------------------------
/demo/Recorderjs/recorder.js:
--------------------------------------------------------------------------------
 1 | (function(window){
 2 | 
 3 |   var WORKER_PATH = 'Recorderjs/recorderWorker.js';
 4 | 
 5 |   var Recorder = function(source, cfg){
 6 |     var config = cfg || {};
 7 |     var bufferLen = config.bufferLen || 4096;
 8 |     this.context = source.context;
 9 |     this.node = (this.context.createScriptProcessor ||
10 |                  this.context.createJavaScriptNode).call(this.context,
11 |                                                          bufferLen, 2, 2);
12 |     var worker = new Worker(config.workerPath || WORKER_PATH);
13 |     worker.postMessage({
14 |       command: 'init',
15 |       config: {
16 |         sampleRate: this.context.sampleRate
17 |       }
18 |     });
19 |     var recording = false,
20 |       currCallback;
21 | 
22 |     this.node.onaudioprocess = function(e){
23 |       if (!recording) return;
24 |       worker.postMessage({
25 |         command: 'record',
26 |         buffer: [
27 |           e.inputBuffer.getChannelData(0),
28 |           e.inputBuffer.getChannelData(1)
29 |         ]
30 |       });
31 |     }
32 | 
33 |     this.configure = function(cfg){
34 |       for (var prop in cfg){
35 |         if (cfg.hasOwnProperty(prop)){
36 |           config[prop] = cfg[prop];
37 |         }
38 |       }
39 |     }
40 | 
41 |     this.record = function(){
42 |       recording = true;
43 |     }
44 | 
45 |     this.stop = function(){
46 |       recording = false;
47 |     }
48 | 
49 |     this.clear = function(){
50 |       worker.postMessage({ command: 'clear' });
51 |     }
52 | 
53 |     this.getBuffer = function(cb) {
54 |       currCallback = cb || config.callback;
55 |       worker.postMessage({ command: 'getBuffer' })
56 |     }
57 | 
58 |     this.exportWAV = function(cb, type){
59 |       currCallback = cb || config.callback;
60 |       type = type || config.type || 'audio/wav';
61 |       if (!currCallback) throw new Error('Callback not set');
62 |       worker.postMessage({
63 |         command: 'exportWAV',
64 |         type: type
65 |       });
66 |     }
67 | 
68 |     worker.onmessage = function(e){
69 |       var blob = e.data;
70 |       currCallback(blob);
71 |     }
72 | 
73 |     source.connect(this.node);
74 |     this.node.connect(this.context.destination);    //this should not be necessary
75 |   };
76 | 
77 |   Recorder.forceDownload = function(blob, filename){
78 |     var url = (window.URL || window.webkitURL).createObjectURL(blob);
79 |     var link = window.document.createElement('a');
80 |     link.href = url;
81 |     link.download = filename || 'output.wav';
82 |     var click = document.createEvent("Event");
83 |     click.initEvent("click", true, true);
84 |     link.dispatchEvent(click);
85 |   }
86 | 
87 |   window.Recorder = Recorder;
88 | 
89 | })(window);
90 | 


--------------------------------------------------------------------------------
/demo/Recorderjs/recorderWorker.js:
--------------------------------------------------------------------------------
  1 | var recLength = 0,
  2 |   recBuffersL = [],
  3 |   recBuffersR = [],
  4 |   sampleRate;
  5 | 
  6 | this.onmessage = function(e){
  7 |   switch(e.data.command){
  8 |     case 'init':
  9 |       init(e.data.config);
 10 |       break;
 11 |     case 'record':
 12 |       record(e.data.buffer);
 13 |       break;
 14 |     case 'exportWAV':
 15 |       exportWAV(e.data.type);
 16 |       break;
 17 |     case 'getBuffer':
 18 |       getBuffer();
 19 |       break;
 20 |     case 'clear':
 21 |       clear();
 22 |       break;
 23 |   }
 24 | };
 25 | 
 26 | function init(config){
 27 |   sampleRate = config.sampleRate;
 28 | }
 29 | 
 30 | function record(inputBuffer){
 31 |   recBuffersL.push(inputBuffer[0]);
 32 |   recBuffersR.push(inputBuffer[1]);
 33 |   recLength += inputBuffer[0].length;
 34 | }
 35 | 
 36 | function exportWAV(type){
 37 |   var bufferL = mergeBuffers(recBuffersL, recLength);
 38 |   var bufferR = mergeBuffers(recBuffersR, recLength);
 39 |   var interleaved = interleave(bufferL, bufferR);
 40 |   var dataview = encodeWAV(interleaved);
 41 |   var audioBlob = new Blob([dataview], { type: type });
 42 | 
 43 |   this.postMessage(audioBlob);
 44 | }
 45 | 
 46 | function getBuffer() {
 47 |   var buffers = [];
 48 |   buffers.push( mergeBuffers(recBuffersL, recLength) );
 49 |   buffers.push( mergeBuffers(recBuffersR, recLength) );
 50 |   this.postMessage(buffers);
 51 | }
 52 | 
 53 | function clear(){
 54 |   recLength = 0;
 55 |   recBuffersL = [];
 56 |   recBuffersR = [];
 57 | }
 58 | 
 59 | function mergeBuffers(recBuffers, recLength){
 60 |   var result = new Float32Array(recLength);
 61 |   var offset = 0;
 62 |   for (var i = 0; i < recBuffers.length; i++){
 63 |     result.set(recBuffers[i], offset);
 64 |     offset += recBuffers[i].length;
 65 |   }
 66 |   return result;
 67 | }
 68 | 
 69 | function interleave(inputL, inputR){
 70 |   var length = inputL.length + inputR.length;
 71 |   var result = new Float32Array(length);
 72 | 
 73 |   var index = 0,
 74 |     inputIndex = 0;
 75 | 
 76 |   while (index < length){
 77 |     result[index++] = inputL[inputIndex];
 78 |     result[index++] = inputR[inputIndex];
 79 |     inputIndex++;
 80 |   }
 81 |   return result;
 82 | }
 83 | 
 84 | function floatTo16BitPCM(output, offset, input){
 85 |   for (var i = 0; i < input.length; i++, offset+=2){
 86 |     var s = Math.max(-1, Math.min(1, input[i]));
 87 |     output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
 88 |   }
 89 | }
 90 | 
 91 | function writeString(view, offset, string){
 92 |   for (var i = 0; i < string.length; i++){
 93 |     view.setUint8(offset + i, string.charCodeAt(i));
 94 |   }
 95 | }
 96 | 
 97 | function encodeWAV(samples){
 98 |   var buffer = new ArrayBuffer(44 + samples.length * 2);
 99 |   var view = new DataView(buffer);
100 | 
101 |   /* RIFF identifier */
102 |   writeString(view, 0, 'RIFF');
103 |   /* RIFF chunk length */
104 |   view.setUint32(4, 36 + samples.length * 2, true);
105 |   /* RIFF type */
106 |   writeString(view, 8, 'WAVE');
107 |   /* format chunk identifier */
108 |   writeString(view, 12, 'fmt ');
109 |   /* format chunk length */
110 |   view.setUint32(16, 16, true);
111 |   /* sample format (raw) */
112 |   view.setUint16(20, 1, true);
113 |   /* channel count */
114 |   view.setUint16(22, 2, true);
115 |   /* sample rate */
116 |   view.setUint32(24, sampleRate, true);
117 |   /* byte rate (sample rate * block align) */
118 |   view.setUint32(28, sampleRate * 4, true);
119 |   /* block align (channel count * bytes per sample) */
120 |   view.setUint16(32, 4, true);
121 |   /* bits per sample */
122 |   view.setUint16(34, 16, true);
123 |   /* data chunk identifier */
124 |   writeString(view, 36, 'data');
125 |   /* data chunk length */
126 |   view.setUint32(40, samples.length * 2, true);
127 | 
128 |   floatTo16BitPCM(view, 44, samples);
129 | 
130 |   return view;
131 | }
132 | 


--------------------------------------------------------------------------------
/demo/form/DirectoryManager.php:
--------------------------------------------------------------------------------
  1 | directory = $directory;
 54 |         
 55 |         $this->createDirectory($this->directory);
 56 | 
 57 |         $this->iterator = new DirectoryIterator($directory);
 58 |         
 59 |         if (is_object($this->iterator)) {
 60 |             $this->browse();
 61 |             
 62 |             return true;
 63 |    
 64 |         } else {
 65 |             throw new Exception("$directory is invalid directory !");
 66 |         } 
 67 |     }
 68 |     
 69 |     /**
 70 |      * List all directory content recursively
 71 |      * 
 72 |      * @param array $files
 73 |      *  
 74 |      * @return mixed
 75 |      */
 76 |     public function browse()
 77 |     {
 78 |         $files = (array) array();
 79 |         
 80 |         if (empty($this->iterator)) {
 81 |             return false;
 82 |         }
 83 |         
 84 |         foreach ($this->iterator as $info) {
 85 |             
 86 |             if ($info->isFile()) {
 87 |                 
 88 |                 $files[$info->__toString()] = $info;
 89 |                 
 90 |                 //Save filenames to work on it later
 91 |                 $this->fileNames[$info->getFilename()] = $info->getFilename();
 92 |             
 93 |             } else if (!$info->isDot()) {
 94 |                 
 95 |                 $list = array(
 96 |                     $info->__toString() => $this->recursiveDirectoryIterator(
 97 |                         $this->directory.DIRECTORY_SEPARATOR.$info->__toString()
 98 |                     )
 99 |                 );
100 |                 
101 |                 $this->files = (!empty($files)) ? 
102 |                     array_merge_recursive($files, $filest) : $list ;
103 |             }
104 |         }
105 |         
106 |         return $this->files;        
107 |     }
108 |     
109 |     
110 |     /**
111 |      * Get directories content
112 |      * 
113 |      * @return array
114 |      */
115 |     public function getContent()
116 |     {
117 |         return (array) $this->fileNames;
118 |     }
119 | 
120 |     /**
121 |      * Create directory if needed
122 |      * 
123 |      * @param string $directory Directory to create
124 |      * 
125 |      * @return boolean
126 |      */
127 |     private function createDirectory($directory)
128 |     {
129 |         if (!is_dir($directory)) {
130 |             mkdir($directory, 0777, true);
131 |         }
132 |         
133 |         return true;
134 |     }    
135 |     
136 |     /**
137 |      * Move all directory files or one specific file to another directory
138 |      * Try to create directory if not exists
139 |      * 
140 |      * @param string $newDirectory New directory where to move files
141 |      * @param string $fileToMove   Specific file to move
142 |      * 
143 |      * @return boolean
144 |      */
145 |     public function move($newDirectory, $fileToMove = null)
146 |     {
147 |         if((sizeof($this->fileNames)>=1) && !empty($this->fileNames)) {
148 |             
149 |             $this->createDirectory($newDirectory);
150 |             
151 |             foreach ($this->fileNames as $key => $file){
152 |                 $basePath = $this->directory . DIRECTORY_SEPARATOR . $file;
153 |                 $newPath  = $newDirectory . DIRECTORY_SEPARATOR . $file;
154 | 
155 |                 if (!empty($fileToMove)) {
156 |                     ($file === $fileToMove) ? copy($basePath, $newPath) : '' ;
157 |                 } else {
158 |                     copy($basePath, $newPath);
159 |                 }
160 | 
161 |             }
162 |             
163 |             return true;
164 |         }
165 |         
166 |         return false;
167 |     }
168 |      
169 |     /**
170 |      * Delete all directory content and folders, or one specific file
171 |      * 
172 |      * @param string $fileToDelete Specific file to delete
173 |      * 
174 |      * @return boolean
175 |      */
176 |     public function delete($fileToDelete = null)
177 |     {
178 |         $files = new RecursiveIteratorIterator(
179 |             new RecursiveDirectoryIterator($this->directory, 
180 |                 RecursiveDirectoryIterator::SKIP_DOTS
181 |             ),
182 |             RecursiveIteratorIterator::CHILD_FIRST
183 |         );
184 | 
185 |         foreach ($files as $fileInfo) {
186 |             
187 |             if (($fileInfo->getFilename() === $fileToDelete) && !empty($fileToDelete)) {
188 |                 unlink($fileInfo->getRealPath());
189 |                 $this->updateFiles($fileInfo);
190 |                 
191 |             } else if (empty($fileToDelete)) { 
192 |                 unlink($fileInfo->getRealPath());
193 |                 $this->updateFiles($fileInfo);
194 |             }            
195 |             
196 |         }
197 | 
198 |         //Remove all empty directories        
199 |         (empty($fileToDelete)) ? rmdir($this->directory) : ''; 
200 |         
201 |         return true;
202 |     }
203 |     
204 |     /**
205 |      * Update file content
206 |      * 
207 |      * @param object $info Informations on files
208 |      * 
209 |      * @return array
210 |      */
211 |     private function updateFiles($info)
212 |     {
213 |         if ($info->getFilename() === $this->fileNames[$info->getFilename()]) {
214 |             unset($this->fileNames[$info->getFilename()]);
215 |         }
216 |         
217 |         return $this->fileNames;
218 |     } 
219 | }


--------------------------------------------------------------------------------
/demo/form/Session.php:
--------------------------------------------------------------------------------
 1 | id = time();
27 |             $_SESSION['id'] = "".$this->id."";            
28 |         } else {
29 |             $this->id = $_SESSION['id'];
30 |         }
31 |         
32 |         return $_SESSION;
33 |     }
34 |     
35 |     /**
36 |      * Return id
37 |      * 
38 |      * @return integer
39 |      */
40 |     public function getId()
41 |     {
42 |         return $this->id;
43 |     }
44 |     
45 |     /**
46 |      * Destroy actual session
47 |      */
48 |     public function destroy()
49 |     {
50 |         $this->id = 0;
51 |         unset($_SESSION['id']);
52 |         session_unset();               
53 |     }
54 | }
55 | 


--------------------------------------------------------------------------------
/demo/form/audioProcess.php:
--------------------------------------------------------------------------------
 1 | init();
15 | 
16 | if (isset($_POST)) {
17 |     
18 |     /*************************************
19 |      *** GET PICTURE ***
20 |      ************************************/    
21 |     //Variables
22 |     $path               = (string) filter_input(INPUT_GET, 'path');                 
23 |     $extension          = (string) filter_input(INPUT_GET, 'extension');
24 |     $type               = (string) filter_input(INPUT_GET, 'type');     
25 | 
26 |     //Set session and directory
27 |     $id                 = $session->getId();    
28 |     
29 |     //"/media/Temp/1234/1234.yyy"
30 |     $simplePath             = $path . $id . DIRECTORY_SEPARATOR . $id . '.' . $extension;
31 |     
32 |     //"C:/xxxx/media/Temp/1234"
33 |     $basePath                = (string) $_SERVER['DOCUMENT_ROOT'] . $path . $id;   
34 |     
35 |     //"C:/xxxx/media/Temp/1234/1234.yyy"
36 |     $baseFilename            = (string) $basePath . DIRECTORY_SEPARATOR . $id . '.' . $extension;    
37 |     
38 |     //Search inside "C:/xxxx/media/Temp/1234" directory
39 |     $directory->setDirectoryIterator($basePath);
40 | 
41 |     //Get media
42 |     $media = file_get_contents('php://input');
43 | 
44 |     $firstArray     = array('\\', '/', '%5C');
45 |     $secondArray    = array('/', '/', '/');
46 | 
47 |     //Format strings
48 |     $baseFilename   = str_replace($firstArray, $secondArray, $baseFilename);
49 |     $simplePath     = str_replace($firstArray, $secondArray, $simplePath);
50 | 
51 |     //If a content exists, we delete and replace it
52 |     $directory->delete($id . '.' . $extension);
53 | 
54 |     //Create file and return status
55 |     file_put_contents($baseFilename, $media);       
56 |        
57 |     //Return media url inside media directory
58 |     echo $simplePath;    
59 | }
60 | 


--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 | 
 5 | 
 6 | jsHtml5AudioRecorder
 7 | 
 8 | 
 9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
15 | 	
16 |     
21 |          
22 |  
23 |     
24 |        
25 |                    
26 |     
27 | 
28 | 
29 | 


--------------------------------------------------------------------------------
/demo/js/init.js:
--------------------------------------------------------------------------------
 1 | /***************************************************
 2 |     Detect browser
 3 | ***************************************************/
 4 | if((window.chrome !== null) && (window.navigator.vendor === "Google Inc.")) {
 5 | } else { 
 6 |    alert('This application will only work on Google Chrome, Firefox and Opera!');
 7 | }
 8 | 
 9 | var jsAudioRecorder = new jsHtml5AudioRecorder();
10 | 
11 | /***************************************************
12 | 	Init Html5 Audio Streaming
13 | ***************************************************/
14 | jsAudioRecorder.Recorder            = Recorder;         //External library that effectively record audio stream
15 | 
16 | jsAudioRecorder.mediaPath           = '/medias/Temp/';  //Path where to store audio files
17 | jsAudioRecorder.audioExtension      = 'wav';            //Only wav format is supported
18 | jsAudioRecorder.audioTagId          = 'myAudio';
19 | jsAudioRecorder.showStreamOnFinish  = false;            //Show audio player on finish?
20 | 
21 | jsAudioRecorder.phpFile             = '/form/audioProcess.php'; //Php file that will proceed to audio file 
22 | 
23 | jsAudioRecorder.init();
24 | 
25 | 
26 | function startRecording() {
27 |     jsAudioRecorder.startRecording();
28 | }
29 | 
30 | /**
31 |  * You can use "save", "saveAndDownload" or "saveAndStream", "downloadAndStream" parameters
32 |  */
33 | function stopRecording() {
34 |     //For demo
35 |     jsAudioRecorder.stopRecording('downloadAndStream');
36 |     
37 |     //In production
38 |     //jsAudioRecorder.stopRecording('saveAndStream');
39 | }
40 | 


--------------------------------------------------------------------------------
/demo/jsHtml5AudioRecorder.js:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * Object:  jsHtml5AudioRecorder
  3 |  * Version: master
  4 |  * Author:  Edouard Kombo
  5 |  * Twitter: @EdouardKombo
  6 |  * Github:  https://github.com/edouardkombo
  7 |  * Blog:    http://creativcoders.wordpress.com
  8 |  * Url:     https://github.com/edouardkombo/jsHtml5AudioRecorder
  9 |  * 
 10 |  * Dual licensed under the MIT and GPL licenses:
 11 |  * http://www.opensource.org/licenses/mit-license.php
 12 |  * http://www.gnu.org/licenses/gpl.html
 13 |  * 
 14 |  * Record live audio stream in html5 with echo cancellation, save it on server or download directly from browser
 15 |  */
 16 | 
 17 | var jsHtml5AudioRecorder = function(){};
 18 | 
 19 | jsHtml5AudioRecorder.prototype = {
 20 |     audioContext: '',
 21 |     Recorder: false,
 22 |     mediaPath: '',
 23 |     audioExtension: '',
 24 |     audioLink: '',    
 25 |     phpFile: '',
 26 |     fftSize: 2048,
 27 |     audioTagId: 'audio',
 28 |     showStreamOnFinish: false,
 29 |     
 30 |     /**
 31 |      * Get Proper html5 getUsermedia from window.navigator object, depending on the browser
 32 |      * 
 33 |      * @returns {undefined}
 34 |      */
 35 |     init: function (){
 36 |         if (!navigator.getUserMedia) {
 37 |             navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
 38 |         }
 39 |  
 40 |         try {
 41 |             this.audioContext    = new AudioContext();
 42 |             console.log('Audio context set up.');
 43 |             console.log('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));
 44 |             
 45 |         } catch (e) {
 46 |             alert('No web audio support in this browser!');
 47 |         }
 48 |         
 49 |         window.onload = this.onLoad();
 50 |         
 51 |     },
 52 |     
 53 |     /**
 54 |      * Create audio context and live stream, and pass it to getUserMedia html5 api
 55 |      * 
 56 |      * @returns {undefined}
 57 |      */
 58 |     onLoad: function () {
 59 |         navigator.getUserMedia({audio:true}, this.startStream.bind(this), function(e) {
 60 |             alert('No audio stream!');
 61 |             console.log(e);
 62 |         });        
 63 |     },
 64 | 
 65 |     /**
 66 |      * Create live audio stream with echo cancellation
 67 |      * 
 68 |      * @param {Object} stream
 69 |      * @returns {undefined}
 70 |      */
 71 |     startStream: function(stream) {
 72 |         
 73 |         //Create virtual input from empty amplification factor object
 74 |         var virtualInput        = this.audioContext.createGain();
 75 |        
 76 |         //Assign stream source to html5 audio context object
 77 |         var microphone         = this.audioContext.createMediaStreamSource(stream);
 78 |         
 79 |         //Connect media source output to virtualInput input
 80 |         //So, virtualInput now equals microphone input
 81 |         microphone.connect(virtualInput);
 82 | 
 83 |         //Set audio quality and assign it to virtualInput
 84 |         var analyserNode        = this.audioContext.createAnalyser();
 85 |         analyserNode.fftSize    = this.fftSize;
 86 |         virtualInput.connect( analyserNode );
 87 | 
 88 |         //Set the stream to RecorderJs from Matt Diamond
 89 |         this.Recorder           = new Recorder( virtualInput );
 90 | 
 91 |         //Set volume to zero
 92 |         var amplificationFactor = this.audioContext.createGain();
 93 |         amplificationFactor.gain.value     = 0.0;
 94 |         
 95 |         //We set volume to zero to output, so we cancel echo
 96 |         amplificationFactor.connect( this.audioContext.destination );        
 97 |     },   
 98 |     
 99 |     /**
100 |      * Start audio record
101 |      * 
102 |      * @returns {undefined}
103 |      */
104 |     startRecording: function() {
105 |         
106 |         //Delete audio element if already exists to avoid element duplication and cache
107 |         var audioElement = document.getElementById(this.audioTagId);
108 |         if (audioElement) {
109 |             audioElement.remove();
110 |         }
111 |         
112 |         this.Recorder && this.Recorder.record();        
113 |         console.log('Recording audio...');
114 |     },
115 |     
116 |     /**
117 |      * Stop audio record and choose export method (save or download)
118 |      * 
119 |      * @param {String} method
120 |      * @returns {undefined}
121 |      */
122 |     stopRecording: function(method) {
123 |         this.Recorder && this.Recorder.stop();
124 | 
125 |         this.Recorder && this.Recorder.exportWAV(function(blob) {
126 |             console.log(blob);
127 |             
128 |             if (method === 'save') {
129 |                 this.save(blob, false);
130 | 
131 |             } else if (method === 'download') {
132 |                 this.download(blob, false);
133 | 
134 |             } else if (method === 'stream') {
135 |                 this.stream(blob);
136 | 
137 |             } else if (method === 'saveAndDownload') {
138 |                 this.save(blob, false);
139 |                 this.download(blob, false);
140 | 
141 |             } else if (method === 'saveAndStream') {
142 |                 this.save(blob, true);
143 | 
144 |             } else if (method === 'downloadAndStream') {
145 |                 this.download(blob, true);
146 | 
147 |             } else {
148 |                 this.save(blob, false);
149 |             }
150 |             
151 |         }.bind(this));
152 | 
153 |         this.Recorder.clear();
154 |         console.log('Stop Recording audio!');        
155 |     },
156 | 
157 |     /**
158 |      * Save audio file to server
159 |      * 
160 |      * @param {Object} blob
161 |      * @param {String} stream
162 |      * @returns {undefined}
163 |      */
164 |     save: function (blob, stream) {
165 |         
166 |         var datas       = 'path='+this.mediaPath+'&extension='+this.audioExtension;                  
167 | 
168 |         var client = new XMLHttpRequest();
169 |         client.onreadystatechange = function() 
170 |         {
171 |             if (client.readyState === 4 && client.status === 200) 
172 |             {
173 |                 console.log(client.response);
174 |                 
175 |                 //Get audio file link
176 |                 this.audioLink = client.response;
177 |                 
178 |                 if (stream) {
179 |                     this.stream(blob);
180 |                 }                
181 |             }
182 |         }.bind(this);
183 |         client.open("post", this.phpFile+'?'+datas, true);
184 |         client.setRequestHeader("X-Requested-With", "XMLHttpRequest");
185 |         client.setRequestHeader("cache-Control", "no-store, no-cache, must-revalidate");
186 |         client.setRequestHeader("cache-Control", "post-check=0, pre-check=0");
187 |         client.setRequestHeader("cache-Control", "max-age=0");
188 |         client.setRequestHeader("Pragma", "no-cache");            
189 |         client.setRequestHeader("X-File-Name", encodeURIComponent('1'));
190 |         client.setRequestHeader("Content-Type", "application/octet-stream");
191 |         client.send(blob);
192 |     },   
193 | 
194 |     /**
195 |      * Directly download audio file from the browser and stream it if specified
196 |      * 
197 |      * @param {Object} blob
198 |      * @param {String} stream
199 |      * @returns {undefined}
200 |      */
201 |     download: function(blob, stream) {
202 |         
203 |         var url             = window.URL.createObjectURL(blob);
204 |         //Create a link
205 |         var hf              = document.createElement('a');
206 | 
207 |         var temporaryId     = new Date().toISOString();
208 |         
209 |         //Define link attributes
210 |         hf.href             = url;
211 |         hf.id               = temporaryId;
212 |         hf.download         = temporaryId + '.' + this.audioExtension;
213 |         hf.innerHTML        = hf.download;
214 |         hf.style.display    = 'none';
215 |         hf.style.visibility = 'hidden';
216 |         //Append the link inside html code
217 |         document.body.appendChild(hf);
218 | 
219 |         //Simulate click on link to download file, and instantly delete link
220 |         document.getElementById(hf.id).click();
221 |         document.getElementById(hf.id).remove();
222 |         
223 |         if (stream) {
224 |             this.stream(blob);
225 |         }        
226 |     },
227 |     
228 |     /**
229 |      * Stream
230 |      * 
231 |      * @param {Object} blob
232 |      * @returns {undefined}
233 |      */
234 |     stream: function(blob) {
235 |         
236 |         var url             = window.URL.createObjectURL(blob);
237 |         
238 |         //Create audio tag
239 |         var audio           = document.createElement('audio');
240 |         
241 |         //Define audio tag attributes
242 |         audio.src               = url;
243 |         audio.id                = this.audioTagId;
244 |         
245 |         if (this.showStreamOnFinish) {
246 |             audio.style.display     = 'block';
247 |             audio.style.visibility  = 'visible';            
248 |         } else {
249 |             audio.style.display     = 'none';
250 |             audio.style.visibility  = 'hidden';            
251 |         }        
252 |          
253 |         document.body.appendChild(audio);
254 |         
255 |         audio.setAttribute('autoplay', false);         
256 |         audio.setAttribute('controls', true);
257 |         audio.pause();
258 |     }    
259 | };


--------------------------------------------------------------------------------
/demo/medias/.htaccess:
--------------------------------------------------------------------------------
  1 | # Apache Server Configs v1.1.0 | MIT License
  2 | # https://github.com/h5bp/server-configs-apache
  3 | 
  4 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access
  5 | # to the main server config file (usually called `httpd.conf`), you should add
  6 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
  7 | 
  8 | php_value    upload_max_filesize    64M
  9 | php_value    post_max_size          64M
 10 | 
 11 | 
 12 | # ##############################################################################
 13 | # # CROSS-ORIGIN RESOURCE SHARING (CORS)                                       #
 14 | # ##############################################################################
 15 | 
 16 | # ------------------------------------------------------------------------------
 17 | # | Cross-domain AJAX requests                                                 |
 18 | # ------------------------------------------------------------------------------
 19 | 
 20 | # Enable cross-origin AJAX requests.
 21 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
 22 | # http://enable-cors.org/
 23 | 
 24 | # 
 25 | #    Header set Access-Control-Allow-Origin "*"
 26 | # 
 27 | 
 28 | # ------------------------------------------------------------------------------
 29 | # | CORS-enabled images                                                        |
 30 | # ------------------------------------------------------------------------------
 31 | 
 32 | # Send the CORS header for images when browsers request it.
 33 | # https://developer.mozilla.org/en/CORS_Enabled_Image
 34 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
 35 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
 36 | 
 37 | 
 38 |     
 39 |         
 40 |             SetEnvIf Origin ":" IS_CORS
 41 |             Header set Access-Control-Allow-Origin "*" env=IS_CORS
 42 |         
 43 |     
 44 | 
 45 | 
 46 | # ------------------------------------------------------------------------------
 47 | # | Web fonts access                                                           |
 48 | # ------------------------------------------------------------------------------
 49 | 
 50 | # Allow access from all domains for web fonts
 51 | 
 52 | 
 53 |     
 54 |         Header set Access-Control-Allow-Origin "*"
 55 |     
 56 | 
 57 | 
 58 | 
 59 | # ##############################################################################
 60 | # # ERRORS                                                                     #
 61 | # ##############################################################################
 62 | 
 63 | # ------------------------------------------------------------------------------
 64 | # | 404 error prevention for non-existing redirected folders                   |
 65 | # ------------------------------------------------------------------------------
 66 | 
 67 | # Prevent Apache from returning a 404 error for a rewrite if a directory
 68 | # with the same name does not exist.
 69 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
 70 | # http://www.webmasterworld.com/apache/3808792.htm
 71 | 
 72 | Options -MultiViews
 73 | 
 74 | # ------------------------------------------------------------------------------
 75 | # | Custom error messages / pages                                              |
 76 | # ------------------------------------------------------------------------------
 77 | 
 78 | # You can customize what Apache returns to the client in case of an error (see
 79 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
 80 | 
 81 | ErrorDocument 404 /404.html
 82 | 
 83 | 
 84 | # ##############################################################################
 85 | # # INTERNET EXPLORER                                                          #
 86 | # ##############################################################################
 87 | 
 88 | # ------------------------------------------------------------------------------
 89 | # | Better website experience                                                  |
 90 | # ------------------------------------------------------------------------------
 91 | 
 92 | # Force IE to render pages in the highest available mode in the various
 93 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
 94 | 
 95 | 
 96 |     Header set X-UA-Compatible "IE=edge"
 97 |     # `mod_headers` can't match based on the content-type, however, we only
 98 |     # want to send this header for HTML pages and not for the other resources
 99 |     
100 |         Header unset X-UA-Compatible
101 |     
102 | 
103 | 
104 | # ------------------------------------------------------------------------------
105 | # | Cookie setting from iframes                                                |
106 | # ------------------------------------------------------------------------------
107 | 
108 | # Allow cookies to be set from iframes in IE.
109 | 
110 | # 
111 | #   Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
112 | # 
113 | 
114 | # ------------------------------------------------------------------------------
115 | # | Screen flicker                                                             |
116 | # ------------------------------------------------------------------------------
117 | 
118 | # Stop screen flicker in IE on CSS rollovers (this only works in
119 | # combination with the `ExpiresByType` directives for images from below).
120 | 
121 | # BrowserMatch "MSIE" brokenvary=1
122 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
123 | # BrowserMatch "Opera" !brokenvary
124 | # SetEnvIf brokenvary 1 force-no-vary
125 | 
126 | 
127 | # ##############################################################################
128 | # # MIME TYPES AND ENCODING                                                    #
129 | # ##############################################################################
130 | 
131 | # ------------------------------------------------------------------------------
132 | # | Proper MIME types for all files                                            |
133 | # ------------------------------------------------------------------------------
134 | 
135 | 
136 | 
137 |   # Audio
138 |     AddType audio/mp4                                   m4a f4a f4b
139 |     AddType audio/ogg                                   oga ogg
140 | 
141 |   # JavaScript
142 |     # Normalize to standard type (it's sniffed in IE anyways):
143 |     # http://tools.ietf.org/html/rfc4329#section-7.2
144 |     AddType application/javascript                      js
145 |     AddType application/json                            json
146 | 
147 |   # Video
148 |     AddType video/mp4                                   mp4 m4v f4v f4p
149 |     AddType video/ogg                                   ogv
150 |     AddType video/webm                                  webm
151 |     AddType video/x-flv                                 flv
152 | 
153 |   # Web fonts
154 |     AddType application/font-woff                       woff
155 |     AddType application/vnd.ms-fontobject               eot
156 | 
157 |     # Browsers usually ignore the font MIME types and sniff the content,
158 |     # however, Chrome shows a warning if other MIME types are used for the
159 |     # following fonts.
160 |     AddType application/x-font-ttf                      ttc ttf
161 |     AddType font/opentype                               otf
162 | 
163 |     # Make SVGZ fonts work on iPad:
164 |     # https://twitter.com/FontSquirrel/status/14855840545
165 |     AddType     image/svg+xml                           svg svgz
166 |     AddEncoding gzip                                    svgz
167 | 
168 |   # Other
169 |     AddType application/octet-stream                    safariextz
170 |     AddType application/x-chrome-extension              crx
171 |     AddType application/x-opera-extension               oex
172 |     AddType application/x-shockwave-flash               swf
173 |     AddType application/x-web-app-manifest+json         webapp
174 |     AddType application/x-xpinstall                     xpi
175 |     AddType application/xml                             atom rdf rss xml
176 |     AddType image/webp                                  webp
177 |     AddType image/x-icon                                ico
178 |     AddType text/cache-manifest                         appcache manifest
179 |     AddType text/vtt                                    vtt
180 |     AddType text/x-component                            htc
181 |     AddType text/x-vcard                                vcf
182 | 
183 | 
184 | 
185 | # ------------------------------------------------------------------------------
186 | # | UTF-8 encoding                                                             |
187 | # ------------------------------------------------------------------------------
188 | 
189 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
190 | AddDefaultCharset utf-8
191 | 
192 | # Force UTF-8 for certain file formats.
193 | 
194 |     AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
195 | 
196 | 
197 | 
198 | # ##############################################################################
199 | # # URL REWRITES                                                               #
200 | # ##############################################################################
201 | 
202 | # ------------------------------------------------------------------------------
203 | # | Rewrite engine                                                             |
204 | # ------------------------------------------------------------------------------
205 | 
206 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is
207 | # necessary for the following directives to work.
208 | 
209 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to
210 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
211 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
212 | 
213 | # Also, some cloud hosting services require `RewriteBase` to be set:
214 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
215 | 
216 | 
217 |     Options +FollowSymlinks
218 |   # Options +SymLinksIfOwnerMatch
219 |     RewriteEngine On
220 |   # RewriteBase /
221 | 
222 | 
223 | # ------------------------------------------------------------------------------
224 | # | Suppressing / Forcing the "www." at the beginning of URLs                  |
225 | # ------------------------------------------------------------------------------
226 | 
227 | # The same content should never be available under two different URLs especially
228 | # not with and without "www." at the beginning. This can cause SEO problems
229 | # (duplicate content), therefore, you should choose one of the alternatives and
230 | # redirect the other one.
231 | 
232 | # By default option 1 (no "www.") is activated:
233 | # http://no-www.org/faq.php?q=class_b
234 | 
235 | # If you'd prefer to use option 2, just comment out all the lines from option 1
236 | # and uncomment the ones from option 2.
237 | 
238 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
239 | 
240 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
241 | 
242 | # Option 1: rewrite www.example.com → example.com
243 | 
244 | 
245 |     RewriteCond %{HTTPS} !=on
246 |     RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
247 |     RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
248 | 
249 | 
250 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
251 | 
252 | # Option 2: rewrite example.com → www.example.com
253 | 
254 | # Be aware that the following might not be a good idea if you use "real"
255 | # subdomains for certain parts of your website.
256 | 
257 | # 
258 | #    RewriteCond %{HTTPS} !=on
259 | #    RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
260 | #    RewriteCond %{HTTP_HOST} !=localhost [NC]
261 | #    RewriteCond %{HTTP_HOST} !=127.0.0.1
262 | #    RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
263 | # 
264 | 
265 | 
266 | # ##############################################################################
267 | # # SECURITY                                                                   #
268 | # ##############################################################################
269 | 
270 | # ------------------------------------------------------------------------------
271 | # | Content Security Policy (CSP)                                              |
272 | # ------------------------------------------------------------------------------
273 | 
274 | # You can mitigate the risk of cross-site scripting and other content-injection
275 | # attacks by setting a Content Security Policy which whitelists trusted sources
276 | # of content for your site.
277 | 
278 | # The example header below allows ONLY scripts that are loaded from the current
279 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't
280 | # work as-is for your site!
281 | 
282 | # To get all the details you'll need to craft a reasonable policy for your site,
283 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
284 | # see the specification: http://w3.org/TR/CSP).
285 | 
286 | # 
287 | #    Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
288 | #    
289 | #        Header unset Content-Security-Policy
290 | #    
291 | # 
292 | 
293 | # ------------------------------------------------------------------------------
294 | # | File access                                                                |
295 | # ------------------------------------------------------------------------------
296 | 
297 | # Block access to directories without a default document.
298 | # Usually you should leave this uncommented because you shouldn't allow anyone
299 | # to surf through every directory on your server (which may includes rather
300 | # private places like the CMS's directories).
301 | 
302 | 
303 |     Options -Indexes
304 | 
305 | 
306 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
307 | 
308 | # Block access to hidden files and directories.
309 | # This includes directories used by version control systems such as Git and SVN.
310 | 
311 | 
312 |     RewriteCond %{SCRIPT_FILENAME} -d [OR]
313 |     RewriteCond %{SCRIPT_FILENAME} -f
314 |     RewriteRule "(^|/)\." - [F]
315 | 
316 | 
317 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
318 | 
319 | # Block access to backup and source files.
320 | # These files may be left by some text editors and can pose a great security
321 | # danger when anyone has access to them.
322 | 
323 | 
324 |     Order allow,deny
325 |     Deny from all
326 |     Satisfy All
327 | 
328 | 
329 | # ------------------------------------------------------------------------------
330 | # | Secure Sockets Layer (SSL)                                                 |
331 | # ------------------------------------------------------------------------------
332 | 
333 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
334 | # prevent `https://www.example.com` when your certificate only allows
335 | # `https://secure.example.com`.
336 | 
337 | # 
338 | #    RewriteCond %{SERVER_PORT} !^443
339 | #    RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
340 | # 
341 | 
342 | # ------------------------------------------------------------------------------
343 | # | HTTP Strict Transport Security (HSTS)                                      |
344 | # ------------------------------------------------------------------------------
345 | 
346 | # Force client-side SSL redirection.
347 | 
348 | # If a user types "example.com" in his browser, the above rule will redirect
349 | # him to the secure version of the site. That still leaves a window of oppor-
350 | # tunity (the initial HTTP connection) for an attacker to downgrade or redirect
351 | # the request. The following header ensures that browser will ONLY connect to
352 | # your server via HTTPS, regardless of what the users type in the address bar.
353 | # http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-6.1
354 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
355 | 
356 | # (!) Remove the `includeSubDomains` optional directive if the subdomains are
357 | # not using HTTPS.
358 | 
359 | # 
360 | #    Header set Strict-Transport-Security "max-age=16070400; includeSubDomains"
361 | # 
362 | 
363 | # ------------------------------------------------------------------------------
364 | # | Server software information                                                |
365 | # ------------------------------------------------------------------------------
366 | 
367 | # Avoid displaying the exact Apache version number, the description of the
368 | # generic OS-type and the information about Apache's compiled-in modules.
369 | 
370 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
371 | 
372 | # ServerTokens Prod
373 | 
374 | 
375 | # ##############################################################################
376 | # # WEB PERFORMANCE                                                            #
377 | # ##############################################################################
378 | 
379 | # ------------------------------------------------------------------------------
380 | # | Compression                                                                |
381 | # ------------------------------------------------------------------------------
382 | 
383 | 
384 | 
385 |     # Force compression for mangled headers.
386 |     # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
387 |     
388 |         
389 |             SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
390 |             RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
391 |         
392 |     
393 | 
394 |     # Compress all output labeled with one of the following MIME-types
395 |     # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
396 |     #  and can remove the `` and `` lines
397 |     #  as `AddOutputFilterByType` is still in the core directives).
398 |     
399 |         AddOutputFilterByType DEFLATE application/atom+xml \
400 |                                       application/javascript \
401 |                                       application/json \
402 |                                       application/rss+xml \
403 |                                       application/vnd.ms-fontobject \
404 |                                       application/x-font-ttf \
405 |                                       application/x-web-app-manifest+json \
406 |                                       application/xhtml+xml \
407 |                                       application/xml \
408 |                                       font/opentype \
409 |                                       image/svg+xml \
410 |                                       image/x-icon \
411 |                                       text/css \
412 |                                       text/html \
413 |                                       text/plain \
414 |                                       text/x-component \
415 |                                       text/xml
416 |     
417 | 
418 | 
419 | 
420 | # ------------------------------------------------------------------------------
421 | # | Content transformations                                                    |
422 | # ------------------------------------------------------------------------------
423 | 
424 | # Prevent some of the mobile network providers from modifying the content of
425 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
426 | 
427 | # 
428 | #    Header set Cache-Control "no-transform"
429 | # 
430 | 
431 | # ------------------------------------------------------------------------------
432 | # | ETag removal                                                               |
433 | # ------------------------------------------------------------------------------
434 | 
435 | # Since we're sending far-future expires headers (see below), ETags can
436 | # be removed: http://developer.yahoo.com/performance/rules.html#etags.
437 | 
438 | # `FileETag None` is not enough for every server.
439 | 
440 |     Header unset ETag
441 | 
442 | 
443 | FileETag None
444 | 
445 | # ------------------------------------------------------------------------------
446 | # | Expires headers (for better cache control)                                 |
447 | # ------------------------------------------------------------------------------
448 | 
449 | # The following expires headers are set pretty far in the future. If you don't
450 | # control versioning with filename-based cache busting, consider lowering the
451 | # cache time for resources like CSS and JS to something like 1 week.
452 | 
453 | 
454 | 
455 |     ExpiresActive on
456 |     ExpiresDefault                                      "access plus 1 month"
457 | 
458 |   # CSS
459 |     ExpiresByType text/css                              "access plus 1 year"
460 | 
461 |   # Data interchange
462 |     ExpiresByType application/json                      "access plus 0 seconds"
463 |     ExpiresByType application/xml                       "access plus 0 seconds"
464 |     ExpiresByType text/xml                              "access plus 0 seconds"
465 | 
466 |   # Favicon (cannot be renamed!)
467 |     ExpiresByType image/x-icon                          "access plus 1 week"
468 | 
469 |   # HTML components (HTCs)
470 |     ExpiresByType text/x-component                      "access plus 1 month"
471 | 
472 |   # HTML
473 |     ExpiresByType text/html                             "access plus 0 seconds"
474 | 
475 |   # JavaScript
476 |     ExpiresByType application/javascript                "access plus 1 year"
477 | 
478 |   # Manifest files
479 |     ExpiresByType application/x-web-app-manifest+json   "access plus 0 seconds"
480 |     ExpiresByType text/cache-manifest                   "access plus 0 seconds"
481 | 
482 |   # Media
483 |     ExpiresByType audio/ogg                             "access plus 1 month"
484 |     ExpiresByType image/gif                             "access plus 1 month"
485 |     ExpiresByType image/jpeg                            "access plus 1 month"
486 |     ExpiresByType image/png                             "access plus 1 month"
487 |     ExpiresByType video/mp4                             "access plus 1 month"
488 |     ExpiresByType video/ogg                             "access plus 1 month"
489 |     ExpiresByType video/webm                            "access plus 1 month"
490 | 
491 |   # Web feeds
492 |     ExpiresByType application/atom+xml                  "access plus 1 hour"
493 |     ExpiresByType application/rss+xml                   "access plus 1 hour"
494 | 
495 |   # Web fonts
496 |     ExpiresByType application/font-woff                 "access plus 1 month"
497 |     ExpiresByType application/vnd.ms-fontobject         "access plus 1 month"
498 |     ExpiresByType application/x-font-ttf                "access plus 1 month"
499 |     ExpiresByType font/opentype                         "access plus 1 month"
500 |     ExpiresByType image/svg+xml                         "access plus 1 month"
501 | 
502 | 
503 | 
504 | # ------------------------------------------------------------------------------
505 | # | Filename-based cache busting                                               |
506 | # ------------------------------------------------------------------------------
507 | 
508 | # If you're not using a build process to manage your filename version revving,
509 | # you might want to consider enabling the following directives to route all
510 | # requests such as `/css/style.12345.css` to `/css/style.css`.
511 | 
512 | # To understand why this is important and a better idea than `*.css?v231`, read:
513 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
514 | 
515 | # 
516 | #    RewriteCond %{REQUEST_FILENAME} !-f
517 | #    RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
518 | # 
519 | 
520 | # ------------------------------------------------------------------------------
521 | # | File concatenation                                                         |
522 | # ------------------------------------------------------------------------------
523 | 
524 | # Allow concatenation from within specific CSS and JS files, e.g.:
525 | # Inside of `script.combined.js` you could have
526 | #   
527 | #   
528 | # and they would be included into this single file.
529 | 
530 | # 
531 | #    
532 | #        Options +Includes
533 | #        AddOutputFilterByType INCLUDES application/javascript application/json
534 | #        SetOutputFilter INCLUDES
535 | #    
536 | #    
537 | #        Options +Includes
538 | #        AddOutputFilterByType INCLUDES text/css
539 | #        SetOutputFilter INCLUDES
540 | #    
541 | # 
542 | 
543 | # ------------------------------------------------------------------------------
544 | # | Persistent connections                                                     |
545 | # ------------------------------------------------------------------------------
546 | 
547 | # Allow multiple requests to be sent over the same TCP connection:
548 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
549 | 
550 | # Enable if you serve a lot of static content but, be aware of the
551 | # possible disadvantages!
552 | 
553 | # 
554 | #    Header set Connection Keep-Alive
555 | # 
556 | 


--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 | Click here to see jsHtml5AudioRecorder demo
2 | 


--------------------------------------------------------------------------------
/jsHtml5AudioRecorder.js:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * Object:  jsHtml5AudioRecorder
  3 |  * Version: master
  4 |  * Author:  Edouard Kombo
  5 |  * Twitter: @EdouardKombo
  6 |  * Github:  https://github.com/edouardkombo
  7 |  * Blog:    http://creativcoders.wordpress.com
  8 |  * Url:     https://github.com/edouardkombo/jsHtml5AudioRecorder
  9 |  * 
 10 |  * Dual licensed under the MIT and GPL licenses:
 11 |  * http://www.opensource.org/licenses/mit-license.php
 12 |  * http://www.gnu.org/licenses/gpl.html
 13 |  * 
 14 |  * Record live audio stream in html5 with echo cancellation, save it on server or download directly from browser
 15 |  */
 16 | 
 17 | var jsHtml5AudioRecorder = function(){};
 18 | 
 19 | jsHtml5AudioRecorder.prototype = {
 20 |     audioContext: '',
 21 |     Recorder: false,
 22 |     mediaPath: '',
 23 |     audioExtension: '',
 24 |     audioLink: '',    
 25 |     phpFile: '',
 26 |     fftSize: 2048,
 27 |     audioTagId: 'audio',
 28 |     showStreamOnFinish: false,
 29 |     
 30 |     /**
 31 |      * Get Proper html5 getUsermedia from window.navigator object, depending on the browser
 32 |      * 
 33 |      * @returns {undefined}
 34 |      */
 35 |     init: function (){
 36 |         if (!navigator.getUserMedia) {
 37 |             navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
 38 |         }
 39 |  
 40 |         try {
 41 |             this.audioContext    = new AudioContext();
 42 |             console.log('Audio context set up.');
 43 |             console.log('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));
 44 |             
 45 |         } catch (e) {
 46 |             alert('No web audio support in this browser!');
 47 |         }
 48 |         
 49 |         window.onload = this.onLoad();
 50 |         
 51 |     },
 52 |     
 53 |     /**
 54 |      * Create audio context and live stream, and pass it to getUserMedia html5 api
 55 |      * 
 56 |      * @returns {undefined}
 57 |      */
 58 |     onLoad: function () {
 59 |         navigator.getUserMedia({audio:true}, this.startStream.bind(this), function(e) {
 60 |             alert('No audio stream!');
 61 |             console.log(e);
 62 |         });        
 63 |     },
 64 | 
 65 |     /**
 66 |      * Create live audio stream with echo cancellation
 67 |      * 
 68 |      * @param {Object} stream
 69 |      * @returns {undefined}
 70 |      */
 71 |     startStream: function(stream) {
 72 |         
 73 |         //Create virtual input from empty amplification factor object
 74 |         var virtualInput        = this.audioContext.createGain();
 75 |        
 76 |         //Assign stream source to html5 audio context object
 77 |         var microphone         = this.audioContext.createMediaStreamSource(stream);
 78 |         
 79 |         //Connect media source output to virtualInput input
 80 |         //So, virtualInput now equals microphone input
 81 |         microphone.connect(virtualInput);
 82 | 
 83 |         //Set audio quality and assign it to virtualInput
 84 |         var analyserNode        = this.audioContext.createAnalyser();
 85 |         analyserNode.fftSize    = this.fftSize;
 86 |         virtualInput.connect( analyserNode );
 87 | 
 88 |         //Set the stream to RecorderJs from Matt Diamond
 89 |         this.Recorder           = new Recorder( virtualInput );
 90 | 
 91 |         //Set volume to zero
 92 |         var amplificationFactor = this.audioContext.createGain();
 93 |         amplificationFactor.gain.value     = 0.0;
 94 |         
 95 |         //We set volume to zero to output, so we cancel echo
 96 |         amplificationFactor.connect( this.audioContext.destination );        
 97 |     },   
 98 |     
 99 |     /**
100 |      * Start audio record
101 |      * 
102 |      * @returns {undefined}
103 |      */
104 |     startRecording: function() {
105 |         
106 |         //Delete audio element if already exists to avoid element duplication and cache
107 |         var audioElement = document.getElementById(this.audioTagId);
108 |         if (audioElement) {
109 |             audioElement.remove();
110 |         }
111 |         
112 |         this.Recorder && this.Recorder.record();        
113 |         console.log('Recording audio...');
114 |     },
115 |     
116 |     /**
117 |      * Stop audio record and choose export method (save or download)
118 |      * 
119 |      * @param {String} method
120 |      * @returns {undefined}
121 |      */
122 |     stopRecording: function(method) {
123 |         this.Recorder && this.Recorder.stop();
124 | 
125 |         this.Recorder && this.Recorder.exportWAV(function(blob) {
126 |             console.log(blob);
127 |             
128 |             if (method === 'save') {
129 |                 this.save(blob, false);
130 | 
131 |             } else if (method === 'download') {
132 |                 this.download(blob, false);
133 | 
134 |             } else if (method === 'stream') {
135 |                 this.stream(blob);
136 | 
137 |             } else if (method === 'saveAndDownload') {
138 |                 this.save(blob, false);
139 |                 this.download(blob, false);
140 | 
141 |             } else if (method === 'saveAndStream') {
142 |                 this.save(blob, true);
143 | 
144 |             } else if (method === 'downloadAndStream') {
145 |                 this.download(blob, true);
146 | 
147 |             } else {
148 |                 this.save(blob, false);
149 |             }
150 |             
151 |         }.bind(this));
152 | 
153 |         this.Recorder.clear();
154 |         console.log('Stop Recording audio!');        
155 |     },
156 | 
157 |     /**
158 |      * Save audio file to server
159 |      * 
160 |      * @param {Object} blob
161 |      * @param {String} stream
162 |      * @returns {undefined}
163 |      */
164 |     save: function (blob, stream) {
165 |         
166 |         var datas       = 'path='+this.mediaPath+'&extension='+this.audioExtension;                  
167 | 
168 |         var client = new XMLHttpRequest();
169 |         client.onreadystatechange = function() 
170 |         {
171 |             if (client.readyState === 4 && client.status === 200) 
172 |             {
173 |                 console.log(client.response);
174 |                 
175 |                 //Get audio file link
176 |                 this.audioLink = client.response;
177 |                 
178 |                 if (stream) {
179 |                     this.stream(blob);
180 |                 }                
181 |             }
182 |         }.bind(this);
183 |         client.open("post", this.phpFile+'?'+datas, true);
184 |         client.setRequestHeader("X-Requested-With", "XMLHttpRequest");
185 |         client.setRequestHeader("cache-Control", "no-store, no-cache, must-revalidate");
186 |         client.setRequestHeader("cache-Control", "post-check=0, pre-check=0");
187 |         client.setRequestHeader("cache-Control", "max-age=0");
188 |         client.setRequestHeader("Pragma", "no-cache");            
189 |         client.setRequestHeader("X-File-Name", encodeURIComponent('1'));
190 |         client.setRequestHeader("Content-Type", "application/octet-stream");
191 |         client.send(blob);
192 |     },   
193 | 
194 |     /**
195 |      * Directly download audio file from the browser and stream it if specified
196 |      * 
197 |      * @param {Object} blob
198 |      * @param {String} stream
199 |      * @returns {undefined}
200 |      */
201 |     download: function(blob, stream) {
202 |         
203 |         var url             = window.URL.createObjectURL(blob);
204 |         //Create a link
205 |         var hf              = document.createElement('a');
206 | 
207 |         var temporaryId     = new Date().toISOString();
208 |         
209 |         //Define link attributes
210 |         hf.href             = url;
211 |         hf.id               = temporaryId;
212 |         hf.download         = temporaryId + '.' + this.audioExtension;
213 |         hf.innerHTML        = hf.download;
214 |         hf.style.display    = 'none';
215 |         hf.style.visibility = 'hidden';
216 |         //Append the link inside html code
217 |         document.body.appendChild(hf);
218 | 
219 |         //Simulate click on link to download file, and instantly delete link
220 |         document.getElementById(hf.id).click();
221 |         document.getElementById(hf.id).remove();
222 |         
223 |         if (stream) {
224 |             this.stream(blob);
225 |         }        
226 |     },
227 |     
228 |     /**
229 |      * Stream
230 |      * 
231 |      * @param {Object} blob
232 |      * @returns {undefined}
233 |      */
234 |     stream: function(blob) {
235 |         
236 |         var url             = window.URL.createObjectURL(blob);
237 |         
238 |         //Create audio tag
239 |         var audio           = document.createElement('audio');
240 |         
241 |         //Define audio tag attributes
242 |         audio.src               = url;
243 |         audio.id                = this.audioTagId;
244 |         
245 |         if (this.showStreamOnFinish) {
246 |             audio.style.display     = 'block';
247 |             audio.style.visibility  = 'visible';            
248 |         } else {
249 |             audio.style.display     = 'none';
250 |             audio.style.visibility  = 'hidden';            
251 |         }        
252 |          
253 |         document.body.appendChild(audio);
254 |         
255 |         audio.setAttribute('autoplay', false);         
256 |         audio.setAttribute('controls', true);
257 |         audio.pause();
258 |     }    
259 | };


--------------------------------------------------------------------------------
/jsHtml5AudioRecorder.min.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Object:  jsHtml5AudioRecorder
 3 |  * Version: master
 4 |  * Author:  Edouard Kombo
 5 |  * Twitter: @EdouardKombo
 6 |  * Github:  https://github.com/edouardkombo
 7 |  * Blog:    http://creativcoders.wordpress.com
 8 |  * Url:     https://github.com/edouardkombo/jsHtml5AudioRecorder
 9 |  * 
10 |  * Dual licensed under the MIT and GPL licenses:
11 |  * http://www.opensource.org/licenses/mit-license.php
12 |  * http://www.gnu.org/licenses/gpl.html
13 |  * 
14 |  * Record live audio stream in html5 with echo cancellation, save it on server or download directly from browser
15 |  */
16 | 
17 | var jsHtml5AudioRecorder=function(){};jsHtml5AudioRecorder.prototype={audioContext:"",Recorder:false,mediaPath:"",audioExtension:"",audioLink:"",phpFile:"",fftSize:2048,audioTagId:"audio",showStreamOnFinish:false,init:function(){if(!navigator.getUserMedia){navigator.getUserMedia=navigator.webkitGetUserMedia||navigator.mozGetUserMedia}try{this.audioContext=new AudioContext;console.log("Audio context set up.");console.log("navigator.getUserMedia "+(navigator.getUserMedia?"available.":"not present!"))}catch(e){alert("No web audio support in this browser!")}window.onload=this.onLoad()},onLoad:function(){navigator.getUserMedia({audio:true},this.startStream.bind(this),function(e){alert("No audio stream!");console.log(e)})},startStream:function(e){var t=this.audioContext.createGain();var n=this.audioContext.createMediaStreamSource(e);n.connect(t);var r=this.audioContext.createAnalyser();r.fftSize=this.fftSize;t.connect(r);this.Recorder=new Recorder(t);var i=this.audioContext.createGain();i.gain.value=0;i.connect(this.audioContext.destination)},startRecording:function(){var e=document.getElementById(this.audioTagId);if(e){e.remove()}this.Recorder&&this.Recorder.record();console.log("Recording audio...")},stopRecording:function(e){this.Recorder&&this.Recorder.stop();this.Recorder&&this.Recorder.exportWAV(function(t){console.log(t);if(e==="save"){this.save(t,false)}else if(e==="download"){this.download(t,false)}else if(e==="stream"){this.stream(t)}else if(e==="saveAndDownload"){this.save(t,false);this.download(t,false)}else if(e==="saveAndStream"){this.save(t,true)}else if(e==="downloadAndStream"){this.download(t,true)}else{this.save(t,false)}}.bind(this));this.Recorder.clear();console.log("Stop Recording audio!")},save:function(e,t){var n="path="+this.mediaPath+"&extension="+this.audioExtension;var r=new XMLHttpRequest;r.onreadystatechange=function(){if(r.readyState===4&&r.status===200){console.log(r.response);this.audioLink=r.response;if(t){this.stream(e)}}}.bind(this);r.open("post",this.phpFile+"?"+n,true);r.setRequestHeader("X-Requested-With","XMLHttpRequest");r.setRequestHeader("cache-Control","no-store, no-cache, must-revalidate");r.setRequestHeader("cache-Control","post-check=0, pre-check=0");r.setRequestHeader("cache-Control","max-age=0");r.setRequestHeader("Pragma","no-cache");r.setRequestHeader("X-File-Name",encodeURIComponent("1"));r.setRequestHeader("Content-Type","application/octet-stream");r.send(e)},download:function(e,t){var n=window.URL.createObjectURL(e);var r=document.createElement("a");var i=(new Date).toISOString();r.href=n;r.id=i;r.download=i+"."+this.audioExtension;r.innerHTML=r.download;r.style.display="none";r.style.visibility="hidden";document.body.appendChild(r);document.getElementById(r.id).click();document.getElementById(r.id).remove();if(t){this.stream(e)}},stream:function(e){var t=window.URL.createObjectURL(e);var n=document.createElement("audio");n.src=t;n.id=this.audioTagId;if(this.showStreamOnFinish){n.style.display="block";n.style.visibility="visible"}else{n.style.display="none";n.style.visibility="hidden"}document.body.appendChild(n);n.setAttribute("autoplay",false);n.setAttribute("controls",true);n.pause()}}
18 | 


--------------------------------------------------------------------------------
/nbproject/project.properties:
--------------------------------------------------------------------------------
 1 | <<<<<<< HEAD
 2 | config.folder=
 3 | file.reference.www-jsHtml5AudioRecorder=.
 4 | files.encoding=UTF-8
 5 | site.root.folder=${file.reference.www-jsHtml5AudioRecorder}
 6 | test.folder=
 7 | =======
 8 | include.path=${php.global.include.path}
 9 | php.version=PHP_53
10 | source.encoding=UTF-8
11 | src.dir=.
12 | tags.asp=false
13 | tags.short=false
14 | web.root=.
15 | >>>>>>> 4001ca9bd1d975894770fd68d4a96daf6d058aea
16 | 


--------------------------------------------------------------------------------
/nbproject/project.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | <<<<<<< HEAD
 4 |     org.netbeans.modules.web.clientproject
 5 |     
 6 |         
 7 | =======
 8 |     org.netbeans.modules.php.project
 9 |     
10 |         
11 | >>>>>>> 4001ca9bd1d975894770fd68d4a96daf6d058aea
12 |             jsHtml5AudioRecorder
13 |         
14 |     
15 | 
16 | 


--------------------------------------------------------------------------------