From 86b6e1a246e3b737013a4cb60c1ef5445d65e53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= Date: Sat, 13 Aug 2022 03:33:20 +0100 Subject: [PATCH] Fix AudioContext warnings --- .../features/musicplayer/MusicPlayer.tsx | 6 +-- .../features/musicplayer/musicPlayerSlice.ts | 37 +++++++------ src/music/features/visualizer/Renderer.ts | 8 +-- src/music/features/visualizer/Visualizer.tsx | 54 ++++++++++++++----- 4 files changed, 64 insertions(+), 41 deletions(-) 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) {