[Glitch] Add color extraction for audio thumbnails
Port 99f3a55540 to glitch-soc
Signed-off-by: Thibaut Girka <thib@sitedethib.com>
			
			
This commit is contained in:
		
							parent
							
								
									ad73e05f46
								
							
						
					
					
						commit
						69cddc3909
					
				
					 6 changed files with 63 additions and 236 deletions
				
			
		|  | @ -592,7 +592,9 @@ class Status extends ImmutablePureComponent { | ||||||
|                 src={attachment.get('url')} |                 src={attachment.get('url')} | ||||||
|                 alt={attachment.get('description')} |                 alt={attachment.get('description')} | ||||||
|                 poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} |                 poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} | ||||||
|                 blurhash={attachment.get('blurhash')} |                 backgroundColor={attachment.getIn(['meta', 'colors', 'background'])} | ||||||
|  |                 foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])} | ||||||
|  |                 accentColor={attachment.getIn(['meta', 'colors', 'accent'])} | ||||||
|                 duration={attachment.getIn(['meta', 'original', 'duration'], 0)} |                 duration={attachment.getIn(['meta', 'original', 'duration'], 0)} | ||||||
|                 width={this.props.cachedMediaWidth} |                 width={this.props.cachedMediaWidth} | ||||||
|                 height={110} |                 height={110} | ||||||
|  |  | ||||||
|  | @ -5,131 +5,12 @@ import { formatTime } from 'flavours/glitch/features/video'; | ||||||
| import Icon from 'flavours/glitch/components/icon'; | 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 { getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; | import { getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; | ||||||
| import { debounce } from 'lodash'; | import { debounce } from 'lodash'; | ||||||
| 
 | 
 | ||||||
| const digitCharacters = [ | const hex2rgba = (hex, alpha = 1) => { | ||||||
|   '0', |   const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16)); | ||||||
|   '1', |   return `rgba(${r}, ${g}, ${b}, ${alpha})`; | ||||||
|   '2', |  | ||||||
|   '3', |  | ||||||
|   '4', |  | ||||||
|   '5', |  | ||||||
|   '6', |  | ||||||
|   '7', |  | ||||||
|   '8', |  | ||||||
|   '9', |  | ||||||
|   'A', |  | ||||||
|   'B', |  | ||||||
|   'C', |  | ||||||
|   'D', |  | ||||||
|   'E', |  | ||||||
|   'F', |  | ||||||
|   'G', |  | ||||||
|   'H', |  | ||||||
|   'I', |  | ||||||
|   'J', |  | ||||||
|   'K', |  | ||||||
|   'L', |  | ||||||
|   'M', |  | ||||||
|   'N', |  | ||||||
|   'O', |  | ||||||
|   'P', |  | ||||||
|   'Q', |  | ||||||
|   'R', |  | ||||||
|   'S', |  | ||||||
|   'T', |  | ||||||
|   'U', |  | ||||||
|   'V', |  | ||||||
|   'W', |  | ||||||
|   'X', |  | ||||||
|   'Y', |  | ||||||
|   'Z', |  | ||||||
|   'a', |  | ||||||
|   'b', |  | ||||||
|   'c', |  | ||||||
|   'd', |  | ||||||
|   'e', |  | ||||||
|   'f', |  | ||||||
|   'g', |  | ||||||
|   'h', |  | ||||||
|   'i', |  | ||||||
|   'j', |  | ||||||
|   'k', |  | ||||||
|   'l', |  | ||||||
|   'm', |  | ||||||
|   'n', |  | ||||||
|   'o', |  | ||||||
|   'p', |  | ||||||
|   'q', |  | ||||||
|   'r', |  | ||||||
|   's', |  | ||||||
|   't', |  | ||||||
|   'u', |  | ||||||
|   'v', |  | ||||||
|   'w', |  | ||||||
|   'x', |  | ||||||
|   'y', |  | ||||||
|   'z', |  | ||||||
|   '#', |  | ||||||
|   '$', |  | ||||||
|   '%', |  | ||||||
|   '*', |  | ||||||
|   '+', |  | ||||||
|   ',', |  | ||||||
|   '-', |  | ||||||
|   '.', |  | ||||||
|   ':', |  | ||||||
|   ';', |  | ||||||
|   '=', |  | ||||||
|   '?', |  | ||||||
|   '@', |  | ||||||
|   '[', |  | ||||||
|   ']', |  | ||||||
|   '^', |  | ||||||
|   '_', |  | ||||||
|   '{', |  | ||||||
|   '|', |  | ||||||
|   '}', |  | ||||||
|   '~', |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| const decode83 = (str) => { |  | ||||||
|   let value = 0; |  | ||||||
|   let c, digit; |  | ||||||
| 
 |  | ||||||
|   for (let i = 0; i < str.length; i++) { |  | ||||||
|     c = str[i]; |  | ||||||
|     digit = digitCharacters.indexOf(c); |  | ||||||
|     value = value * 83 + digit; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return value; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const decodeRGB = int => ({ |  | ||||||
|   r: Math.max(0, (int >> 16)), |  | ||||||
|   g: Math.max(0, (int >> 8) & 255), |  | ||||||
|   b: Math.max(0, (int & 255)), |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| const luma = ({ r, g, b }) => 0.2126 * r + 0.7152 * g + 0.0722 * b; |  | ||||||
| 
 |  | ||||||
| const adjustColor = ({ r, g, b }, lumaThreshold = 100) => { |  | ||||||
|   let delta; |  | ||||||
| 
 |  | ||||||
|   if (luma({ r, g, b }) >= lumaThreshold) { |  | ||||||
|     delta = -80; |  | ||||||
|   } else { |  | ||||||
|     delta = 80; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return { |  | ||||||
|     r: r + delta, |  | ||||||
|     g: g + delta, |  | ||||||
|     b: b + delta, |  | ||||||
|   }; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|  | @ -157,7 +38,9 @@ class Audio extends React.PureComponent { | ||||||
|     fullscreen: PropTypes.bool, |     fullscreen: PropTypes.bool, | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|     cacheWidth: PropTypes.func, |     cacheWidth: PropTypes.func, | ||||||
|     blurhash: PropTypes.string, |     backgroundColor: PropTypes.string, | ||||||
|  |     foregroundColor: PropTypes.string, | ||||||
|  |     accentColor: PropTypes.string, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
|  | @ -169,7 +52,6 @@ class Audio extends React.PureComponent { | ||||||
|     muted: false, |     muted: false, | ||||||
|     volume: 0.5, |     volume: 0.5, | ||||||
|     dragging: false, |     dragging: false, | ||||||
|     color: { r: 255, g: 255, b: 255 }, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   setPlayerRef = c => { |   setPlayerRef = c => { | ||||||
|  | @ -207,10 +89,6 @@ class Audio extends React.PureComponent { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setBlurhashCanvasRef = c => { |  | ||||||
|     this.blurhashCanvas = c; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   setCanvasRef = c => { |   setCanvasRef = c => { | ||||||
|     this.canvas = c; |     this.canvas = c; | ||||||
| 
 | 
 | ||||||
|  | @ -221,41 +99,13 @@ class Audio extends React.PureComponent { | ||||||
|   |   | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     window.addEventListener('resize', this.handleResize, { passive: true }); |     window.addEventListener('resize', this.handleResize, { passive: true }); | ||||||
| 
 |  | ||||||
|     if (!this.props.blurhash) { |  | ||||||
|       const img = new Image(); |  | ||||||
|       img.crossOrigin = 'anonymous'; |  | ||||||
|       img.onload = () => this.handlePosterLoad(img); |  | ||||||
|       img.src = this.props.poster; |  | ||||||
|     } else { |  | ||||||
|       this._setColorScheme(); |  | ||||||
|       this._decodeBlurhash(); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidUpdate (prevProps, prevState) { |   componentDidUpdate (prevProps, prevState) { | ||||||
|     if (prevProps.poster !== this.props.poster && !this.props.blurhash) { |     if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height) { | ||||||
|       const img = new Image(); |       this._clear(); | ||||||
|       img.crossOrigin = 'anonymous'; |       this._draw(); | ||||||
|       img.onload = () => this.handlePosterLoad(img); |  | ||||||
|       img.src = this.props.poster; |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     if (prevState.blurhash !== this.state.blurhash || prevProps.blurhash !== this.props.blurhash) { |  | ||||||
|       this._setColorScheme(); |  | ||||||
|       this._decodeBlurhash(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this._clear(); |  | ||||||
|     this._draw(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   _decodeBlurhash () { |  | ||||||
|     const context = this.blurhashCanvas.getContext('2d'); |  | ||||||
|     const pixels = decode(this.props.blurhash || this.state.blurhash, 32, 32); |  | ||||||
|     const outputImageData = new ImageData(pixels, 32, 32); |  | ||||||
| 
 |  | ||||||
|     context.putImageData(outputImageData, 0, 0); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillUnmount () { |   componentWillUnmount () { | ||||||
|  | @ -410,31 +260,6 @@ class Audio extends React.PureComponent { | ||||||
|     this.analyser = analyser; |     this.analyser = analyser; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handlePosterLoad = image => { |  | ||||||
|     const canvas  = document.createElement('canvas'); |  | ||||||
|     const context = canvas.getContext('2d'); |  | ||||||
| 
 |  | ||||||
|     canvas.width  = image.width; |  | ||||||
|     canvas.height = image.height; |  | ||||||
| 
 |  | ||||||
|     context.drawImage(image, 0, 0); |  | ||||||
| 
 |  | ||||||
|     const inputImageData = context.getImageData(0, 0, image.width, image.height); |  | ||||||
|     const blurhash = encode(inputImageData.data, image.width, image.height, 4, 4); |  | ||||||
| 
 |  | ||||||
|     this.setState({ blurhash }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   _setColorScheme () { |  | ||||||
|     const blurhash     = this.props.blurhash || this.state.blurhash; |  | ||||||
|     const averageColor = decodeRGB(decode83(blurhash.slice(2, 6))); |  | ||||||
| 
 |  | ||||||
|     this.setState({ |  | ||||||
|       color: adjustColor(averageColor), |  | ||||||
|       darkText: luma(averageColor) >= 165, |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleDownload = () => { |   handleDownload = () => { | ||||||
|     fetch(this.props.src).then(res => res.blob()).then(blob => { |     fetch(this.props.src).then(res => res.blob()).then(blob => { | ||||||
|       const element   = document.createElement('a'); |       const element   = document.createElement('a'); | ||||||
|  | @ -594,8 +419,8 @@ class Audio extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|     const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2); |     const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2); | ||||||
| 
 | 
 | ||||||
|     const mainColor = `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`; |     const mainColor = this._getAccentColor(); | ||||||
|     const lastColor = `rgba(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b}, 0)`; |     const lastColor = hex2rgba(mainColor, 0); | ||||||
| 
 | 
 | ||||||
|     gradient.addColorStop(0, mainColor); |     gradient.addColorStop(0, mainColor); | ||||||
|     gradient.addColorStop(0.6, mainColor); |     gradient.addColorStop(0.6, mainColor); | ||||||
|  | @ -617,17 +442,25 @@ class Audio extends React.PureComponent { | ||||||
|     return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())); |     return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _getColor () { |   _getAccentColor () { | ||||||
|     return `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`; |     return this.props.accentColor || '#ffffff'; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _getBackgroundColor () { | ||||||
|  |     return this.props.backgroundColor || '#000000'; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _getForegroundColor () { | ||||||
|  |     return this.props.foregroundColor || '#ffffff'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   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, dragging } = this.state; | ||||||
|     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.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> |       <div className={classNames('audio-player', { editable })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (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,24 +472,15 @@ class Audio extends React.PureComponent { | ||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         <canvas |         <canvas | ||||||
|           className='audio-player__background' |  | ||||||
|           onClick={this.togglePlay} |  | ||||||
|           width='32' |  | ||||||
|           height='32' |  | ||||||
|           style={{ width: this.state.width, height: this.state.height, position: 'absolute', top: 0, left: 0 }} |  | ||||||
|           ref={this.setBlurhashCanvasRef} |  | ||||||
|           aria-label={alt} |  | ||||||
|           title={alt} |  | ||||||
|           role='button' |           role='button' | ||||||
|           tabIndex='0' |  | ||||||
|         /> |  | ||||||
| 
 |  | ||||||
|         <canvas |  | ||||||
|           className='audio-player__canvas' |           className='audio-player__canvas' | ||||||
|           width={this.state.width} |           width={this.state.width} | ||||||
|           height={this.state.height} |           height={this.state.height} | ||||||
|           style={{ width: '100%', position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }} |           style={{ width: '100%', position: 'absolute', top: 0, left: 0 }} | ||||||
|           ref={this.setCanvasRef} |           ref={this.setCanvasRef} | ||||||
|  |           onClick={this.togglePlay} | ||||||
|  |           title={alt} | ||||||
|  |           aria-label={alt} | ||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         <img |         <img | ||||||
|  | @ -669,12 +493,12 @@ class Audio extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|         <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}> |         <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}> | ||||||
|           <div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} /> |           <div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} /> | ||||||
|           <div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getColor() }} /> |           <div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getAccentColor() }} /> | ||||||
| 
 | 
 | ||||||
|           <span |           <span | ||||||
|             className={classNames('video-player__seek__handle', { active: dragging })} |             className={classNames('video-player__seek__handle', { active: dragging })} | ||||||
|             tabIndex='0' |             tabIndex='0' | ||||||
|             style={{ left: `${progress}%`, backgroundColor: this._getColor() }} |             style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }} | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|  | @ -685,12 +509,12 @@ class Audio extends React.PureComponent { | ||||||
|               <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={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}> |               <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: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} /> | ||||||
| 
 | 
 | ||||||
|                 <span |                 <span | ||||||
|                   className={classNames('video-player__volume__handle')} |                   className={classNames('video-player__volume__handle')} | ||||||
|                   tabIndex='0' |                   tabIndex='0' | ||||||
|                   style={{ left: `${volume * 100}%`, backgroundColor: this._getColor() }} |                   style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} | ||||||
|                 /> |                 /> | ||||||
|               </div> |               </div> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -143,7 +143,9 @@ export default class DetailedStatus extends ImmutablePureComponent { | ||||||
|             alt={attachment.get('description')} |             alt={attachment.get('description')} | ||||||
|             duration={attachment.getIn(['meta', 'original', 'duration'], 0)} |             duration={attachment.getIn(['meta', 'original', 'duration'], 0)} | ||||||
|             poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} |             poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} | ||||||
|             blurhash={attachment.get('blurhash')} |             backgroundColor={attachment.getIn(['meta', 'colors', 'background'])} | ||||||
|  |             foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])} | ||||||
|  |             accentColor={attachment.getIn(['meta', 'colors', 'accent'])} | ||||||
|             height={150} |             height={150} | ||||||
|           /> |           /> | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  | @ -38,7 +38,9 @@ export default class AudioModal extends ImmutablePureComponent { | ||||||
|             duration={media.getIn(['meta', 'original', 'duration'], 0)} |             duration={media.getIn(['meta', 'original', 'duration'], 0)} | ||||||
|             height={150} |             height={150} | ||||||
|             poster={media.get('preview_url') || status.getIn(['account', 'avatar_static'])} |             poster={media.get('preview_url') || status.getIn(['account', 'avatar_static'])} | ||||||
|             blurhash={media.get('blurhash')} |             backgroundColor={media.getIn(['meta', 'colors', 'background'])} | ||||||
|  |             foregroundColor={media.getIn(['meta', 'colors', 'foreground'])} | ||||||
|  |             accentColor={media.getIn(['meta', 'colors', 'accent'])} | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -329,7 +329,9 @@ class FocalPointModal extends ImmutablePureComponent { | ||||||
|                 duration={media.getIn(['meta', 'original', 'duration'], 0)} |                 duration={media.getIn(['meta', 'original', 'duration'], 0)} | ||||||
|                 height={150} |                 height={150} | ||||||
|                 poster={media.get('preview_url') || account.get('avatar_static')} |                 poster={media.get('preview_url') || account.get('avatar_static')} | ||||||
|                 blurhash={media.get('blurhash')} |                 backgroundColor={media.getIn(['meta', 'colors', 'background'])} | ||||||
|  |                 foregroundColor={media.getIn(['meta', 'colors', 'foreground'])} | ||||||
|  |                 accentColor={media.getIn(['meta', 'colors', 'accent'])} | ||||||
|                 editable |                 editable | ||||||
|               /> |               /> | ||||||
|             )} |             )} | ||||||
|  |  | ||||||
|  | @ -347,36 +347,31 @@ | ||||||
| 
 | 
 | ||||||
|   .video-player__volume::before, |   .video-player__volume::before, | ||||||
|   .video-player__seek::before { |   .video-player__seek::before { | ||||||
|     background: rgba($white, 0.15); |     background: currentColor; | ||||||
|  |     opacity: 0.15; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &.with-light-background { |   .video-player__seek__buffer { | ||||||
|     color: $black; |     background: currentColor; | ||||||
|  |     opacity: 0.2; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|     .video-player__volume::before, |   .video-player__buttons button { | ||||||
|     .video-player__seek::before { |     color: currentColor; | ||||||
|       background: rgba($black, 0.15); |     opacity: 0.75; | ||||||
|  | 
 | ||||||
|  |     &:active, | ||||||
|  |     &:hover, | ||||||
|  |     &:focus { | ||||||
|  |       color: currentColor; | ||||||
|  |       opacity: 1; | ||||||
|     } |     } | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|     .video-player__seek__buffer { |   .video-player__time-sep, | ||||||
|       background: rgba($black, 0.2); |   .video-player__time-total, | ||||||
|     } |   .video-player__time-current { | ||||||
| 
 |     color: currentColor; | ||||||
|     .video-player__buttons button { |  | ||||||
|       color: rgba($black, 0.75); |  | ||||||
| 
 |  | ||||||
|       &:active, |  | ||||||
|       &:hover, |  | ||||||
|       &:focus { |  | ||||||
|         color: $black; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     .video-player__time-sep, |  | ||||||
|     .video-player__time-total, |  | ||||||
|     .video-player__time-current { |  | ||||||
|       color: $black; |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .video-player__seek::before, |   .video-player__seek::before, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue