Polish video player CSS, add timer on fullscreen/modal/public pages (#5928)
This commit is contained in:
		
					parent
					
						
							
								b0db4dad79
							
						
					
				
			
			
				commit
				
					
						70ce2a2095
					
				
			
		
					 4 changed files with 104 additions and 31 deletions
				
			
		|  | @ -23,6 +23,7 @@ export default class VideoModal extends ImmutablePureComponent { | ||||||
|             src={media.get('url')} |             src={media.get('url')} | ||||||
|             startTime={time} |             startTime={time} | ||||||
|             onCloseVideo={onClose} |             onCloseVideo={onClose} | ||||||
|  |             detailed | ||||||
|             description={media.get('description')} |             description={media.get('description')} | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  | @ -17,6 +17,18 @@ const messages = defineMessages({ | ||||||
|   exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' }, |   exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | const formatTime = secondsNum => { | ||||||
|  |   let hours   = Math.floor(secondsNum / 3600); | ||||||
|  |   let minutes = Math.floor((secondsNum - (hours * 3600)) / 60); | ||||||
|  |   let seconds = secondsNum - (hours * 3600) - (minutes * 60); | ||||||
|  | 
 | ||||||
|  |   if (hours   < 10) hours   = '0' + hours; | ||||||
|  |   if (minutes < 10) minutes = '0' + minutes; | ||||||
|  |   if (seconds < 10) seconds = '0' + seconds; | ||||||
|  | 
 | ||||||
|  |   return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const findElementPosition = el => { | const findElementPosition = el => { | ||||||
|   let box; |   let box; | ||||||
| 
 | 
 | ||||||
|  | @ -83,11 +95,13 @@ export default class Video extends React.PureComponent { | ||||||
|     startTime: PropTypes.number, |     startTime: PropTypes.number, | ||||||
|     onOpenVideo: PropTypes.func, |     onOpenVideo: PropTypes.func, | ||||||
|     onCloseVideo: PropTypes.func, |     onCloseVideo: PropTypes.func, | ||||||
|  |     detailed: PropTypes.bool, | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
|     progress: 0, |     currentTime: 0, | ||||||
|  |     duration: 0, | ||||||
|     paused: true, |     paused: true, | ||||||
|     dragging: false, |     dragging: false, | ||||||
|     fullscreen: false, |     fullscreen: false, | ||||||
|  | @ -117,7 +131,10 @@ export default class Video extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleTimeUpdate = () => { |   handleTimeUpdate = () => { | ||||||
|     this.setState({ progress: 100 * (this.video.currentTime / this.video.duration) }); |     this.setState({ | ||||||
|  |       currentTime: Math.floor(this.video.currentTime), | ||||||
|  |       duration: Math.floor(this.video.duration), | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleMouseDown = e => { |   handleMouseDown = e => { | ||||||
|  | @ -143,8 +160,10 @@ export default class Video extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|   handleMouseMove = throttle(e => { |   handleMouseMove = throttle(e => { | ||||||
|     const { x } = getPointerPosition(this.seek, e); |     const { x } = getPointerPosition(this.seek, e); | ||||||
|     this.video.currentTime = this.video.duration * x; |     const currentTime = Math.floor(this.video.duration * x); | ||||||
|     this.setState({ progress: x * 100 }); | 
 | ||||||
|  |     this.video.currentTime = currentTime; | ||||||
|  |     this.setState({ currentTime }); | ||||||
|   }, 60); |   }, 60); | ||||||
| 
 | 
 | ||||||
|   togglePlay = () => { |   togglePlay = () => { | ||||||
|  | @ -226,11 +245,12 @@ export default class Video extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt } = this.props; |     const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props; | ||||||
|     const { progress, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; |     const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; | ||||||
|  |     const progress = (currentTime / duration) * 100; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div className={classNames('video-player', { inactive: !revealed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> |       <div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> | ||||||
|         <video |         <video | ||||||
|           ref={this.setVideoRef} |           ref={this.setVideoRef} | ||||||
|           src={src} |           src={src} | ||||||
|  | @ -267,19 +287,30 @@ export default class Video extends React.PureComponent { | ||||||
|             /> |             /> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|  |           <div className='video-player__buttons-bar'> | ||||||
|             <div className='video-player__buttons left'> |             <div className='video-player__buttons left'> | ||||||
|               <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> |               <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> | ||||||
|               <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> |               <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> | ||||||
|  | 
 | ||||||
|               {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} |               {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} | ||||||
|  | 
 | ||||||
|  |               {(detailed || fullscreen) && | ||||||
|  |                 <span> | ||||||
|  |                   <span className='video-player__time-current'>{formatTime(currentTime)}</span> | ||||||
|  |                   <span className='video-player__time-sep'>/</span> | ||||||
|  |                   <span className='video-player__time-total'>{formatTime(duration)}</span> | ||||||
|  |                 </span> | ||||||
|  |               } | ||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <div className='video-player__buttons right'> |             <div className='video-player__buttons right'> | ||||||
|               {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} |               {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} | ||||||
|             {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-times' /></button>} |               {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>} | ||||||
|               <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> |               <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3998,6 +3998,7 @@ button.icon-button.active i.fa-retweet { | ||||||
|   position: relative; |   position: relative; | ||||||
|   background: $base-shadow-color; |   background: $base-shadow-color; | ||||||
|   max-width: 100%; |   max-width: 100%; | ||||||
|  |   border-radius: 4px; | ||||||
| 
 | 
 | ||||||
|   video { |   video { | ||||||
|     height: 100%; |     height: 100%; | ||||||
|  | @ -4032,8 +4033,8 @@ button.icon-button.active i.fa-retweet { | ||||||
|     left: 0; |     left: 0; | ||||||
|     right: 0; |     right: 0; | ||||||
|     box-sizing: border-box; |     box-sizing: border-box; | ||||||
|     background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 60%, transparent); |     background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent); | ||||||
|     padding: 0 10px; |     padding: 0 15px; | ||||||
|     opacity: 0; |     opacity: 0; | ||||||
|     transition: opacity .1s ease; |     transition: opacity .1s ease; | ||||||
| 
 | 
 | ||||||
|  | @ -4086,40 +4087,67 @@ button.icon-button.active i.fa-retweet { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__buttons { |   &__buttons-bar { | ||||||
|  |     display: flex; | ||||||
|  |     justify-content: space-between; | ||||||
|     padding-bottom: 10px; |     padding-bottom: 10px; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__buttons { | ||||||
|     font-size: 16px; |     font-size: 16px; | ||||||
|  |     white-space: nowrap; | ||||||
|  |     overflow: hidden; | ||||||
|  |     text-overflow: ellipsis; | ||||||
| 
 | 
 | ||||||
|     &.left { |     &.left { | ||||||
|       float: left; |  | ||||||
| 
 |  | ||||||
|       button { |       button { | ||||||
|         padding-right: 10px; |         padding-left: 0; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     &.right { |     &.right { | ||||||
|       float: right; |  | ||||||
| 
 |  | ||||||
|       button { |       button { | ||||||
|         padding-left: 10px; |         padding-right: 0; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     button { |     button { | ||||||
|       background: transparent; |       background: transparent; | ||||||
|       padding: 0; |       padding: 2px 10px; | ||||||
|  |       font-size: 16px; | ||||||
|       border: 0; |       border: 0; | ||||||
|       color: $white; |       color: rgba($white, 0.75); | ||||||
| 
 | 
 | ||||||
|       &:active, |       &:active, | ||||||
|       &:hover, |       &:hover, | ||||||
|       &:focus { |       &:focus { | ||||||
|         color: $ui-highlight-color; |         color: $white; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   &__time-sep, | ||||||
|  |   &__time-total, | ||||||
|  |   &__time-current { | ||||||
|  |     font-size: 14px; | ||||||
|  |     font-weight: 500; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__time-current { | ||||||
|  |     color: $white; | ||||||
|  |     margin-left: 10px; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__time-sep { | ||||||
|  |     display: inline-block; | ||||||
|  |     margin: 0 6px; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__time-sep, | ||||||
|  |   &__time-total { | ||||||
|  |     color: $white; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   &__seek { |   &__seek { | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|     height: 24px; |     height: 24px; | ||||||
|  | @ -4129,6 +4157,7 @@ button.icon-button.active i.fa-retweet { | ||||||
|       content: ""; |       content: ""; | ||||||
|       width: 100%; |       width: 100%; | ||||||
|       background: rgba($white, 0.35); |       background: rgba($white, 0.35); | ||||||
|  |       border-radius: 4px; | ||||||
|       display: block; |       display: block; | ||||||
|       position: absolute; |       position: absolute; | ||||||
|       height: 4px; |       height: 4px; | ||||||
|  | @ -4140,8 +4169,9 @@ button.icon-button.active i.fa-retweet { | ||||||
|       display: block; |       display: block; | ||||||
|       position: absolute; |       position: absolute; | ||||||
|       height: 4px; |       height: 4px; | ||||||
|  |       border-radius: 4px; | ||||||
|       top: 10px; |       top: 10px; | ||||||
|       background: $ui-highlight-color; |       background: lighten($ui-highlight-color, 8%); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     &__buffer { |     &__buffer { | ||||||
|  | @ -4158,7 +4188,8 @@ button.icon-button.active i.fa-retweet { | ||||||
|       top: 6px; |       top: 6px; | ||||||
|       margin-left: -6px; |       margin-left: -6px; | ||||||
|       transition: opacity .1s ease; |       transition: opacity .1s ease; | ||||||
|       background: $ui-highlight-color; |       background: lighten($ui-highlight-color, 8%); | ||||||
|  |       box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); | ||||||
|       pointer-events: none; |       pointer-events: none; | ||||||
| 
 | 
 | ||||||
|       &.active { |       &.active { | ||||||
|  | @ -4172,6 +4203,16 @@ button.icon-button.active i.fa-retweet { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   &.detailed, | ||||||
|  |   &.fullscreen { | ||||||
|  |     .video-player__buttons { | ||||||
|  |       button { | ||||||
|  |         padding-top: 10px; | ||||||
|  |         padding-bottom: 10px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .media-spoiler-video { | .media-spoiler-video { | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ | ||||||
|   - if !status.media_attachments.empty? |   - if !status.media_attachments.empty? | ||||||
|     - if status.media_attachments.first.video? |     - if status.media_attachments.first.video? | ||||||
|       - video = status.media_attachments.first |       - video = status.media_attachments.first | ||||||
|       %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380) }} |       %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380, detailed: true) }} | ||||||
|     - else |     - else | ||||||
|       %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }} |       %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }} | ||||||
|   - elsif status.preview_cards.first |   - elsif status.preview_cards.first | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue