<commit_before>/*$*********************************************************************\

Name            : -

Source File     : player.c

Description     : ickstream player model 

Comments        : This modul implements the ick player model and manages the 
                  playback chain consisting out of
                   - the feeder (data pump from source)
                   - the codec (transcodes the feeder stream to pcm)
                   - the audio interface (the hardware data sink)

Called by       : ickstream protocols 

Calls           : 

Error Messages  : -
  
Date            : 24.02.2013

Updates         : -
                  
Author          : //MAF 

Remarks         : -

*************************************************************************
 * Copyright (c) 2013, ickStream GmbH
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright 
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright 
 *     notice, this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of ickStream nor the names of its contributors 
 *     may be used to endorse or promote products derived from this software 
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\************************************************************************/

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#include <jansson.h>
#ifdef ICK_UUID
#include <uuid/uuid.h>
#endif

#include "ickutils.h"
#include "ickService.h"
#include "ickMessage.h"
#include "ickCloud.h"
#include "ickScrobble.h"
#include "metaIcy.h"
#include "persist.h"
#include "playlist.h"
#include "feed.h"
#include "audio.h"
#include "player.h"
#include "hmi.h"

// #define ICK_RAWMETA

/*=========================================================================*\
	Global symbols
\*=========================================================================*/
// none


/*=========================================================================*\
       Macro and type definitions 
\*=========================================================================*/
typedef enum {
  PlayerThreadNonexistent,
  PlayerThreadInitialized,
  PlayerThreadRunning,
  PlayerThreadTerminating,
  PlayerThreadTerminatedOk,
  PlayerThreadTerminatedError
} PlayerThreadState;

 
/*=========================================================================*\
	Private symbols
\*=========================================================================*/

// persistent
static const char         *playerUUID;  
static const char         *playerInterface;  
static const char         *playerAudioDevice;  
static const char         *playerHWID;  
static const char         *playerModel;  
static const char         *playerName;
static double              playerVolume;
static bool                playerMuted;
static Playlist           *playerQueue;
static PlayerPlaybackMode  playerPlaybackMode = PlaybackQueue;
static AudioFormat         defaultAudioFormat;

// transient
pthread_mutex_t            playerMutex;
static PlayerState         playerState = PlayerStateStop;
static double              lastChange;
static AudioIf            *audioIf;

// Playback thread
static pthread_t                   playbackThread;
static volatile PlayerThreadState  playbackThreadState;
static char                       *currentTrackId;
static CodecInstance              *codecInstance;


/*=========================================================================*\
	Private prototypes
\*=========================================================================*/
static int        _playerSetVolume( double volume, bool muted );
static void      *_playbackThread( void *arg );
static int        _playItem( PlaylistItem *item, AudioFormat *format );
static AudioFeed *_feedFromPlayListItem( PlaylistItem *item, Codec **codec, const char **type, AudioFormat *format, int timeout );
static int        _audioFeedCallback( AudioFeed *feed, void* usrData );
static int        _codecNewFormatCallback( CodecInstance *instance, void *userData );
#ifdef ICK_RAWMETA
static void       _codecMetaCallback( CodecInstance *instance, CodecMetaType mType, json_t *jMeta, void *userData );
#endif


/*=========================================================================*\
      Init player
\*=========================================================================*/
int playerInit( void )
{
  DBGMSG( "Initializing player module..." );
  
/*------------------------------------------------------------------------*\
    Init state. 
\*------------------------------------------------------------------------*/
  playerState = PlayerStateStop;

/*------------------------------------------------------------------------*\
    Get volume, safe default is volume 0 and unmuted 
\*------------------------------------------------------------------------*/
  playerVolume   = persistGetReal( "PlayerVolume" );
  playerMuted    = persistGetBool( "PlayerMuted" );  	

/*------------------------------------------------------------------------*\
    Load playlist
\*------------------------------------------------------------------------*/
  json_t *jQueue = persistGetJSON( "PlayerQueue" );
  playerQueue    = playlistFromJSON( jQueue );
  playlistLock( playerQueue );
  playlistSetCursorPos( playerQueue, persistGetInteger("PlayerQueuePosition") );
  playlistUnlock( playerQueue );

/*------------------------------------------------------------------------*\
    Get repeat mode
\*------------------------------------------------------------------------*/
  playerPlaybackMode = persistGetInteger( "PlayerPlaybackMode" );

/*------------------------------------------------------------------------*\
    Get default audio format
\*------------------------------------------------------------------------*/
  if( !playerGetDefaultAudioFormat() ) {
    logerr( "Invalid or no default audio format." );
    return -1;
  }


/*------------------------------------------------------------------------*\
    Init mutex
\*------------------------------------------------------------------------*/
  ickMutexInit( &playerMutex );

/*------------------------------------------------------------------------*\
    Inform HMI and set timestamp 
\*------------------------------------------------------------------------*/
  hmiNewQueue( playerQueue );
  hmiNewState( playerState );
  hmiNewVolume( playerVolume, playerMuted );
  hmiNewPlaybackMode( playerPlaybackMode );
  lastChange = srvtime( );

/*------------------------------------------------------------------------*\
    If possible tell cloud services about the current address
\*------------------------------------------------------------------------*/
  if( ickCloudGetAccessToken() )
    ickCloudSetDeviceAddress( );

/*------------------------------------------------------------------------*\
    That's all
\*------------------------------------------------------------------------*/
  return 0;
}


/*=========================================================================*\
    Shut down player
\*=========================================================================*/
void playerShutdown( void )
{
  DBGMSG( "Shutting down player module..." );
  
/*------------------------------------------------------------------------*\
    Save playback queue state
\*------------------------------------------------------------------------*/
  playlistLock( playerQueue );
  persistSetInteger( "PlayerQueuePosition", playlistGetCursorPos(playerQueue) );
  persistSetJSON_new( "PlayerQueue", playlistGetJSON(playerQueue,PlaylistHybrid,0,0) );
  playlistUnlock( playerQueue );

/*------------------------------------------------------------------------*\
    Shut down player thread (if any)
\*------------------------------------------------------------------------*/
  if( playerState==PlayerStatePlay || playerState==PlayerStatePause )
    pthread_join( playbackThread, NULL );

/*------------------------------------------------------------------------*\
    Close audio interface
\*------------------------------------------------------------------------*/
  if( audioIf ) {
    audioIfDelete( audioIf, true );
    audioIf = NULL;
  }

/*------------------------------------------------------------------------*\
    Delete mutex
\*------------------------------------------------------------------------*/
  pthread_mutex_destroy( &playerMutex );
}


/*=========================================================================*\
      Get an ickstream protocol string from repeat mode
\*=========================================================================*/
const char *playerPlaybackModeToStr( PlayerPlaybackMode mode )
{
  DBGMSG( "playerPlaybackModeToStr: mode %d", mode );

  // Translate
  switch( mode ) {
    case PlaybackQueue:         return "QUEUE";
    case PlaybackShuffle:       return "QUEUE_SHUFFLE";
    case PlaybackRepeatQueue:   return "QUEUE_REPEAT";
    case PlaybackRepeatItem:    return "QUEUE_REPEAT_ITEM";
    case PlaybackRepeatShuffle: return "QUEUE_REPEAT_SHUFFLE";
    case PlaybackDynamic:       return "QUEUE_DYNAMIC";
  }

  // Not known or unsupported
  logerr( "Playback queue mode %d unknown or not supported by ickstream protocol.");
  return "QUEUE";
}


/*=========================================================================*\
      Get playback mode form ickstream protocol string
\*=========================================================================*/
PlayerPlaybackMode playerPlaybackModeFromStr( const char *str )
{
  DBGMSG( "playerPlaybackModeFromStr: \"%s\"", str );

  // Translate
  if( !strcmp(str,"QUEUE") )
    return PlaybackQueue;
  if( !strcmp(str,"QUEUE_SHUFFLE") )
    return PlaybackShuffle;
  if( !strcmp(str,"QUEUE_REPEAT") )
    return PlaybackRepeatQueue;
  if( !strcmp(str,"QUEUE_REPEAT_ITEM") )
    return PlaybackRepeatItem;
  if( !strcmp(str,"QUEUE_REPEAT_SHUFFLE") )
    return PlaybackRepeatShuffle;
  if( !strcmp(str,"QUEUE_DYNAMIC") )
    return PlaybackDynamic;

  // Not known or unsupported
  return -1;
}


/*=========================================================================*\
      Get playback queue (playlist) 
\*=========================================================================*/
Playlist *playerGetQueue( void )
{
  return playerQueue;
}


/*=========================================================================*\
      Clear player queue 
\*=========================================================================*/
void playerResetQueue( void )
{
  DBGMSG( "playerResetQueue" );
  playlistLock( playerQueue );
  playlistReset( playerQueue, true );
  playlistUnlock( playerQueue );
}


/*=========================================================================*\
      Get timstamp of last  change 
\*=========================================================================*/
double playerGetLastChange( void )
{
  DBGMSG( "playerGetLastChange: %lf", lastChange );
  return lastChange;
}


