Merge branch 'inputs1' - new input modules

This commit is contained in:
ejurgensen 2017-01-29 13:16:58 +01:00
commit 0af75217e9
39 changed files with 2673 additions and 1426 deletions

View File

@ -110,7 +110,7 @@ FORK_FUNC_REQUIRE([COMMON], [GNU libunistring], [LIBUNISTRING], [unistring],
FORK_MODULES_CHECK([FORKED], [ZLIB], [zlib], [deflate], [zlib.h])
FORK_MODULES_CHECK([FORKED], [CONFUSE], [libconfuse], [cfg_init], [confuse.h])
FORK_MODULES_CHECK([FORKED], [MINIXML], [mxml], [mxmlNewElement], [mxml.h],
[AC_CHECK_FUNCS([mxmlGetOpaque])])
[AC_CHECK_FUNCS([mxmlGetOpaque] [mxmlGetText] [mxmlGetType] [mxmlGetFirstChild])])
dnl SQLite3 requires extra checks
FORK_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],

View File

@ -116,8 +116,9 @@ library {
# File types the scanner should ignore
# Non-audio files will never be added to the database, but here you
# can prevent the scanner from even probing them. This might improve
# scan time. By default .db, .ini, .db-journal and .pdf are ignored.
# filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf" }
# scan time. By default .db, .ini, .db-journal, .pdf and .metadata are
# ignored.
# filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf", ".metadata" }
# File paths the scanner should ignore
# If you want to exclude files on a more advanced basis you can enter
@ -153,6 +154,11 @@ library {
# no_decode = { "format", "format" }
# Formats that should always be decoded
# force_decode = { "format", "format" }
# Watch named pipes in the library for data and autostart playback when
# there is data to be read. To exclude specific pipes from watching,
# consider using the above _ignore options.
# pipe_autostart = true
}
# Local audio output

View File

@ -6,7 +6,7 @@ ITUNES_SRC=library/filescanner_itunes.c
endif
if COND_SPOTIFY
SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h
SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h inputs/spotify.c
endif
if COND_LASTFM
@ -100,7 +100,6 @@ forked_daapd_SOURCES = main.c \
http.c http.h \
dmap_common.c dmap_common.h \
transcode.c transcode.h \
pipe.c pipe.h \
artwork.c artwork.h \
misc.c misc.h \
rng.c rng.h \
@ -108,6 +107,8 @@ forked_daapd_SOURCES = main.c \
daap_query.c daap_query.h \
player.c player.h \
worker.c worker.h \
input.h input.c \
inputs/file_http.c inputs/pipe.c \
outputs.h outputs.c \
outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
@ -117,7 +118,7 @@ forked_daapd_SOURCES = main.c \
$(MPD_SRC) \
listener.c listener.h \
commands.c commands.h \
ffmpeg-compat.h \
ffmpeg-compat.h mxml-compat.h \
$(GPERF_SRC) \
$(ANTLR_SRC)

View File

@ -40,7 +40,6 @@
#include "logger.h"
#include "conffile.h"
#include "cache.h"
#include "player.h"
#include "http.h"
#include "avio_evbuffer.h"
@ -1105,6 +1104,7 @@ static int
source_item_stream_get(struct artwork_ctx *ctx)
{
struct http_client_ctx client;
struct db_queue_item *queue_item;
struct keyval *kv;
const char *content_type;
char *url;
@ -1116,9 +1116,15 @@ source_item_stream_get(struct artwork_ctx *ctx)
ret = ART_E_NONE;
url = player_get_icy_artwork_url(ctx->id);
if (!url)
return ART_E_NONE;
queue_item = db_queue_fetch_byfileid(ctx->id);
if (!queue_item || !queue_item->artwork_url)
{
free_queue_item(queue_item, 0);
return ART_E_NONE;
}
url = strdup(queue_item->artwork_url);
free_queue_item(queue_item, 0);
len = strlen(url);
if ((len < 14) || (len > PATH_MAX)) // Can't be shorter than http://a/1.jpg

View File

@ -42,6 +42,7 @@
#include "httpd_daap.h"
#include "db.h"
#include "cache.h"
#include "listener.h"
#include "commands.h"
@ -73,8 +74,8 @@ static pthread_t tid_cache;
// Event base, pipes and events
struct event_base *evbase_cache;
static struct event *g_cacheev;
static struct commands_base *cmdbase;
static struct event *cache_daap_updateev;
static int g_initialized;
@ -91,8 +92,6 @@ struct stash
uint8_t *data;
} g_stash;
// After being triggered wait 60 seconds before rebuilding cache
static struct timeval g_wait = { 60, 0 };
static int g_suspended;
// The user may configure a threshold (in msec), and queries slower than
@ -608,6 +607,7 @@ cache_daap_query_add(void *arg, int *retval)
#define Q_TMPL "INSERT OR REPLACE INTO queries (user_agent, query, msec, timestamp) VALUES ('%q', '%q', %d, %" PRIi64 ");"
#define Q_CLEANUP "DELETE FROM queries WHERE id NOT IN (SELECT id FROM queries ORDER BY timestamp DESC LIMIT 20);"
struct cache_arg *cmdarg;
struct timeval delay = { 60, 0 };
char *query;
char *errmsg;
int ret;
@ -663,7 +663,9 @@ cache_daap_query_add(void *arg, int *retval)
return COMMAND_END;
}
cache_daap_trigger();
// Will set of cache regeneration after waiting a bit (so there is less risk
// of disturbing the user)
evtimer_add(cache_daap_updateev, &delay);
*retval = 0;
return COMMAND_END;
@ -790,7 +792,13 @@ cache_daap_update_cb(int fd, short what, void *arg)
char *query;
int ret;
DPRINTF(E_INFO, L_CACHE, "Timeout reached, time to update DAAP cache\n");
if (g_suspended)
{
DPRINTF(E_DBG, L_CACHE, "Got a request to update DAAP cache while suspended\n");
return;
}
DPRINTF(E_LOG, L_CACHE, "Beginning DAAP cache update\n");
ret = sqlite3_exec(g_db_hdl, "DELETE FROM replies;", NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
@ -845,63 +853,28 @@ cache_daap_update_cb(int fd, short what, void *arg)
sqlite3_finalize(stmt);
DPRINTF(E_INFO, L_CACHE, "DAAP cache updated\n");
DPRINTF(E_LOG, L_CACHE, "DAAP cache updated\n");
}
/* This function will just set a timer, which when it times out will trigger
* the actual cache update. The purpose is to avoid avoid cache updates when
* the database is busy, eg during a library scan.
/* Sets off an update by activating the event. The delay is because we are low
* priority compared to other listeners of database updates.
*/
static enum command_state
cache_daap_update_timer(void *arg, int *ret)
cache_daap_update(void *arg, int *retval)
{
if (!g_cacheev)
{
*ret = -1;
return COMMAND_END;
}
evtimer_add(g_cacheev, &g_wait);
*ret = 0;
struct timeval delay = { 10, 0 };
*retval = event_add(cache_daap_updateev, &delay);
return COMMAND_END;
}
static enum command_state
cache_daap_suspend_timer(void *arg, int *ret)
/* Callback from filescanner thread */
static void
cache_daap_listener_cb(enum listener_event_type type)
{
if (!g_cacheev)
{
*ret = -1;
return COMMAND_END;
}
g_suspended = evtimer_pending(g_cacheev, NULL);
if (g_suspended)
evtimer_del(g_cacheev);
*ret = 0;
return COMMAND_END;
commands_exec_async(cmdbase, cache_daap_update, NULL);
}
static enum command_state
cache_daap_resume_timer(void *arg, int *ret)
{
if (!g_cacheev)
{
*ret = -1;
return COMMAND_END;
}
if (g_suspended)
evtimer_add(g_cacheev, &g_wait);
*ret = 0;
return COMMAND_END;
}
/*
* Updates cached timestamps to current time for all cache entries for the given path, if the file was not modfied
@ -1328,31 +1301,16 @@ cache(void *arg)
*
*/
void
cache_daap_trigger(void)
{
if (!g_initialized)
return;
commands_exec_async(cmdbase, cache_daap_update_timer, NULL);
}
void
cache_daap_suspend(void)
{
if (!g_initialized)
return;
commands_exec_async(cmdbase, cache_daap_suspend_timer, NULL);
g_suspended = 1;
}
void
cache_daap_resume(void)
{
if (!g_initialized)
return;
commands_exec_async(cmdbase, cache_daap_resume_timer, NULL);
g_suspended = 0;
}
int
@ -1377,15 +1335,13 @@ cache_daap_add(const char *query, const char *ua, int msec)
if (!g_initialized)
return;
cmdarg = (struct cache_arg *)malloc(sizeof(struct cache_arg));
cmdarg = calloc(1, sizeof(struct cache_arg));
if (!cmdarg)
{
DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_arg\n");
return;
}
memset(cmdarg, 0, sizeof(struct cache_arg));
cmdarg->query = strdup(query);
cmdarg->ua = strdup(ua);
cmdarg->msec = msec;
@ -1422,15 +1378,13 @@ cache_artwork_ping(const char *path, time_t mtime, int del)
if (!g_initialized)
return;
cmdarg = (struct cache_arg *)malloc(sizeof(struct cache_arg));
cmdarg = calloc(1, sizeof(struct cache_arg));
if (!cmdarg)
{
DPRINTF(E_LOG, L_CACHE, "Could not allocate cache_arg\n");
return;
}
memset(cmdarg, 0, sizeof(struct cache_arg));
cmdarg->path = strdup(path);
cmdarg->mtime = mtime;
cmdarg->del = del;
@ -1629,8 +1583,8 @@ cache_init(void)
goto evbase_fail;
}
g_cacheev = evtimer_new(evbase_cache, cache_daap_update_cb, NULL);
if (!g_cacheev)
cache_daap_updateev = evtimer_new(evbase_cache, cache_daap_update_cb, NULL);
if (!cache_daap_updateev)
{
DPRINTF(E_LOG, L_CACHE, "Could not create cache event\n");
goto evnew_fail;
@ -1638,6 +1592,13 @@ cache_init(void)
cmdbase = commands_base_new(evbase_cache, NULL);
ret = listener_add(cache_daap_listener_cb, LISTENER_DATABASE);
if (ret < 0)
{
DPRINTF(E_LOG, L_CACHE, "Could not create listener event\n");
goto listener_fail;
}
DPRINTF(E_INFO, L_CACHE, "cache thread init\n");
ret = pthread_create(&tid_cache, NULL, cache, NULL);
@ -1657,6 +1618,8 @@ cache_init(void)
return 0;
thread_fail:
listener_remove(cache_daap_listener_cb);
listener_fail:
commands_base_free(cmdbase);
evnew_fail:
event_base_free(evbase_cache);
@ -1675,6 +1638,9 @@ cache_deinit(void)
return;
g_initialized = 0;
listener_remove(cache_daap_listener_cb);
commands_base_destroy(cmdbase);
ret = pthread_join(tid_cache, NULL);

View File

@ -6,9 +6,6 @@
/* ---------------------------- DAAP cache API --------------------------- */
void
cache_daap_trigger(void);
void
cache_daap_suspend(void);

View File

@ -82,13 +82,14 @@ static cfg_opt_t sec_library[] =
CFG_STR("name_radio", "Radio", CFGF_NONE),
CFG_STR_LIST("artwork_basenames", "{artwork,cover,Folder}", CFGF_NONE),
CFG_BOOL("artwork_individual", cfg_false, CFGF_NONE),
CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf}", CFGF_NONE),
CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf,.metadata}", CFGF_NONE),
CFG_STR_LIST("filepath_ignore", NULL, CFGF_NONE),
CFG_BOOL("filescan_disable", cfg_false, CFGF_NONE),
CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE),
CFG_BOOL("itunes_smartpl", cfg_false, CFGF_NONE),
CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
CFG_STR_LIST("force_decode", NULL, CFGF_NONE),
CFG_BOOL("pipe_autostart", cfg_true, CFGF_NONE),
CFG_END()
};

249
src/db.c
View File

