From bd45d46c3c584c1b18cfab0e5033b1df10a3dd3f Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 13 Oct 2021 09:57:02 +0200 Subject: [PATCH 1/3] UI: Fix YT broadcast start/stop failing due to redundant transition/reset - If the stream is already live or going live, do not reset or transition - If the stream is testing, transition but do not reset - If the stream is starting a test, error out since this can take a while - If the attempted transition was redunant, still return a success Fixes #5403 --- UI/youtube-api-wrappers.cpp | 58 ++++++++++++++++++++++++------------- UI/youtube-api-wrappers.hpp | 3 +- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/UI/youtube-api-wrappers.cpp b/UI/youtube-api-wrappers.cpp index 80d53a377..e686fc2e3 100644 --- a/UI/youtube-api-wrappers.cpp +++ b/UI/youtube-api-wrappers.cpp @@ -405,7 +405,31 @@ bool YoutubeApiWrappers::StartBroadcast(const QString &broadcast_id) lastErrorMessage.clear(); lastErrorReason.clear(); - if (!ResetBroadcast(broadcast_id)) + Json json_out; + if (!FindBroadcast(broadcast_id, json_out)) + return false; + + auto lifeCycleStatus = + json_out["items"][0]["status"]["lifeCycleStatus"].string_value(); + + if (lifeCycleStatus == "live" || lifeCycleStatus == "liveStarting") + // Broadcast is already (going to be) live + return true; + else if (lifeCycleStatus == "testStarting") { + // User will need to wait a few seconds before attempting to start broadcast + lastErrorMessage = + QTStr("YouTube.Actions.Error.BroadcastTestStarting"); + lastErrorReason.clear(); + return false; + } + + // Only reset if broadcast has monitoring enabled and is not already in "testing" mode + auto monitorStreamEnabled = + json_out["items"][0]["contentDetails"]["monitorStream"] + ["enableMonitorStream"] + .bool_value(); + if (lifeCycleStatus != "testing" && monitorStreamEnabled && + !ResetBroadcast(broadcast_id, json_out)) return false; const QString url_template = YOUTUBE_LIVE_BROADCAST_TRANSITION_URL @@ -413,9 +437,10 @@ bool YoutubeApiWrappers::StartBroadcast(const QString &broadcast_id) "&broadcastStatus=%2" "&part=status"; const QString live = url_template.arg(broadcast_id, "live"); - Json json_out; - return InsertCommand(QT_TO_UTF8(live), "application/json", "POST", "{}", - json_out); + bool success = InsertCommand(QT_TO_UTF8(live), "application/json", + "POST", "{}", json_out); + // Return a success if the command failed, but was redundant (broadcast already live) + return success || lastErrorReason == "redundantTransition"; } bool YoutubeApiWrappers::StartLatestBroadcast() @@ -434,8 +459,10 @@ bool YoutubeApiWrappers::StopBroadcast(const QString &broadcast_id) "&part=status"; const QString url = url_template.arg(broadcast_id); Json json_out; - return InsertCommand(QT_TO_UTF8(url), "application/json", "POST", "{}", - json_out); + bool success = InsertCommand(QT_TO_UTF8(url), "application/json", + "POST", "{}", json_out); + // Return a success if the command failed, but was redundant (broadcast already stopped) + return success || lastErrorReason == "redundantTransition"; } bool YoutubeApiWrappers::StopLatestBroadcast() @@ -453,24 +480,12 @@ QString YoutubeApiWrappers::GetBroadcastId() return this->broadcast_id; } -bool YoutubeApiWrappers::ResetBroadcast(const QString &broadcast_id) +bool YoutubeApiWrappers::ResetBroadcast(const QString &broadcast_id, + json11::Json &json_out) { lastErrorMessage.clear(); lastErrorReason.clear(); - const QString url_template = YOUTUBE_LIVE_BROADCAST_URL - "?part=id,snippet,contentDetails,status" - "&id=%1"; - const QString url = url_template.arg(broadcast_id); - Json json_out; - - if (!InsertCommand(QT_TO_UTF8(url), "application/json", "", nullptr, - json_out)) - return false; - - const QString put = YOUTUBE_LIVE_BROADCAST_URL - "?part=id,snippet,contentDetails,status"; - auto snippet = json_out["items"][0]["snippet"]; auto status = json_out["items"][0]["status"]; auto contentDetails = json_out["items"][0]["contentDetails"]; @@ -514,6 +529,9 @@ bool YoutubeApiWrappers::ResetBroadcast(const QString &broadcast_id) {"startWithSlate", contentDetails["startWithSlate"]}, }}, }; + + const QString put = YOUTUBE_LIVE_BROADCAST_URL + "?part=id,snippet,contentDetails,status"; return InsertCommand(QT_TO_UTF8(put), "application/json", "PUT", data.dump().c_str(), json_out); } diff --git a/UI/youtube-api-wrappers.hpp b/UI/youtube-api-wrappers.hpp index 1a6bc4f26..6fc939ee2 100644 --- a/UI/youtube-api-wrappers.hpp +++ b/UI/youtube-api-wrappers.hpp @@ -70,7 +70,8 @@ public: const QString &thumbnail_file); bool StartBroadcast(const QString &broadcast_id); bool StopBroadcast(const QString &broadcast_id); - bool ResetBroadcast(const QString &broadcast_id); + bool ResetBroadcast(const QString &broadcast_id, + json11::Json &json_out); bool StartLatestBroadcast(); bool StopLatestBroadcast(); From 2170a6b8ac4b0c29c7f475e35244f3563447cedb Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 13 Oct 2021 10:53:59 +0200 Subject: [PATCH 2/3] UI: Show warning if starting/stopping broadcast fails --- UI/data/locale/en-US.ini | 7 +++++++ UI/window-basic-main.cpp | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 5586a7301..da98079a0 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -394,6 +394,8 @@ Output.BadPath.Text="The configured file output path is invalid. Please check yo # broadcast setup messages Output.NoBroadcast.Title="No Broadcast Configured" Output.NoBroadcast.Text="You need to set up a broadcast before you can start streaming." +Output.BroadcastStartFailed="Failed to start broadcast" +Output.BroadcastStopFailed="Failed to stop broadcast" # log upload dialog text and messages LogReturnDialog="Log Upload Successful" @@ -1248,6 +1250,8 @@ YouTube.Actions.Error.BroadcastNotFound="The selected broadcast was not found." YouTube.Actions.Error.FileMissing="Selected file does not exist." YouTube.Actions.Error.FileOpeningFailed="Failed opening selected file." YouTube.Actions.Error.FileTooLarge="Selected file is too large (Limit: 2 MiB)." +YouTube.Actions.Error.BroadcastTransitionFailed="Transitioning the broadcast failed: %1

