diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3b9d28d955..d1c6825b06 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1492,6 +1492,8 @@ 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_download" = "Download"; "lng_rights_edit_admin" = "Manage permissions"; "lng_rights_edit_admin_header" = "What can this admin do?"; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 8e99d9daa9..dff7759079 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -300,6 +300,9 @@ 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()) { Media::Player::instance()->playPause({ data, msgId }); @@ -705,55 +708,32 @@ void DocumentData::performActionOnLoad() { return; } - auto loc = location(true); - auto already = loc.name(); - auto item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : nullptr; - auto showImage = !isVideoFile() && (size < App::kImageSizeLimit); - auto playVoice = isVoiceMessage() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); - auto playMusic = isAudioFile() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); - auto playAnimation = isAnimation() - && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) + const auto loc = location(true); + const auto &already = loc.name(); + const auto item = _actionOnLoadMsgId.msg + ? App::histItemById(_actionOnLoadMsgId) + : nullptr; + const auto showImage = !isVideoFile() && (size < App::kImageSizeLimit); + const auto playVoice = isVoiceMessage(); + const auto playMusic = isAudioFile(); + const auto playAnimation = isAnimation() + && loaded() && showImage && item; - if (auto applyTheme = isTheme()) { + if (isTheme()) { if (!loc.isEmpty() && loc.accessEnable()) { Core::App().showDocument(this, item); loc.accessDisable(); - return; } - } - if (playVoice || playMusic) { - DocumentOpenClickHandler::Open({}, this, item, ActionOnLoadNone); - } else if (playAnimation) { - if (loaded()) { - if (_actionOnLoad == ActionOnLoadPlayInline && item) { - _owner->requestAnimationPlayInline(item); - } else { - Core::App().showDocument(this, item); - } - } - } else { - if (already.isEmpty()) return; - - if (_actionOnLoad == ActionOnLoadOpenWith) { + } else if (_actionOnLoad == ActionOnLoadOpenWith) { + if (!already.isEmpty()) { File::OpenWith(already, QCursor::pos()); - } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { - if (isVoiceMessage() || isAudioFile() || isVideoFile()) { - if (Data::IsValidMediaFile(already)) { - File::Launch(already); - } - _owner->markMediaRead(this); - } else if (loc.accessEnable()) { - if (showImage && QImageReader(loc.name()).canRead()) { - Core::App().showDocument(this, item); - } else { - LaunchWithWarning(already, item); - } - loc.accessDisable(); - } else { - LaunchWithWarning(already, item); - } } + } else if (playVoice + || playMusic + || playAnimation + || !already.isEmpty()) { + DocumentOpenClickHandler::Open({}, this, item, _actionOnLoad); } _actionOnLoad = ActionOnLoadNone; } @@ -1222,6 +1202,14 @@ bool DocumentData::canBePlayed() const { && (loaded() || canBeStreamed()); } +void DocumentData::setInappPlaybackFailed() { + _inappPlaybackFailed = true; +} + +bool DocumentData::inappPlaybackFailed() const { + return _inappPlaybackFailed; +} + auto DocumentData::createStreamingLoader(Data::FileOrigin origin) const -> std::unique_ptr { // #TODO streaming create local file loader @@ -1596,4 +1584,28 @@ base::binary_guard ReadImageAsync( return std::move(right); } +void HandleUnsupportedMedia( + not_null document, + FullMsgId contextId) { + document->setInappPlaybackFailed(); + 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); + } +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index a4828490a1..2c284bfc9e 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -228,6 +228,9 @@ public: [[nodiscard]] auto createStreamingLoader(Data::FileOrigin origin) const -> std::unique_ptr; + void setInappPlaybackFailed(); + [[nodiscard]] bool inappPlaybackFailed() const; + ~DocumentData(); DocumentId id = 0; @@ -272,6 +275,7 @@ private: int32 _duration = -1; bool _isImage = false; bool _supportsStreaming = false; + bool _inappPlaybackFailed = false; ActionOnLoad _actionOnLoad = ActionOnLoadNone; FullMsgId _actionOnLoadMsgId; @@ -393,4 +397,8 @@ 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 e0924effc1..db1d3f6814 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -389,11 +389,9 @@ MainWidget::MainWidget( connect(_dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled())); connect(this, SIGNAL(dialogsUpdated()), _dialogs, SLOT(onListScroll())); connect(_history, SIGNAL(cancelled()), _dialogs, SLOT(activate())); - subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) { - if (audioId.type() != AudioMsgId::Type::Video) { - handleAudioUpdate(audioId); - } - }); + subscribe( + Media::Player::instance()->updatedNotifier(), + [=](const Media::Player::TrackState &state) { handleAudioUpdate(state); }); subscribe(session().calls().currentCallChanged(), [this](Calls::Call *call) { setCurrentCall(call); }); session().data().currentExportView( @@ -1173,29 +1171,17 @@ void MainWidget::messagesAffected( } } -void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { +void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) { using State = Media::Player::State; - const auto document = audioId.audio(); - auto state = Media::Player::instance()->getState(audioId.type()); - if (state.id == audioId && state.state == State::StoppedAtStart) { - state.state = State::Stopped; - Media::Player::mixer()->clearStoppedAtStart(audioId); - - auto filepath = document->filepath(DocumentData::FilePathResolveSaveFromData); - if (!filepath.isEmpty()) { - if (Data::IsValidMediaFile(filepath)) { - File::Launch(filepath); - } - } + const auto document = state.id.audio(); + if (!Media::Player::IsStoppedOrStopping(state.state)) { + createPlayer(); + } else if (state.state == State::StoppedAtStart) { + Data::HandleUnsupportedMedia(document, state.id.contextId()); + closeBothPlayers(); } - if (state.id == audioId) { - if (!Media::Player::IsStoppedOrStopping(state.state)) { - createPlayer(); - } - } - - if (const auto item = App::histItemById(audioId.contextId())) { + if (const auto item = App::histItemById(state.id.contextId())) { session().data().requestItemRepaint(item); } if (const auto items = InlineBots::Layout::documentItems()) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 60f4c5c2f9..4ad0db8055 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -42,6 +42,7 @@ namespace Player { class Widget; class VolumeWidget; class Panel; +struct TrackState; } // namespace Player } // namespace Media @@ -350,7 +351,7 @@ private: void animationCallback(); void handleAdaptiveLayoutUpdate(); void updateWindowAdaptiveLayout(); - void handleAudioUpdate(const AudioMsgId &audioId); + void handleAudioUpdate(const Media::Player::TrackState &state); void updateMediaPlayerPosition(); void updateMediaPlaylistPosition(int x); void updateControlsGeometry(); diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index d53aceee1c..905eb7c982 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -1247,14 +1247,6 @@ void Mixer::setStoppedState(Track *current, State state) { } } -void Mixer::clearStoppedAtStart(const AudioMsgId &audio) { - QMutexLocker lock(&AudioMutex); - auto track = trackForType(audio.type()); - if (track && track->state.id == audio && track->state.state == State::StoppedAtStart) { - setStoppedState(track); - } -} - // Thread: Main. Must be locked: AudioMutex. void Mixer::detachTracks() { for (auto i = 0; i != kTogetherLimit; ++i) { diff --git a/Telegram/SourceFiles/media/audio/media_audio.h b/Telegram/SourceFiles/media/audio/media_audio.h index 9aec4ce90f..0c95fd6444 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.h +++ b/Telegram/SourceFiles/media/audio/media_audio.h @@ -153,8 +153,6 @@ public: TrackState currentState(AudioMsgId::Type type); - void clearStoppedAtStart(const AudioMsgId &audio); - // Thread: Main. Must be locked: AudioMutex. void detachTracks(); diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 3cbd6f571a..59ffa5a05b 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -327,7 +327,9 @@ void Instance::play(AudioMsgId::Type type) { if (IsStopped(state.state)) { play(state.id); } else if (data->streamed) { - data->streamed->player.resume(); + if (data->streamed->player.active()) { + data->streamed->player.resume(); + } emitUpdate(type); } else { mixer()->resume(state.id); @@ -414,7 +416,9 @@ Streaming::PlaybackOptions Instance::streamingOptions( void Instance::pause(AudioMsgId::Type type) { if (const auto data = getData(type)) { if (data->streamed) { - data->streamed->player.pause(); + if (data->streamed->player.active()) { + data->streamed->player.pause(); + } emitUpdate(type); } else { const auto state = getState(type); @@ -442,12 +446,9 @@ void Instance::stop(AudioMsgId::Type type) { void Instance::playPause(AudioMsgId::Type type) { if (const auto data = getData(type)) { if (data->streamed) { - if (data->streamed->player.finished() - || data->streamed->player.failed()) { - auto options = Streaming::PlaybackOptions(); - options.mode = Streaming::Mode::Audio; - options.audioId = data->streamed->id; - data->streamed->player.play(options); + if (!data->streamed->player.active()) { + data->streamed->player.play( + streamingOptions(data->streamed->id)); } else if (data->streamed->player.paused()) { data->streamed->player.resume(); } else { @@ -702,12 +703,19 @@ void Instance::handleStreamingUpdate( }; finishTrack(data->streamed->info.audio.state); emitUpdate(data->type); + if (data->streamed && data->streamed->player.finished()) { + data->streamed = nullptr; + } }); } void Instance::handleStreamingError( not_null data, Streaming::Error &&error) { + emitUpdate(data->type); + if (data->streamed && data->streamed->player.failed()) { + data->streamed = nullptr; + } } } // namespace Player diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp index e8f8977c34..e38acf98dc 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp @@ -98,7 +98,7 @@ void File::Context::logFatal(QLatin1String method, AvErrorWrap error) { Stream File::Context::initStream(AVMediaType type) { auto result = Stream(); const auto index = result.index = av_find_best_stream( - _formatContext, + _format.get(), type, -1, -1, @@ -108,7 +108,7 @@ Stream File::Context::initStream(AVMediaType type) { return {}; } - const auto info = _formatContext->streams[index]; + const auto info = _format->streams[index]; if (type == AVMEDIA_TYPE_VIDEO) { result.rotation = ReadRotationFromMetadata(info); } else if (type == AVMEDIA_TYPE_AUDIO) { @@ -130,7 +130,7 @@ Stream File::Context::initStream(AVMediaType type) { result.timeBase = info->time_base; result.duration = (info->duration != AV_NOPTS_VALUE) ? PtsToTimeCeil(info->duration, result.timeBase) - : PtsToTimeCeil(_formatContext->duration, kUniversalTimeBase); + : PtsToTimeCeil(_format->duration, kUniversalTimeBase); if (result.duration == kTimeUnknown || !result.duration) { return {}; } @@ -151,7 +151,7 @@ void File::Context::seekToPosition( // //const auto seekFlags = 0; //error = av_seek_frame( - // _formatContext, + // _format, // streamIndex, // TimeToPts(position, kUniversalTimeBase), // seekFlags); @@ -160,7 +160,7 @@ void File::Context::seekToPosition( //} // error = av_seek_frame( - _formatContext, + _format.get(), stream.index, TimeToPts( std::clamp(position, crl::time(0), stream.duration - 1), @@ -176,7 +176,7 @@ base::variant File::Context::readPacket() { auto error = AvErrorWrap(); auto result = Packet(); - error = av_read_frame(_formatContext, &result.fields()); + error = av_read_frame(_format.get(), &result.fields()); if (unroll()) { return AvErrorWrap(); } else if (!error) { @@ -193,37 +193,16 @@ void File::Context::start(crl::time position) { if (unroll()) { return; } - _ioBuffer = reinterpret_cast(av_malloc(AVBlockSize)); - _ioContext = avio_alloc_context( - _ioBuffer, - AVBlockSize, - 0, - static_cast(this), + _format = MakeFormatPointer( + static_cast(this), &Context::Read, nullptr, &Context::Seek); - _formatContext = avformat_alloc_context(); - if (!_formatContext) { - return logFatal(qstr("avformat_alloc_context")); + if (!_format) { + return fail(); } - _formatContext->pb = _ioContext; - auto options = (AVDictionary*)nullptr; - const auto guard = gsl::finally([&] { av_dict_free(&options); }); - av_dict_set(&options, "usetoc", "1", 0); - error = avformat_open_input( - &_formatContext, - nullptr, - nullptr, - &options); - if (error) { - _ioBuffer = nullptr; - return logFatal(qstr("avformat_open_input"), error); - } - _opened = true; - _formatContext->flags |= AVFMT_FLAG_FAST_SEEK; - - if ((error = avformat_find_stream_info(_formatContext, nullptr))) { + if ((error = avformat_find_stream_info(_format.get(), nullptr))) { return logFatal(qstr("avformat_find_stream_info"), error); } @@ -299,20 +278,7 @@ void File::Context::fail() { _delegate->fileError(); } -File::Context::~Context() { - if (_opened) { - avformat_close_input(&_formatContext); - } - if (_ioContext) { - av_freep(&_ioContext->buffer); - av_freep(&_ioContext); - } else if (_ioBuffer) { - av_freep(&_ioBuffer); - } - if (_formatContext) { - avformat_free_context(_formatContext); - } -} +File::Context::~Context() = default; bool File::Context::finished() const { // #TODO streaming later looping @@ -348,6 +314,7 @@ void File::stop() { _context->interrupt(); _thread.join(); } + _reader.stop(); _context.reset(); } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file.h b/Telegram/SourceFiles/media/streaming/media_streaming_file.h index ab590c0044..6fc71874c1 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file.h @@ -87,9 +87,7 @@ private: crl::semaphore _semaphore; std::atomic _interrupted = false; - uchar *_ioBuffer = nullptr; - AVIOContext *_ioContext = nullptr; - AVFormatContext *_formatContext = nullptr; + FormatPointer _format; }; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index 4137ae880b..2af53981ce 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -355,7 +355,7 @@ void Player::provideStartInformation() { void Player::fail() { _sessionLifetime = rpl::lifetime(); const auto stopGuarded = crl::guard(&_sessionGuard, [=] { stop(); }); - _stage = Stage::Failed; + _lastFailureStage = _stage; _updates.fire_error({}); stopGuarded(); } @@ -364,6 +364,7 @@ void Player::play(const PlaybackOptions &options) { Expects(options.speed >= 0.5 && options.speed <= 2.); stop(); + _lastFailureStage = Stage::Uninitialized; _options = options; if (!Media::Audio::SupportsSpeedControl()) { @@ -374,14 +375,14 @@ void Player::play(const PlaybackOptions &options) { } void Player::pause() { - Expects(valid()); + Expects(active()); _pausedByUser = true; updatePausedState(); } void Player::resume() { - Expects(valid()); + Expects(active()); _pausedByUser = false; updatePausedState(); @@ -510,9 +511,7 @@ void Player::start() { void Player::stop() { _file->stop(); _sessionLifetime = rpl::lifetime(); - if (_stage != Stage::Failed) { - _stage = Stage::Uninitialized; - } + _stage = Stage::Uninitialized; _audio = nullptr; _video = nullptr; invalidate_weak_ptrs(&_sessionGuard); @@ -526,11 +525,14 @@ void Player::stop() { } bool Player::failed() const { - return (_stage == Stage::Failed); + return (_lastFailureStage != Stage::Uninitialized); } bool Player::playing() const { - return (_stage == Stage::Started) && !_paused && !finished(); + return (_stage == Stage::Started) + && !paused() + && !finished() + && !failed(); } bool Player::buffering() const { @@ -548,7 +550,7 @@ bool Player::finished() const { } void Player::setSpeed(float64 speed) { - Expects(valid()); + Expects(active()); Expects(speed >= 0.5 && speed <= 2.); if (!Media::Audio::SupportsSpeedControl()) { @@ -565,12 +567,12 @@ void Player::setSpeed(float64 speed) { } } -bool Player::valid() const { - return (_stage != Stage::Uninitialized) && (_stage != Stage::Failed); +bool Player::active() const { + return (_stage != Stage::Uninitialized) && !finished() && !failed(); } bool Player::ready() const { - return valid() && (_stage != Stage::Initializing); + return (_stage != Stage::Uninitialized) && (_stage != Stage::Initializing); } rpl::producer Player::updates() const { @@ -588,7 +590,11 @@ Media::Player::TrackState Player::prepareLegacyState() const { auto result = Media::Player::TrackState(); result.id = _audioId.externalPlayId() ? _audioId : _options.audioId; - result.state = finished() + result.state = (_lastFailureStage == Stage::Started) + ? State::StoppedAtError + : failed() + ? State::StoppedAtStart + : finished() ? State::StoppedAtEnd : paused() ? State::Paused @@ -609,6 +615,8 @@ Media::Player::TrackState Player::prepareLegacyState() const { : document->duration(); if (duration > 0) { result.length = duration * crl::time(1000); + } else { + result.length = std::max(result.position, crl::time(0)); } } result.frequency = kMsFrequency; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.h b/Telegram/SourceFiles/media/streaming/media_streaming_player.h index 6c3c3c2e17..1720e81347 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.h @@ -44,10 +44,10 @@ public: void resume(); void stop(); - bool valid() const; - bool ready() const; + [[nodiscard]] bool active() const; + [[nodiscard]] bool ready() const; - float64 speed() const; + [[nodiscard]] float64 speed() const; void setSpeed(float64 speed); // 0.5 <= speed <= 2. [[nodiscard]] bool playing() const; @@ -72,7 +72,6 @@ private: Initializing, Ready, Started, - Failed }; // Thread-safe. @@ -143,6 +142,7 @@ private: // Belongs to the main thread. Information _information; Stage _stage = Stage::Uninitialized; + Stage _lastFailureStage = Stage::Uninitialized; bool _pausedByUser = false; bool _pausedByWaitingForData = false; bool _paused = false; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp index fc9db6a5a4..3ce075bf93 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp @@ -540,6 +540,10 @@ Reader::Reader( } } +void Reader::stop() { + _waiting = nullptr; +} + std::shared_ptr Reader::InitCacheHelper( std::optional baseKey) { if (!baseKey) { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h index eac09d0449..b37e59d069 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.h @@ -39,6 +39,8 @@ public: void headerDone(); + void stop(); + ~Reader(); private: diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp index d84fa43985..3d046f2f7f 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp @@ -22,6 +22,7 @@ constexpr auto kSkipInvalidDataPackets = 10; constexpr auto kAlignImageBy = 16; constexpr auto kPixelBytesSize = 4; constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied; +constexpr auto kAvioBlockSize = 4096; void AlignedImageBufferCleanupHandler(void* data) { const auto buffer = static_cast(data); @@ -68,6 +69,82 @@ QImage CreateFrameStorage(QSize size) { cleanupData); } +IOPointer MakeIOPointer( + void *opaque, + int(*read)(void *opaque, uint8_t *buffer, int bufferSize), + int(*write)(void *opaque, uint8_t *buffer, int bufferSize), + int64_t(*seek)(void *opaque, int64_t offset, int whence)) { + auto buffer = reinterpret_cast(av_malloc(kAvioBlockSize)); + if (!buffer) { + LogError(qstr("av_malloc")); + return {}; + } + auto result = IOPointer(avio_alloc_context( + buffer, + kAvioBlockSize, + write ? 1 : 0, + opaque, + read, + write, + seek)); + if (!result) { + av_freep(&buffer); + LogError(qstr("avio_alloc_context")); + return {}; + } + return result; +} + +void IODeleter::operator()(AVIOContext *value) { + if (value) { + av_freep(&value->buffer); + avio_context_free(&value); + } +} + +FormatPointer MakeFormatPointer( + void *opaque, + int(*read)(void *opaque, uint8_t *buffer, int bufferSize), + int(*write)(void *opaque, uint8_t *buffer, int bufferSize), + int64_t(*seek)(void *opaque, int64_t offset, int whence)) { + auto io = MakeIOPointer(opaque, read, write, seek); + if (!io) { + return {}; + } + auto result = avformat_alloc_context(); + if (!result) { + LogError(qstr("avformat_alloc_context")); + return {}; + } + result->pb = io.get(); + + auto options = (AVDictionary*)nullptr; + const auto guard = gsl::finally([&] { av_dict_free(&options); }); + av_dict_set(&options, "usetoc", "1", 0); + const auto error = AvErrorWrap(avformat_open_input( + &result, + nullptr, + nullptr, + &options)); + if (error) { + // avformat_open_input freed 'result' in case an error happened. + LogError(qstr("avformat_open_input"), error); + return {}; + } + result->flags |= AVFMT_FLAG_FAST_SEEK; + + // Now FormatPointer will own and free the IO context. + io.release(); + return FormatPointer(result); +} + +void FormatDeleter::operator()(AVFormatContext *value) { + if (value) { + const auto deleter = IOPointer(value->pb); + avformat_close_input(&value); + } +} + CodecPointer MakeCodecPointer(not_null stream) { auto error = AvErrorWrap(); @@ -120,10 +197,10 @@ void FrameDeleter::operator()(AVFrame *value) { av_frame_free(&value); } -SwsContextPointer MakeSwsContextPointer( +SwscalePointer MakeSwscalePointer( not_null frame, QSize resize, - SwsContextPointer *existing) { + SwscalePointer *existing) { // We have to use custom caching for SwsContext, because // sws_getCachedContext checks passed flags with existing context flags, // and re-creates context if they're different, but in the process of @@ -139,7 +216,7 @@ SwsContextPointer MakeSwsContextPointer( } if (frame->format <= AV_PIX_FMT_NONE || frame->format >= AV_PIX_FMT_NB) { LogError(qstr("frame->format")); - return SwsContextPointer(); + return SwscalePointer(); } const auto result = sws_getCachedContext( @@ -157,12 +234,12 @@ SwsContextPointer MakeSwsContextPointer( if (!result) { LogError(qstr("sws_getCachedContext")); } - return SwsContextPointer( + return SwscalePointer( result, { resize, QSize{ frame->width, frame->height }, frame->format }); } -void SwsContextDeleter::operator()(SwsContext *value) { +void SwscaleDeleter::operator()(SwsContext *value) { if (value) { sws_freeContext(value); } @@ -341,11 +418,11 @@ QImage ConvertFrame( from += deltaFrom; } } else { - stream.swsContext = MakeSwsContextPointer( + stream.swscale = MakeSwscalePointer( frame, resize, - &stream.swsContext); - if (!stream.swsContext) { + &stream.swscale); + if (!stream.swscale) { return QImage(); } @@ -354,7 +431,7 @@ QImage ConvertFrame( int linesize[AV_NUM_DATA_POINTERS] = { storage.bytesPerLine(), 0 }; const auto lines = sws_scale( - stream.swsContext.get(), + stream.swscale.get(), frame->data, frame->linesize, 0, diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h index 76aebb0ebf..6b07071713 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h @@ -115,6 +115,26 @@ private: }; +struct IODeleter { + void operator()(AVIOContext *value); +}; +using IOPointer = std::unique_ptr; +[[nodiscard]] IOPointer MakeIOPointer( + void *opaque, + int(*read)(void *opaque, uint8_t *buffer, int bufferSize), + int(*write)(void *opaque, uint8_t *buffer, int bufferSize), + int64_t(*seek)(void *opaque, int64_t offset, int whence)); + +struct FormatDeleter { + void operator()(AVFormatContext *value); +}; +using FormatPointer = std::unique_ptr; +[[nodiscard]] FormatPointer MakeFormatPointer( + void *opaque, + int(*read)(void *opaque, uint8_t *buffer, int bufferSize), + int(*write)(void *opaque, uint8_t *buffer, int bufferSize), + int64_t(*seek)(void *opaque, int64_t offset, int whence)); + struct CodecDeleter { void operator()(AVCodecContext *value); }; @@ -129,18 +149,18 @@ using FramePointer = std::unique_ptr; [[nodiscard]] bool FrameHasData(AVFrame *frame); void ClearFrameMemory(AVFrame *frame); -struct SwsContextDeleter { +struct SwscaleDeleter { QSize resize; QSize frameSize; int frameFormat = int(AV_PIX_FMT_NONE); void operator()(SwsContext *value); }; -using SwsContextPointer = std::unique_ptr; -[[nodiscard]] SwsContextPointer MakeSwsContextPointer( +using SwscalePointer = std::unique_ptr; +[[nodiscard]] SwscalePointer MakeSwscalePointer( not_null frame, QSize resize, - SwsContextPointer *existing = nullptr); + SwscalePointer *existing = nullptr); struct Stream { int index = -1; @@ -156,7 +176,7 @@ struct Stream { // Video only. int rotation = 0; - SwsContextPointer swsContext; + SwscalePointer swscale; }; void LogError(QLatin1String method); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index dd475f83ff..bcb8a479b9 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1925,9 +1925,12 @@ void OverlayWidget::streamingReady(Streaming::Information &&info) { validateStreamedGoodThumbnail(); if (videoShown()) { const auto contentSize = ConvertScale(videoSize()); - _w = contentSize.width(); - _h = contentSize.height(); - contentSizeChanged(); + if (_w != contentSize.width() || _h != contentSize.height()) { + update(contentRect()); + _w = contentSize.width(); + _h = contentSize.height(); + contentSizeChanged(); + } } this->update(contentRect()); playbackWaitingChange(false); @@ -2014,6 +2017,7 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) { void OverlayWidget::handleStreamingError(Streaming::Error &&error) { playbackWaitingChange(false); + updatePlaybackState(); } void OverlayWidget::playbackWaitingChange(bool waiting) { @@ -2124,7 +2128,13 @@ void OverlayWidget::refreshClipControllerGeometry() { } void OverlayWidget::playbackControlsPlay() { - playbackPauseResume(); + const auto legacy = _streamed->player.prepareLegacyState(); + if (legacy.state == Player::State::StoppedAtStart) { + Data::HandleUnsupportedMedia(_doc, _msgid); + close(); + } else { + playbackPauseResume(); + } } void OverlayWidget::playbackControlsPause() {