Adding retry when Icecast closes endpoint between tracks, causing 404

This commit is contained in:
James Barnsley
2020-05-17 20:22:15 +12:00
parent 8c688d2fbc
commit c2aff5c921
5 changed files with 127 additions and 65 deletions

View File

@ -162,18 +162,17 @@ class OutputControl extends React.Component {
Local browser
</div>
<div className="output-control__item__controls">
<span
className="output-control__item__action"
onClick={(e) => coreActions.cachebustHttpStream()}
>
<Icon name="refresh" />
</span>
<MuteControl
className="output-control__item__mute"
noTooltip
mute={http_streaming_mute}
onMuteChange={(mute) => coreActions.set({ http_streaming_mute: mute })}
/>
<VolumeControl
className="output-control__item__volume"
volume={http_streaming_volume}
mute={http_streaming_mute}
onVolumeChange={(percent) => coreActions.set({ http_streaming_volume: percent })}
onMuteChange={(mute) => coreActions.set({ http_streaming_mute: mute })}
/>
</div>
</div>

View File

@ -1,5 +1,5 @@
import React, { createRef } from 'react';
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as coreActions from '../services/core/actions';
@ -13,14 +13,18 @@ class Stream extends React.Component {
play_state: '',
cachebuster: `${Date.now()}`,
url: null,
loaded: false,
retryCount: 0,
};
this.audioRef = createRef();
this.audio = new Audio();
this.audio.onerror = (error) => this.onError(error);
}
static getDerivedStateFromProps(props, state) {
const {
volume,
mute,
enabled: propEnabled,
play_state: propPlayState,
url: propUrl,
@ -30,23 +34,28 @@ class Stream extends React.Component {
} = props;
const {
play_state: statePlayState,
enabled: stateEnabled,
uri: stateUri,
url: stateUrl,
cachebuster,
} = state;
let { retryCount } = state;
// Same track as before, and still playing
if (
propUri
&& propUri === stateUri
&& propPlayState === statePlayState
&& propEnabled === stateEnabled
&& propUrl === stateUrl
) {
return null;
}
let url = null;
let fullUrl = null;
if (propEnabled && propUrl && propUri) {
url = `${propUrl}?cb=${cachebuster}_${propUri}`;
console.log(`Playing stream: ${url}`);
fullUrl = `${propUrl}?cb=${cachebuster}_${propUri}`;
retryCount = 0;
}
return {
@ -55,34 +64,60 @@ class Stream extends React.Component {
uri: propUri,
play_state: propPlayState,
volume,
url,
mute,
fullUrl,
loaded: false,
retryCount,
};
}
componentDidUpdate = () => {
const { volume } = this.props;
if (this.audioRef.current) {
this.audioRef.current.volume = volume / 100;
componentDidUpdate = (prevProps, prevState) => {
const {
fullUrl,
volume,
} = this.state;
const { mute } = this.props;
if (!fullUrl) return null;
this.audio.muted = mute;
this.audio.volume = volume / 100;
// Only update URL if it's changed. This prevents re-loading the stream when something unrelated
// (like volume) was changed
if (prevState.fullUrl !== fullUrl) {
this.play(fullUrl);
}
}
render = () => {
const { url } = this.state;
if (!url) return null;
return (
<div key={url}>
<audio autoPlay ref={this.audioRef}>
<source src={url} />
</audio>
</div>
);
onError = (error) => {
const { retryCount } = this.state;
if (retryCount < 3) {
console.error(`Audio failed to load. Retrying ${retryCount+1}/3`, error);
setTimeout(
() => {
this.play();
this.setState({ retryCount: retryCount + 1 });
},
250,
);
}
}
play = (url = null) => {
const { fullUrl } = this.state;
console.info(`Playing stream: ${url || fullUrl}`);
this.audio.src = url || fullUrl;
this.audio.play();
}
render = () => null;
}
const mapStateToProps = (state) => ({
current_track: state.core.current_track || {},
play_state: state.mopidy.play_state,
enabled: state.core.http_streaming_enabled,
mute: state.core.http_streaming_mute || false,
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),
});