
When MPD explicitly starts playing, ignore the "REOPEN_AFTER" timeout. This timeout was useful when MPD attempted to reopen a failed device over and over, but it confuses users when they explicitly tell MPD to start playing, while MPD insists to wait for the 10 seconds to pass.
295 lines
6.1 KiB
C
295 lines
6.1 KiB
C
/*
|
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
* http://www.musicpd.org
|
|
*
|
|
* 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 "output_all.h"
|
|
#include "output_internal.h"
|
|
#include "output_control.h"
|
|
|
|
#include <assert.h>
|
|
|
|
static struct audio_format input_audio_format;
|
|
|
|
static struct audio_output *audio_outputs;
|
|
static unsigned int num_audio_outputs;
|
|
|
|
unsigned int audio_output_count(void)
|
|
{
|
|
return num_audio_outputs;
|
|
}
|
|
|
|
struct audio_output *
|
|
audio_output_get(unsigned i)
|
|
{
|
|
assert(i < num_audio_outputs);
|
|
|
|
return &audio_outputs[i];
|
|
}
|
|
|
|
struct audio_output *
|
|
audio_output_find(const char *name)
|
|
{
|
|
for (unsigned i = 0; i < num_audio_outputs; ++i) {
|
|
struct audio_output *ao = audio_output_get(i);
|
|
|
|
if (strcmp(ao->name, name) == 0)
|
|
return ao;
|
|
}
|
|
|
|
/* name not found */
|
|
return NULL;
|
|
}
|
|
|
|
static unsigned
|
|
audio_output_config_count(void)
|
|
{
|
|
unsigned int nr = 0;
|
|
const struct config_param *param = NULL;
|
|
|
|
while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
|
|
nr++;
|
|
if (!nr)
|
|
nr = 1; /* we'll always have at least one device */
|
|
return nr;
|
|
}
|
|
|
|
void
|
|
audio_output_all_init(void)
|
|
{
|
|
const struct config_param *param = NULL;
|
|
unsigned int i;
|
|
|
|
notify_init(&audio_output_client_notify);
|
|
|
|
num_audio_outputs = audio_output_config_count();
|
|
audio_outputs = g_new(struct audio_output, num_audio_outputs);
|
|
|
|
for (i = 0; i < num_audio_outputs; i++)
|
|
{
|
|
struct audio_output *output = &audio_outputs[i];
|
|
unsigned int j;
|
|
|
|
param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
|
|
|
|
/* only allow param to be NULL if there just one audioOutput */
|
|
assert(param || (num_audio_outputs == 1));
|
|
|
|
if (!audio_output_init(output, param)) {
|
|
if (param)
|
|
{
|
|
g_error("problems configuring output device "
|
|
"defined at line %i\n", param->line);
|
|
}
|
|
else
|
|
{
|
|
g_error("No audio_output specified and unable to "
|
|
"detect a default audio output device\n");
|
|
}
|
|
}
|
|
|
|
/* require output names to be unique: */
|
|
for (j = 0; j < i; j++) {
|
|
if (!strcmp(output->name, audio_outputs[j].name)) {
|
|
g_error("output devices with identical "
|
|
"names: %s\n", output->name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
audio_output_all_finish(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num_audio_outputs; i++) {
|
|
audio_output_finish(&audio_outputs[i]);
|
|
}
|
|
|
|
g_free(audio_outputs);
|
|
audio_outputs = NULL;
|
|
num_audio_outputs = 0;
|
|
|
|
notify_deinit(&audio_output_client_notify);
|
|
}
|
|
|
|
|
|
/**
|
|
* Determine if all (active) outputs have finished the current
|
|
* command.
|
|
*/
|
|
static bool
|
|
audio_output_all_finished(void)
|
|
{
|
|
for (unsigned i = 0; i < num_audio_outputs; ++i)
|
|
if (audio_output_is_open(&audio_outputs[i]) &&
|
|
!audio_output_command_is_finished(&audio_outputs[i]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void audio_output_wait_all(void)
|
|
{
|
|
while (!audio_output_all_finished())
|
|
notify_wait(&audio_output_client_notify);
|
|
}
|
|
|
|
/**
|
|
* Resets the "reopen" flag on all audio devices. MPD should
|
|
* immediately retry to open the device instead of waiting for the
|
|
* timeout when the user wants to start playback.
|
|
*/
|
|
static void
|
|
audio_output_all_reset_reopen(void)
|
|
{
|
|
for (unsigned i = 0; i < num_audio_outputs; ++i)
|
|
audio_outputs[i].reopen_after = 0;
|
|
}
|
|
|
|
static void
|
|
audio_output_all_update(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (!audio_format_defined(&input_audio_format))
|
|
return;
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i)
|
|
audio_output_update(&audio_outputs[i], &input_audio_format);
|
|
}
|
|
|
|
bool
|
|
audio_output_all_play(const char *buffer, size_t length)
|
|
{
|
|
bool ret = false;
|
|
unsigned int i;
|
|
|
|
assert(length > 0);
|
|
/* no partial frames allowed */
|
|
assert((length % audio_format_frame_size(&input_audio_format)) == 0);
|
|
|
|
audio_output_all_update();
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i)
|
|
if (audio_output_is_open(&audio_outputs[i]))
|
|
audio_output_play(&audio_outputs[i],
|
|
buffer, length);
|
|
|
|
while (true) {
|
|
bool finished = true;
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i) {
|
|
struct audio_output *ao = &audio_outputs[i];
|
|
|
|
if (!audio_output_is_open(ao))
|
|
continue;
|
|
|
|
if (audio_output_command_is_finished(ao))
|
|
ret = true;
|
|
else {
|
|
finished = false;
|
|
audio_output_signal(ao);
|
|
}
|
|
}
|
|
|
|
if (finished)
|
|
break;
|
|
|
|
notify_wait(&audio_output_client_notify);
|
|
};
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
audio_output_all_open(const struct audio_format *audio_format)
|
|
{
|
|
bool ret = false;
|
|
unsigned int i;
|
|
|
|
if (!audio_outputs)
|
|
return false;
|
|
|
|
if (audio_format != NULL)
|
|
input_audio_format = *audio_format;
|
|
|
|
audio_output_all_reset_reopen();
|
|
audio_output_all_update();
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i) {
|
|
if (audio_outputs[i].open)
|
|
ret = true;
|
|
}
|
|
|
|
if (!ret)
|
|
/* close all devices if there was an error */
|
|
audio_output_all_close();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
audio_output_all_pause(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
audio_output_all_update();
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i)
|
|
if (audio_output_is_open(&audio_outputs[i]))
|
|
audio_output_pause(&audio_outputs[i]);
|
|
|
|
audio_output_wait_all();
|
|
}
|
|
|
|
void
|
|
audio_output_all_cancel(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
audio_output_all_update();
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i) {
|
|
if (audio_output_is_open(&audio_outputs[i]))
|
|
audio_output_cancel(&audio_outputs[i]);
|
|
}
|
|
|
|
audio_output_wait_all();
|
|
}
|
|
|
|
void
|
|
audio_output_all_close(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i)
|
|
audio_output_close(&audio_outputs[i]);
|
|
}
|
|
|
|
void
|
|
audio_output_all_tag(const struct tag *tag)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num_audio_outputs; ++i)
|
|
if (audio_output_is_open(&audio_outputs[i]))
|
|
audio_output_send_tag(&audio_outputs[i], tag);
|
|
|
|
audio_output_wait_all();
|
|
}
|