Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to Spotify's http based protocol #1855

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/inputs/librespot-c/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,31 @@ PROTO_SRC = \
src/proto/mercury.pb-c.c src/proto/mercury.pb-c.h \
src/proto/metadata.pb-c.c src/proto/metadata.pb-c.h

HTTP_PROTO_SRC = \
src/proto/connectivity.pb-c.c src/proto/connectivity.pb-c.h \
src/proto/clienttoken.pb-c.c src/proto/clienttoken.pb-c.h \
src/proto/login5_user_info.pb-c.h src/proto/login5_user_info.pb-c.c \
src/proto/login5.pb-c.h src/proto/login5.pb-c.c \
src/proto/login5_identifiers.pb-c.h src/proto/login5_identifiers.pb-c.c \
src/proto/login5_credentials.pb-c.h src/proto/login5_credentials.pb-c.c \
src/proto/login5_client_info.pb-c.h src/proto/login5_client_info.pb-c.c \
src/proto/login5_challenges_hashcash.pb-c.h src/proto/login5_challenges_hashcash.pb-c.c \
src/proto/login5_challenges_code.pb-c.h src/proto/login5_challenges_code.pb-c.c \
src/proto/google_duration.pb-c.h src/proto/google_duration.pb-c.c \
src/proto/storage_resolve.pb-c.h src/proto/storage_resolve.pb-c.c

CORE_SRC = \
src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c
src/librespot-c.c src/connection.c src/channel.c src/crypto.c src/commands.c \
src/http.c

librespot_c_a_SOURCES = \
$(CORE_SRC) \
$(SHANNON_SRC) \
$(PROTO_SRC)
$(PROTO_SRC) \
$(HTTP_PROTO_SRC)

noinst_HEADERS = \
librespot-c.h src/librespot-c-internal.h src/connection.h \
src/channel.h src/crypto.h src/commands.h
src/channel.h src/crypto.h src/commands.h src/http.h

EXTRA_DIST = README.md LICENSE
5 changes: 5 additions & 0 deletions src/inputs/librespot-c/configure.ac
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
AC_INIT([librespot-c], [0.1])
AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE([foreign subdir-objects])
AM_SILENT_RULES([yes])

AC_PROG_CC
AM_PROG_AR
AC_PROG_RANLIB

AM_CPPFLAGS="-Wall"
AC_SUBST([AM_CPPFLAGS])

AC_CHECK_HEADERS_ONCE([sys/utsname.h])

AC_CHECK_HEADERS([endian.h sys/endian.h libkern/OSByteOrder.h], [found_endian_headers=yes; break;])
Expand Down
17 changes: 11 additions & 6 deletions src/inputs/librespot-c/librespot-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <pthread.h>

#define LIBRESPOT_C_VERSION_MAJOR 0
#define LIBRESPOT_C_VERSION_MINOR 2
#define LIBRESPOT_C_VERSION_MINOR 4


struct sp_session;
Expand Down Expand Up @@ -37,18 +37,21 @@ struct sp_metadata
size_t file_len;
};

// How to identify towards Spotify. The device_id can be set to an actual value
// identifying the client, but the rest are unfortunately best left as zeroes,
// which will make librespot-c use defaults that spoof whitelisted clients.
struct sp_sysinfo
{
char client_name[16];
char client_id[33];
char client_version[16];
char client_build_id[16];
char device_id[41]; // librespot gives a 20 byte id (so 40 char hex + 1 zero term)
};

struct sp_callbacks
{
// Bring your own https client and tcp connector
int (*https_get)(char **body, const char *url);
// Bring your own tcp connector
int (*tcp_connect)(const char *address, unsigned short port);
void (*tcp_disconnect)(int fd);

Expand All @@ -60,10 +63,9 @@ struct sp_callbacks
void (*logmsg)(const char *fmt, ...);
};



// Deprecated, use login_token and login_stored_cred instead
struct sp_session *
librespotc_login_password(const char *username, const char *password);
librespotc_login_password(const char *username, const char *password) __attribute__ ((deprecated));

struct sp_session *
librespotc_login_stored_cred(const char *username, uint8_t *stored_cred, size_t stored_cred_len);
Expand All @@ -74,6 +76,9 @@ librespotc_login_token(const char *username, const char *token);
int
librespotc_logout(struct sp_session *session);

int
librespotc_legacy_set(struct sp_session *session, int use_legacy);

int
librespotc_bitrate_set(struct sp_session *session, enum sp_bitrates bitrate);

