music_player.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. #include "music_player.h"
  2. #include "audio_constants.h"
  3. #include "miniaudio_backend.h"
  4. #include <QCoreApplication>
  5. #include <QFileInfo>
  6. #include <QMetaObject>
  7. #include <QTimer>
  8. #include <QtGlobal>
  9. #include <algorithm>
  10. #include <qcoreapplication.h>
  11. #include <qfileinfo.h>
  12. #include <qglobal.h>
  13. #include <qhashfunctions.h>
  14. #include <qnamespace.h>
  15. #include <qobject.h>
  16. #include <qobjectdefs.h>
  17. #include <qthread.h>
  18. #include <string>
  19. using namespace Game::Audio;
  20. static inline void requireGuiThread(const char *where) {
  21. if ((QCoreApplication::instance() == nullptr) ||
  22. QThread::currentThread() != QCoreApplication::instance()->thread()) {
  23. qFatal("%s must be called on the GUI thread", where);
  24. }
  25. }
  26. auto MusicPlayer::getInstance() -> MusicPlayer & {
  27. static MusicPlayer instance;
  28. return instance;
  29. }
  30. MusicPlayer::MusicPlayer() : QObject(nullptr) {}
  31. MusicPlayer::~MusicPlayer() { shutdown(); }
  32. auto MusicPlayer::initialize(int musicChannels) -> bool {
  33. static constexpr int MIN_CHANNELS = 1;
  34. if (m_initialized) {
  35. return true;
  36. }
  37. if (QCoreApplication::instance() == nullptr) {
  38. qWarning() << "MusicPlayer: no Q(Gui)Application instance";
  39. return false;
  40. }
  41. ensureOnGuiThread("MusicPlayer::initialize");
  42. m_channelCount = std::max(MIN_CHANNELS, musicChannels);
  43. m_backend = new MiniaudioBackend(this);
  44. if (!m_backend->initialize(AudioConstants::DEFAULT_SAMPLE_RATE,
  45. AudioConstants::DEFAULT_OUTPUT_CHANNELS,
  46. m_channelCount)) {
  47. qWarning() << "MusicPlayer: backend init failed";
  48. m_backend->deleteLater();
  49. m_backend = nullptr;
  50. return false;
  51. }
  52. m_initialized = true;
  53. qInfo() << "MusicPlayer initialized (miniaudio backend) channels:"
  54. << m_channelCount;
  55. return true;
  56. }
  57. void MusicPlayer::shutdown() {
  58. if (!m_initialized) {
  59. return;
  60. }
  61. if ((QCoreApplication::instance() != nullptr) &&
  62. QThread::currentThread() != QCoreApplication::instance()->thread()) {
  63. QMetaObject::invokeMethod(
  64. this, [this]() { shutdown(); }, Qt::BlockingQueuedConnection);
  65. return;
  66. }
  67. ensureOnGuiThread("MusicPlayer::shutdown");
  68. if (m_backend != nullptr) {
  69. m_backend->shutdown();
  70. m_backend->deleteLater();
  71. m_backend = nullptr;
  72. }
  73. m_tracks.clear();
  74. m_channelCount = 0;
  75. m_initialized = false;
  76. }
  77. void MusicPlayer::registerTrack(const std::string &trackId,
  78. const std::string &filePath) {
  79. if ((QCoreApplication::instance() != nullptr) &&
  80. QThread::currentThread() != QCoreApplication::instance()->thread()) {
  81. QMetaObject::invokeMethod(
  82. this, [this, trackId, filePath]() { registerTrack(trackId, filePath); },
  83. Qt::QueuedConnection);
  84. return;
  85. }
  86. ensureOnGuiThread("MusicPlayer::registerTrack");
  87. QFileInfo const fi(QString::fromStdString(filePath));
  88. if (!fi.exists()) {
  89. qWarning() << "MusicPlayer: Missing asset"
  90. << QString::fromStdString(trackId) << "->"
  91. << fi.absoluteFilePath();
  92. return;
  93. }
  94. m_tracks[trackId] = fi.absoluteFilePath();
  95. if (m_backend != nullptr) {
  96. if (!m_backend->predecode(QString::fromStdString(trackId),
  97. fi.absoluteFilePath())) {
  98. qWarning() << "MusicPlayer: predecode failed for"
  99. << fi.absoluteFilePath();
  100. } else {
  101. qDebug() << "MusicPlayer: predecoded" << fi.absoluteFilePath();
  102. }
  103. }
  104. }
  105. void MusicPlayer::play(const std::string &id, float v, bool loop) {
  106. play(id, v, loop, m_defaultChannel, AudioConstants::DEFAULT_FADE_IN_MS);
  107. }
  108. void MusicPlayer::stop() {
  109. stop(m_defaultChannel, AudioConstants::DEFAULT_FADE_OUT_MS);
  110. }
  111. void MusicPlayer::pause() { pause(m_defaultChannel); }
  112. void MusicPlayer::resume() { resume(m_defaultChannel); }
  113. void MusicPlayer::setVolume(float v) {
  114. setVolume(m_defaultChannel, v, AudioConstants::NO_FADE_MS);
  115. }
  116. auto MusicPlayer::play(const std::string &id, float vol, bool loop, int channel,
  117. int fadeMs) -> int {
  118. if (!m_initialized || (m_backend == nullptr)) {
  119. qWarning() << "MusicPlayer not initialized";
  120. return -1;
  121. }
  122. int result = -1;
  123. if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
  124. QMetaObject::invokeMethod(
  125. this,
  126. [this, id, vol, loop, channel, fadeMs, &result]() mutable {
  127. int const ch = channel < 0 ? findFreeChannel()
  128. : std::min(channel, m_channelCount - 1);
  129. play_gui(id, vol, loop, ch, fadeMs);
  130. result = ch;
  131. },
  132. Qt::BlockingQueuedConnection);
  133. return result;
  134. }
  135. int const ch =
  136. channel < 0 ? findFreeChannel() : std::min(channel, m_channelCount - 1);
  137. play_gui(id, vol, loop, ch, fadeMs);
  138. return ch;
  139. }
  140. void MusicPlayer::stop(int ch, int ms) {
  141. if (!m_initialized || (m_backend == nullptr)) {
  142. return;
  143. }
  144. if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
  145. QMetaObject::invokeMethod(
  146. this, [this, ch, ms]() { stop_gui(ch, ms); }, Qt::QueuedConnection);
  147. return;
  148. }
  149. stop_gui(ch, ms);
  150. }
  151. void MusicPlayer::pause(int ch) {
  152. if (!m_initialized || (m_backend == nullptr)) {
  153. return;
  154. }
  155. if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
  156. QMetaObject::invokeMethod(
  157. this, [this, ch]() { pause_gui(ch); }, Qt::QueuedConnection);
  158. return;
  159. }
  160. pause_gui(ch);
  161. }
  162. void MusicPlayer::resume(int ch) {
  163. if (!m_initialized || (m_backend == nullptr)) {
  164. return;
  165. }
  166. if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
  167. QMetaObject::invokeMethod(
  168. this, [this, ch]() { resume_gui(ch); }, Qt::QueuedConnection);
  169. return;
  170. }
  171. resume_gui(ch);
  172. }
  173. void MusicPlayer::setVolume(int ch, float v, int ms) {
  174. if (!m_initialized || (m_backend == nullptr)) {
  175. return;
  176. }
  177. if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
  178. QMetaObject::invokeMethod(
  179. this, [this, ch, v, ms]() { setVolume_gui(ch, v, ms); },
  180. Qt::QueuedConnection);
  181. return;
  182. }
  183. setVolume_gui(ch, v, ms);
  184. }
  185. void MusicPlayer::stopAll(int ms) {
  186. if (!m_initialized || (m_backend == nullptr)) {
  187. return;
  188. }
  189. if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
  190. QMetaObject::invokeMethod(
  191. this, [this, ms]() { stopAll_gui(ms); }, Qt::QueuedConnection);
  192. return;
  193. }
  194. stopAll_gui(ms);
  195. }
  196. void MusicPlayer::setMasterVolume(float v, int ms) {
  197. if (!m_initialized || (m_backend == nullptr)) {
  198. return;
  199. }
  200. if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
  201. QMetaObject::invokeMethod(
  202. this, [this, v, ms]() { setMasterVolume_gui(v, ms); },
  203. Qt::QueuedConnection);
  204. return;
  205. }
  206. setMasterVolume_gui(v, ms);
  207. }
  208. auto MusicPlayer::isPlaying() const -> bool {
  209. return (m_backend != nullptr) && m_backend->any_channel_playing();
  210. }
  211. auto MusicPlayer::isPlaying(int ch) const -> bool {
  212. return (m_backend != nullptr) && m_backend->channel_playing(ch);
  213. }
  214. void MusicPlayer::ensureOnGuiThread(const char *where) {
  215. requireGuiThread(where);
  216. }
  217. auto MusicPlayer::findFreeChannel() const -> int {
  218. if (m_backend == nullptr) {
  219. return 0;
  220. }
  221. for (int i = 0; i < m_channelCount; ++i) {
  222. if (!m_backend->channel_playing(i)) {
  223. return i;
  224. }
  225. }
  226. return 0;
  227. }
  228. void MusicPlayer::play_gui(const std::string &id, float vol, bool loop, int ch,
  229. int fadeMs) {
  230. if (m_backend == nullptr) {
  231. return;
  232. }
  233. auto it = m_tracks.find(id);
  234. if (it == m_tracks.end()) {
  235. qWarning() << "Unknown trackId:" << QString::fromStdString(id);
  236. return;
  237. }
  238. m_backend->play(ch, QString::fromStdString(id), vol, loop, fadeMs);
  239. }
  240. void MusicPlayer::stop_gui(int ch, int ms) { m_backend->stop(ch, ms); }
  241. void MusicPlayer::pause_gui(int ch) { m_backend->pause(ch); }
  242. void MusicPlayer::resume_gui(int ch) { m_backend->resume(ch); }
  243. void MusicPlayer::setVolume_gui(int ch, float v, int ms) {
  244. m_backend->set_volume(ch, v, ms);
  245. }
  246. void MusicPlayer::setMasterVolume_gui(float v, int ms) {
  247. m_backend->set_master_volume(v, ms);
  248. }
  249. void MusicPlayer::stopAll_gui(int ms) { m_backend->stop_all(ms); }