Просмотр исходного кода

Merge pull request #5242 from derrod/yt-better-buttons

Adjustments to YouTube Broadcast Setup
Jim 4 лет назад
Родитель
Сommit
5c9aa83e05

+ 4 - 0
UI/auth-base.cpp

@@ -61,7 +61,11 @@ void Auth::Load()
 	if (main->auth) {
 		if (main->auth->LoadInternal()) {
 			main->auth->LoadUI();
+			main->SetBroadcastFlowEnabled(
+				main->auth->broadcastFlow());
 		}
+	} else {
+		main->SetBroadcastFlowEnabled(false);
 	}
 }
 

+ 2 - 0
UI/auth-base.hpp

@@ -34,6 +34,7 @@ public:
 		std::string service;
 		Type type;
 		bool externalOAuth;
+		bool usesBroadcastFlow;
 	};
 
 	typedef std::function<std::shared_ptr<Auth>()> create_cb;
@@ -44,6 +45,7 @@ public:
 	inline Type type() const { return def.type; }
 	inline const char *service() const { return def.service.c_str(); }
 	inline bool external() const { return def.externalOAuth; }
+	inline bool broadcastFlow() const { return def.usesBroadcastFlow; }
 
 	virtual void LoadUI() {}
 

+ 3 - 3
UI/auth-youtube.hpp

