Browse Source

UI: Add replay buffer options to simple output mode

jp9000 9 years ago
parent
commit
752c118f2e

+ 14 - 2
UI/data/locale/en-US.ini

@@ -58,6 +58,7 @@ Hours="Hours"
 Minutes="Minutes"
 Seconds="Seconds"
 Deprecated="Deprecated"
+ReplayBuffer="Replay Buffer"
 
 # quick transitions
 QuickTransitions.SwapScenes="Swap Preview/Output Scenes After Transitioning"
@@ -123,6 +124,8 @@ Output.RecordNoSpace.Title="Insufficient disk space"
 Output.RecordNoSpace.Msg="There is not sufficient disk space to continue recording."
 Output.RecordError.Title="Recording error"
 Output.RecordError.Msg="An unspecified error occurred while recording."
+Output.ReplayBuffer.NoHotkey.Title="No hotkey set!"
+Output.ReplayBuffer.NoHotkey.Msg="No hotkey set for replay buffer.  Please set a hotkey to use for saving replay recordings."
 
 # output recording messages
 Output.BadPath.Title="Bad File Path"
@@ -304,9 +307,12 @@ Basic.Main.Scenes="Scenes"
 Basic.Main.Sources="Sources"
 Basic.Main.Connecting="Connecting..."
 Basic.Main.StartRecording="Start Recording"
+Basic.Main.StartReplayBuffer="Start Replay Buffer"
 Basic.Main.StartStreaming="Start Streaming"
 Basic.Main.StopRecording="Stop Recording"
 Basic.Main.StoppingRecording="Stopping Recording..."
+Basic.Main.StopReplayBuffer="Stop Replay Buffer"
+Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..."
 Basic.Main.StopStreaming="Stop Streaming"
 Basic.Main.StoppingStreaming="Stopping Stream..."
 Basic.Main.ForceStopStreaming="Stop Streaming (discard delay)"
@@ -414,6 +420,12 @@ 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.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.Simple.SavePath="Recording Path"
 Basic.Settings.Output.Simple.RecordingQuality="Recording Quality"
 Basic.Settings.Output.Simple.RecordingQuality.Stream="Same as stream"
@@ -562,8 +574,8 @@ 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"
-Basic.Hotkeys.StopRecording="Stop Recording"
+Basic.Hotkeys.StartRecording="Start Recording/Replay Buffer"
+Basic.Hotkeys.StopRecording="Stop Recording/Replay Buffer"
 Basic.Hotkeys.SelectScene="Switch to scene"
 
 # system tray

+ 230 - 145
UI/forms/OBSBasicSettings.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>981</width>
-    <height>667</height>
+    <height>720</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -739,6 +739,25 @@
                 <property name="labelAlignment">
                  <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
                 </property>
+                <item row="0" column="0">
+                 <widget class="QLabel" name="label_18">
+                  <property name="minimumSize">
+                   <size>
+                    <width>170</width>
+                    <height>0</height>
+                   </size>
+                  </property>
+                  <property name="text">
+                   <string>Basic.Settings.Output.Simple.SavePath</string>
+                  </property>
+                  <property name="alignment">
+                   <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+                  </property>
+                  <property name="buddy">
+                   <cstring>simpleOutputPath</cstring>
+                  </property>
+                 </widget>
+                </item>
                 <item row="0" column="1">
                  <layout class="QHBoxLayout" name="horizontalLayout_5">
                   <item>
@@ -760,32 +779,36 @@
                   </item>
                  </layout>
                 </item>
-                <item row="0" column="0">
-                 <widget class="QLabel" name="label_18">
-                  <property name="minimumSize">
-                   <size>
-                    <width>170</width>
-                    <height>0</height>
-                   </size>
-                  </property>
+                <item row="1" column="1">
+                 <widget class="QCheckBox" name="simpleNoSpace">
                   <property name="text">
