React audio Cannot set property 'volume' of undefined - javascript

I am trying to play an mp3 using the Audio element but whenever the player renders an error occurs:- Cannot set property 'volume' of undefined.
Play prop is just a boolean value.
The useRef() current property shows me the mp3 file when I console.log it.
I removed the volume property but then it displays the same error for audio.play().
why is the audio undefined?
import React, { useState, useRef } from "react";
import "../../Static/player.css";
import Nowplaying from "./Nowplaying";
import SongInfo from "./SongInfo";
import Slider from "./Slider";
import Duration from "./Duration";
import song from "../../Static/assets/song.mp3";
const Player = (props) => {
const { Play } = props;
const [percentage, setPercentage] = useState(0)
const [duration, setDuration] = useState(0)
const [currentTime, setCurrentTime] = useState(0)
const audioRef = useRef()
const onChange = (e) => {
const audio = audioRef.current
audio.currentTime = (audio.duration / 100) * e.target.value
setPercentage(e.target.value)
}
const play = () => {
const audio = audioRef.current
audio.volume = 0.1
if (!Play) {
audio.play()
}
if (Play) {
audio.pause()
}
}
const getCurrDuration = (e) => {
const percent = ((e.currentTarget.currentTime / e.currentTarget.duration) * 100).toFixed(2)
const time = e.currentTarget.currentTime
setPercentage(+percent)
setCurrentTime(time.toFixed(2))
}
if (Play) {
play();
} else {
play();
}
return (
<div className="player-screen">
<div className="play-screen">
<div className="navbar">
<Nowplaying />
</div>
<div className="song-info">
<SongInfo />
</div>
<div className="player-controls">
<Slider percentage={percentage} onChange={onChange} />
<Duration
duration={duration}
currentTime={currentTime}
/>
<audio
ref={audioRef}
onTimeUpdate={getCurrDuration}
onLoadedData={(e) => {
setDuration(e.currentTarget.duration.toFixed(2));
}}
src={song}
></audio>
</div>
</div>
</div>
);
};
export default Player;
what wrong am I doing?

This part of code:
if (Play) {
play();
} else {
play();
}
gets immediately called, before React even has the chance to set audioRef.current to the Audio element. Your function hasn't even finished rendering yet, React doesn't even know where audioRef is used.
Move that piece of code into a useEffect, or better yet, replace your audioRef with a callback function (which can still store the Audio element in another ref or state variable), as shown here.

Related

what is making props behavior inconsistent when set as a variable and passed into another function

