OBSBasic_Streaming.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. Zachary Lund <[email protected]>
  4. Philippe Groarke <[email protected]>
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ******************************************************************************/
  16. #include "OBSBasic.hpp"
  17. #include <components/UIValidation.hpp>
  18. #ifdef YOUTUBE_ENABLED
  19. #include <docks/YouTubeAppDock.hpp>
  20. #include <utility/YoutubeApiWrappers.hpp>
  21. #endif
  22. #include <qt-wrappers.hpp>
  23. #define STREAMING_START "==== Streaming Start ==============================================="
  24. #define STREAMING_STOP "==== Streaming Stop ================================================"
  25. void OBSBasic::DisplayStreamStartError()
  26. {
  27. QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str())
  28. : QTStr("Output.StartFailedGeneric");
  29. emit StreamingStopped();
  30. if (sysTrayStream) {
  31. sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
  32. sysTrayStream->setEnabled(true);
  33. }
  34. QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message);
  35. }
  36. void OBSBasic::StartStreaming()
  37. {
  38. if (outputHandler->StreamingActive())
  39. return;
  40. if (disableOutputsRef)
  41. return;
  42. if (auth && auth->broadcastFlow()) {
  43. if (!broadcastActive && !broadcastReady) {
  44. QMessageBox no_broadcast(this);
  45. no_broadcast.setText(QTStr("Output.NoBroadcast.Text"));
  46. QPushButton *SetupBroadcast =
  47. no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole);
  48. no_broadcast.setDefaultButton(SetupBroadcast);
  49. no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole);
  50. no_broadcast.setIcon(QMessageBox::Information);
  51. no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title"));
  52. no_broadcast.exec();
  53. if (no_broadcast.clickedButton() == SetupBroadcast)
  54. QMetaObject::invokeMethod(this, "SetupBroadcast");
  55. return;
  56. }
  57. }
  58. emit StreamingPreparing();
  59. if (sysTrayStream) {
  60. sysTrayStream->setEnabled(false);
  61. sysTrayStream->setText("Basic.Main.PreparingStream");
  62. }
  63. auto finish_stream_setup = [&](bool setupStreamingResult) {
  64. if (!setupStreamingResult) {
  65. DisplayStreamStartError();
  66. return;
  67. }
  68. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING);
  69. SaveProject();
  70. emit StreamingStarting(autoStartBroadcast);
  71. if (sysTrayStream)
  72. sysTrayStream->setText("Basic.Main.Connecting");
  73. if (!outputHandler->StartStreaming(service)) {
  74. DisplayStreamStartError();
  75. return;
  76. }
  77. if (autoStartBroadcast) {
  78. emit BroadcastStreamStarted(autoStopBroadcast);
  79. broadcastActive = true;
  80. }
  81. bool recordWhenStreaming =
  82. config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
  83. if (recordWhenStreaming)
  84. StartRecording();
  85. bool replayBufferWhileStreaming =
  86. config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
  87. if (replayBufferWhileStreaming)
  88. StartReplayBuffer();
  89. #ifdef YOUTUBE_ENABLED
  90. if (!autoStartBroadcast)
  91. OBSBasic::ShowYouTubeAutoStartWarning();
  92. #endif
  93. };
  94. setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup);
  95. }
  96. void OBSBasic::StopStreaming()
  97. {
  98. SaveProject();
  99. if (outputHandler->StreamingActive())
  100. outputHandler->StopStreaming(streamingStopping);
  101. // special case: force reset broadcast state if
  102. // no autostart and no autostop selected
  103. if (!autoStartBroadcast && !broadcastActive) {
  104. broadcastActive = false;
  105. autoStartBroadcast = true;
  106. autoStopBroadcast = true;
  107. broadcastReady = false;
  108. }
  109. if (autoStopBroadcast) {
  110. broadcastActive = false;
  111. broadcastReady = false;
  112. }
  113. emit BroadcastStreamReady(broadcastReady);
  114. OnDeactivate();
  115. bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
  116. bool keepRecordingWhenStreamStops =
  117. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops");
  118. if (recordWhenStreaming && !keepRecordingWhenStreamStops)
  119. StopRecording();
  120. bool replayBufferWhileStreaming =
  121. config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
  122. bool keepReplayBufferStreamStops =
  123. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops");
  124. if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
  125. StopReplayBuffer();
  126. }
  127. void OBSBasic::ForceStopStreaming()
  128. {
  129. SaveProject();
  130. if (outputHandler->StreamingActive())
  131. outputHandler->StopStreaming(true);
  132. // special case: force reset broadcast state if
  133. // no autostart and no autostop selected
  134. if (!autoStartBroadcast && !broadcastActive) {
  135. broadcastActive = false;
  136. autoStartBroadcast = true;
  137. autoStopBroadcast = true;
  138. broadcastReady = false;
  139. }
  140. if (autoStopBroadcast) {
  141. broadcastActive = false;
  142. broadcastReady = false;
  143. }
  144. emit BroadcastStreamReady(broadcastReady);
  145. OnDeactivate();
  146. bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
  147. bool keepRecordingWhenStreamStops =
  148. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops");
  149. if (recordWhenStreaming && !keepRecordingWhenStreamStops)
  150. StopRecording();
  151. bool replayBufferWhileStreaming =
  152. config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
  153. bool keepReplayBufferStreamStops =
  154. config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops");
  155. if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
  156. StopReplayBuffer();
  157. }
  158. void OBSBasic::StreamDelayStarting(int sec)
  159. {
  160. emit StreamingStarted(true);
  161. if (sysTrayStream) {
  162. sysTrayStream->setText(QTStr("Basic.Main.StopStreaming"));
  163. sysTrayStream->setEnabled(true);
  164. }
  165. ui->statusbar->StreamDelayStarting(sec);
  166. OnActivate();
  167. }
  168. void OBSBasic::StreamDelayStopping(int sec)
  169. {
  170. emit StreamingStopped(true);
  171. if (sysTrayStream) {
  172. sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
  173. sysTrayStream->setEnabled(true);
  174. }
  175. ui->statusbar->StreamDelayStopping(sec);
  176. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
  177. }
  178. void OBSBasic::StreamingStart()
  179. {
  180. emit StreamingStarted();
  181. OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
  182. ui->statusbar->StreamStarted(output);
  183. if (sysTrayStream) {
  184. sysTrayStream->setText(QTStr("Basic.Main.StopStreaming"));
  185. sysTrayStream->setEnabled(true);
  186. }
  187. #ifdef YOUTUBE_ENABLED
  188. if (!autoStartBroadcast) {
  189. // get a current stream key
  190. obs_service_t *service_obj = GetService();
  191. OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
  192. std::string key = obs_data_get_string(settings, "stream_id");
  193. if (!key.empty() && !youtubeStreamCheckThread) {
  194. youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); });
  195. youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread");
  196. youtubeStreamCheckThread->start();
  197. }
  198. }
  199. #endif
  200. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED);
  201. OnActivate();
  202. #ifdef YOUTUBE_ENABLED
  203. if (YouTubeAppDock::IsYTServiceSelected())
  204. youtubeAppDock->IngestionStarted();
  205. #endif
  206. blog(LOG_INFO, STREAMING_START);
  207. }
  208. void OBSBasic::StreamStopping()
  209. {
  210. emit StreamingStopping();
  211. if (sysTrayStream)
  212. sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming"));
  213. streamingStopping = true;
  214. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
  215. }
  216. void OBSBasic::StreamingStop(int code, QString last_error)
  217. {
  218. const char *errorDescription = "";
  219. DStr errorMessage;
  220. bool use_last_error = false;
  221. bool encode_error = false;
  222. bool should_reconnect = false;
  223. /* Ignore stream key error for multitrack output if its internal reconnect handling is active. */
  224. if (code == OBS_OUTPUT_INVALID_STREAM && outputHandler->multitrackVideo &&
  225. outputHandler->multitrackVideo->RestartOnError()) {
  226. code = OBS_OUTPUT_SUCCESS;
  227. should_reconnect = true;
  228. }
  229. switch (code) {
  230. case OBS_OUTPUT_BAD_PATH:
  231. errorDescription = Str("Output.ConnectFail.BadPath");
  232. break;
  233. case OBS_OUTPUT_CONNECT_FAILED:
  234. use_last_error = true;
  235. errorDescription = Str("Output.ConnectFail.ConnectFailed");
  236. break;
  237. case OBS_OUTPUT_INVALID_STREAM:
  238. errorDescription = Str("Output.ConnectFail.InvalidStream");
  239. break;
  240. case OBS_OUTPUT_ENCODE_ERROR:
  241. encode_error = true;
  242. break;
  243. case OBS_OUTPUT_HDR_DISABLED:
  244. errorDescription = Str("Output.ConnectFail.HdrDisabled");
  245. break;
  246. default:
  247. case OBS_OUTPUT_ERROR:
  248. use_last_error = true;
  249. errorDescription = Str("Output.ConnectFail.Error");
  250. break;
  251. case OBS_OUTPUT_DISCONNECTED:
  252. /* doesn't happen if output is set to reconnect. note that
  253. * reconnects are handled in the output, not in the UI */
  254. use_last_error = true;
  255. errorDescription = Str("Output.ConnectFail.Disconnected");
  256. }
  257. if (use_last_error && !last_error.isEmpty())
  258. dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error));
  259. else
  260. dstr_copy(errorMessage, errorDescription);
  261. ui->statusbar->StreamStopped();
  262. emit StreamingStopped();
  263. if (sysTrayStream) {
  264. sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
  265. sysTrayStream->setEnabled(true);
  266. }
  267. streamingStopping = false;
  268. OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED);
  269. OnDeactivate();
  270. #ifdef YOUTUBE_ENABLED
  271. if (YouTubeAppDock::IsYTServiceSelected())
  272. youtubeAppDock->IngestionStopped();
  273. #endif
  274. blog(LOG_INFO, STREAMING_STOP);
  275. if (encode_error) {
  276. QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg")
  277. : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error);
  278. OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg);
  279. } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
  280. OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage));
  281. } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
  282. SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning);
  283. }
  284. // Reset broadcast button state/text
  285. if (!broadcastActive)
  286. SetBroadcastFlowEnabled(auth && auth->broadcastFlow());
  287. if (should_reconnect)
  288. QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection);
  289. }
  290. void OBSBasic::StreamActionTriggered()
  291. {
  292. if (outputHandler->StreamingActive()) {
  293. bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream");
  294. #ifdef YOUTUBE_ENABLED
  295. if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) {
  296. QMessageBox::StandardButton button = OBSMessageBox::question(
  297. this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"),
  298. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  299. if (button == QMessageBox::No)
  300. return;
  301. confirm = false;
  302. }
  303. #endif
  304. if (confirm && isVisible()) {
  305. QMessageBox::StandardButton button =
  306. OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"),
  307. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  308. if (button == QMessageBox::No)
  309. return;
  310. }
  311. StopStreaming();
  312. } else {
  313. if (!UIValidation::NoSourcesConfirmation(this))
  314. return;
  315. Auth *auth = GetAuth();
  316. auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream
  317. : UIValidation::StreamSettingsConfirmation(this, service);
  318. switch (action) {
  319. case StreamSettingsAction::ContinueStream:
  320. break;
  321. case StreamSettingsAction::OpenSettings:
  322. on_action_Settings_triggered();
  323. return;
  324. case StreamSettingsAction::Cancel:
  325. return;
  326. }
  327. bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream");
  328. bool bwtest = false;
  329. if (this->auth) {
  330. OBSDataAutoRelease settings = obs_service_get_settings(service);
  331. bwtest = obs_data_get_bool(settings, "bwtest");
  332. // Disable confirmation if this is going to open broadcast setup
  333. if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive)
  334. confirm = false;
  335. }
  336. if (bwtest && isVisible()) {
  337. QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"),
  338. QTStr("ConfirmBWTest.Text"));
  339. if (button == QMessageBox::No)
  340. return;
  341. } else if (confirm && isVisible()) {
  342. QMessageBox::StandardButton button =
  343. OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"),
  344. QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
  345. if (button == QMessageBox::No)
  346. return;
  347. }
  348. StartStreaming();
  349. }
  350. }
  351. bool OBSBasic::StreamingActive()
  352. {
  353. if (!outputHandler)
  354. return false;
  355. return outputHandler->StreamingActive();
  356. }