Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decode only part of the mp3 for use with WebAudio API?

In my web application, I have a requirement to play part of mp3 file. This is a local web app, so I don't care about downloads etc, everything is stored locally.

My use case is as follows:

  • determine file to play
  • determine start and stop of the sound
  • load the file [I use BufferLoader]
  • play

Quite simple.

Right now I just grab the mp3 file, decode it in memory for use with WebAudio API, and play it. Unfortunately, because the mp3 files can get quite long [30minutes of audio for example] the decoded file in memory can take up to 900MB. That's a bit too much to handle.

Is there any option, where I could decode only part of the file? How could I detect where to start and how far to go? I cannot anticipate the bitrate, it can be constant, but I would expect variable as well.

Here's an example of what I did: http://tinyurl.com/z9vjy34

The code [I've tried to make it as compact as possible]:

var MediaPlayerAudioContext = window.AudioContext || window.webkitAudioContext;

var MediaPlayer = function () {
    this.mediaPlayerAudioContext = new MediaPlayerAudioContext();
    this.currentTextItem = 0;
    this.playing = false; 
    this.active = false;
    this.currentPage = null;
    this.currentAudioTrack = 0;
};

MediaPlayer.prototype.setPageNumber = function (page_number) {
    this.pageTotalNumber = page_number
};

MediaPlayer.prototype.generateAudioTracks = function () {
    var audioTracks = [];
    var currentBegin;
    var currentEnd;
    var currentPath;
    audioTracks[0] = {
        begin: 4.300,
        end: 10.000,
        path: "example.mp3"
    };
    this.currentPageAudioTracks = audioTracks;
};

MediaPlayer.prototype.show = function () {
    this.mediaPlayerAudioContext = new MediaPlayerAudioContext();
};

MediaPlayer.prototype.hide = function () {
    if (this.playing) {
        this.stop();
    }
    this.mediaPlayerAudioContext = null;
    this.active = false;
};

MediaPlayer.prototype.play = function () {
    this.stopped = false;
    console.trace();

    this.playMediaPlayer();
};

MediaPlayer.prototype.playbackStarted = function() {
    this.playing = true;
};

MediaPlayer.prototype.playMediaPlayer = function () {
    var instance = this;

    var audioTrack = this.currentPageAudioTracks[this.currentAudioTrack];
    var newBufferPath = audioTrack.path;

    if (this.mediaPlayerBufferPath && this.mediaPlayerBufferPath === newBufferPath) {

        this.currentBufferSource = this.mediaPlayerAudioContext.createBufferSource();
        this.currentBufferSource.buffer = this.mediaPlayerBuffer;
        this.currentBufferSource.connect(this.mediaPlayerAudioContext.destination);

        this.currentBufferSource.onended = function () {
            instance.currentBufferSource.disconnect(0);
            instance.audioTrackFinishedPlaying()
        };

        this.playing = true;
        this.currentBufferSource.start(0, audioTrack.begin, audioTrack.end - audioTrack.begin);

        this.currentAudioStartTimeInAudioContext = this.mediaPlayerAudioContext.currentTime;
        this.currentAudioStartTimeOffset = audioTrack.begin;
        this.currentTrackStartTime = this.mediaPlayerAudioContext.currentTime - (this.currentTrackResumeOffset || 0);
        this.currentTrackResumeOffset = null;

    }
    else {
        function finishedLoading(bufferList) {
            instance.mediaPlayerBuffer = bufferList[0];
            instance.playMediaPlayer();
        }

        if (this.currentBufferSource){
            this.currentBufferSource.disconnect(0);
            this.currentBufferSource.stop(0);
            this.currentBufferSource = null;
        }

        this.mediaPlayerBuffer = null;
        this.mediaPlayerBufferPath = newBufferPath;
        this.bufferLoader = new BufferLoader(this.mediaPlayerAudioContext, [this.mediaPlayerBufferPath], finishedLoading);
        this.bufferLoader.load();
    }
};

MediaPlayer.prototype.stop = function () {
    this.stopped = true;
    if (this.currentBufferSource) {
        this.currentBufferSource.onended = null;
        this.currentBufferSource.disconnect(0);
        this.currentBufferSource.stop(0);
        this.currentBufferSource = null;

    }
    this.bufferLoader = null;
    this.mediaPlayerBuffer = null;
    this.mediaPlayerBufferPath = null;
    this.currentTrackStartTime = null;
    this.currentTrackResumeOffset = null;
    this.currentAudioTrack = 0;

    if (this.currentTextTimeout) {
        clearTimeout(this.currentTextTimeout);
        this.textHighlightFinished();
        this.currentTextTimeout = null;
        this.currentTextItem = null;
    }

    this.playing = false;
};

MediaPlayer.prototype.getNumberOfPages = function () {
    return this.pageTotalNumber;
};

MediaPlayer.prototype.playbackFinished = function () {

    this.currentAudioTrack = 0;
    this.playing = false;

};

MediaPlayer.prototype.audioTrackFinishedPlaying = function () {

    this.currentAudioTrack++;

    if (this.currentAudioTrack >= this.currentPageAudioTracks.length) {
        this.playbackFinished();
    } else {
        this.playMediaPlayer();
    }
};

//
//
// Buffered Loader
//
// Class used to get the sound files
//
function BufferLoader(context, urlList, callback) {
    this.context = context;
    this.urlList = urlList;
    this.onload = callback;
    this.bufferList = [];
    this.loadCount = 0;
}

// this allows us to handle media files with embedded artwork/id3 tags
function syncStream(node) { // should be done by api itself. and hopefully will.
    var buf8 = new Uint8Array(node.buf);
    buf8.indexOf = Array.prototype.indexOf;
    var i = node.sync, b = buf8;
    while (1) {
        node.retry++;
        i = b.indexOf(0xFF, i);
        if (i == -1 || (b[i + 1] & 0xE0 == 0xE0 )) break;
        i++;
    }
    if (i != -1) {
        var tmp = node.buf.slice(i); //carefull there it returns copy
        delete(node.buf);
        node.buf = null;
        node.buf = tmp;
        node.sync = i;
        return true;
    }
    return false;
}

BufferLoader.prototype.loadBuffer = function (url, index) {
    // Load buffer asynchronously
    var request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.responseType = "arraybuffer";

    var loader = this;

    function decode(sound) {
        loader.context.decodeAudioData(
                sound.buf,
                function (buffer) {
                    if (!buffer) {
                        alert('error decoding file data');
                        return
                    }
                    loader.bufferList[index] = buffer;
                    if (++loader.loadCount == loader.urlList.length)
                        loader.onload(loader.bufferList);
                },
                function (error) {
                    if (syncStream(sound)) {
                        decode(sound);
                    } else {
                        console.error('decodeAudioData error', error);
                    }
                }
        );
    }

    request.onload = function () {
        // Asynchronously decode the audio file data in request.response
        var sound = {};
        sound.buf = request.response;
        sound.sync = 0;
        sound.retry = 0;
        decode(sound);
    };
    request.onerror = function () {
        alert('BufferLoader: XHR error');
    };
    request.send();
};

BufferLoader.prototype.load = function () {
    for (var i = 0; i < this.urlList.length; ++i)
        this.loadBuffer(this.urlList[i], i);
};
like image 857
Krystian Avatar asked Nov 09 '22 20:11

Krystian


1 Answers

There is no way of streaming with decodeAudioData(), you need to use MediaElement with createMediaStreamSource and run your stuff then. decodeAudioData() cannot stream on a part.@zre00ne And mp3 will be decoded big!!! Verybig!!!

like image 99
Kilian Hertel Avatar answered Nov 14 '22 22:11

Kilian Hertel