@ -41,6 +41,7 @@
#include "logger.h"
#include "cache.h"
#include "listener.h"
#include "library.h"
#include "misc.h"
#include "db.h"
#include "db_init.h"
@ -355,14 +356,12 @@ db_escape_string(const char *str)
void
free_pi(struct pairing_info *pi, int content_only)
{
if (pi->remote_id)
free(pi->remote_id);
if (!pi)
return;
if (pi->name)
free(pi->name);
if (pi->guid)
free(pi->guid);
free(pi->remote_id);
free(pi->name);
free(pi->guid);
if (!content_only)
free(pi);
@ -373,77 +372,33 @@ free_pi(struct pairing_info *pi, int content_only)
void
free_mfi(struct media_file_info *mfi, int content_only)
{
if (mfi->path)
free(mfi->path);
if (!mfi)
return;
if (mfi->fname)
free(mfi->fname);
if (mfi->title)
free(mfi->title);
if (mfi->artist)
free(mfi->artist);
if (mfi->album)
free(mfi->album);
if (mfi->genre)
free(mfi->genre);
if (mfi->comment)
free(mfi->comment);
if (mfi->type)
free(mfi->type);
if (mfi->composer)
free(mfi->composer);
if (mfi->orchestra)
free(mfi->orchestra);
if (mfi->conductor)
free(mfi->conductor);
if (mfi->grouping)
free(mfi->grouping);
if (mfi->description)
free(mfi->description);
if (mfi->codectype)
free(mfi->codectype);
if (mfi->album_artist)
free(mfi->album_artist);
if (mfi->tv_series_name)
free(mfi->tv_series_name);
if (mfi->tv_episode_num_str)
free(mfi->tv_episode_num_str);
if (mfi->tv_network_name)
free(mfi->tv_network_name);
if (mfi->title_sort)
free(mfi->title_sort);
if (mfi->artist_sort)
free(mfi->artist_sort);
if (mfi->album_sort)
free(mfi->album_sort);
if (mfi->composer_sort)
free(mfi->composer_sort);
if (mfi->album_artist_sort)
free(mfi->album_artist_sort);
if (mfi->virtual_path)
free(mfi->virtual_path);
free(mfi->path);
free(mfi->fname);
free(mfi->title);
free(mfi->artist);
free(mfi->album);
free(mfi->genre);
free(mfi->comment);
free(mfi->type);
free(mfi->composer);
free(mfi->orchestra);
free(mfi->conductor);
free(mfi->grouping);
free(mfi->description);
free(mfi->codectype);
free(mfi->album_artist);
free(mfi->tv_series_name);
free(mfi->tv_episode_num_str);
free(mfi->tv_network_name);
free(mfi->title_sort);
free(mfi->artist_sort);
free(mfi->album_sort);
free(mfi->composer_sort);
free(mfi->album_artist_sort);
free(mfi->virtual_path);
if (!content_only)
free(mfi);
@ -488,17 +443,13 @@ unicode_fixup_mfi(struct media_file_info *mfi)
void
free_pli(struct playlist_info *pli, int content_only)
{
if (pli->title)
free(pli->title);
if (!pli)
return;
if (pli->query)
free(pli->query);
if (pli->path)
free(pli->path);
if (pli->virtual_path)
free(pli->virtual_path);
free(pli->title);
free(pli->query);
free(pli->path);
free(pli->virtual_path);
if (!content_only)
free(pli);
@ -509,8 +460,10 @@ free_pli(struct playlist_info *pli, int content_only)
void
free_di(struct directory_info *di, int content_only)
{
if (di->virtual_path)
free(di->virtual_path);
if (!di)
return;
free(di->virtual_path);
if (!content_only)
free(di);
@ -1446,8 +1399,15 @@ db_query_end(struct query_params *qp)
qp->stmt = NULL;
}
/*
* Utility function for running write queries (INSERT, UPDATE, DELETE). If you
* set free to non-zero, the function will free the query. If you set
* library_update to non-zero it means that the update was not just of some
* internal value (like a timestamp), but of something that requires clients
* to update their cache of the library (and of course also of our own cache).
*/
static int
db_query_run(char *query, int free, int cache_update)
db_query_run(char *query, int free, int library_update)
{
char *errmsg;
int ret;
@ -1473,10 +1433,10 @@ db_query_run(char *query, int free, int cache_update)
if (free)
sqlite3_free(query);
if (cache_update)
cache_daap_trigger();
else
cache_daap_resume();
cache_daap_resume();
if (library_update)
library_update_trigger();
return ((ret != SQLITE_OK) ? -1 : 0);
}
@ -2404,7 +2364,7 @@ db_file_add(struct media_file_info *mfi)
sqlite3_free(query);
cache_daap_trigger();
library_update_trigger();
return 0;
@ -2483,7 +2443,7 @@ db_file_update(struct media_file_info *mfi)
sqlite3_free(query);
cache_daap_trigger();
library_update_trigger();
return 0;
@ -2491,7 +2451,7 @@ db_file_update(struct media_file_info *mfi)
}
void
db_file_save_seek(int id, uint32_t seek)
db_file_seek_update(int id, uint32_t seek)
{
#define Q_TMPL "UPDATE files SET seek = %d WHERE id = %d;"
char *query;
@ -3944,29 +3904,25 @@ db_speaker_clear_all(void)
void
free_queue_item(struct db_queue_item *queue_item, int content_only)
{
if (queue_item->path)
free(queue_item->path);
if (queue_item->virtual_path)
free(queue_item->virtual_path);
if (queue_item->title)
free(queue_item->title);
if (queue_item->artist)
free(queue_item->artist);
if (queue_item->album_artist)
free(queue_item->album_artist);
if (queue_item->album)
free(queue_item->album);
if (queue_item->genre)
free(queue_item->genre);
if (queue_item->artist_sort)
free(queue_item->artist_sort);
if (queue_item->album_sort)
free(queue_item->album_sort);
if (queue_item->album_artist_sort)
free(queue_item->album_artist_sort);
if (!queue_item)
return;
if (!content_only && queue_item)
free(queue_item->path);
free(queue_item->virtual_path);
free(queue_item->title);
free(queue_item->artist);
free(queue_item->album_artist);
free(queue_item->album);
free(queue_item->genre);
free(queue_item->artist_sort);
free(queue_item->album_sort);
free(queue_item->album_artist_sort);
free(queue_item->artwork_url);
if (!content_only)
free(queue_item);
else
memset(queue_item, 0, sizeof(struct db_queue_item));
}
/*
@ -3998,7 +3954,7 @@ db_queue_get_version()
}
/*
* Increments the version of the queue in the admin table and notifies listener of LISTENER_PLAYLIST
* Increments the version of the queue in the admin table and notifies listener of LISTENER_QUEUE
* about the change.
*
* This function must be called after successfully modifying the queue table in order to send
@ -4036,30 +3992,7 @@ queue_inc_version_and_notify()
db_transaction_end();
listener_notify(LISTENER_PLAYLIST);
}
void
db_queue_update_icymetadata(int id, char *artist, char *album)
{
#define Q_TMPL "UPDATE queue SET artist = TRIM(%Q), album = TRIM(%Q) WHERE id = %d;"
char *query;
if (id == 0)
return;
query = sqlite3_mprintf(Q_TMPL, artist, album, id);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return;
}
db_query_run(query, 1, 0);
queue_inc_version_and_notify();
#undef Q_TMPL
listener_notify(LISTENER_QUEUE);
}
static int
@ -4094,6 +4027,35 @@ queue_add_file(struct db_media_file_info *dbmfi, int pos, int shuffle_pos)
#undef Q_TMPL
}
int
db_queue_update_item(struct db_queue_item *qi)
{
#define Q_TMPL "UPDATE queue SET " \
"file_id = %d, song_length = %d, data_kind = %d, media_kind = %d, " \
"pos = %d, shuffle_pos = %d, path = '%q', virtual_path = %Q, " \
"title = %Q, artist = %Q, album_artist = %Q, album = %Q, " \
"genre = %Q, songalbumid = %" PRIi64 ", time_modified = %d, " \
"artist_sort = %Q, album_sort = %Q, album_artist_sort = %Q, " \
"year = %d, track = %d, disc = %d, artwork_url = %Q " \
"WHERE id = %d;"
char *query;
int ret;
query = sqlite3_mprintf(Q_TMPL,
qi->file_id, qi->song_length, qi->data_kind, qi->media_kind,
qi->pos, qi->shuffle_pos, qi->path, qi->virtual_path,
qi->title, qi->artist, qi->album_artist, qi->album,
qi->genre, qi->songalbumid, qi->time_modified,
qi->artist_sort, qi->album_sort, qi->album_artist_sort,
qi->year, qi->track, qi->disc, qi->artwork_url,
qi->id);
ret = db_query_run(query, 1, 0);
return ret;
#undef Q_TMPL
}
/*
* Adds the files matching the given query to the queue after the item with the given item id
*
@ -4426,6 +4388,7 @@ queue_enum_fetch(struct query_params *query_params, struct db_queue_item *queue_
queue_item->year = sqlite3_column_int(query_params->stmt, 19);
queue_item->track = sqlite3_column_int(query_params->stmt, 20);
queue_item->disc = sqlite3_column_int(query_params->stmt, 21);
queue_item->artwork_url = strdup_if((char *)sqlite3_column_text(query_params->stmt, 22), keep_item);
return 0;
}

View File

@ -418,6 +418,8 @@ struct db_queue_item
uint32_t year;
uint32_t track;
uint32_t disc;
char *artwork_url;
};
char *
@ -542,7 +544,7 @@ int
db_file_update(struct media_file_info *mfi);
void
db_file_save_seek(int id, uint32_t seek);
db_file_seek_update(int id, uint32_t seek);
void
db_file_delete_bypath(char *path);
@ -687,8 +689,8 @@ db_speaker_clear_all(void);
int
db_queue_get_version();
void
db_queue_update_icymetadata(int id, char *artist, char *album);
int
db_queue_update_item(struct db_queue_item *queue_item);
int
db_queue_add_by_queryafteritemid(struct query_params *qp, uint32_t item_id);

View File

@ -1,3 +1,4 @@
/*
* Copyright (C) 2009-2011 Julien BLACHE <jb@jblache.org>
* Copyright (C) 2010 Kai Elwert <elwertk@googlemail.com>
@ -182,6 +183,7 @@
" year INTEGER DEFAULT 0," \
" track INTEGER DEFAULT 0," \
" disc INTEGER DEFAULT 0" \
" artwork_url VARCHAR(4096) DEFAULT NULL," \
");"
#define TRG_GROUPS_INSERT_FILES \

View File

@ -26,7 +26,7 @@
* is a major upgrade. In other words minor version upgrades permit downgrading
* forked-daapd after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 19
#define SCHEMA_VERSION_MINOR 02
#define SCHEMA_VERSION_MINOR 03
int
db_init_indices(sqlite3 *hdl);

View File

@ -1529,6 +1529,24 @@ static const struct db_upgrade_query db_upgrade_v1902_queries[] =
{ U_V1902_SCVER_MINOR, "set schema_version_minor to 02" },
};
#define U_V1903_ALTER_QUEUE_ADD_ARTWORKURL \
"ALTER TABLE queue ADD COLUMN artwork_url VARCHAR(4096) DEFAULT NULL;"
#define U_V1903_SCVER_MAJOR \
"UPDATE admin SET value = '19' WHERE key = 'schema_version_major';"
#define U_V1903_SCVER_MINOR \
"UPDATE admin SET value = '03' WHERE key = 'schema_version_minor';"
static const struct db_upgrade_query db_upgrade_v1903_queries[] =
{
{ U_V1903_ALTER_QUEUE_ADD_ARTWORKURL, "alter table queue add column artwork_url" },
{ U_V1903_SCVER_MAJOR, "set schema_version_major to 19" },
{ U_V1903_SCVER_MINOR, "set schema_version_minor to 03" },
};
int
db_upgrade(sqlite3 *hdl, int db_ver)
{
@ -1651,6 +1669,11 @@ db_upgrade(sqlite3 *hdl, int db_ver)
if (ret < 0)
return -1;
case 1902:
ret = db_generic_upgrade(hdl, db_upgrade_v1903_queries, sizeof(db_upgrade_v1903_queries) / sizeof(db_upgrade_v1903_queries[0]));
if (ret < 0)
return -1;
break;
default:

View File

@ -31,7 +31,7 @@
#endif
#ifndef HAVE_AV_PACKET_RESCALE_TS
static void
__attribute__((unused)) static void
av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
{
if (pkt->pts != AV_NOPTS_VALUE)
@ -48,7 +48,7 @@ av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
#ifndef HAVE_AVFORMAT_ALLOC_OUTPUT_CONTEXT2
# include <libavutil/opt.h>
static int
__attribute__((unused)) static int
avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename)
{
AVFormatContext *s = avformat_alloc_context();

578
src/input.c Normal file
View File

@ -0,0 +1,578 @@
/*
* Copyright (C) 2017 Espen Jürgensen <espenjurgensen@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
# include <pthread_np.h>
#endif
#include "misc.h"
#include "logger.h"
#include "input.h"
// Disallow further writes to the buffer when its size is larger than this threshold
#define INPUT_BUFFER_THRESHOLD STOB(44100)
// How long (in sec) to wait for player read before looping in playback thread
#define INPUT_LOOP_TIMEOUT 1
#define DEBUG 1 //TODO disable
extern struct input_definition input_file;
extern struct input_definition input_http;
extern struct input_definition input_pipe;
#ifdef HAVE_SPOTIFY_H
extern struct input_definition input_spotify;
#endif
// Must be in sync with enum input_types
static struct input_definition *inputs[] = {
&input_file,
&input_http,
&input_pipe,
#ifdef HAVE_SPOTIFY_H
&input_spotify,
#endif
NULL
};
struct input_buffer
{
// Raw pcm stream data
struct evbuffer *evbuf;
// If non-zero, remaining length of buffer until EOF
size_t eof;
// If non-zero, remaining length of buffer until (possible) new metadata
size_t metadata;
// Optional callback to player if buffer is full
input_cb full_cb;
// Locks for sharing the buffer between input and player thread
pthread_mutex_t mutex;
pthread_cond_t cond;
};
/* --- Globals --- */
// Input thread
static pthread_t tid_input;
// Input buffer
static struct input_buffer input_buffer;
// Timeout waiting in playback loop
static struct timespec input_loop_timeout = { INPUT_LOOP_TIMEOUT, 0 };
#ifdef DEBUG
static size_t debug_elapsed;
#endif
/* ------------------------------ MISC HELPERS ---------------------------- */
static short
flags_set(size_t len)
{
short flags = 0;
if (input_buffer.eof)
{
if (len >= input_buffer.eof)
{
flags |= INPUT_FLAG_EOF;
input_buffer.eof = 0;
}
else
input_buffer.eof -= len;
}
if (input_buffer.metadata)
{
if (len >= input_buffer.metadata)
{
flags |= INPUT_FLAG_METADATA;
input_buffer.metadata = 0;
}
else
input_buffer.metadata -= len;
}
return flags;
}
static int
map_data_kind(int data_kind)
{
switch (data_kind)
{
case DATA_KIND_FILE:
return INPUT_TYPE_FILE;
case DATA_KIND_HTTP:
return INPUT_TYPE_HTTP;
case DATA_KIND_PIPE:
return INPUT_TYPE_PIPE;
#ifdef HAVE_SPOTIFY_H
case DATA_KIND_SPOTIFY:
return INPUT_TYPE_SPOTIFY;
#endif
default:
return -1;
}
}
static int
source_check_and_map(struct player_source *ps, const char *action, char check_setup)
{
int type;
#ifdef DEBUG
DPRINTF(E_DBG, L_PLAYER, "Action is %s\n", action);
#endif
if (!ps)
{
DPRINTF(E_LOG, L_PLAYER, "Stream %s called with invalid player source\n", action);
return -1;
}
if (check_setup && !ps->setup_done)
{
DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, %s not possible\n", action);
return -1;
}
type = map_data_kind(ps->data_kind);
if (type < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Unsupported input type, %s not possible\n", action);
return -1;
}
return type;
}
/* ----------------------------- PLAYBACK LOOP ---------------------------- */
/* Thread: input */
// TODO Thread safety of ps? Do we need the return of the loop?
static void *
playback(void *arg)
{
struct player_source *ps = arg;
int type;
type = source_check_and_map(ps, "start", 1);
if ((type < 0) || (inputs[type]->disabled))
goto thread_exit;
// Loops until input_loop_break is set or no more input, e.g. EOF
inputs[type]->start(ps);
#ifdef DEBUG
DPRINTF(E_DBG, L_PLAYER, "Playback loop stopped (break is %d)\n", input_loop_break);
#endif
thread_exit:
pthread_exit(NULL);
}
void
input_wait(void)
{
struct timespec ts;
pthread_mutex_lock(&input_buffer.mutex);
ts = timespec_reltoabs(input_loop_timeout);
pthread_cond_timedwait(&input_buffer.cond, &input_buffer.mutex, &ts);
pthread_mutex_unlock(&input_buffer.mutex);
}
// Called by input modules from within the playback loop
int
input_write(struct evbuffer *evbuf, short flags)
{
struct timespec ts;
int ret;
pthread_mutex_lock(&input_buffer.mutex);
while ( (!input_loop_break) && (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) )
{
if (input_buffer.full_cb)
{
input_buffer.full_cb();
input_buffer.full_cb = NULL;
}
if (flags & INPUT_FLAG_NONBLOCK)
{
pthread_mutex_unlock(&input_buffer.mutex);
return EAGAIN;
}
ts = timespec_reltoabs(input_loop_timeout);
pthread_cond_timedwait(&input_buffer.cond, &input_buffer.mutex, &ts);
}
if (!input_loop_break)
{
ret = evbuffer_add_buffer(input_buffer.evbuf, evbuf);
if (ret < 0)
DPRINTF(E_LOG, L_PLAYER, "Error adding stream data to input buffer\n");
if (!input_buffer.eof && (flags & INPUT_FLAG_EOF))
input_buffer.eof = evbuffer_get_length(input_buffer.evbuf);
if (!input_buffer.metadata && (flags & INPUT_FLAG_METADATA))
input_buffer.metadata = evbuffer_get_length(input_buffer.evbuf);
}
else
ret = 0;
pthread_mutex_unlock(&input_buffer.mutex);
return ret;
}
/* -------------------- Interface towards player thread ------------------- */
/* Thread: player */
int
input_read(void *data, size_t size, short *flags)
{
int len;
*flags = 0;
if (!tid_input)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! Read called, but playback not running\n");
return -1;
}
pthread_mutex_lock(&input_buffer.mutex);
#ifdef DEBUG
debug_elapsed += size;
if (debug_elapsed > STOB(441000)) // 10 sec
{
DPRINTF(E_DBG, L_PLAYER, "Input buffer has %zu bytes\n", evbuffer_get_length(input_buffer.evbuf));
debug_elapsed = 0;
}
#endif
len = evbuffer_remove(input_buffer.evbuf, data, size);
if (len < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Error reading stream data from input buffer\n");
goto out_unlock;
}
*flags = flags_set(len);
out_unlock:
pthread_cond_signal(&input_buffer.cond);
pthread_mutex_unlock(&input_buffer.mutex);
return len;
}
void
input_buffer_full_cb(input_cb cb)
{
pthread_mutex_lock(&input_buffer.mutex);
input_buffer.full_cb = cb;
pthread_mutex_unlock(&input_buffer.mutex);
}
int
input_setup(struct player_source *ps)
{
int type;
type = source_check_and_map(ps, "setup", 0);
if ((type < 0) || (inputs[type]->disabled))
return -1;
if (!inputs[type]->setup)
return 0;
return inputs[type]->setup(ps);
}
int
input_start(struct player_source *ps)
{
int ret;
if (tid_input)
{
DPRINTF(E_WARN, L_PLAYER, "Input start called, but playback already running\n");
return 0;
}
input_loop_break = 0;
ret = pthread_create(&tid_input, NULL, playback, ps);
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not spawn input thread: %s\n", strerror(errno));
return -1;
}
#if defined(HAVE_PTHREAD_SETNAME_NP)
pthread_setname_np(tid_input, "input");
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
pthread_set_name_np(tid_input, "input");
#endif
return 0;
}
int
input_pause(struct player_source *ps)
{
short flags;
int ret;
#ifdef DEBUG
DPRINTF(E_DBG, L_PLAYER, "Pause called, stopping playback loop\n");
#endif
if (!tid_input)
return -1;
pthread_mutex_lock(&input_buffer.mutex);
input_loop_break = 1;
pthread_cond_signal(&input_buffer.cond);
pthread_mutex_unlock(&input_buffer.mutex);
// TODO What if input thread is hanging waiting for source? Kill thread?
ret = pthread_join(tid_input, NULL);
if (ret != 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not join input thread: %s\n", strerror(errno));
return -1;
}
tid_input = 0;
input_flush(&flags);
return 0;
}
int
input_stop(struct player_source *ps)
{
int type;
if (tid_input)
input_pause(ps);
if (!ps)
return 0;
type = source_check_and_map(ps, "stop", 1);
if ((type < 0) || (inputs[type]->disabled))
return -1;
if (!inputs[type]->stop)
return 0;
return inputs[type]->stop(ps);
}
int
input_seek(struct player_source *ps, int seek_ms)
{
int type;
type = source_check_and_map(ps, "seek", 1);
if ((type < 0) || (inputs[type]->disabled))
return -1;
if (!inputs[type]->seek)
return 0;
if (tid_input)
input_pause(ps);
return inputs[type]->seek(ps, seek_ms);
}
void
input_flush(short *flags)
{
size_t len;
pthread_mutex_lock(&input_buffer.mutex);
len = evbuffer_get_length(input_buffer.evbuf);
evbuffer_drain(input_buffer.evbuf, len);
*flags = flags_set(len);
input_buffer.eof = 0;
input_buffer.metadata = 0;
input_buffer.full_cb = NULL;
pthread_mutex_unlock(&input_buffer.mutex);
#ifdef DEBUG
DPRINTF(E_DBG, L_PLAYER, "Flush with flags %d\n", *flags);
#endif
}
int
input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime)
{
int type;
if (!metadata || !ps || !ps->stream_start || !ps->output_start)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! Unhandled case in input_metadata_get()\n");
return -1;
}
memset(metadata, 0, sizeof(struct input_metadata));
metadata->item_id = ps->item_id;
metadata->startup = startup;
metadata->offset = ps->output_start - ps->stream_start;
metadata->rtptime = ps->stream_start;
// Note that the source may overwrite the above progress metadata
type = source_check_and_map(ps, "metadata_get", 1);
if ((type < 0) || (inputs[type]->disabled))
return -1;
if (!inputs[type]->metadata_get)
return 0;
return inputs[type]->metadata_get(metadata, ps, rtptime);
}
void
input_metadata_free(struct input_metadata *metadata, int content_only)
{
free(metadata->artist);
free(metadata->title);
free(metadata->album);
free(metadata->genre);
free(metadata->artwork_url);
if (!content_only)
free(metadata);
else
memset(metadata, 0, sizeof(struct input_metadata));
}
int
input_init(void)
{
int no_input;
int ret;
int i;
// Prepare input buffer
pthread_mutex_init(&input_buffer.mutex, NULL);
pthread_cond_init(&input_buffer.cond, NULL);
input_buffer.evbuf = evbuffer_new();
if (!input_buffer.evbuf)
{
DPRINTF(E_LOG, L_PLAYER, "Out of memory for input buffer\n");
return -1;
}
no_input = 1;
for (i = 0; inputs[i]; i++)
{
if (inputs[i]->type != i)
{
DPRINTF(E_FATAL, L_PLAYER, "BUG! Input definitions are misaligned with input enum\n");
return -1;
}
if (!inputs[i]->init)
{
no_input = 0;
continue;
}
ret = inputs[i]->init();
if (ret < 0)
inputs[i]->disabled = 1;
else
no_input = 0;
}
if (no_input)
return -1;
return 0;
}
void
input_deinit(void)
{
int i;
input_stop(NULL);
for (i = 0; inputs[i]; i++)
{
if (inputs[i]->disabled)
continue;
if (inputs[i]->deinit)
inputs[i]->deinit();
}
pthread_cond_destroy(&input_buffer.cond);
pthread_mutex_destroy(&input_buffer.mutex);
evbuffer_free(input_buffer.evbuf);
}

