mopidy-musicbox-webclient/mopidy_musicbox_webclient/static/js/progress_timer.js
2016-03-05 15:49:48 +02:00

177 lines
5.3 KiB
JavaScript

var progressTimer
var progressElement = document.getElementById('trackslider')
var positionNode = document.createTextNode('')
var durationNode = document.createTextNode('')
var START_BEATS = 5 // 0.5 seconds, needs to be less than 1s to prevent unwanted updates.
var RUN_BEATS = 300 // 30 seconds assuming default timer update rate of 100ms
var callbackHeartbeats = 0 // Timer will check syncs on every n-number of calls.
var targetPosition = null
var MAX_SYNCS = 5 // Maximum number of consecutive successful syncs to perform.
var syncsLeft = MAX_SYNCS
var synced = false
var consecutiveSyncs = 0
document.getElementById('songelapsed').appendChild(positionNode)
document.getElementById('songlength').appendChild(durationNode)
function timerCallback (position, duration, isRunning) {
updateTimers(position, duration, isRunning)
if (callbackHeartbeats === 0) {
callbackHeartbeats = getHeartbeat()
}
if (mopidy && position > 0) {
// Mopidy and timer are both initialized.
if (callbackHeartbeats-- === 1) {
// Get time position from Mopidy on every nth callback until
// synced.
mopidy.playback.getTimePosition().then(
function (mopidy_position) {
syncTimer(position, mopidy_position)
}
)
}
}
}
function updateTimers (position, duration, isRunning) {
var ready = !(duration === Infinity && position === 0 && !isRunning) // Timer has been properly initialized.
var streaming = (duration === Infinity && position > 0) // Playing a stream.
var ok = synced && isRunning // Normal operation.
var syncing = !synced && isRunning // Busy syncing.
if (!ready) {
// Make sure that default values are displayed while the timer is being initialized.
positionNode.nodeValue = ''
durationNode.nodeValue = ''
$('#trackslider').val(0).slider('refresh')
} else {
durationNode.nodeValue = format(duration || Infinity)
if (syncing) {
if (!targetPosition) {
// Waiting for Mopidy to provide a target position.
positionNode.nodeValue = '(wait)'
} else {
// Busy seeking to new target position.
positionNode.nodeValue = '(sync)'
}
} else if (synced || streaming) {
positionNode.nodeValue = format(position)
}
}
if (ok) {
// Don't update the track slider unless it is synced and running.
// (prevents awkward 'jitter' animation).
$('#trackslider').val(position).slider('refresh')
}
}
function getHeartbeat () {
if (syncsLeft > 0 && callbackHeartbeats === 0) {
// Step back exponentially while increasing heartbeat.
return Math.round(delay_exponential(5, 2, MAX_SYNCS - syncsLeft))
} else if (syncsLeft === 0 && callbackHeartbeats === 0) {
// Sync completed, keep checking using maximum number of heartbeats.
return RUN_BEATS
} else {
return START_BEATS
}
}
function syncTimer (current, target) {
if (target) {
var drift = Math.abs(target - current)
if (drift <= 500) {
syncsLeft--
// Less than 500ms == in sync.
if (++consecutiveSyncs === 2) {
// Need at least two consecutive syncs to know that Mopidy
// is progressing playback and we are in sync.
synced = true
targetPosition = null
consecutiveSyncs = 0
}
} else {
// Drift is too large, re-sync with Mopidy.
reset()
targetPosition = target
progressTimer.set(targetPosition)
}
}
}
function toInt (value) {
return value.match(/^\w*\d+\w*$/) ? parseInt(value) : null
}
function format (milliseconds) {
if (milliseconds === Infinity) {
return '(n/a)'
} else if (milliseconds === 0) {
return '0:00'
}
var seconds = Math.floor(milliseconds / 1000)
var minutes = Math.floor(seconds / 60)
seconds = seconds % 60
seconds = seconds < 10 ? '0' + seconds : seconds
return minutes + ':' + seconds
}
function delay_exponential (base, growthFactor, attempts) {
/* Calculate number of beats between syncs based on exponential function.
The format is::
base * growthFactor ^ (attempts - 1)
If ``base`` is set to 'rand' then a random number between
0 and 1 will be used as the base.
Base must be greater than 0.
*/
if (base === 'rand') {
base = Math.random()
}
beats = base * (Math.pow(growthFactor, (attempts - 1)))
return beats
}
function reset () {
synced = false
consecutiveSyncs = 0
syncsLeft = MAX_SYNCS
callbackHeartbeats = START_BEATS
targetPosition = null
}
function setProgressTimer (pos) {
reset()
targetPosition = pos
progressTimer.set(pos)
if (!play) {
// Set lapsed time and slider position directly as timer callback is not currently
// running.
positionNode.nodeValue = format(pos)
$('#trackslider').val(pos).slider('refresh')
}
}
function updatePosition (pos) {
positionNode.nodeValue = format(pos)
}
function startProgressTimer () {
reset()
progressTimer.start()
}
function resetProgressTimer () {
progressTimer.reset()
reset()
targetPosition = 0
}