Skip to content

Instantly share code, notes, and snippets.

@leeemon
Forked from remy/audiosprite.js
Created February 24, 2014 13:04
Show Gist options
  • Save leeemon/9188032 to your computer and use it in GitHub Desktop.
Save leeemon/9188032 to your computer and use it in GitHub Desktop.

Revisions

  1. @remy remy created this gist Dec 23, 2010.
    124 changes: 124 additions & 0 deletions audiosprite.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,124 @@
    function Track(src, spriteLength, audioLead) {
    var track = this,
    audio = document.createElement('audio');
    audio.src = src;
    audio.autobuffer = true;
    audio.load();
    audio.muted = true; // makes no difference on iOS :(

    /* This is the magic. Since we can't preload, and loading requires a user's
    input. So we bind a touch event to the body, and fingers crossed, the
    user taps. This means we can call play() and immediate pause - which will
    start the download process - so it's effectively preloaded.
    This logic is pretty insane, but forces iOS devices to successfully
    skip an unload audio to a specific point in time.
    first we play, when the play event fires we pause, allowing the asset
    to be downloaded, once the progress event fires, we should have enough
    to skip the currentTime head to a specific point. */

    var force = function () {
    audio.pause();
    audio.removeEventListener('play', force, false);
    };

    var progress = function () {
    audio.removeEventListener('progress', progress, false);
    if (track.updateCallback !== null) track.updateCallback();
    };

    audio.addEventListener('play', force, false);
    audio.addEventListener('progress', progress, false);

    var kickoff = function () {
    audio.play();
    document.documentElement.removeEventListener(click, kickoff, true);
    };

    document.documentElement.addEventListener(click, kickoff, true);

    this.updateCallback = null;
    this.audio = audio;
    this.playing = false;
    this.lastUsed = 0;
    this.spriteLength = spriteLength;
    this.audioLead = audioLead;
    }

    Track.prototype.play = function (position) {
    var track = this,
    audio = this.audio,
    lead = this.audioLead,
    length = this.spriteLength,
    time = lead + position * length,
    nextTime = time + length;

    clearInterval(track.timer);
    track.playing = true;
    track.lastUsed = +new Date;

    audio.muted = false;
    audio.pause();
    try {
    if (time == 0) time = 0.01; // yay hacks. Sometimes setting time to 0 doesn't play back
    audio.currentTime = time;
    audio.play();
    } catch (e) {
    this.updateCallback = function () {
    track.updateCallback = null;
    audio.currentTime = time;
    audio.play();
    };
    audio.play();
    }

    track.timer = setInterval(function () {
    if (audio.currentTime >= nextTime) {
    audio.pause();
    audio.muted = true;
    clearInterval(track.timer);
    player.playing = false;
    }
    }, 10);
    };

    var player = (function (src, n, spriteLength, audioLead) {
    var tracks = [],
    total = n,
    i;

    while (n--) {
    tracks.push(new Track(src, spriteLength, audioLead));
    }

    return {
    tracks: tracks,
    play: function (position) {
    var i = total,
    track = null;

    while (i--) {
    if (tracks[i].playing === false) {
    track = tracks[i];
    break;
    } else if (track === null || tracks[i].lastUsed < track.lastUsed) {
    track = tracks[i];
    }
    }

    if (track) {
    track.play(position);
    } else {
    // console.log('could not find a track to play :(');
    }
    }
    };
    })('myaudiosprite.mp3', 1, 1, 0.25);

    // myaudiosprite.mp3 is the complete audio sprite
    // 1 = the number of tracks, increase this for the desktop
    // 1 = the length of the individual audio clip
    // 0.25 = the lead on the audio - hopefully zero, but in case any junk is added


    // Usage: player.play(position)