libspotify
jukebox.c

The jukebox.c example shows how you can use playback and playlist functions.

#include <errno.h>
#include <libgen.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <libspotify/api.h>
#include "audio.h"
/* --- Data --- */
extern const uint8_t g_appkey[];
extern const size_t g_appkey_size;
static audio_fifo_t g_audiofifo;
static pthread_mutex_t g_notify_mutex;
static pthread_cond_t g_notify_cond;
static int g_notify_do;
static int g_playback_done;
static sp_session *g_sess;
static sp_playlist *g_jukeboxlist;
const char *g_listname;
static int g_remove_tracks = 0;
static sp_track *g_currenttrack;
static int g_track_index;
static void try_jukebox_start(void)
{
if (!g_jukeboxlist)
return;
if (!sp_playlist_num_tracks(g_jukeboxlist)) {
fprintf(stderr, "jukebox: No tracks in playlist. Waiting\n");
return;
}
if (sp_playlist_num_tracks(g_jukeboxlist) < g_track_index) {
fprintf(stderr, "jukebox: No more tracks in playlist. Waiting\n");
return;
}
t = sp_playlist_track(g_jukeboxlist, g_track_index);
if (g_currenttrack && t != g_currenttrack) {
/* Someone changed the current track */
audio_fifo_flush(&g_audiofifo);
g_currenttrack = NULL;
}
if (!t)
return;
return;
if (g_currenttrack == t)
return;
g_currenttrack = t;
printf("jukebox: Now playing \"%s\"...\n", sp_track_name(t));
fflush(stdout);
}
/* -------------------------- PLAYLIST CALLBACKS ------------------------- */
static void tracks_added(sp_playlist *pl, sp_track * const *tracks,
int num_tracks, int position, void *userdata)
{
if (pl != g_jukeboxlist)
return;
printf("jukebox: %d tracks were added\n", num_tracks);
fflush(stdout);
try_jukebox_start();
}
static void tracks_removed(sp_playlist *pl, const int *tracks,
int num_tracks, void *userdata)
{
int i, k = 0;
if (pl != g_jukeboxlist)
return;
for (i = 0; i < num_tracks; ++i)
if (tracks[i] < g_track_index)
++k;
g_track_index -= k;
printf("jukebox: %d tracks were removed\n", num_tracks);
fflush(stdout);
try_jukebox_start();
}
static void tracks_moved(sp_playlist *pl, const int *tracks,
int num_tracks, int new_position, void *userdata)
{
if (pl != g_jukeboxlist)
return;
printf("jukebox: %d tracks were moved around\n", num_tracks);
fflush(stdout);
try_jukebox_start();
}
static void playlist_renamed(sp_playlist *pl, void *userdata)
{
const char *name = sp_playlist_name(pl);
if (!strcasecmp(name, g_listname)) {
g_jukeboxlist = pl;
g_track_index = 0;
try_jukebox_start();
} else if (g_jukeboxlist == pl) {
printf("jukebox: current playlist renamed to \"%s\".\n", name);
g_jukeboxlist = NULL;
g_currenttrack = NULL;
}
}
static sp_playlist_callbacks pl_callbacks = {
.tracks_added = &tracks_added,
.tracks_removed = &tracks_removed,
.tracks_moved = &tracks_moved,
.playlist_renamed = &playlist_renamed,
};
/* -------------------- PLAYLIST CONTAINER CALLBACKS --------------------- */
static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
int position, void *userdata)
{
sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
if (!strcasecmp(sp_playlist_name(pl), g_listname)) {
g_jukeboxlist = pl;
try_jukebox_start();
}
}
static void playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl,
int position, void *userdata)
{
sp_playlist_remove_callbacks(pl, &pl_callbacks, NULL);
}
static void container_loaded(sp_playlistcontainer *pc, void *userdata)
{
fprintf(stderr, "jukebox: Rootlist synchronized (%d playlists)\n",
}
static sp_playlistcontainer_callbacks pc_callbacks = {
.playlist_added = &playlist_added,
.playlist_removed = &playlist_removed,
.container_loaded = &container_loaded,
};
/* --------------------------- SESSION CALLBACKS ------------------------- */
static void logged_in(sp_session *sess, sp_error error)
{
int i;
if (SP_ERROR_OK != error) {
fprintf(stderr, "jukebox: Login failed: %s\n",
exit(2);
}
pc,
&pc_callbacks,
NULL);
printf("jukebox: Looking at %d playlists\n", sp_playlistcontainer_num_playlists(pc));
for (i = 0; i < sp_playlistcontainer_num_playlists(pc); ++i) {
sp_playlist_add_callbacks(pl, &pl_callbacks, NULL);
if (!strcasecmp(sp_playlist_name(pl), g_listname)) {
g_jukeboxlist = pl;
try_jukebox_start();
}
}
if (!g_jukeboxlist) {
printf("jukebox: No such playlist. Waiting for one to pop up...\n");
fflush(stdout);
}
}
static void notify_main_thread(sp_session *sess)
{
pthread_mutex_lock(&g_notify_mutex);
g_notify_do = 1;
pthread_cond_signal(&g_notify_cond);
pthread_mutex_unlock(&g_notify_mutex);
}
static int music_delivery(sp_session *sess, const sp_audioformat *format,
const void *frames, int num_frames)
{
audio_fifo_t *af = &g_audiofifo;
audio_fifo_data_t *afd;
size_t s;
if (num_frames == 0)
return 0; // Audio discontinuity, do nothing
pthread_mutex_lock(&af->mutex);
/* Buffer one second of audio */
if (af->qlen > format->sample_rate) {
pthread_mutex_unlock(&af->mutex);
return 0;
}
s = num_frames * sizeof(int16_t) * format->channels;
afd = malloc(sizeof(*afd) + s);
memcpy(afd->samples, frames, s);
afd->nsamples = num_frames;
afd->rate = format->sample_rate;
afd->channels = format->channels;
TAILQ_INSERT_TAIL(&af->q, afd, link);
af->qlen += num_frames;
pthread_cond_signal(&af->cond);
pthread_mutex_unlock(&af->mutex);
return num_frames;
}
static void end_of_track(sp_session *sess)
{
pthread_mutex_lock(&g_notify_mutex);
g_playback_done = 1;
g_notify_do = 1;
pthread_cond_signal(&g_notify_cond);
pthread_mutex_unlock(&g_notify_mutex);
}
static void metadata_updated(sp_session *sess)
{
try_jukebox_start();
}
static void play_token_lost(sp_session *sess)
{
audio_fifo_flush(&g_audiofifo);
if (g_currenttrack != NULL) {
g_currenttrack = NULL;
}
}
static sp_session_callbacks session_callbacks = {
.logged_in = &logged_in,
.notify_main_thread = &notify_main_thread,
.music_delivery = &music_delivery,
.metadata_updated = &metadata_updated,
.play_token_lost = &play_token_lost,
.log_message = NULL,
.end_of_track = &end_of_track,
};
static sp_session_config spconfig = {
.cache_location = "tmp",
.settings_location = "tmp",
.application_key = g_appkey,
.application_key_size = 0, // Set in main()
.user_agent = "spotify-jukebox-example",
.callbacks = &session_callbacks,
NULL,
};
/* ------------------------- END SESSION CALLBACKS ----------------------- */
static void track_ended(void)
{
int tracks = 0;
if (g_currenttrack) {
g_currenttrack = NULL;
if (g_remove_tracks) {
sp_playlist_remove_tracks(g_jukeboxlist, &tracks, 1);
} else {
++g_track_index;
try_jukebox_start();
}
}
}
static void usage(const char *progname)
{
fprintf(stderr, "usage: %s -u <username> -p <password> -l <listname> [-d]\n", progname);
fprintf(stderr, "warning: -d will delete the tracks played from the list!\n");
}
int main(int argc, char **argv)
{
sp_error err;
int next_timeout = 0;
const char *username = NULL;
const char *password = NULL;
int opt;
while ((opt = getopt(argc, argv, "u:p:l:d")) != EOF) {
switch (opt) {
case 'u':
username = optarg;
break;
case 'p':
password = optarg;
break;
case 'l':
g_listname = optarg;
break;
case 'd':
g_remove_tracks = 1;
break;
default:
exit(1);
}
}
if (!username || !password || !g_listname) {
usage(basename(argv[0]));
exit(1);
}
audio_init(&g_audiofifo);
/* Create session */
spconfig.application_key_size = g_appkey_size;
err = sp_session_create(&spconfig, &sp);
if (SP_ERROR_OK != err) {
fprintf(stderr, "Unable to create session: %s\n",
exit(1);
}
g_sess = sp;
pthread_mutex_init(&g_notify_mutex, NULL);
pthread_cond_init(&g_notify_cond, NULL);
sp_session_login(sp, username, password, 0, NULL);
pthread_mutex_lock(&g_notify_mutex);
for (;;) {
if (next_timeout == 0) {
while(!g_notify_do)
pthread_cond_wait(&g_notify_cond, &g_notify_mutex);
} else {
struct timespec ts;
#if _POSIX_TIMERS > 0
clock_gettime(CLOCK_REALTIME, &ts);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
TIMEVAL_TO_TIMESPEC(&tv, &ts);
#endif
ts.tv_sec += next_timeout / 1000;
ts.tv_nsec += (next_timeout % 1000) * 1000000;
pthread_cond_timedwait(&g_notify_cond, &g_notify_mutex, &ts);
}
g_notify_do = 0;
pthread_mutex_unlock(&g_notify_mutex);
if (g_playback_done) {
track_ended();
g_playback_done = 0;
}
do {
sp_session_process_events(sp, &next_timeout);
} while (next_timeout == 0);
pthread_mutex_lock(&g_notify_mutex);
}
return 0;
}

Generated on Tue Jun 4 2013 15:11:46.
Copyright © 2006–2013 Spotify Ltd