mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Implement adaptive quality selection.
This commit is contained in:
parent
a386d70ae4
commit
d6ac883efa
37 changed files with 654 additions and 193 deletions
|
@ -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...";
|
||||
|
|
|
@ -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) {
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue