import { useRef, useCallback, useState, useEffect } from "react"; const pointWidth = 2; const pointMargin = 3; const intervalsPerTag = 10; function timeTag(timepoint) { if (isNaN(timepoint)) return "00:00"; timepoint = Math.round(timepoint); let second = Math.round(timepoint % 60); let minute = Math.floor(timepoint / 60); return minute.toString().padStart(2, '0') + ":" + second.toString().padStart(2, '0'); } const pointCoordinates = ({ index, pointWidth, pointMargin, canvasHeight, maxAmplitude, amplitude, }) => { let pointHeight = Math.round((amplitude / 100) * maxAmplitude) if (pointHeight < 3) pointHeight = 3; const verticalCenter = Math.round((canvasHeight - pointHeight) / 2) return [ index * (pointWidth + pointMargin), // x starting point (canvasHeight - pointHeight) - verticalCenter + 10, // y starting point pointWidth, // width pointHeight, // height ] } function drawText(context, duration, interval) { context.save(); context.fillStyle = "red"; context.textBaseline = "top"; context.font = "12px arial"; context.fillStyle = "#99A3AF"; let scales = Math.floor(duration / interval); scales = Math.ceil(scales / intervalsPerTag) * intervalsPerTag + 1; for (let i = 0; i < scales; i++) { context.fillStyle = "#9DA7B2"; context.fillRect(i * (pointWidth + pointMargin), 0, pointWidth, (i % intervalsPerTag) == 0 ? 12 : 6); } context.fillStyle = "#99A3AF"; let timepoint = 0; for (let i = 0; i <= scales; i++) { if (i % intervalsPerTag == 0) { context.fillText(timeTag(timepoint / 1000), i * (pointWidth + pointMargin) - 14, 10 + 4); } timepoint += interval; } context.restore(); } const paintCanvas = ({ canvas, waveformData, duration, scrollLeft, leftPadding, canvasHeight, pointWidth, pointMargin, interval }) => { // console.log("paintCanvas", duration, canvasHeight, canvas.width, scrollLeft); const context = canvas.getContext('2d'); context.save(); context.clearRect(0, 0, canvas.width, canvas.height); context.translate(leftPadding, 0);; drawText(context, duration, interval); // 画刻度尺 waveformData.forEach((p, i) => { context.beginPath() const coordinates = pointCoordinates({ index: i, pointWidth, pointMargin, canvasHeight, maxAmplitude: canvasHeight - 30, // 留出空间画时间轴 amplitude: p, }) context.rect(...coordinates) context.fillStyle = (coordinates[0] <= scrollLeft) ? '#FF595A' : '#ABB5BC' context.fill() }); context.restore(); } // duration ms export default function ({ width, duration, currentTime, playing, seek, waveData }) { const interval = (duration > 20 * 60 * 1000) ? 200 : 100; // ms const container = useRef(null); const canvas = useRef(null); const [scrollLeft, setScrollLeft] = useState(0); const [mouseDown, setMouseDown] = useState(false); const [clickInfo, setClickInfo] = useState({ startX: 0, scrollLeft: 0, }); const onMouseDown = (event) => { setMouseDown(true); setClickInfo({ startX: event.pageX - container.current.offsetLeft, scrollLeft: container.current.scrollLeft, }); } const onMouseLeave = (event) => { setMouseDown(false); } const onMouseUp = (event) => { setMouseDown(false); seek(scrollLeft * interval / (pointWidth + pointMargin) / 1000); } const onMouseMove = (event) => { event.preventDefault(); if (!mouseDown) return; const x = event.pageX - container.current.offsetLeft; const scroll = x - clickInfo.startX; container.current.scrollLeft = clickInfo.scrollLeft - scroll; } const onScroll = () => { setScrollLeft(Math.round(container.current.scrollLeft)); }; const paintWaveform = useCallback(() => { paintCanvas({ canvas: canvas.current, waveformData: waveData, duration: duration, scrollLeft: scrollLeft, leftPadding: Math.round(width / 2), canvasHeight: canvas.current.height, pointWidth, pointMargin, interval, }) }, [duration, width, scrollLeft, waveData]) useEffect(() => { if (!canvas.current) return; paintWaveform() }, [canvas, width, duration, scrollLeft, waveData]) useEffect(() => { if (!mouseDown) container.current.scrollLeft = Math.round(currentTime * 1000 / interval * (pointWidth + pointMargin)); }, [currentTime]); return
}