Skip to content

Commit 3093e0f

Browse files
committed
add playback controls
1 parent 9270a80 commit 3093e0f

8 files changed

Lines changed: 580 additions & 3 deletions

File tree

scripts/build-shotcut-msys2.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ function deploy
12401240

12411241
log Copying some libs from Qt
12421242
if [ "$DEBUG_BUILD" != "1" -o "$SDK" = "1" ]; then
1243-
cmd cp -p "$QTDIR"/bin/Qt6{Charts,Concurrent,Core,Core5Compat,Gui,Multimedia,Network,OpenGL,OpenGLWidgets,Qml,QmlMeta,QmlModels,QmlWorkerScript,Quick,QuickControls2*,QuickDialogs2,QuickDialogs2QuickImpl,QuickDialogs2Utils,QuickLayouts,QuickTemplates2,QuickWidgets,Sql,Svg,SvgWidgets,UiTools,WebSockets,Widgets,Xml}.dll .
1243+
cmd cp -p "$QTDIR"/bin/Qt6{Charts,Concurrent,Core,Core5Compat,Gui,Multimedia,MultimediaQuick,Network,OpenGL,OpenGLWidgets,Qml,QmlMeta,QmlModels,QmlWorkerScript,Quick,QuickControls2*,QuickDialogs2,QuickDialogs2QuickImpl,QuickDialogs2Utils,QuickLayouts,QuickShapes,QuickTemplates2,QuickWidgets,Sql,Svg,SvgWidgets,UiTools,WebSockets,Widgets,Xml}.dll .
12441244
fi
12451245

12461246
if [ "$ENABLE_GLAXNIMATE" = "1" ]; then

