ReactJS ProgressBar value is not updating on par with state.progressValue - javascript

I'm currently using
import { Progress } from 'react-sweet-progress';
and before I also tried import { Progress } from 'reactstrap'; which just uses bootstrap 4 ProgressBar.
I have a state that maintains the progressValue, and using audio HTML tag to call in an online audio src and playing it. During timeupdate eventListener, I update my progressValue in my state, and reflect it back by setting the value of <Progress> as this.state.progressValue.
class FooBar extends Component {
state = {
progressValue: 0
}
handleProgress = () => {
this.currentTimeInterval = null;
this.audio.onplay = () => {
this.setState({
progressValue: 0,
play: true
});
this.audio.addEventListener('timeupdate', () => {
this.setState({
progressValue: Math.floor(this.audio.currentTime / this.audio.duration * 100)
}, function() {
console.log(this.state.progressValue);
})
}, true)
this.audio.onpause = () => {
clearInterval(this.currentTimeInterval);
}
}
render() {
return(
<audio
ref={(audio) => { this.audio = audio }}
src={http://www.music.helsinki.fi/tmt/opetus/uusmedia/esim/a2002011001-e02.wav}
autoPlay
onLoadedData={this.handleProgress}
/>
<Progress value={this.state.progressValue} />
);
}
}
The timing, however, doesn't seem to match up where the audio will be playing and the progressValue will be delayed in the sense that audio will be ahead of the progressValue. Hence, by the time audio finishes, it would likely take another 2~3 seconds for the progressBar to reach 100%.
I also tried:
this.currentTimeInterval = setInterval(() => {
this.setState({
progressValue: Math.floor(this.audio.currentTime / this.audio.duration * 100)
}))
}, 100)
and tried manipulating the timeInterval of 100ms to a smaller number, which makes it closer but the delay still exists.
3 Questions:
1) What is causing this to happen?
2) Is there a way to fix this?
3) If the answer is 'no' to 2), is there another component I can use to display the progress of an audio file? (Not the default controls from audio tag).
Thank you!

Related

Javascript navigator.mediasession image not showing in navigator / media controls disappearing

Again, working on my audio player...
I've rediscovered a mediasession updater in my code that adds a little notification from the browser with controls for my music, and it used to work ok, a little finicky, but now...
Nothing. I tinkered around a bit and got my play/pause buttons working, along with the next/prev songs and seek buttons, and the music title and artist. But I have now found that if you press the play button, the next/prev button, or sometimes the pause button, that the options and the song details disappear, leaving me with only the play/pause button, which works perfectly...
Now, to the code. I have a function that loads the songs in my player (loadSong(songIndex)), and near the end, I have a few lines of code that reset the navigator's metadata:
navigator.mediaSession.metadata.title = song.songname;
navigator.mediaSession.metadata.author = song.artistname;
navigator.mediaSession.metadata.artwork = ("./Music/" + song.thumbnail);
The artist and name work perfectly, but the thumbnail doesn't... More on that in a separate question.
I set up the actual controls like this:
/*media controls*/
let index = 0;
let skipTime = 10;
const actionHandlers = [
['play', () => { main.playPauseControl.click(); navigator.mediaSession.playbackState = "playing"; updatePositionState();}],
['pause', () => { main.playPauseControl.click(); navigator.mediaSession.playbackState = "paused"; updatePositionState();}],
['previoustrack', () => { main.prevControl.click(); updatePositionState();}],
['nexttrack', () => { main.nextControl.click(); updatePositionState();}],
['seekbackward', (details) => { main.audio.currentTime = Math.max(main.audio.currentTime - skipTime, 0); updatePositionState();}],
['seekforward', (details) => { main.audio.currentTime = Math.min(main.audio.currentTime + skipTime, main.audio.duration); updatePositionState();}],
];
for (const [action, handler] of actionHandlers) {
try {
navigator.mediaSession.setActionHandler(action, handler);
} catch (error) {
console.log(`The media session action "${action}" is not supported yet.`);
}
}
I have some other things linked to the mediaSession in order to register interactions outside the navigator
main.audio.addEventListener('play', function() {
navigator.mediaSession.playbackState = 'playing';
main.playPauseControl.classList.remove("paused");
});
main.audio.addEventListener('pause', function() {
navigator.mediaSession.playbackState = 'paused';
main.playPauseControl.classList.add("paused");
});
Now this all works, initially, but as soon as I interact with the navigator, BOOM, everything disappears. Can someone PLEASE tell me what's happening... this has been bugging me for a while...
P.S. sorry for the long question.
This answer is going to sound weird but have you tried not touching the media session from inside the callbacks? I know the Google Chrome example calls updatePositionState, but it actually has the same play/pause issue as your code.
So concretely, try this:
const actionHandlers = [
['play', () => { main.playPauseControl.click(); }],
['pause', () => { main.playPauseControl.click(); }],
['previoustrack', () => { main.prevControl.click(); }],
['nexttrack', () => { main.nextControl.click(); }],
['seekbackward', (details) => { main.audio.currentTime = Math.max(main.audio.currentTime - skipTime, 0); }],
['seekforward', (details) => { main.audio.currentTime = Math.min(main.audio.currentTime + skipTime, main.audio.duration); }],
];
I've done this for readtomyshoe and it no longer has this issue.

