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>
Related
The situation is like this - I have two videos, positioned on top of each other. These videos are almost the same (content wise), when you enter the page, video 1 starts playing. There is also a button, which when pressed should sync the second video to the first one and slowly fadeIn (it's hidden at first).
The problem I am having is with the syncing, when I try to set the currentTime of the second video, the player obviously first buffers some frames and after that starts playing, causing desync. I also tried some syncing timeouts with setting currentTime each 10ms until the second video's readyState turns to 4, but that causes pretty big delay.
The code I am using so far:
function switchVideo(first, second) {
var currentTime = first.currentTime;
second.pause();
second.currentTime = currentTime;
second.play();
syncAllowed = true;
var videoInterval = setInterval(function() {
if(next.readyState >= 4) {
second.addClass('show');
syncAllowed = false;
setTimeout(function() {
first.pause();
}, 500);
clearInterval(videoInterval);
}
}, 100);
}
var syncVideos = setInterval(function() {
if(syncAllowed) {
second.currentTime = first.currentTime;
}
}, 10);
So the question is, is there any way to sync one video to another one that is already playing, without having too much delay for the first video to get enought data for playing after setting it's current time? Also I noticed that there is a little bit of lag going backwards in time, which is weird, because that portion of video should already be buffered.
Thanks for any tips!
After the video ends i want it to be played again, but this time from a different start time. I have added a video ended event handler :
video.addEventListener('ended', handleVideoEnd);
And my handleVideoEnd looks like this:
function handleVideoEnd(e) {
if (currentVideo.loop) {
var video = document.getElementById('video');
video.currentTime = 1;
video.play();
}
}
Unfortunately when i use that code my video becomes corrupted for a couple of seconds resulting in that effect: http://screenshot.sh/m1UaF580NNx8B
This problem, however does not appear if i set the currentTime to 0.
I'm using webm video and testing it in chrome currently. Did anyone have a similar issue when seeking a video part?
With some codecs and containers, it's possible for a video to change resolution mid-stream. This is particularly common with RTC-style video streams where resolution can scale up/down based on available bandwidth. In other cases, the recording device might be rotated and the video may flip from portrait to landscape or vice versa.
When playing these videos on a web page (simple <video> tag), how can I detect when this change in size occurs with JavaScript?
The best I can think of is verifying the size of the video every frame, but there is quite a bit of overhead to this method. If there were a way to have a callback fired when the video changed sizes, or an event triggered, that'd be best.
Example video that resizes, severely: https://bugzilla.mozilla.org/attachment.cgi?id=8722238
There is now a resize event which fires when the video resolution changes.
HTML
<p data-content="resolution"></p>
<video src="https://bug1250345.bmoattachments.org/attachment.cgi?id=8722238"></video>
JavaScript
document.querySelector('video').addEventListener('resize', (e) => {
document.querySelector('[data-content="resolution"]').textContent = [
e.target.videoWidth,
e.target.videoHeight
].join('x');
});
(JSFiddle: http://jsfiddle.net/qz61o2xt/)
References:
HTML Living Standard Media Events - resize
Chromium Bug: Add a 'resize' event to elements for when the video data changes dimensions
Chromium Bug: Fire 'resize' event on initial metadata load, too.
look if this helps I'm not sure.
Link - https://www.w3.org/2010/05/video/mediaevents.html
You can use loadedmetadata event to define global variables or utilize Element.dataset to reflect initial .videoWidth, .videoHeight properties of <video> element; at timeupdate event of <video> initially stored and current event .videoWidth, .videoHeight values, if one of the properties changed call function
window.onload = function() {
function handleResolutionChange(event) {
// do stuff if `.videoWidth` or `.videoHeight` changed from initial value
}
var video = document.querySelector("video");
video.addEventListener("loadedmetadata", function(event) {
event.target.dataset.width = event.target.videoWidth;
event.target.dataset.height = event.target.videoHeight;
})
video.addEventListener("timeupdate", function(event) {
if (+event.target.dataset.width !== event.target.videoWidth
&& +event.target.dataset.height !== event.target.videoHeight) {
// call `handleResolutionChange` one or more times
// if `event.target` `.videoWidth` or `.videoHeight` changed
handleResolutionChange.call(event.target, event)
}
})
}
I currently have an HTML5 video event issue in Safari. I am playing a single video on my page. The video loads and plays correctly. However, the play event does not always fire. If the user:
Clicks play
Watches the video to the end (ended event fires)
Clicks play again
The play event does not fire on the second click. If I pause/play the movie at that time, the correct events fire.
How can I make the video tag's play event fire if the video has completed and the user presses play again?
drawVidPlayer is called with the videos index as part of the page render
function drawVidPlayer(vindex){
var turl=vidList[vindex]['thumbUrl'];
var vurl=vidList[vindex]['url'];
var valias=vidList[vindex]['type'];
destroyVidPlayer();
$('#mediaspot').css('backgroundColor', '#000000');
$('#mediaspot').show();
$('#mediaspot').html('<video controls="controls" id="twnvideo" poster="'+turl+'" style="height:225px; width:460px;"><source src="'+vurl+'" type="video/ogg" /><source src="'+vurl+'" type="video/mp4" /><source src="'+vurl+'" type="video/webm" />Your browser does not support the video tag.</video>').appendTo('#wrap_media_vod');
var velem=document.getElementsByTagName('video')[0];
velem.addEventListener('play', initVidTimer, false);
velem.addEventListener('pause', killVidTimer, false);
velem.addEventListener('ended', killVidTimer, false);
}
function destroyVidPlayer(){
var velem=document.getElementsByTagName('video')[0];
if(velem!=undefined){
velem.removeEventListener('play', initVidTimer);
velem.removeEventListener('pause', killVidTimer);
velem.removeEventListener('ended', killVidTimer);
}
$('#mediaspot').empty();
$('#mediaspot').html('');
}
function initVidTimer(){
if(activityTimer==null){
external.OnUserActivity(19);
activityTimer=setInterval(function(){
external.WriteLog('activity timer running');
external.OnUserActivity(19);
}, 5000);
}
}
function killVidTimer(){
clearInterval(activityTimer);
activityTimer=null; // Kill keepAlive timer
var velem=document.getElementsByTagName('video')[0];
external.WriteLog(velem.ended);
}
HTML5 now specifies that the browser must throw the timeupdate, paused, and ended events when the playback position reaches the end of a media file, but the spec wasn't always that clear. As a result, this behavior is inconsistent between browsers. Some don't set paused=true or fire the paused event when the file ends.
In your Safari issue, paused is still equal to false when the video starts to play for the second time - so there is no reason for the browser to fire the play event again.
This may no longer be an issue in Safari 6, but it still breaks in IE 9. Take a look at the End Events column in this chart from longtailvideo.com - they outline the inconsistencies well.
It would easy to normalize this issue with a couple lines of code - like this:
$("video").on("ended", function () {
if (!this.paused) this.pause();
});
This puts the video in the paused state on ended, so it will throw the play event correctly on replay.
You can try this working sample in IE 9 or to see what I mean on this jsfiddle: http://jsfiddle.net/PWnUb/
I had the same issue, I solved with a bit of jquery:
function videoend(){
var duration = $("video").get(0).duration;
var current = $("video").get(0).currentTime;
if(current==duration){
//Whatever you wanna do when video ends
};
}
$(document).ready(function(){
setInterval("videoend()", 200); //or any other time you wanna use
});
Hope this helps.
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"