about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2020-01-11 02:02:21 +0100
committerGitHub <noreply@github.com>2020-01-11 02:02:21 +0100
commitbaa3db3001b576a3d7b1ae5ca8b414416bd4567f (patch)
treeda37d67e91d0bad17aafe132bbde4752082b6b05
parentd1f68fb5898b5720d410cf2e588bea5374129cf2 (diff)
Change audio/video playback to stop playback when out of view (#12486)
Change video player to not loop, since the audio player doesn't

Change playback and mute buttons to feel snappier
-rw-r--r--app/javascript/mastodon/features/audio/index.js35
-rw-r--r--app/javascript/mastodon/features/video/index.js50
2 files changed, 60 insertions, 25 deletions
diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js
index 1b4cdbb4f..fda5a074f 100644
--- a/app/javascript/mastodon/features/audio/index.js
+++ b/app/javascript/mastodon/features/audio/index.js
@@ -37,15 +37,14 @@ class Audio extends React.PureComponent {
     volume: 0.5,
   };
 
-  // hard coded in components.scss
-  // any way to get ::before values programatically?
-
-  volWidth = 50;
-
+  // 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;
   }
 
@@ -61,6 +60,8 @@ class Audio extends React.PureComponent {
     if (this.waveform) {
       this._updateWaveform();
     }
+
+    window.addEventListener('scroll', this.handleScroll);
   }
 
   componentDidUpdate (prevProps) {
@@ -70,6 +71,8 @@ class Audio extends React.PureComponent {
   }
 
   componentWillUnmount () {
+    window.removeEventListener('scroll', this.handleScroll);
+
     if (this.wavesurfer) {
       this.wavesurfer.destroy();
       this.wavesurfer = null;
@@ -128,16 +131,15 @@ class Audio extends React.PureComponent {
         this.loaded = true;
       }
 
-      this.wavesurfer.play();
-      this.setState({ paused: false });
+      this.setState({ paused: false }, () => this.wavesurfer.play());
     } else {
-      this.wavesurfer.pause();
-      this.setState({ paused: true });
+      this.setState({ paused: true }, () => this.wavesurfer.pause());
     }
   }
 
   toggleMute = () => {
-    this.wavesurfer.setMute(!this.state.muted);
+    const muted = !this.state.muted;
+    this.setState({ muted }, () => this.wavesurfer.setMute(muted));
   }
 
   handleVolumeMouseDown = e => {
@@ -176,6 +178,19 @@ class Audio extends React.PureComponent {
     }
   }, 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 () {
     const { height, intl, alt, editable } = this.props;
     const { paused, muted, volume, currentTime } = this.state;
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index f6aeb8c9a..0e2a99416 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -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'),
   };
 
-  // hard coded in components.scss
-  // any way to get ::before values programatically?
-  volWidth = 50;
+  // 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;
   }
 
@@ -138,6 +140,7 @@ class Video extends React.PureComponent {
 
     if (c) {
       if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
+
       this.setState({
         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.
 
     if(!isNaN(x)) {
-      var slideamt = x;
+      let slideamt = x;
+
       if(x > 1) {
         slideamt = 1;
       } else if(x < 0) {
         slideamt = 0;
       }
+
       this.video.volume = slideamt;
       this.setState({ volume: slideamt });
     }
@@ -252,9 +257,9 @@ class Video extends React.PureComponent {
 
   togglePlay = () => {
     if (this.state.paused) {
-      this.video.play();
+      this.setState({ paused: false }, () => this.video.play());
     } 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('MSFullscreenChange', this.handleFullscreenChange, true);
 
+    window.addEventListener('scroll', this.handleScroll);
+
     if (this.props.blurhash) {
       this._decode();
     }
   }
 
   componentWillUnmount () {
+    window.removeEventListener('scroll', this.handleScroll);
+
     document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true);
     document.removeEventListener('webkitfullscreenchange', 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) {
       this.video.pause();
     }
+
     if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
       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 = () => {
     this.setState({ fullscreen: isFullscreen() });
   }
@@ -326,8 +349,11 @@ class Video extends React.PureComponent {
   }
 
   toggleMute = () => {
-    this.video.muted = !this.video.muted;
-    this.setState({ muted: this.video.muted });
+    const muted = !this.video.muted;
+
+    this.setState({ muted }, () => {
+      this.video.muted = muted;
+    });
   }
 
   toggleReveal = () => {
@@ -430,7 +456,6 @@ class Video extends React.PureComponent {
           src={src}
           poster={preview}
           preload={preload}
-          loop
           role='button'
           tabIndex='0'
           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>}
               {(!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>}
-              <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>
+              <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>
               <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>