Three.js: Audio pause not working properly

Created on 2 Aug 2017  路  8Comments  路  Source: mrdoob/three.js

Hello everyone,

It's my first time posting an issue and I am new to Github so please be tolerant.
I am using the latest version of three.js.
I noticed that pausing an audio source (Positional or not) wasn't working properly. When playing after pause, there was a gap between the "offset" of the audio when pausing and that of the audio when playing again afterwards. Indeed, as the code is using AudioBufferSourceNodes that can only be played once, it is necessary to create a new source node when playing, but it has to be set to the right offset.
After looking at the code, I suggest the following fix :
(modifications appear in the Audio constructor (this.offset), and the methods play, pause and stop)


function Audio( listener ) {

        Object3D.call( this );

        this.type = 'Audio';

        this.context = listener.context;

        this.gain = this.context.createGain();
        this.gain.connect( listener.getInput() );

        this.autoplay = false;

        this.buffer = null;
        this.loop = false;
        this.startTime = this.context.currentTime;
        this.playbackRate = 1;
        this.isPlaying = false;
        this.hasPlaybackControl = true;
        this.sourceType = 'empty';
        this.offset = 0;
        this.filters = [];
        this.offset = 0;

    }
        play: function () {

            if ( this.isPlaying === true ) {

                console.warn( 'THREE.Audio: Audio is already playing.' );
                return;

            }

            if ( this.hasPlaybackControl === false ) {

                console.warn( 'THREE.Audio: this Audio has no playback control.' );
                return;

            }

            var source = this.context.createBufferSource();

            source.buffer = this.buffer;
            source.loop = this.loop;
            source.onended = this.onEnded.bind( this );
            this.startTime = this.context.currentTime;
            source.playbackRate.setValueAtTime( this.playbackRate, this.startTime );
            source.start( this.startTime, this.offset );

            this.isPlaying = true;

            this.source = source;

            return this.connect();

        },

        pause: function () {

            if ( this.hasPlaybackControl === false ) {

                console.warn( 'THREE.Audio: this Audio has no playback control.' );
                return;

            }
            if( this.isPlaying ) {
                this.source.stop();
                this.offset += this.context.currentTime - this.startTime;
                this.isPlaying = false;
            }

            return this;

        },

        stop: function () {

            if ( this.hasPlaybackControl === false ) {

                console.warn( 'THREE.Audio: this Audio has no playback control.' );
                return;

            }

            this.source.stop();
            this.offset = 0;
            this.isPlaying = false;

            return this;

        }

Bug

Most helpful comment

Thank you for your answers,
I will try to do the pull request on monday. I'll tell you if I need help, thanks for proposing.
Pablo

All 8 comments

Is it possible that you create a basic fiddle that illustrates the problem? From my point of view, the current implementation looks correct. But maybe i'm missing something...

Hi and thank you for your answer.

I tried it but it's kind of hard to load an audio file through jsfiddle (tried but didn't succeed). So I've made this minimal example : you can use any mp3 file.

var camera, scene, renderer;
var geometry, material, mesh;

init();
animate();

function init() {

    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.z = 1000;

    scene = new THREE.Scene();
   var listener = new THREE.AudioListener();
    camera.add(listener);
   var audio = new THREE.Audio(listener);
   console.log(listener.context);
    var audioLoader = new THREE.AudioLoader();

   audioLoader.load('4190.mp3', function (buffer) {
      audio.setBuffer(buffer);
         for (var i = 0; i < 100; i++) {
            setTimeout(toggle, i * 1000);
      }
   });

     function toggle () {
         if(audio.isPlaying) audio.pause();
         else audio.play();
   }

    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);

    document.body.appendChild(renderer.domElement);
}

function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}

If you use the current three.js then the audio will start and stop playing but it will not play back at the position where it stopped.

Pablo

After some testing i can confirm this bug. Using just this.context.currentTime in order to determine the offset is not sufficient.

I've prepared a demo to test the new code. It sounds better than before, although the difference is a little bit inconvenient to spot.

@pablolh Do you think the result is okay like that?

Sample track, no pause
Same track with play/pause pattern | with fix
Same track with play/pause pattern | without fix

I've investigated this issue a little bit closer and indeed, your proposal is the cleaner implementation. To recap:

  • this.startTime indicates when the sound should begin to play
  • this.offset is an offset to the time within the audio buffer
  • this.startTime is always set to the value of this.context.currentTime when .play() is called
  • this.offset is calculated when the audio is paused via .pause()
  • playback starts with source.start( this.startTime, this.offset );

This new approach fits actually nicer to the parameters of AudioBufferSourceNode.start().

@pablolh Do you mind to create a PR with the changes? I can guide you through the PR if you like...

Thank you for your answers,
I will try to do the pull request on monday. I'll tell you if I need help, thanks for proposing.
Pablo

@pablolh Are you still planing to do the PR? :innocent:

Tried it this morning, hope it's done properly !
Sorry for the delay.

No problem :wink:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

akshaysrin picture akshaysrin  路  3Comments

boyravikumar picture boyravikumar  路  3Comments

zsitro picture zsitro  路  3Comments

seep picture seep  路  3Comments

konijn picture konijn  路  3Comments