/*
 * Copyright (C) 2010-2011 Julien BLACHE <jb@jblache.org>
 *
 * This program is free software; you can redistribute it 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 more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <inttypes.h>
#include <stdint.h>
#include <errno.h>
#include <time.h>
#include <pthread.h>

#if defined(__linux__)
# include <sys/timerfd.h>
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
# include <signal.h>
#endif

#ifdef HAVE_LIBEVENT2
# include <event2/event.h>
# include <event2/buffer.h>
#else
# include <event.h>
# define evbuffer_get_length(x) (x)->off
#endif

#include <gcrypt.h>

#include "db.h"
#include "daap_query.h"
#include "logger.h"
#include "mdns.h"
#include "conffile.h"
#include "misc.h"
#include "rng.h"
#include "player.h"
#include "raop.h"
#include "laudio.h"
#include "worker.h"
#include "listener.h"

#ifdef LASTFM
# include "lastfm.h"
#endif

/* These handle getting the media data */
#include "transcode.h"
#include "pipe.h"
#include "http.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif

#ifndef MIN
# define MIN(a, b) ((a < b) ? a : b)
#endif

#ifndef MAX
#define MAX(a, b) ((a > b) ? a : b)
#endif

// Default volume (must be from 0 - 100)
#define PLAYER_DEFAULT_VOLUME 50

enum player_sync_source
  {
    PLAYER_SYNC_CLOCK,
    PLAYER_SYNC_LAUDIO,
  };

struct volume_param {
  int volume;
  uint64_t spk_id;
};

struct player_command;
typedef int (*cmd_func)(struct player_command *cmd);

struct spk_enum
{
  spk_enum_cb cb;
  void *arg;
};

enum range_type
  {
    RANGEARG_NONE,
    RANGEARG_ID,
    RANGEARG_POS,
    RANGEARG_RANGE
  };

/*
 * Identifies an item or a range of items
 *
 * Depending on item_range.type the item(s) are identified by:
 * - item id (type = RANGEARG_ID) given in item_range.id
 * - item position (type = RANGEARG_POS) given in item_range.start_pos
 * - start and end position (type = RANGEARG_RANGE) given in item_range.start_pos to item_range.end_pos
 *
 * The pointer id_ptr may be set to an item id by the called function.
 */
struct item_range
{
  enum range_type type;

  uint32_t id;
  int start_pos;
  int end_pos;

  char shuffle;

  uint32_t *id_ptr;
};

struct icy_artwork
{
  uint32_t id;
  char *artwork_url;
};

struct player_metadata
{
  int id;
  uint64_t rtptime;
  uint64_t offset;
  int startup;

  struct raop_metadata *rmd;
};

struct player_command
{
  pthread_mutex_t lck;
  pthread_cond_t cond;

  cmd_func func;
  cmd_func func_bh;

  int nonblock;

  union {
    struct volume_param vol_param;
    void *noarg;
    struct spk_enum *spk_enum;
    struct raop_device *rd;
    struct player_status *status;
    struct player_source *ps;
    struct player_metadata *pmd;
    uint32_t *id_ptr;
    uint64_t *raop_ids;
    enum repeat_mode mode;
    uint32_t id;
    int intval;
    int ps_pos[2];
    struct item_range item_range;
    struct icy_artwork icy;
  } arg;

  int ret;

  int raop_pending;

  struct player_queue *queue;
};

/* Keep in sync with enum raop_devtype */
static const char *raop_devtype[] =
  {
    "AirPort Express 1 - 802.11g",
    "AirPort Express 2 - 802.11n",
    "AirPort Express 3 - 802.11n",
    "AppleTV",
    "Other",
  };


struct event_base *evbase_player;

static int exit_pipe[2];
static int cmd_pipe[2];
static int player_exit;
static struct event *exitev;
static struct event *cmdev;
static pthread_t tid_player;

/* Player status */
static enum play_status player_state;
static enum repeat_mode repeat;
static char shuffle;

/* Playback timer */
#if defined(__linux__)
static int pb_timer_fd;
#else
timer_t pb_timer;
#endif
static struct event *pb_timer_ev;
static struct timespec pb_timer_last;
static struct timespec packet_timer_last;
static uint64_t MINIMUM_STREAM_PERIOD;
static struct timespec packet_time = { 0, AIRTUNES_V2_STREAM_PERIOD };
static struct timespec timer_res;

/* Sync source */
static enum player_sync_source pb_sync_source;

/* Sync values */
static struct timespec pb_pos_stamp;
static uint64_t pb_pos;

/* Stream position (packets) */
static uint64_t last_rtptime;

/* AirPlay devices */
static int dev_autoselect;
static struct raop_device *dev_list;

/* Device status */
static enum laudio_state laudio_status;
static int laudio_selected;
static int laudio_volume;
static int laudio_relvol;
static int raop_sessions;

/* Commands */
static struct player_command *cur_cmd;

/* Last commanded volume */
static int master_volume;

/* Shuffle RNG state */
struct rng_ctx shuffle_rng;

/* Audio source */
static struct player_source *source_head;
static struct player_source *shuffle_head;
static struct player_source *cur_playing;
static struct player_source *cur_streaming;
static uint32_t cur_plid;
static uint32_t cur_plversion;
static struct evbuffer *audio_buf;

/* Play history */
static struct player_history *history;

/* Command helpers */
static void
command_async_end(struct player_command *cmd)
{
  cur_cmd = NULL;

  pthread_cond_signal(&cmd->cond);
  pthread_mutex_unlock(&cmd->lck);

  /* Process commands again */
  event_add(cmdev, NULL);
}

static void
command_init(struct player_command *cmd)
{
  memset(cmd, 0, sizeof(struct player_command));

  pthread_mutex_init(&cmd->lck, NULL);
  pthread_cond_init(&cmd->cond, NULL);
}

static void
command_deinit(struct player_command *cmd)
{
  pthread_cond_destroy(&cmd->cond);
  pthread_mutex_destroy(&cmd->lck);
}


static void
status_update(enum play_status status)
{
  player_state = status;

  listener_notify(LISTENER_PLAYER);

  if (status == PLAY_PLAYING)
    dev_autoselect = 0;
}


/* Volume helpers */
static int
rel_to_vol(int relvol)
{
  float vol;

  if (relvol == 100)
    return master_volume;

  vol = ((float)relvol * (float)master_volume) / 100.0;

  return (int)vol;
}

static int
vol_to_rel(int volume)
{
  float rel;

  if (volume == master_volume)
    return 100;

  rel = ((float)volume / (float)master_volume) * 100.0;

  return (int)rel;
}

/* Master volume helpers */
static void
volume_master_update(int newvol)
{
  struct raop_device *rd;

  master_volume = newvol;

  if (laudio_selected)
    laudio_relvol = vol_to_rel(laudio_volume);

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd->selected)
	rd->relvol = vol_to_rel(rd->volume);
    }
}

static void
volume_master_find(void)
{
  struct raop_device *rd;
  int newmaster;

  newmaster = -1;

  if (laudio_selected)
    newmaster = laudio_volume;

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd->selected && (rd->volume > newmaster))
	newmaster = rd->volume;
    }

  volume_master_update(newmaster);
}


/* Device select/deselect hooks */
static void
speaker_select_laudio(void)
{
  laudio_selected = 1;

  if (laudio_volume > master_volume)
    {
      if (player_state == PLAY_STOPPED || master_volume == -1)
	volume_master_update(laudio_volume);
      else
	laudio_volume = master_volume;
    }

  laudio_relvol = vol_to_rel(laudio_volume);
}

static void
speaker_select_raop(struct raop_device *rd)
{
  rd->selected = 1;

  if (rd->volume > master_volume)
    {
      if (player_state == PLAY_STOPPED || master_volume == -1)
	volume_master_update(rd->volume);
      else
	rd->volume = master_volume;
    }

  rd->relvol = vol_to_rel(rd->volume);
}

static void
speaker_deselect_laudio(void)
{
  laudio_selected = 0;

  if (laudio_volume == master_volume)
    volume_master_find();
}

static void
speaker_deselect_raop(struct raop_device *rd)
{
  rd->selected = 0;

  if (rd->volume == master_volume)
    volume_master_find();
}


static int
player_get_current_pos_clock(uint64_t *pos, struct timespec *ts, int commit)
{
  uint64_t delta;
  int ret;

  ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &timer_res);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno));

      return -1;
    }

  delta = (ts->tv_sec - pb_pos_stamp.tv_sec) * 1000000 + (ts->tv_nsec - pb_pos_stamp.tv_nsec) / 1000;

#ifdef DEBUG_SYNC
  DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " usec\n", delta);
#endif

  delta = (delta * 44100) / 1000000;

#ifdef DEBUG_SYNC
  DPRINTF(E_DBG, L_PLAYER, "Delta is %" PRIu64 " samples\n", delta);
#endif

  *pos = pb_pos + delta;

  if (commit)
    {
      pb_pos = *pos;

      pb_pos_stamp.tv_sec = ts->tv_sec;
      pb_pos_stamp.tv_nsec = ts->tv_nsec;

#ifdef DEBUG_SYNC
      DPRINTF(E_DBG, L_PLAYER, "Pos: %" PRIu64 " (clock)\n", *pos);
#endif
    }

  return 0;
}

static int
player_get_current_pos_laudio(uint64_t *pos, struct timespec *ts, int commit)
{
  int ret;

  *pos = laudio_get_pos();

  ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &timer_res);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno));

      return -1;
    }

  if (commit)
    {
      pb_pos = *pos;

      pb_pos_stamp.tv_sec = ts->tv_sec;
      pb_pos_stamp.tv_nsec = ts->tv_nsec;

#ifdef DEBUG_SYNC
      DPRINTF(E_DBG, L_PLAYER, "Pos: %" PRIu64 " (laudio)\n", *pos);
#endif
    }

  return 0;
}

int
player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
{
  switch (pb_sync_source)
    {
      case PLAYER_SYNC_CLOCK:
	return player_get_current_pos_clock(pos, ts, commit);

      case PLAYER_SYNC_LAUDIO:
	return player_get_current_pos_laudio(pos, ts, commit);
    }

  return -1;
}

static int
pb_timer_start(struct timespec *ts)
{
  struct itimerspec next;
  int ret;

  next.it_interval.tv_sec = 0;
  next.it_interval.tv_nsec = 0;
  next.it_value.tv_sec = ts->tv_sec;
  next.it_value.tv_nsec = ts->tv_nsec;

#if defined(__linux__)
  ret = timerfd_settime(pb_timer_fd, TFD_TIMER_ABSTIME, &next, NULL);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not arm playback timer: %s\n", strerror(errno));

      return -1;
    }

  ret = event_add(pb_timer_ev, NULL);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not add playback timer event\n");

      return -1;
    }
#else
  ret = timer_settime(pb_timer, TIMER_ABSTIME, &next, NULL);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not arm playback timer: %s\n", strerror(errno));

      return -1;
    }
#endif

  return 0;
}

static int
pb_timer_stop(void)
{
  struct itimerspec next;
  int ret;

  memset(&next, 0, sizeof(struct itimerspec));

#if defined(__linux__)
  ret = timerfd_settime(pb_timer_fd, TFD_TIMER_ABSTIME, &next, NULL);

  event_del(pb_timer_ev);
#else
  ret = timer_settime(pb_timer, TIMER_ABSTIME, &next, NULL);
#endif
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not disarm playback timer: %s\n", strerror(errno));

      return -1;
    }

  return 0;
}

/* Forward */
static void
playback_abort(void);

static int
queue_clear(struct player_command *cmd);

static void
player_metadata_send(struct player_metadata *pmd);

static void
player_laudio_status_cb(enum laudio_state status)
{
  struct timespec ts;
  uint64_t pos;

  switch (status)
    {
      /* Switch sync to clock sync */
      case LAUDIO_STOPPING:
	DPRINTF(E_DBG, L_PLAYER, "Local audio stopping\n");

	laudio_status = status;

	/* Synchronize pb_pos and pb_pos_stamp before laudio stops entirely */
	player_get_current_pos_laudio(&pos, &ts, 1);

	pb_sync_source = PLAYER_SYNC_CLOCK;
	break;

      /* Switch sync to laudio sync */
      case LAUDIO_RUNNING:
	DPRINTF(E_DBG, L_PLAYER, "Local audio running\n");

	laudio_status = status;

	pb_sync_source = PLAYER_SYNC_LAUDIO;
	break;

      case LAUDIO_FAILED:
	DPRINTF(E_DBG, L_PLAYER, "Local audio failed\n");

	pb_sync_source = PLAYER_SYNC_CLOCK;

	laudio_close();

	if (raop_sessions == 0)
	  playback_abort();

	speaker_deselect_laudio();
	break;

      default:
      	laudio_status = status;
	break;
    }
}

/* Callback from the worker thread (async operation as it may block) */
static void
playcount_inc_cb(void *arg)
{
  int *id = arg;

  db_file_inc_playcount(*id);
}

#ifdef LASTFM
/* Callback from the worker thread (async operation as it may block) */
static void
scrobble_cb(void *arg)
{
  int *id = arg;

  lastfm_scrobble(*id);
}
#endif

/* Callback from the worker thread
 * This prepares metadata in the worker thread, since especially the artwork
 * retrieval may take some time. raop_metadata_prepare() is thread safe. The
 * sending, however, must be done in the player thread.
 */
static void
metadata_prepare_cb(void *arg)
{
  struct player_metadata *pmd = arg;

  pmd->rmd = raop_metadata_prepare(pmd->id);

  if (pmd->rmd)
    player_metadata_send(pmd);
}

/* Callback from the worker thread (async operation as it may block) */
static void
update_icy_cb(void *arg)
{
  struct http_icy_metadata *metadata = arg;

  db_file_update_icy(metadata->id, metadata->artist, metadata->title);

  http_icy_metadata_free(metadata, 1);
}

/* Metadata */
static void
metadata_prune(uint64_t pos)
{
  raop_metadata_prune(pos);
}

static void
metadata_purge(void)
{
  raop_metadata_purge();
}

static void
metadata_trigger(struct player_source *ps, int startup)
{
  struct player_metadata pmd;

  memset(&pmd, 0, sizeof(struct player_metadata));

  pmd.id = ps->id;
  pmd.startup = startup;

  /* Determine song boundaries, dependent on context */

  /* Restart after pause/seek */
  if (ps->stream_start)
    {
      pmd.offset = ps->output_start - ps->stream_start;
      pmd.rtptime = ps->stream_start;
    }
  else if (startup)
    {
      /* Will be set later, right before sending */
    }
  /* Generic case */
  else if (cur_streaming && (cur_streaming->end))
    {
      pmd.rtptime = cur_streaming->end + 1;
    }
  else
    {
      DPRINTF(E_LOG, L_PLAYER, "PTOH! Unhandled song boundary case in metadata_trigger()\n");
    }

  /* Defer the actual work of preparing the metadata to the worker thread */
  worker_execute(metadata_prepare_cb, &pmd, sizeof(struct player_metadata), 0);
}

