【问题标题】:Executing 'realtime' javascript without hanging the browser在不挂起浏览器的情况下执行“实时”javascript
【发布时间】:2010-10-27 13:24:05
【问题描述】:

我正在尝试用 Javascript 编写一个简单的音乐音序器。

声音将由SoundManager2播放

我很快意识到 setTimeout 和 setInterval 对于这种计时是没用的。他们的准确性根本不够好。

所以我正在尝试的是:

创建一个声音事件队列(一个二维数组 [ note, time ])

在while循环中处理队列

在伪代码中可能如下所示:

// The queue of time/note values (millisecs)
var q = [[0, C], [250, D], [500, E]]

var begin = (new Date).getTime()

while(q.length > 0 ){
    var now = (new Date).getTime()
    var eventTime = q[0][0] + begin

    if( now >= eventTime){

        playNote(q[0][1]) // Play the note  
        q.shift()       // Now that the note has been played, discard it.
    }
}

通过调试,我发现这个方法看起来足够精确(playNote 调用是在它应该的确切时间进行的)。

然而,在播放“序列”时,所有其他 Javascript(包括实际发出声音的位)都被暂停,这不足为奇。

这意味着我在运行序列所需的时间内保持沉默,然后执行所有对 playNote 的调用。

我尝试将while循环隔离在它自己的函数中,然后通过setTimeout调用它,希望这会创建一个后台线程来执行它自己的事情(即播放序列)而不会停止所有其他JS执行。那不行

我还尝试使用 setTimeout 调用 playNote 函数,希望虽然 UI 确实被冻结,但至少序列可以正确播放,但这也不起作用。

我也尝试了一种组合,但正如您可能已经猜到的那样,这也不起作用。

所以我的问题是:

如何在运行期间不关闭所有其他代码执行的情况下以精确的时间处理事件队列

【问题讨论】:

    标签: javascript real-time


    【解决方案1】:

    我不知道是否有针对旧浏览器的解决方案,但 web workers 旨在在 JavaScript 中进行并行执行。

    Chrome和Firefox的最新版本都支持,其他浏览器我不知道。

    【讨论】:

    • 旧版浏览器与我无关。这个东西最终将成为一个在线应用程序,可以公平地说“如果你想使用它,你必须 DITCH NS4.6!” (而且我也不需要支持 IE)。但是,如果它可以在移动设备(android - IOS)上的 webkit 中运行,那就太棒了。
    • @Acebone,我相信两个移动平台都支持它,但必须谷歌才能确定。
    • 好吧,由于浏览器的限制,Webworkers 不会在后台实例化 Flash 或 HTML5 音频。所以我的问题仍然存在
    【解决方案2】:

    如果您将 while 循环替换为递归函数(它不必是自执行的),浏览器不会冻结。

    (function foo (q) {
        var now = (new Date).getTime(),
            eventTime = q[0][0] + begin;
        if (q.length > 0) {
            if (now >= eventTime) {
                playNote(q[0][1]); // Play the note
                q.shift(); // Discard played note
            }
            return foo(q); // Pass q into the next round
        } else {
            return; // End the "loop"
        }
    }(q));
    

    编辑:递归调用+计时器精度的问题应该由setInterval处理:

    var timer = setInterval(function foo () {
        var now = (new Date).getTime(),
            eventTime = q[0][0] + begin;
        if (q.length > 0) {
            if (now >= eventTime) {
                playNote(q[0][1]); // Play the note
                q.shift(); // Discard played note
            }
        } else {
            clearInterval(timer); // End the "loop"
        }
    }, 10); // This is in ms (10 is just a random number)
    

    【讨论】:

    • 我能问一下括号是干什么用的吗?
    • 这是一个自执行函数:括号使其成为函数表达式而不是语句,() 运算符调用命名匿名函数并将q 作为参数传递。像这样写它使它像while循环一样,因为它会在 JS 引擎到达它时立即执行。
    • 不幸的是,这会产生如此多的递归调用,只播放一个音符,整个过程被 JSEngine 关闭(至少在 Firefox 中)。
    • 我尝试使用 setTimeout 调用来抑制调用,这样如果距离下一个事件超过 X 毫秒,则在进一步递归之前等待 X - Y 毫秒。这个想法是,如果 Y > 不管 setTimeout 有什么精度,你可以在保持精度的同时抑制递归调用的数量它不起作用(或者至少我不能让它起作用)。所有音符都被弹奏,但任何精度都会丢失
    • setTimeout 真的很容易落后。 (计时器的工作方式意味着你不真的知道你的函数何时会被调用)setInterval 通常更适合这种事情。 (我会更新我的答案。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多