|
|
@ -6,7 +6,7 @@ import Icon from 'flavours/glitch/components/icon'; |
|
|
|
import classNames from 'classnames'; |
|
|
|
import classNames from 'classnames'; |
|
|
|
import { throttle } from 'lodash'; |
|
|
|
import { throttle } from 'lodash'; |
|
|
|
import { encode, decode } from 'blurhash'; |
|
|
|
import { encode, decode } from 'blurhash'; |
|
|
|
import { getPointerPosition } from 'mastodon/features/video'; |
|
|
|
import { getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; |
|
|
|
|
|
|
|
|
|
|
|
const digitCharacters = [ |
|
|
|
const digitCharacters = [ |
|
|
|
'0', |
|
|
|
'0', |
|
|
@ -140,7 +140,7 @@ const messages = defineMessages({ |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const TICK_SIZE = 10; |
|
|
|
const TICK_SIZE = 10; |
|
|
|
const PADDING = 180; |
|
|
|
const PADDING = 180; |
|
|
|
|
|
|
|
|
|
|
|
export default @injectIntl |
|
|
|
export default @injectIntl |
|
|
|
class Audio extends React.PureComponent { |
|
|
|
class Audio extends React.PureComponent { |
|
|
@ -150,10 +150,8 @@ class Audio extends React.PureComponent { |
|
|
|
alt: PropTypes.string, |
|
|
|
alt: PropTypes.string, |
|
|
|
poster: PropTypes.string, |
|
|
|
poster: PropTypes.string, |
|
|
|
duration: PropTypes.number, |
|
|
|
duration: PropTypes.number, |
|
|
|
peaks: PropTypes.arrayOf(PropTypes.number), |
|
|
|
|
|
|
|
width: PropTypes.number, |
|
|
|
width: PropTypes.number, |
|
|
|
height: PropTypes.number, |
|
|
|
height: PropTypes.number, |
|
|
|
preload: PropTypes.bool, |
|
|
|
|
|
|
|
editable: PropTypes.bool, |
|
|
|
editable: PropTypes.bool, |
|
|
|
intl: PropTypes.object.isRequired, |
|
|
|
intl: PropTypes.object.isRequired, |
|
|
|
cacheWidth: PropTypes.func, |
|
|
|
cacheWidth: PropTypes.func, |
|
|
@ -171,18 +169,6 @@ class Audio extends React.PureComponent { |
|
|
|
color: { r: 255, g: 255, b: 255 }, |
|
|
|
color: { r: 255, g: 255, b: 255 }, |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// hard coded in components.scss
|
|
|
|
|
|
|
|
// any way to get ::before values programatically?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
volWidth = 50; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
volOffset = 70; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
volHandleOffset = v => { |
|
|
|
|
|
|
|
const offset = v * this.volWidth + this.volOffset; |
|
|
|
|
|
|
|
return (offset > 110) ? 110 : offset; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setPlayerRef = c => { |
|
|
|
setPlayerRef = c => { |
|
|
|
this.player = c; |
|
|
|
this.player = c; |
|
|
|
|
|
|
|
|
|
|
@ -359,24 +345,23 @@ class Audio extends React.PureComponent { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
handleMouseVolSlide = throttle(e => { |
|
|
|
handleMouseVolSlide = throttle(e => { |
|
|
|
const rect = this.volume.getBoundingClientRect(); |
|
|
|
const { x } = getPointerPosition(this.volume, e); |
|
|
|
const x = (e.clientX - rect.left) / this.volWidth; // x position within the element.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(!isNaN(x)) { |
|
|
|
if(!isNaN(x)) { |
|
|
|
let slideamt = x; |
|
|
|
this.setState({ volume: x }, () => { |
|
|
|
|
|
|
|
this.audio.volume = x; |
|
|
|
if (x > 1) { |
|
|
|
|
|
|
|
slideamt = 1; |
|
|
|
|
|
|
|
} else if(x < 0) { |
|
|
|
|
|
|
|
slideamt = 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.setState({ volume: slideamt }, () => { |
|
|
|
|
|
|
|
this.audio.volume = slideamt; |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
}, 60); |
|
|
|
}, 60); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleMouseEnter = () => { |
|
|
|
|
|
|
|
this.setState({ hovered: true }); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleMouseLeave = () => { |
|
|
|
|
|
|
|
this.setState({ hovered: false }); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_initAudioContext () { |
|
|
|
_initAudioContext () { |
|
|
|
const context = new AudioContext(); |
|
|
|
const context = new AudioContext(); |
|
|
|
const analyser = context.createAnalyser(); |
|
|
|
const analyser = context.createAnalyser(); |
|
|
@ -412,6 +397,24 @@ class Audio extends React.PureComponent { |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleDownload = () => { |
|
|
|
|
|
|
|
fetch(this.props.src).then(res => res.blob()).then(blob => { |
|
|
|
|
|
|
|
const element = document.createElement('a'); |
|
|
|
|
|
|
|
const objectURL = URL.createObjectURL(blob); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
element.setAttribute('href', objectURL); |
|
|
|
|
|
|
|
element.setAttribute('download', fileNameFromURL(this.props.src)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(element); |
|
|
|
|
|
|
|
element.click(); |
|
|
|
|
|
|
|
document.body.removeChild(element); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
URL.revokeObjectURL(objectURL); |
|
|
|
|
|
|
|
}).catch(err => { |
|
|
|
|
|
|
|
console.error(err); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_renderCanvas () { |
|
|
|
_renderCanvas () { |
|
|
|
requestAnimationFrame(() => { |
|
|
|
requestAnimationFrame(() => { |
|
|
|
this._clear(); |
|
|
|
this._clear(); |
|
|
@ -575,13 +578,10 @@ class Audio extends React.PureComponent { |
|
|
|
render () { |
|
|
|
render () { |
|
|
|
const { src, intl, alt, editable } = this.props; |
|
|
|
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, darkText, dragging } = this.state; |
|
|
|
|
|
|
|
const progress = (currentTime / duration) * 100; |
|
|
|
const volumeWidth = muted ? 0 : volume * this.volWidth; |
|
|
|
|
|
|
|
const volumeHandleLoc = muted ? this.volHandleOffset(0) : this.volHandleOffset(volume); |
|
|
|
|
|
|
|
const progress = (currentTime / duration) * 100; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.state.height || this.props.height }}> |
|
|
|
<div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.state.height || this.props.height }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> |
|
|
|
<audio |
|
|
|
<audio |
|
|
|
src={src} |
|
|
|
src={src} |
|
|
|
ref={this.setAudioRef} |
|
|
|
ref={this.setAudioRef} |
|
|
@ -639,18 +639,17 @@ class Audio extends React.PureComponent { |
|
|
|
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button> |
|
|
|
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button> |
|
|
|
<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> |
|
|
|
<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='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}> |
|
|
|
<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: `${volumeWidth}px`, backgroundColor: this._getColor() }} /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<span |
|
|
|
<span |
|
|
|
className={classNames('video-player__volume__handle')} |
|
|
|
className={classNames('video-player__volume__handle')} |
|
|
|
tabIndex='0' |
|
|
|
tabIndex='0' |
|
|
|
style={{ left: `${volumeHandleLoc}px`, backgroundColor: this._getColor() }} |
|
|
|
style={{ left: `${volume * 100}%`, backgroundColor: this._getColor() }} |
|
|
|
/> |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<span> |
|
|
|
<span className='video-player__time'> |
|
|
|
<span className='video-player__time-current'>{formatTime(currentTime)}</span> |
|
|
|
<span className='video-player__time-current'>{formatTime(currentTime)}</span> |
|
|
|
<span className='video-player__time-sep'>/</span> |
|
|
|
<span className='video-player__time-sep'>/</span> |
|
|
|
<span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span> |
|
|
|
<span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span> |
|
|
@ -658,11 +657,7 @@ class Audio extends React.PureComponent { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div className='video-player__buttons right'> |
|
|
|
<div className='video-player__buttons right'> |
|
|
|
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)}> |
|
|
|
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} onClick={this.handleDownload}><Icon id='download' fixedWidth /></button> |
|
|
|
<a className='video-player__download__icon' href={this.props.src} download> |
|
|
|
|
|
|
|
<Icon id='download' fixedWidth /> |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
</button> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|