UNPKG

@chayns/react-audio-editor

Version:

Tool to edit audio tracks.

509 lines (444 loc) 22.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = undefined; var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _promise = require('babel-runtime/core-js/promise'); var _promise2 = _interopRequireDefault(_promise); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); var _defineProperty3 = _interopRequireDefault(_defineProperty2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _class, _temp, _initialiseProps; var _inlineWorker = require('inline-worker'); var _inlineWorker2 = _interopRequireDefault(_inlineWorker); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var Track = (_temp = _class = function () { function Track(audioFile) { var _Track$filterIds$GAIN, _Track$filterIds$LOW_, _Track$filterIds$HIGH, _filterData; (0, _classCallCheck3.default)(this, Track); _initialiseProps.call(this); switch (chayns.env.browser.name) {// set maximum playback rate case 'firefox': this.maxPlaybackRate = 4; break; case 'chrome': this.maxPlaybackRate = 16; break; case 'edge': this.maxPlaybackRate = 1; break; default: this.maxPlaybackRate = 1; } this.cutListener = function () {}; var AudioContext = window.AudioContext || window.webkitAudioContext; this.audioContext = new AudioContext(); // create invisible audio object this.audioElement = document.createElement('audio'); this.audioElement.src = URL.createObjectURL(audioFile); this.audioElement.setAttribute('controls', ''); this.source = this.audioContext.createMediaElementSource(this.audioElement); this.audioNodes = {}; this.start = 0; this.end = 100; this.filterData = (_filterData = {}, (0, _defineProperty3.default)(_filterData, Track.filterIds.GAIN, (_Track$filterIds$GAIN = {}, (0, _defineProperty3.default)(_Track$filterIds$GAIN, Track.filterOptions.GAIN, 1), (0, _defineProperty3.default)(_Track$filterIds$GAIN, 'enabled', true), (0, _defineProperty3.default)(_Track$filterIds$GAIN, 'filterType', Track.filterTypes.GAIN), _Track$filterIds$GAIN)), (0, _defineProperty3.default)(_filterData, Track.filterIds.LOW_PASS, (_Track$filterIds$LOW_ = {}, (0, _defineProperty3.default)(_Track$filterIds$LOW_, Track.filterOptions.FREQUENCY, 1000), (0, _defineProperty3.default)(_Track$filterIds$LOW_, Track.filterOptions.PEAK, 0), (0, _defineProperty3.default)(_Track$filterIds$LOW_, Track.filterOptions.DETUNE, 0), (0, _defineProperty3.default)(_Track$filterIds$LOW_, 'enabled', false), (0, _defineProperty3.default)(_Track$filterIds$LOW_, 'filterType', Track.filterTypes.BIQUAD), _Track$filterIds$LOW_)), (0, _defineProperty3.default)(_filterData, Track.filterIds.HIGH_PASS, (_Track$filterIds$HIGH = {}, (0, _defineProperty3.default)(_Track$filterIds$HIGH, Track.filterOptions.FREQUENCY, 1000), (0, _defineProperty3.default)(_Track$filterIds$HIGH, Track.filterOptions.PEAK, 0), (0, _defineProperty3.default)(_Track$filterIds$HIGH, Track.filterOptions.DETUNE, 0), (0, _defineProperty3.default)(_Track$filterIds$HIGH, 'enabled', false), (0, _defineProperty3.default)(_Track$filterIds$HIGH, 'filterType', Track.filterTypes.BIQUAD), _Track$filterIds$HIGH)), _filterData); this.callbacks = {}; this.createAudioNode(Track.filterIds.GAIN, Track.filterTypes.GAIN); this.createAudioNode(Track.filterIds.LOW_PASS, Track.filterTypes.BIQUAD); this.createAudioNode(Track.filterIds.HIGH_PASS, Track.filterTypes.BIQUAD); this.connect(); this.cancelSaving = function () {}; // eslint-disable-next-line func-names this.workerFunction = function () { function init() { // eslint-disable-next-line no-undef importScripts('https://tappqa.tobit.com/training/2017/jg/lame.min.js'); // eslint-disable-next-line no-undef this.mymp3Encoder = new lamejs.Mp3Encoder(2, 48000, 320); this.buffers = []; } function audioprocess(config) { var left = config.left, right = config.right; var l = new Int16Array(left.length); var r = new Int16Array(right.length); for (var i = 0; i < left.length; i += 1) { l[i] = left[i] * 32767.5; } for (var _i = 0; _i < right.length; _i += 1) { r[_i] = right[_i] * 32767.5; } var buffer = this.mymp3Encoder.encodeBuffer(l, r); this.buffers.push(buffer); } function getbuffer(config) { var callbackId = config.callbackId; var mp3Buffer = this.mymp3Encoder.flush(); if (mp3Buffer.length > 0) this.buffers.push(mp3Buffer); this.postMessage({ command: 'getBuffer', data: this.buffers, callbackId: callbackId }); } function cancel() { this.mymp3Encoder.flush(); } this.onmessage = function (e) { switch (e.data.command) { case 'audioprocess': audioprocess(e.data.config); break; case 'getbuffer': getbuffer(e.data.config); break; case 'cancel': cancel(); break; case 'init': init(e.data.config); break; default: break; } }; }; } (0, _createClass3.default)(Track, [{ key: 'createAudioNode', value: function createAudioNode(name, filterType) { switch (filterType) { case Track.filterTypes.GAIN: this.audioNodes[name] = this.audioContext.createGain(); break; case Track.filterTypes.BIQUAD: this.audioNodes[name] = this.audioContext.createBiquadFilter(); this.audioNodes[name].type = name; break; default: break; } } }, { key: 'connect', value: function connect() { var _this = this; this.disconnect(); var keys = (0, _keys2.default)(this.audioNodes); var length = keys.length; keys.slice().reverse().forEach(function (name, index) { if (!_this.filterData[name].enabled) { keys.splice(length - index - 1, 1); } }); keys.forEach(function (name, index, array) { if (index === 0) { _this.source.connect(_this.audioNodes[name]); } if (index === array.length - 1) { _this.audioNodes[name].connect(_this.audioContext.destination); } else { _this.audioNodes[name].connect(_this.audioNodes[array[index + 1]]); } }); } }, { key: 'disconnect', value: function disconnect() { var _this2 = this; this.source.disconnect(); (0, _keys2.default)(this.audioNodes).forEach(function (name) { _this2.audioNodes[name].disconnect(); }); } }, { key: 'getFilterValue', value: function getFilterValue(filterId, argumentName) { return this.filterData[filterId][argumentName]; } }, { key: 'setPlayPauseListener', value: function setPlayPauseListener(listener) { this.audioElement.addEventListener('pause', function () { listener(false); }); this.audioElement.addEventListener('play', function () { listener(true); }); } }, { key: 'beginRecording', value: function beginRecording(source, audioContext) { var _this3 = this; var worker = new _inlineWorker2.default(this.workerFunction); worker.postMessage({ command: 'init', config: {} }); worker.onmessage = function (e) { var cb = _this3.callbacks[e.data.callbackId]; _this3.callbacks[e.data.callbackId] = null; if (typeof cb === 'function') { cb(e.data.data); } }; var processor = audioContext.createScriptProcessor(0, 2, 2); // TODO dynamic number of channels processor.onaudioprocess = function (event) { // TODO use addEventListener("audioprocess") var left = event.inputBuffer.getChannelData(0); var right = event.inputBuffer.getChannelData(1); worker.postMessage({ command: 'audioprocess', config: { left: left, right: right } }); }; source.connect(processor); this.connectToNothing(processor, audioContext); return function () { return new _promise2.default(function (resolve) { if (audioContext.close) audioContext.close(); source.disconnect(); processor.disconnect(); processor.onaudioprocess = null; var callbackId = Math.random().toString(); _this3.callbacks[callbackId] = function (buffers) { resolve(buffers); }; worker.postMessage({ command: 'getbuffer', config: { callbackId: callbackId } }); }); }; } }]); return Track; }(), _class.filterTypes = { GAIN: 0, BIQUAD: 1 }, _class.filterIds = { GAIN: 'gain', LOW_PASS: 'lowpass', HIGH_PASS: 'highpass' }, _class.filterOptions = { GAIN: 'gain', FREQUENCY: 'frequency', PEAK: 'Q', DETUNE: 'detune' }, _initialiseProps = function _initialiseProps() { var _this4 = this; this.getSpectrum = function (callback) { var AudioContext = window.AudioContext || window.webkitAudioContext; _this4.spectrumAudioContext = new AudioContext(); var audioElement = document.createElement('audio'); audioElement.src = _this4.audioElement.src; audioElement.setAttribute('controls', ''); var playbackRate = _this4.maxPlaybackRate; audioElement.playbackRate = playbackRate; _this4.spectrum = { left: [], right: [] }; var source = _this4.spectrumAudioContext.createMediaElementSource(audioElement); var processor = _this4.spectrumAudioContext.createScriptProcessor(512, 2, 2); processor.onaudioprocess = function (event) { // TODO use addEventListener("audioprocess") var left = event.inputBuffer.getChannelData(0); var right = event.inputBuffer.getChannelData(1); var l = 0; var r = 0; for (var i = 0; i < left.length; i += 1) { l += left[i]; } for (var _i2 = 0; _i2 < right.length; _i2 += 1) { r += right[_i2]; } _this4.spectrum.left.push(l); _this4.spectrum.right.push(r); var time = event.playbackTime * playbackRate / audioElement.duration * 100; // TODO dont use event.playbacktime callback(l, r, time); }; source.connect(processor); _this4.connectToNothing(processor, _this4.spectrumAudioContext); audioElement.play(); audioElement.onended = function () { _this4.spectrumAudioContext.close(); }; }; this.connectToNothing = function (audioNode, audioContext) { if (chayns.env.browser.name === 'edge') { var gainNode = audioContext.createGain(); gainNode.gain.value = 0; audioNode.connect(gainNode); gainNode.connect(audioContext.destination); } else { audioNode.connect(audioContext.createMediaStreamDestination()); } }; this.setFilter = function (filterId, filterType, argumentName, value) { _this4.filterData[filterId][argumentName] = value; switch (filterType) { case Track.filterTypes.GAIN: _this4.filterData[filterId][argumentName] = value; _this4.audioNodes[filterId][argumentName].value = value; break; case Track.filterTypes.BIQUAD: _this4.filterData[filterId][argumentName] = value; if (argumentName === 'enabled') { _this4.connect(); } else { _this4.audioNodes[filterId][argumentName].value = value; } break; default: break; } }; this.cut = function (start, end) { _this4.cutListener(start, end); var position = _this4.audioElement.currentTime / _this4.audioElement.duration * 100; if (position < start) { _this4.setPlayTime(start); } else if (position > end) { _this4.pause(); _this4.setPlayTime(start); } _this4.start = start; _this4.end = end; }; this.setCutListener = function (listener) { _this4.cutListener = listener; }; this.setPlayTime = function (time) { _this4.audioElement.currentTime = time * _this4.audioElement.duration / 100; }; this.play = function () { _this4.audioElement.play(); }; this.pause = function () { _this4.audioElement.pause(); }; this.setProgressListener = function (listener) { _this4.audioElement.addEventListener('timeupdate', function () { var position = _this4.audioElement.currentTime / _this4.audioElement.duration * 100; listener(position); if (position > _this4.end) { _this4.pause(); _this4.setPlayTime(_this4.start); } }); }; this.getMp3 = function (callback) { return new _promise2.default(function () { var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(resolve, reject) { var _ref2; var bufferPromises, duration, selectedDuration, numberOfPlayers, AudioContext, interval, progress, lastValue, _loop, i, buffers, buffer, file; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: bufferPromises = []; duration = _this4.audioElement.duration; selectedDuration = (_this4.end - _this4.start) * duration / 100; numberOfPlayers = 1; AudioContext = window.AudioContext || window.webkitAudioContext; interval = 100; progress = 0; lastValue = 0; callback(0); _this4.mp3Interval = setInterval(function () { progress += interval; var value = Math.round(progress / (selectedDuration * 10 / numberOfPlayers)); if (lastValue !== value) { lastValue = value; callback(value); } }, interval); _this4.cancelSaving = function () { clearInterval(_this4.mp3Interval); callback(null); bufferPromises = []; reject(); _this4.cancelSaving = function () {}; }; _loop = function _loop(i) { bufferPromises.push(new _promise2.default(function (resolve2) { var audioPlayer = document.createElement('audio'); audioPlayer.src = _this4.audioElement.src; audioPlayer.setAttribute('controls', ''); audioPlayer.currentTime = _this4.start * duration / 100 + selectedDuration / numberOfPlayers * i; var audioContext = new AudioContext(); var source = audioContext.createMediaElementSource(audioPlayer); var keys = (0, _keys2.default)(_this4.audioNodes); keys.forEach(function (name) { if (_this4.filterData[name].enabled) { var node = void 0; switch (_this4.filterData[name].filterType) { case Track.filterTypes.GAIN: node = audioContext.createGain(); break; case Track.filterTypes.BIQUAD: node = audioContext.createBiquadFilter(); node.type = name; break; default: break; } (0, _keys2.default)(_this4.filterData[name]).forEach(function (key) { if (key !== 'enabled' && key !== 'filterType') { node[key].value = _this4.filterData[name][key]; } }); source.connect(node); source = node; } }); audioPlayer.oncanplaythrough = function () { var stopRecording = _this4.beginRecording(source, audioContext); audioPlayer.play(); setTimeout(function () { var buffer = stopRecording(); audioPlayer.pause(); resolve2(buffer); }, selectedDuration / numberOfPlayers * 1000); }; })); }; for (i = 0; i < numberOfPlayers; i += 1) { _loop(i); } _context.next = 15; return _promise2.default.all(bufferPromises); case 15: buffers = _context.sent; buffer = (_ref2 = []).concat.apply(_ref2, (0, _toConsumableArray3.default)(buffers)); file = new Blob(buffer, { type: 'audio/mp3' }); file.name = 'Track-' + new Date().toLocaleDateString() + '-' + new Date().toLocaleTimeString() + '.mp3'; file.lastModifiedDate = new Date(); resolve(file); clearInterval(_this4.mp3Interval); callback(null); case 23: case 'end': return _context.stop(); } } }, _callee, _this4); })); return function (_x, _x2) { return _ref.apply(this, arguments); }; }()); }; this.remove = function () { // class destructor _this4.audioContext.close(); _this4.spectrumAudioContext.close(); }; }, _temp); exports.default = Track; module.exports = exports.default;