LATER EDIT
This question should be deleted, because the problem I'm reporting isn't even real.
There was no 'long chain of closures', that was me misunderstanding the google chrome watch window.
Any memory leak may be caused by the video elements not cleaning up properly. This is a different problem which has been addressed in other questions.
Here's some code to do this (handling images or videos)
var IMAGE = 0; var VIDEO = 1; var mediaElements = [{ url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Rembrandt_van_Rijn_-_Self-Portrait_-_Google_Art_Project.jpg/180px-Rembrandt_van_Rijn_-_Self-Portrait_-_Google_Art_Project.jpg', mediaType: IMAGE }, { url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/English_Pok%C3%A9mon_logo.svg/269px-English_Pok%C3%A9mon_logo.svg.png', mediaType: IMAGE }, { url: 'http://www.w3schools.com/html/mov_bbb.mp4', mediaType: VIDEO }, { url: 'https://upload.wikimedia.org/wikipedia/en/thumb/3/34/Teuchitlan_scale_model_1_cropped.jpg/133px-Teuchitlan_scale_model_1_cropped.jpg', mediaType: IMAGE }, { url: 'https://upload.wikimedia.org/wikipedia/en/f/f7/Sugimoris025.png', mediaType: IMAGE } ]; $(function () { function displayMediaFile(mediaFile) { $('#imageTarget').empty(); if (mediaFile.mediaType === IMAGE) { $('#imageTarget').append($('<img />').attr('src', mediaFile.url)); return new Promise(function (resolve, reject) { window.setTimeout(resolve, 2000); }); } else { var videoElement = $('<video></video>').attr('autoplay', ''); var sourceElement = $('<source><source>') .attr('src', mediaFile.url) .attr('type', 'video/mp4') .appendTo(videoElement); videoElement = videoElement.appendTo('#imageTarget'); return new Promise(function (resolve, reject) { videoElement[0].onended = resolve; }); } }; function iterateThroughPlaylist(index) { if (index >= mediaElements.length) { index = 0; } console.log('displaying image ' + index); displayMediaFile(mediaElements[index]).then(function () { iterateThroughPlaylist(index + 1); }); } iterateThroughPlaylist(0); });
See this jsfiddle for a complete working example.
So the actual 'displayMediaFile' function returns a Promise. This promise resolves when the play is complete, and then we move on to the next image.
The problem is, it stops running after a while. I put a breakpoint in the iterateThroughImages
method, then looked at the call stack. I could see an extremely long chain of closures.
Can I produce code with the same simplicity, but somehow avoid having the runtime keep the closures in memory?
Promises don't seem to be the best solution here.
var imageUrls = [ url1, url2, url3, url4 ];
(function play(index){
console.log('displaying image ' + index);
var url = imageUrls[index];
$('#imageTarget').empty().append($('<img />').attr('src', url));
window.setTimeout(play, 2000, (index+1)%imageUrls.length);
})(0);
You could still use promises inside if you have other operations to chain but don't build an infinite chain of promises.
Side note: you don't really have to delete and recreate the img
element, you could simply change its src
property.
This is a case where promises are not the best solution. As long term stability is an issue it's better to make iterateThroughPlaylist
run again more directly with :
window.setTimeout(iterateThroughPlaylist, 2000);
videoElement[0].onended = iterateThroughPlaylist;
As with promises, there's no recursion because iterateThroughPlaylist
will be called in a later event turn in both cases.
$(function () {
var mediaTypes = { 'IMAGE':0, 'VIDEO':1 },
mediaElements = [
...
...
...
],
index = -1;
function displayMediaFile(index, fn) {
var mediaFile = mediaElements[index];
$('#imageTarget').empty();
switch(mediaFile.mediaType) {
case mediaTypes.IMAGE:
$('#imageTarget').append($('<img/>').attr('src', mediaFile.url));
window.setTimeout(fn, 2000);
break;
case mediaTypes.VIDEO:
var videoElement = $('<video/>')
.attr('autoplay', '')
.append($('<source/>').attr({ 'src': mediaFile.url, 'type': 'video/mp4')})
.appendTo('#imageTarget');
videoElement[0].onended = fn;
break;
default:
//do nothing and let the sequence lapse, or
//eg. window.setTimeout(fn, 2000); to keep trying
}
};
function iterateThroughPlaylist() {
index = (index + 1) % mediaElements.length;
displayMediaFile(index, arguments.callee);
}
iterateThroughPlaylist();
});
Notes
arguments.callee
as a callback is just a way of not hard-coding the function name iterateThroughPlaylist
displayMediaFile
or iterateThroughPlaylist
, so only the outermost $(function () {...}
forms a closure as expected. $(function () {...}
structure avoids the need to use the global namespace. switch/case
will make it easier to add further media types if necessary. <img>
and <video>
elements up front and reuse them.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.