-                   <string>Basic.Settings.Output.Simple.SavePath</string>
+                   <string>Basic.Settings.Output.NoSpaceFileName</string>
                   </property>
-                  <property name="alignment">
-                   <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+                  <property name="checked">
+                   <bool>true</bool>
+                  </property>
+                 </widget>
+                </item>
+                <item row="2" column="0">
+                 <widget class="QLabel" name="label_26">
+                  <property name="text">
+                   <string>Basic.Settings.Output.Simple.RecordingQuality</string>
                   </property>
                   <property name="buddy">
-                   <cstring>simpleOutputPath</cstring>
+                   <cstring>simpleOutRecQuality</cstring>
                   </property>
                  </widget>
                 </item>
-                <item row="1" column="1">
-                 <widget class="QCheckBox" name="simpleNoSpace">
+                <item row="2" column="1">
+                 <widget class="QComboBox" name="simpleOutRecQuality"/>
+                </item>
+                <item row="3" column="0">
+                 <widget class="QLabel" name="simpleOutRecFormatLabel">
                   <property name="text">
-                   <string>Basic.Settings.Output.NoSpaceFileName</string>
+                   <string>Basic.Settings.Output.Format</string>
                   </property>
-                  <property name="checked">
-                   <bool>true</bool>
+                  <property name="buddy">
+                   <cstring>simpleOutRecFormat</cstring>
                   </property>
                  </widget>
                 </item>
@@ -823,54 +846,116 @@
                   </item>
                  </widget>
                 </item>
-                <item row="3" column="0">
-                 <widget class="QLabel" name="simpleOutRecFormatLabel">
+                <item row="4" column="0">
+                 <widget class="QLabel" name="simpleOutRecEncoderLabel">
                   <property name="text">
-                   <string>Basic.Settings.Output.Format</string>
+                   <string>Basic.Settings.Output.Encoder</string>
                   </property>
                   <property name="buddy">
-                   <cstring>simpleOutRecFormat</cstring>
+                   <cstring>simpleOutRecEncoder</cstring>
                   </property>
                  </widget>
                 </item>
-                <item row="2" column="1">
-                 <widget class="QComboBox" name="simpleOutRecQuality"/>
+                <item row="4" column="1">
+                 <widget class="QComboBox" name="simpleOutRecEncoder"/>
                 </item>
-                <item row="2" column="0">
-                 <widget class="QLabel" name="label_26">
+                <item row="5" column="0">
+                 <widget class="QLabel" name="label_420">
                   <property name="text">
-                   <string>Basic.Settings.Output.Simple.RecordingQuality</string>
+                   <string>Basic.Settings.Output.CustomMuxerSettings</string>
                   </property>
                   <property name="buddy">
-                   <cstring>simpleOutRecQuality</cstring>
+                   <cstring>simpleOutMuxCustom</cstring>
                   </property>
                  </widget>
                 </item>
-                <item row="4" column="1">
-                 <widget class="QComboBox" name="simpleOutRecEncoder"/>
+                <item row="5" column="1">
+                 <widget class="QLineEdit" name="simpleOutMuxCustom"/>
                 </item>
-                <item row="4" column="0">
-                 <widget class="QLabel" name="simpleOutRecEncoderLabel">
+                <item row="6" column="1">
+                 <widget class="QCheckBox" name="simpleReplayBuf">
                   <property name="text">
-                   <string>Basic.Settings.Output.Encoder</string>
+                   <string>Basic.Settings.Output.UseReplayBuffer</string>
                   </property>
-                  <property name="buddy">
-                   <cstring>simpleOutRecEncoder</cstring>
+                  <property name="checked">
+                   <bool>true</bool>
                   </property>
                  </widget>
                 </item>
