diff --git a/UI/api-interface.cpp b/UI/api-interface.cpp index 6a5bcc84e..4612100f8 100644 --- a/UI/api-interface.cpp +++ b/UI/api-interface.cpp @@ -234,6 +234,21 @@ struct OBSStudioAPI : obs_frontend_callbacks { return main->outputHandler->RecordingActive(); } + void obs_frontend_replay_buffer_start(void) override + { + QMetaObject::invokeMethod(main, "StartReplayBuffer"); + } + + void obs_frontend_replay_buffer_stop(void) override + { + QMetaObject::invokeMethod(main, "StopReplayBuffer"); + } + + bool obs_frontend_replay_buffer_active(void) override + { + return main->outputHandler->ReplayBufferActive(); + } + void *obs_frontend_add_tools_menu_qaction(const char *name) override { main->ui->menuTools->setEnabled(true); @@ -286,6 +301,13 @@ struct OBSStudioAPI : obs_frontend_callbacks { return out; } + obs_output_t *obs_frontend_get_replay_buffer_output(void) override + { + OBSOutput out = main->outputHandler->replayBuffer; + obs_output_addref(out); + return out; + } + config_t *obs_frontend_get_profile_config(void) override { return main->basicConfig; diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 8802a12ae..a8ebb2b15 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -420,12 +420,14 @@ Basic.Settings.Output.Mode="Output Mode" Basic.Settings.Output.Mode.Simple="Simple" Basic.Settings.Output.Mode.Adv="Advanced" Basic.Settings.Output.Mode.FFmpeg="FFmpeg Output" -Basic.Settings.Output.UseReplayBuffer="Replay Buffer Mode" +Basic.Settings.Output.UseReplayBuffer="Enable Replay Buffer" Basic.Settings.Output.ReplayBuffer.SecondsMax="Maximum Replay Time (Seconds)" Basic.Settings.Output.ReplayBuffer.MegabytesMax="Maximum Memory (Megabytes)" Basic.Settings.Output.ReplayBuffer.Estimate="Estimated memory usage: %1 MB" Basic.Settings.Output.ReplayBuffer.EstimateUnknown="Cannot estimate memory usage. Please set maximum memory limit." Basic.Settings.Output.ReplayBuffer.HotkeyMessage="(Note: Make sure to set a hotkey for the replay buffer in the hotkeys section)" +Basic.Settings.Output.ReplayBuffer.Prefix="Replay Buffer Filename Prefix" +Basic.Settings.Output.ReplayBuffer.Suffix="Suffix" Basic.Settings.Output.Simple.SavePath="Recording Path" Basic.Settings.Output.Simple.RecordingQuality="Recording Quality" Basic.Settings.Output.Simple.RecordingQuality.Stream="Same as stream" @@ -572,10 +574,6 @@ Basic.Settings.Hotkeys="Hotkeys" Basic.Settings.Hotkeys.Pair="Key combinations shared with '%1' act as toggles" # basic mode hotkeys -Basic.Hotkeys.StartStreaming="Start Streaming" -Basic.Hotkeys.StopStreaming="Stop Streaming" -Basic.Hotkeys.StartRecording="Start Recording/Replay Buffer" -Basic.Hotkeys.StopRecording="Stop Recording/Replay Buffer" Basic.Hotkeys.SelectScene="Switch to scene" # system tray diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index 8c9ccdb61..ad11a9dfb 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -943,13 +943,6 @@ - - - - - - - @@ -957,6 +950,13 @@ + + + + + + + diff --git a/UI/obs-frontend-api/obs-frontend-api.cpp b/UI/obs-frontend-api/obs-frontend-api.cpp index 5d01b64d8..6951bc920 100644 --- a/UI/obs-frontend-api/obs-frontend-api.cpp +++ b/UI/obs-frontend-api/obs-frontend-api.cpp @@ -205,6 +205,23 @@ bool obs_frontend_recording_active(void) : false; } +void obs_frontend_replay_buffer_start(void) +{ + if (callbacks_valid()) c->obs_frontend_replay_buffer_start(); +} + +void obs_frontend_replay_buffer_stop(void) +{ + if (callbacks_valid()) c->obs_frontend_replay_buffer_stop(); +} + +bool obs_frontend_replay_buffer_active(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_replay_buffer_active() + : false; +} + void *obs_frontend_add_tools_menu_qaction(const char *name) { return !!callbacks_valid() @@ -248,6 +265,13 @@ obs_output_t *obs_frontend_get_recording_output(void) : nullptr; } +obs_output_t *obs_frontend_get_replay_buffer_output(void) +{ + return !!callbacks_valid() + ? c->obs_frontend_get_replay_buffer_output() + : nullptr; +} + config_t *obs_frontend_get_profile_config(void) { return !!callbacks_valid() diff --git a/UI/obs-frontend-api/obs-frontend-api.h b/UI/obs-frontend-api/obs-frontend-api.h index 8a6272a2a..7be113cee 100644 --- a/UI/obs-frontend-api/obs-frontend-api.h +++ b/UI/obs-frontend-api/obs-frontend-api.h @@ -70,6 +70,10 @@ 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_replay_buffer_start(void); +EXPORT void obs_frontend_replay_buffer_stop(void); +EXPORT bool obs_frontend_replay_buffer_active(void); + typedef void (*obs_frontend_cb)(void *private_data); EXPORT void *obs_frontend_add_tools_menu_qaction(const char *name); @@ -94,7 +98,12 @@ enum obs_frontend_event { OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED, OBS_FRONTEND_EVENT_PROFILE_CHANGED, OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED, - OBS_FRONTEND_EVENT_EXIT + OBS_FRONTEND_EVENT_EXIT, + + OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING, + OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED, + OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING, + OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED }; typedef void (*obs_frontend_event_cb)(enum obs_frontend_event event, @@ -116,6 +125,7 @@ EXPORT void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, EXPORT obs_output_t *obs_frontend_get_streaming_output(void); EXPORT obs_output_t *obs_frontend_get_recording_output(void); +EXPORT obs_output_t *obs_frontend_get_replay_buffer_output(void); EXPORT config_t *obs_frontend_get_profile_config(void); EXPORT config_t *obs_frontend_get_global_config(void); diff --git a/UI/obs-frontend-api/obs-frontend-internal.hpp b/UI/obs-frontend-api/obs-frontend-internal.hpp index cb8da9647..f22d89142 100644 --- a/UI/obs-frontend-api/obs-frontend-internal.hpp +++ b/UI/obs-frontend-api/obs-frontend-internal.hpp @@ -39,6 +39,10 @@ struct obs_frontend_callbacks { virtual void obs_frontend_recording_stop(void)=0; virtual bool obs_frontend_recording_active(void)=0; + virtual void obs_frontend_replay_buffer_start(void)=0; + virtual void obs_frontend_replay_buffer_stop(void)=0; + virtual bool obs_frontend_replay_buffer_active(void)=0; + virtual void *obs_frontend_add_tools_menu_qaction(const char *name)=0; virtual void obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data)=0; @@ -50,6 +54,7 @@ struct obs_frontend_callbacks { virtual obs_output_t *obs_frontend_get_streaming_output(void)=0; virtual obs_output_t *obs_frontend_get_recording_output(void)=0; + virtual obs_output_t *obs_frontend_get_replay_buffer_output(void)=0; virtual config_t *obs_frontend_get_profile_config(void)=0; virtual config_t *obs_frontend_get_global_config(void)=0; diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index c9e4b6cfa..525d0a686 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -84,6 +84,36 @@ static void OBSRecordStopping(void *data, calldata_t *params) UNUSED_PARAMETER(params); } +static void OBSStartReplayBuffer(void *data, calldata_t *params) +{ + BasicOutputHandler *output = static_cast(data); + + output->replayBufferActive = true; + QMetaObject::invokeMethod(output->main, "ReplayBufferStart"); + + UNUSED_PARAMETER(params); +} + +static void OBSStopReplayBuffer(void *data, calldata_t *params) +{ + BasicOutputHandler *output = static_cast(data); + int code = (int)calldata_int(params, "code"); + + output->replayBufferActive = false; + QMetaObject::invokeMethod(output->main, + "ReplayBufferStop", Q_ARG(int, code)); + + UNUSED_PARAMETER(params); +} + +static void OBSReplayBufferStopping(void *data, calldata_t *params) +{ + BasicOutputHandler *output = static_cast(data); + QMetaObject::invokeMethod(output->main, "ReplayBufferStopping"); + + UNUSED_PARAMETER(params); +} + static void FindBestFilename(string &strPath, bool noSpace) { int num = 2; @@ -154,6 +184,7 @@ struct SimpleOutput : BasicOutputHandler { string videoEncoder; string videoQuality; bool usingRecordingPreset = false; + bool recordingConfigured = false; bool ffmpegOutput = false; bool lowCPUx264 = false; @@ -179,12 +210,18 @@ struct SimpleOutput : BasicOutputHandler { void LoadStreamingPreset_h264(const char *encoder); + void UpdateRecording(); + bool ConfigureRecording(bool useReplayBuffer); + virtual bool StartStreaming(obs_service_t *service) override; virtual bool StartRecording() override; + virtual bool StartReplayBuffer() override; virtual void StopStreaming(bool force) override; virtual void StopRecording(bool force) override; + virtual void StopReplayBuffer(bool force) override; virtual bool StreamingActive() const override; virtual bool RecordingActive() const override; + virtual bool ReplayBufferActive() const override; }; void SimpleOutput::LoadRecordingPreset_Lossless() @@ -306,21 +343,34 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) LoadRecordingPreset(); if (!ffmpegOutput) { - replayBuffer = config_get_bool(main->Config(), + bool useReplayBuffer = config_get_bool(main->Config(), "SimpleOutput", "RecRB"); - if (replayBuffer) { + if (useReplayBuffer) { const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); obs_data_t *hotkey = obs_data_create_from_json(str); - fileOutput = obs_output_create("replay_buffer", + replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); obs_data_release(hotkey); - } else { - fileOutput = obs_output_create("ffmpeg_muxer", - "simple_file_output", nullptr, nullptr); + if (!replayBuffer) + throw "Failed to create replay buffer output " + "(simple output)"; + obs_output_release(replayBuffer); + + signal_handler_t *signal = + obs_output_get_signal_handler(replayBuffer); + + startReplayBuffer.Connect(signal, "start", + OBSStartReplayBuffer, this); + stopReplayBuffer.Connect(signal, "stop", + OBSStopReplayBuffer, this); + replayBufferStopping.Connect(signal, "stopping", + OBSReplayBufferStopping, this); } + fileOutput = obs_output_create("ffmpeg_muxer", + "simple_file_output", nullptr, nullptr); if (!fileOutput) throw "Failed to create recording output " "(simple output)"; @@ -649,8 +699,11 @@ static void ensure_directory_exists(string &path) os_mkdirs(directory.c_str()); } -bool SimpleOutput::StartRecording() +void SimpleOutput::UpdateRecording() { + if (replayBufferActive || recordingActive) + return; + if (usingRecordingPreset) { if (!ffmpegOutput) UpdateRecordingSettings(); @@ -661,6 +714,20 @@ bool SimpleOutput::StartRecording() if (!Active()) SetupOutputs(); + if (!ffmpegOutput) { + obs_output_set_video_encoder(fileOutput, h264Recording); + obs_output_set_audio_encoder(fileOutput, aacRecording, 0); + } + if (replayBuffer) { + obs_output_set_video_encoder(replayBuffer, h264Recording); + obs_output_set_audio_encoder(replayBuffer, aacRecording, 0); + } + + recordingConfigured = true; +} + +bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) +{ const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); const char *format = config_get_string(main->Config(), @@ -706,13 +773,8 @@ bool SimpleOutput::StartRecording() if (!overwriteIfExists) FindBestFilename(strPath, noSpace); - if (!ffmpegOutput) { - obs_output_set_video_encoder(fileOutput, h264Recording); - obs_output_set_audio_encoder(fileOutput, aacRecording, 0); - } - obs_data_t *settings = obs_data_create(); - if (replayBuffer) { + if (updateReplayBuffer) { obs_data_set_string(settings, "directory", path); obs_data_set_string(settings, "format", filenameFormat); obs_data_set_string(settings, "extension", format); @@ -723,17 +785,36 @@ bool SimpleOutput::StartRecording() obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str()); } + obs_data_set_string(settings, "muxer_settings", mux); - obs_output_update(fileOutput, settings); + if (updateReplayBuffer) + obs_output_update(replayBuffer, settings); + else + obs_output_update(fileOutput, settings); obs_data_release(settings); + return true; +} - if (obs_output_start(fileOutput)) { - return true; - } +bool SimpleOutput::StartRecording() +{ + UpdateRecording(); + if (!ConfigureRecording(false)) + return false; + if (!obs_output_start(fileOutput)) + return false; + return true; +} - return false; +bool SimpleOutput::StartReplayBuffer() +{ + UpdateRecording(); + if (!ConfigureRecording(true)) + return false; + if (!obs_output_start(replayBuffer)) + return false; + return true; } void SimpleOutput::StopStreaming(bool force) @@ -752,6 +833,14 @@ void SimpleOutput::StopRecording(bool force) obs_output_stop(fileOutput); } +void SimpleOutput::StopReplayBuffer(bool force) +{ + if (force) + obs_output_force_stop(replayBuffer); + else + obs_output_stop(replayBuffer); +} + bool SimpleOutput::StreamingActive() const { return obs_output_active(streamOutput); @@ -762,6 +851,11 @@ bool SimpleOutput::RecordingActive() const return obs_output_active(fileOutput); } +bool SimpleOutput::ReplayBufferActive() const +{ + return obs_output_active(replayBuffer); +} + /* ------------------------------------------------------------------------ */ struct AdvancedOutput : BasicOutputHandler { diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index e9d442284..ec814a782 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -5,19 +5,23 @@ class OBSBasic; struct BasicOutputHandler { OBSOutput fileOutput; OBSOutput streamOutput; + OBSOutput replayBuffer; bool streamingActive = false; bool recordingActive = false; bool delayActive = false; - bool replayBuffer = false; + bool replayBufferActive = false; OBSBasic *main; OBSSignal startRecording; OBSSignal stopRecording; + OBSSignal startReplayBuffer; + OBSSignal stopReplayBuffer; OBSSignal startStreaming; OBSSignal stopStreaming; OBSSignal streamDelayStarting; OBSSignal streamStopping; OBSSignal recordStopping; + OBSSignal replayBufferStopping; inline BasicOutputHandler(OBSBasic *main_) : main(main_) {} @@ -25,16 +29,20 @@ struct BasicOutputHandler { virtual bool StartStreaming(obs_service_t *service) = 0; virtual bool StartRecording() = 0; + virtual bool StartReplayBuffer() {return false;} virtual void StopStreaming(bool force = false) = 0; virtual void StopRecording(bool force = false) = 0; + virtual void StopReplayBuffer(bool force = false) {(void)force;} virtual bool StreamingActive() const = 0; virtual bool RecordingActive() const = 0; + virtual bool ReplayBufferActive() const {return false;} virtual void Update() = 0; inline bool Active() const { - return streamingActive || recordingActive || delayActive; + return streamingActive || recordingActive || delayActive || + replayBufferActive; } }; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 4aa21cfe5..72918ddc0 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1032,6 +1032,14 @@ void OBSBasic::InitPrimitives() obs_leave_graphics(); } +void OBSBasic::ReplayBufferClicked() +{ + if (outputHandler->ReplayBufferActive()) + StopReplayBuffer(); + else + StartReplayBuffer(); +}; + void OBSBasic::ResetOutputs() { ProfileScope("OBSBasic::ResetOutputs"); @@ -1045,15 +1053,23 @@ void OBSBasic::ResetOutputs() CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - if (outputHandler->replayBuffer) - ui->recordButton->setText( - QTStr("Basic.Main.StartReplayBuffer")); - else - ui->recordButton->setText( - QTStr("Basic.Main.StartRecording")); + delete replayBufferButton; - if (sysTrayRecord) - sysTrayRecord->setText(ui->recordButton->text()); + if (outputHandler->replayBuffer) { + replayBufferButton = new QPushButton( + QTStr("Basic.Main.StartReplayBuffer"), + this); + connect(replayBufferButton, + &QPushButton::clicked, + this, + &OBSBasic::ReplayBufferClicked); + + ui->buttonsVLayout->insertWidget(2, replayBufferButton); + } + + if (sysTrayReplayBuffer) + sysTrayReplayBuffer->setEnabled( + !!outputHandler->replayBuffer); } else { outputHandler->Update(); } @@ -1357,9 +1373,9 @@ void OBSBasic::CreateHotkeys() streamingHotkeys = obs_hotkey_pair_register_frontend( "OBSBasic.StartStreaming", - Str("Basic.Hotkeys.StartStreaming"), + Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Hotkeys.StopStreaming"), + Str("Basic.Main.StopStreaming"), MAKE_CALLBACK(!basic.outputHandler->StreamingActive(), basic.StartStreaming), MAKE_CALLBACK(basic.outputHandler->StreamingActive(), @@ -1385,9 +1401,9 @@ void OBSBasic::CreateHotkeys() recordingHotkeys = obs_hotkey_pair_register_frontend( "OBSBasic.StartRecording", - Str("Basic.Hotkeys.StartRecording"), + Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Hotkeys.StopRecording"), + Str("Basic.Main.StopRecording"), MAKE_CALLBACK(!basic.outputHandler->RecordingActive(), basic.StartRecording), MAKE_CALLBACK(basic.outputHandler->RecordingActive(), @@ -1395,6 +1411,19 @@ void OBSBasic::CreateHotkeys() this, this); LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); + + replayBufHotkeys = obs_hotkey_pair_register_frontend( + "OBSBasic.StartReplayBuffer", + Str("Basic.Main.StartReplayBuffer"), + "OBSBasic.StopReplayBuffer", + Str("Basic.Main.StopReplayBuffer"), + MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), + basic.StartReplayBuffer), + MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), + basic.StopReplayBuffer), + this, this); + LoadHotkeyPair(replayBufHotkeys, + "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); #undef MAKE_CALLBACK auto togglePreviewProgram = [] (void *data, obs_hotkey_id, @@ -1431,6 +1460,7 @@ void OBSBasic::ClearHotkeys() { obs_hotkey_pair_unregister(streamingHotkeys); obs_hotkey_pair_unregister(recordingHotkeys); + obs_hotkey_pair_unregister(replayBufHotkeys); obs_hotkey_unregister(forceStreamingStopHotkey); obs_hotkey_unregister(togglePreviewProgramHotkey); obs_hotkey_unregister(transitionHotkey); @@ -3660,6 +3690,10 @@ void OBSBasic::OpenSceneFilters() "==== Recording Start ===============================================" #define RECORDING_STOP \ "==== Recording Stop ================================================" +#define REPLAY_BUFFER_START \ + "==== Replay Buffer Start ===========================================" +#define REPLAY_BUFFER_STOP \ + "==== Replay Buffer Stop ============================================" #define STREAMING_START \ "==== Streaming Start ===============================================" #define STREAMING_STOP \ @@ -3918,31 +3952,11 @@ void OBSBasic::StreamingStop(int code) } } -#define RP_NO_HOTKEY_TITLE QTStr("Output.ReplayBuffer.NoHotkey.Title") -#define RP_NO_HOTKEY_TEXT QTStr("Output.ReplayBuffer.NoHotkey.Msg") - void OBSBasic::StartRecording() { if (outputHandler->RecordingActive()) return; - if (outputHandler->replayBuffer) { - obs_output_t *output = outputHandler->fileOutput; - obs_data_t *hotkeys = obs_hotkeys_save_output(output); - obs_data_array_t *bindings = obs_data_get_array(hotkeys, - "ReplayBuffer.Save"); - size_t count = obs_data_array_count(bindings); - obs_data_array_release(bindings); - obs_data_release(hotkeys); - - if (!count) { - QMessageBox::information(this, - RP_NO_HOTKEY_TITLE, - RP_NO_HOTKEY_TEXT); - return; - } - } - if (api) api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTING); @@ -3952,10 +3966,7 @@ void OBSBasic::StartRecording() void OBSBasic::RecordStopping() { - if (outputHandler->replayBuffer) - ui->recordButton->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - else - ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording")); + ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording")); if (sysTrayRecord) sysTrayRecord->setText(ui->recordButton->text()); @@ -3978,11 +3989,7 @@ void OBSBasic::StopRecording() void OBSBasic::RecordingStart() { ui->statusbar->RecordingStarted(outputHandler->fileOutput); - - if (outputHandler->replayBuffer) - ui->recordButton->setText(QTStr("Basic.Main.StopReplayBuffer")); - else - ui->recordButton->setText(QTStr("Basic.Main.StopRecording")); + ui->recordButton->setText(QTStr("Basic.Main.StopRecording")); if (sysTrayRecord) sysTrayRecord->setText(ui->recordButton->text()); @@ -3999,14 +4006,11 @@ void OBSBasic::RecordingStart() void OBSBasic::RecordingStop(int code) { ui->statusbar->RecordingStopped(); - - if (outputHandler->replayBuffer) - ui->recordButton->setText(QTStr("Basic.Main.StartReplayBuffer")); - else - ui->recordButton->setText(QTStr("Basic.Main.StartRecording")); + ui->recordButton->setText(QTStr("Basic.Main.StartRecording")); if (sysTrayRecord) sysTrayRecord->setText(ui->recordButton->text()); + blog(LOG_INFO, RECORDING_STOP); if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { @@ -4043,6 +4047,131 @@ void OBSBasic::RecordingStop(int code) OnDeactivate(); } +#define RP_NO_HOTKEY_TITLE QTStr("Output.ReplayBuffer.NoHotkey.Title") +#define RP_NO_HOTKEY_TEXT QTStr("Output.ReplayBuffer.NoHotkey.Msg") + +void OBSBasic::StartReplayBuffer() +{ + if (!outputHandler || !outputHandler->replayBuffer) + return; + if (outputHandler->ReplayBufferActive()) + return; + + obs_output_t *output = outputHandler->replayBuffer; + obs_data_t *hotkeys = obs_hotkeys_save_output(output); + obs_data_array_t *bindings = obs_data_get_array(hotkeys, + "ReplayBuffer.Save"); + size_t count = obs_data_array_count(bindings); + obs_data_array_release(bindings); + obs_data_release(hotkeys); + + if (!count) { + QMessageBox::information(this, + RP_NO_HOTKEY_TITLE, + RP_NO_HOTKEY_TEXT); + return; + } + + if (api) + api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); + + SaveProject(); + outputHandler->StartReplayBuffer(); +} + +void OBSBasic::ReplayBufferStopping() +{ + if (!outputHandler || !outputHandler->replayBuffer) + return; + + replayBufferButton->setText(QTStr("Basic.Main.StoppingReplayBuffer")); + + if (sysTrayReplayBuffer) + sysTrayReplayBuffer->setText(replayBufferButton->text()); + + replayBufferStopping = true; + if (api) + api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); +} + +void OBSBasic::StopReplayBuffer() +{ + if (!outputHandler || !outputHandler->replayBuffer) + return; + + SaveProject(); + + if (outputHandler->ReplayBufferActive()) + outputHandler->StopReplayBuffer(replayBufferStopping); + + OnDeactivate(); +} + +void OBSBasic::ReplayBufferStart() +{ + if (!outputHandler || !outputHandler->replayBuffer) + return; + + replayBufferButton->setText(QTStr("Basic.Main.StopReplayBuffer")); + + if (sysTrayReplayBuffer) + sysTrayReplayBuffer->setText(replayBufferButton->text()); + + replayBufferStopping = false; + if (api) + api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); + + OnActivate(); + + blog(LOG_INFO, REPLAY_BUFFER_START); +} + +void OBSBasic::ReplayBufferStop(int code) +{ + if (!outputHandler || !outputHandler->replayBuffer) + return; + + replayBufferButton->setText(QTStr("Basic.Main.StartReplayBuffer")); + + if (sysTrayReplayBuffer) + sysTrayReplayBuffer->setText(replayBufferButton->text()); + + blog(LOG_INFO, REPLAY_BUFFER_STOP); + + if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { + QMessageBox::information(this, + QTStr("Output.RecordFail.Title"), + QTStr("Output.RecordFail.Unsupported")); + + } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { + QMessageBox::information(this, + QTStr("Output.RecordNoSpace.Title"), + QTStr("Output.RecordNoSpace.Msg")); + + } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { + QMessageBox::information(this, + QTStr("Output.RecordError.Title"), + QTStr("Output.RecordError.Msg")); + + } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { + SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), + QSystemTrayIcon::Warning); + + } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { + SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), + QSystemTrayIcon::Warning); + + } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { + SysTrayNotify(QTStr("Output.RecordError.Msg"), + QSystemTrayIcon::Warning); + } + + if (api) + api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); + + OnDeactivate(); +} + void OBSBasic::on_streamButton_clicked() { if (outputHandler->StreamingActive()) { @@ -4737,10 +4866,13 @@ void OBSBasic::SystemTrayInit() trayIcon); sysTrayRecord = new QAction(QTStr("Basic.Main.StartRecording"), trayIcon); + sysTrayReplayBuffer = new QAction(QTStr("Basic.Main.StartReplayBuffer"), + trayIcon); exit = new QAction(QTStr("Exit"), trayIcon); - sysTrayRecord->setText(ui->recordButton->text()); + if (outputHandler && !outputHandler->replayBuffer) + sysTrayReplayBuffer->setEnabled(false); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, @@ -4751,6 +4883,8 @@ void OBSBasic::SystemTrayInit() this, SLOT(on_streamButton_clicked())); connect(sysTrayRecord, SIGNAL(triggered()), this, SLOT(on_recordButton_clicked())); + connect(sysTrayReplayBuffer, &QAction::triggered, + this, &OBSBasic::ReplayBufferClicked); connect(exit, SIGNAL(triggered()), this, SLOT(close())); @@ -4758,6 +4892,7 @@ void OBSBasic::SystemTrayInit() trayMenu->addAction(showHide); trayMenu->addAction(sysTrayStream); trayMenu->addAction(sysTrayRecord); + trayMenu->addAction(sysTrayReplayBuffer); trayMenu->addAction(exit); trayIcon->setContextMenu(trayMenu); } diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 3de906518..8cc5ef20a 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -131,6 +131,7 @@ private: std::unique_ptr outputHandler; bool streamingStopping = false; bool recordingStopping = false; + bool replayBufferStopping = false; gs_vertbuffer_t *box = nullptr; gs_vertbuffer_t *boxLeft = nullptr; @@ -152,9 +153,12 @@ private: QPointer startStreamMenu; + QPointer replayBufferButton; + QPointer trayIcon; QPointer sysTrayStream; QPointer sysTrayRecord; + QPointer sysTrayReplayBuffer; QPointer showHide; QPointer exit; QPointer trayMenu; @@ -245,7 +249,8 @@ private: QListWidgetItem *GetTopSelectedSourceItem(); - obs_hotkey_pair_id streamingHotkeys, recordingHotkeys; + obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, + replayBufHotkeys; obs_hotkey_id forceStreamingStopHotkey; void InitDefaultTransitions(); @@ -314,6 +319,8 @@ private: void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; + void ReplayBufferClicked(); + public slots: void StartStreaming(); void StopStreaming(); @@ -333,6 +340,13 @@ public slots: void RecordStopping(); void RecordingStop(int code); + void StartReplayBuffer(); + void StopReplayBuffer(); + + void ReplayBufferStart(); + void ReplayBufferStopping(); + void ReplayBufferStop(int code); + void SaveProjectDeferred(); void SaveProject(); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 0c45c65aa..f4b741928 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -2663,10 +2663,13 @@ void OBSBasicSettings::SaveHotkeySettings() obs_data_array_release(array); } - const char *id = obs_obj_get_id(main->outputHandler->fileOutput); + if (!main->outputHandler || !main->outputHandler->replayBuffer) + return; + + const char *id = obs_obj_get_id(main->outputHandler->replayBuffer); if (strcmp(id, "replay_buffer") == 0) { obs_data_t *hotkeys = obs_hotkeys_save_output( - main->outputHandler->fileOutput); + main->outputHandler->replayBuffer); config_set_string(config, "Hotkeys", "ReplayBuffer", obs_data_get_json(hotkeys)); obs_data_release(hotkeys);