@ -6,7 +6,7 @@ import Icon from 'mastodon/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 'mastodon/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,17 +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 ;
@ -364,20 +351,11 @@ class Audio extends React.PureComponent {
}
}
handleMouseVolSlide = throttle ( e => {
handleMouseVolSlide = throttle ( e => {
const rect = this . volume . getBoundingClientRect ( ) ;
const x = ( e . clientX - rect . left ) / this . volWidth ; // x position within the element.
const { x } = getPointerPosition ( this . volume , e ) ;
if ( ! isNaN ( x ) ) {
if ( ! isNaN ( x ) ) {
let slideamt = x ;
if ( x > 1 ) {
slideamt = 1 ;
} else if ( x < 0 ) {
slideamt = 0 ;
}
this . setState ( { volume : slideamt } , ( ) => {
this . audio . volume = slideamt ;
this . setState ( { volume : x } , ( ) => {
this . audio . volume = x ;
} ) ;
} ) ;
}
}
} , 60 ) ;
} , 60 ) ;
@ -395,6 +373,14 @@ class Audio extends React.PureComponent {
}
}
} , 150 , { trailing : true } ) ;
} , 150 , { trailing : true } ) ;
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 ( ) ;
@ -430,6 +416,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 ( ) ;
@ -593,13 +597,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 volumeWidth = muted ? 0 : volume * this . volWidth ;
const volumeHandleLoc = muted ? this . volHandleOffset ( 0 ) : this . volHandleOffset ( volume ) ;
const progress = ( currentTime / duration ) * 100 ;
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 }
@ -657,18 +658,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 / > < / b u t t o n >
< 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 / > < / b u t t o n >
< 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 / > < / b u t t o n >
< 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 / > < / b u t t o n >
< div className = 'video-player__volume' onMouseDown = { this . handleVolumeMouseDown } ref = { this . setVolumeRef } >
& nbsp ;
< div className = 'video-player__volume__current' style = { { width : ` ${ volumeWidth } px ` , backgroundColor : this . _getColor ( ) } } / >
< 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 ( ) } } / >
< 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 ( ) } }
/ >
/ >
< / d i v >
< / d i v >
< span >
< span className = 'video-player__time' >
< span className = 'video-player__time-current' > { formatTime ( currentTime ) } < / s p a n >
< span className = 'video-player__time-current' > { formatTime ( currentTime ) } < / s p a n >
< span className = 'video-player__time-sep' > / < / s p a n >
< span className = 'video-player__time-sep' > / < / s p a n >
< span className = 'video-player__time-total' > { formatTime ( this . state . duration || Math . floor ( this . props . duration ) ) } < / s p a n >
< span className = 'video-player__time-total' > { formatTime ( this . state . duration || Math . floor ( this . props . duration ) ) } < / s p a n >
@ -676,11 +676,7 @@ class Audio extends React.PureComponent {
< / d i v >
< / d i v >
< 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 ) } >
< a className = 'video-player__download__icon' href = { this . props . src } download >
< Icon id = 'download' fixedWidth / >
< / a >
< / b u t t o n >
< button type = 'button' title = { intl . formatMessage ( messages . download ) } aria - label = { intl . formatMessage ( messages . download ) } onClick = { this . handleDownload } > < Icon id = 'download' fixedWidth / > < / b u t t o n >
< / d i v >
< / d i v >
< / d i v >
< / d i v >
< / d i v >
< / d i v >