Alex Ker
Alex Ker

Reputation: 55

JavaScript onload and onerror not being called

var images;
function preloadTrial(actor, event) {
  return new Promise(function(res) {
    var i = 0;
    images = [];
    var handler = function(resolve, reject) {
      var img = new Image;
      var source = '/static/videos/' + actor + '/' + event + '/' + i + '.png';
      img.onload = function() {
        i++;
        resolve(img);
      }
      img.onerror = function() {
        reject()
      }
      img.src = source;
    }
    var _catch = function() { res(images) }
    var operate = function(value) {
      if (value) images.push(value);
      new Promise(handler).then(operate).catch(_catch);
    }
    operate();
  })
}

function playSequence(time){
  var delta = (time - currentTime) / 1000;
  currentFrame += (delta * FPS);
  var frameNum = Math.floor(currentFrame);
  if (frameNum >= numFramesPlay) {
    currentFrame = frameNum = 0;
    return;
  }else{
    requestAnimationFrame(playSequence);
    currentImage.src = images[frameNum];
    currentTime = time;
    console.log("display"+currentImage.src);
  }
};

function rightNow() {
  if (window['performance'] && window['performance']['now']) {
    return window['performance']['now']();
  } else {
    return +(new Date());
  }
};

currentImage = document.getElementById("instructionImage");
// Then use like this
preloadTrial('examples', 'ex1').then(function(value) {
  playSequence(currentTime=rightNow());
});

I wrote a Javascript function that is suppose to load a directory full of numbered .png files. However, I do not know the number of items inside the directory beforehand. So I made a function that continues to store images until the source gives me an error. But when I run the code the program does not even enter the .onload and .onerror functions, resulting in an infinite loop.

Edit: This is my current code. It appears that images are correctly assigned and pushed into the array images. But when I attempt to load it onto a img tag (currentImage.src) and run playSequence, it does not display.

Upvotes: 0

Views: 1720

Answers (3)

T4rk1n
T4rk1n

Reputation: 1470

You could use promises to handle the pre-loading of the images. Chain the resolves on the onload event and reject onerror to end the cycle.

function preloadImages(baseurl, extension, starter) {
  return new Promise(function(res) {
    var i = starter;
    var images = [];
    // Inner promise handler
    var handler = function(resolve, reject) {
      var img = new Image;
      var source = baseurl + i + '.' + extension;
      img.onload = function() {
        i++;
        resolve(img);
      }
      img.onerror = function() {
        reject('Rejected after '+ i + 'frames.');
      }
      img.src = source;
    }
    // Once you catch the inner promise you resolve the outer one.
    var _catch = function() { res(images) }
    var operate = function(value) {
      if (value) images.push(value);
      // Inner recursive promises chain.
      // Stop with the catch resolving the outer promise.
      new Promise(handler).then(operate).catch(_catch);
    }
    operate();
  })
}

To simulate a video player, you can draw on a HTML5 canvas.

function play(canvas, imagelist, refreshRate, frameWidth, frameHeight) {
    // Since we're using promises, let's promisify the animation too.
    return new Promise(function(resolve) {
        // May need to adjust the framerate
        // requestAnimationFrame is about 60/120 fps depending on the browser
        // and the refresh rate of the display devices.
        var ctx = canvas.getContext('2d');
        var ts, i = 0, delay = 1000 / refreshRate;
        var roll = function(timestamp) {
            if (!ts || timestamp - ts >= delay) {
                // Since the image was prefetched you need to specify the rect.
                ctx.drawImage(imagelist[i], 0, 0, frameWidth, frameHeight);
                i++;
                ts = timestamp;
            }
            if (i < imagelist.length) 
                requestAnimationFrame(roll);
            else 
                resolve(i);
        }
        roll();
    })
}

To test I used ffmpeg to cut a video with the following command:

ffmpeg -i input.mp4 -ss 00:00:14.435 -vframes 100 %d.png

And I used devd.io to quickly create a static folder containing the script and images and a basic index.html.

imageroller.js - with the above code.

var preload = preloadImages('/static/videos/examples/testvid/', 'png', 1);
preload.then(function(value) {
    console.log('starting play');
    var canvas = document.getElementById("canvas");
    play(canvas, value, 24, 720, 400) // ~480p 24fps
        .then(function(frame){ 
            console.log('roll finished after ' + frame + ' frames.') 
        })
});

While the preloading of the images was pretty slow, if you keep the number of frames to an acceptable level you can make some nice loops.

Upvotes: 2

Garuno
Garuno

Reputation: 2200

The optimal solution would be to provide a server-side API that tells you beforehand, how many Images there are in the directories. If that is not possible, you should load the images one after the other to prevent excess requests to the server. In this case i would put the image loading code in a separate function and call it if the previous image was loaded successfully, like so:

function loadImage(actor, event, i, loadCallback, errorCallback) {
var image = new Image();
var source ='/static/videos/'+actor+'/'+event+'/'+i+'.png';
image.onload = loadCallback;
image.onerror = errorCallback;
image.src = source;
return image;
}

and then call this function in your while loop and in the loadCallback.

Upvotes: 0

J. Chen
J. Chen

Reputation: 357

I haven't tested the snippet below (and there are probably cleaner solutions) but the idea should be correct. Basically we have a recursive function loadImages(), and we pass in the images array and a callback function. We wait for our current image to load; if it loads, we push it into images and call loadImages() again. If it throws an error, we know we are finished loading, so we return our callback function. Let me know if you have any questions.

function preloadTrial(actor, event) {
  let images = [];
  loadImages(images, actor, event, function () {
    // code to run when done loading
  });
};

function loadImages (images, actor, event, callback) {
  let img = new Image();
  let i = images.length;
  let source ='/static/videos/'+actor+'/'+event+'/'+i+'.png';
  img.onload = function() {
    images.push(img);
    return loadImages(images, actor, event, callback);
  }
  img.onerror = function() {
    return callback(images);
  }
  img.src = source;
}

Upvotes: 0

Related Questions