简体   繁体   中英

Node.js hangs on multiple setTimeout looping calls

I'm trying to write class that will allow to me to pause the playing of an audio file. The class accepts raw PCM data, and you provide to the class how often to send sample chunks. For example, you can specify every 20 millisecond to pass a chunk through. The class also implements a pause() and resume() function.

The class also uses a SerialQueue module which I wrote to make sure that data isn't be sliced and concatenated at the same time on the buffer. So you'll see a lot of references to that in the following code.

The issue that I'm having is that it will call setTimeout a bunch of times, but then finally freeze randomly on a setTimeout. My processor usage will spike immediately and nothing else happens.

Here is the entire code plus my test code:

var nopus = require( './lib/node-opus' );
var Speaker = require( 'speaker' );
var fs = require( 'fs' );
var path = require( 'path' );
var SerialQueue = require( './lib/serial-queue' );
var Transform = require( 'stream' ).Transform;
var inherits = require( 'util' ).inherits;

function ThrottlePCM( opts ) {
    // Default to an empty object
    if( !opts )
        opts = {};

    // Pass through the options
    Transform.call( this, opts );

    this.milliseconds = opts.milliseconds | 20;
    this.bitDepth = opts.bitDepth | 16;
    this.channels = opts.channels | 1;
    this.sampleRate = opts.sampleRate | 48000;

    // Set our frame size
    if( this.milliseconds==2.5 )
        this.frameSize = this.sampleRate/400;
    else if( this.milliseconds==5 )
        this.frameSize = this.sampleRate/200;
    else if( this.milliseconds==10 )
        this.frameSize = this.sampleRate/100;
    else if( this.milliseconds==20 )
        this.frameSize = this.sampleRate/50;
    else if( this.milliseconds==40 )
        this.frameSize = this.sampleRate/25;
    else if( this.milliseconds==60 )
        this.frameSize = 3*this.sampleRate/50;
    else
        throw new Error( "Millisecond value is not supported." );

    this.bytesPerBeat = this.frameSize*this.bitDepth/8*this.channels;

    console.log( "Bytes per beat %d.", this.bytesPerBeat );

    this.buffer = null;
    this.queue = new SerialQueue();

    // Taken from TooTallNate
    this.totalBytes = 0;
    this.startTime = Date.now();
    this.pauseTime = null;

    // Can we pass
    this.canPass = true;
    this.paused = false;
    this.flushCallback = null;
}

inherits( ThrottlePCM, Transform );

ThrottlePCM.prototype._transform = function( data, encoding, done ) {
    var that = this;

    this.queue.queue( function() {
        // Append the buffer
        if( that.buffer )
            that.buffer = Buffer.concat( [ that.buffer, data ] );
        else
            that.buffer = data;

        // Nen no tame
        if( that.canPass )
            that.passThrough();
    } );

    // We are ready for more data
    done();
};

ThrottlePCM.prototype.pause = function() {
    this.paused = true;
    this.pauseTime = Date.now();
};

ThrottlePCM.prototype.resume = function() {
    this.paused = false;
    this.startTime+= Date.now()-this.pauseTime;

    console.log( "Difference is %d: %d", Date.now()-this.pauseTime, this.startTime );

    var that = this;

    this.queue.queue( function() {
        that.passThrough();
    } );

};

ThrottlePCM.prototype.passThrough = function() {
    // Are we paused?
    if( this.paused ) {
        this.canPass = true;
        return;
    }

    // No pass now
    this.canPass = false;

    // The rest of us
    var that = this;
    var totalBeats = (Date.now()-this.startTime)/this.milliseconds;
    var expected = totalBeats*this.bytesPerBeat;

    function passMe() {
        console.log( "== Inkasemeen" );
        that.queue.queue( function() {
            if( !that.buffer ) {
                // Should we just flush?
                if( that.flushCallback ) {
                    var callback = that.flushCallback;
                    that.flushCallback = null;

                    console.log( "Antipass" );

                    callback();
                }
                else
                    that.canPass = true; // We can pass now from on timer
                return;
            }

            var output;

            if( that.buffer.length>that.bytesPerBeat ) {
                output = that.buffer.slice( 0, that.bytesPerBeat );
                that.buffer = that.buffer.slice( that.bytesPerBeat );
            }
            else {
                output = that.buffer;
                that.buffer = null;
            }

            that.push( output );
            that.totalBytes+= output.length;

            // Re-call us
            that.passThrough();
        } );
    }

    console.log( "--\nTotal Beats: %d\nTotal Bytes: %d\nExpected: %d\nBytes Per Beat: %d\nMilliseconds: %s", totalBeats, this.totalBytes, expected, this.bytesPerBeat, this.milliseconds );

    if( this.totalBytes>expected ) {
        var remainder = this.totalBytes-expected;
        var sleepTime = remainder/this.bytesPerBeat*this.milliseconds;

        console.log( "++\nSleep time: %d", sleepTime );

        if( sleepTime ) {
            setTimeout( passMe, sleepTime );
        }
        else {
            passMe();
        }
    }
    else {
        console.log( "Bytes are higher by %d (%d-%d)", expected-this.totalBytes, expected, this.totalBytes );
        passMe();
    }
};

ThrottlePCM.prototype._flush = function( done ) {
    console.log( "Flush called." );

    // No action here I don't think
    this.flushCallback = done;

    var that = this;

    this.queue.queue( function() {
        // Show ourselves flushy
        if( that.canPass )
            that.passThrough();
    } );
};

var format = {
    channels: 1,
    bitDepth: 16,
    sampleRate: 48000,
    bitrate: 16000,
    milliseconds: 60
};

var rate = nopus.getFrameSizeFromMilliseconds*format.channels*nopus.binding.sizeof_opus_int16;
var speaker = new Speaker( format );
var decoder = new nopus.Decoder( format );
var throttle = decoder.pipe( new ThrottlePCM( format ) );

throttle.pipe( speaker );

var file = fs.createReadStream( path.join( __dirname, 'files/audio/233' ) );

file.pipe( decoder );

This produces the following output:

Bytes per beat 5760.
--
Total Beats: 0.1
Total Bytes: 0
Expected: 576
Bytes Per Beat: 5760
Milliseconds: 60
Bytes are higher by 576 (576-0)
== Inkasemeen
--
Total Beats: 0.15
Total Bytes: 1920
Expected: 864
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 11
== Inkasemeen
--
Total Beats: 0.26666666666666666
Total Bytes: 7680
Expected: 1536
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 64
== Inkasemeen
--
Total Beats: 1.3666666666666667
Total Bytes: 13440
Expected: 7872
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 58
== Inkasemeen
--
Total Beats: 2.3833333333333333
Total Bytes: 19200
Expected: 13728
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 57
== Inkasemeen
--
Total Beats: 3.283333333333333
Total Bytes: 24960
Expected: 18912
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 63
== Inkasemeen
--
Total Beats: 4.35
Total Bytes: 30720
Expected: 25055.999999999996
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 59.000000000000036

It hangs at different points throughout. As you can see it stops RIGHT before the passMe function is called and "== Inkasemeen" is printed.

My Node.js version is v0.10.30.

As always, thanks so much!

Found the issue! It turns out that Node.js doesn't like you passing a decimal to setTimeout! Rounding off the decimal fixed the issue.

if( sleepTime>0 ) {
    setTimeout( passMe, sleepTime|0 );
}
else {
    passMe();
}

Let me know if this code is useful to anybody. If it is I can post a github when it's all finished. It works completely, now, though.

Also note that there is a Throttle module out already from TooTallNate that probably satisfies most people's needs for throttling streams https://github.com/TooTallNate/node-throttle .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM