首页 > 解决方案 > 如何在源更改时重新加载音频组件

问题描述

我有一个位于页面底部的音频播放器。在页面上,有一个播客剧集列表,每个剧集下面都有一个“播放剧集”按钮。每个播客都是 EpisodeInfo 组件的一个实例,我将播客信息作为道具传递给它。

音频播放器:

import React, {useState, useRef , useEffect} from 'react';
import styles from '../styles/audioPlayer.module.scss'
import {BsArrowCounterclockwise, BsArrowClockwise,} from 'react-icons/bs'
import {FaPlay, FaPause} from 'react-icons/fa'

const AudioPlayer = (props) => {
    // State
    const [isPlaying, setIsPlaying] = useState(false);
    const [duration, setDuration] = useState(0);
    const [currentTime, setCurrentTime] = useState(0);
    const [episodeUrl, setEpisodeUrl] = useState(props.url);

    // References
    const audioPlayer = useRef(null);   // reference our audio component
    const progressBar = useRef(null);   // reference our progress bar
    const animationRef = useRef(null);  // reference the animation
    const isInitialMount = useRef(true);  //reference the initial mount

    useEffect(() => {
        const seconds = Math.floor(audioPlayer.current.duration);
        setDuration(seconds);
        progressBar.current.max = seconds;
    }, [audioPlayer?.current?.loadedmetadata, audioPlayer?.current?.readyState]);

    const calculateTime = (secs) => {
        const minutes = Math.floor(secs / 60);
        const returnedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
        const seconds = Math.floor(secs % 60);
        const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
        return `${returnedMinutes}:${returnedSeconds}`;
    }

    const togglePlayPause = () => {
        const prevValue = isPlaying;
        setIsPlaying(!prevValue);
        if (!prevValue && episodeUrl) {
          audioPlayer.current.play();
          animationRef.current = requestAnimationFrame(whilePlaying)
        } else {
          audioPlayer.current.pause();
          cancelAnimationFrame(animationRef.current);
        }
    }

    const whilePlaying = () => {
        progressBar.current.value = audioPlayer.current.currentTime;
        changePlayerCurrentTime();
        animationRef.current = requestAnimationFrame(whilePlaying);
    }

    const changeRange = () => {
        audioPlayer.current.currentTime = progressBar.current.value;
        changePlayerCurrentTime();
    }

    const changePlayerCurrentTime = () => {
        progressBar.current.style.setProperty('--seek-before-width', `${progressBar.current.value / duration * 100}%`)
        setCurrentTime(progressBar.current.value);
    }

    const backTen = () => {
        progressBar.current.value = Number(progressBar.current.value - 10);
        changeRange();
    }

    const forwardTen = () => {
        progressBar.current.value = Number(parseFloat(progressBar.current.value) + 10);
        changeRange();
      }

    useEffect(() => {
        if (isInitialMount.current) {
            isInitialMount.current = false;
         } else {
            setEpisodeUrl(props.url);
            audioPlayer.current.play();
        }
    }, [props.url])

    return (
        <div className={styles.audioPlayer}>
            <audio ref={audioPlayer} src={episodeUrl} preload="metadata" ></audio>
            <button className={styles.button} onClick={backTen}><BsArrowCounterclockwise /></button>
            <button className={styles.button} onClick={togglePlayPause}> {isPlaying ? <FaPause /> : <FaPlay />} </button>
            <button className={styles.button} onClick={forwardTen}><BsArrowClockwise /></button>

            <div className={styles.time}>{calculateTime(currentTime)}</div>
            <input type="range" className={styles.progressBar} defaultValue="0" ref={progressBar} onChange={changeRange} />
            <div className={styles.time}>{(duration && !isNaN(duration)) && calculateTime(duration)}</div>
        </div>
    );
};

export {AudioPlayer};

音频播放器所在的页面:

import { useState } from 'react'
import Navbar from '../components/navbar'
import styles from '../styles/listen.module.scss'
import EpisodeInfo from '../components/episodeInfo'
import { AudioPlayer } from '../components/AudioPlayer'
import { db } from '../config/firebaseConfig'
import { useCollectionData } from 'react-firebase-hooks/firestore'


export default function Listen () {
    
    // Firestore
    const episodesRef = db.collection("episodes");
    const query = episodesRef.orderBy('episodeNumber');
    const [episodesReturned] = useCollectionData(query, {idField: 'id'});

    //State
    const [episodeUrl, setEpisodeUrl] = useState('/episode1.mp3');

    function handleCallback (childData) {
        setEpisodeUrl("/episode" + childData + ".mp3");
    }

    return (
        <div className={styles.body}>
            <Navbar />
            <div className={styles.mainWrapper}>
                {episodesReturned && episodesReturned.slice(0).reverse().map(episode => <EpisodeInfo key={episode.id} episode={episode} parentCallback={handleCallback} />)}
            </div>
            <div className={styles.sticky}>
                <AudioPlayer url={episodeUrl} />
            </div>
        </div>
    )
}

以及在收听页面上为每一集呈现的组件:

import { useState } from 'react'
import styles from '../styles/episodeInfo.module.scss'

export default function EpisodeInfo (props) {

    const { episodeNumber, episodeTitle, blurb, length, date} = props.episode

    function passNameToParent () {
        props.parentCallback(episodeNumber)
    }

    return (
        <div className={styles.wrapper}>
            <img src="album-cover.jpg" className={styles.albumMain}/>
            <div className={styles.info}>
                <p className={styles.number}>EP. {episodeNumber}</p>
                <h1 className={styles.title}>{episodeTitle}</h1>
                <p className={styles.blurb}>{blurb}</p>
                <div>
                    <a onClick={passNameToParent} className={styles.button}>Play Episode</a>
                    <h3 className={styles.meta}>{length} mins</h3>
                    <h3 className={styles.meta}>•&lt;/h3>
                    <h3 className={styles.meta}>{date}</h3>
                </div>
            </div>
        </div>
    )
}

单击“播放剧集”按钮时,它将其剧集编号传递给收听页面,然后将源输入更改为作为道具传递的音频播放器(至少我认为它正在这样做。)

这很好用,当我在剧集下按下按钮时,源会发生变化,但是,让新源播放的唯一方法是手动暂停然后播放音频播放器。我尝试通过useEffect在 URL 属性更改时使用暂停、加载和播放播放器来解决此问题,但它不起作用。我确定我忽略了一些东西,也许有一种更简单的方法可以做到这一点,但我还没有找到它。

非常感谢任何帮助,谢谢!

标签: reactjsnext.jshtml5-audioreact-propsuse-effect

解决方案


推荐阅读