/* Checks if there is new HTTP ICY metadata, and if so sends updates to clients */
void
metadata_check_icy(void)
{
  struct http_icy_metadata *metadata;
  int changed;

  transcode_metadata(cur_streaming->ctx, &metadata, &changed);
  if (!metadata)
    return;

  if (!changed || !metadata->title)
    goto no_update;

  if (metadata->title[0] == '\0')
    goto no_update;

  metadata->id = cur_streaming->id;

  /* Defer the database update to the worker thread */
  worker_execute(update_icy_cb, metadata, sizeof(struct http_icy_metadata), 0);

  /* Triggers preparing and sending RAOP metadata */
  metadata_trigger(cur_streaming, 0);

  /* Only free the struct, the content must be preserved for update_icy_cb */
  free(metadata);

  status_update(player_state);

  return;

 no_update:
  http_icy_metadata_free(metadata, 0);
}

/* Audio sources */

/* Helper */
static struct player_source *
next_ps(struct player_source *ps, char shuffle)
{
  if (shuffle)
    return ps->shuffle_next;
  else
    return ps->pl_next;
}

/* Thread: httpd (DACP) */
struct player_source *
player_queue_make(struct query_params *qp)
{
  struct db_media_file_info dbmfi;
  struct player_source *q_head;
  struct player_source *q_tail;
  struct player_source *ps;
  uint32_t id;
  uint32_t song_length;
  int ret;

  ret = db_query_start(qp);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not start query\n");

      return NULL;
    }

  DPRINTF(E_DBG, L_PLAYER, "Player queue query returned %d items\n", qp->results);

  q_head = NULL;
  q_tail = NULL;
  while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id))
    {
      ret = safe_atou32(dbmfi.id, &id);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n");

	  continue;
	}

      ret = safe_atou32(dbmfi.song_length, &song_length);
      if (ret < 0)
      	{
      	  DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n");

      	  continue;
      	}

      ps = (struct player_source *)malloc(sizeof(struct player_source));
      if (!ps)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Out of memory for struct player_source\n");

	  ret = -1;
	  break;
	}

      memset(ps, 0, sizeof(struct player_source));

      ps->id = id;
      ps->len_ms = song_length;

      if (!q_head)
	q_head = ps;

      if (q_tail)
	{
	  q_tail->pl_next = ps;
	  ps->pl_prev = q_tail;

	  q_tail->shuffle_next = ps;
	  ps->shuffle_prev = q_tail;
	}

      q_tail = ps;

      DPRINTF(E_DBG, L_PLAYER, "Added song id %d (%s)\n", id, dbmfi.title);
    }

  db_query_end(qp);

  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Error fetching results\n");

      return NULL;
    }

  if (!q_head)
    return NULL;

  q_head->pl_prev = q_tail;
  q_tail->pl_next = q_head;
  q_head->shuffle_prev = q_tail;
  q_tail->shuffle_next = q_head;

  return q_head;
}

static int
find_first_song_id(const char *query)
{
  struct db_media_file_info dbmfi;
  struct query_params qp;
  int id;
  int ret;

  memset(&qp, 0, sizeof(struct query_params));

  /* We only want the id of the first song */
  qp.type = Q_ITEMS;
  qp.idx_type = I_FIRST;
  qp.sort = S_NONE;
  qp.offset = 0;
  qp.limit = 1;
  qp.filter = daap_query_parse_sql(query);
  if (!qp.filter)
    {
      DPRINTF(E_LOG, L_PLAYER, "Improper DAAP query!\n");

      return -1;
    }

  ret = db_query_start(&qp);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not start query\n");

      goto no_query_start;
    }

  if (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
    {
      ret = safe_atoi32(dbmfi.id, &id);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Invalid song id in query result!\n");

	  goto no_result;
	}

      DPRINTF(E_DBG, L_PLAYER, "Found index song (id %d)\n", id);
      ret = 1;
    }
  else
    {
      DPRINTF(E_LOG, L_PLAYER, "No song matches query (results %d): %s\n", qp.results, qp.filter);

      goto no_result;
    }

 no_result:
  db_query_end(&qp);

 no_query_start:
  if (qp.filter)
    free(qp.filter);

  if (ret == 1)
    return id;
  else
    return -1;
}


/* Thread: httpd (DACP) */
int
player_queue_make_daap(struct player_source **head, const char *query, const char *queuefilter, const char *sort, int quirk)
{
  struct media_file_info *mfi;
  struct query_params qp;
  struct player_source *ps;
  int64_t albumid;
  int64_t artistid;
  int plid;
  int id;
  int idx;
  int ret;
  int len;
  char *s;
  char buf[1024];

  if (query)
    {
      id = find_first_song_id(query);
      if (id < 0)
        return -1;
    }
  else
    id = 0;

  memset(&qp, 0, sizeof(struct query_params));

  qp.offset = 0;
  qp.limit = 0;
  qp.sort = S_NONE;
  qp.idx_type = I_NONE;

  if (quirk)
    {
      qp.sort = S_ALBUM;
      qp.type = Q_ITEMS;
      mfi = db_file_fetch_byid(id);
      if (!mfi)
	return -1;
      snprintf(buf, sizeof(buf), "f.songalbumid = %" PRIi64, mfi->songalbumid);
      free_mfi(mfi, 0);
      qp.filter = strdup(buf);
    }
  else if (queuefilter)
    {
      len = strlen(queuefilter);
      if ((len > 6) && (strncmp(queuefilter, "album:", 6) == 0))
	{
	  qp.type = Q_ITEMS;
	  ret = safe_atoi64(strchr(queuefilter, ':') + 1, &albumid);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Invalid album id in queuefilter: %s\n", queuefilter);

	      return -1;
	    }
	  snprintf(buf, sizeof(buf), "f.songalbumid = %" PRIi64, albumid);
	  qp.filter = strdup(buf);
	}
      else if ((len > 7) && (strncmp(queuefilter, "artist:", 7) == 0))
	{
	  qp.type = Q_ITEMS;
	  ret = safe_atoi64(strchr(queuefilter, ':') + 1, &artistid);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Invalid artist id in queuefilter: %s\n", queuefilter);

	      return -1;
	    }
	  snprintf(buf, sizeof(buf), "f.songartistid = %" PRIi64, artistid);
	  qp.filter = strdup(buf);
	}
      else if ((len > 9) && (strncmp(queuefilter, "playlist:", 9) == 0))
	{
	  qp.type = Q_PLITEMS;
	  ret = safe_atoi32(strchr(queuefilter, ':') + 1, &plid);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Invalid playlist id in queuefilter: %s\n", queuefilter);

	      return -1;
	    }
	  qp.id = plid;
	  qp.filter = strdup("1 = 1");
	}
      else if ((len > 6) && (strncmp(queuefilter, "genre:", 6) == 0))
	{
	  qp.type = Q_ITEMS;
	  s = db_escape_string(queuefilter + 6);
	  if (!s)
	    return -1;
	  snprintf(buf, sizeof(buf), "f.genre = '%s'", s);
	  qp.filter = strdup(buf);
	}
      else
	{
	  DPRINTF(E_LOG, L_PLAYER, "Unknown queuefilter %s\n", queuefilter);

	  // If the queuefilter is unkown, ignore it and use the query parameter instead to build the sql query
	  id = 0;
	  qp.type = Q_ITEMS;
	  qp.filter = daap_query_parse_sql(query);
	}
    }
  else
    {
      id = 0;
      qp.type = Q_ITEMS;
      qp.filter = daap_query_parse_sql(query);
    }

  if (sort)
    {
      if (strcmp(sort, "name") == 0)
	qp.sort = S_NAME;
      else if (strcmp(sort, "album") == 0)
	qp.sort = S_ALBUM;
      else if (strcmp(sort, "artist") == 0)
	qp.sort = S_ARTIST;
    }

  ps = player_queue_make(&qp);

  if (qp.filter)
    free(qp.filter);

  if (ps)
    *head = ps;
  else
    return -1;

  idx = 0;
  while (id && ps && ps->pl_next && (ps->id != id) && (ps->pl_next != *head))
    {
      idx++;
      ps = ps->pl_next;
    }

  return idx;
}

struct player_source *
player_queue_make_pl(int plid, uint32_t *id)
{
  struct query_params qp;
  struct player_source *ps;
  struct player_source *p;
  uint32_t i;
  char buf[124];

  memset(&qp, 0, sizeof(struct query_params));

  if (plid)
    {
      qp.id = plid;
      qp.type = Q_PLITEMS;
      qp.offset = 0;
      qp.limit = 0;
      qp.sort = S_NONE;
    }
  else if (*id)
    {
      qp.id = 0;
      qp.type = Q_ITEMS;
      qp.offset = 0;
      qp.limit = 0;
      qp.sort = S_NONE;
      snprintf(buf, sizeof(buf), "f.id = %" PRIu32, *id);
      qp.filter = strdup(buf);
    }
  else
    return NULL;

  qp.idx_type = I_NONE;

  ps = player_queue_make(&qp);

  if (qp.filter)
    free(qp.filter);

  /* Shortcut for shuffled playlist */
  if (*id == 0)
    return ps;

  p = ps;
  i = 0;
  do
    {
      if (p->id == *id)
	{
	  *id = i;
	  break;
	}

      p = p->pl_next;
      i++;
    }
  while (p != ps);

  return ps;
}

struct player_source *
player_queue_make_mpd(char *path, int recursive)
{
  struct query_params qp;
  struct player_source *ps;

  memset(&qp, 0, sizeof(struct query_params));

  qp.type = Q_ITEMS;
  qp.idx_type = I_NONE;
  qp.sort = S_ALBUM;

  if (recursive)
    {
      qp.filter = sqlite3_mprintf("f.virtual_path LIKE '/%q%%'", path);
      if (!qp.filter)
	DPRINTF(E_DBG, L_PLAYER, "Out of memory\n");
    }
  else
    {
      qp.filter = sqlite3_mprintf("f.virtual_path LIKE '/%q'", path);
      if (!qp.filter)
	DPRINTF(E_DBG, L_PLAYER, "Out of memory\n");
    }

  ps = player_queue_make(&qp);

  sqlite3_free(qp.filter);
  return ps;
}

static void
source_free(struct player_source *ps)
{
  switch (ps->data_kind)
    {
      case DATA_KIND_FILE:
      case DATA_KIND_HTTP:
	if (ps->ctx)
	  transcode_cleanup(ps->ctx);
	break;

      case DATA_KIND_SPOTIFY:
#ifdef HAVE_SPOTIFY_H
	spotify_playback_stop();
#endif
	break;

      case DATA_KIND_PIPE:
	pipe_cleanup();
	break;
    }

  free(ps);
}

static void
source_stop(struct player_source *ps)
{
  struct player_source *tmp;

  while (ps)
    {
      switch (ps->data_kind)
	{
	  case DATA_KIND_FILE:
	  case DATA_KIND_HTTP:
	    if (ps->ctx)
	      {
		transcode_cleanup(ps->ctx);
		ps->ctx = NULL;
	      }
	    break;

          case DATA_KIND_SPOTIFY:
#ifdef HAVE_SPOTIFY_H
	    spotify_playback_stop();
#endif
	    break;

	  case DATA_KIND_PIPE:
	    pipe_cleanup();
	    break;
        }

      tmp = ps;
      ps = ps->play_next;

      tmp->play_next = NULL;
    }
}

/*
 * Shuffles the items between head and tail (excluding head and tail)
 */
static void
source_shuffle(struct player_source *head, struct player_source *tail)
{
  struct player_source *ps;
  struct player_source **ps_array;
  int nitems;
  int i;

  if (!head)
    return;

  if (!tail)
    return;

  if (!shuffle)
    {
      ps = head;
      do
	{
	  ps->shuffle_next = ps->pl_next;
	  ps->shuffle_prev = ps->pl_prev;
	  ps = ps->pl_next;
	}
      while (ps != head);
    }

  // Count items in queue (excluding head and tail)
  ps = head->shuffle_next;
  if (!cur_streaming)
    nitems = 1;
  else
    nitems = 0;
  while (ps != tail)
    {
      nitems++;
      ps = ps->shuffle_next;
    }

  // Do not reshuffle queue with one item
  if (nitems < 1)
    return;

  // Construct array for number of items in queue
  ps_array = (struct player_source **)malloc(nitems * sizeof(struct player_source *));
  if (!ps_array)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not allocate memory for shuffle array\n");
      return;
    }

  // Fill array with items in queue (excluding head and tail)
  if (cur_streaming)
    ps = head->shuffle_next;
  else
    ps = head;
  i = 0;
  do
    {
      ps_array[i] = ps;

      ps = ps->shuffle_next;
      i++;
    }
  while (ps != tail);

  shuffle_ptr(&shuffle_rng, (void **)ps_array, nitems);

  for (i = 0; i < nitems; i++)
    {
      ps = ps_array[i];

      if (i > 0)
	ps->shuffle_prev = ps_array[i - 1];

      if (i < (nitems - 1))
	ps->shuffle_next = ps_array[i + 1];
    }

  // Insert shuffled items between head and tail
  if (cur_streaming)
    {
      ps_array[0]->shuffle_prev = head;
      ps_array[nitems - 1]->shuffle_next = tail;
      head->shuffle_next = ps_array[0];
      tail->shuffle_prev = ps_array[nitems - 1];
    }
  else
    {
      ps_array[0]->shuffle_prev = ps_array[nitems - 1];
      ps_array[nitems - 1]->shuffle_next = ps_array[0];
      shuffle_head = ps_array[0];
    }

  free(ps_array);

  return;
}

static void
source_reshuffle(void)
{
  struct player_source *head;
  struct player_source *tail;

  if (cur_streaming)
    head = cur_streaming;
  else if (shuffle)
    head = shuffle_head;
  else
    head = source_head;

  if (repeat == REPEAT_ALL)
    tail = head;
  else if (shuffle)
    tail = shuffle_head;
  else
    tail = source_head;

  source_shuffle(head, tail);

  if (repeat == REPEAT_ALL)
    shuffle_head = head;
}

