Skip to content

Commit

Permalink
Add feature to delay playback for a time while the receiver powers on
Browse files Browse the repository at this point in the history
  • Loading branch information
chewi committed Sep 29, 2023
1 parent 985fd11 commit 60d00fa
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ An MPD client to control Yamaha AV receivers with YNCA (network control) support
## Features

* Powers the receiver on and switches input when playback starts.
* Delays playback for a configurable time while the receiver powers on.
* Switches sound program to a per-song choice, "Straight" for multi-channel songs, or some default.
* Configurable using an INI file.

Expand Down
49 changes: 38 additions & 11 deletions mpd-ynca.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace po = boost::program_options;

class YncaClient {
public:
YncaClient(const std::string &host, const int port);
YncaClient(const std::string &host, const unsigned int port);
~YncaClient() = default;

std::string get_command(const std::string &command);
Expand All @@ -60,7 +60,7 @@ class YncaClient {
tcp::resolver::query resolver_query;
};

YncaClient::YncaClient(const std::string &host, const int port) :
YncaClient::YncaClient(const std::string &host, const unsigned int port) :
io_service(),
socket(io_service),
resolver(io_service),
Expand Down Expand Up @@ -119,15 +119,17 @@ void YncaClient::with_connection(std::function<void ()> func) {
catch(...) {}
}

void connect_loop(YncaClient &ynca, const std::string &scene, const std::string &input, const optional<std::string> &default_program) {
void connect_loop(YncaClient &ynca, const std::string &scene, const std::string &input, const optional<std::string> &default_program, std::chrono::seconds startup_delay) {
struct mpd_status *status = NULL;
enum mpd_state old_state, new_state;

unsigned int elapsed_ms;
const struct mpd_audio_format *format;
uint8_t channels;

static const std::string \
on_cmd = "@MAIN:PWR=On", \
pwr_get_cmd = "@MAIN:PWR=?", \
pwr_on_cmd = "@MAIN:PWR=On", \
sound_prog_cmd = "@MAIN:SOUNDPRG=", \
straight_cmd = "@MAIN:STRAIGHT=On", \
input_get_cmd = "@MAIN:INP=?";
Expand Down Expand Up @@ -180,17 +182,40 @@ void connect_loop(YncaClient &ynca, const std::string &scene, const std::string
goto error;

new_state = mpd_status_get_state(status);
elapsed_ms = mpd_status_get_elapsed_ms(status);
format = mpd_status_get_audio_format(status);
channels = format ? format->channels : 0;
mpd_status_free(status);

if (new_state == MPD_STATE_PLAY) {
ynca.with_connection([&]() {
if (old_state != MPD_STATE_PLAY) {
// Power the receiver on and switch to the
// configured scene when playback starts.
ynca.put_command(on_cmd);
// When playback starts, check the power status.
bool was_off = ynca.get_command(pwr_get_cmd).find(pwr_on_cmd + "\r\n") == std::string::npos;

if (was_off) {
// If powered off, pause or stop the audio depending on
// the previous state, then power on. We don't pause
// unconditionally because MPD seems to chop off a
// little audio when unpausing, and this is most
// noticeable when playing a track from the start.
if (old_state == MPD_STATE_PAUSE) {
mpd_run_pause(conn, true);
mpd_run_seek_current(conn, elapsed_ms / 1000.f, false);
} else {
mpd_run_stop(conn);
}
ynca.put_command(pwr_on_cmd);
}

// Set the scene whether already powered on or not.
ynca.put_command(scene_put_cmd);

if (was_off) {
// If power was off, wait a bit, then resume audio.
std::this_thread::sleep_for(startup_delay);
mpd_run_play(conn);
}
} else if (ynca.get_command(input_get_cmd).find(input_put_cmd + "\r\n") == std::string::npos) {
// Stop playback if the input has changed.
mpd_run_stop(conn);
Expand Down Expand Up @@ -246,7 +271,7 @@ void connect_loop(YncaClient &ynca, const std::string &scene, const std::string
std::cerr << "MPD connection error: " << mpd_connection_get_error_message(conn) << std::endl;
mpd_connection_free(conn);
std::this_thread::sleep_for(std::chrono::seconds(1));
return connect_loop(ynca, scene, input, default_program);
return connect_loop(ynca, scene, input, default_program, startup_delay);
}

int main() {
Expand All @@ -256,10 +281,11 @@ int main() {

config.add_options()
("host", po::value<std::string>(), "Yamaha AV hostname")
("port", po::value<int>()->default_value(50000), "Yamaha AV port")
("port", po::value<unsigned int>()->default_value(50000), "Yamaha AV port")
("scene", po::value<std::string>(), "Yamaha AV scene for MPD")
("input", po::value<std::string>(), "Yamaha AV input for MPD")
("default-program", po::value(&default_program), "Yamaha AV default sound program name")
("startup-delay", po::value<unsigned int>()->default_value(5), "Startup delay")
;

const std::filesystem::path *cf = nullptr;
Expand Down Expand Up @@ -319,14 +345,15 @@ int main() {

YncaClient ynca = YncaClient(
vm["host"].as<std::string>(),
vm["port"].as<int>()
vm["port"].as<unsigned int>()
);

connect_loop(
ynca,
vm["scene"].as<std::string>(),
vm["input"].as<std::string>(),
default_program
default_program,
std::chrono::seconds(vm["startup-delay"].as<unsigned int>())
);

return EXIT_FAILURE;
Expand Down
16 changes: 11 additions & 5 deletions ynca.conf
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ scene = Scene 1
# Valid values include: HDMI1, AUDIO2, AV3
input = HDMI1

# The sound program to switch to by default when the song
# changes. This can be overridden by setting the "ynca_program"
# sticker against individual songs in the MPD media library.
# Multi-channel songs will fall back to "Straight" mode instead. When
# not defined, the entire sound program switching feature is disabled.
# The sound program to switch to by default when the song changes. This can be
# overridden by setting the "ynca_program" sticker against individual songs in
# the MPD media library. Multi-channel songs will fall back to "Straight" mode
# instead. When not defined, the entire sound program switching feature is
# disabled.
#
# OPTIONAL
# Valid values include: 7ch Stereo, 2ch Stereo, Hall in Munich
default-program = 7ch Stereo

# The amount of time in seconds to delay audio playback when the AV receiver
# needs to be powered on.
#
# OPTIONAL
startup-delay = 10

0 comments on commit 60d00fa

Please sign in to comment.