1919
2020#include " actions.h"
2121#include " mainwindow.h"
22+ #include " mltcontroller.h"
23+ #include " player.h"
2224#include " qmltypes/qmlutilities.h"
2325
26+ #ifdef Q_OS_WIN
27+ #include < windows.h>
28+ #endif
29+
2430#include < private/qrhi_p.h>
2531#include < QDebug>
2632#include < QDir>
@@ -44,6 +50,19 @@ static float hlgOetf(float linear)
4450 return a * logf (12 .0f * linear - b) + c;
4551}
4652
53+ static QString formatTimecode (int frames, double fps)
54+ {
55+ if (frames < 0 || fps <= 0.0 )
56+ return QStringLiteral (" 00:00" );
57+ const int totalSec = static_cast <int >(frames / fps);
58+ const int h = totalSec / 3600 ;
59+ const int m = (totalSec % 3600 ) / 60 ;
60+ const int s = totalSec % 60 ;
61+ if (h > 0 )
62+ return QString (" %1:%2:%3" ).arg (h).arg (m, 2 , 10 , QChar (' 0' )).arg (s, 2 , 10 , QChar (' 0' ));
63+ return QString (" %1:%2" ).arg (m, 2 , 10 , QChar (' 0' )).arg (s, 2 , 10 , QChar (' 0' ));
64+ }
65+
4766HdrPreviewWindow::HdrPreviewWindow (QWindow *parent)
4867 : QQuickView(QmlUtilities::sharedEngine(), parent)
4968{
@@ -64,6 +83,8 @@ HdrPreviewWindow::HdrPreviewWindow(QWindow *parent)
6483
6584 resize (960 , 540 );
6685
86+ connect (this , &QWindow::windowStateChanged, this , [this ]() { emit fullScreenChanged (); });
87+
6788#ifdef Q_OS_MACOS
6889 // Override NSScreen.maximumExtendedDynamicRangeColorComponentValue so that
6990 // Qt's video shader outputs > 1.0 values (HDR) on the first frame, which
@@ -113,9 +134,160 @@ void HdrPreviewWindow::pushFrame(const QVideoFrame &frame)
113134 }
114135 updateHdrGain ();
115136 m_videoSink->setVideoFrame (frame);
137+
138+ // Track playback position from frame timestamp
139+ const qint64 pts = frame.startTime (); // microseconds
140+ const double fps = MLT.profile ().fps ();
141+ if (pts >= 0 && fps > 0.0 ) {
142+ const int frameNum = qRound (pts * fps / 1000000.0 );
143+ if (m_videoPosition != frameNum) {
144+ m_videoPosition = frameNum;
145+ emit videoPositionChanged ();
146+ }
147+ }
148+ // Track duration from the active producer
149+ if (auto *prod = MLT.producer ()) {
150+ const int len = qMax (0 , prod->get_length () - 1 );
151+ if (m_videoDuration != len) {
152+ m_videoDuration = len;
153+ emit videoDurationChanged ();
154+ }
155+ }
156+ }
157+ }
158+
159+ void HdrPreviewWindow::triggerPlayPause ()
160+ {
161+ Actions[" playerPlayPauseAction" ]->trigger ();
162+ }
163+
164+ void HdrPreviewWindow::triggerRewind ()
165+ {
166+ Actions[" playerRewindAction" ]->trigger ();
167+ }
168+
169+ void HdrPreviewWindow::triggerFastForward ()
170+ {
171+ Actions[" playerFastForwardAction" ]->trigger ();
172+ }
173+
174+ void HdrPreviewWindow::toggleFullScreen ()
175+ {
176+ if (windowStates () & Qt::WindowFullScreen) {
177+ setWindowStates (Qt::WindowNoState);
178+ if (m_normalGeometry.isValid ())
179+ setGeometry (m_normalGeometry);
180+ } else {
181+ m_normalGeometry = geometry ();
182+ showFullScreen ();
183+ }
184+ }
185+
186+ QString HdrPreviewWindow::positionText () const
187+ {
188+ return formatTimecode (m_videoPosition, MLT.profile ().fps ());
189+ }
190+
191+ QString HdrPreviewWindow::durationText () const
192+ {
193+ return formatTimecode (m_videoDuration, MLT.profile ().fps ());
194+ }
195+
196+ void HdrPreviewWindow::seekToFrame (int frame)
197+ {
198+ MAIN.player ()->seek (frame);
199+ }
200+
201+ void HdrPreviewWindow::setPlaying (bool playing)
202+ {
203+ if (m_isPlaying != playing) {
204+ m_isPlaying = playing;
205+ emit playingChanged ();
116206 }
117207}
118208
209+ bool HdrPreviewWindow::nativeEvent (const QByteArray &eventType, void *message, qintptr *result)
210+ {
211+ #ifdef Q_OS_WIN
212+ if (eventType == " windows_generic_MSG" ) {
213+ MSG *msg = static_cast <MSG *>(message);
214+ if (msg->message == WM_SIZING && !(windowStates () & Qt::WindowFullScreen)) {
215+ const int darNum = MLT.profile ().display_aspect_num ();
216+ const int darDen = MLT.profile ().display_aspect_den ();
217+ if (darNum > 0 && darDen > 0 ) {
218+ RECT *r = reinterpret_cast <RECT *>(msg->lParam );
219+ // Measure the actual frame overhead from the live window state.
220+ // AdjustWindowRectEx under-reports the DWM extended frame on
221+ // Windows 10/11, leading to a wrong client-area AR calculation.
222+ RECT curWin, curClient;
223+ GetWindowRect (msg->hwnd , &curWin);
224+ GetClientRect (msg->hwnd , &curClient);
225+ const int fw = (curWin.right - curWin.left ) - (curClient.right - curClient.left );
226+ const int fh = (curWin.bottom - curWin.top ) - (curClient.bottom - curClient.top );
227+ const int clientW = (r->right - r->left ) - fw;
228+ const int clientH = (r->bottom - r->top ) - fh;
229+ const bool heightPrimary = (msg->wParam == WMSZ_TOP || msg->wParam == WMSZ_BOTTOM);
230+ if (heightPrimary) {
231+ // Height drives: adjust width from the right
232+ r->right = r->left + qRound ((double ) clientH * darNum / darDen) + fw;
233+ } else {
234+ // Width drives: adjust height
235+ const int newH = qRound ((double ) clientW * darDen / darNum) + fh;
236+ const bool fromTop = (msg->wParam == WMSZ_TOPLEFT
237+ || msg->wParam == WMSZ_TOPRIGHT);
238+ if (fromTop)
239+ r->top = r->bottom - newH;
240+ else
241+ r->bottom = r->top + newH;
242+ }
243+ *result = TRUE ;
244+ return true ;
245+ }
246+ }
247+ }
248+ #endif
249+ return QQuickView::nativeEvent (eventType, message, result);
250+ }
251+
252+ void HdrPreviewWindow::resizeEvent (QResizeEvent *event)
253+ {
254+ QQuickView::resizeEvent (event);
255+ #ifndef Q_OS_WIN
256+ // On Windows, WM_SIZING handles AR constraining smoothly.
257+ // On other platforms, snap to the correct AR after each resize.
258+ if (windowStates () & Qt::WindowFullScreen)
259+ return ;
260+ const QSize newSize = event->size ();
261+ const QSize oldSize = event->oldSize ();
262+ if (!oldSize.isValid ())
263+ return ;
264+ const int darNum = MLT.profile ().display_aspect_num ();
265+ const int darDen = MLT.profile ().display_aspect_den ();
266+ if (darNum <= 0 || darDen <= 0 )
267+ return ;
268+ // Infer which axis the user is dragging by which changed proportionally more
269+ const double wChange = qAbs ((double ) (newSize.width () - oldSize.width ())
270+ / qMax (oldSize.width (), 1 ));
271+ const double hChange = qAbs ((double ) (newSize.height () - oldSize.height ())
272+ / qMax (oldSize.height (), 1 ));
273+ int targetW, targetH;
274+ if (wChange >= hChange) {
275+ targetW = newSize.width ();
276+ targetH = qRound ((double ) targetW * darDen / darNum);
277+ } else {
278+ targetH = newSize.height ();
279+ targetW = qRound ((double ) targetH * darNum / darDen);
280+ }
281+ if (targetW != newSize.width () || targetH != newSize.height ()) {
282+ // Defer to avoid recursion
283+ QTimer::singleShot (0 , this , [this , targetW, targetH]() {
284+ if (!(windowStates () & Qt::WindowFullScreen))
285+ resize (targetW, targetH);
286+ });
287+ }
288+ #endif
289+ }
290+
119291void HdrPreviewWindow::keyPressEvent (QKeyEvent *event)
120292{
121293 // Forward to MainWindow for J/K/L transport handling
0 commit comments