Search code examples
htmlaudiohtml5-audio

play audio range in html5


I would like to be able to have buttons that can play certain audio ranges from a larger file. Something like:

<button onclick="playClip('http://blah/source1.mp3', 2.5, 3.0, 1.0)">Play clip 1</button>
<button onclick="playClip('http://blah/source2.mp3', 10.0, 2.0, 0.5)">Play clip 2 slow</button>

where playClip has a pattern like this:

function playClip(src, startOffset, length, rate) {
  // What to put here?
}

Or instead of a length, an ending offset.

Can some one point me to code that can do that, or help me write it? The closest I could find is https://gist.github.com/remy/753003/download# but I need different sized clips, from possibly different files, and with a playback rate specified. I'm afraid I've limited experience with Javascript.

I'm trying to replace a Silverlight app that does this.

Thanks.

-John


Solution

  • Here's an extract of my current code, which uses both the audio control's events and timeout to make sure the audio stops. There's a reference to a volume slider you might need to trim.

    var jt_audioControl;
    var jt_audioSource;
    var jt_audioFiles;
    var jt_audioFileIndex;
    var jt_audioFile;
    var jt_audioStartTime;
    var jt_audioEndTime;
    var jt_audioPlaybackRate;
    var jt_audioTimeoutHandle;
    var jt_audioLink;
    var jt_audioMimeType;
    var jt_audioMediaType;
    var jt_volumeSlider;
    
    function jt_onAudioTimeUpdate() {
        if (jt_audioEndTime > 0.0) {
            if (jt_audioControl.currentTime >= jt_audioEndTime) {
                //alert('stopped: jt_audioControl.currentTime = ' + jt_audioControl.currentTime + ' jt_audioEndTime = ' + jt_audioEndTime);
                jt_audioControl.pause();
                //jt_audioStartTime = jt_audioEndTime = 0.0;
            }
        }
    }
    
    function jt_onAudioCanPlay() {
        jt_audioControl.pause();
        jt_audioControl.currentTime = jt_audioStartTime;
        jt_audioControl.defaultPlaybackRate = jt_audioPlaybackRate;
        jt_audioControl.playbackRate = jt_audioPlaybackRate;
        jt_audioControl.play();
        jt_audioControl.currentTime = jt_audioStartTime;
        jt_volumeSliderChanged();   // Set initial value to slider.
        var timeout = (((jt_audioEndTime - jt_audioStartTime)) / jt_audioPlaybackRate) * 1000;
        //alert('jt_audioEndTime = ' + jt_audioEndTime + ', timeout = ' + timeout);
        if (jt_audioEndTime > 0.0) {
            jt_audioTimeoutHandle = setTimeout(jt_onAudioEnded, timeout);
            //alert('jt_audioTimeoutHandle = ' + jt_audioTimeoutHandle);
        }
        else if (jt_audioTimeoutHandle != null) {
            clearTimeout(jt_audioTimeoutHandle);
            jt_audioTimeoutHandle = null;
        }
    }
    
    function jt_onAudioEnded() {
        //alert('ended called');
        if (jt_audioFiles == null)
            return;
        while (jt_audioControl.position < jt_audioControl.duration)
            ;
        jt_audioFileIndex = jt_audioFileIndex + 1;
        if (jt_audioFileIndex < jt_audioFiles.length) {
            jt_createAudio(jt_audioFiles[jt_audioFileIndex]);
        }
        else {
            jt_audioControl.pause();
            jt_audioFiles = null;
            jt_audioFileIndex = 0;
        }
    }
    
    function jt_onAudioError(e) {
        var msg;
        switch (e.target.error.code) {
            case e.target.error.MEDIA_ERR_ABORTED:
                msg = 'You aborted the video playback.';
                break;
            case e.target.error.MEDIA_ERR_NETWORK:
                msg = 'A network error caused the video download to fail part-way.';
                break;
            case e.target.error.MEDIA_ERR_DECODE:
                msg = 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.';
                break;
            case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
                msg = 'The video could not be loaded, either because the server or network failed or because the format is not supported.';
                break;
            default:
                msg = 'An unknown error occurred.';
                break;
        }
        alert('Error loading media: ' + msg + ' Media file: ' + jt_audioFile + ' MIME type: ' + jt_audioMimeType);
    }
    
    function jt_addSource(url) {
        var srcUrl;
        var doConvertCheck = false;
        var offset = url.lastIndexOf(".");
        if (offset == -1) {
            if (jt_audioSource == null) {
                jt_audioSource = document.createElement('source');
                jt_audioControl.appendChild(jt_audioSource);
            }
            jt_audioMimeType = 'audio/mpeg3';
            jt_audioMediaType = 'audio/mpeg3';
            jt_audioSource.type = jt_audioMediaType;
            jt_audioSource.src = url;
            jt_audioControl.load();
            return;
        }
        var base = url.substr(0, offset);
        var ext = url.substr(offset).toLowerCase();
        var newExt = ext;
        if (jt_audioControl.canPlayType('audio/mpeg3')) {
            jt_audioMimeType = 'audio/mpeg3';
            jt_audioMediaType = 'audio/mpeg3';
            if (ext != '.mp3')
                newExt = '-aa.mp3';
        }
        else if (jt_audioControl.canPlayType('audio/mpeg')) {
            jt_audioMimeType = 'audio/mpeg3';
            jt_audioMediaType = 'audio/mpeg';
            if (ext != '.mp3')
                newExt = '-aa.mp3';
        }
        else if (jt_audioControl.canPlayType('audio/mp3')) {
            jt_audioMimeType = 'audio/mpeg3';
            jt_audioMediaType = 'audio/mp3';
            if (ext != '.mp3')
                newExt = '-aa.mp3';
        }
        else if (jt_audioControl.canPlayType('audio/ogg')) {
            jt_audioMimeType = 'audio/ogg';
            jt_audioMediaType = 'audio/ogg';
            if (ext != '.ogg')
                newExt = '-aa.ogg';
        }
        else {
            alert('Sorry, can not play file: ' + url);
        }
        srcUrl = base + newExt;
        if (srcUrl.lastIndexOf('~', 0) === 0) {
            if (window.location.hostname == '') {
                srcUrl = srcUrl.substr(2);
            }
            else {
                var url = 'http://' + window.location.hostname;
                if (window.location.port.toString() != '')
                    url = usrl + ':' + window.location.port.toString()
                srcUrl = url + srcUrl.substr(1);
            }
        }
        //alert('srcUrl = ' + srcUrl);
        if (jt_audioSource == null) {
            jt_audioSource = document.createElement('source');
            jt_audioControl.appendChild(jt_audioSource);
        }
        jt_audioSource.type = jt_audioMediaType;
        if (doConvertCheck) {
            jt_audioLink = "/ConvertCheck?path=" + srcUrl + "&" + "mimeType=" + jt_audioMimeType
            jt_audioSource.src = jt_audioLink;
        }
        else {
            jt_audioSource.src = srcUrl;
        }
        jt_audioControl.load();
    }
    
    function jt_extractTimeRange(url) {
        var rangeFieldIndex = url.lastIndexOf("#t");
        if (rangeFieldIndex >= 0) {
            var rangeString = url.substr(rangeFieldIndex + 2);
            var range = rangeString.split(',');
            jt_audioStartTime = parseFloat(range[0]);
            jt_audioEndTime = parseFloat(range[1]);
        }
        else {
            jt_audioStartTime = 0.0;
            jt_audioEndTime = 0.0;
            return url;
        }
        return url.substr(0, rangeFieldIndex);
    }
    
    function jt_createAudio(url) {
        url = jt_extractTimeRange(url);
        if (jt_audioControl == null) {
            jt_audioFile = url;
            jt_audioControl = new Audio();
            // The ontimeupdate handler seems to be called unreliably, so we'll use
            // setTimeout as well in the oncanplay handler.
            jt_audioControl.ontimeupdate = jt_onAudioTimeUpdate;
            jt_audioControl.onloadedmetadata = jt_onAudioCanPlay;
            jt_audioControl.onended = jt_onAudioEnded;
            jt_audioControl.onerror = jt_onAudioError;
            jt_addSource(url);
            // We'll let the oncanplay call play once loaded.
        }
        else if (jt_audioFile != url) {
            jt_audioFile = url;
            jt_addSource(url);
        }
        else {
            //jt_onAudioLoaded();
            jt_audioControl.load();
        }
    }
    
    function jt_playAudioFile(url) {
        jt_audioFiles = null;
        jt_audioFileIndex = 0;
        jt_audioStartTime = 0.0;
        jt_audioEndTime = 0.0;
        jt_audioPlaybackRate = 1.0;
        jt_createAudio(url);
    }
    
    function jt_playSlowAudioFile(url) {
        jt_audioFiles = null;
        jt_audioFileIndex = 0;
        jt_audioStartTime = 0.0;
        jt_audioEndTime = 0.0;
        jt_audioPlaybackRate = 0.5;
        jt_createAudio(url);
    }
    
    function jt_playAudioFileList(urls) {
        jt_audioFiles = urls;
        jt_audioFileIndex = 0;
        jt_audioStartTime = 0.0;
        jt_audioEndTime = 0.0;
        jt_audioPlaybackRate = 1.0;
        if ((urls != null) && (urls.length > 0)) {
            jt_createAudio(urls[0]);
        }
    }
    
    function jt_playSlowAudioFileList(urls) {
        jt_audioFiles = urls;
        jt_audioFileIndex = 0;
        jt_audioStartTime = 0.0;
        jt_audioEndTime = 0.0;
        jt_audioPlaybackRate = 0.5;
        if ((urls != null) && (urls.length > 0)) {
            jt_createAudio(urls[0]);
        }
    }
    
    function jt_playAudioFileSegment(url, startTime, endTime) {
        jt_audioFiles = null;
        jt_audioFileIndex = 0;
        url = url + '#t' + startTime.toString() + ',' + endTime.toString();
        jt_audioPlaybackRate = 1.0;
        jt_createAudio(url);
    }
    
    function jt_playSlowAudioFileSegment(url, startTime, endTime) {
        jt_audioFiles = null;
        jt_audioFileIndex = 0;
        url = url + '#t' + startTime.toString() + ',' + endTime.toString();
        jt_audioPlaybackRate = 0.5;
        jt_createAudio(url);
    }
    
    function jt_stopAudio() {
        if (jt_audioControl != null)
            jt_audioControl.pause();
    }
    
    function jt_volumeSliderChanged() {
        if (jt_volumeSlider == null) {
            jt_volumeSlider = document.getElementById('volumeSlider');
            if (jt_volumeSlider == null)
                return;
        }
        var value = jt_volumeSlider.value;
        if (jt_audioControl != null)
            jt_audioControl.volume = value / 10;
        $.ajax({
            url: "/Common/SetUserOptionAjax?key=AudioVolume&value=" + value.toString(),
            type: "POST"
        });
    }