import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Link from './Link'; import ProgressSlider from './Fields/ProgressSlider'; import VolumeControl from './Fields/VolumeControl'; import MuteControl from './Fields/MuteControl'; import OutputControl from './Fields/OutputControl'; import Dater from './Dater'; import ArtistSentence from './ArtistSentence'; import Thumbnail from './Thumbnail'; import Icon from './Icon'; import * as helpers from '../helpers'; import * as uiActions from '../services/ui/actions'; import * as coreActions from '../services/core/actions'; import * as mopidyActions from '../services/mopidy/actions'; class PlaybackControls extends React.Component { constructor(props) { super(props); this.stream = null; this.state = { expanded: false, current_track: null, transition_track: null, transition_direction: null, }; } componentDidMount() { if (this.props.http_streaming_enabled) { // Bust our cache, and by consequence, play our stream this.props.coreActions.cachebustHttpStream(); } if (this.props.current_track) { this.setState({ current_track: this.props.current_track }); } } playStream(props = this.props) { if (!this.stream) { this.stream = new Audio(); } else { this.stream.src = null; } if (!props.http_streaming_enabled || !props.http_streaming_url) { return false; } this.stream.src = `${props.http_streaming_url}?cb=${props.http_streaming_cachebuster}`; this.stream.muted = props.http_streaming_mute; this.stream.volume = props.http_streaming_volume / 100; this.stream.play(); console.log(`Playing stream: ${this.stream.src}`); } componentWillReceiveProps(nextProps) { // Cachebuster changed // This happens when playback changes, so that the stream is "new", rather // than the original stream. This prevents the browser cache from starting // the stream right from the beginning (which could be hours of continuous playback). if (this.props.http_streaming_cachebuster !== nextProps.http_streaming_cachebuster) { this.playStream(nextProps); } // Just been enabled if (!this.props.http_streaming_enabled && nextProps.http_streaming_enabled) { this.playStream(nextProps); } if (this.stream) { // Just been muted if (this.props.http_streaming_mute !== nextProps.http_streaming_mute) { this.stream.muted = nextProps.http_streaming_mute; } // Just had volume changed if (this.props.http_streaming_volume !== nextProps.http_streaming_volume) { this.stream.volume = nextProps.http_streaming_volume / 100; } // Just been disabled if (!nextProps.http_streaming_enabled) { this.stream = null; } } // Started a track or changed to no track if ((!this.props.current_track && nextProps.current_track) || (this.props.current_track && !nextProps.current_track)) { this.setState({ current_track: nextProps.current_track }); } // Direct swap-out of a track (with no 'null' intermediate state) // This is precautionary and I've never been able to trigger it, but it's a good safety if (this.props.current_track && nextProps.current_track && this.props.current_track.uri !== nextProps.current_track.uri) { this.setState({ current_track: nextProps.current_track }); } // Images have just loaded // A bit niggly to have to deeply check this... if (this.props.current_track && nextProps.current_track) { if ((this.props.current_track.images && !nextProps.current_track.images) || (!this.props.current_track.images && nextProps.current_track.images)) { this.setState({ current_track: nextProps.current_track }); } } } handleTouchStart(e) { const timestamp = Math.floor(Date.now()); // Save touch start details this.start_time = timestamp; this.start_position = { x: e.touches[0].clientX, }; return false; } handleTouchEnd(e) { const timestamp = Math.floor(Date.now()); const tap_distance_threshold = 10; // Max distance (px) between touchstart and touchend to qualify as a tap const tap_time_threshold = 200; // Max time (ms) between touchstart and touchend to qualify as a tap const end_position = { x: e.changedTouches[0].clientX, }; // Too long between touchstart and touchend if (this.start_time + tap_time_threshold < timestamp) { return false; } // Make sure there's enough distance between start and end before we handle // this event as a 'tap' if (this.start_position.x + tap_distance_threshold > end_position.x && this.start_position.x - tap_distance_threshold < end_position.x) { // Scroll to top (without smooth_scroll) helpers.scrollTo(null, false); this.props.history.push('/queue'); } else { // Swipe to the left = previous track if (this.start_position.x < end_position.x) { this.setTransition('previous'); this.props.mopidyActions.previous(); // Swipe to the right = skip track } else if (this.start_position.x > end_position.x) { this.setTransition('next'); this.props.mopidyActions.next(); } } this.end_time = timestamp; e.preventDefault(); } setTransition(direction) { this.setState({ current_track: null, transition_track: this.state.current_track, transition_direction: direction, }); // Allow time for the animation to complete, then remove // the transitioning track from state setTimeout(() => { this.setState({ transition_track: null, transition_direction: null, }); }, 250); } renderPlayButton() { let button = ; if (this.props.play_state == 'playing') { button = ; } return button; } renderConsumeButton() { let button = ( ); if (this.props.consume) { button = ( ); } return button; } renderRandomButton() { let button = ( ); if (this.props.random) { button = ( ); } return button; } renderRepeatButton() { let button = ( ); if (this.props.repeat) { button = ( ); } return button; } render() { const { next_track, touch_enabled, time_position } = this.props; const { current_track, expanded } = this.state; let images = false; if (current_track && current_track.images) { images = current_track.images; } return (