import Icon from 'flavours/glitch/components/icon';
import classNames from 'classnames';
import { throttle } from 'lodash';
-import { encode, decode } from 'blurhash';
import { getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
import { debounce } from 'lodash';
-const digitCharacters = [
- '0',
- '1',
- '2',
- '3',
- '4',
- '5',
- '6',
- '7',
- '8',
- '9',
- 'A',
- 'B',
- 'C',
- 'D',
- 'E',
- 'F',
- 'G',
- 'H',
- 'I',
- 'J',
- 'K',
- 'L',
- 'M',
- 'N',
- 'O',
- 'P',
- 'Q',
- 'R',
- 'S',
- 'T',
- 'U',
- 'V',
- 'W',
- 'X',
- 'Y',
- 'Z',
- 'a',
- 'b',
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i',
- 'j',
- 'k',
- 'l',
- 'm',
- 'n',
- 'o',
- 'p',
- 'q',
- 'r',
- 's',
- 't',
- 'u',
- 'v',
- 'w',
- 'x',
- 'y',
- 'z',
- '#',
- '$',
- '%',
- '*',
- '+',
- ',',
- '-',
- '.',
- ':',
- ';',
- '=',
- '?',
- '@',
- '[',
- ']',
- '^',
- '_',
- '{',
- '|',
- '}',
- '~',
-];
-
-const decode83 = (str) => {
- let value = 0;
- let c, digit;
-
- for (let i = 0; i < str.length; i++) {
- c = str[i];
- digit = digitCharacters.indexOf(c);
- value = value * 83 + digit;
- }
-
- return value;
-};
-
-const decodeRGB = int => ({
- r: Math.max(0, (int >> 16)),
- g: Math.max(0, (int >> 8) & 255),
- b: Math.max(0, (int & 255)),
-});
-
-const luma = ({ r, g, b }) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
-
-const adjustColor = ({ r, g, b }, lumaThreshold = 100) => {
- let delta;
-
- if (luma({ r, g, b }) >= lumaThreshold) {
- delta = -80;
- } else {
- delta = 80;
- }
-
- return {
- r: r + delta,
- g: g + delta,
- b: b + delta,
- };
+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})`;
};
const messages = defineMessages({
fullscreen: PropTypes.bool,
intl: PropTypes.object.isRequired,
cacheWidth: PropTypes.func,
- blurhash: PropTypes.string,
+ backgroundColor: PropTypes.string,
+ foregroundColor: PropTypes.string,
+ accentColor: PropTypes.string,
};
state = {
muted: false,
volume: 0.5,
dragging: false,
- color: { r: 255, g: 255, b: 255 },
};
setPlayerRef = c => {
}
}
- setBlurhashCanvasRef = c => {
- this.blurhashCanvas = c;
- }
-
setCanvasRef = c => {
this.canvas = c;
componentDidMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
-
- if (!this.props.blurhash) {
- const img = new Image();
- img.crossOrigin = 'anonymous';
- img.onload = () => this.handlePosterLoad(img);
- img.src = this.props.poster;
- } else {
- this._setColorScheme();
- this._decodeBlurhash();
- }
}
componentDidUpdate (prevProps, prevState) {
- if (prevProps.poster !== this.props.poster && !this.props.blurhash) {
- const img = new Image();
- img.crossOrigin = 'anonymous';
- img.onload = () => this.handlePosterLoad(img);
- img.src = this.props.poster;
- }
-
- if (prevState.blurhash !== this.state.blurhash || prevProps.blurhash !== this.props.blurhash) {
- this._setColorScheme();
- this._decodeBlurhash();
+ if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height) {
+ this._clear();
+ this._draw();
}
-
- this._clear();
- this._draw();
- }
-
- _decodeBlurhash () {
- const context = this.blurhashCanvas.getContext('2d');
- const pixels = decode(this.props.blurhash || this.state.blurhash, 32, 32);
- const outputImageData = new ImageData(pixels, 32, 32);
-
- context.putImageData(outputImageData, 0, 0);
}
componentWillUnmount () {
this.analyser = analyser;
}
- handlePosterLoad = image => {
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
-
- canvas.width = image.width;
- canvas.height = image.height;
-
- context.drawImage(image, 0, 0);
-
- const inputImageData = context.getImageData(0, 0, image.width, image.height);
- const blurhash = encode(inputImageData.data, image.width, image.height, 4, 4);
-
- this.setState({ blurhash });
- }
-
- _setColorScheme () {
- const blurhash = this.props.blurhash || this.state.blurhash;
- const averageColor = decodeRGB(decode83(blurhash.slice(2, 6)));
-
- this.setState({
- color: adjustColor(averageColor),
- darkText: luma(averageColor) >= 165,
- });
- }
-
handleDownload = () => {
fetch(this.props.src).then(res => res.blob()).then(blob => {
const element = document.createElement('a');
const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2);
- const mainColor = `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`;
- const lastColor = `rgba(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b}, 0)`;
+ const mainColor = this._getAccentColor();
+ const lastColor = hex2rgba(mainColor, 0);
gradient.addColorStop(0, mainColor);
gradient.addColorStop(0.6, mainColor);
return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient()));
}
- _getColor () {
- return `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`;
+ _getAccentColor () {
+ return this.props.accentColor || '#ffffff';
+ }
+
+ _getBackgroundColor () {
+ return this.props.backgroundColor || '#000000';
+ }
+
+ _getForegroundColor () {
+ return this.props.foregroundColor || '#ffffff';
}
render () {
const { src, intl, alt, editable } = this.props;
- const { paused, muted, volume, currentTime, duration, buffer, darkText, dragging } = this.state;
+ const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state;
const progress = (currentTime / duration) * 100;
return (
- <div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+ <div className={classNames('audio-player', { editable })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<audio
src={src}
ref={this.setAudioRef}
/>
<canvas
- className='audio-player__background'
- onClick={this.togglePlay}
- width='32'
- height='32'
- style={{ width: this.state.width, height: this.state.height, position: 'absolute', top: 0, left: 0 }}
- ref={this.setBlurhashCanvasRef}
- aria-label={alt}
- title={alt}
role='button'
- tabIndex='0'
- />
-
- <canvas
className='audio-player__canvas'
width={this.state.width}
height={this.state.height}
- style={{ width: '100%', position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}
+ style={{ width: '100%', position: 'absolute', top: 0, left: 0 }}
ref={this.setCanvasRef}
+ onClick={this.togglePlay}
+ title={alt}
+ aria-label={alt}
/>
<img
<div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
<div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
- <div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getColor() }} />
+ <div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getAccentColor() }} />
<span
className={classNames('video-player__seek__handle', { active: dragging })}
tabIndex='0'
- style={{ left: `${progress}%`, backgroundColor: this._getColor() }}
+ style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
/>
</div>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}>
- <div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getColor() }} />
+ <div className='video-player__volume__current' style={{ width: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} />
<span
className={classNames('video-player__volume__handle')}
tabIndex='0'
- style={{ left: `${volume * 100}%`, backgroundColor: this._getColor() }}
+ style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }}
/>
</div>