/* Helper */
static int
source_open(struct player_source *ps, int no_md, int seek)
{
  struct media_file_info *mfi;
  char *url;
  int ret;

  ps->setup_done = 0;
  ps->stream_start = 0;
  ps->output_start = 0;
  ps->end = 0;
  ps->play_next = NULL;

  mfi = db_file_fetch_byid(ps->id);
  if (!mfi)
    {
      DPRINTF(E_LOG, L_PLAYER, "Couldn't fetch file id %d\n", ps->id);

      return -1;
    }

  if (mfi->disabled)
    {
      DPRINTF(E_DBG, L_PLAYER, "File id %d is disabled, skipping\n", ps->id);

      free_mfi(mfi, 0);
      return -1;
    }

  DPRINTF(E_INFO, L_PLAYER, "Opening '%s' (%s)\n", mfi->title, mfi->path);

  ps->data_kind = mfi->data_kind;
  ps->media_kind = mfi->media_kind;

  // Setup the source type responsible for getting the audio
  switch (mfi->data_kind)
    {
      case DATA_KIND_HTTP:
	ret = http_stream_setup(&url, mfi->path);
	if (ret < 0)
	  break;

	free(mfi->path);
	mfi->path = url;

	ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
	break;

      case DATA_KIND_SPOTIFY:
#ifdef HAVE_SPOTIFY_H
	ret = spotify_playback_play(mfi);
	if (seek && mfi->seek)
	  {
	    DPRINTF(E_DBG, L_PLAYER, "Source id %d started with seek %d\n", ps->id, mfi->seek);
	    ret = spotify_playback_seek(mfi->seek);
	  }
#else
	ret = -1;
#endif
	break;

      case DATA_KIND_PIPE:
	ret = pipe_setup(mfi);
	break;

      default:
	ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
	if (seek && mfi->seek)
	  {
	    DPRINTF(E_DBG, L_PLAYER, "Source id %d started with seek %d\n", ps->id, mfi->seek);
	    ret = transcode_seek(ps->ctx, mfi->seek);
	  }
    }

  free_mfi(mfi, 0);

  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not open file id %d\n", ps->id);

      return -1;
    }

  if (!no_md)
    metadata_trigger(ps, (player_state == PLAY_PLAYING) ? 0 : 1);

  ps->setup_done = 1;

  return ret;
}

static int
source_next(int force)
{
  struct player_source *ps;
  struct player_source *head;
  struct player_source *limit;
  enum repeat_mode r_mode;
  int ret;

  head = (shuffle) ? shuffle_head : source_head;
  limit = head;
  r_mode = repeat;

  /* Force repeat mode at user request */
  if (force && (r_mode == REPEAT_SONG))
    r_mode = REPEAT_ALL;

  /* Playlist has only one file, treat REPEAT_ALL as REPEAT_SONG */
  if ((r_mode == REPEAT_ALL) && (source_head == source_head->pl_next))
    r_mode = REPEAT_SONG;
  /* Playlist has only one file, not a user action, treat as REPEAT_ALL
   * and source_check() will stop playback
   */
  else if (!force && (r_mode == REPEAT_OFF) && (source_head == source_head->pl_next))
    r_mode = REPEAT_SONG;

  if (!cur_streaming)
    ps = head;
  else
    ps = next_ps(cur_streaming, shuffle);

  switch (r_mode)
    {
      case REPEAT_SONG:
	if (!cur_streaming)
	  break;

	if ((cur_streaming->data_kind == DATA_KIND_FILE) && cur_streaming->ctx)
	  {
	    ret = transcode_seek(cur_streaming->ctx, 0);

	    /* source_open() takes care of sending metadata, but we don't
	     * call it when repeating a song as we just seek back to 0
	     * so we have to handle metadata ourselves here
	     */
	    if (ret >= 0)
	      metadata_trigger(cur_streaming, 0);
	  }
	else
	  ret = source_open(cur_streaming, force, 0);

	if (ret < 0)
	  {
	    DPRINTF(E_LOG, L_PLAYER, "Failed to restart song for song repeat\n");

	    return -1;
	  }

	return 0;

      case REPEAT_ALL:
	if (!shuffle)
	  {
	    limit = ps;
	    break;
	  }

	/* Reshuffle before repeating playlist */
	if (cur_streaming && (ps == shuffle_head))
	  {
	    source_reshuffle();
	    ps = shuffle_head;
	  }

	limit = shuffle_head;

	break;

      case REPEAT_OFF:
	limit = head;

	if (force && (ps == limit))
	  {
	    DPRINTF(E_DBG, L_PLAYER, "End of playlist reached and repeat is OFF\n");

	    playback_abort();
	    return 0;
	  }
	break;
    }

  do
    {
      ret = source_open(ps, force, 0);
      if (ret < 0)
	{
	  if (shuffle)
	    ps = ps->shuffle_next;
	  else
	    ps = ps->pl_next;

	  continue;
	}

      break;
    }
  while (ps != limit);

  /* Couldn't open any of the files in our queue */
  if (ret < 0)
    {
      DPRINTF(E_WARN, L_PLAYER, "Could not open any file in the queue (next)\n");

      return -1;
    }

  if (!force && cur_streaming)
    cur_streaming->play_next = ps;

  cur_streaming = ps;

  return 0;
}

static int
source_prev(void)
{
  struct player_source *ps;
  struct player_source *head;
  struct player_source *limit;
  int ret;

  if (!cur_streaming)
    return -1;

  head = (shuffle) ? shuffle_head : source_head;
  ps = (shuffle) ? cur_streaming->shuffle_prev : cur_streaming->pl_prev;
  limit = ps;

  if ((repeat == REPEAT_OFF) && (cur_streaming == head))
    {
      DPRINTF(E_DBG, L_PLAYER, "Start of playlist reached and repeat is OFF\n");

      playback_abort();
      return 0;
    }

  /* We are not reshuffling on prev calls in the shuffle case - should we? */

  do
    {
      ret = source_open(ps, 1, 0);
      if (ret < 0)
	{
	  if (shuffle)
	    ps = ps->shuffle_prev;
	  else
	    ps = ps->pl_prev;

	  continue;
	}

      break;
    }
  while (ps != limit);

  /* Couldn't open any of the files in our queue */
  if (ret < 0)
    {
      DPRINTF(E_WARN, L_PLAYER, "Could not open any file in the queue (prev)\n");

      return -1;
    }

  cur_streaming = ps;

  return 0;
}

/*
 * Returns the position of the given player_source (ps) in the playqueue or shufflequeue.
 * First song in the queue has position 0. Depending on the 'shuffle' argument,
 * the position is either determined in the playqueue or shufflequeue.
 *
 * @param ps the source to search in the queue
 * @param shuffle 0 search in the playqueue, 1 search in the shufflequeue
 * @return position 0-based in the queue (-1 if not found)
 */
static int
source_position(struct player_source *source, char shuffle)
{
  struct player_source *ps;
  struct player_source *head;
  int ret;

  head = shuffle ? shuffle_head : source_head;
  if (!head)
    return -1;

  if (source == head)
    return 0;

  ret = 0;
  for (ps = next_ps(head, shuffle); ps != head; ps = next_ps(ps, shuffle))
    {
      ret++;
      if (ps == source)
	return ret;
    }

  DPRINTF(E_LOG, L_PLAYER, "Bug! source_position was given non-existent source\n");

  return -1;
}

static uint32_t
source_count()
{
  struct player_source *ps;
  uint32_t ret;

  ret = 0;

  if (source_head)
    {
      ret++;
      for (ps = source_head->pl_next; ps != source_head; ps = ps->pl_next)
	ret++;
    }

  return ret;
}

/*
 * Updates cur_playing and notifies remotes and raop devices about
 * changes.
 */
static uint64_t
source_check(void)
{
  struct timespec ts;
  struct player_source *ps;
  struct player_source *head;
  uint64_t pos;
  enum repeat_mode r_mode;
  int i;
  int id;
  int ret;

  if (!cur_streaming)
    return 0;

  ret = player_get_current_pos(&pos, &ts, 0);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Couldn't get current playback position\n");

      return 0;
    }

  /* If cur_playing is NULL, we are still in the first two seconds after starting the stream */
  if (!cur_playing)
    {
      if (pos >= cur_streaming->output_start)
	{
	  cur_playing = cur_streaming;
	  status_update(PLAY_PLAYING);

	  /* Start of streaming, no metadata to prune yet */
	}

      return pos;
    }

  /* Check if we are still in the middle of the current playing song */
  if ((cur_playing->end == 0) || (pos < cur_playing->end))
    return pos;

  /* We have reached the end of the current playing song, update cur_playing to the next song in the queue
     and initialize stream_start and output_start values. */

  r_mode = repeat;
  /* Playlist has only one file, treat REPEAT_ALL as REPEAT_SONG */
  if ((r_mode == REPEAT_ALL) && (source_head == source_head->pl_next))
    r_mode = REPEAT_SONG;

  if (r_mode == REPEAT_SONG)
    {
      ps = cur_playing;

      /* Check that we haven't gone to the next file already
       * (repeat song toggled in the last 2 seconds of a song)
       */
      if (cur_playing->play_next)
	{
	  cur_playing = cur_playing->play_next;

	  if (ps->setup_done)
	    {
	      if ((ps->data_kind == DATA_KIND_FILE) && ps->ctx)
		{
	          transcode_cleanup(ps->ctx);
	          ps->ctx = NULL;
		}
	      ps->play_next = NULL;
	    }
        }

      cur_playing->stream_start = ps->end + 1;
      cur_playing->output_start = cur_playing->stream_start;

      /* Do not use cur_playing to reset the end position, it may have changed */
      ps->end = 0;

      status_update(PLAY_PLAYING);

      metadata_prune(pos);

      return pos;
    }

  head = (shuffle) ? shuffle_head : source_head;

  i = 0;
  while (cur_playing && (cur_playing->end != 0) && (pos > cur_playing->end))
    {
      i++;

      id = (int)cur_playing->id;
      worker_execute(playcount_inc_cb, &id, sizeof(int), 5);
#ifdef LASTFM
      worker_execute(scrobble_cb, &id, sizeof(int), 8);
#endif

      /* Stop playback if:
       * - at end of playlist (NULL)
       * - repeat OFF and at end of playlist (wraparound)
       */
      if (!cur_playing->play_next
	  || ((r_mode == REPEAT_OFF) && (cur_playing->play_next == head)))
	{
	  playback_abort();

	  return pos;
        }

      ps = cur_playing;
      cur_playing = cur_playing->play_next;

      cur_playing->stream_start = ps->end + 1;
      cur_playing->output_start = cur_playing->stream_start;

      if (ps->setup_done)
	{
	  if ((ps->data_kind == DATA_KIND_FILE) && ps->ctx)
	    {
	      transcode_cleanup(ps->ctx);
	      ps->ctx = NULL;
	    }
	  ps->play_next = NULL;
	}
    }

  if (i > 0)
    {
      DPRINTF(E_DBG, L_PLAYER, "Playback switched to next song\n");

      status_update(PLAY_PLAYING);

      metadata_prune(pos);
    }

  return pos;
}

struct player_history *
player_history_get(void)
{
  return history;
}

/*
 * Add the song with the given id to the list of previously played songs
 */
static void
history_add(uint32_t id)
{
  unsigned int cur_index;
  unsigned int next_index;
  
  /* Check if the current song is already the last in the history to avoid duplicates */
  cur_index = (history->start_index + history->count - 1) % MAX_HISTORY_COUNT;
  if (id == history->id[cur_index])
    {
      DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming song already in history\n");
      return;
    }

  /* Calculate the next index and update the start-index and count for the id-buffer */
  next_index = (history->start_index + history->count) % MAX_HISTORY_COUNT;
  if (next_index == history->start_index && history->count > 0)
    history->start_index = (history->start_index + 1) % MAX_HISTORY_COUNT;

  history->id[next_index] = id;

  if (history->count < MAX_HISTORY_COUNT)
    history->count++;
}

static int
source_read(uint8_t *buf, int len, uint64_t rtptime)
{
  int new;
  int ret;
  int nbytes;
  int icy_timer;

  if (!cur_streaming)
    return 0;

  nbytes = 0;
  new = 0;
  while (nbytes < len)
    {
      if (new)
	{
	  DPRINTF(E_DBG, L_PLAYER, "New file\n");

	  new = 0;

	  // add song to the played history
	  history_add(cur_streaming->id);

	  ret = source_next(0);
	  if (ret < 0)
	    return -1;
	}

      if (evbuffer_get_length(audio_buf) == 0)
	{
	  switch (cur_streaming->data_kind)
	    {
	      case DATA_KIND_HTTP:
		ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer);

		if (icy_timer)
		  metadata_check_icy();
		break;

	      case DATA_KIND_FILE:
		ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes, &icy_timer);
		break;

#ifdef HAVE_SPOTIFY_H
	      case DATA_KIND_SPOTIFY:
		ret = spotify_audio_get(audio_buf, len - nbytes);
		break;
#endif

	      case DATA_KIND_PIPE:
		ret = pipe_audio_get(audio_buf, len - nbytes);
		break;

	      default:
		ret = -1;
	    }
	    
	  if (ret <= 0)
	    {
	      /* EOF or error */
	      cur_streaming->end = rtptime + BTOS(nbytes) - 1;

	      new = 1;
	      continue;
	    }
	}

      nbytes += evbuffer_remove(audio_buf, buf + nbytes, len - nbytes);
    }

  return nbytes;
}


static void
playback_write(void)
{
  uint8_t rawbuf[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
  int ret;

  source_check();
  /* Make sure playback is still running after source_check() */
  if (player_state == PLAY_STOPPED)
    return;

  last_rtptime += AIRTUNES_V2_PACKET_SAMPLES;

  memset(rawbuf, 0, sizeof(rawbuf));

  ret = source_read(rawbuf, sizeof(rawbuf), last_rtptime);
  if (ret < 0)
    {
      DPRINTF(E_DBG, L_PLAYER, "Error reading from source, aborting playback\n");

      playback_abort();
      return;
    }

  if (laudio_status & LAUDIO_F_STARTED)
    laudio_write(rawbuf, last_rtptime);

  if (raop_sessions > 0)
    raop_v2_write(rawbuf, last_rtptime);
}

static void
player_playback_cb(int fd, short what, void *arg)
{
  uint32_t packet_send_count = 0;
  struct timespec next_tick;
  struct timespec stream_period  = { 0, MINIMUM_STREAM_PERIOD };
  int ret;
#if defined(__linux__)
  uint64_t ticks;

  /* Acknowledge timer */
  ret = read(fd, &ticks, sizeof(ticks));
  if (ret <= 0)
    DPRINTF(E_WARN, L_PLAYER, "Error reading timer.\n");
#endif /* __linux__ */

  /* Decide how many packets to send */
  next_tick = timespec_add(pb_timer_last, stream_period);
  do
    {
      playback_write();
      packet_timer_last = timespec_add(packet_timer_last, packet_time);
      packet_send_count++;
      /* not possible to have more than 126 audio packets per second */
      if (packet_send_count > 126)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Timing error detected during playback! Aborting.\n");

	  playback_abort();
	  return;
	}
    }
  while (timespec_cmp(packet_timer_last, next_tick) < 0);

  /* Make sure playback is still running */
  if (player_state == PLAY_STOPPED)
    return;

  pb_timer_last = timespec_add(pb_timer_last, stream_period);

  ret = pb_timer_start(&pb_timer_last);
  if (ret < 0)
    playback_abort();
}

static void
device_free(struct raop_device *dev)
{
  free(dev->name);

  if (dev->v4_address)
    free(dev->v4_address);

  if (dev->v6_address)
    free(dev->v6_address);

  free(dev);
}

