diff --git a/Telegram/SourceFiles/api/api_ringtones.cpp b/Telegram/SourceFiles/api/api_ringtones.cpp index c44cb0bdb..49e4b0bc9 100644 --- a/Telegram/SourceFiles/api/api_ringtones.cpp +++ b/Telegram/SourceFiles/api/api_ringtones.cpp @@ -11,7 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/random.h" #include "base/unixtime.h" #include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_session.h" +#include "data/notify/data_notify_settings.h" #include "main/main_session.h" #include "storage/file_upload.h" #include "storage/localimageloader.h" @@ -83,7 +85,7 @@ void Ringtones::upload( filemime, content); - const auto uploadedData = UploadedData{ filename, filemime }; + const auto uploadedData = UploadedData{ filename, filemime, content }; const auto fakeId = FullMsgId( _session->userPeerId(), _session->data().nextLocalMessageId()); @@ -111,9 +113,12 @@ void Ringtones::ready(const FullMsgId &msgId, const MTPInputFile &file) { file, MTP_string(uploadedData.filename), MTP_string(uploadedData.filemime) - )).done([=](const MTPDocument &result) { + )).done([=, content = uploadedData.content](const MTPDocument &result) { const auto document = _session->data().processDocument(result); _list.documents.insert(_list.documents.begin(), document->id); + const auto media = document->createMediaView(); + media->setBytes(content); + document->owner().notifySettings().cacheSound(document); _uploadDones.fire_copy(document->id); }).fail([=](const MTP::Error &error) { _uploadFails.fire_copy(error.type()); @@ -134,6 +139,7 @@ void Ringtones::requestList() { _list.documents.reserve(data.vringtones().v.size()); for (const auto &d : data.vringtones().v) { const auto document = _session->data().processDocument(d); + document->forceToCache(true); _list.documents.emplace_back(document->id); } requestList(); diff --git a/Telegram/SourceFiles/api/api_ringtones.h b/Telegram/SourceFiles/api/api_ringtones.h index 8b903ca1d..f27978f6c 100644 --- a/Telegram/SourceFiles/api/api_ringtones.h +++ b/Telegram/SourceFiles/api/api_ringtones.h @@ -42,6 +42,7 @@ private: struct UploadedData { QString filename; QString filemime; + QByteArray content; }; void ready(const FullMsgId &msgId, const MTPInputFile &file); diff --git a/Telegram/SourceFiles/boxes/ringtones_box.cpp b/Telegram/SourceFiles/boxes/ringtones_box.cpp index 40e0c3eb9..6155152ee 100644 --- a/Telegram/SourceFiles/boxes/ringtones_box.cpp +++ b/Telegram/SourceFiles/boxes/ringtones_box.cpp @@ -13,15 +13,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/call_delayed.h" #include "base/event_filter.h" #include "base/unixtime.h" +#include "base/timer_rpl.h" #include "core/file_utilities.h" #include "core/mime_type.h" +#include "core/application.h" #include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_document_resolver.h" #include "data/data_peer.h" #include "data/data_session.h" #include "data/notify/data_notify_settings.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "media/audio/media_audio.h" #include "settings/settings_common.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/buttons.h" @@ -40,6 +44,50 @@ namespace { constexpr auto kDefaultValue = -1; constexpr auto kNoSoundValue = -2; +constexpr auto kNoDetachTimeout = crl::time(250); + +class AudioCreator final { +public: + AudioCreator(); + AudioCreator(AudioCreator &&other); + AudioCreator &operator=(AudioCreator &&other); + ~AudioCreator(); + +private: + rpl::lifetime _lifetime; + bool _attached = false; + +}; + +AudioCreator::AudioCreator() +: _attached(true) { + crl::async([] { + QMutexLocker lock(Media::Player::internal::audioPlayerMutex()); + Media::Audio::AttachToDevice(); + }); + base::timer_each( + kNoDetachTimeout + ) | rpl::start_with_next([=] { + Media::Audio::StopDetachIfNotUsedSafe(); + }, _lifetime); +} + +AudioCreator::AudioCreator(AudioCreator &&other) +: _lifetime(base::take(other._lifetime)) +, _attached(base::take(other._attached)) { +} + +AudioCreator &AudioCreator::operator=(AudioCreator &&other) { + _lifetime = base::take(other._lifetime); + _attached = base::take(other._attached); + return *this; +} + +AudioCreator::~AudioCreator() { + if (_attached) { + Media::Audio::ScheduleDetachIfNotUsedSafe(); + } +} } // namespace @@ -79,8 +127,9 @@ void RingtonesBox( padding.setTop(padding.bottom()); struct State { + AudioCreator creator; std::shared_ptr group; - std::vector documentIds; + std::vector> medias; Data::NotifySound chosen; base::unique_qptr menu; QPointer defaultButton; @@ -113,6 +162,9 @@ void RingtonesBox( } if (value == kDefaultValue) { state->defaultButton = button; + button->setClickedCallback([=] { + Core::App().notifications().playSound(session, 0); + }); } if (value < 0) { return; @@ -120,6 +172,14 @@ void RingtonesBox( while (state->buttons.size() <= value) { state->buttons.push_back(nullptr); } + button->setClickedCallback([=] { + const auto media = state->medias[value].get(); + if (media->loaded()) { + Core::App().notifications().playSound( + session, + media->owner()->id); + } + }); base::install_event_filter(button, [=](not_null e) { if (e->type() != QEvent::ContextMenu || state->menu) { return base::EventFilterResult::Continue; @@ -128,7 +188,7 @@ void RingtonesBox( button, st::popupMenuWithIcons); auto callback = [=] { - const auto id = state->documentIds[value]; + const auto id = state->medias[value]->owner()->id; session->api().ringtones().remove(id); }; state->menu->addAction( @@ -172,7 +232,7 @@ void RingtonesBox( object_ptr(container)); const auto rebuild = [=] { - state->documentIds.clear(); + const auto old = base::take(state->medias); auto value = 0; while (custom->count()) { delete custom->widgetAt(0); @@ -183,7 +243,8 @@ void RingtonesBox( const auto document = session->data().document(id); const auto text = ExtractRingtoneName(document); addToGroup(custom, value++, text, chosen); - state->documentIds.push_back(id); + state->medias.push_back(document->createMediaView()); + document->owner().notifySettings().cacheSound(document); } custom->resizeToWidth(container->width()); @@ -260,7 +321,7 @@ void RingtonesBox( ? Data::NotifySound() : (value == kNoSoundValue) ? Data::NotifySound{ .none = true } - : Data::NotifySound{ .id = state->documentIds[value] }; + : Data::NotifySound{ .id = state->medias[value]->owner()->id }; save(sound); box->closeBox(); }); diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp index 49ea4a973..3664e40ae 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp @@ -163,24 +163,16 @@ void NotifySettings::updateLocal(not_null peer) { } if (const auto sound = peer->notifySound(); sound && sound->id) { - const auto cache = [=](DocumentId id) { - if (const auto document = _owner->document(id)) { - const auto view = document->createMediaView(); - _ringtones.views.emplace(id, view); - document->forceToCache(true); - document->save(Data::FileOriginRingtones(), QString()); - } - }; if (const auto doc = _owner->document(sound->id); !doc->isNull()) { - cache(sound->id); + cacheSound(doc); } else { _ringtones.pendingIds.push_back(sound->id); if (!_ringtones.pendingLifetime) { // Not requested yet. _owner->session().api().ringtones().listUpdates( ) | rpl::start_with_next([=] { - for (const auto &id : base::take(_ringtones.pendingIds)) { - cache(id); + for (const auto id : base::take(_ringtones.pendingIds)) { + cacheSound(id); } _ringtones.pendingLifetime.destroy(); }, _ringtones.pendingLifetime); @@ -190,6 +182,20 @@ void NotifySettings::updateLocal(not_null peer) { } } +void NotifySettings::cacheSound(DocumentId id) { + cacheSound(_owner->document(id)); +} + +void NotifySettings::cacheSound(not_null document) { + if (document->isNull()) { + return; + } + const auto view = document->createMediaView(); + _ringtones.views.emplace(document->id, view); + document->forceToCache(true); + document->save(Data::FileOriginRingtones(), QString()); +} + void NotifySettings::updateLocal(DefaultNotify type) { defaultValue(type).updates.fire({}); diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.h b/Telegram/SourceFiles/data/notify/data_notify_settings.h index 3f2dc1068..2d1f792d7 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.h +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.h @@ -39,7 +39,10 @@ public: std::optional sound = std::nullopt); void resetToDefault(not_null peer); - std::shared_ptr lookupRingtone(DocumentId id) const; + void cacheSound(DocumentId id); + void cacheSound(not_null document); + [[nodiscard]] std::shared_ptr lookupRingtone( + DocumentId id) const; [[nodiscard]] rpl::producer<> defaultUpdates(DefaultNotify type) const; [[nodiscard]] rpl::producer<> defaultUpdates( diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index cfb5258e3..b83fc2b28 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -510,7 +510,7 @@ void System::showNext() { } if (settings.soundNotify() && !_manager->skipAudio()) { const auto track = lookupSound( - alertPeer, + &alertPeer->owner(), alertPeer->owner().notifySettings().sound(alertPeer).id); track->playOnce(); Media::Player::mixer()->suppressAll(track->getLengthMs()); @@ -699,7 +699,7 @@ void System::showNext() { } not_null System::lookupSound( - not_null peer, + not_null owner, DocumentId id) { if (!id) { ensureSoundCreated(); @@ -709,7 +709,7 @@ not_null System::lookupSound( if (i != end(_customSoundTracks)) { return i->second.get(); } - const auto ¬ifySettings = peer->owner().notifySettings(); + const auto ¬ifySettings = owner->notifySettings(); const auto custom = notifySettings.lookupRingtone(id); if (custom && !custom->bytes().isEmpty()) { const auto j = _customSoundTracks.emplace( @@ -747,6 +747,10 @@ void System::notifySettingsChanged(ChangeType type) { return _settingsChanged.fire(std::move(type)); } +void System::playSound(not_null session, DocumentId id) { + lookupSound(&session->data(), id)->playOnce(); +} + Manager::DisplayOptions Manager::getNotificationOptions( HistoryItem *item, ItemNotificationType type) const { diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h index 283dc221e..09f0ae419 100644 --- a/Telegram/SourceFiles/window/notifications_manager.h +++ b/Telegram/SourceFiles/window/notifications_manager.h @@ -15,6 +15,7 @@ struct ItemNotification; enum class ItemNotificationType; namespace Data { +class Session; class CloudImageView; } // namespace Data @@ -99,6 +100,8 @@ public: [[nodiscard]] rpl::producer settingsChanged() const; void notifySettingsChanged(ChangeType type); + void playSound(not_null session, DocumentId id); + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -159,7 +162,7 @@ private: void showGrouped(); void ensureSoundCreated(); [[nodiscard]] not_null lookupSound( - not_null peer, + not_null owner, DocumentId id); base::flat_map<