reactjs - 如何在源更改时重新加载音频组件
问题描述
我有一个位于页面底部的音频播放器。在页面上,有一个播客剧集列表,每个剧集下面都有一个“播放剧集”按钮。每个播客都是 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}>•</h3>
<h3 className={styles.meta}>{date}</h3>
</div>
</div>
</div>
)
}
单击“播放剧集”按钮时,它将其剧集编号传递给收听页面,然后将源输入更改为作为道具传递的音频播放器(至少我认为它正在这样做。)
这很好用,当我在剧集下按下按钮时,源会发生变化,但是,让新源播放的唯一方法是手动暂停然后播放音频播放器。我尝试通过useEffect
在 URL 属性更改时使用暂停、加载和播放播放器来解决此问题,但它不起作用。我确定我忽略了一些东西,也许有一种更简单的方法可以做到这一点,但我还没有找到它。
非常感谢任何帮助,谢谢!
解决方案
推荐阅读
- ruby-on-rails - Rolify 与具有特定角色的用户建立关联
- postgresql - 如果两个具有不同隔离级别的事务访问同一个表会发生什么
- typescript - 如何在打字稿中具有多种值类型的类上设置索引签名?
- java - 无法在 Android Studio (Java) 上查找和读取文本文件
- excel - 将轮班时间转换为轮班长度的 Excel 宏
- python - Tkinter 泡菜保存和加载
- python - Pandas 删除重复行,包括索引
- javascript - React Hooks - 将 map() 函数中的类名仅设置为一项
- javascript - 如何拉出嵌套状态对象而不返回“未定义”
- mongodb - FindOneAndUpdate 在我的 Mongoose 模型中的嵌套数组