mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-25 04:19:15 -05:00
[input] Add input interface to player - WIP
This commit is contained in:
parent
c50b038397
commit
3e24f857fa
@ -108,6 +108,8 @@ forked_daapd_SOURCES = main.c \
|
|||||||
daap_query.c daap_query.h \
|
daap_query.c daap_query.h \
|
||||||
player.c player.h \
|
player.c player.h \
|
||||||
worker.c worker.h \
|
worker.c worker.h \
|
||||||
|
input.h input.c \
|
||||||
|
inputs/file_http.c \
|
||||||
outputs.h outputs.c \
|
outputs.h outputs.c \
|
||||||
outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \
|
outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \
|
||||||
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
|
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \
|
||||||
|
470
src/input.c
Normal file
470
src/input.c
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include <event2/event.h>
|
||||||
|
#include <event2/buffer.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#ifdef HAVE_PTHREAD_NP_H
|
||||||
|
# include <pthread_np.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "input.h"
|
||||||
|
|
||||||
|
// Disallow further writes to the buffer when its size is larger than this threshold
|
||||||
|
#define INPUT_BUFFER_THRESHOLD STOB(44100)
|
||||||
|
|
||||||
|
#define DEBUG 1 //TODO disable
|
||||||
|
|
||||||
|
extern struct input_definition input_file;
|
||||||
|
extern struct input_definition input_http;
|
||||||
|
|
||||||
|
// Must be in sync with enum input_types
|
||||||
|
static struct input_definition *inputs[] = {
|
||||||
|
&input_file,
|
||||||
|
&input_http,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
struct input_buffer
|
||||||
|
{
|
||||||
|
// Raw pcm stream data
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
|
||||||
|
// If non-zero, remaining length of buffer until EOF
|
||||||
|
size_t eof;
|
||||||
|
// If non-zero, remaining length of buffer until (possible) new metadata
|
||||||
|
size_t metadata;
|
||||||
|
|
||||||
|
// Locks for sharing the buffer between input and player thread
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* --- Globals --- */
|
||||||
|
// Input thread
|
||||||
|
static pthread_t tid_input;
|
||||||
|
|
||||||
|
// Input buffer
|
||||||
|
static struct input_buffer input_buffer;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
static size_t debug_elapsed;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------ MISC HELPERS ---------------------------- */
|
||||||
|
|
||||||
|
static short
|
||||||
|
flags_set(size_t len)
|
||||||
|
{
|
||||||
|
short flags = 0;
|
||||||
|
|
||||||
|
if (input_buffer.eof)
|
||||||
|
{
|
||||||
|
if (len >= input_buffer.eof)
|
||||||
|
{
|
||||||
|
flags |= INPUT_FLAG_EOF;
|
||||||
|
input_buffer.eof = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
input_buffer.eof -= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_buffer.metadata)
|
||||||
|
{
|
||||||
|
if (len >= input_buffer.metadata)
|
||||||
|
{
|
||||||
|
flags |= INPUT_FLAG_METADATA;
|
||||||
|
input_buffer.metadata = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
input_buffer.metadata -= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
map_data_kind(int data_kind)
|
||||||
|
{
|
||||||
|
switch (data_kind)
|
||||||
|
{
|
||||||
|
case DATA_KIND_FILE:
|
||||||
|
return INPUT_TYPE_FILE;
|
||||||
|
|
||||||
|
case DATA_KIND_HTTP:
|
||||||
|
return INPUT_TYPE_HTTP;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
source_check_and_map(struct player_source *ps, const char *action, char check_setup)
|
||||||
|
{
|
||||||
|
int type;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
DPRINTF(E_DBG, L_PLAYER, "Action is %s\n", action);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!ps)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Stream %s called with invalid player source\n", action);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_setup && !ps->setup_done)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, %s not possible\n", action);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = map_data_kind(ps->data_kind);
|
||||||
|
if (type < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Unsupported input type, %s not possible\n", action);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------- PLAYBACK LOOP ---------------------------- */
|
||||||
|
/* Thread: input */
|
||||||
|
|
||||||
|
// TODO Thread safety of ps? Do we need the return of the loop?
|
||||||
|
static void *
|
||||||
|
playback(void *arg)
|
||||||
|
{
|
||||||
|
struct player_source *ps = arg;
|
||||||
|
int type;
|
||||||
|
|
||||||
|
type = source_check_and_map(ps, "start", 1);
|
||||||
|
if ((type < 0) || (inputs[type]->disabled))
|
||||||
|
goto thread_exit;
|
||||||
|
|
||||||
|
// Loops until input_loop_break is set or no more input, e.g. EOF
|
||||||
|
inputs[type]->start(ps);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
DPRINTF(E_DBG, L_PLAYER, "Playback loop stopped (break is %d)\n", input_loop_break);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
thread_exit:
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by input modules from within the playback loop
|
||||||
|
int
|
||||||
|
input_write(struct evbuffer *evbuf, short flags)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
while ( (!input_loop_break) && (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) )
|
||||||
|
{
|
||||||
|
if (flags & INPUT_FLAG_NONBLOCK)
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock(&input_buffer.mutex);
|
||||||
|
return EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_cond_wait(&input_buffer.cond, &input_buffer.mutex);
|
||||||
|
|
||||||
|
// TODO protect against infinite looping and waiting?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input_loop_break)
|
||||||
|
{
|
||||||
|
ret = evbuffer_add_buffer(input_buffer.evbuf, evbuf);
|
||||||
|
if (ret < 0)
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Error adding stream data to input buffer\n");
|
||||||
|
|
||||||
|
if (!input_buffer.eof && (flags & INPUT_FLAG_EOF))
|
||||||
|
input_buffer.eof = evbuffer_get_length(input_buffer.evbuf);
|
||||||
|
if (!input_buffer.metadata && (flags & INPUT_FLAG_METADATA))
|
||||||
|
input_buffer.metadata = evbuffer_get_length(input_buffer.evbuf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------- Interface towards player thread ------------------- */
|
||||||
|
/* Thread: player */
|
||||||
|
|
||||||
|
int
|
||||||
|
input_read(struct evbuffer *evbuf, size_t want, short *flags)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
|
||||||
|
*flags = 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
debug_elapsed += want;
|
||||||
|
if (debug_elapsed > STOB(441000)) // 10 sec
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_PLAYER, "Input buffer has %zu bytes\n", evbuffer_get_length(input_buffer.evbuf));
|
||||||
|
debug_elapsed = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
len = evbuffer_remove_buffer(input_buffer.evbuf, evbuf, want);
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Error reading stream data from input buffer\n");
|
||||||
|
goto out_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
*flags = flags_set(len);
|
||||||
|
|
||||||
|
out_unlock:
|
||||||
|
pthread_cond_signal(&input_buffer.cond);
|
||||||
|
pthread_mutex_unlock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
input_setup(struct player_source *ps)
|
||||||
|
{
|
||||||
|
int type;
|
||||||
|
|
||||||
|
type = source_check_and_map(ps, "setup", 0);
|
||||||
|
if ((type < 0) || (inputs[type]->disabled))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!inputs[type]->setup)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return inputs[type]->setup(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
input_start(struct player_source *ps)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (tid_input)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Bug! Input start called, but playback already running\n");
|
||||||
|
input_pause(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
input_loop_break = 0;
|
||||||
|
|
||||||
|
ret = pthread_create(&tid_input, NULL, playback, ps);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Could not spawn input thread: %s\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(HAVE_PTHREAD_SETNAME_NP)
|
||||||
|
pthread_setname_np(tid_input, "input");
|
||||||
|
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
|
||||||
|
pthread_set_name_np(tid_input, "input");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
input_pause(struct player_source *ps)
|
||||||
|
{
|
||||||
|
short flags;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
DPRINTF(E_DBG, L_PLAYER, "Pause called, stopping playback loop\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!tid_input)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
input_loop_break = 1;
|
||||||
|
|
||||||
|
pthread_cond_signal(&input_buffer.cond);
|
||||||
|
pthread_mutex_unlock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
ret = pthread_join(tid_input, NULL);
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Could not join input thread: %s\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tid_input = 0;
|
||||||
|
|
||||||
|
input_flush(&flags);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
input_stop(struct player_source *ps)
|
||||||
|
{
|
||||||
|
int type;
|
||||||
|
|
||||||
|
if (tid_input)
|
||||||
|
input_pause(ps);
|
||||||
|
|
||||||
|
if (!ps)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
type = source_check_and_map(ps, "stop", 1);
|
||||||
|
if ((type < 0) || (inputs[type]->disabled))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!inputs[type]->stop)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return inputs[type]->stop(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
input_seek(struct player_source *ps, int seek_ms)
|
||||||
|
{
|
||||||
|
int type;
|
||||||
|
|
||||||
|
type = source_check_and_map(ps, "seek", 1);
|
||||||
|
if ((type < 0) || (inputs[type]->disabled))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!inputs[type]->seek)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (tid_input)
|
||||||
|
input_pause(ps);
|
||||||
|
|
||||||
|
return inputs[type]->seek(ps, seek_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
input_flush(short *flags)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
len = evbuffer_get_length(input_buffer.evbuf);
|
||||||
|
|
||||||
|
evbuffer_drain(input_buffer.evbuf, len);
|
||||||
|
|
||||||
|
*flags = flags_set(len);
|
||||||
|
|
||||||
|
input_buffer.eof = 0;
|
||||||
|
input_buffer.metadata = 0;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&input_buffer.mutex);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
DPRINTF(E_DBG, L_PLAYER, "Flush with flags %d\n", *flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
input_init(void)
|
||||||
|
{
|
||||||
|
int no_input;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// Prepare input buffer
|
||||||
|
pthread_mutex_init(&input_buffer.mutex, NULL);
|
||||||
|
pthread_cond_init(&input_buffer.cond, NULL);
|
||||||
|
|
||||||
|
input_buffer.evbuf = evbuffer_new();
|
||||||
|
if (!input_buffer.evbuf)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_PLAYER, "Out of memory for input buffer\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
no_input = 1;
|
||||||
|
for (i = 0; inputs[i]; i++)
|
||||||
|
{
|
||||||
|
if (inputs[i]->type != i)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_PLAYER, "BUG! Input definitions are misaligned with input enum\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputs[i]->init)
|
||||||
|
{
|
||||||
|
no_input = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = inputs[i]->init();
|
||||||
|
if (ret < 0)
|
||||||
|
inputs[i]->disabled = 1;
|
||||||
|
else
|
||||||
|
no_input = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (no_input)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
input_deinit(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
input_stop(NULL);
|
||||||
|
|
||||||
|
for (i = 0; inputs[i]; i++)
|
||||||
|
{
|
||||||
|
if (inputs[i]->disabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (inputs[i]->deinit)
|
||||||
|
inputs[i]->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_free(input_buffer.evbuf);
|
||||||
|
}
|
||||||
|
|
176
src/input.h
Normal file
176
src/input.h
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
|
||||||
|
#ifndef __INPUT_H__
|
||||||
|
#define __INPUT_H__
|
||||||
|
|
||||||
|
#include <event2/buffer.h>
|
||||||
|
#include "transcode.h"
|
||||||
|
|
||||||
|
// Must be in sync with inputs[] in input.c
|
||||||
|
enum input_types
|
||||||
|
{
|
||||||
|
INPUT_TYPE_FILE,
|
||||||
|
INPUT_TYPE_HTTP,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum input_flags
|
||||||
|
{
|
||||||
|
// Write to input buffer must not block
|
||||||
|
INPUT_FLAG_NONBLOCK = (1 << 0),
|
||||||
|
// Flags end of file
|
||||||
|
INPUT_FLAG_EOF = (1 << 1),
|
||||||
|
// Flags possible new stream metadata
|
||||||
|
INPUT_FLAG_METADATA = (1 << 2),
|
||||||
|
};
|
||||||
|
|
||||||
|
struct player_source
|
||||||
|
{
|
||||||
|
/* Id of the file/item in the files database */
|
||||||
|
uint32_t id;
|
||||||
|
|
||||||
|
/* Item-Id of the file/item in the queue */
|
||||||
|
uint32_t item_id;
|
||||||
|
|
||||||
|
/* Length of the file/item in milliseconds */
|
||||||
|
uint32_t len_ms;
|
||||||
|
|
||||||
|
enum data_kind data_kind;
|
||||||
|
enum media_kind media_kind;
|
||||||
|
char *path;
|
||||||
|
|
||||||
|
/* Start time of the media item as rtp-time
|
||||||
|
The stream-start is the rtp-time the media item did or would have
|
||||||
|
started playing (after seek or pause), therefor the elapsed time of the
|
||||||
|
media item is always:
|
||||||
|
elapsed time = current rtptime - stream-start */
|
||||||
|
uint64_t stream_start;
|
||||||
|
|
||||||
|
/* Output start time of the media item as rtp-time
|
||||||
|
The output start time is the rtp-time of the first audio packet send
|
||||||
|
to the audio outputs.
|
||||||
|
It differs from stream-start especially after a seek, where the first audio
|
||||||
|
packet has the next rtp-time as output start and stream start becomes the
|
||||||
|
rtp-time the media item would have been started playing if the seek did
|
||||||
|
not happen. */
|
||||||
|
uint64_t output_start;
|
||||||
|
|
||||||
|
/* End time of media item as rtp-time
|
||||||
|
The end time is set if the reading (source_read) of the media item reached
|
||||||
|
end of file, until then it is 0. */
|
||||||
|
uint64_t end;
|
||||||
|
|
||||||
|
struct transcode_ctx *xcode;
|
||||||
|
int setup_done;
|
||||||
|
|
||||||
|
struct player_source *play_next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct input_definition
|
||||||
|
{
|
||||||
|
// Name of the input
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
// Type of input
|
||||||
|
enum input_types type;
|
||||||
|
|
||||||
|
// Set to 1 if the input initialization failed
|
||||||
|
char disabled;
|
||||||
|
|
||||||
|
// Prepare a playback session
|
||||||
|
int (*setup)(struct player_source *ps);
|
||||||
|
|
||||||
|
// Starts playback loop (must be defined)
|
||||||
|
int (*start)(struct player_source *ps);
|
||||||
|
|
||||||
|
// Cleans up when playback loop has ended
|
||||||
|
int (*stop)(struct player_source *ps);
|
||||||
|
|
||||||
|
// Changes the playback position
|
||||||
|
int (*seek)(struct player_source *ps, int seek_ms);
|
||||||
|
|
||||||
|
// Initialization function called during startup
|
||||||
|
int (*init)(void);
|
||||||
|
|
||||||
|
// Deinitialization function called at shutdown
|
||||||
|
void (*deinit)(void);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Input modules use this to test if playback should be stopped or seeked
|
||||||
|
*/
|
||||||
|
int input_loop_break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transfer stream data to the player's input buffer. The input evbuf will be
|
||||||
|
* drained on succesful write. This is to avoid copying memory. If the player's
|
||||||
|
* input buffer is full the function will block until the write can be made
|
||||||
|
* (unless INPUT_FILE_NONBLOCK is set).
|
||||||
|
*
|
||||||
|
* @in evbuf Raw audio data to write
|
||||||
|
* @in flags One or more INPUT_FLAG_*
|
||||||
|
* @return 0 on success, EAGAIN if buffer was full (and _NONBLOCK is set),
|
||||||
|
* -1 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
input_write(struct evbuffer *evbuf, short flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @in evbuf Output buffer
|
||||||
|
* @in want How much data to move to the output buffer
|
||||||
|
* @out flags Flags INPUT_FLAG_EOF or INPUT_FLAG_METADATA
|
||||||
|
* @return Number of bytes moved
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
input_read(struct evbuffer *evbuf, size_t want, short *flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initializes the given player source for playback
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
input_setup(struct player_source *ps);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tells the input to start or resume playback, i.e. after calling this function
|
||||||
|
* the input buffer will begin to fill up, and should be read periodically with
|
||||||
|
* input_read(). Before calling this input_setup() must have been called.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
input_start(struct player_source *ps);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pauses playback of the given player source (stops playback loop) and flushes
|
||||||
|
* the input buffer
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
input_pause(struct player_source *ps);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stops playback loop (if running), flushes input buffer and cleans up the
|
||||||
|
* player source
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
input_stop(struct player_source *ps);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Seeks playback position to seek_ms. Returns actual seek position, 0 on
|
||||||
|
* unseekable, -1 on error. May block.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
input_seek(struct player_source *ps, int seek_ms);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush input buffer. Output flags will be the same as input_read().
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
input_flush(short *flags);
|
||||||
|
|
||||||
|
int
|
||||||
|
input_init(void);
|
||||||
|
|
||||||
|
void
|
||||||
|
input_deinit(void);
|
||||||
|
|
||||||
|
#endif /* !__INPUT_H__ */
|
125
src/inputs/file_http.c
Normal file
125
src/inputs/file_http.c
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* 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 <event2/buffer.h>
|
||||||
|
|
||||||
|
#include "transcode.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "input.h"
|
||||||
|
|
||||||
|
static int
|
||||||
|
setup(struct player_source *ps)
|
||||||
|
{
|
||||||
|
ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL);
|
||||||
|
if (!ps->xcode)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
ps->setup_done = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http_setup(struct player_source *ps)
|
||||||
|
{
|
||||||
|
char *url;
|
||||||
|
|
||||||
|
if (http_stream_setup(&url, ps->path) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
free(ps->path);
|
||||||
|
ps->path = url;
|
||||||
|
|
||||||
|
return setup(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
start(struct player_source *ps)
|
||||||
|
{
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
short flags;
|
||||||
|
int ret;
|
||||||
|
int icy_timer;
|
||||||
|
|
||||||
|
evbuf = evbuffer_new();
|
||||||
|
|
||||||
|
ret = -1;
|
||||||
|
flags = 0;
|
||||||
|
while (!input_loop_break && !(flags & INPUT_FLAG_EOF))
|
||||||
|
{
|
||||||
|
// We set "wanted" to 1 because the read size doesn't matter to us
|
||||||
|
// TODO optimize?
|
||||||
|
ret = transcode(evbuf, 1, ps->xcode, &icy_timer);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
flags = ((ret == 0) ? INPUT_FLAG_EOF : 0) |
|
||||||
|
(icy_timer ? INPUT_FLAG_METADATA : 0);
|
||||||
|
|
||||||
|
ret = input_write(evbuf, flags);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuffer_free(evbuf);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
stop(struct player_source *ps)
|
||||||
|
{
|
||||||
|
transcode_cleanup(ps->xcode);
|
||||||
|
|
||||||
|
ps->xcode = NULL;
|
||||||
|
ps->setup_done = 0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
seek(struct player_source *ps, int seek_ms)
|
||||||
|
{
|
||||||
|
return transcode_seek(ps->xcode, seek_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct input_definition input_file =
|
||||||
|
{
|
||||||
|
.name = "file",
|
||||||
|
.type = INPUT_TYPE_FILE,
|
||||||
|
.disabled = 0,
|
||||||
|
.setup = setup,
|
||||||
|
.start = start,
|
||||||
|
.stop = stop,
|
||||||
|
.seek = seek,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct input_definition input_http =
|
||||||
|
{
|
||||||
|
.name = "http",
|
||||||
|
.type = INPUT_TYPE_HTTP,
|
||||||
|
.disabled = 0,
|
||||||
|
.setup = http_setup,
|
||||||
|
.start = start,
|
||||||
|
.stop = stop,
|
||||||
|
};
|
375
src/player.c
375
src/player.c
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Julien BLACHE <jb@jblache.org>
|
* Copyright (C) 2010-2011 Julien BLACHE <jb@jblache.org>
|
||||||
* Copyright (C) 2016 Espen Jürgensen <espenjurgensen@gmail.com>
|
* Copyright (C) 2016-2017 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -15,6 +15,22 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program; if not, write to the Free Software
|
* along with this program; if not, write to the Free Software
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
|
* About player.c
|
||||||
|
* --------------
|
||||||
|
* The main tasks of the player are the following:
|
||||||
|
* - handle playback commands, status checks and events from other threads
|
||||||
|
* - receive audio from the input thread and to own the playback buffer
|
||||||
|
* - feed the outputs at the appropriate rate (controlled by the playback timer)
|
||||||
|
* - output device handling (partly outsourced to outputs.c)
|
||||||
|
* - notify about playback status changes
|
||||||
|
* - maintain the playback queue
|
||||||
|
*
|
||||||
|
* The player thread should *never* be making operations that may block, since
|
||||||
|
* that could block callers requesting status (effectively making forked-daapd
|
||||||
|
* unresponsive) and it could also starve the outputs.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
@ -55,18 +71,13 @@
|
|||||||
#include "listener.h"
|
#include "listener.h"
|
||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
|
|
||||||
/* Audio outputs */
|
/* Audio and metadata outputs */
|
||||||
#include "outputs.h"
|
#include "outputs.h"
|
||||||
|
|
||||||
/* Audio inputs */
|
/* Audio and metadata input */
|
||||||
#include "transcode.h"
|
#include "input.h"
|
||||||
#include "pipe.h"
|
|
||||||
#ifdef HAVE_SPOTIFY_H
|
|
||||||
# include "spotify.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Metadata input/output */
|
/* Scrobbling */
|
||||||
#include "http.h"
|
|
||||||
#ifdef LASTFM
|
#ifdef LASTFM
|
||||||
# include "lastfm.h"
|
# include "lastfm.h"
|
||||||
#endif
|
#endif
|
||||||
@ -84,48 +95,6 @@
|
|||||||
// Used to keep the player from getting ahead of a rate limited source (see below)
|
// Used to keep the player from getting ahead of a rate limited source (see below)
|
||||||
#define PLAYER_TICKS_MAX_OVERRUN 2
|
#define PLAYER_TICKS_MAX_OVERRUN 2
|
||||||
|
|
||||||
struct player_source
|
|
||||||
{
|
|
||||||
/* Id of the file/item in the files database */
|
|
||||||
uint32_t id;
|
|
||||||
|
|
||||||
/* Item-Id of the file/item in the queue */
|
|
||||||
uint32_t item_id;
|
|
||||||
|
|
||||||
/* Length of the file/item in milliseconds */
|
|
||||||
uint32_t len_ms;
|
|
||||||
|
|
||||||
enum data_kind data_kind;
|
|
||||||
enum media_kind media_kind;
|
|
||||||
char *path;
|
|
||||||
|
|
||||||
/* Start time of the media item as rtp-time
|
|
||||||
The stream-start is the rtp-time the media item did or would have
|
|
||||||
started playing (after seek or pause), therefor the elapsed time of the
|
|
||||||
media item is always:
|
|
||||||
elapsed time = current rtptime - stream-start */
|
|
||||||
uint64_t stream_start;
|
|
||||||
|
|
||||||
/* Output start time of the media item as rtp-time
|
|
||||||
The output start time is the rtp-time of the first audio packet send
|
|
||||||
to the audio outputs.
|
|
||||||
It differs from stream-start especially after a seek, where the first audio
|
|
||||||
packet has the next rtp-time as output start and stream start becomes the
|
|
||||||
rtp-time the media item would have been started playing if the seek did
|
|
||||||
not happen. */
|
|
||||||
uint64_t output_start;
|
|
||||||
|
|
||||||
/* End time of media item as rtp-time
|
|
||||||
The end time is set if the reading (source_read) of the media item reached
|
|
||||||
end of file, until then it is 0. */
|
|
||||||
uint64_t end;
|
|
||||||
|
|
||||||
struct transcode_ctx *xcode;
|
|
||||||
int setup_done;
|
|
||||||
|
|
||||||
struct player_source *play_next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct volume_param {
|
struct volume_param {
|
||||||
int volume;
|
int volume;
|
||||||
uint64_t spk_id;
|
uint64_t spk_id;
|
||||||
@ -594,125 +563,11 @@ history_add(uint32_t id, uint32_t item_id)
|
|||||||
|
|
||||||
/* Audio sources */
|
/* Audio sources */
|
||||||
|
|
||||||
/*
|
|
||||||
* Initializes the given player source for playback
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
stream_setup(struct player_source *ps)
|
|
||||||
{
|
|
||||||
char *url;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!ps)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "No player source given to stream_setup\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps->setup_done)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Given player source already setup (id = %d)\n", ps->id);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup depending on data kind
|
|
||||||
switch (ps->data_kind)
|
|
||||||
{
|
|
||||||
case DATA_KIND_FILE:
|
|
||||||
ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL);
|
|
||||||
ret = ps->xcode ? 0 : -1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DATA_KIND_HTTP:
|
|
||||||
ret = http_stream_setup(&url, ps->path);
|
|
||||||
if (ret < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
free(ps->path);
|
|
||||||
ps->path = url;
|
|
||||||
|
|
||||||
ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL);
|
|
||||||
ret = ps->xcode ? 0 : -1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DATA_KIND_SPOTIFY:
|
|
||||||
#ifdef HAVE_SPOTIFY_H
|
|
||||||
ret = spotify_playback_setup(ps->path);
|
|
||||||
#else
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Player source has data kind 'spotify' (%d), but forked-daapd is compiled without spotify support - cannot setup source '%s'\n",
|
|
||||||
ps->data_kind, ps->path);
|
|
||||||
ret = -1;
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DATA_KIND_PIPE:
|
|
||||||
ret = pipe_setup(ps->path);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Unknown data kind (%d) for player source - cannot setup source '%s'\n",
|
|
||||||
ps->data_kind, ps->path);
|
|
||||||
ret = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret == 0)
|
|
||||||
ps->setup_done = 1;
|
|
||||||
else
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Failed to setup player source (id = %d)\n", ps->id);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Starts or resumes plaback for the given player source
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
stream_play(struct player_source *ps)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!ps)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Stream play called with no active streaming player source\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ps->setup_done)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, play not possible\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start/resume playback depending on data kind
|
|
||||||
switch (ps->data_kind)
|
|
||||||
{
|
|
||||||
case DATA_KIND_HTTP:
|
|
||||||
case DATA_KIND_FILE:
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
#ifdef HAVE_SPOTIFY_H
|
|
||||||
case DATA_KIND_SPOTIFY:
|
|
||||||
ret = spotify_playback_play();
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case DATA_KIND_PIPE:
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
ret = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read up to "len" data from the given player source and returns
|
* Read up to "len" data from the given player source and returns
|
||||||
* the actual amount of data read.
|
* the actual amount of data read.
|
||||||
*/
|
*/
|
||||||
static int
|
/*static int
|
||||||
stream_read(struct player_source *ps, int len)
|
stream_read(struct player_source *ps, int len)
|
||||||
{
|
{
|
||||||
int icy_timer;
|
int icy_timer;
|
||||||
@ -759,149 +614,7 @@ stream_read(struct player_source *ps, int len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/*
|
|
||||||
* Pauses playback of the given player source
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
stream_pause(struct player_source *ps)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!ps)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Stream pause called with no active streaming player source\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ps->setup_done)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, pause not possible\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause playback depending on data kind
|
|
||||||
switch (ps->data_kind)
|
|
||||||
{
|
|
||||||
case DATA_KIND_HTTP:
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DATA_KIND_FILE:
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
#ifdef HAVE_SPOTIFY_H
|
|
||||||
case DATA_KIND_SPOTIFY:
|
|
||||||
ret = spotify_playback_pause();
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case DATA_KIND_PIPE:
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
ret = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Seeks to the given position in milliseconds of the given player source
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
stream_seek(struct player_source *ps, int seek_ms)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!ps)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Stream seek called with no active streaming player source\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ps->setup_done)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, seek not possible\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek depending on data kind
|
|
||||||
switch (ps->data_kind)
|
|
||||||
{
|
|
||||||
case DATA_KIND_HTTP:
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DATA_KIND_FILE:
|
|
||||||
ret = transcode_seek(ps->xcode, seek_ms);
|
|
||||||
break;
|
|
||||||
|
|
||||||
#ifdef HAVE_SPOTIFY_H
|
|
||||||
case DATA_KIND_SPOTIFY:
|
|
||||||
ret = spotify_playback_seek(seek_ms);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case DATA_KIND_PIPE:
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
ret = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Stops playback and cleanup for the given player source
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
stream_stop(struct player_source *ps)
|
|
||||||
{
|
|
||||||
if (!ps)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Stream cleanup called with no active streaming player source\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ps->setup_done)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, cleanup not possible\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (ps->data_kind)
|
|
||||||
{
|
|
||||||
case DATA_KIND_FILE:
|
|
||||||
case DATA_KIND_HTTP:
|
|
||||||
if (ps->xcode)
|
|
||||||
{
|
|
||||||
transcode_cleanup(ps->xcode);
|
|
||||||
ps->xcode = NULL;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DATA_KIND_SPOTIFY:
|
|
||||||
#ifdef HAVE_SPOTIFY_H
|
|
||||||
spotify_playback_stop();
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DATA_KIND_PIPE:
|
|
||||||
pipe_cleanup();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ps->setup_done = 0;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static struct player_source *
|
static struct player_source *
|
||||||
source_now_playing()
|
source_now_playing()
|
||||||
@ -959,7 +672,7 @@ source_stop()
|
|||||||
struct player_source *ps_temp;
|
struct player_source *ps_temp;
|
||||||
|
|
||||||
if (cur_streaming)
|
if (cur_streaming)
|
||||||
stream_stop(cur_streaming);
|
input_stop(cur_streaming);
|
||||||
|
|
||||||
ps_playing = source_now_playing();
|
ps_playing = source_now_playing();
|
||||||
|
|
||||||
@ -997,20 +710,20 @@ source_pause(uint64_t pos)
|
|||||||
if (!ps_playing)
|
if (!ps_playing)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (cur_streaming)
|
if (cur_streaming && (cur_streaming == ps_playing))
|
||||||
{
|
{
|
||||||
if (ps_playing != cur_streaming)
|
if (ps_playing != cur_streaming)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_PLAYER,
|
DPRINTF(E_DBG, L_PLAYER,
|
||||||
"Pause called on playing source (id=%d) and streaming source already "
|
"Pause called on playing source (id=%d) and streaming source already "
|
||||||
"switched to the next item (id=%d)\n", ps_playing->id, cur_streaming->id);
|
"switched to the next item (id=%d)\n", ps_playing->id, cur_streaming->id);
|
||||||
ret = stream_stop(cur_streaming);
|
ret = input_stop(cur_streaming);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ret = stream_pause(cur_streaming);
|
ret = input_pause(cur_streaming);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1034,7 +747,7 @@ source_pause(uint64_t pos)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path);
|
DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path);
|
||||||
|
|
||||||
ret = stream_setup(cur_streaming);
|
ret = input_setup(cur_streaming);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s'\n", cur_streaming->path);
|
DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s'\n", cur_streaming->path);
|
||||||
@ -1045,7 +758,9 @@ source_pause(uint64_t pos)
|
|||||||
/* Seek back to the pause position */
|
/* Seek back to the pause position */
|
||||||
seek_frames = (pos - cur_streaming->stream_start);
|
seek_frames = (pos - cur_streaming->stream_start);
|
||||||
seek_ms = (int)((seek_frames * 1000) / 44100);
|
seek_ms = (int)((seek_frames * 1000) / 44100);
|
||||||
ret = stream_seek(cur_streaming, seek_ms);
|
ret = input_seek(cur_streaming, seek_ms);
|
||||||
|
|
||||||
|
// TODO what if ret < 0?
|
||||||
|
|
||||||
/* Adjust start_pos to take into account the pause and seek back */
|
/* Adjust start_pos to take into account the pause and seek back */
|
||||||
cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
|
cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000;
|
||||||
@ -1067,7 +782,7 @@ source_seek(int seek_ms)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = stream_seek(cur_streaming, seek_ms);
|
ret = input_seek(cur_streaming, seek_ms);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -1086,7 +801,7 @@ source_play()
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = stream_play(cur_streaming);
|
ret = input_start(cur_streaming);
|
||||||
|
|
||||||
ticks_skip = 0;
|
ticks_skip = 0;
|
||||||
memset(rawbuf, 0, sizeof(rawbuf));
|
memset(rawbuf, 0, sizeof(rawbuf));
|
||||||
@ -1116,7 +831,7 @@ source_open(struct player_source *ps, uint64_t start_pos, int seek_ms)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = stream_setup(ps);
|
ret = input_setup(ps);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id);
|
DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id);
|
||||||
@ -1151,7 +866,7 @@ source_open(struct player_source *ps, uint64_t start_pos, int seek_ms)
|
|||||||
static int
|
static int
|
||||||
source_close(uint64_t end_pos)
|
source_close(uint64_t end_pos)
|
||||||
{
|
{
|
||||||
stream_stop(cur_streaming);
|
input_stop(cur_streaming);
|
||||||
|
|
||||||
cur_streaming->end = end_pos;
|
cur_streaming->end = end_pos;
|
||||||
|
|
||||||
@ -1339,6 +1054,7 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
int nbytes;
|
int nbytes;
|
||||||
|
short flags;
|
||||||
char *silence_buf;
|
char *silence_buf;
|
||||||
struct player_source *ps;
|
struct player_source *ps;
|
||||||
|
|
||||||
@ -1350,9 +1066,10 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
|
|||||||
{
|
{
|
||||||
if (evbuffer_get_length(audio_buf) == 0)
|
if (evbuffer_get_length(audio_buf) == 0)
|
||||||
{
|
{
|
||||||
|
flags = 0;
|
||||||
if (cur_streaming)
|
if (cur_streaming)
|
||||||
{
|
{
|
||||||
ret = stream_read(cur_streaming, len - nbytes);
|
ret = input_read(audio_buf, len - nbytes, &flags);
|
||||||
}
|
}
|
||||||
else if (cur_playing)
|
else if (cur_playing)
|
||||||
{
|
{
|
||||||
@ -1369,9 +1086,10 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret <= 0)
|
// if (ret == 0)
|
||||||
|
//TODO Underrun -> pause
|
||||||
|
if ((ret < 0) || (flags & INPUT_FLAG_EOF))
|
||||||
{
|
{
|
||||||
/* EOF or error */
|
|
||||||
source_close(rtptime + BTOS(nbytes) - 1);
|
source_close(rtptime + BTOS(nbytes) - 1);
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_PLAYER, "New file\n");
|
DPRINTF(E_DBG, L_PLAYER, "New file\n");
|
||||||
@ -3224,7 +2942,6 @@ player_playback_prev(void)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
player_speaker_enumerate(spk_enum_cb cb, void *arg)
|
player_speaker_enumerate(spk_enum_cb cb, void *arg)
|
||||||
{
|
{
|
||||||
@ -3539,6 +3256,13 @@ player_init(void)
|
|||||||
goto outputs_fail;
|
goto outputs_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = input_init();
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_PLAYER, "Input initiation failed\n");
|
||||||
|
goto input_fail;
|
||||||
|
}
|
||||||
|
|
||||||
ret = pthread_create(&tid_player, NULL, player, NULL);
|
ret = pthread_create(&tid_player, NULL, player, NULL);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -3554,6 +3278,8 @@ player_init(void)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
thread_fail:
|
thread_fail:
|
||||||
|
input_deinit();
|
||||||
|
input_fail:
|
||||||
outputs_deinit();
|
outputs_deinit();
|
||||||
outputs_fail:
|
outputs_fail:
|
||||||
commands_base_free(cmdbase);
|
commands_base_free(cmdbase);
|
||||||
@ -3599,6 +3325,7 @@ player_deinit(void)
|
|||||||
|
|
||||||
evbuffer_free(audio_buf);
|
evbuffer_free(audio_buf);
|
||||||
|
|
||||||
|
input_deinit();
|
||||||
outputs_deinit();
|
outputs_deinit();
|
||||||
|
|
||||||
event_base_free(evbase_player);
|
event_base_free(evbase_player);
|
||||||
|
@ -13,11 +13,6 @@
|
|||||||
/* AirTunes v2 number of samples per packet */
|
/* AirTunes v2 number of samples per packet */
|
||||||
#define AIRTUNES_V2_PACKET_SAMPLES 352
|
#define AIRTUNES_V2_PACKET_SAMPLES 352
|
||||||
|
|
||||||
|
|
||||||
/* Samples to bytes, bytes to samples */
|
|
||||||
#define STOB(s) ((s) * 4)
|
|
||||||
#define BTOS(b) ((b) / 4)
|
|
||||||
|
|
||||||
/* Maximum number of previously played songs that are remembered */
|
/* Maximum number of previously played songs that are remembered */
|
||||||
#define MAX_HISTORY_COUNT 20
|
#define MAX_HISTORY_COUNT 20
|
||||||
|
|
||||||
@ -115,7 +110,6 @@ player_playback_next(void);
|
|||||||
int
|
int
|
||||||
player_playback_prev(void);
|
player_playback_prev(void);
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
player_volume_set(int vol);
|
player_volume_set(int vol);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user