Implement adaptive quality selection.

This commit is contained in:
John Preston 2024-10-31 18:03:56 +04:00
parent a386d70ae4
commit d6ac883efa
37 changed files with 654 additions and 193 deletions

View file

@ -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...";

View file

@ -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) {
});
}

View file

@ -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;
}

View file

@ -929,8 +929,8 @@ public:
[[nodiscard]] rpl::producer<int> 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<bool> _weatherInCelsius;
QByteArray _tonsiteStorageToken;
rpl::variable<int> _ivZoom = 100;
int _videoQuality = 0;
Media::VideoQuality _videoQuality;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View file

@ -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<not_null<DocumentData*>> & {
static const auto empty = std::vector<not_null<DocumentData*>>();
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*> 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)) {

View file

@ -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<not_null<DocumentData*>> qualities);
[[nodiscard]] int resolveVideoQuality() const;
[[nodiscard]] auto resolveQualities(HistoryItem *context) const
-> const std::vector<not_null<DocumentData*>> &;
[[nodiscard]] not_null<DocumentData*> chooseQuality(
HistoryItem *context,
Media::VideoQuality request);
[[nodiscard]] bool loading() const;
[[nodiscard]] QString loadingFilePath() const;

View file

@ -41,6 +41,43 @@ bool PruneDestroyedAndSet(
return result;
}
[[nodiscard]] auto LookupOtherQualities(
DocumentData *original,
not_null<DocumentData*> quality,
HistoryItem *context)
-> std::vector<Media::Streaming::QualityDescriptor> {
if (!original || !context) {
return {};
}
auto qualities = original->resolveQualities(context);
if (qualities.empty()) {
return {};
}
auto result = std::vector<Media::Streaming::QualityDescriptor>();
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<PhotoData*> quality,
HistoryItem *context)
-> std::vector<Media::Streaming::QualityDescriptor> {
Expects(!original);
return {};
}
} // namespace
Streaming::Streaming(not_null<Session*> owner)
@ -50,7 +87,6 @@ Streaming::Streaming(not_null<Session*> owner)
Streaming::~Streaming() = default;
template <typename Data>
[[nodiscard]] std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
@ -84,10 +120,16 @@ template <typename Data>
base::flat_map<not_null<Data*>, std::weak_ptr<Document>> &documents,
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
not_null<Data*> 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 <typename Data>
if (!reader) {
return nullptr;
}
auto result = std::make_shared<Document>(data, std::move(reader));
auto result = std::make_shared<Document>(
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::Reader> Streaming::sharedReader(
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
not_null<DocumentData*> document,
FileOrigin origin) {
return sharedDocument(_fileDocuments, _fileReaders, document, origin);
return sharedDocument(
_fileDocuments,
_fileReaders,
document,
nullptr,
nullptr,
origin);
}
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
not_null<DocumentData*> quality,
not_null<DocumentData*> original,
HistoryItem *context,
FileOrigin origin) {
return sharedDocument(
_fileDocuments,
_fileReaders,
quality,
original,
context,
origin);
}
std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
@ -149,7 +214,13 @@ std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
not_null<PhotoData*> photo,
FileOrigin origin) {
return sharedDocument(_photoDocuments, _photoReaders, photo, origin);
return sharedDocument(
_photoDocuments,
_photoReaders,
photo,
nullptr,
nullptr,
origin);
}
void Streaming::keepAlive(not_null<DocumentData*> document) {

View file

@ -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<Document> sharedDocument(
not_null<DocumentData*> document,
FileOrigin origin);
[[nodiscard]] std::shared_ptr<Document> sharedDocument(
not_null<DocumentData*> quality,
not_null<DocumentData*> original,
HistoryItem *context,
FileOrigin origin);
[[nodiscard]] std::shared_ptr<Reader> sharedReader(
not_null<PhotoData*> photo,
@ -68,6 +71,8 @@ private:
base::flat_map<not_null<Data*>, std::weak_ptr<Document>> &documents,
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
not_null<Data*> data,
DocumentData *original,
HistoryItem *context,
FileOrigin origin);
template <typename Data>

View file

@ -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<DocumentData*> chosen,
std::shared_ptr<::Media::Streaming::Document> shared,
Fn<void()> waitingCallback);
const not_null<DocumentData*> chosen;
::Media::Streaming::Instance instance;
::Media::Streaming::FrameRequest frozenRequest;
QImage frozenFrame;
@ -118,9 +121,11 @@ struct Gif::Streamed {
};
Gif::Streamed::Streamed(
not_null<DocumentData*> chosen,
std::shared_ptr<::Media::Streaming::Document> shared,
Fn<void()> waitingCallback)
: instance(std::move(shared), std::move(waitingCallback)) {
: chosen(chosen)
, instance(std::move(shared), std::move(waitingCallback)) {
}
[[nodiscard]] bool IsHiddenRoundMessage(not_null<Element*> 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<Streamed>(
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) {
});
}

View file

@ -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) {
});
}

View file

@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/algorithm.h"
#include <compare>
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;

