我正在学习 ReactJS 和 TypeScript。我正在开发一个音乐播放器,它必须从文件 (FileReader
) 播放音频。在开发过程中,解决了多个播放线程,play()
无法调用pause()
等问题。
我从这里的一些答案中找到了很好的代码,但我遇到了问题:当我按下播放按钮时,音乐开始播放,但在暂停后点击音乐停止,再次按下播放后音乐从头开始。我猜是因为每次按下按钮后 Audio()
或 audio.src
更新,因为此事件会发出重新渲染。
我试图将播放器功能移动到 App.tsx 组件并通过道具播放/暂停按钮状态......一个坏主意......
我试过存储音频,不在状态。
我试过将 audio.src
推入 useEffect(() => {}, []);
,但它会出现
DOMExeption:来源不正确(因为我猜它是 null)。
App.tsx:
import React, { useState, useEffect, useRef } from 'react';
import './App.css';
import CoverBG from "./App/CoverBG";
import PlayList from "./App/PlayList";
import MusicPlayer from "./App/MusicPlayer";
import { IAudio } from './App/interfaces';
import jsmediatags from 'jsmediatags';
const App: React.FC = () => {
const [track, setTrack] = useState<IAudio>({src: null, id: 0,title: null,album: null,author: null});
/* if (player) {
player.addEventListener("canplaythrough", () => set_duration(player.duration), false);
player.ontimeupdate = () => updateTime();
}
const updateTime = () => {
set_currTime(player.currentTime);
}*/
const scanTracks = (ev: React.ChangeEvent<HTMLInputElement>) => {
let target = ev.currentTarget;
let file = target!.files![0];
const reader = new FileReader();
if (target.files && file) {
reader.onload = function (ev2) {
jsmediatags.read(file, {
onSuccess: (result) => {
setTrack({
src: ev2.target!.result as string,
id: 0,
title: result.tags.TPE1.data as string,
album: result.tags.album as string,
author: result.tags.artist as string
});
},
onError: (err) => {
console.log("jsmediatags error: ", err.type, err.info);
}
});
}
reader.readAsDataURL(file);
}
}
return (
<>
<input type="file" multiple id="audio_src" onChange={scanTracks} />
<CoverBG image="https://www.nieuweplaat.nl/wp-content/uploads/2016/08/skillet.jpg"></CoverBG>
<PlayList></PlayList>
<MusicPlayer
audio={track}
></MusicPlayer>
</>
);
}
export default App;
MusicPlayer.tsx:
import React, { useState, useEffect, useRef} from 'react';
import './MusicPlayer.css';
import { IAudioProp } from './interfaces';
const useAudio = (file: string) => {
const [playing, set_playing] = useState<boolean>(false);
const [audio] = useState<HTMLAudioElement>(new Audio());
audio.setAttribute('src', file);
console.log("new player render");
const toggle = (): void => set_playing(!playing);
useEffect(() => {
playing ? audio.play() : audio.pause();
}, [playing]);
useEffect(() => {
audio.addEventListener('ended', () => set_playing(false));
return () => {
audio.removeEventListener('ended', () => set_playing(false));
};
}, []);
return [playing, toggle] as const;
}
const MusicPlayer: React.FC<IAudioProp> = (props: IAudioProp) => {
const [playing, toggle] = useAudio(props.audio.src as string);
const evPlayPauseBtn__clicked = (ev: React.MouseEvent<HTMLButtonElement>) => {
toggle();
}
const slider = useRef<HTMLInputElement>(null);
return (
<div className="MusicPlayer">
{ console.log("player render") }
<div className="duration-slider">
<input type="range" min={0} max={100} ref={slider} />
</div>
<div className="data-controls">
<div className="audio-data">
<div className="title">{props!.audio.title ? props!.audio.title : "Unknown"}</div>
<div className="author-album">
<span className="author">{props!.audio.author ? props!.audio.author : "Unknown"}</span> - <span className="album">{props!.audio.album ? props!.audio.album : "Unknown"}</span>
</div>
</div>
<div className="controls">
<button className="btn-prev">
<i className="material-icons">skip_previous</i>
</button>
<button className="btn-play-pause" onClick={evPlayPauseBtn__clicked}>
<i className="material-icons">{playing ? "pause" : "play_arrow"}</i>
</button>
<button className="btn-next">
<i className="material-icons">skip_next</i>
</button>
</div>
</div>
</div>
);
}
export default MusicPlayer;
接口:
export interface IAudio {
src: string | null;
id: number;
title: string | null;
album: string | null;
author: string | null;
}
export interface IAudioProp {
audio: IAudio;
}