При каждом ре-рендере обновляется Audio()

В продессе изучения ReactJS и TypeScript. Пишу плеер, что должен проигрывать музыку из File (приходит через проп от компонента App, а туда из input[type="file"] проходя через FileReader). В процессе разработки сталкивался с проблемами типа удвоение-утроение... и на каждый рендер размножение дорожек или play() не может быть вызван из-за pause()... Нашёл неплохой код на английском stackoverflow с созданием кастомного хука. Есть только проблема - на каждое нажатие кнопки play/pause создаётся новый инстанс Audio() или переуказание src я так понял. Play - играет, а Pause останавливает и при следующем нажатии Play трек играет заново.

Уже и пробовал выносить сам функционал плеера в App.tsx и через проп из плеера брать состояние кнопки (не по приниципам реакта, похоже это и хотелось бы этого избежать). Пробовал не хранить Audio в стейте. Пробовал audio.src в useEffect(() => {}, []); вставялть, но на тот момент оно null и браузер ругается DOMExeption'ом, что источник не валидный.

Хотелось бы ответ развёрнутый, что я не так делаю и как это лучше реализовать. (Использую react 17.0.2, typescript 4.3.4)

Код компонета 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 шт):