There is a canvas that draws a video . I want to change the source of the video and while doing this just by changing the src attribute. I don’t understand what happens with the canvas in this case, but every time the source changes (or load from the video), the performance drops and the CPU load increases. Obviously, doing something wrong.

Actually, the question is why this is happening and how to correctly change the source of the video in order to avoid possible performance problems?

PS In the snippet, the canvas is small, it starts to lag after 30+ replacements, in the draft, the full screen and two pieces of them, there it is noticeable after 6-8 replacements.

 videos = new Array(); videos[0] = { source: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4" }; videos[1] = { source: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4" }; videos[2] = { source: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4" }; var setSource = function(num) { $('video').attr('src', videos[num].source); } var loadVideo = function() { var i = 0; var loadTimer = setInterval(function() { i += 1; $('video').get(0).load(); if (i == 41) { clearInterval(loadTimer); }; }, 100); } const canvas = document.querySelector('canvas'); const ctx = canvas.getContext("2d"); const video = document.querySelector('video'); video.addEventListener('play', () => { function step() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); requestAnimationFrame(step); } requestAnimationFrame(step); }) let frameCount = function _fc(timeStart) { let now = performance.now(); let duration = now - timeStart; if (duration < 1000) { _fc.counter++; } else { _fc.fps = _fc.counter; _fc.counter = 0; timeStart = now; $("#fps-counter").html(_fc.fps); if (_fc.fps >= 55) { $("#fps-counter").css("color", "green"); } else if (_fc.fps >= 30) { $("#fps-counter").css("color", "orange"); } else if (_fc.fps < 30) { $("#fps-counter").css("color", "red"); } } requestAnimationFrame(() => frameCount(timeStart)); }; frameCount.counter = 0; frameCount.fps = 0; frameCount(performance.now()); 
 canvas, video { position: absolute; right: 0; margin: auto; } canvas { top: 0; width: 300px; border: 1px solid red; } video { bottom: 0; width: 100px; border: 1px solid blue; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p>Set video source</p> <button onclick="setSource(0)">1</button> <button onclick="setSource(1)">2</button> <button onclick="setSource(2)">3</button> <p>video.load() x40</p> <button onclick="loadVideo()">load()</button> <p> fps: <span id='fps-counter'>0</span></p> <video autoplay muted loop src='https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4'></video> <canvas></canvas> 

  • And my video in the canvas does not show at all ( - Stepan Kasyanenko
  • @StepanKasyanenko It seems the opera does not draw a canvas if the video is not displayed. Try it now - Ragtime Kitty

1 answer 1

The point of the play event. Each time a source is changed or loaded, this event is raised. Accordingly, you have a lot of identical calls to the step function. Because of this, there is a leak.

The solution in the example is just proof of concept. In the real code it will be necessary to do something differently.

 videos = new Array(); videos[0] = { source: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4" }; videos[1] = { source: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4" }; videos[2] = { source: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4" }; var setSource = function(num) { $('video').attr('src', videos[num].source); } var loadVideo = function() { var i = 0; var loadTimer = setInterval(function() { i += 1; $('video').get(0).load(); if (i == 41) { clearInterval(loadTimer); }; }, 100); } const canvas = document.querySelector('canvas'); const ctx = canvas.getContext("2d"); const video = document.querySelector('video'); let isPlaying = false; video.addEventListener('play', () => { if (!isPlaying) { console.log('play'); requestAnimationFrame(step); isPlaying = true; } }); function step() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); requestAnimationFrame(step); } let frameCount = function _fc(timeStart) { let now = performance.now(); let duration = now - timeStart; if (duration < 1000) { _fc.counter++; } else { _fc.fps = _fc.counter; _fc.counter = 0; timeStart = now; $("#fps-counter").html(_fc.fps); if (_fc.fps >= 55) { $("#fps-counter").css("color", "green"); } else if (_fc.fps >= 30) { $("#fps-counter").css("color", "orange"); } else if (_fc.fps < 30) { $("#fps-counter").css("color", "red"); } } requestAnimationFrame(() => frameCount(timeStart)); }; frameCount.counter = 0; frameCount.fps = 0; frameCount(performance.now()); 
 canvas, video { position: absolute; right: 0; margin: auto; } canvas { top: 0; width: 300px; border: 1px solid red; } video { bottom: 0; width: 100px; border: 1px solid blue; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p>Set video source</p> <button onclick="setSource(0)">1</button> <button onclick="setSource(1)">2</button> <button onclick="setSource(2)">3</button> <p>video.load() x40</p> <button onclick="loadVideo()">load()</button> <p> fps: <span id='fps-counter'>0</span></p> <video autoplay muted loop src='https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4'></video> <canvas></canvas> 

  • Thanks, now at least I understand the reason. And how is this method bad? On what basis do I fix this? - Ragtime Kitty
  • one
    The way is bad because it is not a way. To understand how to fix, you need to look at the real code. And also the functionality that you need. Globally, you need to write two functions, one will start the requestAnimationFrame cycle, checking that the cycle is already running, the second one to stop. - Stepan Kasyanenko