import React from "react"; import { connect } from "react-redux"; import Controls from "./components/controls"; import Visualizer from "./components/visualizer"; import { State } from "./store"; type AudioState = { audioContext: AudioContext; audioSource: HTMLAudioElement; audioSourceNode: MediaElementAudioSourceNode; audioVolume: GainNode; }; type MusicPlayerProps = { playing: boolean; muted: boolean; source?: string; }; class MusicPlayer extends React.Component { private audioState: AudioState; constructor(props: MusicPlayerProps) { super(props); const context = new AudioContext(); const source = new Audio(); const sourceNode = context.createMediaElementSource(source); const volume = context.createGain(); sourceNode.connect(volume); volume.connect(context.destination); this.audioState = { audioContext: context, audioSourceNode: sourceNode, audioSource: source, audioVolume: volume, }; } render() { return (
); } async componentDidUpdate() { const context = this.audioState.audioContext; const source = this.audioState.audioSource; const volume = this.audioState.audioVolume; // First, set the audio source (if it changed) if (this.props.source && source.src != this.props.source) { source.src = this.props.source; } if (this.props.playing) { // Chrome is super awkward about AudioContext, and won't // even allow creating one without complaining about // wanting user input, so we need to resume the context // before we can actually play. // // Luckily, this has no adverse effects on Firefox. try { await context.resume(); await source.play(); } catch (error) { if (error instanceof DOMException) { console.error(`Could not play audio: ${error.message}`); } else { throw error; } } } else { source.pause(); } if (!this.props.muted) { volume.gain.setValueAtTime(1, context.currentTime); } else { volume.gain.setValueAtTime(0, context.currentTime); } } } function mapStateToProps(state: State): MusicPlayerProps { return { playing: state.musicState.playing, muted: state.musicState.muted, source: state.musicState.source, }; } export default connect(mapStateToProps)(MusicPlayer);