/* Helpers */
static void
device_remove(struct raop_device *dev)
{
  struct raop_device *rd;
  struct raop_device *prev;
  int ret;

  prev = NULL;
  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd == dev)
	break;

      prev = rd;
    }

  if (!rd)
    return;

  DPRINTF(E_DBG, L_PLAYER, "Removing AirPlay device %s; stopped advertising\n", dev->name);

  /* Make sure device isn't selected anymore */
  if (dev->selected)
    speaker_deselect_raop(dev);

  /* Save device volume */
  ret = db_speaker_save(dev->id, 0, dev->volume);
  if (ret < 0)
    DPRINTF(E_LOG, L_PLAYER, "Could not save state for speaker %s\n", dev->name);

  if (!prev)
    dev_list = dev->next;
  else
    prev->next = dev->next;

  device_free(dev);
}

static int
device_check(struct raop_device *dev)
{
  struct raop_device *rd;

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd == dev)
	break;
    }

  return (rd) ? 0 : -1;
}

static int
device_add(struct player_command *cmd)
{
  struct raop_device *dev;
  struct raop_device *rd;
  int selected;
  int ret;

  dev = cmd->arg.rd;

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd->id == dev->id)
	break;
    }

  /* New device */
  if (!rd)
    {
      rd = dev;

      ret = db_speaker_get(rd->id, &selected, &rd->volume);
      if (ret < 0)
	{
	  selected = 0;
	  rd->volume = (master_volume >= 0) ? master_volume : PLAYER_DEFAULT_VOLUME;
	}

      if (dev_autoselect && selected)
	speaker_select_raop(rd);

      rd->next = dev_list;
      dev_list = rd;
    }
  else
    {
      rd->advertised = 1;

      if (dev->v4_address)
	{
	  if (rd->v4_address)
	    free(rd->v4_address);

	  rd->v4_address = dev->v4_address;
	  rd->v4_port = dev->v4_port;

	  /* Address is ours now */
	  dev->v4_address = NULL;
	}

      if (dev->v6_address)
	{
	  if (rd->v6_address)
	    free(rd->v6_address);

	  rd->v6_address = dev->v6_address;
	  rd->v6_port = dev->v6_port;

	  /* Address is ours now */
	  dev->v6_address = NULL;
	}

      if (rd->name)
	free(rd->name);
      rd->name = dev->name;
      dev->name = NULL;

      rd->devtype = dev->devtype;

      rd->has_password = dev->has_password;
      rd->password = dev->password;

      device_free(dev);
    }

  return 0;
}

static int
device_remove_family(struct player_command *cmd)
{
  struct raop_device *dev;
  struct raop_device *rd;

  dev = cmd->arg.rd;

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd->id == dev->id)
        break;
    }

  if (!rd)
    {
      DPRINTF(E_WARN, L_PLAYER, "AirPlay device %s stopped advertising, but not in our list\n", dev->name);

      device_free(dev);
      return 0;
    }

  /* v{4,6}_port non-zero indicates the address family stopped advertising */
  if (dev->v4_port && rd->v4_address)
    {
      free(rd->v4_address);
      rd->v4_address = NULL;
      rd->v4_port = 0;
    }

  if (dev->v6_port && rd->v6_address)
    {
      free(rd->v6_address);
      rd->v6_address = NULL;
      rd->v6_port = 0;
    }

  if (!rd->v4_address && !rd->v6_address)
    {
      rd->advertised = 0;

      if (!rd->session)
	device_remove(rd);
    }

  device_free(dev);

  return 0;
}

static int
metadata_send(struct player_command *cmd)
{
  struct player_metadata *pmd;

  pmd = cmd->arg.pmd;

  /* Do the setting of rtptime which was deferred in metadata_trigger because we
   * wanted to wait until we had the actual last_rtptime
   */
  if ((pmd->rtptime == 0) && (pmd->startup))
    pmd->rtptime = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;

  raop_metadata_send(pmd->rmd, pmd->rtptime, pmd->offset, pmd->startup);

  return 0;
}

/* RAOP callbacks executed in the player thread */
static void
device_streaming_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status)
{
  int ret;

  if (status == RAOP_FAILED)
    {
      raop_sessions--;

      ret = device_check(dev);
      if (ret < 0)
	{
	  DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during streaming!\n");

	  return;
	}

      DPRINTF(E_LOG, L_PLAYER, "AirPlay device %s FAILED\n", dev->name);

      if (player_state == PLAY_PLAYING)
	speaker_deselect_raop(dev);

      dev->session = NULL;

      if (!dev->advertised)
	device_remove(dev);
    }
  else if (status == RAOP_STOPPED)
    {
      raop_sessions--;

      ret = device_check(dev);
      if (ret < 0)
	{
	  DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during streaming!\n");

	  return;
	}

      DPRINTF(E_INFO, L_PLAYER, "AirPlay device %s stopped\n", dev->name);

      dev->session = NULL;

      if (!dev->advertised)
	device_remove(dev);
    }
}

static void
device_command_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status)
{
  cur_cmd->raop_pending--;

  raop_set_status_cb(rs, device_streaming_cb);

  if (status == RAOP_FAILED)
    device_streaming_cb(dev, rs, status);

  if (cur_cmd->raop_pending == 0)
    {
      if (cur_cmd->func_bh)
	cur_cmd->ret = cur_cmd->func_bh(cur_cmd);
      else
	cur_cmd->ret = 0;

      command_async_end(cur_cmd);
    }
}

static void
device_shutdown_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status)
{
  int ret;

  cur_cmd->raop_pending--;

  if (raop_sessions)
    raop_sessions--;

  ret = device_check(dev);
  if (ret < 0)
    {
      DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared before shutdown completion!\n");

      if (cur_cmd->ret != -2)
	cur_cmd->ret = -1;
      goto out;
    }

  dev->session = NULL;

  if (!dev->advertised)
    device_remove(dev);

 out:
  if (cur_cmd->raop_pending == 0)
    {
      /* cur_cmd->ret already set
       *  - to 0 (or -2 if password issue) in speaker_set()
       *  - to -1 above on error
       */
      command_async_end(cur_cmd);
    }
}

static void
device_lost_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status)
{
  /* We lost that device during startup for some reason, not much we can do here */
  if (status == RAOP_FAILED)
    DPRINTF(E_WARN, L_PLAYER, "Failed to stop lost device\n");
  else
    DPRINTF(E_INFO, L_PLAYER, "Lost device stopped properly\n");
}

static void
device_activate_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status)
{
  struct timespec ts;
  int ret;

  cur_cmd->raop_pending--;

  ret = device_check(dev);
  if (ret < 0)
    {
      DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during startup!\n");

      raop_set_status_cb(rs, device_lost_cb);
      raop_device_stop(rs);

      if (cur_cmd->ret != -2)
	cur_cmd->ret = -1;
      goto out;
    }

  if (status == RAOP_PASSWORD)
    {
      status = RAOP_FAILED;
      cur_cmd->ret = -2;
    }

  if (status == RAOP_FAILED)
    {
      speaker_deselect_raop(dev);

      if (!dev->advertised)
	device_remove(dev);

      if (cur_cmd->ret != -2)
	cur_cmd->ret = -1;
      goto out;
    }

  dev->session = rs;

  raop_sessions++;

  if ((player_state == PLAY_PLAYING) && (raop_sessions == 1))
    {
      ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Could not get current time: %s\n", strerror(errno));

	  /* Fallback to nearest timer expiration time */
	  ts.tv_sec = pb_timer_last.tv_sec;
	  ts.tv_nsec = pb_timer_last.tv_nsec;
	}

      raop_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &ts);
    }

  raop_set_status_cb(rs, device_streaming_cb);

 out:
  if (cur_cmd->raop_pending == 0)
    {
      /* cur_cmd->ret already set
       *  - to 0 in speaker_set() (default)
       *  - to -2 above if password issue
       *  - to -1 above on error
       */
      command_async_end(cur_cmd);
    }
}

static void
device_probe_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status)
{
  int ret;

  cur_cmd->raop_pending--;

  ret = device_check(dev);
  if (ret < 0)
    {
      DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during probe!\n");

      if (cur_cmd->ret != -2)
	cur_cmd->ret = -1;
      goto out;
    }

  if (status == RAOP_PASSWORD)
    {
      status = RAOP_FAILED;
      cur_cmd->ret = -2;
    }

  if (status == RAOP_FAILED)
    {
      speaker_deselect_raop(dev);

      if (!dev->advertised)
	device_remove(dev);

      if (cur_cmd->ret != -2)
	cur_cmd->ret = -1;
      goto out;
    }

 out:
  if (cur_cmd->raop_pending == 0)
    {
      /* cur_cmd->ret already set
       *  - to 0 in speaker_set() (default)
       *  - to -2 above if password issue
       *  - to -1 above on error
       */
      command_async_end(cur_cmd);
    }
}

static void
device_restart_cb(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status)
{
  int ret;

  cur_cmd->raop_pending--;

  ret = device_check(dev);
  if (ret < 0)
    {
      DPRINTF(E_WARN, L_PLAYER, "AirPlay device disappeared during restart!\n");

      raop_set_status_cb(rs, device_lost_cb);
      raop_device_stop(rs);

      goto out;
    }

  if (status == RAOP_FAILED)
    {
      speaker_deselect_raop(dev);

      if (!dev->advertised)
	device_remove(dev);

      goto out;
    }

  dev->session = rs;

  raop_sessions++;
  raop_set_status_cb(rs, device_streaming_cb);

 out:
  if (cur_cmd->raop_pending == 0)
    {
      cur_cmd->ret = cur_cmd->func_bh(cur_cmd);

      command_async_end(cur_cmd);
    }
}


/* Internal abort routine */
static void
playback_abort(void)
{
  if (laudio_status != LAUDIO_CLOSED)
    laudio_close();

  if (raop_sessions > 0)
    raop_playback_stop();

  pb_timer_stop();

  if (cur_playing)
    source_stop(cur_playing);
  else
    source_stop(cur_streaming);

  queue_clear(NULL);

  cur_playing = NULL;
  cur_streaming = NULL;

  evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf));

  status_update(PLAY_STOPPED);

  metadata_purge();
}

/* Actual commands, executed in the player thread */
static int
get_status(struct player_command *cmd)
{
  struct timespec ts;
  struct player_source *ps;
  struct player_status *status;
  uint64_t pos;
  int ret;

  status = cmd->arg.status;

  memset(status, 0, sizeof(struct player_status));

  status->shuffle = shuffle;
  status->repeat = repeat;

  status->volume = master_volume;

  status->plid = cur_plid;
  status->plversion = cur_plversion;

  switch (player_state)
    {
      case PLAY_STOPPED:
	DPRINTF(E_DBG, L_PLAYER, "Player status: stopped\n");

	status->status = PLAY_STOPPED;
	break;

      case PLAY_PAUSED:
	DPRINTF(E_DBG, L_PLAYER, "Player status: paused\n");

	status->status = PLAY_PAUSED;
	status->id = cur_streaming->id;

	pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - cur_streaming->stream_start;
	status->pos_ms = (pos * 1000) / 44100;
	status->len_ms = cur_streaming->len_ms;

	status->pos_pl = source_position(cur_streaming, 0);

	break;

      case PLAY_PLAYING:
	if (!cur_playing)
	  {
	    DPRINTF(E_DBG, L_PLAYER, "Player status: playing (buffering)\n");

	    status->status = PLAY_PAUSED;
	    ps = cur_streaming;

	    /* Avoid a visible 2-second jump backward for the client */
	    pos = ps->output_start - ps->stream_start;
	  }
	else
	  {
	    DPRINTF(E_DBG, L_PLAYER, "Player status: playing\n");

	    status->status = PLAY_PLAYING;
	    ps = cur_playing;

	    ret = player_get_current_pos(&pos, &ts, 0);
	    if (ret < 0)
	      {
		DPRINTF(E_LOG, L_PLAYER, "Could not get current stream position for playstatus\n");

		pos = 0;
	      }

	    if (pos < ps->stream_start)
	      pos = 0;
	    else
	      pos -= ps->stream_start;
	  }

	status->pos_ms = (pos * 1000) / 44100;
	status->len_ms = ps->len_ms;

	status->id = ps->id;
	status->pos_pl = source_position(ps, 0);

	ps = next_ps(ps, shuffle);
	status->next_id = ps->id;
	status->next_pos_pl = source_position(ps, 0);

	status->playlistlength = source_count();
	break;
    }

  return 0;
}

static int
now_playing(struct player_command *cmd)
{
  uint32_t *id;

  id = cmd->arg.id_ptr;

  if (cur_playing)
    *id = cur_playing->id;
  else if (cur_streaming)
    *id = cur_streaming->id;
  else
    return -1;

  return 0;
}

static int
artwork_url_get(struct player_command *cmd)
{
  struct player_source *ps;

  cmd->arg.icy.artwork_url = NULL;

  if (cur_playing)
    ps = cur_playing;
  else if (cur_streaming)
    ps = cur_streaming;
  else
    return -1;

  /* Check that we are playing a viable stream, and that it has the requested id */
  if (!ps->ctx || ps->data_kind != DATA_KIND_HTTP || ps->id != cmd->arg.icy.id)
    return -1;

  transcode_metadata_artwork_url(ps->ctx, &cmd->arg.icy.artwork_url);

  return 0;
}

static int
playback_stop(struct player_command *cmd)
{
  if (laudio_status != LAUDIO_CLOSED)
    laudio_close();

  /* We may be restarting very soon, so we don't bring the devices to a
   * full stop just yet; this saves time when restarting, which is nicer
   * for the user.
   */
  cmd->raop_pending = raop_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);

  pb_timer_stop();

  if (cur_playing)
    {
      history_add(cur_playing->id);
      source_stop(cur_playing);
    }
  else if (cur_streaming)
    {
      history_add(cur_streaming->id);
      source_stop(cur_streaming);
    }

  cur_playing = NULL;
  cur_streaming = NULL;

  evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf));

  status_update(PLAY_STOPPED);

  metadata_purge();

  /* We're async if we need to flush RAOP devices */
  if (cmd->raop_pending > 0)
    return 1; /* async */

  return 0;
}

/* Playback startup bottom half */
static int
playback_start_bh(struct player_command *cmd)
{
  int ret;

  if ((laudio_status == LAUDIO_CLOSED) && (raop_sessions == 0))
    {
      DPRINTF(E_LOG, L_PLAYER, "Cannot start playback: no output started\n");

      goto out_fail;
    }

  /* Start laudio first as it can fail, but can be stopped easily if needed */
  if (laudio_status == LAUDIO_OPEN)
    {
      laudio_set_volume(laudio_volume);

      ret = laudio_start(pb_pos, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Local audio failed to start\n");

	  goto out_fail;
	}
    }

  ret = clock_gettime_with_res(CLOCK_MONOTONIC, &pb_pos_stamp, &timer_res);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Couldn't get current clock: %s\n", strerror(errno));

      goto out_fail;
    }

  pb_timer_stop();

  /*
   * initialize the packet timer to the same relative time that we have 
   * for the playback timer.
   */
  packet_timer_last.tv_sec = pb_pos_stamp.tv_sec;
  packet_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;

  pb_timer_last.tv_sec = pb_pos_stamp.tv_sec;
  pb_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;

  ret = pb_timer_start(&pb_timer_last);
  if (ret < 0)
    goto out_fail;

  /* Everything OK, start RAOP */
  if (raop_sessions > 0)
    raop_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp);

  status_update(PLAY_PLAYING);

  return 0;

 out_fail:
  playback_abort();

  return -1;
}

