2023-06-14 10:17:57 +08:00

159 lines
5.8 KiB
JavaScript

import { useEffect, useRef, useState, useCallback } from "react";
import useSetTrackProgress from "./useSetTrackProgress.js"
import { useResizeDetector } from 'react-resize-detector';
const pointWidth = 2;
const pointMargin = 3;
const trackDuration = 60; // <audio> duration: seconds
const chunkedData = [10, 12, 12, 15, 20, 30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
30, 34, 36, 37, 35, 37, 63, 73, 83, 67, 86, 76, 74, 83, 90, 79, 85, 90, 83, 83, 75, 57, 70, 82, 58, 73, 73, 85, 73, 76, 75,
];
function timeTag(timepoint) {
if (isNaN(timepoint)) return "00:00";
timepoint = parseInt(timepoint);
let second = parseInt(timepoint % 60);
let minute = parseInt(timepoint / 60);
return minute.toString().padStart(2, '0') + ":" + second.toString().padStart(2, '0');
}
const pointCoordinates = ({
index, pointWidth, pointMargin, canvasHeight, maxAmplitude, amplitude,
}) => {
const pointHeight = Math.round((amplitude / 100) * maxAmplitude)
const verticalCenter = Math.round((canvasHeight - pointHeight) / 2)
return [
index * (pointWidth + pointMargin) + 14, // x starting point
(canvasHeight - pointHeight) - verticalCenter + 10, // y starting point
pointWidth, // width
pointHeight, // height
]
}
function drawText(context, width, duration) {
context.fillStyle = "red";
context.textBaseline = "top";
context.font = "12px arial";
context.fillStyle = "#99A3AF";
let timepoint = 0;
let segements = parseInt((width - 30) / 80);
let interval = parseInt(duration / segements);
for (let i = 0; i < segements; i++) {
context.fillStyle = "#99A3AF";
context.fillText(timeTag(timepoint), i * 80, 10 + 4);
timepoint += interval;
}
for (let i = 14; i < width; i += 40) {
context.fillStyle = "#9DA7B2";
context.fillRect(i, 0, 1, (i - 14) % 80 == 0 ? 12 : 6);
}
}
const paintCanvas = ({
canvas, waveformData, duration, canvasWidth, canvasHeight, pointWidth, pointMargin,
playingPoint, hoverXCoord,
}) => {
// console.log("paintCanvas", canvasHeight, playingPoint, hoverXCoord);
const ref = canvas.current;
const ctx = ref.getContext('2d')
ctx.clearRect(0, 0, ref.width, ref.height);
drawText(ctx, canvasWidth, duration);
waveformData.forEach((p, i) => {
ctx.beginPath()
const coordinates = pointCoordinates({
index: i,
pointWidth,
pointMargin,
canvasHeight,
maxAmplitude: canvasHeight - 30, // 留出空间画时间轴
amplitude: p,
})
ctx.rect(...coordinates)
const withinHover = hoverXCoord >= coordinates[0]
const alreadyPlayed = i < playingPoint
if (withinHover) {
ctx.fillStyle = alreadyPlayed ? '#FFB3B3' : '#badebf'
} else if (alreadyPlayed) {
ctx.fillStyle = '#FF595A'
} else {
ctx.fillStyle = '#ABB5BC'
}
ctx.fill()
}
);
}
export default function ({ width }) {
const canvas = useRef(null);
const [trackPlaying, setTrackPlaying] = useState(true);
const [trackProgress, setTrackProgress] = useState(0); // [0,100]
const [hoverXCoord, setHoverXCoord] = useState(0);
const [startTime, setStartTime] = useState(Date.now());
const playingPoint = (trackProgress * (canvas.current ? width : 0) / 100) / (pointWidth + pointMargin);
const paintWaveform = useCallback(() => {
paintCanvas({
canvas,
waveformData: chunkedData,
duration: trackDuration,
canvasWidth: width,
canvasHeight: canvas.current.height,
pointWidth,
pointMargin,
playingPoint,
hoverXCoord,
})
}, [playingPoint, hoverXCoord])
useSetTrackProgress({
trackProgress, setTrackProgress, trackDuration, startTime,
trackPlaying
});
useEffect(() => {
if (!canvas.current) return;
paintWaveform()
}, [canvas, width])
useEffect(() => {
paintWaveform()
}, [playingPoint])
const setDefaultX = useCallback(() => {
setHoverXCoord(0);
}, [])
const handleMouseMove = useCallback((e) => {
setHoverXCoord(e.clientX - canvas.current.getBoundingClientRect().left);
}, [])
const seekTrack = (e) => {
const xCoord = e.clientX - canvas.current.getBoundingClientRect().left
const seekMs = trackDuration * (xCoord / width);
setStartTime(Date.now() / 1000 - seekMs)
}
return <canvas height={70} width={width} ref={canvas}
onBlur={setDefaultX}
onMouseOut={setDefaultX}
onMouseMove={handleMouseMove}
onClick={seekTrack}
/>
}