防止在重新渲染组件时更新 Audio()

时间:2021-06-22 12:10:19

标签: javascript reactjs typescript

我正在学习 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;
}

0 个答案:

没有答案