241
src/input.h Normal file
View File

@ -0,0 +1,241 @@
#ifndef __INPUT_H__
#define __INPUT_H__
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <event2/buffer.h>
#include "transcode.h"
// Must be in sync with inputs[] in input.c
enum input_types
{
INPUT_TYPE_FILE,
INPUT_TYPE_HTTP,
INPUT_TYPE_PIPE,
#ifdef HAVE_SPOTIFY_H
INPUT_TYPE_SPOTIFY,
#endif
};
enum input_flags
{
// Write to input buffer must not block
INPUT_FLAG_NONBLOCK = (1 << 0),
// Flags end of file
INPUT_FLAG_EOF = (1 << 1),
// Flags possible new stream metadata
INPUT_FLAG_METADATA = (1 << 2),
};
struct player_source
{
/* Id of the file/item in the files database */
uint32_t id;
/* Item-Id of the file/item in the queue */
uint32_t item_id;
/* Length of the file/item in milliseconds */
uint32_t len_ms;
enum data_kind data_kind;
enum media_kind media_kind;
char *path;
/* Start time of the media item as rtp-time
The stream-start is the rtp-time the media item did or would have
started playing (after seek or pause), therefor the elapsed time of the
media item is always:
elapsed time = current rtptime - stream-start */
uint64_t stream_start;
/* Output start time of the media item as rtp-time
The output start time is the rtp-time of the first audio packet send
to the audio outputs.
It differs from stream-start especially after a seek, where the first audio
packet has the next rtp-time as output start and stream start becomes the
rtp-time the media item would have been started playing if the seek did
not happen. */
uint64_t output_start;
/* End time of media item as rtp-time
The end time is set if the reading (source_read) of the media item reached
end of file, until then it is 0. */
uint64_t end;
struct transcode_ctx *xcode;
int setup_done;
struct player_source *play_next;
};
typedef int (*input_cb)(void);
struct input_metadata
{
uint32_t item_id;
int startup;
uint64_t rtptime;
uint64_t offset;
// The player will update queue_item with the below
uint32_t song_length;
char *artist;
char *title;
char *album;
char *genre;
char *artwork_url;
};
struct input_definition
{
// Name of the input
const char *name;
// Type of input
enum input_types type;
// Set to 1 if the input initialization failed
char disabled;
// Prepare a playback session
int (*setup)(struct player_source *ps);
// Starts playback loop (must be defined)
int (*start)(struct player_source *ps);
// Cleans up when playback loop has ended
int (*stop)(struct player_source *ps);
// Changes the playback position
int (*seek)(struct player_source *ps, int seek_ms);
// Return metadata
int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime);
// Initialization function called during startup
int (*init)(void);
// Deinitialization function called at shutdown
void (*deinit)(void);
};
/*
* Input modules should use this to test if playback should end
*/
int input_loop_break;
/*
* Transfer stream data to the player's input buffer. The input evbuf will be
* drained on succesful write. This is to avoid copying memory. If the player's
* input buffer is full the function will block until the write can be made
* (unless INPUT_FILE_NONBLOCK is set).
*
* @in evbuf Raw audio data to write
* @in flags One or more INPUT_FLAG_*
* @return 0 on success, EAGAIN if buffer was full (and _NONBLOCK is set),
* -1 on error
*/
int
input_write(struct evbuffer *evbuf, short flags);
/*
* Input modules can use this to wait in the playback loop (like input_write()
* would have done)
*/
void
input_wait(void);
/*
* Move a chunk of stream data from the player's input buffer to an output
* buffer. Should only be called by the player thread. Will not block.
*
* @in data Output buffer
* @in size How much data to move to the output buffer
* @out flags Flags INPUT_FLAG_EOF or INPUT_FLAG_METADATA
* @return Number of bytes moved, -1 on error
*/
int
input_read(void *data, size_t size, short *flags);
/*
* Player can set this to get a callback from the input when the input buffer
* is full. The player may use this to resume playback after an underrun.
*
* @in cb The callback
*/
void
input_buffer_full_cb(input_cb cb);
/*
* Initializes the given player source for playback
*/
int
input_setup(struct player_source *ps);
/*
* Tells the input to start or resume playback, i.e. after calling this function
* the input buffer will begin to fill up, and should be read periodically with
* input_read(). Before calling this input_setup() must have been called.
*/
int
input_start(struct player_source *ps);
/*
* Pauses playback of the given player source (stops playback loop) and flushes
* the input buffer
*/
int
input_pause(struct player_source *ps);
/*
* Stops playback loop (if running), flushes input buffer and cleans up the
* player source
*/
int
input_stop(struct player_source *ps);
/*
* Seeks playback position to seek_ms. Returns actual seek position, 0 on
* unseekable, -1 on error. May block.
*/
int
input_seek(struct player_source *ps, int seek_ms);
/*
* Flush input buffer. Output flags will be the same as input_read().
*/
void
input_flush(short *flags);
/*
* Gets metadata from the input, returns 0 if metadata is set, otherwise -1
*/
int
input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime);
/*
* Free the entire struct
*/
void
input_metadata_free(struct input_metadata *metadata, int content_only);
/*
* Called by player_init (so will run in main thread)
*/
int
input_init(void);
/*
* Called by player_deinit (so will run in main thread)
*/
void
input_deinit(void);
#endif /* !__INPUT_H__ */

