From eab10d48b2f86d7eaf9afd243ab3984d3d8ff5f2 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Sun, 7 Jul 2019 15:47:29 -0700 Subject: [PATCH] UI: Add pause support Adds support for pausing recordings. When settings are eligible for recordings, a pause button will appear next to the recording button. If the settings are not eligible, it will warn the user in the output settings that they cannot pause recordings if those settings are used. --- UI/CMakeLists.txt | 2 + UI/api-interface.cpp | 12 ++ UI/data/locale/en-US.ini | 7 + UI/data/themes/Acri.qss | 4 + UI/data/themes/Dark.qss | 17 ++ UI/data/themes/Dark/media-pause.svg | 3 + UI/data/themes/Rachni.qss | 4 + UI/data/themes/System.qss | 4 + UI/forms/OBSBasic.ui | 90 ++++++---- UI/forms/images/media-pause.svg | 3 + UI/forms/obs.qrc | 1 + UI/obs-frontend-api/obs-frontend-api.cpp | 11 ++ UI/obs-frontend-api/obs-frontend-api.h | 5 + UI/obs-frontend-api/obs-frontend-internal.hpp | 2 + UI/record-button.cpp | 18 ++ UI/record-button.hpp | 12 ++ UI/window-basic-main-outputs.cpp | 2 + UI/window-basic-main.cpp | 157 +++++++++++++++++- UI/window-basic-main.hpp | 13 +- UI/window-basic-settings.cpp | 13 ++ UI/window-basic-status-bar.cpp | 23 ++- docs/sphinx/reference-frontend-api.rst | 24 +++ 22 files changed, 385 insertions(+), 42 deletions(-) create mode 100644 UI/data/themes/Dark/media-pause.svg create mode 100644 UI/forms/images/media-pause.svg create mode 100644 UI/record-button.cpp create mode 100644 UI/record-button.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index ac5411056..1ed137860 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -235,6 +235,7 @@ set(obs_SOURCES slider-ignorewheel.cpp combobox-ignorewheel.cpp spinbox-ignorewheel.cpp + record-button.cpp volume-control.cpp adv-audio-control.cpp item-widget-helpers.cpp @@ -289,6 +290,7 @@ set(obs_HEADERS focus-list.hpp menu-button.hpp mute-checkbox.hpp + record-button.hpp volume-control.hpp adv-audio-control.hpp item-widget-helpers.hpp diff --git a/UI/api-interface.cpp b/UI/api-interface.cpp index f57f3700d..0d928ac76 100644 --- a/UI/api-interface.cpp +++ b/UI/api-interface.cpp @@ -21,6 +21,7 @@ void EnumSceneCollections(function &&cb); extern volatile bool streaming_active; extern volatile bool recording_active; +extern volatile bool recording_paused; extern volatile bool replaybuf_active; /* ------------------------------------------------------------------------- */ @@ -265,6 +266,17 @@ struct OBSStudioAPI : obs_frontend_callbacks { return os_atomic_load_bool(&recording_active); } + void obs_frontend_recording_pause(bool pause) override + { + QMetaObject::invokeMethod(main, pause ? "PauseRecording" + : "UnpauseRecording"); + } + + bool obs_frontend_recording_paused(void) override + { + return os_atomic_load_bool(&recording_paused); + } + void obs_frontend_replay_buffer_start(void) override { QMetaObject::invokeMethod(main, "StartReplayBuffer"); diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 2400d1fb0..ec9ef1924 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -281,6 +281,9 @@ Output.StartRecordingFailed="Failed to start recording" Output.StartReplayFailed="Failed to start replay buffer" Output.StartFailedGeneric="Starting the output failed. Please check the log for details.\n\nNote: If you are using the NVENC or AMD encoders, make sure your video drivers are up to date." +# replay buffer + pause warning message +Output.ReplayBuffer.PauseWarning.Title="Cannot save replays while paused" +Output.ReplayBuffer.PauseWarning.Text="Warning: Replays cannot be saved while recording is paused." # output connect messages Output.ConnectFail.Title="Failed to connect" @@ -501,6 +504,8 @@ Basic.Main.StartRecording="Start Recording" Basic.Main.StartReplayBuffer="Start Replay Buffer" Basic.Main.StartStreaming="Start Streaming" Basic.Main.StopRecording="Stop Recording" +Basic.Main.PauseRecording="Pause Recording" +Basic.Main.UnpauseRecording="Unpause Recording" Basic.Main.StoppingRecording="Stopping Recording..." Basic.Main.StopReplayBuffer="Stop Replay Buffer" Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..." @@ -678,6 +683,7 @@ Basic.Settings.Output.Simple.RecordingQuality.HQ="Indistinguishable Quality, Lar Basic.Settings.Output.Simple.RecordingQuality.Lossless="Lossless Quality, Tremendously Large File Size" Basic.Settings.Output.Simple.Warn.VideoBitrate="Warning: The streaming video bitrate will be set to %1, which is the upper limit for the current streaming service. If you're sure you want to go above %1, enable advanced encoder options and uncheck \"Enforce streaming service bitrate limits\"." Basic.Settings.Output.Simple.Warn.AudioBitrate="Warning: The streaming audio bitrate will be set to %1, which is the upper limit for the current streaming service. If you're sure you want to go above %1, enable advanced encoder options and uncheck \"Enforce streaming service bitrate limits\"." +Basic.Settings.Output.Simple.Warn.CannotPause="Warning: Recordings cannot be paused if the recording quality is set to \"Same as stream\"." Basic.Settings.Output.Simple.Warn.Encoder="Warning: Recording with a software encoder at a different quality than the stream will require extra CPU usage if you stream and record at the same time." Basic.Settings.Output.Simple.Warn.Lossless="Warning: Lossless quality generates tremendously large file sizes! Lossless quality can use upward of 7 gigabytes of disk space per minute at high resolutions and framerates. Lossless is not recommended for long recordings unless you have a very large amount of disk space available." Basic.Settings.Output.Simple.Warn.Lossless.Msg="Are you sure you want to use lossless quality?" @@ -909,6 +915,7 @@ SceneItemHide="Hide '%1'" OutputWarnings.NoTracksSelected="You must select at least one track" OutputWarnings.MultiTrackRecording="Warning: Certain formats (such as FLV) do not support multiple tracks per recording" OutputWarnings.MP4Recording="Warning: Recordings saved to MP4/MOV will be unrecoverable if the file cannot be finalized (e.g. as a result of BSODs, power losses, etc.). If you want to record multiple audio tracks consider using MKV and remux the recording to MP4/MOV after it is finished (File → Remux Recordings)" +OutputWarnings.CannotPause="Warning: Recordings cannot be paused if the recording encoder is set to \"(Use stream encoder)\"" # deleting final scene FinalScene.Title="Delete Scene" diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index ae2ef8c9a..e98cd8b3d 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -353,6 +353,10 @@ QToolButton:pressed { qproperty-icon: url(./Dark/down.svg); } +* [themeID="pauseIconSmall"] { + qproperty-icon: url(./Dark/media-pause.svg); +} + /* Tab Widget */ QTabWidget::pane { /* The tab widget frame */ diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index 693a5561f..9a7c65e74 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -253,6 +253,10 @@ QToolButton:pressed { qproperty-icon: url(./Dark/down.svg); } +* [themeID="pauseIconSmall"] { + qproperty-icon: url(./Dark/media-pause.svg); +} + /* Tab Widget */ @@ -577,6 +581,19 @@ OBSHotkeyLabel[hotkeyPairHover=true] { color: red; } +/* Pause */ +PauseCheckBox { + outline: none; +} + +PauseCheckBox::indicator:checked { + image: url(:/res/images/media-pause.svg); +} + +PauseCheckBox::indicator:unchecked { + image: url(:/res/images/media-play.svg); +} + /* Group Collapse Checkbox */ SourceTreeSubItemCheckBox { diff --git a/UI/data/themes/Dark/media-pause.svg b/UI/data/themes/Dark/media-pause.svg new file mode 100644 index 000000000..6050b9472 --- /dev/null +++ b/UI/data/themes/Dark/media-pause.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 88bde15e7..1e6a697f2 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -507,6 +507,10 @@ QToolButton:pressed { qproperty-icon: url(./Dark/down.svg); } +* [themeID="pauseIconSmall"] { + qproperty-icon: url(./Dark/media-pause.svg); +} + /***********************/ /* --- Combo boxes --- */ /***********************/ diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index c68e0c462..0eb01cf19 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -39,6 +39,10 @@ qproperty-icon: url(:/res/images/down.svg); } +* [themeID="pauseIconSmall"] { + qproperty-icon: url(:/res/images/media-pause.svg); +} + MuteCheckBox { outline: none; } diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index b082729f8..3b72c6295 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -116,7 +116,7 @@ 0 0 1079 - 22 + 21 @@ -656,7 +656,7 @@ 0 0 - 64 + 80 16 @@ -843,7 +843,7 @@ - + :/res/images/add.png:/res/images/add.png @@ -878,7 +878,7 @@ - + :/res/images/list_remove.png:/res/images/list_remove.png @@ -913,7 +913,7 @@ - + :/res/images/configuration21_16.png:/res/images/configuration21_16.png @@ -1000,7 +1000,7 @@ 8 - + 2 @@ -1037,29 +1037,48 @@ - - - true + + + 2 - - - 0 - 0 - + + 0 - - - 130 - 0 - + + 0 - - Basic.Main.StartRecording + + 0 - - true + + 0 - + + + + true + + + + 0 + 0 + + + + + 130 + 0 + + + + Basic.Main.StartRecording + + + true + + + + @@ -1115,7 +1134,7 @@ - + :/res/images/add.png:/res/images/add.png @@ -1127,7 +1146,7 @@ - + :/res/images/add.png:/res/images/add.png @@ -1139,7 +1158,7 @@ - + :/res/images/list_remove.png:/res/images/list_remove.png @@ -1157,7 +1176,7 @@ - + :/res/images/list_remove.png:/res/images/list_remove.png @@ -1178,7 +1197,7 @@ true - + :/res/images/properties.png:/res/images/properties.png @@ -1190,7 +1209,7 @@ - + :/res/images/up.png:/res/images/up.png @@ -1205,7 +1224,7 @@ true - + :/res/images/up.png:/res/images/up.png @@ -1217,7 +1236,7 @@ - + :/res/images/down.png:/res/images/down.png @@ -1232,7 +1251,7 @@ true - + :/res/images/down.png:/res/images/down.png @@ -1733,6 +1752,11 @@
window-dock.hpp
1 + + RecordButton + QPushButton +
record-button.hpp
+
diff --git a/UI/forms/images/media-pause.svg b/UI/forms/images/media-pause.svg new file mode 100644 index 000000000..8e45ee21f --- /dev/null +++ b/UI/forms/images/media-pause.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc index 7c647c7b7..87e11e359 100644 --- a/UI/forms/obs.qrc +++ b/UI/forms/obs.qrc @@ -1,5 +1,6 @@ + images/media-pause.svg images/mute.svg images/refresh.svg images/no_sources.svg diff --git a/UI/obs-frontend-api/obs-frontend-api.cpp b/UI/obs-frontend-api/obs-frontend-api.cpp index 0181a87ab..c5fafeb0c 100644 --- a/UI/obs-frontend-api/obs-frontend-api.cpp +++ b/UI/obs-frontend-api/obs-frontend-api.cpp @@ -227,6 +227,17 @@ bool obs_frontend_recording_active(void) return !!callbacks_valid() ? c->obs_frontend_recording_active() : false; } +void obs_frontend_recording_pause(bool pause) +{ + if (!!callbacks_valid()) + c->obs_frontend_recording_pause(pause); +} + +bool obs_frontend_recording_paused(void) +{ + return !!callbacks_valid() ? c->obs_frontend_recording_paused() : false; +} + void obs_frontend_replay_buffer_start(void) { if (callbacks_valid()) diff --git a/UI/obs-frontend-api/obs-frontend-api.h b/UI/obs-frontend-api/obs-frontend-api.h index fb0d8b686..68fa917fd 100644 --- a/UI/obs-frontend-api/obs-frontend-api.h +++ b/UI/obs-frontend-api/obs-frontend-api.h @@ -44,6 +44,9 @@ enum obs_frontend_event { OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP, OBS_FRONTEND_EVENT_FINISHED_LOADING, + + OBS_FRONTEND_EVENT_RECORDING_PAUSED, + OBS_FRONTEND_EVENT_RECORDING_UNPAUSED, }; /* ------------------------------------------------------------------------- */ @@ -152,6 +155,8 @@ EXPORT bool obs_frontend_streaming_active(void); EXPORT void obs_frontend_recording_start(void); EXPORT void obs_frontend_recording_stop(void); EXPORT bool obs_frontend_recording_active(void); +EXPORT void obs_frontend_recording_pause(bool pause); +EXPORT bool obs_frontend_recording_paused(void); EXPORT void obs_frontend_replay_buffer_start(void); EXPORT void obs_frontend_replay_buffer_save(void); diff --git a/UI/obs-frontend-api/obs-frontend-internal.hpp b/UI/obs-frontend-api/obs-frontend-internal.hpp index 617b842fc..80051ca79 100644 --- a/UI/obs-frontend-api/obs-frontend-internal.hpp +++ b/UI/obs-frontend-api/obs-frontend-internal.hpp @@ -43,6 +43,8 @@ struct obs_frontend_callbacks { virtual void obs_frontend_recording_start(void) = 0; virtual void obs_frontend_recording_stop(void) = 0; virtual bool obs_frontend_recording_active(void) = 0; + virtual void obs_frontend_recording_pause(bool pause) = 0; + virtual bool obs_frontend_recording_paused(void) = 0; virtual void obs_frontend_replay_buffer_start(void) = 0; virtual void obs_frontend_replay_buffer_save(void) = 0; diff --git a/UI/record-button.cpp b/UI/record-button.cpp new file mode 100644 index 000000000..8b82c2b0f --- /dev/null +++ b/UI/record-button.cpp @@ -0,0 +1,18 @@ +#include "record-button.hpp" +#include "window-basic-main.hpp" + +void RecordButton::resizeEvent(QResizeEvent *event) +{ + OBSBasic *main = OBSBasic::Get(); + if (!main->pause) + return; + + QSize newSize = event->size(); + QSize pauseSize = main->pause->size(); + int height = main->ui->recordButton->size().height(); + + if (pauseSize.height() != height || pauseSize.width() != height) { + main->pause->setMinimumSize(height, height); + main->pause->setMaximumSize(height, height); + } +} diff --git a/UI/record-button.hpp b/UI/record-button.hpp new file mode 100644 index 000000000..c1782aafa --- /dev/null +++ b/UI/record-button.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +class RecordButton : public QPushButton { + Q_OBJECT + +public: + inline RecordButton(QWidget *parent = nullptr) : QPushButton(parent) {} + + virtual void resizeEvent(QResizeEvent *event) override; +}; diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index f15bca4ef..93cd37c7c 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -12,6 +12,7 @@ extern bool EncoderAvailable(const char *encoder); volatile bool streaming_active = false; volatile bool recording_active = false; +volatile bool recording_paused = false; volatile bool replaybuf_active = false; static void OBSStreamStarting(void *data, calldata_t *params) @@ -88,6 +89,7 @@ static void OBSStopRecording(void *data, calldata_t *params) output->recordingActive = false; os_atomic_set_bool(&recording_active, false); + os_atomic_set_bool(&recording_paused, false); QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index dabc30a53..f482e33a4 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -2112,6 +2112,17 @@ void OBSBasic::CreateHotkeys() LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); + pauseHotkeys = obs_hotkey_pair_register_frontend( + "OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), + "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), + MAKE_CALLBACK(basic.pause && !basic.pause->isChecked(), + basic.PauseRecording, "Pausing recording"), + MAKE_CALLBACK(basic.pause && basic.pause->isChecked(), + basic.UnpauseRecording, "Unpausing recording"), + this, this); + LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", + "OBSBasic.UnpauseRecording"); + replayBufHotkeys = obs_hotkey_pair_register_frontend( "OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), @@ -2169,6 +2180,7 @@ void OBSBasic::ClearHotkeys() { obs_hotkey_pair_unregister(streamingHotkeys); obs_hotkey_pair_unregister(recordingHotkeys); + obs_hotkey_pair_unregister(pauseHotkeys); obs_hotkey_pair_unregister(replayBufHotkeys); obs_hotkey_pair_unregister(togglePreviewHotkeys); obs_hotkey_unregister(forceStreamingStopHotkey); @@ -5319,6 +5331,7 @@ void OBSBasic::RecordingStart() api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTED); OnActivate(); + UpdatePause(); blog(LOG_INFO, RECORDING_START); } @@ -5385,11 +5398,46 @@ void OBSBasic::RecordingStop(int code, QString last_error) AutoRemux(); OnDeactivate(); + UpdatePause(false); } #define RP_NO_HOTKEY_TITLE QTStr("Output.ReplayBuffer.NoHotkey.Title") #define RP_NO_HOTKEY_TEXT QTStr("Output.ReplayBuffer.NoHotkey.Msg") +extern volatile bool recording_paused; +extern volatile bool replaybuf_active; + +void OBSBasic::ShowReplayBufferPauseWarning() +{ + auto msgBox = []() { + QMessageBox msgbox(App()->GetMainWindow()); + msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." + "PauseWarning.Title")); + msgbox.setText(QTStr("Output.ReplayBuffer." + "PauseWarning.Text")); + msgbox.setIcon(QMessageBox::Icon::Information); + msgbox.addButton(QMessageBox::Ok); + + QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); + msgbox.setCheckBox(cb); + + msgbox.exec(); + + if (cb->isChecked()) { + config_set_bool(App()->GlobalConfig(), "General", + "WarnedAboutReplayBufferPausing", true); + config_save_safe(App()->GlobalConfig(), "tmp", nullptr); + } + }; + + bool warned = config_get_bool(App()->GlobalConfig(), "General", + "WarnedAboutReplayBufferPausing"); + if (!warned) { + QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, + Q_ARG(VoidFunc, msgBox)); + } +} + void OBSBasic::StartReplayBuffer() { if (!outputHandler || !outputHandler->replayBuffer) @@ -5423,8 +5471,12 @@ void OBSBasic::StartReplayBuffer() api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); SaveProject(); - if (!outputHandler->StartReplayBuffer()) + + if (!outputHandler->StartReplayBuffer()) { replayBufferButton->setChecked(false); + } else if (os_atomic_load_bool(&recording_paused)) { + ShowReplayBufferPauseWarning(); + } } void OBSBasic::ReplayBufferStopping() @@ -7295,3 +7347,106 @@ void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) patronJson = QT_TO_UTF8(text); } + +void OBSBasic::PauseRecording() +{ + if (!pause || !outputHandler || !outputHandler->fileOutput) + return; + + obs_output_t *output = outputHandler->fileOutput; + + if (obs_output_pause(output, true)) { + pause->setChecked(true); + os_atomic_set_bool(&recording_paused, true); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_RECORDING_PAUSED); + + if (os_atomic_load_bool(&replaybuf_active)) + ShowReplayBufferPauseWarning(); + } +} + +void OBSBasic::UnpauseRecording() +{ + if (!pause || !outputHandler || !outputHandler->fileOutput) + return; + + obs_output_t *output = outputHandler->fileOutput; + + if (obs_output_pause(output, false)) { + pause->setChecked(false); + os_atomic_set_bool(&recording_paused, false); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); + } +} + +void OBSBasic::PauseToggled() +{ + if (!pause || !outputHandler || !outputHandler->fileOutput) + return; + + obs_output_t *output = outputHandler->fileOutput; + bool enable = !obs_output_paused(output); + + if (obs_output_pause(output, enable)) { + os_atomic_set_bool(&recording_paused, enable); + + if (api) + api->on_event( + enable ? OBS_FRONTEND_EVENT_RECORDING_PAUSED + : OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); + + if (enable && os_atomic_load_bool(&replaybuf_active)) + ShowReplayBufferPauseWarning(); + } else { + pause->setChecked(!enable); + } +} + +void OBSBasic::UpdatePause(bool activate) +{ + if (!activate || !outputHandler || !outputHandler->RecordingActive()) { + pause.reset(); + return; + } + + const char *mode = config_get_string(basicConfig, "Output", "Mode"); + bool adv = astrcmpi(mode, "Advanced") == 0; + bool shared; + + if (adv) { + const char *recType = + config_get_string(basicConfig, "AdvOut", "RecType"); + + if (astrcmpi(recType, "FFmpeg") == 0) { + shared = config_get_bool(basicConfig, "AdvOut", + "FFOutputToFile"); + } else { + const char *recordEncoder = config_get_string( + basicConfig, "AdvOut", "RecEncoder"); + shared = astrcmpi(recordEncoder, "none") == 0; + } + } else { + const char *quality = config_get_string( + basicConfig, "SimpleOutput", "RecQuality"); + shared = strcmp(quality, "Stream") == 0; + } + + if (!shared) { + pause.reset(new QPushButton()); + pause->setAccessibleName(QTStr("Basic.Main.PauseRecording")); + pause->setToolTip(QTStr("Basic.Main.PauseRecording")); + pause->setCheckable(true); + pause->setChecked(false); + pause->setProperty("themeID", + QVariant(QStringLiteral("pauseIconSmall"))); + connect(pause.data(), &QAbstractButton::clicked, this, + &OBSBasic::PauseToggled); + ui->recordingLayout->addWidget(pause.data()); + } else { + pause.reset(); + } +} diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 62d65c7dd..287b4957a 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -123,6 +123,7 @@ class OBSBasic : public OBSMainWindow { friend class Auth; friend class AutoConfig; friend class AutoConfigStreamPage; + friend class RecordButton; friend struct OBSStudioAPI; enum class MoveDir { Up, Down, Left, Right }; @@ -204,6 +205,7 @@ private: QPointer transitionButton; QPointer replayBufferButton; + QScopedPointer pause; QScopedPointer trayIcon; QPointer sysTrayStream; @@ -323,8 +325,8 @@ private: int GetTopSelectedSourceItem(); - obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, replayBufHotkeys, - togglePreviewHotkeys; + obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, + replayBufHotkeys, togglePreviewHotkeys; obs_hotkey_id forceStreamingStopHotkey; void InitDefaultTransitions(); @@ -440,6 +442,7 @@ public slots: void RecordStopping(); void RecordingStop(int code, QString last_error); + void ShowReplayBufferPauseWarning(); void StartReplayBuffer(); void StopReplayBuffer(); @@ -465,6 +468,9 @@ public slots: void UpdatePatronJson(const QString &text, const QString &error); + void PauseRecording(); + void UnpauseRecording(); + private slots: void AddSceneItem(OBSSceneItem item); void AddScene(OBSSource source); @@ -557,6 +563,7 @@ private: static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); void AutoRemux(); + void UpdatePause(bool activate = true); public: OBSSource GetProgramSource(); @@ -760,6 +767,8 @@ private slots: void on_resetUI_triggered(); void on_lockUI_toggled(bool lock); + void PauseToggled(); + void logUploadFinished(const QString &text, const QString &error); void updateCheckFinished(); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 2785b1381..95f8cfa25 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -729,6 +729,8 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) SLOT(AdvOutRecCheckWarnings())); connect(ui->advOutRecFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(AdvOutRecCheckWarnings())); + connect(ui->advOutRecEncoder, SIGNAL(currentIndexChanged(int)), this, + SLOT(AdvOutRecCheckWarnings())); AdvOutRecCheckWarnings(); ui->buttonBox->button(QDialogButtonBox::Apply)->setIcon(QIcon()); @@ -3929,6 +3931,13 @@ void OBSBasicSettings::AdvOutRecCheckWarnings() warningMsg = QTStr("OutputWarnings.MultiTrackRecording"); } + bool useStreamEncoder = ui->advOutRecEncoder->currentIndex() == 0; + if (useStreamEncoder) { + if (!warningMsg.isEmpty()) + warningMsg += "\n\n"; + warningMsg += QTStr("OutputWarnings.CannotPause"); + } + if (ui->advOutRecFormat->currentText().compare("mp4") == 0 || ui->advOutRecFormat->currentText().compare("mov") == 0) { if (!warningMsg.isEmpty()) @@ -4387,6 +4396,10 @@ void OBSBasicSettings::SimpleRecordingEncoderChanged() warning += "\n\n"; warning += SIMPLE_OUTPUT_WARNING("Encoder"); } + } else { + if (!warning.isEmpty()) + warning += "\n\n"; + warning += SIMPLE_OUTPUT_WARNING("CannotPause"); } if (qual != "Lossless" && diff --git a/UI/window-basic-status-bar.cpp b/UI/window-basic-status-bar.cpp index b94a8c0bb..74dde3a3c 100644 --- a/UI/window-basic-status-bar.cpp +++ b/UI/window-basic-status-bar.cpp @@ -241,17 +241,28 @@ void OBSBasicStatusBar::UpdateStreamTime() } } +extern volatile bool recording_paused; + void OBSBasicStatusBar::UpdateRecordTime() { - totalRecordSeconds++; + bool paused = os_atomic_load_bool(&recording_paused); - int seconds = totalRecordSeconds % 60; - int totalMinutes = totalRecordSeconds / 60; - int minutes = totalMinutes % 60; - int hours = totalMinutes / 60; + if (!paused) + totalRecordSeconds++; QString text; - text.sprintf("REC: %02d:%02d:%02d", hours, minutes, seconds); + + if (paused) { + text = QStringLiteral("REC: PAUSED"); + } else { + int seconds = totalRecordSeconds % 60; + int totalMinutes = totalRecordSeconds / 60; + int minutes = totalMinutes % 60; + int hours = totalMinutes / 60; + + text.sprintf("REC: %02d:%02d:%02d", hours, minutes, seconds); + } + recordTime->setText(text); recordTime->setMinimumWidth(recordTime->width()); } diff --git a/docs/sphinx/reference-frontend-api.rst b/docs/sphinx/reference-frontend-api.rst index 3b5b86db6..46df5bd63 100644 --- a/docs/sphinx/reference-frontend-api.rst +++ b/docs/sphinx/reference-frontend-api.rst @@ -124,6 +124,18 @@ Structures/Enumerations the program is either about to load a new scene collection, or the program is about to exit. + - **OBS_FRONTEND_FINISHED_LOADING** + + Triggered when the program has finished loading. + + - **OBS_FRONTEND_EVENT_RECORDING_PAUSED** + + Triggered when the recording has been paused. + + - **OBS_FRONTEND_EVENT_RECORDING_UNPAUSED** + + Triggered when the recording has been unpaused. + .. type:: struct obs_frontend_source_list - DARRAY(obs_source_t*) **sources** @@ -402,6 +414,18 @@ Functions --------------------------------------- +.. function:: void obs_frontend_recording_pause(bool pause) + + :pause: *true* to pause recording, *false* to unpause. + +--------------------------------------- + +.. function:: bool obs_frontend_recording_paused(void) + + :return: *true* if recording paused, *false* otherwise. + +--------------------------------------- + .. function:: void obs_frontend_replay_buffer_start(void) Starts replay buffer.