1.完成进度条的seek功能。
This commit is contained in:
parent
bf11df50e9
commit
b2fd82e9f4
@ -35,6 +35,7 @@ export default function () {
|
|||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const accessToken = useSelector(state => state.user.accessToken);
|
const accessToken = useSelector(state => state.user.accessToken);
|
||||||
const passportId = useSelector(state => state.user.passportId);
|
const passportId = useSelector(state => state.user.passportId);
|
||||||
|
const currentTime = useSelector(state => state.recorder.currentTime);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
yzs.get_record_list(accessToken, passportId).then(list => {
|
yzs.get_record_list(accessToken, passportId).then(list => {
|
||||||
dispatch(setList(list.result));
|
dispatch(setList(list.result));
|
||||||
@ -53,8 +54,8 @@ export default function () {
|
|||||||
mr: `24px`,
|
mr: `24px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlayerBar />
|
<PlayerBar currentTime={currentTime} />
|
||||||
<RecordLyrics />
|
<RecordLyrics currentTime={currentTime} />
|
||||||
</Box>
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</div >
|
</div >
|
||||||
|
@ -6,7 +6,7 @@ import { useResizeDetector } from 'react-resize-detector';
|
|||||||
import pauseIcon from "./assets/play.png";
|
import pauseIcon from "./assets/play.png";
|
||||||
import playIcon from "./assets/pause.png";
|
import playIcon from "./assets/pause.png";
|
||||||
import downloadIcon from "./assets/download.png";
|
import downloadIcon from "./assets/download.png";
|
||||||
import { togglePauseState } from "./business/recorderSlice.js"
|
import { setCurrentTime, togglePauseState } from "./business/recorderSlice.js"
|
||||||
import Waveform from "./components/Waveform";
|
import Waveform from "./components/Waveform";
|
||||||
|
|
||||||
const durationFormat = (time) => {
|
const durationFormat = (time) => {
|
||||||
@ -18,10 +18,9 @@ const durationFormat = (time) => {
|
|||||||
return hour.toString().padStart(2, '0') + ":" + minute.toString().padStart(2, '0') + ":" + second.toString().padStart(2, '0');
|
return hour.toString().padStart(2, '0') + ":" + minute.toString().padStart(2, '0') + ":" + second.toString().padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function () {
|
export default function ({ currentTime }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [duration, setDuration] = useState("00:00:00");
|
const [duration, setDuration] = useState(0);
|
||||||
const [currentTime, setCurrentTime] = useState("00:00:00");
|
|
||||||
const [canvasWidth, setCanvasWidth] = useState(0);
|
const [canvasWidth, setCanvasWidth] = useState(0);
|
||||||
const currentIndex = useSelector(state => state.recorder.currentIndex);
|
const currentIndex = useSelector(state => state.recorder.currentIndex);
|
||||||
const recordList = useSelector(state => state.recorder.list);
|
const recordList = useSelector(state => state.recorder.list);
|
||||||
@ -43,10 +42,10 @@ export default function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDurationChange = (event) => {
|
const onDurationChange = (event) => {
|
||||||
setDuration(durationFormat(player.current.duration));
|
setDuration(player.current.duration);
|
||||||
}
|
}
|
||||||
const onTimeUpdate = (event) => {
|
const onTimeUpdate = (event) => {
|
||||||
setCurrentTime(durationFormat(player.current.currentTime));
|
dispatch(setCurrentTime(player.current.currentTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResize = useCallback((width, height) => {
|
const onResize = useCallback((width, height) => {
|
||||||
@ -57,7 +56,15 @@ export default function () {
|
|||||||
onResize: onResize
|
onResize: onResize
|
||||||
});
|
});
|
||||||
|
|
||||||
return <Stack container >
|
const seekRecord = (second) => {
|
||||||
|
player.current.currentTime = second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Stack container sx={{
|
||||||
|
position: "sticky",
|
||||||
|
top: 48,
|
||||||
|
backgroundColor: "#FAFAFA",
|
||||||
|
}} >
|
||||||
<Container disableGutters maxWidth={false} sx={{
|
<Container disableGutters maxWidth={false} sx={{
|
||||||
height: 60,
|
height: 60,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -75,7 +82,7 @@ export default function () {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<audio autoplay ref={player} src={currentBlob} onDurationChange={onDurationChange} onTimeUpdate={onTimeUpdate} />
|
<audio autoplay ref={player} src={currentBlob} onDurationChange={onDurationChange} onTimeUpdate={onTimeUpdate} />
|
||||||
<Waveform width={canvasWidth} />
|
<Waveform width={canvasWidth} duration={duration} currentTime={currentTime} playing={!pause} seek={seekRecord} />
|
||||||
<Select
|
<Select
|
||||||
defaultValue={1.0}
|
defaultValue={1.0}
|
||||||
sx={{
|
sx={{
|
||||||
@ -89,12 +96,10 @@ export default function () {
|
|||||||
<MenuItem value={1.5}>1.5X</MenuItem>
|
<MenuItem value={1.5}>1.5X</MenuItem>
|
||||||
<MenuItem value={2.0}>2.0X</MenuItem>
|
<MenuItem value={2.0}>2.0X</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Container maxWidth={false} sx={{ backgroundColor: "#F2F4F5" }}>
|
<Container maxWidth={false} sx={{ backgroundColor: "#F2F4F5" }}>
|
||||||
<Typography sx={{ color: "#6B7486" }}> {currentTime} / {duration}</Typography>
|
<Typography sx={{ color: "#6B7486" }}> {durationFormat(currentTime)} / {durationFormat(duration)}</Typography>
|
||||||
|
|
||||||
</Container>
|
</Container>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -3,9 +3,14 @@ import { Typography, Paper } from "@mui/material";
|
|||||||
import styles from './RecordLyrics.module.css';
|
import styles from './RecordLyrics.module.css';
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
|
||||||
|
function isHighlight(currentTime, { start, end }) {
|
||||||
|
currentTime *= 1000;
|
||||||
|
return (currentTime > start) && (currentTime <= end);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
|
export default function ({ currentTime }) {
|
||||||
const currentLyric = useSelector(state => state.recorder.currentLyric);
|
const currentLyric = useSelector(state => state.recorder.currentLyric);
|
||||||
const currentIndex = useSelector(state => state.recorder.currentIndex);
|
const currentIndex = useSelector(state => state.recorder.currentIndex);
|
||||||
const recordList = useSelector(state => state.recorder.list);
|
const recordList = useSelector(state => state.recorder.list);
|
||||||
@ -16,8 +21,7 @@ export default function () {
|
|||||||
return <Paper className={styles.lyricsBrowser}>
|
return <Paper className={styles.lyricsBrowser}>
|
||||||
{recordList.at(currentIndex).type === 1 ? (typeof currentLyric === "object" ? currentLyric.map((lyric, index) => {
|
{recordList.at(currentIndex).type === 1 ? (typeof currentLyric === "object" ? currentLyric.map((lyric, index) => {
|
||||||
return <div className={styles.lyricItem}>
|
return <div className={styles.lyricItem}>
|
||||||
<Typography align="left">{lyric.text}</Typography>
|
<Typography align="left" color={isHighlight(currentTime, lyric) ? "red" : "black"}>{lyric.text}</Typography>
|
||||||
<Typography align="left">{lyric.translation}</Typography>
|
|
||||||
</div>
|
</div>
|
||||||
}) : <React.Fragment />) : <div> {typeof currentLyric === "string" ? currentLyric : ""}</div>}
|
}) : <React.Fragment />) : <div> {typeof currentLyric === "string" ? currentLyric : ""}</div>}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@ -7,6 +7,7 @@ export const recorderSlice = createSlice({
|
|||||||
currentIndex: 0,
|
currentIndex: 0,
|
||||||
currentLyric: [],
|
currentLyric: [],
|
||||||
currentBlob: {},
|
currentBlob: {},
|
||||||
|
currentTime: 0, // 当前音频播放时间
|
||||||
pause: true,
|
pause: true,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
@ -23,6 +24,9 @@ export const recorderSlice = createSlice({
|
|||||||
setCurrentBlob: (state, action) => {
|
setCurrentBlob: (state, action) => {
|
||||||
state.currentBlob = action.payload;
|
state.currentBlob = action.payload;
|
||||||
},
|
},
|
||||||
|
setCurrentTime: (state, action) => {
|
||||||
|
state.currentTime = action.payload;
|
||||||
|
},
|
||||||
togglePauseState: (state) => {
|
togglePauseState: (state) => {
|
||||||
state.pause = !state.pause;
|
state.pause = !state.pause;
|
||||||
},
|
},
|
||||||
@ -30,6 +34,6 @@ export const recorderSlice = createSlice({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Action creators are generated for each case reducer function
|
// Action creators are generated for each case reducer function
|
||||||
export const { setCurrentIndex, setList, setCurrentLyric, setCurrentBlob, togglePauseState } = recorderSlice.actions
|
export const { setCurrentIndex, setList, setCurrentLyric, setCurrentBlob, togglePauseState, setCurrentTime } = recorderSlice.actions
|
||||||
|
|
||||||
export default recorderSlice.reducer
|
export default recorderSlice.reducer
|
@ -1,11 +1,10 @@
|
|||||||
import { useEffect, useRef, useState, useCallback } from "react";
|
import { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import useSetTrackProgress from "./useSetTrackProgress.js"
|
|
||||||
import { useResizeDetector } from 'react-resize-detector';
|
|
||||||
|
|
||||||
const pointWidth = 2;
|
const pointWidth = 2;
|
||||||
const pointMargin = 3;
|
const pointMargin = 3;
|
||||||
|
const leftPadding = 14; // 留出空间画时间戳
|
||||||
|
const rightPadding = 14;
|
||||||
|
|
||||||
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,
|
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,
|
||||||
@ -33,7 +32,7 @@ const pointCoordinates = ({
|
|||||||
const pointHeight = Math.round((amplitude / 100) * maxAmplitude)
|
const pointHeight = Math.round((amplitude / 100) * maxAmplitude)
|
||||||
const verticalCenter = Math.round((canvasHeight - pointHeight) / 2)
|
const verticalCenter = Math.round((canvasHeight - pointHeight) / 2)
|
||||||
return [
|
return [
|
||||||
index * (pointWidth + pointMargin) + 14, // x starting point
|
index * (pointWidth + pointMargin) + leftPadding, // x starting point
|
||||||
(canvasHeight - pointHeight) - verticalCenter + 10, // y starting point
|
(canvasHeight - pointHeight) - verticalCenter + 10, // y starting point
|
||||||
pointWidth, // width
|
pointWidth, // width
|
||||||
pointHeight, // height
|
pointHeight, // height
|
||||||
@ -46,15 +45,16 @@ function drawText(context, width, duration) {
|
|||||||
context.fillStyle = "#99A3AF";
|
context.fillStyle = "#99A3AF";
|
||||||
|
|
||||||
let timepoint = 0;
|
let timepoint = 0;
|
||||||
let segements = parseInt((width - 30) / 80);
|
let segements = parseInt((width - leftPadding - rightPadding) / 80);
|
||||||
let interval = parseInt(duration / segements);
|
let interval = Math.ceil(duration / segements);
|
||||||
for (let i = 0; i < segements; i++) {
|
console.log("segements", segements, width / 80, width);
|
||||||
|
for (let i = 0; i <= segements; i++) {
|
||||||
context.fillStyle = "#99A3AF";
|
context.fillStyle = "#99A3AF";
|
||||||
context.fillText(timeTag(timepoint), i * 80, 10 + 4);
|
context.fillText(timeTag(timepoint), i * 80, 10 + 4);
|
||||||
timepoint += interval;
|
timepoint += interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 14; i < width; i += 40) {
|
for (let i = leftPadding; i < width - rightPadding; i += 40) {
|
||||||
context.fillStyle = "#9DA7B2";
|
context.fillStyle = "#9DA7B2";
|
||||||
context.fillRect(i, 0, 1, (i - 14) % 80 == 0 ? 12 : 6);
|
context.fillRect(i, 0, 1, (i - 14) % 80 == 0 ? 12 : 6);
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ const paintCanvas = ({
|
|||||||
canvas, waveformData, duration, canvasWidth, canvasHeight, pointWidth, pointMargin,
|
canvas, waveformData, duration, canvasWidth, canvasHeight, pointWidth, pointMargin,
|
||||||
playingPoint, hoverXCoord,
|
playingPoint, hoverXCoord,
|
||||||
}) => {
|
}) => {
|
||||||
// console.log("paintCanvas", canvasHeight, playingPoint, hoverXCoord);
|
console.log("paintCanvas", duration, canvasHeight, playingPoint, hoverXCoord);
|
||||||
const ref = canvas.current;
|
const ref = canvas.current;
|
||||||
const ctx = ref.getContext('2d')
|
const ctx = ref.getContext('2d')
|
||||||
ctx.clearRect(0, 0, ref.width, ref.height);
|
ctx.clearRect(0, 0, ref.width, ref.height);
|
||||||
@ -96,20 +96,18 @@ const paintCanvas = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ({ width }) {
|
export default function ({ width, duration, currentTime, playing, seek }) {
|
||||||
const canvas = useRef(null);
|
const canvas = useRef(null);
|
||||||
const [trackPlaying, setTrackPlaying] = useState(true);
|
|
||||||
const [trackProgress, setTrackProgress] = useState(0); // [0,100]
|
const [trackProgress, setTrackProgress] = useState(0); // [0,100]
|
||||||
const [hoverXCoord, setHoverXCoord] = useState(0);
|
const [hoverXCoord, setHoverXCoord] = useState(0);
|
||||||
const [startTime, setStartTime] = useState(Date.now());
|
|
||||||
|
|
||||||
const playingPoint = (trackProgress * (canvas.current ? width : 0) / 100) / (pointWidth + pointMargin);
|
const playingPoint = (trackProgress * (canvas.current ? (width - leftPadding) : 0) / 100) / (pointWidth + pointMargin);
|
||||||
|
|
||||||
const paintWaveform = useCallback(() => {
|
const paintWaveform = useCallback(() => {
|
||||||
paintCanvas({
|
paintCanvas({
|
||||||
canvas,
|
canvas,
|
||||||
waveformData: chunkedData,
|
waveformData: chunkedData,
|
||||||
duration: trackDuration,
|
duration: duration,
|
||||||
canvasWidth: width,
|
canvasWidth: width,
|
||||||
canvasHeight: canvas.current.height,
|
canvasHeight: canvas.current.height,
|
||||||
pointWidth,
|
pointWidth,
|
||||||
@ -117,24 +115,21 @@ export default function ({ width }) {
|
|||||||
playingPoint,
|
playingPoint,
|
||||||
hoverXCoord,
|
hoverXCoord,
|
||||||
})
|
})
|
||||||
}, [playingPoint, hoverXCoord])
|
}, [playingPoint, hoverXCoord, duration, width])
|
||||||
|
|
||||||
useSetTrackProgress({
|
|
||||||
trackProgress, setTrackProgress, trackDuration, startTime,
|
|
||||||
trackPlaying
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvas.current) return;
|
if (!canvas.current) return;
|
||||||
paintWaveform()
|
paintWaveform()
|
||||||
|
}, [canvas, width, duration, hoverXCoord])
|
||||||
}, [canvas, width])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
paintWaveform()
|
paintWaveform()
|
||||||
}, [playingPoint])
|
}, [playingPoint])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTrackProgress(currentTime * 100 / duration);
|
||||||
|
}, [currentTime]);
|
||||||
|
|
||||||
const setDefaultX = useCallback(() => {
|
const setDefaultX = useCallback(() => {
|
||||||
setHoverXCoord(0);
|
setHoverXCoord(0);
|
||||||
@ -146,8 +141,8 @@ export default function ({ width }) {
|
|||||||
|
|
||||||
const seekTrack = (e) => {
|
const seekTrack = (e) => {
|
||||||
const xCoord = e.clientX - canvas.current.getBoundingClientRect().left
|
const xCoord = e.clientX - canvas.current.getBoundingClientRect().left
|
||||||
const seekMs = trackDuration * (xCoord / width);
|
const seekSecond = duration * ((xCoord - leftPadding) / (width - leftPadding));
|
||||||
setStartTime(Date.now() / 1000 - seekMs)
|
seek(seekSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <canvas height={70} width={width} ref={canvas}
|
return <canvas height={70} width={width} ref={canvas}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
const clamp = (val, min, max) => Math.min(Math.max(val, min), max)
|
|
||||||
|
|
||||||
export default ({
|
|
||||||
trackProgress, trackDuration, startTime,
|
|
||||||
setTrackProgress, trackPlaying,
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
|
||||||
let animation
|
|
||||||
if (trackPlaying) {
|
|
||||||
animation = window.requestAnimationFrame(() => {
|
|
||||||
console.log("ani");
|
|
||||||
const trackProgressPerc = ((Date.now() / 1000 - startTime)) * 100 / trackDuration
|
|
||||||
setTrackProgress(
|
|
||||||
clamp(
|
|
||||||
trackProgressPerc,
|
|
||||||
0, 100,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
window.cancelAnimationFrame(animation)
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
trackPlaying,
|
|
||||||
trackDuration,
|
|
||||||
startTime,
|
|
||||||
trackProgress,
|
|
||||||
])
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user