Stop function is not working as expected. Need Help. React

So I'm new to React and I have made a timer that increases after clicking on Start. Similarly, I made a stop button to stop the timer and set the variable to 0. But what's happening is it starts again from 0.
Help would be appreciated.
Thanks in Advance.
import React from 'react';
class Timer extends React.Component {
constructor() {
super();
this.state = {
secondsElapsed: 0
};
}
start = () => {
this.setState({
secondsElapsed: this.state.secondsElapsed + 1
});
}
stop = () => {
this.setState({
secondsElapsed: 0
});
}
handleClick = (e) => {
this.interval = setInterval(this.start, 1000);
}
render() {
return ( <React.Fragment><br/>
<ul>
<li><span onClick = {this.handleClick}> Start Timer <br/><br/></span></li>
<li><span onClick = {this.stop}> Stop Timer </span></li>
</ul>
<Resultant new = {this.state.secondsElapsed}/>
</React.Fragment>);
}
}
class Resultant extends React.Component {
render() {
return ( <div>
<h3> Seconds Elapsed: {this.props.new} </h3>
</div>);
}
}
export default Timer;
You have to clear the interval to stop the function from running repeatedly:
stop = () => {
clearInterval(this.interval);
this.setState({
secondsElapsed: 0
});
}
For more details about timing events in JavaScript here.
In your stop(), you are only resetting the secondElapsed state back to 0. You also need to stop the interval from updating the state again.
You can try adding clearInterval(this.interval) in the stop().
Change your stop function to this:
stop = () => {
this.setState({
secondsElapsed: 0
});
clearInterval(this.interval); // You should clear the interval to stop it from incrementing seconds when "stop" is pressed
}
Problem
You are using setInterval by assigning it to a value named interval by this.interval = setInterval() so it starts doing that each second but later after stopping it still uses it because you didn't stop interval so it is making it in the "background".
Solution
You just need to add line with clearing interval in the beginning of your stop function like:
stop = () => {
clearInterval(this.interval)
// rest of code
}
so it will stop running that in the background even if you click on the stop button.

Drum machine <audio> lag

