How to properly unload/destroy a VIDEO element - javascript

I'm working on a realtime media browsing/playback application that uses <video> objects in the browser for playback, when available.
I'm using a mix of straight javascript, and jQuery,
My concern is specifically with memory. The application never reloads in the window, and the user can watch many videos, so memory management becomes a large concern over time. In testing today, I see the memory profile jumping by the size of the video to be streamed with each subsequent load, and never dropping back down to the baseline.
I've tried the following things with the same result:
1 - Empty the parent container containing the created element, eg:
$(container_selector).empty();
2 - Pause and remove children matching 'video', and then empty the parent container:
$(container_selector).children().filter("video").each(function(){
this.pause();
$(this).remove();
});
$(container_selector).empty();
Has anyone else run into this issue, and is there a better way to do this?

It is very tricky to dispose video from the DOM structure. It may lead to browser crashing. Here is the solution that helped me in my project.
var videoElement = document.getElementById('id_of_the_video_element_here');
videoElement.pause();
videoElement.removeAttribute('src'); // empty source
videoElement.load();
this will reset everything, silent without errors !
Edit: Here are the full details as recommended in the Standard: https://html.spec.whatwg.org/multipage/media.html#best-practices-for-authors-using-media-elements
Hope it resolve your query.

This "solution" is reported to work, presumably because it would make those video container objects available for garbage collection (see the note below for a discussion of why delete shouldn't be making a difference). In any case, your results are likely to vary by browser:
$(container_selector).children().filter("video").each(function(){
this.pause(); // can't hurt
delete this; // #sparkey reports that this did the trick (even though it makes no sense!)
$(this).remove(); // this is probably what actually does the trick
});
$(container_selector).empty();
Note: There's no doubt that the delete keyword is specified only to remove properties from objects (as others have pointed out in the comments). Logging this to the console both before and after the delete this line, above, shows the same result each time. delete this should do nothing and make no difference. Yet this answer continues to receive a trickle of votes, and people have reported that omitting delete this makes it stop working. Perhaps there's strangeness in how some browser JS engines implement delete, or an unusual interaction between a browser's delete and what jQuery is doing with this.
So, just be aware, if this answer solves your problem, that if it does work, it's not clear why that's the case, and it's just as likely to stop working for any number of reasons.

To reset the video to Blank without removing it
$("#video-intro").first().attr('src','')
It stops the video

delete(this);
is not a solution. If it worked for x or y it is a browser misbehaviour. Read here:
The delete operator removes a property from an object.
The truth is that some browsers (Firefox for example) will cache in memory the video buffer when autoplay property is on. It is a pain to deal with.
Removing the video tag from the DOM or pausing it can only produce unstable results. You have to unload the buffer.
var video = document.getElementById('video-id');
video.src = "";
My experiment shows that it is done as so but unfortunately this is browser implementation not completely specified by the spec. You do not need to call load() after src change. When changing the src of a video tag you implicitly call a load() on it, this is stated in the W3C spec.

This snippet doesn't do any effecient DOM manipulations (no tag removal) and doesn't fire error event for <video> unlike this answer:
var video = document.getElementById('video');
video.removeAttribute('src');
video.load();
Furthermore, it doesn't fire loadstart event. And it's like it should work - no video, no load start.
Checked in Chrome 54 / FF 49.

Just to clarify for anyone trying this later, the solution was this: (confirmed with h264 videos in Safari 5.0, untested in FF/opera yet)
$(container_selector).children().filter("video").each(function(){
this.pause();
delete(this);
$(this).remove();
});
$(container_selector).empty();

I was having an issue while dynamically loading some videos. I had two sources in my <video> element. One mp4 and the other webm as fallback. So I had to iterate through the <source>'s like so.
function removeMedia(){
let videos = document.getElementsByTagName('video');
for(let vid in videos){
if(typeof videos[vid] == 'object'){
let srcs = videos[vid].getElementsByTagName('source');
videos[vid].pause();
for(let xsrc in srcs){
if(srcs[xsrc].src !== undefined){
srcs[xsrc].src = '';
}
}
videos[vid].load();
videos[vid].parentNode.removeChild(videos[vid]);
}
}
}

ok, here's a simple solution which certainly works:
var bodypage = document.getElementsByTagName('body')[0];
var control_to_remove = document.getElementById('id_of_the_element_here');
bodypage.removeChild(control_to_remove);

According to this bug:
https://bugs.chromium.org/p/chromium/issues/detail?id=255456&can=2&q=255456&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
this seems to be a memory leak issue in Chrome!

var video = document.getElementById('video');
if (video.firstChild) {
video.removeChild(video.firstChild);
video.load();
}

I've encountered this problem on a more complicated level where we are loading ~80 videos on a page, and having problems with memory management in IE and Edge. I posted our solution on a similar question I asked specifically about our issue: https://stackoverflow.com/a/52119742/1253298

My code did not use a <video> element with a src tag, but instead used multiple <source> children to set a video in multiple formats.
To properly destroy and unload this video, I had to use a combination of multiple answers on this page, which resulted in:
var videoElement = $('#my-video')
videoElement[0].pause() // Pause video
videoElement.empty() // Remove all <source> children
videoElement.load() // Load the now sourceless video
delete videoElement // The call mentioned in other answers
videoElement.remove() // Removing the video element altogether
Hope this helps someone.

Here is an answer on how to close the camera - not only pausing. It is the stream that should be stopped - not the video elements reference:
stream.stop()

Not much complicated. Just put your src to null.
Eg: document.querySelector('#yourVideo').src = null;
It will remove your video src attribute. Done.

This is what I did to solve this problem.
I created 2 video elements (video1 & video2).
After finished using video1, get the source(src) attribute value and then remove video1 from DOM.
Then set video2 source (src) to whatever value you got from video1.
Do not use stream from video1 as it is cached in memory.
Hope this will help.

One solution that worked for me in AngularJS is using below code:
In case you don't want to remove your source url, and reset to start of the video
let videoElement = $document[0].getElementById('video-id');
videoElement.pause();
videoElement.seekable.start(0);
videoElement.load();
And in case you want to remove the source from video tag:
let videoElement = $document[0].getElementById('video-id');
videoElement.pause();
videoElement.src="";
videoElement.load();
Hope someone finds it useful.

I know this is an old question, but I came across the same issue, and tried almost every solution mentioning <video>'s src attribute, and all solutions seemed to have their drawbacks.
In my specify case, besides <video> elements, I am also using <audio> elements at the same time.
I was reading an article at MDN when I realized that dealing with the src attribute could be the wrong thing to do. Instead, I rewrote all my code to append and remove <source> elements to both <video> and <audio> elements.
That was the only way I found that does not trigger a new load or generates error or other undesirable notifications.
This is a minimal/simplified version of the code I am using (tested on Firefox 86 and Chrome 88).
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-us">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui, shrink-to-fit=no" />
</head>
<body>
<button type="button" onclick="play()">Play</button>
<button type="button" onclick="stop()">Stop</button>
<video id="myVideo"></video>
<script type="text/javascript">
"use strict";
var myVideo = document.getElementById("myVideo");
myVideo.onloadstart = () => {
console.log("onloadstart");
};
myVideo.onloadeddata = () => {
console.log("onloadeddata");
};
myVideo.onload = () => {
console.log("onload");
};
myVideo.onerror = () => {
console.log("onerror");
};
function play() {
while (myVideo.firstChild)
myVideo.removeChild(myVideo.firstChild);
var source = document.createElement("source");
source.src = "example.mp4";
myVideo.appendChild(source);
myVideo.load();
myVideo.play();
}
function stop() {
while (myVideo.firstChild)
myVideo.removeChild(myVideo.firstChild);
myVideo.load();
}
</script>
</body>
</html>

In my case, i used the solution mentioned above by #toon lite:
Array.from(document.getElementsByTagName('video')).forEach(video => {
video.pause();
video.removeAttribute('src');
video.load();
})
But it occurs the another problem in Chrome browser (version 93):
[Intervention] Blocked attempt to create a WebMediaPlayer as there are too many WebMediaPlayers already in existence. See crbug.com/1144736#c27
I guess it is all about the browser version's limit (mine is too old), anyway i fixed this bug by adding some extra operations:
video.src = '';
video.srcObject = null;
video.remove()
Finally the code looks like:
Array.from(document.getElementsByTagName('video')).forEach(video => {
video.pause();
video.removeAttribute('src'); // video.src = '' works so this line can be deleted
video.load();
video.src = '';
video.srcObject = null;
video.remove()
})

Related

Start multiple downloads and get feedback when download prompt window showed

I'm currently writing a little program that generates an html file and opens it with the default browser to start multiple downloads.
I don't want to open a tab/window for every download, so creating hidden iframes for the downloads seemed like a good solution.
I'm using onload on the iframes to find out if the download prompts for each download have shown up yet. This approach seems to be very unreliable in the Internet Explorer though.
So I'm wondering if there is there a way to fix this or maybe a better approach?
(Without libraries please.)
Here is my html/js code:
<!DOCTYPE html>
<!-- saved from url=(0016)http://localhost -->
<html><head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<title>Downloads</title>
<script>
"use strict";
var downloadsInfo = {
"http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe":"Status: Connecting",
"http://download.piriform.com/ccsetup410.exe":"Status: Connecting"
};
var i = 0;
var iv = setInterval(function() {
i = ++i % 4;
var j = 0;
var finished = true;
for (var key in downloadsInfo) {
var value = downloadsInfo[key];
if (value != "Status: Download Started!") {
value = value+Array(i+1).join(".");
finished = false;
}
document.getElementsByTagName("div")[j].innerHTML = key+"<br/>"+value;
j = j+1;
}
if (finished) {
alert('Done! You can close this window/tab now.');
clearInterval(iv);
}
}, 800);
</script>
</head><body>
<h3>Please wait for your downloads to start and do not reload this site.</h3>
<div></div> <br/><br/>
<div></div> <br/><br/>
<iframe src="http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe" onload="downloadsInfo['http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe'] = 'Status: Download Started!';" style="display:none"></iframe>
<iframe src="http://download.piriform.com/ccsetup410.exe" onload="downloadsInfo['http://download.piriform.com/ccsetup410.exe'] = 'Status: Download Started!';" style="display:none"></iframe>
</body></html>
Quite simply you can't know whether a native browser download started. Every browser has different ways this is handled, the user may set up his browser to prompt the location or he might just let it auto download to the Downloads folder (the default in most browsers nowadays). If he's prompting for a location he might cancel by mistake, yet your setup would still claim the download started. So, no, there is no way whatsoever to reliably inform the user that they can close a tab once all downloads are started/finished... provided that you use the native browser download mechanism.
The way to achieve this effect would be possibly by first downloading the file using Javascript (requiring you to have access to those files, hotlinking to third party files is of course not an option then). To see this in action try downloading a file from mega.nz. I was planning on writing up how to do this by hand, but there is already a nice (quite outdated) answer outlining this.
If the intention is only to ensure that the download has started you could implement a trigger on the back end to note when the file has been accessed. In it's simplest form this would look like:
Page download.html requests file.php?location=[...]&randomHash=1234
Once file.php is actually loaded it will set a flag in memory or the database that randomHash id 1234 has started.
file.php redirects the page with a 302 header to the actual file location.
download.html checks periodically using Ajax whether flag randomHash=1234 has been raised. If so it knows the download has started.
Indeed IE is reported to not always behave nicely with the onload event handler of iframes. There is an active bug tracker record opened.
The problem is discussed in a number of places around the web, and what seems to be the most reliable solution is to have an indirect download with nested iframes: the iframe loads a HTML file with an iframe that loads the file to download. The reason for that is that IE does not seem to like iframes that point to something else than HTML. So if you have the possibility to do that in your program:
For each file to download, generate a HTML file with a body that looks like this:
<iframe src="http://filetodownload.exe" style="display:none"></iframe>
Store this file in a temporary folder, e.g. C:\tmp\filetodownload.html
In your "master" generated HTML file, replace the iframe source with this intermediate file:
<iframe src="C:\tmp\filetodownload.html"
onload="downloadsInfo['http://filetodownload.exe']='Status: Download Started!';"
style="display:none"></iframe>
That may do the trick. But following IE's tradition, this could or could not work depending on the case...
If it does not work, some solutions that have proved useful include:
Put the onload handler in a function, and write in the definition of the iframe: onload="return theonloadfunction()" (even if the function does not return anything)
Instead of using the onload attribute, attach the event handler in javascript, like so:
iframe = document.getElementById("theiframeid")
iframe.attachEvent("onload", theonloadfunction);`
Note that attachEvent is for IE only. If you want to support other browsers you will have to detect it and use addEventListener for the non-IE cases.
Finally, you may try combinations of two or more of these solutions :)
<html>
<head>
<meta content = 'text/html;charset=utf-8' http-equiv = 'Content-Type'>
<meta content = 'utf-8' http-equiv = 'encoding'>
<script>
/*
First, I removed the setInterval(). Since you rely on the onload property we can aswell just check it on each onload.
Second, I changed your downloadsInfo to an object array.
Also be aware while testing, that some browsers cache your cancel/block choice and do not reask again for the same url.
Additionally firefox does not fire on frame downloads.
Furthermore the alert in your test might not show for overlapping or setting reasons.
*/
var downloadsInfo = [
{url: "http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe", Status: "Connecting"},
{url: "http://download.piriform.com/ccsetup410.exe", Status: "Connecting"}
];
//IE has a problem in sometimes merely firing the onload propery once, which we bypass by dynamically creating them
//It is also less limited.
function iframeConnect(){
for(var i=0, j=downloadsInfo.length; i<j; i++){
var tF = document.createElement('iframe');
tF.arrayIndex = i; //For convenience
tF.style.display = 'none';
//Normal load event, working in ie8-11, chrome, safari
tF.onload = function(){
iframeExecuted(this.arrayIndex);
};
//Workaround for firefox, opera and some ie9
tF.addEventListener('DOMSubtreeModified', function(){
iframeExecuted(this.arrayIndex);
}, false);
document.body.appendChild(tF);
tF.src = downloadsInfo[i].url;
}
}
function iframeExecuted(i){
downloadsInfo[i].Status = 'Executed';
var tStatus = iframeFinished();
var tE = document.querySelector('h3');
if (tStatus.Done) tE.innerHTML = 'Finished'
else tE.innerHTML = 'Processed ' + tStatus.Processed + ' of ' + tStatus.Started;
}
function iframeFinished(){
for(var i=0, j=downloadsInfo.length; i<j; i++){
if (downloadsInfo[i].Status != 'Executed') break;
}
//Note that the Processed value is not accurate, yet it solves is testing purpose.
return {Done: (i == j), Processed: i, Started: j}
}
</script>
</head>
<body onload = 'iframeConnect()'>
<h3>Please wait for your downloads to start and do not reload this site.</h3>
</body>
</html>

Check to see if a video loads in iPad with JavaScript

I am having a bit of an issue with checking to see if a video is being loaded in iPad. I need to check and see if loads because I am looping through to load all videos with an increment like video_1.mp4, video_2.mp4, video_3.mp4. However, it seems like it ignores the "readyState" and goes straight to the else statement.
Here is the code:
function loadMedia() {
var media = document.getElementsByTagName("video")[0];
if (media.readyState === 4) {
alert("Video has been loaded!");
} else {
alert("Video hasn't been loaded!");
}
}
Is readyState supported by iPad?
Edit: Added more code.
The loadMedia function is binded to window.onload via an anonymous function.
window.onload = (function () {
loadMedia();
});
Here is the HTML:
<video class="video" controls="controls" poster="images/posters/tb_1.jpg" preload="metadata">
<source src="media/tb_1.mp4" type="video/mp4; codecs='avc1.42E01E, mp4a.40.2'" />
We apologize, but your browser does not support this video. Please consider an upgrade.
</video>
Apple documentation seems to at least suggest that readyState for media elements has been around since iOS 3.0:
http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLMediaElementClassReference/HTMLMediaElement/HTMLMediaElement.html
That's sort of indirect documentation, perhaps, but it's something.
Since firing up a JS debugger on devices can be annoying, you might want to change this:
alert("Video hasn't been loaded!")
To this:
alert("Video hasn't been loaded! " + media.readyState);
If you turn on the console in the browser (and, if you like, switch to console.log() rather than alert()), this should tell you if your problem is that readyState is undefined (and therefore likely not supported) or if the problem is that maybe media is undefined (seems unlikely).
Of course, you may have also found a bug. You might want to search Apple's bug tracker, if they allow such things.

How do you check if a HTML5 audio element is loaded?

I am wanting to know how to check if a HTML5 audio element is loaded.
To find out when the audio is ready to start playing, add listeners for the oncanplay or oncanplaythrough events. To find out when the audio has loaded at all, listen to the onloadeddata event:
<audio oncanplay="myOnCanPlayFunction()"
oncanplaythrough="myOnCanPlayThroughFunction()"
onloadeddata="myOnLoadedData()"
src="myaudio.ogg"
controls>
Download
</audio>
<script>
function myOnCanPlayFunction() { console.log('Can play'); }
function myOnCanPlayThroughFunction() { console.log('Can play through'); }
function myOnLoadedData() { console.log('Loaded data'); }
</script>
Check out robertc's answer for how to use event listeners. You can also directly check an audio element's ready state:
var myAudio = $('audio')[0];
var readyState = myAudio.readyState;
readyState will be a number. From Mozilla's docs:
0 - No information is available about the media resource.
1 - Enough of the media resource has been retrieved that the metadata attributes are initialized. Seeking will no longer raise an exception.
2 - Data is available for the current playback position, but not enough to actually play more than one frame.
3 - Data for the current playback position as well as for at least a little bit of time into the future is available (in other words, at least two frames of video, for example).
4 - Enough data is available—and the download rate is high enough—that the media can be played through to the end without interruption.
Another option is networkState:
var myAudio = new Audio(url);
var networkState = myAudio.networkState;
networkState is a helpful companion property to the previously mentioned readyState, and can be handy in certain contexts where readyState might fall short, such as discerning whether or not a file is likely going to load at all. This can be done by checking it at set intervals.

MediaElement.js setSrc not working for flash fallbacks on FF, IE7-8

I've seen a few discussions about this, but no real answers. I've had a lot of success getting mediaelement.js working for me except that it simply will not let me setSrc() on flash fallbacks. This is a huge bummer after so much work.
For a little background I'm using mediaelement-and-player.js v2.1.9 and using their player's API to change the media src via player.setSrc. I'm playing audio MP3s.
I'm getting this error in FF Mac:
this.media.setSrc is not a function
And this error in IE8 Win:
SCRIPT445: Object doesn't support this action
I find it hard to believe that this wasn't fully tested given that it seems a base part of their API. I've seen some other issues about similar problems but again, no real answers.
You would need to add "flashmediaelement.swf" to your code.
Had the same problem. Solved it by adding non-empty src and type="audio/mp3" attributes:
<audio id="player" controls src="#" type="audio/mp3" preload="none"></audio>
Presence of preload="none" is recommended here, because without it the element will send an additional request to a current page's URL in an attempt to download the audio.
Update: found an alternative way, zero-length WAV file can be embedded in src, thus you may use preload attribute normally and stop worrying about that an unneeded request will be sent if a user will click the play button before you set normal src.
<audio id="player" controls type="audio/mp3" src="data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=">
Don't worry about type and src incompatibility, because, according to audio element specification, type isn't legal attribute of audio tag at all (type is only a source tag's attribute), here it's placed only to fix MediaElement.js behavior.
I answered a similar question on github. Here's my solution:
This occurs when the setSrc method is called too soon after initializing the mediaElement player. Due to the flash fallback the swf (and therefore its api methods) will not be available until the success event is fired. After that setSrc works fine in IE8..
I didn't want to set the initial source from within the success handler. Therefore I used a boolean var to check whether the success event had occurred. In my source setting method I check for its value and use recursiveness (with a setTimeout to prevent overkill) whenever the boolean var equals false.. Did the trick for me.
//create the tag
var video = $("<video>",{id:"videoElement",width:640,height:360}).appendTo('body');//jquery
var mediaElementInitialized = true
//create the mediaelement
var mediaElement = new MediaElementPlayer("#videoElement",{
/**
* YOU MUST SET THE TYPE WHEN NO SRC IS PROVIDED AT INITIALISATION
* (This one is not very well documented.. If one leaves the type out, the success event will never fire!!)
**/
type: ["video/mp4"],
features: ['playpause','progress','current','duration','tracks','volume'],
//more options here..
success: function(mediaElement, domObject){
mediaElementInitialized = true;
},
error: function(e){alert(e);}
}
);
var setSource = function(src){
if(mediaElementInitialized == true){
if(mediaElement){
mediaElement.setSrc(src);
mediaElement.play();
}
} else {
//recursive.. ie8/flashplayer fallback fix..
var self = this;
setTimeout(function(){
self.setSource(src);
},100);
}
}
var plugin = new MediaElementPlayer(#mplay_audio_p',
{
//...params...
});
var url="http://www.somesite.com/audiofile.mp3";
plugin.setSrc(url);
plugin.load();
plugin.play();

Playing dynamically embedded sound object via Javascript

I need to background load some WAV files for an HTML page using AJAX. I use AJAX to get the details of the WAV files, then use the embed tag, and I can confirm that the files have loaded successfully because when I set autostart to true, the files play. However, I need the files to play only when the user clicks on a button (or an event is fired). The following is my code to preload these files:
function preloadMedia() {
for(var i = 0; i < testQuestions.length; i++) {
var soundEmbed = document.createElement("embed");
soundEmbed.setAttribute("src", "/media/sounds/" + testQuestions[i].mediaFile);
soundEmbed.setAttribute("hidden", true);
soundEmbed.setAttribute("id", testQuestions[i].id);
soundEmbed.setAttribute("autostart", false);
soundEmbed.setAttribute("width", 0);
soundEmbed.setAttribute("height", 0);
soundEmbed.setAttribute("enablejavascript", true);
document.body.appendChild((soundEmbed));
}
}
I use the following code to play the file (based on what sound file that user wants to play)
function soundPlay(which) {
var sounder = document.getElementById(which);
sounder.Play();
}
Something is wrong here, as none of the browsers I have tested on play the files using the code above. There are no errors, and the code just returns.
I would have left it at that (that is - I would have convinced the client to convert all WAV's to MP3 and use MooTools). But I realized that I could play the sound files, which were not dynamically embeded.
Thus, the same soundPlay function would work for a file embeded in the following manner:
<embed src="/media/sounds/hug_sw1.wav" id="sound2" width="0" heigh="0" autostart="false" enablejavascript="true"/>
anywhere within the HTML.
And it plays well in all the browsers.
Anyone have a clue on this? Is this some sort of undocumented security restriction in all the browsers? (Please remember that the files do get preloaded dynamically, as I can confirm by setting the autostart property to true - They all play).
Any help appreciated.
Hmm.. perhaps, you need to wait for the embed object to load its "src" after calling preloadMedia() ?
Are you sure that the media file is loaded when you call soundPlay() ?
i know your question got a bit old by now, but in case you still wonder...
soundEmbed.setAttribute("id", testQuestions[i].id);
you used the same id twice, yet getElementById returns only one element, or false if it doesn't find exactly one matching element.
you could try something like this:
soundEmbed.setAttribute("id", "soundEmbed_"+testQuestions[i].id);
always keep in mind that an id must be unique
Just a tip for more compatibility:
I read here that width and height need to be > 0 for Firefox on MacOS.

Categories

Resources