【问题标题】:Synchronised Audio and Canvas同步音频和画布
【发布时间】:2016-01-14 18:26:49
【问题描述】:

问题

我正在尝试为画布的帧计时以与后台播放的音频同步。
当我使用window.requestAnimationFrame 时,您会在一秒钟内在不同的浏览器/计算机上获得不稳定的帧数。我还尝试了setInterval(function(){ //code }, 1);,它比window.requestAnimationFrame 更接近完美,但它仍然没有正确完成工作。


我的代码是这样工作的。

setInterval 每毫秒触发一次,然后检查在 x 时间应该生成哪些对象,它还清除和更新画布。此外,我使用毫秒来计算对象的速度,以便它们在正确的 x 时间到达画布上的某个位置。

有没有更好的方法?
我将在下面列出我的代码,以及我当前的 CodePen 的链接,以便您可以看到它的运行情况。 (点击画布区开始动画背景有音乐要同步,但点击后才会开始播放


Sonar Song on CodePen

var Queues = [
    [1, '#FFFFFF', 'line', true, [20,20], [80,80], 500, 950],
    [2, '#FFFFFF', 'line', true, [80,80], [55,20], 950, 1200]
];

var playing = false;


$(document).click(function(){
    if(playing == false) {
        var audio = document.getElementById("audio");
        audio.currentTime = 0;
        audio.play();
        apparatus();
        playing = true;
    } else {
        var audio = document.getElementById("audio");
        audio.pause();
        playing = false;
        $('body canvas').remove();
    }

});

function apparatus() {

    var canvas, context, toggle, time = 0, vidtime = 0;

    var points = [];

    init();
    function init() {

        canvas = document.createElement( 'canvas' );
        context = canvas.getContext( '2d' );
        canvas.width = $(document).width();
        canvas.height = $(document).height();
        document.body.appendChild( canvas );

    }

    var point = function(options) {
        this.position = {}, this.end_position = {}, this.distance = {}, this.velocity = {}, this.time = {};
        points.push(this);

        // TIMING
        this.time.start = options[6];
        this.time.end = options[7];

        // VECTORS
        this.position.x = options[4][0] * (canvas.width / 100);
        this.position.y = options[4][1] * (canvas.height / 100);
        this.end_position.x = options[5][0] * (canvas.width / 100);
        this.end_position.y = options[5][1] * (canvas.height / 100);

        this.distance.x = Math.abs(this.position.x - this.end_position.x);
        this.distance.y = Math.abs(this.position.y - this.end_position.y);

        if(this.position.x > this.end_position.x) {
            this.velocity.x = -Math.abs(this.distance.x / (this.time.end - this.time.start)); 
        } else {
            this.velocity.x = Math.abs(this.distance.x / (this.time.end - this.time.start)); 
        }
        if(this.position.y > this.end_position.y) {
            this.velocity.y = -Math.abs(this.distance.y / (this.time.end - this.time.start)); 
        } else {
            this.velocity.y = Math.abs(this.distance.y / (this.time.end - this.time.start)); 
        }

        // STYLING
        this.style = options[2];
        this.color = options[1];

        //-- STYLING / STYLE TYPES
        if(this.style == 'line') {
        }
    }

    point.prototype.draw = function() {
        this.position.x += this.velocity.x;
        this.position.y += this.velocity.y;

        context.fillStyle = this.color;
        context.beginPath();
        context.arc( this.position.x, this.position.y, 10, 0, Math.PI * 2, true );
        context.closePath();
        context.fill();
    }

    setInterval(function(){
        time++;
        console.log(time);
        context.fillStyle = '#000000';
        context.fillRect(0, 0,  $(document).width(),  $(document).height());
        for(var i = 0; i < Queues.length; i++) {
            if(time == Queues[i][6]) {
                new point(Queues[i]);
            }
            if(time == Queues[i][7]) {
                console.log('la');
            }
        }
        for(var i = 0; i <= points.length; i++) {
            if(points[i] != null) {
                points[i].draw();
            }
        }
    }, 1);
}

【问题讨论】:

    标签: javascript jquery html canvas


    【解决方案1】:

    同步

    您不能依赖 setInterval 或 setTimeout,因为它们只会响应尽可能接近请求的时间,因此对于同步动画和时间等内容非常不可靠。

    您需要使用一致的时间进行同步。你还必须让你的代码适应迟到。

    日期和请求动画帧

    首先要抓紧时间。 JavaScript 通过 Date 对象以毫秒为单位提供时间。

    以下将返回自过去某个时间以来的当前时间(以毫秒为单位)(我不知道什么时候 1452798031458 毫秒前)

    var currentTime = new Date().valueOf();
    

    或者如果你使用 window.requestAnimationFrame 你会得到时间作为第一个参数

    //  main update loop
    function update(time){
        // time is the millisecond time 
        requestAnimationFrame(update); // request new frame
    }
    update(new Date().valueOf);
    

    如果您希望某事在设定的时间发生,您必须考虑机会之间的时间来测试时间,因为这可能会有所不同。

    定期活动时间

    让我们尝试基于帧时间的计时事件。

    先用一些变量来跟踪时间

    var frameTime;     // time between calls to update
    var lastFrameTime;  // the time of the perviouse call to update
    var eventTime;      // the event start time that we want to stay in sync with
    var timeInterval = 1000; // when we want sync events
    

    然后开始我们想要保持同步的事件。

    function startSound(){
         sound.play(); // assuming you have the sound.
         eventTime = new Date().valueOf(); // now
    }
    

    更新函数会尽可能接近我们想要的时间。

    function update(time){
        frameTime = time - lastFrameTime; // how long since the last call to update     
        var timeSinceEvent = time-eventTime; // get the time since the event started
        var timeSinceLastSync = timeSinceEvent % timeInterval;
    
        // Several options here so explained below
    
        requestAnimationFrame(update); // request new frame
        lastFrameTime = time; // remember the last frame time
    }
    startSound(); // start playing
    lastFrameTime = new Date().valueOf;
    update(new Date().valueOf);
    

    现在您有一些选择。人类的感知在 1/10 秒以下的任何时间都不能很好地分离视觉效果,人类的听觉非常擅长及时分离事件(当在录音中弹吉他时有 10 毫秒的延迟时我会很生气)所以你有考虑您所展示的内容。

    视觉事件

    视觉上是最简单的。您只需在同步时间之后尽可能接近地触发您的 snyc 事件。

    if(timeSinceLastSync > timeInterval){
         // do the visual event
         eventTime += timeInterval;  // update the event time to the time this
                                     // event was to have happened.
    }
    

    这将在帧时间 1/60(分)秒内触发视觉事件。你会注意到我添加到 eventTime。我没有得到当前时间,因为当前时间可能长达 30 毫秒,如果我们添加一个毫秒错误,它会很快累积。

    音频、高精度事件

    对于音频事件,我们通过查看最后一帧时间来抢占事件时间

    // make sure we are near the next event time. this is to avoid firing the event 
    // twice. Once on the frame before and once on the frame after.
    if((timeInterval - timeSinceLastSync) >  timeInterval/2){ 
        // will the time to the new sync event be less than half the last frame time
        // or have we passed that time.
        if((timeInterval - timeSinceLastSync) < frameTime || timeSinceLastSync > timeInterval ){
            // fire the synced event.
            eventTime += timeInterval;  // update the event time to the time this
                                     // event was or will happened.
        }
    }
    

    我们在这里所做的是使用上一帧完成的时间并假设当前帧将花费相同的时间。然后我们找到距离同步事件多长时间,如果该时间小于帧时间的一半,则触发该事件。或者我们可能已经过了同步事件时间。无论哪种方式,我们现在都需要触发该事件。

    然后再次通过添加更新当前同步事件时间。

    此方法将使您尽可能接近您想要触发事件的时间。 (假设 60fps,在 1/120 秒内)

    改进

    您可以对此进行改进。 JavaScript 是阻塞的,所以有些事情你需要等到你的更新函数结束。您还可以通过获取退出功能之前的时间来跟踪您在更新功能中花费的时间。然后,您将估计您请求触发的代码将发生多长时间,并可以将其包含在计算中。通常帧之间的时间为 16 毫秒,但您的更新功能可能只运行 8 毫秒,因此会触发事件下一帧前 8ms。

    性能和微秒

    在某些浏览器上还有一个微秒(1/1,000,000 秒)计时器,称为performance

    // assume no performance API
    var usePerformance = false
    // at the start of you code check if its available
    if(typeof performance === "function"){  // is performance available
        usePerformance = true;
    }
    

    为了避免每次您需要使用任一 API 创建单独的函数时都必须测试性能 API

    var updateP = function(){
    }
    // normal update function
    var updateN = function(){
    }
    // set the correct update function
    var update;
    if(usePerformance){
        update = updateP;
    }else{
        update = updateN;
    }
    

    使用性能

    使用性能 API

    var now = performance.now(); // returns the time in ms with a fractional
                                 // representing the microseconds.
    

    事件之间的时间

    var n = performance.now();
    console.log(performance.now()-n);  // returns 0.021 depending on the system speed
    

    希望这会有所帮助。

    【讨论】:

    • 看来我得重新制作它了!感谢所有不同的方法来处理它!
    猜你喜欢
    • 2016-10-14
    • 1970-01-01
    • 2020-07-03
    • 2017-01-11
    • 1970-01-01
    相关资源
    最近更新 更多