I'm putting together a drum machine/sequencer and while the main functionality of the sequencer works fine, the audio that I've embedded in each drum cell does have a noticeable lag when the sequencer is first played. It seems to correct itself after the first beat and everything plays as normal, until other sounds are added to the pattern.
I have three rows of table cells, with each row representing a different drum sound. When the user constructs a drum pattern using all the sounds available, the loop eventually seems to go out of sync, with some sounds playing a fraction of a second later than others, but later corrects itself. My main concern is that the playback of the samples is inconsistent.
I've embedded the tags in elements with a preload attribute set to auto.
<table class="pad">
<tbody>
<tr>
<td class="kick sounds">
<audio preload="auto" src="https://raw.githubusercontent.com/wesbos/JavaScript30/master/01%20-%20JavaScript%20Drum%20Kit/sounds/kick.wav"></audio>
</td>
</tr>
</tbody>
</table>
So each there are 8 table cells to each row and each cell has the same format as above (with an element embedded). Admittedly, I can imagine that the way I've structured this is quite inefficient and that using the web audio API would work better, but I've yet to learn APIs. Is there something I can do in JS that can make the playback of these audio samples quicker?
EDIT: Here is the sequencer code. It cycles through each element and and checks if there is a cell selected. If so, that element's audio file is played. If not, it skips over to the next column.
class Sequencer {
playButton = btn;
clearButton = clear;
sounds = Array.from(sounds);
kicks = Array.from(kicks);
hihats = Array.from(hihats);
snares = Array.from(snares);
currentBeatIndexKick = 0;
currentBeatIndexHiHat = 0;
currentBeatIndexSnare = 0;
isPlaying = false;
constructor(msPerBeat) {
this.msPerBeat = msPerBeat;
this.playButton.addEventListener('click', () => this.toggleStartStop())
this.clearButton.addEventListener('click', () => this.clear())
this.sounds.forEach(sound => {
sound.addEventListener('click', e => {
if (((e.target.classList.contains('kick')) || (e.target.classList.contains('hihat')) || (e.target.classList.contains('snare'))) && !e.target.classList.contains('selected')) {
e.target.classList.add('selected');
} else {
e.target.classList.remove('selected');
}
})
})
}
toggleStartStop() {
if (this.isPlaying) {
this.stop();
} else {
this.start();
}
}
clear() {
this.kicks.forEach(kick => {
if (kick.classList.contains('selected')) {
kick.classList.remove('selected');
}
});
hihats.forEach(hihat => {
if (hihat.classList.contains('selected')) {
hihat.classList.remove('selected');
}
});
snares.forEach(snare => {
if (snare.classList.contains('selected')) {
snare.classList.remove('selected');
}
});
this.stop();
console.clear();
}
stop() {
this.isPlaying = false;
this.currentBeatIndexKick = 0;
this.currentBeatIndexHiHat = 0;
this.currentBeatIndexSnare = 0;
this.playButton.innerText = 'Play';
}
start() {
this.isPlaying = true;
this.playCurrentNoteAndSetTimeoutKick() // kicks
this.playCurrentNoteAndSetTimeoutHiHats() // hihats
this.playCurrentNoteAndSetTimeoutSnares() // snares
this.playButton.innerText = 'Stop';
}
playCurrentNoteAndSetTimeoutKick() {
if (this.isPlaying && this.kicks[this.currentBeatIndexKick].classList.contains('selected')) {
this.kicks[this.currentBeatIndexKick].childNodes[1].play();
setTimeout(() => {
this.toNextBeatKicks();
this.playCurrentNoteAndSetTimeoutKick();
}, this.msPerBeat)
}
if (this.isPlaying && !this.kicks[this.currentBeatIndexKick].classList.contains('selected'))
setTimeout(() => {
this.toNextBeatKicks();
this.playCurrentNoteAndSetTimeoutKick();
}, this.msPerBeat)
}
playCurrentNoteAndSetTimeoutHiHats() {
if (this.isPlaying && this.hihats[this.currentBeatIndexHiHat].classList.contains('selected')) {
this.hihats[this.currentBeatIndexHiHat].childNodes[1].play();
setTimeout(() => {
this.toNextBeatHiHats();
this.playCurrentNoteAndSetTimeoutHiHats();
}, this.msPerBeat)
}
if (this.isPlaying && !this.hihats[this.currentBeatIndexHiHat].classList.contains('selected'))
setTimeout(() => {
this.toNextBeatHiHats();
this.playCurrentNoteAndSetTimeoutHiHats();
}, this.msPerBeat)
}
playCurrentNoteAndSetTimeoutSnares() {
if (this.isPlaying && this.snares[this.currentBeatIndexSnare].classList.contains('selected')) {
this.snares[this.currentBeatIndexSnare].childNodes[1].play();
setTimeout(() => {
this.toNextBeatSnares();
this.playCurrentNoteAndSetTimeoutSnares();
}, this.msPerBeat)
}
if (this.isPlaying && !this.snares[this.currentBeatIndexSnare].classList.contains('selected'))
setTimeout(() => {
this.toNextBeatSnares();
this.playCurrentNoteAndSetTimeoutSnares();
}, this.msPerBeat)
}
toNextBeatKicks() {
this.currentBeatIndexKick = ++this.currentBeatIndexKick % this.kicks.length;
}
toNextBeatHiHats() {
this.currentBeatIndexHiHat = ++this.currentBeatIndexHiHat % this.hihats.length;
}
toNextBeatSnares() {
this.currentBeatIndexSnare = ++this.currentBeatIndexSnare % this.snares.length;
}
}
const sequencer = new Sequencer(213)
I think what you might have to do is to play the audio file programmatically. It's kind of hard to understand how your sequencer works without giving us more access to your code, the way I would approach this is by programmatically playing the audio file once the user has selected the sequence. I would assign a variable to play once the steps are selected on your sequencer.
var audio = new Audio('audio_file.mp3');
audio.play();
audio.play would be called on the sequence. Not sure if this helps but that's part of the api I think you were referring to.

