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"
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;
const char *g_listname;
static int g_remove_tracks = 0;
static int g_track_index;
static void try_jukebox_start(void)
{
if (!g_jukeboxlist)
return;
fprintf(stderr, "jukebox: No tracks in playlist. Waiting\n");
return;
}
fprintf(stderr, "jukebox: No more tracks in playlist. Waiting\n");
return;
}
if (g_currenttrack && t != g_currenttrack) {
audio_fifo_flush(&g_audiofifo);
g_currenttrack = NULL;
}
if (!t)
return;
return;
if (g_currenttrack == t)
return;
g_currenttrack = t;
fflush(stdout);
}
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)
{
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;
}
}
.tracks_removed = &tracks_removed,
.tracks_moved = &tracks_moved,
.playlist_renamed = &playlist_renamed,
};
int position, void *userdata)
{
g_jukeboxlist = pl;
try_jukebox_start();
}
}
int position, void *userdata)
{
}
{
fprintf(stderr, "jukebox: Rootlist synchronized (%d playlists)\n",
}
.playlist_removed = &playlist_removed,
.container_loaded = &container_loaded,
};
{
int i;
fprintf(stderr, "jukebox: Login failed: %s\n",
exit(2);
}
pc,
&pc_callbacks,
NULL);
g_jukeboxlist = pl;
try_jukebox_start();
}
}
if (!g_jukeboxlist) {
printf("jukebox: No such playlist. Waiting for one to pop up...\n");
fflush(stdout);
}
}
{
pthread_mutex_lock(&g_notify_mutex);
g_notify_do = 1;
pthread_cond_signal(&g_notify_cond);
pthread_mutex_unlock(&g_notify_mutex);
}
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;
pthread_mutex_lock(&af->mutex);
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;
TAILQ_INSERT_TAIL(&af->q, afd, link);
af->qlen += num_frames;
pthread_cond_signal(&af->cond);
pthread_mutex_unlock(&af->mutex);
return num_frames;
}
{
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);
}
{
try_jukebox_start();
}
{
audio_fifo_flush(&g_audiofifo);
if (g_currenttrack != NULL) {
g_currenttrack = NULL;
}
}
.notify_main_thread = ¬ify_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,
};
.cache_location = "tmp",
.settings_location = "tmp",
.application_key = g_appkey,
.application_key_size = 0,
.user_agent = "spotify-jukebox-example",
.callbacks = &session_callbacks,
NULL,
};
static void track_ended(void)
{
int tracks = 0;
if (g_currenttrack) {
g_currenttrack = NULL;
if (g_remove_tracks) {
} 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)
{
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);
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);
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 {
} while (next_timeout == 0);
pthread_mutex_lock(&g_notify_mutex);
}
return 0;
}