addon/mixins/playable.js
import Ember from 'ember';
/**
* Provides classes that are capable of interacting with the Web Audio API's
* AudioContext.
*
* @public
* @module Audio
*/
const {
run: { later },
Mixin
} = Ember;
/**
* A mixin that allows an object to start and stop an audio source, now or in
* the future, as well as track whether the audio source is currently playing or
* not.
*
* Consuming object must implement `wireConnections` and `getNodeFrom` methods.
* These methods are included in the {{#crossLink "Connectable"}}{{/crossLink}}
* mixin.
*
* @public
* @class Playable
*/
export default Mixin.create({
/**
* Whether an audio source is playing or not.
*
* @public
* @property isPlaying
* @type {boolean}
* @default false
*/
isPlaying: false,
/**
* Plays the audio source immediately.
*
* @public
* @method play
*/
play() {
this._play(this.get('audioContext.currentTime'));
},
/**
* Plays the audio source at the specified moment in time. A "moment in time"
* is measured in seconds from the moment that the
* {{#crossLink "AudioContext"}}{{/crossLink}} was instantiated.
*
* Functionally equivalent to {{#crossLink "Playable/_play:method"}}{{/crossLink}}.
*
* @param {number} time The moment in time (in seconds, relative to the
* {{#crossLink "AudioContext"}}AudioContext's{{/crossLink}} "beginning of
* time") when the audio source should be played.
*
* @public
* @method playAt
*/
playAt(time) {
this._play(time);
},
/**
* Plays the audio source in specified amount of seconds from "now".
*
* @public
* @method playIn
*
* @param {number} seconds Number of seconds from "now" that the audio source
* should be played.
*/
playIn(seconds) {
this._play(this.get('audioContext.currentTime') + seconds);
},
/**
* Starts playing the audio source immediately, but stops after specified
* seconds have elapsed.
*
* @public
* @method playFor
*
* @param {number} seconds The amount of time after which the audio source is
* stopped.
*/
playFor(seconds) {
this.play();
this.stopIn(seconds);
},
/**
* Starts playing the audio source after `playIn` seconds have elapsed, then
* stops the audio source `stopAfter` seconds after it started playing.
*
* @public
* @method playInAndStopAfter
*
* @param {number} playIn Number of seconds from "now" that the audio source
* should play.
*
* @param {number} stopAfter Number of seconds from when the audio source
* started playing that the audio source should be stopped.
*/
playInAndStopAfter(playIn, stopAfter) {
this.playIn(playIn);
this.stopIn(playIn + stopAfter);
},
/**
* Stops the audio source immediately.
*
* @public
* @method stop
*/
stop() {
this._stop(this.get('audioContext.currentTime'));
},
/**
* Stops the audio source after specified seconds have elapsed.
*
* @public
* @method stopIn
*
* @param {number} seconds Number of seconds from "now" that the audio source
* should be stopped.
*/
stopIn(seconds) {
this._stop(this.get('audioContext.currentTime') + seconds);
},
/**
* Stops the audio source at the specified "moment in time" relative to the
* "beginning of time" according to the `audioContext`.
*
* Functionally equivalent to the `_stop` method.
*
* @public
* @method stopAt
*
* @param {number} time The time that the audio source should be stopped.
*/
stopAt(time) {
this._stop(time);
},
/**
* The underlying method that backs all of the `stop` methods. Stops sound and
* set `isPlaying` to false at specified time.
*
* Functionally equivalent to the `stopAt` method.
*
* @private
* @method _stop
*
* @param {number} stopAt The moment in time (in seconds, relative to the
* {{#crossLink "AudioContext"}}AudioContext's{{/crossLink}} "beginning of
* time") when the audio source should be stopped.
*/
_stop(stopAt) {
const node = this.getNodeFrom('audioSource');
const currentTime = this.get('audioContext.currentTime');
if (node) {
node.stop(stopAt);
}
if (stopAt === currentTime) {
this.set('isPlaying', false);
} else {
later(() => this.set('isPlaying', false), (stopAt - currentTime) * 1000);
}
},
/**
* The underlying method that backs all of the `play` methods. Plays sound and
* sets `isPlaying` to true at specified time.
*
* Functionally equivalent to `playAt`.
*
* @param {number} time The moment in time (in seconds, relative to the
* {{#crossLink "AudioContext"}}AudioContext's{{/crossLink}} "beginning of
* time") when the audio source should be played.
*
* @method _play
* @private
*/
_play(playAt) {
const currentTime = this.get('audioContext.currentTime');
this.wireConnections();
const node = this.getNodeFrom('audioSource');
node.start(playAt, this.get('startOffset'));
this.set('_startedPlayingAt', playAt);
if (playAt === currentTime) {
this.set('isPlaying', true);
} else {
later(() => this.set('isPlaying', true), (playAt - currentTime) * 1000);
}
}
});