【问题标题】:How to decode only part of the mp3 for use with WebAudio API?如何仅解码部分 mp3 以与 WebAudio API 一起使用?
【发布时间】:2016-02-21 15:58:47
【问题描述】:

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

我的用例如下:

  • 确定要播放的文件
  • 确定声音的开始和停止
  • 加载文件[我用的是BufferLoader]
  • 播放

很简单。

现在我只是抓取 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);
};

【问题讨论】:

  • 您是否正在将 MP3 转换为 WAV 文件? 900MB 太疯狂了。
  • 我没有找到直接使用mp3的方法,总是要解码。
  • 如果你只是想玩它为什么不使用audio标签?
  • @LJᛃ 我目前正在这样做,但它非常不准确。我必须与它同步字幕,而且ontimeupdate 事件的触发频率不够高,无法很好地替代我以前的代码。

标签: javascript web-audio-api


【解决方案1】:

无法使用 decodeAudioData() 进行流式传输,您需要将 MediaElement 与 createMediaStreamSource 一起使用,然后运行您的内容。 decodeAudioData() 无法流式传输。@zre00ne 并且 mp3 将被大解码!!!好大!!!

【讨论】:

  • 对,就是这样。 20MB mp3 变成 900MB PCM :/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-15
  • 1970-01-01
  • 1970-01-01
  • 2012-05-23
  • 1970-01-01
  • 1970-01-01
  • 2020-05-14
相关资源
最近更新 更多