mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
dc5c64a109
* refactor(waveform-seek): H3.1 — extract helpers + constants + types * refactor(waveform-seek): H3.2 — extract drawSeekbar + style renderers to utils/waveformSeekRenderers.ts * refactor(waveform-seek): H3.3 — split renderers into static + animated * refactor(waveform-seek): H3.4 — extract SeekbarPreview to WaveformSeekPreview.tsx * refactor(waveform-seek): H3.5 — extract useWaveformHeights hook + hoist constants * refactor(waveform-seek): H3.6 — extract useWaveformInterpolation hook
291 lines
7.6 KiB
TypeScript
291 lines
7.6 KiB
TypeScript
import {
|
|
BAR_COUNT, SEG_COUNT,
|
|
getColors, setShadowBlur, setupCanvas, waveformBarThickness,
|
|
} from './waveformSeekHelpers';
|
|
|
|
export function drawWaveform(
|
|
canvas: HTMLCanvasElement,
|
|
heights: Float32Array | null,
|
|
progress: number,
|
|
buffered: number,
|
|
) {
|
|
const r = setupCanvas(canvas);
|
|
if (!r) return;
|
|
const { ctx, w, h } = r;
|
|
const { played, buffered: buffCol, unplayed } = getColors();
|
|
const pNorm = Math.max(0, Math.min(1, progress));
|
|
const bNorm = Math.max(pNorm, Math.min(1, buffered));
|
|
|
|
if (!heights) {
|
|
// No waveform data yet: flat rail like `drawLineDot`, but do not return early
|
|
// before played/buffered — otherwise there is no visible playhead.
|
|
const cy = h / 2;
|
|
const lh = 2;
|
|
const dotR = 5;
|
|
ctx.globalAlpha = 0.35;
|
|
ctx.fillStyle = unplayed;
|
|
ctx.fillRect(0, cy - lh / 2, w, lh);
|
|
if (buffered > 0) {
|
|
ctx.globalAlpha = 0.55;
|
|
ctx.fillStyle = buffCol;
|
|
ctx.fillRect(0, cy - lh / 2, Math.min(1, buffered) * w, lh);
|
|
}
|
|
if (progress > 0) {
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = played;
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 5);
|
|
ctx.fillRect(0, cy - lh / 2, pNorm * w, lh);
|
|
setShadowBlur(ctx, 0);
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
if (w > 0) {
|
|
const dx = Math.max(dotR, Math.min(w - dotR, pNorm * w));
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 7);
|
|
ctx.beginPath();
|
|
ctx.arc(dx, cy, dotR, 0, Math.PI * 2);
|
|
ctx.fillStyle = played;
|
|
ctx.fill();
|
|
setShadowBlur(ctx, 0);
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
return;
|
|
}
|
|
const x1Of = (i: number) => (i / BAR_COUNT) * w;
|
|
const x2Of = (i: number) => ((i + 1) / BAR_COUNT) * w;
|
|
ctx.globalAlpha = 0.28;
|
|
ctx.fillStyle = unplayed;
|
|
for (let i = 0; i < BAR_COUNT; i++) {
|
|
if (i / BAR_COUNT < bNorm) continue;
|
|
const bh = waveformBarThickness(h, heights[i]);
|
|
const x = x1Of(i);
|
|
ctx.fillRect(x, (h - bh) / 2, x2Of(i) - x, bh);
|
|
}
|
|
|
|
ctx.globalAlpha = 0.45;
|
|
ctx.fillStyle = buffCol;
|
|
for (let i = 0; i < BAR_COUNT; i++) {
|
|
const frac = i / BAR_COUNT;
|
|
if (frac < pNorm || frac >= bNorm) continue;
|
|
const bh = waveformBarThickness(h, heights[i]);
|
|
const x = x1Of(i);
|
|
ctx.fillRect(x, (h - bh) / 2, x2Of(i) - x, bh);
|
|
}
|
|
|
|
if (pNorm > 0) {
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = played;
|
|
for (let i = 0; i < BAR_COUNT; i++) {
|
|
if (i / BAR_COUNT >= pNorm) break;
|
|
const bh = waveformBarThickness(h, heights[i]);
|
|
const x = x1Of(i);
|
|
ctx.fillRect(x, (h - bh) / 2, x2Of(i) - x, bh);
|
|
}
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
export function drawLineDot(canvas: HTMLCanvasElement, progress: number, buffered: number) {
|
|
const r = setupCanvas(canvas);
|
|
if (!r) return;
|
|
const { ctx, w, h } = r;
|
|
const { played, buffered: buffCol, unplayed } = getColors();
|
|
const cy = h / 2;
|
|
const lh = 2;
|
|
const dotR = 5;
|
|
|
|
ctx.globalAlpha = 0.35;
|
|
ctx.fillStyle = unplayed;
|
|
ctx.fillRect(0, cy - lh / 2, w, lh);
|
|
|
|
if (buffered > 0) {
|
|
ctx.globalAlpha = 0.55;
|
|
ctx.fillStyle = buffCol;
|
|
ctx.fillRect(0, cy - lh / 2, buffered * w, lh);
|
|
}
|
|
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = played;
|
|
ctx.fillRect(0, cy - lh / 2, progress * w, lh);
|
|
|
|
const dx = Math.max(dotR, Math.min(w - dotR, progress * w));
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 7);
|
|
ctx.beginPath();
|
|
ctx.arc(dx, cy, dotR, 0, Math.PI * 2);
|
|
ctx.fillStyle = played;
|
|
ctx.fill();
|
|
setShadowBlur(ctx, 0);
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
export function drawBar(canvas: HTMLCanvasElement, progress: number, buffered: number) {
|
|
const r = setupCanvas(canvas);
|
|
if (!r) return;
|
|
const { ctx, w, h } = r;
|
|
const { played, buffered: buffCol, unplayed } = getColors();
|
|
const bh = 4;
|
|
const rad = bh / 2;
|
|
const y = (h - bh) / 2;
|
|
|
|
ctx.globalAlpha = 0.3;
|
|
ctx.fillStyle = unplayed;
|
|
ctx.beginPath();
|
|
ctx.roundRect(0, y, w, bh, rad);
|
|
ctx.fill();
|
|
|
|
if (buffered > 0) {
|
|
ctx.globalAlpha = 0.5;
|
|
ctx.fillStyle = buffCol;
|
|
ctx.beginPath();
|
|
ctx.roundRect(0, y, buffered * w, bh, rad);
|
|
ctx.fill();
|
|
}
|
|
|
|
if (progress > 0) {
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = played;
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 5);
|
|
ctx.beginPath();
|
|
ctx.roundRect(0, y, progress * w, bh, rad);
|
|
ctx.fill();
|
|
setShadowBlur(ctx, 0);
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
export function drawThick(canvas: HTMLCanvasElement, progress: number, buffered: number) {
|
|
const r = setupCanvas(canvas);
|
|
if (!r) return;
|
|
const { ctx, w, h } = r;
|
|
const { played, buffered: buffCol, unplayed } = getColors();
|
|
const bh = Math.min(14, h);
|
|
const rad = bh / 2;
|
|
const y = (h - bh) / 2;
|
|
|
|
ctx.globalAlpha = 0.25;
|
|
ctx.fillStyle = unplayed;
|
|
ctx.beginPath();
|
|
ctx.roundRect(0, y, w, bh, rad);
|
|
ctx.fill();
|
|
|
|
if (buffered > 0) {
|
|
ctx.globalAlpha = 0.45;
|
|
ctx.fillStyle = buffCol;
|
|
ctx.beginPath();
|
|
ctx.roundRect(0, y, buffered * w, bh, rad);
|
|
ctx.fill();
|
|
}
|
|
|
|
if (progress > 0) {
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = played;
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 10);
|
|
ctx.beginPath();
|
|
ctx.roundRect(0, y, progress * w, bh, rad);
|
|
ctx.fill();
|
|
setShadowBlur(ctx, 0);
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
export function drawSegmented(canvas: HTMLCanvasElement, progress: number, buffered: number) {
|
|
const r = setupCanvas(canvas);
|
|
if (!r) return;
|
|
const { ctx, w, h } = r;
|
|
const { played, buffered: buffCol, unplayed } = getColors();
|
|
const gap = 2;
|
|
const segW = (w - gap * (SEG_COUNT - 1)) / SEG_COUNT;
|
|
const segH = h * 0.65;
|
|
const y = (h - segH) / 2;
|
|
const playedIdx = Math.floor(progress * SEG_COUNT);
|
|
|
|
for (let i = 0; i < SEG_COUNT; i++) {
|
|
const frac = i / SEG_COUNT;
|
|
const x = i * (segW + gap);
|
|
setShadowBlur(ctx, 0);
|
|
if (frac < progress) {
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = played;
|
|
if (i === playedIdx - 1) {
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 5);
|
|
}
|
|
} else if (frac < buffered) {
|
|
ctx.globalAlpha = 0.55;
|
|
ctx.fillStyle = buffCol;
|
|
} else {
|
|
ctx.globalAlpha = 0.28;
|
|
ctx.fillStyle = unplayed;
|
|
}
|
|
ctx.beginPath();
|
|
ctx.roundRect(x, y, Math.max(1, segW), segH, 1);
|
|
ctx.fill();
|
|
}
|
|
setShadowBlur(ctx, 0);
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
// ── new styles ────────────────────────────────────────────────────────────────
|
|
|
|
export function drawNeon(canvas: HTMLCanvasElement, progress: number, buffered: number) {
|
|
const r = setupCanvas(canvas);
|
|
if (!r) return;
|
|
const { ctx, w, h } = r;
|
|
const { played, unplayed } = getColors();
|
|
const cy = h / 2;
|
|
|
|
// Ghost track — barely visible
|
|
ctx.globalAlpha = 0.07;
|
|
ctx.fillStyle = unplayed;
|
|
ctx.fillRect(0, cy - 1, w, 2);
|
|
|
|
if (buffered > 0) {
|
|
ctx.globalAlpha = 0.12;
|
|
ctx.fillStyle = unplayed;
|
|
ctx.fillRect(0, cy - 1, buffered * w, 2);
|
|
}
|
|
|
|
if (progress <= 0) return;
|
|
|
|
const px = progress * w;
|
|
|
|
// Wide outer glow
|
|
ctx.globalAlpha = 0.18;
|
|
ctx.fillStyle = played;
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 22);
|
|
ctx.fillRect(0, cy - 5, px, 10);
|
|
|
|
// Mid glow
|
|
ctx.globalAlpha = 0.45;
|
|
setShadowBlur(ctx, 12);
|
|
ctx.fillRect(0, cy - 2.5, px, 5);
|
|
|
|
// Inner glow
|
|
ctx.globalAlpha = 0.85;
|
|
setShadowBlur(ctx, 5);
|
|
ctx.fillRect(0, cy - 1.5, px, 3);
|
|
|
|
// Bright white core
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = '#ffffff';
|
|
ctx.shadowColor = played;
|
|
setShadowBlur(ctx, 4);
|
|
ctx.fillRect(0, cy - 0.75, px, 1.5);
|
|
|
|
// End-cap flare
|
|
setShadowBlur(ctx, 16);
|
|
ctx.beginPath();
|
|
ctx.arc(px, cy, 2.5, 0, Math.PI * 2);
|
|
ctx.fillStyle = '#ffffff';
|
|
ctx.fill();
|
|
|
|
setShadowBlur(ctx, 0);
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|