I am creating a simple audio player using React.
When pushing the "Play" button, audio will be played from a url and the button will display "Pause". Once the audio is finished the button will reset to "Play."
There are three buttons "one" "two" "three". Each one will load a new audio url to be played by the button. Here is the codesandbox.
I'm new to useEffect and useState and I think there is an issue with how I'm updating this with props.
In my audio player component, if I directly set the url (without updating it with props), the audio will play as expected when the "play" button is pushed. However, when I set the url with props it won't play (even though the console.log() displays the correct url).
here is my Audio3.js component (responsible for playing audio:
import React, { useState, useEffect } from "react";
const useAudio = (url) => {
console.log("in useAudio", url)
const [audio] = useState(new Audio(url));
const [playing, setPlaying] = useState(false);
const toggle = () => setPlaying(!playing);
useEffect(() => {
playing ? audio.play() : audio.pause();
}, [playing]);
useEffect(() => {
audio.addEventListener("ended", () => setPlaying(false));
return () => {
audio.removeEventListener("ended", () => setPlaying(false));
};
}, []);
return [playing, toggle];
};
const Player = (props) => {
console.log("the current page is", props.currPage)
// const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/2.mp3?raw=true"
const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/" +
props.currPage +
".mp3?raw=true"
console.log("the audio 3 is ", url);
const [playing, toggle] = useAudio(url);
return (
<div>
<button onClick={toggle}>{playing ? "Pause" : "Play"}</button>
</div>
);
};
export default Player;
Here is the threeButtons.js file:
import React from "react";
function Tabs(props) {
return (
<>
<button onClick={() => props.handleChangeProps(1)}>ONE</button>
<br />
<button onClick={() => props.handleChangeProps(2)}>TWO</button>
<br />
<button onClick={() => props.handleChangeProps(3)}>THRE</button>
</>
);
}
export default Tabs;
Here is the header component that houses the audio Play button:
import React from "react";
import Audio from "./Audio3";
function Header(props) {
console.log("header", props.currPage);
return (
<Audio currPage={props.currPage} />
);
}
export default Header;
And lastly, here is my App.js
import React, { useState } from "react";
import Header from "./components/header";
import ConceptTabs from "./components/threeButtons";
function App() {
const [pageID, setPageID] = useState(0);
// goes to ConceptTabs
let handleChange = (id) => {
console.log("clicked", id);
handleChange2(id);
setPageID(id);
return id;
};
// Goes to Audio2
let handleChange2 = (id) => {
console.log("clicked2", id);
return id;
};
console.log("pageID", pageID);
return (
<>
<Header currPage={pageID} />
<br />
<ConceptTabs handleChangeProps={handleChange} />
</>
);
}
export default App;
Thanks so much
const [audio] = useState(new Audio(url));
is triggered only once when component mounts. If you want to change it when url change, you'll need to have a useEffect
const audio = useRef(new Audio(url));
useEffect(() => {
audio.current = new Audio(url);
// add listeners
return () => {
if (audio.current) {
// Destroy audio & listeners
}
}
}, [url])
You'll also notice that I've used a useRef instead of useState for your audio, which is better when you need to store something which do not need a "re-render" when changing value

React-Webcam Recording, Replay, Re-recording, Storing

Just making a video recording component that you can replay afterwards to see if you like it, if not you just re-record, and finally it will store it in the database. I'm using react-webcam for this, with some functionality I've found online.
I have a handleDownload function which sets the video tag source with the blob I just recorded. Originally it downloaded the video file when clicking the button, but I want the video to be replayable as soon as I stop a recording. Ideally, I want to use the same react-webcam component, but not sure I can do that, so for now this will do.
It works when I set the function to onClick listener to the button, however, it doesn't work when I call the function inside of handleStopCaptureClick
so I tried to implement a useEffect which causes handleDownload to run after we stop capturing. This doesn't work either - thoughts? Thanks!
import React, {useEffect} from "react";
import Webcam from "react-webcam";
export const WebcamStreamCapture = () => {
const webcamRef = React.useRef(null);
const mediaRecorderRef = React.useRef(null);
const [capturing, setCapturing] = React.useState(false);
const [recordedChunks, setRecordedChunks] = React.useState([]);
const isInitialMount = React.useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
if (!capturing) {
console.log('running handleDownload')
handleDownload();
}
}
}, [capturing])
const handleStartCaptureClick = React.useCallback(() => {
setCapturing(true);
mediaRecorderRef.current = new MediaRecorder(webcamRef.current.stream, {
mimeType: "video/webm"
});
mediaRecorderRef.current.addEventListener(
"dataavailable",
handleDataAvailable
);
mediaRecorderRef.current.start();
}, [webcamRef, setCapturing, mediaRecorderRef]);
const handleDataAvailable = React.useCallback(
({ data }) => {
if (data.size > 0) {
setRecordedChunks((prev) => prev.concat(data));
}
},
[setRecordedChunks]
);
const handleStopCaptureClick = React.useCallback(() => {
mediaRecorderRef.current.stop();
setCapturing(false);
}, [mediaRecorderRef, webcamRef, setCapturing]);
const handleDownload = React.useCallback(() => {
if (recordedChunks.length) {
const blob = new Blob(recordedChunks, {
type: "video/webm"
});
const url = URL.createObjectURL(blob);
const video = document.getElementById("video-replay");
video.src = url
}
}, [recordedChunks]);
return (
<div className="d-flex flex-column align-items-center">
<Webcam audio={false} ref={webcamRef} height={400} width={500}/>
<video id="video-replay" height="400" width="500" controls></video>
{capturing ? (
<button className="btn btn-danger" onClick={handleStopCaptureClick}>Stop Capture</button>
) : (
<button className="btn btn-danger" onClick={handleStartCaptureClick}>Start Capture</button>
)}
{recordedChunks.length > 0 && (
<div>
<button onClick={handleDownload}>Download</button>
</div>
)}
</div>
);
};
Possible Solution
So I caught myself thinking, if the chunks aren't appearing/working during the useEffect either, it must mean that when capturing stops in handleStopCaptureClick it takes the state a while to update, including chunks I suppose. By changing the dependency from 'capturing' to 'recordedChunks' in useEffect, I was successful in making the video appear right after you stop recording.
Solution: By changing the dependency from 'capturing' to 'recordedChunks' in useEffect, I was successful in making the video appear right after you stop recording.

Prevent updating Audio() on re-render Component