Idle timer within Vue Component

I am have some issues resetting my timer when no longer idle. I am using Vue Idle for this, which is a wrapper for idle.js.
So I have a modal with the id timeout-modal. When Vue Idle triggers the idle function, I call showWarningMessage.
Within this function, I first display my modal. I then create a timer which my modal uses to do a countdown. So this all works fine.
<script>
export default {
data() {
return {
timerId: 0,
remainingTimeoutSeconds: 10000
}
},
computed: {
second() {
return this.remainingTimeoutSeconds / 1000;
}
},
onIdle () {
this.showWarningMessage();
},
methods: {
showWarningMessage() {
this.$bvModal.show('timeout-modal');
this.warning = true;
this.timerId = setInterval(() => {
this.remainingTimeoutSeconds -= 1000;
}, 1000);
},
}
}
</script>
Now within the modal there is a continue button. When pressed, it should basically reset the above timer. At the moment I have
handleContinueButtonClick(response) {
if (response.data.success) {
console.log("IN")
this.$bvModal.hide('app-timeout-reminder-modal');
clearInterval(this.timerId);
return;
}
}
So what this should do is hide the modal, and then reset the timer back to 10 seconds. It is entering the above as the console is printing IN. The modal is also
hidden when I click OK.
However, the next time the modal is displayed, the timer is already near 0 as it did not reset back to 10.
Is there any reason why I cant get this back to 10 seconds? I thought clearInterval should reset the timer?
Thanks
I thought clearInterval should reset the timer?
Do you mean this.remainingTimeoutSeconds is set automatically when calling clearInterval?
The answer is no.
You need to reset that value as 10000 like blow;
handleContinueButtonClick(response) {
if (response.data.success) {
console.log("IN")
this.$bvModal.hide('app-timeout-reminder-modal');
this.remainingTimeoutSeconds = 10000;
clearInterval(this.timerId);
return;
}
}
or
showWarningMessage() {
this.$bvModal.show('timeout-modal');
this.warning = true;
this.remainingTimeoutSeconds = 10000;
this.timerId = setInterval(() => {
this.remainingTimeoutSeconds -= 1000;
}, 1000);
}

Function setIntervall become faster after change router

https://protected-temple-97157.herokuapp.com/
here theres my app, if you open you see that there no problems on slideshow, the image change after the 6 seconds, but if you go to other router and then comeback on Home after the first image the slideshow become more faster
componentDidMount() {
this.slide();
}
slide = () => {
$(".slideshow > .card:gt(0)").hide();
setInterval(() => {
$(".slideshow > .card:first")
.fadeOut(3000)
.next()
.fadeIn(3000)
.end()
.appendTo('.slideshow')
}, 6000)
}
I guess you add a new Interval each time you come back to the site. If you look closely you see that the time difference between the slides varies, therefore multiple intervals are set.
You can prevent this by calling setInterval only once, initially, or use clearInterval to clear the previous interval.
It's not that the slideshow is faster, but you will get another interval that changes slides because you don't stop the previous interval when the component is unmounted.
You can put the number returned from setInterval on the component and call clearInterval in componentWillUnmount to get around this.
Example
class App extends React.Component {
interval = null;
componentDidMount() {
this.slide();
}
componentWillUnmount() {
clearInterval(this.interval);
}
slide = () => {
$(".slideshow > .card:gt(0)").hide();
this.interval = setInterval(() => {
$(".slideshow > .card:first")
.fadeOut(3000)
.next()
.fadeIn(3000)
.end()
.appendTo(".slideshow");
}, 6000);
};
}

Categories