155
src/inputs/file_http.c Normal file
View File

@ -0,0 +1,155 @@
/*
* Copyright (C) 2017 Espen Jurgensen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <event2/buffer.h>
#include "transcode.h"
#include "http.h"
#include "misc.h"
#include "input.h"
static int
setup(struct player_source *ps)
{
ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL);
if (!ps->xcode)
return -1;
ps->setup_done = 1;
return 0;
}
static int
setup_http(struct player_source *ps)
{
char *url;
if (http_stream_setup(&url, ps->path) < 0)
return -1;
free(ps->path);
ps->path = url;
return setup(ps);
}
static int
start(struct player_source *ps)
{
struct evbuffer *evbuf;
short flags;
int ret;
int icy_timer;
evbuf = evbuffer_new();
ret = -1;
flags = 0;
while (!input_loop_break && !(flags & INPUT_FLAG_EOF))
{
// We set "wanted" to 1 because the read size doesn't matter to us
// TODO optimize?
ret = transcode(evbuf, 1, ps->xcode, &icy_timer);
if (ret < 0)
break;
flags = ((ret == 0) ? INPUT_FLAG_EOF : 0) |
(icy_timer ? INPUT_FLAG_METADATA : 0);
ret = input_write(evbuf, flags);
if (ret < 0)
break;
}
evbuffer_free(evbuf);
return ret;
}
static int
stop(struct player_source *ps)
{
transcode_cleanup(ps->xcode);
ps->xcode = NULL;
ps->setup_done = 0;
return 0;
}
static int
seek(struct player_source *ps, int seek_ms)
{
return transcode_seek(ps->xcode, seek_ms);
}
static int
metadata_get_http(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
{
struct http_icy_metadata *m;
int changed;
m = transcode_metadata(ps->xcode, &changed);
if (!m)
return -1;
if (!changed)
{
http_icy_metadata_free(m, 0);
return -1; // TODO Perhaps a problem since this prohibits the player updating metadata
}
if (m->artist)
swap_pointers(&metadata->artist, &m->artist);
// Note we map title to album, because clients should show stream name as titel
if (m->title)
swap_pointers(&metadata->album, &m->title);
if (m->artwork_url)
swap_pointers(&metadata->artwork_url, &m->artwork_url);
http_icy_metadata_free(m, 0);
return 0;
}
struct input_definition input_file =
{
.name = "file",
.type = INPUT_TYPE_FILE,
.disabled = 0,
.setup = setup,
.start = start,
.stop = stop,
.seek = seek,
};
struct input_definition input_http =
{
.name = "http",
.type = INPUT_TYPE_HTTP,
.disabled = 0,
.setup = setup_http,
.start = start,
.stop = stop,
.metadata_get = metadata_get_http,
};

852
src/inputs/pipe.c Normal file
View File

@ -0,0 +1,852 @@
/*
* Copyright (C) 2017 Espen Jurgensen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* About pipe.c
* --------------
* This module will read a PCM16 stream from a named pipe and write it to the
* input buffer. The user may start/stop playback from a pipe by selecting it
* through a client. If the user has configured pipe_autostart, then pipes in
* the library will also be watched for data, and playback will start/stop
* automatically.
*
* The module will also look for pipes with a .metadata suffix, and if found,
* the metadata will be parsed and fed to the player. The metadata must be in
* the format Shairport uses for this purpose.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <mxml.h>
#include "input.h"
#include "misc.h"
#include "logger.h"
#include "db.h"
#include "conffile.h"
#include "listener.h"
#include "player.h"
#include "worker.h"
#include "mxml-compat.h"
// Maximum number of pipes to watch for data
#define PIPE_MAX_WATCH 4
// Max number of bytes to read from a pipe at a time
#define PIPE_READ_MAX 65536
// Max number of bytes to buffer from metadata pipes
#define PIPE_METADATA_BUFLEN_MAX 262144
extern struct event_base *evbase_worker;
enum pipestate
{
PIPE_NEW,
PIPE_DEL,
PIPE_OPEN,
};
enum pipetype
{
PIPE_PCM,
PIPE_METADATA,
};
struct pipe
{
int id; // The mfi id of the pipe
int fd; // File descriptor
bool is_autostarted; // We autostarted the pipe (and we will autostop)
char *path; // Path
enum pipestate state; // Newly appeared, marked for deletion, open/ready
enum pipetype type; // PCM (audio) or metadata
event_callback_fn cb; // Callback when there is data to read
struct event *ev; // Event for the callback
// TODO mutex
struct pipe *next;
};
// From config - should we watch library pipes for data or only start on request
static int pipe_autostart;
// Global list of pipes we are watching. If watching/autostart is disabled this
// will just point to the currently playing pipe (if any).
static struct pipe *pipelist;
// Single pipe that we start watching for metadata after playback starts
static struct pipe *pipe_metadata;
// We read metadata into this evbuffer
static struct evbuffer *pipe_metadata_buf;
// Parsed metadata goes here
static struct input_metadata pipe_metadata_parsed;
// Mutex to share the parsed metadata
static pthread_mutex_t pipe_metadata_lock;
// True if there is new metadata to push to the player
static bool pipe_metadata_is_new;
/* ------------------------------- FORWARDS ------------------------------- */
static void
pipe_watch_reset(struct pipe *pipe);
static void
pipe_metadata_watch_del(void *arg);
/* -------------------------------- HELPERS ------------------------------- */
static struct pipe *
pipe_new(const char *path, int id, enum pipetype type, event_callback_fn cb)
{
struct pipe *pipe;
pipe = calloc(1, sizeof(struct pipe));
pipe->path = strdup(path);
pipe->id = id;
pipe->fd = -1;
pipe->state = PIPE_NEW;
pipe->type = type;
pipe->cb = cb;
if (type == PIPE_PCM)
{
pipe->next = pipelist;
pipelist = pipe;
}
return pipe;
}
static void
pipe_free(struct pipe *pipe)
{
free(pipe->path);
free(pipe);
}
static void
pipelist_prune(void)
{
struct pipe *pipe;
struct pipe *next;
for (pipe = pipelist; pipe; pipe = next)
{
next = pipe->next;
if (pipelist->state == PIPE_DEL)
{
pipe_free(pipelist);
pipelist = next;
}
else if (next && (next->state == PIPE_DEL))
{
pipe->next = next->next;
pipe_free(next);
next = pipe->next;
}
}
}
static struct pipe *
pipe_find(int id)
{
struct pipe *pipe;
for (pipe = pipelist; pipe; pipe = pipe->next)
{
if (id == pipe->id)
return pipe;
}
return NULL;
}
// Convert to macro?
static inline uint32_t
dmapval(const char s[4])
{
return ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0));
}
static void
parse_progress(struct input_metadata *m, char *progress)
{
char *s;
char *ptr;
uint64_t start;
uint64_t pos;
uint64_t end;
if (!(s = strtok_r(progress, "/", &ptr)))
return;
safe_atou64(s, &start);
if (!(s = strtok_r(NULL, "/", &ptr)))
return;
safe_atou64(s, &pos);
if (!(s = strtok_r(NULL, "/", &ptr)))
return;
safe_atou64(s, &end);
if (!start || !pos || !end)
return;
m->rtptime = start; // Not actually used - we have our own rtptime
m->offset = (pos > start) ? (pos - start) : 0;
m->song_length = (end - start) * 10 / 441; // Convert to ms based on 44100
}
// returns 1 on metadata found, 0 on nothing, -1 on error
static int
parse_item(struct input_metadata *m, const char *item)
{
mxml_node_t *xml;
mxml_node_t *haystack;
mxml_node_t *needle;
const char *s;
uint32_t type;
uint32_t code;
char *progress;
char **data;
int ret;
ret = 0;
xml = mxmlNewXML("1.0");
if (!xml)
return -1;
// DPRINTF(E_DBG, L_PLAYER, "Parsing %s\n", item);
haystack = mxmlLoadString(xml, item, MXML_NO_CALLBACK);
if (!haystack)
{
DPRINTF(E_LOG, L_PLAYER, "Could not parse pipe metadata\n");
goto out_error;
}
type = 0;
if ( (needle = mxmlFindElement(haystack, haystack, "type", NULL, NULL, MXML_DESCEND)) &&
(s = mxmlGetText(needle, NULL)) )
sscanf(s, "%8x", &type);
code = 0;
if ( (needle = mxmlFindElement(haystack, haystack, "code", NULL, NULL, MXML_DESCEND)) &&
(s = mxmlGetText(needle, NULL)) )
sscanf(s, "%8x", &code);
if (!type || !code)
{
DPRINTF(E_LOG, L_PLAYER, "No type (%d) or code (%d) in pipe metadata, aborting\n", type, code);
goto out_error;
}
if (code == dmapval("asal"))
data = &m->album;
else if (code == dmapval("asar"))
data = &m->artist;
else if (code == dmapval("minm"))
data = &m->title;
else if (code == dmapval("asgn"))
data = &m->genre;
else if (code == dmapval("prgr"))
data = &progress;
else
goto out_nothing;
if ( (needle = mxmlFindElement(haystack, haystack, "data", NULL, NULL, MXML_DESCEND)) &&
(s = mxmlGetText(needle, NULL)) )
{
pthread_mutex_lock(&pipe_metadata_lock);
if (data != &progress)
free(*data);
*data = b64_decode(s);
DPRINTF(E_DBG, L_PLAYER, "Read Shairport metadata (type=%8x, code=%8x): '%s'\n", type, code, *data);
if (data == &progress)
{
parse_progress(m, progress);
free(*data);
}
pthread_mutex_unlock(&pipe_metadata_lock);
ret = 1;
}
out_nothing:
mxmlDelete(xml);
return ret;
out_error:
mxmlDelete(xml);
return -1;
}
static char *
extract_item(struct evbuffer *evbuf)
{
struct evbuffer_ptr evptr;
size_t size;
char *item;
evptr = evbuffer_search(evbuf, "</item>", strlen("</item>"), NULL);
if (evptr.pos < 0)
return NULL;
size = evptr.pos + strlen("</item>") + 1;
item = malloc(size);
if (!item)
return NULL;
evbuffer_remove(evbuf, item, size - 1);
item[size - 1] = '\0';
return item;
}
static int
pipe_metadata_parse(struct input_metadata *m, struct evbuffer *evbuf)
{
char *item;
int found;
int ret;
found = 0;
while ((item = extract_item(evbuf)))
{
ret = parse_item(m, item);
free(item);
if (ret < 0)
return -1;
if (ret > 0)
found = 1;
}
return found;
}
/* ---------------------------- GENERAL PIPE I/O -------------------------- */
/* Thread: worker */
// Some data arrived on a pipe we watch - let's autostart playback
static void
pipe_read_cb(evutil_socket_t fd, short event, void *arg)
{
struct pipe *pipe = arg;
struct player_status status;
struct db_queue_item *queue_item;
int ret;
ret = player_get_status(&status);
if ((ret < 0) || (status.status == PLAY_PLAYING))
{
DPRINTF(E_LOG, L_PLAYER, "Data arrived on pipe '%s', but player is busy\n", pipe->path);
pipe_watch_reset(pipe);
return;
}
DPRINTF(E_INFO, L_PLAYER, "Autostarting pipe '%s' (fd %d)\n", pipe->path, fd);
db_queue_clear();
ret = db_queue_add_by_fileid(pipe->id, 0, 0);
if (ret < 0)
return;
queue_item = db_queue_fetch_byfileid(pipe->id);
if (!queue_item)
return;
player_playback_start_byitem(queue_item);
pipe->is_autostarted = 1;
free_queue_item(queue_item, 0);
}
// Updates pipelist with pipe items from the db. Pipes that are no longer in
// the db get marked with PIPE_DEL. Returns count of pipes that should be
// watched (may or may not equal length of pipelist)
static int
pipe_enum(void)
{
struct query_params qp;
struct db_media_file_info dbmfi;
struct pipe *pipe;
char filter[32];
int count;
int id;
int ret;
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.filter = filter;
snprintf(filter, sizeof(filter), "f.data_kind = %d", DATA_KIND_PIPE);
ret = db_query_start(&qp);
if (ret < 0)
return -1;
for (pipe = pipelist; pipe; pipe = pipe->next)
pipe->state = PIPE_DEL;
count = 0;
while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
{
ret = safe_atoi32(dbmfi.id, &id);
if (ret < 0)
continue;
count++;
if ((pipe = pipe_find(id)))
{
pipe->state = PIPE_OPEN;
continue;
}
pipe_new(dbmfi.path, id, PIPE_PCM, pipe_read_cb);
}
db_query_end(&qp);
return count;
}
// Opens a pipe and starts watching it for data if autostart is configured
static int
pipe_open(const char *path, bool silent)
{
struct stat sb;
int fd;
DPRINTF(E_DBG, L_PLAYER, "(Re)opening pipe: '%s'\n", path);
if (lstat(path, &sb) < 0)
{
if (!silent)
DPRINTF(E_LOG, L_PLAYER, "Could not lstat() '%s': %s\n", path, strerror(errno));
return -1;
}
if (!S_ISFIFO(sb.st_mode))
{
DPRINTF(E_LOG, L_PLAYER, "Source type is pipe, but path is not a fifo: %s\n", path);
return -1;
}
fd = open(path, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not open pipe for reading '%s': %s\n", path, strerror(errno));
return -1;
}
return fd;
}
static void
pipe_close(int fd)
{
if (fd >= 0)
close(fd);
}
static int
pipe_watch_add(struct pipe *pipe)
{
bool silent;
silent = (pipe->type == PIPE_METADATA);
pipe->fd = pipe_open(pipe->path, silent);
if (pipe->fd < 0)
return -1;
pipe->ev = event_new(evbase_worker, pipe->fd, EV_READ, pipe->cb, pipe);
if (!pipe->ev)
{
DPRINTF(E_LOG, L_PLAYER, "Could not watch pipe for new data '%s'\n", pipe->path);
pipe_close(pipe->fd);
return -1;
}
event_add(pipe->ev, NULL);
pipe->state = PIPE_OPEN;
return 0;
}
static void
pipe_watch_del(struct pipe *pipe)
{
if (pipe->ev)
event_free(pipe->ev);
pipe_close(pipe->fd);
pipe->fd = -1;
}
// If a read on pipe returns 0 it is an EOF, and we must close it and reopen it
// for renewed watching. The event will be freed and reallocated by this.
static void
pipe_watch_reset(struct pipe *pipe)
{
pipe_watch_del(pipe);
pipe_watch_add(pipe);
}
static void
pipe_watch_update(void *arg)
{
struct pipe *pipe;
int count;
count = pipe_enum(); // Count does not include pipes with state PIPE_DEL
if (count < 0)
return;
for (pipe = pipelist; pipe; pipe = pipe->next)
{
DPRINTF(E_DBG, L_PLAYER, "Processing pipe '%s', state is %d\n", pipe->path, pipe->state);
if ((pipe->state == PIPE_NEW) && (count > PIPE_MAX_WATCH))
DPRINTF(E_LOG, L_PLAYER, "Max open pipes reached, will not watch %s\n", pipe->path);
else if (pipe->state == PIPE_NEW)
pipe_watch_add(pipe);
else if (pipe->state == PIPE_DEL)
pipe_watch_del(pipe);
}
pipelist_prune();
}
// Thread: filescanner
static void
pipe_listener_cb(enum listener_event_type type)
{
worker_execute(pipe_watch_update, NULL, 0, 0);
}
/* -------------------------- METADATA PIPE HANDLING ---------------------- */
/* Thread: worker */
// Some metadata arrived on a pipe we watch
static void
pipe_metadata_read_cb(evutil_socket_t fd, short event, void *arg)
{
int ret;
ret = evbuffer_read(pipe_metadata_buf, pipe_metadata->fd, PIPE_READ_MAX);
if (ret < 0)
{
if (errno != EAGAIN)
pipe_metadata_watch_del(NULL);
return;
}
else if (ret == 0)
{
pipe_watch_reset(pipe_metadata);
goto readd;
}
if (evbuffer_get_length(pipe_metadata_buf) > PIPE_METADATA_BUFLEN_MAX)
{
DPRINTF(E_LOG, L_PLAYER, "Can't process data from metadata pipe, reading will stop\n");
pipe_metadata_watch_del(NULL);
return;
}
ret = pipe_metadata_parse(&pipe_metadata_parsed, pipe_metadata_buf);
if (ret < 0)
{
pipe_metadata_watch_del(NULL);
return;
}
else if (ret > 0)
{
// Trigger notification in playback loop
pipe_metadata_is_new = 1;
}
readd:
if (pipe_metadata && pipe_metadata->ev)
event_add(pipe_metadata->ev, NULL);
}
static void
pipe_metadata_watch_add(void *arg)
{
char *base_path = arg;
char path[PATH_MAX];
int ret;
ret = snprintf(path, sizeof(path), "%s.metadata", base_path);
if ((ret < 0) || (ret > sizeof(path)))
return;
pipe_metadata = pipe_new(path, 0, PIPE_METADATA, pipe_metadata_read_cb);
if (!pipe_metadata)
return;
pipe_metadata_buf = evbuffer_new();
ret = pipe_watch_add(pipe_metadata);
if (ret < 0)
{
evbuffer_free(pipe_metadata_buf);
pipe_free(pipe_metadata);
pipe_metadata = NULL;
return;
}
}
static void
pipe_metadata_watch_del(void *arg)
{
if (!pipe_metadata)
return;
evbuffer_free(pipe_metadata_buf);
pipe_watch_del(pipe_metadata);
pipe_free(pipe_metadata);
pipe_metadata = NULL;
}
/* -------------------------- PIPE INPUT INTERFACE ------------------------ */
/* Thread: player/input */
static int
setup(struct player_source *ps)
{
struct pipe *pipe;
// If autostart is disabled then this is the first time we encounter the pipe
if (!pipe_autostart)
pipe_new(ps->path, ps->id, PIPE_PCM, NULL);
pipe = pipe_find(ps->id);
if (!pipe)
{
DPRINTF(E_LOG, L_PLAYER, "Unknown pipe '%s'\n", ps->path);
return -1;
}
// TODO pipe mutex here
if (pipe->state != PIPE_OPEN)
{
pipe->fd = pipe_open(pipe->path, 0);
if (pipe->fd < 0)
return -1;
pipe->state = PIPE_OPEN;
}
if (pipe->ev)
event_del(pipe->ev); // Avoids autostarting pipe if manually started by user
worker_execute(pipe_metadata_watch_add, pipe->path, strlen(pipe->path) + 1, 0);
ps->setup_done = 1;
return 0;
}
static int
start(struct player_source *ps)
{
struct pipe *pipe;
struct evbuffer *evbuf;
short flags;
int ret;
pipe = pipe_find(ps->id);
if (!pipe)
return -1;
evbuf = evbuffer_new();
if (!evbuf)
{
DPRINTF(E_LOG, L_PLAYER, "Out of memory for pipe evbuf\n");
return -1;
}
ret = -1;
while (!input_loop_break)
{
ret = evbuffer_read(evbuf, pipe->fd, PIPE_READ_MAX);
if ((ret == 0) && (pipe->is_autostarted))
{
input_write(evbuf, INPUT_FLAG_EOF); // Autostop
break;
}
else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN)))
{
input_wait();
continue;
}
else if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe: %s\n", strerror(errno));
break;
}
flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0);
pipe_metadata_is_new = 0;
ret = input_write(evbuf, flags);
if (ret < 0)
break;
}
evbuffer_free(evbuf);
return ret;
}
static int
stop(struct player_source *ps)
{
struct pipe *pipe;
DPRINTF(E_DBG, L_PLAYER, "Stopping pipe\n");
pipe = pipe_find(ps->id);
if (!pipe)
{
DPRINTF(E_LOG, L_PLAYER, "Unknown pipe '%s'\n", ps->path);
return -1;
}
if (!pipe_autostart)
{
// Since autostart is disabled we are now done with the pipe
pipe_close(pipe->fd);
pipe->state = PIPE_DEL;
pipelist_prune();
}
else
{
// Reset the pipe and start watching it again for new data
pipe->is_autostarted = 0;
pipe_watch_reset(pipe);
}
if (pipe_metadata)
worker_execute(pipe_metadata_watch_del, NULL, 0, 0);
ps->setup_done = 0;
return 0;
}
static int
metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
{
pthread_mutex_lock(&pipe_metadata_lock);
if (pipe_metadata_parsed.artist)
swap_pointers(&metadata->artist, &pipe_metadata_parsed.artist);
if (pipe_metadata_parsed.title)
swap_pointers(&metadata->title, &pipe_metadata_parsed.title);
if (pipe_metadata_parsed.album)
swap_pointers(&metadata->album, &pipe_metadata_parsed.album);
if (pipe_metadata_parsed.genre)
swap_pointers(&metadata->genre, &pipe_metadata_parsed.genre);
if (pipe_metadata_parsed.artwork_url)
swap_pointers(&metadata->artwork_url, &pipe_metadata_parsed.artwork_url);
if (pipe_metadata_parsed.song_length)
{
if (rtptime > ps->stream_start)
metadata->rtptime = rtptime - pipe_metadata_parsed.offset;
metadata->offset = pipe_metadata_parsed.offset;
metadata->song_length = pipe_metadata_parsed.song_length;
}
input_metadata_free(&pipe_metadata_parsed, 1);
pthread_mutex_unlock(&pipe_metadata_lock);
return 0;
}
// Thread: main
static int
init(void)
{
pipe_autostart = cfg_getbool(cfg_getsec(cfg, "library"), "pipe_autostart");
CHECK_ERR(L_PLAYER, mutex_init(&pipe_metadata_lock));
if (pipe_autostart)
return listener_add(pipe_listener_cb, LISTENER_DATABASE);
else
return 0;
}
static void
deinit(void)
{
struct pipe *pipe;
for (pipe = pipelist; pipelist; pipe = pipelist)
{
pipelist = pipe->next;
pipe_watch_del(pipe);
pipe_free(pipe);
}
CHECK_ERR(L_PLAYER, pthread_mutex_destroy(&pipe_metadata_lock));
if (pipe_autostart)
listener_remove(pipe_listener_cb);
}
struct input_definition input_pipe =
{
.name = "pipe",
.type = INPUT_TYPE_PIPE,
.disabled = 0,
.setup = setup,
.start = start,
.stop = stop,
.metadata_get = metadata_get,
.init = init,
.deinit = deinit,
};

