diff --git a/build/data/obs-studio/locale/en.txt b/build/data/obs-studio/locale/en.txt
index ce5007569..de8d78b85 100644
--- a/build/data/obs-studio/locale/en.txt
+++ b/build/data/obs-studio/locale/en.txt
@@ -20,6 +20,7 @@ Mixer="Mixer"
Browse="Browse"
Mono="Mono"
Stereo="Stereo"
+DroppedFrames="Dropped Frames %1 (%2%)"
# "name already exists" dialog box
NameExists.Title="Name already exists"
@@ -74,6 +75,10 @@ Basic.SourceSelect.AddExisting="Add Existing"
# properties window
Basic.PropertiesWindow.AutoSelectFormat="%1 (unsupported; autoselect: %2)"
+# status bar
+Basic.StatusBar.Reconnecting="Disconnected, reconnecting (attempt %1)"
+Basic.StatusBar.ReconnectSuccessful="Reconnection successful"
+
# transform window
Basic.TransformWindow="Scene Item Transform"
Basic.TransformWindow.Position="Position"
diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt
index 6231e904b..110fc2fa9 100644
--- a/obs/CMakeLists.txt
+++ b/obs/CMakeLists.txt
@@ -57,6 +57,7 @@ set(obs_SOURCES
window-basic-settings.cpp
window-basic-properties.cpp
window-basic-source-select.cpp
+ window-basic-status-bar.cpp
window-basic-transform.cpp
window-basic-preview.cpp
window-namedialog.cpp
@@ -73,6 +74,7 @@ set(obs_HEADERS
window-basic-settings.hpp
window-basic-properties.hpp
window-basic-source-select.hpp
+ window-basic-status-bar.hpp
window-basic-transform.hpp
window-basic-preview.hpp
window-namedialog.hpp
diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui
index 3ba575e53..b46f2e3df 100644
--- a/obs/forms/OBSBasic.ui
+++ b/obs/forms/OBSBasic.ui
@@ -511,7 +511,7 @@
-
+
@@ -776,6 +776,11 @@
1
+
+ OBSBasicStatusBar
+ QStatusBar
+ window-basic-status-bar.hpp
+
diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp
index eeef77fdb..3608b932f 100644
--- a/obs/window-basic-main.cpp
+++ b/obs/window-basic-main.cpp
@@ -90,6 +90,10 @@ OBSBasic::OBSBasic(QWidget *parent)
QAbstractItemDelegate::EndEditHint)));
cpuUsageInfo = os_cpu_usage_info_start();
+ cpuUsageTimer = new QTimer(this);
+ connect(cpuUsageTimer, SIGNAL(timeout()),
+ ui->statusbar, SLOT(UpdateCPUUsage()));
+ cpuUsageTimer->start(3000);
}
static void SaveAudioDevice(const char *name, int channel, obs_data_t parent)
@@ -552,6 +556,7 @@ OBSBasic::~OBSBasic()
* can be freed, and we have no control over the destruction order of
* the Qt UI stuff, so we have to manually clear any references to
* libobs. */
+ delete cpuUsageTimer;
os_cpu_usage_info_destroy(cpuUsageInfo);
delete properties;
@@ -1682,6 +1687,7 @@ void OBSBasic::StreamingStart()
{
ui->streamButton->setText("Stop Streaming");
ui->streamButton->setEnabled(true);
+ ui->statusbar->StreamStarted(streamOutput);
}
void OBSBasic::StreamingStop(int code)
@@ -1713,6 +1719,7 @@ void OBSBasic::StreamingStop(int code)
}
activeRefs--;
+ ui->statusbar->StreamStopped();
ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
ui->streamButton->setEnabled(true);
diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp
index d57f18464..25620f506 100644
--- a/obs/window-basic-main.hpp
+++ b/obs/window-basic-main.hpp
@@ -62,6 +62,7 @@ private:
QNetworkAccessManager networkManager;
+ QPointer cpuUsageTimer;
os_cpu_usage_info_t cpuUsageInfo = nullptr;
QBuffer logUploadPostData;
diff --git a/obs/window-basic-status-bar.cpp b/obs/window-basic-status-bar.cpp
new file mode 100644
index 000000000..a6a871175
--- /dev/null
+++ b/obs/window-basic-status-bar.cpp
@@ -0,0 +1,214 @@
+#include
+#include "obs-app.hpp"
+#include "window-basic-main.hpp"
+#include "window-basic-status-bar.hpp"
+
+OBSBasicStatusBar::OBSBasicStatusBar(QWidget *parent)
+ : QStatusBar (parent),
+ droppedFrames (new QLabel),
+ sessionTime (new QLabel),
+ cpuUsage (new QLabel),
+ kbps (new QLabel)
+{
+ sessionTime->setText(QString("00:00:00"));
+ cpuUsage->setText(QString("CPU: 0.0%"));
+
+ droppedFrames->setAlignment(Qt::AlignRight);
+ sessionTime->setAlignment(Qt::AlignRight);
+ cpuUsage->setAlignment(Qt::AlignRight);
+ kbps->setAlignment(Qt::AlignRight);
+
+ droppedFrames->setIndent(20);
+ sessionTime->setIndent(20);
+ cpuUsage->setIndent(20);
+ kbps->setIndent(10);
+
+ addPermanentWidget(droppedFrames);
+ addPermanentWidget(sessionTime);
+ addPermanentWidget(cpuUsage);
+ addPermanentWidget(kbps);
+}
+
+void OBSBasicStatusBar::IncRef()
+{
+ if (activeRefs++ == 0) {
+ refreshTimer = new QTimer(this);
+ connect(refreshTimer, SIGNAL(timeout()),
+ this, SLOT(UpdateStatusBar()));
+ totalSeconds = 0;
+ refreshTimer->start(1000);
+ }
+}
+
+void OBSBasicStatusBar::DecRef()
+{
+ if (--activeRefs == 0) {
+ delete refreshTimer;
+ sessionTime->setText(QString("00:00:00"));
+ droppedFrames->setText("");
+ kbps->setText("");
+ }
+}
+
+#define BITRATE_UPDATE_SECONDS 2
+
+void OBSBasicStatusBar::UpdateBandwidth()
+{
+ if (!streamOutput)
+ return;
+
+ if (++bitrateUpdateSeconds < BITRATE_UPDATE_SECONDS)
+ return;
+
+ uint64_t bytesSent = obs_output_get_total_bytes(streamOutput);
+ uint64_t bytesSentTime = os_gettime_ns();
+ uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8;
+
+ double timePassed = double(bytesSentTime - lastBytesSentTime) /
+ 1000000000.0;
+
+ double kbitsPerSec = double(bitsBetween) / timePassed / 1000.0;
+
+ QString text;
+ text += QString("kbp/s: ") +
+ QString::number(kbitsPerSec, 'f', 0);
+ kbps->setText(text);
+ kbps->setMinimumWidth(kbps->width());
+
+ lastBytesSent = bytesSent;
+ lastBytesSentTime = bytesSentTime;
+ bitrateUpdateSeconds = 0;
+}
+
+void OBSBasicStatusBar::UpdateCPUUsage()
+{
+ OBSBasic *main = qobject_cast(parent());
+ if (!main)
+ return;
+
+ QString text;
+ text += QString("CPU: ") +
+ QString::number(main->GetCPUUsage(), 'f', 1) + QString("%");
+ cpuUsage->setText(text);
+ cpuUsage->setMinimumWidth(cpuUsage->width());
+}
+
+void OBSBasicStatusBar::UpdateSessionTime()
+{
+ totalSeconds++;
+
+ int seconds = totalSeconds % 60;
+ int totalMinutes = totalSeconds / 60;
+ int minutes = totalMinutes % 60;
+ int hours = totalMinutes / 60;
+
+ QString text;
+ text.sprintf("%02d:%02d:%02d", hours, minutes, seconds);
+ sessionTime->setText(text);
+ sessionTime->setMinimumWidth(sessionTime->width());
+}
+
+void OBSBasicStatusBar::UpdateDroppedFrames()
+{
+ if (!streamOutput)
+ return;
+
+ int totalDropped = obs_output_get_frames_dropped(streamOutput);
+ int totalFrames = obs_output_get_total_frames(streamOutput);
+ double percent = (double)totalDropped / (double)totalFrames * 100.0;
+
+ if (!totalFrames)
+ return;
+
+ QString text = QTStr("DroppedFrames");
+ text = text.arg(QString::number(totalDropped),
+ QString::number(percent, 'f', 1));
+ droppedFrames->setText(text);
+ droppedFrames->setMinimumWidth(droppedFrames->width());
+}
+
+void OBSBasicStatusBar::OBSOutputReconnect(void *data, calldata_t params)
+{
+ OBSBasicStatusBar *statusBar =
+ reinterpret_cast(data);
+
+ QMetaObject::invokeMethod(statusBar, "Reconnect");
+ UNUSED_PARAMETER(params);
+}
+
+void OBSBasicStatusBar::OBSOutputReconnectSuccess(void *data, calldata_t params)
+{
+ OBSBasicStatusBar *statusBar =
+ reinterpret_cast(data);
+
+ QMetaObject::invokeMethod(statusBar, "ReconnectSuccess");
+ UNUSED_PARAMETER(params);
+}
+
+void OBSBasicStatusBar::Reconnect()
+{
+ retries++;
+
+ QString reconnectMsg = QTStr("Basic.StatusBar.Reconnecting");
+ showMessage(reconnectMsg.arg(QString::number(retries)));
+}
+
+void OBSBasicStatusBar::ReconnectSuccess()
+{
+ showMessage(QTStr("Basic.StatusBar.ReconnectSuccessful"), 4000);
+ retries = 0;
+ bitrateUpdateSeconds = -1;
+ lastBytesSent = 0;
+ lastBytesSentTime = os_gettime_ns();
+}
+
+void OBSBasicStatusBar::UpdateStatusBar()
+{
+ UpdateBandwidth();
+ UpdateSessionTime();
+ UpdateDroppedFrames();
+}
+
+void OBSBasicStatusBar::StreamStarted(obs_output_t output)
+{
+ streamOutput = output;
+
+ signal_handler_connect(obs_output_signalhandler(streamOutput),
+ "reconnect", OBSOutputReconnect, this);
+ signal_handler_connect(obs_output_signalhandler(streamOutput),
+ "reconnect_success", OBSOutputReconnectSuccess, this);
+
+ retries = 0;
+ lastBytesSent = 0;
+ lastBytesSentTime = os_gettime_ns();
+ IncRef();
+}
+
+void OBSBasicStatusBar::StreamStopped()
+{
+ if (streamOutput) {
+ signal_handler_disconnect(
+ obs_output_signalhandler(streamOutput),
+ "reconnect", OBSOutputReconnect, this);
+ signal_handler_disconnect(
+ obs_output_signalhandler(streamOutput),
+ "reconnect_success", OBSOutputReconnectSuccess,
+ this);
+
+ streamOutput = nullptr;
+ clearMessage();
+ DecRef();
+ }
+}
+
+void OBSBasicStatusBar::RecordingStarted(obs_output_t output)
+{
+ recordOutput = output;
+ IncRef();
+}
+
+void OBSBasicStatusBar::RecordingStopped()
+{
+ recordOutput = nullptr;
+ DecRef();
+}
diff --git a/obs/window-basic-status-bar.hpp b/obs/window-basic-status-bar.hpp
new file mode 100644
index 000000000..1e2d1952f
--- /dev/null
+++ b/obs/window-basic-status-bar.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+class QLabel;
+
+class OBSBasicStatusBar : public QStatusBar {
+ Q_OBJECT
+
+private:
+ QLabel *droppedFrames;
+ QLabel *sessionTime;
+ QLabel *cpuUsage;
+ QLabel *kbps;
+
+ obs_output_t streamOutput = nullptr;
+ obs_output_t recordOutput = nullptr;
+
+ int retries = 0;
+ int activeRefs = 0;
+ int totalSeconds = 0;
+
+ int bitrateUpdateSeconds = 0;
+ uint64_t lastBytesSent = 0;
+ uint64_t lastBytesSentTime = 0;
+
+ QPointer refreshTimer;
+
+ void DecRef();
+ void IncRef();
+
+ obs_output_t GetOutput();
+
+ void UpdateBandwidth();
+ void UpdateSessionTime();
+ void UpdateDroppedFrames();
+
+ static void OBSOutputReconnect(void *data, calldata_t params);
+ static void OBSOutputReconnectSuccess(void *data, calldata_t params);
+
+private slots:
+ void Reconnect();
+ void ReconnectSuccess();
+ void UpdateStatusBar();
+ void UpdateCPUUsage();
+
+public:
+ OBSBasicStatusBar(QWidget *parent);
+
+ void StreamStarted(obs_output_t output);
+ void StreamStopped();
+ void RecordingStarted(obs_output_t output);
+ void RecordingStopped();
+};
diff --git a/vs/2013/obs-studio/obs-studio.vcxproj b/vs/2013/obs-studio/obs-studio.vcxproj
index e0978207b..f14f19519 100644
--- a/vs/2013/obs-studio/obs-studio.vcxproj
+++ b/vs/2013/obs-studio/obs-studio.vcxproj
@@ -58,6 +58,24 @@
.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork"
+
+ $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing window-basic-status-bar.hpp...
+ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork"
+ $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing window-basic-status-bar.hpp...
+ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork"
+ $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing window-basic-status-bar.hpp...
+ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork"
+ $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing window-basic-status-bar.hpp...
+ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork"
+
@@ -276,6 +294,7 @@
+
@@ -315,6 +334,10 @@
true
true
+
+ true
+ true
+
true
true
@@ -377,6 +400,10 @@
true
true
+
+ true
+ true
+
true
true
diff --git a/vs/2013/obs-studio/obs-studio.vcxproj.filters b/vs/2013/obs-studio/obs-studio.vcxproj.filters
index d0e155c8f..4e8efcc4a 100644
--- a/vs/2013/obs-studio/obs-studio.vcxproj.filters
+++ b/vs/2013/obs-studio/obs-studio.vcxproj.filters
@@ -98,6 +98,9 @@
Header Files
+
+ Header Files
+
@@ -255,6 +258,15 @@
Source Files
+
+ Source Files
+
+
+ Generated Files\Debug
+
+
+ Generated Files\Release
+