Polish video player CSS, add timer on fullscreen/modal/public pages (#5928)

This commit is contained in:
Eugen Rochko 2017-12-09 00:55:58 +01:00 committed by GitHub
parent b0db4dad79
commit 70ce2a2095
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 31 deletions

View file

@ -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>

View file

@ -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,16 +287,27 @@ export default class Video extends React.PureComponent {
/> />
</div> </div>
<div className='video-player__buttons left'> <div className='video-player__buttons-bar'>
<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> <div className='video-player__buttons left'>
<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(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
{!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></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>
</div>
<div className='video-player__buttons right'> {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></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>} {(detailed || fullscreen) &&
<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> <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 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>}
{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>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -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 {

View file

@ -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