about summary refs log tree commit diff
path: root/app/javascript/mastodon/features/video/index.js
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-04-27 03:24:09 +0200
committerGitHub <noreply@github.com>2019-04-27 03:24:09 +0200
commitfba96c808d25d2fc35ec63ee6745a1e55a95d707 (patch)
tree01b9427a5d22fbedf92de37df0488aacb55b7fdc /app/javascript/mastodon/features/video/index.js
parentc008911249a2fc0efaf22b83e51ea8510e67acac (diff)
Add blurhash (#10630)
* Add blurhash

* Use fallback color for spoiler when blurhash missing

* Federate the blurhash and accept it as long as it's at most 5x5

* Display unknown media attachments as blurhash placeholders

* Improve style of embed actions and spoiler button

* Change blurhash resolution from 3x3 to 4x4

* Improve dependency definitions

* Fix code style issues
Diffstat (limited to 'app/javascript/mastodon/features/video/index.js')
-rw-r--r--app/javascript/mastodon/features/video/index.js49
1 files changed, 41 insertions, 8 deletions
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 55dd249e1..7b6113e6a 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -7,6 +7,7 @@ import classNames from 'classnames';
 import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
 import { displayMedia } from '../../initial_state';
 import Icon from 'mastodon/components/icon';
+import { decode } from 'blurhash';
 
 const messages = defineMessages({
   play: { id: 'video.play', defaultMessage: 'Play' },
@@ -102,6 +103,7 @@ class Video extends React.PureComponent {
     inline: PropTypes.bool,
     cacheWidth: PropTypes.func,
     intl: PropTypes.object.isRequired,
+    blurhash: PropTypes.string,
   };
 
   state = {
@@ -139,6 +141,7 @@ class Video extends React.PureComponent {
 
   setVideoRef = c => {
     this.video = c;
+
     if (this.video) {
       this.setState({ volume: this.video.volume, muted: this.video.muted });
     }
@@ -152,6 +155,10 @@ class Video extends React.PureComponent {
     this.volume = c;
   }
 
+  setCanvasRef = c => {
+    this.canvas = c;
+  }
+
   handleClickRoot = e => e.stopPropagation();
 
   handlePlay = () => {
@@ -170,7 +177,6 @@ class Video extends React.PureComponent {
   }
 
   handleVolumeMouseDown = e => {
-
     document.addEventListener('mousemove', this.handleMouseVolSlide, true);
     document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
     document.addEventListener('touchmove', this.handleMouseVolSlide, true);
@@ -190,7 +196,6 @@ class Video extends React.PureComponent {
   }
 
   handleMouseVolSlide = throttle(e => {
-
     const rect = this.volume.getBoundingClientRect();
     const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
 
@@ -261,6 +266,10 @@ class Video extends React.PureComponent {
     document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
     document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
     document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
+
+    if (this.props.blurhash) {
+      this._decode();
+    }
   }
 
   componentWillUnmount () {
@@ -270,6 +279,24 @@ class Video extends React.PureComponent {
     document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
   }
 
+  componentDidUpdate (prevProps) {
+    if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
+      this._decode();
+    }
+  }
+
+  _decode () {
+    const hash   = this.props.blurhash;
+    const pixels = decode(hash, 32, 32);
+
+    if (pixels) {
+      const ctx       = this.canvas.getContext('2d');
+      const imageData = new ImageData(pixels, 32, 32);
+
+      ctx.putImageData(imageData, 0, 0);
+    }
+  }
+
   handleFullscreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
   }
@@ -314,6 +341,7 @@ class Video extends React.PureComponent {
 
   handleOpenVideo = () => {
     const { src, preview, width, height, alt } = this.props;
+
     const media = fromJS({
       type: 'video',
       url: src,
@@ -351,6 +379,7 @@ class Video extends React.PureComponent {
     }
 
     let preload;
+
     if (startTime || fullscreen || dragging) {
       preload = 'auto';
     } else if (detailed) {
@@ -360,6 +389,7 @@ class Video extends React.PureComponent {
     }
 
     let warning;
+
     if (sensitive) {
       warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
     } else {
@@ -377,7 +407,9 @@ class Video extends React.PureComponent {
         onClick={this.handleClickRoot}
         tabIndex={0}
       >
-        <video
+        <canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': revealed })} />
+
+        {revealed && <video
           ref={this.setVideoRef}
           src={src}
           poster={preview}
@@ -397,12 +429,13 @@ class Video extends React.PureComponent {
           onLoadedData={this.handleLoadedData}
           onProgress={this.handleProgress}
           onVolumeChange={this.handleVolumeChange}
-        />
+        />}
 
-        <button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
-          <span className='video-player__spoiler__title'>{warning}</span>
-          <span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
-        </button>
+        <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed })}>
+          <button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
+            <span className='spoiler-button__overlay__label'>{warning}</span>
+          </button>
+        </div>
 
         <div className={classNames('video-player__controls', { active: paused || hovered })}>
           <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>