music.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #include "music.h"
  2. #include <QAudioOutput>
  3. #include <QCoreApplication>
  4. #include <QMediaPlayer>
  5. #include <QMetaObject>
  6. #include <QThread>
  7. #include <QTimer>
  8. #include <QUrl>
  9. Music::Music(const std::string &file_path)
  10. : file_path(file_path), loaded(false), audio_output(nullptr),
  11. main_thread(nullptr), playing(false), marked_for_deletion(false) {
  12. if (!QCoreApplication::instance()) {
  13. return;
  14. }
  15. main_thread = QCoreApplication::instance()->thread();
  16. player = new QMediaPlayer();
  17. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  18. audio_output = new QAudioOutput(player);
  19. player->setAudioOutput(audio_output);
  20. QObject::connect(player, &QMediaPlayer::errorOccurred,
  21. [file_path = this->file_path](QMediaPlayer::Error error,
  22. const QString &desc) {
  23. qWarning() << "QMediaPlayer error for"
  24. << QString::fromStdString(file_path)
  25. << "- Error code:" << static_cast<int>(error)
  26. << "Message:" << desc;
  27. });
  28. QObject::connect(
  29. player, &QMediaPlayer::mediaStatusChanged,
  30. [file_path = this->file_path, this](QMediaPlayer::MediaStatus status) {
  31. qDebug() << "Media status for" << QString::fromStdString(file_path)
  32. << ":" << static_cast<int>(status);
  33. if (status == QMediaPlayer::EndOfMedia) {
  34. playing = false;
  35. }
  36. });
  37. QObject::connect(
  38. player, &QMediaPlayer::playbackStateChanged,
  39. [file_path = this->file_path](QMediaPlayer::PlaybackState state) {
  40. qDebug() << "Playback state for" << QString::fromStdString(file_path)
  41. << ":" << static_cast<int>(state);
  42. });
  43. player->setSource(QUrl::fromLocalFile(QString::fromStdString(file_path)));
  44. loaded = (player->error() == QMediaPlayer::NoError);
  45. #else
  46. player->setMedia(QUrl::fromLocalFile(QString::fromStdString(file_path)));
  47. loaded = (player->mediaStatus() != QMediaPlayer::InvalidMedia &&
  48. player->mediaStatus() != QMediaPlayer::NoMedia);
  49. #endif
  50. }
  51. Music::~Music() { cleanup_player(); }
  52. void Music::cleanup_player() {
  53. if (!player || marked_for_deletion) {
  54. return;
  55. }
  56. marked_for_deletion = true;
  57. QMediaPlayer *raw_player = player.data();
  58. if (!raw_player) {
  59. return;
  60. }
  61. if (QCoreApplication::instance() && main_thread) {
  62. raw_player->deleteLater();
  63. }
  64. }
  65. bool Music::is_loaded() const { return loaded && !marked_for_deletion; }
  66. void Music::play(float volume, bool loop) {
  67. if (!player || !loaded || marked_for_deletion) {
  68. return;
  69. }
  70. QPointer<QMediaPlayer> player_ptr = player;
  71. QAudioOutput *output = audio_output;
  72. if (!player_ptr || !QCoreApplication::instance()) {
  73. return;
  74. }
  75. QMetaObject::invokeMethod(
  76. QCoreApplication::instance(),
  77. [player_ptr, output, volume, loop, this]() {
  78. if (!player_ptr) {
  79. return;
  80. }
  81. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  82. bool is_currently_playing =
  83. (player_ptr->playbackState() == QMediaPlayer::PlayingState);
  84. if (output) {
  85. output->setVolume(volume);
  86. }
  87. player_ptr->setLoops(loop ? QMediaPlayer::Infinite : 1);
  88. if (!is_currently_playing) {
  89. qDebug() << "Starting playback for"
  90. << QString::fromStdString(file_path);
  91. playing = true;
  92. player_ptr->play();
  93. } else {
  94. qDebug() << "Already playing" << QString::fromStdString(file_path)
  95. << "- updating volume only";
  96. }
  97. #else
  98. player_ptr->setVolume(static_cast<int>(volume * 100));
  99. playing = true;
  100. player_ptr->play();
  101. #endif
  102. },
  103. Qt::QueuedConnection);
  104. }
  105. void Music::stop() {
  106. if (!player || marked_for_deletion) {
  107. return;
  108. }
  109. playing = false;
  110. QPointer<QMediaPlayer> player_ptr = player;
  111. if (!player_ptr || !QCoreApplication::instance()) {
  112. return;
  113. }
  114. QMetaObject::invokeMethod(
  115. QCoreApplication::instance(),
  116. [player_ptr]() {
  117. if (player_ptr) {
  118. player_ptr->stop();
  119. }
  120. },
  121. Qt::QueuedConnection);
  122. }
  123. void Music::pause() {
  124. if (!player || marked_for_deletion) {
  125. return;
  126. }
  127. QPointer<QMediaPlayer> player_ptr = player;
  128. if (!player_ptr || !QCoreApplication::instance()) {
  129. return;
  130. }
  131. QMetaObject::invokeMethod(
  132. QCoreApplication::instance(),
  133. [player_ptr]() {
  134. if (player_ptr) {
  135. player_ptr->pause();
  136. }
  137. },
  138. Qt::QueuedConnection);
  139. }
  140. void Music::resume() {
  141. if (!player || marked_for_deletion) {
  142. return;
  143. }
  144. QPointer<QMediaPlayer> player_ptr = player;
  145. if (!player_ptr || !QCoreApplication::instance()) {
  146. return;
  147. }
  148. QMetaObject::invokeMethod(
  149. QCoreApplication::instance(),
  150. [player_ptr]() {
  151. if (!player_ptr) {
  152. return;
  153. }
  154. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  155. if (player_ptr->playbackState() == QMediaPlayer::PausedState) {
  156. player_ptr->play();
  157. }
  158. #else
  159. if (player_ptr->state() == QMediaPlayer::PausedState) {
  160. player_ptr->play();
  161. }
  162. #endif
  163. },
  164. Qt::QueuedConnection);
  165. }
  166. void Music::set_volume(float volume) {
  167. if (!player || marked_for_deletion) {
  168. return;
  169. }
  170. QPointer<QMediaPlayer> player_ptr = player;
  171. if (!player_ptr || !QCoreApplication::instance()) {
  172. return;
  173. }
  174. QMetaObject::invokeMethod(
  175. QCoreApplication::instance(),
  176. [player_ptr, volume]() {
  177. if (!player_ptr) {
  178. return;
  179. }
  180. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  181. if (player_ptr->audioOutput()) {
  182. player_ptr->audioOutput()->setVolume(volume);
  183. }
  184. #else
  185. player_ptr->setVolume(static_cast<int>(volume * 100));
  186. #endif
  187. },
  188. Qt::QueuedConnection);
  189. }
  190. void Music::fade_out() {
  191. static constexpr int FADE_OUT_DELAY_MS = 50;
  192. if (!player || marked_for_deletion) {
  193. return;
  194. }
  195. QPointer<QMediaPlayer> player_ptr = player;
  196. if (!player_ptr || !QCoreApplication::instance()) {
  197. return;
  198. }
  199. QMetaObject::invokeMethod(
  200. QCoreApplication::instance(),
  201. [player_ptr, this]() {
  202. if (!player_ptr) {
  203. return;
  204. }
  205. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  206. if (player_ptr->audioOutput()) {
  207. player_ptr->audioOutput()->setVolume(0.0F);
  208. }
  209. QTimer::singleShot(FADE_OUT_DELAY_MS, [player_ptr, this]() {
  210. if (player_ptr &&
  211. player_ptr->playbackState() == QMediaPlayer::PlayingState) {
  212. qDebug() << "Fading out and pausing"
  213. << QString::fromStdString(file_path);
  214. player_ptr->pause();
  215. playing = false;
  216. }
  217. });
  218. #else
  219. player_ptr->setVolume(0);
  220. player_ptr->pause();
  221. playing = false;
  222. #endif
  223. },
  224. Qt::QueuedConnection);
  225. }