scripts/ajax.js
author Dan
Sun, 24 Aug 2008 01:28:52 -0400
changeset 37 65e70ada71c9
parent 22 3a4f0cd0794e
child 39 38dbcda3cf20
permissions -rw-r--r--
Major changes to webserver backend. All socket functions are abstracted to allow support for stream_* which seems to be both more widely supported and better at handling blocking and timeouts, at the cost of a small bit of speed. Keep-Alive times out properly and thanks to a bit of IPC code from stream_create_pair(), zombie children are mostly eliminated by proper pcntl_wait() being called when a child shuts down normally, and children die within 0.2sec if the parent receives a SIGTERM or SIGINT, even if the children are waiting on the socket.

/**
 * AJAX functions
 * 
 * Greyhound - real web management for Amarok
 * Copyright (C) 2008 Dan Fuhry
 *
 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
 */

var ajax;
var is_playing = false, current_track = -1, current_track_length, current_track_pos, ct_advance_timeout = false, ct_counter = false, playlist_md5 = false, first_load = true;

var onload_hooks = new Array();

function addOnloadHook(func)
{
  if ( typeof ( func ) == 'function' )
  {
    if ( typeof(onload_hooks.push) == 'function' )
    {
      onload_hooks.push(func);
    }
    else
    {
      onload_hooks[onload_hooks.length] = func;
    }
  }
}

function runOnloadHooks(e)
{
  var _errorTrapper = 0;
  for ( var _oLc = 0; _oLc < onload_hooks.length; _oLc++ )
  {
    _errorTrapper++;
    if ( _errorTrapper >= 1000 )
      break;
    var _f = onload_hooks[_oLc];
    if ( typeof(_f) == 'function' )
    {
      _f(e);
    }
  }
}