96
src/inputs/spotify.c Normal file
View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2017 Espen Jurgensen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include "input.h"
#include "spotify.h"
static int
setup(struct player_source *ps)
{
int ret;
ret = spotify_playback_setup(ps->path);
if (ret < 0)
return -1;
ps->setup_done = 1;
return 0;
}
static int
start(struct player_source *ps)
{
int ret;
ret = spotify_playback_play();
if (ret < 0)
return -1;
while (!input_loop_break)
{
input_wait();
}
ret = spotify_playback_pause();
return ret;
}
static int
stop(struct player_source *ps)
{
int ret;
ret = spotify_playback_stop();
if (ret < 0)
return -1;
ps->setup_done = 0;
return 0;
}
static int
seek(struct player_source *ps, int seek_ms)
{
int ret;
ret = spotify_playback_seek(seek_ms);
if (ret < 0)
return -1;
return ret;
}
struct input_definition input_spotify =
{
.name = "Spotify",
.type = INPUT_TYPE_SPOTIFY,
.disabled = 0,
.setup = setup,
.start = start,
.stop = stop,
.seek = seek,
};

View File

@ -35,6 +35,8 @@
#include <event2/http.h>
#include <curl/curl.h>
#include "mxml-compat.h"
#include "db.h"
#include "lastfm.h"
#include "logger.h"
@ -215,24 +217,6 @@ param_sign(struct keyval *kv)
return ret;
}
/* For compability with mxml 2.6 */
#ifndef HAVE_MXMLGETOPAQUE
const char * /* O - Opaque string or NULL */
mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
{
if (!node)
return (NULL);
if (node->type == MXML_OPAQUE)
return (node->value.opaque);
else if (node->type == MXML_ELEMENT &&
node->child &&
node->child->type == MXML_OPAQUE)
return (node->child->value.opaque);
else
return (NULL);
}
#endif
/* --------------------------------- MAIN --------------------------------- */

