wdi.PlaybackProcess = $.spcExtend(wdi.EventObject.prototype, { _lastApp: null, started: false, minBuffSize: 1024*32, frequency: null, channels: null, audioContext: null, startTime: null, // controls the playback time if no delay occurs hasAudioSupport: true, //whether the browser supports HTML5 Web Audio API typedBuffer: null, position: null, init: function(c) { this.app = c.app; this.audioContext = this.getAudioContext(); if (this.audioContext) { this.hasAudioSupport = true; } else { this.hasAudioSupport = false; wdi.Debug.warn('The client browser does not support Web Audio API'); } this.startTime = 0; this.typedBuffer = new ArrayBuffer(1024*32); this.position = 0; }, getAudioContext: function() { //standard browser object try { return new AudioContext(); } catch(e) { } //chrome and safari try { return new webkitAudioContext(); } catch(e) { } return false; }, process: function(spiceMessage) { // if (this.hasAudioSupport && !Modernizr.touch) { if (this.hasAudioSupport) { switch (spiceMessage.messageType) { case wdi.SpiceVars.SPICE_MSG_PLAYBACK_MODE: break; case wdi.SpiceVars.SPICE_MSG_PLAYBACK_START: var packet = spiceMessage.args; this.channels = packet.channels; this.frequency = packet.frequency; break; case wdi.SpiceVars.SPICE_MSG_PLAYBACK_STOP: this.startTime = 0; var packet = spiceMessage.args; this.flush(); break; case wdi.SpiceVars.SPICE_MSG_PLAYBACK_DATA: // While we receive data chunks, we store them in a buffer, so than when it is full we play the sound and empty it. // With this we get a more fluid playback and better overall performance than if we just played the data the moment we got it var packet = spiceMessage.args; var dataTimestamp = spiceMessage.args.multimedia_time; var tmpview = new Uint8Array(this.typedBuffer); tmpview.set(packet.data, this.position); this.position += packet.data.length; this._lastApp = this.app; if(this.position >= this.minBuffSize) { // ok, the buffer is full. We send the data to be played and later we can empty it to make room for more audio this.flush(dataTimestamp); } break; } } else { //TODO: // If the browser doesn't support Web Audio, we could still attach a wav header to the raw PCM we receive from spice and use the more widespread supported audio tag // Meanwhile, we can skip all the audio packets and gain some performance at least } }, /** * Plays all the audio buffer and empties it * * @param app * @param dataTimestamp */ flush: function(dataTimestamp) { if(this.position > 0) { if (this.started) { this.playSound(this.typedBuffer, dataTimestamp); } this.position = 0; this.typedBuffer = new ArrayBuffer(1024*32); } }, /** * Plays the raw pcm data passed as param using HTML5's Web Audio API * * @param buffer */ playSound: function(buffer, dataTimestamp) { if(this.channels == 2) { return this.playSoundStereo(buffer, dataTimestamp); } var audio = new Int16Array(buffer); var channelData = new Array(this.channels); for(var i = 0;i