import React, { useState, useRef, useEffect } from 'react';
import { Play, Pause, Volume2, VolumeX, Radio, RefreshCw, Activity, Wifi, WifiOff } from 'lucide-react';
// Default Configuration (Update with your live stream and WebSocket API endpoints)
const DEFAULT_STREAM_URL = "https://stream.zeno.fm/f3wvbb1424ztv";
const DEFAULT_WS_URL = "wss://api.focus360.io/v1/market-stream";
export default function MarketDashboardWidget() {
// --- Radio State & Refs ---
const audioRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [volume, setVolume] = useState(0.8);
const [radioStatus, setRadioStatus] = useState('idle'); // 'idle' | 'connecting' | 'playing' | 'error'
const [streamUrl, setStreamUrl] = useState(DEFAULT_STREAM_URL);
const [currentSegment, setCurrentSegment] = useState('Live Audio Stream');
// --- Real-time Data & WebSocket State ---
const [wsUrl, setWsUrl] = useState(DEFAULT_WS_URL);
const [wsConnected, setWsConnected] = useState(false);
const [dataPoints, setDataPoints] = useState([100]);
const wsRef = useRef(null);
// --- Chart Canvas Ref ---
const canvasRef = useRef(null);
// 1. Sync Radio Volume
useEffect(() => {
if (audioRef.current) {
audioRef.current.volume = isMuted ? 0 : volume;
}
}, [volume, isMuted]);
// 2. Real-time WebSocket Client Connection
useEffect(() => {
let ws;
try {
ws = new WebSocket(wsUrl);
wsRef.current = ws;
ws.onopen = () => {
setWsConnected(true);
// Subscribe to live market data feed
ws.send(JSON.stringify({ action: 'subscribe', channel: 'FOCUS360_MARKETS' }));
};
ws.onmessage = (event) => {
try {
const payload = JSON.parse(event.data);
// Handle incoming price ticks
if (payload.type === 'TICK' && typeof payload.price === 'number') {
setDataPoints((prevPoints) => {
const updated = [...prevPoints, payload.price];
return updated.slice(-60); // Retain last 60 ticks for graph rendering
});
}
// Handle incoming radio stream metadata
if (payload.type === 'RADIO_META' && payload.title) {
setCurrentSegment(payload.title);
}
} catch (err) {
console.error("Error parsing WebSocket frame:", err);
}
};
ws.onerror = () => setWsConnected(false);
ws.onclose = () => setWsConnected(false);
} catch (err) {
console.warn("WebSocket setup failed, fallback mode active:", err);
setWsConnected(false);
}
return () => {
if (ws) ws.close();
};
}, [wsUrl]);
// Fallback Simulation when WebSocket is offline
useEffect(() => {
if (wsConnected) return;
const interval = setInterval(() => {
setDataPoints((prevPoints) => {
const lastVal = prevPoints[prevPoints.length - 1] || 100;
const nextVal = lastVal + (Math.random() - 0.48) * 2;
const updated = [...prevPoints, nextVal];
return updated.slice(-60);
});
}, 500);
return () => clearInterval(interval);
}, [wsConnected]);
// 3. Render Chart from Real-time Data Stream
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
ctx.clearRect(0, 0, width, height);
if (dataPoints.length === 0) return;
const min = Math.min(...dataPoints) - 2;
const max = Math.max(...dataPoints) + 2;
// Draw Grid
ctx.strokeStyle = '#1e293b';
ctx.lineWidth = 1;
for (let y = 0; y < height; y += 30) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
// Draw Price Trend
ctx.beginPath();
ctx.strokeStyle = radioStatus === 'playing' ? '#10b981' : '#3b82f6';
ctx.lineWidth = 2;
dataPoints.forEach((pt, idx) => {
const x = (idx / Math.max(dataPoints.length - 1, 1)) * width;
const y = height - ((pt - min) / (max - min || 1)) * height;
if (idx === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.stroke();
// Fill Gradient Area
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.fillStyle = radioStatus === 'playing'
? 'rgba(16, 185, 129, 0.08)'
: 'rgba(59, 130, 246, 0.08)';
ctx.fill();
}, [dataPoints, radioStatus]);
// 4. Radio Controls
const togglePlay = () => {
const audio = audioRef.current;
if (!audio) return;
if (isPlaying) {
audio.pause();
audio.src = ''; // Drop buffer for live stream sync
setIsPlaying(false);
setRadioStatus('idle');
} else {
setRadioStatus('connecting');
audio.src = streamUrl;
audio.play()
.then(() => {
setIsPlaying(true);
setRadioStatus('playing');
})
.catch((err) => {
console.error("Radio playback error:", err);
setRadioStatus('error');
setIsPlaying(false);
});
}
};
const toggleMute = () => setIsMuted(!isMuted);
return (
{/* Radio Audio Element */}
);
}