闭社主体 forked from https://github.com/tootsuite/mastodon
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.

196 lines
5.9 KiB

  1. import React from 'react';
  2. import ImmutablePropTypes from 'react-immutable-proptypes';
  3. import PropTypes from 'prop-types';
  4. import IconButton from './icon_button';
  5. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  6. import { isIOS } from '../is_mobile';
  7. const messages = defineMessages({
  8. toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
  9. toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
  10. expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
  11. });
  12. @injectIntl
  13. export default class VideoPlayer extends React.PureComponent {
  14. static propTypes = {
  15. media: ImmutablePropTypes.map.isRequired,
  16. width: PropTypes.number,
  17. height: PropTypes.number,
  18. sensitive: PropTypes.bool,
  19. intl: PropTypes.object.isRequired,
  20. autoplay: PropTypes.bool,
  21. onOpenVideo: PropTypes.func.isRequired,
  22. };
  23. static defaultProps = {
  24. width: 239,
  25. height: 110,
  26. };
  27. state = {
  28. visible: !this.props.sensitive,
  29. preview: true,
  30. muted: true,
  31. hasAudio: true,
  32. videoError: false,
  33. };
  34. handleClick = () => {
  35. this.setState({ muted: !this.state.muted });
  36. }
  37. handleVideoClick = (e) => {
  38. e.stopPropagation();
  39. const node = this.video;
  40. if (node.paused) {
  41. node.play();
  42. } else {
  43. node.pause();
  44. }
  45. }
  46. handleOpen = () => {
  47. this.setState({ preview: !this.state.preview });
  48. }
  49. handleVisibility = () => {
  50. this.setState({
  51. visible: !this.state.visible,
  52. preview: true,
  53. });
  54. }
  55. handleExpand = () => {
  56. this.video.pause();
  57. this.props.onOpenVideo(this.props.media, this.video.currentTime);
  58. }
  59. setRef = (c) => {
  60. this.video = c;
  61. }
  62. handleLoadedData = () => {
  63. if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) {
  64. this.setState({ hasAudio: false });
  65. }
  66. }
  67. handleVideoError = () => {
  68. this.setState({ videoError: true });
  69. }
  70. componentDidMount () {
  71. if (!this.video) {
  72. return;
  73. }
  74. this.video.addEventListener('loadeddata', this.handleLoadedData);
  75. this.video.addEventListener('error', this.handleVideoError);
  76. }
  77. componentDidUpdate () {
  78. if (!this.video) {
  79. return;
  80. }
  81. this.video.addEventListener('loadeddata', this.handleLoadedData);
  82. this.video.addEventListener('error', this.handleVideoError);
  83. }
  84. componentWillUnmount () {
  85. if (!this.video) {
  86. return;
  87. }
  88. this.video.removeEventListener('loadeddata', this.handleLoadedData);
  89. this.video.removeEventListener('error', this.handleVideoError);
  90. }
  91. render () {
  92. const { media, intl, width, height, sensitive, autoplay } = this.props;
  93. let spoilerButton = (
  94. <div className={`status__video-player-spoiler ${this.state.visible ? 'status__video-player-spoiler--visible' : ''}`}>
  95. <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
  96. </div>
  97. );
  98. let expandButton = (
  99. <div className='status__video-player-expand'>
  100. <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
  101. </div>
  102. );
  103. let muteButton = '';
  104. if (this.state.hasAudio) {
  105. muteButton = (
  106. <div className='status__video-player-mute'>
  107. <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
  108. </div>
  109. );
  110. }
  111. if (!this.state.visible) {
  112. if (sensitive) {
  113. return (
  114. <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
  115. {spoilerButton}
  116. <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
  117. <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
  118. </div>
  119. );
  120. } else {
  121. return (
  122. <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
  123. {spoilerButton}
  124. <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
  125. <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
  126. </div>
  127. );
  128. }
  129. }
  130. if (this.state.preview && !autoplay) {
  131. return (
  132. <div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}>
  133. {spoilerButton}
  134. <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div>
  135. </div>
  136. );
  137. }
  138. if (this.state.videoError) {
  139. return (
  140. <div style={{ width: `${width}px`, height: `${height}px` }} className='video-error-cover' >
  141. <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span>
  142. </div>
  143. );
  144. }
  145. return (
  146. <div className='status__video-player' style={{ width: `${width}px`, height: `${height}px` }}>
  147. {spoilerButton}
  148. {muteButton}
  149. {expandButton}
  150. <video
  151. className='status__video-player-video'
  152. role='button'
  153. tabIndex='0'
  154. ref={this.setRef}
  155. src={media.get('url')}
  156. autoPlay={!isIOS()}
  157. loop
  158. muted={this.state.muted}
  159. onClick={this.handleVideoClick}
  160. />
  161. </div>
  162. );
  163. }
  164. }