Detecting video resolution changes - javascript

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)
}
})
}

Related

Why does a <video> stop playing when removed from the DOM, while it can still play detached?

I was working on a custom component and stumbled upon this strange behavior. Basically, it is possible to play a video file without adding a <video> element to the DOM at all.
const video = document.createElement('video');
video.src = "https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c6/Video_2017-03-19_23-01-33.webm/Video_2017-03-19_23-01-33.webm.480p.webm";
function start()
{
if (video.paused)
{
video.play();
console.log('paused', video.paused);
}
}
<div><button onclick='start()'>Start</button></div>
Anyway, if at some point the element is added to the DOM tree and subsequently removed, then the video pauses automatically (!)
const video = document.createElement('video');
video.src = "https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c6/Video_2017-03-19_23-01-33.webm/Video_2017-03-19_23-01-33.webm.480p.webm";
function start()
{
if (video.paused)
{
video.play().then(
() =>
setTimeout(
() =>
{
document.body.removeChild(video);
setTimeout(() => console.log('paused after', video.paused), 0);
},
3000
)
);
document.body.appendChild(video);
console.log('paused before', video.paused);
}
}
<div><button onclick='start()'>Start</button></div>
The same considerations apply to <audio> elements, too.
I have two questions here:
What part of the specification indicates that when a video element is removed from the DOM then it should stop playing?
What is the rationale for allowing detached videos to play but stopping videos when they are removed from the DOM tree? This is the part that surprises me most. If there is a use case for playing detached videos on a page, then why would subsequently detaching a video stop playback? On the other hand, if a detached video is stopped because there is no reason to keep playing it, then why allow it to start detached in first place?
The specification is confusing. First it says:
Media elements that are potentially playing while not in a document must not play any video, but should play any audio component. Media elements must not stop playing just because all references to them have been removed; only once a media element is in a state where no further audio could ever be played by that element may the element be garbage collected.
If I understand this correctly, removing the element from the DOM should stop the video, but the audio should continue.
But later it says:
When a media element is removed from a Document, the user agent must run the following steps:
Await a stable state, allowing the task that removed the media element from the Document to continue. The synchronous section consists of all the remaining steps of this algorithm. (Steps in the synchronous section are marked with ⌛.)
⌛ If the media element is in a document, return.
⌛ Run the internal pause steps for the media element.
Step 3 says to pause the media.
Step 2 seems redundant -- how can the element be in a document if it's being removed from the document? But step 1 could add it back to the document (or a different document). That's why it requires waiting for a stable state (which is why you needed to use setTimeout() in your example).
I think the second quote takes precedence, because when the pause steps are run, the element is no longer "potentially playing".

HTML5 video preload solution

