From 3aeaf9b897a7fff8ebb08055b06717e78e5cf914 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 23 Aug 2019 22:38:02 +0200 Subject: [Glitch] Add audio player Port 4190e31626907059aebf32b1be66715dacb989a9 to glitch-soc Signed-off-by: Thibaut Girka --- .../flavours/glitch/features/audio/index.js | 219 +++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 app/javascript/flavours/glitch/features/audio/index.js (limited to 'app/javascript/flavours/glitch/features/audio') diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js new file mode 100644 index 000000000..1daf56bc1 --- /dev/null +++ b/app/javascript/flavours/glitch/features/audio/index.js @@ -0,0 +1,219 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import WaveSurfer from 'wavesurfer.js'; +import { defineMessages, injectIntl } from 'react-intl'; +import { formatTime } from 'flavours/glitch/features/video'; +import Icon from 'flavours/glitch/components/icon'; +import classNames from 'classnames'; +import { throttle } from 'lodash'; + +const messages = defineMessages({ + play: { id: 'video.play', defaultMessage: 'Play' }, + pause: { id: 'video.pause', defaultMessage: 'Pause' }, + mute: { id: 'video.mute', defaultMessage: 'Mute sound' }, + unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' }, +}); + +const arrayOf = (length, fill) => (new Array(length)).fill(fill); + +export default @injectIntl +class Audio extends React.PureComponent { + + static propTypes = { + src: PropTypes.string.isRequired, + alt: PropTypes.string, + duration: PropTypes.number, + height: PropTypes.number, + preload: PropTypes.bool, + editable: PropTypes.bool, + intl: PropTypes.object.isRequired, + }; + + state = { + currentTime: 0, + duration: null, + paused: true, + muted: false, + volume: 0.5, + }; + + // hard coded in components.scss + // any way to get ::before values programatically? + + volWidth = 50; + + volOffset = 70; + + volHandleOffset = v => { + const offset = v * this.volWidth + this.volOffset; + return (offset > 110) ? 110 : offset; + } + + setVolumeRef = c => { + this.volume = c; + } + + setWaveformRef = c => { + this.waveform = c; + } + + componentDidMount () { + if (this.waveform) { + this._updateWaveform(); + } + } + + componentDidUpdate (prevProps) { + if (this.waveform && prevProps.src !== this.props.src) { + this._updateWaveform(); + } + } + + componentWillUnmount () { + if (this.wavesurfer) { + this.wavesurfer.destroy(); + this.wavesurfer = null; + } + } + + _updateWaveform () { + const { src, height, duration, preload } = this.props; + + const progressColor = window.getComputedStyle(document.querySelector('.audio-player__progress-placeholder')).getPropertyValue('background-color'); + const waveColor = window.getComputedStyle(document.querySelector('.audio-player__wave-placeholder')).getPropertyValue('background-color'); + + if (this.wavesurfer) { + this.wavesurfer.destroy(); + } + + const wavesurfer = WaveSurfer.create({ + container: this.waveform, + height, + barWidth: 3, + cursorWidth: 0, + progressColor, + waveColor, + forceDecode: true, + }); + + wavesurfer.setVolume(this.state.volume); + + if (preload) { + wavesurfer.load(src); + } else { + wavesurfer.load(src, arrayOf(1, 0.5), null, duration); + } + + wavesurfer.on('ready', () => this.setState({ duration: Math.floor(wavesurfer.getDuration()) })); + wavesurfer.on('audioprocess', () => this.setState({ currentTime: Math.floor(wavesurfer.getCurrentTime()) })); + wavesurfer.on('pause', () => this.setState({ paused: true })); + wavesurfer.on('play', () => this.setState({ paused: false })); + wavesurfer.on('volume', volume => this.setState({ volume })); + wavesurfer.on('mute', muted => this.setState({ muted })); + + this.wavesurfer = wavesurfer; + } + + togglePlay = () => { + if (this.state.paused) { + if (!this.props.preload) { + this.wavesurfer.createBackend(); + this.wavesurfer.createPeakCache(); + this.wavesurfer.load(this.props.src); + } + + this.wavesurfer.play(); + } else { + this.wavesurfer.pause(); + } + } + + toggleMute = () => { + this.wavesurfer.setMute(!this.state.muted); + } + + handleVolumeMouseDown = e => { + document.addEventListener('mousemove', this.handleMouseVolSlide, true); + document.addEventListener('mouseup', this.handleVolumeMouseUp, true); + document.addEventListener('touchmove', this.handleMouseVolSlide, true); + document.addEventListener('touchend', this.handleVolumeMouseUp, true); + + this.handleMouseVolSlide(e); + + e.preventDefault(); + e.stopPropagation(); + } + + handleVolumeMouseUp = () => { + document.removeEventListener('mousemove', this.handleMouseVolSlide, true); + document.removeEventListener('mouseup', this.handleVolumeMouseUp, true); + document.removeEventListener('touchmove', this.handleMouseVolSlide, true); + document.removeEventListener('touchend', this.handleVolumeMouseUp, true); + } + + handleMouseVolSlide = throttle(e => { + const rect = this.volume.getBoundingClientRect(); + const x = (e.clientX - rect.left) / this.volWidth; // x position within the element. + + if(!isNaN(x)) { + let slideamt = x; + + if (x > 1) { + slideamt = 1; + } else if(x < 0) { + slideamt = 0; + } + + this.wavesurfer.setVolume(slideamt); + } + }, 60); + + render () { + const { height, intl, alt, editable } = this.props; + const { paused, muted, volume, currentTime } = this.state; + + const volumeWidth = muted ? 0 : volume * this.volWidth; + const volumeHandleLoc = muted ? this.volHandleOffset(0) : this.volHandleOffset(volume); + + return ( +
+
+
+ +
+ +
+
+
+ + + +
+
+ + +
+ + + {formatTime(currentTime)} + / + {formatTime(this.state.duration || Math.floor(this.props.duration))} + +
+
+
+
+ ); + } + +} -- cgit From 84d4d75c91db62adb7f81ba6382f2f756898e6eb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 25 Aug 2019 02:13:40 +0200 Subject: [Glitch] Fix public page crash due to audio player, fix unpause in audio player Port e72bac7576263445630926dd9992004ece7b73c4 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/features/audio/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'app/javascript/flavours/glitch/features/audio') diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js index 1daf56bc1..bdd297d86 100644 --- a/app/javascript/flavours/glitch/features/audio/index.js +++ b/app/javascript/flavours/glitch/features/audio/index.js @@ -84,6 +84,7 @@ class Audio extends React.PureComponent { if (this.wavesurfer) { this.wavesurfer.destroy(); + this.loaded = false; } const wavesurfer = WaveSurfer.create({ @@ -100,8 +101,10 @@ class Audio extends React.PureComponent { if (preload) { wavesurfer.load(src); + this.loaded = true; } else { wavesurfer.load(src, arrayOf(1, 0.5), null, duration); + this.loaded = false; } wavesurfer.on('ready', () => this.setState({ duration: Math.floor(wavesurfer.getDuration()) })); @@ -116,15 +119,18 @@ class Audio extends React.PureComponent { togglePlay = () => { if (this.state.paused) { - if (!this.props.preload) { + if (!this.props.preload && !this.loaded) { this.wavesurfer.createBackend(); this.wavesurfer.createPeakCache(); this.wavesurfer.load(this.props.src); + this.loaded = true; } this.wavesurfer.play(); + this.setState({ paused: false }); } else { this.wavesurfer.pause(); + this.setState({ paused: true }); } } -- cgit From b90bd31cfdc812da770ec3abd3a1a3a597d85a04 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 25 Aug 2019 15:09:19 +0200 Subject: [Glitch] Fix more visual issues with the audio player Port 2e99e3cab349db6102505736e3b4b94abe776b80 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/components/status.js | 3 ++- app/javascript/flavours/glitch/features/audio/index.js | 11 ++++++----- .../glitch/features/status/components/detailed_status.js | 2 +- app/javascript/flavours/glitch/styles/components/media.scss | 13 +++++++++++++ .../flavours/glitch/styles/mastodon-light/diff.scss | 7 +++++++ 5 files changed, 29 insertions(+), 7 deletions(-) (limited to 'app/javascript/flavours/glitch/features/audio') diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 47e110128..e7bf1f4d0 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -575,7 +575,8 @@ class Status extends ImmutablePureComponent { src={attachment.get('url')} alt={attachment.get('description')} duration={attachment.getIn(['meta', 'original', 'duration'], 0)} - height={110} + peaks={[0]} + height={70} /> )} diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js index bdd297d86..0830a4684 100644 --- a/app/javascript/flavours/glitch/features/audio/index.js +++ b/app/javascript/flavours/glitch/features/audio/index.js @@ -14,8 +14,6 @@ const messages = defineMessages({ unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' }, }); -const arrayOf = (length, fill) => (new Array(length)).fill(fill); - export default @injectIntl class Audio extends React.PureComponent { @@ -23,6 +21,7 @@ class Audio extends React.PureComponent { src: PropTypes.string.isRequired, alt: PropTypes.string, duration: PropTypes.number, + peaks: PropTypes.arrayOf(PropTypes.number), height: PropTypes.number, preload: PropTypes.bool, editable: PropTypes.bool, @@ -77,7 +76,7 @@ class Audio extends React.PureComponent { } _updateWaveform () { - const { src, height, duration, preload } = this.props; + const { src, height, duration, peaks, preload } = this.props; const progressColor = window.getComputedStyle(document.querySelector('.audio-player__progress-placeholder')).getPropertyValue('background-color'); const waveColor = window.getComputedStyle(document.querySelector('.audio-player__wave-placeholder')).getPropertyValue('background-color'); @@ -94,7 +93,8 @@ class Audio extends React.PureComponent { cursorWidth: 0, progressColor, waveColor, - forceDecode: true, + backend: 'MediaElement', + interact: preload, }); wavesurfer.setVolume(this.state.volume); @@ -103,7 +103,7 @@ class Audio extends React.PureComponent { wavesurfer.load(src); this.loaded = true; } else { - wavesurfer.load(src, arrayOf(1, 0.5), null, duration); + wavesurfer.load(src, peaks, 'none', duration); this.loaded = false; } @@ -123,6 +123,7 @@ class Audio extends React.PureComponent { this.wavesurfer.createBackend(); this.wavesurfer.createPeakCache(); this.wavesurfer.load(this.props.src); + this.wavesurfer.toggleInteraction(); this.loaded = true; } diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index b676410d6..5242c7d5c 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -140,7 +140,7 @@ export default class DetailedStatus extends ImmutablePureComponent { src={attachment.get('url')} alt={attachment.get('description')} duration={attachment.getIn(['meta', 'original', 'duration'], 0)} - height={150} + height={110} preload /> ); diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index e207562a5..6dee7725c 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -347,6 +347,19 @@ &__waveform { padding: 15px 0; + position: relative; + overflow: hidden; + + &::before { + content: ""; + display: block; + position: absolute; + border-top: 1px solid lighten($ui-base-color, 4%); + width: 100%; + height: 0; + left: 0; + top: calc(50% + 1px); + } } &__progress-placeholder { diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss index 35a8ce7a3..4c2b76a21 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss @@ -372,3 +372,10 @@ .directory__tag > div { box-shadow: none; } + +.audio-player .video-player__controls button, +.audio-player .video-player__time-sep, +.audio-player .video-player__time-current, +.audio-player .video-player__time-total { + color: $primary-text-color; +} -- cgit