View File

@ -21,10 +21,7 @@
# include <config.h>
#endif
#include "library.h"
#include <errno.h>
#include <event2/event.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
@ -38,6 +35,9 @@
#include <uninorm.h>
#include <unistr.h>
#include <event2/event.h>
#include "library.h"
#include "cache.h"
#include "commands.h"
#include "conffile.h"
@ -45,20 +45,14 @@
#include "library/filescanner.h"
#include "logger.h"
#include "misc.h"
#include "listener.h"
#include "player.h"
static struct commands_base *cmdbase;
static pthread_t tid_library;
struct event_base *evbase_lib;
/* Flag for aborting scan on exit */
static bool scan_exit;
/* Flag for scan in progress */
static bool scanning;
extern struct library_source filescanner;
#ifdef HAVE_SPOTIFY_H
extern struct library_source spotifyscanner;
@ -72,6 +66,19 @@ static struct library_source *sources[] = {
NULL
};
/* Flag for aborting scan on exit */
static bool scan_exit;
/* Flag for scan in progress */
static bool scanning;
// After being told by db that the library was updated through update_trigger(),
// wait 60 seconds before notifying listeners of LISTENER_DATABASE. This is to
// avoid bombarding the listeners while there are many db updates, and to make
// sure they only get a single update (useful for the cache).
static struct timeval library_update_wait = { 60, 0 };
static struct event *updateev;
static void
sort_tag_create(char **sort_tag, char *src_tag)
@ -630,6 +637,24 @@ fullrescan(void *arg, int *ret)
return COMMAND_END;
}
static void
update_trigger_cb(int fd, short what, void *arg)
{
listener_notify(LISTENER_DATABASE);
}
static enum command_state
update_trigger(void *arg, int *retval)
{
evtimer_add(updateev, &library_update_wait);
*retval = 0;
return COMMAND_END;
}
/* --------------------------- LIBRARY INTERFACE -------------------------- */
void
library_rescan()
{
@ -692,6 +717,8 @@ initscan()
DPRINTF(E_LOG, L_LIB, "Library init scan completed in %.f sec\n", difftime(endtime, starttime));
scanning = false;
listener_notify(LISTENER_DATABASE);
}
/*
@ -721,6 +748,15 @@ library_is_exiting()
return scan_exit;
}
void
library_update_trigger(void)
{
if (scanning)
return;
commands_exec_async(cmdbase, update_trigger, NULL);
}
/*
* Execute the function 'func' with the given argument 'arg' in the library thread.
*
@ -786,13 +822,8 @@ library_init(void)
scan_exit = false;
scanning = false;
evbase_lib = event_base_new();
if (!evbase_lib)
{
DPRINTF(E_FATAL, L_LIB, "Could not create an event base\n");
return -1;
}
CHECK_NULL(L_LIB, evbase_lib = event_base_new());
CHECK_NULL(L_LIB, updateev = evtimer_new(evbase_lib, update_trigger_cb, NULL));
for (i = 0; sources[i]; i++)
{
@ -804,15 +835,9 @@ library_init(void)
sources[i]->disabled = 1;
}
cmdbase = commands_base_new(evbase_lib, NULL);
CHECK_NULL(L_LIB, cmdbase = commands_base_new(evbase_lib, NULL));
ret = pthread_create(&tid_library, NULL, library, NULL);
if (ret != 0)
{
DPRINTF(E_FATAL, L_LIB, "Could not spawn library thread: %s\n", strerror(errno));
goto thread_fail;
}
CHECK_ERR(L_LIB, pthread_create(&tid_library, NULL, library, NULL));
#if defined(HAVE_PTHREAD_SETNAME_NP)
pthread_setname_np(tid_library, "library");
@ -821,11 +846,6 @@ library_init(void)
#endif
return 0;
thread_fail:
event_base_free(evbase_lib);
return -1;
}
/* Thread: main */

View File

@ -86,6 +86,9 @@ library_set_scanning(bool is_scanning);
bool
library_is_exiting();
void
library_update_trigger(void);
int
library_exec_async(command_function func, void *arg);

View File

@ -113,7 +113,6 @@ static struct stacked_dir *dirstack;
/* From library.c */
extern struct event_base *evbase_lib;
#ifndef __linux__
struct deferred_file
{
@ -1501,8 +1500,12 @@ filescanner_init(void)
int ret;
ret = inofd_event_set();
if (ret < 0)
{
return -1;
}
return ret;
return 0;
}
/* Thread: main */

View File

@ -6,8 +6,8 @@ enum listener_event_type
{
/* The player has been started, stopped or seeked */
LISTENER_PLAYER = (1 << 0),
/* The current playlist has been modified */
LISTENER_PLAYLIST = (1 << 1),
/* The current playback queue has been modified */
LISTENER_QUEUE = (1 << 1),
/* The volume has been changed */
LISTENER_VOLUME = (1 << 2),
/* A speaker has been enabled or disabled */

View File

@ -27,6 +27,7 @@
#endif
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
@ -47,6 +48,8 @@ safe_atoi32(const char *str, int32_t *val)
char *end;
long intval;
*val = 0;
errno = 0;
intval = strtol(str, &end, 10);
@ -83,6 +86,8 @@ safe_atou32(const char *str, uint32_t *val)
char *end;
unsigned long intval;
*val = 0;
errno = 0;
intval = strtoul(str, &end, 10);
@ -119,6 +124,8 @@ safe_hextou32(const char *str, uint32_t *val)
char *end;
unsigned long intval;
*val = 0;
/* A hex shall begin with 0x */
if (strncmp(str, "0x", 2) != 0)
return safe_atou32(str, val);
@ -159,6 +166,8 @@ safe_atoi64(const char *str, int64_t *val)
char *end;
long long intval;
*val = 0;
errno = 0;
intval = strtoll(str, &end, 10);
@ -195,6 +204,8 @@ safe_atou64(const char *str, uint64_t *val)
char *end;
unsigned long long intval;
*val = 0;
errno = 0;
intval = strtoull(str, &end, 10);
@ -231,6 +242,8 @@ safe_hextou64(const char *str, uint64_t *val)
char *end;
unsigned long long intval;
*val = 0;
errno = 0;
intval = strtoull(str, &end, 16);
@ -568,6 +581,14 @@ trimwhitespace(const char *str)
return out;
}
void
swap_pointers(char **a, char **b)
{
char *t = *a;
*a = *b;
*b = t;
}
uint32_t
djb_hash(const void *data, size_t len)
{
@ -928,6 +949,21 @@ timespec_cmp(struct timespec time1, struct timespec time2)
return 0;
}
struct timespec
timespec_reltoabs(struct timespec relative)
{
struct timespec absolute;
#if _POSIX_TIMERS > 0
clock_gettime(CLOCK_REALTIME, &absolute);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
TIMEVAL_TO_TIMESPEC(&tv, &absolute);
#endif
return timespec_add(absolute, relative);
}
#if defined(HAVE_MACH_CLOCK) || defined(HAVE_MACH_TIMER)
#include <mach/mach_time.h> /* mach_absolute_time */