@@ -12,9 +12,9 @@ class BrowserDock;
 #endif
 
 inline const std::vector<Auth::Def> youtubeServices = {
-	{"YouTube - RTMP", Auth::Type::OAuth_LinkedAccount, true},
-	{"YouTube - RTMPS", Auth::Type::OAuth_LinkedAccount, true},
-	{"YouTube - HLS", Auth::Type::OAuth_LinkedAccount, true}};
+	{"YouTube - RTMP", Auth::Type::OAuth_LinkedAccount, true, true},
+	{"YouTube - RTMPS", Auth::Type::OAuth_LinkedAccount, true, true},
+	{"YouTube - HLS", Auth::Type::OAuth_LinkedAccount, true, true}};
 
 class YoutubeAuth : public OAuthStreamKey {
 	Q_OBJECT

+ 18 - 6
UI/data/locale/en-US.ini

@@ -391,6 +391,10 @@ Output.RecordError.EncodeErrorMsg="An encoder error occurred while recording."
 Output.BadPath.Title="Bad File Path"
 Output.BadPath.Text="The configured file output path is invalid. Please check your settings to confirm that a valid file path has been set."
 
+# broadcast setup messages
+Output.NoBroadcast.Title="No Broadcast Configured"
+Output.NoBroadcast.Text="You need to set up a broadcast before you can start streaming."
+
 # log upload dialog text and messages
 LogReturnDialog="Log Upload Successful"
 LogReturnDialog.Description="Your log file has been uploaded. You can now share the URL for debugging or support purposes."
@@ -642,8 +646,10 @@ Basic.Main.UnpauseRecording="Unpause Recording"
 Basic.Main.StoppingRecording="Stopping Recording..."
 Basic.Main.StopReplayBuffer="Stop Replay Buffer"
 Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..."
+Basic.Main.SetupBroadcast="Manage Broadcast"
 Basic.Main.StopStreaming="Stop Streaming"
 Basic.Main.StopBroadcast="End Broadcast"
+Basic.Main.AutoStopEnabled="(Auto Stop)"
 Basic.Main.StoppingStreaming="Stopping Stream..."
 Basic.Main.ForceStopStreaming="Stop Streaming (discard delay)"
 Basic.Main.ShowContextBar="Show Source Toolbar"
@@ -1187,7 +1193,9 @@ YouTube.Auth.WaitingAuth.Title="YouTube User Authorization"
 YouTube.Auth.WaitingAuth.Text="Please complete the authorization in your external browser.<br>If the external browser does not open, follow this link and complete the authorization:<br>%1"
 YouTube.AuthError.Text="Failed to get channel information: %1."
 
-YouTube.Actions.CreateNewEvent="Create New Event"
+YouTube.Actions.WindowTitle="YouTube Broadcast Setup - Channel: %1"
+YouTube.Actions.CreateNewEvent="Create New Broadcast"
+YouTube.Actions.ChooseEvent="Select Existing Broadcast"
 YouTube.Actions.Title="Title*"
 YouTube.Actions.MyBroadcast="My Broadcast"
 YouTube.Actions.Description="Description"
@@ -1213,10 +1221,15 @@ YouTube.Actions.EnableDVR="Enable DVR"
 YouTube.Actions.360Video="360 video"
 YouTube.Actions.360Video.Help="<a href='https://vr.youtube.com/create/360/'>(?)</a>"
 YouTube.Actions.ScheduleForLater="Schedule for later"
-YouTube.Actions.Create_GoLive="Create event and go live"
-YouTube.Actions.Choose_GoLive="Go live with selected event"
-YouTube.Actions.Create_Save="Schedule event with above settings"
-YouTube.Actions.Dashboard="YouTube Studio..."
+YouTube.Actions.RememberSettings="Remember these settings"
+
+YouTube.Actions.Create_Ready="Create broadcast"
+YouTube.Actions.Create_GoLive="Create broadcast and start streaming"
+YouTube.Actions.Choose_Ready="Select broadcast"
+YouTube.Actions.Choose_GoLive="Select broadcast and start streaming"
+YouTube.Actions.Create_Schedule="Schedule broadcast"
+YouTube.Actions.Create_Schedule_Ready="Schedule and select broadcast"
+YouTube.Actions.Dashboard="Open YouTube Studio"
 
 YouTube.Actions.Error.Title="Live broadcast creation error"
 YouTube.Actions.Error.Text="YouTube access error '%1'.<br/>A detailed error description can be found at <a href='https://developers.google.com/youtube/v3/live/docs/errors'>https://developers.google.com/youtube/v3/live/docs/errors</a>"
@@ -1230,7 +1243,6 @@ YouTube.Actions.EventsLoading="Loading list of events..."
 YouTube.Actions.EventCreated.Title="Event Created"
 YouTube.Actions.EventCreated.Text="Event created successfully."
 
-YouTube.Actions.ChooseEvent="Select Existing Event"
 YouTube.Actions.Stream="Stream"
 YouTube.Actions.Stream.ScheduledFor="Scheduled for %1"
 YouTube.Actions.Stream.Resume="Resume interrupted stream"

+ 125 - 94
UI/forms/OBSYoutubeActions.ui

@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>583</width>
-    <height>510</height>
+    <width>585</width>
+    <height>536</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -23,7 +23,7 @@
    </size>
   </property>
   <property name="windowTitle">
-   <string>YouTube Actions</string>
+   <string>YouTube.Actions.WindowTitle</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
@@ -228,6 +228,113 @@
          </item>
         </layout>
        </item>
+       <item row="8" column="0">
+        <spacer name="horizontalSpacer_14">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="8" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <item>
+          <widget class="QCheckBox" name="checkDVR">
+           <property name="text">
+            <string>YouTube.Actions.EnableDVR</string>
+           </property>
+           <property name="checked">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="9" column="0">
+        <spacer name="horizontalSpacer_8">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="9" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_7">
+         <item>
+          <widget class="QCheckBox" name="check360Video">
+           <property name="text">
+            <string>YouTube.Actions.360Video</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="help360Video">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>YouTube.Actions.360Video.Help</string>
+           </property>
+           <property name="textFormat">
+            <enum>Qt::RichText</enum>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_10">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </item>
+       <item row="10" column="0">
+        <spacer name="horizontalSpacer_12">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="10" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_9">
+         <item>
+          <widget class="QCheckBox" name="checkScheduledLater">
+           <property name="enabled">
+            <bool>true</bool>
+           </property>
+           <property name="text">
+            <string>YouTube.Actions.ScheduleForLater</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
        <item row="11" column="0">
         <spacer name="horizontalSpacer_6">
          <property name="orientation">
@@ -320,33 +427,6 @@
          </item>
         </layout>
        </item>
-       <item row="10" column="1">
-        <layout class="QHBoxLayout" name="horizontalLayout_9">
-         <item>
-          <widget class="QCheckBox" name="checkScheduledLater">
-           <property name="enabled">
-            <bool>true</bool>
-           </property>
-           <property name="text">
-            <string>YouTube.Actions.ScheduleForLater</string>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item row="10" column="0">
-        <spacer name="horizontalSpacer_12">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
        <item row="13" column="0">
         <spacer name="horizontalSpacer_13">
          <property name="orientation">
@@ -380,35 +460,19 @@
          </item>
         </layout>
        </item>
-       <item row="8" column="1">
-        <layout class="QHBoxLayout" name="horizontalLayout_3">
+       <item row="14" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_12">
          <item>
-          <widget class="QCheckBox" name="checkDVR">
+          <widget class="QCheckBox" name="checkRememberSettings">
            <property name="text">
-            <string>YouTube.Actions.EnableDVR</string>
-           </property>
-           <property name="checked">
-            <bool>true</bool>
+            <string>YouTube.Actions.RememberSettings</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
-       <item row="8" column="0">
-        <spacer name="horizontalSpacer_14">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item row="9" column="0">
-        <spacer name="horizontalSpacer_8">
+       <item row="14" column="0">
+        <spacer name="horizontalSpacer">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
@@ -420,46 +484,6 @@
          </property>
         </spacer>
        </item>
-       <item row="9" column="1">
-        <layout class="QHBoxLayout" name="horizontalLayout_7">
-         <item>
-          <widget class="QCheckBox" name="check360Video">
-           <property name="text">
-            <string>YouTube.Actions.360Video</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QLabel" name="help360Video">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="text">
-            <string>YouTube.Actions.360Video.Help</string>
-           </property>
-           <property name="textFormat">
-            <enum>Qt::RichText</enum>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <spacer name="horizontalSpacer_10">
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-           <property name="sizeHint" stdset="0">
-            <size>
-             <width>40</width>
-             <height>20</height>
-            </size>
-           </property>
-          </spacer>
-         </item>
-        </layout>
-       </item>
       </layout>
      </widget>
      <widget class="QWidget" name="ytEventList">
@@ -480,7 +504,7 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>522</width>
+            <width>524</width>
             <height>192</height>
            </rect>
           </property>
@@ -576,7 +600,7 @@
     </widget>
    </item>
    <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,2,6">
+    <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,2,0,6">
      <item>
       <widget class="QPushButton" name="cancelButton">
        <property name="text">
@@ -591,6 +615,13 @@
        </property>
       </widget>
      </item>
+     <item>
+      <widget class="QPushButton" name="saveButton">
+       <property name="text">
+        <string>YouTube.Actions.Create_Ready</string>
+       </property>
+      </widget>
+     </item>
      <item>
       <widget class="QPushButton" name="okButton">
        <property name="text">

+ 5 - 1
UI/window-basic-auto-config.cpp

@@ -1130,8 +1130,12 @@ void AutoConfig::SaveStreamSettings()
 	main->SetService(newService);
 	main->SaveService();
 	main->auth = streamPage->auth;
-	if (!!main->auth)
+	if (!!main->auth) {
 		main->auth->LoadUI();
+		main->SetBroadcastFlowEnabled(main->auth->broadcastFlow());
+	} else {
+		main->SetBroadcastFlowEnabled(false);
+	}
 
 	/* ---------------------------------- */
 	/* save stream settings               */

+ 94 - 42
UI/window-basic-main.cpp

@@ -6145,7 +6145,8 @@ void OBSBasic::DisplayStreamStartError()
 
 #if YOUTUBE_ENABLED
 void OBSBasic::YouTubeActionDialogOk(const QString &id, const QString &key,
-				     bool autostart, bool autostop)
+				     bool autostart, bool autostop,
+				     bool start_now)
 {
 	//blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key));
 	obs_service_t *service_obj = GetService();
@@ -6160,8 +6161,12 @@ void OBSBasic::YouTubeActionDialogOk(const QString &id, const QString &key,
 	obs_service_update(service_obj, settings);
 	autoStartBroadcast = autostart;
 	autoStopBroadcast = autostop;
+	broadcastReady = true;
 
 	obs_data_release(settings);
+
+	if (start_now)
+		QMetaObject::invokeMethod(this, "StartStreaming");
 }
 
 void OBSBasic::YoutubeStreamCheck(const std::string &key)
@@ -6254,33 +6259,28 @@ void OBSBasic::StartStreaming()
 	if (disableOutputsRef)
 		return;
 
-	Auth *auth = GetAuth();
-	if (auth) {
-		auth->OnStreamConfig();
-#if YOUTUBE_ENABLED
-		if (!broadcastActive && autoStartBroadcast &&
-		    IsYouTubeService(auth->service())) {
-			OBSYoutubeActions *dialog;
-			dialog = new OBSYoutubeActions(this, auth);
-			connect(dialog, &OBSYoutubeActions::ok, this,
-				&OBSBasic::YouTubeActionDialogOk);
-			int result = dialog->Valid() ? dialog->exec()
-						     : QDialog::Rejected;
-			if (result != QDialog::Accepted) {
-				ui->streamButton->setText(
-					QTStr("Basic.Main.StartStreaming"));
-				ui->streamButton->setEnabled(true);
-				ui->streamButton->setChecked(false);
+	if (auth && auth->broadcastFlow()) {
+		if (!broadcastActive && !broadcastReady) {
+			ui->streamButton->setChecked(false);
 
-				if (sysTrayStream) {
-					sysTrayStream->setText(
-						ui->streamButton->text());
-					sysTrayStream->setEnabled(true);
-				}
-				return;
-			}
+			QMessageBox no_broadcast(this);
+			no_broadcast.setText(QTStr("Output.NoBroadcast.Text"));
+			QPushButton *SetupBroadcast = no_broadcast.addButton(
+				QTStr("Basic.Main.SetupBroadcast"),
+				QMessageBox::YesRole);
+			no_broadcast.setDefaultButton(SetupBroadcast);
+			no_broadcast.addButton(QTStr("Close"),
+					       QMessageBox::NoRole);
+			no_broadcast.setIcon(QMessageBox::Information);
+			no_broadcast.setWindowTitle(
+				QTStr("Output.NoBroadcast.Title"));
+			no_broadcast.exec();
+
+			if (no_broadcast.clickedButton() == SetupBroadcast)
+				QMetaObject::invokeMethod(this,
+							  "SetupBroadcast");
+			return;
 		}
-#endif
 	}
 
 	if (!outputHandler->SetupStreaming(service)) {
@@ -6296,6 +6296,7 @@ void OBSBasic::StartStreaming()
 	ui->streamButton->setEnabled(false);
 	ui->streamButton->setChecked(false);
 	ui->streamButton->setText(QTStr("Basic.Main.Connecting"));
+	ui->broadcastButton->setChecked(false);
 
 	if (sysTrayStream) {
 		sysTrayStream->setEnabled(false);
@@ -6308,7 +6309,6 @@ void OBSBasic::StartStreaming()
 	}
 
 	if (!autoStartBroadcast) {
-		ui->broadcastButton->setVisible(true);
 		ui->broadcastButton->setText(
 			QTStr("Basic.Main.StartBroadcast"));
 		ui->broadcastButton->setProperty("broadcastState", "ready");
@@ -6318,11 +6318,12 @@ void OBSBasic::StartStreaming()
 		ui->broadcastButton->setEnabled(false);
 	} else if (!autoStopBroadcast) {
 		broadcastActive = true;
-		ui->broadcastButton->setVisible(true);
 		ui->broadcastButton->setText(QTStr("Basic.Main.StopBroadcast"));
 		ui->broadcastButton->setProperty("broadcastState", "active");
 		ui->broadcastButton->style()->unpolish(ui->broadcastButton);
 		ui->broadcastButton->style()->polish(ui->broadcastButton);
+	} else {
+		ui->broadcastButton->setEnabled(false);
 	}
 
 	bool recordWhenStreaming = config_get_bool(
@@ -6343,6 +6344,14 @@ void OBSBasic::StartStreaming()
 
 void OBSBasic::BroadcastButtonClicked()
 {
+	if (!broadcastReady ||
+	    !broadcastActive && !outputHandler->StreamingActive()) {
+		SetupBroadcast();
+		if (broadcastReady)
+			ui->broadcastButton->setChecked(true);
+		return;
+	}
+
 	if (!autoStartBroadcast) {
 #if YOUTUBE_ENABLED
 		std::shared_ptr<YoutubeApiWrappers> ytAuth =
@@ -6352,20 +6361,20 @@ void OBSBasic::BroadcastButtonClicked()
 		}
 #endif
 		broadcastActive = true;
-
 		autoStartBroadcast = true; // and clear the flag
+
 		if (!autoStopBroadcast) {
 			ui->broadcastButton->setText(
 				QTStr("Basic.Main.StopBroadcast"));
-			ui->broadcastButton->setProperty("broadcastState",
-							 "active");
-			ui->broadcastButton->style()->unpolish(
-				ui->broadcastButton);
-			ui->broadcastButton->style()->polish(
-				ui->broadcastButton);
 		} else {
-			ui->broadcastButton->setVisible(false);
+			ui->broadcastButton->setText(
+				QTStr("Basic.Main.AutoStopEnabled"));
+			ui->broadcastButton->setEnabled(false);
 		}
+
+		ui->broadcastButton->setProperty("broadcastState", "active");
+		ui->broadcastButton->style()->unpolish(ui->broadcastButton);
+		ui->broadcastButton->style()->polish(ui->broadcastButton);
 	} else if (!autoStopBroadcast) {
 #if YOUTUBE_ENABLED
 		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
@@ -6378,6 +6387,7 @@ void OBSBasic::BroadcastButtonClicked()
 				QMessageBox::No);
 
 			if (button == QMessageBox::No) {
+				ui->broadcastButton->setChecked(true);
 				return;
 			}
 		}
@@ -6389,12 +6399,42 @@ void OBSBasic::BroadcastButtonClicked()
 		}
 #endif
 		broadcastActive = false;
+		broadcastReady = false;
 
 		autoStopBroadcast = true;
-		ui->broadcastButton->setVisible(false);
-
 		QMetaObject::invokeMethod(this, "StopStreaming");
+		SetBroadcastFlowEnabled(true);
+	}
+}
+
+void OBSBasic::SetBroadcastFlowEnabled(bool enabled)
+{
+	ui->broadcastButton->setEnabled(enabled);
+	ui->broadcastButton->setVisible(enabled);
+	ui->broadcastButton->setChecked(broadcastReady);
+	ui->broadcastButton->setProperty("broadcastState", "idle");
+	ui->broadcastButton->style()->unpolish(ui->broadcastButton);
+	ui->broadcastButton->style()->polish(ui->broadcastButton);
+	ui->broadcastButton->setText(QTStr("Basic.Main.SetupBroadcast"));
+}
+
+void OBSBasic::SetupBroadcast()
+{
+	Auth *auth = GetAuth();
+#if YOUTUBE_ENABLED
+	if (IsYouTubeService(auth->service())) {
+		OBSYoutubeActions *dialog;
+		dialog = new OBSYoutubeActions(this, auth, broadcastReady);
+		connect(dialog, &OBSYoutubeActions::ok, this,
+			&OBSBasic::YouTubeActionDialogOk);
+		int result = dialog->Valid() ? dialog->exec()
+					     : QDialog::Rejected;
+		if (result != QDialog::Accepted) {
+			if (!broadcastReady)
+				ui->broadcastButton->setChecked(false);
+		}
 	}
+#endif
 }
 
 #ifdef _WIN32
@@ -6509,11 +6549,13 @@ void OBSBasic::StopStreaming()
 		broadcastActive = false;
 		autoStartBroadcast = true;
 		autoStopBroadcast = true;
-		ui->broadcastButton->setVisible(false);
+		broadcastReady = false;
 	}
 