View file

@ -717,7 +717,7 @@ SpeedController::SpeedController(
Fn<float64(bool lastNonDefault)> value,
Fn<void(float64)> change,
std::vector<int> qualities,
Fn<int()> quality,
Fn<VideoQuality()> quality,
Fn<void(int)> 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<Ui::DropdownMenu*> menu) {
@ -810,7 +810,8 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> 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<Ui::Menu::Action>(
raw,
st.qualityMenu,
@ -837,9 +838,15 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> 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));

View file

@ -132,7 +132,7 @@ public:
Fn<float64(bool lastNonDefault)> value,
Fn<void(float64)> change,
std::vector<int> qualities = {},
Fn<int()> quality = nullptr,
Fn<VideoQuality()> quality = nullptr,
Fn<void(int)> 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<int> _qualities;
Fn<int()> _lookupQuality;
Fn<VideoQuality()> _lookupQuality;
Fn<void(int)> _changeQuality;
rpl::variable<int> _quality;
rpl::variable<VideoQuality> _quality;
};

View file

@ -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()) {

View file

@ -17,9 +17,6 @@ inline constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
inline constexpr auto kDurationMax = crl::time(std::numeric_limits<int>::max());
inline constexpr auto kDurationUnavailable = std::numeric_limits<crl::time>::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;
};

View file

@ -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<DocumentData*> document,
std::shared_ptr<Reader> reader)
: Document(std::move(reader), document, nullptr) {
std::shared_ptr<Reader> reader,
std::vector<QualityDescriptor> 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<PhotoData*> photo,
std::shared_ptr<Reader> reader)
: Document(std::move(reader), nullptr, photo) {
std::shared_ptr<Reader> reader,
std::vector<QualityDescriptor> otherQualities)
: Document(std::move(reader), {}, photo, {}) {
}
Document::Document(std::unique_ptr<Loader> loader)
: Document(std::make_shared<Reader>(std::move(loader)), nullptr, nullptr) {
: Document(std::make_shared<Reader>(std::move(loader)), {}, {}, {}) {
}
Document::Document(
std::shared_ptr<Reader> reader,
DocumentData *document,
PhotoData *photo)
PhotoData *photo,
std::vector<QualityDescriptor> 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<int> 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<QualityDescriptor> 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<uint32>::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<uint32>::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()) {

View file

@ -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<DocumentData*> document,
std::shared_ptr<Reader> reader);
std::shared_ptr<Reader> reader,
std::vector<QualityDescriptor> otherQualities = {});
Document(
not_null<PhotoData*> photo,
std::shared_ptr<Reader> reader);
std::shared_ptr<Reader> reader,
std::vector<QualityDescriptor> otherQualities = {});
explicit Document(std::unique_ptr<Loader> loader);
void play(const PlaybackOptions &options);
@ -41,11 +47,15 @@ public:
[[nodiscard]] float64 waitingOpacity() const;
[[nodiscard]] Ui::RadialState waitingState() const;
void setOtherQualities(std::vector<QualityDescriptor> value);
[[nodiscard]] rpl::producer<int> switchQualityRequests() const;
private:
Document(
std::shared_ptr<Reader> reader,
DocumentData *document,
PhotoData *photo);
PhotoData *photo,
std::vector<QualityDescriptor> 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<not_null<Instance*>> _instances;
std::vector<QualityDescriptor> _otherQualities;
rpl::event_stream<int> _switchQualityRequests;
SpeedEstimate _lastSpeedEstimate;
bool _waiting = false;
};
} // namespace Streaming
} // namespace Media
} // namespace Media::Streaming

View file

@ -490,6 +490,14 @@ void File::setLoaderPriority(int priority) {
_reader->setLoaderPriority(priority);
}
int64 File::size() const {
return _reader->size();
}
rpl::producer<SpeedEstimate> File::speedEstimate() const {
return _reader->speedEstimate();
}
File::~File() {
stop();
}

View file

@ -42,6 +42,9 @@ public:
[[nodiscard]] bool isRemoteLoader() const;
void setLoaderPriority(int priority);
[[nodiscard]] int64 size() const;
[[nodiscard]] rpl::producer<SpeedEstimate> speedEstimate() const;
~File();
private:

View file

