system_media_controls_manager.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "media/system_media_controls_manager.h"
  8. #include "media/audio/media_audio.h"
  9. #include "base/platform/base_platform_system_media_controls.h"
  10. #include "core/application.h"
  11. #include "core/core_settings.h"
  12. #include "data/data_document.h"
  13. #include "data/data_document_media.h"
  14. #include "data/data_file_origin.h"
  15. #include "main/main_account.h"
  16. #include "main/main_session.h"
  17. #include "media/audio/media_audio.h"
  18. #include "media/streaming/media_streaming_instance.h"
  19. #include "media/streaming/media_streaming_player.h"
  20. #include "ui/text/format_song_document_name.h"
  21. namespace Media {
  22. namespace {
  23. [[nodiscard]] auto RepeatModeToLoopStatus(Media::RepeatMode mode) {
  24. using Mode = Media::RepeatMode;
  25. using Status = base::Platform::SystemMediaControls::LoopStatus;
  26. switch (mode) {
  27. case Mode::None: return Status::None;
  28. case Mode::One: return Status::Track;
  29. case Mode::All: return Status::Playlist;
  30. }
  31. Unexpected("RepeatModeToLoopStatus in SystemMediaControlsManager");
  32. }
  33. } // namespace
  34. bool SystemMediaControlsManager::Supported() {
  35. return base::Platform::SystemMediaControls::Supported();
  36. }
  37. SystemMediaControlsManager::SystemMediaControlsManager()
  38. : _controls(std::make_unique<base::Platform::SystemMediaControls>()) {
  39. using PlaybackStatus
  40. = base::Platform::SystemMediaControls::PlaybackStatus;
  41. using Command = base::Platform::SystemMediaControls::Command;
  42. _controls->setApplicationName(AppName.utf16());
  43. const auto inited = _controls->init();
  44. if (!inited) {
  45. LOG(("SystemMediaControlsManager failed to init."));
  46. return;
  47. }
  48. const auto type = AudioMsgId::Type::Song;
  49. using TrackState = Media::Player::TrackState;
  50. const auto mediaPlayer = Media::Player::instance();
  51. auto trackFilter = rpl::filter([=](const TrackState &state) {
  52. return (state.id.type() == type);
  53. });
  54. mediaPlayer->updatedNotifier(
  55. ) | trackFilter | rpl::map([=](const TrackState &state) {
  56. using namespace Media::Player;
  57. if (_streamed) {
  58. const auto &player = _streamed->player();
  59. if (player.buffering() || !player.playing()) {
  60. return PlaybackStatus::Paused;
  61. }
  62. }
  63. if (IsStoppedOrStopping(state.state)) {
  64. return PlaybackStatus::Stopped;
  65. } else if (IsPausedOrPausing(state.state)) {
  66. return PlaybackStatus::Paused;
  67. }
  68. return PlaybackStatus::Playing;
  69. }) | rpl::distinct_until_changed(
  70. ) | rpl::start_with_next([=](PlaybackStatus status) {
  71. _controls->setPlaybackStatus(status);
  72. }, _lifetime);
  73. rpl::merge(
  74. mediaPlayer->stops(type) | rpl::map_to(false),
  75. mediaPlayer->startsPlay(type) | rpl::map_to(true)
  76. ) | rpl::distinct_until_changed() | rpl::start_with_next([=](bool audio) {
  77. _controls->setEnabled(audio);
  78. if (audio) {
  79. _controls->setIsNextEnabled(mediaPlayer->nextAvailable(type));
  80. _controls->setIsPreviousEnabled(
  81. mediaPlayer->previousAvailable(type));
  82. _controls->setIsPlayPauseEnabled(true);
  83. _controls->setIsStopEnabled(true);
  84. _controls->setPlaybackStatus(PlaybackStatus::Playing);
  85. _controls->updateDisplay();
  86. } else {
  87. _cachedMediaView.clear();
  88. _streamed = nullptr;
  89. _controls->clearMetadata();
  90. }
  91. _lifetimeDownload.destroy();
  92. }, _lifetime);
  93. auto trackChanged = mediaPlayer->trackChanged(
  94. ) | rpl::filter([=](AudioMsgId::Type audioType) {
  95. return audioType == type;
  96. });
  97. auto unlocked = Core::App().passcodeLockChanges(
  98. ) | rpl::filter([=](bool locked) {
  99. return !locked && (mediaPlayer->current(type));
  100. }) | rpl::map([=] {
  101. return type;
  102. }) | rpl::before_next([=] {
  103. _controls->setEnabled(true);
  104. _controls->updateDisplay();
  105. });
  106. rpl::merge(
  107. std::move(trackChanged),
  108. std::move(unlocked)
  109. ) | rpl::start_with_next([=](AudioMsgId::Type audioType) {
  110. _lifetimeDownload.destroy();
  111. const auto current = mediaPlayer->current(audioType);
  112. if (!current) {
  113. return;
  114. }
  115. if ((_lastAudioMsgId.contextId() == current.contextId())
  116. && (_lastAudioMsgId.audio() == current.audio())
  117. && (_lastAudioMsgId.type() == current.type())) {
  118. return;
  119. }
  120. const auto document = current.audio();
  121. const auto &[title, performer] = Ui::Text::FormatSongNameFor(document)
  122. .composedName();
  123. _controls->setArtist(performer);
  124. _controls->setTitle(title);
  125. if (_controls->seekingSupported()) {
  126. const auto state = mediaPlayer->getState(audioType);
  127. _controls->setDuration(state.length);
  128. // macOS NowPlaying and Linux MPRIS update the track position
  129. // according to the rate property
  130. // while the playback status is "playing",
  131. // so we should change the track position only when
  132. // the track is changed
  133. // or when the position is changed by the user.
  134. _controls->setPosition(state.position);
  135. _streamed = std::make_unique<Media::Streaming::Instance>(
  136. document,
  137. current.contextId(),
  138. nullptr);
  139. }
  140. // Setting a thumbnail can take a long time,
  141. // so we need to update the display before that.
  142. _controls->updateDisplay();
  143. if (document && document->isSongWithCover()) {
  144. const auto view = document->createMediaView();
  145. view->thumbnailWanted(current.contextId());
  146. _cachedMediaView.push_back(view);
  147. if (const auto imagePtr = view->thumbnail()) {
  148. _controls->setThumbnail(imagePtr->original());
  149. } else {
  150. document->session().downloaderTaskFinished(
  151. ) | rpl::start_with_next([=] {
  152. if (const auto imagePtr = view->thumbnail()) {
  153. _controls->setThumbnail(imagePtr->original());
  154. _lifetimeDownload.destroy();
  155. }
  156. }, _lifetimeDownload);
  157. _controls->clearThumbnail();
  158. }
  159. } else {
  160. _controls->clearThumbnail();
  161. }
  162. _lastAudioMsgId = current;
  163. }, _lifetime);
  164. mediaPlayer->playlistChanges(
  165. type
  166. ) | rpl::start_with_next([=] {
  167. _controls->setIsNextEnabled(mediaPlayer->nextAvailable(type));
  168. _controls->setIsPreviousEnabled(mediaPlayer->previousAvailable(type));
  169. }, _lifetime);
  170. using Media::RepeatMode;
  171. using Media::OrderMode;
  172. Core::App().settings().playerRepeatModeValue(
  173. ) | rpl::start_with_next([=](RepeatMode mode) {
  174. _controls->setLoopStatus(RepeatModeToLoopStatus(mode));
  175. }, _lifetime);
  176. Core::App().settings().playerOrderModeValue(
  177. ) | rpl::start_with_next([=](OrderMode mode) {
  178. if (mode != OrderMode::Shuffle) {
  179. _lastOrderMode = mode;
  180. }
  181. _controls->setShuffle(mode == OrderMode::Shuffle);
  182. }, _lifetime);
  183. _controls->commandRequests(
  184. ) | rpl::start_with_next([=](Command command) {
  185. switch (command) {
  186. case Command::PlayPause: mediaPlayer->playPause(type); break;
  187. case Command::Play: mediaPlayer->play(type); break;
  188. case Command::Pause: mediaPlayer->pause(type); break;
  189. case Command::Next: mediaPlayer->next(type); break;
  190. case Command::Previous: mediaPlayer->previous(type); break;
  191. case Command::Stop: mediaPlayer->stop(type); break;
  192. case Command::Raise: Core::App().activate(); break;
  193. case Command::LoopNone: {
  194. Core::App().settings().setPlayerRepeatMode(RepeatMode::None);
  195. Core::App().saveSettingsDelayed();
  196. break;
  197. }
  198. case Command::LoopTrack: {
  199. Core::App().settings().setPlayerRepeatMode(RepeatMode::One);
  200. Core::App().saveSettingsDelayed();
  201. break;
  202. }
  203. case Command::LoopPlaylist: {
  204. Core::App().settings().setPlayerRepeatMode(RepeatMode::All);
  205. Core::App().saveSettingsDelayed();
  206. break;
  207. }
  208. case Command::Shuffle: {
  209. const auto current = Core::App().settings().playerOrderMode();
  210. Core::App().settings().setPlayerOrderMode((current == OrderMode::Shuffle)
  211. ? _lastOrderMode
  212. : OrderMode::Shuffle);
  213. Core::App().saveSettingsDelayed();
  214. break;
  215. }
  216. case Command::Quit: {
  217. Media::Player::instance()->stopAndClose();
  218. break;
  219. }
  220. }
  221. }, _lifetime);
  222. if (_controls->seekingSupported()) {
  223. mediaPlayer->seekingChanges(
  224. type
  225. ) | rpl::filter([](Media::Player::Instance::Seeking seeking) {
  226. return (seeking == Media::Player::Instance::Seeking::Finish);
  227. }) | rpl::map([=] {
  228. return mediaPlayer->getState(type).position;
  229. }) | rpl::distinct_until_changed(
  230. ) | rpl::start_with_next([=](int position) {
  231. _controls->setPosition(position);
  232. _controls->updateDisplay();
  233. }, _lifetime);
  234. _controls->seekRequests(
  235. ) | rpl::start_with_next([=](float64 progress) {
  236. mediaPlayer->finishSeeking(type, progress);
  237. }, _lifetime);
  238. _controls->updatePositionRequests(
  239. ) | rpl::start_with_next([=] {
  240. _controls->setPosition(mediaPlayer->getState(type).position);
  241. }, _lifetime);
  242. }
  243. Core::App().passcodeLockValue(
  244. ) | rpl::filter([=](bool locked) {
  245. return locked && Core::App().maybePrimarySession();
  246. }) | rpl::start_with_next([=] {
  247. _controls->setEnabled(false);
  248. }, _lifetime);
  249. if (_controls->volumeSupported()) {
  250. rpl::single(
  251. Core::App().settings().songVolume()
  252. ) | rpl::then(
  253. Core::App().settings().songVolumeChanges()
  254. ) | rpl::start_with_next([=](float64 volume) {
  255. _controls->setVolume(volume);
  256. }, _lifetime);
  257. _controls->volumeChangeRequests(
  258. ) | rpl::start_with_next([](float64 volume) {
  259. Player::mixer()->setSongVolume(volume);
  260. if (volume > 0) {
  261. Core::App().settings().setRememberedSongVolume(volume);
  262. }
  263. Core::App().settings().setSongVolume(volume);
  264. }, _lifetime);
  265. }
  266. }
  267. SystemMediaControlsManager::~SystemMediaControlsManager() = default;
  268. } // namespace Media