diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index a4dd392860..40c1df5ffa 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -301,7 +301,7 @@ void DocumentOpenClickHandler::Open( location.accessDisable(); return; } else if (data->canBePlayed()) { - if (data->isAudioFile()) { + if (data->isAudioFile() || data->isVoiceMessage()) { Media::Player::instance()->playPause({ data, msgId }); } else { Core::App().showDocument(data, context); @@ -615,7 +615,9 @@ void DocumentData::replaceGoodThumbnail( _goodThumbnail->replaceSource(std::move(source)); } -void DocumentData::setGoodThumbnail(QImage &&image, QByteArray &&bytes) { +void DocumentData::setGoodThumbnailOnUpload( + QImage &&image, + QByteArray &&bytes) { Expects(uploadingData != nullptr); if (image.isNull()) { @@ -1215,7 +1217,10 @@ bool DocumentData::canBeStreamed() const { } bool DocumentData::canBePlayed() const { - return (isAnimation() || isVideoFile() || isAudioFile()) + return (isAnimation() + || isVideoFile() + || isAudioFile() + || isVoiceMessage()) && (loaded() || canBeStreamed()); } diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index e04ebe5d7f..a4828490a1 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -184,7 +184,7 @@ public: [[nodiscard]] Image *goodThumbnail() const; [[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const; - void setGoodThumbnail(QImage &&image, QByteArray &&bytes); + void setGoodThumbnailOnUpload(QImage &&image, QByteArray &&bytes); void refreshGoodThumbnail(); void replaceGoodThumbnail(std::unique_ptr &&source); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 7e205db21c..02b9bd3bab 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -59,7 +59,8 @@ MTPDmessage::Flags NewForwardedFlags( result &= ~MTPDmessage::Flag::f_media; } } - if (!peer->isChannel() && media->forwardedBecomesUnread()) { + if ((!peer->isChannel() || peer->isMegagroup()) + && media->forwardedBecomesUnread()) { result |= MTPDmessage::Flag::f_media_unread; } } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index f93b956154..1887369897 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4300,7 +4300,7 @@ void HistoryWidget::sendFileConfirmed( MTP_long(groupId)), NewMessageUnread); } else if (file->type == SendMediaType::Audio) { - if (!peer->isChannel()) { + if (!peer->isChannel() || peer->isMegagroup()) { flags |= MTPDmessage::Flag::f_media_unread; } auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0; diff --git a/Telegram/SourceFiles/history/media/history_media_document.cpp b/Telegram/SourceFiles/history/media/history_media_document.cpp index 15e0feb238..9d1eec2555 100644 --- a/Telegram/SourceFiles/history/media/history_media_document.cpp +++ b/Telegram/SourceFiles/history/media/history_media_document.cpp @@ -315,10 +315,10 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, } auto icon = [&] { - if (showPause) { - return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause)); - } else if (radial || _data->loading()) { + if (_data->loading() || _data->uploading()) { return &(outbg ? (selected ? st::historyFileOutCancelSelected : st::historyFileOutCancel) : (selected ? st::historyFileInCancelSelected : st::historyFileInCancel)); + } else if (showPause) { + return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause)); } else if (loaded || _data->canBePlayed()) { if (_data->canBePlayed()) { return &(outbg ? (selected ? st::historyFileOutPlaySelected : st::historyFileOutPlay) : (selected ? st::historyFileInPlaySelected : st::historyFileInPlay)); @@ -335,7 +335,7 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, auto statuswidth = namewidth; auto voiceStatusOverride = QString(); - if (auto voice = Get()) { + if (const auto voice = Get()) { const VoiceWaveform *wf = nullptr; uchar norm_value = 0; if (const auto voiceData = _data->voice()) { @@ -463,6 +463,13 @@ TextState HistoryDocument::textState(QPoint point, StateRequest request) const { nametop = st::msgFileThumbNameTop - topMinus; linktop = st::msgFileThumbLinkTop - topMinus; bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; + + QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); + if ((_data->loading() || _data->uploading()) && rthumb.contains(point)) { + result.link = _cancell; + return result; + } + if (_data->status != FileUploadFailed) { if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) { result.link = (_data->loading() || _data->uploading()) @@ -471,6 +478,17 @@ TextState HistoryDocument::textState(QPoint point, StateRequest request) const { return result; } } + } else { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nameright = st::msgFilePadding.left(); + nametop = st::msgFileNameTop - topMinus; + bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); + if ((_data->loading() || _data->uploading()) && inner.contains(point)) { + result.link = _cancell; + return result; + } } if (const auto voice = Get()) { @@ -490,7 +508,7 @@ TextState HistoryDocument::textState(QPoint point, StateRequest request) const { } auto painth = height(); - if (auto captioned = Get()) { + if (const auto captioned = Get()) { if (point.y() >= bottom) { result = TextState(_parent, captioned->_caption.getState( point - QPoint(st::msgPadding.left(), bottom), @@ -504,10 +522,11 @@ TextState HistoryDocument::textState(QPoint point, StateRequest request) const { painth -= st::msgPadding.bottom(); } } - if (QRect(0, 0, width(), painth).contains(point) && !_data->loading() && !_data->uploading() && !_data->isNull()) { - if (_data->loading() || _data->uploading()) { - result.link = _cancell; - } else if (loaded || _data->canBePlayed()) { + if (QRect(0, 0, width(), painth).contains(point) + && !_data->loading() + && !_data->uploading() + && !_data->isNull()) { + if (loaded || _data->canBePlayed()) { result.link = _openl; } else { result.link = _savel; diff --git a/Telegram/SourceFiles/history/media/history_media_video.cpp b/Telegram/SourceFiles/history/media/history_media_video.cpp index af5a82f548..9decda064d 100644 --- a/Telegram/SourceFiles/history/media/history_media_video.cpp +++ b/Telegram/SourceFiles/history/media/history_media_video.cpp @@ -226,15 +226,13 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, crl p.setOpacity(1); } - const auto canPlay = _data->canBePlayed(); auto icon = [&]() -> const style::icon * { - if (canPlay && !radial) { - return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); - } else if (radial || _data->loading()) { - if (_parent->data()->id > 0 || _data->uploading()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } + if (_data->loading() || _data->uploading()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } else if (!IsServerMsgId(_parent->data()->id)) { return nullptr; + } else if (_data->canBePlayed()) { + return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); }(); @@ -276,7 +274,6 @@ TextState HistoryVideo::textState(QPoint point, StateRequest request) const { } auto result = TextState(_parent); - const auto canPlay = _data->canBePlayed(); auto paintx = 0, painty = 0, paintw = width(), painth = height(); bool bubble = _parent->hasBubble(); @@ -298,10 +295,13 @@ TextState HistoryVideo::textState(QPoint point, StateRequest request) const { painth -= st::mediaCaptionSkip; } if (QRect(paintx, painty, paintw, painth).contains(point)) { - if (_data->uploading()) { + if (_data->loading() || _data->uploading()) { result.link = _cancell; + } else if (!IsServerMsgId(_parent->data()->id)) { + } else if (_data->canBePlayed()) { + result.link = _openl; } else { - result.link = canPlay ? _openl : (_data->loading() ? _cancell : _savel); + result.link = _savel; } } if (_caption.isEmpty() && _parent->media() == this) { @@ -394,13 +394,12 @@ void HistoryVideo::drawGrouped( auto icon = [&]() -> const style::icon * { if (_data->waitingForAlbum()) { return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); - } else if (canPlay && !radial) { - return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); - } else if (radial || _data->loading()) { - if (_parent->data()->id > 0 || _data->uploading()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } + } else if (_data->loading() || _data->uploading()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } else if (!IsServerMsgId(_realParent->id)) { return nullptr; + } else if (_data->canBePlayed()) { + return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); } return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); }(); @@ -436,12 +435,12 @@ TextState HistoryVideo::getStateGrouped( if (!geometry.contains(point)) { return {}; } - return TextState(_parent, _data->uploading() + return TextState(_parent, (_data->loading() || _data->uploading()) ? _cancell + : !IsServerMsgId(_realParent->id) + ? nullptr : _data->canBePlayed() ? _openl - : _data->loading() - ? _cancell : _savel); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 8e8d1d6ec0..fa96e0f5e9 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -188,14 +188,14 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons p.setOpacity(radialOpacity * p.opacity()); p.setOpacity(radialOpacity); - auto icon = ([loaded, radial, loading] { - if (loaded && !radial) { - return &st::historyFileInPlay; - } else if (radial || loading) { + auto icon = [&] { + if (radial || loading) { return &st::historyFileInCancel; + } else if (loaded) { + return &st::historyFileInPlay; } return &st::historyFileInDownload; - })(); + }(); QRect inner((_width - st::msgFileSize) / 2, (height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); icon->paintInCenter(p, inner); if (radial) { @@ -778,11 +778,11 @@ void File::paint(Painter &p, const QRect &clip, const PaintContext *context) con _animation->radial.draw(p, radialCircle, st::msgFileRadialLine, st::historyFileInRadialFg); } - auto icon = ([&] { - if (showPause) { - return &st::historyFileInPause; - } else if (radial || _document->loading()) { + auto icon = [&] { + if (radial || _document->loading()) { return &st::historyFileInCancel; + } else if (showPause) { + return &st::historyFileInPause; } else if (true || _document->loaded()) { if (_document->isImage()) { return &st::historyFileInImage; @@ -793,7 +793,7 @@ void File::paint(Painter &p, const QRect &clip, const PaintContext *context) con return &st::historyFileInDocument; } return &st::historyFileInDownload; - })(); + }(); icon->paintInCenter(p, inner); int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop; diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index 42d8191461..319ce04f0d 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -798,27 +798,6 @@ void Mixer::play( auto current = trackForType(type); if (!current) return; - if (type == AudioMsgId::Type::Video) { - auto pauseType = [this](AudioMsgId::Type type) { - auto current = trackForType(type); - switch (current->state.state) { - case State::Starting: - case State::Resuming: - case State::Playing: { - current->state.state = State::Pausing; - resetFadeStartPosition(type); - } break; - case State::Stopping: { - current->state.state = State::Pausing; - } break; - - } - }; - - pauseType(AudioMsgId::Type::Song); - pauseType(AudioMsgId::Type::Voice); - } - if (current->state.id != audio) { if (fadedStop(type, &fadedStart)) { stopped = current->state.id; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_common.h b/Telegram/SourceFiles/media/streaming/media_streaming_common.h index 103e364433..32f9b78422 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_common.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_common.h @@ -78,6 +78,7 @@ using PreloadedAudio = PreloadedUpdate; using UpdateAudio = PlaybackUpdate; struct WaitingForData { + bool waiting = false; }; struct MutedByOther { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index 9a8e2c03ea..a5acce8227 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -450,6 +450,7 @@ void Player::checkResumeFromWaitingForData() { if (_pausedByWaitingForData && bothReceivedEnough(kBufferFor)) { _pausedByWaitingForData = false; updatePausedState(); + _updates.fire({ WaitingForData{ false } }); } } @@ -467,7 +468,7 @@ void Player::start() { }) | rpl::start_with_next([=] { _pausedByWaitingForData = true; updatePausedState(); - _updates.fire({ WaitingForData() }); + _updates.fire({ WaitingForData{ true } }); }, _sessionLifetime); if (guard && _audio) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index bd83d05801..ccc583444a 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -19,11 +19,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/text_options.h" #include "media/audio/media_audio.h" -#include "media/clip/media_clip_reader.h" #include "media/view/media_view_playback_controls.h" #include "media/view/media_view_group_thumbs.h" #include "media/streaming/media_streaming_player.h" #include "media/streaming/media_streaming_loader.h" +#include "media/player/media_player_instance.h" #include "history/history.h" #include "history/history_message.h" #include "data/data_media_types.h" @@ -45,6 +45,10 @@ namespace Media { namespace View { namespace { +constexpr auto kGoodThumbnailQuality = 87; +constexpr auto kWaitingFastDuration = crl::time(200); +constexpr auto kWaitingShowDuration = crl::time(500); +constexpr auto kWaitingShowDelay = crl::time(500); constexpr auto kPreloadCount = 4; // Preload X message ids before and after current. @@ -146,20 +150,30 @@ struct OverlayWidget::Streamed { not_null owner, std::unique_ptr loader, QWidget *controlsParent, - not_null controlsDelegate); + not_null controlsDelegate, + AnimationCallbacks loadingCallbacks); Streaming::Player player; Streaming::Information info; PlaybackControls controls; + + bool waiting = false; + Ui::InfiniteRadialAnimation radial; + Animation fading; + base::Timer timer; + + bool resumeOnCallEnd = false; }; OverlayWidget::Streamed::Streamed( not_null owner, std::unique_ptr loader, QWidget *controlsParent, - not_null controlsDelegate) + not_null controlsDelegate, + AnimationCallbacks loadingCallbacks) : player(owner, std::move(loader)) -, controls(controlsParent, controlsDelegate) { +, controls(controlsParent, controlsDelegate) +, radial(std::move(loadingCallbacks), st::mediaviewStreamingRadial) { } OverlayWidget::OverlayWidget() @@ -195,11 +209,13 @@ OverlayWidget::OverlayWidget() } }); subscribe(Auth().calls().currentCallChanged(), [this](Calls::Call *call) { - if (call - && _streamed - && !_streamed->player.paused() - && !_streamed->player.finished()) { - playbackPauseResume(); + if (!_streamed) { + return; + } + if (call) { + playbackPauseOnCall(); + } else { + playbackResumeOnCall(); } }); subscribe(Auth().documentUpdated, [this](DocumentData *document) { @@ -644,6 +660,12 @@ void OverlayWidget::step_state(crl::time ms, bool timer) { } } +void OverlayWidget::step_waiting(crl::time ms, bool timer) { + if (timer && !anim::Disabled()) { + update(radialRect()); + } +} + void OverlayWidget::updateCursor() { setCursor(_controlsState == ControlsHidden ? Qt::BlankCursor @@ -665,7 +687,7 @@ float64 OverlayWidget::radialProgress() const { bool OverlayWidget::radialLoading() const { if (_doc) { - return _doc->loading(); + return _doc->loading() && !_streamed; } else if (_photo) { return _photo->large()->loading(); } @@ -695,7 +717,7 @@ crl::time OverlayWidget::radialTimeShift() const { } void OverlayWidget::step_radial(crl::time ms, bool timer) { - if (!_doc && !_photo) { + if ((!_doc && !_photo) || _streamed) { _radial.stop(); return; } @@ -911,7 +933,7 @@ void OverlayWidget::onScreenResized(int screen) { } if (!ignore) { moveToScreen(); - auto item = (_msgid ? App::histItemById(_msgid) : nullptr); + const auto item = App::histItemById(_msgid); if (_photo) { displayPhoto(_photo, item); } else if (_doc) { @@ -921,7 +943,7 @@ void OverlayWidget::onScreenResized(int screen) { } void OverlayWidget::onToMessage() { - if (auto item = _msgid ? App::histItemById(_msgid) : 0) { + if (const auto item = App::histItemById(_msgid)) { close(); Ui::showPeerHistoryAtItem(item); } @@ -1873,17 +1895,36 @@ void OverlayWidget::initStreaming() { } void OverlayWidget::initStreamingThumbnail() { - if (!_doc->hasThumbnail()) { + Expects(_doc != nullptr); + + const auto good = _doc->goodThumbnail(); + const auto useGood = (good && good->loaded()); + const auto thumb = _doc->thumbnail(); + const auto useThumb = (thumb && thumb->loaded()); + const auto blurred = _doc->thumbnailInline(); + if (!useGood && !thumb && !blurred) { + return; + } else if (_doc->dimensions.isEmpty()) { return; } - if (_doc->dimensions.width() && _doc->dimensions.height()) { - auto w = _doc->dimensions.width(); - auto h = _doc->dimensions.height(); - _current = _doc->thumbnail()->pixNoCache(fileOrigin(), w, h, VideoThumbOptions(_doc), w / cIntRetinaFactor(), h / cIntRetinaFactor()); - _current.setDevicePixelRatio(cRetinaFactor()); - } else { - _current = _doc->thumbnail()->pixNoCache(fileOrigin(), _doc->thumbnail()->width(), _doc->thumbnail()->height(), VideoThumbOptions(_doc), st::mediaviewFileIconSize, st::mediaviewFileIconSize); - } + const auto w = _doc->dimensions.width(); + const auto h = _doc->dimensions.height(); + const auto options = VideoThumbOptions(_doc); + const auto goodOptions = (options & ~Images::Option::Blurred); + _current = (useGood + ? good + : useThumb + ? thumb + : blurred + ? blurred + : Image::BlankMedia().get())->pixNoCache( + fileOrigin(), + w, + h, + useGood ? goodOptions : options, + w / cIntRetinaFactor(), + h / cIntRetinaFactor()); + _current.setDevicePixelRatio(cRetinaFactor()); } void OverlayWidget::createStreamingObjects() { @@ -1891,7 +1932,8 @@ void OverlayWidget::createStreamingObjects() { &_doc->owner(), _doc->createStreamingLoader(fileOrigin()), this, - static_cast(this)); + static_cast(this), + animation(this, &OverlayWidget::step_waiting)); if (videoIsGifv()) { _streamed->controls.hide(); @@ -1901,27 +1943,61 @@ void OverlayWidget::createStreamingObjects() { } } +void OverlayWidget::validateStreamedGoodThumbnail() { + Expects(_streamed != nullptr); + Expects(_doc != nullptr); + + const auto good = _doc->goodThumbnail(); + const auto &image = _streamed->info.video.cover; + if (image.isNull() || (good && good->loaded()) || _doc->uploading()) { + return; + } + auto bytes = QByteArray(); + { + auto buffer = QBuffer(&bytes); + image.save(&buffer, "JPG", kGoodThumbnailQuality); + } + const auto length = bytes.size(); + if (!length || length > Storage::kMaxFileInMemory) { + LOG(("App Error: Bad thumbnail data for saving to cache.")); + } else if (_doc->uploading()) { + _doc->setGoodThumbnailOnUpload( + base::duplicate(image), + std::move(bytes)); + } else { + _doc->owner().cache().putIfEmpty( + _doc->goodThumbnailCacheKey(), + Storage::Cache::Database::TaggedValue( + std::move(bytes), + Data::kImageCacheTag)); + _doc->refreshGoodThumbnail(); + } +} + void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) { using namespace Streaming; update.data.match([&](Information &update) { _streamed->info = std::move(update); + validateStreamedGoodThumbnail(); this->update(contentRect()); - }, [&](PreloadedVideo &update) { + playbackWaitingChange(false); + }, [&](const PreloadedVideo &update) { _streamed->info.video.state.receivedTill = update.till; //updatePlaybackState(); - }, [&](UpdateVideo &update) { + }, [&](const UpdateVideo &update) { _streamed->info.video.state.position = update.position; this->update(contentRect()); Core::App().updateNonIdle(); updatePlaybackState(); - }, [&](PreloadedAudio &update) { + }, [&](const PreloadedAudio &update) { _streamed->info.audio.state.receivedTill = update.till; //updatePlaybackState(); - }, [&](UpdateAudio & update) { + }, [&](const UpdateAudio &update) { _streamed->info.audio.state.position = update.position; updatePlaybackState(); - }, [&](WaitingForData) { + }, [&](const WaitingForData &update) { + playbackWaitingChange(update.waiting); }, [&](MutedByOther) { }, [&](Finished) { const auto finishTrack = [](Media::Streaming::TrackState &state) { @@ -1934,6 +2010,43 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) { } void OverlayWidget::handleStreamingError(Streaming::Error &&error) { + playbackWaitingChange(false); +} + +void OverlayWidget::playbackWaitingChange(bool waiting) { + Expects(_streamed != nullptr); + + if (_streamed->waiting == waiting) { + return; + } + _streamed->waiting = waiting; + const auto fade = [=](crl::time duration) { + if (!_streamed->radial.animating()) { + _streamed->radial.start( + st::defaultInfiniteRadialAnimation.sineDuration); + } + _streamed->fading.start( + [=] { update(radialRect()); }, + _streamed->waiting ? 0. : 1., + _streamed->waiting ? 1. : 0., + duration); + }; + if (waiting) { + if (_streamed->radial.animating()) { + _streamed->timer.cancel(); + fade(kWaitingFastDuration); + } else { + _streamed->timer.callOnce(kWaitingShowDelay); + _streamed->timer.setCallback([=] { + fade(kWaitingShowDuration); + }); + } + } else { + _streamed->timer.cancel(); + if (_streamed->radial.animating()) { + fade(kWaitingFastDuration); + } + } } void OverlayWidget::initThemePreview() { @@ -2026,13 +2139,16 @@ void OverlayWidget::playbackControlsFromFullScreen() { void OverlayWidget::playbackPauseResume() { Expects(_streamed != nullptr); + _streamed->resumeOnCallEnd = false; if (const auto item = App::histItemById(_msgid)) { if (_streamed->player.failed()) { displayDocument(_doc, item); } else if (_streamed->player.finished()) { restartAtSeekPosition(0); + } else if (_streamed->player.paused()) { + _streamed->player.resume(); } else { - togglePauseResume(); + _streamed->player.pause(); } } else { clearStreaming(); @@ -2041,32 +2157,28 @@ void OverlayWidget::playbackPauseResume() { } } -void OverlayWidget::togglePauseResume() { - Expects(_streamed != nullptr); - - if (_streamed->player.paused()) { - _streamed->player.resume(); - } else { - _streamed->player.pause(); - } -} - void OverlayWidget::restartAtSeekPosition(crl::time position) { Expects(_streamed != nullptr); _autoplayVideoDocument = _doc; - if (_current.isNull() && videoShown()) { + if (videoShown()) { + _streamed->info.video.cover = videoFrame(); _current = Images::PixmapFast(videoFrame()); + update(contentRect()); } auto options = Streaming::PlaybackOptions(); options.position = position; _streamed->player.play(options); + Media::Player::instance()->pause(AudioMsgId::Type::Voice); + Media::Player::instance()->pause(AudioMsgId::Type::Song); + _streamed->info.audio.state.position = _streamed->info.video.state.position = position; updatePlaybackState(); + playbackWaitingChange(true); } void OverlayWidget::playbackControlsSeekProgress(crl::time position) { @@ -2107,6 +2219,25 @@ void OverlayWidget::playbackToggleFullScreen() { update(); } +void OverlayWidget::playbackPauseOnCall() { + Expects(_streamed != nullptr); + + if (_streamed->player.finished() || _streamed->player.paused()) { + return; + } + _streamed->player.pause(); + _streamed->resumeOnCallEnd = true; +} + +void OverlayWidget::playbackResumeOnCall() { + Expects(_streamed != nullptr); + + if (_streamed->resumeOnCallEnd) { + _streamed->resumeOnCallEnd = false; + _streamed->player.resume(); + } +} + void OverlayWidget::updatePlaybackState() { Expects(_streamed != nullptr); @@ -2464,9 +2595,34 @@ void OverlayWidget::checkGroupThumbsAnimation() { } void OverlayWidget::paintDocRadialLoading(Painter &p, bool radial, float64 radialOpacity) { - float64 o = overLevel(OverIcon); - if (radial || (_doc && !_doc->loaded() && !_streamed)) { - QRect inner(QPoint(_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2), _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)), st::radialSize); + QRect inner(QPoint(_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2), _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)), st::radialSize); + + if (_streamed) { + const auto ms = crl::now(); + _streamed->radial.step(ms); + if (!_streamed->radial.animating()) { + return; + } + const auto fade = _streamed->fading.current( + ms, + _streamed->waiting ? 1. : 0.); + if (fade == 0.) { + if (!_streamed->waiting) { + _streamed->radial.stop(anim::type::instant); + } + return; + } + p.setOpacity(fade); + p.setPen(Qt::NoPen); + p.setBrush(st::msgDateImgBg); + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); + _streamed->radial.draw(p, arc.topLeft(), arc.size(), width());// , st::radialLine, st::radialFg); + } else if (radial || (_doc && !_doc->loaded())) { + float64 o = overLevel(OverIcon); p.setPen(Qt::NoPen); p.setOpacity(_doc->loaded() ? radialOpacity : 1.); @@ -2478,12 +2634,12 @@ void OverlayWidget::paintDocRadialLoading(Painter &p, bool radial, float64 radia } p.setOpacity(1.); - auto icon = ([radial, this]() -> const style::icon* { + auto icon = [&]() -> const style::icon* { if (radial || _doc->loading()) { return &st::historyFileThumbCancel; } return &st::historyFileThumbDownload; - })(); + }(); if (icon) { icon->paintInCenter(p, inner); } @@ -2549,15 +2705,17 @@ void OverlayWidget::keyPressEvent(QKeyEvent *e) { const auto ctrl = e->modifiers().testFlag(Qt::ControlModifier); if (_streamed) { // Ctrl + F for full screen toggle is in eventFilter(). - const auto toggleFull = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) && (e->modifiers().testFlag(Qt::AltModifier) || ctrl); + const auto toggleFull = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) + && (e->modifiers().testFlag(Qt::AltModifier) || ctrl); if (toggleFull) { playbackToggleFullScreen(); return; + } else if (e->key() == Qt::Key_Space) { + playbackPauseResume(); + return; } else if (_fullScreenVideo) { if (e->key() == Qt::Key_Escape) { playbackToggleFullScreen(); - } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { - playbackPauseResume(); } return; } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 79718bdbff..1a90c76cb6 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -161,6 +161,9 @@ private: void playbackControlsFromFullScreen() override; void playbackPauseResume(); void playbackToggleFullScreen(); + void playbackPauseOnCall(); + void playbackResumeOnCall(); + void playbackWaitingChange(bool waiting); void updateOver(QPoint mpos); void moveToScreen(); @@ -227,7 +230,6 @@ private: void updatePlaybackState(); void restartAtSeekPosition(crl::time position); - void togglePauseResume(); void refreshClipControllerGeometry(); void refreshCaptionGeometry(); @@ -237,6 +239,7 @@ private: void createStreamingObjects(); void handleStreamingUpdate(Streaming::Update &&update); void handleStreamingError(Streaming::Error &&error); + void validateStreamedGoodThumbnail(); void initThemePreview(); void destroyThemePreview(); @@ -259,6 +262,7 @@ private: void step_state(crl::time ms, bool timer); void step_radial(crl::time ms, bool timer); + void step_waiting(crl::time ms, bool timer); void zoomIn(); void zoomOut(); diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index e0125ad205..53eb0e2e5f 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -187,6 +187,11 @@ mediaviewGroupWidthMax: 160px; mediaviewGroupSkip: 3px; mediaviewGroupSkipCurrent: 12px; +mediaviewStreamingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + color: radialFg; + thickness: radialLine; +} + themePreviewSize: size(903px, 584px); themePreviewBg: windowBg; themePreviewOverlayOpacity: 0.8; diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 975a4a4530..a1ed998622 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -693,12 +693,12 @@ void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const } auto icon = [&] { - if (showPause) { + if (_data->loading() || _data->uploading()) { + return &(selected ? _st.songCancelSelected : _st.songCancel); + } else if (showPause) { return &(selected ? _st.songPauseSelected : _st.songPause); } else if (_status.size() < 0 || _status.size() == FileStatusSizeLoaded) { return &(selected ? _st.songPlaySelected : _st.songPlay); - } else if (_data->loading()) { - return &(selected ? _st.songCancelSelected : _st.songCancel); } return &(selected ? _st.songDownloadSelected : _st.songDownload); }(); @@ -962,10 +962,10 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con } auto icon = [&] { - if (showPause) { - return &(selected ? _st.songPauseSelected : _st.songPause); - } else if (_data->loading() || _data->uploading()) { + if (_data->loading() || _data->uploading()) { return &(selected ? _st.songCancelSelected : _st.songCancel); + } else if (showPause) { + return &(selected ? _st.songPauseSelected : _st.songPause); } else if (_data->canBePlayed()) { return &(selected ? _st.songPlaySelected : _st.songPlay); } @@ -1107,7 +1107,7 @@ TextState Document::getState( ? _cancell : _data->canBePlayed() ? _openl - : _openl; + : _savel; return { parent(), link }; } const auto namerect = rtlrect( @@ -1137,10 +1137,10 @@ TextState Document::getState( _width); if (rthumb.contains(point)) { - const auto link = loaded - ? _openl - : (_data->loading() || _data->uploading()) + const auto link = (_data->loading() || _data->uploading()) ? _cancell + : loaded + ? _openl : _savel; return { parent(), link }; } diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 2ad8df6e5e..ebe9ea3622 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -201,7 +201,7 @@ void Uploader::upload( std::move(file->thumb)); document->uploadingData = std::make_unique( document->size); - document->setGoodThumbnail( + document->setGoodThumbnailOnUpload( std::move(file->goodThumbnail), std::move(file->goodThumbnailBytes)); if (!file->content.isEmpty()) { diff --git a/Telegram/SourceFiles/ui/effects/radial_animation.cpp b/Telegram/SourceFiles/ui/effects/radial_animation.cpp index f6ec473b29..bedd6f914c 100644 --- a/Telegram/SourceFiles/ui/effects/radial_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/radial_animation.cpp @@ -127,9 +127,9 @@ void InfiniteRadialAnimation::start(crl::time skip) { } } -void InfiniteRadialAnimation::stop() { +void InfiniteRadialAnimation::stop(anim::type animated) { const auto now = crl::now(); - if (anim::Disabled()) { + if (anim::Disabled() || animated == anim::type::instant) { _workFinished = now; } if (!_workFinished) { diff --git a/Telegram/SourceFiles/ui/effects/radial_animation.h b/Telegram/SourceFiles/ui/effects/radial_animation.h index 43a9b52ebd..c522acf5d1 100644 --- a/Telegram/SourceFiles/ui/effects/radial_animation.h +++ b/Telegram/SourceFiles/ui/effects/radial_animation.h @@ -70,7 +70,7 @@ public: } void start(crl::time skip = 0); - void stop(); + void stop(anim::type animated = anim::type::normal); void step(crl::time ms); void step() {