-	if (autoStopBroadcast)
+	if (autoStopBroadcast) {
 		broadcastActive = false;
+		broadcastReady = false;
+	}
 
 	OnDeactivate();
 
@@ -6547,11 +6589,13 @@ void OBSBasic::ForceStopStreaming()
 		broadcastActive = false;
 		autoStartBroadcast = true;
 		autoStopBroadcast = true;
-		ui->broadcastButton->setVisible(false);
+		broadcastReady = false;
 	}
 
-	if (autoStopBroadcast)
+	if (autoStopBroadcast) {
 		broadcastActive = false;
+		broadcastReady = false;
+	}
 
 	OnDeactivate();
 
@@ -6756,6 +6800,10 @@ void OBSBasic::StreamingStop(int code, QString last_error)
 		startStreamMenu->deleteLater();
 		startStreamMenu = nullptr;
 	}
+
+	// Reset broadcast button state/text
+	if (!broadcastActive)
+		SetBroadcastFlowEnabled(auth && auth->broadcastFlow());
 }
 
 void OBSBasic::AutoRemux(QString input)
@@ -7271,6 +7319,10 @@ void OBSBasic::on_streamButton_clicked()
 				obs_service_get_settings(service);
 			bwtest = obs_data_get_bool(settings, "bwtest");
 			obs_data_release(settings);
