pagraphcontrol/components/volume-peaks-provider/index.js
2020-06-18 16:23:41 +03:00

139 lines
2.8 KiB
JavaScript

const { EventEmitter } = require('events');
const { spawn } = require('child_process');
const { connect } = require('react-redux');
const React = require('react');
const r = require('r-dom');
const { primaryPulseServer } = require('../../reducers/pulse');
const PA_SUBSCRIPTION_EVENT_SOURCE = 0x0001;
const PA_SUBSCRIPTION_EVENT_SINK_INPUT = 0x0002;
const VolumePeaksContext = React.createContext(null);
function spawnProcess({ onPeak, onExit }) {
const process = spawn('papeaks', [
'--output',
'binary',
], {
shell: true,
stdio: [ 'ignore', 'pipe', 'inherit' ],
});
let leftover = null;
const handleData = data => {
if (leftover) {
data = Buffer.concat([ leftover, data ]);
}
let p = 0;
while (p < data.length) {
const left = data.length - p;
if (left >= 12) {
leftover = null;
} else {
leftover = data.slice(p);
break;
}
const type = data.readInt32LE(p);
p += 4;
const index = data.readInt32LE(p);
p += 4;
const peak = data.readFloatLE(p);
p += 4;
const typeString = type === PA_SUBSCRIPTION_EVENT_SOURCE
? 'source'
: (type === PA_SUBSCRIPTION_EVENT_SINK_INPUT
? 'sinkInput'
: 'unexpected');
onPeak(typeString, index, peak);
}
};
const handleExit = () => {
process.off('data', handleData);
process.off('exit', handleExit);
if (onExit) {
onExit();
}
};
process.stdout.on('data', handleData);
process.on('exit', handleExit);
return process;
}
class VolumePeaksProvider extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.emitter = new EventEmitter();
}
static getDerivedStateFromProps(props) {
const state = props.hideLiveVolumePeaks ? 'closed' : props.state;
return { state };
}
componentDidMount() {
if (this.state.state === 'ready') {
this._spawnProcess();
}
}
componentDidUpdate(previousProps, previousState) {
if (this.state.state !== 'ready' && previousState.state === 'ready') {
this._killProcess();
} else if (this.state.state === 'ready' && previousState.state !== 'ready') {
this._spawnProcess();
}
}
componentWillUnmount() {
this._killProcess();
this.emitter.removeAllListeners();
}
_spawnProcess() {
this.process = spawnProcess({
onPeak: (type, index, peak) => {
this.emitter.emit('peak', type, index, peak);
},
});
}
_killProcess() {
if (this.process && !this.process.killed) {
this.process.kill();
}
}
render() {
return r(VolumePeaksContext.Provider, {
value: this.emitter,
}, this.props.children);
}
}
module.exports = {
VolumePeaksProvider: connect(
state => ({
state: state.pulse[primaryPulseServer].state,
hideLiveVolumePeaks: state.preferences.hideLiveVolumePeaks,
}),
)(VolumePeaksProvider),
VolumePeaksConsumer: VolumePeaksContext.Consumer,
};