@chayns/react-audio-editor
Version:
Tool to edit audio tracks.
509 lines (444 loc) • 22.2 kB
JavaScript
;
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;