+			// Disable confirmation if this is going to open broadcast setup
+			if (auth && auth->broadcastFlow() && !broadcastReady &&
+			    !broadcastActive)
+				confirm = false;
 		}
 
 		if (bwtest && isVisible()) {

+ 7 - 1
UI/window-basic-main.hpp

@@ -170,6 +170,7 @@ class OBSBasic : public OBSMainWindow {
 	friend class DeviceCaptureToolbar;
 	friend class DeviceToolbarPropertiesThread;
 	friend class OBSBasicSourceSelect;
+	friend class OBSYoutubeActions;
 	friend struct BasicOutputHandler;
 	friend struct OBSStudioAPI;
 
@@ -567,14 +568,17 @@ private:
 	bool autoStartBroadcast = true;
 	bool autoStopBroadcast = true;
 	bool broadcastActive = false;
+	bool broadcastReady = false;
 	QPointer<QThread> youtubeStreamCheckThread;
 #if YOUTUBE_ENABLED
 	void YoutubeStreamCheck(const std::string &key);
 	void ShowYouTubeAutoStartWarning();
 	void YouTubeActionDialogOk(const QString &id, const QString &key,
-				   bool autostart, bool autostop);
+				   bool autostart, bool autostop,
+				   bool start_now);
 #endif
 	void BroadcastButtonClicked();
+	void SetBroadcastFlowEnabled(bool enabled);
 
 	void UpdatePreviewSafeAreas();
 	bool drawSafeAreas = false;
@@ -585,6 +589,8 @@ public slots:
 
 	void DisplayStreamStartError();
 
+	void SetupBroadcast();
+
 	void StartStreaming();
 	void StopStreaming();
 	void ForceStopStreaming();

+ 6 - 1
UI/window-basic-settings-stream.cpp

@@ -243,8 +243,12 @@ void OBSBasicSettings::SaveStream1Settings()
 	main->SetService(newService);
 	main->SaveService();
 	main->auth = auth;
-	if (!!main->auth)
+	if (!!main->auth) {
 		main->auth->LoadUI();
+		main->SetBroadcastFlowEnabled(main->auth->broadcastFlow());
+	} else {
+		main->SetBroadcastFlowEnabled(false);
+	}
 
 	SaveCheckBox(ui->ignoreRecommended, "Stream1", "IgnoreRecommended");
 }
