如何仅解码部分 mp3 以用于 WebAudio API?

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

在我的网络应用程序中,我需要播放 mp3 文件的一部分。这是一个本地网络应用程序,所以我不关心下载等,一切都存储在本地。

我的用例如下:

很简单。

现在我只是抓取 mp3 文件,在内存中将其解码以用于 WebAudio API,然后播放。 不幸的是,由于 mp3 文件可能会变得很长 [例如 30 分钟的音频],因此内存中的解码文件最多可能占用 900MB。这有点太多了。

有什么选项可以让我只解码文件的一部分吗?我怎样才能检测到从哪里开始以及要走多远? 我无法预测比特率,它可以是恒定的,但我希望它也是可变的。

这是我所做的一个例子: http://tinyurl.com/z9vjy34

代码[我尽量让它紧凑]:

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);
};

无法使用 decodeAudioData() 进行流式传输,您需要将 MediaElement 与 createMediaStreamSource 和 运行 一起使用。 decodeAudioData() 无法在某个部分进行流式传输。@zre00ne 并且 mp3 将被解码大!!!很大!!!