Preloading/Lazy-loading of external media assets

Hello fellow meteorites,

I’ve been working on a meteor webapp that uses external media files (some large images and some .mp3 files). I’ve been using wavesurfer.js to load my .mp3 files and that’s been working great. However, it takes a few seconds to load the song (and in turn the waveform). What I want to do is preload some of these files – some of the larger images that I know are going to be needed and some of the .mp3 files.

I saw this on StackOverflow but there’s not answer there. I know of the existence of waitOn-lib and meteor-preloader packages, but those are for external JS and CSS libraries.

I’m not convinced that I need a CDN for this app as the files aren’t significantly large – .mp3 files are just over 2mb

What is my best bet in preloading these assets? Has anyone used PreloadJS with Meteor yet? Is that something I should try out or are there better alternatives? Is there a way to run a ‘waitOn’ for media files (and show a spinner while loading)?

Thanks in advance guys!

–AN

I’m not an iron router expert, but I was under the impression that waitOn will…well…wait on until whatever happens happens… Manuel’s tutorial is a nice bit of info and discusses returning objects/arrays/etc in waitOn (though briefly).

http://www.manuel-schoebel.com/blog/iron-router-tutorial

So in theory something like

waitOn: function() { while(1) {}}

would cause the loading template to spin…for all eternity (please, someone correct me)

2 Likes

Forcing iron-router to wait for a few seconds, while displaying the loading spinner, would be one of the ways to hack this together. I’ll take a deeper look at it.

So i’m not sure I 100% follow the requirements but it sounds like you want to load 1 mp3 from wavesurfer right away and when that’s loaded, then start loading the others that might be used?

If so I wouldn’t do this in the router… that ends up being really messy. IR can re-run unexpectedly and can create weird edge case bugs. Instead putting this in the topmost template will help control the logic.

Something like this?


Template.musicPage.onRendered(function(){
  var firstSong = 'example/media/demo.mp3';
  initWaveSurf(firstSong);
});

function initWaveSurf() {
  var wavesurfer = Object.create(WaveSurfer);

  wavesurfer.init({
    container: document.querySelector('#wave'),
    waveColor: 'violet',
    progressColor: 'purple'
  });

  wavesurfer.on('ready', function () {
    wavesurfer.play();
    preloadSongs();
  });

  wavesurfer.load(firstSong);
}

function preloadSongs(wave) {
  waver.load('2.mp3');
  waver.load('3.mp3');
}

I’m not sure if you need to init for every song, if so perhaps save objects to memory and then access them once they really click play.

Or what if you just load the mp3 without wavesurfer so it’s saved in the cache:

function loadSound (src) {
    var sound = document.createElement("audio");
    if ("src" in sound) {
        sound.autoPlay = false;
    }
    else {
        sound = document.createElement("bgsound");
        sound.volume = -10000;
        sound.play = function () {
            this.src = src;
            this.volume = 0;
        }
    }
    sound.src = src;
    document.body.appendChild(sound);
    return sound;
 }

// and then
var sound = loadSound("/second.mp3");  //  preload
sound.play(); // muted so it shouldn't play
1 Like

@SkinnyGeek1010,

Basically my webapp has multiple pages/templates and a certain few of them have to load a song file (.mp3) and display the waveform. What I want to achieve is some sort of preloading/lazy loading system so that when a user clicks is directed to a page with a song, the ‘loading’ template would render – waiting for the loading of the song (external media assets) – and once that’s done it would render the page (with the waveform already displayed and the song already loaded/playable).

I don’t necessarily need to use IR, I just assumed waitOn would be a good way to hook it up to the loading templating system of IR.

That being said, I will give your code a try and get back to you when I have tested it on the app.

Also, if I’m not mistaken, the second code snippet you wrote is to load the .mp3 file in the background. How would I go about telling blaze to wait for that song to load to then render the template?

Cheers!!

I mean, maybe you could do something crazy like using a template that looks like your iron router loading template…and in it’s onRendered() call your external sources and in their success callback then manually call Blaze.render() with the rest of the actual template you want to use (if i am making even the most remote bit of sense in this i’ll be pleased as punch with myself)??? so that way the ONLY way the end template gets rendered is if the wave was successfully loaded or whatever

EDIT: this would not address something like the route rendering twice in a row (which irritates me to no end)

Hmmm i’m not sure if there’s a hook for that. However with that method I think it would be a, load them in the background and whenever they click the play button they should have been loaded a while ago. Do all waveforms need shown right away or at a later time?

For the first that needs to load right away I would use the template’s template-variable to set a loading state to true, init the the wavesurf then set the loading var to false in the wavesurfer on ready callback. Then the loading spinner can operate based on that variable.

Also it sounds like you want to block all rendering of the page until the mp3 is downloaded. If it’s a few mb this could be a really bad use experience… if it’s layed out like soundcloud, perhaps showing a gray box with a spinner where the track waveform would be, then swap the box with the waveform on loaded.

1 Like

@SkinnyGeek1010 the app only ever needs to have one song/waveform loaded at a time. That being said, I want the song to start playing as soon as it’s ready (or template is rendered). With that in mind would you suggest that I use a template-variable or a soundcloud-y div-specific loading approach?

I do see the issue with waiting on a template to load because it needs to download a 2mb file. So I believe the div-specific loader like Soundcloud might be more interesting.

Would something like AJAX loader be my best bet here? Or could I do this entirely on Blaze?

So we have two problems here. 1. How can we play the first one as soon as possible, and 2. How can we preload data for nth plays.

For issue 1, I don’t see a way around setting up a loading indicator. Load as much of the template as possible and then where it makes sense, put a ‘loading’ or spinner while the data is being loaded. Just set a spinner state variable to true, load waveform, and then use it’s ‘ready’ event hook to set the spinner loading state to false (Blaze will re-render).

For issue 2, I would just load the 2 preloaded songs in a hidden container with wavesurfer. Then when you go to re-render it the next time, it should be instant since it’s in the browser cache.

Hope this helps!