Fix audio and video items in account gallery in web UI (#14282)
* Fix audio and video items in account gallery in web UI - Fix audio items not using thumbnails - Fix video items not using custom thumbnails - Fix video items autoplaying like GIFs * Change audio and video items in account gallery to autoplay when opened in web UI * Fix code style issuegh/stable
parent
96e89d1ef4
commit
6cc5b822f5
|
@ -63,55 +63,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
const status = attachment.get('status');
|
const status = attachment.get('status');
|
||||||
const title = status.get('spoiler_text') || attachment.get('description');
|
const title = status.get('spoiler_text') || attachment.get('description');
|
||||||
|
|
||||||
let thumbnail = '';
|
let thumbnail, label, icon, content;
|
||||||
let icon;
|
|
||||||
|
|
||||||
if (attachment.get('type') === 'unknown') {
|
|
||||||
// Skip
|
|
||||||
} else if (attachment.get('type') === 'audio') {
|
|
||||||
thumbnail = (
|
|
||||||
<span className='account-gallery__item__icons'>
|
|
||||||
<Icon id='music' />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else if (attachment.get('type') === 'image') {
|
|
||||||
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
|
|
||||||
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
|
|
||||||
const x = ((focusX / 2) + .5) * 100;
|
|
||||||
const y = ((focusY / -2) + .5) * 100;
|
|
||||||
|
|
||||||
thumbnail = (
|
|
||||||
<img
|
|
||||||
src={attachment.get('preview_url')}
|
|
||||||
alt={attachment.get('description')}
|
|
||||||
title={attachment.get('description')}
|
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
|
||||||
onLoad={this.handleImageLoad}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
|
|
||||||
const autoPlay = !isIOS() && autoPlayGif;
|
|
||||||
const label = attachment.get('type') === 'video' ? <Icon id='play' /> : 'GIF';
|
|
||||||
|
|
||||||
thumbnail = (
|
|
||||||
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
|
||||||
<video
|
|
||||||
className='media-gallery__item-gifv-thumbnail'
|
|
||||||
aria-label={attachment.get('description')}
|
|
||||||
title={attachment.get('description')}
|
|
||||||
role='application'
|
|
||||||
src={attachment.get('url')}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
autoPlay={autoPlay}
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className='media-gallery__gifv__label'>{label}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
icon = (
|
icon = (
|
||||||
|
@ -119,6 +71,60 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
<Icon id='eye-slash' />
|
<Icon id='eye-slash' />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
if (['audio', 'video'].includes(attachment.get('type'))) {
|
||||||
|
content = (
|
||||||
|
<img
|
||||||
|
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
|
||||||
|
alt={attachment.get('description')}
|
||||||
|
onLoad={this.handleImageLoad}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (attachment.get('type') === 'audio') {
|
||||||
|
label = <Icon id='music' />;
|
||||||
|
} else {
|
||||||
|
label = <Icon id='play' />;
|
||||||
|
}
|
||||||
|
} else if (attachment.get('type') === 'image') {
|
||||||
|
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
|
||||||
|
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
|
||||||
|
const x = ((focusX / 2) + .5) * 100;
|
||||||
|
const y = ((focusY / -2) + .5) * 100;
|
||||||
|
|
||||||
|
content = (
|
||||||
|
<img
|
||||||
|
src={attachment.get('preview_url')}
|
||||||
|
alt={attachment.get('description')}
|
||||||
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
|
onLoad={this.handleImageLoad}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (attachment.get('type') === 'gifv') {
|
||||||
|
content = (
|
||||||
|
<video
|
||||||
|
className='media-gallery__item-gifv-thumbnail'
|
||||||
|
aria-label={attachment.get('description')}
|
||||||
|
role='application'
|
||||||
|
src={attachment.get('url')}
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
|
autoPlay={!isIOS() && autoPlayGif}
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
label = 'GIF';
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnail = (
|
||||||
|
<div className='media-gallery__gifv'>
|
||||||
|
{content}
|
||||||
|
|
||||||
|
<span className='media-gallery__gifv__label'>{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -126,13 +132,11 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
<a className='media-gallery__item-thumbnail' href={status.get('url')} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
|
<a className='media-gallery__item-thumbnail' href={status.get('url')} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
|
||||||
<Blurhash
|
<Blurhash
|
||||||
hash={attachment.get('blurhash')}
|
hash={attachment.get('blurhash')}
|
||||||
className={classNames('media-gallery__preview', {
|
className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })}
|
||||||
'media-gallery__preview--hidden': visible && loaded,
|
|
||||||
})}
|
|
||||||
dummy={!useBlurhash}
|
dummy={!useBlurhash}
|
||||||
/>
|
/>
|
||||||
{visible && thumbnail}
|
|
||||||
{!visible && icon}
|
{visible ? thumbnail : icon}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -101,9 +101,9 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
|
|
||||||
handleOpenMedia = attachment => {
|
handleOpenMedia = attachment => {
|
||||||
if (attachment.get('type') === 'video') {
|
if (attachment.get('type') === 'video') {
|
||||||
this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
|
this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
|
||||||
} else if (attachment.get('type') === 'audio') {
|
} else if (attachment.get('type') === 'audio') {
|
||||||
this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status') }));
|
this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
|
||||||
} else {
|
} else {
|
||||||
const media = attachment.getIn(['status', 'media_attachments']);
|
const media = attachment.getIn(['status', 'media_attachments']);
|
||||||
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Audio extends React.PureComponent {
|
||||||
backgroundColor: PropTypes.string,
|
backgroundColor: PropTypes.string,
|
||||||
foregroundColor: PropTypes.string,
|
foregroundColor: PropTypes.string,
|
||||||
accentColor: PropTypes.string,
|
accentColor: PropTypes.string,
|
||||||
|
autoPlay: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -259,6 +260,14 @@ class Audio extends React.PureComponent {
|
||||||
this.setState({ hovered: false });
|
this.setState({ hovered: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLoadedData = () => {
|
||||||
|
const { autoPlay } = this.props;
|
||||||
|
|
||||||
|
if (autoPlay) {
|
||||||
|
this.audio.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_initAudioContext () {
|
_initAudioContext () {
|
||||||
const context = new AudioContext();
|
const context = new AudioContext();
|
||||||
const source = context.createMediaElementSource(this.audio);
|
const source = context.createMediaElementSource(this.audio);
|
||||||
|
@ -336,7 +345,7 @@ class Audio extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { src, intl, alt, editable } = this.props;
|
const { src, intl, alt, editable, autoPlay } = this.props;
|
||||||
const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state;
|
const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state;
|
||||||
const progress = (currentTime / duration) * 100;
|
const progress = (currentTime / duration) * 100;
|
||||||
|
|
||||||
|
@ -345,10 +354,11 @@ class Audio extends React.PureComponent {
|
||||||
<audio
|
<audio
|
||||||
src={src}
|
src={src}
|
||||||
ref={this.setAudioRef}
|
ref={this.setAudioRef}
|
||||||
preload='none'
|
preload={autoPlay ? 'auto' : 'none'}
|
||||||
onPlay={this.handlePlay}
|
onPlay={this.handlePlay}
|
||||||
onPause={this.handlePause}
|
onPause={this.handlePause}
|
||||||
onProgress={this.handleProgress}
|
onProgress={this.handleProgress}
|
||||||
|
onLoadedData={this.handleLoadedData}
|
||||||
crossOrigin='anonymous'
|
crossOrigin='anonymous'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,27 @@ import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Audio from 'mastodon/features/audio';
|
import Audio from 'mastodon/features/audio';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { previewState } from './video_modal';
|
import { previewState } from './video_modal';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
export default class AudioModal extends ImmutablePureComponent {
|
const mapStateToProps = (state, { status }) => ({
|
||||||
|
account: state.getIn(['accounts', status.get('account')]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class AudioModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
|
options: PropTypes.shape({
|
||||||
|
autoPlay: PropTypes.bool,
|
||||||
|
}),
|
||||||
|
account: ImmutablePropTypes.map,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,7 +60,8 @@ export default class AudioModal extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, status } = this.props;
|
const { media, status, account } = this.props;
|
||||||
|
const options = this.props.options || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal audio-modal'>
|
<div className='modal-root__modal audio-modal'>
|
||||||
|
@ -60,10 +71,11 @@ export default class AudioModal extends ImmutablePureComponent {
|
||||||
alt={media.get('description')}
|
alt={media.get('description')}
|
||||||
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') || account.get('avatar_static')}
|
||||||
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
|
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
|
||||||
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
|
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
|
||||||
accentColor={media.getIn(['meta', 'colors', 'accent'])}
|
accentColor={media.getIn(['meta', 'colors', 'accent'])}
|
||||||
|
autoPlay={options.autoPlay}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -154,12 +154,13 @@ export const makeGetNotification = () => {
|
||||||
export const getAccountGallery = createSelector([
|
export const getAccountGallery = createSelector([
|
||||||
(state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()),
|
(state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()),
|
||||||
state => state.get('statuses'),
|
state => state.get('statuses'),
|
||||||
], (statusIds, statuses) => {
|
(state, id) => state.getIn(['accounts', id]),
|
||||||
|
], (statusIds, statuses, account) => {
|
||||||
let medias = ImmutableList();
|
let medias = ImmutableList();
|
||||||
|
|
||||||
statusIds.forEach(statusId => {
|
statusIds.forEach(statusId => {
|
||||||
const status = statuses.get(statusId);
|
const status = statuses.get(statusId);
|
||||||
medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status)));
|
medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status).set('account', account)));
|
||||||
});
|
});
|
||||||
|
|
||||||
return medias;
|
return medias;
|
||||||
|
|
Reference in New Issue