I'm learning ReactJS and TypeScript. I'm developing a music player, that must play audio from File (FileReader). In process of development, I have solved some issues like more than one playing thread, play() cannot be called because of pause().
I found good code from some answers here, but I have a problem with it: when I press the play button, music starts playing, but after the pause clicked music stops and after pressing play again music starts from the beginning. I guess it because of Audio() or audio.src updates after each button press, because this event emits re-render.
I tried to move player functionality to App.tsx component and get through the prop play/pause button state... a bad idea...
I tried store Audio, not in the state.
I tried push audio.src into useEffect(() => {}, []);, but it trows
DOMExeption: incorrect source (because it's null I guess).
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;
Interfaces:
export interface IAudio {
src: string | null;
id: number;
title: string | null;
album: string | null;
author: string | null;
}
export interface IAudioProp {
audio: IAudio;
}

How do I fast-forward the audio in my react component?

I have a react component that display an audio player. I need to implement a feature where you can go back and forward in the audio 15 seconds. I have a function called skip that does this but it only updates the time and is not moving the audio track. What I am doing wrong? Here is an image of my audio player
import React, { useState, useEffect, useRef } from "react";
import "./AudioPlayer.css";
import Navbar from "../../components/Navbar/Navbar";
import { useParams } from "react-router-dom";
import axios from "axios";
import Slider from "../../components/Slider/Slider";
import ControlPanel from "../../components/Controls/ControlPanel";
import * as RiIcons from "react-icons/ri";
function AudioPlayer() {
const [episodeData, setEpisodeData] = useState([]);
const [percentage, setPercentage] = useState();
const [isPlaying, setIsPlaying] = useState(false);
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState();
const [speed, setSpeed] = useState(1);
const audioRef = useRef();
const onChange = (e) => {
const audio = audioRef.current;
audio.currentTime = (audio.duration / 100) * e.target.value;
setPercentage(e.target.value);
};
const play = () => {
const audio = audioRef.current;
// audio.playbackRate = speed;
audio.volume = 0.1;
if (!isPlaying) {
setIsPlaying(true);
audio.play();
}
if (isPlaying) {
setIsPlaying(false);
audio.pause();
}
};
const getCurrDuration = (e) => {
const percent = (
(e.currentTarget.currentTime / e.currentTarget.duration) *
100
).toFixed(2);
const time = e.currentTarget.currentTime;
setPercentage(+percent);
setCurrentTime(time.toFixed(2));
};
const changeSpeed = () => {
if (speed >= 2) {
setSpeed(0.5);
} else setSpeed(speed + 0.5);
};
const skip = (time) => {
const audio = audioRef.current;
if (time == "back") {
console.log("15");
setCurrentTime(audio.currentTime - 15);
} else if (time == "fwd") {
console.log("15");
setCurrentTime(audio.currentTime + 15);
}
};
const { id } = useParams();
const headers = { jwt_token: localStorage.token };
useEffect(() => {
axios
.get(`/api/get/episodes/${id}`, { headers })
.then((res) => setEpisodeData(res.data));
}, []);
useEffect(() => {
const audio = audioRef.current;
audio.playbackRate = speed;
}, [speed]);
return (
<div>
<Navbar />
<div>
<div style={{ width: "60%", margin: "0 auto", paddingTop: "10rem" }}>
<div className="app-container">
<h3 style={{ color: "#fff" }}>{episodeData.podcast_title}</h3>
<h3 style={{ color: "#fff" }}>{episodeData.episode_title}</h3>
<Slider percentage={percentage} onChange={onChange} />
<audio
ref={audioRef}
onTimeUpdate={getCurrDuration}
onLoadedData={(e) => {
setDuration(e.currentTarget.duration.toFixed(2));
}}
src={episodeData.episode_audio}
></audio>
<ControlPanel
play={play}
isPlaying={isPlaying}
duration={duration}
currentTime={currentTime}
/>
<button className="speed-button" onClick={() => changeSpeed()}>
{speed}x
</button>
<button onClick={() => skip("back")}>
BACK 15 SECONDS
<RiIcons.RiArrowGoBackLine color={"white"} size={16} />
</button>
<button onClick={() => skip("fwd")}>
<RiIcons.RiArrowGoForwardLine color={"white"} size={16} />
FORWARD 15 SECONDS
</button>
</div>
</div>
</div>
</div>
);
}
export default AudioPlayer;
The problem with your code is, you're trying to change the state of component where as you should be changing the current time of the audio player.
The currentTime property sets or returns the current position (in seconds) of the audio/video playback.
When setting this property, the playback will jump to the specified position.
const skip = (time) => {
const audio = audioRef.current;
if (time == 'back') {
// changes
audio.currentTime = audio.currentTime - 15;
} else if (time == 'fwd') {
// changes
audio.currentTime = audio.currentTime + 15;
}
};
By changing the current time of the audio player using ref the audio player jumps to the specified position, so your onchange function will also be called.

Preventing page from refreshing/reloading-ReactJS

I've built a countdown counter using React hooks, but while I was comparing its accuracy with its vanilla JS counterpart (I was displaying their current timer on the document title, i.e., I was active on a third tab), I noticed that the react timer stopped after awhile , and when I opened the tab, the page refreshed and the timer reset to its initial state, while this didn't/doesn't happen in the vanilla JS version. I would like to mention that I opened maybe 15 YouTube videos, because I want the timer to be working while doing heavy duty work on my machine. How can I prevent this from happening?
Here is the code
App.js
import React, { useState } from 'react';
import convertTime from '../helper-functions/convertTime';
import useInterval from '../hooks/useInterval';
const Countdown = () => {
const [count, setCount] = useState(82.5 * 60);
const [delay, setDelay] = useState(1000);
const [isPlaying, setIsPlaying] = useState(false);
document.title = convertTime(count);
useInterval(() => setCount(count - 1), isPlaying ? delay : null);
const handlePlayClick = () => setIsPlaying(true);
const handlePauseClick = () => setIsPlaying(false);
const handleResetClick = () => {
setCount(82.5 * 60);
setDelay(1000);
setIsPlaying(false);
};
return (
<div className='counter'>
<div className='time'>{convertTime(count)}</div>
<div className='actions'>
<button onClick={handlePlayClick}>play</button>
<button onClick={handlePauseClick}>pause</button>
<button onClick={handleResetClick}>reset</button>
</div>
</div>
);
};
export default Countdown;
useInterval.js
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
export default useInterval;

Categories