addon/classes/track.js
import Ember from 'ember';
import Sound from './sound';
import { zeroify } from 'ember-audio/utils';
/**
* Provides classes that are capable of interacting with the Web Audio API's
* AudioContext.
*
* @public
* @module Audio
*/
const {
computed
} = Ember;
/**
* A class that represents a "track" of music, similar in concept to a track on
* a CD or an MP3 player. Provides methods for tracking the play position of the
* underlying {{#crossLink "AudioBuffer"}}{{/crossLink}}, and pausing/resuming.
*
* @public
* @class Track
* @extends Sound
* @todo move play override to _play so that all super.play methods work
*/
const Track = Sound.extend({
/**
* Computed property. Value is an object containing the current play position
* of the audioBuffer in three formats. The three
* formats are `raw`, `string`, and `pojo`.
*
* Play position of 6 minutes would be output as:
*
* {
* raw: 360, // seconds
* string: '06:00',
* pojo: {
* minutes: 6,
* seconds: 0
* }
* }
*
* @public
* @property position
* @type {object}
*/
position: computed('startOffset', function() {
const startOffset = this.get('startOffset');
const minutes = Math.floor(startOffset / 60);
const seconds = startOffset - (minutes * 60);
return {
raw: startOffset,
string: `${zeroify(minutes)}:${zeroify(seconds)}`,
pojo: { minutes, seconds }
};
}),
/**
* Computed property. Value is the current play position of the
* audioBuffer, formatted as a percentage.
*
* @public
* @property percentPlayed
* @type {number}
*/
percentPlayed: computed('duration', 'startOffset', function() {
const ratio = this.get('startOffset') / this.get('duration.raw');
return ratio * 100;
}),
/**
* Plays the audio source immediately.
*
* @public
* @method play
*/
play() {
this._super();
this.getNodeFrom('audioSource').onended = () => this.stop();
this._trackPlayPosition();
},
/**
* Pauses the audio source by stopping without
* setting startOffset back to 0.
*
* @public
* @method pause
*/
pause() {
if (this.get('isPlaying')) {
const node = this.getNodeFrom('audioSource');
node.onended = function() {};
node.stop();
this.set('isPlaying', false);
}
},
/**
* Stops the audio source and sets
* startOffset to 0.
*
* @public
* @method stop
*/
stop() {
this.set('startOffset', 0);
if (this.get('isPlaying')) {
this.getNodeFrom('audioSource').onended = function() {};
this._super();
}
},
/**
* Sets up a `requestAnimationFrame` based loop that updates the
* startOffset as `audioContext.currentTime` grows.
* Loop ends when `isPlaying` is false.
*
* @method _trackPlayPosition
* @private
*/
_trackPlayPosition() {
const ctx = this.get('audioContext');
const startOffset = this.get('startOffset');
const startedPlayingAt = this.get('_startedPlayingAt');
const animate = () => {
if (this.get('isPlaying')) {
this.set('startOffset', startOffset + ctx.currentTime - startedPlayingAt);
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}
});
export default Track;