If this error persists open the broadcast in YouTube Studio and try manually." +YouTube.Actions.Error.BroadcastTestStarting="Broadcast is transitioning to the test stage, this can take some time. Please try again in 10-30 seconds." YouTube.Actions.EventsLoading="Loading list of events..." YouTube.Actions.EventCreated.Title="Event Created" @@ -1268,3 +1272,6 @@ YouTube.Actions.AutoStopStreamingWarning="You will not be able to reconnect.
# YouTube API errors in format "YouTube.Errors." YouTube.Errors.liveStreamingNotEnabled="Live streaming is not enabled on the selected YouTube channel.

See youtube.com/features for more information." YouTube.Errors.livePermissionBlocked="Live streaming is unavailable on the selected YouTube Channel.
Please note that it may take up to 24 hours for live streaming to become available after enabling it in your channel settings.

See youtube.com/features for details." +YouTube.Errors.errorExecutingTransition="Transition failed due to a backend error. Please try again in a few seconds." +YouTube.Errors.errorStreamInactive="YouTube is not receiving data for your stream. Please check your configuration and try again." +YouTube.Errors.invalidTransition="The attempted transition was invalid. This may be due to the stream not having finished a previous transition. Please wait a few seconds and try again." diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 557286115..e17a78bb9 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -6364,7 +6364,24 @@ void OBSBasic::BroadcastButtonClicked() std::shared_ptr ytAuth = dynamic_pointer_cast(auth); if (ytAuth.get()) { - ytAuth->StartLatestBroadcast(); + if (!ytAuth->StartLatestBroadcast()) { + auto last_error = ytAuth->GetLastError(); + if (last_error.isEmpty()) + last_error = QTStr( + "YouTube.Actions.Error.YouTubeApi"); + if (!ytAuth->GetTranslatedError(last_error)) + last_error = + QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") + .arg(last_error, + ytAuth->GetBroadcastId()); + + OBSMessageBox::warning( + this, + QTStr("Output.BroadcastStartFailed"), + last_error, true); + ui->broadcastButton->setChecked(false); + return; + } } #endif broadcastActive = true; @@ -6402,7 +6419,22 @@ void OBSBasic::BroadcastButtonClicked() std::shared_ptr ytAuth = dynamic_pointer_cast(auth); if (ytAuth.get()) { - ytAuth->StopLatestBroadcast(); + if (!ytAuth->StopLatestBroadcast()) { + auto last_error = ytAuth->GetLastError(); + if (last_error.isEmpty()) + last_error = QTStr( + "YouTube.Actions.Error.YouTubeApi"); + if (!ytAuth->GetTranslatedError(last_error)) + last_error = + QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") + .arg(last_error, + ytAuth->GetBroadcastId()); + + OBSMessageBox::warning( + this, + QTStr("Output.BroadcastStopFailed"), + last_error, true); + } } #endif broadcastActive = false; From 9d8a011b78039b58f8bffe2a0d4b34b78dd64e74 Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 13 Oct 2021 12:41:49 +0200 Subject: [PATCH 3/3] UI: Fix broadcast button state for autostart without autostop --- UI/window-basic-main.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index e17a78bb9..b2984de9f 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -6323,14 +6323,19 @@ void OBSBasic::StartStreaming() ui->broadcastButton->style()->polish(ui->broadcastButton); // well, we need to disable button while stream is not active ui->broadcastButton->setEnabled(false); - } else if (!autoStopBroadcast) { - broadcastActive = true; - ui->broadcastButton->setText(QTStr("Basic.Main.StopBroadcast")); + } else { + if (!autoStopBroadcast) { + ui->broadcastButton->setText( + QTStr("Basic.Main.StopBroadcast")); + } else { + 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 { - ui->broadcastButton->setEnabled(false); + broadcastActive = true; } bool recordWhenStreaming = config_get_bool(