Source: Audio.js

/**
 *  Audio is the base class for game audio, loaded into a HTML5 'audio' tag.
 */
class Audio {
  /**
	* Create a new Audio with the specified options.
	* @param {object} options The options object for the Asset - see below
	* @example
const { Asset } = require('tenkai')

var asset = new Asset({
  src: 'music/maintitle.mp3',   // Source file
  type: 'audio/mpeg',           // MIME type of the asset file
  startTime: 0,                 // Start offset in ms (optional)
  endTime: 1040,                // End offset in ms (optional)
  loop: true                    // Loop the audio continuously (optional, default false)
})
	*/
  constructor (options = {}) {
    this.src = options.src
    this.type = options.type

    this.startTime = options.startTime
    this.endTime = options.endTime
    this.loop = !!options.loop

    this._callonloaded = null
  }

  /**
	* Load the Audio asynchronously from its src and optionally trigger the supplied callback when available.
	* @param {function} callback The callback function to invoke when the asset has been loaded
	* @example
audio.load((err, au) => {
  console.log("Audio "+au.name+" Loaded")
})
	*/
  load (callback) {
    this.element = document.createElement('audio')
    this._callonloaded = callback
    this.element.oncanplaythrough = this._oncanplaythrough.bind(this)
    this.element.onloadedmetadata = this._onloadedmetadata.bind(this)
    this.element.ontimeupdate = this._ontimeupdate.bind(this)
    var source = document.createElement('source')
    source.src = this.src
    source.type = this.type
    this.element.appendChild(source)
  }

  /**
	* Play the audio from the current position.
	*/
  play () {
    this.element.play()
  }

  /**
	* Play the audio from the start time index to the end time index and optionally loop.
	* @param {float} start The start offset in ms (optional)
	* @param {float} end The end offset in ms (optional)
	* @param {boolean} start Loop the audio (optional)
	*/
  playRange (start = null, end = null, loop = null) {
    if (start) this.startTime = start
    if (end) this.endTime = end
    if (loop) this.loop = loop
    this.play()
  }

  /**
	* Pause the audio at the current position.
	*/
  pause () {
    this.element.pause()
  }

  /**
	* Stop the audio and reset it to the beginning
	*/
  stop () {
    this.element.oncanplaythrough = null
    this.element.pause()
    this.element.currentTime = '0'
  }

  /**
	* Fade out the audio over the given duration then call the optional callback
	* @param {integer} duration Fade duration in ms
	* @param {function} callback The callback function to invoke when the fade has completed
	*/
  fadeOut (duration = 1000, callback) {
    this._fade(0, duration, () => {
      this.pause()
      if (callback) callback(null, this)
    })
  }

  /**
	* Fade in the audio over the given duration then call the optional callback
	* @param {integer} duration Fade duration in ms
	* @param {function} callback The callback function to invoke when the fade has completed
	*/
  fadeIn (duration = 1000, callback) {
    this.element.volume = 0
    this.play()
    this._fade(1, duration, () => {
      if (callback) callback(null, this)
    })
  }

  _fade (toValue, duration, callback) {
    if (this._fadeTimeout) clearTimeout(this._fadeTimeout)

    var dv = (toValue - this.element.volume) / (duration / 10)
    var x = () => {
      var p = this.element.volume + dv
      if ((dv < 0 && p <= toValue) || (dv > 0 && p >= toValue)) {
        this.element.volume = toValue
        delete this._fadeTimeout
        if (callback) return callback()
      } else {
        this.element.volume = p
        this._fadeTimeout = setTimeout(x, 10)
      }
    }
    x()
  }

  _oncanplaythrough () {
    if (this._callonloaded) {
      var x = this._callonloaded
      this._callonloaded = null
      x()
    }
  }

  _onloadedmetadata () {
    this.duration = this.element.duration * 1000
    if (!this.endTime) this.endTime = this.duration
    this.element.playbackRate = 1
    this.element.currentTime = (this.startTime || 0) / 1000
  }

  _ontimeupdate () {
    if (this.element.currentTime * 1000 >= this.endTime) {
      if (this.loop) {
        this.element.currentTime = this.startTime / 1000
      } else {
        this.element.pause()
      }
    }
  }
}

module.exports = Audio