-                <item row="5" column="0">
-                 <widget class="QLabel" name="label_420">
+               </layout>
+              </widget>
+             </item>
+             <item>
+              <widget class="QGroupBox" name="replayBufferGroupBox">
+               <property name="title">
+                <string>ReplayBuffer</string>
+               </property>
+               <layout class="QFormLayout" name="formLayout_24">
+                <property name="fieldGrowthPolicy">
+                 <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+                </property>
+                <property name="labelAlignment">
+                 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+                </property>
+                <item row="0" column="0">
+                 <widget class="QLabel" name="label_35">
                   <property name="text">
-                   <string>Basic.Settings.Output.CustomMuxerSettings</string>
+                   <string>Basic.Settings.Output.ReplayBuffer.SecondsMax</string>
                   </property>
-                  <property name="buddy">
-                   <cstring>simpleOutMuxCustom</cstring>
+                 </widget>
+                </item>
+                <item row="0" column="1">
+                 <widget class="QSpinBox" name="simpleRBSecMax">
+                  <property name="suffix">
+                   <string notr="true"> sec</string>
+                  </property>
+                  <property name="minimum">
+                   <number>5</number>
+                  </property>
+                  <property name="maximum">
+                   <number>21600</number>
+                  </property>
+                  <property name="value">
+                   <number>15</number>
                   </property>
                  </widget>
                 </item>
-                <item row="5" column="1">
-                 <widget class="QLineEdit" name="simpleOutMuxCustom"/>
+                <item row="1" column="0">
+                 <widget class="QLabel" name="simpleRBMegsMaxLabel">
+                  <property name="text">
+                   <string>Basic.Settings.Output.ReplayBuffer.MegabytesMax</string>
+                  </property>
+                 </widget>
+                </item>
+                <item row="1" column="1">
+                 <widget class="QSpinBox" name="simpleRBMegsMax">
+                  <property name="suffix">
+                   <string notr="true"> MB</string>
+                  </property>
+                  <property name="minimum">
+                   <number>20</number>
+                  </property>
+                  <property name="maximum">
+                   <number>8192</number>
+                  </property>
+                  <property name="value">
+                   <number>512</number>
+                  </property>
+                 </widget>
+                </item>
+                <item row="3" column="1">
+                 <widget class="QLabel" name="simpleRBEstimate">
+                  <property name="text">
+                   <string notr="true"/>
+                  </property>
+                 </widget>
+                </item>
+                <item row="2" column="1">
+                 <widget class="QLabel" name="label_45">
+                  <property name="text">
+                   <string>Basic.Settings.Output.ReplayBuffer.HotkeyMessage</string>
+                  </property>
+                 </widget>
                 </item>
                </layout>
               </widget>
@@ -2545,8 +2630,8 @@
              <rect>
               <x>0</x>
               <y>0</y>
-              <width>80</width>
-              <height>16</height>
+              <width>98</width>
+              <height>28</height>
              </rect>
             </property>
            </widget>
@@ -3384,12 +3469,12 @@
    <slot>setCurrentIndex(int)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>310</x>
-     <y>29</y>
+     <x>159</x>
+     <y>30</y>
     </hint>
     <hint type="destinationlabel">
      <x>241</x>
-     <y>34</y>
+     <y>30</y>
     </hint>
    </hints>
   </connection>
@@ -3400,12 +3485,12 @@
    <slot>setCurrentIndex(int)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>586</x>
+     <y>38</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>401</x>
+     <y>102</y>
     </hint>
    </hints>
   </connection>
