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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -100,7 +100,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1584404587";
var build = "1584604056";
var version = "3.46.0";
// Construct the script tag

5
package.json Executable file → Normal file
View File

@ -18,11 +18,12 @@
"react-dom": "^16.12.0",
"react-fontawesome": "^1.7.1",
"react-ga": "^2.7.0",
"react-howler": "^3.7.4",
"react-input-range": "1.3.0",
"react-redux": "^6",
"react-router": "^5",
"react-router-dom": "^5.1.2",
"react-sortablejs": "^1.5.0",
"react-router": "^5"
"react-sortablejs": "^1.5.0"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.8.3",

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);