I'm creating a website with some animations. One of them is the logo animation. It's called 'lbv.mp4'. As it has some weight, there's a lag with autoplay, so I decided to show content when it is fully loaded.
The plan is:
the video is checked for loading
start the video
add a class to the page element to trigger the animation
setTimeout for the length of the video which will make visibility: hidden for the clip to open a static image underneath.
This is better described in the following code:
video.addEventListener('loadeddata', function () {
if (video.readyState === 4) {
video.play();
$('#page').addClass('transition');
document.setTimeout(function(){
video.attr('style', 'visibility: hidden');
}, 750);
}
The only problem is that I can't get it working neither with pure JS, neither with JQuery. The video isn't loading, the classes aren't given. Tested in Safari, Chrome and Firefox.
So the final question is: 'Is there another to make it easier, or how to fix my solution?'
Event loadeddata means :
The first frame of the media has finished loading.
readyState === 4 means :
Enough data is available—and the download rate is high enough—that the media can be played through to the end without interruption.
You have chances that loadeddata is triggered but readyState is not 4.
Since loadeddata is triggered only once, the video won't ever play.
You should try to add logs to verify this assumption.
What I would try is the following :
Use <video> with autoplay
Listen for the playing event on the video to start transition
Listen for the ended event on the video to hide it
Reference for attributes, events and readyState:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Video
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState

Are "muted" and "volume = 0.0" the same? Event Listener treats them differently

I'm very new to coding and I've come to a problem I'm not finding a solution to. I'm making a little media player that I plan to use with athletes I coach. My idea is to force them to listen to my comments on game film they watch. So the player pauses if it's volume is too low. I've made it so playback pauses when the volume is reduced to below 10%. However, when the mute button(in the default HTML5 media player) is pressed, playback does not pause.
So are muted and a volume of 0.0 not the same? When I bring the slider all the way down to zero(I can only do this without the alert, or else it gets stuck at 0.1 when the alert fires), the volume/mute button shows mute. But I'm sure that's because I have to pass the 0.1 threshold the event listener is looking for.
This is what my code looks like
video.addEventListener("volumechange", function() {
if (video.volume < 0.1) {
video.pause();
alert('Video Paused: Volume too low');
}
});
I plan to customize the controls of my player anyway, so I think I'll be able to make it work with onclick events pretty easily, but I felt this bit of confusion was worth figuring out in the meantime.
So are volume slider changes all that's included in the volumechange event? Or is there a way to make it so the muted state is recognized by the volumechange event listener? Or a way to make a separate one for muted?(which I know is not an event.) Or should I just table it until I make my custom controls?
Thanks.
The description for the volumechange event says:
Sent when the audio volume changes (both when the volume is set and when the muted attribute is changed).
Assuming this is accurate, your event handler will trigger on mute and unmute, so it should just be a matter of using the muted attribute in your if statement:
if (video.volume < 0.1 || video.muted) {
// Pause.
}
The value of volume is independent of muted and does not change when you mute or unmute.
Just use both video.volume and video.muted together:
if (video.volume < 0.1 || video.muted) {
//your logic
}

How should I register event handlers to a video that is handled by the videojs?

I want to register an event handler to a video that is handled by the videojs but I can`t select the element in a reliable manner because the videojs removes the attributes from the video tag and add them to a container element that it adds.
videojs seems to append the same suffix: _html5_api to every video element ID, when it is wrapped inside the container div. Quoting from the source:
// Update tag id/class for use as HTML5 playback tech
// Might think we should do this after embedding in container so .vjs-tech class
// doesn't flash 100% width/height, but class only applies with .video-js parent
tag.id += '_html5_api';
So, one would argue that, a trivial fix would be something like this:
var vid = document.getElementById("ORIGINALVIDEOID_html5_api")
Of course, this hack lacks reliability since this suffix might change in future versions. However, one thing that is unlikely to be changed in the future, is the presence of the video element (albeit with a different ID) inside the wrapper div.
So, a more reliable way to obtain the video element per se is (assuming that the video tag ID is "cool"):
videojs("cool").ready(function(){
// Approach 1
var video1 = this.contentEl().querySelector("video");
console.log("video1");
console.log(video1);
// Approach 2
var video_id = this.contentEl().querySelector("video").getAttribute("id");
var video2 = document.getElementById(video_id);
console.log("video2");
console.log(video2);
// Not really needed, but here is a test that both approaches yield the same result
console.log("video1 === video2 ?")
console.log(video1===video2)
})
which yields in Firefox:
I included two approaches in the above script: one straightforward and one indirect (via the document and using the acquired ID). Of course you can use whichever of video1 and video2 you want.
A few things to note here:
This works only when inside a videojs().ready() function; this is a way to be 100% sure that the player is loaded
contentEl() returns the wrapper div and then, querySelector() is used on it to access the video element.
The other answers are trying to get a video element within the player but this is flawed as the player tech can be something other than a video element, e.g. the Flash tech. You should use the video.js API to listen to the events which will be surfaced from the tech.
var player = videojs("id");
player.on('play', function() {…});

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

Categories