gab-social/app/javascript/gabsocial/components/video.js

198 lines
5.0 KiB
JavaScript

import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import { is } from 'immutable'
import { decode } from 'blurhash'
import videojs from 'video.js'
import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from '../utils/media_aspect_ratio'
import { displayMedia } from '../initial_state'
import Button from './button'
import Icon from './icon'
import SensitiveMediaItem from './sensitive_media_item'
import Text from './text'
import '!style-loader!css-loader!video.js/dist/video-js.min.css'
const videoJsOptions = {
autoplay: false,
playbackRates: [0.5, 1, 1.5, 2],
controls: true,
sources: [{}],
}
class Video extends ImmutablePureComponent {
state = {
containerWidth: this.props.width,
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
}
componentDidMount() {
videoJsOptions.sources = [{ src: this.props.src }]
this.videoPlayer = videojs(this.video, videoJsOptions)
}
componentWillUnmount() {
if (this.videoPlayer) {
this.videoPlayer.dispose()
}
}
componentWillReceiveProps(nextProps) {
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
this.setState({ revealed: nextProps.visible })
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.revealed && !this.state.revealed && this.video) {
this.video.pause()
}
}
setPlayerRef = (n) => {
this.player = n
if (n) {
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth)
this.setState({
containerWidth: n.offsetWidth,
})
}
}
setVideoRef = (n) => {
this.video = n
}
handleClickRoot = (e) => e.stopPropagation()
toggleReveal = () => {
if (this.props.onToggleVisibility) {
this.props.onToggleVisibility()
} else {
this.setState({ revealed: !this.state.revealed })
}
}
render() {
const {
preview,
src,
inline,
startTime,
intl,
alt,
detailed,
sensitive,
aspectRatio,
} = this.props
const {
containerWidth,
revealed,
} = this.state
const playerStyle = {}
let { width, height } = this.props
if (inline && containerWidth) {
width = containerWidth
const minSize = containerWidth / (16 / 9)
if (isPanoramic(aspectRatio)) {
height = Math.max(Math.floor(containerWidth / maximumAspectRatio), minSize)
} else if (isPortrait(aspectRatio)) {
height = Math.max(Math.floor(containerWidth / minimumAspectRatio), minSize)
} else {
height = Math.floor(containerWidth / aspectRatio)
}
playerStyle.height = height
}
let preload
if (startTime) {
preload = 'auto'
} else if (detailed) {
preload = 'metadata'
} else {
preload = 'none'
}
if (!revealed && sensitive) {
return <SensitiveMediaItem onClick={this.toggleReveal} />
}
return (
<div
className={[_s.d, _s.mt10, _s.outlineNone].join(' ')}
style={playerStyle}
ref={this.setPlayerRef}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClickRoot}
tabIndex={0}
>
<div data-vjs-player>
<video
className={[_s.d, _s.h100PC, _s.w100PC, _s.outlineNone, 'video-js'].join(' ')}
ref={this.setVideoRef}
playsInline
poster={preview}
preload={preload}
role='button'
tabIndex='0'
aria-label={alt}
title={alt}
width={width}
height={height}
/>
</div>
{
revealed && sensitive &&
<div className={[_s.posAbs, _s.z2, _s.top0, _s.right0, _s.mt10, _s.mr10].join(' ')}>
<Button
title={intl.formatMessage(messages.toggle_visible)}
icon='hidden'
backgroundColor='black'
className={[_s.px10, _s.bgBlackOpaque_onHover].join(' ')}
onClick={this.toggleReveal}
/>
</div>
}
</div>
)
}
}
const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide media' },
})
Video.propTypes = {
preview: PropTypes.string,
src: PropTypes.string.isRequired,
alt: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
sensitive: PropTypes.bool,
startTime: PropTypes.number,
detailed: PropTypes.bool,
inline: PropTypes.bool,
cacheWidth: PropTypes.func,
visible: PropTypes.bool,
onToggleVisibility: PropTypes.func,
intl: PropTypes.object.isRequired,
blurhash: PropTypes.string,
aspectRatio: PropTypes.number,
meta: ImmutablePropTypes.map,
}
export default injectIntl(Video)