2021-04-08 22:53:19 +02:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2021-07-05 21:09:02 +02:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include <config.h>
|
|
|
|
#endif
|
|
|
|
|
2021-04-08 22:53:19 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
|
|
|
|
#include <event2/event.h>
|
|
|
|
|
|
|
|
#include "input.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include "logger.h"
|
|
|
|
#include "conffile.h"
|
|
|
|
#include "listener.h"
|
|
|
|
#include "http.h"
|
|
|
|
#include "db.h"
|
|
|
|
#include "transcode.h"
|
|
|
|
#include "spotify.h"
|
|
|
|
#include "librespot-c/librespot-c.h"
|
|
|
|
|
|
|
|
// Haven't actually studied ffmpeg's probe size requirements, this is just a
|
|
|
|
// guess
|
|
|
|
#define SPOTIFY_PROBE_SIZE_MIN 16384
|
|
|
|
|
|
|
|
// The transcoder will say EOF if too little data is provided to it
|
|
|
|
#define SPOTIFY_BUF_MIN 4096
|
|
|
|
|
|
|
|
// Limits how much of the Spotify Ogg file we fetch and buffer (in read_buf).
|
|
|
|
// This will also in effect throttle in librespot-c.
|
|
|
|
#define SPOTIFY_BUF_MAX (512 * 1024)
|
|
|
|
|
|
|
|
struct global_ctx
|
|
|
|
{
|
2021-10-30 18:23:42 +02:00
|
|
|
bool is_initialized;
|
2021-04-08 22:53:19 +02:00
|
|
|
struct spotify_status status;
|
|
|
|
|
|
|
|
struct sp_session *session;
|
|
|
|
enum sp_bitrates bitrate_preferred;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct download_ctx
|
|
|
|
{
|
|
|
|
bool is_started;
|
|
|
|
bool is_ended;
|
|
|
|
struct transcode_ctx *xcode;
|
|
|
|
|
|
|
|
struct evbuffer *read_buf;
|
|
|
|
int read_fd;
|
|
|
|
|
|
|
|
uint32_t len_ms;
|
|
|
|
size_t len_bytes;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct global_ctx spotify_ctx;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
// Must be initialized statically since we don't have anywhere to do it at
|
|
|
|
// runtime. We are in the special situation that multiple threads can result in
|
|
|
|
// calls to initialize(), e.g. input_init() and library init scan, thus it must
|
|
|
|
// have the lock ready to use to be thread safe.
|
|
|
|
static pthread_mutex_t spotify_ctx_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
|
2021-04-08 22:53:19 +02:00
|
|
|
static struct media_quality spotify_quality = { 44100, 16, 2, 0 };
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------ Utility funcs ----------------------------- */
|
|
|
|
|
|
|
|
static void
|
|
|
|
hextobin(uint8_t *data, size_t data_len, const char *hexstr, size_t hexstr_len)
|
|
|
|
{
|
|
|
|
char hex[] = { 0, 0, 0 };
|
|
|
|
const char *ptr;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (2 * data_len < hexstr_len)
|
|
|
|
{
|
|
|
|
memset(data, 0, data_len);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ptr = hexstr;
|
|
|
|
for (i = 0; i < data_len; i++, ptr+=2)
|
|
|
|
{
|
|
|
|
memcpy(hex, ptr, 2);
|
|
|
|
data[i] = strtol(hex, NULL, 16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
postlogin(struct global_ctx *ctx)
|
|
|
|
{
|
|
|
|
struct sp_credentials credentials;
|
|
|
|
char *db_stored_cred;
|
|
|
|
char *ptr;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = librespotc_credentials_get(&credentials, ctx->session);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Error getting Spotify credentials: %s\n", librespotc_last_errmsg());
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK_NULL(L_SPOTIFY, db_stored_cred = malloc(2 * credentials.stored_cred_len + 1));
|
|
|
|
for (i = 0, ptr = db_stored_cred; i < credentials.stored_cred_len; i++)
|
|
|
|
ptr += sprintf(ptr, "%02x", credentials.stored_cred[i]);
|
|
|
|
|
|
|
|
db_admin_set("spotify_username", credentials.username);
|
|
|
|
db_admin_set("spotify_stored_cred", db_stored_cred);
|
|
|
|
|
|
|
|
free(db_stored_cred);
|
|
|
|
|
|
|
|
ctx->status.logged_in = true;
|
|
|
|
snprintf(ctx->status.username, sizeof(ctx->status.username), "%s", credentials.username);
|
|
|
|
|
|
|
|
librespotc_bitrate_set(ctx->session, ctx->bitrate_preferred);
|
|
|
|
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Logged into Spotify succesfully with username %s\n", credentials.username);
|
|
|
|
|
|
|
|
listener_notify(LISTENER_SPOTIFY);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
static int
|
|
|
|
login_stored_cred(struct global_ctx *ctx, const char *username, const char *db_stored_cred)
|
|
|
|
{
|
|
|
|
size_t db_stored_cred_len;
|
|
|
|
uint8_t *stored_cred = NULL;
|
|
|
|
size_t stored_cred_len;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
db_stored_cred_len = strlen(db_stored_cred);
|
|
|
|
stored_cred_len = db_stored_cred_len / 2;
|
|
|
|
|
|
|
|
CHECK_NULL(L_SPOTIFY, stored_cred = malloc(stored_cred_len));
|
|
|
|
hextobin(stored_cred, stored_cred_len, db_stored_cred, db_stored_cred_len);
|
|
|
|
|
|
|
|
ctx->session = librespotc_login_stored_cred(username, stored_cred, stored_cred_len);
|
|
|
|
if (!ctx->session)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Error logging into Spotify: %s\n", librespotc_last_errmsg());
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = postlogin(ctx);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
free(stored_cred);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
free(stored_cred);
|
|
|
|
if (ctx->session)
|
|
|
|
librespotc_logout(ctx->session);
|
|
|
|
ctx->session = NULL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-04-08 22:53:19 +02:00
|
|
|
// If there is evbuf size is below max, reads from a non-blocking fd until error,
|
|
|
|
// EAGAIN or evbuf full
|
|
|
|
static int
|
|
|
|
fd_read(bool *eofptr, struct evbuffer *evbuf, int fd)
|
|
|
|
{
|
|
|
|
size_t len = evbuffer_get_length(evbuf);
|
|
|
|
bool eof = false;
|
|
|
|
int total = 0;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
while (len + total < SPOTIFY_BUF_MAX && !eof)
|
|
|
|
{
|
|
|
|
ret = evbuffer_read(evbuf, fd, -1); // Each read is 4096 bytes (EVBUFFER_READ_MAX)
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
eof = true;
|
|
|
|
else if (ret < 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
total += ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (eofptr)
|
|
|
|
*eofptr = eof;
|
|
|
|
|
|
|
|
if (ret < 0 && errno != EAGAIN)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ------------------ Callbacks from librespot-c thread --------------------- */
|
|
|
|
|
|
|
|
static void
|
|
|
|
progress_cb(int fd, void *cb_arg, size_t received, size_t len)
|
|
|
|
{
|
|
|
|
DPRINTF(E_SPAM, L_SPOTIFY, "Progress %zu/%zu\n", received, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
https_get_cb(char **out, const char *url)
|
|
|
|
{
|
|
|
|
struct http_client_ctx ctx = { 0 };
|
|
|
|
char *body;
|
|
|
|
size_t len;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ctx.url = url;
|
|
|
|
ctx.input_body = evbuffer_new();
|
|
|
|
|
2019-02-09 17:04:25 +01:00
|
|
|
ret = http_client_request(&ctx, NULL);
|
2021-04-08 22:53:19 +02:00
|
|
|
if (ret < 0 || ctx.response_code != HTTP_OK)
|
|
|
|
{
|
2023-08-29 20:14:19 +02:00
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Failed to get AP list from '%s' (return %d, error code %d)\n", ctx.url, ret, ctx.response_code);
|
2021-04-08 22:53:19 +02:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = evbuffer_get_length(ctx.input_body);
|
|
|
|
body = malloc(len + 1);
|
|
|
|
|
|
|
|
evbuffer_remove(ctx.input_body, body, len);
|
|
|
|
body[len] = '\0'; // For safety
|
|
|
|
|
|
|
|
*out = body;
|
|
|
|
|
|
|
|
evbuffer_free(ctx.input_body);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
evbuffer_free(ctx.input_body);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tcp_connect(const char *address, unsigned short port)
|
|
|
|
{
|
|
|
|
return net_connect(address, port, SOCK_STREAM, "spotify");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tcp_disconnect(int fd)
|
|
|
|
{
|
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
thread_name_set(pthread_t thread)
|
|
|
|
{
|
2021-07-05 21:40:31 +02:00
|
|
|
thread_setname(thread, "spotify");
|
2021-04-08 22:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
logmsg_cb(const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
DVPRINTF(E_DBG, L_SPOTIFY, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
hexdump_cb(const char *msg, uint8_t *data, size_t data_len)
|
|
|
|
{
|
|
|
|
// DHEXDUMP(E_DBG, L_SPOTIFY, data, data_len, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-05-25 22:49:35 +02:00
|
|
|
/* ------------------------ librespot-c initialization ---------------------- */
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
struct sp_callbacks callbacks = {
|
|
|
|
.https_get = https_get_cb,
|
|
|
|
.tcp_connect = tcp_connect,
|
|
|
|
.tcp_disconnect = tcp_disconnect,
|
|
|
|
|
|
|
|
.thread_name_set = thread_name_set,
|
|
|
|
|
|
|
|
.hexdump = hexdump_cb,
|
|
|
|
.logmsg = logmsg_cb,
|
|
|
|
};
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
// Called from main thread as part of player_init, or from library thread as
|
|
|
|
// part of relogin. Caller must use mutex for thread safety.
|
|
|
|
static int
|
|
|
|
initialize(struct global_ctx *ctx)
|
|
|
|
{
|
2022-05-16 23:28:18 +02:00
|
|
|
struct sp_sysinfo sysinfo = { 0 };
|
2021-10-30 18:23:42 +02:00
|
|
|
cfg_t *spotify_cfg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
spotify_cfg = cfg_getsec(cfg, "spotify");
|
|
|
|
|
|
|
|
if (ctx->is_initialized)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
snprintf(sysinfo.device_id, sizeof(sysinfo.device_id), "%" PRIx64, libhash); // TODO use a UUID instead
|
|
|
|
|
|
|
|
ret = librespotc_init(&sysinfo, &callbacks);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Error initializing Spotify: %s\n", librespotc_last_errmsg());
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (cfg_getint(spotify_cfg, "bitrate"))
|
|
|
|
{
|
|
|
|
case 1:
|
|
|
|
ctx->bitrate_preferred = SP_BITRATE_96;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
ctx->bitrate_preferred = SP_BITRATE_160;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
ctx->bitrate_preferred = SP_BITRATE_320;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ctx->bitrate_preferred = SP_BITRATE_ANY;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->is_initialized = true;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
ctx->is_initialized = false;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* --------------------- Implementation (input thread) ---------------------- */
|
|
|
|
|
2021-04-08 22:53:19 +02:00
|
|
|
static int64_t
|
|
|
|
download_seek(void *arg, int64_t offset, enum transcode_seek_type type)
|
|
|
|
{
|
|
|
|
struct download_ctx *download = arg;
|
|
|
|
int64_t out;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case XCODE_SEEK_SIZE:
|
|
|
|
out = download->len_bytes;
|
|
|
|
break;
|
|
|
|
case XCODE_SEEK_SET:
|
|
|
|
// Flush read buffer
|
|
|
|
evbuffer_drain(download->read_buf, -1);
|
|
|
|
|
|
|
|
ret = librespotc_seek(download->read_fd, offset);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
fd_read(NULL, download->read_buf, download->read_fd);
|
|
|
|
|
|
|
|
out = offset;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_SPOTIFY, "Seek to offset %" PRIi64 " requested, type %d, returning %" PRIi64 "\n", offset, type, out);
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
|
|
|
error:
|
|
|
|
DPRINTF(E_WARN, L_SPOTIFY, "Seek error\n");
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Has to be called after we have started receiving data, since ffmpeg needs to
|
|
|
|
// probe the data to find the audio streams
|
|
|
|
static int
|
|
|
|
download_xcode_setup(struct download_ctx *download)
|
|
|
|
{
|
|
|
|
struct transcode_ctx *xcode;
|
|
|
|
struct transcode_evbuf_io xcode_evbuf_io = { 0 };
|
|
|
|
|
|
|
|
CHECK_NULL(L_SPOTIFY, xcode = malloc(sizeof(struct transcode_ctx)));
|
|
|
|
|
|
|
|
xcode_evbuf_io.evbuf = download->read_buf;
|
|
|
|
xcode_evbuf_io.seekfn = download_seek;
|
|
|
|
xcode_evbuf_io.seekfn_arg = download;
|
|
|
|
|
|
|
|
xcode->decode_ctx = transcode_decode_setup(XCODE_OGG, NULL, DATA_KIND_SPOTIFY, NULL, &xcode_evbuf_io, download->len_ms);
|
|
|
|
if (!xcode->decode_ctx)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
xcode->encode_ctx = transcode_encode_setup(XCODE_PCM16, NULL, xcode->decode_ctx, NULL, 0, 0);
|
|
|
|
if (!xcode->encode_ctx)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
download->xcode = xcode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
transcode_cleanup(&xcode);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
download_free(struct download_ctx *download)
|
|
|
|
{
|
|
|
|
if (!download)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (download->read_fd >= 0)
|
|
|
|
librespotc_close(download->read_fd);
|
|
|
|
|
|
|
|
if (download->read_buf)
|
|
|
|
evbuffer_free(download->read_buf);
|
|
|
|
|
|
|
|
transcode_cleanup(&download->xcode);
|
|
|
|
free(download);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct download_ctx *
|
|
|
|
download_new(int fd, uint32_t len_ms, size_t len_bytes)
|
|
|
|
{
|
|
|
|
struct download_ctx *download;
|
|
|
|
|
|
|
|
CHECK_NULL(L_SPOTIFY, download = calloc(1, sizeof(struct download_ctx)));
|
|
|
|
CHECK_NULL(L_SPOTIFY, download->read_buf = evbuffer_new());
|
|
|
|
|
|
|
|
download->read_fd = fd;
|
|
|
|
download->len_ms = len_ms;
|
|
|
|
download->len_bytes = len_bytes;
|
|
|
|
|
|
|
|
return download;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
stop(struct input_source *source)
|
|
|
|
{
|
|
|
|
struct download_ctx *download = source->input_ctx;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_SPOTIFY, "stop()\n");
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
download_free(download);
|
|
|
|
|
|
|
|
if (source->evbuf)
|
|
|
|
evbuffer_free(source->evbuf);
|
|
|
|
|
|
|
|
source->input_ctx = NULL;
|
|
|
|
source->evbuf = NULL;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
setup(struct input_source *source)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
struct download_ctx *download;
|
|
|
|
struct sp_metadata metadata;
|
|
|
|
int probe_bytes;
|
|
|
|
int fd;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_SPOTIFY, "setup()\n");
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
fd = librespotc_open(source->path, ctx->session);
|
|
|
|
if (fd < 0)
|
|
|
|
{
|
2021-11-20 14:55:27 +01:00
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Error opening source: %s\n", librespotc_last_errmsg());
|
2021-04-08 22:53:19 +02:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = librespotc_metadata_get(&metadata, fd);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Error getting track metadata: %s\n", librespotc_last_errmsg());
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Seems we have a valid source, now setup a read + decoding context. The
|
|
|
|
// closing of the fd is from now on part of closing the download_ctx, which is
|
|
|
|
// done in stop().
|
|
|
|
download = download_new(fd, source->len_ms, metadata.file_len);
|
|
|
|
|
|
|
|
CHECK_NULL(L_SPOTIFY, source->evbuf = evbuffer_new());
|
|
|
|
CHECK_NULL(L_SPOTIFY, source->input_ctx = download);
|
|
|
|
|
|
|
|
source->quality = spotify_quality;
|
|
|
|
|
|
|
|
// At this point enough bytes should be ready for transcode setup (ffmpeg probing)
|
|
|
|
probe_bytes = fd_read(NULL, download->read_buf, fd);
|
|
|
|
if (probe_bytes < SPOTIFY_PROBE_SIZE_MIN)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Not enough audio data for ffmpeg probing (%d)\n", probe_bytes);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = download_xcode_setup(download);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
stop(source);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
play(struct input_source *source)
|
|
|
|
{
|
|
|
|
struct download_ctx *download = source->input_ctx;
|
|
|
|
size_t buflen;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
// Starts the download. We don't do that in setup because the player/input
|
|
|
|
// might run seek() before starting download.
|
|
|
|
if (!download->is_started)
|
|
|
|
{
|
|
|
|
librespotc_write(download->read_fd, progress_cb, download);
|
|
|
|
download->is_started = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!download->is_ended)
|
|
|
|
{
|
|
|
|
ret = fd_read(&download->is_ended, download->read_buf, download->read_fd);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
buflen = evbuffer_get_length(download->read_buf);
|
|
|
|
if (buflen < SPOTIFY_BUF_MIN)
|
|
|
|
goto wait;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the Ogg Vorbis to PCM in chunks of 16 packets, which is pretty much
|
|
|
|
// a randomly chosen chunk size
|
|
|
|
ret = transcode(source->evbuf, NULL, download->xcode, 16);
|
|
|
|
if (ret == 0)
|
|
|
|
{
|
|
|
|
input_write(source->evbuf, &source->quality, INPUT_FLAG_EOF);
|
|
|
|
stop(source);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
ret = input_write(source->evbuf, &source->quality, 0);
|
|
|
|
if (ret == EAGAIN)
|
|
|
|
goto wait;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
input_write(NULL, NULL, INPUT_FLAG_ERROR);
|
|
|
|
stop(source);
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
wait:
|
|
|
|
DPRINTF(E_DBG, L_SPOTIFY, "Waiting for data\n");
|
|
|
|
input_wait();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
seek(struct input_source *source, int seek_ms)
|
|
|
|
{
|
|
|
|
struct download_ctx *download = source->input_ctx;
|
|
|
|
int ret;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
// This will make transcode call back to download_seek(), but with a byte
|
|
|
|
// offset instead of a ms position, which is what librespot-c requires
|
|
|
|
ret = transcode_seek(download->xcode, seek_ms);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
return ret;
|
2021-04-08 22:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
ret = initialize(&spotify_ctx);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
return ret;
|
2021-04-08 22:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
deinit(void)
|
|
|
|
{
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
|
|
|
|
2021-04-08 22:53:19 +02:00
|
|
|
librespotc_deinit();
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
struct input_definition input_spotify =
|
|
|
|
{
|
|
|
|
.name = "Spotify",
|
|
|
|
.type = INPUT_TYPE_SPOTIFY,
|
|
|
|
.disabled = 0,
|
|
|
|
.setup = setup,
|
|
|
|
.stop = stop,
|
|
|
|
.play = play,
|
|
|
|
.seek = seek,
|
|
|
|
.init = init,
|
|
|
|
.deinit = deinit,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* -------------------- Functions exposed via spotify.h --------------------- */
|
|
|
|
/* Called from other threads than the input thread */
|
|
|
|
|
|
|
|
static int
|
|
|
|
login(const char *username, const char *password, const char **errmsg)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
int ret;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
ctx->session = librespotc_login_password(username, password);
|
|
|
|
if (!ctx->session)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
ret = postlogin(ctx);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (ctx->session)
|
|
|
|
librespotc_logout(ctx->session);
|
|
|
|
ctx->session = NULL;
|
|
|
|
|
|
|
|
if (errmsg)
|
|
|
|
*errmsg = librespotc_last_errmsg();
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
login_token(const char *username, const char *token, const char **errmsg)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
int ret;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
ctx->session = librespotc_login_token(username, token);
|
|
|
|
if (!ctx->session)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
ret = postlogin(ctx);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (ctx->session)
|
|
|
|
librespotc_logout(ctx->session);
|
|
|
|
ctx->session = NULL;
|
|
|
|
|
|
|
|
if (errmsg)
|
|
|
|
*errmsg = librespotc_last_errmsg();
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
logout(void)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
|
|
|
|
db_admin_delete("spotify_username");
|
|
|
|
db_admin_delete("spotify_stored_cred");
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
librespotc_logout(ctx->session);
|
|
|
|
ctx->session = NULL;
|
|
|
|
|
2021-10-05 20:46:44 +02:00
|
|
|
memset(&ctx->status, 0, sizeof(ctx->status));
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-12-26 20:20:31 +01:00
|
|
|
|
|
|
|
listener_notify(LISTENER_SPOTIFY);
|
2021-04-08 22:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
relogin(void)
|
|
|
|
{
|
2021-10-30 18:23:42 +02:00
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
char *username = NULL;
|
|
|
|
char *db_stored_cred = NULL;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
|
|
|
|
|
|
|
ret = initialize(ctx);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
// Re-login if we have stored credentials
|
|
|
|
db_admin_get(&username, "spotify_username");
|
|
|
|
db_admin_get(&db_stored_cred, "spotify_stored_cred");
|
|
|
|
if (username && db_stored_cred)
|
|
|
|
{
|
|
|
|
ret = login_stored_cred(ctx, username, db_stored_cred);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(username);
|
|
|
|
free(db_stored_cred);
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
free(username);
|
|
|
|
free(db_stored_cred);
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
|
|
|
return -1;
|
2021-04-08 22:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
status_get(struct spotify_status *status)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_lock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
|
|
|
|
memcpy(status->username, ctx->status.username, sizeof(status->username));
|
|
|
|
status->logged_in = ctx->status.logged_in;
|
|
|
|
status->installed = true;
|
2021-07-11 12:19:49 +02:00
|
|
|
status->has_podcast_support = true;
|
2021-04-08 22:53:19 +02:00
|
|
|
|
2021-10-30 18:23:42 +02:00
|
|
|
pthread_mutex_unlock(&spotify_ctx_lock);
|
2021-04-08 22:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
struct spotify_backend spotify_librespotc =
|
|
|
|
{
|
|
|
|
.login = login,
|
|
|
|
.login_token = login_token,
|
|
|
|
.logout = logout,
|
|
|
|
.relogin = relogin,
|
|
|
|
.status_get = status_get,
|
|
|
|
};
|
|
|
|
|