@@ -667,6 +671,7 @@ void OBSBasicSettings::on_disconnectAccount_clicked()
 
 	main->auth.reset();
 	auth.reset();
+	main->SetBroadcastFlowEnabled(false);
 
 	std::string service = QT_TO_UTF8(ui->service->currentText());
 

+ 225 - 91
UI/window-youtube-actions.cpp

@@ -13,11 +13,13 @@ const QString SchedulDateAndTimeFormat = "yyyy-MM-dd'T'hh:mm:ss'Z'";
 const QString RepresentSchedulDateAndTimeFormat = "dddd, MMMM d, yyyy h:m";
 const QString IndexOfGamingCategory = "20";
 
-OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth)
+OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth,
+				     bool broadcastReady)
 	: QDialog(parent),
 	  ui(new Ui::OBSYoutubeActions),
 	  apiYouTube(dynamic_cast<YoutubeApiWrappers *>(auth)),
-	  workerThread(new WorkerThread(apiYouTube))
+	  workerThread(new WorkerThread(apiYouTube)),
+	  broadcastReady(broadcastReady)
 {
 	setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
 	ui->setupUi(this);
@@ -94,19 +96,10 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth)
 		Cancel();
 		return;
 	}
-	ChannelDescription channel;
-	if (!apiYouTube->GetChannelDescription(channel)) {
-		blog(LOG_DEBUG, "Could not get channel description.");
-		ShowErrorDialog(
-			parent,
-			apiYouTube->GetLastError().isEmpty()
-				? QTStr("YouTube.Actions.Error.General")
-				: QTStr("YouTube.Actions.Error.Text")
-					  .arg(apiYouTube->GetLastError()));
-		Cancel();
-		return;
-	}
-	this->setWindowTitle(channel.title);
+
+	const char *name = config_get_string(OBSBasic::Get()->Config(),
+					     "YouTube", "ChannelName");
+	this->setWindowTitle(QTStr("YouTube.Actions.WindowTitle").arg(name));
 
 	QVector<CategoryDescription> category_list;
 	if (!apiYouTube->GetVideoCategoriesList(category_list)) {
@@ -128,6 +121,8 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth)
 
 	connect(ui->okButton, &QPushButton::clicked, this,
 		&OBSYoutubeActions::InitBroadcast);
+	connect(ui->saveButton, &QPushButton::clicked, this,
+		&OBSYoutubeActions::ReadyBroadcast);
 	connect(ui->cancelButton, &QPushButton::clicked, this, [&]() {
 		blog(LOG_DEBUG, "YouTube live broadcast creation cancelled.");
 		// Close the dialog.
@@ -225,9 +220,24 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth)
 				});
 			ui->scrollAreaWidgetContents->layout()->addWidget(
 				label);
+
+			if (selectedBroadcast == broadcast)
+				label->clicked();
 		});
 	workerThread->start();
 
+	OBSBasic *main = OBSBasic::Get();
+	bool rememberSettings = config_get_bool(main->basicConfig, "YouTube",
+						"RememberSettings");
+	if (rememberSettings)
+		LoadSettings();
+
+	// Switch to events page and select readied broadcast once loaded
+	if (broadcastReady) {
+		ui->tabWidget->setCurrentIndex(1);
+		selectedBroadcast = apiYouTube->GetBroadcastId();
+	}
+
 #ifdef __APPLE__
 	// MacOS theming issues
 	this->resize(this->width() + 200, this->height() + 120);
@@ -249,9 +259,9 @@ void WorkerThread::run()
 		return;
 	json11::Json broadcasts;
 
-	for (QString broacastStatus : {"active", "upcoming"}) {
+	for (QString broadcastStatus : {"active", "upcoming"}) {
 		if (!apiYouTube->GetBroadcastsList(broadcasts, "",
-						   broacastStatus)) {
+						   broadcastStatus)) {
 			emit failed();
 			return;
 		}
@@ -290,11 +300,9 @@ void WorkerThread::run()
 
 				// Treat already started streams as autostart for UI purposes
 				bool astart =
-					status == "live"
-						? true
-						: item["contentDetails"]
-						      ["enableAutoStart"]
-							      .bool_value();
+					status == "live" ||
+					item["contentDetails"]["enableAutoStart"]
+						.bool_value();
 				bool astop =
 					item["contentDetails"]["enableAutoStop"]
 						.bool_value();
@@ -329,7 +337,7 @@ void WorkerThread::run()
 					    broadcasts,
 					    QString::fromStdString(
 						    nextPageToken),
-					    broacastStatus)) {
+					    broadcastStatus)) {
 					emit failed();
 					return;
 				}
@@ -342,80 +350,64 @@ void WorkerThread::run()
 
 void OBSYoutubeActions::UpdateOkButtonStatus()
 {
+	bool enable = false;
+
 	if (ui->tabWidget->currentIndex() == 0) {
-		ui->okButton->setEnabled(
-			!ui->title->text().isEmpty() &&
-			!ui->privacyBox->currentText().isEmpty() &&
-			(ui->yesMakeForKids->isChecked() ||
-			 ui->notMakeForKids->isChecked()));
+		enable = !ui->title->text().isEmpty() &&
+			 !ui->privacyBox->currentText().isEmpty() &&
+			 (ui->yesMakeForKids->isChecked() ||
+			  ui->notMakeForKids->isChecked());
+		ui->okButton->setEnabled(enable);
+		ui->saveButton->setEnabled(enable);
+
 		if (ui->checkScheduledLater->checkState() == Qt::Checked) {
 			ui->okButton->setText(
-				QTStr("YouTube.Actions.Create_Save"));
+				QTStr("YouTube.Actions.Create_Schedule"));
+			ui->saveButton->setText(
+				QTStr("YouTube.Actions.Create_Schedule_Ready"));
 		} else {
 			ui->okButton->setText(
 				QTStr("YouTube.Actions.Create_GoLive"));
+			ui->saveButton->setText(
+				QTStr("YouTube.Actions.Create_Ready"));
 		}
-
 		ui->pushButton->setVisible(false);
 	} else {
-		ui->okButton->setEnabled(!selectedBroadcast.isEmpty());
+		enable = !selectedBroadcast.isEmpty();
+		ui->okButton->setEnabled(enable);
+		ui->saveButton->setEnabled(enable);
 		ui->okButton->setText(QTStr("YouTube.Actions.Choose_GoLive"));
+		ui->saveButton->setText(QTStr("YouTube.Actions.Choose_Ready"));
 
 		ui->pushButton->setVisible(true);
 	}
 }
-
-bool OBSYoutubeActions::StreamNowAction(YoutubeApiWrappers *api,
-					StreamDescription &stream)
+bool OBSYoutubeActions::CreateEventAction(YoutubeApiWrappers *api,
+					  StreamDescription &stream,
+					  bool stream_later,
+					  bool ready_broadcast)
 {
 	YoutubeApiWrappers *apiYouTube = api;
 	BroadcastDescription broadcast = {};
 	UiToBroadcast(broadcast);
-	// stream now is always autostart/autostop
-	broadcast.auto_start = true;
-	broadcast.auto_stop = true;
 
-	blog(LOG_DEBUG, "Scheduled date and time: %s",
-	     broadcast.schedul_date_time.toStdString().c_str());
-	if (!apiYouTube->InsertBroadcast(broadcast)) {
-		blog(LOG_DEBUG, "No broadcast created.");
-		return false;
-	}
-	stream = {"", "", "OBS Studio Video Stream"};
-	if (!apiYouTube->InsertStream(stream)) {
-		blog(LOG_DEBUG, "No stream created.");
-		return false;
-	}
-	if (!apiYouTube->BindStream(broadcast.id, stream.id)) {
-		blog(LOG_DEBUG, "No stream binded.");
-		return false;
-	}
-	if (!apiYouTube->SetVideoCategory(broadcast.id, broadcast.title,
-					  broadcast.description,
-					  broadcast.category.id)) {
-		blog(LOG_DEBUG, "No category set.");
-		return false;
+	if (stream_later) {
+		// DateTime parser means that input datetime is a local, so we need to move it
+		auto dateTime = ui->scheduledTime->dateTime();
+		auto utcDTime = dateTime.addSecs(-dateTime.offsetFromUtc());
+		broadcast.schedul_date_time =
+			utcDTime.toString(SchedulDateAndTimeFormat);
+	} else {
+		// stream now is always autostart/autostop
+		broadcast.auto_start = true;
+		broadcast.auto_stop = true;
+		broadcast.schedul_date_time =
+			QDateTime::currentDateTimeUtc().toString(
+				SchedulDateAndTimeFormat);
 	}
 
-	if (broadcast.privacy != "private")
-		apiYouTube->SetChatId(broadcast.id);
-	else
-		apiYouTube->ResetChat();
-
-	return true;
-}
-
-bool OBSYoutubeActions::StreamLaterAction(YoutubeApiWrappers *api)
-{
-	YoutubeApiWrappers *apiYouTube = api;
-	BroadcastDescription broadcast = {};
-	UiToBroadcast(broadcast);
-
-	// DateTime parser means that input datetime is a local, so we need to move it
-	auto dateTime = ui->scheduledTime->dateTime();
-	auto utcDTime = dateTime.addSecs(-dateTime.offsetFromUtc());
-	broadcast.schedul_date_time =
-		utcDTime.toString(SchedulDateAndTimeFormat);
+	autostart = broadcast.auto_start;
+	autostop = broadcast.auto_stop;
 
 	blog(LOG_DEBUG, "Scheduled date and time: %s",
 	     broadcast.schedul_date_time.toStdString().c_str());
@@ -430,10 +422,22 @@ bool OBSYoutubeActions::StreamLaterAction(YoutubeApiWrappers *api)
 		return false;
 	}
 
-	if (broadcast.privacy != "private")
-		apiYouTube->SetChatId(broadcast.id);
-	else
-		apiYouTube->ResetChat();
+	if (!stream_later || ready_broadcast) {
+		stream = {"", "", "OBS Studio Video Stream"};
+		if (!apiYouTube->InsertStream(stream)) {
+			blog(LOG_DEBUG, "No stream created.");
+			return false;
+		}
+		if (!apiYouTube->BindStream(broadcast.id, stream.id)) {
+			blog(LOG_DEBUG, "No stream binded.");
+			return false;
+		}
+
+		if (broadcast.privacy != "private")
+			apiYouTube->SetChatId(broadcast.id);
+		else
+			apiYouTube->ResetChat();
+	}
 
 	return true;
 }