@ -36,6 +36,21 @@ Instance::Instance(
std::move(waitingCallback)) {
}
Instance::Instance(
not_null<DocumentData*> quality,
not_null<DocumentData*> original,
HistoryItem *context,
Data::FileOrigin origin,
Fn<void()> waitingCallback)
: Instance(
quality->owner().streaming().sharedDocument(
quality,
original,
context,
origin),
std::move(waitingCallback)) {
}
Instance::Instance(
not_null<PhotoData*> photo,
Data::FileOrigin origin,
@ -72,6 +87,10 @@ const Information &Instance::info() const {
return _shared->info();
}
rpl::producer<int> Instance::switchQualityRequests() const {
return _shared->switchQualityRequests();
}
void Instance::play(const PlaybackOptions &options) {
Expects(_shared != nullptr);

View file

@ -34,6 +34,12 @@ public:
not_null<DocumentData*> document,
Data::FileOrigin origin,
Fn<void()> waitingCallback);
Instance(
not_null<DocumentData*> quality,
not_null<DocumentData*> original,
HistoryItem *context,
Data::FileOrigin origin,
Fn<void()> waitingCallback);
Instance(
not_null<PhotoData*> photo,
Data::FileOrigin origin,
@ -45,6 +51,7 @@ public:
[[nodiscard]] const Player &player() const;
[[nodiscard]] const Information &info() const;
[[nodiscard]] rpl::producer<int> switchQualityRequests() const;
void play(const PlaybackOptions &options);
void pause();

View file

@ -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<LoadedPart> parts() const = 0;
[[nodiscard]] virtual auto speedEstimate() const
-> rpl::producer<SpeedEstimate> = 0;
virtual void attachDownloader(
not_null<Storage::StreamedFileDownloader*> downloader) = 0;
@ -74,5 +77,4 @@ private:
};
} // namespace Streaming
} // namespace Media
} // namespace Media::Streaming

View file

@ -84,6 +84,10 @@ rpl::producer<LoadedPart> LoaderLocal::parts() const {
return _parts.events();
}
rpl::producer<SpeedEstimate> LoaderLocal::speedEstimate() const {
return rpl::never<SpeedEstimate>();
}
void LoaderLocal::attachDownloader(
not_null<Storage::StreamedFileDownloader*> downloader) {
Unexpected("Downloader attached to a local streaming loader.");

View file

@ -33,6 +33,7 @@ public:
// Parts will be sent from the main thread.
[[nodiscard]] rpl::producer<LoadedPart> parts() const override;
[[nodiscard]] rpl::producer<SpeedEstimate> speedEstimate() const override;
void attachDownloader(
not_null<Storage::StreamedFileDownloader*> downloader) override;

View file

@ -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<Storage::DownloadManagerMtproto*> 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<LoadedPart> LoaderMtproto::parts() const {
return _parts.events();
}
rpl::producer<SpeedEstimate> 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

View file

@ -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<LoadedPart> parts() const override;
[[nodiscard]] rpl::producer<SpeedEstimate> speedEstimate() const override;
void attachDownloader(
not_null<Storage::StreamedFileDownloader*> 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<LoadedPart> _parts;
rpl::event_stream<SpeedEstimate> _speedEstimate;
std::vector<StatsEntry> _stats;
crl::time _firstRequestStart = 0;
base::Timer _statsTimer;
Storage::StreamedFileDownloader *_downloader = nullptr;

View file

@ -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<bool> Player::fullInCache() const {
return _fullInCache.events();
}
int64 Player::fileSize() const {
return _file->size();
}
QSize Player::videoSize() const {
return _information.video.size;
}

View file

@ -60,6 +60,7 @@ public:
[[nodiscard]] rpl::producer<Update, Error> updates() const;
[[nodiscard]] rpl::producer<bool> fullInCache() const;
[[nodiscard]] int64 fileSize() const;
[[nodiscard]] QSize videoSize() const;
[[nodiscard]] QImage frame(
const FrameRequest &request,

View file

@ -1099,6 +1099,10 @@ void Reader::continueDownloaderFromMainThread() {
}
}
rpl::producer<SpeedEstimate> Reader::speedEstimate() const {
return _loader->speedEstimate();
}
void Reader::setLoaderPriority(int priority) {
if (_realPriority == priority) {
return;

View file

@ -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<Storage::StreamedFileDownloader*> downloader);
void continueDownloaderFromMainThread();
[[nodiscard]] rpl::producer<SpeedEstimate> speedEstimate() const;
~Reader();

View file

@ -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<DocumentData*> document,
not_null<DocumentData*> quality,
not_null<DocumentData*> original,
HistoryItem *context,
Data::FileOrigin origin,
Fn<void()> waitingCallback);
Streamed(
@ -449,10 +452,12 @@ private:
};
OverlayWidget::Streamed::Streamed(
not_null<DocumentData*> document,
not_null<DocumentData*> quality,
not_null<DocumentData*> original,
HistoryItem *context,
Data::FileOrigin origin,
Fn<void()> 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<not_null<DocumentData*>> & {
static const auto empty = std::vector<not_null<DocumentData*>>();
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<DocumentData*> 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<Streamed>(video, origin, callback);
_streamed = std::make_unique<Streamed>(
video,
_document,
_message,
origin,
callback);
} else {
_streamed = std::make_unique<Streamed>(_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<int> 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<int> 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();
}
}

View file

@ -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<int> 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<DocumentData*> chooseQuality() const;
[[nodiscard]] auto resolveQualities() const
-> const std::vector<not_null<DocumentData*>> &;
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<Data::PhotoMedia> _photoMedia;
std::shared_ptr<Data::DocumentMedia> _documentMedia;

View file

@ -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();
});

View file

@ -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(

View file

@ -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<int> = 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;

View file

@ -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) {
});
}

View file

@ -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) {
});
}