The code powering m.abunchtell.com https://m.abunchtell.com
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 

220 líneas
6.7 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import WaveSurfer from 'wavesurfer.js';
  4. import { defineMessages, injectIntl } from 'react-intl';
  5. import { formatTime } from 'mastodon/features/video';
  6. import Icon from 'mastodon/components/icon';
  7. import classNames from 'classnames';
  8. import { throttle } from 'lodash';
  9. const messages = defineMessages({
  10. play: { id: 'video.play', defaultMessage: 'Play' },
  11. pause: { id: 'video.pause', defaultMessage: 'Pause' },
  12. mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
  13. unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
  14. });
  15. const arrayOf = (length, fill) => (new Array(length)).fill(fill);
  16. export default @injectIntl
  17. class Audio extends React.PureComponent {
  18. static propTypes = {
  19. src: PropTypes.string.isRequired,
  20. alt: PropTypes.string,
  21. duration: PropTypes.number,
  22. height: PropTypes.number,
  23. preload: PropTypes.bool,
  24. editable: PropTypes.bool,
  25. intl: PropTypes.object.isRequired,
  26. };
  27. state = {
  28. currentTime: 0,
  29. duration: null,
  30. paused: true,
  31. muted: false,
  32. volume: 0.5,
  33. };
  34. // hard coded in components.scss
  35. // any way to get ::before values programatically?
  36. volWidth = 50;
  37. volOffset = 70;
  38. volHandleOffset = v => {
  39. const offset = v * this.volWidth + this.volOffset;
  40. return (offset > 110) ? 110 : offset;
  41. }
  42. setVolumeRef = c => {
  43. this.volume = c;
  44. }
  45. setWaveformRef = c => {
  46. this.waveform = c;
  47. }
  48. componentDidMount () {
  49. if (this.waveform) {
  50. this._updateWaveform();
  51. }
  52. }
  53. componentDidUpdate (prevProps) {
  54. if (this.waveform && prevProps.src !== this.props.src) {
  55. this._updateWaveform();
  56. }
  57. }
  58. componentWillUnmount () {
  59. if (this.wavesurfer) {
  60. this.wavesurfer.destroy();
  61. this.wavesurfer = null;
  62. }
  63. }
  64. _updateWaveform () {
  65. const { src, height, duration, preload } = this.props;
  66. const progressColor = window.getComputedStyle(document.querySelector('.audio-player__progress-placeholder')).getPropertyValue('background-color');
  67. const waveColor = window.getComputedStyle(document.querySelector('.audio-player__wave-placeholder')).getPropertyValue('background-color');
  68. if (this.wavesurfer) {
  69. this.wavesurfer.destroy();
  70. }
  71. const wavesurfer = WaveSurfer.create({
  72. container: this.waveform,
  73. height,
  74. barWidth: 3,
  75. cursorWidth: 0,
  76. progressColor,
  77. waveColor,
  78. forceDecode: true,
  79. });
  80. wavesurfer.setVolume(this.state.volume);
  81. if (preload) {
  82. wavesurfer.load(src);
  83. } else {
  84. wavesurfer.load(src, arrayOf(1, 0.5), null, duration);
  85. }
  86. wavesurfer.on('ready', () => this.setState({ duration: Math.floor(wavesurfer.getDuration()) }));
  87. wavesurfer.on('audioprocess', () => this.setState({ currentTime: Math.floor(wavesurfer.getCurrentTime()) }));
  88. wavesurfer.on('pause', () => this.setState({ paused: true }));
  89. wavesurfer.on('play', () => this.setState({ paused: false }));
  90. wavesurfer.on('volume', volume => this.setState({ volume }));
  91. wavesurfer.on('mute', muted => this.setState({ muted }));
  92. this.wavesurfer = wavesurfer;
  93. }
  94. togglePlay = () => {
  95. if (this.state.paused) {
  96. if (!this.props.preload) {
  97. this.wavesurfer.createBackend();
  98. this.wavesurfer.createPeakCache();
  99. this.wavesurfer.load(this.props.src);
  100. }
  101. this.wavesurfer.play();
  102. } else {
  103. this.wavesurfer.pause();
  104. }
  105. }
  106. toggleMute = () => {
  107. this.wavesurfer.setMute(!this.state.muted);
  108. }
  109. handleVolumeMouseDown = e => {
  110. document.addEventListener('mousemove', this.handleMouseVolSlide, true);
  111. document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
  112. document.addEventListener('touchmove', this.handleMouseVolSlide, true);
  113. document.addEventListener('touchend', this.handleVolumeMouseUp, true);
  114. this.handleMouseVolSlide(e);
  115. e.preventDefault();
  116. e.stopPropagation();
  117. }
  118. handleVolumeMouseUp = () => {
  119. document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
  120. document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
  121. document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
  122. document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
  123. }
  124. handleMouseVolSlide = throttle(e => {
  125. const rect = this.volume.getBoundingClientRect();
  126. const x = (e.clientX - rect.left) / this.volWidth; // x position within the element.
  127. if(!isNaN(x)) {
  128. let slideamt = x;
  129. if (x > 1) {
  130. slideamt = 1;
  131. } else if(x < 0) {
  132. slideamt = 0;
  133. }
  134. this.wavesurfer.setVolume(slideamt);
  135. }
  136. }, 60);
  137. render () {
  138. const { height, intl, alt, editable } = this.props;
  139. const { paused, muted, volume, currentTime } = this.state;
  140. const volumeWidth = muted ? 0 : volume * this.volWidth;
  141. const volumeHandleLoc = muted ? this.volHandleOffset(0) : this.volHandleOffset(volume);
  142. return (
  143. <div className={classNames('audio-player', { editable })}>
  144. <div className='audio-player__progress-placeholder' style={{ display: 'none' }} />
  145. <div className='audio-player__wave-placeholder' style={{ display: 'none' }} />
  146. <div
  147. className='audio-player__waveform'
  148. aria-label={alt}
  149. title={alt}
  150. style={{ height }}
  151. ref={this.setWaveformRef}
  152. />
  153. <div className='video-player__controls active'>
  154. <div className='video-player__buttons-bar'>
  155. <div className='video-player__buttons left'>
  156. <button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
  157. <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
  158. <div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
  159. <div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
  160. <span
  161. className={classNames('video-player__volume__handle')}
  162. tabIndex='0'
  163. style={{ left: `${volumeHandleLoc}px` }}
  164. />
  165. </div>
  166. <span>
  167. <span className='video-player__time-current'>{formatTime(currentTime)}</span>
  168. <span className='video-player__time-sep'>/</span>
  169. <span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span>
  170. </span>
  171. </div>
  172. </div>
  173. </div>
  174. </div>
  175. );
  176. }
  177. }