1
0

window-basic-settings-stream.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  1. #include <QMessageBox>
  2. #include <QUrl>
  3. #include "window-basic-settings.hpp"
  4. #include "obs-frontend-api.h"
  5. #include "obs-app.hpp"
  6. #include "window-basic-main.hpp"
  7. #include "qt-wrappers.hpp"
  8. #include "url-push-button.hpp"
  9. #ifdef BROWSER_AVAILABLE
  10. #include <browser-panel.hpp>
  11. #endif
  12. #include "auth-oauth.hpp"
  13. #include "ui-config.h"
  14. #if YOUTUBE_ENABLED
  15. #include "youtube-api-wrappers.hpp"
  16. #endif
  17. struct QCef;
  18. struct QCefCookieManager;
  19. extern QCef *cef;
  20. extern QCefCookieManager *panel_cookies;
  21. enum class ListOpt : int {
  22. ShowAll = 1,
  23. Custom,
  24. };
  25. enum class Section : int {
  26. Connect,
  27. StreamKey,
  28. };
  29. inline bool OBSBasicSettings::IsCustomService() const
  30. {
  31. return ui->service->currentData().toInt() == (int)ListOpt::Custom;
  32. }
  33. void OBSBasicSettings::InitStreamPage()
  34. {
  35. ui->connectAccount2->setVisible(false);
  36. ui->disconnectAccount->setVisible(false);
  37. ui->bandwidthTestEnable->setVisible(false);
  38. ui->twitchAddonDropdown->setVisible(false);
  39. ui->twitchAddonLabel->setVisible(false);
  40. ui->connectedAccountLabel->setVisible(false);
  41. ui->connectedAccountText->setVisible(false);
  42. int vertSpacing = ui->topStreamLayout->verticalSpacing();
  43. QMargins m = ui->topStreamLayout->contentsMargins();
  44. m.setBottom(vertSpacing / 2);
  45. ui->topStreamLayout->setContentsMargins(m);
  46. m = ui->loginPageLayout->contentsMargins();
  47. m.setTop(vertSpacing / 2);
  48. ui->loginPageLayout->setContentsMargins(m);
  49. m = ui->streamkeyPageLayout->contentsMargins();
  50. m.setTop(vertSpacing / 2);
  51. ui->streamkeyPageLayout->setContentsMargins(m);
  52. LoadServices(false);
  53. ui->twitchAddonDropdown->addItem(
  54. QTStr("Basic.Settings.Stream.TTVAddon.None"));
  55. ui->twitchAddonDropdown->addItem(
  56. QTStr("Basic.Settings.Stream.TTVAddon.BTTV"));
  57. ui->twitchAddonDropdown->addItem(
  58. QTStr("Basic.Settings.Stream.TTVAddon.FFZ"));
  59. ui->twitchAddonDropdown->addItem(
  60. QTStr("Basic.Settings.Stream.TTVAddon.Both"));
  61. connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
  62. SLOT(UpdateServerList()));
  63. connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
  64. SLOT(UpdateKeyLink()));
  65. connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
  66. SLOT(UpdateVodTrackSetting()));
  67. connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
  68. SLOT(UpdateServiceRecommendations()));
  69. connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
  70. SLOT(UpdateResFPSLimits()));
  71. connect(ui->customServer, SIGNAL(textChanged(const QString &)), this,
  72. SLOT(UpdateKeyLink()));
  73. connect(ui->ignoreRecommended, SIGNAL(clicked(bool)), this,
  74. SLOT(DisplayEnforceWarning(bool)));
  75. connect(ui->ignoreRecommended, SIGNAL(toggled(bool)), this,
  76. SLOT(UpdateResFPSLimits()));
  77. connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
  78. SLOT(UpdateMoreInfoLink()));
  79. connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
  80. SLOT(UpdateAdvNetworkGroup()));
  81. connect(ui->customServer, SIGNAL(textChanged(const QString &)), this,
  82. SLOT(UpdateAdvNetworkGroup()));
  83. }
  84. void OBSBasicSettings::LoadStream1Settings()
  85. {
  86. bool ignoreRecommended =
  87. config_get_bool(main->Config(), "Stream1", "IgnoreRecommended");
  88. obs_service_t *service_obj = main->GetService();
  89. const char *type = obs_service_get_type(service_obj);
  90. loading = true;
  91. OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
  92. const char *service = obs_data_get_string(settings, "service");
  93. const char *server = obs_data_get_string(settings, "server");
  94. const char *key = obs_data_get_string(settings, "key");
  95. if (strcmp(type, "rtmp_custom") == 0) {
  96. ui->service->setCurrentIndex(0);
  97. ui->customServer->setText(server);
  98. bool use_auth = obs_data_get_bool(settings, "use_auth");
  99. const char *username =
  100. obs_data_get_string(settings, "username");
  101. const char *password =
  102. obs_data_get_string(settings, "password");
  103. ui->authUsername->setText(QT_UTF8(username));
  104. ui->authPw->setText(QT_UTF8(password));
  105. ui->useAuth->setChecked(use_auth);
  106. } else {
  107. int idx = ui->service->findText(service);
  108. if (idx == -1) {
  109. if (service && *service)
  110. ui->service->insertItem(1, service);
  111. idx = 1;
  112. }
  113. ui->service->setCurrentIndex(idx);
  114. bool bw_test = obs_data_get_bool(settings, "bwtest");
  115. ui->bandwidthTestEnable->setChecked(bw_test);
  116. idx = config_get_int(main->Config(), "Twitch", "AddonChoice");
  117. ui->twitchAddonDropdown->setCurrentIndex(idx);
  118. }
  119. UpdateServerList();
  120. if (strcmp(type, "rtmp_common") == 0) {
  121. int idx = ui->server->findData(server);
  122. if (idx == -1) {
  123. if (server && *server)
  124. ui->server->insertItem(0, server, server);
  125. idx = 0;
  126. }
  127. ui->server->setCurrentIndex(idx);
  128. }
  129. ui->key->setText(key);
  130. lastService.clear();
  131. on_service_currentIndexChanged(0);
  132. UpdateKeyLink();
  133. UpdateMoreInfoLink();
  134. UpdateVodTrackSetting();
  135. UpdateServiceRecommendations();
  136. bool streamActive = obs_frontend_streaming_active();
  137. ui->streamPage->setEnabled(!streamActive);
  138. ui->ignoreRecommended->setChecked(ignoreRecommended);
  139. loading = false;
  140. QMetaObject::invokeMethod(this, "UpdateResFPSLimits",
  141. Qt::QueuedConnection);
  142. }
  143. void OBSBasicSettings::SaveStream1Settings()
  144. {
  145. bool customServer = IsCustomService();
  146. const char *service_id = customServer ? "rtmp_custom" : "rtmp_common";
  147. obs_service_t *oldService = main->GetService();
  148. OBSDataAutoRelease hotkeyData = obs_hotkeys_save_service(oldService);
  149. OBSDataAutoRelease settings = obs_data_create();
  150. if (!customServer) {
  151. obs_data_set_string(settings, "service",
  152. QT_TO_UTF8(ui->service->currentText()));
  153. obs_data_set_string(
  154. settings, "server",
  155. QT_TO_UTF8(ui->server->currentData().toString()));
  156. } else {
  157. obs_data_set_string(
  158. settings, "server",
  159. QT_TO_UTF8(ui->customServer->text().trimmed()));
  160. obs_data_set_bool(settings, "use_auth",
  161. ui->useAuth->isChecked());
  162. if (ui->useAuth->isChecked()) {
  163. obs_data_set_string(
  164. settings, "username",
  165. QT_TO_UTF8(ui->authUsername->text()));
  166. obs_data_set_string(settings, "password",
  167. QT_TO_UTF8(ui->authPw->text()));
  168. }
  169. }
  170. if (!!auth && strcmp(auth->service(), "Twitch") == 0) {
  171. bool choiceExists = config_has_user_value(
  172. main->Config(), "Twitch", "AddonChoice");
  173. int currentChoice =
  174. config_get_int(main->Config(), "Twitch", "AddonChoice");
  175. int newChoice = ui->twitchAddonDropdown->currentIndex();
  176. config_set_int(main->Config(), "Twitch", "AddonChoice",
  177. newChoice);
  178. if (choiceExists && currentChoice != newChoice)
  179. forceAuthReload = true;
  180. obs_data_set_bool(settings, "bwtest",
  181. ui->bandwidthTestEnable->isChecked());
  182. } else {
  183. obs_data_set_bool(settings, "bwtest", false);
  184. }
  185. obs_data_set_string(settings, "key", QT_TO_UTF8(ui->key->text()));
  186. OBSServiceAutoRelease newService = obs_service_create(
  187. service_id, "default_service", settings, hotkeyData);
  188. if (!newService)
  189. return;
  190. main->SetService(newService);
  191. main->SaveService();
  192. main->auth = auth;
  193. if (!!main->auth) {
  194. main->auth->LoadUI();
  195. main->SetBroadcastFlowEnabled(main->auth->broadcastFlow());
  196. } else {
  197. main->SetBroadcastFlowEnabled(false);
  198. }
  199. SaveCheckBox(ui->ignoreRecommended, "Stream1", "IgnoreRecommended");
  200. }
  201. void OBSBasicSettings::UpdateMoreInfoLink()
  202. {
  203. if (IsCustomService()) {
  204. ui->moreInfoButton->hide();
  205. return;
  206. }
  207. QString serviceName = ui->service->currentText();
  208. obs_properties_t *props = obs_get_service_properties("rtmp_common");
  209. obs_property_t *services = obs_properties_get(props, "service");
  210. OBSDataAutoRelease settings = obs_data_create();
  211. obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName));
  212. obs_property_modified(services, settings);
  213. const char *more_info_link =
  214. obs_data_get_string(settings, "more_info_link");
  215. if (!more_info_link || (*more_info_link == '\0')) {
  216. ui->moreInfoButton->hide();
  217. } else {
  218. ui->moreInfoButton->setTargetUrl(QUrl(more_info_link));
  219. ui->moreInfoButton->show();
  220. }
  221. obs_properties_destroy(props);
  222. }
  223. void OBSBasicSettings::UpdateKeyLink()
  224. {
  225. QString serviceName = ui->service->currentText();
  226. QString customServer = ui->customServer->text().trimmed();
  227. QString streamKeyLink;
  228. obs_properties_t *props = obs_get_service_properties("rtmp_common");
  229. obs_property_t *services = obs_properties_get(props, "service");
  230. OBSDataAutoRelease settings = obs_data_create();
  231. obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName));
  232. obs_property_modified(services, settings);
  233. streamKeyLink = obs_data_get_string(settings, "stream_key_link");
  234. if (customServer.contains("fbcdn.net") && IsCustomService()) {
  235. streamKeyLink =
  236. "https://www.facebook.com/live/producer?ref=OBS";
  237. }
  238. if (serviceName == "Dacast") {
  239. ui->streamKeyLabel->setText(
  240. QTStr("Basic.AutoConfig.StreamPage.EncoderKey"));
  241. } else {
  242. ui->streamKeyLabel->setText(
  243. QTStr("Basic.AutoConfig.StreamPage.StreamKey"));
  244. }
  245. if (QString(streamKeyLink).isNull() ||
  246. QString(streamKeyLink).isEmpty()) {
  247. ui->getStreamKeyButton->hide();
  248. } else {
  249. ui->getStreamKeyButton->setTargetUrl(QUrl(streamKeyLink));
  250. ui->getStreamKeyButton->show();
  251. }
  252. obs_properties_destroy(props);
  253. }
  254. void OBSBasicSettings::LoadServices(bool showAll)
  255. {
  256. obs_properties_t *props = obs_get_service_properties("rtmp_common");
  257. OBSDataAutoRelease settings = obs_data_create();
  258. obs_data_set_bool(settings, "show_all", showAll);
  259. obs_property_t *prop = obs_properties_get(props, "show_all");
  260. obs_property_modified(prop, settings);
  261. ui->service->blockSignals(true);
  262. ui->service->clear();
  263. QStringList names;
  264. obs_property_t *services = obs_properties_get(props, "service");
  265. size_t services_count = obs_property_list_item_count(services);
  266. for (size_t i = 0; i < services_count; i++) {
  267. const char *name = obs_property_list_item_string(services, i);
  268. names.push_back(name);
  269. }
  270. if (showAll)
  271. names.sort(Qt::CaseInsensitive);
  272. for (QString &name : names)
  273. ui->service->addItem(name);
  274. if (!showAll) {
  275. ui->service->addItem(
  276. QTStr("Basic.AutoConfig.StreamPage.Service.ShowAll"),
  277. QVariant((int)ListOpt::ShowAll));
  278. }
  279. ui->service->insertItem(
  280. 0, QTStr("Basic.AutoConfig.StreamPage.Service.Custom"),
  281. QVariant((int)ListOpt::Custom));
  282. if (!lastService.isEmpty()) {
  283. int idx = ui->service->findText(lastService);
  284. if (idx != -1)
  285. ui->service->setCurrentIndex(idx);
  286. }
  287. obs_properties_destroy(props);
  288. ui->service->blockSignals(false);
  289. }
  290. static inline bool is_auth_service(const std::string &service)
  291. {
  292. return Auth::AuthType(service) != Auth::Type::None;
  293. }
  294. static inline bool is_external_oauth(const std::string &service)
  295. {
  296. return Auth::External(service);
  297. }
  298. static void reset_service_ui_fields(Ui::OBSBasicSettings *ui,
  299. std::string &service, bool loading)
  300. {
  301. bool external_oauth = is_external_oauth(service);
  302. if (external_oauth) {
  303. ui->streamKeyWidget->setVisible(false);
  304. ui->streamKeyLabel->setVisible(false);
  305. ui->connectAccount2->setVisible(true);
  306. ui->useStreamKeyAdv->setVisible(true);
  307. ui->streamStackWidget->setCurrentIndex((int)Section::StreamKey);
  308. } else if (cef) {
  309. QString key = ui->key->text();
  310. bool can_auth = is_auth_service(service);
  311. int page = can_auth && (!loading || key.isEmpty())
  312. ? (int)Section::Connect
  313. : (int)Section::StreamKey;
  314. ui->streamStackWidget->setCurrentIndex(page);
  315. ui->streamKeyWidget->setVisible(true);
  316. ui->streamKeyLabel->setVisible(true);
  317. ui->connectAccount2->setVisible(can_auth);
  318. ui->useStreamKeyAdv->setVisible(false);
  319. } else {
  320. ui->connectAccount2->setVisible(false);
  321. ui->useStreamKeyAdv->setVisible(false);
  322. }
  323. ui->connectedAccountLabel->setVisible(false);
  324. ui->connectedAccountText->setVisible(false);
  325. ui->disconnectAccount->setVisible(false);
  326. }
  327. #if YOUTUBE_ENABLED
  328. static void get_yt_ch_title(Ui::OBSBasicSettings *ui)
  329. {
  330. const char *name = config_get_string(OBSBasic::Get()->Config(),
  331. "YouTube", "ChannelName");
  332. if (name) {
  333. ui->connectedAccountText->setText(name);
  334. } else {
  335. // if we still not changed the service page
  336. if (IsYouTubeService(QT_TO_UTF8(ui->service->currentText()))) {
  337. ui->connectedAccountText->setText(
  338. QTStr("Auth.LoadingChannel.Error"));
  339. }
  340. }
  341. }
  342. #endif
  343. void OBSBasicSettings::UseStreamKeyAdvClicked()
  344. {
  345. ui->streamKeyWidget->setVisible(true);
  346. }
  347. void OBSBasicSettings::on_service_currentIndexChanged(int)
  348. {
  349. bool showMore = ui->service->currentData().toInt() ==
  350. (int)ListOpt::ShowAll;
  351. if (showMore)
  352. return;
  353. std::string service = QT_TO_UTF8(ui->service->currentText());
  354. bool custom = IsCustomService();
  355. ui->disconnectAccount->setVisible(false);
  356. ui->bandwidthTestEnable->setVisible(false);
  357. ui->twitchAddonDropdown->setVisible(false);
  358. ui->twitchAddonLabel->setVisible(false);
  359. if (lastService != service.c_str()) {
  360. reset_service_ui_fields(ui.get(), service, loading);
  361. }
  362. ui->useAuth->setVisible(custom);
  363. ui->authUsernameLabel->setVisible(custom);
  364. ui->authUsername->setVisible(custom);
  365. ui->authPwLabel->setVisible(custom);
  366. ui->authPwWidget->setVisible(custom);
  367. if (custom) {
  368. ui->streamkeyPageLayout->insertRow(1, ui->serverLabel,
  369. ui->serverStackedWidget);
  370. ui->serverStackedWidget->setCurrentIndex(1);
  371. ui->serverStackedWidget->setVisible(true);
  372. ui->serverLabel->setVisible(true);
  373. on_useAuth_toggled();
  374. } else {
  375. ui->serverStackedWidget->setCurrentIndex(0);
  376. }
  377. auth.reset();
  378. if (!main->auth) {
  379. return;
  380. }
  381. auto system_auth_service = main->auth->service();
  382. bool service_check = service.find(system_auth_service) !=
  383. std::string::npos;
  384. #if YOUTUBE_ENABLED
  385. service_check = service_check ? service_check
  386. : IsYouTubeService(system_auth_service) &&
  387. IsYouTubeService(service);
  388. #endif
  389. if (service_check) {
  390. auth = main->auth;
  391. OnAuthConnected();
  392. }
  393. }
  394. void OBSBasicSettings::UpdateServerList()
  395. {
  396. QString serviceName = ui->service->currentText();
  397. bool showMore = ui->service->currentData().toInt() ==
  398. (int)ListOpt::ShowAll;
  399. if (showMore) {
  400. LoadServices(true);
  401. ui->service->showPopup();
  402. return;
  403. } else {
  404. lastService = serviceName;
  405. }
  406. obs_properties_t *props = obs_get_service_properties("rtmp_common");
  407. obs_property_t *services = obs_properties_get(props, "service");
  408. OBSDataAutoRelease settings = obs_data_create();
  409. obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName));
  410. obs_property_modified(services, settings);
  411. obs_property_t *servers = obs_properties_get(props, "server");
  412. ui->server->clear();
  413. size_t servers_count = obs_property_list_item_count(servers);
  414. for (size_t i = 0; i < servers_count; i++) {
  415. const char *name = obs_property_list_item_name(servers, i);
  416. const char *server = obs_property_list_item_string(servers, i);
  417. ui->server->addItem(name, server);
  418. }
  419. obs_properties_destroy(props);
  420. }
  421. void OBSBasicSettings::on_show_clicked()
  422. {
  423. if (ui->key->echoMode() == QLineEdit::Password) {
  424. ui->key->setEchoMode(QLineEdit::Normal);
  425. ui->show->setText(QTStr("Hide"));
  426. } else {
  427. ui->key->setEchoMode(QLineEdit::Password);
  428. ui->show->setText(QTStr("Show"));
  429. }
  430. }
  431. void OBSBasicSettings::on_authPwShow_clicked()
  432. {
  433. if (ui->authPw->echoMode() == QLineEdit::Password) {
  434. ui->authPw->setEchoMode(QLineEdit::Normal);
  435. ui->authPwShow->setText(QTStr("Hide"));
  436. } else {
  437. ui->authPw->setEchoMode(QLineEdit::Password);
  438. ui->authPwShow->setText(QTStr("Show"));
  439. }
  440. }
  441. OBSService OBSBasicSettings::SpawnTempService()
  442. {
  443. bool custom = IsCustomService();
  444. const char *service_id = custom ? "rtmp_custom" : "rtmp_common";
  445. OBSDataAutoRelease settings = obs_data_create();
  446. if (!custom) {
  447. obs_data_set_string(settings, "service",
  448. QT_TO_UTF8(ui->service->currentText()));
  449. obs_data_set_string(
  450. settings, "server",
  451. QT_TO_UTF8(ui->server->currentData().toString()));
  452. } else {
  453. obs_data_set_string(
  454. settings, "server",
  455. QT_TO_UTF8(ui->customServer->text().trimmed()));
  456. }
  457. obs_data_set_string(settings, "key", QT_TO_UTF8(ui->key->text()));
  458. OBSServiceAutoRelease newService = obs_service_create(
  459. service_id, "temp_service", settings, nullptr);
  460. return newService.Get();
  461. }
  462. void OBSBasicSettings::OnOAuthStreamKeyConnected()
  463. {
  464. OAuthStreamKey *a = reinterpret_cast<OAuthStreamKey *>(auth.get());
  465. if (a) {
  466. bool validKey = !a->key().empty();
  467. if (validKey)
  468. ui->key->setText(QT_UTF8(a->key().c_str()));
  469. ui->streamKeyWidget->setVisible(false);
  470. ui->streamKeyLabel->setVisible(false);
  471. ui->connectAccount2->setVisible(false);
  472. ui->disconnectAccount->setVisible(true);
  473. ui->useStreamKeyAdv->setVisible(false);
  474. ui->connectedAccountLabel->setVisible(false);
  475. ui->connectedAccountText->setVisible(false);
  476. if (strcmp(a->service(), "Twitch") == 0) {
  477. ui->bandwidthTestEnable->setVisible(true);
  478. ui->twitchAddonLabel->setVisible(true);
  479. ui->twitchAddonDropdown->setVisible(true);
  480. } else {
  481. ui->bandwidthTestEnable->setChecked(false);
  482. }
  483. #if YOUTUBE_ENABLED
  484. if (IsYouTubeService(a->service())) {
  485. ui->key->clear();
  486. ui->connectedAccountLabel->setVisible(true);
  487. ui->connectedAccountText->setVisible(true);
  488. ui->connectedAccountText->setText(
  489. QTStr("Auth.LoadingChannel.Title"));
  490. get_yt_ch_title(ui.get());
  491. }
  492. #endif
  493. }
  494. ui->streamStackWidget->setCurrentIndex((int)Section::StreamKey);
  495. }
  496. void OBSBasicSettings::OnAuthConnected()
  497. {
  498. std::string service = QT_TO_UTF8(ui->service->currentText());
  499. Auth::Type type = Auth::AuthType(service);
  500. if (type == Auth::Type::OAuth_StreamKey ||
  501. type == Auth::Type::OAuth_LinkedAccount) {
  502. OnOAuthStreamKeyConnected();
  503. }
  504. if (!loading) {
  505. stream1Changed = true;
  506. EnableApplyButton(true);
  507. }
  508. }
  509. void OBSBasicSettings::on_connectAccount_clicked()
  510. {
  511. std::string service = QT_TO_UTF8(ui->service->currentText());
  512. OAuth::DeleteCookies(service);
  513. auth = OAuthStreamKey::Login(this, service);
  514. if (!!auth) {
  515. OnAuthConnected();
  516. ui->useStreamKeyAdv->setVisible(false);
  517. }
  518. }
  519. #define DISCONNECT_COMFIRM_TITLE \
  520. "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Title"
  521. #define DISCONNECT_COMFIRM_TEXT \
  522. "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Text"
  523. void OBSBasicSettings::on_disconnectAccount_clicked()
  524. {
  525. QMessageBox::StandardButton button;
  526. button = OBSMessageBox::question(this, QTStr(DISCONNECT_COMFIRM_TITLE),
  527. QTStr(DISCONNECT_COMFIRM_TEXT));
  528. if (button == QMessageBox::No) {
  529. return;
  530. }
  531. main->auth.reset();
  532. auth.reset();
  533. main->SetBroadcastFlowEnabled(false);
  534. std::string service = QT_TO_UTF8(ui->service->currentText());
  535. #ifdef BROWSER_AVAILABLE
  536. OAuth::DeleteCookies(service);
  537. #endif
  538. ui->bandwidthTestEnable->setChecked(false);
  539. reset_service_ui_fields(ui.get(), service, loading);
  540. ui->bandwidthTestEnable->setVisible(false);
  541. ui->twitchAddonDropdown->setVisible(false);
  542. ui->twitchAddonLabel->setVisible(false);
  543. ui->key->setText("");
  544. ui->connectedAccountLabel->setVisible(false);
  545. ui->connectedAccountText->setVisible(false);
  546. }
  547. void OBSBasicSettings::on_useStreamKey_clicked()
  548. {
  549. ui->streamStackWidget->setCurrentIndex((int)Section::StreamKey);
  550. }
  551. void OBSBasicSettings::on_useAuth_toggled()
  552. {
  553. if (!IsCustomService())
  554. return;
  555. bool use_auth = ui->useAuth->isChecked();
  556. ui->authUsernameLabel->setVisible(use_auth);
  557. ui->authUsername->setVisible(use_auth);
  558. ui->authPwLabel->setVisible(use_auth);
  559. ui->authPwWidget->setVisible(use_auth);
  560. }
  561. void OBSBasicSettings::UpdateVodTrackSetting()
  562. {
  563. bool enableForCustomServer = config_get_bool(
  564. GetGlobalConfig(), "General", "EnableCustomServerVodTrack");
  565. bool enableVodTrack = ui->service->currentText() == "Twitch";
  566. bool wasEnabled = !!vodTrackCheckbox;
  567. if (enableForCustomServer && IsCustomService())
  568. enableVodTrack = true;
  569. if (enableVodTrack == wasEnabled)
  570. return;
  571. if (!enableVodTrack) {
  572. delete vodTrackCheckbox;
  573. delete vodTrackContainer;
  574. delete simpleVodTrack;
  575. return;
  576. }
  577. /* -------------------------------------- */
  578. /* simple output mode vod track widgets */
  579. bool simpleAdv = ui->simpleOutAdvanced->isChecked();
  580. bool vodTrackEnabled = config_get_bool(main->Config(), "SimpleOutput",
  581. "VodTrackEnabled");
  582. simpleVodTrack = new QCheckBox(this);
  583. simpleVodTrack->setText(
  584. QTStr("Basic.Settings.Output.Simple.TwitchVodTrack"));
  585. simpleVodTrack->setVisible(simpleAdv);
  586. simpleVodTrack->setChecked(vodTrackEnabled);
  587. int pos;
  588. ui->simpleStreamingLayout->getWidgetPosition(ui->simpleOutAdvanced,
  589. &pos, nullptr);
  590. ui->simpleStreamingLayout->insertRow(pos + 1, nullptr, simpleVodTrack);
  591. HookWidget(simpleVodTrack, SIGNAL(clicked(bool)),
  592. SLOT(OutputsChanged()));
  593. connect(ui->simpleOutAdvanced, SIGNAL(toggled(bool)),
  594. simpleVodTrack.data(), SLOT(setVisible(bool)));
  595. /* -------------------------------------- */
  596. /* advanced output mode vod track widgets */
  597. vodTrackCheckbox = new QCheckBox(this);
  598. vodTrackCheckbox->setText(
  599. QTStr("Basic.Settings.Output.Adv.TwitchVodTrack"));
  600. vodTrackCheckbox->setLayoutDirection(Qt::RightToLeft);
  601. vodTrackContainer = new QWidget(this);
  602. QHBoxLayout *vodTrackLayout = new QHBoxLayout();
  603. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  604. vodTrack[i] = new QRadioButton(QString::number(i + 1));
  605. vodTrackLayout->addWidget(vodTrack[i]);
  606. HookWidget(vodTrack[i], SIGNAL(clicked(bool)),
  607. SLOT(OutputsChanged()));
  608. }
  609. HookWidget(vodTrackCheckbox, SIGNAL(clicked(bool)),
  610. SLOT(OutputsChanged()));
  611. vodTrackLayout->addStretch();
  612. vodTrackLayout->setContentsMargins(0, 0, 0, 0);
  613. vodTrackContainer->setLayout(vodTrackLayout);
  614. ui->advOutTopLayout->insertRow(2, vodTrackCheckbox, vodTrackContainer);
  615. vodTrackEnabled =
  616. config_get_bool(main->Config(), "AdvOut", "VodTrackEnabled");
  617. vodTrackCheckbox->setChecked(vodTrackEnabled);
  618. vodTrackContainer->setEnabled(vodTrackEnabled);
  619. connect(vodTrackCheckbox, SIGNAL(clicked(bool)), vodTrackContainer,
  620. SLOT(setEnabled(bool)));
  621. int trackIndex =
  622. config_get_int(main->Config(), "AdvOut", "VodTrackIndex");
  623. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  624. vodTrack[i]->setChecked((i + 1) == trackIndex);
  625. }
  626. }
  627. OBSService OBSBasicSettings::GetStream1Service()
  628. {
  629. return stream1Changed ? SpawnTempService()
  630. : OBSService(main->GetService());
  631. }
  632. void OBSBasicSettings::UpdateServiceRecommendations()
  633. {
  634. bool customServer = IsCustomService();
  635. ui->ignoreRecommended->setVisible(!customServer);
  636. ui->enforceSettingsLabel->setVisible(!customServer);
  637. OBSService service = GetStream1Service();
  638. int vbitrate, abitrate;
  639. BPtr<obs_service_resolution> res_list;
  640. size_t res_count;
  641. int fps;
  642. obs_service_get_max_bitrate(service, &vbitrate, &abitrate);
  643. obs_service_get_supported_resolutions(service, &res_list, &res_count);
  644. obs_service_get_max_fps(service, &fps);
  645. QString text;
  646. #define ENFORCE_TEXT(x) QTStr("Basic.Settings.Stream.Recommended." x)
  647. if (vbitrate)
  648. text += ENFORCE_TEXT("MaxVideoBitrate")
  649. .arg(QString::number(vbitrate));
  650. if (abitrate) {
  651. if (!text.isEmpty())
  652. text += "<br>";
  653. text += ENFORCE_TEXT("MaxAudioBitrate")
  654. .arg(QString::number(abitrate));
  655. }
  656. if (res_count) {
  657. if (!text.isEmpty())
  658. text += "<br>";
  659. obs_service_resolution best_res = {};
  660. int best_res_pixels = 0;
  661. for (size_t i = 0; i < res_count; i++) {
  662. obs_service_resolution res = res_list[i];
  663. int res_pixels = res.cx + res.cy;
  664. if (res_pixels > best_res_pixels) {
  665. best_res = res;
  666. best_res_pixels = res_pixels;
  667. }
  668. }
  669. QString res_str =
  670. QString("%1x%2").arg(QString::number(best_res.cx),
  671. QString::number(best_res.cy));
  672. text += ENFORCE_TEXT("MaxResolution").arg(res_str);
  673. }
  674. if (fps) {
  675. if (!text.isEmpty())
  676. text += "<br>";
  677. text += ENFORCE_TEXT("MaxFPS").arg(QString::number(fps));
  678. }
  679. #undef ENFORCE_TEXT
  680. #if YOUTUBE_ENABLED
  681. if (IsYouTubeService(QT_TO_UTF8(ui->service->currentText()))) {
  682. if (!text.isEmpty())
  683. text += "<br><br>";
  684. text += "<a href=\"https://www.youtube.com/t/terms\">"
  685. "YouTube Terms of Service</a><br>"
  686. "<a href=\"http://www.google.com/policies/privacy\">"
  687. "Google Privacy Policy</a><br>"
  688. "<a href=\"https://security.google.com/settings/security/permissions\">"
  689. "Google Third-Party Permissions</a>";
  690. }
  691. #endif
  692. ui->enforceSettingsLabel->setText(text);
  693. }
  694. void OBSBasicSettings::DisplayEnforceWarning(bool checked)
  695. {
  696. if (IsCustomService())
  697. return;
  698. if (!checked) {
  699. SimpleRecordingEncoderChanged();
  700. return;
  701. }
  702. QMessageBox::StandardButton button;
  703. #define ENFORCE_WARNING(x) \
  704. QTStr("Basic.Settings.Stream.IgnoreRecommended.Warn." x)
  705. button = OBSMessageBox::question(this, ENFORCE_WARNING("Title"),
  706. ENFORCE_WARNING("Text"));
  707. #undef ENFORCE_WARNING
  708. if (button == QMessageBox::No) {
  709. QMetaObject::invokeMethod(ui->ignoreRecommended, "setChecked",
  710. Qt::QueuedConnection,
  711. Q_ARG(bool, false));
  712. return;
  713. }
  714. SimpleRecordingEncoderChanged();
  715. }
  716. bool OBSBasicSettings::ResFPSValid(obs_service_resolution *res_list,
  717. size_t res_count, int max_fps)
  718. {
  719. if (!res_count && !max_fps)
  720. return true;
  721. if (res_count) {
  722. QString res = ui->outputResolution->currentText();
  723. bool found_res = false;
  724. int cx, cy;
  725. if (sscanf(QT_TO_UTF8(res), "%dx%d", &cx, &cy) != 2)
  726. return false;
  727. for (size_t i = 0; i < res_count; i++) {
  728. if (res_list[i].cx == cx && res_list[i].cy == cy) {
  729. found_res = true;
  730. break;
  731. }
  732. }
  733. if (!found_res)
  734. return false;
  735. }
  736. if (max_fps) {
  737. int fpsType = ui->fpsType->currentIndex();
  738. if (fpsType != 0)
  739. return false;
  740. std::string fps_str = QT_TO_UTF8(ui->fpsCommon->currentText());
  741. float fps;
  742. sscanf(fps_str.c_str(), "%f", &fps);
  743. if (fps > (float)max_fps)
  744. return false;
  745. }
  746. return true;
  747. }
  748. extern void set_closest_res(int &cx, int &cy,
  749. struct obs_service_resolution *res_list,
  750. size_t count);
  751. /* Checks for and updates the resolution and FPS limits of a service, if any.
  752. *
  753. * If the service has a resolution and/or FPS limit, this will enforce those
  754. * limitations in the UI itself, preventing the user from selecting a
  755. * resolution or FPS that's not supported.
  756. *
  757. * This is an unpleasant thing to have to do to users, but there is no other
  758. * way to ensure that a service's restricted resolution/framerate values are
  759. * properly enforced, otherwise users will just be confused when things aren't
  760. * working correctly. The user can turn it off if they're partner (or if they
  761. * want to risk getting in trouble with their service) by selecting the "Ignore
  762. * recommended settings" option in the stream section of settings.
  763. *
  764. * This only affects services that have a resolution and/or framerate limit, of
  765. * which as of this writing, and hopefully for the foreseeable future, there is
  766. * only one.
  767. */
  768. void OBSBasicSettings::UpdateResFPSLimits()
  769. {
  770. if (loading)
  771. return;
  772. int idx = ui->service->currentIndex();
  773. if (idx == -1)
  774. return;
  775. bool ignoreRecommended = ui->ignoreRecommended->isChecked();
  776. BPtr<obs_service_resolution> res_list;
  777. size_t res_count = 0;
  778. int max_fps = 0;
  779. if (!IsCustomService() && !ignoreRecommended) {
  780. OBSService service = GetStream1Service();
  781. obs_service_get_supported_resolutions(service, &res_list,
  782. &res_count);
  783. obs_service_get_max_fps(service, &max_fps);
  784. }
  785. /* ------------------------------------ */
  786. /* Check for enforced res/FPS */
  787. QString res = ui->outputResolution->currentText();
  788. QString fps_str;
  789. int cx = 0, cy = 0;
  790. double max_fpsd = (double)max_fps;
  791. int closest_fps_index = -1;
  792. double fpsd;
  793. sscanf(QT_TO_UTF8(res), "%dx%d", &cx, &cy);
  794. if (res_count)
  795. set_closest_res(cx, cy, res_list, res_count);
  796. if (max_fps) {
  797. int fpsType = ui->fpsType->currentIndex();
  798. if (fpsType == 1) { //Integer
  799. fpsd = (double)ui->fpsInteger->value();
  800. } else if (fpsType == 2) { //Fractional
  801. fpsd = (double)ui->fpsNumerator->value() /
  802. (double)ui->fpsDenominator->value();
  803. } else { //Common
  804. sscanf(QT_TO_UTF8(ui->fpsCommon->currentText()), "%lf",
  805. &fpsd);
  806. }
  807. double closest_diff = 1000000000000.0;
  808. for (int i = 0; i < ui->fpsCommon->count(); i++) {
  809. double com_fpsd;
  810. sscanf(QT_TO_UTF8(ui->fpsCommon->itemText(i)), "%lf",
  811. &com_fpsd);
  812. if (com_fpsd > max_fpsd) {
  813. continue;
  814. }
  815. double diff = fabs(com_fpsd - fpsd);
  816. if (diff < closest_diff) {
  817. closest_diff = diff;
  818. closest_fps_index = i;
  819. fps_str = ui->fpsCommon->itemText(i);
  820. }
  821. }
  822. }
  823. QString res_str =
  824. QString("%1x%2").arg(QString::number(cx), QString::number(cy));
  825. /* ------------------------------------ */
  826. /* Display message box if res/FPS bad */
  827. bool valid = ResFPSValid(res_list, res_count, max_fps);
  828. if (!valid) {
  829. /* if the user was already on facebook with an incompatible
  830. * resolution, assume it's an upgrade */
  831. if (lastServiceIdx == -1 && lastIgnoreRecommended == -1) {
  832. ui->ignoreRecommended->setChecked(true);
  833. ui->ignoreRecommended->setProperty("changed", true);
  834. stream1Changed = true;
  835. EnableApplyButton(true);
  836. UpdateResFPSLimits();
  837. return;
  838. }
  839. QMessageBox::StandardButton button;
  840. #define WARNING_VAL(x) \
  841. QTStr("Basic.Settings.Output.Warn.EnforceResolutionFPS." x)
  842. QString str;
  843. if (res_count)
  844. str += WARNING_VAL("Resolution").arg(res_str);
  845. if (max_fps) {
  846. if (!str.isEmpty())
  847. str += "\n";
  848. str += WARNING_VAL("FPS").arg(fps_str);
  849. }
  850. button = OBSMessageBox::question(this, WARNING_VAL("Title"),
  851. WARNING_VAL("Msg").arg(str));
  852. #undef WARNING_VAL
  853. if (button == QMessageBox::No) {
  854. if (idx != lastServiceIdx)
  855. QMetaObject::invokeMethod(
  856. ui->service, "setCurrentIndex",
  857. Qt::QueuedConnection,
  858. Q_ARG(int, lastServiceIdx));
  859. else
  860. QMetaObject::invokeMethod(ui->ignoreRecommended,
  861. "setChecked",
  862. Qt::QueuedConnection,
  863. Q_ARG(bool, true));
  864. return;
  865. }
  866. }
  867. /* ------------------------------------ */
  868. /* Update widgets/values if switching */
  869. /* to/from enforced resolution/FPS */
  870. ui->outputResolution->blockSignals(true);
  871. if (res_count) {
  872. ui->outputResolution->clear();
  873. ui->outputResolution->setEditable(false);
  874. HookWidget(ui->outputResolution,
  875. SIGNAL(currentIndexChanged(int)),
  876. SLOT(VideoChangedResolution()));
  877. int new_res_index = -1;
  878. for (size_t i = 0; i < res_count; i++) {
  879. obs_service_resolution val = res_list[i];
  880. QString str =
  881. QString("%1x%2").arg(QString::number(val.cx),
  882. QString::number(val.cy));
  883. ui->outputResolution->addItem(str);
  884. if (val.cx == cx && val.cy == cy)
  885. new_res_index = (int)i;
  886. }
  887. ui->outputResolution->setCurrentIndex(new_res_index);
  888. if (!valid) {
  889. ui->outputResolution->setProperty("changed", true);
  890. videoChanged = true;
  891. EnableApplyButton(true);
  892. }
  893. } else {
  894. QString baseRes = ui->baseResolution->currentText();
  895. int baseCX, baseCY;
  896. sscanf(QT_TO_UTF8(baseRes), "%dx%d", &baseCX, &baseCY);
  897. if (!ui->outputResolution->isEditable()) {
  898. RecreateOutputResolutionWidget();
  899. ui->outputResolution->blockSignals(true);
  900. ResetDownscales((uint32_t)baseCX, (uint32_t)baseCY,
  901. true);
  902. ui->outputResolution->setCurrentText(res);
  903. }
  904. }
  905. ui->outputResolution->blockSignals(false);
  906. if (max_fps) {
  907. for (int i = 0; i < ui->fpsCommon->count(); i++) {
  908. double com_fpsd;
  909. sscanf(QT_TO_UTF8(ui->fpsCommon->itemText(i)), "%lf",
  910. &com_fpsd);
  911. if (com_fpsd > max_fpsd) {
  912. SetComboItemEnabled(ui->fpsCommon, i, false);
  913. continue;
  914. }
  915. }
  916. ui->fpsType->setCurrentIndex(0);
  917. ui->fpsCommon->setCurrentIndex(closest_fps_index);
  918. if (!valid) {
  919. ui->fpsType->setProperty("changed", true);
  920. ui->fpsCommon->setProperty("changed", true);
  921. videoChanged = true;
  922. EnableApplyButton(true);
  923. }
  924. } else {
  925. for (int i = 0; i < ui->fpsCommon->count(); i++)
  926. SetComboItemEnabled(ui->fpsCommon, i, true);
  927. }
  928. SetComboItemEnabled(ui->fpsType, 1, !max_fps);
  929. SetComboItemEnabled(ui->fpsType, 2, !max_fps);
  930. /* ------------------------------------ */
  931. lastIgnoreRecommended = (int)ignoreRecommended;
  932. lastServiceIdx = idx;
  933. }
  934. bool OBSBasicSettings::IsServiceOutputHasNetworkFeatures()
  935. {
  936. if (IsCustomService())
  937. return ui->customServer->text().startsWith("rtmp");
  938. OBSServiceAutoRelease service = SpawnTempService();
  939. const char *output = obs_service_get_output_type(service);
  940. if (!output)
  941. return true;
  942. if (strcmp(output, "rtmp_output") == 0)
  943. return true;
  944. return false;
  945. }