Files
obs-studio/frontend/widgets/OBSBasic_Streaming.cpp

455 lines
13 KiB
C++

/******************************************************************************
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
Zachary Lund <admin@computerquip.com>
Philippe Groarke <philippe.groarke@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSBasic.hpp"
#include <components/UIValidation.hpp>
#ifdef YOUTUBE_ENABLED
#include <docks/YouTubeAppDock.hpp>
#include <utility/YoutubeApiWrappers.hpp>
#endif
#include <qt-wrappers.hpp>
#define STREAMING_START "==== Streaming Start ==============================================="
#define STREAMING_STOP "==== Streaming Stop ================================================"
void OBSBasic::DisplayStreamStartError()
{
QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str())
: QTStr("Output.StartFailedGeneric");
emit StreamingStopped();
if (sysTrayStream) {
sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
sysTrayStream->setEnabled(true);
}
QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message);
}
void OBSBasic::StartStreaming()
{
if (outputHandler->StreamingActive())
return;
if (disableOutputsRef)
return;
if (auth && auth->broadcastFlow()) {
if (!broadcastActive && !broadcastReady) {
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;
}
}
emit StreamingPreparing();
if (sysTrayStream) {
sysTrayStream->setEnabled(false);
sysTrayStream->setText("Basic.Main.PreparingStream");
}
auto finish_stream_setup = [&](bool setupStreamingResult) {
if (!setupStreamingResult) {
DisplayStreamStartError();
return;
}
OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING);
SaveProject();
emit StreamingStarting(autoStartBroadcast);
if (sysTrayStream)
sysTrayStream->setText("Basic.Main.Connecting");
if (!outputHandler->StartStreaming(service)) {
DisplayStreamStartError();
return;
}
if (autoStartBroadcast) {
emit BroadcastStreamStarted(autoStopBroadcast);
broadcastActive = true;
}
bool recordWhenStreaming =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
if (recordWhenStreaming)
StartRecording();
bool replayBufferWhileStreaming =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
if (replayBufferWhileStreaming)
StartReplayBuffer();
#ifdef YOUTUBE_ENABLED
if (!autoStartBroadcast)
OBSBasic::ShowYouTubeAutoStartWarning();
#endif
};
setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup);
}
void OBSBasic::StopStreaming()
{
SaveProject();
if (outputHandler->StreamingActive())
outputHandler->StopStreaming(streamingStopping);
// special case: force reset broadcast state if
// no autostart and no autostop selected
if (!autoStartBroadcast && !broadcastActive) {
broadcastActive = false;
autoStartBroadcast = true;
autoStopBroadcast = true;
broadcastReady = false;
}
if (autoStopBroadcast) {
broadcastActive = false;
broadcastReady = false;
}
emit BroadcastStreamReady(broadcastReady);
OnDeactivate();
bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
bool keepRecordingWhenStreamStops =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops");
if (recordWhenStreaming && !keepRecordingWhenStreamStops)
StopRecording();
bool replayBufferWhileStreaming =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
bool keepReplayBufferStreamStops =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops");
if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
StopReplayBuffer();
}
void OBSBasic::ForceStopStreaming()
{
SaveProject();
if (outputHandler->StreamingActive())
outputHandler->StopStreaming(true);
// special case: force reset broadcast state if
// no autostart and no autostop selected
if (!autoStartBroadcast && !broadcastActive) {
broadcastActive = false;
autoStartBroadcast = true;
autoStopBroadcast = true;
broadcastReady = false;
}
if (autoStopBroadcast) {
broadcastActive = false;
broadcastReady = false;
}
emit BroadcastStreamReady(broadcastReady);
OnDeactivate();
bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming");
bool keepRecordingWhenStreamStops =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops");
if (recordWhenStreaming && !keepRecordingWhenStreamStops)
StopRecording();
bool replayBufferWhileStreaming =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
bool keepReplayBufferStreamStops =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops");
if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
StopReplayBuffer();
}
void OBSBasic::StreamDelayStarting(int sec)
{
emit StreamingStarted(true);
if (sysTrayStream) {
sysTrayStream->setText(QTStr("Basic.Main.StopStreaming"));
sysTrayStream->setEnabled(true);
}
ui->statusbar->StreamDelayStarting(sec);
OnActivate();
}
void OBSBasic::StreamDelayStopping(int sec)
{
emit StreamingStopped(true);
if (sysTrayStream) {
sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
sysTrayStream->setEnabled(true);
}
ui->statusbar->StreamDelayStopping(sec);
OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
}
void OBSBasic::StreamingStart()
{
emit StreamingStarted();
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
ui->statusbar->StreamStarted(output);
if (sysTrayStream) {
sysTrayStream->setText(QTStr("Basic.Main.StopStreaming"));
sysTrayStream->setEnabled(true);
}
#ifdef YOUTUBE_ENABLED
if (!autoStartBroadcast) {
// get a current stream key
obs_service_t *service_obj = GetService();
OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
std::string key = obs_data_get_string(settings, "stream_id");
if (!key.empty() && !youtubeStreamCheckThread) {
youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); });
youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread");
youtubeStreamCheckThread->start();
}
}
#endif
OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED);
OnActivate();
#ifdef YOUTUBE_ENABLED
if (YouTubeAppDock::IsYTServiceSelected())
youtubeAppDock->IngestionStarted();
#endif
blog(LOG_INFO, STREAMING_START);
}
void OBSBasic::StreamStopping()
{
emit StreamingStopping();
if (sysTrayStream)
sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming"));
streamingStopping = true;
OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
}
void OBSBasic::StreamingStop(int code, QString last_error)
{
const char *errorDescription = "";
DStr errorMessage;
bool use_last_error = false;
bool encode_error = false;
bool should_reconnect = false;
/* Ignore stream key error for multitrack output if its internal reconnect handling is active. */
if (code == OBS_OUTPUT_INVALID_STREAM && outputHandler->multitrackVideo &&
outputHandler->multitrackVideo->RestartOnError()) {
code = OBS_OUTPUT_SUCCESS;
should_reconnect = true;
}
switch (code) {
case OBS_OUTPUT_BAD_PATH:
errorDescription = Str("Output.ConnectFail.BadPath");
break;
case OBS_OUTPUT_CONNECT_FAILED:
use_last_error = true;
errorDescription = Str("Output.ConnectFail.ConnectFailed");
break;
case OBS_OUTPUT_INVALID_STREAM:
errorDescription = Str("Output.ConnectFail.InvalidStream");
break;
case OBS_OUTPUT_ENCODE_ERROR:
encode_error = true;
break;
case OBS_OUTPUT_HDR_DISABLED:
errorDescription = Str("Output.ConnectFail.HdrDisabled");
break;
default:
case OBS_OUTPUT_ERROR:
use_last_error = true;
errorDescription = Str("Output.ConnectFail.Error");
break;
case OBS_OUTPUT_DISCONNECTED:
/* doesn't happen if output is set to reconnect. note that
* reconnects are handled in the output, not in the UI */
use_last_error = true;
errorDescription = Str("Output.ConnectFail.Disconnected");
}
if (use_last_error && !last_error.isEmpty())
dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error));
else
dstr_copy(errorMessage, errorDescription);
ui->statusbar->StreamStopped();
emit StreamingStopped();
if (sysTrayStream) {
sysTrayStream->setText(QTStr("Basic.Main.StartStreaming"));
sysTrayStream->setEnabled(true);
}
streamingStopping = false;
OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED);
OnDeactivate();
#ifdef YOUTUBE_ENABLED
if (YouTubeAppDock::IsYTServiceSelected())
youtubeAppDock->IngestionStopped();
#endif
blog(LOG_INFO, STREAMING_STOP);
if (encode_error) {
QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg")
: QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error);
OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg);
} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage));
} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning);
}
// Reset broadcast button state/text
if (!broadcastActive)
SetBroadcastFlowEnabled(auth && auth->broadcastFlow());
if (should_reconnect)
QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection);
}
void OBSBasic::StreamActionTriggered()
{
if (outputHandler->StreamingActive()) {
bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream");
#ifdef YOUTUBE_ENABLED
if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) {
QMessageBox::StandardButton button = OBSMessageBox::question(
this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (button == QMessageBox::No)
return;
confirm = false;
}
#endif
if (confirm && isVisible()) {
QMessageBox::StandardButton button =
OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (button == QMessageBox::No)
return;
}
StopStreaming();
} else {
if (!UIValidation::NoSourcesConfirmation(this))
return;
Auth *auth = GetAuth();
auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream
: UIValidation::StreamSettingsConfirmation(this, service);
switch (action) {
case StreamSettingsAction::ContinueStream:
break;
case StreamSettingsAction::OpenSettings:
on_action_Settings_triggered();
return;
case StreamSettingsAction::Cancel:
return;
}
bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream");
bool bwtest = false;
if (this->auth) {
OBSDataAutoRelease settings = obs_service_get_settings(service);
bwtest = obs_data_get_bool(settings, "bwtest");
// Disable confirmation if this is going to open broadcast setup
if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive)
confirm = false;
}
if (bwtest && isVisible()) {
QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"),
QTStr("ConfirmBWTest.Text"));
if (button == QMessageBox::No)
return;
} else if (confirm && isVisible()) {
QMessageBox::StandardButton button =
OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (button == QMessageBox::No)
return;
}
StartStreaming();
}
}
bool OBSBasic::StreamingActive()
{
if (!outputHandler)
return false;
return outputHandler->StreamingActive();
}