/*=========================================================================*\
      Get playback state 
\*=========================================================================*/
PlayerState playerGetState( void )
{
  DBGMSG( "playerGetState: %d", playerState );
  return playerState;
}


/*=========================================================================*\
      Get repeat mode 
\*=========================================================================*/
PlayerPlaybackMode playerGetPlaybackMode( void )
{
  DBGMSG( "playerGetPlaybackMode: %d", playerPlaybackMode );
  return playerPlaybackMode;
}


/*=========================================================================*\
    Get default audio Format
\*=========================================================================*/
const AudioFormat *playerGetDefaultAudioFormat( void )
{
  const char *formatStr;

/*------------------------------------------------------------------------*\
    Already initialized?
\*------------------------------------------------------------------------*/
  if( audioFormatIsComplete(&defaultAudioFormat) )
    return &defaultAudioFormat;

/*------------------------------------------------------------------------*\
    Try to get persisted value
\*------------------------------------------------------------------------*/
  formatStr = persistGetString( "DefaultAudioFormat" );
  if( !formatStr )
    return NULL;

/*------------------------------------------------------------------------*\
    Try to parse the format string
\*------------------------------------------------------------------------*/
  if( audioStrFormat(&defaultAudioFormat,formatStr) ||
      !audioFormatIsComplete(&defaultAudioFormat))
    return NULL;

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  return &defaultAudioFormat;
}


/*=========================================================================*\
    Get player hardware ID
      return NULL if not supported
      We use the MAC address as default (network name must be set).
\*=========================================================================*/
const char *playerGetHWID( void )
{
  struct ifreq request;
  int    fd, i, retcode;
  char   buf[10], hwid[20];

/*------------------------------------------------------------------------*\
    Already initialized?
\*------------------------------------------------------------------------*/
  if( playerHWID )
    return playerHWID;
    
/*------------------------------------------------------------------------*\
    Create ioctl dtata structure and open an INET socket
\*------------------------------------------------------------------------*/
  const char *ifname = playerGetInterface();
  if( !ifname )
    return NULL;
  strncpy( request.ifr_name, ifname, IFNAMSIZ-1 );
  fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);

/*------------------------------------------------------------------------*\
    Perform request and close socket
\*------------------------------------------------------------------------*/
  retcode = ioctl( fd, SIOCGIFHWADDR, &request );
  close( fd );
  
/*------------------------------------------------------------------------*\
    Error?
\*------------------------------------------------------------------------*/
  if( retcode ) {
    logerr( "Error inquiring MAC from \"%s\": %s ", 
                     playerInterface, strerror(errno) );
    return NULL;
  }

/*------------------------------------------------------------------------*\
    Setup result
\*------------------------------------------------------------------------*/
  *hwid = 0;
  for( i=0; i<6; i++ ) {
    sprintf( buf, "%s%02X", i?":":"", (unsigned char)request.ifr_addr.sa_data[i] );
    strcat( hwid, buf );
  }
  
/*------------------------------------------------------------------------*\
    Store and return result
\*------------------------------------------------------------------------*/
  DBGMSG(  "playerGetHWID: \"%s\"", hwid );
  playerHWID = strdup( hwid );
  return playerHWID;
}


/*=========================================================================*\
    Get current IP address
\*=========================================================================*/
const char *playerGetIpAddress( void )
{
  const char *deviceIf;
  const char *addrStr = NULL;

/*------------------------------------------------------------------------*\
    Need an interface
\*------------------------------------------------------------------------*/
  deviceIf = playerGetInterface();
  if( !deviceIf ) {
    logerr( "playerGetIpAddress: Device interface not yet defined." );
    return NULL;
  }

/*------------------------------------------------------------------------*\
    The interface could be already defined by an address
\*------------------------------------------------------------------------*/
  if( inet_addr(deviceIf)!=INADDR_NONE )
    addrStr = deviceIf;

/*------------------------------------------------------------------------*\
    Get the address of the interface
\*------------------------------------------------------------------------*/
  else {
    int          fd;
    struct ifreq req;
    int          len;
    memset( &req, 0, sizeof(req) );

    fd = socket( PF_INET, SOCK_DGRAM, 0 );
    if( fd<0 ) {
      logerr( "playerGetIpAddress: Could not get socket (%s).", strerror(errno) );
      return NULL;
    }

    strncpy( req.ifr_name, deviceIf, IFNAMSIZ );
    if( ioctl(fd,SIOCGIFADDR,&req,&len)<0 ) {
      logerr( "playerGetIpAddress: Could not ioctl socket (%s).", strerror(errno) );
      close( fd );
      return NULL;
    }

    addrStr = inet_ntoa( ((struct sockaddr_in *)&req.ifr_addr)->sin_addr );
    close( fd );
  }

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  DBGMSG( "playerGetIpAddress (%s): addrStr.", deviceIf, addrStr );
  return addrStr;
}


/*=========================================================================*\
    Get player UUID
\*=========================================================================*/
const char *playerGetUUID( void )
{

/*------------------------------------------------------------------------*\
    Get persistent value
\*------------------------------------------------------------------------*/
  if( !playerUUID )
    playerUUID = persistGetString( "DeviceUUID" );

/*------------------------------------------------------------------------*\
    Need to set value once
\*------------------------------------------------------------------------*/
  if( !playerUUID ) {
#ifdef ICK_UUID
    uuid_t newUUID;
    char   strUUID[37];
    uuid_generate_random( newUUID );
    uuid_unparse_lower( newUUID, strUUID );
    persistSetString( "DeviceUUID", strUUID );  // persist local buffer
    playerUUID = persistGetString( "DeviceUUID" );
#else
    logwarn( "No UUID generator, trying to use hardware id as default." );
    playerUUID = playerGetHWID();
    if( !playerUUID ) {
      logerr( "Could not determine player UUID" );
      return NULL;
    }
    persistSetString( "DeviceUUID", playerUUID );
#endif
    lognotice( "Initialized player UUID to: %s", playerUUID );
  }

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  DBGMSG( "playerGetUUID: \"%s\"", playerUUID?playerUUID:"(null)" );
  return playerUUID;
}


/*=========================================================================*\
    Get player Model
\*=========================================================================*/
const char *playerGetModel( void )
{
  if( !playerModel )
    playerModel = "Generic UN*X Player";
  DBGMSG( "playerGetModel: \"%s\"", playerModel?playerModel:"(null)" );
  return playerModel;
}


/*=========================================================================*\
    Get network interface name
\*=========================================================================*/
const char *playerGetInterface( void )
{
  if( !playerInterface )
    playerInterface = persistGetString( "PlayerInterface" );
  DBGMSG( "playerGetInterface: \"%s\"", playerInterface?playerInterface:"(null)" );
  return playerInterface;
}


/*=========================================================================*\
    Get audio device name
\*=========================================================================*/
const char *playerGetAudioDevice( void )
{
  if( !playerAudioDevice )
    playerAudioDevice = persistGetString( "PlayerAudioDevice" );
  DBGMSG( "playerGetAudioDevice: \"%s\"", playerAudioDevice?playerAudioDevice:"(null)" );
  return playerAudioDevice;
}


/*=========================================================================*\
    Get player name
\*=========================================================================*/
const char *playerGetName( void )
{
  if( !playerName )
    playerName = persistGetString( "PlayerName" );
  DBGMSG( "playerGetName: \"%s\"", playerName?playerName:"(null)" );
  return playerName;
}


/*=========================================================================*\
      Get Volume 
\*=========================================================================*/
double playerGetVolume( void )
{
  DBGMSG( "playerGetVolume: %.2lf%%", playerVolume*100 );
  return playerVolume;
}


/*=========================================================================*\
      Get Muting state 
\*=========================================================================*/
bool playerGetMuting( void )
{
  DBGMSG( "playerGetMuting: %s", playerMuted?"On":"Off" );
  return playerMuted;
}


/*=========================================================================*\
      Get playback position 
\*=========================================================================*/
double playerGetSeekPos( void )
{
  double pos = 0;
    
/*------------------------------------------------------------------------*\
    Get Position from codec
\*------------------------------------------------------------------------*/
  if( codecInstance && codecGetSeekTime(codecInstance,&pos) )
    logwarn( "playerGetSeekPos: could not get seek time" );   

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  DBGMSG( "playerGetSeekPos: %.2lfs", pos );
  return pos;	
}


/*=========================================================================*\
    Set default audio format
\*=========================================================================*/
int playerSetDefaultAudioFormat( const char *format )
{
  DBGMSG( "Setting default audio format: \"%s\".", format );

/*------------------------------------------------------------------------*\
    Try to parse the format string
\*------------------------------------------------------------------------*/
  if( audioStrFormat(&defaultAudioFormat,format) ) {
    logerr( "Cannot parse default audio format (%s).", format );
    return -1;
  }

/*------------------------------------------------------------------------*\
    Should be complete
\*------------------------------------------------------------------------*/
  if( !audioFormatIsComplete(&defaultAudioFormat) ){
    logerr( "Default audio format needs to be complete (%s).", format );
    return -1;
  }

/*------------------------------------------------------------------------*\
    Store value and return
\*------------------------------------------------------------------------*/
  persistSetString( "DefaultAudioFormat", format );
  return 0;
}


