From ef43010ab940c46640aef3c583c03660b9bbb61f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Rustad?= Date: Tue, 24 Jul 2012 21:42:18 +0200 Subject: [PATCH 1/1] Initial commit Working command line interface that lets you view your own playlists, play songs from these, and perform simple searches. --- .gitignore | 8 ++ Makefile | 16 +++ appkey.c | 28 +++++ appkey.h | 7 ++ audio.c | 99 +++++++++++++++ audio.h | 39 ++++++ command.c | 308 +++++++++++++++++++++++++++++++++++++++++++++ command.h | 28 +++++ spots.c | 356 +++++++++++++++++++++++++++++++++++++++++++++++++++++ spots.h | 49 ++++++++ 10 files changed, 938 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 appkey.c create mode 100644 appkey.h create mode 100644 audio.c create mode 100644 audio.h create mode 100644 command.c create mode 100644 command.h create mode 100644 spots.c create mode 100644 spots.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1133a11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.o +spots +*.swp +.cache/ +.settings/ +libspotify-12.1.51-Linux-x86_64-release/ +libspotify-12.1.51-Linux-x86_64-release.tar.gz +libspotify/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..25fb284 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +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 diff --git a/appkey.c b/appkey.c new file mode 100644 index 0000000..f59b6a8 --- /dev/null +++ b/appkey.c @@ -0,0 +1,28 @@ +#include +#include +#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); diff --git a/appkey.h b/appkey.h new file mode 100644 index 0000000..ea87004 --- /dev/null +++ b/appkey.h @@ -0,0 +1,7 @@ +#ifndef _APPKEY_H +#define _APPKEY_H + +extern const uint8_t g_appkey[]; +extern const size_t g_appkey_size; + +#endif diff --git a/audio.c b/audio.c new file mode 100644 index 0000000..1b468f7 --- /dev/null +++ b/audio.c @@ -0,0 +1,99 @@ +#include +#include + +#include + +#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); +} + diff --git a/audio.h b/audio.h new file mode 100644 index 0000000..c14a03f --- /dev/null +++ b/audio.h @@ -0,0 +1,39 @@ +#ifndef _AUDIO_H +#define _AUDIO_H + +#include +#include + +#include + +#include +#include + +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 diff --git a/command.c b/command.c new file mode 100644 index 0000000..66920a8 --- /dev/null +++ b/command.c @@ -0,0 +1,308 @@ +#include +#include + +#include + +#include +#include + +#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); diff --git a/command.h b/command.h new file mode 100644 index 0000000..fdad1e3 --- /dev/null +++ b/command.h @@ -0,0 +1,28 @@ +#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 diff --git a/spots.c b/spots.c new file mode 100644 index 0000000..5413e0d --- /dev/null +++ b/spots.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +#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; +} diff --git a/spots.h b/spots.h new file mode 100644 index 0000000..6d0fe4e --- /dev/null +++ b/spots.h @@ -0,0 +1,49 @@ +#ifndef _SPOTS_H +#define _SPOTS_H + +#include +#include + +#include + +#include +#include + +#include + +#include + +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 -- 2.47.3