0
0
Fork 0

Audio player visualization improvements (#14281)

* Fix audio player ticks position

* Split visualizer code into own file to comply with license

* Change top-left corner of visualizer always showing peaks, clean up code
This commit is contained in:
ThibG 2020-07-10 13:57:05 +02:00 committed by GitHub
parent a78b87d800
commit a2abe35e0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 149 deletions

View file

@ -7,11 +7,7 @@ import classNames from 'classnames';
import { throttle } from 'lodash';
import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
import { debounce } from 'lodash';
const hex2rgba = (hex, alpha = 1) => {
const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16));
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
import Visualizer from './visualizer';
const messages = defineMessages({
play: { id: 'video.play', defaultMessage: 'Play' },
@ -21,9 +17,6 @@ const messages = defineMessages({
download: { id: 'video.download', defaultMessage: 'Download file' },
});
// Some parts of the canvas rendering code in this file have been adopted from
// https://codepen.io/alexdevp/full/RNELPV by Alex Permyakov
const TICK_SIZE = 10;
const PADDING = 180;
@ -57,6 +50,11 @@ class Audio extends React.PureComponent {
dragging: false,
};
constructor (props) {
super(props);
this.visualizer = new Visualizer(TICK_SIZE);
}
setPlayerRef = c => {
this.player = c;
@ -95,9 +93,7 @@ class Audio extends React.PureComponent {
setCanvasRef = c => {
this.canvas = c;
if (c) {
this.canvasContext = c.getContext('2d');
}
this.visualizer.setCanvas(c);
}
componentDidMount () {
@ -265,17 +261,12 @@ class Audio extends React.PureComponent {
_initAudioContext () {
const context = new AudioContext();
const analyser = context.createAnalyser();
const source = context.createMediaElementSource(this.audio);
analyser.smoothingTimeConstant = 0.6;
analyser.fftSize = 2048;
source.connect(analyser);
this.visualizer.setAudioContext(context, source);
source.connect(context.destination);
this.audioContext = context;
this.analyser = analyser;
}
handleDownload = () => {
@ -308,20 +299,12 @@ class Audio extends React.PureComponent {
});
}
_clear () {
this.canvasContext.clearRect(0, 0, this.state.width, this.state.height);
_clear() {
this.visualizer.clear(this.state.width, this.state.height);
}
_draw () {
this.canvasContext.save();
const ticks = this._getTicks(360 * this._getScaleCoefficient(), TICK_SIZE);
ticks.forEach(tick => {
this._drawTick(tick.x1, tick.y1, tick.x2, tick.y2);
});
this.canvasContext.restore();
_draw() {
this.visualizer.draw(this._getCX(), this._getCY(), this._getAccentColor(), this._getRadius(), this._getScaleCoefficient());
}
_getRadius () {
@ -332,126 +315,6 @@ class Audio extends React.PureComponent {
return (this.state.height || this.props.height) / 982;
}
_getTicks (count, size, animationParams = [0, 90]) {
const radius = this._getRadius();
const ticks = this._getTickPoints(count);
const lesser = 200;
const m = [];
const bufferLength = this.analyser ? this.analyser.frequencyBinCount : 0;
const frequencyData = new Uint8Array(bufferLength);
const allScales = [];
const scaleCoefficient = this._getScaleCoefficient();
if (this.analyser) {
this.analyser.getByteFrequencyData(frequencyData);
}
ticks.forEach((tick, i) => {
const coef = 1 - i / (ticks.length * 2.5);
let delta = ((frequencyData[i] || 0) - lesser * coef) * scaleCoefficient;
if (delta < 0) {
delta = 0;
}
let k;
if (animationParams[0] <= tick.angle && tick.angle <= animationParams[1]) {
k = radius / (radius - this._getSize(tick.angle, animationParams[0], animationParams[1]) - delta);
} else {
k = radius / (radius - (size + delta));
}
const x1 = tick.x * (radius - size);
const y1 = tick.y * (radius - size);
const x2 = x1 * k;
const y2 = y1 * k;
m.push({ x1, y1, x2, y2 });
if (i < 20) {
let scale = delta / (200 * scaleCoefficient);
scale = scale < 1 ? 1 : scale;
allScales.push(scale);
}
});
const scale = allScales.reduce((pv, cv) => pv + cv, 0) / allScales.length;
return m.map(({ x1, y1, x2, y2 }) => ({
x1: x1,
y1: y1,
x2: x2 * scale,
y2: y2 * scale,
}));
}
_getSize (angle, l, r) {
const scaleCoefficient = this._getScaleCoefficient();
const maxTickSize = TICK_SIZE * 9 * scaleCoefficient;
const m = (r - l) / 2;
const x = (angle - l);
let h;
if (x === m) {
return maxTickSize;
}
const d = Math.abs(m - x);
const v = 40 * Math.sqrt(1 / d);
if (v > maxTickSize) {
h = maxTickSize;
} else {
h = Math.max(TICK_SIZE, v);
}
return h;
}
_getTickPoints (count) {
const PI = 360;
const coords = [];
const step = PI / count;
let rad;
for(let deg = 0; deg < PI; deg += step) {
rad = deg * Math.PI / (PI / 2);
coords.push({ x: Math.cos(rad), y: -Math.sin(rad), angle: deg });
}
return coords;
}
_drawTick (x1, y1, x2, y2) {
const cx = this._getCX();
const cy = this._getCY();
const dx1 = Math.ceil(cx + x1);
const dy1 = Math.ceil(cy + y1);
const dx2 = Math.ceil(cx + x2);
const dy2 = Math.ceil(cy + y2);
const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2);
const mainColor = this._getAccentColor();
const lastColor = hex2rgba(mainColor, 0);
gradient.addColorStop(0, mainColor);
gradient.addColorStop(0.6, mainColor);
gradient.addColorStop(1, lastColor);
this.canvasContext.beginPath();
this.canvasContext.strokeStyle = gradient;
this.canvasContext.lineWidth = 2;
this.canvasContext.moveTo(dx1, dy1);
this.canvasContext.lineTo(dx2, dy2);
this.canvasContext.stroke();
}
_getCX() {
return Math.floor(this.state.width / 2);
}