static struct player_source *
queue_get_source_byid(uint32_t id)
{
  struct player_source *ps;

  if (!source_head)
    return NULL;

  ps = source_head->pl_next;
  while (ps->id != id && ps != source_head)
    {
      ps = ps->pl_next;
    }

  return ps;
}

static struct player_source *
queue_get_source_bypos(int pos)
{
  struct player_source *ps;
  int i;

  if (!source_head)
    return NULL;

  ps = source_head;
  for (i = pos; i > 0; i--)
    ps = ps->pl_next;

  return ps;
}

static int
playback_start(struct player_command *cmd)
{
  struct raop_device *rd;
  uint32_t *idx_id;
  struct player_source *ps;
  int ret;

  if (!source_head)
    {
      DPRINTF(E_LOG, L_PLAYER, "Nothing to play!\n");

      return -1;
    }

  idx_id = cmd->arg.item_range.id_ptr;

  if (player_state == PLAY_PLAYING)
    {
      /*
       * If player is already playing a song, only return current playing song id
       * and do not change player state (ignores given arguments for playing a
       * specified song by pos or id).
       */
      if (idx_id)
	{
	  if (cur_playing)
	    *idx_id = cur_playing->id;
	  else
	    *idx_id = cur_streaming->id;
	}

      status_update(player_state);

      return 0;
    }

  // Update global playback position
  pb_pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - 88200;

  /*
   * If either an item id or an item position is given, get the corresponding
   * player_source from the queue.
   */
  if (cmd->arg.item_range.type == RANGEARG_ID)
    ps = queue_get_source_byid(cmd->arg.item_range.id);
  else if (cmd->arg.item_range.type == RANGEARG_POS)
    ps = queue_get_source_bypos(cmd->arg.item_range.start_pos);
  else
    ps = NULL;

  /*
   * Update queue and cur_streaming depending on
   * - given player_source to start playing
   * - player state
   */
  if (ps)
    {
      /*
       * A song is specified in the arguments (by id or pos) and the corresponding
       * player_source (ps) from the queue was found.
       *
       * Stop playback (if it was paused) and prepare to start playback on ps.
       */
      if (cur_playing)
	source_stop(cur_playing);
      else if (cur_streaming)
	source_stop(cur_streaming);

      cur_playing = NULL;
      cur_streaming = NULL;

      if (shuffle)
	{
	  source_reshuffle();
	  cur_streaming = shuffle_head;
	}
      else
	cur_streaming = ps;

      ret = source_open(cur_streaming, 0, 1);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Couldn't jump to source %d in queue\n", cur_streaming->id);

	  playback_abort();
	  return -1;
	}

      if (idx_id)
	*idx_id = cur_streaming->id;

      cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
      cur_streaming->output_start = cur_streaming->stream_start;
    }
  else if (!cur_streaming)
    {
      /*
       * Player was stopped, start playing the queue
       */
      if (shuffle)
	source_reshuffle();

      ret = source_next(0);
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Couldn't find anything to play!\n");

	  playback_abort();
	  return -1;
	}

      cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
      cur_streaming->output_start = cur_streaming->stream_start;
    }
  else
    {
      /*
       * Player was paused, resume playing cur_streaming
       *
       * After a pause, the source is still open so source_open() doesn't get
       * called and we have to handle metadata ourselves.
       */
      metadata_trigger(cur_streaming, 1);
    }

  /* Start local audio if needed */
  if (laudio_selected && (laudio_status == LAUDIO_CLOSED))
    {
      ret = laudio_open();
      if (ret < 0)
	DPRINTF(E_LOG, L_PLAYER, "Could not open local audio, will try AirPlay\n");
    }

  /* Start RAOP sessions on selected devices if needed */
  cmd->raop_pending = 0;

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd->selected && !rd->session)
	{
	  ret = raop_device_start(rd, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Could not start selected AirPlay device %s\n", rd->name);
	      continue;
	    }

	  cmd->raop_pending++;
	}
    }

  /* Try to autoselect a non-selected RAOP device if the above failed */
  if ((laudio_status == LAUDIO_CLOSED) && (cmd->raop_pending == 0) && (raop_sessions == 0))
    for (rd = dev_list; rd; rd = rd->next)
      {
        if (!rd->session)
	  {
	    speaker_select_raop(rd);
	    ret = raop_device_start(rd, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
	    if (ret < 0)
	      {
		DPRINTF(E_DBG, L_PLAYER, "Could not autoselect AirPlay device %s\n", rd->name);
		speaker_deselect_raop(rd);
		continue;
	      }

	    DPRINTF(E_INFO, L_PLAYER, "Autoselecting AirPlay device %s\n", rd->name);
	    cmd->raop_pending++;
	    break;
	  }
      }

  /* No luck finding valid output */
  if ((laudio_status == LAUDIO_CLOSED) && (cmd->raop_pending == 0) && (raop_sessions == 0))
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not start playback: no output selected or couldn't start any output\n");

      playback_abort();
      return -1;
    }

  /* We're async if we need to start RAOP devices */
  if (cmd->raop_pending > 0)
    return 1; /* async */

  /* Otherwise, just run the bottom half */
  return playback_start_bh(cmd);
}

static int
playback_prev_bh(struct player_command *cmd)
{
  int ret;
  int pos_sec;

  if (!cur_streaming)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n");
      return -1;
    }

  /* Only add to history if playback started. */
  if (cur_streaming->end > cur_streaming->stream_start)
    history_add(cur_streaming->id);

  source_stop(cur_streaming);

  /* Compute the playing time in seconds for the current song. */
  if (cur_streaming->end > cur_streaming->stream_start)
    pos_sec = (cur_streaming->end - cur_streaming->stream_start) / 44100;
  else
    pos_sec = 0;

  /* Only skip to the previous song if the playing time is less than 3 seconds,
   otherwise restart the current song. */
  DPRINTF(E_DBG, L_PLAYER, "Skipping song played %d sec\n", pos_sec);
  if (pos_sec < 3)
    {
      ret = source_prev();
      if (ret < 0)
	{
	  playback_abort();

	  return -1;
	}
    }
  else
    {
      ret = source_open(cur_streaming, 1, 0);
      if (ret < 0)
	{
	  playback_abort();

	  return -1;
	}
    }

  if (player_state == PLAY_STOPPED)
    return -1;

  cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
  cur_streaming->output_start = cur_streaming->stream_start;

  cur_playing = NULL;

  /* Silent status change - playback_start() sends the real status update */
  player_state = PLAY_PAUSED;

  return 0;
}


static int
playback_next_bh(struct player_command *cmd)
{
  int ret;

  if (!cur_streaming)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not get current stream source\n");
      return -1;
    }

  /* Only add to history if playback started. */
  if (cur_streaming->end > cur_streaming->stream_start)
    history_add(cur_streaming->id);

  source_stop(cur_streaming);

  ret = source_next(1);
  if (ret < 0)
    {
      playback_abort();

      return -1;
    }

  if (player_state == PLAY_STOPPED)
    return -1;

  cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
  cur_streaming->output_start = cur_streaming->stream_start;

  cur_playing = NULL;

  /* Silent status change - playback_start() sends the real status update */
  player_state = PLAY_PAUSED;

  return 0;
}

static int
playback_seek_bh(struct player_command *cmd)
{
  struct player_source *ps;
  int ms;
  int ret;

  ms = cmd->arg.intval;

  if (cur_playing)
    ps = cur_playing;
  else
    ps = cur_streaming;

  ps->end = 0;

  /* Seek to commanded position */
  switch (ps->data_kind)
    {
      case DATA_KIND_FILE:
	ret = transcode_seek(ps->ctx, ms);
	break;
#ifdef HAVE_SPOTIFY_H
      case DATA_KIND_SPOTIFY:
	ret = spotify_playback_seek(ms);
	break;
#endif
      case DATA_KIND_PIPE:
      case DATA_KIND_HTTP:
	ret = 1;
	break;

      default:
	ret = -1;
    }

  if (ret < 0)
    {
      playback_abort();

      return -1;
    }

  /* Adjust start_pos for the new position */
  ps->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
  ps->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;

  cur_streaming = ps;
  cur_playing = NULL;

  /* Silent status change - playback_start() sends the real status update */
  player_state = PLAY_PAUSED;

  return 0;
}

static int
playback_pause_bh(struct player_command *cmd)
{
  struct player_source *ps;
  uint64_t pos;
  int ms;
  int ret;

  if (cur_playing)
    ps = cur_playing;
  else
    ps = cur_streaming;

  pos = ps->end;
  ps->end = 0;

  /* Seek back to current playback position */
  pos -= ps->stream_start;
  ms = (int)((pos * 1000) / 44100);

  switch (ps->data_kind)
    {
      case DATA_KIND_FILE:
	ret = transcode_seek(ps->ctx, ms);
	break;
#ifdef HAVE_SPOTIFY_H
      case DATA_KIND_SPOTIFY:
	ret = spotify_playback_seek(ms);
	break;
#endif
      default:
	ret = -1;
    }

  if (ret < 0)
    {
      playback_abort();

      return -1;
    }

  /* Adjust start_pos to take into account the pause and seek back */
  ps->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
  ps->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;

  cur_streaming = ps;
  cur_playing = NULL;

  status_update(PLAY_PAUSED);

  if (ps->media_kind & (MEDIA_KIND_MOVIE | MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK | MEDIA_KIND_TVSHOW))
    db_file_save_seek(ps->id, ret);

  return 0;
}

static int
playback_pause(struct player_command *cmd)
{
  struct player_source *ps;
  uint64_t pos;

  pos = source_check();
  if (pos == 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not retrieve current position for pause\n");

      playback_abort();
      return -1;
    }

  /* Make sure playback is still running after source_check() */
  if (player_state == PLAY_STOPPED)
    return -1;

  if (cur_playing)
    ps = cur_playing;
  else
    ps = cur_streaming;

  /* Store pause position */
  ps->end = pos;

  cmd->raop_pending = raop_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);

  if (laudio_status != LAUDIO_CLOSED)
    laudio_stop();

  pb_timer_stop();

  if (ps->play_next)
    source_stop(ps->play_next);

  cur_playing = NULL;
  cur_streaming = ps;
  cur_streaming->play_next = NULL;

  evbuffer_drain(audio_buf, evbuffer_get_length(audio_buf));

  metadata_purge();

  /* We're async if we need to flush RAOP devices */
  if (cmd->raop_pending > 0)
    return 1; /* async */

  /* Otherwise, just run the bottom half */
  return cmd->func_bh(cmd);
}

static int
speaker_enumerate(struct player_command *cmd)
{
  struct raop_device *rd;
  struct spk_enum *spk_enum;
  struct spk_flags flags;
  char *laudio_name;

  spk_enum = cmd->arg.spk_enum;

  laudio_name = cfg_getstr(cfg_getsec(cfg, "audio"), "nickname");

  /* Auto-select local audio if there are no AirPlay devices */
  if (!dev_list && !laudio_selected)
    speaker_select_laudio();

  flags.selected = laudio_selected;
  flags.has_password = 0;
  flags.has_video = 0;

  spk_enum->cb(0, laudio_name, laudio_relvol, flags, spk_enum->arg);

#ifdef DEBUG_RELVOL
  DPRINTF(E_DBG, L_PLAYER, "*** master: %d\n", master_volume);
  DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol);
#endif

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (rd->advertised || rd->selected)
	{
	  flags.selected = rd->selected;
	  flags.has_password = rd->has_password;
	  flags.has_video = (rd->devtype == RAOP_DEV_APPLETV);

	  spk_enum->cb(rd->id, rd->name, rd->relvol, flags, spk_enum->arg);

#ifdef DEBUG_RELVOL
	  DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol);
#endif
	}
    }

  return 0;
}

static int
speaker_activate(struct raop_device *rd)
{
  struct timespec ts;
  uint64_t pos;
  int ret;

  if (!rd)
    {
      /* Local */
      DPRINTF(E_DBG, L_PLAYER, "Activating local audio\n");

      if (laudio_status == LAUDIO_CLOSED)
	{
	  ret = laudio_open();
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Could not open local audio\n");

	      return -1;
	    }
	}

      if (player_state == PLAY_PLAYING)
	{
	  laudio_set_volume(laudio_volume);

	  ret = player_get_current_pos(&pos, &ts, 0);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Could not get current stream position for local audio start\n");

	      laudio_close();
	      return -1;
	    }

	  ret = laudio_start(pos, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Local playback failed to start\n");

	      laudio_close();
	      return -1;
	    }
	}

      return 0;
    }
  else
    {
      /* RAOP */
      if (player_state == PLAY_PLAYING)
	{
	  DPRINTF(E_DBG, L_PLAYER, "Activating RAOP device %s\n", rd->name);

	  ret = raop_device_start(rd, device_activate_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Could not start device %s\n", rd->name);

	      return -1;
	    }
	}
      else
	{
	  DPRINTF(E_DBG, L_PLAYER, "Probing RAOP device %s\n", rd->name);

	  ret = raop_device_probe(rd, device_probe_cb);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Could not probe device %s\n", rd->name);

	      return -1;
	    }
	}

      return 1;
    }

  return -1;
}

static int
speaker_deactivate(struct raop_device *rd)
{
  if (!rd)
    {
      /* Local */
      DPRINTF(E_DBG, L_PLAYER, "Deactivating local audio\n");

      if (laudio_status == LAUDIO_CLOSED)
	return 0;

      if (laudio_status & LAUDIO_F_STARTED)
	laudio_stop();

      laudio_close();

      return 0;
    }
  else
    {
      /* RAOP */
      DPRINTF(E_DBG, L_PLAYER, "Deactivating RAOP device %s\n", rd->name);

      raop_set_status_cb(rd->session, device_shutdown_cb);
      raop_device_stop(rd->session);

      return 1;
    }

  return -1;
}

