cd-rec-status.cpp 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. #include <obs-module.h>
  2. #include <obs-frontend-api.h>
  3. #include <QLabel>
  4. #include <QTimer>
  5. #include <QVBoxLayout>
  6. #include <QWidget>
  7. #include <QDockWidget>
  8. #include <QWebSocket>
  9. #include <QJsonDocument>
  10. #include <QJsonObject>
  11. OBS_DECLARE_MODULE()
  12. OBS_MODULE_USE_DEFAULT_LOCALE("cd-rec-status", "en-US")
  13. class CDStatusDock : public QWidget {
  14. public:
  15. CDStatusDock(QWidget *parent = nullptr) : QWidget(parent), socket(new QWebSocket()), retryDotCount(0) {
  16. QVBoxLayout *layout = new QVBoxLayout(this);
  17. statusLabel = new QLabel("Connecting to websocket...", this);
  18. layout->addWidget(statusLabel);
  19. setLayout(layout);
  20. retryDotCount = 0;
  21. // Retry every 3s
  22. reconnectTimer = new QTimer(this);
  23. reconnectTimer->setInterval(3000);
  24. connect(reconnectTimer, &QTimer::timeout, this, &CDStatusDock::tryReconnect);
  25. // Animate retrying text every 500ms
  26. retryAnimationTimer = new QTimer(this);
  27. retryAnimationTimer->setInterval(500);
  28. connect(retryAnimationTimer, &QTimer::timeout, this, &CDStatusDock::updateRetryingLabel);
  29. connect(socket, &QWebSocket::connected, this, &CDStatusDock::onConnected);
  30. connect(socket, &QWebSocket::disconnected, this, &CDStatusDock::onDisconnected);
  31. connect(socket, &QWebSocket::textMessageReceived, this, &CDStatusDock::onMessageReceived);
  32. socket->open(QUrl(QStringLiteral("ws://localhost:8765")));
  33. }
  34. ~CDStatusDock() {
  35. reconnectTimer->stop();
  36. retryAnimationTimer->stop();
  37. socket->close();
  38. delete socket;
  39. }
  40. private slots:
  41. void onConnected() {
  42. reconnectTimer->stop();
  43. retryAnimationTimer->stop();
  44. statusLabel->setText("Connected to websocket");
  45. updateBackgroundColor(Qt::gray);
  46. }
  47. void onDisconnected() {
  48. retryDotCount = 0;
  49. reconnectTimer->start();
  50. retryAnimationTimer->start();
  51. updateRetryingLabel();
  52. updateBackgroundColor(Qt::red);
  53. }
  54. void tryReconnect() {
  55. if (socket->state() == QAbstractSocket::ConnectedState ||
  56. socket->state() == QAbstractSocket::ConnectingState)
  57. return;
  58. socket->abort(); // force close if half-open
  59. socket->open(QUrl(QStringLiteral("ws://localhost:8765")));
  60. updateRetryingLabel();
  61. }
  62. void onMessageReceived(const QString &message) {
  63. QJsonParseError parseError;
  64. QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &parseError);
  65. if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
  66. statusLabel->setText("Invalid status message");
  67. updateBackgroundColor(Qt::red);
  68. return;
  69. }
  70. QJsonObject obj = doc.object();
  71. bool isRecording = obj["recording"].toBool();
  72. QString statusText;
  73. if (isRecording) {
  74. statusText =
  75. QString("Recording: active\nCD: %1\nTrack: %2\nElapsed CD Time: %3\nElapsed Track Time: %4")
  76. .arg(obj["cd"].toInt())
  77. .arg(obj["track"].toInt())
  78. .arg(obj["cd_time"].toString())
  79. .arg(obj["track_time"].toString());
  80. } else {
  81. statusText = QString("Recording is currently not active.");
  82. }
  83. statusLabel->setText(statusText);
  84. updateBackgroundColor(isRecording ? QColor("#228B22") : Qt::gray);
  85. }
  86. private:
  87. void updateBackgroundColor(const QColor &bgColor) {
  88. this->setStyleSheet(QString(
  89. "background-color: %1;"
  90. "color: %2;"
  91. ).arg(bgColor.name())
  92. .arg(isColorDark(bgColor) ? "white" : "black"));
  93. }
  94. bool isColorDark(const QColor &color) {
  95. // Perceived brightness formula
  96. int brightness = (color.red() * 299 + color.green() * 587 + color.blue() * 114) / 1000;
  97. return brightness < 128;
  98. }
  99. void updateRetryingLabel() {
  100. retryDotCount = (retryDotCount % 3) + 1;
  101. QString dots = QString(".").repeated(retryDotCount);
  102. statusLabel->setText(QString("Disconnected from websocket\nRetrying%1").arg(dots));
  103. }
  104. private:
  105. QLabel *statusLabel;
  106. QWebSocket *socket;
  107. QTimer *reconnectTimer;
  108. QTimer *retryAnimationTimer;
  109. int retryDotCount;
  110. };
  111. static void *create_cd_status_dock(obs_source_t *) {
  112. return new CDStatusDock();
  113. }
  114. bool obs_module_load(void)
  115. {
  116. QDockWidget *dock = new QDockWidget("CD Rec Status");
  117. dock->setObjectName("cd-rec-status-dock");
  118. dock->setWidget(new CDStatusDock());
  119. obs_frontend_add_custom_qdock("cd_status_dock", (void *)dock);
  120. blog(LOG_INFO, "CD Rec Status Dock loaded!");
  121. return true;
  122. }
  123. MODULE_EXPORT const char *obs_module_description(void)
  124. {
  125. return obs_module_text("Description");
  126. }
  127. MODULE_EXPORT const char *obs_module_name(void)
  128. {
  129. return obs_module_text("CD Rec Status");
  130. }