View File

@ -10,6 +10,11 @@
#include <time.h>
#include <pthread.h>
/* Samples to bytes, bytes to samples */
#define STOB(s) ((s) * 4)
#define BTOS(b) ((b) / 4)
struct onekeyval {
char *name;
char *value;
@ -77,6 +82,9 @@ unicode_fixup_string(char *str, const char *fromcode);
char *
trimwhitespace(const char *str);
void
swap_pointers(char **a, char **b);
uint32_t
djb_hash(const void *data, size_t len);
@ -140,6 +148,9 @@ timespec_add(struct timespec time1, struct timespec time2);
int
timespec_cmp(struct timespec time1, struct timespec time2);
struct timespec
timespec_reltoabs(struct timespec relative);
/* initialize mutex with error checking (not default on all platforms) */
int
mutex_init(pthread_mutex_t *mutex);

View File

@ -596,7 +596,7 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
}
else if (0 == strcmp(argv[i], "playlist"))
{
client->events |= LISTENER_PLAYLIST;
client->events |= LISTENER_QUEUE;
}
else if (0 == strcmp(argv[i], "mixer"))
{
@ -617,7 +617,7 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
}
}
else
client->events = LISTENER_PLAYER | LISTENER_PLAYLIST | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS;
client->events = LISTENER_PLAYER | LISTENER_QUEUE | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS;
idle_clients = client;
@ -4544,7 +4544,7 @@ mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type
evbuffer_add(client->evbuffer, "changed: player\n", 16);
break;
case LISTENER_PLAYLIST:
case LISTENER_QUEUE:
evbuffer_add(client->evbuffer, "changed: playlist\n", 18);
break;
@ -4871,7 +4871,7 @@ int mpd_init(void)
#endif
idle_clients = NULL;
listener_add(mpd_listener_cb, LISTENER_PLAYER | LISTENER_PLAYLIST | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS);
listener_add(mpd_listener_cb, LISTENER_PLAYER | LISTENER_QUEUE | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS);
return 0;

58
src/mxml-compat.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef __MXML_COMPAT_H__
#define __MXML_COMPAT_H__
/* For compability with mxml 2.6 */
#ifndef HAVE_MXMLGETTEXT
__attribute__((unused)) static const char * /* O - Text string or NULL */
mxmlGetText(mxml_node_t *node, /* I - Node to get */
int *whitespace) /* O - 1 if string is preceded by whitespace, 0 otherwise */
{
if (node->type == MXML_TEXT)
return (node->value.text.string);
else if (node->type == MXML_ELEMENT &&
node->child &&
node->child->type == MXML_TEXT)
return (node->child->value.text.string);
else
return (NULL);
}
#endif
#ifndef HAVE_MXMLGETOPAQUE
__attribute__((unused)) static const char * /* O - Opaque string or NULL */
mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
{
if (!node)
return (NULL);
if (node->type == MXML_OPAQUE)
return (node->value.opaque);
else if (node->type == MXML_ELEMENT &&
node->child &&
node->child->type == MXML_OPAQUE)
return (node->child->value.opaque);
else
return (NULL);
}
#endif
#ifndef HAVE_MXMLGETFIRSTCHILD
__attribute__((unused)) static mxml_node_t * /* O - First child or NULL */
mxmlGetFirstChild(mxml_node_t *node) /* I - Node to get */
{
if (!node || node->type != MXML_ELEMENT)
return (NULL);
return (node->child);
}
#endif
#ifndef HAVE_MXMLGETTYPE
__attribute__((unused)) static mxml_type_t /* O - Type of node */
mxmlGetType(mxml_node_t *node) /* I - Node to get */
{
return (node->type);
}
#endif
#endif /* !__MXML_COMPAT_H__ */

View File

@ -342,7 +342,10 @@ outputs_init(void)
}
if (!outputs[i]->init)
continue;
{
no_output = 0;
continue;
}
ret = outputs[i]->init();
if (ret < 0)

View File

@ -32,6 +32,7 @@
#include <event2/event.h>
#include <asoundlib.h>
#include "misc.h"
#include "conffile.h"
#include "logger.h"
#include "player.h"

View File

@ -33,6 +33,7 @@
#include <event2/event.h>
#include "misc.h"
#include "conffile.h"
#include "logger.h"
#include "player.h"

View File

@ -1,129 +0,0 @@
/*
* Copyright (C) 2014 Espen Jürgensen <espenjurgensen@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "pipe.h"
#include "logger.h"
#define PIPE_BUFFER_SIZE 8192
static int g_fd = -1;
static uint16_t *g_buf = NULL;
int
pipe_setup(const char *path)
{
struct stat sb;
if (!path)
{
DPRINTF(E_LOG, L_PLAYER, "Path to pipe is NULL\n");
return -1;
}
DPRINTF(E_DBG, L_PLAYER, "Setting up pipe: %s\n", path);
if (lstat(path, &sb) < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not lstat() '%s': %s\n", path, strerror(errno));
return -1;
}
if (!S_ISFIFO(sb.st_mode))
{
DPRINTF(E_LOG, L_PLAYER, "Source type is pipe, but path is not a fifo: %s\n", path);
return -1;
}
pipe_cleanup();
g_fd = open(path, O_RDONLY | O_NONBLOCK);
if (g_fd < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not open pipe for reading '%s': %s\n", path, strerror(errno));
return -1;
}
g_buf = (uint16_t *)malloc(PIPE_BUFFER_SIZE);
if (!g_buf)
{
DPRINTF(E_LOG, L_PLAYER, "Out of memory for buffer\n");
return -1;
}
return 0;
}
void
pipe_cleanup(void)
{
if (g_fd >= 0)
close(g_fd);
g_fd = -1;
if (g_buf)
free(g_buf);
g_buf = NULL;
return;
}
int
pipe_audio_get(struct evbuffer *evbuf, int wanted)
{
int got;
if (wanted > PIPE_BUFFER_SIZE)
wanted = PIPE_BUFFER_SIZE;
got = read(g_fd, g_buf, wanted);
if ((got < 0) && (errno != EAGAIN))
{
DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe: %s\n", strerror(errno));
return -1;
}
// If the other end of the pipe is not writing or the read was blocked,
// we just return silence
if (got <= 0)
{
memset(g_buf, 0, wanted);
got = wanted;
}
evbuffer_add(evbuf, g_buf, got);
return got;
}

View File

@ -1,16 +0,0 @@
#ifndef __PIPE_H__
#define __PIPE_H__
#include <event2/buffer.h>
int
pipe_setup(const char *path);
void
pipe_cleanup(void);
int
pipe_audio_get(struct evbuffer *evbuf, int wanted);
#endif /* !__PIPE_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -13,11 +13,6 @@
/* AirTunes v2 number of samples per packet */
#define AIRTUNES_V2_PACKET_SAMPLES 352
/* Samples to bytes, bytes to samples */
#define STOB(s) ((s) * 4)
#define BTOS(b) ((b) / 4)
/* Maximum number of previously played songs that are remembered */
#define MAX_HISTORY_COUNT 20
@ -85,9 +80,6 @@ player_get_status(struct player_status *status);
int
player_now_playing(uint32_t *id);
char *
player_get_icy_artwork_url(uint32_t id);
void
player_speaker_enumerate(spk_enum_cb cb, void *arg);
@ -95,7 +87,7 @@ int
player_speaker_set(uint64_t *ids);
int
player_playback_start();
player_playback_start(void);
int
player_playback_start_byitem(struct db_queue_item *queue_item);
@ -115,7 +107,6 @@ player_playback_next(void);
int
player_playback_prev(void);
int
player_volume_set(int vol);

View File