scripts/build-shotcut.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,7 @@ function install_shotcut_linux {
12021202
cmd install -p -c COPYING "$FINAL_INSTALL_DIR"
12031203
cmd install -p -c "$QTDIR"/translations/qt_*.qm "$FINAL_INSTALL_DIR"/share/shotcut/translations
12041204
cmd install -p -c "$QTDIR"/translations/qtbase_*.qm "$FINAL_INSTALL_DIR"/share/shotcut/translations
1205-
cmd install -p -c "$QTDIR"/lib/libQt6{Charts,Concurrent,Core,Core5Compat,DBus,Gui,LabsFolderListModel,Multimedia,Network,OpenGL,OpenGLWidgets,Qml,QmlMeta,QmlModels,QmlWorkerScript,Quick,QuickControls2*,QuickDialogs2,QuickDialogs2QuickImpl,QuickDialogs2Utils,QuickLayouts,QuickTemplates2,QuickWidgets,Sql,Svg,SvgWidgets,UiTools,WaylandClient,WaylandEglClientHwIntegration,WebSockets,Widgets,Xml,X11Extras,XcbQpa}.so.6 "$FINAL_INSTALL_DIR"/lib
1205+
cmd install -p -c "$QTDIR"/lib/libQt6{Charts,Concurrent,Core,Core5Compat,DBus,Gui,LabsFolderListModel,Multimedia,MultimediaQuick,Network,OpenGL,OpenGLWidgets,Qml,QmlMeta,QmlModels,QmlWorkerScript,Quick,QuickControls2*,QuickDialogs2,QuickDialogs2QuickImpl,QuickDialogs2Utils,QuickLayouts,QuickShapes,QuickTemplates2,QuickWidgets,Sql,Svg,SvgWidgets,UiTools,WaylandClient,WaylandEglClientHwIntegration,WebSockets,Widgets,Xml,X11Extras,XcbQpa}.so.6 "$FINAL_INSTALL_DIR"/lib
12061206
cmd install -p -c "$QTDIR"/lib/lib{icudata,icui18n,icuuc}.so* "$FINAL_INSTALL_DIR"/lib
12071207
cmd install -d "$FINAL_INSTALL_DIR"/lib/qt6/sqldrivers
12081208
cmd cp -a "$QTDIR"/plugins/{egldeviceintegrations,generic,iconengines,imageformats,multimedia,platforminputcontexts,platforms,platformthemes,tls,wayland-decoration-client,wayland-graphics-integration-client,wayland-shell-integration,xcbglintegrations} "$FINAL_INSTALL_DIR"/lib/qt6

src/hdrpreviewwindow.cpp

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@
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+
4766
HdrPreviewWindow::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+
119291
void HdrPreviewWindow::keyPressEvent(QKeyEvent *event)
120292
{
121293
// Forward to MainWindow for J/K/L transport handling

src/hdrpreviewwindow.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#include <QKeyEvent>
2222
#include <QPointer>
2323
#include <QQuickView>
24+
#include <QRect>
25+
#include <QResizeEvent>
26+
#include <QString>
2427
#include <QTimer>
2528
#include <QVideoFrame>
2629
#include <QVideoSink>
@@ -29,24 +32,49 @@ class HdrPreviewWindow : public QQuickView
2932
{
3033
Q_OBJECT
3134
Q_PROPERTY(float hdrGain READ hdrGain NOTIFY hdrGainChanged)
35+
Q_PROPERTY(bool playing READ isPlaying NOTIFY playingChanged)
36+
Q_PROPERTY(bool fullScreen READ isFullScreen NOTIFY fullScreenChanged)
37+
Q_PROPERTY(int videoPosition READ videoPosition NOTIFY videoPositionChanged)
38+
Q_PROPERTY(int videoDuration READ videoDuration NOTIFY videoDurationChanged)
39+
Q_PROPERTY(QString positionText READ positionText NOTIFY videoPositionChanged)
40+
Q_PROPERTY(QString durationText READ durationText NOTIFY videoDurationChanged)
3241

3342
public:
3443
explicit HdrPreviewWindow(QWindow *parent = nullptr);
3544
~HdrPreviewWindow();
3645

3746
Q_INVOKABLE void setVideoSink(QVideoSink *sink);
3847
float hdrGain() const { return m_hdrGain; }
48+
bool isPlaying() const { return m_isPlaying; }
49+
bool isFullScreen() const { return windowStates() & Qt::WindowFullScreen; }
50+
int videoPosition() const { return m_videoPosition; }
51+
int videoDuration() const { return m_videoDuration; }
52+
QString positionText() const;
53+
QString durationText() const;
54+
55+
Q_INVOKABLE void triggerPlayPause();
56+
Q_INVOKABLE void triggerRewind();
57+
Q_INVOKABLE void triggerFastForward();
58+
Q_INVOKABLE void toggleFullScreen();
59+
Q_INVOKABLE void seekToFrame(int frame);
3960

4061
public slots:
4162
void pushFrame(const QVideoFrame &frame);
4263
void setHlg(bool isHlg);
64+
void setPlaying(bool playing);
4365

4466
signals:
4567
void hdrGainChanged();
68+
void playingChanged();
69+
void fullScreenChanged();
70+
void videoPositionChanged();
71+
void videoDurationChanged();
4672

4773
protected:
4874
void keyPressEvent(QKeyEvent *event) override;
4975
void keyReleaseEvent(QKeyEvent *event) override;
76+
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
77+
void resizeEvent(QResizeEvent *event) override;
5078

5179
private slots:
5280
void checkEdrHeadroom();
@@ -58,9 +86,13 @@ private slots:
5886
QTimer m_edrTimer;
5987
bool m_loggedSwapChain{false};
6088
bool m_isHlg{false};
89+
bool m_isPlaying{false};
6190
float m_lastLoggedHeadroom{0.0f};
6291
int m_edrCheckCount{0};
6392
float m_hdrGain{1.0f};
93+
QRect m_normalGeometry;
94+
int m_videoPosition{0};
95+
int m_videoDuration{0};
6496
};
6597

6698
#endif // HDRPREVIEWWINDOW_H

src/mainwindow.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,10 @@ void MainWindow::setupSettingsMenu()
12901290
&Mlt::VideoWidget::hlgActiveChanged,
12911291
m_hdrPreviewWindow,
12921292
&HdrPreviewWindow::setHlg);
1293+
auto *win = m_hdrPreviewWindow;
1294+
connect(m_player, &Player::played, win, [win](double) { win->setPlaying(true); });
1295+
connect(m_player, &Player::paused, win, [win](int) { win->setPlaying(false); });
1296+
connect(m_player, &Player::stopped, win, [win]() { win->setPlaying(false); });
12931297
connect(m_hdrPreviewWindow,
12941298
&QWindow::visibleChanged,
12951299
this,

src/mainwindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class MainWindow : public QMainWindow
8686
QString fileName() const { return m_currentFile; }
8787
bool isSourceClipMyProject(QString resource = MLT.resource(), bool withDialog = true);
8888
bool keyframesDockIsVisible() const;
89+
Player *player() const { return m_player; }
8990

9091
void keyPressEvent(QKeyEvent *);
9192
void keyReleaseEvent(QKeyEvent *);

0 commit comments

Comments
 (0)