Rework website with bulma instead of bootstrap #6
|
@ -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 (
|
||||
<div className="is-flex-grow-1 is-flex is-flex-direction-column">
|
||||
<Visualizer
|
||||
audioContext={musicPlayer.audioContext}
|
||||
audioNode={musicPlayer.audioNode}
|
||||
/>
|
||||
<Visualizer />
|
||||
<div className="is-flex-grow-0">
|
||||
<Controls />
|
||||
</div>
|
||||
|
|
|
@ -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<PlayState> => {
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Renderer | null>(null);
|
||||
let renderer = rendererState[0];
|
||||
const setRenderer = rendererState[1];
|
||||
const [renderError, setRenderError] = useState<JSX.Element | null>(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,
|
||||
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) {
|
||||
|
|
Reference in a new issue