| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "media/streaming/media_streaming_document.h"
- #include "media/streaming/media_streaming_instance.h"
- #include "media/streaming/media_streaming_loader.h"
- #include "media/streaming/media_streaming_reader.h"
- #include "data/data_session.h"
- #include "data/data_document.h"
- #include "data/data_photo.h"
- #include "data/data_document_media.h"
- #include "data/data_file_origin.h"
- #include "main/main_session.h"
- #include "storage/file_download.h" // Storage::kMaxFileInMemory.
- #include "styles/style_widgets.h"
- #include <QtCore/QBuffer>
- namespace Media {
- namespace Streaming {
- namespace {
- 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,
- std::vector<QualityDescriptor> otherQualities)
- : Document(std::move(reader), document, {}, std::move(otherQualities)) {
- _player.fullInCache(
- ) | rpl::start_with_next([=](bool fullInCache) {
- _document->setLoadedInMediaCache(fullInCache);
- }, _player.lifetime());
- }
- Document::Document(
- not_null<PhotoData*> 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)), {}, {}, {}) {
- }
- Document::Document(
- std::shared_ptr<Reader> reader,
- DocumentData *document,
- PhotoData *photo,
- std::vector<QualityDescriptor> otherQualities)
- : _document(document)
- , _photo(photo)
- , _player(std::move(reader))
- , _radial(
- [=] { waitingCallback(); },
- st::defaultInfiniteRadialAnimation)
- , _otherQualities(std::move(otherQualities)) {
- resubscribe();
- }
- void Document::resubscribe() {
- _subscription = _player.updates(
- ) | rpl::start_with_next_error([=](Update &&update) {
- handleUpdate(std::move(update));
- }, [=](Streaming::Error &&error) {
- handleError(std::move(error));
- resubscribe();
- });
- }
- Player &Document::player() {
- return _player;
- }
- const Player &Document::player() const {
- return _player;
- }
- const Information &Document::info() const {
- return _info;
- }
- void Document::play(const PlaybackOptions &options) {
- _player.play(options);
- _info.audio.state.position
- = _info.video.state.position
- = options.position;
- waitingChange(true);
- }
- void Document::saveFrameToCover() {
- _info.video.cover = _player.ready()
- ? _player.currentFrameImage()
- : _info.video.cover;
- }
- void Document::registerInstance(not_null<Instance*> instance) {
- _instances.emplace(instance);
- }
- void Document::unregisterInstance(not_null<Instance*> instance) {
- _instances.remove(instance);
- _player.unregisterInstance(instance);
- refreshPlayerPriority();
- }
- void Document::refreshPlayerPriority() {
- if (_instances.empty()) {
- return;
- }
- const auto max = ranges::max_element(
- _instances,
- ranges::less(),
- &Instance::priority);
- _player.setLoaderPriority((*max)->priority());
- }
- bool Document::waitingShown() const {
- if (!_fading.animating() && !_waiting) {
- _radial.stop(anim::type::instant);
- return false;
- }
- return _radial.animating();
- }
- float64 Document::waitingOpacity() const {
- return _fading.value(_waiting ? 1. : 0.);
- }
- 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));
- }, [&](PreloadedVideo update) {
- _info.video.state.receivedTill = update.till;
- checkSwitchToHigherQuality();
- }, [&](UpdateVideo update) {
- _info.video.state.position = update.position;
- }, [&](PreloadedAudio update) {
- _info.audio.state.receivedTill = update.till;
- }, [&](UpdateAudio update) {
- _info.audio.state.position = update.position;
- }, [&](WaitingForData update) {
- waitingChange(update.waiting);
- }, [&](SpeedEstimate update) {
- checkForQualitySwitch(update);
- }, [](MutedByOther) {
- }, [&](Finished) {
- const auto finishTrack = [](TrackState &state) {
- state.position = state.receivedTill = state.duration;
- };
- finishTrack(_info.audio.state);
- finishTrack(_info.video.state);
- });
- }
- 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) {
- _document->setNotSupportsStreaming();
- } else if (error == Error::OpenFailed) {
- _document->setInappPlaybackFailed();
- }
- } else if (_photo) {
- if (error == Error::NotStreamable || error == Error::OpenFailed) {
- _photo->setVideoPlaybackFailed();
- }
- }
- waitingChange(false);
- }
- void Document::ready(Information &&info) {
- _info = std::move(info);
- validateGoodThumbnail();
- waitingChange(false);
- }
- void Document::waitingChange(bool waiting) {
- if (_waiting == waiting) {
- return;
- }
- _waiting = waiting;
- const auto fade = [=](crl::time duration) {
- if (!_radial.animating()) {
- _radial.start(
- st::defaultInfiniteRadialAnimation.sineDuration);
- }
- _fading.start([=] {
- waitingCallback();
- }, _waiting ? 0. : 1., _waiting ? 1. : 0., duration);
- checkSwitchToLowerQuality();
- };
- if (waiting) {
- if (_radial.animating()) {
- _timer.cancel();
- fade(kWaitingFastDuration);
- } else {
- _timer.callOnce(kWaitingShowDelay);
- _timer.setCallback([=] {
- fade(kWaitingShowDuration);
- });
- }
- } else {
- _timer.cancel();
- if (_radial.animating()) {
- fade(kWaitingFastDuration);
- }
- }
- }
- void Document::validateGoodThumbnail() {
- if (_info.video.cover.isNull()
- || !_document
- || _document->goodThumbnailChecked()) {
- return;
- }
- const auto sticker = (_document->sticker() != nullptr);
- const auto document = _document;
- const auto information = _info.video;
- const auto key = document->goodThumbnailCacheKey();
- const auto guard = base::make_weak(&document->session());
- document->owner().cache().get(key, [=](QByteArray value) {
- if (!value.isEmpty()) {
- return;
- }
- const auto image = [&] {
- auto result = information.cover;
- if (information.rotation != 0) {
- auto transform = QTransform();
- transform.rotate(information.rotation);
- result = result.transformed(transform);
- }
- if (result.size() != information.size) {
- result = result.scaled(
- information.size,
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation);
- }
- if (!sticker && information.alpha) {
- result = Images::Opaque(std::move(result));
- }
- return result;
- }();
- auto bytes = QByteArray();
- {
- auto buffer = QBuffer(&bytes);
- image.save(&buffer, sticker ? "WEBP" : "JPG", kGoodThumbQuality);
- }
- const auto length = bytes.size();
- if (!length || length > Storage::kMaxFileInMemory) {
- LOG(("App Error: Bad thumbnail data for saving to cache."));
- bytes = "(failed)"_q;
- }
- crl::on_main(guard, [=] {
- if (const auto active = document->activeMediaView()) {
- active->setGoodThumbnail(image);
- }
- if (bytes != "(failed)"_q) {
- document->setGoodThumbnailChecked(true);
- }
- document->owner().cache().putIfEmpty(
- document->goodThumbnailCacheKey(),
- Storage::Cache::Database::TaggedValue(
- base::duplicate(bytes),
- Data::kImageCacheTag));
- });
- });
- }
- void Document::waitingCallback() {
- for (const auto &instance : _instances) {
- instance->callWaitingCallback();
- }
- }
- } // namespace Streaming
- } // namespace Media
|