【问题标题】:How to avoid NaN being rendered in audio player duration?如何避免在音频播放器持续时间内呈现 NaN?
【发布时间】:2022-06-25 05:31:29
【问题描述】:

我制作了一个很酷的小型音频播放器,但在获取数据时遇到了问题。页面在return语句src中的音频文件之前渲染,这里:

<audio ref={audio} src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg" alt="oops, something went wrong..."></audio>

NaN 显示在此行表示的持续时间中:

{/* duration */}
        <div className={styles.duration}>{(duration && !isNaN(duration)) && calculateTime(duration)}</div>  

上面这行代码并没有阻止NaN,所以我尝试获取useEffect,如下所示,但这使这个问题变得更糟。

const [data, setData] = useState([])
--------------------

useEffect(() => {
        fetch("https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg").then(
            res => setData(res.loadedmetadata)
        )
    })
--------------------

 <audio ref={audio} src={data} alt="oops, something went wrong..."></audio>

如果有人可以看一下并指出正确的方向,我将不胜感激。下面我将提供我的组件的所有代码。

import React, { useState, useRef, useEffect } from 'react';
import styles from '../styles/AudioPlayer.module.css';
import {BsArrowClockwise} from 'react-icons/bs';
import {BsArrowCounterclockwise} from 'react-icons/bs';
import {BsPlayCircleFill} from 'react-icons/bs';
import {BsPauseCircleFill} from 'react-icons/bs';

const AudioPlayer = () => {
  //state
  const [isPlaying, setIsPlaying] = useState(false);
  const [duration, setDuration] = useState(0);
  const [currentTime, setCurrentTime] = useState(0);
  const [data, setData] = useState([])

  //refs
  const audio = useRef();
  const progressBar = useRef();
  const progressBarAnimation = useRef(); 

  //effects
  useEffect(() => {
    const seconds = Math.floor(audio.current.duration);
    setDuration(seconds);
    progressBar.current.max = seconds;
  }, [ audio?.current?.loadedmetadata, audio?.current?.readyState ]);
 
    
    useEffect(() => {
        fetch("https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg").then(
            res => setData(res.loadedmetadata)
        )
    })
    

  //functions & Handlers
  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 isPlayingHandler = () => {
    const prevValue = isPlaying;
    setIsPlaying(!prevValue);
    if (!prevValue) {
      audio.current.play();
      progressBarAnimation.current = requestAnimationFrame(whilePlaying);
    } else {
      audio.current.pause();
      cancelAnimationFrame(progressBarAnimation.current);
    };
  };

  const whilePlaying = () => {
    progressBar.current.value = audio.current.currentTime;
    progressBarValueTicker();
    progressBarAnimation.current = requestAnimationFrame(whilePlaying);
  };

  const progressHandler = () => {
    audio.current.currentTime = progressBar.current.value;
    progressBarValueTicker();
  };

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

  const backwardFifteen = () => {
    console.log(progressBar.current.value)
    progressBar.current.value = Number(progressBar.current.value) - 15;
    console.log(progressBar.current.value)
    progressHandler();
    
  };

  const forwardFifteen = () => {
    console.log(progressBar.current.value)
    progressBar.current.value = Number(progressBar.current.value) + 15;
    console.log(progressBar.current.value)
    progressHandler();
  };

  return(
    <>
      <div>
         {/* eventually, a loop component tag will replace the below line to loop all audio file title and descriptions*/}
      </div>
      <div className={styles.audioWrapper}>
        {/* eventually, a loop component tag will replace the below line to loop all audio files*/}
        <audio ref={audio} src={data} alt="oops, something went wrong..."></audio>
        {/* <audio ref={audio} src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg" alt="oops, something went wrong..."></audio> */}
        <button className={styles.sideButtons} onClick={backwardFifteen}><BsArrowCounterclockwise />15</button>
        <button className={styles.playPauseButton} onClick={isPlayingHandler}>
          { isPlaying ? <BsPauseCircleFill /> : <BsPlayCircleFill /> }</button>
        <button className={styles.sideButtons} onClick={forwardFifteen}>15<BsArrowClockwise /></button>

        {/* current time */}
        <div className={styles.currentTime}>{calculateTime(currentTime)}</div>

        {/* progress bar */}
        <div>
          <input type="range" ref={progressBar} className={styles.progressBar} onChange={progressHandler} defaultValue='0'/>
        </div>

        {/* duration */}
        <div className={styles.duration}>{(duration && !isNaN(duration)) && calculateTime(duration)}</div>  
      </div>
    </>
  );
};

export default AudioPlayer;

【问题讨论】:

    标签: reactjs next.js nan


    【解决方案1】:

    NaN 被渲染的原因是因为NaN 是一个falsy 值,并且会立即在下面的表达式中返回该值。

    (duration && !isNaN(duration)) && calculateTime(duration)
    // `NaN && !isNaN(NaN)` returns `NaN` because it is falsy
    

    只需删除第一个条件即可避免 NaN 被渲染。

    !isNaN(duration) && calculateTime(duration)
    

    但是,实际的duration 值仍将是NaN,并且不会呈现任何内容。这是因为当您检查 useEffect 中的 audio.current.duration 值时,持续时间实际上还没有更新。

    要解决此问题,您可以在audio 元素中监听onDurationChange 事件,并在它被触发时更新duration 状态变量。

    // Convert the `useEffect` code into a function instead
    const onDurationChangeHandler = (e) => {
        const seconds = Math.floor(e.target.duration);
        setDuration(seconds);
        progressBar.current.max = seconds;
    };
    
    <audio
        ref={audio}
        src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg"
        alt="oops, something went wrong..."
        onDurationChange={onDurationChangeHandler}
    ></audio>
    

    【讨论】:

      【解决方案2】:
          const onLoadedMetadata = ()=>{
              const seconds = Math.floor(audioPlayer.current.duration);
              setDuration(seconds);
              progressBar.current.max = seconds;
          }
      
      
      <audio ref={audioPlayer} src={audio_file} preload="metadata" onLoadedMetadata={onLoadedMetadata}></audio>
      

      【讨论】:

        猜你喜欢
        • 2012-06-07
        • 1970-01-01
        • 2013-04-21
        • 1970-01-01
        • 1970-01-01
        • 2021-12-14
        • 2023-02-03
        • 2012-09-03
        相关资源
        最近更新 更多