159 lines
5.8 KiB
JavaScript
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}
|
|
/>
|
|
} |