diff --git a/.gitignore b/.gitignore index 7b6ed99..71f9247 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -lib/ -dist/ - # Logs logs *.log diff --git a/dist/style.css b/dist/style.css new file mode 100644 index 0000000..f375398 --- /dev/null +++ b/dist/style.css @@ -0,0 +1,99 @@ +.uppy-Audio-container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } + +.uppy-Audio-buttonContainer { + width: 100%; + height: 75px; + border-top: 1px solid #eaeaea; + display: flex; + align-items: center; + justify-content: center; + padding: 0 20px; } + +.uppy-Audio-button { + width: 45px; + height: 45px; + border-radius: 50%; + background-color: #e32437; + color: #fff; + cursor: pointer; + transition: all 0.3s; } + .uppy-Audio-button:focus { + outline: none; } + .uppy-Audio-button::-moz-focus-inner { + border: 0; } + .uppy-Audio-button:focus { + box-shadow: 0 0 0 3px rgba(34, 117, 215, 0.5); } + [data-uppy-theme="dark"] .uppy-Audio-button:focus { + outline: none; } + [data-uppy-theme="dark"] .uppy-Audio-button::-moz-focus-inner { + border: 0; } + [data-uppy-theme="dark"] .uppy-Audio-button:focus { + box-shadow: 0 0 0 2px rgba(170, 225, 255, 0.85); } + +.uppy-Audio-button svg { + width: 30px; + height: 30px; + max-width: 100%; + max-height: 100%; + display: inline-block; + vertical-align: text-top; + overflow: hidden; + fill: currentColor; } + +.uppy-size--md .uppy-Audio-button { + width: 60px; + height: 60px; } + +.uppy-Audio-button:hover { + background-color: #d31b2d; } + +.uppy-Audio-button--picture { + margin-right: 12px; } + +.uppy-Audio-permissons { + padding: 15px; + display: flex; + align-items: center; + justify-content: center; + flex-flow: column wrap; + height: 100%; + flex: 1; } + +.uppy-Audio-permissons p { + max-width: 450px; + line-height: 1.3; + text-align: center; + line-height: 1.45; + color: #939393; + margin: 0; } + +.uppy-Audio-permissonsIcon svg { + width: 100px; + height: 75px; + color: #bbb; + margin-bottom: 30px; } + +.uppy-Audio-recordingLength { + position: absolute; + right: 20px; + color: #757575; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } + +.uppy-Audio-title { + font-size: 22px; + line-height: 1.35; + font-weight: 400; + margin: 0; + margin-bottom: 5px; + padding: 0 15px; + max-width: 500px; + text-align: center; + color: #333; } + [data-uppy-theme="dark"] .uppy-Audio-title { + color: #eaeaea; } diff --git a/dist/style.min.css b/dist/style.min.css new file mode 100644 index 0000000..8ca6c77 --- /dev/null +++ b/dist/style.min.css @@ -0,0 +1 @@ +.uppy-Audio-container{width:100%;height:100%;display:flex;justify-content:center;align-items:center;flex-direction:column}.uppy-Audio-buttonContainer{width:100%;height:75px;border-top:1px solid #eaeaea;display:flex;align-items:center;justify-content:center;padding:0 20px}.uppy-Audio-button{width:45px;height:45px;border-radius:50%;background-color:#e32437;color:#fff;cursor:pointer;transition:all 0.3s}.uppy-Audio-button:focus{outline:none}.uppy-Audio-button::-moz-focus-inner{border:0}.uppy-Audio-button:focus{box-shadow:0 0 0 3px rgba(34,117,215,0.5)}[data-uppy-theme="dark"] .uppy-Audio-button:focus{outline:none}[data-uppy-theme="dark"] .uppy-Audio-button::-moz-focus-inner{border:0}[data-uppy-theme="dark"] .uppy-Audio-button:focus{box-shadow:0 0 0 2px rgba(170,225,255,0.85)}.uppy-Audio-button svg{width:30px;height:30px;max-width:100%;max-height:100%;display:inline-block;vertical-align:text-top;overflow:hidden;fill:currentColor}.uppy-size--md .uppy-Audio-button{width:60px;height:60px}.uppy-Audio-button:hover{background-color:#d31b2d}.uppy-Audio-button--picture{margin-right:12px}.uppy-Audio-permissons{padding:15px;display:flex;align-items:center;justify-content:center;flex-flow:column wrap;height:100%;flex:1}.uppy-Audio-permissons p{max-width:450px;line-height:1.3;text-align:center;line-height:1.45;color:#939393;margin:0}.uppy-Audio-permissonsIcon svg{width:100px;height:75px;color:#bbb;margin-bottom:30px}.uppy-Audio-recordingLength{position:absolute;right:20px;color:#757575;font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}.uppy-Audio-title{font-size:22px;line-height:1.35;font-weight:400;margin:0;margin-bottom:5px;padding:0 15px;max-width:500px;text-align:center;color:#333}[data-uppy-theme="dark"] .uppy-Audio-title{color:#eaeaea} diff --git a/lib/MicrophoneIcon.js b/lib/MicrophoneIcon.js new file mode 100644 index 0000000..190f2fe --- /dev/null +++ b/lib/MicrophoneIcon.js @@ -0,0 +1,17 @@ +var _require = require('preact'), + h = _require.h; + +module.exports = function (props) { + // TODO: Replace this camera icon by a microphone + return h("svg", { + "aria-hidden": "true", + focusable: "false", + fill: "#0097DC", + width: "66", + height: "55", + viewBox: "0 0 66 55" + }, h("path", { + d: "M57.3 8.433c4.59 0 8.1 3.51 8.1 8.1v29.7c0 4.59-3.51 8.1-8.1 8.1H8.7c-4.59 0-8.1-3.51-8.1-8.1v-29.7c0-4.59 3.51-8.1 8.1-8.1h9.45l4.59-7.02c.54-.54 1.35-1.08 2.16-1.08h16.2c.81 0 1.62.54 2.16 1.08l4.59 7.02h9.45zM33 14.64c-8.62 0-15.393 6.773-15.393 15.393 0 8.62 6.773 15.393 15.393 15.393 8.62 0 15.393-6.773 15.393-15.393 0-8.62-6.773-15.393-15.393-15.393zM33 40c-5.648 0-9.966-4.319-9.966-9.967 0-5.647 4.318-9.966 9.966-9.966s9.966 4.319 9.966 9.966C42.966 35.681 38.648 40 33 40z", + "fill-rule": "evenodd" + })); +}; \ No newline at end of file diff --git a/lib/MicrophoneScreen.js b/lib/MicrophoneScreen.js new file mode 100644 index 0000000..30d9c50 --- /dev/null +++ b/lib/MicrophoneScreen.js @@ -0,0 +1,41 @@ +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } + +var _require = require('preact'), + h = _require.h, + Component = _require.Component; + +var RecordButton = require('./RecordButton'); + +var RecordingLength = require('./RecordingLength'); + +function isModeAvailable(modes, mode) { + return modes.indexOf(mode) !== -1; +} + +var MicrophoneScreen = /*#__PURE__*/function (_Component) { + _inheritsLoose(MicrophoneScreen, _Component); + + function MicrophoneScreen() { + return _Component.apply(this, arguments) || this; + } + + var _proto = MicrophoneScreen.prototype; + + _proto.componentWillUnmount = function componentWillUnmount() { + this.props.onStop(); + }; + + _proto.render = function render() { + var shouldShowRecordButton = this.props.supportsRecording; + var shouldShowRecordingLength = this.props.supportsRecording && this.props.showRecordingLength; + return h("div", { + "class": "uppy uppy-Audio-container" + }, h("div", { + "class": "uppy-Webcam-buttonContainer" + }, shouldShowRecordingLength ? RecordingLength(this.props) : null, ' ', shouldShowRecordButton ? RecordButton(this.props) : null)); + }; + + return MicrophoneScreen; +}(Component); + +module.exports = MicrophoneScreen; \ No newline at end of file diff --git a/lib/PermissionsScreen.js b/lib/PermissionsScreen.js new file mode 100644 index 0000000..932c2cd --- /dev/null +++ b/lib/PermissionsScreen.js @@ -0,0 +1,12 @@ +var _require = require('preact'), + h = _require.h; + +module.exports = function (props) { + return h("div", { + "class": "uppy-Audio-permissons" + }, h("div", { + "class": "uppy-Audio-permissonsIcon" + }, props.icon()), h("h1", { + "class": "uppy-Audio-title" + }, props.hasMicrophone ? props.i18n('allowAccessTitle') : props.i18n('noMicrophoneTitle')), h("p", null, props.hasMicrophone ? props.i18n('allowAccessDescription') : props.i18n('noMicrophoneDescription'))); +}; \ No newline at end of file diff --git a/lib/RecordButton.js b/lib/RecordButton.js new file mode 100644 index 0000000..fe30a9a --- /dev/null +++ b/lib/RecordButton.js @@ -0,0 +1,52 @@ +var _require = require('preact'), + h = _require.h; + +module.exports = function RecordButton(_ref) { + var recording = _ref.recording, + onStartRecording = _ref.onStartRecording, + onStopRecording = _ref.onStopRecording, + i18n = _ref.i18n; + + if (recording) { + return h("button", { + "class": "uppy-u-reset uppy-c-btn uppy-Audio-button", + type: "button", + title: i18n('stopRecording'), + "aria-label": i18n('stopRecording'), + onclick: onStopRecording, + "data-uppy-super-focusable": true + }, h("svg", { + "aria-hidden": "true", + focusable: "false", + "class": "uppy-c-icon", + width: "100", + height: "100", + viewBox: "0 0 100 100" + }, h("rect", { + x: "15", + y: "15", + width: "70", + height: "70" + }))); + } + + return h("button", { + "class": "uppy-u-reset uppy-c-btn uppy-Audio-button", + type: "button", + title: i18n('startRecording'), + "aria-label": i18n('startRecording'), + onclick: onStartRecording, + "data-uppy-super-focusable": true + }, h("svg", { + "aria-hidden": "true", + focusable: "false", + "class": "uppy-c-icon", + width: "100", + height: "100", + viewBox: "0 0 100 100" + }, h("circle", { + cx: "50", + cy: "50", + r: "40" + }))); +}; \ No newline at end of file diff --git a/lib/RecordingLength.js b/lib/RecordingLength.js new file mode 100644 index 0000000..1d94503 --- /dev/null +++ b/lib/RecordingLength.js @@ -0,0 +1,16 @@ +var _require = require('preact'), + h = _require.h; + +var formatSeconds = require('./formatSeconds'); + +module.exports = function RecordingLength(_ref) { + var recordingLengthSeconds = _ref.recordingLengthSeconds, + i18n = _ref.i18n; + var formattedRecordingLengthSeconds = formatSeconds(recordingLengthSeconds); + return h("div", { + "class": "uppy-Webcam-recordingLength", + "aria-label": i18n('recordingLength', { + recording_length: formattedRecordingLengthSeconds + }) + }, formattedRecordingLengthSeconds); +}; \ No newline at end of file diff --git a/lib/formatSeconds.js b/lib/formatSeconds.js new file mode 100644 index 0000000..ac7c96e --- /dev/null +++ b/lib/formatSeconds.js @@ -0,0 +1,10 @@ +/** + * Takes an Integer value of seconds (e.g. 83) and converts it into a human-readable formatted string (e.g. '1:23'). + * + * @param {Integer} seconds + * @returns {string} the formatted seconds (e.g. '1:23' for 1 minute and 23 seconds) + * + */ +module.exports = function formatSeconds(seconds) { + return Math.floor(seconds / 60) + ":" + String(seconds % 60).padStart(2, 0); +}; \ No newline at end of file diff --git a/lib/getFileTypeExtension.js b/lib/getFileTypeExtension.js new file mode 100644 index 0000000..794fa23 --- /dev/null +++ b/lib/getFileTypeExtension.js @@ -0,0 +1,11 @@ +// Patched https://github.com/transloadit/uppy/blob/master/packages/%40uppy/utils/src/getFileTypeExtension.js +// Adds wav support +var mimeToExtensions = { + 'audio/wav': 'wav' +}; + +module.exports = function getFileTypeExtension(mimeType) { + // Remove the ; bit in 'video/x-matroska;codecs=avc1' + mimeType = mimeType.replace(/;.*$/, ''); + return mimeToExtensions[mimeType] || null; +}; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..5f017c4 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,426 @@ +var _class, _temp; + +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +var _require = require('preact'), + h = _require.h; + +var _require2 = require('@uppy/core'), + Plugin = _require2.Plugin; + +var Translator = require('@uppy/utils/lib/Translator'); + +var mimeTypes = require('@uppy/utils/lib/mimeTypes'); + +var canvasToBlob = require('@uppy/utils/lib/canvasToBlob'); + +var Recorder = require('opus-recorder/dist/recorder.min.js'); + +var getFileTypeExtension = require('./getFileTypeExtension'); + +var supportsMediaRecorder = require('./supportsMediaRecorder'); + +var MicrophoneIcon = require('./MicrophoneIcon'); + +var MicrophoneScreen = require('./MicrophoneScreen'); + +var PermissionsScreen = require('./PermissionsScreen'); +/** + * Normalize a MIME type or file extension into a MIME type. + * + * @param {string} fileType - MIME type or a file extension prefixed with `.`. + * @returns {string|undefined} The MIME type or `undefined` if the fileType is an extension and is not known. + */ + + +function toMimeType(fileType) { + if (fileType[0] === '.') { + return mimeTypes[fileType.slice(1)]; + } + + return fileType; +} +/** + * Setup getUserMedia, with polyfill for older browsers + * Adapted from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia + */ + + +function getMediaDevices() { + // eslint-disable-next-line compat/compat + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + // eslint-disable-next-line compat/compat + return navigator.mediaDevices; + } + + var _getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia; + + if (!_getUserMedia) { + return null; + } + + return { + getUserMedia: function getUserMedia(opts) { + return new Promise(function (resolve, reject) { + _getUserMedia.call(navigator, opts, resolve, reject); + }); + } + }; +} +/** + * Audio + */ + + +module.exports = (_temp = _class = /*#__PURE__*/function (_Plugin) { + _inheritsLoose(Audio, _Plugin); + + function Audio(uppy, opts) { + var _this; + + _this = _Plugin.call(this, uppy, opts) || this; + _this.mediaDevices = getMediaDevices(); + _this.supportsUserMedia = !!_this.mediaDevices; + _this.protocol = location.protocol.match(/https/i) ? 'https' : 'http'; + _this.id = _this.opts.id || 'Audio'; + _this.title = _this.opts.title || 'Audio'; + _this.type = 'acquirer'; + + _this.icon = function () { + return h("svg", { + "aria-hidden": "true", + focusable: "false", + "data-prefix": "fas", + "data-icon": "microphone", + role: "img", + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 352 512", + "class": "svg-inline--fa fa-microphone fa-w-11" + }, h("path", { + fill: "currentColor", + d: "M176 352c53.02 0 96-42.98 96-96V96c0-53.02-42.98-96-96-96S80 42.98 80 96v160c0 53.02 42.98 96 96 96zm160-160h-16c-8.84 0-16 7.16-16 16v48c0 74.8-64.49 134.82-140.79 127.38C96.71 376.89 48 317.11 48 250.3V208c0-8.84-7.16-16-16-16H16c-8.84 0-16 7.16-16 16v40.16c0 89.64 63.97 169.55 152 181.69V464H96c-8.84 0-16 7.16-16 16v16c0 8.84 7.16 16 16 16h160c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16h-56v-33.77C285.71 418.47 352 344.9 352 256v-48c0-8.84-7.16-16-16-16z", + "class": "" + })); + }; + + _this.defaultLocale = { + strings: { + startRecording: 'Begin audio recording', + stopRecording: 'Stop audio recording', + allowAccessTitle: 'Please allow access to your microphone', + allowAccessDescription: 'In order to record audio with your microphone, please allow microphone access for this site.', + noMicrophoneTitle: 'Microphone Not Available', + noMicrophoneDescription: 'In order to record audio, please connect a microphone device', + recordingStoppedMaxSize: 'Recording stopped because the file size is about to exceed the limit', + recordingLength: 'Recording length %{recording_length}' + } + }; // set default options + + var defaultOptions = { + modes: ['wav'], + showRecordingLength: false, + encoderPath: 'waveWorker.min.js' + }; + _this.opts = _extends({}, defaultOptions, opts); + + _this.i18nInit(); + + _this.install = _this.install.bind(_assertThisInitialized(_this)); + _this.setPluginState = _this.setPluginState.bind(_assertThisInitialized(_this)); + _this.render = _this.render.bind(_assertThisInitialized(_this)); // Microphone controls + + _this._start = _this._start.bind(_assertThisInitialized(_this)); + _this._stop = _this._stop.bind(_assertThisInitialized(_this)); + _this._startRecording = _this._startRecording.bind(_assertThisInitialized(_this)); + _this._stopRecording = _this._stopRecording.bind(_assertThisInitialized(_this)); + _this.audioActive = false; + return _this; + } + + var _proto = Audio.prototype; + + _proto.setOptions = function setOptions(newOpts) { + _Plugin.prototype.setOptions.call(this, newOpts); + + this.i18nInit(); + }; + + _proto.i18nInit = function i18nInit() { + this.translator = new Translator([this.defaultLocale, this.uppy.locale, this.opts.locale]); + this.i18n = this.translator.translate.bind(this.translator); + this.i18nArray = this.translator.translateArray.bind(this.translator); + this.setPluginState(); // so that UI re-renders and we see the updated locale + }; + + _proto.hasMicrophoneCheck = function hasMicrophoneCheck() { + if (!this.mediaDevices) { + return Promise.resolve(false); + } + + return this.mediaDevices.enumerateDevices().then(function (devices) { + return devices.some(function (device) { + return device.kind === 'audioinput'; + }); + }); + }; + + _proto.getConstraints = function getConstraints() { + return { + audio: true + }; + }; + + _proto._start = function _start() { + var _this2 = this; + + if (!this.supportsUserMedia) { + return Promise.reject(new Error('Webcam access not supported')); + } + + this.audioActive = true; + var constraints = this.getConstraints(); + this.hasMicrophoneCheck().then(function (hasMicrophone) { + _this2.setPluginState({ + hasMicrophone: hasMicrophone + }); // ask user for access to their microphone + + + return _this2.mediaDevices.getUserMedia(constraints).then(function (stream) { + _this2.stream = stream; + + _this2.setPluginState({ + microphoneReady: true + }); + })["catch"](function (err) { + _this2.setPluginState({ + microphoneError: err + }); + }); + }); + } + /** + * @returns {object} + */ + ; + + _proto._getRecorderOptions = function _getRecorderOptions() { + var options = {}; // Try to use the `opts.preferredVideoMimeType` or one of the `allowedFileTypes` for the recording. + // If the browser doesn't support it, we'll fall back to the browser default instead. + // Safari doesn't have the `isTypeSupported` API. + // TODO: Reimplement that part for opus-recorder + // See https://github.com/chris-rudmin/opus-recorder#general-config-options + // if (MediaRecorder.isTypeSupported) { + // const { restrictions } = this.uppy.opts + // let preferredVideoMimeTypes = [] + // if (this.opts.preferredVideoMimeType) { + // preferredVideoMimeTypes = [this.opts.preferredVideoMimeType] + // } else if (restrictions.allowedFileTypes) { + // preferredVideoMimeTypes = restrictions.allowedFileTypes.map(toMimeType).filter(isVideoMimeType) + // } + // const acceptableMimeTypes = preferredVideoMimeTypes.filter((candidateType) => + // MediaRecorder.isTypeSupported(candidateType) && + // getFileTypeExtension(candidateType)) + // if (acceptableMimeTypes.length > 0) { + // options.mimeType = acceptableMimeTypes[0] + // } + // } + + options.encoderPath = this.opts.encoderPath; + options.mimeType = "audio/wav"; + options.mediaTrackConstraints = true; // https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints + + options.monitorGain = 0; + options.numberOfChannels = 1; + options.recordingGain = 1; + options.wavBitDepth = 16; + options.streamPages = true; + return options; + }; + + _proto._startRecording = function _startRecording() { + var _this3 = this; + + this.recorder = new Recorder(this._getRecorderOptions()); + this.recordingChunks = []; + var stoppingBecauseOfMaxSize = false; + + this.recorder.ondataavailable = function (data) { + _this3.recordingChunks.push(new Blob([data], { + type: 'audio/wav' + })); + + var restrictions = _this3.uppy.opts.restrictions; + + if (_this3.recordingChunks.length > 1 && restrictions.maxFileSize != null && !stoppingBecauseOfMaxSize) { + var totalSize = _this3.recordingChunks.reduce(function (acc, chunk) { + return acc + chunk.size; + }, 0); // Exclude the initial chunk from the average size calculation because it is likely to be a very small outlier + + + var averageChunkSize = (totalSize - _this3.recordingChunks[0].size) / (_this3.recordingChunks.length - 1); + var expectedEndChunkSize = averageChunkSize * 3; + var maxSize = Math.max(0, restrictions.maxFileSize - expectedEndChunkSize); + + if (totalSize > maxSize) { + stoppingBecauseOfMaxSize = true; + + _this3.uppy.info(_this3.i18n('recordingStoppedMaxSize'), 'warning', 4000); + + _this3._stopRecording(); + } + } + }; // use a "time slice" of 500ms: ondataavailable will be called each 500ms + // smaller time slices mean we can more accurately check the max file size restriction + // this.recorder.start(500) + // FIXME: time slice is not supported by opus-recorder + + + this.recorder.start(this.stream); + + if (this.opts.showRecordingLength) { + // Start the recordingLengthTimer if we are showing the recording length. + this.recordingLengthTimer = setInterval(function () { + var currentRecordingLength = _this3.getPluginState().recordingLengthSeconds; + + _this3.setPluginState({ + recordingLengthSeconds: currentRecordingLength + 1 + }); + }, 1000); + } + + this.setPluginState({ + isRecording: true + }); + }; + + _proto._stopRecording = function _stopRecording() { + var _this4 = this; + + var stopped = new Promise(function (resolve, reject) { + _this4.recorder.onstop = function () { + resolve(); + }; + + _this4.recorder.stop(); + + if (_this4.opts.showRecordingLength) { + // Stop the recordingLengthTimer if we are showing the recording length. + clearInterval(_this4.recordingLengthTimer); + + _this4.setPluginState({ + recordingLengthSeconds: 0 + }); + } + }); + return stopped.then(function () { + _this4.setPluginState({ + isRecording: false + }); + + return _this4.getAudio(); + }).then(function (file) { + try { + _this4.uppy.addFile(file); + } catch (err) { + // Logging the error, exept restrictions, which is handled in Core + if (!err.isRestriction) { + _this4.uppy.log(err); + } + } + }).then(function () { + _this4.recordingChunks = null; + _this4.recorder = null; + }, function (error) { + _this4.recordingChunks = null; + _this4.recorder = null; + throw error; + }); + }; + + _proto._stop = function _stop() { + this.stream.getAudioTracks().forEach(function (track) { + track.stop(); + }); + this.audioActive = false; + this.stream = null; + }; + + _proto.getAudio = function getAudio() { + var mimeType = this.recordingChunks[0].type; + var fileExtension = getFileTypeExtension(mimeType); + + if (!fileExtension) { + return Promise.reject(new Error("Could not retrieve recording: Unsupported media type \"" + mimeType + "\"")); + } + + var name = "audio-" + Date.now() + "." + fileExtension; + var blob = new Blob(this.recordingChunks, { + type: mimeType + }); + var file = { + source: this.id, + name: name, + data: new Blob([blob], { + type: mimeType + }), + type: mimeType + }; + return Promise.resolve(file); + }; + + _proto.render = function render() { + if (!this.audioActive) { + this._start(); + } + + var audioState = this.getPluginState(); + + if (!audioState.microphoneReady || !audioState.hasMicrophone) { + return h(PermissionsScreen, { + icon: MicrophoneIcon, + i18n: this.i18n, + hadMicrophone: audioState.hasMicrophone + }); + } + + return h(MicrophoneScreen, _extends({}, audioState, { + onStartRecording: this._startRecording, + onStopRecording: this._stopRecording, + onStop: this._stop, + i18n: this.i18n, + modes: this.opts.modes, + showRecordingLength: this.opts.showRecordingLength, + supportsRecording: supportsMediaRecorder(), + recording: audioState.isRecording, + src: this.stream + })); + }; + + _proto.install = function install() { + this.setPluginState({ + microphoneReady: false, + recordingLengthSeconds: 0 + }); + var target = this.opts.target; + + if (target) { + this.mount(target, this); + } + }; + + _proto.uninstall = function uninstall() { + if (this.stream) { + this._stop(); + } + + this.unmount(); + }; + + return Audio; +}(Plugin), _defineProperty(_class, "VERSION", require('../package.json').version), _temp); \ No newline at end of file diff --git a/lib/supportsMediaRecorder.js b/lib/supportsMediaRecorder.js new file mode 100644 index 0000000..ebe7ce8 --- /dev/null +++ b/lib/supportsMediaRecorder.js @@ -0,0 +1,3 @@ +module.exports = function supportsMediaRecorder() { + return typeof MediaRecorder === 'function' && !!MediaRecorder.prototype && typeof MediaRecorder.prototype.start === 'function'; +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 11ebd16..ce0d4d5 100644 --- a/src/index.js +++ b/src/index.js @@ -60,13 +60,10 @@ module.exports = class Audio extends Plugin { this.id = this.opts.id || 'Audio' this.title = this.opts.title || 'Audio' this.type = 'acquirer' - // TODO: Replace this camera icon to a microphone icon this.icon = () => ( -