[input] Add Spotify input module

This commit is contained in:
ejurgensen 2016-12-29 00:39:23 +01:00
parent c92ebf9dfb
commit 79639c73ed
7 changed files with 161 additions and 202 deletions

View File

@ -6,7 +6,7 @@ ITUNES_SRC=library/filescanner_itunes.c
endif
if COND_SPOTIFY
SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h
SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h inputs/spotify.c
endif
if COND_LASTFM

View File

@ -46,11 +46,17 @@
extern struct input_definition input_file;
extern struct input_definition input_http;
#ifdef HAVE_SPOTIFY_H
extern struct input_definition input_spotify;
#endif
// Must be in sync with enum input_types
static struct input_definition *inputs[] = {
&input_file,
&input_http,
#ifdef HAVE_SPOTIFY_H
&input_spotify,
#endif
NULL
};
@ -124,6 +130,11 @@ map_data_kind(int data_kind)
case DATA_KIND_HTTP:
return INPUT_TYPE_HTTP;
#ifdef HAVE_SPOTIFY_H
case DATA_KIND_SPOTIFY:
return INPUT_TYPE_SPOTIFY;
#endif
default:
return -1;
}
@ -185,6 +196,15 @@ playback(void *arg)
pthread_exit(NULL);
}
void
input_wait(void)
{
pthread_mutex_lock(&input_buffer.mutex);
pthread_cond_wait(&input_buffer.cond, &input_buffer.mutex);
pthread_mutex_unlock(&input_buffer.mutex);
}
// Called by input modules from within the playback loop
int
input_write(struct evbuffer *evbuf, short flags)
@ -333,6 +353,7 @@ input_pause(struct player_source *ps)
pthread_cond_signal(&input_buffer.cond);
pthread_mutex_unlock(&input_buffer.mutex);
// TODO What if input thread is hanging waiting for source? Kill thread?
ret = pthread_join(tid_input, NULL);
if (ret != 0)
{

View File

@ -2,6 +2,9 @@
#ifndef __INPUT_H__
#define __INPUT_H__
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <event2/buffer.h>
#include "transcode.h"
@ -10,6 +13,9 @@ enum input_types
{
INPUT_TYPE_FILE,
INPUT_TYPE_HTTP,
#ifdef HAVE_SPOTIFY_H
INPUT_TYPE_SPOTIFY,
#endif
};
enum input_flags
@ -96,7 +102,7 @@ struct input_definition
};
/*
* Input modules use this to test if playback should be stopped or seeked
* Input modules should use this to test if playback should end
*/
int input_loop_break;
@ -114,6 +120,13 @@ int input_loop_break;
int
input_write(struct evbuffer *evbuf, short flags);
/*
* Input modules can use this to wait in the playback loop (like input_write()
* would have done)
*/
void
input_wait(void);
/*
* Move a chunk of stream data from the player's input buffer to an output
* buffer. Should only be called by the player thread. Will not block.

96
src/inputs/spotify.c Normal file
View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2017 Espen Jurgensen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include "input.h"
#include "spotify.h"
static int
setup(struct player_source *ps)
{
int ret;
ret = spotify_playback_setup(ps->path);
if (ret < 0)
return -1;
ps->setup_done = 1;
return 0;
}
static int
start(struct player_source *ps)
{
int ret;
ret = spotify_playback_play();
if (ret < 0)
return -1;
while (!input_loop_break)
{
input_wait();
}
ret = spotify_playback_pause();
return ret;
}
static int
stop(struct player_source *ps)
{
int ret;
ret = spotify_playback_stop();
if (ret < 0)
return -1;
ps->setup_done = 0;
return 0;
}
static int
seek(struct player_source *ps, int seek_ms)
{
int ret;
ret = spotify_playback_seek(seek_ms);
if (ret < 0)
return -1;
return ret;
}
struct input_definition input_spotify =
{
.name = "Spotify",
.type = INPUT_TYPE_SPOTIFY,
.disabled = 0,
.setup = setup,
.start = start,
.stop = stop,
.seek = seek,
};

View File

@ -1097,9 +1097,9 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
return -1;
}
// if (ret == 0)
//TODO Underrun -> pause
if ((ret < 0) || (flags & INPUT_FLAG_EOF))
if (ret == 0)
sleep(1); // TODO Underrun -> proper pause
else if ((ret < 0) || (flags & INPUT_FLAG_EOF))
{
source_close(rtptime + BTOS(nbytes) - 1);

View File

@ -55,6 +55,7 @@
#include "cache.h"
#include "commands.h"
#include "library.h"
#include "input.h"
/* TODO for the web api:
* - UI should be prettier
@ -88,22 +89,6 @@
#define SPOTIFY_WEB_REQUESTS_MAX 20
/* --- Types --- */
typedef struct audio_fifo_data
{
TAILQ_ENTRY(audio_fifo_data) link;
int nsamples;
int16_t samples[0];
} audio_fifo_data_t;
typedef struct audio_fifo
{
TAILQ_HEAD(, audio_fifo_data) q;
int qlen;
int fullcount;
pthread_mutex_t mutex;
pthread_cond_t cond;
} audio_fifo_t;
enum spotify_state
{
SPOTIFY_STATE_INACTIVE,
@ -114,12 +99,6 @@ enum spotify_state
SPOTIFY_STATE_STOPPED,
};
struct audio_get_param
{
struct evbuffer *evbuf;
int wanted;
};
struct artwork_get_param
{
struct evbuffer *evbuf;
@ -164,8 +143,8 @@ static int spotify_saved_plid;
// Flag to avoid triggering playlist change events while the (re)scan is running
static bool scanning;
// Audio fifo
static audio_fifo_t *g_audio_fifo;
// Audio buffer
static struct evbuffer *spotify_audio_buffer;
/**
* The application key is specific to forked-daapd, and allows Spotify
@ -1180,25 +1159,6 @@ mk_reltime(struct timespec *ts, time_t sec)
ts->tv_sec += sec;
}
static void
audio_fifo_flush(void)
{
audio_fifo_data_t *afd;
DPRINTF(E_DBG, L_SPOTIFY, "Flushing audio fifo\n");
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex));
while((afd = TAILQ_FIRST(&g_audio_fifo->q))) {
TAILQ_REMOVE(&g_audio_fifo->q, afd, link);
free(afd);
}
g_audio_fifo->qlen = 0;
g_audio_fifo->fullcount = 0;
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
}
static enum command_state
playback_setup(void *arg, int *retval)
{
@ -1240,8 +1200,6 @@ playback_setup(void *arg, int *retval)
return COMMAND_END;
}
audio_fifo_flush();
*retval = 0;
return COMMAND_END;
}
@ -1330,8 +1288,6 @@ playback_seek(void *arg, int *retval)
return COMMAND_END;
}
audio_fifo_flush();
*retval = 0;
return COMMAND_END;
}
@ -1353,92 +1309,14 @@ playback_eot(void *arg, int *retval)
g_state = SPOTIFY_STATE_STOPPING;
// TODO 1) This will block for a while, but perhaps ok?
// 2) spotify_audio_buffer not entirely thread safe here (but unlikely risk...)
input_write(spotify_audio_buffer, INPUT_FLAG_EOF);
*retval = 0;
return COMMAND_END;
}
static enum command_state
audio_get(void *arg, int *retval)
{
struct audio_get_param *audio;
struct timespec ts;
audio_fifo_data_t *afd;
int processed;
int timeout;
int ret;
int err;
int s;
audio = (struct audio_get_param *) arg;
afd = NULL;
processed = 0;
// If spotify was paused begin by resuming playback
if (g_state == SPOTIFY_STATE_PAUSED)
playback_play(NULL, retval);
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex));
while ((processed < audio->wanted) && (g_state != SPOTIFY_STATE_STOPPED))
{
// If track has ended and buffer is empty
if ((g_state == SPOTIFY_STATE_STOPPING) && (g_audio_fifo->qlen <= 0))
{
DPRINTF(E_DBG, L_SPOTIFY, "Track finished\n");
g_state = SPOTIFY_STATE_STOPPED;
break;
}
// If buffer is empty, wait for audio, but use timed wait so we don't
// risk waiting forever (maybe the player stopped while we were waiting)
timeout = 0;
while ( !(afd = TAILQ_FIRST(&g_audio_fifo->q)) &&
(g_state != SPOTIFY_STATE_STOPPED) &&
(g_state != SPOTIFY_STATE_STOPPING) &&
(timeout < SPOTIFY_TIMEOUT) )
{
DPRINTF(E_DBG, L_SPOTIFY, "Waiting for audio\n");
timeout += 5;
mk_reltime(&ts, 5);
CHECK_ERR_EXCEPT(L_SPOTIFY, pthread_cond_timedwait(&g_audio_fifo->cond, &g_audio_fifo->mutex, &ts), err, ETIMEDOUT);
}
if ((!afd) && (timeout >= SPOTIFY_TIMEOUT))
{
DPRINTF(E_LOG, L_SPOTIFY, "Timeout waiting for audio (waited %d sec)\n", timeout);
spotify_playback_stop_nonblock();
}
if (!afd)
break;
TAILQ_REMOVE(&g_audio_fifo->q, afd, link);
g_audio_fifo->qlen -= afd->nsamples;
s = afd->nsamples * sizeof(int16_t) * 2;
ret = evbuffer_add(audio->evbuf, afd->samples, s);
free(afd);
afd = NULL;
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for evbuffer (tried to add %d bytes)\n", s);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
*retval = -1;
return COMMAND_END;
}
processed += s;
}
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
*retval = processed;
return COMMAND_END;
}
static void
artwork_loaded_cb(sp_image *image, void *userdata)
{
@ -1681,8 +1559,8 @@ logged_out(sp_session *sess)
static int music_delivery(sp_session *sess, const sp_audioformat *format,
const void *frames, int num_frames)
{
audio_fifo_data_t *afd;
size_t s;
size_t size;
int ret;
/* No support for resampling right now */
if ((format->sample_rate != 44100) || (format->channels != 2))
@ -1692,44 +1570,26 @@ static int music_delivery(sp_session *sess, const sp_audioformat *format,
return num_frames;
}
// Audio discontinuity, e.g. seek
if (num_frames == 0)
return 0; // Audio discontinuity, do nothing
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex));
/* Buffer three seconds of audio */
if (g_audio_fifo->qlen > (3 * format->sample_rate))
{
// If the buffer has been full the last 300 times (~about a minute) we
// assume the player thread paused/died without telling us, so we signal pause
if (g_audio_fifo->fullcount < 300)
g_audio_fifo->fullcount++;
else
{
DPRINTF(E_WARN, L_SPOTIFY, "Buffer full more than 300 times, pausing\n");
spotify_playback_pause_nonblock();
g_audio_fifo->fullcount = 0;
}
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
evbuffer_drain(spotify_audio_buffer, evbuffer_get_length(spotify_audio_buffer));
return 0;
}
else
g_audio_fifo->fullcount = 0;
s = num_frames * sizeof(int16_t) * format->channels;
size = num_frames * sizeof(int16_t) * format->channels;
afd = malloc(sizeof(*afd) + s);
memcpy(afd->samples, frames, s);
ret = evbuffer_add(spotify_audio_buffer, frames, size);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory adding audio to buffer\n");
return num_frames;
}
afd->nsamples = num_frames;
TAILQ_INSERT_TAIL(&g_audio_fifo->q, afd, link);
g_audio_fifo->qlen += num_frames;
CHECK_ERR(L_SPOTIFY, pthread_cond_signal(&g_audio_fifo->cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex));
// The input buffer only accepts writing when it is approaching depletion, and
// because we use NONBLOCK it will just return if this is not the case. So in
// most cases no actual write is made and spotify_audio_buffer will just grow.
input_write(spotify_audio_buffer, INPUT_FLAG_NONBLOCK);
return num_frames;
}
@ -1975,18 +1835,6 @@ spotify_playback_seek(int ms)
return -1;
}
/* Thread: player */
int
spotify_audio_get(struct evbuffer *evbuf, int wanted)
{
struct audio_get_param audio;
audio.evbuf = evbuf;
audio.wanted = wanted;
return commands_exec_sync(cmdbase, audio_get, NULL, &audio);
}
/* Thread: httpd (artwork) and worker */
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
@ -2613,7 +2461,6 @@ spotify_init(void)
event_add(g_notifyev, NULL);
cmdbase = commands_base_new(evbase_spotify, exit_cb);
if (!cmdbase)
{
@ -2651,17 +2498,7 @@ spotify_init(void)
break;
}
/* Prepare audio buffer */
g_audio_fifo = (audio_fifo_t *)malloc(sizeof(audio_fifo_t));
if (!g_audio_fifo)
{
DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for audio buffer\n");
goto audio_fifo_fail;
}
TAILQ_INIT(&g_audio_fifo->q);
g_audio_fifo->qlen = 0;
CHECK_ERR(L_SPOTIFY, mutex_init(&g_audio_fifo->mutex));
CHECK_ERR(L_SPOTIFY, pthread_cond_init(&g_audio_fifo->cond, NULL));
spotify_audio_buffer = evbuffer_new();
CHECK_ERR(L_SPOTIFY, mutex_init(&login_lck));
CHECK_ERR(L_SPOTIFY, pthread_cond_init(&login_cond, NULL));
@ -2687,11 +2524,8 @@ spotify_init(void)
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck));
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&g_audio_fifo->cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&g_audio_fifo->mutex));
free(g_audio_fifo);
evbuffer_free(spotify_audio_buffer);
audio_fifo_fail:
fptr_sp_session_release(g_sess);
g_sess = NULL;
@ -2751,10 +2585,8 @@ spotify_deinit(void)
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck));
/* Clear audio fifo */
CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&g_audio_fifo->cond));
CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&g_audio_fifo->mutex));
free(g_audio_fifo);
/* Free audio buffer */
evbuffer_free(spotify_audio_buffer);
/* Release libspotify handle */
dlclose(g_libhandle);

View File

@ -27,9 +27,6 @@ spotify_playback_stop_nonblock(void);
int
spotify_playback_seek(int ms);
int
spotify_audio_get(struct evbuffer *evbuf, int wanted);
int
spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);