@@ -513,12 +517,9 @@ void OBSYoutubeActions::InitBroadcast()
 	bool success = false;
 	auto action = [&]() {
 		if (ui->tabWidget->currentIndex() == 0) {
-			if (ui->checkScheduledLater->isChecked()) {
-				success = this->StreamLaterAction(apiYouTube);
-			} else {
-				success = this->StreamNowAction(apiYouTube,
-								stream);
-			}
+			success = this->CreateEventAction(
+				apiYouTube, stream,
+				ui->checkScheduledLater->isChecked());
 		} else {
 			success = this->ChooseAnEventAction(apiYouTube, stream);
 		};
@@ -548,13 +549,14 @@ void OBSYoutubeActions::InitBroadcast()
 				blog(LOG_DEBUG, "New valid stream: %s",
 				     QT_TO_UTF8(stream.name));
 				emit ok(QT_TO_UTF8(stream.id),
-					QT_TO_UTF8(stream.name), true, true);
+					QT_TO_UTF8(stream.name), true, true,
+					true);
 				Accept();
 			}
 		} else {
 			// Stream to precreated broadcast usecase.
 			emit ok(QT_TO_UTF8(stream.id), QT_TO_UTF8(stream.name),
-				autostart, autostop);
+				autostart, autostop, true);
 			Accept();
 		}
 	} else {
@@ -571,6 +573,51 @@ void OBSYoutubeActions::InitBroadcast()
 	}
 }
 
+void OBSYoutubeActions::ReadyBroadcast()
+{
+	StreamDescription stream;
+	QMessageBox msgBox(this);
+	msgBox.setWindowFlags(msgBox.windowFlags() &
+			      ~Qt::WindowCloseButtonHint);
+	msgBox.setWindowTitle(QTStr("YouTube.Actions.Notify.Title"));
+	msgBox.setText(QTStr("YouTube.Actions.Notify.CreatingBroadcast"));
+	msgBox.setStandardButtons(QMessageBox::StandardButtons());
+
+	bool success = false;
+	auto action = [&]() {
+		if (ui->tabWidget->currentIndex() == 0) {
+			success = this->CreateEventAction(
+				apiYouTube, stream,
+				ui->checkScheduledLater->isChecked(), true);
+		} else {
+			success = this->ChooseAnEventAction(apiYouTube, stream);
+		};
+		QMetaObject::invokeMethod(&msgBox, "accept",
+					  Qt::QueuedConnection);
+	};
+	QScopedPointer<QThread> thread(CreateQThread(action));
+	thread->start();
+	msgBox.exec();
+	thread->wait();
+
+	if (success) {
+		emit ok(QT_TO_UTF8(stream.id), QT_TO_UTF8(stream.name),
+			autostart, autostop, false);
+		Accept();
+	} else {
+		// Fail.
+		auto last_error = apiYouTube->GetLastError();
+		if (last_error.isEmpty())
+			last_error = QTStr("YouTube.Actions.Error.YouTubeApi");
+		if (!apiYouTube->GetTranslatedError(last_error))
+			last_error =
+				QTStr("YouTube.Actions.Error.NoBroadcastCreated")
+					.arg(last_error);
+
+		ShowErrorDialog(this, last_error);
+	}
+}
+
 void OBSYoutubeActions::UiToBroadcast(BroadcastDescription &broadcast)
 {
 	broadcast.title = ui->title->text();
@@ -587,9 +634,96 @@ void OBSYoutubeActions::UiToBroadcast(BroadcastDescription &broadcast)
 	broadcast.schedul_for_later = ui->checkScheduledLater->isChecked();
 	broadcast.projection = ui->check360Video->isChecked() ? "360"
 							      : "rectangular";
-	// Current time by default.
-	broadcast.schedul_date_time = QDateTime::currentDateTimeUtc().toString(
-		SchedulDateAndTimeFormat);
+
+	if (ui->checkRememberSettings->isChecked())
+		SaveSettings(broadcast);
+}
+
+void OBSYoutubeActions::SaveSettings(BroadcastDescription &broadcast)
+{
+	OBSBasic *main = OBSBasic::Get();
+
+	config_set_string(main->basicConfig, "YouTube", "Title",
+			  QT_TO_UTF8(broadcast.title));
+	config_set_string(main->basicConfig, "YouTube", "Description",
+			  QT_TO_UTF8(broadcast.description));
+	config_set_string(main->basicConfig, "YouTube", "Privacy",
+			  QT_TO_UTF8(broadcast.privacy));
+	config_set_string(main->basicConfig, "YouTube", "CategoryID",
+			  QT_TO_UTF8(broadcast.category.id));
+	config_set_string(main->basicConfig, "YouTube", "Latency",
+			  QT_TO_UTF8(broadcast.latency));
+	config_set_bool(main->basicConfig, "YouTube", "MadeForKids",
+			broadcast.made_for_kids);
+	config_set_bool(main->basicConfig, "YouTube", "AutoStart",
+			broadcast.auto_start);
+	config_set_bool(main->basicConfig, "YouTube", "AutoStop",
+			broadcast.auto_start);
+	config_set_bool(main->basicConfig, "YouTube", "DVR", broadcast.dvr);
+	config_set_bool(main->basicConfig, "YouTube", "ScheduleForLater",
+			broadcast.schedul_for_later);
+	config_set_string(main->basicConfig, "YouTube", "Projection",
+			  QT_TO_UTF8(broadcast.projection));
+	config_set_bool(main->basicConfig, "YouTube", "RememberSettings", true);
+}
+
+void OBSYoutubeActions::LoadSettings()
+{
+	OBSBasic *main = OBSBasic::Get();
+
+	const char *title =
+		config_get_string(main->basicConfig, "YouTube", "Title");
+	ui->title->setText(QT_UTF8(title));
+
+	const char *desc =
+		config_get_string(main->basicConfig, "YouTube", "Description");
+	ui->description->setPlainText(QT_UTF8(desc));
+
+	const char *priv =
+		config_get_string(main->basicConfig, "YouTube", "Privacy");
+	int index = ui->privacyBox->findData(priv);
+	ui->privacyBox->setCurrentIndex(index);
+
+	const char *catID =
+		config_get_string(main->basicConfig, "YouTube", "CategoryID");
+	index = ui->categoryBox->findData(catID);
+	ui->categoryBox->setCurrentIndex(index);
+
+	const char *latency =
+		config_get_string(main->basicConfig, "YouTube", "Latency");
+	index = ui->latencyBox->findData(latency);
+	ui->latencyBox->setCurrentIndex(index);
+
+	bool dvr = config_get_bool(main->basicConfig, "YouTube", "DVR");
+	ui->checkDVR->setChecked(dvr);
+
+	bool forKids =
+		config_get_bool(main->basicConfig, "YouTube", "MadeForKids");
+	if (forKids)
+		ui->yesMakeForKids->setChecked(true);
+	else
+		ui->notMakeForKids->setChecked(true);
+
+	bool autoStart =
+		config_get_bool(main->basicConfig, "YouTube", "AutoStart");
+	ui->checkAutoStart->setChecked(autoStart);
+
+	bool autoStop =
+		config_get_bool(main->basicConfig, "YouTube", "AutoStop");
+	ui->checkAutoStop->setChecked(autoStop);
+
+	bool schedLater = config_get_bool(main->basicConfig, "YouTube",
+					  "ScheduleForLater");
+	ui->checkScheduledLater->setChecked(schedLater);
+
+	const char *projection =
+		config_get_string(main->basicConfig, "YouTube", "Projection");
+	if (projection && *projection) {
+		if (strcmp(projection, "360") == 0)
+			ui->check360Video->setChecked(true);
+		else
+			ui->check360Video->setChecked(false);
+	}
 }
 
 void OBSYoutubeActions::OpenYouTubeDashboard()

