diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js
index 1437deeb0b..6a883759fe 100644
--- a/app/javascript/mastodon/features/ui/components/video_modal.js
+++ b/app/javascript/mastodon/features/ui/components/video_modal.js
@@ -23,6 +23,7 @@ export default class VideoModal extends ImmutablePureComponent {
src={media.get('url')}
startTime={time}
onCloseVideo={onClose}
+ detailed
description={media.get('description')}
/>
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 003bf23a84..0ee8bb6c82 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -17,6 +17,18 @@ const messages = defineMessages({
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 => {
let box;
@@ -83,11 +95,13 @@ export default class Video extends React.PureComponent {
startTime: PropTypes.number,
onOpenVideo: PropTypes.func,
onCloseVideo: PropTypes.func,
+ detailed: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
state = {
- progress: 0,
+ currentTime: 0,
+ duration: 0,
paused: true,
dragging: false,
fullscreen: false,
@@ -117,7 +131,10 @@ export default class Video extends React.PureComponent {
}
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 => {
@@ -143,8 +160,10 @@ export default class Video extends React.PureComponent {
handleMouseMove = throttle(e => {
const { x } = getPointerPosition(this.seek, e);
- this.video.currentTime = this.video.duration * x;
- this.setState({ progress: x * 100 });
+ const currentTime = Math.floor(this.video.duration * x);
+
+ this.video.currentTime = currentTime;
+ this.setState({ currentTime });
}, 60);
togglePlay = () => {
@@ -226,11 +245,12 @@ export default class Video extends React.PureComponent {
}
render () {
- const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt } = this.props;
- const { progress, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
+ const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
+ const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
+ const progress = (currentTime / duration) * 100;
return (
-
+
-
-
-
- {!onCloseVideo && }
-
+
+
+
+
-
- {(!fullscreen && onOpenVideo) && }
- {onCloseVideo && }
-
+ {!onCloseVideo && }
+
+ {(detailed || fullscreen) &&
+
+ {formatTime(currentTime)}
+ /
+ {formatTime(duration)}
+
+ }
+
+
+
+ {(!fullscreen && onOpenVideo) && }
+ {onCloseVideo && }
+
+
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 64a77adc7f..dd61dc519e 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3998,6 +3998,7 @@ button.icon-button.active i.fa-retweet {
position: relative;
background: $base-shadow-color;
max-width: 100%;
+ border-radius: 4px;
video {
height: 100%;
@@ -4032,8 +4033,8 @@ button.icon-button.active i.fa-retweet {
left: 0;
right: 0;
box-sizing: border-box;
- background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 60%, transparent);
- padding: 0 10px;
+ background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent);
+ padding: 0 15px;
opacity: 0;
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;
+ }
+
+ &__buttons {
font-size: 16px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
&.left {
- float: left;
-
button {
- padding-right: 10px;
+ padding-left: 0;
}
}
&.right {
- float: right;
-
button {
- padding-left: 10px;
+ padding-right: 0;
}
}
button {
background: transparent;
- padding: 0;
+ padding: 2px 10px;
+ font-size: 16px;
border: 0;
- color: $white;
+ color: rgba($white, 0.75);
&:active,
&:hover,
&: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 {
cursor: pointer;
height: 24px;
@@ -4129,6 +4157,7 @@ button.icon-button.active i.fa-retweet {
content: "";
width: 100%;
background: rgba($white, 0.35);
+ border-radius: 4px;
display: block;
position: absolute;
height: 4px;
@@ -4140,8 +4169,9 @@ button.icon-button.active i.fa-retweet {
display: block;
position: absolute;
height: 4px;
+ border-radius: 4px;
top: 10px;
- background: $ui-highlight-color;
+ background: lighten($ui-highlight-color, 8%);
}
&__buffer {
@@ -4158,7 +4188,8 @@ button.icon-button.active i.fa-retweet {
top: 6px;
margin-left: -6px;
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;
&.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 {
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 3119ebf4b9..94e081c84d 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -22,7 +22,7 @@
- if !status.media_attachments.empty?
- if status.media_attachments.first.video?
- 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
%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