function ajaxGet(uri, f)
{
  if ( ajax_panicked )
    return false;
  
  if (window.XMLHttpRequest)
  {
    ajax = new XMLHttpRequest();
  }
  else
  {
    if (window.ActiveXObject) {           
      ajax = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else
    {
      alert('AmaroK client-side runtime error: No AJAX support, unable to continue');
      return;
    }
  }
  ajax.onreadystatechange = f;
  ajax.open('GET', uri, true);
  ajax.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" );
  ajax.send(null);
}

function ajaxPost(uri, parms, f)
{
  if ( ajax_panicked )
    return false;
  
  if (window.XMLHttpRequest)
  {
    ajax = new XMLHttpRequest();
  }
  else
  {
    if (window.ActiveXObject)
    {           
      ajax = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else
    {
      alert('AmaroK client-side runtime error: No AJAX support, unable to continue');
      return;
    }
  }
  ajax.onreadystatechange = f;
  ajax.open('POST', uri, true);
  ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  // Setting Content-length in Safari triggers a warning
  if ( !is_Safari )
  {
    ajax.setRequestHeader("Content-length", parms.length);
  }
  ajax.setRequestHeader("Connection", "close");
  ajax.onerror = function()
  {
    ajax_panic();
  }
  ajax.send(parms);
}

function setAjaxLoading()
{
  $('ajax_status').object.src = img_ajax;
  $('ajax_status').object.style.display = 'block';
}

function unsetAjaxLoading()
{
  $('ajax_status').object.src = 'about:blank';
  $('ajax_status').object.style.display = 'none';
}

var refresh_playlist = function()
{
  setAjaxLoading();
  ajaxGet('/action.json/refresh', function()
    {
      if ( ajax.readyState == 4 && ajax.status == 200 )
      {
        unsetAjaxLoading();
        var response = (' ' + ajax.responseText).substr(1);
        // quickie JSON parser :)
        response = eval('(' + response + ')');
        // has the playlist been modified?
        if ( playlist_md5 )
        {
          if ( response.playlist_hash != playlist_md5 )
          {
            // playlist has changed, reload
            window.location.reload();
            return false;
          }
        }
        playlist_md5 = response.playlist_hash;
        // update track number
        if ( response.current_track != current_track )
        {
          var ot_id = 'track_' + current_track;
          var nt_id = 'track_' + response.current_track;
          current_track = response.current_track;
          if ( $(ot_id).hasClass('current') )
          {
            $(ot_id).rmClass('current');
          }
          if ( ! $(nt_id).hasClass('current') )
          {
            $(nt_id).addClass('current');
          }
          pulsar_reset();
        }
        // update playing status
        is_playing = response.is_playing;
        if ( allow_control )
        {
          var img = $('btn_playpause').object.getElementsByTagName('img')[0];
          if ( is_playing )
          {
            img.src = img_pause;
          }
          else
          {
            img.src = img_play;
          }
        }
        // update volume
        if ( response.volume != current_volume )
        {
          set_volume_fill(response.volume);
          current_volume = response.volume;
        }
        // auto-refresh on track advance
        if ( ct_advance_timeout )
        {
          clearTimeout(ct_advance_timeout);
        }
        // countdown/up timer
        var time_remaining = response.current_track_length - response.current_track_pos;
        current_track_length = response.current_track_length;
        current_track_pos = response.current_track_pos;
        if ( ct_counter )
          clearInterval(ct_counter);
        update_clock();
        
        // set page title
        updateTitle(response.current_track_artist, response.current_track_album, response.current_track_title);
        
        // if not playing, set the position slider to zero
        if ( !is_playing && !response.is_paused )
        {
          posslide_set_position(0);
        }
        
        // set advance timer
        if ( is_playing && time_remaining > 0 )
        {
          ct_advance_timeout = setTimeout(refresh_playlist, ( 1000 * time_remaining ));
          ct_counter = setInterval(update_clock, 1000);
        }
        if ( first_load )
        {
          first_load = false;
          jump_current_track();
        }
      }
      else if ( ajax.readyState == 4 && ajax.status != 200 )
      {
        ajax_panic();
        console.debug(ajax);
      }
    });
}

var ajax_panicked = false;

function ajax_panic()
{
  // set error flag
  ajax_panicked = true;
  
  // scroll to the top
  window.scroll(0, 0);
  
  // stop events
  pulsar_reset();
  window.clearInterval(pl_refresh_id);
  if ( ct_counter )
    window.clearInterval(ct_counter);
  if ( pulsar_interval_id )
    window.clearInterval(pulsar_interval_id);
  
  // show error message
  var floater = document.createElement('div');
  floater.style.backgroundColor = '#ffffff';
  floater.style.opacity = 0.7;
  floater.style.filter = 'alpha(opacity=70)';
  floater.style.textAlign = 'center';
  floater.style.paddingTop = '120px';
  floater.style.position = 'fixed';
  floater.style.zIndex = '999';
  floater.style.width = '100%';
  floater.style.height = '100%';
  floater.style.top = '0px';
  floater.style.left = '0px';
  floater.style.color = '#000000';
  floater.innerHTML = 'There was a problem with a refresh request to the server. Please reload the page.';
  var body = document.getElementsByTagName('body')[0];
  body.appendChild(floater);
}

function player_action(action)
{
  var act2 = action;
  setAjaxLoading();
  ajaxGet('/action.json/' + action, function()
    {
      if ( ajax.readyState == 4 && ajax.status == 200 )
      {
        unsetAjaxLoading();
        refresh_playlist();
      }
    });
}

function jump_to_song(tid)
{
  setAjaxLoading();
  if ( tid == current_track )
    return false;
  if ( !allow_control )
    return false;
  ajaxGet('/action.json/jump/' + tid, function()
    {
      if ( ajax.readyState == 4 && ajax.status == 200 )
      {
        unsetAjaxLoading();
        var response = (' ' + ajax.responseText).substr(1);
        // quickie JSON parser :)
        response = eval('(' + response + ')');
        
        // update track number
        var ot_id = 'track_' + current_track;
        var nt_id = 'track_' + tid;
        current_track = tid;
        if ( $(ot_id).hasClass('current') )
        {
          $(ot_id).rmClass('current');
        }
        if ( ! $(nt_id).hasClass('current') )
        {
          $(nt_id).addClass('current');
        }
        // update pulsar
        pulsar_reset();
        // update playing status
        var img = $('btn_playpause').object.getElementsByTagName('img')[0];
        is_playing = true;
        img.src = img_play;
        // auto-refresh on track advance
        if ( ct_advance_timeout )
        {
          clearTimeout(ct_advance_timeout);
        }
        if ( ct_counter )
          clearInterval(ct_counter);
        var time_remaining = response.current_track_length - response.current_track_pos;
        current_track_length = response.current_track_length;
        current_track_pos = response.current_track_pos;
        if ( is_playing )
        {
          ct_advance_timeout = setTimeout(refresh_playlist, ( 1000 * time_remaining ));
          update_clock();
          ct_counter = setInterval(update_clock, 1000);
        }
        updateTitle(response.current_track_artist, response.current_track_album, response.current_track_title);
      }
    });
}

function set_playback_position(pos)
{
  pos = Math.round(( pos / 100 ) * current_track_length);
  setAjaxLoading();
  if ( !allow_control )
    return false;
  ajaxGet('/action.json/seek/' + pos, function()
    {
      if ( ajax.readyState == 4 && ajax.status == 200 )
      {
        unsetAjaxLoading();
        current_track_pos = pos;
        update_clock();
      }
    });
}

function update_clock()
{
  posslide_set_position((100 * (current_track_pos / current_track_length)));
  var str = secs_to_string(current_track_pos) + '/' + secs_to_string(current_track_length);
  $('playmeter').object.innerHTML = str;
  current_track_pos++;
}

function secs_to_string(time)
{
  var count_seconds = time % 60;
  var count_minutes = ( time - count_seconds ) / 60;
  if ( isNaN(count_seconds) )
    count_seconds = 0;
  if ( isNaN(count_minutes) )
    count_minutes = 0;
  return fill_zeroes(count_minutes) + ':' + fill_zeroes(count_seconds);
}

function fill_zeroes(str, len)
{
  if ( !len )
    len = 2;
  if ( typeof(str) == 'number' && str == 0 )
    str = '0';
  str = String(str);
  while ( str.length < len )
  {
    str = '0' + str;
  }
  return str;
}

var pl_refresh_id = setInterval(refresh_playlist, 10000);

window.onload = function(e)
{
  runOnloadHooks(e);
}

addOnloadHook(refresh_playlist);

// scroll to the current track
function jump_current_track()
{
  var top = $('track_' + current_track).Top() - 138;
  window.scroll(0, top);
  if ( typeof(fix_scroll) == 'function' )
  {
    fix_scroll();
  }
}

// pulse the current track
var pulsar_current = 0, pulsar_tdlist = [], pulsar_direction = 1;

var pulsar_reset = function()
{
  // remove any pulsar classes from items that aren't "current"
  var boobylist = document.getElementsByTagName('tr');
  for ( var i = 0; i < boobylist.length; i++ )
  {
    var booby = boobylist[i];
    var match = booby.className.match(/(^| )(pulsar[0-9])( |$)/);
    if ( match && !$(booby).hasClass('current') )
    {
      $(booby).rmClass(match[2]);
    }
  }
  // recalculate list of rows that should pulse
  var tdlist_new = document.getElementsByClassName('current', 'tr');
  if ( pulsar_current == 0 && tdlist_new == pulsar_tdlist )
  {
    return true;
  }
  // reset everything to 0
  pulsar_tdlist = tdlist_new;
  pulsar_current = 0;
  pulsar_direction = 1;
  for ( var i = 0; i < pulsar_tdlist.length; i++ )
  {
    var td = pulsar_reset[i];
    for ( var i = 1; i < 10; i++ )
    {
      if ( $(td).hasClass('pulsar' + i) )
      {
        $(td).rmClass('pulsar' + i);
      }
    }
    if ( ! $(td).hasClass('pulsar0') )
    {
      $(td).addClass('pulsar0');
    }
  }
}

var pulsar_advance = function()
{
  // this should be as optimized as possible, it should use a precalculated
  // list of elements to pulse and whatnot... heck even right now it's not
  // really as optimized as it should be due to the logic, but a lot of it's
  // kinda more or less necessary.
  if ( !is_playing )
    return true;
  if ( pulsar_current + pulsar_direction == 10 )
  {
    pulsar_direction = -1;
  }
  else if ( pulsar_current + pulsar_direction == -1 )
  {
    pulsar_direction = 1;
  }
  var nc = pulsar_current + pulsar_direction;
  for ( var i = 0; i < pulsar_tdlist.length; i++ )
  {
    $(pulsar_tdlist[i]).rmClass('pulsar' + pulsar_current).addClass('pulsar' + nc);
  }
  pulsar_current = nc;
}

addOnloadHook(pulsar_reset);
var pulsar_interval_id = setInterval(pulsar_advance, 50);

function updateTitle(artist, album, track)
{
  var sep = '';
  var str = '';
  if ( track )
  {
    str += sep + track;
    sep = ' - ';
  }
  if ( artist )
  {
    str += sep + artist;
    sep = ' - ';
  }
  if ( album )
  {
    str += sep + album;
    sep = ' - ';
  }
  str += sep + 'AmaroK Playlist';
  document.title = str;
}