static int
speaker_set(struct player_command *cmd)
{
  struct raop_device *rd;
  uint64_t *ids;
  int nspk;
  int i;
  int ret;

  ids = cmd->arg.raop_ids;

  if (ids)
    nspk = ids[0];
  else
    nspk = 0;

  DPRINTF(E_DBG, L_PLAYER, "Speaker set: %d speakers\n", nspk);

  cmd->raop_pending = 0;
  cmd->ret = 0;

  /* RAOP devices */
  for (rd = dev_list; rd; rd = rd->next)
    {
      for (i = 1; i <= nspk; i++)
	{
	  DPRINTF(E_DBG, L_PLAYER, "Set %" PRIu64 " device %" PRIu64 "\n", ids[i], rd->id);

	  if (ids[i] == rd->id)
	    break;
	}

      if (i <= nspk)
	{
	  if (rd->has_password && !rd->password)
	    {
	      DPRINTF(E_INFO, L_PLAYER, "RAOP device %s is password-protected, but we don't have it\n", rd->name);

	      cmd->ret = -2;
	      continue;
	    }

	  DPRINTF(E_DBG, L_PLAYER, "RAOP device %s selected\n", rd->name);

	  if (!rd->selected)
	    speaker_select_raop(rd);

	  if (!rd->session)
	    {
	      ret = speaker_activate(rd);
	      if (ret < 0)
		{
		  DPRINTF(E_LOG, L_PLAYER, "Could not activate RAOP device %s\n", rd->name);

		  speaker_deselect_raop(rd);

		  if (cmd->ret != -2)
		    cmd->ret = -1;
		}

	      /* ret = 1 if RAOP needs to take action */
	      cmd->raop_pending += ret;
	    }
	}
      else
	{
	  DPRINTF(E_DBG, L_PLAYER, "RAOP device %s NOT selected\n", rd->name);

	  if (rd->selected)
	    speaker_deselect_raop(rd);

	  if (rd->session)
	    {
	      ret = speaker_deactivate(rd);
	      if (ret < 0)
		{
		  DPRINTF(E_LOG, L_PLAYER, "Could not deactivate RAOP device %s\n", rd->name);

		  if (cmd->ret != -2)
		    cmd->ret = -1;
		}

	      /* ret = 1 if RAOP needs to take action */
	      cmd->raop_pending += ret;
	    }
	}
    }

  /* Local audio */
  for (i = 1; i <= nspk; i++)
    {
      if (ids[i] == 0)
	break;
    }

  if (i <= nspk)
    {
      DPRINTF(E_DBG, L_PLAYER, "Local audio selected\n");

      if (!laudio_selected)
	speaker_select_laudio();

      if (!(laudio_status & LAUDIO_F_STARTED))
	{
	  ret = speaker_activate(NULL);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Could not activate local audio output\n");

	      speaker_deselect_laudio();

	      if (cmd->ret != -2)
		cmd->ret = -1;
	    }
	}
    }
  else
    {
      DPRINTF(E_DBG, L_PLAYER, "Local audio NOT selected\n");

      if (laudio_selected)
	speaker_deselect_laudio();

      if (laudio_status != LAUDIO_CLOSED)
	{
	  ret = speaker_deactivate(NULL);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_PLAYER, "Could not deactivate local audio output\n");

	      if (cmd->ret != -2)
		cmd->ret = -1;
	    }
	}
    }

  listener_notify(LISTENER_SPEAKER);

  if (cmd->raop_pending > 0)
    return 1; /* async */

  return cmd->ret;
}

static int
volume_set(struct player_command *cmd)
{
  struct raop_device *rd;
  int volume;

  volume = cmd->arg.intval;

  if (master_volume == volume)
    return 0;

  master_volume = volume;

  if (laudio_selected)
    {
      laudio_volume = rel_to_vol(laudio_relvol);
      laudio_set_volume(laudio_volume);

#ifdef DEBUG_RELVOL
      DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol);
#endif
    }

  cmd->raop_pending = 0;

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (!rd->selected)
	continue;

      rd->volume = rel_to_vol(rd->relvol);

#ifdef DEBUG_RELVOL
      DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol);
#endif

      if (rd->session)
	cmd->raop_pending += raop_set_volume_one(rd->session, rd->volume, device_command_cb);
    }

  listener_notify(LISTENER_VOLUME);

  if (cmd->raop_pending > 0)
    return 1; /* async */

  return 0;
}

static int
volume_setrel_speaker(struct player_command *cmd)
{
  struct raop_device *rd;
  uint64_t id;
  int relvol;

  id = cmd->arg.vol_param.spk_id;
  relvol = cmd->arg.vol_param.volume;

  if (id == 0)
    {
      laudio_relvol = relvol;
      laudio_volume = rel_to_vol(relvol);
      laudio_set_volume(laudio_volume);

#ifdef DEBUG_RELVOL
      DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol);
#endif
    }
  else
    {
      for (rd = dev_list; rd; rd = rd->next)
        {
	  if (rd->id != id)
	    continue;

	  if (!rd->selected)
	    return 0;

	  rd->relvol = relvol;
	  rd->volume = rel_to_vol(relvol);

#ifdef DEBUG_RELVOL
	  DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol);
#endif

	  if (rd->session)
	    cmd->raop_pending = raop_set_volume_one(rd->session, rd->volume, device_command_cb);

	  break;
        }
    }

  listener_notify(LISTENER_VOLUME);

  if (cmd->raop_pending > 0)
    return 1; /* async */

  return 0;
}

static int
volume_setabs_speaker(struct player_command *cmd)
{
  struct raop_device *rd;
  uint64_t id;
  int volume;

  id = cmd->arg.vol_param.spk_id;
  volume = cmd->arg.vol_param.volume;

  master_volume = volume;

  if (id == 0)
    {
      laudio_relvol = 100;
      laudio_volume = volume;
      laudio_set_volume(laudio_volume);
    }
  else
    laudio_relvol = vol_to_rel(laudio_volume);

#ifdef DEBUG_RELVOL
  DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol);
#endif

  for (rd = dev_list; rd; rd = rd->next)
    {
      if (!rd->selected)
	continue;

      if (rd->id != id)
	{
	  rd->relvol = vol_to_rel(rd->volume);

#ifdef DEBUG_RELVOL
	  DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol);
#endif
	  continue;
	}
      else
	{
	  rd->relvol = 100;
	  rd->volume = master_volume;

#ifdef DEBUG_RELVOL
	  DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", rd->name, rd->volume, rd->relvol);
#endif

	  if (rd->session)
	    cmd->raop_pending = raop_set_volume_one(rd->session, rd->volume, device_command_cb);
	}
    }

  listener_notify(LISTENER_VOLUME);

  if (cmd->raop_pending > 0)
    return 1; /* async */

  return 0;
}

static int
repeat_set(struct player_command *cmd)
{
  if (cmd->arg.mode == repeat)
    return 0;

  switch (cmd->arg.mode)
    {
      case REPEAT_OFF:
      case REPEAT_SONG:
      case REPEAT_ALL:
	repeat = cmd->arg.mode;
	break;

      default:
	DPRINTF(E_LOG, L_PLAYER, "Invalid repeat mode: %d\n", cmd->arg.mode);
	return -1;
    }

  listener_notify(LISTENER_OPTIONS);

  return 0;
}

static int
shuffle_set(struct player_command *cmd)
{
  switch (cmd->arg.intval)
    {
      case 1:
	if (!shuffle)
	  source_reshuffle();
	/* FALLTHROUGH*/
      case 0:
	shuffle = cmd->arg.intval;
	break;

      default:
	DPRINTF(E_LOG, L_PLAYER, "Invalid shuffle mode: %d\n", cmd->arg.intval);
	return -1;
    }

  listener_notify(LISTENER_OPTIONS);

  return 0;
}

static unsigned int
queue_count()
{
  struct player_source *ps;
  int count;

  if (!source_head)
    return 0;

  count = 1;
  ps = source_head->pl_next;
  while (ps != source_head)
    {
      count++;
      ps = ps->pl_next;
    }

  return count;
}

static int
queue_get(struct player_command *cmd)
{
  int start_pos;
  int end_pos;
  struct player_queue *queue;
  uint32_t *ids;
  unsigned int qlength;
  unsigned int count;
  struct player_source *ps;
  int i;
  int pos;
  char qshuffle;

  queue = malloc(sizeof(struct player_queue));

  qlength = queue_count();
  qshuffle = cmd->arg.item_range.shuffle;

  start_pos = cmd->arg.item_range.start_pos;
  if (start_pos < 0)
    {
      // Set start_pos to the position of the current item + 1
      ps = cur_playing ? cur_playing : cur_streaming;
      start_pos = ps ? source_position(ps, qshuffle) + 1 : 0;
    }

  end_pos = cmd->arg.item_range.end_pos;
  if (cmd->arg.item_range.start_pos < 0)
    end_pos += start_pos;
  if (end_pos <= 0 || end_pos > qlength)
    end_pos = qlength;

  if (end_pos > start_pos)
    count = end_pos - start_pos;
  else
    count = 0;

  ids = malloc(count * sizeof(uint32_t));

  pos = 0;
  ps = qshuffle ? shuffle_head : source_head;
  for (i = 0; i < end_pos; i++)
    {
      if (i >= start_pos)
	{
	  ids[pos] = ps->id;
	  pos++;
	}

	ps = qshuffle ? ps->shuffle_next : ps->pl_next;
    }

  queue->start_pos = start_pos;
  queue->count = count;
  queue->queue = ids;

  queue->length = qlength;
  queue->playingid = 0;
  if (cur_playing)
    queue->playingid = cur_playing->id;
  else if (cur_streaming)
    queue->playingid = cur_streaming->id;

  cmd->queue = queue;

  return 0;
}

void
queue_free(struct player_queue *queue)
{
  free(queue->queue);
  free(queue);
}

static int
queue_add(struct player_command *cmd)
{
  struct player_source *ps;
  struct player_source *ps_shuffle;
  struct player_source *source_tail;
  struct player_source *ps_tail;

  ps = cmd->arg.ps;
  ps_shuffle = ps;

  if (source_head)
    {
      /* Playlist order */
      source_tail = source_head->pl_prev;
      ps_tail = ps->pl_prev;

      source_tail->pl_next = ps;
      ps_tail->pl_next = source_head;

      source_head->pl_prev = ps_tail;
      ps->pl_prev = source_tail;

      /* Shuffle */
      source_tail = shuffle_head->shuffle_prev;
      ps_tail = ps_shuffle->shuffle_prev;

      source_tail->shuffle_next = ps_shuffle;
      ps_tail->shuffle_next = shuffle_head;

      shuffle_head->shuffle_prev = ps_tail;
      ps_shuffle->shuffle_prev = source_tail;
    }
  else
    {
      source_head = ps;
      shuffle_head = ps_shuffle;
    }

  if (shuffle)
    source_reshuffle();

  if (cur_plid != 0)
    cur_plid = 0;
  cur_plversion++;

  listener_notify(LISTENER_PLAYLIST);

  return 0;
}

static int
queue_add_next(struct player_command *cmd)
{
  struct player_source *ps;
  struct player_source *ps_shuffle;
  struct player_source *ps_playing;

  ps = cmd->arg.ps;
  ps_shuffle = ps;

  if (source_head && cur_streaming)
  {
    ps_playing = cur_streaming;

    // Insert ps after ps_playing
    ps->pl_prev->pl_next = ps_playing->pl_next;
    ps_playing->pl_next->pl_prev = ps->pl_prev;
    ps->pl_prev = ps_playing;
    ps_playing->pl_next = ps;

    // Insert ps_shuffle after ps_playing
    ps_shuffle->shuffle_prev->shuffle_next = ps_playing->shuffle_next;
    ps_playing->shuffle_next->shuffle_prev = ps_shuffle->shuffle_prev;
    ps_shuffle->shuffle_prev = ps_playing;
    ps_playing->shuffle_next = ps_shuffle;
  }
  else
  {
    source_head = ps;
    shuffle_head = ps_shuffle;
  }

  if (shuffle)
    source_reshuffle();

  if (cur_plid != 0)
    cur_plid = 0;
  cur_plversion++;

  listener_notify(LISTENER_PLAYLIST);

  return 0;
}

static int
queue_move(struct player_command *cmd)
{
  struct player_source *ps;
  struct player_source *ps_src;
  struct player_source *ps_dst;
  int pos_max;
  int i;

  DPRINTF(E_DBG, L_PLAYER, "Moving song from position %d to be the next song after %d\n", cmd->arg.ps_pos[0],
      cmd->arg.ps_pos[1]);

  ps = cur_playing ? cur_playing : cur_streaming;
  if (!ps)
  {
    DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming song not found\n");
    return -1;
  }

  pos_max = MAX(cmd->arg.ps_pos[0], cmd->arg.ps_pos[1]);
  ps_src = NULL;
  ps_dst = NULL;

  for (i = 0; i <= pos_max; i++)
  {
    if (i == cmd->arg.ps_pos[0])
      ps_src = ps;
    if (i == cmd->arg.ps_pos[1])
      ps_dst = ps;

    ps = next_ps(ps, shuffle);
  }

  if (!ps_src || !ps_dst || (ps_src == ps_dst))
  {
    DPRINTF(E_LOG, L_PLAYER, "Invalid source and/or destination for queue_move\n");
    return -1;
  }

  if (shuffle)
  {
    // Remove ps_src from shuffle queue
    ps_src->shuffle_prev->shuffle_next = ps_src->shuffle_next;
    ps_src->shuffle_next->shuffle_prev = ps_src->shuffle_prev;

    // Insert after ps_dst
    ps_src->shuffle_prev = ps_dst;
    ps_src->shuffle_next = ps_dst->shuffle_next;
    ps_dst->shuffle_next->shuffle_prev = ps_src;
    ps_dst->shuffle_next = ps_src;
  }
  else
  {
    // Remove ps_src from queue
    ps_src->pl_prev->pl_next = ps_src->pl_next;
    ps_src->pl_next->pl_prev = ps_src->pl_prev;

    // Insert after ps_dst
    ps_src->pl_prev = ps_dst;
    ps_src->pl_next = ps_dst->pl_next;
    ps_dst->pl_next->pl_prev = ps_src;
    ps_dst->pl_next = ps_src;
  }

  cur_plversion++;

  listener_notify(LISTENER_PLAYLIST);

  return 0;
}

static int
queue_remove(struct player_command *cmd)
{
  struct player_source *ps;
  struct player_source *ps_current;
  uint32_t pos;
  uint32_t id;
  int i;

  if (cmd->arg.item_range.type == RANGEARG_ID)
    {
      id = cmd->arg.item_range.id;
      pos = 0;
      if (id < 1)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid id %d\n", id);
	  return -1;
	}

      DPRINTF(E_DBG, L_PLAYER, "Removing item with id %d\n", id);
    }
  else
    {
      id = 0;
      pos = cmd->arg.item_range.start_pos;
      if (pos < 1)
	{
	  DPRINTF(E_LOG, L_PLAYER, "Can't remove item, invalid position %d\n", pos);
	  return -1;
	}

      DPRINTF(E_DBG, L_PLAYER, "Removing item from position %d\n", pos);
    }

  ps_current = cur_playing ? cur_playing : cur_streaming;
  if (!ps_current)
    {
      DPRINTF(E_LOG, L_PLAYER, "Current playing/streaming item not found\n");
      return -1;
    }

  i = 0;
  ps = ps_current;
  while (ps)
    {
      i++;
      ps = next_ps(ps, shuffle);

      if (ps == ps_current)
	ps = NULL;
      else if ((i == pos) || (ps->id == id))
	break;
    }

  if (!ps)
    {
      DPRINTF(E_LOG, L_PLAYER, "Can't remove requested item from queue (id %d, pos %d)\n", id, pos);
      return -1;
    }

  if (ps == source_head)
    source_head = ps->pl_next;
  if (ps == shuffle_head)
    shuffle_head = ps->shuffle_next;

  ps->shuffle_prev->shuffle_next = ps->shuffle_next;
  ps->shuffle_next->shuffle_prev = ps->shuffle_prev;

  ps->pl_prev->pl_next = ps->pl_next;
  ps->pl_next->pl_prev = ps->pl_prev;

  source_free(ps);

  cur_plversion++;

  listener_notify(LISTENER_PLAYLIST);

  return 0;
}

