171 lines
5.3 KiB
JavaScript
171 lines
5.3 KiB
JavaScript
import { useRef, useCallback, useState, useEffect } from "react";
|
|
|
|
const pointWidth = 2;
|
|
const pointMargin = 3;
|
|
|
|
const interval = 100; // ms
|
|
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.round(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) {
|
|
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,
|
|
}) => {
|
|
// 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); // 画刻度尺
|
|
|
|
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 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,
|
|
})
|
|
}, [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 <div ref={container}
|
|
onMouseDown={onMouseDown}
|
|
onMouseUp={onMouseUp}
|
|
onMouseMove={onMouseMove}
|
|
onMouseLeave={onMouseLeave}
|
|
onScroll={onScroll}
|
|
style={{
|
|
width: width,
|
|
overflow: "scroll",
|
|
overflowY: "hidden",
|
|
}}>
|
|
<div style={{
|
|
height: 60,
|
|
backgroundColor: "red",
|
|
width: 2,
|
|
position: "absolute",
|
|
left: width / 2 + 70,
|
|
margin: 0,
|
|
padding: 0,
|
|
}} />
|
|
<canvas ref={canvas}
|
|
height={60}
|
|
width={width + (duration / interval) * (pointWidth + pointMargin)}
|
|
/>
|
|
</div>
|
|
} |