Adding ReactHowler to handle playback

This commit is contained in:
James Barnsley
2020-03-20 08:15:15 +13:00
parent 92b8790f81
commit f747cf8484
7 changed files with 10217 additions and 9827 deletions

View File

@ -2,6 +2,7 @@ import React from 'react';
import { bindActionCreators } from 'redux';
import { Route, Switch } from 'react-router-dom';
import { connect } from 'react-redux';
import ReactHowler from 'react-howler'
import ReactGA from 'react-ga';
import Sidebar from './components/Sidebar';

View File

@ -12,6 +12,7 @@ import Dater from './Dater';
import LinksSentence from './LinksSentence';
import Thumbnail from './Thumbnail';
import Icon from './Icon';
import Stream from './Stream';
import { scrollTo } from '../util/helpers';
import * as uiActions from '../services/ui/actions';
import * as coreActions from '../services/core/actions';
@ -40,64 +41,6 @@ class PlaybackControls extends React.Component {
}
}
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}`);
}
componentDidUpdate = ({
http_streaming_cachebuster: prev_http_streaming_cachebuster,
http_streaming_enabled: prev_http_streaming_enabled,
http_streaming_mute: prev_http_streaming_mute,
http_streaming_volume: prev_http_streaming_volume,
}) => {
const {
http_streaming_cachebuster,
http_streaming_enabled,
http_streaming_mute,
http_streaming_volume,
} = this.props;
// 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 (prev_http_streaming_cachebuster !== http_streaming_cachebuster) this.playStream();
// Just been enabled
if (!prev_http_streaming_enabled && http_streaming_enabled) this.playStream();
if (this.stream) {
if (http_streaming_mute !== prev_http_streaming_mute) {
this.stream.muted = http_streaming_mute;
}
// Just had volume changed
if (http_streaming_volume !== prev_http_streaming_volume) {
this.stream.volume = http_streaming_volume / 100;
}
// Just been disabled
if (!http_streaming_enabled) {
this.stream = null;
}
}
}
static getDerivedStateFromProps({ current_track }, state) {
return {
...state,
@ -246,6 +189,8 @@ class PlaybackControls extends React.Component {
return (
<div className={`playback-controls${expanded ? ' playback-controls--expanded' : ''}${touch_enabled ? ' playback-controls--touch-enabled' : ''}`}>
<Stream />
{next_track && next_track.images ? <Thumbnail className="hide" size="large" images={next_track.images} /> : null}
{this.state.transition_track && this.state.transition_direction ? (
@ -332,13 +277,8 @@ class PlaybackControls extends React.Component {
}
}
const mapStateToProps = (state, ownProps) => ({
const mapStateToProps = (state) => ({
snapcast_enabled: state.pusher.config.snapcast_enabled,
http_streaming_enabled: state.core.http_streaming_enabled,
http_streaming_volume: state.core.http_streaming_volume >= 0 ? state.core.http_streaming_volume : 50,
http_streaming_mute: state.core.http_streaming_mute,
http_streaming_url: (state.core.http_streaming_url ? state.core.http_streaming_url : null),
http_streaming_cachebuster: state.core.http_streaming_cachebuster,
current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null),
next_track: (state.core.next_track_uri && state.core.tracks[state.core.next_track_uri] !== undefined ? state.core.tracks[state.core.next_track_uri] : null),
radio_enabled: (!!(state.ui.radio && state.ui.radio.enabled)),

65
src/js/components/Stream.js Executable file
View File

@ -0,0 +1,65 @@
import React from 'react';
import ReactHowler from 'react-howler';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as coreActions from '../services/core/actions';
class Stream extends React.Component {
constructor(props) {
super(props);
this.state = {
uri: '',
play_state: '',
cachebuster: '',
};
}
static getDerivedStateFromProps({ play_state, current_track: { uri } = {} }, state) {
if (uri === state.uri && play_state === state.play_state) return null;
return {
...state,
uri,
play_state,
cachebuster: `${Date.now()}`,
};
}
render = () => {
const {
play_state,
enabled,
volume,
url,
} = this.props;
const { cachebuster } = this.state;
if (!enabled || !url) return null;
return (
<ReactHowler
src={`${url}?cb=${cachebuster}`}
playing={play_state === 'playing'}
volume={volume / 100}
onLoad={() => console.debug(`Loaded stream ${url}?cb=${cachebuster}`)}
html5
/>
);
}
};
const mapStateToProps = (state) => ({
current_track: state.core.current_track || {},
play_state: state.mopidy.play_state,
enabled: state.core.http_streaming_enabled,
volume: state.core.http_streaming_volume >= 0 ? state.core.http_streaming_volume : 50,
url: (state.core.http_streaming_url ? state.core.http_streaming_url : null),
cachebuster: state.core.http_streaming_cachebuster,
});
const mapDispatchToProps = (dispatch) => ({
coreActions: bindActionCreators(coreActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Stream);