/*
 * queue_clear removes all items from the playqueue, playback must be stopped before calling queue_clear
 */
static int
queue_clear(struct player_command *cmd)
{
  struct player_source *ps;

  if (!source_head)
    return 0;

  shuffle_head = NULL;
  source_head->pl_prev->pl_next = NULL;

  for (ps = source_head; ps; ps = source_head)
    {
      source_head = ps->pl_next;

      source_free(ps);
    }

  cur_plid = 0;
  cur_plversion++;

  listener_notify(LISTENER_PLAYLIST);

  return 0;
}

/*
 * Depending on cmd->arg.intval queue_empty removes all items from the history (arg.intval = 1),
 * or removes all upcoming songs from the playqueue (arg.intval != 1). After calling queue_empty
 * to remove the upcoming songs, the playqueue will only contain the current playing song.
 */
static int
queue_empty(struct player_command *cmd)
{
  int clear_hist;
  struct player_source *ps;

  clear_hist = cmd->arg.intval;
  if (clear_hist)
    {
      memset(history, 0, sizeof(struct player_history));
    }
  else
    {
      if (!source_head || !cur_streaming)
	return 0;

      // Stop playback if playing and streaming song are not the same
      if (!cur_playing || cur_playing != cur_streaming)
	{
	  playback_stop(cmd);
	  queue_clear(cmd);
	  return 0;
	}

      // Set head to the current playing song
      shuffle_head = cur_playing;
      source_head = cur_playing;

      // Free all items in the queue except the current playing song
      for (ps = source_head->pl_next; ps != source_head; ps = ps->pl_next)
	{
	  source_free(ps);
	}

      // Make the queue circular again
      source_head->pl_next = source_head;
      source_head->pl_prev = source_head;
      source_head->shuffle_next = source_head;
      source_head->shuffle_prev = source_head;
    }

  cur_plversion++;

  listener_notify(LISTENER_PLAYLIST);

  return 0;
}

static int
queue_plid(struct player_command *cmd)
{
  if (!source_head)
    return 0;

  cur_plid = cmd->arg.id;

  return 0;
}

/* Command processing */
/* Thread: player */
static void
command_cb(int fd, short what, void *arg)
{
  struct player_command *cmd;
  int ret;

  ret = read(cmd_pipe[0], &cmd, sizeof(cmd));
  if (ret != sizeof(cmd))
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");

      goto readd;
    }

  if (cmd->nonblock)
    {
      cmd->func(cmd);

      free(cmd);
      goto readd;
    }

  pthread_mutex_lock(&cmd->lck);

  cur_cmd = cmd;

  ret = cmd->func(cmd);

  if (ret <= 0)
    {
      cmd->ret = ret;

      cur_cmd = NULL;

      pthread_cond_signal(&cmd->cond);
      pthread_mutex_unlock(&cmd->lck);
    }
  else
    {
      /* Command is asynchronous, we don't want to process another command
       * before we're done with this one. See command_async_end().
       */

      return;
    }

 readd:
  event_add(cmdev, NULL);
}


/* Thread: httpd (DACP) - mDNS */
static int
send_command(struct player_command *cmd)
{
  int ret;

  if (!cmd->func)
    {
      DPRINTF(E_LOG, L_PLAYER, "BUG: cmd->func is NULL!\n");

      return -1;
    }

  ret = write(cmd_pipe[1], &cmd, sizeof(cmd));
  if (ret != sizeof(cmd))
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not send command: %s\n", strerror(errno));

      return -1;
    }

  return 0;
}

/* Thread: mDNS */
static int
nonblock_command(struct player_command *cmd)
{
  int ret;

  ret = send_command(cmd);
  if (ret < 0)
    return -1;

  return 0;
}

/* Thread: httpd (DACP) */
static int
sync_command(struct player_command *cmd)
{
  int ret;

  pthread_mutex_lock(&cmd->lck);

  ret = send_command(cmd);
  if (ret < 0)
    {
      pthread_mutex_unlock(&cmd->lck);

      return -1;
    }

  pthread_cond_wait(&cmd->cond, &cmd->lck);

  pthread_mutex_unlock(&cmd->lck);

  ret = cmd->ret;

  return ret;
}

/* Player API executed in the httpd (DACP) thread */
int
player_get_status(struct player_status *status)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = get_status;
  cmd.func_bh = NULL;
  cmd.arg.status = status;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_now_playing(uint32_t *id)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = now_playing;
  cmd.func_bh = NULL;
  cmd.arg.id_ptr = id;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

char *
player_get_icy_artwork_url(uint32_t id)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = artwork_url_get;
  cmd.func_bh = NULL;
  cmd.arg.icy.id = id;

  if (pthread_self() != tid_player)
    ret = sync_command(&cmd);
  else
    ret = artwork_url_get(&cmd);

  command_deinit(&cmd);

  if (ret < 0)
    return NULL;
  else
    return cmd.arg.icy.artwork_url;
}

/*
 * Starts/resumes playback
 *
 * Depending on the player state, this will either resumes playing the current item (player is paused)
 * or begins playing the queue from the beginning.
 *
 * If shuffle is set, the queue is reshuffled prior to starting playback.
 *
 * If a pointer is given as argument "itemid", its value will be set to the playing item id.
 *
 * @param *itemid if not NULL, will be set to the playing item id
 * @return 0 if successful, -1 if an error occurred
 */
int
player_playback_start(uint32_t *itemid)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_start;
  cmd.func_bh = playback_start_bh;
  cmd.arg.item_range.type = RANGEARG_NONE;
  cmd.arg.item_range.id_ptr = itemid;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

/*
 * Starts playback at item number "pos" of the current queue
 *
 * If shuffle is set, the queue is reshuffled prior to starting playback.
 *
 * If a pointer is given as argument "itemid", its value will be set to the playing item id.
 *
 * @param *itemid if not NULL, will be set to the playing item id
 * @return 0 if successful, -1 if an error occurred
 */
int
player_playback_startpos(int pos, uint32_t *itemid)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_start;
  cmd.func_bh = playback_start_bh;
  cmd.arg.item_range.type = RANGEARG_POS;
  cmd.arg.item_range.start_pos = pos;
  cmd.arg.item_range.id_ptr = itemid;
  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

/*
 * Starts playback at item with "id" of the current queue
 *
 * If shuffle is set, the queue is reshuffled prior to starting playback.
 *
 * If a pointer is given as argument "itemid", its value will be set to the playing item id.
 *
 * @param *itemid if not NULL, will be set to the playing item id
 * @return 0 if successful, -1 if an error occurred
 */
int
player_playback_startid(uint32_t id, uint32_t *itemid)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_start;
  cmd.func_bh = playback_start_bh;
  cmd.arg.item_range.type = RANGEARG_ID;
  cmd.arg.item_range.id = id;
  cmd.arg.item_range.id_ptr = itemid;
  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_playback_stop(void)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_stop;
  cmd.arg.noarg = NULL;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_playback_pause(void)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_pause;
  cmd.func_bh = playback_pause_bh;
  cmd.arg.noarg = NULL;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_playback_seek(int ms)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_pause;
  cmd.func_bh = playback_seek_bh;
  cmd.arg.intval = ms;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_playback_next(void)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_pause;
  cmd.func_bh = playback_next_bh;
  cmd.arg.noarg = NULL;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_playback_prev(void)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = playback_pause;
  cmd.func_bh = playback_prev_bh;
  cmd.arg.noarg = NULL;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

void
player_speaker_enumerate(spk_enum_cb cb, void *arg)
{
  struct player_command cmd;
  struct spk_enum spk_enum;

  command_init(&cmd);

  spk_enum.cb = cb;
  spk_enum.arg = arg;

  cmd.func = speaker_enumerate;
  cmd.func_bh = NULL;
  cmd.arg.spk_enum = &spk_enum;

  sync_command(&cmd);

  command_deinit(&cmd);
}

int
player_speaker_set(uint64_t *ids)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = speaker_set;
  cmd.func_bh = NULL;
  cmd.arg.raop_ids = ids;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_volume_set(int vol)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = volume_set;
  cmd.func_bh = NULL;
  cmd.arg.intval = vol;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_volume_setrel_speaker(uint64_t id, int relvol)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = volume_setrel_speaker;
  cmd.func_bh = NULL;
  cmd.arg.vol_param.spk_id = id;
  cmd.arg.vol_param.volume = relvol;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_volume_setabs_speaker(uint64_t id, int vol)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = volume_setabs_speaker;
  cmd.func_bh = NULL;
  cmd.arg.vol_param.spk_id = id;
  cmd.arg.vol_param.volume = vol;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_repeat_set(enum repeat_mode mode)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = repeat_set;
  cmd.func_bh = NULL;
  cmd.arg.mode = mode;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_shuffle_set(int enable)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = shuffle_set;
  cmd.func_bh = NULL;
  cmd.arg.intval = enable;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

/*
 * Retrieves a list of item ids in the queue from postion 'start_pos' to 'end_pos'
 *
 * If start_pos is -1, the list starts with the item next from the current playing item.
 * If end_pos is -1, this list contains all songs starting from 'start_pos'
 *
 * The 'shuffle' argument determines if the items are taken from the playqueue (shuffle = 0)
 * or the shufflequeue (shuffle = 1).
 *
 * @param start_pos Start the listing from 'start_pos'
 * @param end_pos   End the listing at 'end_pos'
 * @param shuffle   If set to 1 use the shuffle queue, otherwise the playqueue
 * @return List of items (ids) in the queue
 */
struct player_queue *
player_queue_get(int start_pos, int end_pos, char shuffle)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = queue_get;
  cmd.func_bh = NULL;
  cmd.arg.item_range.type = RANGEARG_POS;
  cmd.arg.item_range.start_pos = start_pos;
  cmd.arg.item_range.end_pos = end_pos;
  cmd.arg.item_range.shuffle = shuffle;
  cmd.queue = NULL;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  if (ret != 0)
    return NULL;

  return cmd.queue;
}

int
player_queue_add(struct player_source *ps)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = queue_add;
  cmd.func_bh = NULL;
  cmd.arg.ps = ps;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_queue_add_next(struct player_source *ps)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = queue_add_next;
  cmd.func_bh = NULL;
  cmd.arg.ps = ps;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_queue_move(int ps_pos_from, int ps_pos_to)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = queue_move;
  cmd.func_bh = NULL;
  cmd.arg.ps_pos[0] = ps_pos_from;
  cmd.arg.ps_pos[1] = ps_pos_to;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_queue_remove(int ps_pos_remove)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = queue_remove;
  cmd.func_bh = NULL;
  cmd.arg.item_range.type = RANGEARG_POS;
  cmd.arg.item_range.start_pos = ps_pos_remove;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

int
player_queue_removeid(uint32_t id)
{
  struct player_command cmd;
  int ret;

  command_init(&cmd);

  cmd.func = queue_remove;
  cmd.func_bh = NULL;
  cmd.arg.item_range.type = RANGEARG_ID;
  cmd.arg.item_range.id = id;

  ret = sync_command(&cmd);

  command_deinit(&cmd);

  return ret;
}

void
player_queue_clear(void)
{
  struct player_command cmd;

  command_init(&cmd);

  cmd.func = queue_clear;
  cmd.func_bh = NULL;
  cmd.arg.noarg = NULL;

  sync_command(&cmd);

  command_deinit(&cmd);
}

void
player_queue_empty(int clear_hist)
{
  struct player_command cmd;

  command_init(&cmd);

  cmd.func = queue_empty;
  cmd.func_bh = NULL;
  cmd.arg.intval = clear_hist;

  sync_command(&cmd);

  command_deinit(&cmd);
}

void
player_queue_plid(uint32_t plid)
{
  struct player_command cmd;

  command_init(&cmd);

  cmd.func = queue_plid;
  cmd.func_bh = NULL;
  cmd.arg.id = plid;

  sync_command(&cmd);

  command_deinit(&cmd);
}

/* Non-blocking commands used by mDNS */
static void
player_device_add(struct raop_device *rd)
{
  struct player_command *cmd;
  int ret;

  cmd = (struct player_command *)malloc(sizeof(struct player_command));
  if (!cmd)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n");

      device_free(rd);
      return;
    }

  memset(cmd, 0, sizeof(struct player_command));

  cmd->nonblock = 1;

  cmd->func = device_add;
  cmd->arg.rd = rd;

  ret = nonblock_command(cmd);
  if (ret < 0)
    {
      free(cmd);
      device_free(rd);

      return;
    }
}

static void
player_device_remove(struct raop_device *rd)
{
  struct player_command *cmd;
  int ret;

  cmd = (struct player_command *)malloc(sizeof(struct player_command));
  if (!cmd)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not allocate player_command\n");

      device_free(rd);
      return;
    }

  memset(cmd, 0, sizeof(struct player_command));

  cmd->nonblock = 1;

  cmd->func = device_remove_family;
  cmd->arg.rd = rd;

  ret = nonblock_command(cmd);
  if (ret < 0)
    {
      free(cmd);
      device_free(rd);

      return;
    }
}

/* Thread: worker */
static void
player_metadata_send(struct player_metadata *pmd)
{
  struct player_command cmd;

  command_init(&cmd);

  cmd.func = metadata_send;
  cmd.func_bh = NULL;
  cmd.arg.pmd = pmd;

  sync_command(&cmd);

  command_deinit(&cmd);
}