Expand Down
63 changes: 42 additions & 21 deletions src/inputs/librespot-c/src/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ channel_get(uint32_t channel_id, struct sp_session *session)
void
channel_free(struct sp_channel *channel)
{
int i;

if (!channel || channel->state == SP_CHANNEL_STATE_UNALLOCATED)
return;

Expand All @@ -82,6 +84,9 @@ channel_free(struct sp_channel *channel)

free(channel->file.path);

for (i = 0; i < ARRAY_SIZE(channel->file.cdnurl); i++)
free(channel->file.cdnurl[i]);

memset(channel, 0, sizeof(struct sp_channel));

channel->audio_fd[0] = -1;
Expand Down Expand Up @@ -208,7 +213,8 @@ channel_seek_internal(struct sp_channel *channel, size_t pos, bool do_flush)
channel->seek_pos = pos;

// If seek + header isn't word aligned we will get up to 3 bytes before the
// actual seek position. We will remove those when they are received.
// actual seek position with the legacy protocol. We will remove those when
// they are received.
channel->seek_align = (pos + SP_OGG_HEADER_LEN) % 4;

seek_words = (pos + SP_OGG_HEADER_LEN) / 4;
Expand All @@ -218,8 +224,8 @@ channel_seek_internal(struct sp_channel *channel, size_t pos, bool do_flush)
RETURN_ERROR(SP_ERR_DECRYPTION, sp_errmsg);

// Set the offset and received counter to match the seek
channel->file.offset_words = seek_words;
channel->file.received_words = seek_words;
channel->file.offset_bytes = 4 * seek_words;
channel->file.received_bytes = 4 * seek_words;

return 0;

Expand All @@ -241,22 +247,22 @@ channel_pause(struct sp_channel *channel)
channel->state = SP_CHANNEL_STATE_PAUSED;
}

// After a disconnect we connect to another one and try to resume. To make that
// work some data elements need to be reset.
// After a disconnect we connect to another AP and try to resume. To make that
// work during playback some data elements need to be reset.
void
channel_retry(struct sp_channel *channel)
{
size_t pos;

if (!channel)
if (!channel || channel->state != SP_CHANNEL_STATE_PLAYING)
return;

channel->is_data_mode = false;

memset(&channel->header, 0, sizeof(struct sp_channel_header));
memset(&channel->body, 0, sizeof(struct sp_channel_body));

pos = 4 * channel->file.received_words - SP_OGG_HEADER_LEN;
pos = channel->file.received_bytes - SP_OGG_HEADER_LEN;

channel_seek_internal(channel, pos, false); // false => don't flush
}
Expand Down Expand Up @@ -316,12 +322,12 @@ channel_header_handle(struct sp_channel *channel, struct sp_channel_header *head
}

memcpy(&be32, header->data, sizeof(be32));
channel->file.len_words = be32toh(be32);
channel->file.len_bytes = 4 * be32toh(be32);
}
}

static ssize_t
channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len, struct sp_session *session)
channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len)
{
ssize_t parsed_len;
ssize_t consumed_len;
Expand All @@ -333,10 +339,10 @@ channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg
if (msg_len == 0)
{
channel->file.end_of_chunk = true;
channel->file.end_of_file = (channel->file.received_words >= channel->file.len_words);
channel->file.end_of_file = (channel->file.received_bytes >= channel->file.len_bytes);

// In preparation for next chunk
channel->file.offset_words += SP_CHUNK_LEN_WORDS;
channel->file.offset_bytes += SP_CHUNK_LEN;
channel->is_data_mode = false;

return 0;
Expand Down Expand Up @@ -369,22 +375,19 @@ channel_header_trailer_read(struct sp_channel *channel, uint8_t *msg, size_t msg
return ret;
}

static ssize_t
channel_data_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len, struct sp_session *session)
static int
channel_data_read(struct sp_channel *channel, uint8_t *msg, size_t msg_len)
{
const char *errmsg;
int ret;

assert (msg_len % 4 == 0);

channel->file.received_words += msg_len / 4;
channel->file.received_bytes += msg_len;

ret = crypto_aes_decrypt(msg, msg_len, &channel->file.decrypt, &errmsg);
if (ret < 0)
RETURN_ERROR(SP_ERR_DECRYPTION, errmsg);

// Skip Spotify header
// TODO What to do here when seeking
if (!channel->is_spotify_header_received)
{
if (msg_len < SP_OGG_HEADER_LEN)
Expand Down Expand Up @@ -464,7 +467,7 @@ channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_s
msg_len -= sizeof(be);

// Will set data_mode, end_of_file and end_of_chunk as appropriate
consumed_len = channel_header_trailer_read(channel, msg, msg_len, session);
consumed_len = channel_header_trailer_read(channel, msg, msg_len);
if (consumed_len < 0)
RETURN_ERROR((int)consumed_len, sp_errmsg);

Expand All @@ -477,13 +480,31 @@ channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_s
if (!channel->is_data_mode || !(msg_len > 0))
return 0; // Not in data mode or no data to read

consumed_len = channel_data_read(channel, msg, msg_len, session);
if (consumed_len < 0)
RETURN_ERROR((int)consumed_len, sp_errmsg);
ret = channel_data_read(channel, msg, msg_len);
if (ret < 0)
RETURN_ERROR(ret, sp_errmsg);

return 0;

error:
return ret;
}

// With http there is the Spotify Ogg header, but no chunk header/trailer
int
channel_http_body_read(struct sp_channel *channel, uint8_t *body, size_t body_len)
{
int ret;

ret = channel_data_read(channel, body, body_len);
if (ret < 0)
goto error;

channel->file.end_of_chunk = true;
channel->file.end_of_file = (channel->file.received_bytes >= channel->file.len_bytes);
channel->file.offset_bytes += SP_CHUNK_LEN;
return 0;

error:
return ret;
}
3 changes: 3 additions & 0 deletions src/inputs/librespot-c/src/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ channel_retry(struct sp_channel *channel);

int
channel_msg_read(uint16_t *channel_id, uint8_t *msg, size_t msg_len, struct sp_session *session);

int
channel_http_body_read(struct sp_channel *channel, uint8_t *body, size_t body_len);
Loading
Loading