Object.assign(pc, function () {
'use strict';
var Channel;
if (pc.AudioManager.hasAudioContext()) {
/**
* @private
* @constructor
* @name pc.Channel
* @classdesc A channel is created when the pc.AudioManager begins playback of a pc.Sound. Usually created internally by
* pc.AudioManager#playSound or pc.AudioManager#playSound3d. Developers usually won't have to create Channels manually.
* @param {pc.AudioManager} manager The AudioManager instance
* @param {pc.Sound} sound The sound to playback
* @param {Object} [options] Optional options object.
* @param {Number} [options.volume=1] The playback volume, between 0 and 1.
* @param {Number} [options.pitch=1] The relative pitch, default of 1, plays at normal pitch.
* @param {Boolean} [options.loop=false] Whether the sound should loop when it reaches the end or not.
*/
Channel = function (manager, sound, options) {
options = options || {};
this.volume = (options.volume === undefined) ? 1 : options.volume;
this.loop = (options.loop === undefined) ? false : options.loop;
this.pitch = (options.pitch === undefined ? 1 : options.pitch);
this.sound = sound;
this.paused = false;
this.suspended = false;
this.startTime = 0;
this.startOffset = 0;
this.manager = manager;
this.source = null;
var context = manager.context;
this.gain = context.createGain();
};
Object.assign(Channel.prototype, {
/**
* @private
* @function
* @name pc.Channel#play
* @description Begin playback of sound
*/
play: function () {
if (this.source) {
throw new Error('Call stop() before calling play()');
}
this._createSource();
if (!this.source) {
return;
}
this.startTime = this.manager.context.currentTime;
this.source.start(0, this.startOffset % this.source.buffer.duration);
// Initialize volume and loop - note moved to be after start() because of Chrome bug
this.setVolume(this.volume);
this.setLoop(this.loop);
this.setPitch(this.pitch);
this.manager.on('volumechange', this.onManagerVolumeChange, this);
this.manager.on('suspend', this.onManagerSuspend, this);
this.manager.on('resume', this.onManagerResume, this);
// suspend immediately if manager is suspended
if (this.manager.suspended)
this.onManagerSuspend();
},
/**
* @private
* @function
* @name pc.Channel#pause
* @description Pause playback of sound. Call unpause() to resume playback from the same position
*/
pause: function () {
if (this.source) {
this.paused = true;
this.startOffset += this.manager.context.currentTime - this.startTime;
this.source.stop(0);
this.source = null;
}
},
/**
* @private
* @function
* @name pc.Channel#unpause
* @description Resume playback of the sound. Playback resumes at the point that the audio was paused
*/
unpause: function () {
if (this.source || !this.paused) {
console.warn('Call pause() before unpausing.');
return;
}
this._createSource();
if (!this.source) {
return;
}
this.startTime = this.manager.context.currentTime;
this.source.start(0, this.startOffset % this.source.buffer.duration);
// Initialize parameters
this.setVolume(this.volume);
this.setLoop(this.loop);
this.setPitch(this.pitch);
this.paused = false;
},
/**
* @private
* @function
* @name pc.Channel#stop
* @description Stop playback of sound. Calling play() again will restart playback from the beginning of the sound.
*/
stop: function () {
if (this.source) {
this.source.stop(0);
this.source = null;
}
this.manager.off('volumechange', this.onManagerVolumeChange, this);
this.manager.off('suspend', this.onManagerSuspend, this);
this.manager.off('resume', this.onManagerResume, this);
},
/**
* @private
* @function
* @name pc.Channel#setLoop
* @description Enable/disable the loop property to make the sound restart from the beginning when it reaches the end.
* @param {Boolean} loop true to loop the sound, false otherwise.
*/
setLoop: function (loop) {
this.loop = loop;
if (this.source) {
this.source.loop = loop;
}
},
/**
* @private
* @function
* @name pc.Channel#setVolume
* @description Set the volume of playback between 0 and 1.
* @param {Number} volume The volume of the sound. Will be clamped between 0 and 1.
*/
setVolume: function (volume) {
volume = pc.math.clamp(volume, 0, 1);
this.volume = volume;
if (this.gain) {
this.gain.gain.value = volume * this.manager.volume;
}
},
setPitch: function (pitch) {
this.pitch = pitch;
if (this.source) {
this.source.playbackRate.value = pitch;
}
},
isPlaying: function () {
return (!this.paused && (this.source.playbackState === this.source.PLAYING_STATE));
},
getDuration: function () {
return this.source ? this.source.buffer.duration : 0;
},
_createSource: function () {
var context = this.manager.context;
if (this.sound.buffer) {
this.source = context.createBufferSource();
this.source.buffer = this.sound.buffer;
// Connect up the nodes
this.source.connect(this.gain);
this.gain.connect(context.destination);
if (!this.loop) {
// mark source as paused when it ends
this.source.onended = this.pause.bind(this);
}
}
}
});
} else if (pc.AudioManager.hasAudio()) {
Channel = function (manager, sound, options) {
this.volume = options.volume || 1;
this.loop = options.loop || false;
this.sound = sound;
this.pitch = options.pitch !== undefined ? options.pitch : 1;
this.paused = false;
this.suspended = false;
this.manager = manager;
// handle the case where sound was
if (sound.audio) {
this.source = sound.audio.cloneNode(false);
this.source.pause(); // not initially playing
}
};
Object.assign(Channel.prototype, {
play: function () {
if (this.source) {
this.paused = false;
this.setVolume(this.volume);
this.setLoop(this.loop);
this.setPitch(this.pitch);
this.source.play();
}
this.manager.on('volumechange', this.onManagerVolumeChange, this);
this.manager.on('suspend', this.onManagerSuspend, this);
this.manager.on('resume', this.onManagerResume, this);
// suspend immediately if manager is suspended
if (this.manager.suspended)
this.onManagerSuspend();
},
pause: function () {
if (this.source) {
this.paused = true;
this.source.pause();
}
},
unpause: function () {
if (this.source) {
this.paused = false;
this.source.play();
}
},
stop: function () {
if (this.source) {
this.source.pause();
}
this.manager.off('volumechange', this.onManagerVolumeChange, this);
this.manager.off('suspend', this.onManagerSuspend, this);
this.manager.off('resume', this.onManagerResume, this);
},
setVolume: function (volume) {
volume = pc.math.clamp(volume, 0, 1);
this.volume = volume;
if (this.source) {
this.source.volume = volume * this.manager.volume;
}
},
setLoop: function (loop) {
this.loop = loop;
if (this.source) {
this.source.loop = loop;
}
},
setPitch: function (pitch) {
this.pitch = pitch;
if (this.source) {
this.source.playbackRate = pitch;
}
},
getDuration: function () {
return this.source && !isNaN(this.source.duration) ? this.source.duration : 0;
},
isPlaying: function () {
return !this.source.paused;
}
});
} else {
Channel = function () {
};
}
// Add functions which don't depend on source type
Object.assign(Channel.prototype, {
/**
* @private
* @function
* @name pc.Channel#getVolume
* @description Get the current value for the volume. Between 0 and 1.
* @returns {Number} The volume of the channel.
*/
getVolume: function () {
return this.volume;
},
/**
* @private
* @function
* @name pc.Channel#getLoop
* @description Get the current looping state of the Channel
* @returns {Boolean} The loop property for the channel.
*/
getLoop: function () {
return this.loop;
},
/**
* @private
* @function
* @name pc.Channel#getPitch
* @description Get the current pitch of the Channel
* @returns {Number} The pitch of the channel.
*/
getPitch: function () {
return this.pitch;
},
/**
* @private
* @function
* @name pc.Channel#onManagerVolumeChange
* @description Handle the manager's 'volumechange' event.
*/
onManagerVolumeChange: function () {
this.setVolume(this.getVolume());
},
/**
* @private
* @function
* @name pc.Channel#onManagerSuspend
* @description Handle the manager's 'suspend' event.
*/
onManagerSuspend: function () {
if (this.isPlaying() && !this.suspended) {
this.suspended = true;
this.pause();
}
},
/**
* @private
* @function
* @name pc.Channel#onManagerResume
* @description Handle the manager's 'resume' event.
*/
onManagerResume: function () {
if (this.suspended) {
this.suspended = false;
this.unpause();
}
}
});
return {
Channel: Channel
};
}());