web-audio-player
Version:
a cross-browser WebAudio player
165 lines (148 loc) • 4.32 kB
JavaScript
var canPlaySrc = require('./can-play-src')
var createAudioContext = require('./audio-context')
var xhrAudio = require('./xhr-audio')
var EventEmitter = require('events').EventEmitter
var rightNow = require('right-now')
var resume = require('./resume-context')
module.exports = createBufferSource
function createBufferSource (src, opt) {
opt = opt || {}
var emitter = new EventEmitter()
var audioContext = opt.context || createAudioContext()
// a pass-through node so user just needs to
// connect() once
var bufferNode, buffer, duration
var node = audioContext.createGain()
var audioStartTime = null
var audioPauseTime = null
var audioCurrentTime = 0
var playing = false
var loop = opt.loop
emitter.play = function () {
if (playing) return
playing = true
if (opt.autoResume !== false) resume(emitter.context)
disposeBuffer()
bufferNode = audioContext.createBufferSource()
bufferNode.connect(emitter.node)
bufferNode.onended = ended
if (buffer) {
// Might be null undefined if we are still loading
bufferNode.buffer = buffer
}
if (loop) {
bufferNode.loop = true
if (typeof opt.loopStart === 'number') bufferNode.loopStart = opt.loopStart
if (typeof opt.loopEnd === 'number') bufferNode.loopEnd = opt.loopEnd
}
if (duration && audioCurrentTime > duration) {
// for when it loops...
audioCurrentTime = audioCurrentTime % duration
}
var nextTime = audioCurrentTime
bufferNode.start(0, nextTime)
audioStartTime = rightNow()
}
emitter.pause = function () {
if (!playing) return
playing = false
// Don't let the "end" event
// get triggered on manual pause.
bufferNode.onended = null
bufferNode.stop(0)
audioPauseTime = rightNow()
audioCurrentTime += (audioPauseTime - audioStartTime) / 1000
}
emitter.stop = function () {
emitter.pause()
ended()
}
emitter.dispose = function () {
disposeBuffer()
buffer = null
}
emitter.node = node
emitter.context = audioContext
Object.defineProperties(emitter, {
duration: {
enumerable: true, configurable: true,
get: function () {
return duration
}
},
playing: {
enumerable: true, configurable: true,
get: function () {
return playing
}
},
buffer: {
enumerable: true, configurable: true,
get: function () {
return buffer
}
},
volume: {
enumerable: true, configurable: true,
get: function () {
return node.gain.value
},
set: function (n) {
node.gain.value = n
}
}
})
// set initial volume
if (typeof opt.volume === 'number') {
emitter.volume = opt.volume
}
// filter down to a list of playable sources
var sources = Array.isArray(src) ? src : [ src ]
sources = sources.filter(Boolean)
var playable = sources.some(canPlaySrc)
if (playable) {
var source = sources.filter(canPlaySrc)[0]
// Support the same source types as in
// MediaElement mode...
if (typeof source.getAttribute === 'function') {
source = source.getAttribute('src')
} else if (typeof source.src === 'string') {
source = source.src
}
// We have at least one playable source.
// For now just play the first,
// ideally this module could attempt each one.
startLoad(source)
} else {
// no sources can be played...
process.nextTick(function () {
emitter.emit('error', canPlaySrc.createError(sources))
})
}
return emitter
function startLoad (src) {
xhrAudio(audioContext, src, function audioDecoded (err, decoded) {
if (err) return emitter.emit('error', err)
buffer = decoded // store for later use
if (bufferNode) {
// if play() was called early
bufferNode.buffer = buffer
}
duration = buffer.duration
node.buffer = buffer
emitter.emit('load')
}, function audioProgress (amount, total) {
emitter.emit('progress', amount, total)
}, function audioDecoding () {
emitter.emit('decoding')
})
}
function ended () {
emitter.emit('end')
playing = false
audioCurrentTime = 0
}
function disposeBuffer () {
if (bufferNode) bufferNode.disconnect()
}
}