From: Bjørn Rustad Date: Tue, 24 Jul 2012 19:42:18 +0000 (+0200) Subject: Initial commit X-Git-Url: http://git.rustad.me/?a=commitdiff_plain;h=ef43010ab940c46640aef3c583c03660b9bbb61f;p=spots Initial commit Working command line interface that lets you view your own playlists, play songs from these, and perform simple searches. --- ef43010ab940c46640aef3c583c03660b9bbb61f 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