diff --git a/src/music/features/musicplayer/MusicPlayer.tsx b/src/music/features/musicplayer/MusicPlayer.tsx
index b6fd92a..0b6b60f 100644
--- a/src/music/features/musicplayer/MusicPlayer.tsx
+++ b/src/music/features/musicplayer/MusicPlayer.tsx
@@ -2,15 +2,11 @@ import React from "react";
import Controls from "../controls/Controls";
import Visualizer from "../visualizer/Visualizer";
-import { musicPlayer } from "./musicPlayerSlice";
function MusicPlayer() {
return (
-
+
diff --git a/src/music/features/musicplayer/musicPlayerSlice.ts b/src/music/features/musicplayer/musicPlayerSlice.ts
index 14e4a7c..57e7a97 100644
--- a/src/music/features/musicplayer/musicPlayerSlice.ts
+++ b/src/music/features/musicplayer/musicPlayerSlice.ts
@@ -30,27 +30,18 @@ enum PlayState {
//*********************
class MusicPlayer {
- private context: AudioContext;
+ private context?: AudioContext;
private source: HTMLAudioElement;
- private sourceNode: MediaElementAudioSourceNode;
- private volume: GainNode;
+ private sourceNode?: MediaElementAudioSourceNode;
+ private volume?: GainNode;
+ private analyser?: AnalyserNode;
constructor() {
- this.context = new AudioContext();
this.source = new Audio();
- this.sourceNode = this.context.createMediaElementSource(this.source);
- this.volume = this.context.createGain();
-
- this.sourceNode.connect(this.volume);
- this.volume.connect(this.context.destination);
}
- get audioContext() {
- return this.context;
- }
-
- get audioNode() {
- return this.sourceNode;
+ get audioAnalyser() {
+ return this.analyser;
}
set src(source: string) {
@@ -61,6 +52,22 @@ class MusicPlayer {
_: null,
{ getState }: { getState: () => RootState }
): Promise
=> {
+ if (this.context === undefined) {
+ this.context = new AudioContext();
+ this.sourceNode = this.context.createMediaElementSource(
+ this.source
+ );
+ this.volume = this.context.createGain();
+ this.analyser = this.context.createAnalyser();
+
+ this.analyser.fftSize = 2048;
+ this.analyser.smoothingTimeConstant = 0.8;
+
+ this.sourceNode.connect(this.analyser);
+ this.sourceNode.connect(this.volume);
+ this.volume.connect(this.context.destination);
+ }
+
const playing = getState().musicPlayer.playing;
switch (playing) {
diff --git a/src/music/features/visualizer/Renderer.ts b/src/music/features/visualizer/Renderer.ts
index 3597b59..c5f55da 100644
--- a/src/music/features/visualizer/Renderer.ts
+++ b/src/music/features/visualizer/Renderer.ts
@@ -33,16 +33,10 @@ class Renderer {
};
constructor(
- context: AudioContext,
- node: AudioNode,
+ analyser: AnalyserNode,
canvas: HTMLCanvasElement,
overlay: HTMLSpanElement
) {
- const analyser = context.createAnalyser();
- analyser.fftSize = 2048;
- analyser.smoothingTimeConstant = 0.8;
- node.connect(analyser);
-
this.canvas = canvas;
this.overlay = overlay;
this.analyser = analyser;
diff --git a/src/music/features/visualizer/Visualizer.tsx b/src/music/features/visualizer/Visualizer.tsx
index 8081d6b..5c7b8c3 100644
--- a/src/music/features/visualizer/Visualizer.tsx
+++ b/src/music/features/visualizer/Visualizer.tsx
@@ -2,17 +2,41 @@ import React, { useCallback, useState } from "react";
import { Renderer, RendererError } from "./Renderer";
import { ShaderError } from "./Shader";
-function Visualizer({
- audioContext,
- audioNode,
-}: {
- audioContext: AudioContext;
- audioNode: AudioNode;
-}) {
+import { useAppSelector } from "../../hooks";
+import { PlayState, musicPlayer } from "../musicplayer/musicPlayerSlice";
+
+function Visualizer() {
+ const playing = useAppSelector((state) => state.musicPlayer.playing);
+ const rendererState = useState(null);
+ let renderer = rendererState[0];
+ const setRenderer = rendererState[1];
const [renderError, setRenderError] = useState(null);
const visualizer = useCallback(
(visualizer: HTMLDivElement | null) => {
+ // TODO(tlater): Clean up state management. This is all
+ // but trivial; there's seemingly no good place to keep
+ // these big api objects (WebGLRenderingcontext or
+ // AudioContext).
+ //
+ // It's tricky, too, because obviously react expects to be
+ // in control of the DOM, and be allowed to delete our
+ // canvas and create a new one.
+ //
+ // For the moment, this works, but it's a definite hack.
+ if (renderer) {
+ return;
+ }
+
+ // Until we start playing music, there is nothing to render.
+ if (playing !== PlayState.Playing) {
+ return;
+ }
+
+ if (musicPlayer.audioAnalyser === undefined) {
+ throw new Error("MusicPlayer analyser was not set up on time");
+ }
+
// If we're rendering an error message, we won't be
// setting up the visualizer.
//
@@ -35,12 +59,14 @@ function Visualizer({
);
}
- const renderer = new Renderer(
- audioContext,
- audioNode,
- canvas,
- overlay
- );
+ if (renderer === null) {
+ renderer = new Renderer(
+ musicPlayer.audioAnalyser,
+ canvas,
+ overlay
+ );
+ setRenderer(renderer);
+ }
try {
renderer.initializeScene();
@@ -92,7 +118,7 @@ function Visualizer({
}
}
},
- [audioContext, audioNode]
+ [playing, renderer, musicPlayer.audioAnalyser]
);
if (renderError === null) {