@@ -3416,12 +3501,12 @@
    <slot>setVisible(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>180</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>229</y>
     </hint>
    </hints>
   </connection>
@@ -3432,12 +3517,12 @@
    <slot>setVisible(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>180</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>383</x>
+     <y>229</y>
     </hint>
    </hints>
   </connection>
@@ -3448,12 +3533,12 @@
    <slot>setVisible(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>180</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>255</y>
     </hint>
    </hints>
   </connection>
@@ -3464,12 +3549,12 @@
    <slot>setVisible(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>180</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>383</x>
+     <y>255</y>
     </hint>
    </hints>
   </connection>
@@ -3480,12 +3565,12 @@
    <slot>setCurrentIndex(int)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>259</x>
-     <y>48</y>
+     <x>232</x>
+     <y>71</y>
     </hint>
     <hint type="destinationlabel">
      <x>241</x>
-     <y>30</y>
+     <y>83</y>
     </hint>
    </hints>
   </connection>
@@ -3496,12 +3581,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>259</x>
-     <y>60</y>
+     <x>168</x>
+     <y>97</y>
     </hint>
     <hint type="destinationlabel">
-     <x>228</x>
-     <y>50</y>
+     <x>250</x>
+     <y>97</y>
     </hint>
    </hints>
   </connection>
@@ -3512,12 +3597,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>259</x>
-     <y>39</y>
+     <x>168</x>
+     <y>82</y>
     </hint>
     <hint type="destinationlabel">
-     <x>228</x>
-     <y>29</y>
+     <x>232</x>
+     <y>82</y>
     </hint>
    </hints>
   </connection>
@@ -3528,12 +3613,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>259</x>
-     <y>60</y>
+     <x>168</x>
+     <y>86</y>
     </hint>
     <hint type="destinationlabel">
-     <x>228</x>
-     <y>50</y>
+     <x>232</x>
+     <y>86</y>
     </hint>
    </hints>
   </connection>
@@ -3544,12 +3629,12 @@
    <slot>setCurrentIndex(int)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>259</x>
-     <y>60</y>
+     <x>250</x>
+     <y>80</y>
     </hint>
     <hint type="destinationlabel">
-     <x>259</x>
-     <y>60</y>
+     <x>250</x>
+     <y>82</y>
     </hint>
    </hints>
   </connection>
@@ -3560,12 +3645,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>653</x>
+     <y>408</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>397</x>
+     <y>434</y>
     </hint>
    </hints>
   </connection>
@@ -3576,12 +3661,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>653</x>
+     <y>408</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>653</x>
+     <y>457</y>
     </hint>
    </hints>
   </connection>
@@ -3592,12 +3677,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>653</x>
+     <y>408</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>653</x>
+     <y>434</y>
     </hint>
    </hints>
   </connection>
@@ -3608,12 +3693,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>588</x>
+     <y>513</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>332</x>
+     <y>539</y>
     </hint>
    </hints>
   </connection>
@@ -3624,12 +3709,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>588</x>
+     <y>513</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>588</x>
+     <y>539</y>
     </hint>
    </hints>
   </connection>
@@ -3640,12 +3725,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>588</x>
+     <y>513</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>332</x>
+     <y>565</y>
     </hint>
    </hints>
   </connection>
@@ -3656,12 +3741,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>588</x>
+     <y>513</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>588</x>
+     <y>565</y>
     </hint>
    </hints>
   </connection>
@@ -3672,12 +3757,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>720</x>
-     <y>280</y>
+     <x>951</x>
+     <y>349</y>
     </hint>
     <hint type="destinationlabel">
      <x>346</x>
-     <y>306</y>
+     <y>375</y>
     </hint>
    </hints>
   </connection>
@@ -3688,12 +3773,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>761</x>
-     <y>280</y>
+     <x>951</x>
+     <y>349</y>
     </hint>
     <hint type="destinationlabel">
-     <x>778</x>
-     <y>306</y>
+     <x>951</x>
+     <y>375</y>
     </hint>
    </hints>
   </connection>
@@ -3704,12 +3789,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>820</x>
-     <y>280</y>
+     <x>951</x>
+     <y>349</y>
     </hint>
     <hint type="destinationlabel">
-     <x>810</x>
-     <y>329</y>
+     <x>951</x>
+     <y>398</y>
     </hint>
    </hints>
   </connection>
@@ -3720,12 +3805,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>862</x>
-     <y>280</y>
+     <x>951</x>
+     <y>349</y>
     </hint>
     <hint type="destinationlabel">
-     <x>859</x>
-     <y>352</y>
+     <x>951</x>
+     <y>421</y>
     </hint>
    </hints>
   </connection>
@@ -3736,12 +3821,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>866</x>
-     <y>280</y>
+     <x>951</x>
+     <y>349</y>
     </hint>
     <hint type="destinationlabel">
-     <x>866</x>
-     <y>375</y>
+     <x>951</x>
+     <y>444</y>
     </hint>
    </hints>
   </connection>
@@ -3752,12 +3837,12 @@
    <slot>setVisible(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>180</y>
     </hint>
     <hint type="destinationlabel">
-     <x>250</x>
-     <y>39</y>
+     <x>640</x>
+     <y>203</y>
     </hint>
    </hints>
   </connection>
@@ -3768,12 +3853,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>404</x>
-     <y>193</y>
+     <x>705</x>
+     <y>225</y>
     </hint>
     <hint type="destinationlabel">
-     <x>404</x>
-     <y>219</y>
+     <x>705</x>
+     <y>248</y>
     </hint>
    </hints>
   </connection>
@@ -3784,12 +3869,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>404</x>
-     <y>245</y>
+     <x>705</x>
+     <y>271</y>
     </hint>
     <hint type="destinationlabel">
-     <x>404</x>
-     <y>271</y>
+     <x>705</x>
+     <y>294</y>
     </hint>
    </hints>
   </connection>

+ 30 - 4
UI/window-basic-main-outputs.cpp

@@ -306,8 +306,21 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_)
 	LoadRecordingPreset();
 
 	if (!ffmpegOutput) {
-		fileOutput = obs_output_create("ffmpeg_muxer",
-				"simple_file_output", nullptr, nullptr);
+		replayBuffer = config_get_bool(main->Config(),
+				"SimpleOutput", "RecRB");
+		if (replayBuffer) {
+			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",
+					Str("ReplayBuffer"), nullptr, hotkey);
+
+			obs_data_release(hotkey);
+		} else {
+			fileOutput = obs_output_create("ffmpeg_muxer",
+					"simple_file_output", nullptr, nullptr);
+		}
+
 		if (!fileOutput)
 			throw "Failed to create recording output "
 			      "(simple output)";
@@ -660,6 +673,10 @@ bool SimpleOutput::StartRecording()
 				"FilenameFormatting");
 	bool overwriteIfExists = config_get_bool(main->Config(), "Output",
 				"OverwriteIfExists");
+	int rbTime = config_get_int(main->Config(), "SimpleOutput",
+			"RecRBTime");
+	int rbSize = config_get_int(main->Config(), "SimpleOutput",
+			"RecRBSize");
 
 	os_dir_t *dir = path ? os_opendir(path) : nullptr;
 
@@ -695,8 +712,17 @@ bool SimpleOutput::StartRecording()
 	}
 
 	obs_data_t *settings = obs_data_create();
-	obs_data_set_string(settings, ffmpegOutput ? "url" : "path",
-			strPath.c_str());
+	if (replayBuffer) {
+		obs_data_set_string(settings, "directory", path);
+		obs_data_set_string(settings, "format", filenameFormat);
+		obs_data_set_string(settings, "extension", format);
+		obs_data_set_int(settings, "max_time_sec", rbTime);
+		obs_data_set_int(settings, "max_size_mb",
+				usingRecordingPreset ? rbSize : 0);
+	} else {
+		obs_data_set_string(settings, ffmpegOutput ? "url" : "path",
+				strPath.c_str());
+	}
 	obs_data_set_string(settings, "muxer_settings", mux);
 
 	obs_output_update(fileOutput, settings);

+ 1 - 0
UI/window-basic-main-outputs.hpp

@@ -8,6 +8,7 @@ struct BasicOutputHandler {
 	bool                   streamingActive = false;
 	bool                   recordingActive = false;
 	bool                   delayActive = false;
+	bool                   replayBuffer = false;
 	OBSBasic               *main;
 
 	OBSSignal              startRecording;

+ 49 - 3
UI/window-basic-main.cpp

@@ -847,6 +847,9 @@ bool OBSBasic::InitBasicConfigDefaults()
 			"Stream");
 	config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder",
 			SIMPLE_ENCODER_X264);
+	config_set_default_bool(basicConfig, "SimpleOutput", "RecRB", false);
+	config_set_default_int(basicConfig, "SimpleOutput", "RecRBTime", 20);
+	config_set_default_int(basicConfig, "SimpleOutput", "RecRBSize", 512);
 
 	config_set_default_bool  (basicConfig, "AdvOut", "ApplyServiceSettings",
 			true);
@@ -1041,6 +1044,16 @@ void OBSBasic::ResetOutputs()
 		outputHandler.reset(advOut ?
 			CreateAdvancedOutputHandler(this) :
 			CreateSimpleOutputHandler(this));
+
+		if (outputHandler->replayBuffer)
+			ui->recordButton->setText(
+					QTStr("Basic.Main.StartReplayBuffer"));
+		else
+			ui->recordButton->setText(
+					QTStr("Basic.Main.StartRecording"));
+
+		if (sysTrayRecord)
+			sysTrayRecord->setText(ui->recordButton->text());
 	} else {
 		outputHandler->Update();
 	}
