You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
6.4 KiB

7 years ago
7 years ago
  1. import ImmutablePropTypes from 'react-immutable-proptypes';
  2. import PureRenderMixin from 'react-addons-pure-render-mixin';
  3. import IconButton from './icon_button';
  4. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  5. import { isIOS } from '../is_mobile';
  6. const messages = defineMessages({
  7. toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
  8. toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
  9. expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' }
  10. });
  11. const videoStyle = {
  12. position: 'relative',
  13. zIndex: '1',
  14. width: '100%',
  15. height: '100%',
  16. objectFit: 'cover',
  17. top: '50%',
  18. transform: 'translateY(-50%)'
  19. };
  20. const muteStyle = {
  21. position: 'absolute',
  22. top: '10px',
  23. right: '10px',
  24. color: 'white',
  25. textShadow: "0px 1px 1px black, 1px 0px 1px black",
  26. opacity: '0.8',
  27. zIndex: '5'
  28. };
  29. const spoilerStyle = {
  30. marginTop: '8px',
  31. textAlign: 'center',
  32. height: '100%',
  33. cursor: 'pointer',
  34. display: 'flex',
  35. alignItems: 'center',
  36. justifyContent: 'center',
  37. flexDirection: 'column',
  38. position: 'relative'
  39. };
  40. const spoilerSpanStyle = {
  41. display: 'block',
  42. fontSize: '14px'
  43. };
  44. const spoilerSubSpanStyle = {
  45. display: 'block',
  46. fontSize: '11px',
  47. fontWeight: '500'
  48. };
  49. const spoilerButtonStyle = {
  50. position: 'absolute',
  51. top: '6px',
  52. left: '8px',
  53. color: 'white',
  54. textShadow: "0px 1px 1px black, 1px 0px 1px black",
  55. zIndex: '100'
  56. };
  57. const expandButtonStyle = {
  58. position: 'absolute',
  59. bottom: '6px',
  60. right: '8px',
  61. color: 'white',
  62. textShadow: "0px 1px 1px black, 1px 0px 1px black",
  63. zIndex: '100'
  64. };
  65. const VideoPlayer = React.createClass({
  66. propTypes: {
  67. media: ImmutablePropTypes.map.isRequired,
  68. width: React.PropTypes.number,
  69. height: React.PropTypes.number,
  70. sensitive: React.PropTypes.bool,
  71. intl: React.PropTypes.object.isRequired,
  72. autoplay: React.PropTypes.bool,
  73. onOpenVideo: React.PropTypes.func.isRequired
  74. },
  75. getDefaultProps () {
  76. return {
  77. width: 239,
  78. height: 110
  79. };
  80. },
  81. getInitialState () {
  82. return {
  83. visible: !this.props.sensitive,
  84. preview: true,
  85. muted: true,
  86. hasAudio: true
  87. };
  88. },
  89. mixins: [PureRenderMixin],
  90. handleClick () {
  91. this.setState({ muted: !this.state.muted });
  92. },
  93. handleVideoClick (e) {
  94. e.stopPropagation();
  95. const node = ReactDOM.findDOMNode(this).querySelector('video');
  96. if (node.paused) {
  97. node.play();
  98. } else {
  99. node.pause();
  100. }
  101. },
  102. handleOpen () {
  103. this.setState({ preview: !this.state.preview });
  104. },
  105. handleVisibility () {
  106. this.setState({
  107. visible: !this.state.visible,
  108. preview: true
  109. });
  110. },
  111. handleExpand () {
  112. const node = ReactDOM.findDOMNode(this).querySelector('video');
  113. node.pause();
  114. this.props.onOpenVideo(this.props.media);
  115. },
  116. setRef (c) {
  117. this.video = c;
  118. },
  119. handleLoadedData () {
  120. if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) {
  121. this.setState({ hasAudio: false });
  122. }
  123. },
  124. componentDidMount () {
  125. if (!this.video) {
  126. return;
  127. }
  128. this.video.addEventListener('loadeddata', this.handleLoadedData);
  129. },
  130. componentDidUpdate () {
  131. if (!this.video) {
  132. return;
  133. }
  134. this.video.addEventListener('loadeddata', this.handleLoadedData);
  135. },
  136. componentWillUnmount () {
  137. if (!this.video) {
  138. return;
  139. }
  140. this.video.removeEventListener('loadeddata', this.handleLoadedData);
  141. },
  142. render () {
  143. const { media, intl, width, height, sensitive, autoplay } = this.props;
  144. let spoilerButton = (
  145. <div style={spoilerButtonStyle} >
  146. <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
  147. </div>
  148. );
  149. let expandButton = (
  150. <div style={expandButtonStyle} >
  151. <IconButton title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
  152. </div>
  153. );
  154. let muteButton = '';
  155. if (this.state.hasAudio) {
  156. muteButton = (
  157. <div style={muteStyle}>
  158. <IconButton title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
  159. </div>
  160. );
  161. }
  162. if (!this.state.visible) {
  163. if (sensitive) {
  164. return (
  165. <div style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
  166. {spoilerButton}
  167. <span style={spoilerSpanStyle}><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
  168. <span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
  169. </div>
  170. );
  171. } else {
  172. return (
  173. <div style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
  174. {spoilerButton}
  175. <span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
  176. <span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
  177. </div>
  178. );
  179. }
  180. }
  181. if (this.state.preview && !autoplay) {
  182. return (
  183. <div style={{ cursor: 'pointer', position: 'relative', marginTop: '8px', width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }} onClick={this.handleOpen}>
  184. {spoilerButton}
  185. <div style={{ position: 'absolute', top: '50%', left: '50%', fontSize: '36px', transform: 'translate(-50%, -50%)', padding: '5px', borderRadius: '100px', color: 'rgba(255, 255, 255, 0.8)' }}><i className='fa fa-play' /></div>
  186. </div>
  187. );
  188. }
  189. return (
  190. <div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
  191. {spoilerButton}
  192. {muteButton}
  193. {expandButton}
  194. <video ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
  195. </div>
  196. );
  197. }
  198. });
  199. export default injectIntl(VideoPlayer);