+ 10 - 5
UI/window-youtube-actions.hpp

@@ -35,35 +35,40 @@ class OBSYoutubeActions : public QDialog {
 
 signals:
 	void ok(const QString &id, const QString &key, bool autostart,
-		bool autostop);
+		bool autostop, bool start_now);
 
 protected:
 	void UpdateOkButtonStatus();
 
-	bool StreamNowAction(YoutubeApiWrappers *api,
-			     StreamDescription &stream);
-	bool StreamLaterAction(YoutubeApiWrappers *api);
+	bool CreateEventAction(YoutubeApiWrappers *api,
+			       StreamDescription &stream, bool stream_later,
+			       bool ready_broadcast = false);
 	bool ChooseAnEventAction(YoutubeApiWrappers *api,
 				 StreamDescription &stream);
 
 	void ShowErrorDialog(QWidget *parent, QString text);
 
 public:
-	explicit OBSYoutubeActions(QWidget *parent, Auth *auth);
+	explicit OBSYoutubeActions(QWidget *parent, Auth *auth,
+				   bool broadcastReady);
 	virtual ~OBSYoutubeActions() override;
 
 	bool Valid() { return valid; };
 
 private:
 	void InitBroadcast();
+	void ReadyBroadcast();
 	void UiToBroadcast(BroadcastDescription &broadcast);
 	void OpenYouTubeDashboard();
 	void Cancel();
 	void Accept();
+	void SaveSettings(BroadcastDescription &broadcast);
+	void LoadSettings();
 
 	QString selectedBroadcast;
 	bool autostart, autostop;
 	bool valid = false;
+	bool broadcastReady = false;
 	YoutubeApiWrappers *apiYouTube;
 	WorkerThread *workerThread;
 };

+ 5 - 0
UI/youtube-api-wrappers.cpp

@@ -409,6 +409,11 @@ void YoutubeApiWrappers::SetBroadcastId(QString &broadcast_id)
 	this->broadcast_id = broadcast_id;
 }
 
+QString YoutubeApiWrappers::GetBroadcastId()
+{
+	return this->broadcast_id;
+}
+
 bool YoutubeApiWrappers::ResetBroadcast(const QString &broadcast_id)
 {
 	lastErrorMessage.clear();

+ 1 - 0
UI/youtube-api-wrappers.hpp

@@ -72,6 +72,7 @@ public:
 	bool StopLatestBroadcast();
 
 	void SetBroadcastId(QString &broadcast_id);
+	QString GetBroadcastId();
 
 	bool FindBroadcast(const QString &id, json11::Json &json_out);
 	bool FindStream(const QString &id, json11::Json &json_out);