Soundux/soundplayback.cpp
D3S0X fdef3f84af New config structure,
hotkeys for folder tabs
2020-06-26 00:26:45 +02:00

270 lines
8.5 KiB
C++

#include "soundplayback.h"
static vector<PulseAudioRecordingStream *> streams;
SoundPlayback::SoundPlayback(QWidget *parent, Ui::MainWindow* mainWindow) : QObject(parent)
{
this->parent = parent;
this->ui = mainWindow;
}
SoundPlayback::~SoundPlayback()
{
}
bool SoundPlayback::isValidDevice(PulseAudioRecordingStream *stream)
{
return !strstr(stream->source.c_str(), ".monitor") && !strstr(stream->flags.c_str(), "DONT_MOVE");
}
string SoundPlayback::getCommandOutput(char cmd[])
{
array<char, 1028> buffer;
string result;
unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe)
{
throw runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
{
result += buffer.data();
}
return result;
}
void SoundPlayback::checkAndChangeVolume(PulseAudioPlaybackStream *stream, int value)
{
// TODO: Only set it when this was created by Soundboard
// Set the volume if the application is paplay or mpg123
if (stream->applicationName == "paplay" || stream->applicationName == "mpg123")
{
system(("pacmd set-sink-input-volume " + to_string(stream->index) + " " + to_string(value)).c_str());
}
}
void SoundPlayback::stopSound()
{
// Fix continuous playback
if (ui->repeatCheckBox->isChecked())
{
ui->repeatCheckBox->setChecked(false);
}
//TODO: Only kill players started from Soundboard
system("killall mpg123");
system("killall paplay");
ui->stopButton->setDisabled(true);
ui->localVolumeSlider->setDisabled(false);
ui->remoteVolumeSlider->setDisabled(false);
}
bool SoundPlayback::loadSources()
{
// Save previously selected applicaton
auto previouslySelected = ui->outputApplication->currentText();
streams.clear();
ui->outputApplication->clear();
char cmd[] = "pacmd list-source-outputs";
string result = getCommandOutput(cmd);
string delimiter = "\n";
size_t pos = 0;
string currentLine;
// Tell me if there is a better way to parse the pulseaudio output
regex reg(R"rgx(((index: (\d+)))|(driver: )(.*)|(state: )(.*)|(flags: )(.*)|(source: .*)(<(.*)>)|(muted: )(.{0,3})|([a-zA-Z-.0-9_]*)\ =\ (\"(.*)\"))rgx");
smatch sm;
PulseAudioRecordingStream *current = nullptr;
while ((pos = result.find(delimiter)) != string::npos)
{
currentLine = result.substr(0, pos);
if (regex_search(currentLine, sm, reg))
{
auto index = sm[3];
if (index.length() > 0)
{
if (current && isValidDevice(current))
{
streams.push_back(current);
}
current = new PulseAudioRecordingStream();
current->index = stoi(index);
}
else if (current)
{
auto driver = sm[5];
auto state = sm[7];
auto flags = sm[9];
auto source = sm[12];
auto muted = sm[14];
auto propertyName = sm[15];
auto propertyValue = sm[17];
if (driver.length() > 0)
{
current->driver = driver.str();
}
if (state.length() > 0)
{
current->state = state.str();
}
if (flags.length() > 0)
{
current->flags = flags.str();
}
if (source.length() > 0)
{
current->source = source.str();
}
if (muted.length() > 0)
{
current->muted = muted == "yes" ? true : false;
}
if (propertyName.length() > 0)
{
if (propertyName == "application.name")
{
current->applicationName = propertyValue.str();
}
if (propertyName == "application.process.id")
{
current->processId = stoi(propertyValue);
}
if (propertyName == "application.process.binary")
{
current->processBinary = propertyValue.str();
}
}
}
}
result.erase(0, pos + delimiter.length());
}
if (isValidDevice(current))
{
streams.push_back(current);
}
for (auto stream : streams)
{
if (stream->driver == "<protocol-native.c>")
{
ui->outputApplication->addItem(QString(stream->processBinary.c_str()));
}
}
// This automatically sets the selected item to the previous one. if it does not exists it does nothing
ui->outputApplication->setCurrentText(previouslySelected);
// Return if the output was not changed
return ui->outputApplication->currentText() == previouslySelected;
}
void SoundPlayback::playSound(string path)
{
//TODO: Remove this and stop old playback or enable multiple sounds at once (maybe create a setting for it)
if (ui->stopButton->isEnabled())
{
return;
}
// Don't play the sound if the app changed (previous one no longer available)
if (!loadSources())
{
QMessageBox::warning(parent, "", tr("Output stream no longer available...\nAborting\n"), QMessageBox::Ok);
return;
}
bool isMP3 = strstr(path.c_str(), ".mp3");
if (isMP3)
{
ostringstream mpg123Check;
mpg123Check << "which mpg123 >/dev/null 2>&1";
bool canPlayMP3 = (system(mpg123Check.str().c_str()) == 0);
if (!canPlayMP3)
{
QMessageBox::critical(parent, "", tr("Can't play mp3 file!\nmpg123 is not installed or not in the $PATH\nPlease install it and restart the program\n"), QMessageBox::Ok);
return;
}
}
// Get selected application
string selectedApp = ui->outputApplication->currentText().toStdString();
PulseAudioRecordingStream *selected = nullptr;
for (auto stream : streams)
{
if (stream->processBinary == selectedApp)
{
selected = stream;
}
}
if (selected != nullptr)
{
int index = selected->index;
string source = selected->source;
cout << "Source before was " << source << endl;
auto moveToSink = "pacmd move-source-output " + to_string(index) + " soundboard_sink.monitor";
auto moveBack = "pacmd move-source-output " + to_string(index) + " " + source;
// Switch recording stream device to game sink
system(moveToSink.c_str());
auto forMe = std::thread([=]() {
auto cmdForMe = "paplay --volume=" + to_string(ui->localVolumeSlider->value()) + " \"" + path + "\"";
if (isMP3)
{
cmdForMe = "mpg123 -o pulse -f " + to_string(ui->localVolumeSlider->value() / 2) + " \"" + path + "\"";
}
//cout << "cmdForMe " << cmdForMe << endl;
system(cmdForMe.c_str());
});
forMe.detach();
auto forOthers = std::thread([=]() {
ui->stopButton->setDisabled(false);
ui->remoteVolumeSlider->setDisabled(true);
ui->localVolumeSlider->setDisabled(true);
ui->syncCheckBox->setDisabled(true);
ui->outputApplication->setDisabled(true);
ui->refreshAppsButton->setDisabled(true);
auto cmdForOthers = "paplay -d soundboard_sink --volume=" + to_string(ui->remoteVolumeSlider->value()) + " \"" + path + "\"";
if (isMP3)
{
cmdForOthers = "mpg123 -o pulse -a soundboard_sink -f " + to_string(ui->remoteVolumeSlider->value() / 2) + " \"" + path + "\"";
}
//cout << "cmdForOthers " << cmdForOthers << endl;
system(cmdForOthers.c_str());
// Switch recording stream device back
system(moveBack.c_str());
ui->stopButton->setDisabled(true);
ui->remoteVolumeSlider->setDisabled(false);
ui->localVolumeSlider->setDisabled(false);
ui->syncCheckBox->setDisabled(false);
ui->outputApplication->setDisabled(false);
ui->refreshAppsButton->setDisabled(false);
// Repeat when the check box is checked
if (ui->repeatCheckBox->isChecked())
{
playSound(path);
}
});
forOthers.detach();
}
}