├── src ├── styles │ └── app.styl ├── assets │ └── logo.png ├── main.js ├── shared │ ├── Utils.js │ ├── encoder-ogg-worker.js │ ├── encoder-wav-worker.js │ ├── wave-encoder.js │ ├── encoder-mp3-worker.js │ ├── WebAudioPeakMeter.js │ └── RecorderService.js ├── views │ ├── Version.vue │ ├── Home.vue │ ├── Test2.vue │ ├── Test4.vue │ ├── Test5.vue │ ├── Test3.vue │ ├── Test6.vue │ ├── Diagnostics.vue │ └── Test1.vue ├── router │ └── index.js └── App.vue ├── .eslintignore ├── dist ├── favicon.ico ├── workers │ └── encoders │ │ └── OggVorbisEncoder.min.js.mem ├── index.html └── js │ └── app.a3ad6502.js ├── public ├── favicon.ico ├── workers │ └── encoders │ │ └── OggVorbisEncoder.min.js.mem └── index.html ├── docs ├── scrshot-test1.png └── scrshot-test1b.png ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── .eslintrc.js ├── package.json └── vue.config.js /src/styles/app.styl: -------------------------------------------------------------------------------- 1 | @import '~vuetify/src/stylus/main' 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliatech/web-audio-recording-tests/HEAD/dist/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliatech/web-audio-recording-tests/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliatech/web-audio-recording-tests/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /docs/scrshot-test1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliatech/web-audio-recording-tests/HEAD/docs/scrshot-test1.png -------------------------------------------------------------------------------- /docs/scrshot-test1b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliatech/web-audio-recording-tests/HEAD/docs/scrshot-test1b.png -------------------------------------------------------------------------------- /dist/workers/encoders/OggVorbisEncoder.min.js.mem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliatech/web-audio-recording-tests/HEAD/dist/workers/encoders/OggVorbisEncoder.min.js.mem -------------------------------------------------------------------------------- /public/workers/encoders/OggVorbisEncoder.min.js.mem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliatech/web-audio-recording-tests/HEAD/public/workers/encoders/OggVorbisEncoder.min.js.mem -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | /env 21 | /*.iml 22 | /dload 23 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import Vue from 'vue' 3 | import Vuetify from 'vuetify' 4 | import App from './App.vue' 5 | import router from '@/router/index.js' 6 | 7 | // import colors from 'vuetify/es5/util/colors' 8 | 9 | Vue.use(Vuetify, { 10 | theme: { 11 | primary: '#546E7A', 12 | secondary: '#B0BEC5', 13 | accent: '#448AFF', 14 | error: '#EF5350', 15 | warning: '#FFF176', 16 | info: '#2196f3', 17 | success: '#4caf50' 18 | } 19 | }) 20 | 21 | Vue.config.productionTip = false 22 | 23 | new Vue({ 24 | router, 25 | render: h => h(App) 26 | }).$mount('#app') 27 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 |Manual processing and manipulation of the recording audio stream.
11 |Test microphone input selection. See
10 |
Test using analyzer node. 10 |
11 |Recording with multiple simultaneous script processors. In this case, showing peak levels of recording pre 9 | and post gain.
10 |Raw
39 |Tests to verify encoding capabilities while recording. Note that for this test, the native 10 | MediaRecorder is disabled even on browsers that have it.
11 |86 | This is important mostly for safari/edge or any browser without native MediaRecorder support. Without native 87 | encoding, most encoding implementations encode the incoming audio stream to uncompressed PCM audio/wav. 88 | However, this significantly limits the duration of the recording due to memory constraints. And if not encoded 89 | before upload, the upload is significantly larger than it would be if using a compressed encoding. Encoding 90 | the wav data after recording, before uploading, is relatively straightforward. This test is specifically to 91 | test encoding while recording. 92 |
93 |94 | This test uses web workers to do the encoding. Check out 95 | this demo 97 | 98 | for testing encoding with and without workers. 99 |
100 |101 | If using ogg encoder, the resulting recordings will not playback on ios/safari because it doesn't support the 102 | format natively. Also, I was unable to get the minified version (~800k) of the ogg encoder to work. The 103 | unminified version currently used in this test is ~2.3MB (or ~400k gzipped). 104 |
105 | 106 |Click start/stop multiple times to create multiple recordings.
9 |71 | As of 14.3, iOS appears to have a full MediaRecorder implementation. These tests use feature detection, and now 72 | use MediaRecorder on iOS. I have not done extensive testing, but all of these tests seems to work on latest iOS. 73 |
74 |75 | This makes many of the notes below no longer relevant. 76 |
77 | 78 |83 | There are multiple valid ways to do audio recording and playback on every browser/device combination 84 | except iOS/Safari. I believe there are four significant issues, and only the first one seems to be well 85 | known. 86 |
87 |91 | The most common and widely known issue issue is that the success handler of the getUserMedia is no longer 92 | in the context of the user click. As a result, any audio context and processors created in the handler 93 | won't work. The solution is to create the audio context and any processors before calling getUserMedia, 94 | while still in the user click handler context. Then once getUserMedia provides the stream, use the 95 | previously created constructs to setup the graph and start recording. 96 |
99 | There are a number of demos that work for the first recording on iOS/Safari, that then fail with empty 100 | audio on subsequent recordings until page is reloaded. The reason is that after stopping recording and 101 | extracting playable blob, the next recording requires a new AudioStream. This AudioStream can come from 102 | another call to getUserMedia (which won't prompt user after the first time), or potentially, by cloning 103 | the existing audio stream. 104 |
107 | After granting permission to microphone, iOS/Safari will show a red bar notification anytime user switches 108 | away from the tab where permission was granted. To clear this bar, the recording stream's tracks can be 109 | stopped after recording is finished. This can be tested with the checkbox above. Stopping the tracks and 110 | closing the audio context is straightforward and works well, except for this last issue (but see updates): 111 |
112 |119 | To see this, make a recording and verify it plays. Switch to mail app, then back to safari and 120 | make/verify another recording. As long as red bar/microphone is still visible, it generally works. Then, 121 | check the option to stop tracks and close audio context. Make another recording and verify. Switch to 122 | mail app and back and try to make another recording. Most of the time the recording will appear to work, 123 | but the audio will be silent. As far as I can tell, there is no way to detect this, and there is no way 124 | to recover without loading a new tab or force quitting Safari. 125 |
126 |If the tracks are not stopped and so the red bar/icon remains, then this occurs much less 127 | frequently. Of course, that means the red bar is constantly visible though. And even then, starting 128 | another app that uses the microphone will almost always break things again. I have not been able to find 129 | a way to detect, much less, programmatically fix things, when this occurs. My assumption is that the 130 | underlying issue is due to low level iOS/Safari bugs, and not in how this code is setting things up.
131 |UPDATED 2018-04-10: After writing the previous section, I rewrote a number of things and removed all 136 | dependencies. By doing that I was able to ensure that everything gets cleaned up when recording is 137 | complete. I now no longer have stability issues on iOS, though I don't know exactly what was causing it 138 | previously. I thought perhaps it was the web worker, which I now close when recording is complete. But 139 | quick testing shows that even if I don't close the webworker, this new handling seems to be stable after 140 | sleep/lock/switch events.
141 |145 | I haven't vetted this 100%, but before 11.3 release I thought the "Add to Home Screen" and launching as a 146 | full screen web app using apple-mobile-web-app-capable tag worked fine. With 11.3 it no longer works. It 147 | appears as if navigator.mediaDevices is missing completely when launched in this context. Similarly, 148 | clicking links from apps like slack appears to launch in to a context without mediaDevices. Possibly 149 | related 150 | 152 | link on StackOverflow 153 | 154 | . 155 |
156 |