--- /dev/null
+*.o
+spots
+*.swp
+.cache/
+.settings/
+libspotify-12.1.51-Linux-x86_64-release/
+libspotify-12.1.51-Linux-x86_64-release.tar.gz
+libspotify/
--- /dev/null
+TARGET = spots
+CFLAGS += -I./libspotify/include -g
+CFLAGS += $(shell pkg-config --cflags libpulse-simple)
+LDFLAGS += -Wl,-rpath,./libspotify/lib -L./libspotify/lib
+LDLIBS += -lspotify -lpthread -lreadline -lncurses
+LDLIBS += $(shell pkg-config --libs libpulse-simple)
+
+OBJS += spots.o appkey.o command.o audio.o
+
+$(TARGET): $(OBJS)
+ $(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
+
+.PHONY: all clean
+
+clean:
+ rm -f spots *.o
--- /dev/null
+#include <stdint.h>
+#include <stdlib.h>
+#include "appkey.h"
+
+const uint8_t g_appkey[] = {
+ 0x01, 0x85, 0x53, 0xC5, 0x34, 0x57, 0xD3, 0xF5, 0xD9, 0x3D, 0xE9, 0xA4, 0xF3, 0x27, 0x98, 0xBD,
+ 0x69, 0xA7, 0x72, 0x9D, 0x54, 0x70, 0x1E, 0xFF, 0x18, 0xA7, 0xFD, 0x2F, 0x5C, 0x7A, 0xB9, 0x6E,
+ 0x8F, 0x16, 0x13, 0xB4, 0x39, 0x22, 0x77, 0xAB, 0xD7, 0xFE, 0xE2, 0x9C, 0xA8, 0x00, 0x9B, 0x76,
+ 0x9D, 0x10, 0xED, 0x09, 0x73, 0xEC, 0x7E, 0x28, 0xDA, 0x2D, 0x06, 0x54, 0x92, 0x85, 0x4E, 0x65,
+ 0xCC, 0x70, 0xB4, 0xF8, 0xD7, 0xEF, 0x6A, 0x22, 0x03, 0x4C, 0x9B, 0xD5, 0x65, 0x0B, 0x8E, 0xA0,
+ 0x66, 0xA6, 0x76, 0x95, 0xEC, 0x4E, 0x8F, 0x5D, 0xCB, 0xEC, 0x28, 0x4C, 0xBD, 0x19, 0x3D, 0xFC,
+ 0xB3, 0x12, 0x07, 0x71, 0x0A, 0x31, 0x0A, 0x28, 0x4B, 0xC2, 0x10, 0x1B, 0x8B, 0x85, 0xF6, 0x92,
+ 0xB8, 0x3A, 0x9B, 0x42, 0xBA, 0xAB, 0x1A, 0xFE, 0x1C, 0x94, 0x7B, 0x66, 0x9D, 0x20, 0xE3, 0xF1,
+ 0x5A, 0x63, 0xC9, 0xA9, 0x1C, 0x3C, 0x11, 0xB3, 0x25, 0xFC, 0x52, 0xFC, 0xA6, 0x0E, 0xF6, 0x9B,
+ 0x75, 0x4F, 0x23, 0xB3, 0xCF, 0xDE, 0xC5, 0x78, 0x91, 0x0F, 0xD8, 0xFC, 0xBA, 0x80, 0xF4, 0x1A,
+ 0x8A, 0x1D, 0xF5, 0x95, 0x93, 0xD4, 0xA4, 0xA7, 0xC8, 0x80, 0x15, 0x98, 0x02, 0xD4, 0xAD, 0xC9,
+ 0xB3, 0xAB, 0x51, 0xD5, 0x92, 0x6C, 0xF8, 0xC2, 0x32, 0x39, 0x5F, 0x30, 0x5F, 0x5F, 0xB7, 0xD6,
+ 0x52, 0xE7, 0x8E, 0xB5, 0x00, 0x3B, 0xA7, 0x09, 0xF1, 0xFB, 0x43, 0xFE, 0x9E, 0xE9, 0xD2, 0x54,
+ 0xFB, 0x94, 0x33, 0xA2, 0x83, 0x7F, 0xE2, 0xE2, 0x1C, 0x17, 0x9E, 0x79, 0xD3, 0xDE, 0xFA, 0x12,
+ 0xD9, 0x5D, 0x9F, 0xED, 0x63, 0xA8, 0x59, 0x8F, 0x70, 0x0C, 0x6C, 0xD2, 0x15, 0xF8, 0x1A, 0xCE,
+ 0xE9, 0xEF, 0x1C, 0x7B, 0xE0, 0x37, 0x3B, 0xFE, 0x2E, 0x7F, 0x60, 0xD2, 0x30, 0xAB, 0x97, 0xE3,
+ 0xCF, 0x1C, 0x9D, 0x23, 0x97, 0x69, 0x3B, 0x6A, 0x88, 0xDA, 0x1D, 0xC5, 0xC9, 0xAE, 0x9E, 0x6B,
+ 0xE3, 0xC1, 0x4E, 0xAE, 0x98, 0xE1, 0x96, 0xEF, 0x9E, 0x7D, 0x07, 0x74, 0x56, 0xD5, 0xFC, 0x56,
+ 0x7E, 0x2C, 0xA0, 0x05, 0xF4, 0xB1, 0x3E, 0x0A, 0xE9, 0x93, 0x08, 0xF5, 0x08, 0x9A, 0xA7, 0xC7,
+ 0x66, 0x98, 0x7B, 0x4B, 0x88, 0xEE, 0x0A, 0x7F, 0x20, 0x43, 0xA9, 0x4C, 0xF9, 0x76, 0xEF, 0x1D,
+ 0x80,
+};
+const size_t g_appkey_size = sizeof(g_appkey);
--- /dev/null
+#ifndef _APPKEY_H
+#define _APPKEY_H
+
+extern const uint8_t g_appkey[];
+extern const size_t g_appkey_size;
+
+#endif
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include "spots.h"
+#include "audio.h"
+
+int play;
+
+pa_simple *pa_handle;
+
+pthread_mutex_t audio_q_mutex;
+pthread_cond_t audio_q_cond;
+
+struct audio_q_head head;
+
+void play_or_pause (int p)
+{
+ sp_error err;
+
+ fprintf(stderr, "trying to play: %d\n", p);
+
+ err = sp_session_player_play(session, p);
+ pthread_mutex_lock(&audio_q_mutex);
+ play = p;
+ pthread_cond_signal(&audio_q_cond);
+ pthread_mutex_unlock(&audio_q_mutex);
+
+ if (err != SP_ERROR_OK) {
+ fprintf(stderr, "Error pausing: %s\n", sp_error_message(err));
+ }
+}
+
+void audio_q_element_free (struct audio_q_element *e1)
+{
+ free(e1->samples);
+ free(e1);
+}
+
+void audio_q_empty (void)
+{
+ struct audio_q_element *e1, *e2;
+
+ e1 = TAILQ_FIRST(&head);
+ while (e1 != NULL) {
+ e2 = TAILQ_NEXT(e1, entries);
+ audio_q_element_free(e1);
+ e1 = e2;
+ }
+ TAILQ_INIT(&head);
+}
+
+void *audio_thread_function (void *attr)
+{
+ int pa_err;
+ int res;
+ struct audio_q_element *aqe;
+
+ pthread_mutex_lock(&audio_q_mutex);
+ while (1) {
+ aqe = head.tqh_first;
+
+ while (aqe == NULL || !play) {
+ pthread_cond_wait(&audio_q_cond, &audio_q_mutex);
+ aqe = head.tqh_first;
+ }
+
+ pthread_mutex_unlock(&audio_q_mutex);
+
+ res = pa_simple_write(
+ pa_handle,
+ aqe->samples,
+ aqe->num_samples * aqe->channels * 2,
+ &pa_err);
+
+ if (res < 0) {
+ fprintf(stderr, "pa_simple_write failed: %s\n",
+ pa_strerror(pa_err));
+ }
+
+ pthread_mutex_lock(&audio_q_mutex);
+ TAILQ_REMOVE(&head, head.tqh_first, entries);
+ audio_q_element_free(aqe);
+ }
+}
+
+void start_audio_thread (void)
+{
+ int audio_thread_id;
+ pthread_t audio_thread;
+
+ audio_thread_id = pthread_create(
+ &audio_thread,
+ NULL,
+ &audio_thread_function,
+ NULL);
+}
+
--- /dev/null
+#ifndef _AUDIO_H
+#define _AUDIO_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+
+TAILQ_HEAD(audio_q_head, audio_q_element);
+
+extern struct audio_q_head head;
+
+extern pthread_mutex_t audio_q_mutex;
+extern pthread_cond_t audio_q_cond;
+
+extern int play;
+
+extern pa_simple *pa_handle;
+
+struct audio_q_element {
+ void *samples;
+ int num_samples;
+ int sample_rate;
+ int channels;
+ TAILQ_ENTRY(audio_q_element) entries;
+};
+
+void play_or_pause (int p);
+
+void audio_q_element_free (struct audio_q_element *e1);
+void audio_q_empty (void);
+
+void *audio_thread_function (void *attr);
+void start_audio_thread (void);
+
+#endif
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libspotify/api.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include "spots.h"
+#include "command.h"
+#include "audio.h"
+
+void *input_thread_function (void *attr)
+{
+ char *l;
+
+ pthread_mutex_lock(&input_output_mutex);
+ while (1) {
+ while (!show_prompt)
+ pthread_cond_wait(&input_cond, &input_output_mutex);
+
+ l = readline("> ");
+ show_prompt = 0;
+ pthread_mutex_unlock(&input_output_mutex);
+
+ pthread_mutex_lock(¬ify_main_thread_mutex);
+ cmd_line = l;
+ main_thread_notified = 1;
+ pthread_cond_signal(¬ify_main_thread_cond);
+ pthread_mutex_unlock(¬ify_main_thread_mutex);
+
+ pthread_mutex_lock(&input_output_mutex);
+ }
+}
+
+void start_input_thread (void)
+{
+ int input_thread_id;
+ pthread_t input_thread;
+
+ input_thread_id = pthread_create(
+ &input_thread,
+ NULL,
+ &input_thread_function,
+ NULL);
+}
+
+void tokenize (char *cmd, int *argc_ptr, char ***argv_ptr)
+{
+ int argc;
+ int argv_len;
+ char **argv;
+ char *saveptr;
+
+ argc = 0;
+ argv_len = 1;
+ argv = malloc(sizeof(char *) * argv_len);
+ argv[0] = strtok_r(cmd, " ", &saveptr);
+ while (argv[argc] != NULL) {
+ argc++;
+
+ if (argv_len <= argc) {
+ argv_len *= 2;
+ argv = realloc(argv, sizeof(char *) * argv_len);
+ }
+ if (argv == NULL) {
+ fprintf(stderr, "Error allocating space for argv.\n");
+ argc = 0;
+ argv = NULL;
+ break;
+ }
+
+ argv[argc] = strtok_r(NULL, " ", &saveptr);
+ }
+
+ *argc_ptr = argc;
+ *argv_ptr = argv;
+}
+
+void parse_and_exec (char *cmd)
+{
+ int i;
+ int argc;
+ int argv_len;
+ int done;
+ char **argv;
+
+ pthread_mutex_lock(&input_output_mutex);
+ tokenize(cmd, &argc, &argv);
+
+ if (argv == NULL) {
+ return;
+ }
+
+ done = -1;
+ for (i = 0; i < commands_length; ++i) {
+ if (strcmp(commands[i].cmd, cmd) == 0) {
+ done = commands[i].func(argc, argv);
+ break;
+ }
+ }
+
+ if (done == -1) {
+ fprintf(stderr, "Unknown command %s\n", cmd);
+ }
+ if (done) {
+ show_prompt = 1;
+ }
+
+ fflush(stdout);
+ free(argv);
+
+ pthread_cond_signal(&input_cond);
+ pthread_mutex_unlock(&input_output_mutex);
+}
+
+
+int cmd_print_username (int argc, char *argv[])
+{
+ printf("%s\n", sp_session_user_name(session));
+
+ return 1;
+}
+
+int cmd_print_country (int argc, char *argv[])
+{
+ int country = sp_session_user_country(session);
+
+ printf("%c%c\n", country >> 8, country & 0xff);
+
+ return 1;
+}
+
+int cmd_print_playlists (int argc, char *argv[])
+{
+ int i;
+ sp_playlistcontainer *pc;
+ sp_playlist *p;
+
+ pc = sp_session_playlistcontainer(session);
+ for (i = 0; i < sp_playlistcontainer_num_playlists(pc); ++i) {
+ p = sp_playlistcontainer_playlist(pc, i);
+ printf("%d: %s\n", i, sp_playlist_name(p));
+ }
+
+ return 1;
+}
+
+int cmd_start_playlist_track (int argc, char *argv[])
+{
+ int p_idx;
+ int t_idx;
+
+ sp_error err;
+ sp_playlistcontainer *pc;
+ sp_playlist *p;
+ sp_track *t;
+
+ if (argc < 3) {
+ fprintf(stderr, "Wrong number of arguments.\n");
+ return 1;
+ }
+
+ pc = sp_session_playlistcontainer(session);
+
+ p_idx = atoi(argv[1]);
+ t_idx = atoi(argv[2]);
+
+ if (p_idx < 0 || p_idx >= sp_playlistcontainer_num_playlists(pc)) {
+ fprintf(stderr, "Invalid playlist number.\n");
+ return 1;
+ }
+
+ p = sp_playlistcontainer_playlist(pc, p_idx);
+
+ if (t_idx < 0 || t_idx >= sp_playlist_num_tracks(p)) {
+ fprintf(stderr, "Invalid track number.\n");
+ return 1;
+ }
+
+ t = sp_playlist_track(p, t_idx);
+
+ play_or_pause(0);
+ pthread_mutex_lock(&audio_q_mutex);
+ audio_q_empty();
+ pthread_mutex_unlock(&audio_q_mutex);
+
+ err = sp_session_player_load(session, t);
+ if (err != SP_ERROR_OK) {
+ fprintf(stderr, "Error loading track: %s\n", sp_error_message(err));
+ return 1;
+ }
+
+ play_or_pause(1);
+
+ printf("Playing %s by ", sp_track_name(t));
+ print_track_artists(t);
+ printf("\n");
+
+ return 1;
+}
+
+int cmd_print_playlist (int argc, char *argv[])
+{
+ int i, j;
+ int num;
+ sp_playlistcontainer *pc;
+ sp_playlist *p;
+ sp_track *t;
+
+ if (argc < 2) {
+ return 1;
+ }
+
+ pc = sp_session_playlistcontainer(session);
+
+ num = atoi(argv[1]);
+ if (num < 0 || num >= sp_playlistcontainer_num_playlists(pc)) {
+ return 1;
+ }
+
+ p = sp_playlistcontainer_playlist(pc, num);
+ for (i = 0; i < sp_playlist_num_tracks(p); ++i) {
+ t = sp_playlist_track(p, i);
+ print_track(t);
+ printf("\n");
+ }
+
+ return 1;
+}
+
+int cmd_search (int argc, char *argv[])
+{
+ char *query;
+ sp_search *search;
+
+ query = argv[1];
+ sp_search_create(
+ session,
+ query,
+ 0, // track offset? offset among tracks?
+ 10, // track count? number of tracks?
+ 0, // album offset?
+ 10, // album count?
+ 0, // artist offset?
+ 10, // artist count?
+ 0, // playlist offset?
+ 10, // playlist count?
+ SP_SEARCH_STANDARD, // can also be SP_SEARCH_SUGGEST
+ &cb_search_complete,
+ NULL // userdata
+ );
+
+ printf("Thank you for your search.\n");
+
+ return 0;
+}
+
+int cmd_logout (int argc, char *argv[])
+{
+ sp_error err;
+
+ printf("Logging out...\n");
+ err = sp_session_logout(session);
+
+ if (err != SP_ERROR_OK) {
+ fprintf(stderr, "Error logging out: %s.\n",
+ sp_error_message(err));
+ }
+
+ pthread_mutex_lock(¬ify_main_thread_mutex);
+
+ log_out = 1;
+ main_thread_notified = 1;
+
+ pthread_cond_signal(¬ify_main_thread_cond);
+ pthread_mutex_unlock(¬ify_main_thread_mutex);
+
+ return 0;
+}
+
+int cmd_pause (int argc, char *argv[])
+{
+ play_or_pause(0);
+
+ return 1;
+}
+
+int cmd_play (int argc, char *argv[])
+{
+ play_or_pause(1);
+
+ return 1;
+}
+
+command_t commands[] = {
+ { "whoami", &cmd_print_username },
+ { "country", &cmd_print_country },
+ { "playlists", &cmd_print_playlists },
+ { "playlist", &cmd_print_playlist },
+ { "start", &cmd_start_playlist_track },
+ { "play", &cmd_play },
+ { "pause", &cmd_pause },
+ { "search", &cmd_search },
+ { "quit", &cmd_logout },
+};
+
+int commands_length = sizeof(commands)/sizeof(command_t);
--- /dev/null
+#ifndef _COMMAND_H
+#define _COMMAND_H
+
+typedef struct {
+ char *cmd;
+ int (*func)(int argc, char *argv[]);
+} command_t;
+
+extern command_t commands[];
+
+extern int commands_length;
+
+int cmd_print_username (int argc, char *argv[]);
+int cmd_print_country (int argc, char *argv[]);
+int cmd_print_playlists (int argc, char *argv[]);
+int cmd_start_playlist_track (int argc, char *argv[]);
+int cmd_print_playlist (int argc, char *argv[]);
+int cmd_search (int argc, char *argv[]);
+int cmd_logout (int argc, char *argv[]);
+int cmd_pause (int argc, char *argv[]);
+int cmd_play (int argc, char *argv[]);
+
+void *input_thread_function (void *attr);
+void start_input_thread (void);
+void tokenize (char *cmd, int *argc_ptr, char ***argv_ptr);
+void parse_and_exec (char *cmd);
+
+#endif
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include <ncurses.h>
+
+#include <pthread.h>
+
+#include <sys/queue.h>
+
+#include <libspotify/api.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+
+#include "spots.h"
+#include "appkey.h"
+#include "command.h"
+#include "audio.h"
+
+pthread_mutex_t notify_main_thread_mutex;
+pthread_cond_t notify_main_thread_cond;
+
+pthread_mutex_t input_output_mutex;
+pthread_cond_t input_cond;
+
+int main_thread_notified;
+int show_prompt;
+int log_out;
+char *cmd_line;
+
+sp_session *session;
+
+void print_track_artists (sp_track *t)
+{
+ int i;
+ sp_artist *a;
+
+ for (i = 0; i < sp_track_num_artists(t); ++i) {
+ a = sp_track_artist(t, i);
+ printf("%s", sp_artist_name(a));
+ if (i < sp_track_num_artists(t) - 1) {
+ printf(", ");
+ }
+ }
+}
+
+void print_track (sp_track *track)
+{
+ printf("%s\t", sp_track_name(track));
+ print_track_artists(track);
+}
+
+void print_artist (sp_artist *artist)
+{
+ printf("%s", sp_artist_name(artist));
+}
+
+void print_album (sp_album *album)
+{
+ printf("%s", sp_album_name(album));
+}
+
+void print_search (sp_search *search)
+{
+ int i;
+ sp_artist *artist;
+ sp_album *album;
+ sp_track *track;
+ const char *dym;
+
+ printf("The search results are in!\n");
+
+ dym = sp_search_did_you_mean(search);
+ if (dym != NULL && dym[0] != '\0') {
+ printf("Did you mean: %s?\n", dym);
+ }
+
+ printf("=== Tracks ===\n");
+ for (i = 0; i < sp_search_num_tracks(search); ++i) {
+ track = sp_search_track(search, i);
+ print_track(track);
+ printf("\n");
+ }
+
+ printf("=== Artists ===\n");
+ for (i = 0; i < sp_search_num_artists(search); ++i) {
+ artist = sp_search_artist(search, i);
+ print_artist(artist);
+ printf("\n");
+ }
+
+ printf("=== Albums ===\n");
+ for (i = 0; i < sp_search_num_artists(search); ++i) {
+ album = sp_search_album(search, i);
+ print_album(album);
+ printf("\n");
+ }
+}
+
+void trim_end (char *str)
+{
+ char *s = str;
+ while (*s != '\0') ++s;
+
+ while (s != str && isspace(*(s - 1))) --s;
+ *s = '\0';
+}
+
+void cb_search_complete (sp_search *search, void *userdata)
+{
+ pthread_mutex_lock(&input_output_mutex);
+
+ if (sp_search_error(search) != SP_ERROR_OK) {
+ fprintf(stderr, "Search failed: %s\n",
+ sp_error_message(sp_search_error(search)));
+ return;
+ }
+
+ print_search(search);
+
+ show_prompt = 1;
+ pthread_cond_signal(&input_cond);
+ pthread_mutex_unlock(&input_output_mutex);
+
+ sp_search_release(search);
+}
+
+void cb_logged_in (sp_session *session, sp_error error)
+{
+ if (error == SP_ERROR_OK) {
+ fprintf(stderr, "DEBUG: Login successful.\n");
+ } else {
+ fprintf(stderr, "DEBUG: Login failed: %s.\n",
+ sp_error_message(error));
+ exit(3);
+ }
+
+ pthread_mutex_lock(&input_output_mutex);
+ show_prompt = 1;
+ pthread_cond_signal(&input_cond);
+ pthread_mutex_unlock(&input_output_mutex);
+}
+
+void cb_logged_out (sp_session *session)
+{
+ fprintf(stderr, "DEBUG: Logged out.\n");
+}
+
+void cb_notify_main_thread (sp_session *session)
+{
+ pthread_mutex_lock(¬ify_main_thread_mutex);
+ main_thread_notified = 1;
+ pthread_cond_signal(¬ify_main_thread_cond);
+ pthread_mutex_unlock(¬ify_main_thread_mutex);
+}
+
+void cb_connection_error (sp_session *session, sp_error error)
+{
+ fprintf(stderr, "Connection error: %s.\n", sp_error_message(error));
+}
+
+int cb_music_delivery (sp_session *session, const sp_audioformat *format,
+ const void *frames, int num_frames)
+{
+ int pa_err;
+
+ struct audio_q_element *aqe;
+
+ fprintf(stderr, "music delivery\n");
+
+ aqe = malloc(sizeof(struct audio_q_element));
+ aqe->num_samples = num_frames;
+ aqe->sample_rate = format->sample_rate;
+ aqe->channels = format->channels;
+ aqe->samples = malloc(num_frames * format->channels * 2);
+
+ memcpy(aqe->samples, frames, num_frames * format->channels * 2);
+
+ pthread_mutex_lock(&audio_q_mutex);
+ TAILQ_INSERT_TAIL(&head, aqe, entries);
+ pthread_cond_signal(&audio_q_cond);
+ pthread_mutex_unlock(&audio_q_mutex);
+
+ return num_frames;
+}
+
+static void finish (int sig)
+{
+ endwin();
+
+ exit(0);
+}
+
+int main (int argc, char *argv[])
+{
+ sp_error err;
+ char username[128];
+ char password[128];
+ int next_timeout;
+ int pa_err;
+ struct timespec ts;
+
+ sp_session_callbacks session_callbacks = {
+ .logged_in = &cb_logged_in,
+ .logged_out = &cb_logged_out,
+ .metadata_updated = NULL,
+ .connection_error = &cb_connection_error,
+ .message_to_user = NULL,
+ .notify_main_thread = &cb_notify_main_thread,
+ .music_delivery = &cb_music_delivery,
+ .play_token_lost = NULL,
+ .log_message = NULL,
+ .end_of_track = NULL,
+ .streaming_error = NULL,
+ .userinfo_updated = NULL,
+ .start_playback = NULL,
+ .stop_playback = NULL,
+ .get_audio_buffer_stats = NULL,
+ .offline_status_updated = NULL,
+ .offline_error = NULL,
+ .credentials_blob_updated = NULL,
+ .connectionstate_updated = NULL,
+ .scrobble_error = NULL,
+ .private_session_mode_changed = NULL,
+ };
+
+ static const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+ };
+
+ sp_session_config session_config;
+
+ memset(&session_config, 0, sizeof(session_config));
+
+ session_config.api_version = SPOTIFY_API_VERSION;
+ session_config.cache_location = ".cache";
+ session_config.settings_location = ".settings";
+ session_config.application_key = g_appkey;
+ session_config.application_key_size = g_appkey_size;
+ session_config.user_agent = "spshell";
+ session_config.callbacks = &session_callbacks;
+
+ pthread_mutex_init(¬ify_main_thread_mutex, NULL);
+ pthread_mutex_init(&input_output_mutex, NULL);
+ pthread_mutex_init(&audio_q_mutex, NULL);
+ pthread_cond_init(¬ify_main_thread_cond, NULL);
+ pthread_cond_init(&input_cond, NULL);
+ pthread_cond_init(&audio_q_cond, NULL);
+
+ pthread_mutex_lock(¬ify_main_thread_mutex);
+ main_thread_notified = 1;
+ pthread_mutex_unlock(¬ify_main_thread_mutex);
+
+ pthread_mutex_lock(&input_output_mutex);
+ show_prompt = 0;
+ pthread_mutex_unlock(&input_output_mutex);
+
+ pthread_mutex_lock(&audio_q_mutex);
+ play = 0;
+ pthread_mutex_unlock(&audio_q_mutex);
+
+ err = sp_session_create(&session_config, &session);
+ if (err != SP_ERROR_OK) {
+ fprintf(stderr, "Could not create session: %s.",
+ sp_error_message(err));
+ return 1;
+ }
+
+ printf("Please enter your username: ");
+ fgets(username, 128, stdin);
+ trim_end(username);
+ printf("Please enter your password: ");
+ fgets(password, 128, stdin);
+ trim_end(password);
+
+ err = sp_session_login(session, username, password, 0, NULL);
+ if (err != SP_ERROR_OK) {
+ fprintf(stderr, "Could not log in: %s.",
+ sp_error_message(err));
+ return 2;
+ }
+
+ start_input_thread();
+ start_audio_thread();
+
+ pthread_mutex_lock(&audio_q_mutex);
+ TAILQ_INIT(&head);
+ pa_handle = pa_simple_new(
+ NULL,
+ "spots",
+ PA_STREAM_PLAYBACK,
+ NULL,
+ "lamename",
+ &ss,
+ NULL,
+ NULL,
+ &pa_err);
+ pthread_mutex_unlock(&audio_q_mutex);
+
+ if (pa_handle == NULL) {
+ fprintf(stderr, "Error creating pa_handle: %s\n",
+ pa_strerror(pa_err));
+ return 3;
+ }
+
+ pthread_mutex_lock(¬ify_main_thread_mutex);
+ next_timeout = 0;
+ while (!log_out) {
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += next_timeout / 1000;
+
+ if (!main_thread_notified && !cmd_line) {
+ pthread_cond_timedwait(
+ ¬ify_main_thread_cond,
+ ¬ify_main_thread_mutex,
+ &ts);
+ }
+ main_thread_notified = 0;
+
+ if (cmd_line) {
+ char *l = cmd_line;
+ cmd_line = NULL;
+
+ pthread_mutex_unlock(¬ify_main_thread_mutex);
+ parse_and_exec(l);
+ free(l);
+ } else {
+ pthread_mutex_unlock(¬ify_main_thread_mutex);
+ }
+
+ do {
+ err = sp_session_process_events(session, &next_timeout);
+ if (err != SP_ERROR_OK) {
+ fprintf(stderr, "sp_session_process_events failed: %s.\n",
+ sp_error_message(err));
+ return 3;
+ }
+
+ } while (next_timeout == 0);
+
+ pthread_mutex_lock(¬ify_main_thread_mutex);
+ }
+ pthread_mutex_unlock(¬ify_main_thread_mutex);
+
+ play_or_pause(0);
+
+ /* TODO: Kill the other threads? */
+ pa_simple_free(pa_handle);
+ sp_session_release(session);
+
+ return 0;
+}
--- /dev/null
+#ifndef _SPOTS_H
+#define _SPOTS_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+
+#include <sys/queue.h>
+
+#include <libspotify/api.h>
+
+extern pthread_mutex_t notify_main_thread_mutex;
+extern pthread_cond_t notify_main_thread_cond;
+
+extern pthread_mutex_t input_output_mutex;
+extern pthread_cond_t input_cond;
+
+extern int main_thread_notified;
+extern int show_prompt;
+extern int log_out;
+extern char *cmd_line;
+
+extern sp_session *session;
+
+void trim_end (char *str);
+void print_track_artists (sp_track *t);
+void print_track (sp_track *track);
+void print_artist (sp_artist *artist);
+void print_album (sp_album *album);
+void print_search (sp_search *search);
+
+void cb_search_complete (sp_search *search, void *userdata);
+void cb_logged_in (sp_session *session, sp_error error);
+void cb_logged_out (sp_session *session);
+void cb_notify_main_thread (sp_session *session);
+void cb_connection_error (sp_session *session, sp_error error);
+int cb_music_delivery (sp_session *session, const sp_audioformat *format,
+ const void *frames, int num_frames);
+
+void *input_thread_function (void *attr);
+void start_input_thread (void);
+void tokenize (char *cmd, int *argc_ptr, char ***argv_ptr);
+void parse_and_exec (char *cmd);
+
+#endif