Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Staggered asset loading

edited February 2015 in Help! with 2.0
My SugarCube story has custom sounds and art for every passage. The only problem with this... is that Leon's sound macros try to load every single sound at the start of the game. :P

This could prove unpleasant - I don't want people to sit there for 5 minutes before they can play. So is there a way to change the macros to preload, but staggered? Like on game start, 10% of the assets get loaded, then at certain checkpoints it loads more in?

Comments

  • The loading of your external audio resources should happen in the background (i.e. asynchronously), so there shouldn't be any extra startup delay.
  • Oh, cool, my mistake then. I thought from the way it was worded it'd load everything in at once.
  • All of the requests happen at story startup, yes.  However, browsers take the requests asynchronously, loading the audio in the background, so you do not end up waiting around for each to complete.

    Beyond that, (if I recall correctly!) Leon's sound macros do not actually request that the audio sources be loaded.  They simply create a cache of HTMLAudioElement objects, one for each unique piece of audio.  For each of those audio objects, they neither set the preload attribute (so its default value is browser-defined, though metadata is recommend), nor call the load() method (which requests that the browser load the audio source now, in the background).  So, essentially, the macros leave the decision of when to load the audio sources completely up to the browser.
  • Does that basically mean that when a user starts my story, the browser will be loading all the audio in as they read the first few pages?

    If so, would it be a good idea (and even possible?) to specify certain audio elements to be loaded first so that they're ready for the very first passage?

    I have no idea how browsers work but I'm just trying to think through how to handle an audio-heavy game.
  • Claretta wrote:

    Does that basically mean that when a user starts my story, the browser will be loading all the audio in as they read the first few pages?


    No.  It means that the browser will decide when to load the audio.  How the browser decides when to do so varies to some extent from browser to browser.  Though, obviously, when you try to play an audio clip, it will be loaded if it hasn't already been.

    The programmatic nature of the Twine story formats actually hurts here, since the browser can't prefetch link contents to see what resources the next "pages" might require.


    Claretta wrote:

    If so, would it be a good idea (and even possible?) to specify certain audio elements to be loaded first so that they're ready for the very first passage?


    Having another macro in the set whose purpose was to tell the browser to queue one or more clips for loading ASAP could be useful, yes.  And yes, it would be possible (it should even be easy).
  • Instead of coding that macro in, can I cheat by going:

    <<playsound "drums.ogg">>
    <<stopsound "drums.ogg">>

    ?
  • Actually, here's an unofficial update to Leon's sound macros:
    • Fixed a caching bug which would cache the same audio source multiple times, thereby lobotomizing the caching algorithms in some browsers causing huge delays in loading the resources (affected every browser I tested in save Firefox).
    • All operations which might play audio will now check to see if the resource in question has been loaded and if it has not will request that it be loaded (required by certain browser and preload property combinations).
    • Improved the story data discovery code to work in all compatible story formats in both versions of Twine (read: SugarCube in both versions of Twine and the vanilla formats in Twine 1).
    • Added the &lt;&lt;loadsound&gt;&gt; macro.

    /*

    These macros accept either strings or string variables as their first argument. I recommend you set the filenames to specific variables and then use those as arguments to the macros.
    <<loadsound "carolofthebells.mp3" >> requests that the file "carolofthebells.mp3" be immediately queued for loading, if it hasn't already been loaded.
    <<playsound "carolofthebells.mp3" >> plays the file "carolofthebells.mp3" from the start.
    <<loopsound $heartbeat >> starts playing $heartbeat, over and over. Note: currently browsers are not that good at looping audio seamlessly - brief silences between loops may occur.
    <<fadeinsound $heartbeat >> is identical to loopsound, but fades in the sound over 2 seconds.
    <<unloopsound $heartbeat >> makes $heartbeat no longer repeat when it finishes.
    <<stopsound "birds.ogg" >> stops playing "birds.ogg". When <<playsound "birds.ogg" >> is used again, it will start from the beginning.
    <<fadeoutsound "birds.ogg" >> is identical to stopsound, but fades out the sound over 2 seconds.
    <<pausesound "trees.ogg" >> pauses "trees.ogg" at its current location. Use <<playsound "trees.ogg" >> to resume it.
    <<stopallsound>> stops all the sounds.

    */
    (function() {
    "use strict";
    version.extensions['soundMacros'] = {
    major: 1,
    minor: 2,
    revision: 0
    };
    var p = macros['playsound'] = {
    soundtracks: {},
    handler: function(a, b, c, d) {
    var loop = function(m) {
    if (m.loop == undefined) {
    m.loopfn = function() {
    this.play();
    };
    m.addEventListener('ended', m.loopfn, 0);
    } else m.loop = true;
    m.play();
    };
    var s = eval(d.fullArgs());
    if (s) {
    s = s.toString();
    var m = this.soundtracks[s.slice(0, s.lastIndexOf("."))];
    if (m) {
    if (b == "loadsound" || b == "playsound" || b == "loopsound" || b == "fadeoutsound" || b == "fadeinsound") {
    if (m.readyState < HTMLAudioElement.HAVE_CURRENT_DATA) {
    m.preload = "auto";
    m.load();
    }
    }
    if (b == "playsound") {
    m.play();
    } else if (b == "loopsound") {
    loop(m);
    } else if (b == "pausesound") {
    m.pause();
    } else if (b == "unloopsound") {
    if (m.loop != undefined) {
    m.loop = false;
    } else if (m.loopfn) {
    m.removeEventListener('ended', m.loopfn);
    delete m.loopfn;
    }
    } else if (b == "stopsound") {
    m.pause();
    m.currentTime = 0;
    } else if (b == "fadeoutsound" || b == "fadeinsound") {
    if (m.interval) clearInterval(m.interval);
    if (b == "fadeinsound") {
    if (m.currentTime > 0) return;
    m.volume = 0;
    loop(m);
    } else {
    if (!m.currentTime) return;
    m.play();
    }
    var v = m.volume;
    m.interval = setInterval(function() {
    v = Math.min(1, Math.max(0, v + 0.005 * (b == "fadeinsound" ? 1 : -1)));
    m.volume = Math.easeInOut(v);
    if (v == 0 || v == 1) clearInterval(m.interval);
    if (v == 0) {
    m.pause();
    m.currentTime = 0;
    m.volume = 1;
    }
    }, 10);
    }
    }
    }
    }
    };
    macros['loadsound'] = p;
    macros['fadeinsound'] = p;
    macros['fadeoutsound'] = p;
    macros['unloopsound'] = p;
    macros['loopsound'] = p;
    macros['pausesound'] = p;
    macros['stopsound'] = p;
    macros['stopallsound'] = {
    handler: function() {
    var s = macros.playsound.soundtracks;
    for (var j in s) {
    if (s.hasOwnProperty(j)) {
    s[j].pause();
    if (s[j].currentTime) {
    s[j].currentTime = 0;
    }
    }
    }
    }
    };

    var store = document.querySelector("tw-storydata");
    if (store == null) {
    store = document.querySelector("#store-area,#storeArea");
    }
    if (store == null) {
    return false;
    }
    store = store.firstChild;

    var fe = ["ogg", "mp3", "wav", "webm"];
    while (store != null) {
    var b = String.fromCharCode(92);
    var q = '"';
    var re = "['" + q + "]([^" + q + "']*?)" + b + ".(ogg|mp3|wav|webm)['" + q + "]";
    k(new RegExp(re, "gi"));
    store = store.nextSibling;
    }

    function k(c, e) {
    do {
    var d = c.exec(store.innerHTML);
    if (d &amp;&amp; !macros.playsound.soundtracks.hasOwnProperty(d[1])) {
    var a = new Audio();
    if (a.canPlayType) {
    for (var i = -1; i < fe.length; i += 1) {
    if (i >= 0) d[2] = fe[i];
    if (a.canPlayType("audio/" + d[2])) break;
    }
    if (i < fe.length) {
    a.src = d[1] + "." + d[2];
    a.interval = null;
    macros.playsound.soundtracks[d[1]] = a;
    } else console.log("Browser can't play '" + d[1] + "'");
    }
    }
    } while (d);
    }
    }());

  • Claretta wrote:

    Instead of coding that macro in, can I cheat by going:

    <<playsound "drums.ogg">>
    <<stopsound "drums.ogg">>


    That might work.  Still, I'd suggest simply using the updated version above.
  • Thanks! Makes it easier.
Sign In or Register to comment.