diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_common.h b/Telegram/SourceFiles/media/streaming/media_streaming_common.h index 9f9b01c9d0..79af0e4e0e 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_common.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_common.h @@ -61,6 +61,7 @@ struct VideoInformation { QSize size; QImage cover; int rotation = 0; + float64 fps = 0.; bool alpha = false; }; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp index 9e5ee84980..299fdb3555 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_file.cpp @@ -179,6 +179,10 @@ Stream File::Context::initStream( result.rotation = FFmpeg::ReadRotationFromMetadata(info); result.aspect = FFmpeg::ValidateAspectRatio( info->sample_aspect_ratio); + const auto guessed = av_guess_frame_rate(format, info, nullptr); + if (guessed.num > 0 && guessed.den > 0) { + result.fps = av_q2d(guessed); + } } else if (type == AVMEDIA_TYPE_AUDIO) { result.frequency = info->codecpar->sample_rate; if (!result.frequency) { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index b8168c84d8..4e7979fc2e 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -64,6 +64,7 @@ void SaveValidVideoInformation( to.size = from.size; to.cover = std::move(from.cover); to.rotation = from.rotation; + to.fps = from.fps; to.alpha = from.alpha; } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h index 23dfadd89b..cd32347746 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h @@ -41,6 +41,7 @@ struct Stream { // Video only. int rotation = 0; AVRational aspect = FFmpeg::kNormalAspect; + float64 fps = 0.; FFmpeg::SwscalePointer swscale; }; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp index fc1e09ef2f..5e63d08182 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp @@ -734,6 +734,7 @@ void VideoTrackObject::callReady() { _stream.rotation), .cover = frame->original, .rotation = _stream.rotation, + .fps = _stream.fps, .alpha = frame->alpha, } }); } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index fce5414e38..38827b4963 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -146,6 +146,8 @@ constexpr auto kZoomToScreenLevel = 1024; constexpr auto kOverlayLoaderPriority = 2; constexpr auto kSeekTimeMs = 5 * crl::time(1000); constexpr auto kSeekTimeMsLong = 10 * crl::time(1000); +constexpr auto kFrameStepFallbackFps = 30.; +constexpr auto kFrameStepThrottleMs = crl::time(150); // macOS OpenGL renderer fails to render larger texture // even though it reports that max texture size is 16384. @@ -617,6 +619,10 @@ OverlayWidget::OverlayWidget() startSpeedBoost(); }); + _frameStepThrottle.setCallback([=] { + flushPendingFrameStep(); + }); + _docRectImage = QImage( st::mediaviewFileSize * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied); @@ -5071,6 +5077,21 @@ void OverlayWidget::playbackPauseResume() { } } +void OverlayWidget::flushPendingFrameStep() { + if (!_streamed || !_frameStepPending) { + _frameStepThrottle.cancel(); + return; + } + const auto fps = _streamed->instance.info().video.fps; + const auto stepMs = 1000. + / ((fps > 0.) ? fps : kFrameStepFallbackFps); + const auto shift = crl::time(std::round(_frameStepPending * stepMs)); + _frameStepPending = 0; + _streamingStartPaused = true; + seekRelativeTime(shift); + _frameStepThrottle.callOnce(kFrameStepThrottleMs); +} + void OverlayWidget::seekRelativeTime(crl::time time) { Expects(_streamed != nullptr); @@ -6615,6 +6636,15 @@ void OverlayWidget::handleKeyPress(not_null e) { activateControls(); seekRelativeTime(kSeekTimeMsLong); return; + } else if ((key == Qt::Key_Period || key == Qt::Key_Comma) + && _streamed->instance.player().paused()) { + activateControls(); + _frameStepPending += (key == Qt::Key_Period) ? 1 : -1; + if (!_frameStepThrottle.isActive()) { + flushPendingFrameStep(); + _frameStepThrottle.callOnce(kFrameStepThrottleMs); + } + return; } else if (modifiers.testFlag(Qt::AltModifier) && (key == Qt::Key_Left || key == Qt::Key_Right) && _streamed->controls diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 39a0b4aaa2..6b55cba4e9 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -419,6 +419,7 @@ private: void seekRelativeTime(crl::time time); void restartAtProgress(float64 progress); void restartAtSeekPosition(crl::time position); + void flushPendingFrameStep(); void refreshClipControllerGeometry(); void refreshCaptionGeometry(); @@ -805,6 +806,8 @@ private: QRect _speedBoostRect; Ui::Animations::Simple _speedBoostAnimation; base::Timer _speedBoostHoldTimer; + base::Timer _frameStepThrottle; + int _frameStepPending = 0; Ui::Animations::Basic _speedBoostTicker; float64 _speedBoostPhase = 0.; crl::time _speedBoostLastFrame = 0;