/*=========================================================================*\
    Set player UUID (only in debug mode)
\*=========================================================================*/
#ifdef ICK_DEBUG
void playerSetUUID( const char *uuid )
{	
  loginfo( "Setting player UUID to \"%s\"", uuid );
  playerUUID = uuid;  
  persistSetString( "DeviceUUID", uuid );
}
#endif


/*=========================================================================*\
    Set player model
\*=========================================================================*/
void playerSetModel( const char *model )
{	
  loginfo( "Setting player model to \"%s\"", model );
  if( playerModel ) {
    logerr( "Can set player model only once" );
    return;
  }
  playerModel = model;  
} 


/*=========================================================================*\
    Set network interface name
\*=========================================================================*/
void playerSetInterface( const char *name )
{
  loginfo( "Setting interface name to \"%s\"", name );
  if( playerInterface ) {
    logerr( "Can set interface name only once" );
    return;
  }
  playerInterface = name;  
  persistSetString( "PlayerInterface", name );
}


/*=========================================================================*\
    Set audio device name
\*=========================================================================*/
void playerSetAudioDevice( const char *name )
{
  loginfo( "Setting audio device to \"%s\"", name );
  playerAudioDevice = name;  
  persistSetString( "PlayerAudioDevice", name );
}


/*=========================================================================*\
    Set player name
\*=========================================================================*/
void playerSetName( const char *name, bool broadcast )
{
  loginfo( "Setting player name to \"%s\"", name );

/*------------------------------------------------------------------------*\
    Store new name 
\*------------------------------------------------------------------------*/
  playerName = name;  
  persistSetString( "PlayerName", name );

/*------------------------------------------------------------------------*\
    Update timestamp and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  hmiNewConfig();
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );
}


/*=========================================================================*\
      Set Volume 
        returns effective volume (set to 0 if muted)
\*=========================================================================*/
double playerSetVolume( double volume, bool muted, bool broadcast )
{
  loginfo( "Setting Volume to %lf", volume );

/*------------------------------------------------------------------------*\
    Clip value 
\*------------------------------------------------------------------------*/
  if( volume<0 )
    volume = 0;
  if( volume>1 )
    volume = 1;

/*------------------------------------------------------------------------*\
    use internal interface to backend or codec
\*------------------------------------------------------------------------*/
  if( _playerSetVolume(playerVolume,playerMuted) )
    logwarn( "playerSetVolume: could not set volume to %.2lf%%%s", 
              playerVolume*100, playerMuted?" (muted)":"" ); 

/*------------------------------------------------------------------------*\
    Store new volume and muting state
\*------------------------------------------------------------------------*/
  playerVolume = volume;
  persistSetReal( "PlayerVolume", volume );
  playerMuted = muted;
  persistSetBool( "playerMuted", muted );


/*------------------------------------------------------------------------*\
    Update timestamp and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  hmiNewVolume( playerVolume, playerMuted );
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );

/*------------------------------------------------------------------------*\
    Return new volume 
\*------------------------------------------------------------------------*/
  return muted?0:playerVolume;
}


/*=========================================================================*\
      Set Volume 
        internal interface to audio backand or codec 
\*=========================================================================*/
static int _playerSetVolume( double volume, bool muted )
{
  int rc = 0;
  DBGMSG( "_playerSetVolume: %lf%s", volume, muted?" (muted)":"" );

/*------------------------------------------------------------------------*\
    Use backend
\*------------------------------------------------------------------------*/
  if( audioIf && audioIfSupportsVolume(audioIf) ) {
    if( audioIfSetVolume(audioIf,volume,muted) ) {
      logerr( "_playerSetVolume: could not set volume to %.2lf%%%s for backend \"%s\"", 
               volume*100, muted?" (muted)":"", audioIf->devName ); 
      rc = -1;
    }

  }

/*------------------------------------------------------------------------*\
    Use codec
\*------------------------------------------------------------------------*/
  else if( codecInstance ) {
    if( codecSetVolume(codecInstance,playerVolume,playerMuted) ) {
      logerr( "_playerSetVolume: could not set volume to %.2lf%%%s for codec %s", 
                volume*100, muted?" (muted)":"", codecInstance->codec->name );
      rc = -1;
    } 
  }

/*------------------------------------------------------------------------*\
    Return new volume 
\*------------------------------------------------------------------------*/
  return rc;
}


/*=========================================================================*\
      Set repeat mode
\*=========================================================================*/
int playerSetPlaybackMode( PlayerPlaybackMode mode, bool broadcast )
{
  bool playlistChanged = false;
  loginfo( "Setting playback mode to %d", mode );

/*------------------------------------------------------------------------*\
    Store new state
\*------------------------------------------------------------------------*/
  if( playerPlaybackMode==mode )
    return 0;

/*------------------------------------------------------------------------*\
    Store new state 
\*------------------------------------------------------------------------*/
  playerPlaybackMode = mode;
  persistSetInteger( "PlayerPlaybackMode", mode );

/*------------------------------------------------------------------------*\
    Modify mapping if necessary
\*------------------------------------------------------------------------*/
  switch( playerPlaybackMode ) {
    case PlaybackShuffle:
      if( playerQueue ) {
        int startPos = 0;
        int endPos   = playlistGetLength( playerQueue )-1;
        playlistShuffle( playerQueue, startPos, endPos, true );
        playlistChanged = true;
      }
      break;

    case PlaybackRepeatShuffle:
      break;

    case PlaybackQueue:
    case PlaybackRepeatQueue:
    case PlaybackRepeatItem:
    case PlaybackDynamic:
      if( playerQueue ) {
        playlistResetMapping( playerQueue, false );
        playlistChanged = true;
      }
      break;
  }

/*------------------------------------------------------------------------*\
    Update timestamp and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  hmiNewPlaybackMode( mode );
  if( playlistChanged )
    hmiNewQueue( playerQueue );
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );
  if( broadcast && playlistChanged )
    ickMessageNotifyPlaylist( NULL );

/*=========================================================================*\
      return new mode 
\*=========================================================================*/
  return 0;
}


/*=========================================================================*\
      Change playback state 
\*=========================================================================*/
int playerSetState( PlayerState state, bool broadcast )
{
  int           rc = 0;
  int           perr;
  PlaylistItem *newTrack;
  const char   *newTrackId;
  
  DBGMSG( "playerSetState: %s -> %s", playerStateToStr(playerState), playerStateToStr(state) );
  
/*------------------------------------------------------------------------*\
    Lock player, we don't want concurrent modifications going on...
\*------------------------------------------------------------------------*/
  perr = pthread_mutex_lock( &playerMutex );
  if( perr )
    logerr( "playerSetState: locking player mutex: %s", strerror(perr) );

/*------------------------------------------------------------------------*\
    Get current playback item to detect changes in the queue 
\*------------------------------------------------------------------------*/
  playlistLock( playerQueue );
  newTrack = playlistGetCursorItem( playerQueue );
  playlistUnlock( playerQueue );

/*------------------------------------------------------------------------*\
    Switch on target state 
\*------------------------------------------------------------------------*/
  switch( state ) {

/*------------------------------------------------------------------------*\
    Start or unpause playback
\*------------------------------------------------------------------------*/
    case PlayerStatePlay:

      // Is there a track to play ?
      if( !newTrack ) {
        lognotice( "playerSetState (start): Empty queue or no cursor." );
        rc = -1;
       break;
      }
      
      // Unpausing current track?
      playlistItemLock( newTrack );
      newTrackId = playlistItemGetId( newTrack );

      if( playerState==PlayerStatePause && currentTrackId && !strcmp(newTrackId,currentTrackId) ) {
        lognotice( "playerSetState (start): Unpausing current item \"%s\" (%s).",
                   playlistItemGetText(newTrack), playlistItemGetId(newTrack) );
        playlistItemUnlock( newTrack );
        playerState = PlayerStatePlay;
        rc = audioIfSetPause( audioIf, false );
        break;
      }
      playlistItemUnlock( newTrack );

     // Need to stop running track?
      if( playerState==PlayerStatePlay || playerState==PlayerStatePause ) {
        DBGMSG( "playerSetState (start): Request active playback thread to terminate." );
        if( !currentTrackId )
          logerr( "playerSetState (start): internal error (No current track)." );
        else
          lognotice( "playerSetState (start): Stopping current track (%s).", currentTrackId );
        if( playbackThreadState!=PlayerThreadRunning )
          logerr( "playerSetState (start): internal error: %s but no running playback thread.",
                  playerStateToStr(playerState) );
        else {
          playbackThreadState = PlayerThreadTerminating;
          pthread_join( playbackThread, NULL ); 
        }

        // Stop audio interface (if not done by _playbackThread() )
        if( !audioIf ) {
          DBGMSG( "playerSetState (play): No audio interface to drop." );
        }
        else {
          DBGMSG( "playerSetState (play): Drop existing audio interface." );
          if( audioIfStop(audioIf,AudioDrop) )
            logerr( "playerSetState (play): Could not drop audio interface \"%s\".", audioIf->devName );
          else
            audioIf = NULL;
        }
      }

      // Try to setup audio interface if not yet done
      if( !audioIf ) {
        const char         *device;
        const AudioBackend *backend = audioBackendByDeviceString( playerAudioDevice, &device );
        if( backend )
          audioIf = audioIfNew( backend, device, AudioFifoDefaultSize );
        if( !audioIf ) {
          logerr( "playerSetState (start): Could not open audio device \"%s\".", playerAudioDevice );
          rc = -1;
          break;
        }
        if( _playerSetVolume(playerVolume,playerMuted) )
          logwarn( "playerSetState (start): Could not set volume to %.2lf%% (%s).",
                   playerVolume*100, playerMuted?"muted":"unmuted" );
      }

      // Create new playback thread
      rc = pthread_create( &playbackThread, NULL, _playbackThread, NULL );
      DBGMSG( "Starting new player thread." );
      if( rc ) {
        logerr( "playerSetState (start): Unable to start thread (%s).", strerror(rc) );
        rc = -1;
        break;
      }

      // Set new state
      playerState = PlayerStatePlay;
      break;

/*------------------------------------------------------------------------*\
    Pause playback
\*------------------------------------------------------------------------*/
    case PlayerStatePause:

     // not yet initialized?
      if( !audioIf ) {
        logerr( "playerSetState (pause): audio device not yet initialized." );
        rc = -1;
        break;
      } 

      // Is there a, feed->usrData track to play ?
      if( !newTrack ) {
        lognotice( "playerSetState (pause): Empty queue or no cursor" );
        rc = -1;
        break;
      }
      
      // if the track did not change: set pause flag
      playlistItemLock( newTrack );
      newTrackId = playlistItemGetId( newTrack );
      playlistItemUnlock( newTrack );
      if( currentTrackId && !strcmp(newTrackId,currentTrackId) ) {

        // Check for right state
        if( playerState==PlayerStateStop )
          logwarn( "playerSetState (pause): Cannot pause stopped playback." );
        else if( playerState==PlayerStatePause )
          logwarn( "playerSetState (pause): Pause already paused playback." );
        // Pause audio output and set state
        else {
          lognotice( "playerSetState (pause): Playback paused." );
          rc = audioIfSetPause( audioIf, true );
          if( !rc )
            playerState = PlayerStatePause;
        }
        break;
      }
      
      lognotice( "playerSetState (pause): Current track changed, stopping..." );
      // no break here, as we'll change the status to PlayerStateStop in case 
      // we were changing the track in paused state...


/*------------------------------------------------------------------------*\
    Stop playback
\*------------------------------------------------------------------------*/
    case PlayerStateStop:

      // Inform HMI in any case
      hmiNewPosition( 0.0 );
      /*
      playlistItemLock( newTrack );
      newTrackId = playlistItemGetId( newTrack );
      playlistItemUnlock( newTrack );
      if( newTrack && (!currentTrackId || strcmp(newTrackId,currentTrackId)) )
      */
      hmiNewQueue( playerQueue );

      // Stop audio interface (if not done by _playbackThread() )
      if( !audioIf ) {
        DBGMSG( "playerSetState (stop): No audio interface to drop." );
      }
      else {
        DBGMSG( "playerSetState (stop): Drop existing audio interface." );
        if( audioIfStop(audioIf,AudioDrop) )
          logerr( "playerSetState (stop): Could not drop audio interface \"%s\".", audioIf->devName );
        else
          audioIf = NULL;
      }

      // request thread to stop playback and set new player state
      playbackThreadState = PlayerThreadTerminating;
      if( playerState==PlayerStatePlay || playerState==PlayerStatePause )
        pthread_join( playbackThread, NULL );
      playerState = PlayerStateStop;


      break; 

/*------------------------------------------------------------------------*\
    Unknown command
\*------------------------------------------------------------------------*/
    default:
      logerr( "playerSetState: Unknown target state %d.", state );
      rc = -1;
      break;
  }

/*------------------------------------------------------------------------*\
    Update timestamp, unlock player and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  perr = pthread_mutex_unlock( &playerMutex );
  if( perr )
    logerr( "playerSetState: unlocking player mutex: %s", strerror(perr) );
  hmiNewState( playerState );
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );

/*------------------------------------------------------------------------*\
    That's it 
\*------------------------------------------------------------------------*/
  return rc;
}


/*=========================================================================*\
      Player thread
\*=========================================================================*/
const char *playerStateToStr( PlayerState state )
{

/*------------------------------------------------------------------------*\
    Known player states
\*------------------------------------------------------------------------*/
  switch( state ) {
    case PlayerStateStop:  return "Stopped";
    case PlayerStatePlay:  return "Play";
    case PlayerStatePause: return "Paused";
    default: break;
  }

/*------------------------------------------------------------------------*\
    Handle unknown states
\*------------------------------------------------------------------------*/
  logerr( "playerStateToStr: unknown state %d", state );
  return "Unknown";
}

/*=========================================================================*\
      Player thread
\*=========================================================================*/
static void *_playbackThread( void *arg )
{
  PlaylistItem *item;
  AudioFormat   backendFormat;

  DBGMSG( "Player thread: starting." );
  PTHREADSETNAME( "player" );

/*------------------------------------------------------------------------*\
    Init encoding part of audio format from player default
\*------------------------------------------------------------------------*/
  memcpy( &backendFormat, &defaultAudioFormat, sizeof(AudioFormat) );
  backendFormat.sampleRate = -1;
  backendFormat.channels   = -1;

/*------------------------------------------------------------------------*\
    Loop over player queue 
\*------------------------------------------------------------------------*/
  playbackThreadState = PlayerThreadRunning;
  playlistLock( playerQueue );
  item = playlistGetCursorItem( playerQueue );
  playlistItemIncRef( item );
  playlistUnlock( playerQueue );
  while( item && playbackThreadState==PlayerThreadRunning ) {

    // Play item
    if( _playItem(item,&backendFormat) )
      playbackThreadState = PlayerThreadTerminatedError;

    // Error or stopped?
    if( playbackThreadState!=PlayerThreadRunning ) {
      playlistItemDecRef( item );
      break;
    }

    // Repeat track or reconnect stream?
    if( playerPlaybackMode==PlaybackRepeatItem ||
        playlistItemGetType(item)==PlaylistItemStream )
      continue;
    playlistItemDecRef( item );

    // Get next item
    playlistLock( playerQueue );
    item = playlistIncrCursorItem( playerQueue );
    playlistItemIncRef( item );
    playlistUnlock( playerQueue );
    if( item )
      continue;

    // repeat at end of list
    if( playerPlaybackMode==PlaybackRepeatQueue ) {
      playlistLock( playerQueue );
      item = playlistSetCursorPos( playerQueue, 0 );
      playlistItemIncRef( item );
      playlistUnlock( playerQueue );
    }

    // repeat at end of list with shuffling
    else if( playerPlaybackMode==PlaybackRepeatShuffle ) {
      playlistLock( playerQueue );
      playlistShuffle( playerQueue, 0, playlistGetLength(playerQueue)-1, false );
      item = playlistSetCursorPos( playerQueue, 0 );
      playlistItemIncRef( item );
      playlistUnlock( playerQueue );
      ickMessageNotifyPlaylist( NULL );
     }

  }  // End of: Thread main loop
  DBGMSG( "Player thread: End of playback loop (state %d).", playbackThreadState );

/*------------------------------------------------------------------------*\
    Stop audio interface in draining mode if at end of item list
\*------------------------------------------------------------------------*/
  if( playbackThreadState==PlayerThreadRunning ) {
    if( audioIfStop(audioIf,AudioDrain) )
      logerr( "Player thread: Could not stop audio interface \"%s\".", audioIf->devName );
    else
      audioIf = NULL;
  }

/*------------------------------------------------------------------------*\
    Set and broadcast new player state
\*------------------------------------------------------------------------*/
  if( playbackThreadState==PlayerThreadRunning ) {
    lognotice( "_playerThread: End of queue." );
    playerState = PlayerStateStop;
    ickMessageNotifyPlayerState( NULL );
    hmiNewState( playerState );
    hmiNewPosition( 0.0 );
  }

/*------------------------------------------------------------------------*\
    Clean up, that's it ...
\*------------------------------------------------------------------------*/
  Sfree( currentTrackId );<commit_msg>some fixes for playing new track after paused mode<commit_after>/*$*********************************************************************\

Name            : -

Source File     : player.c

Description     : ickstream player model 

Comments        : This modul implements the ick player model and manages the 
                  playback chain consisting out of
                   - the feeder (data pump from source)
                   - the codec (transcodes the feeder stream to pcm)
                   - the audio interface (the hardware data sink)

Called by       : ickstream protocols 

Calls           : 

Error Messages  : -
  
Date            : 24.02.2013

Updates         : -
                  
Author          : //MAF 

Remarks         : -

*************************************************************************
 * Copyright (c) 2013, ickStream GmbH
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright 
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright 
 *     notice, this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of ickStream nor the names of its contributors 
 *     may be used to endorse or promote products derived from this software 
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\************************************************************************/

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#include <jansson.h>
#ifdef ICK_UUID
#include <uuid/uuid.h>
#endif

#include "ickutils.h"
#include "ickService.h"
#include "ickMessage.h"
#include "ickCloud.h"
#include "ickScrobble.h"
#include "metaIcy.h"
#include "persist.h"
#include "playlist.h"
#include "feed.h"
#include "audio.h"
#include "player.h"
#include "hmi.h"

// #define ICK_RAWMETA

/*=========================================================================*\
	Global symbols
\*=========================================================================*/
// none


/*=========================================================================*\
       Macro and type definitions 
\*=========================================================================*/
typedef enum {
  PlayerThreadNonexistent,
  PlayerThreadInitialized,
  PlayerThreadRunning,
  PlayerThreadTerminating,
  PlayerThreadTerminatedOk,
  PlayerThreadTerminatedError
} PlayerThreadState;

 
/*=========================================================================*\
	Private symbols
\*=========================================================================*/

// persistent
static const char         *playerUUID;  
static const char         *playerInterface;  
static const char         *playerAudioDevice;  
static const char         *playerHWID;  
static const char         *playerModel;  
static const char         *playerName;
static double              playerVolume;
static bool                playerMuted;
static Playlist           *playerQueue;
static PlayerPlaybackMode  playerPlaybackMode = PlaybackQueue;
static AudioFormat         defaultAudioFormat;

// transient
pthread_mutex_t            playerMutex;
static PlayerState         playerState = PlayerStateStop;
static double              lastChange;
static AudioIf            *audioIf;

// Playback thread
static pthread_t                   playbackThread;
static volatile PlayerThreadState  playbackThreadState;
static char                       *currentTrackId;
static CodecInstance              *codecInstance;


/*=========================================================================*\
	Private prototypes
\*=========================================================================*/
static int        _playerSetVolume( double volume, bool muted );
static void      *_playbackThread( void *arg );
static int        _playItem( PlaylistItem *item, AudioFormat *format );
static AudioFeed *_feedFromPlayListItem( PlaylistItem *item, Codec **codec, const char **type, AudioFormat *format, int timeout );
static int        _audioFeedCallback( AudioFeed *feed, void* usrData );
static int        _codecNewFormatCallback( CodecInstance *instance, void *userData );
#ifdef ICK_RAWMETA
static void       _codecMetaCallback( CodecInstance *instance, CodecMetaType mType, json_t *jMeta, void *userData );
#endif


/*=========================================================================*\
      Init player
\*=========================================================================*/
int playerInit( void )
{
  DBGMSG( "Initializing player module..." );
  
/*------------------------------------------------------------------------*\
    Init state. 
\*------------------------------------------------------------------------*/
  playerState = PlayerStateStop;

/*------------------------------------------------------------------------*\
    Get volume, safe default is volume 0 and unmuted 
\*------------------------------------------------------------------------*/
  playerVolume   = persistGetReal( "PlayerVolume" );
  playerMuted    = persistGetBool( "PlayerMuted" );  	

/*------------------------------------------------------------------------*\
    Load playlist
\*------------------------------------------------------------------------*/
  json_t *jQueue = persistGetJSON( "PlayerQueue" );
  playerQueue    = playlistFromJSON( jQueue );
  playlistLock( playerQueue );
  playlistSetCursorPos( playerQueue, persistGetInteger("PlayerQueuePosition") );
  playlistUnlock( playerQueue );

/*------------------------------------------------------------------------*\
    Get repeat mode
\*------------------------------------------------------------------------*/
  playerPlaybackMode = persistGetInteger( "PlayerPlaybackMode" );

/*------------------------------------------------------------------------*\
    Get default audio format
\*------------------------------------------------------------------------*/
  if( !playerGetDefaultAudioFormat() ) {
    logerr( "Invalid or no default audio format." );
    return -1;
  }


/*------------------------------------------------------------------------*\
    Init mutex
\*------------------------------------------------------------------------*/
  ickMutexInit( &playerMutex );

/*------------------------------------------------------------------------*\
    Inform HMI and set timestamp 
\*------------------------------------------------------------------------*/
  hmiNewQueue( playerQueue );
  hmiNewState( playerState );
  hmiNewVolume( playerVolume, playerMuted );
  hmiNewPlaybackMode( playerPlaybackMode );
  lastChange = srvtime( );

/*------------------------------------------------------------------------*\
    If possible tell cloud services about the current address
\*------------------------------------------------------------------------*/
  if( ickCloudGetAccessToken() )
    ickCloudSetDeviceAddress( );

/*------------------------------------------------------------------------*\
    That's all
\*------------------------------------------------------------------------*/
  return 0;
}


/*=========================================================================*\
    Shut down player
\*=========================================================================*/
void playerShutdown( void )
{
  DBGMSG( "Shutting down player module..." );
  
/*------------------------------------------------------------------------*\
    Save playback queue state
\*------------------------------------------------------------------------*/
  playlistLock( playerQueue );
  persistSetInteger( "PlayerQueuePosition", playlistGetCursorPos(playerQueue) );
  persistSetJSON_new( "PlayerQueue", playlistGetJSON(playerQueue,PlaylistHybrid,0,0) );
  playlistUnlock( playerQueue );

/*------------------------------------------------------------------------*\
    Shut down player thread (if any)
\*------------------------------------------------------------------------*/
  if( playerState==PlayerStatePlay || playerState==PlayerStatePause )
    pthread_join( playbackThread, NULL );

/*------------------------------------------------------------------------*\
    Close audio interface
\*------------------------------------------------------------------------*/
  if( audioIf ) {
    audioIfDelete( audioIf, true );
    audioIf = NULL;
  }

/*------------------------------------------------------------------------*\
    Delete mutex
\*------------------------------------------------------------------------*/
  pthread_mutex_destroy( &playerMutex );
}


/*=========================================================================*\
      Get an ickstream protocol string from repeat mode
\*=========================================================================*/
const char *playerPlaybackModeToStr( PlayerPlaybackMode mode )
{
  DBGMSG( "playerPlaybackModeToStr: mode %d", mode );

  // Translate
  switch( mode ) {
    case PlaybackQueue:         return "QUEUE";
    case PlaybackShuffle:       return "QUEUE_SHUFFLE";
    case PlaybackRepeatQueue:   return "QUEUE_REPEAT";
    case PlaybackRepeatItem:    return "QUEUE_REPEAT_ITEM";
    case PlaybackRepeatShuffle: return "QUEUE_REPEAT_SHUFFLE";
    case PlaybackDynamic:       return "QUEUE_DYNAMIC";
  }

  // Not known or unsupported
  logerr( "Playback queue mode %d unknown or not supported by ickstream protocol.");
  return "QUEUE";
}


/*=========================================================================*\
      Get playback mode form ickstream protocol string
\*=========================================================================*/
PlayerPlaybackMode playerPlaybackModeFromStr( const char *str )
{
  DBGMSG( "playerPlaybackModeFromStr: \"%s\"", str );

  // Translate
  if( !strcmp(str,"QUEUE") )
    return PlaybackQueue;
  if( !strcmp(str,"QUEUE_SHUFFLE") )
    return PlaybackShuffle;
  if( !strcmp(str,"QUEUE_REPEAT") )
    return PlaybackRepeatQueue;
  if( !strcmp(str,"QUEUE_REPEAT_ITEM") )
    return PlaybackRepeatItem;
  if( !strcmp(str,"QUEUE_REPEAT_SHUFFLE") )
    return PlaybackRepeatShuffle;
  if( !strcmp(str,"QUEUE_DYNAMIC") )
    return PlaybackDynamic;

  // Not known or unsupported
  return -1;
}


/*=========================================================================*\
      Get playback queue (playlist) 
\*=========================================================================*/
Playlist *playerGetQueue( void )
{
  return playerQueue;
}


/*=========================================================================*\
      Clear player queue 
\*=========================================================================*/
void playerResetQueue( void )
{
  DBGMSG( "playerResetQueue" );
  playlistLock( playerQueue );
  playlistReset( playerQueue, true );
  playlistUnlock( playerQueue );
}


/*=========================================================================*\
      Get timstamp of last  change 
\*=========================================================================*/
double playerGetLastChange( void )
{
  DBGMSG( "playerGetLastChange: %lf", lastChange );
  return lastChange;
}


/*=========================================================================*\
      Get playback state 
\*=========================================================================*/
PlayerState playerGetState( void )
{
  DBGMSG( "playerGetState: %d (%s)", playerState, playerStateToStr(playerState) );
  return playerState;
}


/*=========================================================================*\
      Get repeat mode 
\*=========================================================================*/
PlayerPlaybackMode playerGetPlaybackMode( void )
{
  DBGMSG( "playerGetPlaybackMode: %d", playerPlaybackMode );
  return playerPlaybackMode;
}


/*=========================================================================*\
    Get default audio Format
\*=========================================================================*/
const AudioFormat *playerGetDefaultAudioFormat( void )
{
  const char *formatStr;

/*------------------------------------------------------------------------*\
    Already initialized?
\*------------------------------------------------------------------------*/
  if( audioFormatIsComplete(&defaultAudioFormat) )
    return &defaultAudioFormat;

/*------------------------------------------------------------------------*\
    Try to get persisted value
\*------------------------------------------------------------------------*/
  formatStr = persistGetString( "DefaultAudioFormat" );
  if( !formatStr )
    return NULL;

/*------------------------------------------------------------------------*\
    Try to parse the format string
\*------------------------------------------------------------------------*/
  if( audioStrFormat(&defaultAudioFormat,formatStr) ||
      !audioFormatIsComplete(&defaultAudioFormat))
    return NULL;

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  return &defaultAudioFormat;
}


/*=========================================================================*\
    Get player hardware ID
      return NULL if not supported
      We use the MAC address as default (network name must be set).
\*=========================================================================*/
const char *playerGetHWID( void )
{
  struct ifreq request;
  int    fd, i, retcode;
  char   buf[10], hwid[20];

/*------------------------------------------------------------------------*\
    Already initialized?
\*------------------------------------------------------------------------*/
  if( playerHWID )
    return playerHWID;
    
/*------------------------------------------------------------------------*\
    Create ioctl dtata structure and open an INET socket
\*------------------------------------------------------------------------*/
  const char *ifname = playerGetInterface();
  if( !ifname )
    return NULL;
  strncpy( request.ifr_name, ifname, IFNAMSIZ-1 );
  fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);

/*------------------------------------------------------------------------*\
    Perform request and close socket
\*------------------------------------------------------------------------*/
  retcode = ioctl( fd, SIOCGIFHWADDR, &request );
  close( fd );
  
/*------------------------------------------------------------------------*\
    Error?
\*------------------------------------------------------------------------*/
  if( retcode ) {
    logerr( "Error inquiring MAC from \"%s\": %s ", 
                     playerInterface, strerror(errno) );
    return NULL;
  }

/*------------------------------------------------------------------------*\
    Setup result
\*------------------------------------------------------------------------*/
  *hwid = 0;
  for( i=0; i<6; i++ ) {
    sprintf( buf, "%s%02X", i?":":"", (unsigned char)request.ifr_addr.sa_data[i] );
    strcat( hwid, buf );
  }
  
/*------------------------------------------------------------------------*\
    Store and return result
\*------------------------------------------------------------------------*/
  DBGMSG(  "playerGetHWID: \"%s\"", hwid );
  playerHWID = strdup( hwid );
  return playerHWID;
}


/*=========================================================================*\
    Get current IP address
\*=========================================================================*/
const char *playerGetIpAddress( void )
{
  const char *deviceIf;
  const char *addrStr = NULL;

/*------------------------------------------------------------------------*\
    Need an interface
\*------------------------------------------------------------------------*/
  deviceIf = playerGetInterface();
  if( !deviceIf ) {
    logerr( "playerGetIpAddress: Device interface not yet defined." );
    return NULL;
  }

/*------------------------------------------------------------------------*\
    The interface could be already defined by an address
\*------------------------------------------------------------------------*/
  if( inet_addr(deviceIf)!=INADDR_NONE )
    addrStr = deviceIf;

/*------------------------------------------------------------------------*\
    Get the address of the interface
\*------------------------------------------------------------------------*/
  else {
    int          fd;
    struct ifreq req;
    int          len;
    memset( &req, 0, sizeof(req) );

    fd = socket( PF_INET, SOCK_DGRAM, 0 );
    if( fd<0 ) {
      logerr( "playerGetIpAddress: Could not get socket (%s).", strerror(errno) );
      return NULL;
    }

    strncpy( req.ifr_name, deviceIf, IFNAMSIZ );
    if( ioctl(fd,SIOCGIFADDR,&req,&len)<0 ) {
      logerr( "playerGetIpAddress: Could not ioctl socket (%s).", strerror(errno) );
      close( fd );
      return NULL;
    }

    addrStr = inet_ntoa( ((struct sockaddr_in *)&req.ifr_addr)->sin_addr );
    close( fd );
  }

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  DBGMSG( "playerGetIpAddress (%s): addrStr.", deviceIf, addrStr );
  return addrStr;
}


/*=========================================================================*\
    Get player UUID
\*=========================================================================*/
const char *playerGetUUID( void )
{

/*------------------------------------------------------------------------*\
    Get persistent value
\*------------------------------------------------------------------------*/
  if( !playerUUID )
    playerUUID = persistGetString( "DeviceUUID" );

/*------------------------------------------------------------------------*\
    Need to set value once
\*------------------------------------------------------------------------*/
  if( !playerUUID ) {
#ifdef ICK_UUID
    uuid_t newUUID;
    char   strUUID[37];
    uuid_generate_random( newUUID );
    uuid_unparse_lower( newUUID, strUUID );
    persistSetString( "DeviceUUID", strUUID );  // persist local buffer
    playerUUID = persistGetString( "DeviceUUID" );
#else
    logwarn( "No UUID generator, trying to use hardware id as default." );
    playerUUID = playerGetHWID();
    if( !playerUUID ) {
      logerr( "Could not determine player UUID" );
      return NULL;
    }
    persistSetString( "DeviceUUID", playerUUID );
#endif
    lognotice( "Initialized player UUID to: %s", playerUUID );
  }

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  DBGMSG( "playerGetUUID: \"%s\"", playerUUID?playerUUID:"(null)" );
  return playerUUID;
}


/*=========================================================================*\
    Get player Model
\*=========================================================================*/
const char *playerGetModel( void )
{
  if( !playerModel )
    playerModel = "Generic UN*X Player";
  DBGMSG( "playerGetModel: \"%s\"", playerModel?playerModel:"(null)" );
  return playerModel;
}


/*=========================================================================*\
    Get network interface name
\*=========================================================================*/
const char *playerGetInterface( void )
{
  if( !playerInterface )
    playerInterface = persistGetString( "PlayerInterface" );
  DBGMSG( "playerGetInterface: \"%s\"", playerInterface?playerInterface:"(null)" );
  return playerInterface;
}


/*=========================================================================*\
    Get audio device name
\*=========================================================================*/
const char *playerGetAudioDevice( void )
{
  if( !playerAudioDevice )
    playerAudioDevice = persistGetString( "PlayerAudioDevice" );
  DBGMSG( "playerGetAudioDevice: \"%s\"", playerAudioDevice?playerAudioDevice:"(null)" );
  return playerAudioDevice;
}


/*=========================================================================*\
    Get player name
\*=========================================================================*/
const char *playerGetName( void )
{
  if( !playerName )
    playerName = persistGetString( "PlayerName" );
  DBGMSG( "playerGetName: \"%s\"", playerName?playerName:"(null)" );
  return playerName;
}


/*=========================================================================*\
      Get Volume 
\*=========================================================================*/
double playerGetVolume( void )
{
  DBGMSG( "playerGetVolume: %.2lf%%", playerVolume*100 );
  return playerVolume;
}


/*=========================================================================*\
      Get Muting state 
\*=========================================================================*/
bool playerGetMuting( void )
{
  DBGMSG( "playerGetMuting: %s", playerMuted?"On":"Off" );
  return playerMuted;
}


/*=========================================================================*\
      Get playback position 
\*=========================================================================*/
double playerGetSeekPos( void )
{
  double pos = 0;
    
/*------------------------------------------------------------------------*\
    Get Position from codec
\*------------------------------------------------------------------------*/
  if( codecInstance && codecGetSeekTime(codecInstance,&pos) )
    logwarn( "playerGetSeekPos: could not get seek time" );   

/*------------------------------------------------------------------------*\
    That's it
\*------------------------------------------------------------------------*/
  DBGMSG( "playerGetSeekPos: %.2lfs", pos );
  return pos;	
}


/*=========================================================================*\
    Set default audio format
\*=========================================================================*/
int playerSetDefaultAudioFormat( const char *format )
{
  DBGMSG( "Setting default audio format: \"%s\".", format );

/*------------------------------------------------------------------------*\
    Try to parse the format string
\*------------------------------------------------------------------------*/
  if( audioStrFormat(&defaultAudioFormat,format) ) {
    logerr( "Cannot parse default audio format (%s).", format );
    return -1;
  }

/*------------------------------------------------------------------------*\
    Should be complete
\*------------------------------------------------------------------------*/
  if( !audioFormatIsComplete(&defaultAudioFormat) ){
    logerr( "Default audio format needs to be complete (%s).", format );
    return -1;
  }

/*------------------------------------------------------------------------*\
    Store value and return
\*------------------------------------------------------------------------*/
  persistSetString( "DefaultAudioFormat", format );
  return 0;
}


/*=========================================================================*\
    Set player UUID (only in debug mode)
\*=========================================================================*/
#ifdef ICK_DEBUG
void playerSetUUID( const char *uuid )
{	
  loginfo( "Setting player UUID to \"%s\"", uuid );
  playerUUID = uuid;  
  persistSetString( "DeviceUUID", uuid );
}
#endif


/*=========================================================================*\
    Set player model
\*=========================================================================*/
void playerSetModel( const char *model )
{	
  loginfo( "Setting player model to \"%s\"", model );
  if( playerModel ) {
    logerr( "Can set player model only once" );
    return;
  }
  playerModel = model;  
} 


/*=========================================================================*\
    Set network interface name
\*=========================================================================*/
void playerSetInterface( const char *name )
{
  loginfo( "Setting interface name to \"%s\"", name );
  if( playerInterface ) {
    logerr( "Can set interface name only once" );
    return;
  }
  playerInterface = name;  
  persistSetString( "PlayerInterface", name );
}


/*=========================================================================*\
    Set audio device name
\*=========================================================================*/
void playerSetAudioDevice( const char *name )
{
  loginfo( "Setting audio device to \"%s\"", name );
  playerAudioDevice = name;  
  persistSetString( "PlayerAudioDevice", name );
}


/*=========================================================================*\
    Set player name
\*=========================================================================*/
void playerSetName( const char *name, bool broadcast )
{
  loginfo( "Setting player name to \"%s\"", name );

/*------------------------------------------------------------------------*\
    Store new name 
\*------------------------------------------------------------------------*/
  playerName = name;  
  persistSetString( "PlayerName", name );

/*------------------------------------------------------------------------*\
    Update timestamp and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  hmiNewConfig();
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );
}


/*=========================================================================*\
      Set Volume 
        returns effective volume (set to 0 if muted)
\*=========================================================================*/
double playerSetVolume( double volume, bool muted, bool broadcast )
{
  loginfo( "Setting Volume to %lf", volume );

/*------------------------------------------------------------------------*\
    Clip value 
\*------------------------------------------------------------------------*/
  if( volume<0 )
    volume = 0;
  if( volume>1 )
    volume = 1;

/*------------------------------------------------------------------------*\
    use internal interface to backend or codec
\*------------------------------------------------------------------------*/
  if( _playerSetVolume(playerVolume,playerMuted) )
    logwarn( "playerSetVolume: could not set volume to %.2lf%%%s", 
              playerVolume*100, playerMuted?" (muted)":"" ); 

/*------------------------------------------------------------------------*\
    Store new volume and muting state
\*------------------------------------------------------------------------*/
  playerVolume = volume;
  persistSetReal( "PlayerVolume", volume );
  playerMuted = muted;
  persistSetBool( "playerMuted", muted );


/*------------------------------------------------------------------------*\
    Update timestamp and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  hmiNewVolume( playerVolume, playerMuted );
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );

/*------------------------------------------------------------------------*\
    Return new volume 
\*------------------------------------------------------------------------*/
  return muted?0:playerVolume;
}


/*=========================================================================*\
      Set Volume 
        internal interface to audio backand or codec 
\*=========================================================================*/
static int _playerSetVolume( double volume, bool muted )
{
  int rc = 0;
  DBGMSG( "_playerSetVolume: %lf%s", volume, muted?" (muted)":"" );

/*------------------------------------------------------------------------*\
    Use backend
\*------------------------------------------------------------------------*/
  if( audioIf && audioIfSupportsVolume(audioIf) ) {
    if( audioIfSetVolume(audioIf,volume,muted) ) {
      logerr( "_playerSetVolume: could not set volume to %.2lf%%%s for backend \"%s\"", 
               volume*100, muted?" (muted)":"", audioIf->devName ); 
      rc = -1;
    }

  }

/*------------------------------------------------------------------------*\
    Use codec
\*------------------------------------------------------------------------*/
  else if( codecInstance ) {
    if( codecSetVolume(codecInstance,playerVolume,playerMuted) ) {
      logerr( "_playerSetVolume: could not set volume to %.2lf%%%s for codec %s", 
                volume*100, muted?" (muted)":"", codecInstance->codec->name );
      rc = -1;
    } 
  }

/*------------------------------------------------------------------------*\
    Return new volume 
\*------------------------------------------------------------------------*/
  return rc;
}


/*=========================================================================*\
      Set repeat mode
\*=========================================================================*/
int playerSetPlaybackMode( PlayerPlaybackMode mode, bool broadcast )
{
  bool playlistChanged = false;
  loginfo( "Setting playback mode to %d", mode );

/*------------------------------------------------------------------------*\
    Store new state
\*------------------------------------------------------------------------*/
  if( playerPlaybackMode==mode )
    return 0;

/*------------------------------------------------------------------------*\
    Store new state 
\*------------------------------------------------------------------------*/
  playerPlaybackMode = mode;
  persistSetInteger( "PlayerPlaybackMode", mode );

/*------------------------------------------------------------------------*\
    Modify mapping if necessary
\*------------------------------------------------------------------------*/
  switch( playerPlaybackMode ) {
    case PlaybackShuffle:
      if( playerQueue ) {
        int startPos = 0;
        int endPos   = playlistGetLength( playerQueue )-1;
        playlistShuffle( playerQueue, startPos, endPos, true );
        playlistChanged = true;
      }
      break;

    case PlaybackRepeatShuffle:
      break;

    case PlaybackQueue:
    case PlaybackRepeatQueue:
    case PlaybackRepeatItem:
    case PlaybackDynamic:
      if( playerQueue ) {
        playlistResetMapping( playerQueue, false );
        playlistChanged = true;
      }
      break;
  }

/*------------------------------------------------------------------------*\
    Update timestamp and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  hmiNewPlaybackMode( mode );
  if( playlistChanged )
    hmiNewQueue( playerQueue );
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );
  if( broadcast && playlistChanged )
    ickMessageNotifyPlaylist( NULL );

/*=========================================================================*\
      return new mode 
\*=========================================================================*/
  return 0;
}


/*=========================================================================*\
      Change playback state 
\*=========================================================================*/
int playerSetState( PlayerState state, bool broadcast )
{
  int           rc = 0;
  int           perr;
  PlaylistItem *newTrack;
  const char   *newTrackId;
  
  DBGMSG( "playerSetState: %s -> %s", playerStateToStr(playerState), playerStateToStr(state) );
  
/*------------------------------------------------------------------------*\
    Lock player, we don't want concurrent modifications going on...
\*------------------------------------------------------------------------*/
  perr = pthread_mutex_lock( &playerMutex );
  if( perr )
    logerr( "playerSetState: locking player mutex: %s", strerror(perr) );

/*------------------------------------------------------------------------*\
    Get current playback item to detect changes in the queue 
\*------------------------------------------------------------------------*/
  playlistLock( playerQueue );
  newTrack = playlistGetCursorItem( playerQueue );
  playlistUnlock( playerQueue );

/*------------------------------------------------------------------------*\
    Switch on target state 
\*------------------------------------------------------------------------*/
  switch( state ) {

/*------------------------------------------------------------------------*\
    Start or unpause playback
\*------------------------------------------------------------------------*/
    case PlayerStatePlay:

      // Is there a track to play ?
      if( !newTrack ) {
        lognotice( "playerSetState (start): Empty queue or no cursor." );
        rc = -1;
       break;
      }

      // Try to setup audio interface if not yet done
      if( !audioIf ) {
        const char         *device;
        const AudioBackend *backend = audioBackendByDeviceString( playerAudioDevice, &device );
        if( backend )
          audioIf = audioIfNew( backend, device, AudioFifoDefaultSize );
        if( !audioIf ) {
          logerr( "playerSetState (start): Could not open audio device \"%s\".", playerAudioDevice );
          rc = -1;
          break;
        }
        if( _playerSetVolume(playerVolume,playerMuted) )
          logwarn( "playerSetState (start): Could not set volume to %.2lf%% (%s).",
                   playerVolume*100, playerMuted?"muted":"unmuted" );
      }

      // Unpausing current track?
      playlistItemLock( newTrack );
      newTrackId = playlistItemGetId( newTrack );

      if( playerState==PlayerStatePause && currentTrackId && !strcmp(newTrackId,currentTrackId) ) {
        lognotice( "playerSetState (start): Unpausing current item \"%s\" (%s).",
                   playlistItemGetText(newTrack), playlistItemGetId(newTrack) );
        playlistItemUnlock( newTrack );
        playerState = PlayerStatePlay;
        rc = audioIfSetPause( audioIf, false );
        break;
      }
      playlistItemUnlock( newTrack );

     // Need to stop running track?
      if( playerState==PlayerStatePlay || playerState==PlayerStatePause ) {
        DBGMSG( "playerSetState (start): Request active playback thread to terminate." );
        if( !currentTrackId )
          logerr( "playerSetState (start): internal error (No current track)." );
        else
          lognotice( "playerSetState (start): Stopping current track (%s).", currentTrackId );
        if( playbackThreadState!=PlayerThreadRunning )
          logerr( "playerSetState (start): internal error: %s but no running playback thread.",
                  playerStateToStr(playerState) );
        else {
          playbackThreadState = PlayerThreadTerminating;
          pthread_join( playbackThread, NULL ); 
        }

        // Stop audio interface (if not done by _playbackThread() )
        if( !audioIf ) {
          logerr( "playerSetState (play): No audio interface to drop." );
        }
        else {
          DBGMSG( "playerSetState (play): Drop existing audio interface." );
          if( audioIfStop(audioIf,AudioDrop) )
            logerr( "playerSetState (play): Could not drop audio interface \"%s\".", audioIf->devName );
        }
      }

      // Create new playback thread
      rc = pthread_create( &playbackThread, NULL, _playbackThread, NULL );
      DBGMSG( "Starting new player thread." );
      if( rc ) {
        logerr( "playerSetState (start): Unable to start thread (%s).", strerror(rc) );
        rc = -1;
        break;
      }

      // Set new state
      playerState = PlayerStatePlay;
      break;

/*------------------------------------------------------------------------*\
    Pause playback
\*------------------------------------------------------------------------*/
    case PlayerStatePause:

     // not yet initialized?
      if( !audioIf ) {
        logerr( "playerSetState (pause): audio device not yet initialized." );
        rc = -1;
        break;
      } 

      // Is there a, feed->usrData track to play ?
      if( !newTrack ) {
        lognotice( "playerSetState (pause): Empty queue or no cursor" );
        rc = -1;
        break;
      }
      
      // if the track did not change: set pause flag
      playlistItemLock( newTrack );
      newTrackId = playlistItemGetId( newTrack );
      playlistItemUnlock( newTrack );
      if( currentTrackId && !strcmp(newTrackId,currentTrackId) ) {

        // Check for right state
        if( playerState==PlayerStateStop )
          logwarn( "playerSetState (pause): Cannot pause stopped playback." );
        else if( playerState==PlayerStatePause )
          logwarn( "playerSetState (pause): Pause already paused playback." );
        // Pause audio output and set state
        else {
          lognotice( "playerSetState (pause): Playback paused." );
          rc = audioIfSetPause( audioIf, true );
          if( !rc )
            playerState = PlayerStatePause;
        }
        break;
      }
      
      lognotice( "playerSetState (pause): Current track changed, stopping..." );
      // no break here, as we'll change the status to PlayerStateStop in case 
      // we were changing the track in paused state...


/*------------------------------------------------------------------------*\
    Stop playback
\*------------------------------------------------------------------------*/
    case PlayerStateStop:

      // Inform HMI in any case
      hmiNewPosition( 0.0 );
      /*
      playlistItemLock( newTrack );
      newTrackId = playlistItemGetId( newTrack );
      playlistItemUnlock( newTrack );
      if( newTrack && (!currentTrackId || strcmp(newTrackId,currentTrackId)) )
      */
      hmiNewQueue( playerQueue );

      // Stop audio interface
      if( !audioIf ) {
        DBGMSG( "playerSetState (stop): No audio interface to delete." );
      }
      else {
        DBGMSG( "playerSetState (stop): Drop existing audio interface." );
        if( audioIfStop(audioIf,AudioDrop) )
          logerr( "playerSetState (stop): Could not stop audio interface \"%s\".", audioIf->devName );
      }

      // request thread to stop playback and set new player state
      playbackThreadState = PlayerThreadTerminating;
      if( playerState==PlayerStatePlay || playerState==PlayerStatePause )
        pthread_join( playbackThread, NULL );
      playerState = PlayerStateStop;

      break; 

/*------------------------------------------------------------------------*\
    Unknown command
\*------------------------------------------------------------------------*/
    default:
      logerr( "playerSetState: Unknown target state %d.", state );
      rc = -1;
      break;
  }

/*------------------------------------------------------------------------*\
    Update timestamp, unlock player and broadcast new player state
\*------------------------------------------------------------------------*/
  lastChange = srvtime( );
  perr = pthread_mutex_unlock( &playerMutex );
  if( perr )
    logerr( "playerSetState: unlocking player mutex: %s", strerror(perr) );
  hmiNewState( playerState );
  if( broadcast )
    ickMessageNotifyPlayerState( NULL );

/*------------------------------------------------------------------------*\
    That's it 
\*------------------------------------------------------------------------*/
  return rc;
}


/*=========================================================================*\
      Player thread
\*=========================================================================*/
const char *playerStateToStr( PlayerState state )
{

/*------------------------------------------------------------------------*\
    Known player states
\*------------------------------------------------------------------------*/
  switch( state ) {
    case PlayerStateStop:  return "Stopped";
    case PlayerStatePlay:  return "Play";
    case PlayerStatePause: return "Paused";
    default: break;
  }

/*------------------------------------------------------------------------*\
    Handle unknown states
\*------------------------------------------------------------------------*/
  logerr( "playerStateToStr: unknown state %d", state );
  return "Unknown";
}

/*=========================================================================*\
      Player thread
\*=========================================================================*/
static void *_playbackThread( void *arg )
{
  PlaylistItem *item;
  AudioFormat   backendFormat;

  DBGMSG( "Player thread: starting." );
  PTHREADSETNAME( "player" );

/*------------------------------------------------------------------------*\
    Init encoding part of audio format from player default
\*------------------------------------------------------------------------*/
  memcpy( &backendFormat, &defaultAudioFormat, sizeof(AudioFormat) );
  backendFormat.sampleRate = -1;
  backendFormat.channels   = -1;

/*------------------------------------------------------------------------*\
    Loop over player queue 
\*------------------------------------------------------------------------*/
  playbackThreadState = PlayerThreadRunning;
  playlistLock( playerQueue );
  item = playlistGetCursorItem( playerQueue );
  playlistItemIncRef( item );
  playlistUnlock( playerQueue );
  while( item && playbackThreadState==PlayerThreadRunning ) {

    // Play item
    if( _playItem(item,&backendFormat) )
      playbackThreadState = PlayerThreadTerminatedError;

    // Error or stopped?
    if( playbackThreadState!=PlayerThreadRunning ) {
      playlistItemDecRef( item );
      break;
    }

    // Repeat track or reconnect stream?
    if( playerPlaybackMode==PlaybackRepeatItem ||
        playlistItemGetType(item)==PlaylistItemStream )
      continue;
    playlistItemDecRef( item );

    // Get next item
    playlistLock( playerQueue );
    item = playlistIncrCursorItem( playerQueue );
    playlistItemIncRef( item );
    playlistUnlock( playerQueue );
    if( item )
      continue;

    // repeat at end of list
    if( playerPlaybackMode==PlaybackRepeatQueue ) {
      playlistLock( playerQueue );
      item = playlistSetCursorPos( playerQueue, 0 );
      playlistItemIncRef( item );
      playlistUnlock( playerQueue );
    }

    // repeat at end of list with shuffling
    else if( playerPlaybackMode==PlaybackRepeatShuffle ) {
      playlistLock( playerQueue );
      playlistShuffle( playerQueue, 0, playlistGetLength(playerQueue)-1, false );
      item = playlistSetCursorPos( playerQueue, 0 );
      playlistItemIncRef( item );
      playlistUnlock( playerQueue );
      ickMessageNotifyPlaylist( NULL );
     }

  }  // End of: Thread main loop
  DBGMSG( "Player thread: End of playback loop (state %d).", playbackThreadState );

/*------------------------------------------------------------------------*\
    Stop audio interface in draining mode if at end of item list,
    else drop data
\*------------------------------------------------------------------------*/
  if( audioIfStop(audioIf,playbackThreadState==PlayerThreadRunning?AudioDrain:AudioDrop) )
    logerr( "Player thread: Could not stop audio interface \"%s\".", audioIf->devName );

/*------------------------------------------------------------------------*\
    Set and broadcast new player state
\*------------------------------------------------------------------------*/
  if( playbackThreadState==PlayerThreadRunning ) {
    lognotice( "_playerThread: End of queue." );
    playerState = PlayerStateStop;
    ickMessageNotifyPlayerState( NULL );
    hmiNewState( playerState );
    hmiNewPosition( 0.0 );
  }

/*------------------------------------------------------------------------*\
    Clean up, that's it ...
\*------------------------------------------------------------------------*/
  Sfree( currentTrackId );
{'fraction_non_alphanumeric': 0.4140032306578288, 'fraction_numerical': 0.001982955494903359, 'mean_word_length': 1.6210581639803785, 'pattern_counts': {'":': 18, '<': 39, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '296882', 'n_tokens_mistral': 20054, 'n_tokens_neox': 18822, 'n_words': 5213}