90

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?

sparkey0
  • 1,641
  • 1
  • 12
  • 14
  • 2
    I am updating the answer as things have evolved since this question was asked! I believe the 'delete' fix was almost certainly a browser bug at that time, and these days, i'd recommend pausing the video element, removing the src attribute and triggering reload. After that it can be safely removed from the DOM. – sparkey0 Aug 01 '18 at 19:48

18 Answers18

172

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.

Sanborn
  • 289
  • 1
  • 2
  • 14
toon lite
  • 1,844
  • 2
  • 11
  • 2
  • 3
    The `videoElement.load()` seems to be the key so that it works in Firefox. – nils Nov 06 '15 at 14:14
  • 1
    This solution solved a problem of video not playing after being removed and re-added to the DOM in chrome v.47. – CodeToad Dec 29 '15 at 13:34
  • I also had to videoElement.innerHTML="" too otherwise it would keep loading the source. also above link is now dead. – danielgormly Feb 15 '16 at 06:04
  • 1
    This work as it stops video from playing, yet throws the MEDIA_ERR_SRC_NOT_SUPPORTED error, so watch out if you've got an event listener for errors – Voy Mar 29 '16 at 17:40
  • http://stackoverflow.com/a/40419032/782599 this answer doesn't fire `error` event on element. – Paul Annekov Nov 04 '16 at 09:23
  • This answer solved my issue on iOS Safari - I was changing the html of the containing div but eventually videos stopped playing. Adding the above *before* constructing a new – alitheg Jan 17 '17 at 17:47
  • 4
    IE and Edge sends a request to the server if `src` is set to `""`, similar to `img` elements. The attribute should instead be removed, [as recommended by the standard](https://html.spec.whatwg.org/multipage/embedded-content.html#best-practices-for-authors-using-media-elements). – Timothy003 May 17 '17 at 16:38
  • For Edge, my experience is if src = "", then load operation will fail, and if you were to reuse the node and try to set src and load, load will fail. That mean video.play() will not play. – Alocus Jun 04 '18 at 21:03
  • anyone how can i avoid MEDIA_ERROR_DECODE? I am doing the above recommended way of tearing the video element. – Samson Maben Dec 08 '20 at 07:08
19

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.

Ken Redler
  • 23,863
  • 8
  • 57
  • 69
  • Okay, so you put me on the right track -- this = null; did not work, but delete(this); did! – sparkey0 Jul 15 '10 at 19:48
  • Glad to hear it. I'll update the answer to include your findings. – Ken Redler Jul 15 '10 at 19:59
  • Thanks again! Btw, this=null threw an error -- should be avoided. – sparkey0 Jul 15 '10 at 20:13
  • 4
    The `delete` statement simply won't do anything because it only deletes properties. It would be exactly the same, I believe, if it were removed. – Casey Chu Jul 15 '10 at 20:15
  • 1
    Oddly enough it worked - might just be a combination of quirks in Safari or WebKit. Removing that line causes memory to creep up continuously, with it in, it will increase, then drop suddenly, i'm assuming when the gc runs periodically – sparkey0 Jul 15 '10 at 20:25
  • What browsers does this example succeed in? IE, Edge? – BBaysinger Aug 30 '18 at 19:46
6

To reset the video to Blank without removing it

$("#video-intro").first().attr('src','')

It stops the video

Bishoy Hanna
  • 4,539
  • 1
  • 29
  • 31
5
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.

Arnaud Leyder
  • 6,674
  • 5
  • 31
  • 43
  • Latest Chrome (48) working here. I used this technique and it cleaned up my memory leak. I think browser's should automatically clear the `.src` property if an innerHTML was used to replace whatever video element it was in. But, that's for a different topic, thanks/ – NiCk Newman Mar 04 '16 at 15:59
4

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.

Community
  • 1
  • 1
Paul Annekov
  • 3,193
  • 3
  • 19
  • 31
3

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();
sparkey0
  • 1,641
  • 1
  • 12
  • 14
3

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]);
        }
    }
}
tldr
  • 116
  • 3
2

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);
Ganzert
  • 48
  • 4
2
var video = document.getElementById('video');
        if (video.firstChild) {
            video.removeChild(video.firstChild);
            video.load();
        }
Quan Quan
  • 21
  • 2
2

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

BBaysinger
  • 6,614
  • 13
  • 63
  • 132
2

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.

kregus
  • 1,003
  • 10
  • 18
1

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

Community
  • 1
  • 1
Kenneth Bo Christensen
  • 2,256
  • 2
  • 18
  • 21
1

Not much complicated. Just put your src to null.

Eg: document.querySelector('#yourVideo').src = null;

It will remove your video src attribute. Done.

  • In FF/Chrome (and maybe other browsers), this produces an extra network request to URI "null" relative to the current document URL, as the attribute value is converted to string. – Sage Pointer Jul 31 '21 at 21:34
1

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.

1

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.

Nishant Patel
  • 1,334
  • 14
  • 22
0

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>
carlosrafaelgn
  • 831
  • 1
  • 16
  • 19
0

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()
})
lintanfu
  • 46
  • 4