123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- #include "OBSBasicStatusBar.hpp"
- #include "ui_StatusBarWidget.h"
- #include <widgets/OBSBasic.hpp>
- #include "moc_OBSBasicStatusBar.cpp"
- 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;
- 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 = static_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 = static_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("");
- }
|