Rev 58 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* Copyright (C) 2015 by Andreas Theofilu <andreas@theosys.at>
*
* All rights reserved. No warranty, explicit or implicit, provided.
*
* NOTICE: All information contained herein is, and remains
* the property of Andreas Theofilu and his suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to Andreas Theofilu and its suppliers and
* may be covered by European and Foreign Patents, patents in process,
* and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Andreas Theofilu.
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <math.h>
#include <syslog.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>
#include <sqlite3.h>
#include <ao/ao.h>
#include <mpg123.h>
#include "config.h"
#include "mdb.h"
#include "helplib.h"
#include "play.h"
#include "play_flac.h"
#include "user.h"
#define CMD_SET_NONE 0
#define CMD_SET1 1
#define CMD_SET2 2
#define NOWPLAY "/nowplaying.list"
#define BITS 8
int nextCommand = PLAY_NONE;
int playStatus;
int playerActive = FALSE;
int playerRepeat = FALSE;
int playerRandom = FALSE;
int playQuiet = FALSE;
ST_PLAYING playCurrent;
QUEUE *pqueue = NULL;
QUEUE *qstack = NULL;
int queueTotal = 0;
char aoOutPlayers[15][16] =
{
"aixs",
"alsa",
"arts",
"esd",
"irix",
"macosx",
"nas",
"null",
"oss",
"pulse",
"roar",
"sndio",
"sun",
"wmm",
"\0"
};
/* Prototypes */
static int playCallback(void *hint, int argc, char **argv, char **azColName);
void playMP3(int s1, char *file);
int checkQueueDouble(int id);
void freeQueue();
void setCurrentQueue(QUEUE *q);
QUEUE *getRandomQueue();
QUEUE *getQueue(int idx);
void setCurrentQueue(QUEUE *q);
QUEUE *addToQueue(int id, char *path, int fType, char *title, char *artist, char *album, char *genre, char *cover);
/*
* This function is called as a thread.
* It uses the libmpg123 library to decode a MP3 file and the library libao
* to play the decoded samples.
* TODO: Implement a player for the formats OGG and WAV
* TODO: Implement an internet radio
*/
void *pthr_playfile(void *pV_data)
{
char fname[256], query[8192], hv0[256];
int rc, flag, start;
sqlite3 *db;
char *zErrMsg = 0;
struct ST_PlayPars *_playPars;
sqlite3_stmt *res;
if (playerActive)
{
syslog(LOG_DAEMON, "Internal error: Thread \"pthr_playfile\" was called although it is already running!");
pthread_exit(NULL);
}
playerActive = TRUE; /* Kind of locking :-) */
_playPars = (struct ST_PlayPars *)pV_data;
start = 0;
nextCommand = PLAY_NONE; /* Make sure that there is no command left */
if (_playPars == NULL)
{
syslog(LOG_DAEMON, "Internal error: Invalid or no parameters for thread \"pthr_playfile\"!");
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
/* retrieve the data from the database */
strcpy(fname, configs.home); /* Base file name */
strcat(fname, MUSICDB); /* File name of database */
rc = sqlite3_open(fname, &db);
if (rc)
{
syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
strcpy(query, "ERROR:PLAY:Error opening database;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
strcpy (query, "select id, path, type, title, interpret, album, genre, cover from \"main\".\"musicdb\" where ");
flag = FALSE;
if (strcasecmp(_playPars->type, "ID") == 0)
{
strcat(query, "id = ");
strcat(query, _playPars->what);
strcpy(fname, configs.home);
strcat(fname, NOWPLAY);
unlink(fname);
freeQueue();
flag = TRUE;
}
else if (strcasecmp(_playPars->type, "DIRECT") == 0)
{
if (configs.debug)
syslog(LOG_DEBUG, "pthr_playfile(): Playing ID %s direct.", _playPars->what);
strcat(query, "id = ");
strcat(query, _playPars->what);
flag = TRUE;
}
else if (strcasecmp(_playPars->type, "TITLE") == 0)
{
strcat(query, "title = \"");
strcat(query, _playPars->what);
strcat(query, "\" order by title");
flag = TRUE;
}
else if (strcasecmp(_playPars->type, "ARTIST") == 0)
{
strcat(query, "interpret = \"");
strcat(query, _playPars->what);
strcat(query, "\" order by interpret");
flag = TRUE;
}
else if (strcasecmp(_playPars->type, "ALBUM") == 0)
{
strcat(query, "album = \"");
strcat(query, _playPars->what);
strcat(query, "\" order by album");
flag = TRUE;
}
else if (strcasecmp(_playPars->type, "GENRE") == 0)
{
strcat(query, "genre = \"");
strcat(query, _playPars->what);
strcat(query, "\" order by genre");
flag = TRUE;
}
else if (strcasecmp(_playPars->type, "QUEUE") == 0)
{
strcpy(fname, configs.home); /* Base path */
strcat(fname, NOWPLAY); /* Append file name of queue to path */
if (access(fname, R_OK)) /* In case we've no access or the file doesn't exist, quit this thread. */
{
strcpy (hv0, "ERROR:PLAY:No or empty queue;");
write (_playPars->s1, hv0, strlen(hv0));
sqlite3_close(db);
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
start = atoi(_playPars->what);
}
else if (strcasecmp(_playPars->type, "PLAYLIST") == 0)
{
USERS *act;
if (userchain == NULL)
{
strcpy (hv0, "ERROR:PLAY:No user selected;");
write (_playPars->s1, hv0, strlen(hv0));
sqlite3_close(db);
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
/* find uid */
if ((act = findPlaylist(userchain->uname, _playPars->what)) != NULL)
{
if (!playlistToQueue(act->id, TRUE))
{
sqlite3_close(db);
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
}
else if (isnumeric(_playPars->what))
{
int id;
id = atoi(_playPars->what);
if (!playlistToQueue(id, TRUE))
{
sqlite3_close(db);
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
}
else
{
strcpy (hv0, "ERROR:PLAY:No playlist found;");
write (_playPars->s1, hv0, strlen(hv0));
sqlite3_close(db);
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
}
else
{
strcpy(hv0, "ERROR:PLAY:Missing type of argument;");
write(_playPars->s1, hv0, strlen(hv0));
sqlite3_close(db);
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
if (configs.debug)
{
syslog(LOG_DEBUG, "Player: SQL: %s", query);
syslog(LOG_DEBUG, "Player: FLAG: %d, TYPE: %s", flag, _playPars->type);
}
if (flag && strcasecmp(_playPars->type, "DIRECT") == 0)
{
char *p, path[1024], title[255], artist[255], album[255], genre[128], cover[255];
int id, type, err;
if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, sqlite3_errmsg(db));
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
err = 0;
memset(path, 0, sizeof(path));
memset(title, 0, sizeof(title));
memset(artist, 0, sizeof(artist));
memset(album, 0, sizeof(album));
memset(genre, 0, sizeof(genre));
memset(cover, 0, sizeof(cover));
rc = sqlite3_step(res);
id = sqlite3_column_int(res, 0);
if (configs.debug)
syslog(LOG_DEBUG, "Playing directly ID: %d", id);
if ((p = (char *)sqlite3_column_text(res, 1)) == NULL)
{
syslog(LOG_WARNING, "Error getting path from database!");
sqlite3_finalize(res);
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
else
strncpy(path, p, sizeof(path));
type = sqlite3_column_int(res, 2);
if ((p = (char *)sqlite3_column_text(res, 3)) == NULL)
{
syslog(LOG_WARNING, "Error getting title from database!");
sqlite3_finalize(res);
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
else
strncpy(title, p, sizeof(title));
if ((p = (char *)sqlite3_column_text(res, 4)) == NULL)
{
syslog(LOG_WARNING, "Error getting artist from database!");
sqlite3_finalize(res);
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
else
strncpy(artist, p, sizeof(artist));
if ((p = (char *)sqlite3_column_text(res, 5)) == NULL)
{
syslog(LOG_WARNING, "Error getting album from database!");
sqlite3_finalize(res);
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
else
strncpy(album, p, sizeof(album));
if ((p = (char *)sqlite3_column_text(res, 6)) == NULL)
{
syslog(LOG_WARNING, "Error getting genre from database!");
sqlite3_finalize(res);
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
else
strncpy(genre, p, sizeof(genre));
if ((p = (char *)sqlite3_column_text(res, 7)) != NULL)
strncpy(cover, p, sizeof(cover));
sqlite3_finalize(res);
sprintf(query, "PLAYING:%d:", id);
if ((p = urlencode(title)) != NULL)
{
strcat(query, p);
free(p);
}
strcat(query, ":");
if ((p = urlencode(artist)) != NULL)
{
strcat(query, p);
free(p);
}
strcat(query, ":");
if ((p = urlencode(album)) != NULL)
{
strcat(query, p);
free(p);
}
strcat(query, ":");
if ((p = urlencode(genre)) != NULL)
{
strcat(query, p);
free(p);
}
strcat(query, ":");
if (strlen(cover) != 0)
{
if ((p = urlencode(cover)) != NULL)
{
strcat(query, p);
free(p);
}
}
strcat(query, ";");
write(_playPars->s1, query, strlen(query));
while (strlen(path))
{
QUEUE *act;
if (configs.debug)
syslog(LOG_DEBUG, "Player: Trying to play file: %s", path);
if (type == FILE_TYPE_MP3)
playMP3(_playPars->s1, path);
else
playFlac(_playPars->s1, path);
path[0] = 0;
if (playStatus == PLAY_STATUS_STACK || playStatus == PLAY_STATUS_STPLAY)
{
act = qstackNext();
if (act)
{
playStatus = PLAY_STATUS_STPLAY;
nextCommand = PLAY_NONE;
strncpy(path, act->path, sizeof(path));
}
else
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_STOP;
}
}
}
sqlite3_close(db);
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
else if (flag && (rc = sqlite3_exec(db, query, playCallback, (void *)_playPars->what, &zErrMsg)) != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (_playPars->s1, query, strlen(query));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
sqlite3_close(db);
if (configs.debug)
syslog(LOG_DEBUG, "Playing from current queue starting at entry %d", start);
strcpy (fname, configs.home);
strcat (fname, NOWPLAY);
/* start the player and play the file(s) */
if (!access(fname, R_OK))
{
int fd, iflag, num, nextNum;
QUEUE *act;
if ((fd = open(fname, O_RDONLY)) < 0)
{
syslog(LOG_WARNING, "Error opening file %s: %s", fname, strerror(errno));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
iflag = FALSE;
num = 0;
nextNum = -1;
if (start > 0 && start <= queueTotal)
num = start - 1;
scanQueue(fd);
close(fd);
if (playerRandom && !flag && start <= 0)
act = getRandomQueue();
else
act = getQueue(num);
while (act)
{
char *title, *artist, *album, *cover;
if (nextNum >= 0)
{
if (num == nextNum)
nextNum = -1;
else
{
num++;
act = getQueue(num);
continue;
}
}
iflag = TRUE;
title = urlencode(act->title);
artist = urlencode(act->artist);
album = urlencode(act->album);
cover = urlencode(act->cover);
sprintf(query, "PLAYING:%d:", act->id);
if (title != NULL)
{
strcat(query, title);
free(title);
}
else
strcat(query, act->title);
strcat(query, ":");
if (artist != NULL)
{
strcat(query, artist);
free(artist);
}
else
strcat(query, act->artist);
strcat(query, ":");
if (album != NULL)
{
strcat(query, album);
free(album);
}
else
strcat(query, act->album);
strcat(query, ":");
strcat(query, act->genre);
strcat(query, ":");
if (cover != NULL)
{
strcat(query, cover);
free(cover);
}
else
strcat(query, act->cover);
strcat(query, ";");
write(_playPars->s1, query, strlen(query));
setCurrentQueue(act);
act->played = TRUE;
if (act->fType == FILE_TYPE_MP3)
playMP3(_playPars->s1, act->path);
else
playFlac(_playPars->s1, act->path);
if (configs.debug)
syslog(LOG_DEBUG, "Current play status: %d", playStatus);
if (playStatus == PLAY_STATUS_SKIPF)
playStatus = PLAY_STATUS_STOP;
else if (playStatus == PLAY_STATUS_SKIPR && !playerRandom)
{
if (num > 0)
{
nextNum = num - 1;
num = 0;
act = pqueue;
continue;
}
playStatus = PLAY_STATUS_STOP;
}
else if (playStatus == PLAY_STATUS_STACK || playStatus == PLAY_STATUS_STPLAY)
{
act = qstackNext();
if (act)
{
playStatus = PLAY_STATUS_STPLAY;
nextCommand = PLAY_NONE;
continue;
}
else
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_PLAY;
}
}
else if (playStatus == PLAY_STATUS_STOP)
break;
num++;
if (playerRandom)
act = getRandomQueue();
else
act = getQueue(num);
if (playerRepeat && act == NULL)
{
num = 0;
act = pqueue;
}
if (configs.debug)
syslog(LOG_DEBUG, "Do we have a next file to play? >>%s<<", (act == NULL) ? "NO" : "YES");
}
if (!iflag)
syslog(LOG_WARNING, "Found no files to play!");
close (fd);
}
else
syslog(LOG_WARNING, "Error accessing file %s: %s", fname, strerror(errno));
playerActive = FALSE;
pthread_exit(NULL);
return NULL;
}
static int playCallback(void *hint, int argc, char **argv, char **azColName)
{
int i, id, fd, fType;
char path[512], id3_title[256], id3_artist[256], id3_album[256], id3_genre[256], id3_cover[512];
char buffer[8192], *what;
what = (char *)hint;
memset(path, 0, sizeof(path));
memset(id3_title, 0, sizeof(id3_title));
memset(id3_artist, 0, sizeof(id3_artist));
memset(id3_album, 0, sizeof(id3_album));
memset(id3_genre, 0, sizeof(id3_genre));
memset(id3_cover, 0, sizeof(id3_cover));
id = -1;
for(i = 0; i < argc; i++)
{
if (strcasecmp(azColName[i], "id") == 0)
if (argv[i])
id = atoi(argv[i]);
if (strcasecmp(azColName[i], "path") == 0)
if (argv[i])
strncpy(path, argv[i], sizeof(path));
if (strcasecmp(azColName[i], "type") == 0)
if (argv[i])
fType = atoi(argv[i]);
if (strcasecmp(azColName[i], "title") == 0)
if (argv[i])
strncpy(id3_title, argv[i], sizeof(id3_title));
if (strcasecmp(azColName[i], "interpret") == 0)
if (argv[i])
strncpy(id3_artist, argv[i], sizeof(id3_artist));
if (strcasecmp(azColName[i], "album") == 0)
if (argv[i])
strncpy(id3_album, argv[i], sizeof(id3_album));
if (strcasecmp(azColName[i], "genre") == 0)
if (argv[i])
strncpy(id3_genre, argv[i], sizeof(id3_genre));
if (strcasecmp(azColName[i], "cover") == 0)
if (argv[i])
strncpy(id3_cover, argv[i], sizeof(id3_cover));
}
if (id != -1 && strlen(path) > 0)
{
char fname[512];
strcpy(fname, configs.home);
strcat(fname, NOWPLAY);
if (checkQueueDouble(id) == TRUE) /* Make sure every ID in the queue is unique */
return 0;
if ((fd = open(fname, O_RDWR | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP)) < 0)
{
syslog(LOG_WARNING, "Error creating or writing to file %s: %s", fname, strerror(errno));
return 1;
}
sprintf (buffer, "%s\t%d\t%d\t%s\t%s\t%s\t%s\t%s\n", path, fType, id, id3_title, id3_artist, id3_album, id3_genre, id3_cover);
write (fd, buffer, strlen(buffer));
addToQueue(id, path, fType, id3_title, id3_artist, id3_album, id3_genre, id3_cover);
close (fd);
}
else
syslog(LOG_WARNING, "Found no data to play");
return 0;
}
void playMP3(int s1, char *file)
{
mpg123_handle *mh;
unsigned char *buffer;
size_t buffer_size;
size_t done;
off_t frame;
int err, i, rc, flag;
char hv0[128];
int driver;
ao_device *dev;
ao_option ao_opts;
ao_sample_format format;
int channels, encoding;
long rate;
double current_seconds, seconds_left, act_sec;
off_t current_frame, frames_left;
if(file == NULL || strlen(file) == 0)
{
syslog(LOG_DAEMON, "No or invalid file was passed to player!");
return;
}
/* Check if we've a valid sound driver defined */
flag = FALSE;
i = 0;
while (aoOutPlayers[i][0])
{
if (!strcasecmp(aoOutPlayers[i], configs.player))
{
flag = TRUE;
if (configs.debug)
syslog(LOG_DEBUG, "Found sound driver %s to use for playback.", aoOutPlayers[i]);
break;
}
i++;
}
if (configs.debug)
syslog(LOG_DEBUG, "playMP3: Try to play file >>%s<<", file);
/* initializations */
ao_initialize();
if (flag)
{
if ((driver = ao_driver_id(configs.player)) == -1)
{
syslog(LOG_DAEMON, "Error finding the audio out driver %s!", configs.player);
ao_shutdown();
return;
}
}
else if ((driver = ao_default_driver_id()) == -1)
{
syslog(LOG_DAEMON, "Error finding a default audio driver!");
ao_shutdown();
return;
}
mpg123_init();
if (configs.debug)
syslog(LOG_DEBUG, "MPG123 initialized.");
if ((mh = mpg123_new(NULL, &err)) == NULL)
{
syslog(LOG_DAEMON, "Error creating new MP3 handle: %s", mpg123_plain_strerror(err));
ao_shutdown();
return;
}
if (configs.debug)
syslog(LOG_DEBUG, "MPG123 opened.");
buffer_size = mpg123_outblock(mh);
if ((buffer = (unsigned char*)malloc(buffer_size * sizeof(unsigned char))) == NULL)
{
syslog(LOG_DAEMON, "Error allocating memory for MP3 player: %s", strerror(errno));
mpg123_close(mh);
mpg123_delete(mh);
mpg123_exit();
ao_shutdown();
return;
}
/* open the file and get the decoding format */
if (mpg123_open(mh, file) != MPG123_OK)
{
syslog(LOG_DAEMON, "Error opening MP3 file %s: %s", file, mpg123_strerror(mh));
free(buffer);
mpg123_close(mh);
mpg123_delete(mh);
mpg123_exit();
ao_shutdown();
return;
}
if (mpg123_scan(mh) != MPG123_OK)
syslog(LOG_WARNING, "Error scanning MP3 file %s: %s", file, mpg123_strerror(mh));
if (mpg123_getformat(mh, &rate, &channels, &encoding) != MPG123_OK)
{
syslog(LOG_DAEMON, "Error getting the format of MP3 file %s: %s", file, mpg123_strerror(mh));
free(buffer);
mpg123_close(mh);
mpg123_delete(mh);
mpg123_exit();
ao_shutdown();
return;
}
/* set the output format and open the output device */
format.bits = mpg123_encsize(encoding) * BITS;
format.rate = rate;
format.channels = channels;
format.byte_format = AO_FMT_NATIVE;
format.matrix = 0;
if (configs.debug)
{
syslog(LOG_DEBUG, "Format: Bits: %d", format.bits);
syslog(LOG_DEBUG, "Format: Rate: %ld", rate);
syslog(LOG_DEBUG, "Format: Channels: %d", channels);
}
memset(&ao_opts, 0, sizeof(ao_option));
if (configs.debug)
{
syslog(LOG_DEBUG, "Switching on debugging for AO library.");
strcpy(hv0, "debug");
ao_opts.key = strdup(hv0);
strcpy(hv0, "1");
ao_opts.value = strdup(hv0);
dev = ao_open_live(driver, &format, &ao_opts);
syslog(LOG_DEBUG, "AO library opened.");
}
else
dev = ao_open_live(driver, &format, NULL);
if (dev == NULL)
{
switch (errno)
{
case AO_ENODRIVER: sprintf(hv0, "No driver corresponds to \"driver_id\"."); break;
case AO_ENOTLIVE: sprintf(hv0, "This driver (%s) is not a live output device.", configs.player); break;
case AO_EBADOPTION: sprintf(hv0, "A valid option key has an invalid value."); break;
case AO_EOPENDEVICE:sprintf(hv0, "Cannot open the device."); break;
default:
sprintf(hv0, "%s", strerror(errno));
}
syslog(LOG_DAEMON, "Error opening live playback device: %s", hv0);
free(buffer);
mpg123_close(mh);
mpg123_delete(mh);
mpg123_exit();
ao_shutdown();
if (configs.debug)
{
free(ao_opts.key);
free(ao_opts.value);
}
return;
}
if (playStatus != PLAY_STATUS_STPLAY)
playStatus = PLAY_STATUS_PLAY;
/* strcpy (hv0, "PLAYER:PLAY;");
write(s1, hv0, strlen(hv0)); */
handleWrite("PLAYER:PLAY;");
act_sec = 0.0;
flag = FALSE;
current_seconds = seconds_left = 0;
current_frame = frames_left = 0;
nextCommand = PLAY_NONE; /* Reset commands to be sure there is no lost command left */
frame = mpg123_tellframe(mh);
/* decode and play */
while ((rc = mpg123_read(mh, buffer, buffer_size, &done)) == MPG123_OK)
{
int todo;
char hv1[32], hv2[32], hv3[32];
todo = check_command(s1);
if (todo == PLAY_STATUS_STOP)
break;
else if (todo == PLAY_STATUS_FWD)
{
mpg123_seek_frame(mh, 100, SEEK_CUR);
if (playStatus != PLAY_STATUS_STPLAY)
playStatus = PLAY_STATUS_PLAY;
continue;
}
else if (todo == PLAY_STATUS_REW)
{
off_t fr;
fr = mpg123_tellframe(mh);
if (fr > 100)
fr -= 100;
else
fr = 0;
mpg123_seek_frame(mh, fr, SEEK_SET);
if (playStatus != PLAY_STATUS_STPLAY)
playStatus = PLAY_STATUS_PLAY;
continue;
}
/* get position */
if (!flag)
{
mpg123_position(mh, mpg123_tellframe(mh), done, ¤t_frame, &frames_left, ¤t_seconds, &seconds_left);
act_sec = current_seconds;
flag = TRUE;
}
else if (flag && frame != 0)
{
double cs, sl;
mpg123_position(mh, frame, done, ¤t_frame, &frames_left, &cs, &sl);
frame = mpg123_tellframe(mh) / 100;
act_sec = cs;
}
else if (frame == 0)
frame = mpg123_tellframe(mh) / 100;
if (!playQuiet)
{
sprintf(hv0, "POSITION:%s:%s:%s;", secondsToString(act_sec, &hv1[0]), secondsToString((current_seconds + seconds_left) - act_sec, &hv2[0]), secondsToString(current_seconds + seconds_left, &hv3[0]));
/* write(s1, hv0, strlen(hv0)); */
handleWrite(hv0);
}
ao_play(dev, (char *)buffer, done);
}
if (configs.debug && rc != MPG123_OK)
syslog(LOG_DEBUG, "MPG123 Error: %s", mpg123_plain_strerror(rc));
/* clean up */
/* strcpy(hv0, "PLAYER:STOP;");
write (s1, hv0, strlen(hv0)); */
handleWrite("PLAYER:STOP;");
free(buffer);
ao_close(dev);
mpg123_close(mh);
mpg123_delete(mh);
mpg123_exit();
if (configs.debug)
syslog(LOG_DEBUG, "MPG123 closed");
ao_shutdown();
if (configs.debug)
{
free(ao_opts.key);
free(ao_opts.value);
syslog(LOG_DEBUG, "AO library closed.");
}
}
int check_command(int s1)
{
/* char hv0[64]; */
if (nextCommand == PLAY_STOP_NOW)
{
playStatus = PLAY_STATUS_STOP;
return playStatus;
}
if (nextCommand == PLAY_STACK)
{
playStatus = PLAY_STATUS_STACK;
return PLAY_STATUS_STOP;
}
if (nextCommand == PLAY_PLAY && (playStatus == PLAY_STATUS_FWD || playStatus == PLAY_STATUS_REW))
{
playStatus = PLAY_STATUS_PLAY;
return playStatus;
}
if (nextCommand == PLAY_STOP && (playStatus == PLAY_STATUS_PLAY || playStatus == PLAY_STATUS_PAUSE))
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_STOP;
return playStatus;
}
else if (nextCommand == PLAY_STOP && playStatus == PLAY_STATUS_STPLAY)
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_PLAY;
return PLAY_STATUS_STOP;
}
if ((nextCommand == PLAY_PAUSE || nextCommand == PLAY_PLAYPAUSE) && playStatus == PLAY_STATUS_PLAY)
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_PAUSE;
handleWrite ("PLAYER:PAUSE;");
/* write (s1, hv0, strlen(hv0)); */
while (nextCommand != PLAY_PLAY && nextCommand != PLAY_PLAYPAUSE)
sleep(1);
playStatus = PLAY_STATUS_PLAY;
handleWrite ("PLAYER:PLAY;");
/* write (s1, hv0, strlen(hv0)); */
nextCommand = PLAY_NONE;
}
if (nextCommand == PLAY_FWD)
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_FWD;
return PLAY_STATUS_FWD;
}
if (nextCommand == PLAY_REW)
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_REW;
return PLAY_STATUS_REW;
}
if (nextCommand == PLAY_SKIP_FWD)
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_SKIPF;
return PLAY_STATUS_STOP;
}
if (nextCommand == PLAY_SKIP_REW)
{
nextCommand = PLAY_NONE;
playStatus = PLAY_STATUS_SKIPR;
return PLAY_STATUS_STOP;
}
return 0;
}
int checkQueueDouble(int id)
{
QUEUE *act;
act = pqueue;
while (act)
{
if (act->id == id)
return TRUE;
act = act->next;
}
return FALSE;
}
void appendToQueue(int s1, char *type, char *what)
{
char fname[256], query[1024];
int rc;
sqlite3 *db;
char *zErrMsg = 0;
/* retrieve the data from the database */
strcpy(fname, configs.home);
strcat(fname, MUSICDB);
rc = sqlite3_open(fname, &db);
if (rc)
{
syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
strcpy(query, "ERROR:PLAY:Error opening database;");
write (s1, query, strlen(query));
playerActive = FALSE;
return;
}
strcpy (query, "select id, path, type, title, interpret, album, genre, cover from \"main\".\"musicdb\" where ");
if (strcasecmp(type, "ID") == 0)
{
strcat(query, "id = ");
strcat(query, what);
}
else if (strcasecmp(type, "TITLE") == 0)
{
strcat(query, "title = \"");
strcat(query, what);
strcat(query, "\" order by title");
}
else if (strcasecmp(type, "ARTIST") == 0)
{
strcat(query, "interpret = \"");
strcat(query, what);
strcat(query, "\" order by interpret");
}
else if (strcasecmp(type, "ALBUM") == 0)
{
strcat(query, "album = \"");
strcat(query, what);
strcat(query, "\" order by album");
}
else if (strcasecmp(type, "GENRE") == 0)
{
strcat(query, "genre = \"");
strcat(query, what);
strcat(query, "\" order by genre");
}
else if (strcasecmp(type, "PLAYLIST") == 0)
{
USERS *act;
sqlite3_close(db);
act = userchain;
while (act)
{
if (strcmp(act->playlist, what) == 0)
{
playlistToQueue(act->id, FALSE);
break;
}
act = act->next;
}
if (act == NULL)
{
strcpy(query, "ERROR:PLAY:Error transfering playlist entries to queue;");
write (s1, query, strlen(query));
}
return;
}
if ((rc = sqlite3_exec(db, query, playCallback, (void *)what, &zErrMsg)) != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
strcpy(query, "ERROR:PLAY:SQL error;");
write (s1, query, strlen(query));
}
readQueue();
}
QUEUE *addToQueue(int id, char *path, int fType, char *title, char *artist, char *album, char *genre, char *cover)
{
QUEUE *act, *neu;
/* find the end of the chain */
act = pqueue;
while (act && act->next)
{
if (act->id == id) /* If the ID is already in the chain, return the pointer to it */
return act;
act = act->next;
}
/* Allocate memory for a new element in the chain */
if ((neu = (QUEUE *)malloc(sizeof(QUEUE))) == NULL)
{
syslog(LOG_DAEMON, "Error allocating %lu bytes for an entry in the queue: %s", sizeof(QUEUE), strerror(errno));
return NULL;
}
memset(neu, 0, sizeof(QUEUE));
/* Copy the data into the new element */
neu->id = id;
neu->fType = fType;
if (path)
strncpy (neu->path, path, sizeof(neu->path)-1);
if (title)
strncpy (neu->title, title, sizeof(neu->title)-1);
if (artist)
strncpy (neu->artist, artist, sizeof(neu->artist)-1);
if (album)
strncpy (neu->album, album, sizeof(neu->album)-1);
if (genre)
strncpy (neu->genre, genre, sizeof(neu->genre)-1);
if (cover)
strncpy (neu->cover, cover, sizeof(neu->cover)-1);
/* Append the element to the end of the chain */
if (act == NULL)
act = pqueue = neu;
else
act->next = neu;
return neu;
}
/*
* This function copies the content of a playlist into the queue.
* The parameter defines whether the current queue should be deleted or not.
* If the queue should not be deleted, the playlist will be appended.
*/
int playlistToQueue(int uid, int del)
{
char fname[256], query[1024], hv0[256], buffer[8192];
int rc;
sqlite3 *db;
sqlite3_stmt *res;
char path[512], id3_title[256], id3_artist[256], id3_album[256], id3_genre[256];
int id, fd, fType;
unsigned int mode;
strcpy(fname, configs.home);
strcat(fname, MUSICDB);
rc = sqlite3_open(fname, &db);
if (rc)
{
syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
return FALSE;
}
strcpy (query, "select id, path, type, title, interpret, album, genre from \"main\".\"musicdb\" as a where ");
strcat (query, "(select musicid from \"main\".\"playlists\" as b where a.id = b.musicid and b.userid = ");
sprintf(hv0, "%d", uid);
strcat (query, hv0);
strcat (query, ")");
if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
{
syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
sqlite3_close(db);
return FALSE;
}
/* Open the queue */
strcpy(fname, configs.home);
strcat(fname, NOWPLAY);
if (!del)
mode = O_RDWR | O_APPEND | O_CREAT;
else
{
mode = O_RDWR | O_TRUNC | O_CREAT;
if (playerActive)
nextCommand = PLAY_STOP_NOW;
}
if ((fd = open(fname, mode, S_IWUSR | S_IRUSR | S_IRGRP)) <= 0)
{
syslog(LOG_WARNING, "Error opening file %s: %s", fname, strerror(errno));
sqlite3_finalize(res);
sqlite3_close(db);
return FALSE;
}
while ((rc = sqlite3_step(res)) == SQLITE_ROW)
{
memset(path, 0, sizeof(path));
memset(id3_title, 0, sizeof(id3_title));
memset(id3_artist, 0, sizeof(id3_artist));
memset(id3_album, 0, sizeof(id3_album));
memset(id3_genre, 0, sizeof(id3_genre));
id = sqlite3_column_int(res, 0);
strncpy(path, (const char *)sqlite3_column_text(res, 1), sizeof(path));
fType = sqlite3_column_int(res, 2);
strncpy(id3_title, (const char *)sqlite3_column_text(res, 3), sizeof(id3_title));
strncpy(id3_artist, (const char *)sqlite3_column_text(res, 4), sizeof(id3_artist));
strncpy(id3_album, (const char *)sqlite3_column_text(res, 5), sizeof(id3_album));
strncpy(id3_genre, (const char *)sqlite3_column_text(res, 6), sizeof(id3_genre));
sprintf (buffer, "%s\t%d\t%d\t%s\t%s\t%s\t%s\n", path, fType, id, id3_title, id3_artist, id3_album, id3_genre);
write (fd, buffer, strlen(buffer));
}
scanQueue(fd);
close(fd);
sqlite3_finalize(res);
sqlite3_close(db);
return TRUE;
}
int QueueToPlaylist(int s1, char *user, char *playlist)
{
char fname[256], query[1024], hv0[256];
int rc;
sqlite3 *db;
char *zErrMsg = 0;
sqlite3_stmt *res;
int num;
USERS *usp;
QUEUE *act;
strcpy(fname, configs.home);
strcat(fname, MUSICDB);
rc = sqlite3_open(fname, &db);
if (rc)
{
syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
return FALSE;
}
if ((usp = findPlaylist(user, playlist)) != NULL)
{
sprintf(query, "delete from playlists where userid = %d", usp->id);
if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
{
syslog(LOG_DAEMON, "Error preparing SQL statement [%s]: %s", query, sqlite3_errmsg(db));
sqlite3_close(db);
return FALSE;
}
sqlite3_step(res);
}
else if (createUser(s1, user, playlist))
{
if ((usp = findPlaylist(user, playlist)) == NULL)
return FALSE;
}
else
return FALSE;
/* Update the queue in case there is none in memory*/
if (pqueue == NULL)
readQueue();
act = pqueue;
num = 0;
while (act)
{
strcpy (query, "insert into playlists (userid, musicid) values (");
sprintf(hv0, "%d", usp->id);
strcat (query, hv0);
strcat (query, ",");
sprintf(hv0, "%d", act->id);
strcat (query, hv0);
strcat (query, ")");
if ((rc = sqlite3_exec(db, query, NULL, NULL, &zErrMsg)) != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return FALSE;
}
num++;
act = act->next;
}
sqlite3_close(db);
sprintf(hv0, "TRANSFERED:%d;", num);
write (s1, hv0, strlen(hv0));
return TRUE;
}
void freeQueue()
{
QUEUE *act;
act = pqueue;
while (act)
{
pqueue = act->next;
free (act);
act = pqueue;
}
pqueue = NULL;
queueTotal = 0;
}
int readQueue()
{
int fd;
char fname[256];
strcpy (fname, configs.home);
strcat (fname, NOWPLAY);
/* start the player and play the file(s) */
if (!access(fname, R_OK))
{
if ((fd = open(fname, O_RDONLY)) < 0)
{
syslog(LOG_WARNING, "Error opening file %s: %s", fname, strerror(errno));
return FALSE;
}
scanQueue(fd);
close(fd);
}
else
return FALSE;
return TRUE;
}
int scanQueue(int fd)
{
QUEUE *act;
int count, x;
char buffer[8192], *t;
if (pqueue != NULL)
freeQueue();
count = 0;
lseek (fd, 0L, SEEK_SET);
while (readLine (fd, buffer, sizeof(buffer)) != NULL)
{
QUEUE *new;
if ((new = (QUEUE *)malloc(sizeof(QUEUE))) == NULL)
{
syslog(LOG_DAEMON, "Error allocating %lu bytes of memory: %s", sizeof(QUEUE), strerror(errno));
lseek(fd, 0L, SEEK_SET);
return -1;
}
memset(new, 0, sizeof(QUEUE));
if (count > 0)
act->next = new;
else
pqueue = new;
act = new;
x = 0;
t = strsplitt(buffer, '\t');
while (t)
{
switch (x)
{
case 0: strncpy(act->path, t, sizeof(act->path)-1); break;
case 1: act->fType = atoi(t); break;
case 2: act->id = atoi(t); break;
case 3: strncpy(act->title, t, sizeof(act->title)-1); break;
case 4: strncpy(act->artist, t, sizeof(act->artist)-1); break;
case 5: strncpy(act->album, t, sizeof(act->album)-1); break;
case 6: strncpy(act->genre, t, sizeof(act->genre)-1); break;
case 7: strncpy(act->cover, t, sizeof(act->cover)-1); break;
}
t = strsplitt(NULL, '\t');
x++;
}
count++;
}
queueTotal = count;
return count;
}
void setCurrentQueue(QUEUE *q)
{
if (q == NULL)
return;
memset(&playCurrent, 0, sizeof(ST_PLAYING));
playCurrent.id = q->id;
strncpy (playCurrent.title, q->title, sizeof(playCurrent.title));
strncpy (playCurrent.artist, q->artist, sizeof(playCurrent.artist));
strncpy (playCurrent.album, q->album, sizeof(playCurrent.album));
strncpy (playCurrent.genre, q->genre, sizeof(playCurrent.genre));
strncpy (playCurrent.cover, q->cover, sizeof(playCurrent.cover));
}
QUEUE *getQueue(int idx)
{
QUEUE *act;
int pos;
act = pqueue;
pos = 0;
while (act)
{
if (pos == idx)
return act;
pos++;
act = act->next;
}
return NULL;
}
QUEUE *getRandomQueue()
{
QUEUE *act;
int pos;
long r;
if (pqueue == NULL)
return NULL;
if (queueTotal > 1)
r = random_at_most((long)queueTotal-1);
else
{
if (pqueue->played)
return pqueue->next;
else
return pqueue;
}
pos = 0;
act = pqueue;
/* Scan queue and check wheter all entries are allready played */
while (act)
{
if (!act->played)
break;
act = act->next;
}
if (act == NULL) /* If all is played, we reset the status and start again */
{
act = pqueue;
while (act)
{
act->played = FALSE;
act = act->next;
}
}
act = pqueue;
while (act)
{
if (pos == r) /* have we reached the random position? */
{ /* Yes, then check if this entry was already played. */
if (act->played) /* Was it played? */
{ /* Yes, then reset and try again */
r = random_at_most((long)queueTotal - 1);
act = pqueue;
pos = 0;
continue;
}
else /* It was not played before so */
return act; /* return the pointer to it */
}
pos++;
act = act->next;
}
return NULL;
}
/*
* The following functions manage the STACK.
* The stack contains one or more files to play immediately. Even if a file
* of the normal queue is playing, the song is interrupted and the stack
* takes precedence. After the stack has been played or stopped by the user,
* the queue continues to play.
*
* Adding a file to the stack does not alter the queue in any way. The stack
* is a kind of second queue but with higher priority.
*/
QUEUE* qstackAdd(int ID)
{
QUEUE *q;
char query[1024], hv0[64], fname[512];
char *p;
sqlite3_stmt *res;
sqlite3 *db;
int rc;
if (ID < 0)
return NULL;
if ((q = (QUEUE *)malloc(sizeof(QUEUE))) == NULL)
{
syslog(LOG_DAEMON, "Error allocating memory for a stack entry: %s", strerror(errno));
return NULL;
}
memset(q, 0, sizeof(QUEUE));
q->id = ID;
if (qstack)
{
QUEUE *act;
act = qstack;
while (act->next)
act = act->next;
act->next = q;
}
else
qstack = q;
/* retrieve the data from the database */
strcpy(fname, configs.home); /* Base file name */
strcat(fname, MUSICDB); /* File name of database */
rc = sqlite3_open(fname, &db);
if (rc)
{
syslog(LOG_WARNING, "Error opening database %s: %s", fname, sqlite3_errmsg(db));
qstackDelete(ID);
return NULL;
}
strcpy (query, "select id, path, type, title, interpret, album, genre, cover from \"main\".\"musicdb\" where ");
strcat(query, "id = ");
sprintf(hv0, "%d", ID);
strcat(query, hv0);
if (sqlite3_prepare(db, query, -1, &res, NULL) != SQLITE_OK)
{
syslog(LOG_WARNING, "SQL error [%s]: %s", query, sqlite3_errmsg(db));
sqlite3_close(db);
qstackDelete(ID);
return NULL;
}
rc = sqlite3_step(res);
if ((p = (char *)sqlite3_column_text(res, 1)) == NULL)
{
syslog(LOG_WARNING, "Error getting path from database!");
sqlite3_finalize(res);
sqlite3_close(db);
qstackDelete(ID);
return NULL;
}
else
strncpy(q->path, p, sizeof(q->path));
q->fType = sqlite3_column_int(res, 2);
if ((p = (char *)sqlite3_column_text(res, 3)) == NULL)
{
syslog(LOG_WARNING, "Error getting title from database!");
sqlite3_finalize(res);
sqlite3_close(db);
qstackDelete(ID);
return NULL;
}
else
strncpy(q->title, p, sizeof(q->title));
if ((p = (char *)sqlite3_column_text(res, 4)) == NULL)
{
syslog(LOG_WARNING, "Error getting artist from database!");
sqlite3_finalize(res);
sqlite3_close(db);
qstackDelete(ID);
return NULL;
}
else
strncpy(q->artist, p, sizeof(q->artist));
if ((p = (char *)sqlite3_column_text(res, 5)) == NULL)
{
syslog(LOG_WARNING, "Error getting album from database!");
sqlite3_finalize(res);
sqlite3_close(db);
qstackDelete(ID);
return NULL;
}
else
strncpy(q->album, p, sizeof(q->album));
if ((p = (char *)sqlite3_column_text(res, 6)) == NULL)
{
syslog(LOG_WARNING, "Error getting genre from database!");
sqlite3_finalize(res);
sqlite3_close(db);
qstackDelete(ID);
return NULL;
}
else
strncpy(q->genre, p, sizeof(q->genre));
if ((p = (char *)sqlite3_column_text(res, 7)) != NULL)
strncpy(q->cover, p, sizeof(q->cover));
sqlite3_finalize(res);
sqlite3_close(db);
if (configs.debug)
syslog(LOG_DEBUG, "qstackAdd(): Added file >>%d:%s<< to stack.", q->id, q->path);
return q;
}
void qstackDelete(int ID)
{
QUEUE *q, *p;
int flag;
if (qstack == NULL)
return;
q = p = qstack;
flag = 0;
while (q)
{
QUEUE *act;
if (q->id == ID)
{
act = q;
q = q->next;
if (p != act)
p->next = q;
else
qstack = q; /* if the the first entry is to delete, let qstack point to the second entry, if there is one. */
free(q);
if (configs.debug)
syslog(LOG_DEBUG, "qstackDelete(): Deleted ID %d from stack.", ID);
return;
}
q = q->next;
if (flag)
p = p->next;
else
flag = 1;
}
}
void qstackClear()
{
QUEUE *act, *q;
act = qstack;
while (act)
{
q = act->next;
free(act);
act = q;
}
qstack = NULL;
}
QUEUE* qstackNext()
{
QUEUE *act, *p;
int flag;
if (qstack == NULL)
return NULL;
act = p = qstack;
flag = 0;
while (act->next)
{
if (flag)
p = p->next;
else
flag = 1;
act = act->next;
}
if (act != p)
p->next = NULL;
else
qstack = NULL;
if ((p = (QUEUE *)malloc(sizeof(QUEUE))) == NULL)
{
syslog(LOG_DAEMON, "Error allocating memory to get a stack entry: %s", strerror(errno));
free(act);
return NULL;
}
if (configs.debug)
syslog(LOG_DEBUG, "qstackNext(): Pulled file >>%d:%s<< from stack.", act->id, act->path);
memmove(p, act, sizeof(QUEUE));
free(act);
return p;
}