diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c0d7cd9ae..aff3af8c2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3906,6 +3906,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mediaview_downloads" = "Downloads"; "lng_mediaview_playback_speed" = "Playback speed: {speed}"; "lng_mediaview_rotate_video" = "Rotate video"; +"lng_mediaview_quality_auto" = "Auto"; "lng_theme_preview_title" = "Theme Preview"; "lng_theme_preview_generating" = "Generating color theme preview..."; diff --git a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp index 830dc13bd..ff486ead9 100644 --- a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp @@ -534,15 +534,16 @@ void PeerShortInfoCover::handleStreamingUpdate( v::match(update.data, [&](Information &update) { streamingReady(std::move(update)); - }, [&](const PreloadedVideo &update) { - }, [&](const UpdateVideo &update) { + }, [](PreloadedVideo) { + }, [&](UpdateVideo update) { _videoPosition = update.position; _widget->update(); - }, [&](const PreloadedAudio &update) { - }, [&](const UpdateAudio &update) { - }, [&](const WaitingForData &update) { - }, [&](MutedByOther) { - }, [&](Finished) { + }, [](PreloadedAudio) { + }, [](UpdateAudio) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { + }, [](Finished) { }); } diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 9e1046636..e680527e2 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Core { namespace { +constexpr auto kInitialVideoQuality = 480; // Start with SD. + [[nodiscard]] WindowPosition Deserialize(const QByteArray &data) { QDataStream stream(data); stream.setVersion(QDataStream::Qt_5_1); @@ -88,6 +90,19 @@ void LogPosition(const WindowPosition &position, const QString &name) { return RecentEmojiDocument{ id, (test == '1') }; } +[[nodiscard]] quint32 SerializeVideoQuality(Media::VideoQuality quality) { + static_assert(sizeof(Media::VideoQuality) == sizeof(uint32)); + auto result = uint32(); + memcpy(&result, &quality, sizeof(quality)); + return result; +} + +[[nodiscard]] Media::VideoQuality DeserializeVideoQuality(quint32 value) { + auto result = Media::VideoQuality(); + memcpy(&result, &value, sizeof(result)); + return (result.height <= 4320) ? result : Media::VideoQuality(); +} + } // namespace [[nodiscard]] WindowPosition AdjustToScale( @@ -124,7 +139,8 @@ Settings::Settings() , _floatPlayerColumn(Window::Column::Second) , _floatPlayerCorner(RectPart::TopRight) , _dialogsWithChatWidthRatio(DefaultDialogsWidthRatio()) -, _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio()) { +, _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio()) +, _videoQuality({ .height = kInitialVideoQuality }) { } Settings::~Settings() = default; @@ -381,7 +397,7 @@ QByteArray Settings::serialize() const { << qint32(_ivZoom.current()) << qint32(_skipToastsInFocus ? 1 : 0) << qint32(_recordVideoMessages ? 1 : 0) - << qint32(_videoQuality); + << SerializeVideoQuality(_videoQuality); } Ensures(result.size() == size); @@ -508,7 +524,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 ivZoom = _ivZoom.current(); qint32 skipToastsInFocus = _skipToastsInFocus ? 1 : 0; qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0; - qint32 videoQuality = _videoQuality; + quint32 videoQuality = SerializeVideoQuality(_videoQuality); stream >> themesAccentColors; if (!stream.atEnd()) { @@ -1044,9 +1060,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _ivZoom = ivZoom; _skipToastsInFocus = (skipToastsInFocus == 1); _recordVideoMessages = (recordVideoMessages == 1); - _videoQuality = (videoQuality >= 0 && videoQuality <= 4320) - ? videoQuality - : 0; + _videoQuality = DeserializeVideoQuality(videoQuality); } QString Settings::getSoundPath(const QString &key) const { @@ -1437,7 +1451,7 @@ void Settings::resetOnLastLogout() { _ttlVoiceClickTooltipHidden = false; _ivZoom = 100; _recordVideoMessages = false; - _videoQuality = 0; + _videoQuality = {}; _recentEmojiPreload.clear(); _recentEmoji.clear(); @@ -1605,11 +1619,11 @@ void Settings::setIvZoom(int value) { _ivZoom = std::clamp(value, kMin, kMax); } -int Settings::videoQuality() const { +Media::VideoQuality Settings::videoQuality() const { return _videoQuality; } -void Settings::setVideoQuality(int value) { +void Settings::setVideoQuality(Media::VideoQuality value) { _videoQuality = value; } diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 30040ee25..3838463f3 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -929,8 +929,8 @@ public: [[nodiscard]] rpl::producer ivZoomValue() const; void setIvZoom(int value); - [[nodiscard]] int videoQuality() const; - void setVideoQuality(int quality); + [[nodiscard]] Media::VideoQuality videoQuality() const; + void setVideoQuality(Media::VideoQuality quality); [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -1069,7 +1069,7 @@ private: std::optional _weatherInCelsius; QByteArray _tonsiteStorageToken; rpl::variable _ivZoom = 100; - int _videoQuality = 0; + Media::VideoQuality _videoQuality; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index d4306b2fb..af5f95077 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -576,6 +576,13 @@ void DocumentData::setVideoQualities( --count; } qualities.erase(qualities.begin() + count, qualities.end()); + if (!qualities.empty()) { + if (const auto mine = resolveVideoQuality()) { + if (mine > qualities.front()->resolveVideoQuality()) { + qualities.insert(begin(qualities), this); + } + } + } data->qualities = std::move(qualities); } @@ -584,6 +591,40 @@ int DocumentData::resolveVideoQuality() const { return size.isEmpty() ? 0 : std::min(size.width(), size.height()); } +auto DocumentData::resolveQualities(HistoryItem *context) const +-> const std::vector> & { + static const auto empty = std::vector>(); + const auto info = video(); + const auto media = context ? context->media() : nullptr; + if (!info || !media || media->document() != this) { + return empty; + } + return media->hasQualitiesList() ? info->qualities : empty; +} + +not_null DocumentData::chooseQuality( + HistoryItem *context, + Media::VideoQuality request) { + const auto &list = resolveQualities(context); + if (list.empty() || !request.height) { + return this; + } + const auto height = int(request.height); + auto closest = this; + auto closestAbs = std::abs(height - resolveVideoQuality()); + auto closestSize = size; + for (const auto &quality : list) { + const auto abs = std::abs(height - quality->resolveVideoQuality()); + if (abs < closestAbs + || (abs == closestAbs && quality->size < closestSize)) { + closest = quality; + closestAbs = abs; + closestSize = quality->size; + } + } + return closest; +} + void DocumentData::validateLottieSticker() { if (type == FileDocument && hasMimeType(u"application/x-tgsticker"_q)) { diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index ceba0e875..df1f9eab3 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -31,11 +31,13 @@ struct Key; } // namespace Storage namespace Media { -namespace Streaming { -class Loader; -} // namespace Streaming +struct VideoQuality; } // namespace Media +namespace Media::Streaming { +class Loader; +} // namespace Media::Streaming + namespace Data { class Session; class DocumentMedia; @@ -118,6 +120,11 @@ public: void automaticLoadSettingsChanged(); void setVideoQualities(std::vector> qualities); [[nodiscard]] int resolveVideoQuality() const; + [[nodiscard]] auto resolveQualities(HistoryItem *context) const + -> const std::vector> &; + [[nodiscard]] not_null chooseQuality( + HistoryItem *context, + Media::VideoQuality request); [[nodiscard]] bool loading() const; [[nodiscard]] QString loadingFilePath() const; diff --git a/Telegram/SourceFiles/data/data_streaming.cpp b/Telegram/SourceFiles/data/data_streaming.cpp index dd3b13b38..658c83807 100644 --- a/Telegram/SourceFiles/data/data_streaming.cpp +++ b/Telegram/SourceFiles/data/data_streaming.cpp @@ -41,6 +41,43 @@ bool PruneDestroyedAndSet( return result; } +[[nodiscard]] auto LookupOtherQualities( + DocumentData *original, + not_null quality, + HistoryItem *context) +-> std::vector { + if (!original || !context) { + return {}; + } + auto qualities = original->resolveQualities(context); + if (qualities.empty()) { + return {}; + } + auto result = std::vector(); + result.reserve(qualities.size()); + for (const auto &video : qualities) { + if (video != quality) { + if (const auto height = video->resolveVideoQuality()) { + result.push_back({ + .sizeInBytes = uint32(video->size), + .height = uint32(height), + }); + } + } + } + return result; +} + +[[nodiscard]] auto LookupOtherQualities( + DocumentData *original, + not_null quality, + HistoryItem *context) +-> std::vector { + Expects(!original); + + return {}; +} + } // namespace Streaming::Streaming(not_null owner) @@ -50,7 +87,6 @@ Streaming::Streaming(not_null owner) Streaming::~Streaming() = default; - template [[nodiscard]] std::shared_ptr Streaming::sharedReader( base::flat_map, std::weak_ptr> &readers, @@ -84,10 +120,16 @@ template base::flat_map, std::weak_ptr> &documents, base::flat_map, std::weak_ptr> &readers, not_null data, + DocumentData *original, + HistoryItem *context, FileOrigin origin) { + auto otherQualities = LookupOtherQualities(original, data, context); const auto i = documents.find(data); if (i != end(documents)) { if (auto result = i->second.lock()) { + if (!otherQualities.empty()) { + result->setOtherQualities(std::move(otherQualities)); + } return result; } } @@ -95,7 +137,10 @@ template if (!reader) { return nullptr; } - auto result = std::make_shared(data, std::move(reader)); + auto result = std::make_shared( + data, + std::move(reader), + std::move(otherQualities)); if (!PruneDestroyedAndSet(documents, data, result)) { documents.emplace_or_assign(data, result); } @@ -136,7 +181,27 @@ std::shared_ptr Streaming::sharedReader( std::shared_ptr Streaming::sharedDocument( not_null document, FileOrigin origin) { - return sharedDocument(_fileDocuments, _fileReaders, document, origin); + return sharedDocument( + _fileDocuments, + _fileReaders, + document, + nullptr, + nullptr, + origin); +} + +std::shared_ptr Streaming::sharedDocument( + not_null quality, + not_null original, + HistoryItem *context, + FileOrigin origin) { + return sharedDocument( + _fileDocuments, + _fileReaders, + quality, + original, + context, + origin); } std::shared_ptr Streaming::sharedReader( @@ -149,7 +214,13 @@ std::shared_ptr Streaming::sharedReader( std::shared_ptr Streaming::sharedDocument( not_null photo, FileOrigin origin) { - return sharedDocument(_photoDocuments, _photoReaders, photo, origin); + return sharedDocument( + _photoDocuments, + _photoReaders, + photo, + nullptr, + nullptr, + origin); } void Streaming::keepAlive(not_null document) { diff --git a/Telegram/SourceFiles/data/data_streaming.h b/Telegram/SourceFiles/data/data_streaming.h index 14e863f4a..51a6f1854 100644 --- a/Telegram/SourceFiles/data/data_streaming.h +++ b/Telegram/SourceFiles/data/data_streaming.h @@ -12,12 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class PhotoData; class DocumentData; -namespace Media { -namespace Streaming { +namespace Media::Streaming { class Reader; class Document; -} // namespace Streaming -} // namespace Media +} // namespace Media::Streaming namespace Data { @@ -41,6 +39,11 @@ public: [[nodiscard]] std::shared_ptr sharedDocument( not_null document, FileOrigin origin); + [[nodiscard]] std::shared_ptr sharedDocument( + not_null quality, + not_null original, + HistoryItem *context, + FileOrigin origin); [[nodiscard]] std::shared_ptr sharedReader( not_null photo, @@ -68,6 +71,8 @@ private: base::flat_map, std::weak_ptr> &documents, base::flat_map, std::weak_ptr> &readers, not_null data, + DocumentData *original, + HistoryItem *context, FileOrigin origin); template diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index beb58b982..c406982a0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media_spoiler.h" #include "window/window_session_controller.h" #include "core/application.h" // Application::showDocument. +#include "core/core_settings.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/chat/chat_style.h" #include "ui/image/image.h" @@ -109,8 +110,10 @@ constexpr auto kMaxInlineArea = 1920 * 1080; struct Gif::Streamed { Streamed( + not_null chosen, std::shared_ptr<::Media::Streaming::Document> shared, Fn waitingCallback); + const not_null chosen; ::Media::Streaming::Instance instance; ::Media::Streaming::FrameRequest frozenRequest; QImage frozenFrame; @@ -118,9 +121,11 @@ struct Gif::Streamed { }; Gif::Streamed::Streamed( + not_null chosen, std::shared_ptr<::Media::Streaming::Document> shared, Fn waitingCallback) -: instance(std::move(shared), std::move(waitingCallback)) { +: chosen(chosen) +, instance(std::move(shared), std::move(waitingCallback)) { } [[nodiscard]] bool IsHiddenRoundMessage(not_null parent) { @@ -1848,13 +1853,21 @@ void Gif::playAnimation(bool autoplay) { } void Gif::createStreamedPlayer() { + const auto quality = Core::App().settings().videoQuality(); + const auto chosen = _data->chooseQuality(_realParent, quality); + if (_streamed && _streamed->chosen == chosen) { + return; + } auto shared = _data->owner().streaming().sharedDocument( + chosen, _data, + _realParent, _realParent->fullId()); if (!shared) { return; } setStreamed(std::make_unique( + chosen, std::move(shared), [=] { repaintStreamedContent(); })); @@ -1865,6 +1878,20 @@ void Gif::createStreamedPlayer() { handleStreamingError(std::move(error)); }, _streamed->instance.lifetime()); + _streamed->instance.switchQualityRequests( + ) | rpl::start_with_next([=](int quality) { + auto now = Core::App().settings().videoQuality(); + if (now.manual || now.height == quality) { + return; + } + Core::App().settings().setVideoQuality({ + .manual = 0, + .height = uint32(quality), + }); + Core::App().saveSettingsDelayed(); + createStreamedPlayer(); + }, _streamed->instance.lifetime()); + if (_streamed->instance.ready()) { streamingReady(base::duplicate(_streamed->instance.info())); } @@ -1912,14 +1939,15 @@ void Gif::handleStreamingUpdate(::Media::Streaming::Update &&update) { v::match(update.data, [&](Information &update) { streamingReady(std::move(update)); - }, [&](const PreloadedVideo &update) { - }, [&](const UpdateVideo &update) { + }, [](PreloadedVideo) { + }, [&](UpdateVideo) { repaintStreamedContent(); - }, [&](const PreloadedAudio &update) { - }, [&](const UpdateAudio &update) { - }, [&](const WaitingForData &update) { - }, [&](MutedByOther) { - }, [&](Finished) { + }, [](PreloadedAudio) { + }, [](UpdateAudio) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { + }, [](Finished) { }); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index ff7c4872e..3e56d773f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -979,14 +979,15 @@ void Photo::handleStreamingUpdate(::Media::Streaming::Update &&update) { v::match(update.data, [&](Information &update) { streamingReady(std::move(update)); - }, [&](const PreloadedVideo &update) { - }, [&](const UpdateVideo &update) { + }, [](PreloadedVideo) { + }, [&](UpdateVideo) { repaintStreamedContent(); - }, [&](const PreloadedAudio &update) { - }, [&](const UpdateAudio &update) { - }, [&](const WaitingForData &update) { - }, [&](MutedByOther) { - }, [&](Finished) { + }, [](PreloadedAudio) { + }, [](UpdateAudio) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { + }, [](Finished) { }); } diff --git a/Telegram/SourceFiles/media/media_common.h b/Telegram/SourceFiles/media/media_common.h index c155a4084..1f757e581 100644 --- a/Telegram/SourceFiles/media/media_common.h +++ b/Telegram/SourceFiles/media/media_common.h @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/algorithm.h" +#include + namespace Media { enum class RepeatMode { @@ -23,6 +25,18 @@ enum class OrderMode { Shuffle, }; +struct VideoQuality { + uint32 manual : 1 = 0; + uint32 height : 31 = 0; + + friend inline constexpr auto operator<=>( + VideoQuality, + VideoQuality) = default; + friend inline constexpr bool operator==( + VideoQuality, + VideoQuality) = default; +}; + inline constexpr auto kSpeedMin = 0.5; inline constexpr auto kSpeedMax = 2.5; inline constexpr auto kSpedUpDefault = 1.7; diff --git a/Telegram/SourceFiles/media/player/media_player_dropdown.cpp b/Telegram/SourceFiles/media/player/media_player_dropdown.cpp index 97ec184ce..f6ab757b0 100644 --- a/Telegram/SourceFiles/media/player/media_player_dropdown.cpp +++ b/Telegram/SourceFiles/media/player/media_player_dropdown.cpp @@ -717,7 +717,7 @@ SpeedController::SpeedController( Fn value, Fn change, std::vector qualities, - Fn quality, + Fn quality, Fn changeQuality) : WithDropdownController( button, @@ -785,9 +785,9 @@ void SpeedController::save() { _saved.fire({}); } -void SpeedController::setQuality(int quality) { +void SpeedController::setQuality(VideoQuality quality) { _quality = quality; - _changeQuality(quality); + _changeQuality(quality.manual ? quality.height : 0); } void SpeedController::fillMenu(not_null menu) { @@ -810,7 +810,8 @@ void SpeedController::fillMenu(not_null menu) { } const auto add = [&](int quality) { - const auto text = quality ? u"%1p"_q.arg(quality) : u"Original"_q; + const auto automatic = tr::lng_mediaview_quality_auto(tr::now); + const auto text = quality ? u"%1p"_q.arg(quality) : automatic; auto action = base::make_unique_q( raw, st.qualityMenu, @@ -837,9 +838,15 @@ void SpeedController::fillMenu(not_null menu) { }, check->lifetime()); check->setAttribute(Qt::WA_TransparentForMouseEvents); _quality.value( - ) | rpl::start_with_next([=](int now) { - const auto chosen = (now == quality); + ) | rpl::start_with_next([=](VideoQuality now) { + const auto chosen = now.manual + ? (now.height == quality) + : !quality; raw->action()->setEnabled(!chosen); + if (!quality) { + raw->action()->setText(automatic + + (now.manual ? QString() : u"\t%1p"_q.arg(now.height))); + } check->setVisible(chosen); }, raw->lifetime()); menu->addAction(std::move(action)); diff --git a/Telegram/SourceFiles/media/player/media_player_dropdown.h b/Telegram/SourceFiles/media/player/media_player_dropdown.h index 7975677ff..1a0e0bb08 100644 --- a/Telegram/SourceFiles/media/player/media_player_dropdown.h +++ b/Telegram/SourceFiles/media/player/media_player_dropdown.h @@ -132,7 +132,7 @@ public: Fn value, Fn change, std::vector qualities = {}, - Fn quality = nullptr, + Fn quality = nullptr, Fn changeQuality = nullptr); [[nodiscard]] rpl::producer<> saved() const; @@ -146,7 +146,7 @@ private: [[nodiscard]] float64 lastNonDefaultSpeed() const; void toggleDefault(); void setSpeed(float64 newSpeed); - void setQuality(int quality); + void setQuality(VideoQuality quality); void save(); const style::MediaSpeedButton &_st; @@ -158,9 +158,9 @@ private: rpl::event_stream<> _saved; std::vector _qualities; - Fn _lookupQuality; + Fn _lookupQuality; Fn _changeQuality; - rpl::variable _quality; + rpl::variable _quality; }; diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 127009d25..12792f370 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -1303,7 +1303,7 @@ void Instance::handleStreamingUpdate( Streaming::Update &&update) { using namespace Streaming; - v::match(update.data, [&](Information &update) { + v::match(update.data, [&](const Information &update) { if (!update.video.size.isEmpty()) { data->streamed->progress.setValueChangedCallback([=]( float64, @@ -1315,16 +1315,17 @@ void Instance::handleStreamingUpdate( requestRoundVideoResize(); } emitUpdate(data->type); - }, [&](PreloadedVideo &update) { + }, [&](PreloadedVideo) { //emitUpdate(data->type, [](AudioMsgId) { return true; }); - }, [&](UpdateVideo &update) { + }, [&](UpdateVideo) { emitUpdate(data->type); - }, [&](PreloadedAudio &update) { + }, [&](PreloadedAudio) { //emitUpdate(data->type, [](AudioMsgId) { return true; }); - }, [&](UpdateAudio &update) { + }, [&](UpdateAudio) { emitUpdate(data->type); - }, [&](WaitingForData) { - }, [&](MutedByOther) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { }, [&](Finished) { emitUpdate(data->type); if (data->streamed && data->streamed->instance.player().finished()) { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_common.h b/Telegram/SourceFiles/media/streaming/media_streaming_common.h index 86f76fea1..9f9b01c9d 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_common.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_common.h @@ -17,9 +17,6 @@ inline constexpr auto kTimeUnknown = std::numeric_limits::min(); inline constexpr auto kDurationMax = crl::time(std::numeric_limits::max()); inline constexpr auto kDurationUnavailable = std::numeric_limits::max(); -inline constexpr auto kOriginalQuality = 0; -inline constexpr auto kAutoQuality = -1; - namespace Audio { bool SupportsSpeedControl(); } // namespace Audio @@ -96,6 +93,11 @@ struct WaitingForData { bool waiting = false; }; +struct SpeedEstimate { + int bytesPerSecond = 0; + bool unreliable = false; +}; + struct MutedByOther { }; @@ -110,6 +112,7 @@ struct Update { PreloadedAudio, UpdateAudio, WaitingForData, + SpeedEstimate, MutedByOther, Finished> data; }; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp index 9e7a083a2..5ba5ff5e9 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_document.cpp @@ -29,13 +29,16 @@ constexpr auto kWaitingFastDuration = crl::time(200); constexpr auto kWaitingShowDuration = crl::time(500); constexpr auto kWaitingShowDelay = crl::time(500); constexpr auto kGoodThumbQuality = 87; +constexpr auto kSwitchQualityUpPreloadedThreshold = 4 * crl::time(1000); +constexpr auto kSwitchQualityUpSpeedMultiplier = 1.2; } // namespace Document::Document( not_null document, - std::shared_ptr reader) -: Document(std::move(reader), document, nullptr) { + std::shared_ptr reader, + std::vector otherQualities) +: Document(std::move(reader), document, {}, std::move(otherQualities)) { _player.fullInCache( ) | rpl::start_with_next([=](bool fullInCache) { _document->setLoadedInMediaCache(fullInCache); @@ -44,24 +47,27 @@ Document::Document( Document::Document( not_null photo, - std::shared_ptr reader) -: Document(std::move(reader), nullptr, photo) { + std::shared_ptr reader, + std::vector otherQualities) +: Document(std::move(reader), {}, photo, {}) { } Document::Document(std::unique_ptr loader) -: Document(std::make_shared(std::move(loader)), nullptr, nullptr) { +: Document(std::make_shared(std::move(loader)), {}, {}, {}) { } Document::Document( std::shared_ptr reader, DocumentData *document, - PhotoData *photo) + PhotoData *photo, + std::vector otherQualities) : _document(document) , _photo(photo) , _player(std::move(reader)) , _radial( - [=] { waitingCallback(); }, - st::defaultInfiniteRadialAnimation) { + [=] { waitingCallback(); }, + st::defaultInfiniteRadialAnimation) +, _otherQualities(std::move(otherQualities)) { resubscribe(); } @@ -138,20 +144,27 @@ Ui::RadialState Document::waitingState() const { return _radial.computeState(); } +rpl::producer Document::switchQualityRequests() const { + return _switchQualityRequests.events(); +} + void Document::handleUpdate(Update &&update) { v::match(update.data, [&](Information &update) { ready(std::move(update)); - }, [&](const PreloadedVideo &update) { + }, [&](PreloadedVideo update) { _info.video.state.receivedTill = update.till; - }, [&](const UpdateVideo &update) { + checkSwitchToHigherQuality(); + }, [&](UpdateVideo update) { _info.video.state.position = update.position; - }, [&](const PreloadedAudio &update) { + }, [&](PreloadedAudio update) { _info.audio.state.receivedTill = update.till; - }, [&](const UpdateAudio &update) { + }, [&](UpdateAudio update) { _info.audio.state.position = update.position; - }, [&](const WaitingForData &update) { + }, [&](WaitingForData update) { waitingChange(update.waiting); - }, [&](MutedByOther) { + }, [&](SpeedEstimate update) { + checkForQualitySwitch(update); + }, [](MutedByOther) { }, [&](Finished) { const auto finishTrack = [](TrackState &state) { state.position = state.receivedTill = state.duration; @@ -161,6 +174,76 @@ void Document::handleUpdate(Update &&update) { }); } +void Document::setOtherQualities(std::vector value) { + _otherQualities = std::move(value); + checkForQualitySwitch(_lastSpeedEstimate); +} + +void Document::checkForQualitySwitch(SpeedEstimate estimate) { + _lastSpeedEstimate = estimate; + if (!checkSwitchToHigherQuality()) { + checkSwitchToLowerQuality(); + } +} + +bool Document::checkSwitchToHigherQuality() { + if (_otherQualities.empty() + || (_info.video.state.duration == kTimeUnknown) + || (_info.video.state.duration == kDurationUnavailable) + || (_info.video.state.position == kTimeUnknown) + || (_info.video.state.receivedTill == kTimeUnknown) + || !_lastSpeedEstimate.bytesPerSecond + || _lastSpeedEstimate.unreliable + || (_info.video.state.receivedTill + < std::min( + _info.video.state.duration, + (_info.video.state.position + + kSwitchQualityUpPreloadedThreshold)))) { + return false; + } + const auto size = _player.fileSize(); + Assert(size >= 0 && size <= std::numeric_limits::max()); + auto to = QualityDescriptor{ .sizeInBytes = uint32(size) }; + const auto duration = _info.video.state.duration / 1000.; + const auto speed = _player.speed(); + const auto multiplier = speed * kSwitchQualityUpSpeedMultiplier; + for (const auto &descriptor : _otherQualities) { + const auto perSecond = descriptor.sizeInBytes / duration; + if (descriptor.sizeInBytes > to.sizeInBytes + && _lastSpeedEstimate.bytesPerSecond >= perSecond * multiplier) { + to = descriptor; + } + } + if (!to.height) { + return false; + } + _switchQualityRequests.fire_copy(to.height); + return true; +} + +bool Document::checkSwitchToLowerQuality() { + if (_otherQualities.empty() + || !_waiting + || !_radial.animating() + || !_lastSpeedEstimate.bytesPerSecond) { + return false; + } + const auto size = _player.fileSize(); + Assert(size >= 0 && size <= std::numeric_limits::max()); + auto to = QualityDescriptor(); + for (const auto &descriptor : _otherQualities) { + if (descriptor.sizeInBytes < size + && descriptor.sizeInBytes > to.sizeInBytes) { + to = descriptor; + } + } + if (!to.height) { + return false; + } + _switchQualityRequests.fire_copy(to.height); + return true; +} + void Document::handleError(Error &&error) { if (_document) { if (error == Error::NotStreamable) { @@ -195,6 +278,8 @@ void Document::waitingChange(bool waiting) { _fading.start([=] { waitingCallback(); }, _waiting ? 0. : 1., _waiting ? 1. : 0., duration); + + checkSwitchToLowerQuality(); }; if (waiting) { if (_radial.animating()) { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_document.h b/Telegram/SourceFiles/media/streaming/media_streaming_document.h index f192aebb1..bdb579593 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_document.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_document.h @@ -14,20 +14,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class DocumentData; -namespace Media { -namespace Streaming { +namespace Media::Streaming { class Instance; class Loader; +struct QualityDescriptor { + uint32 sizeInBytes = 0; + uint32 height = 0; +}; + class Document { public: Document( not_null document, - std::shared_ptr reader); + std::shared_ptr reader, + std::vector otherQualities = {}); Document( not_null photo, - std::shared_ptr reader); + std::shared_ptr reader, + std::vector otherQualities = {}); explicit Document(std::unique_ptr loader); void play(const PlaybackOptions &options); @@ -41,11 +47,15 @@ public: [[nodiscard]] float64 waitingOpacity() const; [[nodiscard]] Ui::RadialState waitingState() const; + void setOtherQualities(std::vector value); + [[nodiscard]] rpl::producer switchQualityRequests() const; + private: Document( std::shared_ptr reader, DocumentData *document, - PhotoData *photo); + PhotoData *photo, + std::vector otherQualities); friend class Instance; @@ -54,6 +64,9 @@ private: void refreshPlayerPriority(); void waitingCallback(); + void checkForQualitySwitch(SpeedEstimate estimate); + bool checkSwitchToHigherQuality(); + bool checkSwitchToLowerQuality(); void handleUpdate(Update &&update); void handleError(Error &&error); @@ -71,14 +84,15 @@ private: rpl::lifetime _subscription; - bool _waiting = false; mutable Ui::InfiniteRadialAnimation _radial; Ui::Animations::Simple _fading; base::Timer _timer; base::flat_set> _instances; + std::vector _otherQualities; + rpl::event_stream _switchQualityRequests; + SpeedEstimate _lastSpeedEstimate; + bool _waiting = false; }; - -} // namespace Streaming -} // namespace Media +} // namespace Media::Streaming diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp index 22427c2ff..9e5ee8498 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp @@ -490,6 +490,14 @@ void File::setLoaderPriority(int priority) { _reader->setLoaderPriority(priority); } +int64 File::size() const { + return _reader->size(); +} + +rpl::producer File::speedEstimate() const { + return _reader->speedEstimate(); +} + File::~File() { stop(); } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file.h b/Telegram/SourceFiles/media/streaming/media_streaming_file.h index 38af45537..c17a8d195 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file.h @@ -42,6 +42,9 @@ public: [[nodiscard]] bool isRemoteLoader() const; void setLoaderPriority(int priority); + [[nodiscard]] int64 size() const; + [[nodiscard]] rpl::producer speedEstimate() const; + ~File(); private: diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp index 898bf7678..f73efed10 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp @@ -36,6 +36,21 @@ Instance::Instance( std::move(waitingCallback)) { } +Instance::Instance( + not_null quality, + not_null original, + HistoryItem *context, + Data::FileOrigin origin, + Fn waitingCallback) +: Instance( + quality->owner().streaming().sharedDocument( + quality, + original, + context, + origin), + std::move(waitingCallback)) { +} + Instance::Instance( not_null photo, Data::FileOrigin origin, @@ -72,6 +87,10 @@ const Information &Instance::info() const { return _shared->info(); } +rpl::producer Instance::switchQualityRequests() const { + return _shared->switchQualityRequests(); +} + void Instance::play(const PlaybackOptions &options) { Expects(_shared != nullptr); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h index 126cf9db0..9a7cfd9c3 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h @@ -34,6 +34,12 @@ public: not_null document, Data::FileOrigin origin, Fn waitingCallback); + Instance( + not_null quality, + not_null original, + HistoryItem *context, + Data::FileOrigin origin, + Fn waitingCallback); Instance( not_null photo, Data::FileOrigin origin, @@ -45,6 +51,7 @@ public: [[nodiscard]] const Player &player() const; [[nodiscard]] const Information &info() const; + [[nodiscard]] rpl::producer switchQualityRequests() const; void play(const PlaybackOptions &options); void pause(); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_loader.h b/Telegram/SourceFiles/media/streaming/media_streaming_loader.h index 55845c520..f95183bab 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_loader.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_loader.h @@ -7,12 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "media/streaming/media_streaming_common.h" + namespace Storage { class StreamedFileDownloader; } // namespace Storage -namespace Media { -namespace Streaming { +namespace Media::Streaming { struct LoadedPart { int64 offset = 0; @@ -41,6 +42,8 @@ public: // Parts will be sent from the main thread. [[nodiscard]] virtual rpl::producer parts() const = 0; + [[nodiscard]] virtual auto speedEstimate() const + -> rpl::producer = 0; virtual void attachDownloader( not_null downloader) = 0; @@ -74,5 +77,4 @@ private: }; -} // namespace Streaming -} // namespace Media +} // namespace Media::Streaming diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.cpp index a85dbe775..868296d7b 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.cpp @@ -84,6 +84,10 @@ rpl::producer LoaderLocal::parts() const { return _parts.events(); } +rpl::producer LoaderLocal::speedEstimate() const { + return rpl::never(); +} + void LoaderLocal::attachDownloader( not_null downloader) { Unexpected("Downloader attached to a local streaming loader."); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.h b/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.h index 9531f6683..efb6d41bd 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_loader_local.h @@ -33,6 +33,7 @@ public: // Parts will be sent from the main thread. [[nodiscard]] rpl::producer parts() const override; + [[nodiscard]] rpl::producer speedEstimate() const override; void attachDownloader( not_null downloader) override; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.cpp index 15f881403..d6f869c58 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.cpp @@ -14,6 +14,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media { namespace Streaming { +namespace { + +constexpr auto kCheckStatsInterval = crl::time(1000); +constexpr auto kInitialStatsWait = 5 * crl::time(1000); + +} // namespace LoaderMtproto::LoaderMtproto( not_null owner, @@ -22,7 +28,8 @@ LoaderMtproto::LoaderMtproto( Data::FileOrigin origin) : DownloadMtprotoTask(owner, location, origin) , _size(size) -, _api(&api().instance()) { +, _api(&api().instance()) +, _statsTimer([=] { checkStats(); }) { } Storage::Cache::Key LoaderMtproto::baseCacheKey() const { @@ -121,12 +128,32 @@ bool LoaderMtproto::readyToRequest() const { int64 LoaderMtproto::takeNextRequestOffset() { const auto offset = _requested.take(); + Assert(offset.has_value()); + + const auto time = crl::now(); + if (!_firstRequestStart) { + _firstRequestStart = time; + } + _stats.push_back({ .start = crl::now(), .offset = *offset }); Ensures(offset.has_value()); return *offset; } bool LoaderMtproto::feedPart(int64 offset, const QByteArray &bytes) { + const auto time = crl::now(); + for (auto &entry : _stats) { + if (entry.offset == offset && entry.start < time) { + entry.end = time; + if (!_statsTimer.isActive()) { + const auto checkAt = std::max( + time + kCheckStatsInterval, + _firstRequestStart + kInitialStatsWait); + _statsTimer.callOnce(checkAt - time); + } + break; + } + } _parts.fire({ offset, bytes }); return true; } @@ -139,5 +166,56 @@ rpl::producer LoaderMtproto::parts() const { return _parts.events(); } +rpl::producer LoaderMtproto::speedEstimate() const { + return _speedEstimate.events(); +} + +void LoaderMtproto::checkStats() { + const auto time = crl::now(); + const auto from = time - kInitialStatsWait; + { // Erase all stats entries that are too old. + for (auto i = begin(_stats); i != end(_stats);) { + if (i->start >= from) { + break; + } else if (i->end && i->end < from) { + i = _stats.erase(i); + } else { + ++i; + } + } + } + if (_stats.empty()) { + return; + } + // Count duration for which at least one request was in progress. + // This is the time we should consider for download speed. + // We don't count time when no requests were in progress. + auto durationCountedTill = _stats.front().start; + auto duration = crl::time(0); + auto received = int64(0); + for (const auto &entry : _stats) { + if (entry.start > durationCountedTill) { + durationCountedTill = entry.start; + } + const auto till = entry.end ? entry.end : time; + if (till > durationCountedTill) { + duration += (till - durationCountedTill); + durationCountedTill = till; + } + if (entry.end) { + received += Storage::kDownloadPartSize; + } + } + if (duration) { + _speedEstimate.fire({ + .bytesPerSecond = int(std::clamp( + int64(received * 1000 / duration), + int64(0), + int64(64 * 1024 * 1024))), + .unreliable = (received < 3 * Storage::kDownloadPartSize), + }); + } +} + } // namespace Streaming } // namespace Media diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.h b/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.h index 93635efb1..c3a025a1e 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_loader_mtproto.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/timer.h" #include "media/streaming/media_streaming_loader.h" #include "mtproto/sender.h" #include "data/data_file_origin.h" @@ -36,12 +37,19 @@ public: // Parts will be sent from the main thread. [[nodiscard]] rpl::producer parts() const override; + [[nodiscard]] rpl::producer speedEstimate() const override; void attachDownloader( not_null downloader) override; void clearAttachedDownloader() override; private: + struct StatsEntry { + crl::time start = 0; + crl::time end = 0; + int64 offset = 0; + }; + bool readyToRequest() const override; int64 takeNextRequestOffset() override; bool feedPart(int64 offset, const QByteArray &bytes) override; @@ -50,6 +58,8 @@ private: void cancelForOffset(int64 offset); void addToQueueWithPriority(); + void checkStats(); + const int64 _size = 0; int _priority = 0; @@ -57,6 +67,11 @@ private: PriorityQueue _requested; rpl::event_stream _parts; + rpl::event_stream _speedEstimate; + + std::vector _stats; + crl::time _firstRequestStart = 0; + base::Timer _statsTimer; Storage::StreamedFileDownloader *_downloader = nullptr; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index b4133b0da..0005d1754 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -715,6 +715,10 @@ void Player::start() { _stage = Stage::Started; const auto guard = base::make_weak(&_sessionGuard); + _file->speedEstimate() | rpl::start_with_next([=](SpeedEstimate value) { + _updates.fire({ value }); + }, _sessionLifetime); + rpl::merge( _audio ? _audio->waitingForData() : nullptr, _video ? _video->waitingForData() : nullptr @@ -881,6 +885,10 @@ rpl::producer Player::fullInCache() const { return _fullInCache.events(); } +int64 Player::fileSize() const { + return _file->size(); +} + QSize Player::videoSize() const { return _information.video.size; } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.h b/Telegram/SourceFiles/media/streaming/media_streaming_player.h index 148dc7a11..0b8257dae 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.h @@ -60,6 +60,7 @@ public: [[nodiscard]] rpl::producer updates() const; [[nodiscard]] rpl::producer fullInCache() const; + [[nodiscard]] int64 fileSize() const; [[nodiscard]] QSize videoSize() const; [[nodiscard]] QImage frame( const FrameRequest &request, diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp index 95654e590..1788f6dbb 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp @@ -1099,6 +1099,10 @@ void Reader::continueDownloaderFromMainThread() { } } +rpl::producer Reader::speedEstimate() const { + return _loader->speedEstimate(); +} + void Reader::setLoaderPriority(int priority) { if (_realPriority == priority) { return; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h index 179829846..a86a55fcd 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "media/streaming/media_streaming_common.h" #include "media/streaming/media_streaming_loader.h" #include "base/bytes.h" #include "base/weak_ptr.h" @@ -78,6 +79,7 @@ public: void cancelForDownloader( not_null downloader); void continueDownloaderFromMainThread(); + [[nodiscard]] rpl::producer speedEstimate() const; ~Reader(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index ae3217b05..098eecfbd 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_overlay_raster.h" #include "media/view/media_view_overlay_opengl.h" #include "media/stories/media_stories_view.h" +#include "media/streaming/media_streaming_document.h" #include "media/streaming/media_streaming_player.h" #include "media/player/media_player_instance.h" #include "history/history.h" @@ -320,7 +321,9 @@ struct OverlayWidget::Collage { struct OverlayWidget::Streamed { Streamed( - not_null document, + not_null quality, + not_null original, + HistoryItem *context, Data::FileOrigin origin, Fn waitingCallback); Streamed( @@ -449,10 +452,12 @@ private: }; OverlayWidget::Streamed::Streamed( - not_null document, + not_null quality, + not_null original, + HistoryItem *context, Data::FileOrigin origin, Fn waitingCallback) -: instance(document, origin, std::move(waitingCallback)) { +: instance(quality, original, context, origin, std::move(waitingCallback)) { } OverlayWidget::Streamed::Streamed( @@ -2264,36 +2269,6 @@ OverlayWidget::~OverlayWidget() { _dropdown.destroy(); } -auto OverlayWidget::resolveQualities() const --> const std::vector> & { - static const auto empty = std::vector>(); - const auto video = _document ? _document->video() : nullptr; - const auto media = _message ? _message->media() : nullptr; - if (!video || !media || media->document() != _document) { - return empty; - } - return media->hasQualitiesList() ? video->qualities : empty; -} - -not_null OverlayWidget::chooseQuality() const { - Expects(_document != nullptr); - - const auto &list = resolveQualities(); - if (list.empty() || _quality == kOriginalQuality) { - return _document; - } - auto closest = _document; - auto closestAbs = std::abs(_quality - _document->resolveVideoQuality()); - for (const auto &quality : list) { - const auto abs = std::abs(_quality - quality->resolveVideoQuality()); - if (abs < closestAbs) { - closestAbs = abs; - closest = quality; - } - } - return closest; -} - void OverlayWidget::assignMediaPointer(DocumentData *document) { _savePhotoVideoWhenLoaded = SavePhotoVideo::None; _photo = nullptr; @@ -2302,7 +2277,8 @@ void OverlayWidget::assignMediaPointer(DocumentData *document) { _streamedQualityChangeFrame = QImage(); _streamedQualityChangeFinished = false; if ((_document = document)) { - _chosenQuality = chooseQuality(); + _quality = Core::App().settings().videoQuality(); + _chosenQuality = _document->chooseQuality(_message, _quality); _documentMedia = _document->createMediaView(); _documentMedia->goodThumbnailWanted(); _documentMedia->thumbnailWanted(fileOrigin()); @@ -3863,6 +3839,16 @@ bool OverlayWidget::initStreaming(const StartStreaming &startStreaming) { handleStreamingError(std::move(error)); }, _streamed->instance.lifetime()); + _streamed->instance.switchQualityRequests( + ) | rpl::filter([=](int quality) { + return !_quality.manual && _quality.height != quality; + }) | rpl::start_with_next([=](int quality) { + applyVideoQuality({ + .manual = 0, + .height = uint32(quality), + }); + }, _streamed->instance.lifetime()); + if (startStreaming.continueStreaming) { _pip = nullptr; } @@ -3992,7 +3978,12 @@ bool OverlayWidget::createStreamingObjects() { const auto callback = [=] { waitingAnimationCallback(); }; const auto video = _chosenQuality ? _chosenQuality : _document; if (video) { - _streamed = std::make_unique(video, origin, callback); + _streamed = std::make_unique( + video, + _document, + _message, + origin, + callback); } else { _streamed = std::make_unique(_photo, origin, callback); } @@ -4065,19 +4056,20 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) { v::match(update.data, [&](Information &update) { streamingReady(std::move(update)); - }, [&](const PreloadedVideo &update) { + }, [&](PreloadedVideo) { updatePlaybackState(); - }, [&](const UpdateVideo &update) { + }, [&](UpdateVideo update) { updateContentRect(); Core::App().updateNonIdle(); updatePlaybackState(); _streamedPosition = update.position; - }, [&](const PreloadedAudio &update) { + }, [&](PreloadedAudio) { updatePlaybackState(); - }, [&](const UpdateAudio &update) { + }, [&](UpdateAudio) { updatePlaybackState(); - }, [&](WaitingForData) { - }, [&](MutedByOther) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { }, [&](Finished) { updatePlaybackState(); }); @@ -4439,7 +4431,10 @@ float64 OverlayWidget::playbackControlsCurrentSpeed(bool lastNonDefault) { } std::vector OverlayWidget::playbackControlsQualities() { - const auto &list = resolveQualities(); + if (!_document) { + return {}; + } + const auto &list = _document->resolveQualities(_message); if (list.empty()) { return {}; } @@ -4451,37 +4446,53 @@ std::vector OverlayWidget::playbackControlsQualities() { return result; } -int OverlayWidget::playbackControlsCurrentQuality() { - return _chosenQuality ? _chosenQuality->resolveVideoQuality() : _quality; +VideoQuality OverlayWidget::playbackControlsCurrentQuality() { + return _chosenQuality + ? VideoQuality{ + .manual = _quality.manual, + .height = uint32(_chosenQuality->resolveVideoQuality()), + } + : _quality; } void OverlayWidget::playbackControlsQualityChanged(int quality) { - const auto now = _chosenQuality; - if (_quality != quality) { - _quality = quality; - Core::App().settings().setVideoQuality(quality); - Core::App().saveSettingsDelayed(); - if (_document) { - _chosenQuality = chooseQuality(); - if (_chosenQuality != now) { - if (_streamed && _streamed->instance.ready()) { - _streamedQualityChangeFrame = currentVideoFrameImage(); - } - if (_streamed - && (!_streamed->instance.player().active() - || _streamed->instance.player().finished())) { - _streamedQualityChangeFinished = true; - } - _streamingStartPaused = _streamedQualityChangeFinished - || (_streamed && _streamed->instance.player().paused()); - clearStreaming(); - const auto time = _streamedPosition; - const auto startStreaming = StartStreaming(false, time); - if (!canInitStreaming() || !initStreaming(startStreaming)) { - redisplayContent(); - } - } - } + applyVideoQuality({ + .manual = (quality > 0), + .height = quality ? uint32(quality) : _quality.height, + }); +} + +void OverlayWidget::applyVideoQuality(VideoQuality value) { + if (_quality == value) { + return; + } + _quality = value; + Core::App().settings().setVideoQuality(value); + Core::App().saveSettingsDelayed(); + + if (!_document) { + return; + } + const auto resolved = _document->chooseQuality(_message, _quality); + if (_chosenQuality == resolved) { + return; + } + _chosenQuality = resolved; + if (_streamed && _streamed->instance.ready()) { + _streamedQualityChangeFrame = currentVideoFrameImage(); + } + if (_streamed + && (!_streamed->instance.player().active() + || _streamed->instance.player().finished())) { + _streamedQualityChangeFinished = true; + } + _streamingStartPaused = _streamedQualityChangeFinished + || (_streamed && _streamed->instance.player().paused()); + clearStreaming(); + const auto time = _streamedPosition; + const auto startStreaming = StartStreaming(false, time); + if (!canInitStreaming() || !initStreaming(startStreaming)) { + redisplayContent(); } } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 45dbbe794..19f55b423 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -16,9 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user_photos.h" #include "data/data_web_page.h" #include "data/data_cloud_themes.h" // Data::CloudTheme. +#include "media/stories/media_stories_delegate.h" #include "media/view/media_view_playback_controls.h" #include "media/view/media_view_open_common.h" -#include "media/stories/media_stories_delegate.h" +#include "media/media_common.h" class History; @@ -237,7 +238,7 @@ private: void playbackControlsSpeedChanged(float64 speed) override; float64 playbackControlsCurrentSpeed(bool lastNonDefault) override; std::vector playbackControlsQualities() override; - int playbackControlsCurrentQuality() override; + VideoQuality playbackControlsCurrentQuality() override; void playbackControlsQualityChanged(int quality) override; void playbackControlsToFullScreen() override; void playbackControlsFromFullScreen() override; @@ -527,9 +528,7 @@ private: void clearStreaming(bool savePosition = true); [[nodiscard]] bool canInitStreaming() const; [[nodiscard]] bool saveControlLocked() const; - [[nodiscard]] not_null chooseQuality() const; - [[nodiscard]] auto resolveQualities() const - -> const std::vector> &; + void applyVideoQuality(VideoQuality value); [[nodiscard]] bool topShadowOnTheRight() const; void applyHideWindowWorkaround(); @@ -558,7 +557,7 @@ private: PhotoData *_photo = nullptr; DocumentData *_document = nullptr; DocumentData *_chosenQuality = nullptr; - int _quality = {}; + Media::VideoQuality _quality; QString _documentLoadingTo; std::shared_ptr _photoMedia; std::shared_ptr _documentMedia; diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index cf9ea7079..5399ccef9 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -1532,21 +1532,22 @@ void Pip::paintVolumeControllerContent( void Pip::handleStreamingUpdate(Streaming::Update &&update) { using namespace Streaming; - v::match(update.data, [&](Information &update) { + v::match(update.data, [&](const Information &update) { _panel.setAspectRatio( FlipSizeByRotation(update.video.size, _rotation)); - }, [&](const PreloadedVideo &update) { + }, [&](PreloadedVideo) { updatePlaybackState(); - }, [&](const UpdateVideo &update) { + }, [&](UpdateVideo) { _panel.update(); Core::App().updateNonIdle(); updatePlaybackState(); - }, [&](const PreloadedAudio &update) { + }, [&](PreloadedAudio) { updatePlaybackState(); - }, [&](const UpdateAudio &update) { + }, [&](UpdateAudio) { updatePlaybackState(); - }, [&](WaitingForData) { - }, [&](MutedByOther) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { }, [&](Finished) { updatePlaybackState(); }); diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp index 132286c31..4a396fbcf 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp @@ -73,9 +73,10 @@ PlaybackControls::PlaybackControls( _speedToggle->setSpeed(_speedControllable ? _delegate->playbackControlsCurrentSpeed(false) : 1.); - _speedToggle->setQuality(_qualitiesList.empty() + const auto quality = _delegate->playbackControlsCurrentQuality(); + _speedToggle->setQuality((_qualitiesList.empty() || !quality.manual) ? 0 - : _delegate->playbackControlsCurrentQuality()); + : quality.height); if (const auto controller = _speedController.get()) { controller->menuToggledValue( diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.h b/Telegram/SourceFiles/media/view/media_view_playback_controls.h index 0ba2cf973..a67fc73ec 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.h +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "base/object_ptr.h" +#include "media/media_common.h" namespace Ui { class LabelSimple; @@ -46,7 +47,8 @@ public: bool lastNonDefault) = 0; [[nodiscard]] virtual auto playbackControlsQualities() -> std::vector = 0; - [[nodiscard]] virtual int playbackControlsCurrentQuality() = 0; + [[nodiscard]] virtual auto playbackControlsCurrentQuality() + -> VideoQuality = 0; virtual void playbackControlsQualityChanged(int quality) = 0; virtual void playbackControlsToFullScreen() = 0; virtual void playbackControlsFromFullScreen() = 0; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp index d180059dd..49eeacd46 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp @@ -150,14 +150,15 @@ void ItemSingleMediaPreview::setupStreamedPreview( void ItemSingleMediaPreview::handleStreamingUpdate(Update &&update) { v::match(update.data, [&](Information &update) { streamingReady(std::move(update)); - }, [&](const PreloadedVideo &update) { - }, [&](const UpdateVideo &update) { + }, [](PreloadedVideo) { + }, [&](UpdateVideo) { this->update(); - }, [&](const PreloadedAudio &update) { - }, [&](const UpdateAudio &update) { - }, [&](const WaitingForData &update) { - }, [&](MutedByOther) { - }, [&](Finished) { + }, [](PreloadedAudio) { + }, [](UpdateAudio) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { + }, [](Finished) { }); } diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.cpp b/Telegram/SourceFiles/ui/controls/userpic_button.cpp index ea0bfa937..947a15791 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.cpp +++ b/Telegram/SourceFiles/ui/controls/userpic_button.cpp @@ -716,14 +716,15 @@ void UserpicButton::handleStreamingUpdate(Media::Streaming::Update &&update) { v::match(update.data, [&](Information &update) { streamingReady(std::move(update)); - }, [&](const PreloadedVideo &update) { - }, [&](const UpdateVideo &update) { + }, [](PreloadedVideo) { + }, [&](UpdateVideo) { this->update(); - }, [&](const PreloadedAudio &update) { - }, [&](const UpdateAudio &update) { - }, [&](const WaitingForData &update) { - }, [&](MutedByOther) { - }, [&](Finished) { + }, [](PreloadedAudio) { + }, [](UpdateAudio) { + }, [](WaitingForData) { + }, [](SpeedEstimate) { + }, [](MutedByOther) { + }, [](Finished) { }); }