@@ -3905,11 +3918,31 @@ 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);
 
@@ -3919,7 +3952,10 @@ void OBSBasic::StartRecording()
 
 void OBSBasic::RecordStopping()
 {
-	ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording"));
+	if (outputHandler->replayBuffer)
+		ui->recordButton->setText(QTStr("Basic.Main.StoppingReplayBuffer"));
+	else
+		ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording"));
 
 	if (sysTrayRecord)
 		sysTrayRecord->setText(ui->recordButton->text());
@@ -3942,7 +3978,11 @@ void OBSBasic::StopRecording()
 void OBSBasic::RecordingStart()
 {
 	ui->statusbar->RecordingStarted(outputHandler->fileOutput);
-	ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
+
+	if (outputHandler->replayBuffer)
+		ui->recordButton->setText(QTStr("Basic.Main.StopReplayBuffer"));
+	else
+		ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
 
 	if (sysTrayRecord)
 		sysTrayRecord->setText(ui->recordButton->text());
@@ -3959,7 +3999,11 @@ void OBSBasic::RecordingStart()
 void OBSBasic::RecordingStop(int code)
 {
 	ui->statusbar->RecordingStopped();
-	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
+
+	if (outputHandler->replayBuffer)
+		ui->recordButton->setText(QTStr("Basic.Main.StartReplayBuffer"));
+	else
+		ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
 
 	if (sysTrayRecord)
 		sysTrayRecord->setText(ui->recordButton->text());
@@ -4696,6 +4740,8 @@ void OBSBasic::SystemTrayInit()
 	exit = new QAction(QTStr("Exit"),
 			trayIcon);
 
+	sysTrayRecord->setText(ui->recordButton->text());	
+
 	connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
 			this,
 			SLOT(IconActivated(QSystemTrayIcon::ActivationReason)));

+ 1 - 0
UI/window-basic-main.hpp

@@ -86,6 +86,7 @@ class OBSBasic : public OBSMainWindow {
 	friend class OBSBasicPreview;
 	friend class OBSBasicStatusBar;
 	friend class OBSBasicSourceSelect;
+	friend class OBSBasicSettings;
 	friend struct OBSStudioAPI;
 
 	enum class MoveDir {

+ 69 - 0
UI/window-basic-settings.cpp

@@ -44,6 +44,7 @@
 #include "qt-wrappers.hpp"
 #include "window-basic-main.hpp"
 #include "window-basic-settings.hpp"
+#include "window-basic-main-outputs.hpp"
 
 #include <util/platform.h>
 
@@ -299,6 +300,9 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
 	HookWidget(ui->simpleOutRecQuality,  COMBO_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->simpleOutRecEncoder,  COMBO_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->simpleOutMuxCustom,   EDIT_CHANGED,   OUTPUTS_CHANGED);
+	HookWidget(ui->simpleReplayBuf,      CHECK_CHANGED,  OUTPUTS_CHANGED);
+	HookWidget(ui->simpleRBSecMax,       SCROLL_CHANGED, OUTPUTS_CHANGED);
+	HookWidget(ui->simpleRBMegsMax,      SCROLL_CHANGED, OUTPUTS_CHANGED);
 	HookWidget(ui->advOutEncoder,        COMBO_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutUseRescale,     CHECK_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutRescale,        CBEDIT_CHANGED, OUTPUTS_CHANGED);
@@ -525,6 +529,14 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
 			this, SLOT(SimpleRecordingEncoderChanged()));
 	connect(ui->simpleOutEnforce, SIGNAL(toggled(bool)),
 			this, SLOT(SimpleRecordingEncoderChanged()));
+	connect(ui->simpleReplayBuf, SIGNAL(toggled(bool)),
+			this, SLOT(SimpleReplayBufferChanged()));
+	connect(ui->simpleOutputVBitrate, SIGNAL(valueChanged(int)),
+			this, SLOT(SimpleReplayBufferChanged()));
+	connect(ui->simpleOutputABitrate, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(SimpleReplayBufferChanged()));
+	connect(ui->simpleRBSecMax, SIGNAL(valueChanged(int)),
+			this, SLOT(SimpleReplayBufferChanged()));
 	connect(ui->listWidget, SIGNAL(currentRowChanged(int)),
 			this, SLOT(SimpleRecordingEncoderChanged()));
 
@@ -1216,6 +1228,12 @@ void OBSBasicSettings::LoadSimpleOutputSettings()
 			"RecEncoder");
 	const char *muxCustom = config_get_string(main->Config(),
 			"SimpleOutput", "MuxerCustom");
+	bool replayBuf = config_get_bool(main->Config(), "SimpleOutput",
+			"RecRB");
+	int rbTime = config_get_int(main->Config(), "SimpleOutput",
+			"RecRBTime");
+	int rbSize = config_get_int(main->Config(), "SimpleOutput",
+			"RecRBSize");
 
 	curPreset = preset;
 	curQSVPreset = qsvPreset;
@@ -1252,6 +1270,10 @@ void OBSBasicSettings::LoadSimpleOutputSettings()
 
 	ui->simpleOutMuxCustom->setText(muxCustom);
 
+	ui->simpleReplayBuf->setChecked(replayBuf);
+	ui->simpleRBSecMax->setValue(rbTime);
+	ui->simpleRBMegsMax->setValue(rbSize);
+
 	SimpleStreamingEncoderChanged();
 }
 
@@ -1555,6 +1577,7 @@ void OBSBasicSettings::LoadOutputSettings()
 		ui->outputMode->setEnabled(false);
 		ui->outputModeLabel->setEnabled(false);
 		ui->simpleRecordingGroupBox->setEnabled(false);
+		ui->replayBufferGroupBox->setEnabled(false);
 		ui->advOutTopContainer->setEnabled(false);
 		ui->advOutRecTopContainer->setEnabled(false);
 		ui->advOutRecTypeContainer->setEnabled(false);
@@ -2489,6 +2512,9 @@ void OBSBasicSettings::SaveOutputSettings()
 	SaveComboData(ui->simpleOutRecQuality, "SimpleOutput", "RecQuality");
 	SaveComboData(ui->simpleOutRecEncoder, "SimpleOutput", "RecEncoder");
 	SaveEdit(ui->simpleOutMuxCustom, "SimpleOutput", "MuxerCustom");
+	SaveCheckBox(ui->simpleReplayBuf, "SimpleOutput", "RecRB");
+	SaveSpinBox(ui->simpleRBSecMax, "SimpleOutput", "RecRBTime");
+	SaveSpinBox(ui->simpleRBMegsMax, "SimpleOutput", "RecRBSize");
 
 	curAdvStreamEncoder = GetComboData(ui->advOutEncoder);
 
@@ -2636,6 +2662,15 @@ void OBSBasicSettings::SaveHotkeySettings()
 		obs_data_release(data);
 		obs_data_array_release(array);
 	}
