From 6887993f921fdc646ee2b7b9bd865f947dadd7d8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Mar 2019 17:56:27 +0400 Subject: [PATCH] Report streaming failed. --- Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/data/data_document.cpp | 88 +++++++++++-------- Telegram/SourceFiles/data/data_document.h | 14 +-- Telegram/SourceFiles/mainwidget.cpp | 1 - .../media/player/media_player_instance.cpp | 77 ++++++++-------- .../media/player/media_player_instance.h | 4 +- .../streaming/media_streaming_audio_track.cpp | 6 +- .../streaming/media_streaming_audio_track.h | 4 +- .../media/streaming/media_streaming_common.h | 6 +- .../media/streaming/media_streaming_file.cpp | 52 ++++++----- .../media/streaming/media_streaming_file.h | 14 ++- .../streaming/media_streaming_file_delegate.h | 3 +- .../streaming/media_streaming_player.cpp | 45 ++++++---- .../media/streaming/media_streaming_player.h | 14 +-- .../streaming/media_streaming_reader.cpp | 49 +++++++---- .../media/streaming/media_streaming_reader.h | 14 +-- .../streaming/media_streaming_video_track.cpp | 16 ++-- .../streaming/media_streaming_video_track.h | 2 +- .../media/view/media_view_overlay_widget.cpp | 48 +++++----- .../media/view/media_view_overlay_widget.h | 2 +- 20 files changed, 268 insertions(+), 193 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d1c6825b06..d9361da9e2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1492,7 +1492,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_player_message_today" = "Today at {time}"; "lng_player_message_yesterday" = "Yesterday at {time}"; "lng_player_message_date" = "{date} at {time}"; -"lng_player_cant_play" = "This file can't be played in Telegram Desktop.\n\nWould you like to download it and open it in an external player?"; +"lng_player_cant_stream" = "This file can't be played before it is fully downloaded.\n\nWould you like to download it?"; "lng_player_download" = "Download"; "lng_rights_edit_admin" = "Manage permissions"; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 7f439980df..246b91f7b6 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -301,9 +301,6 @@ void DocumentOpenClickHandler::Open( Core::App().showDocument(data, context); location.accessDisable(); return; - } else if (data->inappPlaybackFailed()) { - ::Data::HandleUnsupportedMedia(data, msgId); - return; } else if (data->canBePlayed()) { if (data->isAudioFile() || data->isVoiceMessage() @@ -444,7 +441,7 @@ AuthSession &DocumentData::session() const { void DocumentData::setattributes( const QVector &attributes) { _isImage = false; - _supportsStreaming = false; + _supportsStreaming = SupportsStreaming::Unknown; for (const auto &attribute : attributes) { attribute.match([&](const MTPDdocumentAttributeImageSize & data) { dimensions = QSize(data.vw.v, data.vh.v); @@ -474,7 +471,7 @@ void DocumentData::setattributes( : VideoDocument; } _duration = data.vduration.v; - _supportsStreaming = data.is_supports_streaming(); + setMaybeSupportsStreaming(data.is_supports_streaming()); dimensions = QSize(data.vw.v, data.vh.v); }, [&](const MTPDdocumentAttributeAudio & data) { if (type == FileDocument) { @@ -531,6 +528,11 @@ void DocumentData::setattributes( } } validateGoodThumbnail(); + if (isAudioFile() + || (isAnimation() && !isVideoMessage()) + || isVoiceMessage()) { + setMaybeSupportsStreaming(true); + } } bool DocumentData::checkWallPaperProperties() { @@ -1192,16 +1194,15 @@ bool DocumentData::hasRemoteLocation() const { } bool DocumentData::canBeStreamed() const { - return hasRemoteLocation() - && (isAudioFile() - || ((isAnimation() || isVideoFile()) && supportsStreaming())); + return hasRemoteLocation() && supportsStreaming(); } bool DocumentData::canBePlayed() const { - return (isAnimation() - || isVideoFile() - || isAudioFile() - || isVoiceMessage()) + return !_inappPlaybackFailed + && (isAnimation() + || isVideoFile() + || isAudioFile() + || isVoiceMessage()) && (loaded() || canBeStreamed()); } @@ -1405,7 +1406,20 @@ bool DocumentData::isImage() const { } bool DocumentData::supportsStreaming() const { - return _supportsStreaming; + return (_supportsStreaming == SupportsStreaming::MaybeYes); +} + +void DocumentData::setNotSupportsStreaming() { + _supportsStreaming = SupportsStreaming::No; +} + +void DocumentData::setMaybeSupportsStreaming(bool supports) { + if (_supportsStreaming == SupportsStreaming::No) { + return; + } + _supportsStreaming = supports + ? SupportsStreaming::MaybeYes + : SupportsStreaming::MaybeNo; } void DocumentData::recountIsImage() { @@ -1587,28 +1601,30 @@ base::binary_guard ReadImageAsync( return std::move(right); } -void HandleUnsupportedMedia( - not_null document, - FullMsgId contextId) { - document->setInappPlaybackFailed(); - const auto filepath = document->filepath( - DocumentData::FilePathResolveSaveFromData); - if (filepath.isEmpty()) { - const auto save = [=] { - Ui::hideLayer(); - DocumentSaveClickHandler::Save( - (contextId ? contextId : Data::FileOrigin()), - document, - App::histItemById(contextId)); - }; - Ui::show(Box( - lang(lng_player_cant_play), - lang(lng_player_download), - lang(lng_cancel), - save)); - } else if (IsValidMediaFile(filepath)) { - File::Launch(filepath); - } -} +//void HandleUnsupportedMedia( +// not_null document, +// FullMsgId contextId) { +// using Error = ::Media::Streaming::Error; +// +// document->setInappPlaybackFailed(); +// const auto filepath = document->filepath( +// DocumentData::FilePathResolveSaveFromData); +// if (filepath.isEmpty()) { +// const auto save = [=] { +// Ui::hideLayer(); +// DocumentSaveClickHandler::Save( +// (contextId ? contextId : Data::FileOrigin()), +// document, +// App::histItemById(contextId)); +// }; +// Ui::show(Box( +// lang(lng_player_cant_stream), +// lang(lng_player_download), +// lang(lng_cancel), +// save)); +// } else if (IsValidMediaFile(filepath)) { +// File::Launch(filepath); +// } +//} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 2c284bfc9e..fe60930474 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -167,6 +167,7 @@ public: [[nodiscard]] bool isImage() const; void recountIsImage(); [[nodiscard]] bool supportsStreaming() const; + void setNotSupportsStreaming(); void setData(const QByteArray &data) { _data = data; } @@ -244,10 +245,17 @@ public: std::unique_ptr uploadingData; private: + enum class SupportsStreaming : uchar { + Unknown, + MaybeYes, + MaybeNo, + No, + }; friend class Serialize::Document; LocationType locationType() const; void validateGoodThumbnail(); + void setMaybeSupportsStreaming(bool supports); void destroyLoader(mtpFileLoader *newValue = nullptr) const; @@ -274,7 +282,7 @@ private: std::unique_ptr _additional; int32 _duration = -1; bool _isImage = false; - bool _supportsStreaming = false; + SupportsStreaming _supportsStreaming = SupportsStreaming::Unknown; bool _inappPlaybackFailed = false; ActionOnLoad _actionOnLoad = ActionOnLoadNone; @@ -397,8 +405,4 @@ base::binary_guard ReadImageAsync( FnMut postprocess, FnMut done); -void HandleUnsupportedMedia( - not_null document, - FullMsgId contextId); - } // namespace Data diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index db1d3f6814..c577d8013d 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1177,7 +1177,6 @@ void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) { if (!Media::Player::IsStoppedOrStopping(state.state)) { createPlayer(); } else if (state.state == State::StoppedAtStart) { - Data::HandleUnsupportedMedia(document, state.id.contextId()); closeBothPlayers(); } diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 59ffa5a05b..5ba7a8a2bb 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -134,19 +134,18 @@ void Instance::setCurrent(const AudioMsgId &audioId) { if (data->current == audioId) { return; } - const auto trackChanged = (data->current.audio() != audioId.audio()) - || (data->current.contextId() != audioId.contextId()); + const auto changed = [&](const AudioMsgId & check) { + return (check.audio() != audioId.audio()) + || (check.contextId() != audioId.contextId()); + }; + const auto trackChanged = changed(data->current); + if (trackChanged && data->streamed && changed(data->streamed->id)) { + clearStreamed(data); + } data->current = audioId; if (!trackChanged) { return; } - const auto streamedId = data->streamed - ? data->streamed->id - : AudioMsgId(); - if (streamedId.audio() != audioId.audio() - || streamedId.contextId() != audioId.contextId()) { - data->streamed = nullptr; - } data->current = audioId; data->isPlaying = false; @@ -165,6 +164,16 @@ void Instance::setCurrent(const AudioMsgId &audioId) { } } +void Instance::clearStreamed(not_null data) { + if (!data->streamed) { + return; + } + data->streamed->player.stop(); + data->isPlaying = false; + emitUpdate(data->type); + data->streamed = nullptr; +} + void Instance::refreshPlaylist(not_null data) { if (!validPlaylist(data)) { validatePlaylist(data); @@ -386,6 +395,9 @@ void Instance::playStreamed( const auto data = getData(audioId.type()); Assert(data != nullptr); + if (data->streamed) { + clearStreamed(data); + } data->streamed = std::make_unique( audioId, &audioId.audio()->owner(), @@ -607,38 +619,10 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) { _tracksFinishedNotifier.notify(type); } } - auto isPlaying = !IsStopped(state.state); - if (data->isPlaying != isPlaying) { - data->isPlaying = isPlaying; - if (data->isPlaying) { - preloadNext(data); - } - } + data->isPlaying = !IsStopped(state.state); } } -void Instance::preloadNext(not_null data) { - //if (!data->current || !data->playlistSlice || !data->playlistIndex) { - // return; - //} - //const auto nextIndex = *data->playlistIndex + 1; - //if (const auto item = itemByIndex(data, nextIndex)) { - // if (const auto media = item->media()) { - // if (const auto document = media->document()) { - // const auto isLoaded = document->loaded( - // DocumentData::FilePathResolveSaveFromDataSilent); - // if (!isLoaded) { - // DocumentOpenClickHandler::Open( - // item->fullId(), - // document, - // item, - // ActionOnLoadNone); - // } - // } - // } - //} -} - void Instance::handleLogout() { const auto reset = [&](AudioMsgId::Type type) { const auto data = getData(type); @@ -712,6 +696,23 @@ void Instance::handleStreamingUpdate( void Instance::handleStreamingError( not_null data, Streaming::Error &&error) { + Expects(data->streamed != nullptr); + + const auto document = data->streamed->id.audio(); + const auto contextId = data->streamed->id.contextId(); + if (error == Streaming::Error::NotStreamable) { + document->setNotSupportsStreaming(); + DocumentSaveClickHandler::Save( + (contextId ? contextId : ::Data::FileOrigin()), + document, + App::histItemById(contextId)); + } else if (error == Streaming::Error::OpenFailed) { + document->setInappPlaybackFailed(); + DocumentSaveClickHandler::Save( + (contextId ? contextId : ::Data::FileOrigin()), + document, + App::histItemById(contextId)); + } emitUpdate(data->type); if (data->streamed && data->streamed->player.failed()) { data->streamed = nullptr; diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 2f8b8e552c..5b58da81cc 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -19,7 +19,7 @@ namespace Streaming { class Loader; struct PlaybackOptions; struct Update; -struct Error; +enum class Error; } // namespace Streaming } // namespace Media @@ -188,7 +188,6 @@ private: void validatePlaylist(not_null data); void playlistUpdated(not_null data); bool moveInPlaylist(not_null data, int delta, bool autonext); - void preloadNext(not_null data); HistoryItem *itemByIndex(not_null data, int index); void handleStreamingUpdate( @@ -198,6 +197,7 @@ private: not_null data, Streaming::Error &&error); + void clearStreamed(not_null data); void emitUpdate(AudioMsgId::Type type); template void emitUpdate(AudioMsgId::Type type, CheckCallback check); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp index acb4b05fc0..01ba767a4e 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp @@ -20,7 +20,7 @@ AudioTrack::AudioTrack( Stream &&stream, AudioMsgId audioId, FnMut ready, - Fn error) + Fn error) : _options(options) , _stream(std::move(stream)) , _audioId(audioId) @@ -51,7 +51,7 @@ void AudioTrack::process(Packet &&packet) { if (initialized()) { mixerEnqueue(std::move(packet)); } else if (!tryReadFirstFrame(std::move(packet))) { - _error(); + _error(Error::InvalidData); } } @@ -188,7 +188,7 @@ rpl::producer AudioTrack::playPosition() { return; case State::StoppedAtError: case State::StoppedAtStart: - _error(); + _error(Error::InvalidData); return; case State::Starting: case State::Playing: diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h index b930074785..c6b45d28ff 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h @@ -21,7 +21,7 @@ public: Stream &&stream, AudioMsgId audioId, FnMut ready, - Fn error); + Fn error); // Called from the main thread. // Must be called after 'ready' was invoked. @@ -69,7 +69,7 @@ private: // Assumed to be thread-safe. FnMut _ready; - const Fn _error; + const Fn _error; // First set from the same unspecified thread before _ready is called. // After that is immutable. diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_common.h b/Telegram/SourceFiles/media/streaming/media_streaming_common.h index 0bc7e448d5..50baea6d5f 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_common.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_common.h @@ -100,7 +100,11 @@ struct Update { Finished> data; }; -struct Error { +enum class Error { + OpenFailed, + LoadFailed, + InvalidData, + NotStreamable, }; struct FrameRequest { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp index 6048ba3cc9..6bbd561e65 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp @@ -44,8 +44,8 @@ int File::Context::read(bytes::span buffer) { _semaphore.acquire(); if (_interrupted) { return -1; - } else if (_reader->failed()) { - fail(); + } else if (const auto error = _reader->failed()) { + fail(*error); return -1; } } @@ -84,21 +84,23 @@ void File::Context::logError(QLatin1String method, AvErrorWrap error) { void File::Context::logFatal(QLatin1String method) { if (!unroll()) { LogError(method); - fail(); + fail(_format ? Error::InvalidData : Error::OpenFailed); } } void File::Context::logFatal(QLatin1String method, AvErrorWrap error) { if (!unroll()) { LogError(method, error); - fail(); + fail(_format ? Error::InvalidData : Error::OpenFailed); } } -Stream File::Context::initStream(AVMediaType type) { +Stream File::Context::initStream( + not_null format, + AVMediaType type) { auto result = Stream(); const auto index = result.index = av_find_best_stream( - _format.get(), + format, type, -1, -1, @@ -108,7 +110,7 @@ Stream File::Context::initStream(AVMediaType type) { return {}; } - const auto info = _format->streams[index]; + const auto info = format->streams[index]; if (type == AVMEDIA_TYPE_VIDEO) { result.rotation = ReadRotationFromMetadata(info); result.aspect = ValidateAspectRatio(info->sample_aspect_ratio); @@ -131,7 +133,7 @@ Stream File::Context::initStream(AVMediaType type) { result.timeBase = info->time_base; result.duration = (info->duration != AV_NOPTS_VALUE) ? PtsToTime(info->duration, result.timeBase) - : PtsToTime(_format->duration, kUniversalTimeBase); + : PtsToTime(format->duration, kUniversalTimeBase); if (result.duration == kTimeUnknown || !result.duration) { return {}; } @@ -142,6 +144,7 @@ Stream File::Context::initStream(AVMediaType type) { } void File::Context::seekToPosition( + not_null format, const Stream &stream, crl::time position) { auto error = AvErrorWrap(); @@ -155,7 +158,7 @@ void File::Context::seekToPosition( // //const auto seekFlags = 0; //error = av_seek_frame( - // _format, + // format, // streamIndex, // TimeToPts(position, kUniversalTimeBase), // seekFlags); @@ -164,7 +167,7 @@ void File::Context::seekToPosition( //} // error = av_seek_frame( - _format.get(), + format, stream.index, TimeToPts( std::clamp(position, crl::time(0), stream.duration - 1), @@ -197,43 +200,41 @@ void File::Context::start(crl::time position) { if (unroll()) { return; } - _format = MakeFormatPointer( + auto format = MakeFormatPointer( static_cast(this), &Context::Read, nullptr, &Context::Seek); - if (!_format) { - return fail(); + if (!format) { + return fail(Error::OpenFailed); } - if ((error = avformat_find_stream_info(_format.get(), nullptr))) { + if ((error = avformat_find_stream_info(format.get(), nullptr))) { return logFatal(qstr("avformat_find_stream_info"), error); } - auto video = initStream(AVMEDIA_TYPE_VIDEO); + auto video = initStream(format.get(), AVMEDIA_TYPE_VIDEO); if (unroll()) { return; } - auto audio = initStream(AVMEDIA_TYPE_AUDIO); + auto audio = initStream(format.get(), AVMEDIA_TYPE_AUDIO); if (unroll()) { return; } _reader->headerDone(); - _totalDuration = std::max( - video.codec ? video.duration : kTimeUnknown, - audio.codec ? audio.duration : kTimeUnknown); if (video.codec || audio.codec) { - seekToPosition(video.codec ? video : audio, position); + seekToPosition(format.get(), video.codec ? video : audio, position); } if (unroll()) { return; } if (!_delegate->fileReady(std::move(video), std::move(audio))) { - return fail(); + return fail(Error::OpenFailed); } + _format = std::move(format); } void File::Context::readNextPacket() { @@ -293,15 +294,14 @@ bool File::Context::unroll() const { return failed() || interrupted(); } -void File::Context::fail() { +void File::Context::fail(Error error) { _failed = true; - _delegate->fileError(); + _delegate->fileError(error); } File::Context::~Context() = default; bool File::Context::finished() const { - // #TODO streaming later looping return unroll() || _readTillEnd; } @@ -338,6 +338,10 @@ void File::stop() { _context.reset(); } +bool File::isRemoteLoader() const { + return _reader.isRemoteLoader(); +} + File::~File() { stop(); } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file.h b/Telegram/SourceFiles/media/streaming/media_streaming_file.h index 20f5b3a5d7..38a4c687e9 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file.h @@ -36,6 +36,8 @@ public: void wake(); void stop(); + [[nodiscard]] bool isRemoteLoader() const; + ~File(); private: @@ -66,10 +68,15 @@ private: void logError(QLatin1String method, AvErrorWrap error); void logFatal(QLatin1String method); void logFatal(QLatin1String method, AvErrorWrap error); - void fail(); + void fail(Error error); - Stream initStream(AVMediaType type); - void seekToPosition(const Stream &stream, crl::time position); + Stream initStream( + not_null format, + AVMediaType type); + void seekToPosition( + not_null format, + const Stream &stream, + crl::time position); // TODO base::expected. [[nodiscard]] base::variant readPacket(); @@ -88,7 +95,6 @@ private: std::atomic _interrupted = false; FormatPointer _format; - crl::time _totalDuration = kTimeUnknown; }; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file_delegate.h b/Telegram/SourceFiles/media/streaming/media_streaming_file_delegate.h index 9eccbc29be..475846127d 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file_delegate.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file_delegate.h @@ -12,13 +12,14 @@ namespace Streaming { struct Stream; class Packet; +enum class Error; class FileDelegate { public: [[nodiscard]] virtual bool fileReady( Stream &&video, Stream &&audio) = 0; - virtual void fileError() = 0; + virtual void fileError(Error error) = 0; virtual void fileWaitingForData() = 0; // Return true if reading and processing more packets is desired. diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index d623507265..7d2891d8d5 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -19,7 +19,8 @@ namespace Streaming { namespace { constexpr auto kBufferFor = 3 * crl::time(1000); -constexpr auto kLoadInAdvanceFor = 64 * crl::time(1000); +constexpr auto kLoadInAdvanceForRemote = 64 * crl::time(1000); +constexpr auto kLoadInAdvanceForLocal = 5 * crl::time(1000); constexpr auto kMsFrequency = 1000; // 1000 ms per second. // If we played for 3 seconds and got stuck it looks like we're loading @@ -79,6 +80,7 @@ Player::Player( not_null owner, std::unique_ptr loader) : _file(std::make_unique(owner, std::move(loader))) +, _remoteLoader(_file->isRemoteLoader()) , _renderFrameTimer([=] { checkNextFrame(); }) { } @@ -123,7 +125,7 @@ void Player::trackReceivedTill( state.receivedTill = position; } if (!_pauseReading - && bothReceivedEnough(kLoadInAdvanceFor) + && bothReceivedEnough(loadInAdvanceFor()) && !receivedTillEnd()) { _pauseReading = true; } @@ -145,7 +147,7 @@ void Player::trackPlayedTill( _updates.fire({ PlaybackUpdate{ value } }); } if (_pauseReading - && (!bothReceivedEnough(kLoadInAdvanceFor) || receivedTillEnd())) { + && (!bothReceivedEnough(loadInAdvanceFor()) || receivedTillEnd())) { _pauseReading = false; _file->wake(); ++wakes; @@ -204,10 +206,10 @@ bool Player::fileReady(Stream &&video, Stream &&audio) { }); }; const auto error = [&](auto &stream) { - return [=, &stream] { + return [=, &stream](Error error) { crl::on_main(weak, [=, &stream] { stream = nullptr; - streamFailed(); + streamFailed(error); }); }; }; @@ -253,11 +255,11 @@ bool Player::fileReady(Stream &&video, Stream &&audio) { return true; } -void Player::fileError() { +void Player::fileError(Error error) { _waitingForData = false; crl::on_main(&_sessionGuard, [=] { - fail(); + fail(error); }); } @@ -331,11 +333,11 @@ void Player::streamReady(Information &&information) { provideStartInformation(); } -void Player::streamFailed() { +void Player::streamFailed(Error error) { if (_stage == Stage::Initializing) { provideStartInformation(); } else { - fail(); + fail(error); } } @@ -348,7 +350,7 @@ void Player::provideStartInformation() { } else if ((!_audio && !_video) || (!_audio && _options.mode == Mode::Audio) || (!_video && _options.mode == Mode::Video)) { - fail(); + fail(Error::OpenFailed); } else { _stage = Stage::Ready; @@ -365,11 +367,11 @@ void Player::provideStartInformation() { } } -void Player::fail() { +void Player::fail(Error error) { _sessionLifetime = rpl::lifetime(); const auto stopGuarded = crl::guard(&_sessionGuard, [=] { stop(); }); - _lastFailureStage = _stage; - _updates.fire_error({}); + _lastFailure = error; + _updates.fire_error(std::move(error)); stopGuarded(); } @@ -382,7 +384,7 @@ void Player::play(const PlaybackOptions &options) { const auto previous = getCurrentReceivedTill(); stop(); - _lastFailureStage = Stage::Uninitialized; + _lastFailure = std::nullopt; savePreviousReceivedTill(options, previous); _options = options; @@ -404,6 +406,10 @@ void Player::savePreviousReceivedTill( : kTimeUnknown; } +crl::time Player::loadInAdvanceFor() const { + return _remoteLoader ? kLoadInAdvanceForRemote : kLoadInAdvanceForLocal; +} + void Player::pause() { Expects(active()); @@ -560,8 +566,8 @@ void Player::stop() { _information = Information(); } -bool Player::failed() const { - return (_lastFailureStage != Stage::Uninitialized); +std::optional Player::failed() const { + return _lastFailure; } bool Player::playing() const { @@ -626,10 +632,11 @@ Media::Player::TrackState Player::prepareLegacyState() const { auto result = Media::Player::TrackState(); result.id = _audioId.externalPlayId() ? _audioId : _options.audioId; - result.state = (_lastFailureStage == Stage::Started) - ? State::StoppedAtError - : failed() + result.state = (_lastFailure == Error::OpenFailed + || _lastFailure == Error::NotStreamable) ? State::StoppedAtStart + : _lastFailure + ? State::StoppedAtError : finished() ? State::StoppedAtEnd : paused() diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.h b/Telegram/SourceFiles/media/streaming/media_streaming_player.h index 02f4ffae28..8981ea06a8 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.h @@ -53,7 +53,7 @@ public: [[nodiscard]] bool playing() const; [[nodiscard]] bool buffering() const; [[nodiscard]] bool paused() const; - [[nodiscard]] bool failed() const; + [[nodiscard]] std::optional failed() const; [[nodiscard]] bool finished() const; [[nodiscard]] rpl::producer updates() const; @@ -80,17 +80,17 @@ private: // FileDelegate methods are called only from the File thread. bool fileReady(Stream &&video, Stream &&audio) override; - void fileError() override; + void fileError(Error error) override; void fileWaitingForData() override; bool fileProcessPacket(Packet &&packet) override; bool fileReadMore() override; // Called from the main thread. void streamReady(Information &&information); - void streamFailed(); + void streamFailed(Error error); void start(); void provideStartInformation(); - void fail(); + void fail(Error error); void checkNextFrame(); void renderFrame(crl::time now); void audioReceivedTill(crl::time position); @@ -109,6 +109,7 @@ private: void savePreviousReceivedTill( const PlaybackOptions &options, crl::time previousReceivedTill); + [[nodiscard]] crl::time loadInAdvanceFor() const; template void trackReceivedTill( @@ -150,12 +151,13 @@ private: // Belongs to the main thread. Information _information; Stage _stage = Stage::Uninitialized; - Stage _lastFailureStage = Stage::Uninitialized; + std::optional _lastFailure; bool _pausedByUser = false; bool _pausedByWaitingForData = false; bool _paused = false; bool _audioFinished = false; bool _videoFinished = false; + bool _remoteLoader = false; crl::time _startedTime = kTimeUnknown; crl::time _pausedTime = kTimeUnknown; @@ -163,7 +165,7 @@ private: base::Timer _renderFrameTimer; rpl::event_stream _updates; - crl::time _totalDuration = 0; + crl::time _totalDuration = kTimeUnknown; crl::time _loopingShift = 0; crl::time _previousReceivedTill = kTimeUnknown; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp index 3ce075bf93..b8b8bc6fac 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/streaming/media_streaming_reader.h" +#include "media/streaming/media_streaming_common.h" #include "media/streaming/media_streaming_loader.h" #include "storage/cache/storage_cache_database.h" #include "data/data_session.h" @@ -18,11 +19,14 @@ namespace { constexpr auto kPartSize = Loader::kPartSize; constexpr auto kPartsInSlice = 64; constexpr auto kInSlice = kPartsInSlice * kPartSize; -constexpr auto kMaxPartsInHeader = 72; -constexpr auto kMaxInHeader = kMaxPartsInHeader * kPartSize; +constexpr auto kMaxPartsInHeader = 16; constexpr auto kMaxOnlyInHeader = 80 * kPartSize; constexpr auto kSlicesInMemory = 2; +// 1 MB of header parts can be outside the first slice for us to still +// put the whole first slice of the file in the header cache entry. +//constexpr auto kMaxOutsideHeaderPartsForOptimizedMode = 8; + // 1 MB of parts are requested from cloud ahead of reading demand. constexpr auto kPreloadPartsAhead = 8; @@ -286,6 +290,11 @@ void Reader::Slices::headerDone(bool fromCache) { } } +bool Reader::Slices::headerWontBeFilled() const { + return (_headerMode == HeaderMode::Unknown) + && (_header.parts.size() == kMaxPartsInHeader); +} + void Reader::Slices::applyHeaderCacheData() { if (_header.parts.empty()) { return; @@ -318,21 +327,21 @@ bool Reader::Slices::processCacheResult( return success; } -bool Reader::Slices::processPart(int offset, QByteArray &&bytes) { +std::optional Reader::Slices::processPart( + int offset, + QByteArray &&bytes) { Expects(offset / kInSlice < _data.size()); const auto index = offset / kInSlice; if (_headerMode == HeaderMode::Unknown) { if (_header.parts.contains(offset)) { - return true; - } else if (_header.parts.size() == kMaxPartsInHeader) { - // #TODO streaming later unavailable, full load? - return false; + return {}; + } else if (_header.parts.size() < kMaxPartsInHeader) { + _header.addPart(offset, bytes); } - _header.addPart(offset, bytes); } _data[index].addPart(offset - index * kInSlice, std::move(bytes)); - return true; + return {}; } auto Reader::Slices::fill(int offset, bytes::span buffer) -> FillResult { @@ -544,6 +553,10 @@ void Reader::stop() { _waiting = nullptr; } +bool Reader::isRemoteLoader() const { + return _loader->baseCacheKey().has_value(); +} + std::shared_ptr Reader::InitCacheHelper( std::optional baseKey) { if (!baseKey) { @@ -584,7 +597,7 @@ int Reader::size() const { return _loader->size(); } -bool Reader::failed() const { +std::optional Reader::failed() const { return _failed; } @@ -652,6 +665,10 @@ bool Reader::fillFromSlices(int offset, bytes::span buffer) { using namespace rpl::mappers; auto result = _slices.fill(offset, buffer); + if (!result.filled && _slices.headerWontBeFilled()) { + _failed = Error::NotStreamable; + return false; + } for (const auto sliceNumber : result.sliceNumbersFromCache.values()) { readFromCache(sliceNumber); @@ -724,14 +741,16 @@ bool Reader::processLoadedParts() { if (part.offset == LoadedPart::kFailedOffset || (part.bytes.size() != Loader::kPartSize && part.offset + part.bytes.size() != size())) { - _failed = true; + _failed = Error::LoadFailed; return false; } else if (!_loadingOffsets.remove(part.offset)) { continue; - } else if (!_slices.processPart( - part.offset, - std::move(part.bytes))) { - _failed = true; + } + const auto error = _slices.processPart( + part.offset, + std::move(part.bytes)); + if (error) { + _failed = *error; return false; } } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h index b37e59d069..c71cca8ddd 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h @@ -25,22 +25,25 @@ namespace Streaming { class Loader; struct LoadedPart; +enum class Error; class Reader final { public: Reader(not_null owner, std::unique_ptr loader); - int size() const; - bool fill( + [[nodiscard]] int size() const; + [[nodiscard]] bool fill( int offset, bytes::span buffer, not_null notify); - bool failed() const; + [[nodiscard]] std::optional failed() const; void headerDone(); void stop(); + [[nodiscard]] bool isRemoteLoader() const; + ~Reader(); private: @@ -111,9 +114,10 @@ private: Slices(int size, bool useCache); void headerDone(bool fromCache); + bool headerWontBeFilled() const; bool processCacheResult(int sliceNumber, QByteArray &&result); - bool processPart(int offset, QByteArray &&bytes); + std::optional processPart(int offset, QByteArray &&bytes); FillResult fill(int offset, bytes::span buffer); SerializedSlice unloadToCache(); @@ -167,7 +171,7 @@ private: PriorityQueue _loadingOffsets; Slices _slices; - bool _failed = false; + std::optional _failed; rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp index fdfa682479..7abd1c3493 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp @@ -31,7 +31,7 @@ public: Stream &&stream, const AudioMsgId &audioId, FnMut ready, - Fn error); + Fn error); void process(Packet &&packet); @@ -80,7 +80,7 @@ private: AudioMsgId _audioId; bool _noMoreData = false; FnMut _ready; - Fn _error; + Fn _error; crl::time _pausedTime = kTimeUnknown; crl::time _resumedTime = kTimeUnknown; mutable TimePoint _syncTimePoint; @@ -103,7 +103,7 @@ VideoTrackObject::VideoTrackObject( Stream &&stream, const AudioMsgId &audioId, FnMut ready, - Fn error) + Fn error) : _weak(std::move(weak)) , _options(options) , _shared(shared) @@ -141,7 +141,7 @@ void VideoTrackObject::process(Packet &&packet) { _stream.queue.push_back(std::move(packet)); queueReadFrames(); } else if (!tryReadFirstFrame(std::move(packet))) { - _error(); + _error(Error::InvalidData); } } @@ -209,7 +209,7 @@ auto VideoTrackObject::readFrame(not_null frame) -> FrameResult { } } else if (error.code() != AVERROR(EAGAIN) || _noMoreData) { interrupt(); - _error(); + _error(Error::InvalidData); return FrameResult::Error; } Assert(_stream.queue.empty()); @@ -220,7 +220,7 @@ auto VideoTrackObject::readFrame(not_null frame) -> FrameResult { LOG(("GOT FRAME: %1 (queue %2)").arg(position).arg(_stream.queue.size())); if (position == kTimeUnknown) { interrupt(); - _error(); + _error(Error::InvalidData); return FrameResult::Error; } std::swap(frame->decoded, _stream.frame); @@ -244,7 +244,7 @@ void VideoTrackObject::presentFrameIfNeeded() { if (frame->original.isNull()) { frame->prepared = QImage(); interrupt(); - _error(); + _error(Error::InvalidData); return; } @@ -581,7 +581,7 @@ VideoTrack::VideoTrack( Stream &&stream, const AudioMsgId &audioId, FnMut ready, - Fn error) + Fn error) : _streamIndex(stream.index) , _streamTimeBase(stream.timeBase) , _streamDuration(stream.duration) diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h index f5215219e2..637d235b18 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h @@ -25,7 +25,7 @@ public: Stream &&stream, const AudioMsgId &audioId, FnMut ready, - Fn error); + Fn error); // Thread-safe. [[nodiscard]] int streamIndex() const; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 48b40e8c81..3116bea975 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -163,6 +163,7 @@ struct OverlayWidget::Streamed { base::Timer timer; bool resumeOnCallEnd = false; + std::optional lastError; }; OverlayWidget::Streamed::Streamed( @@ -327,7 +328,7 @@ bool OverlayWidget::videoIsGifv() const { QImage OverlayWidget::videoFrame() const { Expects(videoShown()); - auto request = Media::Streaming::FrameRequest(); + auto request = Streaming::FrameRequest(); //request.radius = (_doc && _doc->isVideoMessage()) // ? ImageRoundRadius::Ellipse // : ImageRoundRadius::None; @@ -893,7 +894,7 @@ void OverlayWidget::showSaveMsgFile() { void OverlayWidget::updateMixerVideoVolume() const { if (_streamed) { - Media::Player::mixer()->setVideoVolume(Global::VideoVolume()); + Player::mixer()->setVideoVolume(Global::VideoVolume()); } } @@ -1514,19 +1515,19 @@ void OverlayWidget::refreshCaption(HistoryItem *item) { void OverlayWidget::refreshGroupThumbs() { const auto existed = (_groupThumbs != nullptr); if (_index && _sharedMediaData) { - Media::View::GroupThumbs::Refresh( + View::GroupThumbs::Refresh( _groupThumbs, *_sharedMediaData, *_index, _groupThumbsAvailableWidth); } else if (_index && _userPhotosData) { - Media::View::GroupThumbs::Refresh( + View::GroupThumbs::Refresh( _groupThumbs, *_userPhotosData, *_index, _groupThumbsAvailableWidth); } else if (_index && _collageData) { - Media::View::GroupThumbs::Refresh( + View::GroupThumbs::Refresh( _groupThumbs, { _msgid, &*_collageData }, *_index, @@ -1555,8 +1556,8 @@ void OverlayWidget::initGroupThumbs() { }, _groupThumbs->lifetime()); _groupThumbs->activateRequests( - ) | rpl::start_with_next([this](Media::View::GroupThumbs::Key key) { - using CollageKey = Media::View::GroupThumbs::CollageKey; + ) | rpl::start_with_next([this](View::GroupThumbs::Key key) { + using CollageKey = View::GroupThumbs::CollageKey; if (const auto photoId = base::get_if(&key)) { const auto photo = Auth().data().photo(*photoId); moveToEntity({ photo, nullptr }); @@ -2027,7 +2028,7 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) { playbackWaitingChange(update.waiting); }, [&](MutedByOther) { }, [&](Finished) { - const auto finishTrack = [](Media::Streaming::TrackState &state) { + const auto finishTrack = [](Streaming::TrackState &state) { state.position = state.receivedTill = state.duration; }; finishTrack(_streamed->info.audio.state); @@ -2037,8 +2038,19 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) { } void OverlayWidget::handleStreamingError(Streaming::Error &&error) { - playbackWaitingChange(false); - updatePlaybackState(); + if (error == Streaming::Error::NotStreamable) { + _doc->setNotSupportsStreaming(); + } else if (error == Streaming::Error::OpenFailed) { + _doc->setInappPlaybackFailed(); + } + if (!_doc->canBePlayed()) { + clearStreaming(); + displayDocument(_doc, App::histItemById(_msgid)); + } else { + _streamed->lastError = std::move(error); + playbackWaitingChange(false); + updatePlaybackState(); + } } void OverlayWidget::playbackWaitingChange(bool waiting) { @@ -2149,13 +2161,7 @@ void OverlayWidget::refreshClipControllerGeometry() { } void OverlayWidget::playbackControlsPlay() { - const auto legacy = _streamed->player.prepareLegacyState(); - if (legacy.state == Player::State::StoppedAtStart) { - Data::HandleUnsupportedMedia(_doc, _msgid); - close(); - } else { - playbackPauseResume(); - } + playbackPauseResume(); } void OverlayWidget::playbackControlsPause() { @@ -2174,9 +2180,11 @@ void OverlayWidget::playbackPauseResume() { Expects(_streamed != nullptr); _streamed->resumeOnCallEnd = false; + _streamed->lastError = std::nullopt; if (const auto item = App::histItemById(_msgid)) { if (_streamed->player.failed()) { - displayDocument(_doc, item); + clearStreaming(); + initStreaming(); } else if (_streamed->player.finished()) { restartAtSeekPosition(0); } else if (_streamed->player.paused()) { @@ -2209,8 +2217,8 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) { } _streamed->player.play(options); - Media::Player::instance()->pause(AudioMsgId::Type::Voice); - Media::Player::instance()->pause(AudioMsgId::Type::Song); + Player::instance()->pause(AudioMsgId::Type::Voice); + Player::instance()->pause(AudioMsgId::Type::Song); _streamed->info.audio.state.position = _streamed->info.video.state.position diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index c9bcd2afd8..e56d870894 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -39,7 +39,7 @@ struct TrackState; namespace Streaming { struct Information; struct Update; -struct Error; +enum class Error; } // namespace Streaming } // namespace Media