Compare commits
12 Commits
master
...
proposed-p
Author | SHA1 | Date | |
---|---|---|---|
|
6512fd0186 | ||
|
8d9014f459 | ||
|
462dd3a4d1 | ||
|
a50de90fe9 | ||
|
2fb8115d19 | ||
|
cdd8d9eabf | ||
|
17dd691791 | ||
|
e714f74e65 | ||
|
0aa259455e | ||
|
d79a20063f | ||
|
74e01b772c | ||
|
a7c0a17fe1 |
@ -1 +1 @@
|
||||
Subproject commit 9b901bad31f9809f4bfaefc8795589db3ce46250
|
||||
Subproject commit 99b77d8909b20cedba452acd18f3e5e0ecc6daa8
|
@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
#include <helper/audio/audio.hpp>
|
||||
#if defined(__linux__)
|
||||
#include <helper/audio/linux/backend.hpp>
|
||||
#elif defined(_WIN32)
|
||||
@ -14,16 +13,15 @@
|
||||
#include <helper/icons/icons.hpp>
|
||||
#include <helper/queue/queue.hpp>
|
||||
#include <helper/ytdl/youtube-dl.hpp>
|
||||
#include <lock.hpp>
|
||||
#include <memory>
|
||||
#include <ui/ui.hpp>
|
||||
#include <var_guard.hpp>
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
namespace Globals
|
||||
{
|
||||
inline Objects::Data gData;
|
||||
inline Objects::Audio gAudio;
|
||||
#if defined(__linux__)
|
||||
inline std::shared_ptr<Objects::IconFetcher> gIcons;
|
||||
inline std::shared_ptr<Objects::AudioBackend> gAudioBackend;
|
||||
@ -40,7 +38,7 @@ namespace Soundux
|
||||
inline std::shared_ptr<Instance::Guard> gGuard;
|
||||
|
||||
/* Allows for fast & easy sound access, is populated on start up */
|
||||
inline sxl::var_guard<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gSounds;
|
||||
inline sxl::var_guard<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gFavorites;
|
||||
inline sxl::lock<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gSounds;
|
||||
inline sxl::lock<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gFavorites;
|
||||
} // namespace Globals
|
||||
} // namespace Soundux
|
@ -23,13 +23,13 @@ namespace Soundux
|
||||
} // namespace traits
|
||||
namespace Objects
|
||||
{
|
||||
std::shared_ptr<Hotkeys> Hotkeys::createInstance()
|
||||
std::unique_ptr<Hotkeys> Hotkeys::createInstance()
|
||||
{
|
||||
std::shared_ptr<Hotkeys> rtn;
|
||||
std::unique_ptr<Hotkeys> rtn;
|
||||
#if defined(__linux__)
|
||||
rtn = std::shared_ptr<X11>(new X11()); // NOLINT
|
||||
rtn = std::unique_ptr<X11>(new X11()); // NOLINT
|
||||
#elif defined(_WIN32)
|
||||
rtn = std::shared_ptr<WindowsHotkeys>(new WindowsHotkeys()); // NOLINT
|
||||
rtn = std::unique_ptr<WindowsHotkeys>(new WindowsHotkeys()); // NOLINT
|
||||
#endif
|
||||
rtn->setup();
|
||||
return rtn;
|
||||
@ -58,14 +58,14 @@ namespace Soundux
|
||||
key.byte2 = byte2;
|
||||
key.type = Enums::KeyType::Midi;
|
||||
|
||||
if (byte0 == 144)
|
||||
{
|
||||
onKeyDown(key);
|
||||
}
|
||||
else if (byte0 == 128)
|
||||
if (byte0 == 128 || (byte0 == 144 && byte2 == 0))
|
||||
{
|
||||
onKeyUp(key);
|
||||
}
|
||||
else if (byte0 == 144)
|
||||
{
|
||||
onKeyDown(key);
|
||||
}
|
||||
else if (byte0 == 176)
|
||||
{
|
||||
if (shouldNotifyKnob)
|
||||
@ -142,40 +142,27 @@ namespace Soundux
|
||||
}
|
||||
return false;
|
||||
}
|
||||
template <typename T> std::optional<Sound> getBestMatch(const T &list, const std::vector<Key> &pressedKeys)
|
||||
template <typename T> std::shared_ptr<Sound> getBestMatch(const T &list, const std::vector<Key> &pressedKeys)
|
||||
{
|
||||
std::optional<Sound> rtn;
|
||||
std::shared_ptr<Sound> rtn;
|
||||
|
||||
for (const auto &_sound : list)
|
||||
for (const auto &sound : list)
|
||||
{
|
||||
const auto &sound = [&]() constexpr
|
||||
{
|
||||
if constexpr (traits::is_pair<std::decay_t<decltype(_sound)>>::value)
|
||||
{
|
||||
return _sound.second.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return _sound;
|
||||
}
|
||||
}
|
||||
();
|
||||
|
||||
if (sound.hotkeys.empty())
|
||||
if (sound->getHotkeys().empty())
|
||||
continue;
|
||||
|
||||
if (pressedKeys == sound.hotkeys)
|
||||
if (pressedKeys == sound->getHotkeys())
|
||||
{
|
||||
rtn = sound;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rtn && rtn->hotkeys.size() > sound.hotkeys.size())
|
||||
if (rtn && rtn->getHotkeys().size() > sound->getHotkeys().size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isCloseMatch(pressedKeys, sound.hotkeys))
|
||||
if (isCloseMatch(pressedKeys, sound->getHotkeys()))
|
||||
{
|
||||
rtn = sound;
|
||||
}
|
||||
@ -200,11 +187,10 @@ namespace Soundux
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<Sound> bestMatch;
|
||||
|
||||
std::shared_ptr<Sound> bestMatch;
|
||||
if (Globals::gSettings.tabHotkeysOnly)
|
||||
{
|
||||
if (Globals::gData.isOnFavorites)
|
||||
if (Globals::gData.onFavorites)
|
||||
{
|
||||
auto sounds = Globals::gData.getFavorites();
|
||||
bestMatch = getBestMatch(sounds, pressedKeys);
|
||||
@ -214,14 +200,14 @@ namespace Soundux
|
||||
auto tab = Globals::gData.getTab(Globals::gSettings.selectedTab);
|
||||
if (tab)
|
||||
{
|
||||
bestMatch = getBestMatch(tab->sounds, pressedKeys);
|
||||
bestMatch = getBestMatch(tab->getSounds(), pressedKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto scopedSounds = Globals::gSounds.scoped();
|
||||
bestMatch = getBestMatch(*scopedSounds, pressedKeys);
|
||||
auto gSounds = Globals::gData.getSounds();
|
||||
bestMatch = getBestMatch(gSounds, pressedKeys);
|
||||
}
|
||||
|
||||
if (bestMatch)
|
||||
@ -229,7 +215,7 @@ namespace Soundux
|
||||
auto pSound = Globals::gGui->playSound(bestMatch->id);
|
||||
if (pSound)
|
||||
{
|
||||
Globals::gGui->onSoundPlayed(*pSound);
|
||||
Globals::gGui->onSoundPlayed(pSound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ namespace Soundux
|
||||
std::atomic<bool> shouldNotifyKnob = false;
|
||||
|
||||
public:
|
||||
static std::shared_ptr<Hotkeys> createInstance();
|
||||
static std::unique_ptr<Hotkeys> createInstance();
|
||||
|
||||
public:
|
||||
virtual void notify(bool);
|
||||
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
#include <core/enums/enums.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
namespace Objects
|
||||
{
|
||||
struct Key;
|
||||
|
||||
struct Sound
|
||||
{
|
||||
std::uint32_t id;
|
||||
std::string name;
|
||||
std::string path;
|
||||
bool isFavorite = false;
|
||||
|
||||
std::vector<Key> hotkeys;
|
||||
std::uint64_t modifiedDate;
|
||||
|
||||
std::optional<int> localVolume;
|
||||
std::optional<int> remoteVolume;
|
||||
};
|
||||
|
||||
struct Tab
|
||||
{
|
||||
std::uint32_t id; //* Equal to index
|
||||
std::string name;
|
||||
std::string path;
|
||||
|
||||
std::vector<Sound> sounds;
|
||||
Enums::SortMode sortMode = Enums::SortMode::ModifiedDate_Descending;
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
67
src/core/objects/sound.cpp
Normal file
67
src/core/objects/sound.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "sound.hpp"
|
||||
|
||||
namespace Soundux::Objects
|
||||
{
|
||||
Sound::Sound(std::uint64_t id, std::string name, std::string path)
|
||||
: id(id), name(std::move(name)), path(std::move(path))
|
||||
{
|
||||
}
|
||||
|
||||
bool Sound::isFavorite() const
|
||||
{
|
||||
return favorite;
|
||||
}
|
||||
void Sound::markFavorite(bool state)
|
||||
{
|
||||
// TODO(): Modify gFavorites
|
||||
favorite = state;
|
||||
}
|
||||
|
||||
std::vector<Key> Sound::getHotkeys() const
|
||||
{
|
||||
return hotkeys.copy();
|
||||
}
|
||||
void Sound::setHotkeys(const std::vector<Key> &keys)
|
||||
{
|
||||
hotkeys.assign(keys);
|
||||
}
|
||||
|
||||
void Sound::setModifiedDate(std::uint64_t newDate)
|
||||
{
|
||||
modifiedDate = newDate;
|
||||
}
|
||||
std::uint64_t Sound::getModifiedDate() const
|
||||
{
|
||||
return modifiedDate;
|
||||
}
|
||||
|
||||
std::string Sound::getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
std::string Sound::getPath() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
std::uint64_t Sound::getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
std::optional<int> Sound::getLocalVolume() const
|
||||
{
|
||||
return localVolume.copy();
|
||||
}
|
||||
std::optional<int> Sound::getRemoteVolume() const
|
||||
{
|
||||
return localVolume.copy();
|
||||
}
|
||||
void Sound::setLocalVolume(std::optional<std::uint32_t> newVolume)
|
||||
{
|
||||
localVolume.assign(newVolume);
|
||||
}
|
||||
void Sound::setRemoteVolume(std::optional<std::uint32_t> newVolume)
|
||||
{
|
||||
remoteVolume.assign(newVolume);
|
||||
}
|
||||
} // namespace Soundux::Objects
|
50
src/core/objects/sound.hpp
Normal file
50
src/core/objects/sound.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <core/hotkeys/keys.hpp>
|
||||
#include <cstdint>
|
||||
#include <lock.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
namespace Objects
|
||||
{
|
||||
struct Sound
|
||||
{
|
||||
std::uint64_t id;
|
||||
std::string name;
|
||||
std::string path;
|
||||
|
||||
sxl::lock<std::vector<Key>> hotkeys;
|
||||
sxl::lock<std::optional<std::uint32_t>> localVolume;
|
||||
sxl::lock<std::optional<std::uint32_t>> remoteVolume;
|
||||
|
||||
std::atomic<bool> favorite = false;
|
||||
std::atomic<std::uint64_t> modifiedDate;
|
||||
|
||||
public:
|
||||
Sound(std::uint64_t, std::string, std::string);
|
||||
|
||||
public:
|
||||
bool isFavorite() const;
|
||||
void markFavorite(bool);
|
||||
|
||||
std::vector<Key> getHotkeys() const;
|
||||
void setHotkeys(const std::vector<Key> &);
|
||||
|
||||
void setModifiedDate(std::uint64_t);
|
||||
std::uint64_t getModifiedDate() const;
|
||||
|
||||
std::string getName() const;
|
||||
std::string getPath() const;
|
||||
std::uint64_t getId() const;
|
||||
|
||||
std::optional<int> getLocalVolume() const;
|
||||
std::optional<int> getRemoteVolume() const;
|
||||
void setLocalVolume(std::optional<std::uint32_t>);
|
||||
void setRemoteVolume(std::optional<std::uint32_t>);
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
@ -1,488 +0,0 @@
|
||||
#include "audio.hpp"
|
||||
#include <core/global/globals.hpp>
|
||||
#include <fancy.hpp>
|
||||
#if defined(_WIN32)
|
||||
#include <helper/misc/misc.hpp>
|
||||
#endif
|
||||
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include <miniaudio.h>
|
||||
|
||||
namespace Soundux::Objects
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
using Soundux::Helpers::widen;
|
||||
#endif
|
||||
|
||||
void Audio::setup()
|
||||
{
|
||||
#if defined(__linux__)
|
||||
nullSink = std::nullopt;
|
||||
#endif
|
||||
for (const auto &device : getAudioDevices())
|
||||
{
|
||||
if (device.isDefault)
|
||||
{
|
||||
defaultPlayback = device;
|
||||
}
|
||||
#if defined(__linux__)
|
||||
if (device.name == "soundux_sink")
|
||||
{
|
||||
nullSink = device;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void Audio::destroy()
|
||||
{
|
||||
stopAll();
|
||||
}
|
||||
std::optional<PlayingSound> Audio::play(const Objects::Sound &sound,
|
||||
const std::optional<Objects::AudioDevice> &playbackDevice)
|
||||
{
|
||||
static std::atomic<std::uint64_t> id = 0;
|
||||
|
||||
auto *decoder = new ma_decoder;
|
||||
#if defined(_WIN32)
|
||||
auto res = ma_decoder_init_file_w(widen(sound.path).c_str(), nullptr, decoder);
|
||||
#else
|
||||
auto res = ma_decoder_init_file(sound.path.c_str(), nullptr, decoder);
|
||||
#endif
|
||||
|
||||
if (res != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to create decoder from file: " << sound.path << ", error: " >>
|
||||
res << std::endl;
|
||||
delete decoder;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto *device = new ma_device;
|
||||
auto config = ma_device_config_init(ma_device_type_playback);
|
||||
auto length_in_pcm_frames = ma_decoder_get_length_in_pcm_frames(decoder);
|
||||
|
||||
config.dataCallback = data_callback;
|
||||
config.sampleRate = decoder->outputSampleRate;
|
||||
config.playback.format = decoder->outputFormat;
|
||||
config.playback.channels = decoder->outputChannels;
|
||||
|
||||
auto pSound = std::make_shared<PlayingSound>();
|
||||
config.pUserData = reinterpret_cast<void *>(static_cast<PlayingSound *>(pSound.get()));
|
||||
|
||||
if (playbackDevice)
|
||||
{
|
||||
config.playback.pDeviceID = &playbackDevice->raw.id;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.playback.pDeviceID = &defaultPlayback.raw.id;
|
||||
}
|
||||
|
||||
if (ma_device_init(nullptr, &config, device) != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to create device" << std::endl;
|
||||
ma_decoder_uninit(decoder);
|
||||
delete decoder;
|
||||
delete device;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (playbackDevice)
|
||||
{
|
||||
if (sound.remoteVolume)
|
||||
{
|
||||
device->masterVolumeFactor = static_cast<float>(*sound.remoteVolume) / 100.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
device->masterVolumeFactor = static_cast<float>(Globals::gSettings.remoteVolume) / 100.f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sound.localVolume)
|
||||
{
|
||||
device->masterVolumeFactor = static_cast<float>(*sound.localVolume) / 100.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
device->masterVolumeFactor = static_cast<float>(Globals::gSettings.localVolume) / 100.f;
|
||||
}
|
||||
}
|
||||
|
||||
if (ma_device_start(device) != MA_SUCCESS)
|
||||
{
|
||||
ma_device_uninit(device);
|
||||
ma_decoder_uninit(decoder);
|
||||
|
||||
delete device;
|
||||
delete decoder;
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to play sound " << sound.path << std::endl;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto soundId = ++id;
|
||||
|
||||
pSound->id = soundId;
|
||||
pSound->sound = sound;
|
||||
pSound->raw.device = device;
|
||||
pSound->raw.decoder = decoder;
|
||||
pSound->length = length_in_pcm_frames;
|
||||
pSound->sampleRate = config.sampleRate;
|
||||
pSound->playbackDevice = playbackDevice ? *playbackDevice : defaultPlayback;
|
||||
pSound->lengthInMs = static_cast<std::uint64_t>(static_cast<double>(pSound->length) /
|
||||
static_cast<double>(config.sampleRate) * 1000);
|
||||
|
||||
playingSounds->emplace(soundId, pSound);
|
||||
return *pSound;
|
||||
}
|
||||
void Audio::stopAll()
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
while (!scoped->empty())
|
||||
{
|
||||
auto &sound = scoped->begin()->second;
|
||||
if (sound->raw.device && sound->raw.decoder)
|
||||
{
|
||||
ma_device_uninit(sound->raw.device);
|
||||
ma_decoder_uninit(sound->raw.decoder);
|
||||
}
|
||||
|
||||
sound->raw.device = nullptr;
|
||||
sound->raw.decoder = nullptr;
|
||||
|
||||
scoped->erase(sound->id);
|
||||
}
|
||||
}
|
||||
bool Audio::stop(const std::uint32_t &soundId)
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
if (scoped->find(soundId) != scoped->end())
|
||||
{
|
||||
auto &sound = scoped->at(soundId);
|
||||
if (sound->raw.device && sound->raw.decoder)
|
||||
{
|
||||
ma_device_uninit(sound->raw.device);
|
||||
ma_decoder_uninit(sound->raw.decoder);
|
||||
}
|
||||
|
||||
sound->raw.device = nullptr;
|
||||
sound->raw.decoder = nullptr;
|
||||
|
||||
scoped->erase(sound->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to stop sound with id " << soundId << ", sound does not exist"
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
std::optional<PlayingSound> Audio::pause(const std::uint32_t &soundId)
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
if (scoped->find(soundId) != scoped->end())
|
||||
{
|
||||
auto &sound = scoped->at(soundId);
|
||||
|
||||
if (!sound->paused)
|
||||
{
|
||||
if (ma_device_get_state(sound->raw.device) == MA_STATE_STARTED)
|
||||
{
|
||||
ma_device_stop(sound->raw.device);
|
||||
}
|
||||
sound->paused = true;
|
||||
}
|
||||
|
||||
return *sound;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to pause sound with id " << soundId << ", sound does not exist"
|
||||
<< std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<PlayingSound> Audio::repeat(const std::uint32_t &soundId, bool shouldRepeat)
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
if (scoped->find(soundId) != scoped->end())
|
||||
{
|
||||
auto &sound = scoped->at(soundId);
|
||||
sound->repeat = shouldRepeat;
|
||||
|
||||
return *sound;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to set repeat for sound with id " << soundId
|
||||
<< ", sound does not exist" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<PlayingSound> Audio::resume(const std::uint32_t &soundId)
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
if (scoped->find(soundId) != scoped->end())
|
||||
{
|
||||
auto &sound = scoped->at(soundId);
|
||||
|
||||
if (sound->paused)
|
||||
{
|
||||
if (ma_device_get_state(sound->raw.device) == MA_STATE_STOPPED)
|
||||
{
|
||||
ma_device_start(sound->raw.device);
|
||||
}
|
||||
sound->paused = false;
|
||||
}
|
||||
|
||||
return *sound;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to resume sound with id " << soundId << ", sound does not exist "
|
||||
<< std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
void Audio::onFinished(PlayingSound sound)
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
if (scoped->find(sound.id) != scoped->end())
|
||||
{
|
||||
ma_device_uninit(sound.raw.device);
|
||||
ma_decoder_uninit(sound.raw.decoder);
|
||||
|
||||
sound.raw.device = nullptr;
|
||||
sound.raw.decoder = nullptr;
|
||||
|
||||
Globals::gGui->onSoundFinished(sound);
|
||||
scoped->erase(sound.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Sound finished but is not playing" << std::endl;
|
||||
}
|
||||
}
|
||||
void Audio::onSoundProgressed(PlayingSound *sound, std::uint64_t frames)
|
||||
{
|
||||
sound->readFrames += frames;
|
||||
sound->buffer += frames;
|
||||
|
||||
if (sound->buffer > (sound->sampleRate / 2))
|
||||
{
|
||||
sound->readInMs = static_cast<std::uint64_t>(
|
||||
(static_cast<double>(sound->readFrames) / static_cast<double>(sound->length)) *
|
||||
static_cast<double>(sound->lengthInMs));
|
||||
|
||||
Globals::gGui->onSoundProgressed(*sound);
|
||||
|
||||
sound->buffer = 0;
|
||||
}
|
||||
}
|
||||
void Audio::onSoundSeeked(PlayingSound *sound, std::uint64_t frame)
|
||||
{
|
||||
sound->shouldSeek = false;
|
||||
sound->readFrames = frame;
|
||||
sound->readInMs = static_cast<std::uint64_t>((static_cast<double>(frame) / static_cast<double>(sound->length)) *
|
||||
static_cast<double>(sound->lengthInMs));
|
||||
}
|
||||
std::optional<PlayingSound> Audio::seek(const std::uint32_t &soundId, std::uint64_t position)
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
if (scoped->find(soundId) != scoped->end())
|
||||
{
|
||||
auto &sound = scoped->at(soundId);
|
||||
sound->seekTo =
|
||||
static_cast<std::uint64_t>((static_cast<double>(position) / static_cast<double>(sound->lengthInMs)) *
|
||||
static_cast<double>(sound->length));
|
||||
sound->shouldSeek = true;
|
||||
|
||||
auto rtn = *sound;
|
||||
rtn.readFrames = rtn.seekTo;
|
||||
rtn.readInMs =
|
||||
static_cast<std::uint64_t>((static_cast<double>(rtn.seekTo) / static_cast<double>(rtn.length)) *
|
||||
static_cast<double>(rtn.lengthInMs));
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to seek sound with id " << soundId << ", sound does not exist"
|
||||
<< std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
void Audio::data_callback(ma_device *device, void *output, [[maybe_unused]] const void *input,
|
||||
std::uint32_t frameCount)
|
||||
{
|
||||
auto *sound = reinterpret_cast<PlayingSound *>(device->pUserData);
|
||||
if (!sound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sound->raw.decoder)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto readFrames = ma_decoder_read_pcm_frames(sound->raw.decoder, output, frameCount);
|
||||
if (sound->shouldSeek)
|
||||
{
|
||||
ma_decoder_seek_to_pcm_frame(sound->raw.decoder, sound->seekTo);
|
||||
Globals::gAudio.onSoundSeeked(sound, sound->seekTo);
|
||||
}
|
||||
if (sound->playbackDevice.isDefault && readFrames > 0)
|
||||
{
|
||||
Globals::gAudio.onSoundProgressed(sound, readFrames);
|
||||
}
|
||||
|
||||
if (readFrames <= 0)
|
||||
{
|
||||
if (sound->repeat)
|
||||
{
|
||||
ma_decoder_seek_to_pcm_frame(sound->raw.decoder, 0);
|
||||
Globals::gAudio.onSoundSeeked(sound, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Globals::gQueue.push_unique(reinterpret_cast<std::uintptr_t>(device),
|
||||
[sound = *sound] { Globals::gAudio.onFinished(sound); });
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<AudioDevice> Audio::getAudioDevices()
|
||||
{
|
||||
std::string defaultName;
|
||||
{
|
||||
ma_device device;
|
||||
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
ma_device_init(nullptr, &deviceConfig, &device);
|
||||
|
||||
defaultName = device.playback.name;
|
||||
|
||||
ma_device_uninit(&device);
|
||||
}
|
||||
|
||||
ma_context context;
|
||||
|
||||
if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to initialize context" << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
ma_device_info *pPlayBackDeviceInfos{};
|
||||
ma_uint32 deviceCount{};
|
||||
|
||||
ma_result result = ma_context_get_devices(&context, &pPlayBackDeviceInfos, &deviceCount, nullptr, nullptr);
|
||||
if (result != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to get playback devices!" << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<AudioDevice> playBackDevices;
|
||||
for (unsigned int i = 0; deviceCount > i; i++)
|
||||
{
|
||||
auto &rawDevice = pPlayBackDeviceInfos[i];
|
||||
|
||||
AudioDevice device;
|
||||
device.raw = rawDevice;
|
||||
device.name = rawDevice.name;
|
||||
device.isDefault = rawDevice.name == defaultName;
|
||||
|
||||
playBackDevices.emplace_back(device);
|
||||
}
|
||||
|
||||
ma_context_uninit(&context);
|
||||
|
||||
for (auto it = playBackDevices.begin(); it != playBackDevices.end(); it++)
|
||||
{
|
||||
if (it->name.find("VB-Audio") != std::string::npos)
|
||||
{
|
||||
if (it != playBackDevices.begin())
|
||||
{
|
||||
std::iter_swap(playBackDevices.begin(), it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playBackDevices;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
std::optional<AudioDevice> Audio::getAudioDevice(const std::string &name)
|
||||
{
|
||||
for (const auto &device : getAudioDevices())
|
||||
{
|
||||
if (device.name == name)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
std::vector<PlayingSound> Audio::getPlayingSounds()
|
||||
{
|
||||
auto scoped = playingSounds.scoped();
|
||||
|
||||
std::vector<PlayingSound> rtn;
|
||||
for (const auto &sound : *scoped)
|
||||
{
|
||||
rtn.emplace_back(*sound.second);
|
||||
}
|
||||
|
||||
return rtn;
|
||||
}
|
||||
PlayingSound::PlayingSound(const PlayingSound &other)
|
||||
{
|
||||
if (&other == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
length = other.length;
|
||||
lengthInMs = other.lengthInMs;
|
||||
readFrames = other.readFrames;
|
||||
sampleRate = other.sampleRate;
|
||||
|
||||
id = other.id;
|
||||
sound = other.sound;
|
||||
buffer = other.buffer;
|
||||
|
||||
seekTo.store(other.seekTo);
|
||||
paused.store(other.paused);
|
||||
repeat.store(other.repeat);
|
||||
readInMs.store(other.readInMs);
|
||||
shouldSeek.store(other.shouldSeek);
|
||||
|
||||
raw.device.store(other.raw.device);
|
||||
raw.decoder.store(other.raw.decoder);
|
||||
playbackDevice = other.playbackDevice;
|
||||
}
|
||||
PlayingSound &PlayingSound::operator=(const PlayingSound &other)
|
||||
{
|
||||
if (&other == this)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
length = other.length;
|
||||
lengthInMs = other.lengthInMs;
|
||||
readFrames = other.readFrames;
|
||||
sampleRate = other.sampleRate;
|
||||
|
||||
id = other.id;
|
||||
sound = other.sound;
|
||||
buffer = other.buffer;
|
||||
|
||||
seekTo.store(other.seekTo);
|
||||
paused.store(other.paused);
|
||||
repeat.store(other.repeat);
|
||||
readInMs.store(other.readInMs);
|
||||
shouldSeek.store(other.shouldSeek);
|
||||
|
||||
raw.device.store(other.raw.device);
|
||||
raw.decoder.store(other.raw.decoder);
|
||||
playbackDevice = other.playbackDevice;
|
||||
|
||||
return *this;
|
||||
}
|
||||
} // namespace Soundux::Objects
|
@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <core/objects/objects.hpp>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <miniaudio.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <var_guard.hpp>
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
namespace Objects
|
||||
{
|
||||
struct AudioDevice
|
||||
{
|
||||
ma_device_info raw;
|
||||
std::string name;
|
||||
bool isDefault;
|
||||
};
|
||||
struct PlayingSound
|
||||
{
|
||||
AudioDevice playbackDevice;
|
||||
|
||||
struct
|
||||
{
|
||||
std::atomic<ma_device *> device;
|
||||
std::atomic<ma_decoder *> decoder;
|
||||
} raw;
|
||||
|
||||
std::uint64_t length = 0;
|
||||
std::uint64_t lengthInMs = 0;
|
||||
std::uint64_t readFrames = 0;
|
||||
std::uint64_t sampleRate = 0;
|
||||
|
||||
std::atomic<bool> paused = false;
|
||||
std::atomic<bool> repeat = false;
|
||||
std::atomic<bool> shouldSeek = false;
|
||||
std::atomic<std::uint64_t> seekTo = 0;
|
||||
std::atomic<std::uint64_t> readInMs = 0;
|
||||
|
||||
Sound sound;
|
||||
std::uint32_t id;
|
||||
std::uint64_t buffer = 0;
|
||||
|
||||
PlayingSound() = default;
|
||||
PlayingSound(const PlayingSound &);
|
||||
PlayingSound &operator=(const PlayingSound &other);
|
||||
};
|
||||
class Audio
|
||||
{
|
||||
sxl::var_guard<std::map<std::uint32_t, std::shared_ptr<PlayingSound>>, std::recursive_mutex> playingSounds;
|
||||
|
||||
void onFinished(PlayingSound);
|
||||
void onSoundSeeked(PlayingSound *, std::uint64_t);
|
||||
void onSoundProgressed(PlayingSound *, std::uint64_t);
|
||||
|
||||
static void data_callback(ma_device *device, void *output, const void *input, std::uint32_t frameCount);
|
||||
|
||||
public:
|
||||
std::optional<PlayingSound> pause(const std::uint32_t &);
|
||||
std::optional<PlayingSound> resume(const std::uint32_t &);
|
||||
std::optional<PlayingSound> repeat(const std::uint32_t &, bool);
|
||||
std::optional<PlayingSound> seek(const std::uint32_t &, std::uint64_t);
|
||||
std::optional<PlayingSound> play(const Objects::Sound &, const std::optional<AudioDevice> & = std::nullopt);
|
||||
|
||||
std::vector<AudioDevice> getAudioDevices();
|
||||
std::vector<Objects::PlayingSound> getPlayingSounds();
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::optional<AudioDevice> getAudioDevice(const std::string &);
|
||||
#endif
|
||||
|
||||
void setup();
|
||||
void destroy();
|
||||
|
||||
void stopAll();
|
||||
bool stop(const std::uint32_t &);
|
||||
|
||||
#if defined(__linux__)
|
||||
std::optional<AudioDevice> nullSink;
|
||||
#endif
|
||||
AudioDevice defaultPlayback;
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
76
src/helper/audio/device/device.cpp
Normal file
76
src/helper/audio/device/device.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#include "device.hpp"
|
||||
#include <fancy.hpp>
|
||||
|
||||
namespace Soundux::Objects
|
||||
{
|
||||
std::vector<AudioDevice> AudioDevice::getDevices()
|
||||
{
|
||||
std::string defaultName;
|
||||
{
|
||||
ma_device device;
|
||||
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
ma_device_init(nullptr, &deviceConfig, &device);
|
||||
|
||||
defaultName = device.playback.name;
|
||||
|
||||
ma_device_uninit(&device);
|
||||
}
|
||||
|
||||
ma_context context;
|
||||
|
||||
if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to initialize context" << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
ma_device_info *pPlayBackDeviceInfos{};
|
||||
ma_uint32 deviceCount{};
|
||||
|
||||
ma_result result = ma_context_get_devices(&context, &pPlayBackDeviceInfos, &deviceCount, nullptr, nullptr);
|
||||
if (result != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to get playback devices!" << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<AudioDevice> playBackDevices;
|
||||
for (unsigned int i = 0; deviceCount > i; i++)
|
||||
{
|
||||
auto &rawDevice = pPlayBackDeviceInfos[i];
|
||||
|
||||
AudioDevice device;
|
||||
device.raw = rawDevice;
|
||||
device.name = rawDevice.name;
|
||||
device.isDefault = rawDevice.name == defaultName;
|
||||
|
||||
playBackDevices.emplace_back(device);
|
||||
}
|
||||
|
||||
ma_context_uninit(&context);
|
||||
|
||||
for (auto it = playBackDevices.begin(); it != playBackDevices.end(); it++)
|
||||
{
|
||||
if (it->name.find("VB-Audio") != std::string::npos)
|
||||
{
|
||||
if (it != playBackDevices.begin())
|
||||
{
|
||||
std::iter_swap(playBackDevices.begin(), it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playBackDevices;
|
||||
}
|
||||
std::optional<AudioDevice> AudioDevice::getDeviceByName(const std::string &name)
|
||||
{
|
||||
for (const auto &device : getDevices())
|
||||
{
|
||||
if (device.name == name)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace Soundux::Objects
|
22
src/helper/audio/device/device.hpp
Normal file
22
src/helper/audio/device/device.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <miniaudio.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
namespace Objects
|
||||
{
|
||||
struct AudioDevice
|
||||
{
|
||||
bool isDefault;
|
||||
std::string name;
|
||||
ma_device_info raw;
|
||||
|
||||
public:
|
||||
static std::vector<AudioDevice> getDevices();
|
||||
static std::optional<AudioDevice> getDeviceByName(const std::string &);
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
@ -19,24 +19,19 @@ namespace Soundux::Objects
|
||||
|
||||
if (pulseInstance && pulseInstance->setup())
|
||||
{
|
||||
if (!pulseInstance->switchOnConnectPresent())
|
||||
if (pulseInstance->isRunningPipeWire())
|
||||
{
|
||||
if (pulseInstance->loadModules())
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
backend = Enums::BackendType::PipeWire;
|
||||
Globals::gSettings.audioBackend = backend;
|
||||
|
||||
pulseInstance->destroy();
|
||||
pulseInstance.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
if (pulseInstance && pulseInstance->isRunningPipeWire())
|
||||
{
|
||||
backend = Enums::BackendType::PipeWire;
|
||||
Globals::gSettings.audioBackend = backend;
|
||||
}
|
||||
}
|
||||
|
||||
if (backend == Enums::BackendType::PipeWire)
|
||||
|
@ -45,13 +45,10 @@ namespace Soundux
|
||||
|
||||
virtual bool stopAllPassthrough() = 0;
|
||||
virtual bool stopPassthrough(const std::string &) = 0;
|
||||
virtual bool passthroughFrom(std::shared_ptr<PlaybackApp>) = 0;
|
||||
virtual bool passthroughFrom(const std::string &) = 0;
|
||||
|
||||
virtual bool stopSoundInput() = 0;
|
||||
virtual bool inputSoundTo(std::shared_ptr<RecordingApp>) = 0;
|
||||
|
||||
virtual std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &) = 0;
|
||||
virtual std::shared_ptr<RecordingApp> getRecordingApp(const std::string &) = 0;
|
||||
virtual bool inputSoundTo(const std::string &) = 0;
|
||||
|
||||
virtual std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps() = 0;
|
||||
virtual std::vector<std::shared_ptr<RecordingApp>> getRecordingApps() = 0;
|
||||
|
@ -56,9 +56,12 @@ namespace Soundux::Objects
|
||||
|
||||
void PipeWire::onNodeInfo(const pw_node_info *info)
|
||||
{
|
||||
if (info && (~nodes).find(info->id) != (~nodes).end())
|
||||
auto &nodes = this->nodes.unsafe();
|
||||
if (info && nodes.find(info->id) != nodes.end())
|
||||
{
|
||||
auto &self = (~nodes).at(info->id);
|
||||
auto &self = nodes.at(info->id);
|
||||
self.inputPorts = info->n_input_ports;
|
||||
self.outputPorts = info->n_output_ports;
|
||||
|
||||
if (const auto *pid = spa_dict_lookup(info->props, "application.process.id"); pid)
|
||||
{
|
||||
@ -69,7 +72,6 @@ namespace Soundux::Objects
|
||||
self.isMonitor = true;
|
||||
}
|
||||
|
||||
//* Yes this is swapped. (For compatibility reasons)
|
||||
if (const auto *appName = spa_dict_lookup(info->props, "application.name"); appName)
|
||||
{
|
||||
self.name = appName;
|
||||
@ -81,41 +83,6 @@ namespace Soundux::Objects
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWire::onPortInfo(const pw_port_info *info)
|
||||
{
|
||||
if (info && (~ports).find(info->id) != (~ports).end())
|
||||
{
|
||||
auto &self = (~ports).at(info->id);
|
||||
self.direction = info->direction;
|
||||
|
||||
if (const auto *nodeId = spa_dict_lookup(info->props, "node.id"); nodeId)
|
||||
{
|
||||
self.parentNode = std::stol(nodeId);
|
||||
}
|
||||
if (const auto *rawPortName = spa_dict_lookup(info->props, "port.name"); rawPortName)
|
||||
{
|
||||
auto portName = std::string(rawPortName);
|
||||
|
||||
if (portName.back() == '1' || portName.back() == 'L')
|
||||
{
|
||||
self.side = Side::LEFT;
|
||||
}
|
||||
else if (portName.back() == '2' || portName.back() == 'R')
|
||||
{
|
||||
self.side = Side::RIGHT;
|
||||
}
|
||||
else if (portName.find("MONO", portName.size() - 4) != std::string::npos)
|
||||
{
|
||||
self.side = Side::MONO;
|
||||
}
|
||||
}
|
||||
if (const auto *portAlias = spa_dict_lookup(info->props, "port.alias"); portAlias)
|
||||
{
|
||||
self.portAlias = std::string(portAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWire::onCoreInfo(const pw_core_info *info)
|
||||
{
|
||||
if (info && info->name && info->version && !version)
|
||||
@ -206,7 +173,7 @@ namespace Soundux::Objects
|
||||
if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
|
||||
{
|
||||
const auto *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
|
||||
if (name && strstr(name, "soundux"))
|
||||
if (name && strcmp(name, "soundux") == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -231,55 +198,14 @@ namespace Soundux::Objects
|
||||
|
||||
if (boundNode)
|
||||
{
|
||||
thiz->nodes->emplace(id, node);
|
||||
//* We lock the mutex here, because we only need access to `nodes` as soon as sync is called.
|
||||
thiz->nodes.write()->emplace(id, node);
|
||||
pw_node_add_listener(boundNode, &listener, &events, thiz); // NOLINT
|
||||
thiz->sync();
|
||||
spa_hook_remove(&listener);
|
||||
PipeWireApi::proxy_destroy(reinterpret_cast<pw_proxy *>(boundNode));
|
||||
}
|
||||
}
|
||||
if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
|
||||
{
|
||||
Port port;
|
||||
port.id = id;
|
||||
|
||||
spa_hook listener;
|
||||
pw_port_events events = {};
|
||||
|
||||
events.info = [](void *data, const pw_port_info *info) {
|
||||
auto *thiz = reinterpret_cast<PipeWire *>(data);
|
||||
if (thiz)
|
||||
{
|
||||
thiz->onPortInfo(info);
|
||||
}
|
||||
};
|
||||
events.version = PW_VERSION_PORT_EVENTS;
|
||||
|
||||
auto *boundPort =
|
||||
reinterpret_cast<pw_port *>(pw_registry_bind(thiz->registry, id, type, version, sizeof(PipeWire)));
|
||||
|
||||
if (boundPort)
|
||||
{
|
||||
thiz->ports->emplace(id, port);
|
||||
pw_port_add_listener(boundPort, &listener, &events, thiz); // NOLINT
|
||||
thiz->sync();
|
||||
spa_hook_remove(&listener);
|
||||
PipeWireApi::proxy_destroy(reinterpret_cast<pw_proxy *>(boundPort));
|
||||
|
||||
auto scopedNodes = thiz->nodes.scoped();
|
||||
auto scopedPorts = thiz->ports.scoped();
|
||||
if (scopedPorts->find(id) != scopedPorts->end())
|
||||
{
|
||||
auto &port = scopedPorts->at(id);
|
||||
if (port.parentNode > 0 && scopedNodes->find(port.parentNode) != scopedNodes->end())
|
||||
{
|
||||
auto &node = scopedNodes->at(port.parentNode);
|
||||
node.ports.emplace(id, port);
|
||||
scopedPorts->erase(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,16 +214,10 @@ namespace Soundux::Objects
|
||||
auto *thiz = reinterpret_cast<PipeWire *>(data);
|
||||
if (thiz)
|
||||
{
|
||||
auto scopedNodes = thiz->nodes.scoped();
|
||||
if (scopedNodes->find(id) != scopedNodes->end())
|
||||
auto nodes = thiz->nodes.write();
|
||||
if (nodes->find(id) != nodes->end())
|
||||
{
|
||||
scopedNodes->erase(id);
|
||||
}
|
||||
|
||||
auto scopedPorts = thiz->ports.scoped();
|
||||
if (scopedPorts->find(id) != scopedPorts->end())
|
||||
{
|
||||
scopedPorts->erase(id);
|
||||
nodes->erase(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -404,13 +324,15 @@ namespace Soundux::Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<int> PipeWire::linkPorts(std::uint32_t in, std::uint32_t out)
|
||||
std::optional<int> PipeWire::createLink(std::uint32_t in, std::uint32_t out)
|
||||
{
|
||||
pw_properties *props = PipeWireApi::properties_new(nullptr, nullptr);
|
||||
|
||||
PipeWireApi::properties_set(props, PW_KEY_APP_NAME, "soundux");
|
||||
PipeWireApi::properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", in);
|
||||
PipeWireApi::properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", out);
|
||||
PipeWireApi::properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%i", -1);
|
||||
PipeWireApi::properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%i", -1);
|
||||
PipeWireApi::properties_set(props, PW_KEY_LINK_INPUT_NODE, std::to_string(in).c_str());
|
||||
PipeWireApi::properties_set(props, PW_KEY_LINK_OUTPUT_NODE, std::to_string(out).c_str());
|
||||
|
||||
auto *proxy = reinterpret_cast<pw_proxy *>(
|
||||
pw_core_create_object(core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0));
|
||||
@ -430,9 +352,9 @@ namespace Soundux::Objects
|
||||
linkEvent.bound = [](void *data, std::uint32_t id) {
|
||||
*reinterpret_cast<std::optional<std::uint32_t> *>(data) = id;
|
||||
};
|
||||
linkEvent.error = [](void *data, [[maybe_unused]] int a, [[maybe_unused]] int b, const char *message) {
|
||||
Fancy::fancy.logTime().warning() << "Failed to create link: " << message << std::endl;
|
||||
*reinterpret_cast<std::optional<std::uint32_t> *>(data) = std::nullopt;
|
||||
linkEvent.error = []([[maybe_unused]] void *data, int seq, int res, const char *message) {
|
||||
Fancy::fancy.logTime().warning()
|
||||
<< "An error occurred while creating a link(" << seq << ":" << res << "): " << message << std::endl;
|
||||
};
|
||||
|
||||
PipeWireApi::proxy_add_listener(proxy, &listener, &linkEvent, &result);
|
||||
@ -449,26 +371,15 @@ namespace Soundux::Objects
|
||||
sync();
|
||||
std::vector<std::shared_ptr<RecordingApp>> rtn;
|
||||
|
||||
auto scopedNodes = nodes.scoped();
|
||||
for (const auto &[nodeId, node] : *scopedNodes)
|
||||
auto nodes = this->nodes.read();
|
||||
for (const auto &[nodeId, node] : *nodes)
|
||||
{
|
||||
if (!node.name.empty() && !node.isMonitor)
|
||||
{
|
||||
bool hasInput = false;
|
||||
for (const auto &[portId, port] : node.ports)
|
||||
{
|
||||
if (port.direction == SPA_DIRECTION_INPUT)
|
||||
{
|
||||
hasInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInput)
|
||||
if (node.inputPorts)
|
||||
{
|
||||
PipeWireRecordingApp app;
|
||||
app.pid = node.pid;
|
||||
app.nodeId = nodeId;
|
||||
app.node = node;
|
||||
app.name = node.name;
|
||||
app.application = node.applicationBinary;
|
||||
rtn.emplace_back(std::make_shared<PipeWireRecordingApp>(app));
|
||||
@ -484,26 +395,15 @@ namespace Soundux::Objects
|
||||
sync();
|
||||
std::vector<std::shared_ptr<PlaybackApp>> rtn;
|
||||
|
||||
auto scopedNodes = nodes.scoped();
|
||||
for (const auto &[nodeId, node] : *scopedNodes)
|
||||
auto nodes = this->nodes.read();
|
||||
for (const auto &[nodeId, node] : *nodes)
|
||||
{
|
||||
if (!node.name.empty() && !node.isMonitor)
|
||||
{
|
||||
bool hasOutput = false;
|
||||
for (const auto &[portId, port] : node.ports)
|
||||
{
|
||||
if (port.direction == SPA_DIRECTION_OUTPUT)
|
||||
{
|
||||
hasOutput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOutput)
|
||||
if (node.outputPorts)
|
||||
{
|
||||
PipeWirePlaybackApp app;
|
||||
app.pid = node.pid;
|
||||
app.nodeId = nodeId;
|
||||
app.node = node;
|
||||
app.name = node.name;
|
||||
app.application = node.applicationBinary;
|
||||
rtn.emplace_back(std::make_shared<PipeWirePlaybackApp>(app));
|
||||
@ -514,44 +414,6 @@ namespace Soundux::Objects
|
||||
return rtn;
|
||||
}
|
||||
|
||||
std::shared_ptr<PlaybackApp> PipeWire::getPlaybackApp(const std::string &app)
|
||||
{
|
||||
auto scopedNodes = nodes.scoped();
|
||||
for (const auto &[nodeId, node] : *scopedNodes)
|
||||
{
|
||||
if (node.applicationBinary == app)
|
||||
{
|
||||
PipeWirePlaybackApp app;
|
||||
app.pid = node.pid;
|
||||
app.nodeId = nodeId;
|
||||
app.name = node.name;
|
||||
app.application = node.applicationBinary;
|
||||
return std::make_shared<PipeWirePlaybackApp>(app);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<RecordingApp> PipeWire::getRecordingApp(const std::string &app)
|
||||
{
|
||||
auto scopedNodes = nodes.scoped();
|
||||
for (const auto &[nodeId, node] : *scopedNodes)
|
||||
{
|
||||
if (node.applicationBinary == app)
|
||||
{
|
||||
PipeWireRecordingApp app;
|
||||
app.pid = node.pid;
|
||||
app.nodeId = nodeId;
|
||||
app.name = node.name;
|
||||
app.application = node.applicationBinary;
|
||||
return std::make_shared<PipeWireRecordingApp>(app);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PipeWire::useAsDefault()
|
||||
{
|
||||
// TODO(pipewire): Find a way to connect the output to the microphone
|
||||
@ -608,69 +470,52 @@ namespace Soundux::Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PipeWire::inputSoundTo(std::shared_ptr<RecordingApp> app)
|
||||
bool PipeWire::inputSoundTo(const std::string &app)
|
||||
{
|
||||
if (!app)
|
||||
sync();
|
||||
|
||||
if (app.empty())
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Invalid app" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (soundInputLinks.count(app->application))
|
||||
|
||||
if (soundInputLinks.count(app))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto pipeWireApp = std::dynamic_pointer_cast<PipeWireRecordingApp>(app);
|
||||
if (!pipeWireApp)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Supplied app was not a Recording App" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
auto nodes = this->nodes.copy();
|
||||
auto ports = this->ports.copy();
|
||||
|
||||
if (!soundInputLinks.count(app->application))
|
||||
{
|
||||
soundInputLinks.emplace(app->application, std::vector<std::uint32_t>{});
|
||||
}
|
||||
std::vector<std::uint32_t> createdLinks;
|
||||
|
||||
for (const auto &[nodeId, node] : nodes)
|
||||
{
|
||||
if (node.applicationBinary != app->application)
|
||||
continue;
|
||||
|
||||
for (const auto &[portId, port] : ports)
|
||||
if (node.rawName == "soundux_sink")
|
||||
{
|
||||
if (port.direction == SPA_DIRECTION_OUTPUT && port.portAlias.find("soundux") != std::string::npos)
|
||||
for (const auto &[appNodeId, appNode] : nodes)
|
||||
{
|
||||
for (const auto &[nodePortId, nodePort] : node.ports)
|
||||
if (appNode.applicationBinary == app && appNode.inputPorts)
|
||||
{
|
||||
if (nodePort.direction == SPA_DIRECTION_INPUT)
|
||||
auto link = createLink(appNodeId, nodeId);
|
||||
|
||||
if (link)
|
||||
{
|
||||
if (nodePort.side == Side::UNDEFINED || port.side == Side::UNDEFINED)
|
||||
continue;
|
||||
|
||||
if (nodePort.side == port.side || nodePort.side == Side::MONO)
|
||||
{
|
||||
auto link = linkPorts(nodePortId, portId);
|
||||
|
||||
if (link)
|
||||
{
|
||||
success = true;
|
||||
soundInputLinks.at(app->application).emplace_back(*link);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
createdLinks.emplace_back(*link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
if (success)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Could not find ports for app " << app->application << std::endl;
|
||||
soundInputLinks.emplace(app, createdLinks);
|
||||
}
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Could not link app " << app << std::endl;
|
||||
}
|
||||
|
||||
return success;
|
||||
@ -690,65 +535,47 @@ namespace Soundux::Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PipeWire::passthroughFrom(std::shared_ptr<PlaybackApp> app)
|
||||
bool PipeWire::passthroughFrom(const std::string &app)
|
||||
{
|
||||
if (!app)
|
||||
sync();
|
||||
|
||||
if (app.empty())
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Invalid app" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pipeWireApp = std::dynamic_pointer_cast<PipeWirePlaybackApp>(app);
|
||||
if (!pipeWireApp)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Supplied app was not a Playback App" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
auto nodes = this->nodes.copy();
|
||||
auto ports = this->ports.copy();
|
||||
|
||||
if (!passthroughLinks.count(app->application))
|
||||
{
|
||||
passthroughLinks.emplace(app->application, std::vector<std::uint32_t>{});
|
||||
}
|
||||
std::vector<std::uint32_t> createdLinks;
|
||||
|
||||
for (const auto &[nodeId, node] : nodes)
|
||||
{
|
||||
if (node.applicationBinary != app->application)
|
||||
continue;
|
||||
|
||||
for (const auto &[portId, port] : ports)
|
||||
if (node.rawName == "soundux_sink")
|
||||
{
|
||||
if (port.direction == SPA_DIRECTION_INPUT && port.portAlias.find("soundux") != std::string::npos)
|
||||
for (const auto &[appNodeId, appNode] : nodes)
|
||||
{
|
||||
for (const auto &[nodePortId, nodePort] : node.ports)
|
||||
if (appNode.applicationBinary == app && appNode.outputPorts)
|
||||
{
|
||||
if (nodePort.side == Side::UNDEFINED || port.side == Side::UNDEFINED)
|
||||
continue;
|
||||
auto link = createLink(nodeId, appNodeId);
|
||||
|
||||
if (nodePort.direction == SPA_DIRECTION_OUTPUT)
|
||||
if (link)
|
||||
{
|
||||
if (nodePort.side == port.side || nodePort.side == Side::MONO)
|
||||
{
|
||||
auto link = linkPorts(portId, nodePortId);
|
||||
|
||||
if (link)
|
||||
{
|
||||
success = true;
|
||||
passthroughLinks.at(app->application).emplace_back(*link);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
createdLinks.emplace_back(*link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
if (success)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Could not find ports for app " << app->application << std::endl;
|
||||
passthroughLinks.emplace(app, createdLinks);
|
||||
}
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Could not link app " << app << std::endl;
|
||||
}
|
||||
|
||||
return success;
|
||||
|
@ -1,8 +1,8 @@
|
||||
#if defined(__linux__)
|
||||
#include "../backend.hpp"
|
||||
#include <lock.hpp>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <var_guard.hpp>
|
||||
|
||||
#include <pipewire/extensions/metadata.h>
|
||||
#include <pipewire/global.h>
|
||||
@ -10,34 +10,10 @@
|
||||
#include <spa/param/props.h>
|
||||
#include <spa/pod/builder.h>
|
||||
|
||||
// TODO(pipewire):
|
||||
//* From the pipewire news of 0.3.26
|
||||
//* - The link factory can now also make links between nodes and
|
||||
//* ports by name so that it can be used in scripts.
|
||||
//*
|
||||
//* Maybe we could try to make use of that, it could reduce loc.
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
namespace Objects
|
||||
{
|
||||
enum class Side
|
||||
{
|
||||
UNDEFINED,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
MONO,
|
||||
};
|
||||
|
||||
struct Port
|
||||
{
|
||||
std::uint32_t id;
|
||||
std::string portAlias;
|
||||
spa_direction direction;
|
||||
Side side = Side::UNDEFINED;
|
||||
std::uint32_t parentNode = 0;
|
||||
};
|
||||
|
||||
struct Node
|
||||
{
|
||||
std::uint32_t id;
|
||||
@ -45,20 +21,19 @@ namespace Soundux
|
||||
std::uint32_t pid;
|
||||
std::string rawName;
|
||||
bool isMonitor = false;
|
||||
std::uint32_t inputPorts;
|
||||
std::uint32_t outputPorts;
|
||||
std::string applicationBinary;
|
||||
std::map<std::uint32_t, Port> ports;
|
||||
};
|
||||
|
||||
struct PipeWirePlaybackApp : public PlaybackApp
|
||||
{
|
||||
std::uint32_t pid;
|
||||
std::uint32_t nodeId;
|
||||
Node node;
|
||||
~PipeWirePlaybackApp() override = default;
|
||||
};
|
||||
struct PipeWireRecordingApp : public RecordingApp
|
||||
{
|
||||
std::uint32_t pid;
|
||||
std::uint32_t nodeId;
|
||||
Node node;
|
||||
~PipeWireRecordingApp() override = default;
|
||||
};
|
||||
|
||||
@ -78,11 +53,9 @@ namespace Soundux
|
||||
pw_registry_events registryEvents;
|
||||
|
||||
private:
|
||||
sxl::var_guard<std::map<std::uint32_t, Node>> nodes;
|
||||
sxl::var_guard<std::map<std::uint32_t, Port>> ports;
|
||||
sxl::lock<std::map<std::uint32_t, Node>> nodes;
|
||||
|
||||
void onNodeInfo(const pw_node_info *);
|
||||
void onPortInfo(const pw_port_info *);
|
||||
void onCoreInfo(const pw_core_info *);
|
||||
|
||||
private:
|
||||
@ -93,7 +66,7 @@ namespace Soundux
|
||||
void sync();
|
||||
bool createNullSink();
|
||||
bool deleteLink(std::uint32_t);
|
||||
std::optional<int> linkPorts(std::uint32_t, std::uint32_t);
|
||||
std::optional<int> createLink(std::uint32_t, std::uint32_t);
|
||||
|
||||
static void onGlobalRemoved(void *, std::uint32_t);
|
||||
static void onGlobalAdded(void *, std::uint32_t, std::uint32_t, const char *, std::uint32_t,
|
||||
@ -115,13 +88,10 @@ namespace Soundux
|
||||
|
||||
bool stopAllPassthrough() override;
|
||||
bool stopPassthrough(const std::string &app) override;
|
||||
bool passthroughFrom(std::shared_ptr<PlaybackApp> app) override;
|
||||
bool passthroughFrom(const std::string &app) override;
|
||||
|
||||
bool stopSoundInput() override;
|
||||
bool inputSoundTo(std::shared_ptr<RecordingApp> app) override;
|
||||
|
||||
std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &app) override;
|
||||
std::shared_ptr<RecordingApp> getRecordingApp(const std::string &app) override;
|
||||
bool inputSoundTo(const std::string &app) override;
|
||||
|
||||
std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps() override;
|
||||
std::vector<std::shared_ptr<RecordingApp>> getRecordingApps() override;
|
||||
|
@ -58,9 +58,49 @@ namespace Soundux::Objects
|
||||
}
|
||||
|
||||
unloadLeftOvers();
|
||||
unloadProblematic();
|
||||
fetchDefaultSource();
|
||||
|
||||
return !(defaultSource.empty() || serverName.empty() || isRunningPipeWire());
|
||||
if (defaultSource.empty() || serverName.empty() || isRunningPipeWire())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return loadModules();
|
||||
}
|
||||
void PulseAudio::unloadProblematic()
|
||||
{
|
||||
await(PulseApi::context_get_module_info_list(
|
||||
context,
|
||||
[]([[maybe_unused]] pa_context *ctx, const pa_module_info *info, [[maybe_unused]] int eol, void *userData) {
|
||||
if (info && info->name && userData)
|
||||
{
|
||||
auto name = std::string(info->name);
|
||||
auto *thiz = reinterpret_cast<PulseAudio *>(userData);
|
||||
|
||||
if (name.find("switch-on-connect") != std::string::npos ||
|
||||
name.find("role-cork") != std::string::npos)
|
||||
{
|
||||
PulseModule module;
|
||||
module.name = name;
|
||||
|
||||
if (info->argument)
|
||||
module.arguments = info->argument;
|
||||
|
||||
thiz->unloadedModules.emplace_back(module);
|
||||
Fancy::fancy.logTime().message() << "Unloading problematic module: " << name << std::endl;
|
||||
}
|
||||
}
|
||||
},
|
||||
this));
|
||||
}
|
||||
void PulseAudio::reloadProblematic()
|
||||
{
|
||||
for (auto &module : unloadedModules)
|
||||
{
|
||||
Fancy::fancy.logTime().message() << "Loading back module: " << module.name << std::endl;
|
||||
PulseApi::context_load_module(context, module.name.c_str(), module.arguments.c_str(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
bool PulseAudio::loadModules()
|
||||
{
|
||||
@ -175,6 +215,8 @@ namespace Soundux::Objects
|
||||
await(PulseApi::context_unload_module(context, *passthroughSink, nullptr, nullptr));
|
||||
if (passthroughLoopBack)
|
||||
await(PulseApi::context_unload_module(context, *passthroughLoopBack, nullptr, nullptr));
|
||||
|
||||
reloadProblematic();
|
||||
}
|
||||
void PulseAudio::await(pa_operation *operation)
|
||||
{
|
||||
@ -380,26 +422,27 @@ namespace Soundux::Objects
|
||||
|
||||
return true;
|
||||
}
|
||||
bool PulseAudio::passthroughFrom(std::shared_ptr<PlaybackApp> app)
|
||||
bool PulseAudio::passthroughFrom(const std::string &app)
|
||||
{
|
||||
if (movedPassthroughApplications.count(app->application))
|
||||
if (movedPassthroughApplications.count(app))
|
||||
{
|
||||
Fancy::fancy.logTime().message()
|
||||
<< "Ignoring sound passthrough request because requested app is already moved" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!app)
|
||||
if (app.empty())
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Tried to passthrough to non existant app" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t originalSink = 0;
|
||||
for (const auto &playbackApp : getPlaybackApps())
|
||||
{
|
||||
auto pulsePlayback = std::dynamic_pointer_cast<PulsePlaybackApp>(playbackApp);
|
||||
|
||||
if (playbackApp->application == app->application)
|
||||
if (playbackApp->application == app)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
@ -419,10 +462,12 @@ namespace Soundux::Objects
|
||||
<< "Failed top move " << pulsePlayback->id << " to passthrough" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
originalSink = pulsePlayback->sink;
|
||||
}
|
||||
}
|
||||
|
||||
movedPassthroughApplications.emplace(app->application, std::dynamic_pointer_cast<PulsePlaybackApp>(app)->sink);
|
||||
movedPassthroughApplications.emplace(app, originalSink);
|
||||
return true;
|
||||
}
|
||||
bool PulseAudio::stopAllPassthrough()
|
||||
@ -491,24 +536,25 @@ namespace Soundux::Objects
|
||||
Fancy::fancy.logTime().warning() << "Could not find moved application " << app << std::endl;
|
||||
return false;
|
||||
}
|
||||
bool PulseAudio::inputSoundTo(std::shared_ptr<RecordingApp> app)
|
||||
bool PulseAudio::inputSoundTo(const std::string &app)
|
||||
{
|
||||
if (!app)
|
||||
if (app.empty())
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Tried to input sound to non existant app" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (movedApplications.find(app->application) != movedApplications.end())
|
||||
if (movedApplications.find(app) != movedApplications.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t originalSource = 0;
|
||||
for (const auto &recordingApp : getRecordingApps())
|
||||
{
|
||||
auto pulseApp = std::dynamic_pointer_cast<PulseRecordingApp>(recordingApp);
|
||||
|
||||
if (pulseApp->application == app->application)
|
||||
if (pulseApp->application == app)
|
||||
{
|
||||
bool success = true;
|
||||
await(PulseApi::context_move_source_output_by_name(
|
||||
@ -526,10 +572,14 @@ namespace Soundux::Objects
|
||||
Fancy::fancy.logTime().warning() << "Failed to move " + pulseApp->application << "(" << pulseApp->id
|
||||
<< ") to soundux sink" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
originalSource = pulseApp->source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
movedApplications.emplace(app->application, std::dynamic_pointer_cast<PulseRecordingApp>(app)->source);
|
||||
movedApplications.emplace(app, originalSource);
|
||||
return true;
|
||||
}
|
||||
bool PulseAudio::stopSoundInput()
|
||||
@ -566,30 +616,6 @@ namespace Soundux::Objects
|
||||
|
||||
return success;
|
||||
}
|
||||
std::shared_ptr<PlaybackApp> PulseAudio::getPlaybackApp(const std::string &application)
|
||||
{
|
||||
for (auto app : getPlaybackApps())
|
||||
{
|
||||
if (app->application == application)
|
||||
{
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
std::shared_ptr<RecordingApp> PulseAudio::getRecordingApp(const std::string &application)
|
||||
{
|
||||
for (auto app : getRecordingApps())
|
||||
{
|
||||
if (app->application == application)
|
||||
{
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PulseAudio::fixPlaybackApps(const std::vector<std::shared_ptr<PlaybackApp>> &originalPlayback)
|
||||
{
|
||||
@ -656,56 +682,6 @@ namespace Soundux::Objects
|
||||
return success;
|
||||
}
|
||||
|
||||
bool PulseAudio::switchOnConnectPresent()
|
||||
{
|
||||
bool isPresent = false;
|
||||
await(PulseApi::context_get_module_info_list(
|
||||
context,
|
||||
[]([[maybe_unused]] pa_context *ctx, const pa_module_info *info, [[maybe_unused]] int eol, void *userData) {
|
||||
if (info && info->name)
|
||||
{
|
||||
if (std::string(info->name).find("switch-on-connect") != std::string::npos)
|
||||
{
|
||||
*reinterpret_cast<bool *>(userData) = true;
|
||||
Fancy::fancy.logTime().warning() << "Switch on connect found: " << info->index << std::endl;
|
||||
}
|
||||
}
|
||||
},
|
||||
&isPresent));
|
||||
|
||||
if (isPresent)
|
||||
{
|
||||
if (Globals::gGui)
|
||||
{
|
||||
Globals::gGui->onSwitchOnConnectDetected(true);
|
||||
}
|
||||
}
|
||||
|
||||
return isPresent;
|
||||
}
|
||||
|
||||
void PulseAudio::unloadSwitchOnConnect()
|
||||
{
|
||||
await(PulseApi::context_get_module_info_list(
|
||||
context,
|
||||
[]([[maybe_unused]] pa_context *ctx, const pa_module_info *info, [[maybe_unused]] int eol,
|
||||
[[maybe_unused]] void *userData) {
|
||||
if (info && info->name)
|
||||
{
|
||||
if (std::string(info->name).find("switch-on-connect") != std::string::npos)
|
||||
{
|
||||
Fancy::fancy.logTime().message() << "Unloading: " << info->index << std::endl;
|
||||
PulseApi::context_unload_module(ctx, info->index, nullptr, nullptr);
|
||||
|
||||
if (Globals::gGui)
|
||||
{
|
||||
Globals::gGui->onSwitchOnConnectDetected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
nullptr));
|
||||
}
|
||||
bool PulseAudio::isRunningPipeWire()
|
||||
{
|
||||
//* The stuff we do here does is broken on pipewire-pulse, use the native backend instead.
|
||||
|
@ -27,6 +27,12 @@ namespace Soundux
|
||||
~PulseRecordingApp() override = default;
|
||||
};
|
||||
|
||||
struct PulseModule
|
||||
{
|
||||
std::string name;
|
||||
std::string arguments;
|
||||
};
|
||||
|
||||
class PulseAudio : public AudioBackend
|
||||
{
|
||||
friend class AudioBackend;
|
||||
@ -47,6 +53,7 @@ namespace Soundux
|
||||
//* ~= ~~~~~~~~~~~~~~~~~~~~~ =~
|
||||
|
||||
std::string serverName;
|
||||
std::vector<PulseModule> unloadedModules;
|
||||
|
||||
std::string defaultSource;
|
||||
std::optional<std::uint32_t> defaultSourceId;
|
||||
@ -56,11 +63,15 @@ namespace Soundux
|
||||
|
||||
std::mutex operationMutex;
|
||||
|
||||
bool loadModules();
|
||||
void unloadLeftOvers();
|
||||
void fetchDefaultSource();
|
||||
void fetchLoopBackSinkId();
|
||||
void await(pa_operation *);
|
||||
|
||||
void unloadProblematic();
|
||||
void reloadProblematic();
|
||||
|
||||
void fixPlaybackApps(const std::vector<std::shared_ptr<PlaybackApp>> &);
|
||||
void fixRecordingApps(const std::vector<std::shared_ptr<RecordingApp>> &);
|
||||
|
||||
@ -68,9 +79,6 @@ namespace Soundux
|
||||
bool setup() override;
|
||||
|
||||
public:
|
||||
//! Is not ran by default to avoid problems with switch-on-connect
|
||||
bool loadModules();
|
||||
|
||||
void destroy() override;
|
||||
bool isRunningPipeWire();
|
||||
|
||||
@ -83,16 +91,10 @@ namespace Soundux
|
||||
|
||||
bool stopAllPassthrough() override;
|
||||
bool stopPassthrough(const std::string &app) override;
|
||||
bool passthroughFrom(std::shared_ptr<PlaybackApp> app) override;
|
||||
bool passthroughFrom(const std::string &app) override;
|
||||
|
||||
bool stopSoundInput() override;
|
||||
bool inputSoundTo(std::shared_ptr<RecordingApp> app) override;
|
||||
|
||||
void unloadSwitchOnConnect();
|
||||
bool switchOnConnectPresent();
|
||||
|
||||
std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &application) override;
|
||||
std::shared_ptr<RecordingApp> getRecordingApp(const std::string &application) override;
|
||||
bool inputSoundTo(const std::string &app) override;
|
||||
|
||||
std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps() override;
|
||||
std::vector<std::shared_ptr<RecordingApp>> getRecordingApps() override;
|
||||
|
255
src/helper/audio/sound/sound.cpp
Normal file
255
src/helper/audio/sound/sound.cpp
Normal file
@ -0,0 +1,255 @@
|
||||
#include "sound.hpp"
|
||||
#include <core/global/globals.hpp>
|
||||
#include <core/objects/sound.hpp>
|
||||
#include <fancy.hpp>
|
||||
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include <miniaudio.h>
|
||||
|
||||
namespace Soundux::Objects
|
||||
{
|
||||
std::atomic<std::uint64_t> PlayingSound::idCounter = 0;
|
||||
std::shared_ptr<PlayingSound> PlayingSound::create(const std::shared_ptr<Sound> &sound,
|
||||
const AudioDevice &playbackDevice)
|
||||
{
|
||||
auto rtn = std::shared_ptr<PlayingSound>(new PlayingSound());
|
||||
|
||||
rtn->sound = sound;
|
||||
rtn->id = ++idCounter;
|
||||
rtn->playbackDevice = playbackDevice;
|
||||
|
||||
auto device = rtn->device.write();
|
||||
auto decoder = rtn->decoder.write();
|
||||
|
||||
*device = std::make_shared<ma_device>();
|
||||
*decoder = std::make_shared<ma_decoder>();
|
||||
|
||||
// TODO(): Use wide path universally
|
||||
#if defined(_WIN32)
|
||||
auto res = ma_decoder_init_file_w(widen(sound.path).c_str(), nullptr, &*decoder);
|
||||
#else
|
||||
auto res = ma_decoder_init_file(sound->getPath().c_str(), nullptr, decoder->get());
|
||||
#endif
|
||||
|
||||
if (res != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure()
|
||||
<< "Failed to create decoder from file: " << sound->getPath() << " (" << res << ")" << std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rtn->sampleRate = decoder->get()->outputSampleRate;
|
||||
rtn->length = ma_decoder_get_length_in_pcm_frames(decoder->get());
|
||||
|
||||
auto config = ma_device_config_init(ma_device_type_playback);
|
||||
|
||||
config.pUserData = rtn.get();
|
||||
config.dataCallback = data_callback;
|
||||
config.sampleRate = rtn->sampleRate;
|
||||
config.playback.format = decoder->get()->outputFormat;
|
||||
config.playback.pDeviceID = &rtn->playbackDevice.raw.id;
|
||||
config.playback.channels = decoder->get()->outputChannels;
|
||||
|
||||
if (ma_device_init(nullptr, &config, device->get()) != MA_SUCCESS)
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to create device" << std::endl;
|
||||
ma_decoder_uninit(decoder->get());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (playbackDevice.isDefault)
|
||||
{
|
||||
if (sound->getLocalVolume())
|
||||
{
|
||||
device->get()->masterVolumeFactor = static_cast<float>(*sound->getLocalVolume()) / 100.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
device->get()->masterVolumeFactor = static_cast<float>(Globals::gSettings.localVolume) / 100.f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sound->getRemoteVolume())
|
||||
{
|
||||
device->get()->masterVolumeFactor = static_cast<float>(*sound->getRemoteVolume()) / 100.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
device->get()->masterVolumeFactor = static_cast<float>(Globals::gSettings.remoteVolume) / 100.f;
|
||||
}
|
||||
}
|
||||
|
||||
if (ma_device_start(device->get()) != MA_SUCCESS)
|
||||
{
|
||||
ma_device_uninit(device->get());
|
||||
ma_decoder_uninit(decoder->get());
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to start device for sound " << sound->getPath() << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return rtn;
|
||||
}
|
||||
PlayingSound::~PlayingSound()
|
||||
{
|
||||
if (device.copy())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
void PlayingSound::stop()
|
||||
{
|
||||
if (device.copy() && decoder.copy())
|
||||
{
|
||||
auto device = this->device.write();
|
||||
auto decoder = this->decoder.write();
|
||||
|
||||
done = true;
|
||||
ma_device_uninit(device->get());
|
||||
ma_decoder_uninit(decoder->get());
|
||||
|
||||
*device = nullptr;
|
||||
*decoder = nullptr;
|
||||
}
|
||||
}
|
||||
void PlayingSound::pause()
|
||||
{
|
||||
if (device.copy())
|
||||
{
|
||||
auto device = this->device.write();
|
||||
if (ma_device_get_state(device->get()) == MA_STATE_STARTED)
|
||||
{
|
||||
ma_device_stop(device->get());
|
||||
}
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().warning()
|
||||
<< "Tried to pause sound " << id << " which is not playing" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool PlayingSound::isPaused() const
|
||||
{
|
||||
auto device = this->device.read();
|
||||
return ma_device_get_state(device->get()) == MA_STATE_STOPPED;
|
||||
}
|
||||
void PlayingSound::resume()
|
||||
{
|
||||
if (device.copy())
|
||||
{
|
||||
auto device = this->device.write();
|
||||
if (ma_device_get_state(device->get()) == MA_STATE_STOPPED)
|
||||
{
|
||||
ma_device_start(device->get());
|
||||
}
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().warning()
|
||||
<< "Tried to resume sound " << id << " which is not paused" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
void PlayingSound::repeat(bool state)
|
||||
{
|
||||
shouldRepeat = state;
|
||||
}
|
||||
std::uint64_t PlayingSound::getLength() const
|
||||
{
|
||||
return static_cast<std::uint64_t>(static_cast<double>(length) / static_cast<double>(sampleRate) * 1000);
|
||||
}
|
||||
std::uint64_t PlayingSound::getRead() const
|
||||
{
|
||||
return static_cast<std::uint64_t>((static_cast<double>(readFrames) / static_cast<double>(length)) *
|
||||
static_cast<double>(getLength()));
|
||||
}
|
||||
void PlayingSound::seek(const std::uint64_t &position)
|
||||
{
|
||||
auto newPos = static_cast<std::uint64_t>((static_cast<double>(position) / static_cast<double>(getLength())) *
|
||||
static_cast<double>(length));
|
||||
|
||||
seekTo = newPos;
|
||||
readFrames = newPos;
|
||||
}
|
||||
std::shared_ptr<Sound> PlayingSound::getSound() const
|
||||
{
|
||||
return sound;
|
||||
}
|
||||
std::uint64_t PlayingSound::getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
bool PlayingSound::isRepeating() const
|
||||
{
|
||||
return shouldRepeat;
|
||||
}
|
||||
AudioDevice PlayingSound::getPlaybackDevice() const
|
||||
{
|
||||
return playbackDevice;
|
||||
}
|
||||
void PlayingSound::setVolume(float newVolume)
|
||||
{
|
||||
if (device.copy())
|
||||
{
|
||||
auto device = this->device.write();
|
||||
device->get()->masterVolumeFactor = newVolume;
|
||||
}
|
||||
}
|
||||
void PlayingSound::onProgressed(const std::uint64_t &frames)
|
||||
{
|
||||
readFrames += frames;
|
||||
buffer += frames;
|
||||
|
||||
if (buffer > (sampleRate / 2))
|
||||
{
|
||||
buffer = 0;
|
||||
Globals::gGui->onSoundProgressed(shared_from_this());
|
||||
}
|
||||
}
|
||||
void PlayingSound::onFinished()
|
||||
{
|
||||
if (device.copy() && done)
|
||||
{
|
||||
stop();
|
||||
Globals::gGui->onSoundFinished(shared_from_this());
|
||||
}
|
||||
}
|
||||
void PlayingSound::data_callback(ma_device *rawDevice, void *output, [[maybe_unused]] const void *input,
|
||||
std::uint32_t frameCount)
|
||||
{
|
||||
auto *thiz = reinterpret_cast<PlayingSound *>(rawDevice->pUserData);
|
||||
|
||||
if (!thiz->done)
|
||||
{
|
||||
auto decoder = thiz->decoder.write();
|
||||
auto readFrames = ma_decoder_read_pcm_frames(decoder->get(), output, frameCount);
|
||||
|
||||
if (thiz->seekTo.load().has_value())
|
||||
{
|
||||
ma_decoder_seek_to_pcm_frame(decoder->get(), thiz->seekTo.load().value());
|
||||
thiz->readFrames = thiz->seekTo.load().value();
|
||||
thiz->seekTo = std::nullopt;
|
||||
}
|
||||
|
||||
if (thiz->playbackDevice.isDefault && readFrames > 0)
|
||||
{
|
||||
thiz->onProgressed(readFrames);
|
||||
}
|
||||
|
||||
if (readFrames <= 0)
|
||||
{
|
||||
if (thiz->shouldRepeat)
|
||||
{
|
||||
ma_decoder_seek_to_pcm_frame(decoder->get(), 0);
|
||||
thiz->readFrames = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
thiz->done = true;
|
||||
Globals::gQueue.push_unique(thiz->id, [thiz = thiz->shared_from_this()]() { thiz->onFinished(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Soundux::Objects
|
66
src/helper/audio/sound/sound.hpp
Normal file
66
src/helper/audio/sound/sound.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <core/objects/sound.hpp>
|
||||
#include <helper/audio/device/device.hpp>
|
||||
#include <lock.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
namespace Objects
|
||||
{
|
||||
class PlayingSound : public std::enable_shared_from_this<PlayingSound>
|
||||
{
|
||||
sxl::lock<std::shared_ptr<ma_device>> device;
|
||||
sxl::lock<std::shared_ptr<ma_decoder>> decoder;
|
||||
|
||||
private:
|
||||
AudioDevice playbackDevice;
|
||||
|
||||
std::uint64_t length{0};
|
||||
std::uint64_t readFrames{0};
|
||||
std::uint32_t sampleRate{0};
|
||||
|
||||
std::atomic<bool> done{false};
|
||||
std::atomic<bool> shouldRepeat{false};
|
||||
std::atomic<std::optional<std::uint64_t>> seekTo;
|
||||
|
||||
std::uint64_t buffer{0};
|
||||
std::shared_ptr<Sound> sound;
|
||||
|
||||
std::uint64_t id;
|
||||
static std::atomic<std::uint64_t> idCounter;
|
||||
|
||||
private:
|
||||
PlayingSound() = default;
|
||||
|
||||
void onFinished();
|
||||
void onProgressed(const std::uint64_t &frames);
|
||||
static void data_callback(ma_device *, void *, const void *, std::uint32_t);
|
||||
|
||||
public:
|
||||
void stop();
|
||||
void pause();
|
||||
void resume();
|
||||
|
||||
//* In Milliseconds
|
||||
std::uint64_t getRead() const;
|
||||
std::uint64_t getLength() const;
|
||||
|
||||
void repeat(bool);
|
||||
void setVolume(float);
|
||||
void seek(const std::uint64_t &);
|
||||
|
||||
bool isPaused() const;
|
||||
bool isRepeating() const;
|
||||
std::uint64_t getId() const;
|
||||
AudioDevice getPlaybackDevice() const;
|
||||
std::shared_ptr<Sound> getSound() const;
|
||||
|
||||
public:
|
||||
~PlayingSound();
|
||||
static std::shared_ptr<PlayingSound> create(const std::shared_ptr<Sound> &, const AudioDevice &);
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
@ -29,9 +29,9 @@ namespace Soundux::Objects
|
||||
|
||||
return true;
|
||||
}
|
||||
std::shared_ptr<IconFetcher> IconFetcher::createInstance()
|
||||
std::unique_ptr<IconFetcher> IconFetcher::createInstance()
|
||||
{
|
||||
auto instance = std::shared_ptr<IconFetcher>(new IconFetcher()); // NOLINT
|
||||
auto instance = std::unique_ptr<IconFetcher>(new IconFetcher()); // NOLINT
|
||||
|
||||
if (instance->setup())
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ namespace Soundux
|
||||
std::optional<int> getPpid(int pid);
|
||||
|
||||
public:
|
||||
static std::shared_ptr<IconFetcher> createInstance();
|
||||
static std::unique_ptr<IconFetcher> createInstance();
|
||||
std::optional<std::string> getIcon(int pid, bool recursive = true);
|
||||
};
|
||||
} // namespace Objects
|
||||
|
@ -124,24 +124,13 @@ namespace nlohmann
|
||||
{
|
||||
static void to_json(json &j, const Soundux::Objects::PlayingSound &obj)
|
||||
{
|
||||
j = {
|
||||
{"sound", obj.sound}, {"id", obj.id},
|
||||
{"length", obj.length}, {"paused", obj.paused.load()},
|
||||
{"lengthInMs", obj.lengthInMs}, {"repeat", obj.repeat.load()},
|
||||
{"readFrames", obj.readFrames}, {"readInMs", obj.readInMs.load()},
|
||||
};
|
||||
}
|
||||
static void from_json(const json &j, Soundux::Objects::PlayingSound &obj)
|
||||
{
|
||||
j.at("id").get_to(obj.id);
|
||||
j.at("sound").get_to(obj.sound);
|
||||
j.at("length").get_to(obj.length);
|
||||
j.at("readFrames").get_to(obj.readFrames);
|
||||
j.at("lengthInMs").get_to(obj.lengthInMs);
|
||||
j["id"] = obj.getId();
|
||||
j["sound"] = obj.getSound();
|
||||
j["paused"] = obj.isPaused();
|
||||
j["repeat"] = obj.isRepeating();
|
||||
|
||||
obj.paused.store(j.at("paused").get<bool>());
|
||||
obj.repeat.store(j.at("repeat").get<bool>());
|
||||
obj.readInMs.store(j.at("readInMs").get<std::uint64_t>());
|
||||
j["lengthInMs"] = obj.getLength();
|
||||
j["readInMs"] = obj.getRead();
|
||||
}
|
||||
};
|
||||
template <> struct adl_serializer<Soundux::Objects::Settings>
|
||||
|
@ -6,10 +6,10 @@
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
|
||||
#pragma push_macro("UNICOCDE")
|
||||
#pragma push_macro("UNICODE")
|
||||
#undef UNICODE
|
||||
#include <process.hpp>
|
||||
#pragma pop_macro("UNICOCDE")
|
||||
#pragma pop_macro("UNICODE")
|
||||
|
||||
#include <regex>
|
||||
#include <system_error>
|
||||
@ -101,7 +101,7 @@ namespace Soundux
|
||||
auto trashFolder = std::filesystem::canonical(home + "/.local/share/Trash");
|
||||
|
||||
auto filePath = std::filesystem::canonical(path);
|
||||
auto trashFileName = filePath.filename().u8string() +
|
||||
auto trashFileName = filePath.filename().string() +
|
||||
std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
|
||||
std::error_code ec;
|
||||
@ -122,7 +122,7 @@ namespace Soundux
|
||||
|
||||
std::ofstream stream(trashFolder / "info" / (trashFileName + ".trashinfo"));
|
||||
stream << "[Trash Info]" << std::endl
|
||||
<< "Path=" << filePath.u8string() << std::endl
|
||||
<< "Path=" << filePath.string() << std::endl
|
||||
<< "DeletionDate=" << ss.str() << std::endl;
|
||||
stream.close();
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "youtube-dl.hpp"
|
||||
#include "process.hpp"
|
||||
#include <core/global/globals.hpp>
|
||||
#include <fancy.hpp>
|
||||
#include <helper/misc/misc.hpp>
|
||||
@ -24,17 +25,17 @@ namespace Soundux::Objects
|
||||
Fancy::fancy.logTime().warning() << "youtube-dl or ffmpeg is not available!" << std::endl;
|
||||
}
|
||||
}
|
||||
std::optional<nlohmann::json> YoutubeDl::getInfo(const std::string &url) const
|
||||
nlohmann::json YoutubeDl::getInfo(const std::string &url) const
|
||||
{
|
||||
if (!isAvailable)
|
||||
{
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!std::regex_match(url, urlRegex))
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Bad url " >> url << std::endl;
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto [result, success] = Helpers::getResultCompact("youtube-dl -i -j \"" + url + "\"");
|
||||
@ -45,7 +46,7 @@ namespace Soundux::Objects
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Failed to parse youtube-dl information" << std::endl;
|
||||
Globals::gGui->onError(Enums::ErrorCode::YtdlInvalidJson);
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nlohmann::json j;
|
||||
@ -67,7 +68,7 @@ namespace Soundux::Objects
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to get info from youtube-dl" << std::endl;
|
||||
Globals::gGui->onError(Enums::ErrorCode::YtdlInformationUnknown);
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
bool YoutubeDl::download(const std::string &url)
|
||||
{
|
||||
@ -99,21 +100,22 @@ namespace Soundux::Objects
|
||||
currentDownload.reset();
|
||||
}
|
||||
|
||||
currentDownload.emplace("youtube-dl --extract-audio --audio-format mp3 --no-mtime \"" + url + "\" -o \"" +
|
||||
currentTab->path + "/%(title)s.%(ext)s" + "\"",
|
||||
"", [](const char *rawData, std::size_t dataLen) {
|
||||
std::string data(rawData, dataLen);
|
||||
static const std::regex progressRegex(R"(([0-9.,]+)%.*(ETA (.+)))");
|
||||
currentDownload = std::make_unique<TinyProcessLib::Process>(
|
||||
"youtube-dl --extract-audio --audio-format mp3 --no-mtime \"" + url + "\" -o \"" + currentTab->path +
|
||||
"/%(title)s.%(ext)s" + "\"",
|
||||
"", [](const char *rawData, std::size_t dataLen) {
|
||||
std::string data(rawData, dataLen);
|
||||
static const std::regex progressRegex(R"(([0-9.,]+)%.*(ETA (.+)))");
|
||||
|
||||
std::smatch match;
|
||||
if (std::regex_search(data, match, progressRegex))
|
||||
{
|
||||
if (match[1].matched && match[3].matched)
|
||||
{
|
||||
Globals::gGui->onDownloadProgressed(std::stof(match[1]), match[3]);
|
||||
}
|
||||
}
|
||||
});
|
||||
std::smatch match;
|
||||
if (std::regex_search(data, match, progressRegex))
|
||||
{
|
||||
if (match[1].matched && match[3].matched)
|
||||
{
|
||||
Globals::gGui->onDownloadProgressed(std::stof(match[1]), match[3]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Fancy::fancy.logTime().success() << "Started download of " >> url << std::endl;
|
||||
auto rtn = currentDownload->get_exit_status() == 0;
|
||||
|
@ -2,10 +2,10 @@
|
||||
#include <json.hpp>
|
||||
#include <optional>
|
||||
|
||||
#pragma push_macro("UNICOCDE")
|
||||
#pragma push_macro("UNICODE")
|
||||
#undef UNICODE
|
||||
#include <process.hpp>
|
||||
#pragma pop_macro("UNICOCDE")
|
||||
#pragma pop_macro("UNICODE")
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
@ -18,14 +18,14 @@ namespace Soundux
|
||||
{
|
||||
bool isAvailable = false;
|
||||
static const std::regex urlRegex;
|
||||
std::optional<TinyProcessLib::Process> currentDownload;
|
||||
std::unique_ptr<TinyProcessLib::Process> currentDownload;
|
||||
|
||||
public:
|
||||
void setup();
|
||||
void killDownload();
|
||||
bool available() const;
|
||||
bool download(const std::string &);
|
||||
std::optional<nlohmann::json> getInfo(const std::string &) const;
|
||||
nlohmann::json getInfo(const std::string &) const;
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
@ -82,7 +82,6 @@ int main(int argc, char **arguments)
|
||||
gWinSound = WinSound::createInstance();
|
||||
#endif
|
||||
|
||||
gAudio.setup();
|
||||
gYtdl.setup();
|
||||
|
||||
#if defined(__linux__)
|
||||
@ -112,7 +111,6 @@ int main(int argc, char **arguments)
|
||||
|
||||
gGui->mainLoop();
|
||||
|
||||
gAudio.destroy();
|
||||
#if defined(__linux__)
|
||||
if (gAudioBackend)
|
||||
{
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit d43538e165a8f7a927b94718276323fc7b71eab5
|
||||
Subproject commit 20ce8b89aeb6ffa6dcccdd509d52c0de24f788e6
|
@ -270,21 +270,6 @@ namespace Soundux::Objects
|
||||
Webview::Function("startPassthrough", [this](const std::string &app) { return startPassthrough(app); }));
|
||||
webview->expose(
|
||||
Webview::Function("stopPassthrough", [this](const std::string &name) { stopPassthrough(name); }));
|
||||
webview->expose(Webview::Function("unloadSwitchOnConnect", []() {
|
||||
auto pulseBackend =
|
||||
std::dynamic_pointer_cast<Soundux::Objects::PulseAudio>(Soundux::Globals::gAudioBackend);
|
||||
if (pulseBackend)
|
||||
{
|
||||
pulseBackend->unloadSwitchOnConnect();
|
||||
pulseBackend->loadModules();
|
||||
Globals::gAudio.setup();
|
||||
}
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().failure()
|
||||
<< "unloadSwitchOnConnect was called but no pulse backend was detected!" << std::endl;
|
||||
}
|
||||
}));
|
||||
#endif
|
||||
}
|
||||
bool WebView::onClose()
|
||||
@ -308,15 +293,6 @@ namespace Soundux::Objects
|
||||
static bool once = false;
|
||||
if (!once)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
if (auto pulseBackend = std::dynamic_pointer_cast<PulseAudio>(Globals::gAudioBackend); pulseBackend)
|
||||
{
|
||||
//* We have to call this so that we can trigger an event in the frontend that switchOnConnect was
|
||||
//* found becausepreviously the UI was not initialized.
|
||||
pulseBackend->switchOnConnectPresent();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto future = std::make_shared<std::future<void>>();
|
||||
*future = std::async(std::launch::async, [future, this] {
|
||||
translations.settings = webview
|
||||
@ -397,21 +373,27 @@ namespace Soundux::Objects
|
||||
webview->callFunction<void>(
|
||||
Webview::JavaScriptFunction("window.hotkeyReceived", Globals::gHotKeys->getKeySequence(keys), keys));
|
||||
}
|
||||
void WebView::onSoundFinished(const PlayingSound &sound)
|
||||
void WebView::onSoundFinished(const std::shared_ptr<PlayingSound> &sound)
|
||||
{
|
||||
Window::onSoundFinished(sound);
|
||||
if (sound.playbackDevice.isDefault)
|
||||
if (sound && sound->getPlaybackDevice().isDefault)
|
||||
{
|
||||
webview->callFunction<void>(Webview::JavaScriptFunction("window.finishSound", sound));
|
||||
webview->callFunction<void>(Webview::JavaScriptFunction("window.finishSound", *sound));
|
||||
}
|
||||
}
|
||||
void WebView::onSoundPlayed(const PlayingSound &sound)
|
||||
void WebView::onSoundPlayed(const std::shared_ptr<PlayingSound> &sound)
|
||||
{
|
||||
webview->callFunction<void>(Webview::JavaScriptFunction("window.onSoundPlayed", sound));
|
||||
if (sound)
|
||||
{
|
||||
webview->callFunction<void>(Webview::JavaScriptFunction("window.onSoundPlayed", *sound));
|
||||
}
|
||||
}
|
||||
void WebView::onSoundProgressed(const PlayingSound &sound)
|
||||
void WebView::onSoundProgressed(const std::shared_ptr<PlayingSound> &sound)
|
||||
{
|
||||
webview->callFunction<void>(Webview::JavaScriptFunction("window.updateSound", sound));
|
||||
if (sound)
|
||||
{
|
||||
webview->callFunction<void>(Webview::JavaScriptFunction("window.updateSound", *sound));
|
||||
}
|
||||
}
|
||||
void WebView::onDownloadProgressed(float progress, const std::string &eta)
|
||||
{
|
||||
@ -438,11 +420,6 @@ namespace Soundux::Objects
|
||||
Window::onAllSoundsFinished();
|
||||
webview->callFunction<void>(Webview::JavaScriptFunction("window.getStore().commit", "clearCurrentlyPlaying"));
|
||||
}
|
||||
void WebView::onSwitchOnConnectDetected(bool state)
|
||||
{
|
||||
webview->callFunction<void>(
|
||||
Webview::JavaScriptFunction("window.getStore().commit", "setSwitchOnConnectLoaded", state));
|
||||
}
|
||||
void WebView::onAdminRequired()
|
||||
{
|
||||
webview->callFunction<void>(
|
||||
|
@ -27,18 +27,18 @@ namespace Soundux
|
||||
void show() override;
|
||||
void setup() override;
|
||||
void mainLoop() override;
|
||||
void onSoundFinished(const PlayingSound &sound) override;
|
||||
void onHotKeyReceived(const std::vector<Key> &keys) override;
|
||||
|
||||
void onAdminRequired() override;
|
||||
void onSettingsChanged() override;
|
||||
void onLocalVolumeChanged(int volume) override;
|
||||
void onRemoteVolumeChanged(int volume) override;
|
||||
void onSwitchOnConnectDetected(bool state) override;
|
||||
void onError(const Enums::ErrorCode &error) override;
|
||||
void onSoundPlayed(const PlayingSound &sound) override;
|
||||
void onSoundProgressed(const PlayingSound &sound) override;
|
||||
void onDownloadProgressed(float progress, const std::string &eta) override;
|
||||
|
||||
void onSoundPlayed(const std::shared_ptr<PlayingSound> &sound) override;
|
||||
void onSoundFinished(const std::shared_ptr<PlayingSound> &sound) override;
|
||||
void onSoundProgressed(const std::shared_ptr<PlayingSound> &sound) override;
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
397
src/ui/ui.cpp
397
src/ui/ui.cpp
@ -22,6 +22,24 @@ namespace Soundux::Objects
|
||||
tab.sounds = getTabContent(tab);
|
||||
Globals::gData.setTab(tab.id, tab);
|
||||
}
|
||||
|
||||
setDevices();
|
||||
}
|
||||
void Window::setDevices()
|
||||
{
|
||||
for (const auto &device : AudioDevice::getDevices())
|
||||
{
|
||||
if (device.isDefault)
|
||||
{
|
||||
defaultPlayback = device;
|
||||
}
|
||||
#if defined(__linux__)
|
||||
if (device.name == "soundux_sink")
|
||||
{
|
||||
remotePlayback = device;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Window::~Window()
|
||||
{
|
||||
@ -204,14 +222,16 @@ namespace Soundux::Objects
|
||||
return {};
|
||||
}
|
||||
#if defined(__linux__)
|
||||
std::optional<PlayingSound> Window::playSound(const std::uint32_t &id)
|
||||
std::shared_ptr<PlayingSound> Window::playSound(const std::uint32_t &id)
|
||||
{
|
||||
auto sound = Globals::gData.getSound(id);
|
||||
auto groupedSounds = this->groupedSounds.write();
|
||||
|
||||
if (sound)
|
||||
{
|
||||
if (!Globals::gSettings.allowOverlapping)
|
||||
{
|
||||
stopSounds(true);
|
||||
stopSounds();
|
||||
}
|
||||
if (Globals::gSettings.muteDuringPlayback)
|
||||
{
|
||||
@ -228,22 +248,17 @@ namespace Soundux::Objects
|
||||
Globals::gHotKeys->pressKeys(Globals::gSettings.pushToTalkKeys);
|
||||
}
|
||||
|
||||
auto playingSound = Globals::gAudio.play(*sound);
|
||||
auto remotePlayingSound = Globals::gAudio.play(*sound, Globals::gAudio.nullSink);
|
||||
auto playingSound = PlayingSound::create(*sound, defaultPlayback);
|
||||
auto remotePlayingSound = PlayingSound::create(*sound, remotePlayback);
|
||||
|
||||
if (playingSound && remotePlayingSound)
|
||||
{
|
||||
groupedSounds->insert({playingSound->id, remotePlayingSound->id});
|
||||
if (Globals::gSettings.outputs.empty() && playingSound)
|
||||
{
|
||||
return *playingSound;
|
||||
}
|
||||
if (!Globals::gSettings.outputs.empty() && Globals::gAudioBackend)
|
||||
{
|
||||
bool moveSuccess = false;
|
||||
for (const auto &outputApp : Globals::gSettings.outputs)
|
||||
{
|
||||
if (Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(outputApp)))
|
||||
if (Globals::gAudioBackend->inputSoundTo(outputApp))
|
||||
{
|
||||
moveSuccess = true;
|
||||
}
|
||||
@ -252,30 +267,32 @@ namespace Soundux::Objects
|
||||
if (!moveSuccess)
|
||||
{
|
||||
if (playingSound)
|
||||
stopSound(playingSound->id);
|
||||
playingSound->stop();
|
||||
if (remotePlayingSound)
|
||||
stopSound(remotePlayingSound->id);
|
||||
remotePlayingSound->stop();
|
||||
|
||||
onError(Enums::ErrorCode::FailedToMoveToSink);
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *playingSound;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl;
|
||||
onError(Enums::ErrorCode::SoundNotFound);
|
||||
return std::nullopt;
|
||||
else
|
||||
{
|
||||
Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl;
|
||||
onError(Enums::ErrorCode::FailedToPlay);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
groupedSounds->insert({playingSound->getId(), {playingSound, remotePlayingSound}});
|
||||
return playingSound;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl;
|
||||
onError(Enums::ErrorCode::FailedToPlay);
|
||||
return std::nullopt;
|
||||
Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl;
|
||||
onError(Enums::ErrorCode::SoundNotFound);
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
// TODO
|
||||
std::optional<PlayingSound> Window::playSound(const std::uint32_t &id)
|
||||
{
|
||||
auto sound = Globals::gData.getSound(id);
|
||||
@ -336,138 +353,102 @@ namespace Soundux::Objects
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
std::optional<PlayingSound> Window::pauseSound(const std::uint32_t &id)
|
||||
std::shared_ptr<PlayingSound> Window::pauseSound(const std::uint32_t &id)
|
||||
{
|
||||
std::optional<std::uint32_t> remoteSoundId;
|
||||
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
auto scoped = groupedSounds.scoped();
|
||||
if (scoped->find(id) == scoped->end())
|
||||
{
|
||||
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteSoundId = scoped->at(id);
|
||||
}
|
||||
}
|
||||
auto playingSound = Globals::gAudio.pause(id);
|
||||
if (remoteSoundId)
|
||||
{
|
||||
Globals::gAudio.pause(*remoteSoundId);
|
||||
}
|
||||
auto groupedSounds = this->groupedSounds.read();
|
||||
|
||||
if (playingSound)
|
||||
if (groupedSounds->count(id))
|
||||
{
|
||||
return *playingSound;
|
||||
auto sounds = groupedSounds->at(id);
|
||||
|
||||
if (sounds.first)
|
||||
{
|
||||
sounds.first->pause();
|
||||
}
|
||||
if (sounds.second)
|
||||
{
|
||||
sounds.second->pause();
|
||||
}
|
||||
|
||||
return sounds.first;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to pause sound " << id << std::endl;
|
||||
onError(Enums::ErrorCode::FailedToPause);
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
std::optional<PlayingSound> Window::resumeSound(const std::uint32_t &id)
|
||||
std::shared_ptr<PlayingSound> Window::resumeSound(const std::uint32_t &id)
|
||||
{
|
||||
std::optional<std::uint32_t> remoteSoundId;
|
||||
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
auto scoped = groupedSounds.scoped();
|
||||
if (scoped->find(id) == scoped->end())
|
||||
{
|
||||
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteSoundId = scoped->at(id);
|
||||
}
|
||||
}
|
||||
auto playingSound = Globals::gAudio.resume(id);
|
||||
if (remoteSoundId)
|
||||
{
|
||||
Globals::gAudio.resume(*remoteSoundId);
|
||||
}
|
||||
auto groupedSounds = this->groupedSounds.read();
|
||||
|
||||
if (playingSound)
|
||||
if (groupedSounds->count(id))
|
||||
{
|
||||
return *playingSound;
|
||||
auto sounds = groupedSounds->at(id);
|
||||
|
||||
if (sounds.first)
|
||||
{
|
||||
sounds.first->resume();
|
||||
}
|
||||
if (sounds.second)
|
||||
{
|
||||
sounds.second->resume();
|
||||
}
|
||||
|
||||
return sounds.first;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to resume sound " << id << std::endl;
|
||||
onError(Enums::ErrorCode::FailedToResume);
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
std::optional<PlayingSound> Window::seekSound(const std::uint32_t &id, std::uint64_t seekTo)
|
||||
std::shared_ptr<PlayingSound> Window::seekSound(const std::uint32_t &id, std::uint64_t seekTo)
|
||||
{
|
||||
std::optional<std::uint32_t> remoteSoundId;
|
||||
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
auto scoped = groupedSounds.scoped();
|
||||
if (scoped->find(id) == scoped->end())
|
||||
{
|
||||
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteSoundId = scoped->at(id);
|
||||
}
|
||||
}
|
||||
auto playingSound = Globals::gAudio.seek(id, seekTo);
|
||||
if (remoteSoundId)
|
||||
{
|
||||
Globals::gAudio.seek(*remoteSoundId, seekTo);
|
||||
}
|
||||
auto groupedSounds = this->groupedSounds.read();
|
||||
|
||||
if (playingSound)
|
||||
if (groupedSounds->count(id))
|
||||
{
|
||||
return *playingSound;
|
||||
auto sounds = groupedSounds->at(id);
|
||||
|
||||
if (sounds.first)
|
||||
{
|
||||
sounds.first->seek(seekTo);
|
||||
}
|
||||
if (sounds.second)
|
||||
{
|
||||
sounds.second->seek(seekTo);
|
||||
}
|
||||
|
||||
return sounds.first;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().warning() << "Failed to seek sound " << id << " to " << seekTo << std::endl;
|
||||
onError(Enums::ErrorCode::FailedToSeek);
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
std::optional<PlayingSound> Window::repeatSound(const std::uint32_t &id, bool shouldRepeat)
|
||||
std::shared_ptr<PlayingSound> Window::repeatSound(const std::uint32_t &id, bool shouldRepeat)
|
||||
{
|
||||
std::optional<std::uint32_t> remoteSoundId;
|
||||
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
auto scoped = groupedSounds.scoped();
|
||||
if (scoped->find(id) == scoped->end())
|
||||
{
|
||||
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteSoundId = scoped->at(id);
|
||||
}
|
||||
}
|
||||
auto playingSound = Globals::gAudio.repeat(id, shouldRepeat);
|
||||
if (remoteSoundId)
|
||||
{
|
||||
Globals::gAudio.repeat(*remoteSoundId, shouldRepeat);
|
||||
}
|
||||
auto groupedSounds = this->groupedSounds.read();
|
||||
|
||||
if (playingSound)
|
||||
if (groupedSounds->count(id))
|
||||
{
|
||||
return *playingSound;
|
||||
auto sounds = groupedSounds->at(id);
|
||||
|
||||
if (sounds.first)
|
||||
{
|
||||
sounds.first->repeat(shouldRepeat);
|
||||
}
|
||||
if (sounds.second)
|
||||
{
|
||||
sounds.second->repeat(shouldRepeat);
|
||||
}
|
||||
|
||||
return sounds.first;
|
||||
}
|
||||
|
||||
Fancy::fancy.logTime().failure() << "Failed to set repeat-state of sound " << id << " to " << shouldRepeat
|
||||
<< std::endl;
|
||||
onError(Enums::ErrorCode::FailedToRepeat);
|
||||
return std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<Tab> Window::removeTab(const std::uint32_t &id)
|
||||
{
|
||||
@ -476,46 +457,37 @@ namespace Soundux::Objects
|
||||
}
|
||||
bool Window::stopSound(const std::uint32_t &id)
|
||||
{
|
||||
std::optional<std::uint32_t> remoteSoundId;
|
||||
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
|
||||
auto groupedSounds = this->groupedSounds.write();
|
||||
|
||||
if (groupedSounds->count(id))
|
||||
{
|
||||
auto scoped = groupedSounds.scoped();
|
||||
if (scoped->find(id) == scoped->end())
|
||||
auto sounds = groupedSounds->at(id);
|
||||
|
||||
if (sounds.first)
|
||||
{
|
||||
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
|
||||
return false;
|
||||
sounds.first->stop();
|
||||
}
|
||||
if (sounds.second)
|
||||
{
|
||||
sounds.second->stop();
|
||||
}
|
||||
|
||||
remoteSoundId = scoped->at(id);
|
||||
}
|
||||
|
||||
auto status = Globals::gAudio.stop(id);
|
||||
if (remoteSoundId)
|
||||
{
|
||||
Globals::gAudio.stop(*remoteSoundId);
|
||||
groupedSounds->erase(id);
|
||||
|
||||
if (groupedSounds->empty())
|
||||
{
|
||||
onAllSoundsFinished();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Globals::gAudio.getPlayingSounds().empty())
|
||||
{
|
||||
onAllSoundsFinished();
|
||||
}
|
||||
|
||||
return status;
|
||||
return false;
|
||||
}
|
||||
void Window::stopSounds(bool sync)
|
||||
void Window::stopSounds()
|
||||
{
|
||||
if (!sync)
|
||||
{
|
||||
Globals::gQueue.push_unique(0, []() { Globals::gAudio.stopAll(); });
|
||||
}
|
||||
else
|
||||
{
|
||||
Globals::gAudio.stopAll();
|
||||
}
|
||||
|
||||
groupedSounds.write()->clear();
|
||||
onAllSoundsFinished();
|
||||
groupedSounds->clear();
|
||||
|
||||
#if defined(__linux__)
|
||||
if (Globals::gAudioBackend)
|
||||
@ -537,13 +509,15 @@ namespace Soundux::Objects
|
||||
if (sound)
|
||||
{
|
||||
sound->get().localVolume = localVolume;
|
||||
auto groupedSounds = this->groupedSounds.write();
|
||||
|
||||
for (auto &playingSound : Globals::gAudio.getPlayingSounds())
|
||||
for (const auto &entry : *groupedSounds)
|
||||
{
|
||||
if (playingSound.sound.id == sound->get().id && playingSound.playbackDevice.isDefault)
|
||||
const auto &local = entry.second.first;
|
||||
if (local->getSound().id == sound->get().id)
|
||||
{
|
||||
playingSound.raw.device.load()->masterVolumeFactor =
|
||||
static_cast<float>(localVolume ? *localVolume : Globals::gSettings.localVolume) / 100.f;
|
||||
local->setVolume(static_cast<float>(localVolume ? *localVolume : Globals::gSettings.localVolume) /
|
||||
100.f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,16 +532,19 @@ namespace Soundux::Objects
|
||||
std::optional<Sound> Window::setCustomRemoteVolume(const std::uint32_t &id, const std::optional<int> &remoteVolume)
|
||||
{
|
||||
auto sound = Globals::gData.getSound(id);
|
||||
auto groupedSounds = this->groupedSounds.write();
|
||||
|
||||
if (sound)
|
||||
{
|
||||
sound->get().remoteVolume = remoteVolume;
|
||||
|
||||
for (auto &playingSound : Globals::gAudio.getPlayingSounds())
|
||||
for (const auto &entry : *groupedSounds)
|
||||
{
|
||||
if (playingSound.sound.id == sound->get().id && !playingSound.playbackDevice.isDefault)
|
||||
const auto &remote = entry.second.second;
|
||||
if (remote && remote->getSound().id == sound->get().id && !remote->getPlaybackDevice().isDefault)
|
||||
{
|
||||
playingSound.raw.device.load()->masterVolumeFactor =
|
||||
static_cast<float>(remoteVolume ? *remoteVolume : Globals::gSettings.remoteVolume) / 100.f;
|
||||
remote->setVolume(
|
||||
static_cast<float>(remoteVolume ? *remoteVolume : Globals::gSettings.remoteVolume) / 100.f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -581,21 +558,25 @@ namespace Soundux::Objects
|
||||
}
|
||||
void Window::onVolumeChanged()
|
||||
{
|
||||
for (const auto &playingSound : Globals::gAudio.getPlayingSounds())
|
||||
auto groupedSounds = this->groupedSounds.write();
|
||||
for (const auto &entry : *groupedSounds)
|
||||
{
|
||||
int newVolume = 0;
|
||||
const auto &sound = playingSound.sound;
|
||||
const auto &[local, remote] = entry.second;
|
||||
const auto &sound = local->getSound();
|
||||
|
||||
if (playingSound.playbackDevice.isDefault)
|
||||
if (local)
|
||||
{
|
||||
newVolume = sound.localVolume ? *sound.localVolume : Globals::gSettings.localVolume;
|
||||
}
|
||||
else
|
||||
{
|
||||
newVolume = sound.remoteVolume ? *sound.remoteVolume : Globals::gSettings.remoteVolume;
|
||||
local->setVolume(
|
||||
static_cast<float>(sound.localVolume ? *sound.localVolume : Globals::gSettings.localVolume) /
|
||||
100.f);
|
||||
}
|
||||
|
||||
playingSound.raw.device.load()->masterVolumeFactor = static_cast<float>(newVolume) / 100.f;
|
||||
if (remote)
|
||||
{
|
||||
remote->setVolume(
|
||||
static_cast<float>(sound.remoteVolume ? *sound.remoteVolume : Globals::gSettings.remoteVolume) /
|
||||
100.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
Settings Window::changeSettings(Settings settings)
|
||||
@ -611,19 +592,19 @@ namespace Soundux::Objects
|
||||
#if defined(__linux__)
|
||||
if (settings.audioBackend != oldSettings.audioBackend)
|
||||
{
|
||||
stopSounds(true);
|
||||
stopSounds();
|
||||
|
||||
if (Globals::gAudioBackend)
|
||||
{
|
||||
Globals::gAudioBackend->destroy();
|
||||
}
|
||||
|
||||
Globals::gAudioBackend = AudioBackend::createInstance(settings.audioBackend);
|
||||
Globals::gAudio.setup();
|
||||
setDevices();
|
||||
}
|
||||
if (Globals::gAudioBackend)
|
||||
{
|
||||
if (!Globals::gAudio.getPlayingSounds().empty())
|
||||
auto groupedSounds = this->groupedSounds.read();
|
||||
if (!groupedSounds->empty())
|
||||
{
|
||||
if (settings.muteDuringPlayback && !oldSettings.muteDuringPlayback)
|
||||
{
|
||||
@ -677,9 +658,10 @@ namespace Soundux::Objects
|
||||
|
||||
for (const auto &outputApp : settings.outputs)
|
||||
{
|
||||
if (!settings.outputs.empty() && !Globals::gAudio.getPlayingSounds().empty())
|
||||
|
||||
if (!settings.outputs.empty() && !groupedSounds->empty())
|
||||
{
|
||||
if (!Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(outputApp)))
|
||||
if (!Globals::gAudioBackend->inputSoundTo(outputApp))
|
||||
{
|
||||
onError(Enums::ErrorCode::FailedToMoveToSink);
|
||||
}
|
||||
@ -818,7 +800,7 @@ namespace Soundux::Objects
|
||||
else if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWireRecordingApp>(stream);
|
||||
pipeWireApp)
|
||||
{
|
||||
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->pid));
|
||||
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->node.pid));
|
||||
if (icon)
|
||||
{
|
||||
iconStream->appIcon = *icon;
|
||||
@ -866,7 +848,7 @@ namespace Soundux::Objects
|
||||
}
|
||||
if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWirePlaybackApp>(stream); pipeWireApp)
|
||||
{
|
||||
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->pid));
|
||||
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->node.pid));
|
||||
if (icon)
|
||||
{
|
||||
iconStream->appIcon = *icon;
|
||||
@ -888,7 +870,7 @@ namespace Soundux::Objects
|
||||
{
|
||||
for (const auto &outputApp : Globals::gSettings.outputs)
|
||||
{
|
||||
if (!Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(outputApp)))
|
||||
if (!Globals::gAudioBackend->inputSoundTo(outputApp))
|
||||
{
|
||||
onError(Enums::ErrorCode::FailedToMoveToSink);
|
||||
success = false;
|
||||
@ -897,7 +879,7 @@ namespace Soundux::Objects
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (!Globals::gAudioBackend->passthroughFrom(Globals::gAudioBackend->getPlaybackApp(name)))
|
||||
if (!Globals::gAudioBackend->passthroughFrom(name))
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
@ -915,19 +897,19 @@ namespace Soundux::Objects
|
||||
{
|
||||
if (Globals::gAudioBackend)
|
||||
{
|
||||
if (Globals::gAudio.getPlayingSounds().empty() &&
|
||||
Globals::gAudioBackend->currentlyPassedThrough().size() == 1)
|
||||
if (!Globals::gAudioBackend->stopPassthrough(name))
|
||||
{
|
||||
onError(Enums::ErrorCode::FailedToMoveBackPassthrough);
|
||||
}
|
||||
|
||||
auto groupedSounds = this->groupedSounds.read();
|
||||
if (groupedSounds->empty() && Globals::gAudioBackend->currentlyPassedThrough().empty())
|
||||
{
|
||||
if (!Globals::gAudioBackend->stopSoundInput())
|
||||
{
|
||||
onError(Enums::ErrorCode::FailedToMoveBack);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Globals::gAudioBackend->stopPassthrough(name))
|
||||
{
|
||||
onError(Enums::ErrorCode::FailedToMoveBackPassthrough);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
@ -936,16 +918,15 @@ namespace Soundux::Objects
|
||||
return Globals::gAudio.getAudioDevices();
|
||||
}
|
||||
#endif
|
||||
void Window::onSoundFinished(const PlayingSound &sound)
|
||||
void Window::onSoundFinished(const std::shared_ptr<PlayingSound> &sound)
|
||||
{
|
||||
auto scoped = groupedSounds.scoped();
|
||||
if (scoped->find(sound.id) != scoped->end())
|
||||
auto groupedSounds = this->groupedSounds.write();
|
||||
if (groupedSounds->count(sound->getId()))
|
||||
{
|
||||
scoped->erase(sound.id);
|
||||
groupedSounds->erase(sound->getId());
|
||||
}
|
||||
scoped.unlock();
|
||||
|
||||
if (Globals::gAudio.getPlayingSounds().size() == 1)
|
||||
if (groupedSounds->empty())
|
||||
{
|
||||
onAllSoundsFinished();
|
||||
}
|
||||
@ -988,7 +969,7 @@ namespace Soundux::Objects
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void Window::onSoundPlayed([[maybe_unused]] const PlayingSound &sound)
|
||||
void Window::onSoundPlayed([[maybe_unused]] const std::shared_ptr<PlayingSound> &sound)
|
||||
{
|
||||
if (!Globals::gSettings.pushToTalkKeys.empty())
|
||||
{
|
||||
@ -1019,25 +1000,37 @@ namespace Soundux::Objects
|
||||
bool Window::toggleSoundPlayback()
|
||||
{
|
||||
bool shouldPause = true;
|
||||
for (const auto &sound : Globals::gAudio.getPlayingSounds())
|
||||
auto groupedSounds = this->groupedSounds.read();
|
||||
|
||||
for (const auto &sounds : *groupedSounds)
|
||||
{
|
||||
if (sound.paused)
|
||||
const auto &[local, remote] = sounds.second;
|
||||
if (local->isPaused())
|
||||
{
|
||||
shouldPause = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto soundsCopy = groupedSounds.copy();
|
||||
for (const auto &[local, remote] : soundsCopy)
|
||||
for (const auto &sounds : *groupedSounds)
|
||||
{
|
||||
const auto &[local, remote] = sounds.second;
|
||||
|
||||
if (shouldPause)
|
||||
{
|
||||
pauseSound(local);
|
||||
local->pause();
|
||||
if (remote)
|
||||
{
|
||||
remote->pause();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resumeSound(local);
|
||||
local->resume();
|
||||
if (remote)
|
||||
{
|
||||
remote->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,12 @@
|
||||
#pragma once
|
||||
#include <core/objects/settings.hpp>
|
||||
#include <helper/audio/audio.hpp>
|
||||
#include <helper/audio/sound/sound.hpp>
|
||||
#if defined(__linux__)
|
||||
#include <helper/audio/linux/backend.hpp>
|
||||
#endif
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <queue>
|
||||
#include <lock.hpp>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <var_guard.hpp>
|
||||
|
||||
namespace Soundux
|
||||
{
|
||||
@ -34,7 +32,10 @@ namespace Soundux
|
||||
friend class Hotkeys;
|
||||
|
||||
protected:
|
||||
sxl::var_guard<std::map<std::uint32_t, std::uint32_t>> groupedSounds;
|
||||
using SoundPair = std::pair<std::shared_ptr<PlayingSound>, std::shared_ptr<PlayingSound>>;
|
||||
sxl::lock<std::map<std::uint32_t, SoundPair>> groupedSounds;
|
||||
AudioDevice defaultPlayback;
|
||||
AudioDevice remotePlayback;
|
||||
|
||||
struct
|
||||
{
|
||||
@ -77,17 +78,18 @@ namespace Soundux
|
||||
virtual std::optional<Tab> setSortMode(const std::uint32_t &, Enums::SortMode);
|
||||
|
||||
protected:
|
||||
void setDevices();
|
||||
virtual void stopSounds();
|
||||
virtual void onVolumeChanged();
|
||||
virtual bool toggleSoundPlayback();
|
||||
virtual void stopSounds(bool = false);
|
||||
virtual bool stopSound(const std::uint32_t &);
|
||||
|
||||
protected:
|
||||
virtual std::optional<PlayingSound> playSound(const std::uint32_t &);
|
||||
virtual std::optional<PlayingSound> pauseSound(const std::uint32_t &);
|
||||
virtual std::optional<PlayingSound> resumeSound(const std::uint32_t &);
|
||||
virtual std::optional<PlayingSound> repeatSound(const std::uint32_t &, bool);
|
||||
virtual std::optional<PlayingSound> seekSound(const std::uint32_t &, std::uint64_t);
|
||||
virtual std::shared_ptr<PlayingSound> playSound(const std::uint32_t &);
|
||||
virtual std::shared_ptr<PlayingSound> pauseSound(const std::uint32_t &);
|
||||
virtual std::shared_ptr<PlayingSound> resumeSound(const std::uint32_t &);
|
||||
virtual std::shared_ptr<PlayingSound> repeatSound(const std::uint32_t &, bool);
|
||||
virtual std::shared_ptr<PlayingSound> seekSound(const std::uint32_t &, std::uint64_t);
|
||||
|
||||
virtual std::optional<Sound> setHotkey(const std::uint32_t &, const std::vector<Key> &);
|
||||
virtual std::optional<Sound> setCustomLocalVolume(const std::uint32_t &, const std::optional<int> &);
|
||||
@ -103,13 +105,13 @@ namespace Soundux
|
||||
virtual void onSettingsChanged() = 0;
|
||||
virtual void onLocalVolumeChanged(int) = 0;
|
||||
virtual void onRemoteVolumeChanged(int) = 0;
|
||||
virtual void onSwitchOnConnectDetected(bool) = 0;
|
||||
virtual void onSoundPlayed(const PlayingSound &);
|
||||
virtual void onError(const Enums::ErrorCode &) = 0;
|
||||
virtual void onSoundFinished(const PlayingSound &);
|
||||
virtual void onHotKeyReceived(const std::vector<Key> &);
|
||||
virtual void onSoundProgressed(const PlayingSound &) = 0;
|
||||
virtual void onDownloadProgressed(float, const std::string &) = 0;
|
||||
|
||||
virtual void onSoundPlayed(const std::shared_ptr<PlayingSound> &);
|
||||
virtual void onSoundFinished(const std::shared_ptr<PlayingSound> &);
|
||||
virtual void onSoundProgressed(const std::shared_ptr<PlayingSound> &) = 0;
|
||||
};
|
||||
} // namespace Objects
|
||||
} // namespace Soundux
|
Loading…
x
Reference in New Issue
Block a user