+
+	const char *id = obs_obj_get_id(main->outputHandler->fileOutput);
+	if (strcmp(id, "replay_buffer") == 0) {
+		obs_data_t *hotkeys = obs_hotkeys_save_output(
+				main->outputHandler->fileOutput);
+		config_set_string(config, "Hotkeys", "ReplayBuffer",
+				obs_data_get_json(hotkeys));
+		obs_data_release(hotkeys);
+	}
 }
 
 #define MINOR_SEPARATOR \
@@ -3292,6 +3327,7 @@ void OBSBasicSettings::SimpleRecordingQualityChanged()
 	ui->simpleOutRecFormatLabel->setVisible(!losslessQuality);
 
 	SimpleRecordingEncoderChanged();
+	SimpleReplayBufferChanged();
 }
 
 void OBSBasicSettings::SimpleStreamingEncoderChanged()
@@ -3364,6 +3400,39 @@ void OBSBasicSettings::SimpleStreamingEncoderChanged()
 	ui->simpleOutPreset->setCurrentIndex(idx);
 }
 
+#define ESTIMATE_STR "Basic.Settings.Output.ReplayBuffer.Estimate"
+#define ESTIMATE_UNKNOWN_STR \
+	"Basic.Settings.Output.ReplayBuffer.EstimateUnknown"
+
+void OBSBasicSettings::SimpleReplayBufferChanged()
+{
+	QString qual = ui->simpleOutRecQuality->currentData().toString();
+	bool replayBufferEnabled = ui->simpleReplayBuf->isChecked();
+	bool lossless = qual == "Lossless";
+	bool streamQuality = qual == "Stream";
+
+	ui->simpleRBMegsMax->setVisible(!streamQuality);
+	ui->simpleRBMegsMaxLabel->setVisible(!streamQuality);
+
+	int vbitrate = ui->simpleOutputVBitrate->value();
+	int abitrate = ui->simpleOutputABitrate->currentText().toInt();
+	int seconds = ui->simpleRBSecMax->value();
+
+	int64_t memMB = int64_t(seconds) * int64_t(vbitrate + abitrate) *
+		1000 / 8 / 1024 / 1024;
+	if (memMB < 1) memMB = 1;
+
+	if (streamQuality)
+		ui->simpleRBEstimate->setText(
+				QTStr(ESTIMATE_STR).arg(
+					QString::number(int(memMB))));
+	else
+		ui->simpleRBEstimate->setText(QTStr(ESTIMATE_UNKNOWN_STR));
+
+	ui->replayBufferGroupBox->setVisible(!lossless && replayBufferEnabled);
+	ui->simpleReplayBuf->setVisible(!lossless);
+}
+
 #define SIMPLE_OUTPUT_WARNING(str) \
 	QTStr("Basic.Settings.Output.Simple.Warn." str)
 

+ 2 - 0
UI/window-basic-settings.hpp

@@ -290,6 +290,8 @@ private slots:
 	void SimpleRecordingEncoderChanged();
 	void SimpleRecordingQualityLosslessWarning(int idx);
 
+	void SimpleReplayBufferChanged();
+
 	void SimpleStreamingEncoderChanged();
 
 protected: