How to tell if a <video> element is currently playing? - javascript

I see that the MediaElement interface exposes attributes like paused, seeking, and ended. Missing from the list, however, is playing.
I know there are playing events that fire when an element starts playing, and timeupdate events events periodically while playing, but I'm looking for a way to determine whether a video is playing right now. Is there an easy way to determine this?
The closest I've got is:
!(video.paused || video.ended || video.seeking || video.readyState < video.HAVE_FUTURE_DATA)

There is not a specific attribute that will reveal whether a MediaElement is currently playing. However, you can deduce this from the state of the other attributes. If:
currentTime is greater than zero, and
paused is false, and
ended is false
then the element is currently playing.
You may also need to check readyState to see if the media stopped due to errors. Maybe something like that:
const isVideoPlaying = video => !!(video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2);

It has been a long time but here is a great tip. You can define .playing as a custom property for all media elements and access it when needed. Here is how:
Object.defineProperty(HTMLMediaElement.prototype, 'playing', {
get: function(){
return !!(this.currentTime > 0 && !this.paused && !this.ended && this.readyState > 2);
}
})
Now you can use it on <video> or <audio> elements like this:
if(document.querySelector('video').playing){ // checks if element is playing right now
// Do anything you want to
}

var video = $('selector').children('video');
var videoElement = video.get(0);
if (!videoElement.paused) {}
One way of doing it using Jquery

See my response here: HTML5 video tag, javascript to detect playing status?
Basicaly, as said before there is no single property to check but according to the spec it's a combination of conditions.

I was facing the same problem. Solution is very simple and straight forward:
// if video status is changed to "ended", then change control button to "Play Again"
video.onended = function() {
$("#play_control_button").text("Play Again");
};
// if video status is changed to "paused", then change control button to "Continue Play"
video.onpause = function() {
$("#play_control_button").text("Continue Play");
};
// if video status is changed to "playing", then change control button to "Stop"
video.onplaying = function() {
$("#play_control_button").text("Stop");
};

var vid = document.getElementById("myVideo");
vid.onplaying = function() {
alert("The video is now playing");};
you can use this see this

you could check for the readyState if its equal or greater than HAVE_FUTURE_DATA and paused is false. This could confirm that video is playing.
As its explained in https://html.spec.whatwg.org/ playing event will be fired when:
"readyState is newly equal to or greater than HAVE_FUTURE_DATA and paused is false, or paused is newly false and readyState is equal to or greater than HAVE_FUTURE_DATA"

Related

Why is video.readyState always 1 in the onseeking event?

I'm building a video player and I want a loader to be shown whenever the video is paused and the user seeks to a certain time in the video that's not yet buffered.
The waiting event only works if the video isn't paused, so it wouldn't work for me in this particular case.
So, to work around this limitation of the waiting event I've attached an event handler to the seeking event, which fires whenever currentTime changes manually, that is to say whenever "seeking" starts to happen, and in that event handler I'm checking the video's readyState property to see if the new place in the video that the user has seeked to is ready to be played or not.
But the problem is that readyState is always 1 in the event handler of the seeking event no matter what, even if the area that you seek to is actually completely buffered and can be played.
In the snippet below, do the following:
Pause the video.
Seek to a time in the video that's not buffered.
The seeking event fires and readyState is 1, which means that point in the video is not yet downloaded.
Now, seek back to an area that's already buffered.
The seeking event fires again, but readyState is STILL 1, but expectedly it should be >= 2 because that area is buffered. Why is that?!
const video = document.getElementsByTagName('video')[0];
video.addEventListener('waiting', () => {
console.log('waiting FIRED');
});
video.addEventListener('seeking', () => {
console.log('seeking FIRED');
console.log('seeking FIRED | currentTime:', video.currentTime);
console.log('seeking FIRED | readyState:', video.readyState);
});
video {
width: 100%;
margin-bottom: 50px;
}
<video src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" controls></video>
I also can't use the seeked event because it fires after the new area that the user has seemed to is buffered, which defeats the whole purpose of what I'm trying to achieve in the first place. I want to be noticed when the user seeks to a new point in the video before that point is buffered, so that I can show a loader or something.
Since the full video is not buffered, a readyState of 1 sounds correct.
Remember that seeking is async, (even though the .currentTime value is updated synchronously, it doesn't represent the "current playback position"). So when the seeking event fires, the browser already left the current position but didn't yet "set the current playback position to the new playback position" nor "established whether or not the media data for the new playback position is available". (specs)
So at this time it can't tell if it has any data at the will-be current playback position, and all it can say is that it has some metadata.
But for what you are trying to do, you'd be better using the HTMLMediaElement.buffered property, which returns a TimeRanges object representing the time-ranges already buffered.
const video = document.getElementsByTagName('video')[0];
video.addEventListener('seeking', () => {
const buffered = video.buffered;
let buffering = true;
// loop through all ranges
for ( let i = 0; i < buffered.length; i++ ) {
const start = buffered.start(i);
const end = buffered.end(i);
// note that currentTime returns the "official playback position", not the "current playback position"
// its value is set synchronously
if( start <= video.currentTime && end >= video.currentTime ) {
buffering = false;
break;
}
}
console.clear();
console.log('buffering:', buffering);
});
video {
width: 100%;
max-height: 100vh;
}
<video src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" controls></video>

Javascript function to check if there is a video on the page and then hide the next button for the duration of the video

I am trying to build a function to check for a video on an html page and if it exists, hide the next button on the page for the duration on the video. So far my code looks like this:
<script type="text/javascript">
$(document).onload(function () {
//grab video in variable
var video = document.getElementsByClassName("Video");
//if video exists, hide the next button, get duration
if (typeof (video) != 'undefined' && video != null) {
//grab the next button by 'next' class
var nextButton = document.getElementsByClassName('next');
//hide next by adding bootstrap 'd-none' class
nextButton.classList.add('d-none');
//grab duration on video to set timer
var duration = video.duration;
//set a timer for the duration of the video
setTimeout(nextButton.classList.remove('d-none', duration))
}
});
I am not sure why my function isn't working. Any help would be awesome, thanks.
Can you share your HTML and the jquery version you're using?
so far, Here are a few things that I've noticed in the above code
I suggest that you try your selectors in chrome console in the page you're developing.
Starting with this.
var video = document.getElementsByClassName("Video");
I'd suggest checking MDN for docs about getElementsByClassName
It returns an array of elements that match your selector, not a single element (assuming that each video element has a class named Video)
so, to access the element, it should be accessed as video[0], but it's usually a good idea to check for array length before accessing the elements.
so, I guess you can do something like
/*that will select the first video element, assuming your video has a class named "Video"
you can also use
var video = document.getElementsByTagName("video")[0];
*/
var video = document.getElementsByClassName("Video")[0];
//check if the element exists
if (video) {
//select the "Next" button, assumuing it has a class named 'next'
var nextButton = document.getElementsByClassName('next')[0];
//hide next by adding bootstrap 'd-none' class
nextButton.classList.add('d-none');
//start playing the video
video.play();
/*grab duration on video to set timer
note the *1000,
since setTimeout takes time in milliseconds, we need to multiply by 1000 to convert to seconds
*/var duration = video.duration * 1000;
//set a timer for the duration of the video
/**
* The syntax here was not correct.
* setTimeout takes a function as first parameter, the duration as second parameter.
*/
setTimeout(() => {
//hide the timer on video duration over
nextButton.classList.remove('d-none')
}, duration);
}
document.getElementsByClassName('class_name') return a NodeList not just a single node.
So if there is only one video element with the video class name then you should change nextButton.classList.add('d-none'); to nextButton[0].classList.add('d-none');.
Or if you have multiple video class named element then you should consider using a loop and adding an ended event listener to each one of them.
And also fix your setTimeout function,
setTimeout(() => {
nextButton.classList.remove('d-none')
}, duration);
if(document.getElementsByTagName("video").length > 0){
document.getElementById("idOfNextButton").style.display = "none";
}
document.getElementsByTagName(tag) gets every element that matches the tag. You can easily hide the element with style.display = "none". If you want to constantly check this, you can repeat the code above using the following:
setInterval(function(){
}, 1); // repeats every millisecond

Please help me to use play/stop code so my code should stop and reset not pause [duplicate]

I am playing a small audio clip on click of each link in my navigation
HTML Code:
<audio tabindex="0" id="beep-one" controls preload="auto" >
<source src="audio/Output 1-2.mp3">
<source src="audio/Output 1-2.ogg">
</audio>
JS code:
$('#links a').click(function(e) {
e.preventDefault();
var beepOne = $("#beep-one")[0];
beepOne.play();
});
It's working fine so far.
Issue is when a sound clip is already running and i click on any link nothing happens.
I tried to stop the already playing sound on click of link, but there is no direct event for that in HTML5's Audio API
I tried following code but it's not working
$.each($('audio'), function () {
$(this).stop();
});
Any suggestions please?
Instead of stop() you could try with:
sound.pause();
sound.currentTime = 0;
This should have the desired effect.
first you have to set an id for your audio element
in your js :
var ply = document.getElementById('player');
var oldSrc = ply.src;// just to remember the old source
ply.src = "";// to stop the player you have to replace the source with nothing
I was having same issue. A stop should stop the stream and onplay go to live if it is a radio. All solutions I saw had a disadvantage:
player.currentTime = 0 keeps downloading the stream.
player.src = '' raise error event
My solution:
var player = document.getElementById('radio');
player.pause();
player.src = player.src;
And the HTML
<audio src="http://radio-stream" id="radio" class="hidden" preload="none"></audio>
Here is my way of doing stop() method:
Somewhere in code:
audioCh1: document.createElement("audio");
and then in stop():
this.audioCh1.pause()
this.audioCh1.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAVFYAAFRWAAABAAgAZGF0YQAAAAA=';
In this way we don`t produce additional request, the old one is cancelled and our audio element is in clean state (tested in Chrome and FF) :>
This method works:
audio.pause();
audio.currentTime = 0;
But if you don't want to have to write these two lines of code every time you stop an audio you could do one of two things. The second I think is the more appropriate one and I'm not sure why the "gods of javascript standards" have not made this standard.
First method: create a function and pass the audio
function stopAudio(audio) {
audio.pause();
audio.currentTime = 0;
}
//then using it:
stopAudio(audio);
Second method (favoured): extend the Audio class:
Audio.prototype.stop = function() {
this.pause();
this.currentTime = 0;
};
I have this in a javascript file I called "AudioPlus.js" which I include in my html before any script that will be dealing with audio.
Then you can call the stop function on audio objects:
audio.stop();
FINALLY CHROME ISSUE WITH "canplaythrough":
I have not tested this in all browsers but this is a problem I came across in Chrome. If you try to set currentTime on an audio that has a "canplaythrough" event listener attached to it then you will trigger that event again which can lead to undesirable results.
So the solution, similar to all cases when you have attached an event listener that you really want to make sure it is not triggered again, is to remove the event listener after the first call. Something like this:
//note using jquery to attach the event. You can use plain javascript as well of course.
$(audio).on("canplaythrough", function() {
$(this).off("canplaythrough");
// rest of the code ...
});
BONUS:
Note that you can add even more custom methods to the Audio class (or any native javascript class for that matter).
For example if you wanted a "restart" method that restarted the audio it could look something like:
Audio.prototype.restart= function() {
this.pause();
this.currentTime = 0;
this.play();
};
It doesn't work sometimes in chrome,
sound.pause();
sound.currentTime = 0;
just change like that,
sound.currentTime = 0;
sound.pause();
From my own javascript function to toggle Play/Pause - since I'm handling a radio stream, I wanted it to clear the buffer so that the listener does not end up coming out of sync with the radio station.
function playStream() {
var player = document.getElementById('player');
(player.paused == true) ? toggle(0) : toggle(1);
}
function toggle(state) {
var player = document.getElementById('player');
var link = document.getElementById('radio-link');
var src = "http://192.81.248.91:8159/;";
switch(state) {
case 0:
player.src = src;
player.load();
player.play();
link.innerHTML = 'Pause';
player_state = 1;
break;
case 1:
player.pause();
player.currentTime = 0;
player.src = '';
link.innerHTML = 'Play';
player_state = 0;
break;
}
}
Turns out, just clearing the currentTime doesn't cut it under Chrome, needed to clear the source too and load it back in. Hope this helps.
As a side note and because I was recently using the stop method provided in the accepted answer, according to this link:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
by setting currentTime manually one may fire the 'canplaythrough' event on the audio element. In the link it mentions Firefox, but I encountered this event firing after setting currentTime manually on Chrome. So if you have behavior attached to this event you might end up in an audio loop.
shamangeorge wrote:
by setting currentTime manually one may fire the 'canplaythrough' event on the audio element.
This is indeed what will happen, and pausing will also trigger the pause event, both of which make this technique unsuitable for use as a "stop" method. Moreover, setting the src as suggested by zaki will make the player try to load the current page's URL as a media file (and fail) if autoplay is enabled - setting src to null is not allowed; it will always be treated as a URL. Short of destroying the player object there seems to be no good way of providing a "stop" method, so I would suggest just dropping the dedicated stop button and providing pause and skip back buttons instead - a stop button wouldn't really add any functionality.
This approach is "brute force", but it works assuming using jQuery is "allowed". Surround your "player" <audio></audio> tags with a div (here with an id of "plHolder").
<div id="plHolder">
<audio controls id="player">
...
</audio>
<div>
Then this javascript should work:
function stopAudio() {
var savePlayer = $('#plHolder').html(); // Save player code
$('#player').remove(); // Remove player from DOM
$('#FlHolder').html(savePlayer); // Restore it
}
I was looking for something similar due to making an application that could be used to layer sounds with each other for focus. What I ended up doing was - when selecting a sound, create the audio element with Javascript:
const audio = document.createElement('audio') as HTMLAudioElement;
audio.src = getSoundURL(clickedTrackId);
audio.id = `${clickedTrackId}-audio`;
console.log(audio.id);
audio.volume = 20/100;
audio.load();
audio.play();
Then, append child to document to actually surface the audio element
document.body.appendChild(audio);
Finally, when unselecting audio, you can stop and remove the audio element altogether - this will also stop streaming.
const audio = document.getElementById(`${clickedTrackId}-audio`) as HTMLAudioElement;
audio.pause();
audio.remove();
If you have several audio players on your site and you like to pause all of them:
$('audio').each( function() {
$(this)[0].pause();
});
I believe it would be good to check if the audio is playing state and reset the currentTime property.
if (sound.currentTime !== 0 && (sound.currentTime > 0 && sound.currentTime < sound.duration) {
sound.currentTime = 0;
}
sound.play();
for me that code working fine. (IE10+)
var Wmp = document.getElementById("MediaPlayer");
Wmp.controls.stop();
<object classid="clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6"
standby="Loading áudio..." style="width: 100%; height: 170px" id="MediaPlayer">...
Hope this help.
What I like to do is completely remove the control using Angular2 then it's reloaded when the next song has an audio path:
<audio id="audioplayer" *ngIf="song?.audio_path">
Then when I want to unload it in code I do this:
this.song = Object.assign({},this.song,{audio_path: null});
When the next song is assigned, the control gets completely recreated from scratch:
this.song = this.songOnDeck;
The simple way to get around this error is to catch the error.
audioElement.play() returns a promise, so the following code with a .catch() should suffice manage this issue:
function playSound(sound) {
sfx.pause();
sfx.currentTime = 0;
sfx.src = sound;
sfx.play().catch(e => e);
}
Note: You may want to replace the arrow function with an anonymous function for backward compatibility.
In IE 11 I used combined variant:
player.currentTime = 0;
player.pause();
player.currentTime = 0;
Only 2 times repeat prevents IE from continuing loading media stream after pause() and flooding a disk by that.
What's wrong with simply this?
audio.load()
As stated by the spec and on MDN, respectively:
Playback of any previously playing media resource for this element stops.
Calling load() aborts all ongoing operations involving this media element

Add attribute to html tag with Javascript

I am using a Javascript code to detect if a video is loaded.
Once it is loaded I want to add an autoplay attribute to the <video> tag to make it play but I can't find a way to add that attribute. Here is the code I use:
window.addEventListener('load', function() {
var video = document.querySelector('#bgvid');
var div = document.getElementById('#bgvid');
function checkLoad() {
if (video.readyState === 4) {
alert('video is loaded')
video.setAttribute("autoplay")
} else {
setTimeout(checkLoad, 100);
}
}
checkLoad();
}, false);
******************* THE SOLUTION ********************
First, thanks DontVoteMeDown for the help.
Proper code should be:
document.getElementById('bgvid').addEventListener('canplaythrough', function() {
this.play();
});
Why not add the attribute to the tag? From the docs:
autoplay: (...) the video will automatically begin to play back as soon as it can do so without stopping to finish loading the data.
So I presume (not sure, indeed) that the video will start playing as soon it loads a part of the video.
Anyway, if you still want to add the attribute, the video tag has some Events, from docs:
canplay: Sent when enough data is available that the media can be played, at least for a couple of frames;
canplaythrough: Sent when the ready state changes to CAN_PLAY_THROUGH, indicating that the entire media can be played without interruption(...);
So you can use one of those events to set the attribute, e.g:
document.getElementById('bgvid').addEventListener('canplay', function() {
this.setAttribute("autoplay", "autoplay");
});
With this you can avoid using timeouts, which isn't the best approach.
with autoplay enabled there is no need to check its load state, the video will simply play when it can, is loaded.
video.autoplay = true;
Look here

Play selected audio while pausing/resetting others

I have two audio elements that play through a button's click event. I've successfully managed to pause one if another is selected but also need to set the paused element back to 0.0 seconds (i.e pause and reset).
I'm aware that Javascript currently doesn't have a stop() method which led assume that this would be done by setting its currentTime to 0. If so I just haven't been able to figure out the best way to incorporate this method in my code.
Right now I'm pausing all audio elements in the latter half of the conditional using $(".audio").trigger("pause"); which doesn't too efficient performance wise. What would be the best way to pause and reset only the previously played audio file and not every one on the page?
http://jsfiddle.net/txrcxfpy/
use below code . check DEMO
$(function() {
$('.track-button').click(function() {
var reSet = $('.track-button').not($(this)).siblings(".audio").get(0);
reSet.pause();
reSet.currentTime = 0;
var $this = $(this),
trackNum = $this.text(),
currentAudio = $this.siblings(".audio"),
audioIsPaused = currentAudio.get(0).paused;
if (audioIsPaused) {
currentAudio.get(0).play();
} else {
currentAudio.get(0).pause();
}
});
});

Categories