130
src/WaveForm.jsx
130
src/WaveForm.jsx
@@ -1,4 +1,4 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import useSize from "./useSize";
|
||||
|
||||
const BLUE_SHADES = [
|
||||
@@ -8,47 +8,130 @@ const BLUE_SHADES = [
|
||||
"rgba(255,255,255,0.2)",
|
||||
];
|
||||
|
||||
const animateBars = (analyser, canvas, ctx, dataArray, bufferLength) => {
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
// Fallback animation for Tizen browsers without Web Audio API
|
||||
const animateFallbackBars = (canvas, ctx, fallbackData) => {
|
||||
const HEIGHT = canvas.height / 2;
|
||||
const barWidth = Math.ceil(canvas.width / bufferLength) * 2.5;
|
||||
const barCount = 64; // Reduced for better performance on TV
|
||||
const barWidth = Math.max(2, Math.floor(canvas.width / barCount));
|
||||
let x = 0;
|
||||
for (let i = 0; i < bufferLength; i++) {
|
||||
const barHeight = (dataArray[i] / 255) * HEIGHT;
|
||||
const blueShade = Math.floor((dataArray[i] / 255) * 4);
|
||||
ctx.fillStyle = BLUE_SHADES[blueShade];
|
||||
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);
|
||||
x += barWidth + 1;
|
||||
|
||||
for (let i = 0; i < barCount; i++) {
|
||||
const barHeight = fallbackData[i] * HEIGHT;
|
||||
const blueShade = Math.floor(fallbackData[i] * 3);
|
||||
ctx.fillStyle = BLUE_SHADES[blueShade] || BLUE_SHADES[0];
|
||||
ctx.fillRect(x, HEIGHT - barHeight, barWidth - 1, barHeight);
|
||||
x += barWidth;
|
||||
}
|
||||
};
|
||||
|
||||
// Real Web Audio API animation
|
||||
const animateBars = (analyser, canvas, ctx, dataArray, bufferLength) => {
|
||||
try {
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
const HEIGHT = canvas.height / 2;
|
||||
const barWidth = Math.ceil(canvas.width / bufferLength) * 2.5;
|
||||
let x = 0;
|
||||
for (let i = 0; i < bufferLength; i++) {
|
||||
const barHeight = (dataArray[i] / 255) * HEIGHT;
|
||||
const blueShade = Math.floor((dataArray[i] / 255) * 4);
|
||||
ctx.fillStyle = BLUE_SHADES[blueShade] || BLUE_SHADES[0];
|
||||
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);
|
||||
x += barWidth + 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error in Web Audio API animation:', error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const WaveForm = ({ analyzerData }) => {
|
||||
const canvasRef = useRef(null);
|
||||
const { dataArray, analyzer, bufferLength } = analyzerData;
|
||||
const [width, height] = useSize();
|
||||
const [useFallback, setUseFallback] = useState(false);
|
||||
const [fallbackData, setFallbackData] = useState([]);
|
||||
const fallbackIntervalRef = useRef(null);
|
||||
|
||||
// Initialize fallback data
|
||||
useEffect(() => {
|
||||
const initialData = Array(64).fill(0).map(() => Math.random() * 0.3 + 0.1);
|
||||
setFallbackData(initialData);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas || !analyzer) return;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
let animationId;
|
||||
let webAudioWorking = true;
|
||||
|
||||
// Check if we have valid analyzer data
|
||||
const hasValidAnalyzer = analyzerData &&
|
||||
analyzerData.analyzer &&
|
||||
analyzerData.dataArray &&
|
||||
analyzerData.bufferLength;
|
||||
|
||||
const render = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.save();
|
||||
ctx.translate(0, canvas.height / 2);
|
||||
animateBars(analyzer, canvas, ctx, dataArray, bufferLength);
|
||||
ctx.restore();
|
||||
animationId = requestAnimationFrame(render);
|
||||
try {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.save();
|
||||
ctx.translate(0, canvas.height / 2);
|
||||
|
||||
if (hasValidAnalyzer && webAudioWorking && !useFallback) {
|
||||
// Try Web Audio API first
|
||||
const success = animateBars(
|
||||
analyzerData.analyzer,
|
||||
canvas,
|
||||
ctx,
|
||||
analyzerData.dataArray,
|
||||
analyzerData.bufferLength
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
webAudioWorking = false;
|
||||
setUseFallback(true);
|
||||
}
|
||||
} else {
|
||||
// Use fallback animation
|
||||
animateFallbackBars(canvas, ctx, fallbackData);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
animationId = requestAnimationFrame(render);
|
||||
} catch (error) {
|
||||
console.warn('Canvas rendering error:', error);
|
||||
setUseFallback(true);
|
||||
animationId = requestAnimationFrame(render);
|
||||
}
|
||||
};
|
||||
|
||||
// Start fallback data animation if needed
|
||||
if (useFallback || !hasValidAnalyzer) {
|
||||
fallbackIntervalRef.current = setInterval(() => {
|
||||
setFallbackData(prevData =>
|
||||
prevData.map(val => {
|
||||
const target = Math.random() * 0.8 + 0.2;
|
||||
const smoothing = 0.15;
|
||||
return val + (target - val) * smoothing;
|
||||
})
|
||||
);
|
||||
}, 100); // Update every 100ms for smooth animation
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationId);
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
if (fallbackIntervalRef.current) {
|
||||
clearInterval(fallbackIntervalRef.current);
|
||||
}
|
||||
};
|
||||
}, [dataArray, analyzer, bufferLength, width, height]);
|
||||
}, [analyzerData, width, height, useFallback, fallbackData]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
@@ -57,10 +140,17 @@ const WaveForm = ({ analyzerData }) => {
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 0,
|
||||
// Add hardware acceleration hint for Tizen
|
||||
willChange: "transform",
|
||||
// Ensure canvas doesn't cause layout issues on TV
|
||||
imageRendering: "pixelated"
|
||||
}}
|
||||
ref={canvasRef}
|
||||
width={width}
|
||||
height={height}
|
||||
// Add accessibility for Smart TV navigation
|
||||
role="img"
|
||||
aria-label={useFallback ? "Audio visualization (compatibility mode)" : "Real-time audio visualization"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Referencia en una nueva incidencia
Block a user