/* RAOP devices discovery - mDNS callback */
/* Thread: main (mdns) */
/* Examples of txt content:
 * Apple TV 2:
     ["sf=0x4" "am=AppleTV2,1" "vs=130.14" "vn=65537" "tp=UDP" "ss=16" "sr=4 4100" "sv=false" "pw=false" "md=0,1,2" "et=0,3,5" "da=true" "cn=0,1,2,3" "ch=2"]
     ["sf=0x4" "am=AppleTV2,1" "vs=105.5" "md=0,1,2" "tp=TCP,UDP" "vn=65537" "pw=false" "ss=16" "sr=44100" "da=true" "sv=false" "et=0,3" "cn=0,1" "ch=2" "txtvers=1"]
 * Apple TV 3:
     ["vv=2" "vs=200.54" "vn=65537" "tp=UDP" "sf=0x44" "pk=8...f" "am=AppleTV3,1" "md=0,1,2" "ft=0x5A7FFFF7,0xE" "et=0,3,5" "da=true" "cn=0,1,2,3"]
 * Sony STR-DN1040:
     ["fv=s9327.1090.0" "am=STR-DN1040" "vs=141.9" "vn=65537" "tp=UDP" "ss=16" "sr=44100" "sv=false" "pw=false" "md=0,2" "ft=0x44F0A00" "et=0,4" "da=true" "cn=0,1" "ch=2" "txtvers=1"]
 * AirFoil:
     ["rastx=iafs" "sm=false" "raver=3.5.3.0" "ek=1" "md=0,1,2" "ramach=Win32NT.6" "et=0,1" "cn=0,1" "sr=44100" "ss=16" "raAudioFormats=ALAC" "raflakyzeroconf=true" "pw=false" "rast=afs" "vn=3" "sv=false" "txtvers=1" "ch=2" "tp=UDP"]
 * Xbmc 13:
     ["am=Xbmc,1" "md=0,1,2" "vs=130.14" "da=true" "vn=3" "pw=false" "sr=44100" "ss=16" "sm=false" "tp=UDP" "sv=false" "et=0,1" "ek=1" "ch=2" "cn=0,1" "txtvers=1"]
 * Shairport (abrasive/1.0):
     ["pw=false" "txtvers=1" "vn=3" "sr=44100" "ss=16" "ch=2" "cn=0,1" "et=0,1" "ek=1" "sm=false" "tp=UDP"]
 * JB2:
     ["fv=95.8947" "am=JB2 Gen" "vs=103.2" "tp=UDP" "vn=65537" "pw=false" "s s=16" "sr=44100" "da=true" "sv=false" "et=0,4" "cn=0,1" "ch=2" "txtvers=1"]
 * Airport Express 802.11g (Gen 1):
     ["tp=TCP,UDP" "sm=false" "sv=false" "ek=1" "et=0,1" "cn=0,1" "ch=2" "ss=16" "sr=44100" "pw=false" "vn=3" "txtvers=1"]
 * Airport Express 802.11n:
     802.11n Gen 2 model (firmware 7.6.4): "am=Airport4,107", "et=0,1"
     802.11n Gen 3 model (firmware 7.6.4): "am=Airport10,115", "et=0,4"
 */
static void
raop_device_cb(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt)
{
  struct raop_device *rd;
  cfg_t *airplay;
  const char *p;
  char *at_name;
  char *password;
  uint64_t id;
  int ret;

  ret = safe_hextou64(name, &id);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not extract AirPlay device ID (%s)\n", name);

      return;
    }

  at_name = strchr(name, '@');
  if (!at_name)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not extract AirPlay device name (%s)\n", name);

      return;
    }
  at_name++;

  DPRINTF(E_DBG, L_PLAYER, "Event for AirPlay device %" PRIx64 "/%s (%d)\n", id, at_name, port);

  rd = (struct raop_device *)malloc(sizeof(struct raop_device));
  if (!rd)
    {
      DPRINTF(E_LOG, L_PLAYER, "Out of memory for new AirPlay device\n");

      return;
    }

  memset(rd, 0, sizeof(struct raop_device));

  rd->id = id;
  rd->name = strdup(at_name);

  if (port < 0)
    {
      /* Device stopped advertising */
      switch (family)
	{
	  case AF_INET:
	    rd->v4_port = 1;
	    break;

	  case AF_INET6:
	    rd->v6_port = 1;
	    break;
	}

      player_device_remove(rd);

      return;
    }

  /* Protocol */
  p = keyval_get(txt, "tp");
  if (!p)
    {
      DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: no tp field in TXT record!\n", name);

      goto free_rd;
    }

  if (*p == '\0')
    {
      DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: tp has no value\n", name);

      goto free_rd;
    }

  if (!strstr(p, "UDP"))
    {
      DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: device does not support AirTunes v2 (tp=%s), discarding\n", name, p);

      goto free_rd;
    }

  /* Password protection */
  password = NULL;
  p = keyval_get(txt, "pw");
  if (!p)
    {
      DPRINTF(E_INFO, L_PLAYER, "AirPlay %s: no pw field in TXT record, assuming no password protection\n", name);

      rd->has_password = 0;
    }
  else if (*p == '\0')
    {
      DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: pw has no value\n", name);

      goto free_rd;
    }
  else
    {
      rd->has_password = (strcmp(p, "false") != 0);
    }

  if (rd->has_password)
    {
      DPRINTF(E_LOG, L_PLAYER, "AirPlay device %s is password-protected\n", name);

      airplay = cfg_gettsec(cfg, "airplay", at_name);
      if (airplay)
	password = cfg_getstr(airplay, "password");

      if (!password)
	DPRINTF(E_LOG, L_PLAYER, "No password given in config for AirPlay device %s\n", name);
    }

  rd->password = password;

  /* Device type */
  rd->devtype = RAOP_DEV_OTHER;
  p = keyval_get(txt, "am");

  if (!p)
    rd->devtype = RAOP_DEV_APEX1_80211G; // First generation AirPort Express
  else if (strncmp(p, "AirPort4", strlen("AirPort4")) == 0)
    rd->devtype = RAOP_DEV_APEX2_80211N; // Second generation
  else if (strncmp(p, "AirPort", strlen("AirPort")) == 0)
    rd->devtype = RAOP_DEV_APEX3_80211N; // Third generation and newer
  else if (strncmp(p, "AppleTV", strlen("AppleTV")) == 0)
    rd->devtype = RAOP_DEV_APPLETV;
  else if (*p == '\0')
    DPRINTF(E_LOG, L_PLAYER, "AirPlay %s: am has no value\n", name);

  /* Encrypt stream */
  p = keyval_get(txt, "ek");
  if (p && (*p == '1'))
    rd->encrypt = 1;
  else
    rd->encrypt = 0;

  /* Metadata support */
  p = keyval_get(txt, "md");
  if (p && (*p != '\0'))
    rd->wants_metadata = 1;
  else
    rd->wants_metadata = 0;

  DPRINTF(E_INFO, L_PLAYER, "AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s\n", 
    name, rd->has_password, rd->encrypt, rd->wants_metadata, raop_devtype[rd->devtype]);

  rd->advertised = 1;

  switch (family)
    {
      case AF_INET:
	rd->v4_address = strdup(address);
	rd->v4_port = port;
	break;

      case AF_INET6:
	rd->v6_address = strdup(address);
	rd->v6_port = port;
	break;
    }

  player_device_add(rd);

  return;

 free_rd:
  device_free(rd);
}

/* Thread: player */
static void *
player(void *arg)
{
  struct raop_device *rd;
  int ret;

  ret = db_perthread_init();
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Error: DB init failed\n");

      pthread_exit(NULL);
    }

  event_base_dispatch(evbase_player);

  if (!player_exit)
    DPRINTF(E_LOG, L_PLAYER, "Player event loop terminated ahead of time!\n");

  /* Save selected devices */
  db_speaker_clear_all();

  ret = db_speaker_save(0, laudio_selected, laudio_volume);
  if (ret < 0)
    DPRINTF(E_LOG, L_PLAYER, "Could not save state for local audio\n");

  for (rd = dev_list; rd; rd = rd->next)
    {
      ret = db_speaker_save(rd->id, rd->selected, rd->volume);
      if (ret < 0)
	DPRINTF(E_LOG, L_PLAYER, "Could not save state for speaker %s\n", rd->name);
    }

  db_perthread_deinit();

  pthread_exit(NULL);
}

/* Thread: player */
static void
exit_cb(int fd, short what, void *arg)
{
  event_base_loopbreak(evbase_player);

  player_exit = 1;
}

/* Thread: main */
int
player_init(void)
{
  uint32_t rnd;
  int raop_v6enabled;
  int mdns_flags;
  int ret;

  player_exit = 0;

  dev_autoselect = 1;
  dev_list = NULL;

  master_volume = -1;

  laudio_selected = 0;
  laudio_status = LAUDIO_CLOSED;
  raop_sessions = 0;

  cur_cmd = NULL;

  source_head = NULL;
  shuffle_head = NULL;
  cur_playing = NULL;
  cur_streaming = NULL;
  cur_plid = 0;
  cur_plversion = 0;

  player_state = PLAY_STOPPED;
  repeat = REPEAT_OFF;
  shuffle = 0;

  history = (struct player_history *)calloc(1, sizeof(struct player_history));

  /*
   * Determine if the resolution of the system timer is > or < the size
   * of an audio packet. NOTE: this assumes the system clock resolution
   * is less than one second.
   */
  if (clock_getres(CLOCK_MONOTONIC, &timer_res) < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not get the system timer resolution.\n");

      return -1;
    }

#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
  /* FreeBSD will report a resolution of 1, but actually has a resolution
   * larger than an audio packet
   */
  if (timer_res.tv_nsec == 1)
    timer_res.tv_nsec = 2 * AIRTUNES_V2_STREAM_PERIOD;
#endif

  MINIMUM_STREAM_PERIOD = MAX(timer_res.tv_nsec, AIRTUNES_V2_STREAM_PERIOD);

  /* Create a timer */
#if defined(__linux__)
  pb_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
  ret = pb_timer_fd;
#else
  ret = timer_create(CLOCK_MONOTONIC, NULL, &pb_timer);
#endif
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create playback timer: %s\n", strerror(errno));

      return -1;
    }

  /* Random RTP time start */
  gcry_randomize(&rnd, sizeof(rnd), GCRY_STRONG_RANDOM);
  last_rtptime = ((uint64_t)1 << 32) | rnd;

  rng_init(&shuffle_rng);

  ret = db_speaker_get(0, &laudio_selected, &laudio_volume);
  if (ret < 0)
    laudio_volume = PLAYER_DEFAULT_VOLUME;
  else if (laudio_selected)
    speaker_select_laudio(); /* Run the select helper */

  audio_buf = evbuffer_new();
  if (!audio_buf)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not allocate evbuffer for audio buffer\n");

      goto audio_fail;
    }

  raop_v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");

#if defined(__linux__)
  ret = pipe2(exit_pipe, O_CLOEXEC);
#else
  ret = pipe(exit_pipe);
#endif
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create pipe: %s\n", strerror(errno));

      goto exit_fail;
    }

# if defined(__linux__)
  ret = pipe2(cmd_pipe, O_CLOEXEC);
# else
  ret = pipe(cmd_pipe);
# endif
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create command pipe: %s\n", strerror(errno));

      goto cmd_fail;
    }

  evbase_player = event_base_new();
  if (!evbase_player)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create an event base\n");

      goto evbase_fail;
    }

#ifdef HAVE_LIBEVENT2
  exitev = event_new(evbase_player, exit_pipe[0], EV_READ, exit_cb, NULL);
  if (!exitev)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create exit event\n");
      goto evnew_fail;
    }

  cmdev = event_new(evbase_player, cmd_pipe[0], EV_READ, command_cb, NULL);
  if (!cmdev)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create cmd event\n");
      goto evnew_fail;
    }

# if defined(__linux__)
  pb_timer_ev = event_new(evbase_player, pb_timer_fd, EV_READ, player_playback_cb, NULL);
# else
  pb_timer_ev = evsignal_new(evbase_player, SIGALRM, player_playback_cb, NULL);
# endif
  if (!pb_timer_ev)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create playback timer event\n");
      goto evnew_fail;
    }
#else
  exitev = (struct event *)malloc(sizeof(struct event));
  if (!exitev)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create exit event\n");
      goto evnew_fail;
    }
  event_set(exitev, exit_pipe[0], EV_READ, exit_cb, NULL);
  event_base_set(evbase_player, exitev);

  cmdev = (struct event *)malloc(sizeof(struct event));
  if (!cmdev)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create cmd event\n");
      goto evnew_fail;
    }
  event_set(cmdev, cmd_pipe[0], EV_READ, command_cb, NULL);
  event_base_set(evbase_player, cmdev);

  pb_timer_ev = (struct event *)malloc(sizeof(struct event));
  if (!pb_timer_ev)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not create playback timer event\n");
      goto evnew_fail;
    }
# if defined(__linux__)
  event_set(pb_timer_ev, pb_timer_fd, EV_READ, player_playback_cb, NULL);
# else
  signal_set(pb_timer_ev, SIGALRM, player_playback_cb, NULL);
# endif
  event_base_set(evbase_player, pb_timer_ev);
#endif /* HAVE_LIBEVENT2 */

  event_add(exitev, NULL);
  event_add(cmdev, NULL);

#ifndef __linux__
  event_add(pb_timer_ev, NULL);
#endif

  ret = laudio_init(player_laudio_status_cb);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Local audio init failed\n");

      goto laudio_fail;
    }

  ret = raop_init(&raop_v6enabled);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "RAOP init failed\n");

      goto raop_fail;
    }

  if (raop_v6enabled)
    mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL;
  else
    mdns_flags = MDNS_WANT_V4;

  ret = mdns_browse("_raop._tcp", mdns_flags, raop_device_cb);
  if (ret < 0)
    {
      DPRINTF(E_FATAL, L_PLAYER, "Could not add mDNS browser for AirPlay devices\n");

      goto mdns_browse_fail;
    }

  ret = pthread_create(&tid_player, NULL, player, NULL);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not spawn player thread: %s\n", strerror(errno));

      goto thread_fail;
    }

  return 0;

 thread_fail:
 mdns_browse_fail:
  raop_deinit();
 raop_fail:
  laudio_deinit();
 laudio_fail:
 evnew_fail:
  event_base_free(evbase_player);
 evbase_fail:
  close(cmd_pipe[0]);
  close(cmd_pipe[1]);
 cmd_fail:
  close(exit_pipe[0]);
  close(exit_pipe[1]);
 exit_fail:
  evbuffer_free(audio_buf);
 audio_fail:
#if defined(__linux__)
  close(pb_timer_fd);
#else
  timer_delete(pb_timer);
#endif

  return -1;
}

/* Thread: main */
void
player_deinit(void)
{
  int ret;
  int dummy = 42;

  ret = write(exit_pipe[1], &dummy, sizeof(dummy));
  if (ret != sizeof(dummy))
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not write to exit pipe: %s\n", strerror(errno));

      return;
    }

  ret = pthread_join(tid_player, NULL);
  if (ret != 0)
    {
      DPRINTF(E_LOG, L_PLAYER, "Could not join HTTPd thread: %s\n", strerror(errno));

      return;
    }

  if (source_head)
    queue_clear(NULL);

  free(history);

  pb_timer_stop();
#if defined(__linux__)
  close(pb_timer_fd);
#else
  timer_delete(pb_timer);
#endif

  evbuffer_free(audio_buf);

  laudio_deinit();
  raop_deinit();

  close(exit_pipe[0]);
  close(exit_pipe[1]);
  close(cmd_pipe[0]);
  close(cmd_pipe[1]);
  cmd_pipe[0] = -1;
  cmd_pipe[1] = -1;
  event_base_free(evbase_player);
}
