diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini
index 44978dd8c..b6db39c48 100644
--- a/UI/data/locale/en-US.ini
+++ b/UI/data/locale/en-US.ini
@@ -517,6 +517,7 @@ Basic.Main.Controls="Controls"
Basic.Main.Connecting="Connecting..."
Basic.Main.StartRecording="Start Recording"
Basic.Main.StartReplayBuffer="Start Replay Buffer"
+Basic.Main.SaveReplay="Save Replay"
Basic.Main.StartStreaming="Start Streaming"
Basic.Main.StopRecording="Stop Recording"
Basic.Main.PauseRecording="Pause Recording"
diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss
index ffd972b3c..e1d4b0c6b 100644
--- a/UI/data/themes/Acri.qss
+++ b/UI/data/themes/Acri.qss
@@ -1044,3 +1044,9 @@ SceneTree#scenes {
padding-right: 10px;
margin: 0px;
}
+
+/* Save replay icon */
+
+* [themeID="replayIconSmall"] {
+ qproperty-icon: url(./Dark/save.svg);
+}
diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss
index 94819ae70..03838e697 100644
--- a/UI/data/themes/Dark.qss
+++ b/UI/data/themes/Dark.qss
@@ -809,3 +809,9 @@ SceneTree {
*[gridMode="true"] SceneTree::item:checked {
background-color: rgb(122,121,122); /* light */
}
+
+/* Save icon */
+
+* [themeID="replayIconSmall"] {
+ qproperty-icon: url(./Dark/save.svg);
+}
diff --git a/UI/data/themes/Dark/save.svg b/UI/data/themes/Dark/save.svg
new file mode 100644
index 000000000..346fca847
--- /dev/null
+++ b/UI/data/themes/Dark/save.svg
@@ -0,0 +1,3 @@
+
diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss
index 2aa304f9b..e852ca7ef 100644
--- a/UI/data/themes/Rachni.qss
+++ b/UI/data/themes/Rachni.qss
@@ -1372,3 +1372,9 @@ SceneTree#scenes {
qproperty-gridItemWidth: 150;
qproperty-gridItemHeight: 30;
}
+
+/* Save icon */
+
+* [themeID="replayIconSmall"] {
+ qproperty-icon: url(./Dark/save.svg);
+}
diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss
index 572946219..99b146761 100644
--- a/UI/data/themes/System.qss
+++ b/UI/data/themes/System.qss
@@ -240,3 +240,9 @@ SceneTree {
qproperty-gridItemWidth: 150;
qproperty-gridItemHeight: 24;
}
+
+/* Save icon */
+
+* [themeID="replayIconSmall"] {
+ qproperty-icon: url(:res/images/save.svg);
+}
diff --git a/UI/forms/images/save.svg b/UI/forms/images/save.svg
new file mode 100644
index 000000000..39fb418fa
--- /dev/null
+++ b/UI/forms/images/save.svg
@@ -0,0 +1,3 @@
+
diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc
index 449b217e1..0cd6cf2ee 100644
--- a/UI/forms/obs.qrc
+++ b/UI/forms/obs.qrc
@@ -1,5 +1,6 @@
+ images/save.svg
images/media-pause.svg
images/mute.svg
images/refresh.svg
diff --git a/UI/record-button.cpp b/UI/record-button.cpp
index 021f5b83e..0e080a796 100644
--- a/UI/record-button.cpp
+++ b/UI/record-button.cpp
@@ -17,3 +17,20 @@ void RecordButton::resizeEvent(QResizeEvent *event)
event->accept();
}
+
+void ReplayBufferButton::resizeEvent(QResizeEvent *event)
+{
+ OBSBasic *main = OBSBasic::Get();
+ if (!main->replay)
+ return;
+
+ QSize replaySize = main->replay->size();
+ int height = main->ui->recordButton->size().height();
+
+ if (replaySize.height() != height || replaySize.width() != height) {
+ main->replay->setMinimumSize(height, height);
+ main->replay->setMaximumSize(height, height);
+ }
+
+ event->accept();
+}
diff --git a/UI/record-button.hpp b/UI/record-button.hpp
index c1782aafa..d6c083e21 100644
--- a/UI/record-button.hpp
+++ b/UI/record-button.hpp
@@ -10,3 +10,16 @@ public:
virtual void resizeEvent(QResizeEvent *event) override;
};
+
+class ReplayBufferButton : public QPushButton {
+ Q_OBJECT
+
+public:
+ inline ReplayBufferButton(const QString &text,
+ QWidget *parent = nullptr)
+ : QPushButton(text, parent)
+ {
+ }
+
+ virtual void resizeEvent(QResizeEvent *event) override;
+};
diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp
index a54f95785..1ed02c9e7 100644
--- a/UI/window-basic-main.cpp
+++ b/UI/window-basic-main.cpp
@@ -1501,18 +1501,22 @@ void OBSBasic::ResetOutputs()
: CreateSimpleOutputHandler(this));
delete replayBufferButton;
+ delete replayLayout;
if (outputHandler->replayBuffer) {
- replayBufferButton = new QPushButton(
+ replayBufferButton = new ReplayBufferButton(
QTStr("Basic.Main.StartReplayBuffer"), this);
replayBufferButton->setCheckable(true);
connect(replayBufferButton.data(),
&QPushButton::clicked, this,
&OBSBasic::ReplayBufferClicked);
+ replayLayout = new QHBoxLayout(this);
+ replayLayout->addWidget(replayBufferButton);
+
replayBufferButton->setProperty("themeID",
"replayBufferButton");
- ui->buttonsVLayout->insertWidget(2, replayBufferButton);
+ ui->buttonsVLayout->insertLayout(2, replayLayout);
}
if (sysTrayReplayBuffer)
@@ -5664,6 +5668,7 @@ void OBSBasic::ReplayBufferStart()
api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED);
OnActivate();
+ UpdateReplayBuffer();
blog(LOG_INFO, REPLAY_BUFFER_START);
}
@@ -5725,6 +5730,7 @@ void OBSBasic::ReplayBufferStop(int code)
api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED);
OnDeactivate();
+ UpdateReplayBuffer(false);
}
void OBSBasic::on_streamButton_clicked()
@@ -7602,6 +7608,26 @@ void OBSBasic::UpdatePause(bool activate)
}
}
+void OBSBasic::UpdateReplayBuffer(bool activate)
+{
+ if (!activate || !outputHandler ||
+ !outputHandler->ReplayBufferActive()) {
+ replay.reset();
+ return;
+ }
+
+ replay.reset(new QPushButton());
+ replay->setAccessibleName(QTStr("Basic.Main.SaveReplay"));
+ replay->setToolTip(QTStr("Basic.Main.SaveReplay"));
+ replay->setCheckable(true);
+ replay->setChecked(false);
+ replay->setProperty("themeID",
+ QVariant(QStringLiteral("replayIconSmall")));
+ connect(replay.data(), &QAbstractButton::clicked, this,
+ &OBSBasic::ReplayBufferSave);
+ replayLayout->addWidget(replay.data());
+}
+
#define MBYTE (1024ULL * 1024ULL)
#define MBYTES_LEFT_STOP_REC 50ULL
#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE)
diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp
index 935e553ba..df0606cb9 100644
--- a/UI/window-basic-main.hpp
+++ b/UI/window-basic-main.hpp
@@ -157,6 +157,7 @@ class OBSBasic : public OBSMainWindow {
friend class AutoConfig;
friend class AutoConfigStreamPage;
friend class RecordButton;
+ friend class ReplayBufferButton;
friend class ExtraBrowsersModel;
friend class ExtraBrowsersDelegate;
friend struct OBSStudioAPI;
@@ -241,7 +242,9 @@ private:
QPointer transitionButton;
QPointer replayBufferButton;
+ QPointer replayLayout;
QScopedPointer pause;
+ QScopedPointer replay;
QScopedPointer trayIcon;
QPointer sysTrayStream;
@@ -669,6 +672,7 @@ private:
void AutoRemux();
void UpdatePause(bool activate = true);
+ void UpdateReplayBuffer(bool activate = true);
bool LowDiskSpace();
void DiskSpaceMessage();