@ -36,7 +36,6 @@
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <time.h>
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
# include <pthread_np.h>
@ -55,6 +54,7 @@
#include "cache.h"
#include "commands.h"
#include "library.h"
#include "input.h"
/* TODO for the web api:
* - UI should be prettier
@ -79,8 +79,6 @@
* then use our normal cleanup of stray files to tidy db and cache.
*/
// How long to wait for audio (in sec) before giving up
#define SPOTIFY_TIMEOUT 20
// How long to wait for artwork (in sec) before giving up
#define SPOTIFY_ARTWORK_TIMEOUT 3
// An upper limit on sequential requests to Spotify's web api
@ -88,22 +86,6 @@
#define SPOTIFY_WEB_REQUESTS_MAX 20
/* --- Types --- */
typedef struct audio_fifo_data
{
TAILQ_ENTRY(audio_fifo_data) link;
int nsamples;
int16_t samples[0];
} audio_fifo_data_t;
typedef struct audio_fifo
{
TAILQ_HEAD(, audio_fifo_data) q;
int qlen;
int fullcount;
pthread_mutex_t mutex;
pthread_cond_t cond;
} audio_fifo_t;
enum spotify_state
{
SPOTIFY_STATE_INACTIVE,
@ -114,12 +96,6 @@ enum spotify_state
SPOTIFY_STATE_STOPPED,
};
struct audio_get_param
{
struct evbuffer *evbuf;
int wanted;
};
struct artwork_get_param
{
struct evbuffer *evbuf;
@ -164,8 +140,11 @@ static int spotify_saved_plid;
// Flag to avoid triggering playlist change events while the (re)scan is running
static bool scanning;
// Audio fifo
static audio_fifo_t *g_audio_fifo;
// Timeout timespec
static struct timespec spotify_artwork_timeout = { SPOTIFY_ARTWORK_TIMEOUT, 0 };
// Audio buffer
static struct evbuffer *spotify_audio_buffer;
/**
* The application key is specific to forked-daapd, and allows Spotify
@ -1167,38 +1146,6 @@ static sp_playlistcontainer_callbacks pc_callbacks = {
/* --------------------- INTERNAL PLAYBACK AND AUDIO ----------------------- */
/* Should only be called from within the spotify thread */
static void
mk_reltime(struct timespec *ts, time_t sec)
{
#if _POSIX_TIMERS > 0
clock_gettime(CLOCK_REALTIME, ts);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
TIMEVAL_TO_TIMESPEC(&tv, ts);
#endif
ts->tv_sec += sec;
}
static void
audio_fifo_flush(void)
{
audio_fifo_data_t *afd;
DPRINTF(E_DBG, L_SPOTIFY, "Flushing audio fifo\n");
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex));
while((afd = TAILQ_FIRST(&g_audio_fifo->q))) {
TAILQ_REMOVE(&g_audio_fifo->q, afd, link);
free(afd);
}
g_audio_fifo->qlen = 0;
g_audio_fifo->fullcount = 0;
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
}
static enum command_state
playback_setup(void *arg, int *retval)
{
@ -1240,8 +1187,6 @@ playback_setup(void *arg, int *retval)
return COMMAND_END;
}
audio_fifo_flush();
*retval = 0;
return COMMAND_END;
}
@ -1330,8 +1275,6 @@ playback_seek(void *arg, int *retval)
return COMMAND_END;
}
audio_fifo_flush();
*retval = 0;
return COMMAND_END;
}
@ -1353,92 +1296,14 @@ playback_eot(void *arg, int *retval)
g_state = SPOTIFY_STATE_STOPPING;
// TODO 1) This will block for a while, but perhaps ok?
// 2) spotify_audio_buffer not entirely thread safe here (but unlikely risk...)
input_write(spotify_audio_buffer, INPUT_FLAG_EOF);
*retval = 0;
return COMMAND_END;
}
static enum command_state
audio_get(void *arg, int *retval)
{
struct audio_get_param *audio;
struct timespec ts;
audio_fifo_data_t *afd;
int processed;
int timeout;
int ret;
int err;
int s;
audio = (struct audio_get_param *) arg;
afd = NULL;
processed = 0;
// If spotify was paused begin by resuming playback
if (g_state == SPOTIFY_STATE_PAUSED)
playback_play(NULL, retval);
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex));
while ((processed < audio->wanted) && (g_state != SPOTIFY_STATE_STOPPED))
{
// If track has ended and buffer is empty
if ((g_state == SPOTIFY_STATE_STOPPING) && (g_audio_fifo->qlen <= 0))
{
DPRINTF(E_DBG, L_SPOTIFY, "Track finished\n");
g_state = SPOTIFY_STATE_STOPPED;
break;
}
// If buffer is empty, wait for audio, but use timed wait so we don't
// risk waiting forever (maybe the player stopped while we were waiting)
timeout = 0;
while ( !(afd = TAILQ_FIRST(&g_audio_fifo->q)) &&
(g_state != SPOTIFY_STATE_STOPPED) &&
(g_state != SPOTIFY_STATE_STOPPING) &&
(timeout < SPOTIFY_TIMEOUT) )
{
DPRINTF(E_DBG, L_SPOTIFY, "Waiting for audio\n");
timeout += 5;
mk_reltime(&ts, 5);
CHECK_ERR_EXCEPT(L_SPOTIFY, pthread_cond_timedwait(&g_audio_fifo->cond, &g_audio_fifo->mutex, &ts), err, ETIMEDOUT);
}
if ((!afd) && (timeout >= SPOTIFY_TIMEOUT))
{
DPRINTF(E_LOG, L_SPOTIFY, "Timeout waiting for audio (waited %d sec)\n", timeout);
spotify_playback_stop_nonblock();
}
if (!afd)
break;
TAILQ_REMOVE(&g_audio_fifo->q, afd, link);
g_audio_fifo->qlen -= afd->nsamples;
s = afd->nsamples * sizeof(int16_t) * 2;
ret = evbuffer_add(audio->evbuf, afd->samples, s);
free(afd);
afd = NULL;
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for evbuffer (tried to add %d bytes)\n", s);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
*retval = -1;
return COMMAND_END;
}
processed += s;
}
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
*retval = processed;
return COMMAND_END;
}
static void
artwork_loaded_cb(sp_image *image, void *userdata)
{
@ -1681,8 +1546,8 @@ logged_out(sp_session *sess)
static int music_delivery(sp_session *sess, const sp_audioformat *format,
const void *frames, int num_frames)
{
audio_fifo_data_t *afd;
size_t s;
size_t size;
int ret;
/* No support for resampling right now */
if ((format->sample_rate != 44100) || (format->channels != 2))
@ -1692,44 +1557,26 @@ static int music_delivery(sp_session *sess, const sp_audioformat *format,
return num_frames;
}
// Audio discontinuity, e.g. seek
if (num_frames == 0)
return 0; // Audio discontinuity, do nothing
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex));
/* Buffer three seconds of audio */
if (g_audio_fifo->qlen > (3 * format->sample_rate))
{
// If the buffer has been full the last 300 times (~about a minute) we
// assume the player thread paused/died without telling us, so we signal pause
if (g_audio_fifo->fullcount < 300)
g_audio_fifo->fullcount++;
else
{
DPRINTF(E_WARN, L_SPOTIFY, "Buffer full more than 300 times, pausing\n");
spotify_playback_pause_nonblock();
g_audio_fifo->fullcount = 0;
}
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
evbuffer_drain(spotify_audio_buffer, evbuffer_get_length(spotify_audio_buffer));
return 0;
}
else
g_audio_fifo->fullcount = 0;
s = num_frames * sizeof(int16_t) * format->channels;
size = num_frames * sizeof(int16_t) * format->channels;
afd = malloc(sizeof(*afd) + s);
memcpy(afd->samples, frames, s);
ret = evbuffer_add(spotify_audio_buffer, frames, size);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory adding audio to buffer\n");
return num_frames;
}
afd->nsamples = num_frames;
TAILQ_INSERT_TAIL(&g_audio_fifo->q, afd, link);
g_audio_fifo->qlen += num_frames;
CHECK_ERR(L_SPOTIFY, pthread_cond_signal(&g_audio_fifo->cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
// The input buffer only accepts writing when it is approaching depletion, and
// because we use NONBLOCK it will just return if this is not the case. So in
// most cases no actual write is made and spotify_audio_buffer will just grow.
input_write(spotify_audio_buffer, INPUT_FLAG_NONBLOCK);
return num_frames;
}
@ -1975,18 +1822,6 @@ spotify_playback_seek(int ms)
return -1;
}
/* Thread: player */
int
spotify_audio_get(struct evbuffer *evbuf, int wanted)
{
struct audio_get_param audio;
audio.evbuf = evbuf;
audio.wanted = wanted;
return commands_exec_sync(cmdbase, audio_get, NULL, &audio);
}
/* Thread: httpd (artwork) and worker */
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
@ -2010,7 +1845,7 @@ spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
if (ret == 0)
{
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&artwork.mutex));
mk_reltime(&ts, SPOTIFY_ARTWORK_TIMEOUT);
ts = timespec_reltoabs(spotify_artwork_timeout);
if (!artwork.is_loaded)
CHECK_ERR_EXCEPT(L_SPOTIFY, pthread_cond_timedwait(&artwork.cond, &artwork.mutex, &ts), err, ETIMEDOUT);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&artwork.mutex));
@ -2613,7 +2448,6 @@ spotify_init(void)
event_add(g_notifyev, NULL);
cmdbase = commands_base_new(evbase_spotify, exit_cb);
if (!cmdbase)
{
@ -2651,17 +2485,7 @@ spotify_init(void)
break;
}
/* Prepare audio buffer */
g_audio_fifo = (audio_fifo_t *)malloc(sizeof(audio_fifo_t));
if (!g_audio_fifo)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for audio buffer\n");
goto audio_fifo_fail;
}
TAILQ_INIT(&g_audio_fifo->q);
g_audio_fifo->qlen = 0;
CHECK_ERR(L_SPOTIFY, mutex_init(&g_audio_fifo->mutex));
CHECK_ERR(L_SPOTIFY, pthread_cond_init(&g_audio_fifo->cond, NULL));
spotify_audio_buffer = evbuffer_new();
CHECK_ERR(L_SPOTIFY, mutex_init(&login_lck));
CHECK_ERR(L_SPOTIFY, pthread_cond_init(&login_cond, NULL));
@ -2687,11 +2511,8 @@ spotify_init(void)
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck));
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&g_audio_fifo->cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&g_audio_fifo->mutex));
free(g_audio_fifo);
evbuffer_free(spotify_audio_buffer);
audio_fifo_fail:
fptr_sp_session_release(g_sess);
g_sess = NULL;
@ -2751,10 +2572,8 @@ spotify_deinit(void)
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck));
/* Clear audio fifo */
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&g_audio_fifo->cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&g_audio_fifo->mutex));
free(g_audio_fifo);
/* Free audio buffer */
evbuffer_free(spotify_audio_buffer);
/* Release libspotify handle */
dlclose(g_libhandle);

View File

@ -27,9 +27,6 @@ spotify_playback_stop_nonblock(void);
int
spotify_playback_seek(int ms);
int
spotify_audio_get(struct evbuffer *evbuf, int wanted);
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);

View File

@ -82,6 +82,9 @@ struct decode_ctx {
// Duration (used to make wav header)
uint32_t duration;
// Data kind (used to determine if ICY metadata is relevant to look for)
enum data_kind data_kind;
// Contains the most recent packet from av_read_frame
// Used for resuming after seek and for freeing correctly
// in transcode_decode()
@ -583,7 +586,7 @@ flush_encoder(struct encode_ctx *ctx, unsigned int stream_index)
/* --------------------------- INPUT/OUTPUT INIT --------------------------- */
static int
open_input(struct decode_ctx *ctx, enum data_kind data_kind, const char *path, int decode_video)
open_input(struct decode_ctx *ctx, const char *path, int decode_video)
{
AVDictionary *options;
AVCodec *decoder;
@ -600,10 +603,10 @@ open_input(struct decode_ctx *ctx, enum data_kind data_kind, const char *path, i
# ifndef HAVE_FFMPEG
// Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts
if (data_kind == DATA_KIND_HTTP)
if (ctx->data_kind == DATA_KIND_HTTP)
ctx->ifmt_ctx->probesize = 64000;
# endif
if (data_kind == DATA_KIND_HTTP)
if (ctx->data_kind == DATA_KIND_HTTP)
av_dict_set(&options, "icy", "1", 0);
// TODO Newest versions of ffmpeg have timeout and reconnect options we should use
@ -1093,7 +1096,6 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte
if (!buffersrc || !format || !buffersink)
{
DPRINTF(E_LOG, L_XCODE, "Filtering source, format or sink element not found\n");
ret = AVERROR_UNKNOWN;
goto out_fail;
}
@ -1245,14 +1247,15 @@ transcode_decode_setup(enum data_kind data_kind, const char *path, uint32_t song
return NULL;
}
if (open_input(ctx, data_kind, path, decode_video) < 0)
ctx->duration = song_length;
ctx->data_kind = data_kind;
if (open_input(ctx, path, decode_video) < 0)
{
free(ctx);
return NULL;
}
ctx->duration = song_length;
av_init_packet(&ctx->packet);
return ctx;
@ -1283,7 +1286,8 @@ transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profil
return NULL;
}
ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->channels * ctx->byte_depth * ctx->sample_rate;
if (src_ctx->data_kind == DATA_KIND_HTTP)
ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->channels * ctx->byte_depth * ctx->sample_rate;
if (profile == XCODE_PCM16_HEADER)
{
@ -1637,6 +1641,8 @@ transcode(struct evbuffer *evbuf, int wanted, struct transcode_ctx *ctx, int *ic
int processed;
int ret;
*icy_timer = 0;
processed = 0;
while (processed < wanted)
{
@ -1653,7 +1659,8 @@ transcode(struct evbuffer *evbuf, int wanted, struct transcode_ctx *ctx, int *ic
}
ctx->encode_ctx->total_bytes += processed;
*icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed);
if (ctx->encode_ctx->icy_interval)
*icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed);
return processed;
}
@ -1822,24 +1829,3 @@ transcode_metadata(struct transcode_ctx *ctx, int *changed)
return m;
}
char *
transcode_metadata_artwork_url(struct transcode_ctx *ctx)
{
struct http_icy_metadata *m;
char *artwork_url;
if (!ctx->decode_ctx->ifmt_ctx || !ctx->decode_ctx->ifmt_ctx->filename)
return NULL;
artwork_url = NULL;
m = http_icy_metadata_get(ctx->decode_ctx->ifmt_ctx, 1);
if (m && m->artwork_url)
artwork_url = strdup(m->artwork_url);
if (m)
http_icy_metadata_free(m, 0);
return artwork_url;
}

View File

@ -100,7 +100,4 @@ transcode_seek(struct transcode_ctx *ctx, int ms);
struct http_icy_metadata *
transcode_metadata(struct transcode_ctx *ctx, int *changed);
char *
transcode_metadata_artwork_url(struct transcode_ctx *ctx);
#endif /* !__TRANSCODE_H__ */

View File

@ -149,15 +149,20 @@ worker_execute(void (*cb)(void *), void *cb_arg, size_t arg_size, int delay)
return;
}
argcpy = malloc(arg_size);
if (!argcpy)
if (arg_size > 0)
{
DPRINTF(E_LOG, L_MAIN, "Out of memory\n");
free(cmdarg);
return;
}
argcpy = malloc(arg_size);
if (!argcpy)
{
DPRINTF(E_LOG, L_MAIN, "Out of memory\n");
free(cmdarg);
return;
}
memcpy(argcpy, cb_arg, arg_size);
memcpy(argcpy, cb_arg, arg_size);
}
else
argcpy = NULL;
cmdarg->cb = cb;
cmdarg->cb_arg = argcpy;