Merge pull request #6433 from matrix-org/travis/voice-messages/errors
Render error state for audio components
This commit is contained in:
commit
98472b4fce
4 changed files with 63 additions and 44 deletions
|
@ -36,6 +36,7 @@ interface IProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
playbackPhase: PlaybackState;
|
playbackPhase: PlaybackState;
|
||||||
|
error?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.audio_messages.AudioPlayer")
|
@replaceableComponent("views.audio_messages.AudioPlayer")
|
||||||
|
@ -55,8 +56,10 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
// Don't wait for the promise to complete - it will emit a progress update when it
|
// Don't wait for the promise to complete - it will emit a progress update when it
|
||||||
// is done, and it's not meant to take long anyhow.
|
// is done, and it's not meant to take long anyhow.
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
this.props.playback.prepare().catch(e => {
|
||||||
this.props.playback.prepare();
|
console.error("Error processing audio file:", e);
|
||||||
|
this.setState({ error: true });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onPlaybackUpdate = (ev: PlaybackState) => {
|
private onPlaybackUpdate = (ev: PlaybackState) => {
|
||||||
|
@ -91,34 +94,37 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
||||||
public render(): ReactNode {
|
public render(): ReactNode {
|
||||||
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
|
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
|
||||||
// events for accessibility
|
// events for accessibility
|
||||||
return <div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
|
return <>
|
||||||
<div className='mx_AudioPlayer_primaryContainer'>
|
<div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
|
||||||
<PlayPauseButton
|
<div className='mx_AudioPlayer_primaryContainer'>
|
||||||
playback={this.props.playback}
|
<PlayPauseButton
|
||||||
playbackPhase={this.state.playbackPhase}
|
playback={this.props.playback}
|
||||||
tabIndex={-1} // prevent tabbing into the button
|
playbackPhase={this.state.playbackPhase}
|
||||||
ref={this.playPauseRef}
|
tabIndex={-1} // prevent tabbing into the button
|
||||||
/>
|
ref={this.playPauseRef}
|
||||||
<div className='mx_AudioPlayer_mediaInfo'>
|
/>
|
||||||
<span className='mx_AudioPlayer_mediaName'>
|
<div className='mx_AudioPlayer_mediaInfo'>
|
||||||
{ this.props.mediaName || _t("Unnamed audio") }
|
<span className='mx_AudioPlayer_mediaName'>
|
||||||
</span>
|
{ this.props.mediaName || _t("Unnamed audio") }
|
||||||
<div className='mx_AudioPlayer_byline'>
|
</span>
|
||||||
<DurationClock playback={this.props.playback} />
|
<div className='mx_AudioPlayer_byline'>
|
||||||
{ /* easiest way to introduce a gap between the components */ }
|
<DurationClock playback={this.props.playback} />
|
||||||
{ this.renderFileSize() }
|
{ /* easiest way to introduce a gap between the components */ }
|
||||||
|
{ this.renderFileSize() }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='mx_AudioPlayer_seek'>
|
||||||
|
<SeekBar
|
||||||
|
playback={this.props.playback}
|
||||||
|
tabIndex={-1} // prevent tabbing into the bar
|
||||||
|
playbackPhase={this.state.playbackPhase}
|
||||||
|
ref={this.seekRef}
|
||||||
|
/>
|
||||||
|
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_AudioPlayer_seek'>
|
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> }
|
||||||
<SeekBar
|
</>;
|
||||||
playback={this.props.playback}
|
|
||||||
tabIndex={-1} // prevent tabbing into the bar
|
|
||||||
playbackPhase={this.state.playbackPhase}
|
|
||||||
ref={this.seekRef}
|
|
||||||
/>
|
|
||||||
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import PlaybackClock from "./PlaybackClock";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { TileShape } from "../rooms/EventTile";
|
import { TileShape } from "../rooms/EventTile";
|
||||||
import PlaybackWaveform from "./PlaybackWaveform";
|
import PlaybackWaveform from "./PlaybackWaveform";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// Playback instance to render. Cannot change during component lifecycle: create
|
// Playback instance to render. Cannot change during component lifecycle: create
|
||||||
|
@ -33,6 +34,7 @@ interface IProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
playbackPhase: PlaybackState;
|
playbackPhase: PlaybackState;
|
||||||
|
error?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
||||||
|
@ -49,8 +51,10 @@ export default class RecordingPlayback extends React.PureComponent<IProps, IStat
|
||||||
|
|
||||||
// Don't wait for the promise to complete - it will emit a progress update when it
|
// Don't wait for the promise to complete - it will emit a progress update when it
|
||||||
// is done, and it's not meant to take long anyhow.
|
// is done, and it's not meant to take long anyhow.
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
this.props.playback.prepare().catch(e => {
|
||||||
this.props.playback.prepare();
|
console.error("Error processing audio file:", e);
|
||||||
|
this.setState({ error: true });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private get isWaveformable(): boolean {
|
private get isWaveformable(): boolean {
|
||||||
|
@ -65,10 +69,13 @@ export default class RecordingPlayback extends React.PureComponent<IProps, IStat
|
||||||
|
|
||||||
public render(): ReactNode {
|
public render(): ReactNode {
|
||||||
const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
|
const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
|
||||||
return <div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
|
return <>
|
||||||
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
<div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
|
||||||
<PlaybackClock playback={this.props.playback} />
|
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
||||||
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
|
<PlaybackClock playback={this.props.playback} />
|
||||||
</div>;
|
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
|
||||||
|
</div>
|
||||||
|
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> }
|
||||||
|
</>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2601,6 +2601,7 @@
|
||||||
"Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.",
|
"Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.",
|
||||||
"Sign in with SSO": "Sign in with SSO",
|
"Sign in with SSO": "Sign in with SSO",
|
||||||
"Unnamed audio": "Unnamed audio",
|
"Unnamed audio": "Unnamed audio",
|
||||||
|
"Error downloading audio": "Error downloading audio",
|
||||||
"Pause": "Pause",
|
"Pause": "Pause",
|
||||||
"Play": "Play",
|
"Play": "Play",
|
||||||
"Couldn't load page": "Couldn't load page",
|
"Couldn't load page": "Couldn't load page",
|
||||||
|
|
|
@ -135,18 +135,23 @@ export class Playback extends EventEmitter implements IDestroyable {
|
||||||
// Safari compat: promise API not supported on this function
|
// Safari compat: promise API not supported on this function
|
||||||
this.audioBuf = await new Promise((resolve, reject) => {
|
this.audioBuf = await new Promise((resolve, reject) => {
|
||||||
this.context.decodeAudioData(this.buf, b => resolve(b), async e => {
|
this.context.decodeAudioData(this.buf, b => resolve(b), async e => {
|
||||||
// This error handler is largely for Safari as well, which doesn't support Opus/Ogg
|
try {
|
||||||
// very well.
|
// This error handler is largely for Safari as well, which doesn't support Opus/Ogg
|
||||||
console.error("Error decoding recording: ", e);
|
// very well.
|
||||||
console.warn("Trying to re-encode to WAV instead...");
|
console.error("Error decoding recording: ", e);
|
||||||
|
console.warn("Trying to re-encode to WAV instead...");
|
||||||
|
|
||||||
const wav = await decodeOgg(this.buf);
|
const wav = await decodeOgg(this.buf);
|
||||||
|
|
||||||
// noinspection ES6MissingAwait - not needed when using callbacks
|
// noinspection ES6MissingAwait - not needed when using callbacks
|
||||||
this.context.decodeAudioData(wav, b => resolve(b), e => {
|
this.context.decodeAudioData(wav, b => resolve(b), e => {
|
||||||
console.error("Still failed to decode recording: ", e);
|
console.error("Still failed to decode recording: ", e);
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Caught decoding error:", e);
|
||||||
reject(e);
|
reject(e);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue