123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- /**
- * @typedef {Object} MicrophonePluginParams
- * @property {MediaStreamConstraints} constraints The constraints parameter is a
- * MediaStreamConstaints object with two members: video and audio, describing
- * the media types requested. Either or both must be specified.
- * @property {number} bufferSize=4096 The buffer size in units of sample-frames.
- * If specified, the bufferSize must be one of the following values: `256`,
- * `512`, `1024`, `2048`, `4096`, `8192`, `16384`
- * @property {number} numberOfInputChannels=1 Integer specifying the number of
- * channels for this node's input. Values of up to 32 are supported.
- * @property {?boolean} deferInit Set to true to manually call
- * `initPlugin('microphone')`
- */
- /**
- * Visualise microphone input in a wavesurfer instance.
- *
- * @implements {PluginClass}
- * @extends {Observer}
- * @example
- * // es6
- * import MicrophonePlugin from 'wavesurfer.microphone.js';
- *
- * // commonjs
- * var MicrophonePlugin = require('wavesurfer.microphone.js');
- *
- * // if you are using <script> tags
- * var MicrophonePlugin = window.WaveSurfer.microphone;
- *
- * // ... initialising wavesurfer with the plugin
- * var wavesurfer = WaveSurfer.create({
- * // wavesurfer options ...
- * plugins: [
- * MicrophonePlugin.create({
- * // plugin options ...
- * })
- * ]
- * });
- */
- export default class MicrophonePlugin {
- /**
- * Microphone plugin definition factory
- *
- * This function must be used to create a plugin definition which can be
- * used by wavesurfer to correctly instantiate the plugin.
- *
- * @param {MicrophonePluginParams} params parameters use to initialise the plugin
- * @return {PluginDefinition} an object representing the plugin
- */
- static create(params) {
- return {
- name: 'microphone',
- deferInit: params && params.deferInit ? params.deferInit : false,
- params: params,
- instance: MicrophonePlugin
- };
- }
- constructor(params, ws) {
- this.params = params;
- this.wavesurfer = ws;
- this.active = false;
- this.paused = false;
- this.reloadBufferFunction = e => this.reloadBuffer(e);
- // cross-browser getUserMedia
- const promisifiedOldGUM = (constraints, successCallback, errorCallback) => {
- // get ahold of getUserMedia, if present
- const getUserMedia = (navigator.getUserMedia ||
- navigator.webkitGetUserMedia ||
- navigator.mozGetUserMedia ||
- navigator.msGetUserMedia
- );
- // Some browsers just don't implement it - return a rejected
- // promise with an error to keep a consistent interface
- if (!getUserMedia) {
- return Promise.reject(
- new Error('getUserMedia is not implemented in this browser')
- );
- }
- // otherwise, wrap the call to the old navigator.getUserMedia with
- // a Promise
- return new Promise((successCallback, errorCallback) => {
- getUserMedia.call(navigator, constraints, successCallback, errorCallback);
- });
- };
- // Older browsers might not implement mediaDevices at all, so we set an
- // empty object first
- if (navigator.mediaDevices === undefined) {
- navigator.mediaDevices = {};
- }
- // Some browsers partially implement mediaDevices. We can't just assign
- // an object with getUserMedia as it would overwrite existing
- // properties. Here, we will just add the getUserMedia property if it's
- // missing.
- if (navigator.mediaDevices.getUserMedia === undefined) {
- navigator.mediaDevices.getUserMedia = promisifiedOldGUM;
- }
- this.constraints = this.params.constraints || {
- video: false,
- audio: true
- };
- this.bufferSize = this.params.bufferSize || 4096;
- this.numberOfInputChannels = this.params.numberOfInputChannels || 1;
- this.numberOfOutputChannels = this.params.numberOfOutputChannels || 1;
- this._onBackendCreated = () => {
- // wavesurfer's AudioContext where we'll route the mic signal to
- this.micContext = this.wavesurfer.backend.getAudioContext();
- };
- }
- init() {
- this.wavesurfer.on('backend-created', this._onBackendCreated);
- if (this.wavesurfer.backend) {
- this._onBackendCreated();
- }
- }
- /**
- * Destroy the microphone plugin.
- */
- destroy() {
- // make sure the buffer is not redrawn during
- // cleanup and demolition of this plugin.
- this.paused = true;
- this.wavesurfer.un('backend-created', this._onBackendCreated);
- this.stop();
- }
- /**
- * Allow user to select audio input device, eg. microphone, and
- * start the visualization.
- */
- start() {
- navigator.mediaDevices.getUserMedia(this.constraints)
- .then((data) => this.gotStream(data))
- .catch((data) => this.deviceError(data));
- }
- /**
- * Pause/resume visualization.
- */
- togglePlay() {
- if (!this.active) {
- // start it first
- this.start();
- } else {
- // toggle paused
- this.paused = !this.paused;
- if (this.paused) {
- this.pause();
- } else {
- this.play();
- }
- }
- }
- /**
- * Play visualization.
- */
- play() {
- this.paused = false;
- this.connect();
- }
- /**
- * Pause visualization.
- */
- pause() {
- this.paused = true;
- // disconnect sources so they can be used elsewhere
- // (eg. during audio playback)
- this.disconnect();
- }
- /**
- * Stop the device stream and remove any remaining waveform drawing from
- * the wavesurfer canvas.
- */
- stop() {
- if (this.active) {
- // stop visualization and device
- this.stopDevice();
- // empty last frame
- this.wavesurfer.empty();
- }
- }
- /**
- * Stop the device and the visualization.
- */
- stopDevice() {
- this.active = false;
- // stop visualization
- this.disconnect();
- // stop stream from device
- if (this.stream) {
- const result = this.detectBrowser();
- // MediaStream.stop is deprecated since:
- // - Firefox 44 (https://www.fxsitecompat.com/en-US/docs/2015/mediastream-stop-has-been-deprecated/)
- // - Chrome 45 (https://developers.google.com/web/updates/2015/07/mediastream-deprecations)
- if ((result.browser === 'chrome' && result.version >= 45) ||
- (result.browser === 'firefox' && result.version >= 44) ||
- (result.browser === 'edge')) {
- if (this.stream.getTracks) { // note that this should not be a call
- this.stream.getTracks().forEach(stream => stream.stop());
- return;
- }
- }
- this.stream.stop();
- }
- }
- /**
- * Connect the media sources that feed the visualization.
- */
- connect() {
- if (this.stream !== undefined) {
- // Create an AudioNode from the stream.
- this.mediaStreamSource = this.micContext.createMediaStreamSource(this.stream);
- this.levelChecker = this.micContext.createScriptProcessor(
- this.bufferSize,
- this.numberOfInputChannels,
- this.numberOfOutputChannels
- );
- this.mediaStreamSource.connect(this.levelChecker);
- this.levelChecker.connect(this.micContext.destination);
- this.levelChecker.onaudioprocess = this.reloadBufferFunction;
- }
- }
- /**
- * Disconnect the media sources that feed the visualization.
- */
- disconnect() {
- if (this.mediaStreamSource !== undefined) {
- this.mediaStreamSource.disconnect();
- }
- if (this.levelChecker !== undefined) {
- this.levelChecker.disconnect();
- this.levelChecker.onaudioprocess = undefined;
- }
- }
- /**
- * Redraw the waveform.
- */
- reloadBuffer(event) {
- if (!this.paused) {
- this.wavesurfer.empty();
- this.wavesurfer.loadDecodedBuffer(event.inputBuffer);
- }
- }
- /**
- * Audio input device is ready.
- *
- * @param {LocalMediaStream} stream The microphone's media stream.
- */
- gotStream(stream) {
- this.stream = stream;
- this.active = true;
- // start visualization
- this.play();
- // notify listeners
- this.fireEvent('deviceReady', stream);
- }
- /**
- * Device error callback.
- */
- deviceError(code) {
- // notify listeners
- this.fireEvent('deviceError', code);
- }
- /**
- * Extract browser version out of the provided user agent string.
- * @param {!string} uastring userAgent string.
- * @param {!string} expr Regular expression used as match criteria.
- * @param {!number} pos position in the version string to be returned.
- * @return {!number} browser version.
- */
- extractVersion(uastring, expr, pos) {
- const match = uastring.match(expr);
- return match && match.length >= pos && parseInt(match[pos], 10);
- }
- /**
- * Browser detector.
- * @return {object} result containing browser, version and minVersion
- * properties.
- */
- detectBrowser() {
- // Returned result object.
- const result = {};
- result.browser = null;
- result.version = null;
- result.minVersion = null;
- // Non supported browser.
- if (typeof window === 'undefined' || !window.navigator) {
- result.browser = 'Not a supported browser.';
- return result;
- }
- // Firefox.
- if (navigator.mozGetUserMedia) {
- result.browser = 'firefox';
- result.version = this.extractVersion(navigator.userAgent, /Firefox\/([0-9]+)\./, 1);
- result.minVersion = 31;
- return result;
- }
- // Chrome/Chromium/Webview.
- if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) {
- result.browser = 'chrome';
- result.version = this.extractVersion(navigator.userAgent, /Chrom(e|ium)\/([0-9]+)\./, 2);
- result.minVersion = 38;
- return result;
- }
- // Edge.
- if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
- result.browser = 'edge';
- result.version = this.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2);
- result.minVersion = 10547;
- return result;
- }
- // Non supported browser default.
- result.browser = 'Not a supported browser.';
- return result;
- }
- }
|