1.实现频谱进度条。
This commit is contained in:
parent
3deb8c058e
commit
bf11df50e9
@ -18,6 +18,7 @@
|
|||||||
"react-cookie": "^4.1.1",
|
"react-cookie": "^4.1.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "^8.0.7",
|
"react-redux": "^8.0.7",
|
||||||
|
"react-resize-detector": "^9.1.0",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.11.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"sha1": "^1.1.1",
|
"sha1": "^1.1.1",
|
||||||
|
@ -40,7 +40,7 @@ export default function () {
|
|||||||
dispatch(setList(list.result));
|
dispatch(setList(list.result));
|
||||||
});
|
});
|
||||||
}, [accessToken, passportId]);
|
}, [accessToken, passportId]);
|
||||||
return <div>
|
return <div className={styles.mainBody}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppBar />
|
<AppBar />
|
||||||
@ -48,10 +48,11 @@ export default function () {
|
|||||||
<Box
|
<Box
|
||||||
component="main"
|
component="main"
|
||||||
sx={{
|
sx={{
|
||||||
width: `calc(100% - 240px)`, ml: `240px`
|
width: `calc(100% - 296px)`,
|
||||||
|
ml: `272px`,
|
||||||
|
mr: `24px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>2022-12-13 14:39_同传翻译</Typography>
|
|
||||||
<PlayerBar />
|
<PlayerBar />
|
||||||
<RecordLyrics />
|
<RecordLyrics />
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
.title {
|
.title {
|
||||||
background-color: burlywood;
|
background-color: burlywood;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mainBody {
|
||||||
|
background-color: #FAFAFA;
|
||||||
|
}
|
112
src/PlayerBar.js
112
src/PlayerBar.js
@ -1,25 +1,101 @@
|
|||||||
import { Grid, MenuItem, Select, FormControl, InputLabel, Button } from "@mui/material"
|
import { MenuItem, Select, IconButton, Typography, Stack, Container } from "@mui/material"
|
||||||
import styles from './PlayerBar.module.css';
|
import styles from './PlayerBar.module.css';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { useEffect, useRef, useState, useCallback } from "react";
|
||||||
|
import { useResizeDetector } from 'react-resize-detector';
|
||||||
|
import pauseIcon from "./assets/play.png";
|
||||||
|
import playIcon from "./assets/pause.png";
|
||||||
|
import downloadIcon from "./assets/download.png";
|
||||||
|
import { togglePauseState } from "./business/recorderSlice.js"
|
||||||
|
import Waveform from "./components/Waveform";
|
||||||
|
|
||||||
|
const durationFormat = (time) => {
|
||||||
|
if (isNaN(time)) return "00:00:00";
|
||||||
|
time = parseInt(time);
|
||||||
|
let second = parseInt(time % 60);
|
||||||
|
let minute = parseInt((time / 60) % 60);
|
||||||
|
let hour = parseInt(time / 3600);
|
||||||
|
return hour.toString().padStart(2, '0') + ":" + minute.toString().padStart(2, '0') + ":" + second.toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return <Grid container>
|
const dispatch = useDispatch();
|
||||||
<Grid item lg={1}> <Button>播放</Button> </Grid>
|
const [duration, setDuration] = useState("00:00:00");
|
||||||
<Grid item lg={10}> <div>进度条</div></Grid>
|
const [currentTime, setCurrentTime] = useState("00:00:00");
|
||||||
<Grid item lg={1}>
|
const [canvasWidth, setCanvasWidth] = useState(0);
|
||||||
<FormControl fullWidth>
|
const currentIndex = useSelector(state => state.recorder.currentIndex);
|
||||||
<InputLabel id="demo-simple-select-label">速度</InputLabel>
|
const recordList = useSelector(state => state.recorder.list);
|
||||||
|
const currentBlob = useSelector(state => state.recorder.currentBlob);
|
||||||
|
const pause = useSelector(state => state.recorder.pause);
|
||||||
|
const player = useRef(null);
|
||||||
|
useEffect(() => {
|
||||||
|
player.current.url = currentBlob
|
||||||
|
console.log(player.current.url);
|
||||||
|
}, [currentBlob]);
|
||||||
|
|
||||||
|
const toggleState = () => {
|
||||||
|
if (pause) {
|
||||||
|
player.current.play();
|
||||||
|
} else {
|
||||||
|
player.current.pause();
|
||||||
|
}
|
||||||
|
dispatch(togglePauseState());
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDurationChange = (event) => {
|
||||||
|
setDuration(durationFormat(player.current.duration));
|
||||||
|
}
|
||||||
|
const onTimeUpdate = (event) => {
|
||||||
|
setCurrentTime(durationFormat(player.current.currentTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
const onResize = useCallback((width, height) => {
|
||||||
|
setCanvasWidth(width - 90 - 60);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { ref: playerBar } = useResizeDetector({
|
||||||
|
onResize: onResize
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Stack container >
|
||||||
|
<Container disableGutters maxWidth={false} sx={{
|
||||||
|
height: 60,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}>
|
||||||
|
<Typography variant="h6" sx={{ flexGrow: 1 }} >{recordList.length > 0 ? recordList.at(currentIndex).editName : ""}</Typography>
|
||||||
|
<IconButton onClick={toggleState}>
|
||||||
|
<img src={downloadIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<div className={styles.playerBar} ref={playerBar}>
|
||||||
|
<IconButton onClick={toggleState} sx={{ flexGrow: 1, width: 70, height: 70 }}>
|
||||||
|
<img src={pause ? pauseIcon : playIcon} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<audio autoplay ref={player} src={currentBlob} onDurationChange={onDurationChange} onTimeUpdate={onTimeUpdate} />
|
||||||
|
<Waveform width={canvasWidth} />
|
||||||
<Select
|
<Select
|
||||||
labelId="demo-simple-select-label"
|
defaultValue={1.0}
|
||||||
id="demo-simple-select"
|
sx={{
|
||||||
label="速度"
|
flexGrow: 1,
|
||||||
|
width: 90
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value={10}>0.5X</MenuItem>
|
<MenuItem value={0.5}>0.5X</MenuItem>
|
||||||
<MenuItem value={20}>1.0X</MenuItem>
|
<MenuItem value={1.0}>1.0X</MenuItem>
|
||||||
<MenuItem value={30}>1.25X</MenuItem>
|
<MenuItem value={1.25}>1.25X</MenuItem>
|
||||||
<MenuItem value={30}>1.5X</MenuItem>
|
<MenuItem value={1.5}>1.5X</MenuItem>
|
||||||
<MenuItem value={30}>2.0X</MenuItem>
|
<MenuItem value={2.0}>2.0X</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</div>
|
||||||
|
|
||||||
|
<Container maxWidth={false} sx={{ backgroundColor: "#F2F4F5" }}>
|
||||||
|
<Typography sx={{ color: "#6B7486" }}> {currentTime} / {duration}</Typography>
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
</Stack>
|
||||||
}
|
}
|
@ -1,3 +1,6 @@
|
|||||||
.playerBar {
|
.playerBar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background-color: #E6EAEC;
|
||||||
|
height: 70px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import ListItemButton from '@mui/material/ListItemButton';
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import { setCurrentIndex, setCurrentLyric } from "./business/recorderSlice.js"
|
import { setCurrentIndex, setCurrentLyric, setCurrentBlob } from "./business/recorderSlice.js"
|
||||||
import yzs from "./business/request.js";
|
import yzs from "./business/request.js";
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
@ -19,13 +19,17 @@ export default function () {
|
|||||||
const recordList = useSelector(state => state.recorder.list);
|
const recordList = useSelector(state => state.recorder.list);
|
||||||
const onSelected = (event, index) => {
|
const onSelected = (event, index) => {
|
||||||
console.log("onSelected", index, recordList.at(index).transResultUrl)
|
console.log("onSelected", index, recordList.at(index).transResultUrl)
|
||||||
|
dispatch(setCurrentIndex(index));
|
||||||
yzs.download(accessToken, recordList.at(index).transResultUrl).then(
|
yzs.download(accessToken, recordList.at(index).transResultUrl).then(
|
||||||
blob => blob.text()
|
blob => blob.text()
|
||||||
).then(text => {
|
).then(text => {
|
||||||
console.log(text);
|
console.log(text);
|
||||||
dispatch(setCurrentLyric(JSON.parse(text)));
|
let payload = recordList.at(index).type === 1 ? JSON.parse(text) : text;
|
||||||
|
dispatch(setCurrentLyric(payload));
|
||||||
|
});
|
||||||
|
yzs.download(accessToken, recordList.at(index).audioUrl).then(blob => {
|
||||||
|
dispatch(setCurrentBlob(URL.createObjectURL(blob)));
|
||||||
});
|
});
|
||||||
dispatch(setCurrentIndex(index));
|
|
||||||
}
|
}
|
||||||
return <Drawer
|
return <Drawer
|
||||||
variant="permanent"
|
variant="permanent"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Typography } from "@mui/material";
|
import React from "react";
|
||||||
|
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'
|
||||||
|
|
||||||
@ -6,13 +7,18 @@ import { useSelector, useDispatch } from 'react-redux'
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const currentLyric = useSelector(state => state.recorder.currentLyric);
|
const currentLyric = useSelector(state => state.recorder.currentLyric);
|
||||||
|
const currentIndex = useSelector(state => state.recorder.currentIndex);
|
||||||
|
const recordList = useSelector(state => state.recorder.list);
|
||||||
|
|
||||||
return <div className={styles.lyricsBrowser}>
|
|
||||||
{currentLyric.map((lyric, index) => {
|
if (recordList.length === 0) return <React.Fragment />;
|
||||||
|
|
||||||
|
return <Paper className={styles.lyricsBrowser}>
|
||||||
|
{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">{lyric.text}</Typography>
|
||||||
<Typography align="left">{lyric.translation}</Typography>
|
<Typography align="left">{lyric.translation}</Typography>
|
||||||
</div>
|
</div>
|
||||||
})}
|
}) : <React.Fragment />) : <div> {typeof currentLyric === "string" ? currentLyric : ""}</div>}
|
||||||
</div>
|
</Paper>
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
.lyricsBrowser {
|
.lyricsBrowser {
|
||||||
|
margin-top: 16px;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lyricItem {
|
.lyricItem {
|
||||||
|
BIN
src/assets/download.png
Normal file
BIN
src/assets/download.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 340 B |
BIN
src/assets/pause.png
Normal file
BIN
src/assets/pause.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 B |
BIN
src/assets/play.png
Normal file
BIN
src/assets/play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 668 B |
@ -6,21 +6,30 @@ export const recorderSlice = createSlice({
|
|||||||
list: [],
|
list: [],
|
||||||
currentIndex: 0,
|
currentIndex: 0,
|
||||||
currentLyric: [],
|
currentLyric: [],
|
||||||
|
currentBlob: {},
|
||||||
|
pause: true,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setList: (state, action) => {
|
setList: (state, action) => {
|
||||||
state.list = action.payload;
|
state.list = action.payload;
|
||||||
},
|
},
|
||||||
setCurrentIndex: (state, action) => {
|
setCurrentIndex: (state, action) => {
|
||||||
|
state.pause = true;
|
||||||
state.currentIndex = action.payload;
|
state.currentIndex = action.payload;
|
||||||
},
|
},
|
||||||
setCurrentLyric: (state, action) => {
|
setCurrentLyric: (state, action) => {
|
||||||
state.currentLyric = action.payload;
|
state.currentLyric = action.payload;
|
||||||
}
|
},
|
||||||
|
setCurrentBlob: (state, action) => {
|
||||||
|
state.currentBlob = action.payload;
|
||||||
|
},
|
||||||
|
togglePauseState: (state) => {
|
||||||
|
state.pause = !state.pause;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Action creators are generated for each case reducer function
|
// Action creators are generated for each case reducer function
|
||||||
export const { setCurrentIndex, setList, setCurrentLyric } = recorderSlice.actions
|
export const { setCurrentIndex, setList, setCurrentLyric, setCurrentBlob, togglePauseState } = recorderSlice.actions
|
||||||
|
|
||||||
export default recorderSlice.reducer
|
export default recorderSlice.reducer
|
159
src/components/Waveform.js
Normal file
159
src/components/Waveform.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
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}
|
||||||
|
/>
|
||||||
|
}
|
32
src/components/useSetTrackProgress.js
Normal file
32
src/components/useSetTrackProgress.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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