Change audio/video playback to stop playback when out of view ()

Change video player to not loop, since the audio player doesn't

Change playback and mute buttons to feel snappier
This commit is contained in:
Eugen Rochko 2020-01-11 02:02:21 +01:00 committed by GitHub
parent d1f68fb589
commit baa3db3001
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 25 deletions
app/javascript/mastodon/features

View file

@ -37,15 +37,14 @@ class Audio extends React.PureComponent {
volume: 0.5, volume: 0.5,
}; };
// hard coded in components.scss // Hard coded in components.scss
// any way to get ::before values programatically? // Any way to get ::before values programatically?
volWidth = 50; volWidth = 50;
volOffset = 70; volOffset = 70;
volHandleOffset = v => { volHandleOffset = v => {
const offset = v * this.volWidth + this.volOffset; const offset = v * this.volWidth + this.volOffset;
return (offset > 110) ? 110 : offset; return (offset > 110) ? 110 : offset;
} }
@ -61,6 +60,8 @@ class Audio extends React.PureComponent {
if (this.waveform) { if (this.waveform) {
this._updateWaveform(); this._updateWaveform();
} }
window.addEventListener('scroll', this.handleScroll);
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
@ -70,6 +71,8 @@ class Audio extends React.PureComponent {
} }
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll);
if (this.wavesurfer) { if (this.wavesurfer) {
this.wavesurfer.destroy(); this.wavesurfer.destroy();
this.wavesurfer = null; this.wavesurfer = null;
@ -128,16 +131,15 @@ class Audio extends React.PureComponent {
this.loaded = true; this.loaded = true;
} }
this.wavesurfer.play(); this.setState({ paused: false }, () => this.wavesurfer.play());
this.setState({ paused: false });
} else { } else {
this.wavesurfer.pause(); this.setState({ paused: true }, () => this.wavesurfer.pause());
this.setState({ paused: true });
} }
} }
toggleMute = () => { toggleMute = () => {
this.wavesurfer.setMute(!this.state.muted); const muted = !this.state.muted;
this.setState({ muted }, () => this.wavesurfer.setMute(muted));
} }
handleVolumeMouseDown = e => { handleVolumeMouseDown = e => {
@ -176,6 +178,19 @@ class Audio extends React.PureComponent {
} }
}, 60); }, 60);
handleScroll = throttle(() => {
if (!this.waveform || !this.wavesurfer) {
return;
}
const { top, height } = this.waveform.getBoundingClientRect();
const inView = (top <= (window.innerHeight || document.documentElement.clientHeight)) && (top + height >= 0);
if (!this.state.paused && !inView) {
this.setState({ paused: true }, () => this.wavesurfer.pause());
}
}, 150, { trailing: true })
render () { render () {
const { height, intl, alt, editable } = this.props; const { height, intl, alt, editable } = this.props;
const { paused, muted, volume, currentTime } = this.state; const { paused, muted, volume, currentTime } = this.state;

View file

@ -124,12 +124,14 @@ class Video extends React.PureComponent {
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'), revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
}; };
// hard coded in components.scss // Hard-coded in components.scss
// any way to get ::before values programatically? // Any way to get ::before values programatically?
volWidth = 50; volWidth = 50;
volOffset = 70; volOffset = 70;
volHandleOffset = v => { volHandleOffset = v => {
const offset = v * this.volWidth + this.volOffset; const offset = v * this.volWidth + this.volOffset;
return (offset > 110) ? 110 : offset; return (offset > 110) ? 110 : offset;
} }
@ -138,6 +140,7 @@ class Video extends React.PureComponent {
if (c) { if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth); if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
this.setState({ this.setState({
containerWidth: c.offsetWidth, containerWidth: c.offsetWidth,
}); });
@ -205,12 +208,14 @@ class Video extends React.PureComponent {
const x = (e.clientX - rect.left) / this.volWidth; //x position within the element. const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
if(!isNaN(x)) { if(!isNaN(x)) {
var slideamt = x; let slideamt = x;
if(x > 1) { if(x > 1) {
slideamt = 1; slideamt = 1;
} else if(x < 0) { } else if(x < 0) {
slideamt = 0; slideamt = 0;
} }
this.video.volume = slideamt; this.video.volume = slideamt;
this.setState({ volume: slideamt }); this.setState({ volume: slideamt });
} }
@ -252,9 +257,9 @@ class Video extends React.PureComponent {
togglePlay = () => { togglePlay = () => {
if (this.state.paused) { if (this.state.paused) {
this.video.play(); this.setState({ paused: false }, () => this.video.play());
} else { } else {
this.video.pause(); this.setState({ paused: true }, () => this.video.pause());
} }
} }
@ -272,12 +277,16 @@ class Video extends React.PureComponent {
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true); document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true); document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
window.addEventListener('scroll', this.handleScroll);
if (this.props.blurhash) { if (this.props.blurhash) {
this._decode(); this._decode();
} }
} }
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll);
document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true); document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true);
document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true); document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange, true); document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
@ -294,6 +303,7 @@ class Video extends React.PureComponent {
if (prevState.revealed && !this.state.revealed && this.video) { if (prevState.revealed && !this.state.revealed && this.video) {
this.video.pause(); this.video.pause();
} }
if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) { if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
this._decode(); this._decode();
} }
@ -313,6 +323,19 @@ class Video extends React.PureComponent {
} }
} }
handleScroll = throttle(() => {
if (!this.video) {
return;
}
const { top, height } = this.video.getBoundingClientRect();
const inView = (top <= (window.innerHeight || document.documentElement.clientHeight)) && (top + height >= 0);
if (!this.state.paused && !inView) {
this.setState({ paused: true }, () => this.video.pause());
}
}, 150, { trailing: true })
handleFullscreenChange = () => { handleFullscreenChange = () => {
this.setState({ fullscreen: isFullscreen() }); this.setState({ fullscreen: isFullscreen() });
} }
@ -326,8 +349,11 @@ class Video extends React.PureComponent {
} }
toggleMute = () => { toggleMute = () => {
this.video.muted = !this.video.muted; const muted = !this.video.muted;
this.setState({ muted: this.video.muted });
this.setState({ muted }, () => {
this.video.muted = muted;
});
} }
toggleReveal = () => { toggleReveal = () => {
@ -430,7 +456,6 @@ class Video extends React.PureComponent {
src={src} src={src}
poster={preview} poster={preview}
preload={preload} preload={preload}
loop
role='button' role='button'
tabIndex='0' tabIndex='0'
aria-label={alt} aria-label={alt}
@ -495,13 +520,8 @@ class Video extends React.PureComponent {
{(!onCloseVideo && !editable) && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>} {(!onCloseVideo && !editable) && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>} {(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>} {onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
<button type='button' aria-label={intl.formatMessage(messages.download)}> <button type='button' aria-label={intl.formatMessage(messages.download)}><a className='video-player__download__icon' href={this.props.src} download><Icon id={'download'} fixedWidth /></a></button>
<a className='video-player__download__icon' href={this.props.src} download>
<Icon id={'download'} fixedWidth />
</a>
</button>
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button> <button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
</div> </div>
</div> </div>
</div> </div>