0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-20 04:42:18 +02:00
obs-studio/UI/window-basic-status-bar.cpp
PatTheMav 710d99ef4d UI: Improve incremental compile times via explicit file includes
When a source file contains an explicit include with a filename
following the "moc_<actual-filename>.cpp" pattern, then CMake's
AUTOMOC generation tool will recognize the matching pair and generate
the replacement header file and add the required include directory
entries.

For all files which do contain Q_OBJECT or similar declarations but do
not have an explicit include directive, the global mocs_compilation.cpp
file will still be generated (which groups all "missing" generated
headers).

The larger this global file is, the more expensive incremental
compilation will be as this file (and all its contained generated
headers) will be re-generated regardless of whether actual changes
occurred.
2024-08-22 16:45:12 -04:00

644 lines
16 KiB
C++

#include <QPainter>
#include <QPixmap>
#include "obs-app.hpp"
#include "window-basic-main.hpp"
#include "moc_window-basic-status-bar.cpp"
#include "window-basic-main-outputs.hpp"
#include "qt-wrappers.hpp"
#include "platform.hpp"
#include "ui_StatusBarWidget.h"
static constexpr int bitrateUpdateSeconds = 2;
static constexpr int congestionUpdateSeconds = 4;
static constexpr float excellentThreshold = 0.0f;
static constexpr float goodThreshold = 0.3333f;
static constexpr float mediocreThreshold = 0.6667f;
static constexpr float badThreshold = 1.0f;
StatusBarWidget::StatusBarWidget(QWidget *parent)
: QWidget(parent),
ui(new Ui::StatusBarWidget)
{
ui->setupUi(this);
}
StatusBarWidget::~StatusBarWidget() {}
OBSBasicStatusBar::OBSBasicStatusBar(QWidget *parent)
: QStatusBar(parent),
excellentPixmap(QIcon(":/res/images/network-excellent.svg")
.pixmap(QSize(16, 16))),
goodPixmap(
QIcon(":/res/images/network-good.svg").pixmap(QSize(16, 16))),
mediocrePixmap(QIcon(":/res/images/network-mediocre.svg")
.pixmap(QSize(16, 16))),
badPixmap(
QIcon(":/res/images/network-bad.svg").pixmap(QSize(16, 16))),
recordingActivePixmap(QIcon(":/res/images/recording-active.svg")
.pixmap(QSize(16, 16))),
recordingPausePixmap(QIcon(":/res/images/recording-pause.svg")
.pixmap(QSize(16, 16))),
streamingActivePixmap(QIcon(":/res/images/streaming-active.svg")
.pixmap(QSize(16, 16)))
{
congestionArray.reserve(congestionUpdateSeconds);
statusWidget = new StatusBarWidget(this);
statusWidget->ui->delayInfo->setText("");
statusWidget->ui->droppedFrames->setText(
QTStr("DroppedFrames").arg("0", "0.0"));
statusWidget->ui->statusIcon->setPixmap(inactivePixmap);
statusWidget->ui->streamIcon->setPixmap(streamingInactivePixmap);
statusWidget->ui->streamTime->setDisabled(true);
statusWidget->ui->recordIcon->setPixmap(recordingInactivePixmap);
statusWidget->ui->recordTime->setDisabled(true);
statusWidget->ui->delayFrame->hide();
statusWidget->ui->issuesFrame->hide();
statusWidget->ui->kbps->hide();
addPermanentWidget(statusWidget, 1);
setMinimumHeight(statusWidget->height());
UpdateIcons();
connect(App(), &OBSApp::StyleChanged, this,
&OBSBasicStatusBar::UpdateIcons);
messageTimer = new QTimer(this);
messageTimer->setSingleShot(true);
connect(messageTimer, &QTimer::timeout, this,
&OBSBasicStatusBar::clearMessage);
clearMessage();
}
void OBSBasicStatusBar::Activate()
{
if (!active) {
refreshTimer = new QTimer(this);
connect(refreshTimer, &QTimer::timeout, this,
&OBSBasicStatusBar::UpdateStatusBar);
int skipped = video_output_get_skipped_frames(obs_get_video());
int total = video_output_get_total_frames(obs_get_video());
totalStreamSeconds = 0;
totalRecordSeconds = 0;
lastSkippedFrameCount = 0;
startSkippedFrameCount = skipped;
startTotalFrameCount = total;
refreshTimer->start(1000);
active = true;
if (streamOutput) {
statusWidget->ui->statusIcon->setPixmap(inactivePixmap);
}
}
if (streamOutput) {
statusWidget->ui->streamIcon->setPixmap(streamingActivePixmap);
statusWidget->ui->streamTime->setDisabled(false);
statusWidget->ui->issuesFrame->show();
statusWidget->ui->kbps->show();
firstCongestionUpdate = true;
}
if (recordOutput) {
statusWidget->ui->recordIcon->setPixmap(recordingActivePixmap);
statusWidget->ui->recordTime->setDisabled(false);
}
}
void OBSBasicStatusBar::Deactivate()
{
OBSBasic *main = qobject_cast<OBSBasic *>(parent());
if (!main)
return;
if (!streamOutput) {
statusWidget->ui->streamTime->setText(QString("00:00:00"));
statusWidget->ui->streamTime->setDisabled(true);
statusWidget->ui->streamIcon->setPixmap(
streamingInactivePixmap);
statusWidget->ui->statusIcon->setPixmap(inactivePixmap);
statusWidget->ui->delayFrame->hide();
statusWidget->ui->issuesFrame->hide();
statusWidget->ui->kbps->hide();
totalStreamSeconds = 0;
congestionArray.clear();
disconnected = false;
firstCongestionUpdate = false;
}
if (!recordOutput) {
statusWidget->ui->recordTime->setText(QString("00:00:00"));
statusWidget->ui->recordTime->setDisabled(true);
statusWidget->ui->recordIcon->setPixmap(
recordingInactivePixmap);
totalRecordSeconds = 0;
}
if (main->outputHandler && !main->outputHandler->Active()) {
delete refreshTimer;
statusWidget->ui->delayInfo->setText("");
statusWidget->ui->droppedFrames->setText(
QTStr("DroppedFrames").arg("0", "0.0"));
statusWidget->ui->kbps->setText("0 kbps");
delaySecTotal = 0;
delaySecStarting = 0;
delaySecStopping = 0;
reconnectTimeout = 0;
active = false;
overloadedNotify = true;
statusWidget->ui->statusIcon->setPixmap(inactivePixmap);
}
}
void OBSBasicStatusBar::UpdateDelayMsg()
{
QString msg;
if (delaySecTotal) {
if (delaySecStarting && !delaySecStopping) {
msg = QTStr("Basic.StatusBar.DelayStartingIn");
msg = msg.arg(QString::number(delaySecStarting));
} else if (!delaySecStarting && delaySecStopping) {
msg = QTStr("Basic.StatusBar.DelayStoppingIn");
msg = msg.arg(QString::number(delaySecStopping));
} else if (delaySecStarting && delaySecStopping) {
msg = QTStr("Basic.StatusBar.DelayStartingStoppingIn");
msg = msg.arg(QString::number(delaySecStopping),
QString::number(delaySecStarting));
} else {
msg = QTStr("Basic.StatusBar.Delay");
msg = msg.arg(QString::number(delaySecTotal));
}
if (!statusWidget->ui->delayFrame->isVisible())
statusWidget->ui->delayFrame->show();
statusWidget->ui->delayInfo->setText(msg);
}
}
void OBSBasicStatusBar::UpdateBandwidth()
{
if (!streamOutput)
return;
if (++seconds < bitrateUpdateSeconds)
return;
OBSOutput output = OBSGetStrongRef(streamOutput);
if (!output)
return;
uint64_t bytesSent = obs_output_get_total_bytes(output);
uint64_t bytesSentTime = os_gettime_ns();
if (bytesSent < lastBytesSent)
bytesSent = 0;
if (bytesSent == 0)
lastBytesSent = 0;
uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8;
double timePassed =
double(bytesSentTime - lastBytesSentTime) / 1000000000.0;
double kbitsPerSec = double(bitsBetween) / timePassed / 1000.0;
QString text;
text += QString::number(kbitsPerSec, 'f', 0) + QString(" kbps");
statusWidget->ui->kbps->setText(text);
statusWidget->ui->kbps->setMinimumWidth(
statusWidget->ui->kbps->width());
if (!statusWidget->ui->kbps->isVisible())
statusWidget->ui->kbps->show();
lastBytesSent = bytesSent;
lastBytesSentTime = bytesSentTime;
seconds = 0;
}
void OBSBasicStatusBar::UpdateCPUUsage()
{
OBSBasic *main = qobject_cast<OBSBasic *>(parent());
if (!main)
return;
QString text;
text += QString("CPU: ") +
QString::number(main->GetCPUUsage(), 'f', 1) + QString("%");
statusWidget->ui->cpuUsage->setText(text);
statusWidget->ui->cpuUsage->setMinimumWidth(
statusWidget->ui->cpuUsage->width());
UpdateCurrentFPS();
}
void OBSBasicStatusBar::UpdateCurrentFPS()
{
struct obs_video_info ovi;
obs_get_video_info(&ovi);
float targetFPS = (float)ovi.fps_num / (float)ovi.fps_den;
QString text = QString::asprintf("%.2f / %.2f FPS",
obs_get_active_fps(), targetFPS);
statusWidget->ui->fpsCurrent->setText(text);
statusWidget->ui->fpsCurrent->setMinimumWidth(
statusWidget->ui->fpsCurrent->width());
}
void OBSBasicStatusBar::UpdateStreamTime()
{
totalStreamSeconds++;
int seconds = totalStreamSeconds % 60;
int totalMinutes = totalStreamSeconds / 60;
int minutes = totalMinutes % 60;
int hours = totalMinutes / 60;
QString text =
QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds);
statusWidget->ui->streamTime->setText(text);
if (streamOutput && !statusWidget->ui->streamTime->isEnabled())
statusWidget->ui->streamTime->setDisabled(false);
if (reconnectTimeout > 0) {
QString msg = QTStr("Basic.StatusBar.Reconnecting")
.arg(QString::number(retries),
QString::number(reconnectTimeout));
showMessage(msg);
disconnected = true;
statusWidget->ui->statusIcon->setPixmap(disconnectedPixmap);
congestionArray.clear();
reconnectTimeout--;
} else if (retries > 0) {
QString msg = QTStr("Basic.StatusBar.AttemptingReconnect");
showMessage(msg.arg(QString::number(retries)));
}
if (delaySecStopping > 0 || delaySecStarting > 0) {
if (delaySecStopping > 0)
--delaySecStopping;
if (delaySecStarting > 0)
--delaySecStarting;
UpdateDelayMsg();
}
}
extern volatile bool recording_paused;
void OBSBasicStatusBar::UpdateRecordTime()
{
bool paused = os_atomic_load_bool(&recording_paused);
if (!paused) {
totalRecordSeconds++;
if (recordOutput && !statusWidget->ui->recordTime->isEnabled())
statusWidget->ui->recordTime->setDisabled(false);
} else {
statusWidget->ui->recordIcon->setPixmap(
streamPauseIconToggle ? recordingPauseInactivePixmap
: recordingPausePixmap);
streamPauseIconToggle = !streamPauseIconToggle;
}
UpdateRecordTimeLabel();
}
void OBSBasicStatusBar::UpdateRecordTimeLabel()
{
int seconds = totalRecordSeconds % 60;
int totalMinutes = totalRecordSeconds / 60;
int minutes = totalMinutes % 60;
int hours = totalMinutes / 60;
QString text =
QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds);
if (os_atomic_load_bool(&recording_paused)) {
text += QStringLiteral(" (PAUSED)");
}
statusWidget->ui->recordTime->setText(text);
}
void OBSBasicStatusBar::UpdateDroppedFrames()
{
if (!streamOutput)
return;
OBSOutput output = OBSGetStrongRef(streamOutput);
if (!output)
return;
int totalDropped = obs_output_get_frames_dropped(output);
int totalFrames = obs_output_get_total_frames(output);
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));
statusWidget->ui->droppedFrames->setText(text);
if (!statusWidget->ui->issuesFrame->isVisible())
statusWidget->ui->issuesFrame->show();
/* ----------------------------------- *
* calculate congestion color */
float congestion = obs_output_get_congestion(output);
float avgCongestion = (congestion + lastCongestion) * 0.5f;
if (avgCongestion < congestion)
avgCongestion = congestion;
if (avgCongestion > 1.0f)
avgCongestion = 1.0f;
lastCongestion = congestion;
if (disconnected)
return;
bool update = firstCongestionUpdate;
float congestionOverTime = avgCongestion;
if (congestionArray.size() >= congestionUpdateSeconds) {
congestionOverTime = accumulate(congestionArray.begin(),
congestionArray.end(), 0.0f) /
(float)congestionArray.size();
congestionArray.clear();
update = true;
} else {
congestionArray.emplace_back(avgCongestion);
}
if (update) {
if (congestionOverTime <= excellentThreshold + EPSILON)
statusWidget->ui->statusIcon->setPixmap(
excellentPixmap);
else if (congestionOverTime <= goodThreshold)
statusWidget->ui->statusIcon->setPixmap(goodPixmap);
else if (congestionOverTime <= mediocreThreshold)
statusWidget->ui->statusIcon->setPixmap(mediocrePixmap);
else if (congestionOverTime <= badThreshold)
statusWidget->ui->statusIcon->setPixmap(badPixmap);
firstCongestionUpdate = false;
}
}
void OBSBasicStatusBar::OBSOutputReconnect(void *data, calldata_t *params)
{
OBSBasicStatusBar *statusBar =
reinterpret_cast<OBSBasicStatusBar *>(data);
int seconds = (int)calldata_int(params, "timeout_sec");
QMetaObject::invokeMethod(statusBar, "Reconnect", Q_ARG(int, seconds));
}
void OBSBasicStatusBar::OBSOutputReconnectSuccess(void *data, calldata_t *)
{
OBSBasicStatusBar *statusBar =
reinterpret_cast<OBSBasicStatusBar *>(data);
QMetaObject::invokeMethod(statusBar, "ReconnectSuccess");
}
void OBSBasicStatusBar::Reconnect(int seconds)
{
OBSBasic *main = qobject_cast<OBSBasic *>(parent());
if (!retries)
main->SysTrayNotify(
QTStr("Basic.SystemTray.Message.Reconnecting"),
QSystemTrayIcon::Warning);
reconnectTimeout = seconds;
if (streamOutput) {
OBSOutput output = OBSGetStrongRef(streamOutput);
if (!output)
return;
delaySecTotal = obs_output_get_active_delay(output);
UpdateDelayMsg();
retries++;
}
}
void OBSBasicStatusBar::ReconnectClear()
{
retries = 0;
reconnectTimeout = 0;
seconds = -1;
lastBytesSent = 0;
lastBytesSentTime = os_gettime_ns();
delaySecTotal = 0;
UpdateDelayMsg();
}
void OBSBasicStatusBar::ReconnectSuccess()
{
OBSBasic *main = qobject_cast<OBSBasic *>(parent());
QString msg = QTStr("Basic.StatusBar.ReconnectSuccessful");
showMessage(msg, 4000);
main->SysTrayNotify(msg, QSystemTrayIcon::Information);
ReconnectClear();
if (streamOutput) {
OBSOutput output = OBSGetStrongRef(streamOutput);
if (!output)
return;
delaySecTotal = obs_output_get_active_delay(output);
UpdateDelayMsg();
disconnected = false;
firstCongestionUpdate = true;
}
}
void OBSBasicStatusBar::UpdateStatusBar()
{
OBSBasic *main = qobject_cast<OBSBasic *>(parent());
UpdateBandwidth();
if (streamOutput)
UpdateStreamTime();
if (recordOutput)
UpdateRecordTime();
UpdateDroppedFrames();
int skipped = video_output_get_skipped_frames(obs_get_video());
int total = video_output_get_total_frames(obs_get_video());
skipped -= startSkippedFrameCount;
total -= startTotalFrameCount;
int diff = skipped - lastSkippedFrameCount;
double percentage = double(skipped) / double(total) * 100.0;
if (diff > 10 && percentage >= 0.1f) {
showMessage(QTStr("HighResourceUsage"), 4000);
if (!main->isVisible() && overloadedNotify) {
main->SysTrayNotify(QTStr("HighResourceUsage"),
QSystemTrayIcon::Warning);
overloadedNotify = false;
}
}
lastSkippedFrameCount = skipped;
}
void OBSBasicStatusBar::StreamDelayStarting(int sec)
{
OBSBasic *main = qobject_cast<OBSBasic *>(parent());
if (!main || !main->outputHandler)
return;
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
streamOutput = OBSGetWeakRef(output);
delaySecTotal = delaySecStarting = sec;
UpdateDelayMsg();
Activate();
}
void OBSBasicStatusBar::StreamDelayStopping(int sec)
{
delaySecTotal = delaySecStopping = sec;
UpdateDelayMsg();
}
void OBSBasicStatusBar::StreamStarted(obs_output_t *output)
{
streamOutput = OBSGetWeakRef(output);
streamSigs.emplace_back(obs_output_get_signal_handler(output),
"reconnect", OBSOutputReconnect, this);
streamSigs.emplace_back(obs_output_get_signal_handler(output),
"reconnect_success", OBSOutputReconnectSuccess,
this);
retries = 0;
lastBytesSent = 0;
lastBytesSentTime = os_gettime_ns();
Activate();
}
void OBSBasicStatusBar::StreamStopped()
{
if (streamOutput) {
streamSigs.clear();
ReconnectClear();
streamOutput = nullptr;
clearMessage();
Deactivate();
}
}
void OBSBasicStatusBar::RecordingStarted(obs_output_t *output)
{
recordOutput = OBSGetWeakRef(output);
Activate();
}
void OBSBasicStatusBar::RecordingStopped()
{
recordOutput = nullptr;
Deactivate();
}
void OBSBasicStatusBar::RecordingPaused()
{
if (recordOutput) {
statusWidget->ui->recordIcon->setPixmap(recordingPausePixmap);
streamPauseIconToggle = true;
}
UpdateRecordTimeLabel();
}
void OBSBasicStatusBar::RecordingUnpaused()
{
if (recordOutput) {
statusWidget->ui->recordIcon->setPixmap(recordingActivePixmap);
}
UpdateRecordTimeLabel();
}
static QPixmap GetPixmap(const QString &filename)
{
QString path = obs_frontend_is_theme_dark() ? "theme:Dark/"
: ":/res/images/";
return QIcon(path + filename).pixmap(QSize(16, 16));
}
void OBSBasicStatusBar::UpdateIcons()
{
disconnectedPixmap = GetPixmap("network-disconnected.svg");
inactivePixmap = GetPixmap("network-inactive.svg");
streamingInactivePixmap = GetPixmap("streaming-inactive.svg");
recordingInactivePixmap = GetPixmap("recording-inactive.svg");
recordingPauseInactivePixmap =
GetPixmap("recording-pause-inactive.svg");
bool streaming = obs_frontend_streaming_active();
if (!streaming) {
statusWidget->ui->streamIcon->setPixmap(
streamingInactivePixmap);
statusWidget->ui->statusIcon->setPixmap(inactivePixmap);
} else {
if (disconnected)
statusWidget->ui->statusIcon->setPixmap(
disconnectedPixmap);
}
bool recording = obs_frontend_recording_active();
if (!recording)
statusWidget->ui->recordIcon->setPixmap(
recordingInactivePixmap);
}
void OBSBasicStatusBar::showMessage(const QString &message, int timeout)
{
messageTimer->stop();
statusWidget->ui->message->setText(message);
if (timeout)
messageTimer->start(timeout);
}
void OBSBasicStatusBar::clearMessage()
{
statusWidget->ui->message->setText("");
}