571 lines
No EOL
18 KiB
C++
571 lines
No EOL
18 KiB
C++
#include "dbus.hpp"
|
|
#include "log.hpp"
|
|
#include "backend.hpp"
|
|
#include <random>
|
|
MprisAPI::MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api)
|
|
: AdaptorInterfaces(connection, std::move(objectPath))
|
|
, dbus_api(dbus_api)
|
|
, connection(connection)
|
|
{
|
|
registerAdaptor();
|
|
connection.enterEventLoopAsync();
|
|
}
|
|
void MprisAPI::Quit() {
|
|
dbus_api->Quit();
|
|
}
|
|
void MprisAPI::Pause() {
|
|
dbus_api->Paused(true);
|
|
}
|
|
void MprisAPI::PlayPause() {
|
|
dbus_api->TogglePause();
|
|
}
|
|
void MprisAPI::Stop() {
|
|
dbus_api->Stop();
|
|
}
|
|
void MprisAPI::Play() {
|
|
dbus_api->Paused(false);
|
|
}
|
|
void MprisAPI::Seek(const int64_t &offset) {
|
|
double value = offset;
|
|
value /= 1000000;
|
|
dbus_api->Position(value);
|
|
}
|
|
void MprisAPI::SetPosition(const sdbus::ObjectPath &TrackId, const int64_t &offset) {
|
|
Seek(offset);
|
|
}
|
|
void MprisAPI::OpenUri(const std::string &Uri) {
|
|
dbus_api->Start(Uri, true);
|
|
}
|
|
std::string MprisAPI::PlaybackStatus() {
|
|
if (dbus_api->IsStopped()) {
|
|
return "Stopped";
|
|
} else {
|
|
return dbus_api->Paused() ? "Paused" : "Playing";
|
|
}
|
|
}
|
|
double MprisAPI::Rate() {
|
|
return dbus_api->Speed() * dbus_api->Tempo();
|
|
}
|
|
void MprisAPI::Rate(const double &value) {
|
|
dbus_api->Speed(value / dbus_api->Tempo());
|
|
}
|
|
std::map<std::string, sdbus::Variant> MprisAPI::Metadata() {
|
|
std::map<std::string, sdbus::Variant> output;
|
|
if (!dbus_api->IsStopped()) {
|
|
output["mpris:length"] = (int64_t)(dbus_api->Length() * 1000000);
|
|
output["mpris:trackid"] = playing_track_id;
|
|
output["xesam:title"] = dbus_api->FileTitle();
|
|
output["xesam:url"] = dbus_api->FilePath();
|
|
} else {
|
|
output["mpris:trackid"] = empty_track_id;
|
|
}
|
|
return output;
|
|
}
|
|
double MprisAPI::Volume() {
|
|
return dbus_api->Volume();
|
|
}
|
|
void MprisAPI::Volume(const double &value) {
|
|
dbus_api->Volume(value);
|
|
}
|
|
int64_t MprisAPI::Position() {
|
|
return dbus_api->Position() * 1000000;
|
|
}
|
|
bool MprisAPI::CanPlay() {
|
|
return !dbus_api->IsStopped();
|
|
}
|
|
bool MprisAPI::CanPause() {
|
|
return !dbus_api->IsStopped();
|
|
}
|
|
bool MprisAPI::CanSeek() {
|
|
return !dbus_api->IsStopped() && dbus_api->Length() > 0;
|
|
}
|
|
|
|
std::vector<meta_t> MprisAPI::GetTracksMetadata(const std::vector<track_id_t> &TrackIds) {
|
|
std::vector<meta_t> output;
|
|
if (!dbus_api->IsStopped()) {
|
|
output.push_back(Metadata());
|
|
auto streams = dbus_api->playback->get_streams();
|
|
int i = 0;
|
|
for (auto stream : streams) {
|
|
std::map<std::string, sdbus::Variant> meta;
|
|
meta["mpris:trackid"] = std::format("{}{}", streamPrefix, i);
|
|
meta["xesam:title"] = stream;
|
|
i++;
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
void MprisAPI::AddTrack(const std::string &Uri, const track_id_t &AfterTrack, const bool &SetAsCurrent) {
|
|
return;
|
|
}
|
|
void MprisAPI::RemoveTrack(const track_id_t &TrackId) {
|
|
return;
|
|
}
|
|
void MprisAPI::GoTo(const track_id_t &TrackId) {
|
|
if (TrackId.starts_with(streamPrefix)) {
|
|
std::string stream = TrackId.substr(streamPrefix.length());
|
|
int streamIdx = std::stoi(stream);
|
|
dbus_api->PlayStream((uint32_t)streamIdx);
|
|
}
|
|
return;
|
|
}
|
|
std::vector<track_id_t> MprisAPI::Tracks() {
|
|
std::vector<track_id_t> output;
|
|
if (dbus_api->IsStopped()) {
|
|
return output;
|
|
}
|
|
output.push_back(playing_track_id);
|
|
auto streams = dbus_api->playback->get_streams();
|
|
int i = 0;
|
|
for (auto stream : streams) {
|
|
output.push_back(std::format("{}{}", streamPrefix, i));
|
|
i++;
|
|
}
|
|
return output;
|
|
}
|
|
bool MprisAPI::CanEditTracks() {
|
|
return false;
|
|
}
|
|
MprisAPI::~MprisAPI() {
|
|
unregisterAdaptor();
|
|
}
|
|
DBusAPI::DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon)
|
|
: AdaptorInterfaces(connection, std::move(objectPath))
|
|
, daemon(daemon)
|
|
, playback(playback)
|
|
, connection(connection)
|
|
{
|
|
registerAdaptor();
|
|
playback->register_handle(this);
|
|
auto mprisConnection = sdbus::createSessionBusConnection("org.mpris.MediaPlayer2.Looper");
|
|
auto &mprisConRef = *mprisConnection.release();
|
|
mpris = new MprisAPI(mprisConRef, "/org/mpris/MediaPlayer2", this);
|
|
threadFunc = std::thread([this]() {
|
|
while (!threadExitFlag.load()) {
|
|
Update();
|
|
std::this_thread::yield();
|
|
}
|
|
});
|
|
connection.enterEventLoopAsync();
|
|
}
|
|
const char *DBusAPI::objectPath = "/com/complecwaft/Looper";
|
|
const char *DBusAPI::busName = "com.complecwaft.Looper";
|
|
DBusAPI *DBusAPI::Create(Playback *playback, bool daemon) {
|
|
auto connection = sdbus::createSessionBusConnection(busName);
|
|
auto &con_ref = *connection.release();
|
|
return new DBusAPI(playback, con_ref, objectPath, daemon);
|
|
}
|
|
double DBusAPI::Position() {
|
|
return playback->GetPosition();
|
|
}
|
|
void DBusAPI::Position(const double &value) {
|
|
playback->Seek(value);
|
|
}
|
|
double DBusAPI::Length() {
|
|
return playback->GetLength();
|
|
}
|
|
std::string DBusAPI::FilePath() {
|
|
return playback->get_current_file().value_or("");
|
|
}
|
|
std::string DBusAPI::FileTitle() {
|
|
return playback->get_current_title().value_or("");
|
|
}
|
|
void DBusAPI::Update() {
|
|
uint16_t signals = playback->handle_signals(PlaybackSignalErrorOccurred|PlaybackSignalFileChanged|PlaybackSignalPaused|PlaybackSignalPitchChanged|PlaybackSignalResumed|PlaybackSignalSeeked|PlaybackSignalSpeedChanged|PlaybackSignalStarted|PlaybackSignalStopped|PlaybackSignalTempoChanged, this);
|
|
if (signals & PlaybackSignalErrorOccurred) {
|
|
auto error = playback->GetError(this).value_or("Unknown error.");
|
|
emitErrorOccurred(error, error);
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"PlaybackStatus", "CanSeek", "CanPlay", "CanPause", "Metadata"});
|
|
}
|
|
if (signals & PlaybackSignalFileChanged) {
|
|
emitFileChanged(playback->get_current_file().value_or(""), playback->get_current_title().value_or(""));
|
|
mpris->emitTrackMetadataChanged(mpris->playing_track_id, mpris->Metadata());
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"Position", "Metadata"});
|
|
}
|
|
if (signals & (PlaybackSignalPaused|PlaybackSignalResumed)) {
|
|
emitPauseChanged(playback->IsPaused());
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"PlaybackStatus"});
|
|
}
|
|
if (signals & PlaybackSignalStopped) {
|
|
emitStopped();
|
|
emitPauseChanged(playback->IsPaused());
|
|
mpris->emitTrackRemoved(mpris->playing_track_id);
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"PlaybackStatus", "CanSeek", "CanPlay", "CanPause", "Metadata"});
|
|
}
|
|
if (signals & PlaybackSignalPitchChanged) {
|
|
emitPitchChanged(playback->GetPitch());
|
|
}
|
|
if (signals & PlaybackSignalTempoChanged) {
|
|
emitTempoChanged(playback->GetTempo());
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"Rate"});
|
|
}
|
|
if (signals & PlaybackSignalSpeedChanged) {
|
|
emitSpeedChanged(playback->GetSpeed());
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"Rate"});
|
|
}
|
|
if (signals & PlaybackSignalSeeked) {
|
|
emitSeeked(playback->GetPosition());
|
|
mpris->emitSeeked(playback->GetPosition() * 1000000);
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"Position"});
|
|
}
|
|
if (signals & PlaybackSignalStarted) {
|
|
emitPlaybackEngineStarted();
|
|
mpris->emitTrackAdded(mpris->Metadata(), mpris->empty_track_id);
|
|
mpris->sendPropertiesChanged(mpris->playerInterface, {"CanSeek", "CanPlay", "CanPause", "Position", "Metadata", "PlaybackStatus"});
|
|
}
|
|
}
|
|
void DBusAPI::Activate(const std::map<std::string, sdbus::Variant> &platform_data) {
|
|
// TODO: Raise window?
|
|
}
|
|
void DBusAPI::Open(const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant> &platform_data) {
|
|
if (uris.size() > 0) {
|
|
Start(uris[0], true);
|
|
}
|
|
}
|
|
void DBusAPI::ActivateAction(const std::string &action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant> &platform_data) {
|
|
// Nothing yet.
|
|
}
|
|
std::string uriToPath(const std::string &uri) {
|
|
|
|
const static std::string file_prefix = "file:";
|
|
std::string tmp = uri;
|
|
if (tmp.starts_with(file_prefix)) {
|
|
tmp = tmp.substr(file_prefix.length());
|
|
return tmp;
|
|
}
|
|
return uri;
|
|
}
|
|
void DBusAPI::Start(const std::string &path, const bool &isUri) {
|
|
StartWithStreamIndex(path, isUri, 0);
|
|
}
|
|
void DBusAPI::StartWithStreamIndex(const std::string &path, const bool &isUri, const uint32_t &streamIndex) {
|
|
if (isUri) {
|
|
StartWithStreamIndex(uriToPath(path), false, streamIndex);
|
|
} else {
|
|
playback->Start(path, (int)streamIndex);
|
|
}
|
|
}
|
|
void DBusAPI::Load(const std::string &path, const bool &isUri) {
|
|
if (isUri) {
|
|
Load(uriToPath(path), false);
|
|
} else {
|
|
playback->Load(path);
|
|
}
|
|
}
|
|
void DBusAPI::Stop() {
|
|
playback->Stop();
|
|
}
|
|
void DBusAPI::TogglePause() {
|
|
playback->Pause();
|
|
}
|
|
double DBusAPI::Speed() {
|
|
return playback->GetSpeed();
|
|
}
|
|
void DBusAPI::Speed(const double &value) {
|
|
playback->SetSpeed(value);
|
|
}
|
|
double DBusAPI::Tempo() {
|
|
return playback->GetTempo();
|
|
}
|
|
void DBusAPI::Tempo(const double &value) {
|
|
playback->SetTempo(value);
|
|
}
|
|
double DBusAPI::Pitch() {
|
|
return playback->GetPitch();
|
|
}
|
|
void DBusAPI::Pitch(const double &value) {
|
|
playback->SetPitch(value);
|
|
}
|
|
double DBusAPI::Volume() {
|
|
return playback->GetVolume();
|
|
}
|
|
void DBusAPI::Volume(const double &value) {
|
|
playback->SetVolume(value);
|
|
}
|
|
bool DBusAPI::Paused() {
|
|
return playback->IsPaused();
|
|
}
|
|
void DBusAPI::Paused(const bool &value) {
|
|
if (value != playback->IsPaused()) {
|
|
playback->Pause();
|
|
}
|
|
}
|
|
bool DBusAPI::IsStopped() {
|
|
return playback->IsStopped();
|
|
}
|
|
bool DBusAPI::IsDaemon() {
|
|
return daemon;
|
|
}
|
|
std::string DBusAPI::CreateHandle() {
|
|
size_t idx = handle_idx++;
|
|
size_t hash = std::hash<size_t>()(idx);
|
|
size_t rand = (size_t)rand_engine();
|
|
size_t rand_hash = std::hash<size_t>()(rand);
|
|
std::string value = std::format("{}:{}:{}", idx, hash, rand_hash);
|
|
handles[value] = (void*)&value;
|
|
playback->register_handle(handles[value]);
|
|
return value;
|
|
}
|
|
void DBusAPI::ClearHandle(const std::string &handle) {
|
|
if (handles.contains(handle)) {
|
|
playback->unregister_handle(handles[handle]);
|
|
handles.erase(handle);
|
|
}
|
|
}
|
|
std::deque<std::string> *DBusAPI::get_errors_by_handle(const std::string &handle) {
|
|
if (handles.contains(handle)) {
|
|
auto *v_handle = handles[handle];
|
|
if (playback->errors_occurred.contains(v_handle)) {
|
|
return &playback->errors_occurred[v_handle];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
std::string DBusAPI::PopFront(const std::string &handle) {
|
|
auto *errors_list = get_errors_by_handle(handle);
|
|
if (errors_list != nullptr) {
|
|
if (errors_list->empty()) {
|
|
return "";
|
|
}
|
|
std::string output = errors_list->front();
|
|
errors_list->pop_front();
|
|
return output;
|
|
}
|
|
return "";
|
|
}
|
|
std::string DBusAPI::PopBack(const std::string &handle) {
|
|
auto *errors_list = get_errors_by_handle(handle);
|
|
if (errors_list != nullptr) {
|
|
if (errors_list->empty()) {
|
|
return "";
|
|
}
|
|
std::string output = errors_list->back();
|
|
errors_list->pop_back();
|
|
return output;
|
|
}
|
|
return "";
|
|
}
|
|
std::string DBusAPI::PeekFront(const std::string &handle) {
|
|
auto *errors_list = get_errors_by_handle(handle);
|
|
if (errors_list != nullptr) {
|
|
if (errors_list->empty()) {
|
|
return "";
|
|
}
|
|
std::string output = errors_list->front();
|
|
return output;
|
|
}
|
|
return "";
|
|
}
|
|
void DBusAPI::Quit() {
|
|
UIBackend::running_ui_backend->QuitHandler();
|
|
}
|
|
std::string DBusAPI::PeekBack(const std::string &handle) {
|
|
auto *errors_list = get_errors_by_handle(handle);
|
|
if (errors_list != nullptr) {
|
|
if (errors_list->empty()) {
|
|
return "";
|
|
}
|
|
std::string output = errors_list->back();
|
|
return output;
|
|
}
|
|
return "";
|
|
}
|
|
uint32_t DBusAPI::GetCount(const std::string &handle) {
|
|
auto *errors_list = get_errors_by_handle(handle);
|
|
if (errors_list != nullptr) {
|
|
return errors_list->size();
|
|
}
|
|
return 0;
|
|
}
|
|
bool DBusAPI::IsEmpty(const std::string& handle) {
|
|
return GetCount(handle) == 0;
|
|
}
|
|
void DBusAPI::Clear(const std::string &handle) {
|
|
auto *errors_list = get_errors_by_handle(handle);
|
|
if (errors_list != nullptr) {
|
|
errors_list->clear();
|
|
}
|
|
}
|
|
std::vector<std::string> DBusAPI::PeekAll(const std::string &handle) {
|
|
auto *errors_list = get_errors_by_handle(handle);
|
|
if (errors_list != nullptr) {
|
|
std::vector<std::string> output;
|
|
for (auto error : (*errors_list)) {
|
|
output.push_back(error);
|
|
}
|
|
return output;
|
|
}
|
|
return std::vector<std::string>();
|
|
}
|
|
std::vector<std::string> DBusAPI::GetAllAndClear(const std::string &handle) {
|
|
auto output = PeekAll(handle);
|
|
Clear(handle);
|
|
return output;
|
|
}
|
|
uint32_t DBusAPI::StreamIdx() {
|
|
return playback->get_current_stream();
|
|
}
|
|
std::vector<std::string> DBusAPI::GetStreams() {
|
|
return playback->get_streams();
|
|
}
|
|
void DBusAPI::PlayStream(const uint32_t &idx) {
|
|
playback->play_stream((int)idx);
|
|
}
|
|
DBusAPI::~DBusAPI() {
|
|
threadExitFlag.store(true);
|
|
threadFunc.join();
|
|
unregisterAdaptor();
|
|
}
|
|
bool DBusAPISender::isOnlyInstance() {
|
|
bool output;
|
|
try {
|
|
auto *tmp = Create();
|
|
output = tmp == nullptr;
|
|
delete tmp;
|
|
return output;
|
|
} catch (sdbus::Error) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::optional<std::string> DBusAPISender::get_current_file() {
|
|
if (IsStopped()) {
|
|
return {};
|
|
} else {
|
|
return FilePath();
|
|
}
|
|
}
|
|
std::optional<std::string> DBusAPISender::get_current_title() {
|
|
if (IsStopped()) {
|
|
return {};
|
|
} else {
|
|
return FileTitle();
|
|
}
|
|
}
|
|
void DBusAPISender::onPlaybackEngineStarted() {
|
|
set_signal(PlaybackSignalStarted);
|
|
}
|
|
void DBusAPISender::onFileChanged(const std::string &FilePath, const std::string &Title) {
|
|
set_signal(PlaybackSignalFileChanged);
|
|
}
|
|
void DBusAPISender::onSpeedChanged(const double &new_speed) {
|
|
set_signal(PlaybackSignalSpeedChanged);
|
|
}
|
|
void DBusAPISender::onTempoChanged(const double &new_tempo) {
|
|
set_signal(PlaybackSignalTempoChanged);
|
|
}
|
|
void DBusAPISender::onPitchChanged(const double &new_pitch) {
|
|
set_signal(PlaybackSignalPitchChanged);
|
|
}
|
|
void DBusAPISender::onPauseChanged(const bool &now_paused) {
|
|
set_signal(now_paused ? PlaybackSignalPaused : PlaybackSignalResumed);
|
|
}
|
|
void DBusAPISender::onStopped() {
|
|
set_signal(PlaybackSignalStopped);
|
|
}
|
|
void DBusAPISender::onErrorOccurred(const std::string &error_desc, const std::string &error_type) {
|
|
set_signal(PlaybackSignalErrorOccurred);
|
|
}
|
|
void DBusAPISender::onSeeked(const double &to_position) {
|
|
set_signal(PlaybackSignalSeeked);
|
|
}
|
|
double DBusAPISender::GetPosition() {
|
|
return ProxyInterfaces::Position();
|
|
}
|
|
double DBusAPISender::GetLength() {
|
|
return ProxyInterfaces::Length();
|
|
}
|
|
void DBusAPISender::Seek(double position) {
|
|
ProxyInterfaces::Position(position);
|
|
}
|
|
void DBusAPISender::Start(std::string filePath, int streamIdx) {
|
|
ProxyInterfaces::Start(filePath, streamIdx);
|
|
}
|
|
void DBusAPISender::Load(std::string filePath) {
|
|
ProxyInterfaces::Load(filePath, false);
|
|
}
|
|
int DBusAPISender::get_current_stream() {
|
|
return (int)ProxyInterfaces::StreamIdx();
|
|
}
|
|
std::vector<std::string> DBusAPISender::get_streams() {
|
|
return ProxyInterfaces::GetStreams();
|
|
}
|
|
bool DBusAPISender::IsPaused() {
|
|
return ProxyInterfaces::Paused();
|
|
}
|
|
void DBusAPISender::Pause() {
|
|
ProxyInterfaces::TogglePause();
|
|
}
|
|
void DBusAPISender::Stop() {
|
|
ProxyInterfaces::Stop();
|
|
}
|
|
bool DBusAPISender::IsStopped() {
|
|
return ProxyInterfaces::IsStopped();
|
|
}
|
|
void DBusAPISender::SetTempo(float tempo) {
|
|
ProxyInterfaces::Tempo(tempo);
|
|
}
|
|
void DBusAPISender::SetPitch(float pitch) {
|
|
ProxyInterfaces::Pitch(pitch);
|
|
}
|
|
void DBusAPISender::SetSpeed(float speed) {
|
|
ProxyInterfaces::Speed(speed);
|
|
}
|
|
void DBusAPISender::SetVolume(float volume) {
|
|
ProxyInterfaces::Volume(volume);
|
|
}
|
|
float DBusAPISender::GetTempo() {
|
|
return (float)ProxyInterfaces::Tempo();
|
|
}
|
|
float DBusAPISender::GetPitch() {
|
|
return (float)ProxyInterfaces::Pitch();
|
|
}
|
|
float DBusAPISender::GetSpeed() {
|
|
return (float)ProxyInterfaces::Speed();
|
|
}
|
|
float DBusAPISender::GetVolume() {
|
|
return (float)ProxyInterfaces::Volume();
|
|
}
|
|
void DBusAPISender::Update() {
|
|
|
|
}
|
|
DBusAPISender *DBusAPISender::Create() {
|
|
try {
|
|
auto connection = sdbus::createSessionBusConnection();
|
|
auto &con_ref = *connection.release();
|
|
auto *output = new DBusAPISender(con_ref, DBusAPI::busName, DBusAPI::objectPath);
|
|
return output;
|
|
} catch (sdbus::Error error) {
|
|
DEBUG.writefln("sdbus::Error: %s: %s", error.getName().c_str(), error.getMessage().c_str());
|
|
return nullptr;
|
|
}
|
|
}
|
|
DBusAPISender::DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath)
|
|
: ProxyInterfaces(connection, std::move(busName), std::move(objectPath)) {
|
|
registerProxy();
|
|
DEBUG.writeln("Pinging DBus API to check for its existance.");
|
|
Ping();
|
|
DEBUG.writeln("Requesting handle...");
|
|
handle = CreateHandle();
|
|
DEBUG.writeln("Preparing updates for UI frontend...");
|
|
if (IsStopped()) {
|
|
set_signal(PlaybackSignalStopped);
|
|
} else {
|
|
filePath = ProxyInterfaces::FilePath();
|
|
title = ProxyInterfaces::FileTitle();
|
|
length = ProxyInterfaces::Length();
|
|
set_signal(PlaybackSignalStarted|PlaybackSignalSeeked|PlaybackSignalFileChanged);
|
|
}
|
|
if (IsPaused()) {
|
|
set_signal(PlaybackSignalPaused);
|
|
} else {
|
|
set_signal(PlaybackSignalResumed);
|
|
}
|
|
set_signal(PlaybackSignalSpeedChanged|PlaybackSignalPitchChanged|PlaybackSignalTempoChanged);
|
|
connection.enterEventLoopAsync();
|
|
}
|
|
DBusAPISender::~DBusAPISender() {
|
|
DEBUG.writeln("Requesting handle deletion...");
|
|
ClearHandle(handle);
|
|
unregisterProxy();
|
|
} |