All files / addon/classes track.js

25% Statements 7/28
0% Branches 0/6
20% Functions 2/10
25.92% Lines 7/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                            1x                                           11x 11x 11x 11x                       1x 1x                                                                                                                                                        
import { computed } from '@ember/object';
import Sound from './sound';
import { createTimeObject } from 'ember-audio/utils';
 
/**
 * 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 offset = this.startOffset;
    const min = Math.floor(offset / 60);
    const sec = offset - min * 60;
    return createTimeObject(offset, min, sec);
  }),
 
  /**
   * Computed property. Value is the current play position of the
   * audioBuffer, formatted as a percentage.
   *
   * @public
   * @property percentPlayed
   * @type {number}
   */
  percentPlayed: computed('duration.raw', 'startOffset', function () {
    const ratio = this.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.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.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.audioContext;
    const startOffset = this.startOffset;
    const startedPlayingAt = this._startedPlayingAt;
 
    const animate = () => {
      if (this.isPlaying) {
        this.set(
          'startOffset',
          startOffset + ctx.currentTime - startedPlayingAt
        );
        requestAnimationFrame(animate);
      }
    };
 
    requestAnimationFrame(animate);
  },
});
 
export default Track;