2016-12-28 18:39:23 -05: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
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
#include <event2/event.h>
|
|
|
|
|
2016-12-28 18:39:23 -05:00
|
|
|
#include "input.h"
|
2021-03-28 14:00:15 -04:00
|
|
|
#include "misc.h"
|
2018-03-01 11:29:10 -05:00
|
|
|
#include "logger.h"
|
2021-03-28 14:00:15 -04:00
|
|
|
#include "http.h"
|
|
|
|
#include "db.h"
|
|
|
|
#include "transcode.h"
|
2016-12-28 18:39:23 -05:00
|
|
|
#include "spotify.h"
|
2021-03-28 14:00:15 -04:00
|
|
|
#include "spotifyc/spotifyc.h"
|
|
|
|
|
|
|
|
#define SPOTIFY_PROBE_SIZE_MIN 65536
|
|
|
|
|
|
|
|
struct global_ctx
|
|
|
|
{
|
|
|
|
pthread_mutex_t lock;
|
|
|
|
pthread_cond_t cond;
|
|
|
|
|
|
|
|
struct sp_session *session;
|
|
|
|
bool response_pending; // waiting for a response from spotifyc
|
|
|
|
struct spotify_status status;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct playback_ctx
|
|
|
|
{
|
|
|
|
struct transcode_ctx *xcode;
|
|
|
|
|
2021-04-06 10:16:32 -04:00
|
|
|
// This buffer gets fairly large, since it reads and holds the Ogg track that
|
|
|
|
// spotifyc downloads. It has no write limit, unlike the input buffer.
|
2021-03-28 14:00:15 -04:00
|
|
|
struct evbuffer *read_buf;
|
2021-04-06 10:16:32 -04:00
|
|
|
int read_fd;
|
2021-03-28 14:00:15 -04:00
|
|
|
size_t read_bytes;
|
|
|
|
|
|
|
|
uint32_t len_ms;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct global_ctx spotify_ctx;
|
|
|
|
|
|
|
|
static bool db_is_initialized;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* -------------------- Callbacks from spotifyc thread ---------------------- */
|
|
|
|
|
|
|
|
static void
|
|
|
|
got_reply(struct global_ctx *ctx)
|
|
|
|
{
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
|
|
|
|
|
|
|
ctx->response_pending = false;
|
|
|
|
|
|
|
|
pthread_cond_signal(&ctx->cond);
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
error_cb(void *cb_arg, int err, const char *errmsg)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = cb_arg;
|
|
|
|
|
|
|
|
got_reply(ctx);
|
|
|
|
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "%s (error code %d)\n", errmsg, err);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
logged_in_cb(struct sp_session *session, void *cb_arg, struct sp_credentials *credentials)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = cb_arg;
|
|
|
|
char *db_stored_cred;
|
|
|
|
char *ptr;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!db_is_initialized)
|
|
|
|
{
|
|
|
|
ret = db_perthread_init();
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Error: DB init failed (spotify thread)\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
db_is_initialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Logged into Spotify succesfully\n");
|
|
|
|
|
|
|
|
if (!credentials->username || !credentials->stored_cred)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "No credentials returned by Spotify, automatic login will not be possible\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
|
|
|
|
|
|
|
ctx->response_pending = false;
|
|
|
|
ctx->status.logged_in = true;
|
|
|
|
snprintf(ctx->status.username, sizeof(ctx->status.username), "%s", credentials->username);
|
|
|
|
|
|
|
|
pthread_cond_signal(&ctx->cond);
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
logged_out_cb(void *cb_arg)
|
|
|
|
{
|
|
|
|
db_admin_delete("spotify_username");
|
|
|
|
db_admin_delete("spotify_stored_cred");
|
|
|
|
|
|
|
|
if (db_is_initialized)
|
|
|
|
db_perthread_deinit();
|
|
|
|
|
|
|
|
db_is_initialized = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
track_opened_cb(struct sp_session *session, void *cb_arg, int fd)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = cb_arg;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_SPOTIFY, "track_opened_cb()\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
|
|
|
|
|
|
|
ctx->response_pending = false;
|
|
|
|
ctx->status.track_opened = true;
|
|
|
|
|
|
|
|
pthread_cond_signal(&ctx->cond);
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
track_closed_cb(struct sp_session *session, void *cb_arg, int fd)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = cb_arg;
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_SPOTIFY, "track_closed_cb()\n");
|
|
|
|
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
|
|
|
|
|
|
|
ctx->response_pending = false;
|
|
|
|
ctx->status.track_opened = false;
|
|
|
|
|
|
|
|
pthread_cond_signal(&ctx->cond);
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
ret = http_client_request(&ctx);
|
|
|
|
if (ret < 0 || ctx.response_code != HTTP_OK)
|
|
|
|
{
|
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Failed to AP list from '%s' (return %d, error code %d)\n", ctx.url, ret, ctx.response_code);
|
|
|
|
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
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* --------------------- Implementation (input thread) ---------------------- */
|
2016-12-28 18:39:23 -05:00
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
struct sp_callbacks callbacks = {
|
|
|
|
.error = error_cb,
|
|
|
|
.logged_in = logged_in_cb,
|
|
|
|
.logged_out = logged_out_cb,
|
|
|
|
.track_opened = track_opened_cb,
|
|
|
|
.track_closed = track_closed_cb,
|
|
|
|
|
|
|
|
.https_get = https_get_cb,
|
|
|
|
.tcp_connect = tcp_connect,
|
|
|
|
.tcp_disconnect = tcp_disconnect,
|
|
|
|
|
|
|
|
.hexdump = hexdump_cb,
|
|
|
|
.logmsg = logmsg_cb,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Has to be called after we have started receiving data, since ffmpeg needs to
|
|
|
|
// probe the data to find the audio streams
|
|
|
|
static int
|
|
|
|
playback_xcode_setup(struct playback_ctx *playback)
|
|
|
|
{
|
|
|
|
struct transcode_ctx *xcode;
|
2021-04-06 10:16:32 -04:00
|
|
|
struct transcode_evbuf_io xcode_evbuf_io = { 0 };
|
2021-03-28 14:00:15 -04:00
|
|
|
|
|
|
|
CHECK_NULL(L_SPOTIFY, xcode = malloc(sizeof(struct transcode_ctx)));
|
|
|
|
|
2021-04-06 10:16:32 -04:00
|
|
|
xcode_evbuf_io.evbuf = playback->read_buf;
|
|
|
|
|
|
|
|
xcode->decode_ctx = transcode_decode_setup(XCODE_OGG, NULL, DATA_KIND_SPOTIFY, NULL, &xcode_evbuf_io, playback->len_ms);
|
2021-03-28 14:00:15 -04:00
|
|
|
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;
|
|
|
|
|
|
|
|
playback->xcode = xcode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
transcode_cleanup(&xcode);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
playback_free(struct playback_ctx *playback)
|
|
|
|
{
|
|
|
|
if (!playback)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (playback->read_buf)
|
|
|
|
evbuffer_free(playback->read_buf);
|
|
|
|
if (playback->read_fd >= 0)
|
|
|
|
close(playback->read_fd);
|
|
|
|
|
|
|
|
transcode_cleanup(&playback->xcode);
|
|
|
|
free(playback);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct playback_ctx *
|
|
|
|
playback_new(struct input_source *source, int fd)
|
|
|
|
{
|
|
|
|
struct playback_ctx *playback;
|
|
|
|
|
|
|
|
CHECK_NULL(L_SPOTIFY, playback = calloc(1, sizeof(struct playback_ctx)));
|
|
|
|
CHECK_NULL(L_SPOTIFY, playback->read_buf = evbuffer_new());
|
|
|
|
|
|
|
|
playback->read_fd = fd;
|
|
|
|
playback->len_ms = source->len_ms;
|
|
|
|
|
|
|
|
return playback;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
stop(struct input_source *source)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
struct playback_ctx *playback = source->input_ctx;
|
|
|
|
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
|
|
|
|
|
|
|
if (playback)
|
|
|
|
{
|
|
|
|
// Only need to request stop if spotifyc still has the track open
|
|
|
|
if (ctx->status.track_opened)
|
|
|
|
spotifyc_stop(playback->read_fd);
|
|
|
|
|
|
|
|
playback_free(playback);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (source->evbuf)
|
|
|
|
evbuffer_free(source->evbuf);
|
|
|
|
|
|
|
|
source->input_ctx = NULL;
|
|
|
|
source->evbuf = NULL;
|
|
|
|
|
|
|
|
ctx->status.track_opened = false;
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2018-03-01 11:29:10 -05:00
|
|
|
|
2016-12-28 18:39:23 -05:00
|
|
|
static int
|
2019-02-16 13:34:36 -05:00
|
|
|
setup(struct input_source *source)
|
2016-12-28 18:39:23 -05:00
|
|
|
{
|
2021-03-28 14:00:15 -04:00
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
2016-12-28 18:39:23 -05:00
|
|
|
int ret;
|
2021-03-28 14:00:15 -04:00
|
|
|
int fd;
|
|
|
|
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
2016-12-28 18:39:23 -05:00
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
fd = spotifyc_open(source->path, ctx->session);
|
|
|
|
if (fd < 0)
|
2018-03-01 11:29:10 -05:00
|
|
|
{
|
2021-03-28 14:00:15 -04:00
|
|
|
DPRINTF(E_LOG, L_SPOTIFY, "Could not create fd for Spotify playback\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->response_pending = true;
|
|
|
|
while (ctx->response_pending)
|
|
|
|
pthread_cond_wait(&ctx->cond, &ctx->lock);
|
2018-03-01 11:29:10 -05:00
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
if (!ctx->status.track_opened)
|
|
|
|
{
|
|
|
|
close(fd);
|
|
|
|
goto error;
|
2018-03-01 11:29:10 -05:00
|
|
|
}
|
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
// 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 playback_ctx, which is
|
|
|
|
// done in stop().
|
|
|
|
source->evbuf = evbuffer_new();
|
|
|
|
source->input_ctx = playback_new(source, fd);
|
|
|
|
if (!source->evbuf || !source->input_ctx)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
source->quality = spotify_quality;
|
2016-12-28 18:39:23 -05:00
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
// FIXME This makes sure we get the beginning of the file for ffmpeg to probe,
|
|
|
|
// but it doesn't work well if the player seeks after setup()
|
|
|
|
ret = spotifyc_play(fd);
|
2016-12-28 18:39:23 -05:00
|
|
|
if (ret < 0)
|
2021-03-28 14:00:15 -04:00
|
|
|
goto error;
|
2016-12-28 18:39:23 -05:00
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
2019-02-16 13:34:36 -05:00
|
|
|
return 0;
|
2021-03-28 14:00:15 -04:00
|
|
|
|
|
|
|
error:
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
stop(source);
|
|
|
|
|
|
|
|
return -1;
|
2016-12-28 18:39:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2021-03-28 14:00:15 -04:00
|
|
|
play(struct input_source *source)
|
2016-12-28 18:39:23 -05:00
|
|
|
{
|
2021-03-28 14:00:15 -04:00
|
|
|
struct playback_ctx *playback = source->input_ctx;
|
|
|
|
int got;
|
2016-12-28 18:39:23 -05:00
|
|
|
int ret;
|
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
got = evbuffer_read(playback->read_buf, playback->read_fd, -1);
|
|
|
|
if (got < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
// ffmpeg requires enough data to be able to probe the Ogg
|
|
|
|
playback->read_bytes += got;
|
|
|
|
if (playback->read_bytes < SPOTIFY_PROBE_SIZE_MIN)
|
|
|
|
{
|
|
|
|
input_wait();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!playback->xcode)
|
|
|
|
{
|
|
|
|
ret = playback_xcode_setup(playback);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the Ogg Vorbis to PCM in chunks of 16 packets, which seems to keep
|
|
|
|
// the input buffer nice and full
|
|
|
|
ret = transcode(source->evbuf, NULL, playback->xcode, 16);
|
|
|
|
if (ret == 0)
|
|
|
|
{
|
|
|
|
input_write(source->evbuf, &source->quality, INPUT_FLAG_EOF);
|
|
|
|
stop(source);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (ret < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
// debug_count++;
|
|
|
|
// if (debug_count % 100 == 0)
|
|
|
|
// DPRINTF(E_DBG, L_SPOTIFY, "source->evbuf is %zu, playback->read_buf %zu, got is %d\n",
|
|
|
|
// evbuffer_get_length(source->evbuf), evbuffer_get_length(playback->read_buf), got);
|
|
|
|
|
|
|
|
input_write(source->evbuf, &source->quality, 0);
|
2016-12-28 18:39:23 -05:00
|
|
|
|
|
|
|
return 0;
|
2021-03-28 14:00:15 -04:00
|
|
|
|
|
|
|
error:
|
|
|
|
input_write(NULL, NULL, INPUT_FLAG_ERROR);
|
|
|
|
stop(source);
|
|
|
|
return -1;
|
2016-12-28 18:39:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2019-02-16 13:34:36 -05:00
|
|
|
seek(struct input_source *source, int seek_ms)
|
2016-12-28 18:39:23 -05:00
|
|
|
{
|
2021-03-28 14:00:15 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
init(void)
|
|
|
|
{
|
|
|
|
char *username = NULL;
|
|
|
|
char *db_stored_cred = NULL;
|
|
|
|
size_t db_stored_cred_len;
|
|
|
|
uint8_t *stored_cred = NULL;
|
|
|
|
size_t stored_cred_len;
|
2016-12-28 18:39:23 -05:00
|
|
|
int ret;
|
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
CHECK_ERR(L_SPOTIFY, mutex_init(&spotify_ctx.lock));
|
|
|
|
CHECK_ERR(L_SPOTIFY, pthread_cond_init(&spotify_ctx.cond, NULL));
|
|
|
|
|
|
|
|
ret = spotifyc_init(&callbacks, &spotify_ctx);
|
2016-12-28 18:39:23 -05:00
|
|
|
if (ret < 0)
|
2021-03-28 14:00:15 -04:00
|
|
|
goto error;
|
2016-12-28 18:39:23 -05:00
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
if ( db_admin_get(&username, "spotify_username") < 0 ||
|
|
|
|
db_admin_get(&db_stored_cred, "spotify_stored_cred") < 0 ||
|
|
|
|
!username || !db_stored_cred )
|
|
|
|
goto end; // User not logged in yet
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
spotify_ctx.session = spotifyc_login_stored_cred(username, stored_cred, stored_cred_len);
|
|
|
|
if (!spotify_ctx.session)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(username);
|
|
|
|
free(db_stored_cred);
|
|
|
|
free(stored_cred);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
free(username);
|
|
|
|
free(db_stored_cred);
|
|
|
|
free(stored_cred);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
deinit(void)
|
|
|
|
{
|
|
|
|
spotifyc_deinit();
|
2016-12-28 18:39:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
struct input_definition input_spotify =
|
|
|
|
{
|
|
|
|
.name = "Spotify",
|
|
|
|
.type = INPUT_TYPE_SPOTIFY,
|
|
|
|
.disabled = 0,
|
|
|
|
.setup = setup,
|
|
|
|
.stop = stop,
|
2021-03-28 14:00:15 -04:00
|
|
|
.play = play,
|
2016-12-28 18:39:23 -05:00
|
|
|
.seek = seek,
|
2021-03-28 14:00:15 -04:00
|
|
|
.init = init,
|
|
|
|
.deinit = deinit,
|
2016-12-28 18:39:23 -05:00
|
|
|
};
|
|
|
|
|
2021-03-28 14:00:15 -04:00
|
|
|
|
|
|
|
/* ------------ Functions exposed via spotify.h (foreign threads) ----------- */
|
|
|
|
|
|
|
|
int
|
|
|
|
spotify_login_user(const char *user, const char *password, const char **errmsg)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
|
|
|
|
|
|
|
ctx->response_pending = true;
|
|
|
|
|
|
|
|
ctx->session = spotifyc_login_password(user, password);
|
|
|
|
if (!ctx->session)
|
|
|
|
{
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
*errmsg = "Error creating Spotify session";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (ctx->response_pending)
|
|
|
|
pthread_cond_wait(&ctx->cond, &ctx->lock);
|
|
|
|
|
|
|
|
ret = ctx->status.logged_in ? 0 : -1;
|
|
|
|
if (ret < 0)
|
|
|
|
*errmsg = spotifyc_last_errmsg();
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
spotify_login(char **arglist)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
spotify_logout(void)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
spotify_status_get(struct spotify_status *status)
|
|
|
|
{
|
|
|
|
struct global_ctx *ctx = &spotify_ctx;
|
|
|
|
|
|
|
|
pthread_mutex_lock(&ctx->lock);
|
|
|
|
|
|
|
|
memcpy(status->username, ctx->status.username, sizeof(status->username));
|
|
|
|
status->logged_in = ctx->status.logged_in;
|
|
|
|
status->installed = true;
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&ctx->lock);
|
|
|
|
}
|