From 7900dadd81999d11c50698d900da6fd7b5c97a03 Mon Sep 17 00:00:00 2001 From: JohannMG Date: Mon, 14 Sep 2020 17:19:13 -0700 Subject: [PATCH 01/13] UI: Add Settings Endpoints for Pre-Live Wizard Adds button entry point and adds text for button. --- UI/data/locale/en-US.ini | 3 ++ UI/forms/OBSBasicSettings.ui | 66 ++++++++++++++++++++--------- UI/window-basic-settings-stream.cpp | 3 ++ 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 1095f5f5335b8a..1df96793eb2302 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -707,6 +707,9 @@ Basic.Settings.Stream.MissingUrlAndApiKey="URL and Stream Key are missing.\n\nOp Basic.Settings.Stream.MissingUrl="Stream URL is missing.\n\nOpen settings to enter the URL in the 'Stream' tab." Basic.Settings.Stream.MissingStreamKey="Stream key is missing.\n\nOpen settings to enter the stream key in the 'Stream' tab." +## Pre-Live Wizard and Settings +Basic.Settings.Stream.PreLiveWizard.RunNow="Check stream settings" + # basic mode 'output' settings Basic.Settings.Output="Output" Basic.Settings.Output.Format="Recording Format" diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index f2b91fc4ceec5e..5c7d55cbefc12e 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -151,8 +151,8 @@ 0 0 - 806 - 1254 + 810 + 1087 @@ -1177,6 +1177,19 @@ + + + + Basic.Settings.Stream.TTVAddon + + + twitchAddonDropdown + + + + + + @@ -1232,18 +1245,29 @@ - - - - - - - Basic.Settings.Stream.TTVAddon - - - twitchAddonDropdown - - + + + + + + Basic.Settings.Stream.PreLiveWizard.RunNow + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -1281,8 +1305,8 @@ 0 0 - 813 - 761 + 740 + 767 @@ -2413,7 +2437,7 @@ 9 0 - 236 + 250 25 @@ -3776,8 +3800,8 @@ 0 0 - 767 - 582 + 691 + 601 @@ -4632,8 +4656,8 @@ 0 0 - 791 - 970 + 810 + 930 diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index b1586d6e97a511..0b8736f0a9447a 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -249,6 +249,7 @@ void OBSBasicSettings::UpdateKeyLink() QString serviceName = ui->service->currentText(); QString customServer = ui->customServer->text(); QString streamKeyLink; + bool hasStreamWizard = false; if (serviceName == "Twitch") { streamKeyLink = "https://dashboard.twitch.tv/settings/stream"; } else if (serviceName.startsWith("YouTube")) { @@ -260,6 +261,7 @@ void OBSBasicSettings::UpdateKeyLink() (customServer.contains("fbcdn.net") && IsCustomService())) { streamKeyLink = "https://www.facebook.com/live/producer?ref=OBS"; + hasStreamWizard = true; } else if (serviceName.startsWith("Twitter")) { streamKeyLink = "https://www.pscp.tv/account/producer"; } else if (serviceName.startsWith("YouStreamer")) { @@ -274,6 +276,7 @@ void OBSBasicSettings::UpdateKeyLink() ui->getStreamKeyButton->setTargetUrl(QUrl(streamKeyLink)); ui->getStreamKeyButton->show(); } + ui->settingWizardBtn->setHidden(!hasStreamWizard); } void OBSBasicSettings::LoadServices(bool showAll) From 7721c4f658b8989e5b940dfce720d5c7bdd2022b Mon Sep 17 00:00:00 2001 From: JohannMG Date: Mon, 14 Sep 2020 17:26:41 -0700 Subject: [PATCH 02/13] UI: Create model to start to pre-stream wizard Adding a model of the current known settings to send to APIs to launch the wizard. No data will be sent at the start without user accepting to proceed. Service name is also included at init so any config service can be used: This modularity will enable other services to use the same wizard flow. At this commit, launches a screen verifying correct settings were captured. First screen is added in the next commit. --- UI/CMakeLists.txt | 9 + UI/common-settings.cpp | 183 ++++++++++++++++++ UI/common-settings.hpp | 47 +++++ UI/pre-stream-wizard/CMakeLists.txt | 0 .../pre-stream-current-settings.hpp | 90 +++++++++ UI/pre-stream-wizard/pre-stream-wizard.cpp | 71 +++++++ UI/pre-stream-wizard/pre-stream-wizard.hpp | 66 +++++++ UI/streaming-settings-util.cpp | 75 +++++++ UI/streaming-settings-util.hpp | 19 ++ UI/window-basic-main-outputs.cpp | 82 +++----- UI/window-basic-main.cpp | 72 +------ UI/window-basic-main.hpp | 6 - UI/window-basic-settings-stream.cpp | 20 ++ UI/window-basic-settings.hpp | 1 + 14 files changed, 613 insertions(+), 128 deletions(-) create mode 100644 UI/common-settings.cpp create mode 100644 UI/common-settings.hpp create mode 100644 UI/pre-stream-wizard/CMakeLists.txt create mode 100644 UI/pre-stream-wizard/pre-stream-current-settings.hpp create mode 100644 UI/pre-stream-wizard/pre-stream-wizard.cpp create mode 100644 UI/pre-stream-wizard/pre-stream-wizard.hpp create mode 100644 UI/streaming-settings-util.cpp create mode 100644 UI/streaming-settings-util.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index df4d264500419c..a9107df079c259 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -11,6 +11,7 @@ if(BROWSER_AVAILABLE_INTERNAL) add_definitions(-DBROWSER_AVAILABLE) endif() +add_subdirectory(pre-stream-wizard) add_subdirectory(obs-frontend-api) # ---------------------------------------------------------------------------- @@ -69,6 +70,7 @@ endif() include_directories(${FFMPEG_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(SYSTEM "obs-frontend-api") +include_directories(SYSTEM "pre-stream-wizard") include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/deps/libff") include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/deps/json11") @@ -188,6 +190,7 @@ set(obs_SOURCES ${obs_PLATFORM_SOURCES} ${obs_libffutil_SOURCES} ../deps/json11/json11.cpp + pre-stream-wizard/pre-stream-wizard.cpp obs-app.cpp window-dock.cpp api-interface.cpp @@ -220,6 +223,8 @@ set(obs_SOURCES window-log-reply.cpp window-projector.cpp window-remux.cpp + common-settings.cpp + streaming-settings-util.cpp auth-base.cpp source-tree.cpp scene-tree.cpp @@ -258,6 +263,8 @@ set(obs_HEADERS ${obs_PLATFORM_HEADERS} ${obs_libffutil_HEADERS} ../deps/json11/json11.hpp + pre-stream-wizard/pre-stream-current-settings.hpp + pre-stream-wizard/pre-stream-wizard.hpp obs-app.hpp platform.hpp window-dock.hpp @@ -282,6 +289,8 @@ set(obs_HEADERS window-log-reply.hpp window-projector.hpp window-remux.hpp + common-settings.hpp + streaming-settings-util.hpp auth-base.hpp source-tree.hpp scene-tree.hpp diff --git a/UI/common-settings.cpp b/UI/common-settings.cpp new file mode 100644 index 00000000000000..2c5d641443d95a --- /dev/null +++ b/UI/common-settings.cpp @@ -0,0 +1,183 @@ +#include "common-settings.hpp" + +#include "audio-encoders.hpp" + +bool IsAdvancedMode(config_t *config) +{ + const char *outputMode = config_get_string(config, "Output", "Mode"); + return (strcmp(outputMode, "Advanced") == 0); +} + +OBSData CommonSettings::GetDataFromJsonFile(const char *jsonFile) +{ + char fullPath[512]; + obs_data_t *data = nullptr; + + int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile); + if (ret > 0) { + BPtr jsonData = os_quick_read_utf8_file(fullPath); + if (!!jsonData) { + data = obs_data_create_from_json(jsonData); + } + } + + if (!data) + data = obs_data_create(); + OBSData dataRet(data); + obs_data_release(data); + return dataRet; +} + +void CommonSettings::GetConfigFPS(config_t *config, uint32_t &num, + uint32_t &den) +{ + uint32_t type = config_get_uint(config, videoSection, "FPSType"); + + if (type == 1) //"Integer" + GetFPSInteger(config, num, den); + else if (type == 2) //"Fraction" + GetFPSFraction(config, num, den); + else if (false) //"Nanoseconds", currently not implemented + GetFPSNanoseconds(config, num, den); + else + GetFPSCommon(config, num, den); +} + +double CommonSettings::GetConfigFPSDouble(config_t *config) +{ + uint32_t num = 0; + uint32_t den = 0; + CommonSettings::GetConfigFPS(config, num, den); + return (double)num / (double)den; +} + +void CommonSettings::GetFPSCommon(config_t *config, uint32_t &num, + uint32_t &den) +{ + const char *val = config_get_string(config, videoSection, "FPSCommon"); + + if (strcmp(val, "10") == 0) { + num = 10; + den = 1; + } else if (strcmp(val, "20") == 0) { + num = 20; + den = 1; + } else if (strcmp(val, "24 NTSC") == 0) { + num = 24000; + den = 1001; + } else if (strcmp(val, "25 PAL") == 0) { + num = 25; + den = 1; + } else if (strcmp(val, "29.97") == 0) { + num = 30000; + den = 1001; + } else if (strcmp(val, "48") == 0) { + num = 48; + den = 1; + } else if (strcmp(val, "50 PAL") == 0) { + num = 50; + den = 1; + } else if (strcmp(val, "59.94") == 0) { + num = 60000; + den = 1001; + } else if (strcmp(val, "60") == 0) { + num = 60; + den = 1; + } else { + num = 30; + den = 1; + } +} + +void CommonSettings::GetFPSInteger(config_t *config, uint32_t &num, + uint32_t &den) +{ + num = (uint32_t)config_get_uint(config, videoSection, "FPSInt"); + den = 1; +} + +void CommonSettings::GetFPSFraction(config_t *config, uint32_t &num, + uint32_t &den) +{ + num = (uint32_t)config_get_uint(config, videoSection, "FPSNum"); + den = (uint32_t)config_get_uint(config, videoSection, "FPSDen"); +} + +void CommonSettings::GetFPSNanoseconds(config_t *config, uint32_t &num, + uint32_t &den) +{ + num = 1000000000; + den = (uint32_t)config_get_uint(config, videoSection, "FPSNS"); +} + +int CommonSettings::GetAudioChannelCount(config_t *config) +{ + const char *channelSetup = + config_get_string(config, "Audio", "ChannelSetup"); + + if (strcmp(channelSetup, "Mono") == 0) + return 1; + if (strcmp(channelSetup, "Stereo") == 0) + return 2; + if (strcmp(channelSetup, "2.1") == 0) + return 3; + if (strcmp(channelSetup, "4.0") == 0) + return 4; + if (strcmp(channelSetup, "4.1") == 0) + return 5; + if (strcmp(channelSetup, "5.1") == 0) + return 6; + if (strcmp(channelSetup, "7.1") == 0) + return 8; + + return 2; +} + +int CommonSettings::GetStreamingAudioBitrate(config_t *config) +{ + bool isAdvancedMode = IsAdvancedMode(config); + + if (isAdvancedMode) { + return GetAdvancedAudioBitrate(config); + } + return GetSimpleAudioBitrate(config); +} + +int CommonSettings::GetSimpleAudioBitrate(config_t *config) +{ + int bitrate = (int)config_get_uint(config, "SimpleOutput", "ABitrate"); + return FindClosestAvailableAACBitrate(bitrate); +} + +int CommonSettings::GetAdvancedAudioBitrate(config_t *config) +{ + int track = config_get_int(config, "AdvOut", "TrackIndex"); + return GetAdvancedAudioBitrateForTrack(config, track - 1); +} + +int CommonSettings::GetAdvancedAudioBitrateForTrack(config_t *config, + int trackIndex) +{ + static const char *names[] = { + "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", + "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", + }; + + // Sanity check for out of bounds + if (trackIndex > 5) + trackIndex = 5; + + int bitrate = (int)config_get_uint(config, "AdvOut", names[trackIndex]); + return FindClosestAvailableAACBitrate(bitrate); +} + +int CommonSettings::GetVideoBitrateInUse(config_t *config) +{ + if (!IsAdvancedMode(config)) { + return config_get_int(config, "SimpleOutput", "VBitrate"); + } + + OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); + int bitrate = obs_data_get_int(streamEncSettings, "bitrate"); + return bitrate; +} diff --git a/UI/common-settings.hpp b/UI/common-settings.hpp new file mode 100644 index 00000000000000..5cdf5d47e66c12 --- /dev/null +++ b/UI/common-settings.hpp @@ -0,0 +1,47 @@ +/* + Helper to get common video and audio setting that are accessed from more than + one file. +*/ +#pragma once + +#include +#include "obs-app.hpp" + +class CommonSettings { + +public: + /* Shared Utility Functions --------------------------*/ + static OBSData GetDataFromJsonFile(const char *jsonFile); + + /* Framerate ----------------------------------------*/ + static void GetConfigFPS(config_t *config, uint32_t &num, + uint32_t &den); + static double GetConfigFPSDouble(config_t *config); + + /* Audio Data ---------------------------------------*/ + // Returns int of audio, sub (the .1 in 2.1) is a channel i.e., 2.1 -> 3ch + static int GetAudioChannelCount(config_t *config); + // Gets streaming track's bitrate, simple or advanced mode + static int GetStreamingAudioBitrate(config_t *config); + static int GetSimpleAudioBitrate(config_t *config); + static int GetAdvancedAudioBitrate(config_t *config); + // Advanced setting's streaming bitrate for track number (starts at 1) + static int GetAdvancedAudioBitrateForTrack(config_t *config, + int trackIndex); + + /* Stream Encoder ——————————————————————————————————*/ + static int GetVideoBitrateInUse(config_t *config); + +private: + // Reused Strings + static constexpr const char *videoSection = "Video"; + + static void GetFPSCommon(config_t *config, uint32_t &num, + uint32_t &den); + static void GetFPSInteger(config_t *config, uint32_t &num, + uint32_t &den); + static void GetFPSFraction(config_t *config, uint32_t &num, + uint32_t &den); + static void GetFPSNanoseconds(config_t *config, uint32_t &num, + uint32_t &den); +}; diff --git a/UI/pre-stream-wizard/CMakeLists.txt b/UI/pre-stream-wizard/CMakeLists.txt new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.hpp b/UI/pre-stream-wizard/pre-stream-current-settings.hpp new file mode 100644 index 00000000000000..3c30da0ccd00c9 --- /dev/null +++ b/UI/pre-stream-wizard/pre-stream-current-settings.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +namespace StreamWizard { +enum class VideoType { + live, // Streaming + vod, // Video On Demand, recording uploads +}; + +enum class StreamProtocol { + rmpts, +}; + +enum class VideoCodec { + h264, +}; + +enum class AudioCodec { + aac, +}; + +enum class StreamRateControlMode { + cbr, + abr, + vbr, + crf, +}; + +// Data to send to encoder config API +struct EncoderSettingsRequest { + //// Stream + StreamProtocol protocol; // Expandable but only supports RTMPS for now + VideoType videoType; // LIVE or VOD (but always live for OBS) + char *serverUrl; + char *serviceName; + + ///// Video Settings + int videoWidth; + int videoHeight; + double framerate; // in frames per second + int videoBitrate; // in kbps e.g., 4000kbps + VideoCodec videoCodec; + + ///// Audio Settings + int audioChannels; + int audioSamplerate; // in Hz, e.g., 48000Hz + int audioBitrate; // in kbps e.g., 128kbps + AudioCodec audioCodec; +}; + +// To be recieved from encoder config API to suggest new ideal +struct EncoderSettingsResponse { + // For response + int videoWidth; + int videoHeight; + double framerate; // in FPS + int videoBitrate; // in kbps + int audioChannels; + int audioSamplerate; // in Hz, e.g., 48000Hz + int audioBitrate; // in kbps e.g., 128kbps + StreamProtocol protocol; + VideoCodec videoCodec; + AudioCodec audioCodec; + + /* Video Codec Settings */ + QString h264Profile; // "high" + float h264Level; // 1 ... 6 + + /* Group of Pictures (gop) settings */ + int gopSizeInFrames; + int gopSizeInSeconds(void) + { + // Uses ceil to account for drop-frame: we round up 23.96 -> 24 + return (int)(gopSizeInFrames / ceil(framerate)); + }; + // Open-GOP is an encoding technique which increases efficiency but often is + // not compatible with streaming + bool gopClosed; + int gopBFrames; // per group-of-pictures + int gopRefFrames; // Controls the size of the DPB (Decoded Picture Buffer). + + bool videoProgressiveScan; + StreamRateControlMode streamRateControlMode; + int streamBufferSize; // in Kb +}; + +} diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp new file mode 100644 index 00000000000000..ab72795791ae46 --- /dev/null +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -0,0 +1,71 @@ +#include "pre-stream-wizard.hpp" + +#include +#include +#include +#include +#include + +namespace StreamWizard { + +enum PSW_Page { Page_InfoDebug }; + +PreStreamWizard::PreStreamWizard( + Destination dest, + QSharedPointer currentSettings, QWidget *parent) + : QWizard(parent), destination_(dest) +{ + this->currentSettings_ = currentSettings; + this->destination_ = dest; + setWizardStyle(QWizard::ModernStyle); + setWindowTitle("Encoder Config"); + + setPage(Page_InfoDebug, makeDebugPage()); +} + +PreStreamWizard::~PreStreamWizard() +{ + currentSettings_ = nullptr; +} + +QWizardPage *PreStreamWizard::makeDebugPage() +{ + QWizardPage *page = new QWizardPage(this); + page->setTitle("Demo + Show Data"); + + // Layout for the entire widget / Wizard PAge + QVBoxLayout *mainLayout = new QVBoxLayout(page); + + // Scroll area that contains values + QScrollArea *scroll = new QScrollArea(); + + // Data list + QWidget *formContainer = new QWidget(); + QFormLayout *form = new QFormLayout(formContainer); + EncoderSettingsRequest *sett = currentSettings_.get(); + + form->addRow("Server Url", + new QLabel(QString::fromUtf8(sett->serverUrl))); + form->addRow("Service", + new QLabel(QString::fromUtf8(sett->serviceName))); + form->addRow("Video Width", + new QLabel(QString::number(sett->videoWidth))); + form->addRow("Video Height", + new QLabel(QString::number(sett->videoHeight))); + form->addRow("Framerate", new QLabel(QString::number(sett->framerate))); + form->addRow("V Bitrate", + new QLabel(QString::number(sett->videoBitrate))); + form->addRow("A channels", + new QLabel(QString::number(sett->audioChannels))); + form->addRow("A Samplerate", + new QLabel(QString::number(sett->audioSamplerate))); + form->addRow("A Bitrate", + new QLabel(QString::number(sett->audioBitrate))); + + page->setLayout(mainLayout); + mainLayout->addWidget(scroll); + scroll->setWidget(formContainer); + return page; +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp new file mode 100644 index 00000000000000..268e3ebe476691 --- /dev/null +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#include "pre-stream-current-settings.hpp" + +namespace StreamWizard { + +/* + To make the wizard expandable we can have multiple destinations. + In the case Facebook, it will use Facebook's no-auth encoder setup API. + Other platforms can use APIs, static JSON files hosted, etc. +*/ +enum class Destination { + Facebook, +}; + +/* There are two contexts for starting a stream + - PreStream: the wizard is triggered between pressing Start Streaming and a + stream. So the user wizard should indicate when the encoder is ready to stream + but also allow the user to abort. + + - Settings: User start config workflow from the settings page or toolbar. + In this case, the wizard should not end with the stream starting but may end + wutg saving the settings if given signal +*/ +enum class Context { + PreStream, + Settings, +}; + +class PreStreamWizard : public QWizard { + Q_OBJECT + +public: + PreStreamWizard(Destination dest, + QSharedPointer currentSettings, + QWidget *parent = nullptr); + ~PreStreamWizard(); + +private: + QWizardPage *makeDebugPage(); + + Destination destination_; + QSharedPointer currentSettings_; + +signals: + // User left the wizard with intention to continue streaming + void userSkippedWizard(void); + + // User canceled, also canceling intent to stream. + void userAbortedStream(void); + + // User ready to start stream + // If newSettings is not null, they should be applied before stream + // If newSettings is null, user or wizard did not opt to apply changes. + void startStreamWithSettings( + QSharedPointer newSettingsOrNull); + + // Apply settings, don't start stream. e.g., is configuring from settings + void applySettings(QSharedPointer newSettings); +}; + +} //namespace StreamWizard diff --git a/UI/streaming-settings-util.cpp b/UI/streaming-settings-util.cpp new file mode 100644 index 00000000000000..4100a81872573c --- /dev/null +++ b/UI/streaming-settings-util.cpp @@ -0,0 +1,75 @@ +#include + +#include "common-settings.hpp" +#include + +/* + We are detecting the stream settings so if they're using a large canvas but + already have the rescaler on, they will be streaming correctly. +*/ +void UpdateStreamingResolution(int *resolutionXY, bool isRescaled, + config_t *config) +{ + + // If scaled, that's the stream resolution sent to servers + if (isRescaled) { + // Only use if there is a resolution in text + const char *rescaleRes = + config_get_string(config, "AdvOut", "RescaleRes"); + if (rescaleRes && *rescaleRes) { + int count = sscanf(rescaleRes, "%ux%u", + &resolutionXY[0], &resolutionXY[1]); + // If text was valid, exit + if (count == 2) { + return; + } + } + } + + // Resolution is not rescaled, use "output resolution" from video tab + resolutionXY[0] = (int)config_get_uint(config, "Video", "OutputCX"); + resolutionXY[1] = (int)config_get_uint(config, "Video", "OutputCY"); +}; + +QSharedPointer +StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( + config_t *config, obs_data_t *settings) +{ + QSharedPointer currentSettings( + new StreamWizard::EncoderSettingsRequest()); + + /* Stream info */ + currentSettings->videoType = StreamWizard::VideoType::live; + // only live and rmpts is supported for now + currentSettings->protocol = StreamWizard::StreamProtocol::rmpts; + + currentSettings->serverUrl = + bstrdup(obs_data_get_string(settings, "server")); + currentSettings->serviceName = + bstrdup(obs_data_get_string(settings, "service")); + + /* Video */ + bool isRescaled = config_get_bool(config, "AdvOut", "Rescale"); + int resolutionXY[2] = {0, 0}; + UpdateStreamingResolution(resolutionXY, isRescaled, config); + currentSettings->videoWidth = resolutionXY[0]; + currentSettings->videoHeight = resolutionXY[1]; + currentSettings->framerate = CommonSettings::GetConfigFPSDouble(config); + + // This ONLY works for simple output right now. If they're in advanced: no + currentSettings->videoBitrate = + CommonSettings::GetVideoBitrateInUse(config); + currentSettings->videoCodec = StreamWizard::VideoCodec::h264; + + /* Audio */ + currentSettings->audioChannels = + CommonSettings::GetAudioChannelCount(config); + currentSettings->audioSamplerate = + config_get_default_int(config, "Audio", "SampleRate"); + currentSettings->audioBitrate = + CommonSettings::GetStreamingAudioBitrate(config); + //For now, only uses AAC + currentSettings->audioCodec = StreamWizard::AudioCodec::aac; + + return currentSettings; +}; diff --git a/UI/streaming-settings-util.hpp b/UI/streaming-settings-util.hpp new file mode 100644 index 00000000000000..91edc3ee5541de --- /dev/null +++ b/UI/streaming-settings-util.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include +#include "pre-stream-current-settings.hpp" + +#include "obs-app.hpp" + +class StreamingSettingsUtility : public QObject { + Q_OBJECT + +public: + // Uses current settings in OBS + static QSharedPointer + makeEncoderSettingsFromCurrentState(config_t *config, + obs_data_t *settings); +}; diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 1cf9224afab5fb..8679b4edbae98c 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "common-settings.hpp" #include "qt-wrappers.hpp" #include "audio-encoders.hpp" #include "window-basic-main.hpp" @@ -275,7 +276,6 @@ struct SimpleOutput : BasicOutputHandler { virtual void Update() override; void SetupOutputs() override; - int GetAudioBitrate() const; void LoadRecordingPreset_h264(const char *encoder); void LoadRecordingPreset_Lossless(); @@ -406,8 +406,10 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) LoadStreamingPreset_h264("obs_x264"); } - if (!CreateAACEncoder(aacStreaming, aacStreamEncID, GetAudioBitrate(), - "simple_aac", 0)) + if (!CreateAACEncoder( + aacStreaming, aacStreamEncID, + CommonSettings::GetSimpleAudioBitrate(main->Config()), + "simple_aac", 0)) throw "Failed to create aac streaming encoder (simple output)"; LoadRecordingPreset(); @@ -464,14 +466,6 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) "stopping", OBSRecordStopping, this); } -int SimpleOutput::GetAudioBitrate() const -{ - int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", - "ABitrate"); - - return FindClosestAvailableAACBitrate(bitrate); -} - void SimpleOutput::Update() { obs_data_t *h264Settings = obs_data_create(); @@ -479,7 +473,8 @@ void SimpleOutput::Update() int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); - int audioBitrate = GetAudioBitrate(); + int audioBitrate = + CommonSettings::GetSimpleAudioBitrate(main->Config()); bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); bool enforceBitrate = config_get_bool(main->Config(), "SimpleOutput", @@ -804,7 +799,9 @@ bool SimpleOutput::SetupStreaming(obs_service_t *service) if (strcmp(codec, "aac") != 0) { const char *id = FindAudioEncoderFromCodec(codec); - int audioBitrate = GetAudioBitrate(); + int audioBitrate = + CommonSettings::GetSimpleAudioBitrate( + main->Config()); obs_data_t *settings = obs_data_create(); obs_data_set_int(settings, "bitrate", audioBitrate); @@ -1076,7 +1073,6 @@ struct AdvancedOutput : BasicOutputHandler { inline void SetupRecording(); inline void SetupFFmpeg(); void SetupOutputs() override; - int GetAudioBitrate(size_t i) const; virtual bool SetupStreaming(obs_service_t *service) override; virtual bool StartStreaming(obs_service_t *service) override; @@ -1090,26 +1086,6 @@ struct AdvancedOutput : BasicOutputHandler { virtual bool ReplayBufferActive() const override; }; -static OBSData GetDataFromJsonFile(const char *jsonFile) -{ - char fullPath[512]; - obs_data_t *data = nullptr; - - int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile); - if (ret > 0) { - BPtr jsonData = os_quick_read_utf8_file(fullPath); - if (!!jsonData) { - data = obs_data_create_from_json(jsonData); - } - } - - if (!data) - data = obs_data_create(); - OBSData dataRet(data); - obs_data_release(data); - return dataRet; -} - static void ApplyEncoderDefaults(OBSData &settings, const obs_encoder_t *encoder) { @@ -1136,8 +1112,10 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) config_get_bool(main->Config(), "AdvOut", "FFOutputToFile"); useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; - OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); - OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); + OBSData streamEncSettings = + CommonSettings::GetDataFromJsonFile("streamEncoder.json"); + OBSData recordEncSettings = + CommonSettings::GetDataFromJsonFile("recordEncoder.json"); const char *rate_control = obs_data_get_string( useStreamEncoder ? streamEncSettings : recordEncSettings, @@ -1215,8 +1193,11 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) char name[9]; sprintf(name, "adv_aac%d", i); - if (!CreateAACEncoder(aacTrack[i], aacEncoderID[i], - GetAudioBitrate(i), name, i)) + if (!CreateAACEncoder( + aacTrack[i], aacEncoderID[i], + CommonSettings::GetAdvancedAudioBitrateForTrack( + main->Config(), i), + name, i)) throw "Failed to create audio encoder " "(advanced output)"; } @@ -1224,7 +1205,9 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) std::string id; int streamTrack = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - if (!CreateAACEncoder(streamAudioEnc, id, GetAudioBitrate(streamTrack), + if (!CreateAACEncoder(streamAudioEnc, id, + CommonSettings::GetAdvancedAudioBitrateForTrack( + main->Config(), streamTrack), "avc_aac_stream", streamTrack)) throw "Failed to create streaming audio encoder " "(advanced output)"; @@ -1246,7 +1229,8 @@ void AdvancedOutput::UpdateStreamSettings() const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - OBSData settings = GetDataFromJsonFile("streamEncoder.json"); + OBSData settings = + CommonSettings::GetDataFromJsonFile("streamEncoder.json"); ApplyEncoderDefaults(settings, h264Streaming); if (applyServiceSettings) @@ -1268,7 +1252,8 @@ void AdvancedOutput::UpdateStreamSettings() inline void AdvancedOutput::UpdateRecordingSettings() { - OBSData settings = GetDataFromJsonFile("recordEncoder.json"); + OBSData settings = + CommonSettings::GetDataFromJsonFile("recordEncoder.json"); obs_encoder_update(h264Recording, settings); } @@ -1456,7 +1441,10 @@ inline void AdvancedOutput::UpdateAudioSettings() for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { settings[i] = obs_data_create(); - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i)); + obs_data_set_int( + settings[i], "bitrate", + CommonSettings::GetAdvancedAudioBitrateForTrack( + main->Config(), i)); } for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { @@ -1505,16 +1493,6 @@ void AdvancedOutput::SetupOutputs() SetupRecording(); } -int AdvancedOutput::GetAudioBitrate(size_t i) const -{ - static const char *names[] = { - "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", - "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", - }; - int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableAACBitrate(bitrate); -} - bool AdvancedOutput::SetupStreaming(obs_service_t *service) { int streamTrack = diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index e2ca1eb47495f5..7c8c953aab8f22 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -51,6 +51,7 @@ #include "window-log-reply.hpp" #include "window-projector.hpp" #include "window-remux.hpp" +#include "common-settings.hpp" #include "qt-wrappers.hpp" #include "context-bar-controls.hpp" #include "obs-proxy-style.hpp" @@ -3910,7 +3911,7 @@ int OBSBasic::ResetVideo() struct obs_video_info ovi; int ret; - GetConfigFPS(ovi.fps_num, ovi.fps_den); + CommonSettings::GetConfigFPS(basicConfig, ovi.fps_num, ovi.fps_den); const char *colorFormat = config_get_string(basicConfig, "Video", "ColorFormat"); @@ -6510,75 +6511,6 @@ void OBSBasic::ToggleAlwaysOnTop() show(); } -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(basicConfig, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(basicConfig, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - else if (false) //"Nanoseconds", currently not implemented - GetFPSNanoseconds(num, den); - else - GetFPSCommon(num, den); -} - config_t *OBSBasic::Config() const { return basicConfig; diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 9d3601b618de16..181bc3c1445425 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -334,12 +334,6 @@ class OBSBasic : public OBSMainWindow { void TimedCheckForUpdates(); void CheckForUpdates(bool manualUpdate); - void GetFPSCommon(uint32_t &num, uint32_t &den) const; - void GetFPSInteger(uint32_t &num, uint32_t &den) const; - void GetFPSFraction(uint32_t &num, uint32_t &den) const; - void GetFPSNanoseconds(uint32_t &num, uint32_t &den) const; - void GetConfigFPS(uint32_t &num, uint32_t &den) const; - void UpdatePreviewScalingMenu(); void LoadSceneListOrder(obs_data_array_t *array); diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 0b8736f0a9447a..e0822dbbd710b7 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -7,6 +7,8 @@ #include "window-basic-main.hpp" #include "qt-wrappers.hpp" #include "url-push-button.hpp" +#include "pre-stream-wizard.hpp" +#include "streaming-settings-util.hpp" #ifdef BROWSER_AVAILABLE #include @@ -77,6 +79,8 @@ void OBSBasicSettings::InitStreamPage() this, SLOT(UpdateKeyLink())); connect(ui->service, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMoreInfoLink())); + connect(ui->settingWizardBtn, SIGNAL(clicked(bool)), this, + SLOT(preStreamWizardLaunch())); } void OBSBasicSettings::LoadStream1Settings() @@ -279,6 +283,22 @@ void OBSBasicSettings::UpdateKeyLink() ui->settingWizardBtn->setHidden(!hasStreamWizard); } +void OBSBasicSettings::preStreamWizardLaunch() +{ + obs_service_t *service_obj = main->GetService(); + obs_data_t *settings = obs_service_get_settings(service_obj); + + QSharedPointer currentSettings = + StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( + main->Config(), settings); + + StreamWizard::PreStreamWizard wiz = StreamWizard::PreStreamWizard( + StreamWizard::Destination::Facebook, currentSettings, this); + wiz.exec(); + + obs_data_release(settings); +} + void OBSBasicSettings::LoadServices(bool showAll) { obs_properties_t *props = obs_get_service_properties("rtmp_common"); diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 89f6795061f25d..a06e949bf128f9 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -240,6 +240,7 @@ private slots: void UpdateServerList(); void UpdateKeyLink(); void UpdateMoreInfoLink(); + void preStreamWizardLaunch(); void on_show_clicked(); void on_authPwShow_clicked(); void on_connectAccount_clicked(); From 9dbd9279bdb4d34926b42f11a4f28cae6be538df Mon Sep 17 00:00:00 2001 From: JohannMG Date: Fri, 18 Sep 2020 14:24:04 -0700 Subject: [PATCH 03/13] UI: Prompt & explain the streaming encoder wizard Here the we explain to the user what the encoder wizard does. Additionally, a radio button set of buttons new resolution. If Facebook Live is the destination, then 720p is recommended otherwise current resolution is used.Additionally, there is a section for Help with a help button that lets whatever provider include further explanation and link to find more information. --- UI/CMakeLists.txt | 4 + UI/data/locale/en-US.ini | 13 ++ UI/pre-stream-wizard/page-input-display.cpp | 49 ++++++ UI/pre-stream-wizard/page-input-display.hpp | 12 ++ UI/pre-stream-wizard/page-start-prompt.cpp | 153 ++++++++++++++++++ UI/pre-stream-wizard/page-start-prompt.hpp | 44 +++++ .../pre-stream-current-settings.hpp | 22 +++ UI/pre-stream-wizard/pre-stream-wizard.cpp | 78 ++++----- UI/pre-stream-wizard/pre-stream-wizard.hpp | 34 +--- UI/window-basic-settings-stream.cpp | 3 +- 10 files changed, 341 insertions(+), 71 deletions(-) create mode 100644 UI/pre-stream-wizard/page-input-display.cpp create mode 100644 UI/pre-stream-wizard/page-input-display.hpp create mode 100644 UI/pre-stream-wizard/page-start-prompt.cpp create mode 100644 UI/pre-stream-wizard/page-start-prompt.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index a9107df079c259..89f4de5419d4c8 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -191,6 +191,8 @@ set(obs_SOURCES ${obs_libffutil_SOURCES} ../deps/json11/json11.cpp pre-stream-wizard/pre-stream-wizard.cpp + pre-stream-wizard/page-input-display.cpp + pre-stream-wizard/page-start-prompt.cpp obs-app.cpp window-dock.cpp api-interface.cpp @@ -265,6 +267,8 @@ set(obs_HEADERS ../deps/json11/json11.hpp pre-stream-wizard/pre-stream-current-settings.hpp pre-stream-wizard/pre-stream-wizard.hpp + pre-stream-wizard/page-input-display.hpp + pre-stream-wizard/page-start-prompt.hpp obs-app.hpp platform.hpp window-dock.hpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 1df96793eb2302..bc08c76b4e89c7 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -708,7 +708,20 @@ Basic.Settings.Stream.MissingUrl="Stream URL is missing.\n\nOpen settings to ent Basic.Settings.Stream.MissingStreamKey="Stream key is missing.\n\nOpen settings to enter the stream key in the 'Stream' tab." ## Pre-Live Wizard and Settings + Basic.Settings.Stream.PreLiveWizard.RunNow="Check stream settings" +PreLiveWizard.Title="Verify Stream Settings" +PreLiveWizard.Prompt.Title="Verify stream settings before stream?" +PreLiveWizard.Prompt.Subtitle.Default="The streaming wizard suggests the most up-to-date settings to improve your stream’s reliability, quality, and latency. \n\nSelect the resolution to stream to your account:" +PreLiveWizard.Prompt.Subtitle.FB="Your destination is set to Facebook Live." +PreLiveWizard.Prompt.Explainer="The Streaming wizard suggests the most up-to-date settings to improve your stream’s reliability, quality, and latency." +PreLiveWizard.Prompt.ResSelectTitle="Select a resolution to stream:" +PreLiveWizard.Prompt.FBResolutionHelp.FB="If you’re unsure, click Open Live Producer, then click on the \"Stream Health\" tab and scroll down to find your maximum resolution." +PreLiveWizard.Prompt.FBResolutionHelpButton.FB="Open Live Producer" +PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip="Opens Facebook Live Producer in your default internet browser" +PreLiveWizard.Prompt.Resolution.720="720p (Recommended for general purpose)" +PreLiveWizard.Prompt.Resolution.1080="1080p (Recommended for gaming)" +PreLiveWizard.Prompt.Resolution.Current="%1x%2 (Current setting)" # basic mode 'output' settings Basic.Settings.Output="Output" diff --git a/UI/pre-stream-wizard/page-input-display.cpp b/UI/pre-stream-wizard/page-input-display.cpp new file mode 100644 index 00000000000000..9f46f5a9c76ed2 --- /dev/null +++ b/UI/pre-stream-wizard/page-input-display.cpp @@ -0,0 +1,49 @@ +#include "page-input-display.hpp" + +#include +#include +#include +#include +#include + +#include "pre-stream-current-settings.hpp" + +QWizardPage *SettingsInputPage(StreamWizard::EncoderSettingsRequest *settings) +{ + QWizardPage *page = new QWizardPage(); + page->setTitle("Demo + Show Data"); + + // Layout for the entire widget / Wizard PAge + QVBoxLayout *mainLayout = new QVBoxLayout(page); + + // Scroll area that contains values + QScrollArea *scroll = new QScrollArea(); + + // Data list + QWidget *formContainer = new QWidget(); + QFormLayout *form = new QFormLayout(formContainer); + + form->addRow("Server Url", + new QLabel(QString::fromUtf8(settings->serverUrl))); + form->addRow("Service", + new QLabel(QString::fromUtf8(settings->serviceName))); + form->addRow("Video Width", + new QLabel(QString::number(settings->videoWidth))); + form->addRow("Video Height", + new QLabel(QString::number(settings->videoHeight))); + form->addRow("Framerate", + new QLabel(QString::number(settings->framerate))); + form->addRow("V Bitrate", + new QLabel(QString::number(settings->videoBitrate))); + form->addRow("A channels", + new QLabel(QString::number(settings->audioChannels))); + form->addRow("A Samplerate", + new QLabel(QString::number(settings->audioSamplerate))); + form->addRow("A Bitrate", + new QLabel(QString::number(settings->audioBitrate))); + + page->setLayout(mainLayout); + mainLayout->addWidget(scroll); + scroll->setWidget(formContainer); + return page; +} diff --git a/UI/pre-stream-wizard/page-input-display.hpp b/UI/pre-stream-wizard/page-input-display.hpp new file mode 100644 index 00000000000000..a15dbb0eed1f86 --- /dev/null +++ b/UI/pre-stream-wizard/page-input-display.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#include "pre-stream-current-settings.hpp" + +extern "C" { + +QWizardPage *SettingsInputPage(StreamWizard::EncoderSettingsRequest *settings); + +} // extern C diff --git a/UI/pre-stream-wizard/page-start-prompt.cpp b/UI/pre-stream-wizard/page-start-prompt.cpp new file mode 100644 index 00000000000000..35c9e007c9d846 --- /dev/null +++ b/UI/pre-stream-wizard/page-start-prompt.cpp @@ -0,0 +1,153 @@ +#include "page-start-prompt.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "obs-app.hpp" + +namespace StreamWizard { + +StartPage::StartPage(Destination dest, LaunchContext launchContext, + QSize videoSize, QWidget *parent) + : QWizardPage(parent) +{ + this->destination_ = dest; + this->launchContext_ = launchContext; + this->startVideoSize_ = videoSize; + + setTitle(QTStr("PreLiveWizard.Prompt.Title")); + + if (destination_ == Destination::Facebook) { + setSubTitle(QTStr("PreLiveWizard.Prompt.Subtitle.FB")); + } else { + setSubTitle(QTStr("PreLiveWizard.Prompt.Subtitle.Default")); + } + + createLayout(); + connectRadioButtons(); + + //Default behavior per destination + if (destination_ == Destination::Facebook) { + res720Button_->setChecked(true); + } else { + resCurrentButton_->setChecked(true); + } +} + +StartPage::~StartPage() +{ + // nop +} + +void StartPage::createLayout() +{ + QVBoxLayout *mainlayout = new QVBoxLayout(this); + + QLabel *explainer = + new QLabel(QTStr("PreLiveWizard.Prompt.Explainer"), this); + explainer->setWordWrap(true); + + QLabel *radioButtonsTitle = + new QLabel(QTStr("PreLiveWizard.Prompt.ResSelectTitle"), this); + radioButtonsTitle->setWordWrap(true); + radioButtonsTitle->setStyleSheet("font-weight: bold;"); + + // Radio Button Group + QString res720Label = QTStr("PreLiveWizard.Prompt.Resolution.720"); + res720Button_ = new QRadioButton(res720Label, this); + + QString res1080label = QTStr("PreLiveWizard.Prompt.Resolution.1080"); + res1080Button_ = new QRadioButton(res1080label, this); + + QString currentResLabel = + QTStr("PreLiveWizard.Prompt.Resolution.Current") + .arg(QString::number(startVideoSize_.width()), + QString::number(startVideoSize_.height())); + resCurrentButton_ = new QRadioButton(currentResLabel, this); + + // Optional Help Section + QLabel *helpLabel = nullptr; + QPushButton *helpButton = nullptr; + + if (destination_ == Destination::Facebook) { + // If FB, specfic help section + helpLabel = new QLabel( + QTStr("PreLiveWizard.Prompt.FBResolutionHelp.FB"), + this); + helpLabel->setWordWrap(true); + helpButton = new QPushButton( + QTStr("PreLiveWizard.Prompt.FBResolutionHelpButton.FB"), + this); + helpButton->setToolTip(QTStr( + "PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip")); + connect(helpButton, &QPushButton::clicked, this, + &StartPage::didPushOpenWebsiteHelp); + } + + setLayout(mainlayout); + mainlayout->addWidget(explainer); + mainlayout->addSpacerItem(new QSpacerItem(12, 12)); + mainlayout->addWidget(radioButtonsTitle); + mainlayout->addWidget(res720Button_); + mainlayout->addWidget(res1080Button_); + mainlayout->addWidget(resCurrentButton_); + + if (helpLabel != nullptr) { + mainlayout->addSpacerItem(new QSpacerItem(24, 24)); + mainlayout->addWidget(helpLabel); + } + if (helpButton != nullptr) { + QHBoxLayout *buttonAndSpacerLayout = new QHBoxLayout(); + buttonAndSpacerLayout->addWidget(helpButton); + QSpacerItem *buttonSpacer = + new QSpacerItem(24, 24, QSizePolicy::MinimumExpanding, + QSizePolicy::Minimum); + buttonAndSpacerLayout->addSpacerItem(buttonSpacer); + mainlayout->addLayout(buttonAndSpacerLayout); + } +} + +void StartPage::connectRadioButtons() +{ + connect(res720Button_, &QRadioButton::clicked, [=]() { + selectedVideoSize_ = QSize(1280, 720); + emit userSelectedResolution(selectedVideoSize_); + }); + connect(res1080Button_, &QRadioButton::clicked, [=]() { + selectedVideoSize_ = QSize(1920, 1080); + emit userSelectedResolution(selectedVideoSize_); + }); + connect(resCurrentButton_, &QRadioButton::clicked, [=]() { + selectedVideoSize_ = QSize(startVideoSize_.width(), + startVideoSize_.height()); + emit userSelectedResolution(selectedVideoSize_); + }); +} + +void StartPage::didPushOpenWebsiteHelp() +{ + QUrl helpUrl; + + // Prepare per-destination + if (destination_ == Destination::Facebook) { + helpUrl = QUrl( + "https://www.facebook.com/live/producer/?ref=OBS_Wizard", + QUrl::TolerantMode); + } else { + return; + } + + // Launch + if (helpUrl.isValid()) { + QDesktopServices::openUrl(helpUrl); + } +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-start-prompt.hpp b/UI/pre-stream-wizard/page-start-prompt.hpp new file mode 100644 index 00000000000000..85475f41bf5b40 --- /dev/null +++ b/UI/pre-stream-wizard/page-start-prompt.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include "pre-stream-current-settings.hpp" + +class QRadioButton; +class QSignalMapper; +class QSize; + +namespace StreamWizard { +// Prompt if the user wants to verify their settings or close. +class StartPage : public QWizardPage { + Q_OBJECT + +public: + StartPage(Destination dest, LaunchContext launchContext, + QSize videoSize, QWidget *parent = nullptr); + ~StartPage(); + +signals: + // emitted selected resolution from start page radio buttons + void userSelectedResolution(QSize newVideoSize); + +private: + // Init information + Destination destination_; + LaunchContext launchContext_; + QSize startVideoSize_; + + // Selected settings + QSize selectedVideoSize_; + QRadioButton *res720Button_; + QRadioButton *res1080Button_; + QRadioButton *resCurrentButton_; + + void createLayout(); + void connectRadioButtons(); + +private slots: + void didPushOpenWebsiteHelp(); + +}; // class StartPage +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.hpp b/UI/pre-stream-wizard/pre-stream-current-settings.hpp index 3c30da0ccd00c9..0ee9b801acb4c7 100644 --- a/UI/pre-stream-wizard/pre-stream-current-settings.hpp +++ b/UI/pre-stream-wizard/pre-stream-current-settings.hpp @@ -29,6 +29,28 @@ enum class StreamRateControlMode { crf, }; +/* There are two launch contexts for starting the wizard + - PreStream: the wizard is triggered between pressing Start Streaming and a + stream. So the user wizard should indicate when the encoder is ready to stream + but also allow the user to abort. + + - Settings: User start config workflow from the settings page or toolbar. + In this case, the wizard should not end with the stream starting but may end + wutg saving the settings if given signal +*/ +enum class LaunchContext { + PreStream, + Settings, +}; + +/* + To make the wizard expandable we can have multiple destinations. + In the case Facebook, it will use Facebook's no-auth encoder API. +*/ +enum class Destination { + Facebook, +}; + // Data to send to encoder config API struct EncoderSettingsRequest { //// Stream diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index ab72795791ae46..b84803e51aa4e0 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -1,26 +1,42 @@ #include "pre-stream-wizard.hpp" #include -#include -#include #include #include +#include + +#include "page-input-display.hpp" +#include "page-start-prompt.hpp" + +#include "obs-app.hpp" namespace StreamWizard { -enum PSW_Page { Page_InfoDebug }; +enum PSW_Page { Page_InfoDebug, Page_StartPrompt }; PreStreamWizard::PreStreamWizard( - Destination dest, + Destination dest, LaunchContext launchContext, QSharedPointer currentSettings, QWidget *parent) - : QWizard(parent), destination_(dest) + : QWizard(parent) { this->currentSettings_ = currentSettings; this->destination_ = dest; - setWizardStyle(QWizard::ModernStyle); - setWindowTitle("Encoder Config"); + this->launchContext_ = launchContext; + connect(this, &QWizard::currentIdChanged, this, + &PreStreamWizard::onPageChanged); - setPage(Page_InfoDebug, makeDebugPage()); + setWizardStyle(QWizard::ModernStyle); + setWindowTitle(QTStr("PreLiveWizard.Title")); + + // Always First Page, prompt + QSize currentRes(currentSettings_->videoWidth, + currentSettings_->videoHeight); + userSelectedNewRes_ = currentRes; + StartPage *startPage = + new StartPage(destination_, launchContext_, currentRes, this); + setPage(Page_StartPrompt, startPage); + connect(startPage, &StartPage::userSelectedResolution, this, + &PreStreamWizard::onUserSelectResolution); } PreStreamWizard::~PreStreamWizard() @@ -28,44 +44,18 @@ PreStreamWizard::~PreStreamWizard() currentSettings_ = nullptr; } -QWizardPage *PreStreamWizard::makeDebugPage() +void PreStreamWizard::onPageChanged(int id) { - QWizardPage *page = new QWizardPage(this); - page->setTitle("Demo + Show Data"); - - // Layout for the entire widget / Wizard PAge - QVBoxLayout *mainLayout = new QVBoxLayout(page); - - // Scroll area that contains values - QScrollArea *scroll = new QScrollArea(); - - // Data list - QWidget *formContainer = new QWidget(); - QFormLayout *form = new QFormLayout(formContainer); - EncoderSettingsRequest *sett = currentSettings_.get(); - - form->addRow("Server Url", - new QLabel(QString::fromUtf8(sett->serverUrl))); - form->addRow("Service", - new QLabel(QString::fromUtf8(sett->serviceName))); - form->addRow("Video Width", - new QLabel(QString::number(sett->videoWidth))); - form->addRow("Video Height", - new QLabel(QString::number(sett->videoHeight))); - form->addRow("Framerate", new QLabel(QString::number(sett->framerate))); - form->addRow("V Bitrate", - new QLabel(QString::number(sett->videoBitrate))); - form->addRow("A channels", - new QLabel(QString::number(sett->audioChannels))); - form->addRow("A Samplerate", - new QLabel(QString::number(sett->audioSamplerate))); - form->addRow("A Bitrate", - new QLabel(QString::number(sett->audioBitrate))); + if (id == Page_StartPrompt) { + setOption(QWizard::NoCancelButton, false); + } +} - page->setLayout(mainLayout); - mainLayout->addWidget(scroll); - scroll->setWidget(formContainer); - return page; +void PreStreamWizard::onUserSelectResolution(QSize newSize) +{ + blog(LOG_INFO, "Selected res %d x %d", newSize.width(), + newSize.height()); + userSelectedNewRes_ = newSize; } } // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index 268e3ebe476691..982fcbd7fd3cd5 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -8,43 +8,21 @@ namespace StreamWizard { -/* - To make the wizard expandable we can have multiple destinations. - In the case Facebook, it will use Facebook's no-auth encoder setup API. - Other platforms can use APIs, static JSON files hosted, etc. -*/ -enum class Destination { - Facebook, -}; - -/* There are two contexts for starting a stream - - PreStream: the wizard is triggered between pressing Start Streaming and a - stream. So the user wizard should indicate when the encoder is ready to stream - but also allow the user to abort. - - - Settings: User start config workflow from the settings page or toolbar. - In this case, the wizard should not end with the stream starting but may end - wutg saving the settings if given signal -*/ -enum class Context { - PreStream, - Settings, -}; - class PreStreamWizard : public QWizard { Q_OBJECT public: - PreStreamWizard(Destination dest, + PreStreamWizard(Destination dest, LaunchContext launchContext, QSharedPointer currentSettings, QWidget *parent = nullptr); ~PreStreamWizard(); private: - QWizardPage *makeDebugPage(); - + // External State Destination destination_; + LaunchContext launchContext_; QSharedPointer currentSettings_; + QSize userSelectedNewRes_; signals: // User left the wizard with intention to continue streaming @@ -61,6 +39,10 @@ class PreStreamWizard : public QWizard { // Apply settings, don't start stream. e.g., is configuring from settings void applySettings(QSharedPointer newSettings); + +private slots: + void onPageChanged(int id); + void onUserSelectResolution(QSize newSize); }; } //namespace StreamWizard diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index e0822dbbd710b7..e5cb6eb756ff34 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -293,7 +293,8 @@ void OBSBasicSettings::preStreamWizardLaunch() main->Config(), settings); StreamWizard::PreStreamWizard wiz = StreamWizard::PreStreamWizard( - StreamWizard::Destination::Facebook, currentSettings, this); + StreamWizard::Destination::Facebook, + StreamWizard::LaunchContext::Settings, currentSettings, this); wiz.exec(); obs_data_release(settings); From 4764a94ee2a257bbdf938bbd6908d53b8787b98e Mon Sep 17 00:00:00 2001 From: JohannMG Date: Thu, 24 Sep 2020 20:16:09 -0700 Subject: [PATCH 04/13] UI: Setup FacebookEncoderSettingsProvider For the facebook destination, setup the json Get. Then, decode and pass the object back to the wizard. This object will be used in the selection and application of settings later. --- CMakeLists.txt | 1 + UI/CMakeLists.txt | 4 + UI/data/locale/en-US.ini | 7 +- .../encoder-settings-provider-facebook.cpp | 239 ++++++++++++++++++ .../encoder-settings-provider-facebook.hpp | 51 ++++ .../pre-stream-current-settings.hpp | 92 +++---- UI/pre-stream-wizard/pre-stream-wizard.cpp | 62 ++++- UI/pre-stream-wizard/pre-stream-wizard.hpp | 27 +- UI/streaming-settings-util.cpp | 2 +- cmake/Modules/CopyMSVCBins.cmake | 2 + 10 files changed, 429 insertions(+), 58 deletions(-) create mode 100644 UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp create mode 100644 UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e4a67175808ec..99d05ac7610cce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,6 +257,7 @@ if(NOT INSTALLER_RUN) endif() find_package(Qt5Widgets ${FIND_MODE}) + find_package(Qt5Network ${FIND_MODE}) endif() add_subdirectory(deps) diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 89f4de5419d4c8..fadb8963a5a043 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -50,6 +50,7 @@ set(CMAKE_AUTOMOC TRUE) find_package(Qt5Widgets ${FIND_MODE}) find_package(Qt5Svg ${FIND_MODE}) find_package(Qt5Xml ${FIND_MODE}) +find_package(Qt5Network ${FIND_MODE}) find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat) @@ -193,6 +194,7 @@ set(obs_SOURCES pre-stream-wizard/pre-stream-wizard.cpp pre-stream-wizard/page-input-display.cpp pre-stream-wizard/page-start-prompt.cpp + pre-stream-wizard/encoder-settings-provider-facebook.cpp obs-app.cpp window-dock.cpp api-interface.cpp @@ -269,6 +271,7 @@ set(obs_HEADERS pre-stream-wizard/pre-stream-wizard.hpp pre-stream-wizard/page-input-display.hpp pre-stream-wizard/page-start-prompt.hpp + pre-stream-wizard/encoder-settings-provider-facebook.hpp obs-app.hpp platform.hpp window-dock.hpp @@ -409,6 +412,7 @@ target_link_libraries(obs Qt5::Widgets Qt5::Svg Qt5::Xml + Qt5::Network obs-frontend-api ${FFMPEG_LIBRARIES} ${LIBCURL_LIBRARIES} diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index bc08c76b4e89c7..e2d52da184b6f5 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -708,7 +708,6 @@ Basic.Settings.Stream.MissingUrl="Stream URL is missing.\n\nOpen settings to ent Basic.Settings.Stream.MissingStreamKey="Stream key is missing.\n\nOpen settings to enter the stream key in the 'Stream' tab." ## Pre-Live Wizard and Settings - Basic.Settings.Stream.PreLiveWizard.RunNow="Check stream settings" PreLiveWizard.Title="Verify Stream Settings" PreLiveWizard.Prompt.Title="Verify stream settings before stream?" @@ -722,6 +721,12 @@ PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip="Opens Facebook Live Prod PreLiveWizard.Prompt.Resolution.720="720p (Recommended for general purpose)" PreLiveWizard.Prompt.Resolution.1080="1080p (Recommended for gaming)" PreLiveWizard.Prompt.Resolution.Current="%1x%2 (Current setting)" +PreLiveWizard.Configure.ServiceNotAvailable.Title="Service not supported" +PreLiveWizard.Configure.ServiceNotAvailable.Description="The service selected in stream settings does not yet have an encoder configuration service." +PreLiveWizard.Configure.Error.Url="Settings setup failed. Query: " +PreLiveWizard.Configure.Error.JsonParse="Problem with server response" +PreLiveWizard.Configure.Error.JsonParseLonger="Wizard is unavailble at the moment." + # basic mode 'output' settings Basic.Settings.Output="Output" diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp new file mode 100644 index 00000000000000..99ade9edec0a64 --- /dev/null +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp @@ -0,0 +1,239 @@ +#include "encoder-settings-provider-facebook.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "obs-app.hpp" +#include "obs-config.h" + +namespace StreamWizard { + +FacebookEncoderSettingsProvider::FacebookEncoderSettingsProvider(QObject *parent) + : QObject(parent) +{ + currentSettings_ = nullptr; + restclient_ = new QNetworkAccessManager(this); +} + +void FacebookEncoderSettingsProvider::setEncoderRequest( + QSharedPointer request) +{ + currentSettings_ = request; +} + +void FacebookEncoderSettingsProvider::run() +{ + // Base URL for request + QUrl requestUrl( + "https://graph.facebook.com/v6.0/video_encoder_settings"); + QUrlQuery inputVideoSettingsQuery = + inputVideoQueryFromCurrentSettings(); + requestUrl.setQuery(inputVideoSettingsQuery); + if (requestUrl.isValid()) { + makeRequest(requestUrl); + } else { + emit returnErrorDescription( + QTStr("PreLiveWizard.Configure.UrlError"), + requestUrl.toDisplayString()); + } +} + +void FacebookEncoderSettingsProvider::makeRequest(QUrl &url) +{ + blog(LOG_INFO, "FacebookEncoderSettingsProvider creating request"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, + "application/json"); + + // GET is made async + settingsReply_ = restclient_->get(request); + // This is the callback when data is ready + connect(restclient_, &QNetworkAccessManager::finished, this, + &FacebookEncoderSettingsProvider::handleResponse); +} + +QUrlQuery FacebookEncoderSettingsProvider::inputVideoQueryFromCurrentSettings() +{ + // Get input settings, shorten name + EncoderSettingsRequest *input = currentSettings_.get(); + + QUrlQuery inputVideoSettingsQuery; + inputVideoSettingsQuery.addQueryItem("video_type", "live"); + if (input->userSelectedResolution.isNull()) { + inputVideoSettingsQuery.addQueryItem( + "input_video_width", + QString::number(input->videoWidth)); + inputVideoSettingsQuery.addQueryItem( + "input_video_height", + QString::number(input->videoHeight)); + } else { + QSize wizardResolution = input->userSelectedResolution.toSize(); + inputVideoSettingsQuery.addQueryItem( + "input_video_width", + QString::number(wizardResolution.width())); + inputVideoSettingsQuery.addQueryItem( + "input_video_height", + QString::number(wizardResolution.height())); + } + inputVideoSettingsQuery.addQueryItem("input_video_framerate", + QString::number(input->framerate)); + inputVideoSettingsQuery.addQueryItem( + "input_video_bitrate", QString::number(input->videoBitrate)); + inputVideoSettingsQuery.addQueryItem( + "input_audio_channels", QString::number(input->audioChannels)); + inputVideoSettingsQuery.addQueryItem( + "input_audio_samplerate", + QString::number(input->audioSamplerate)); + if (input->protocol == StreamProtocol::rtmps) { + inputVideoSettingsQuery.addQueryItem("cap_streaming_protocols", + "rtmps"); + } + // Defaults in OBS + inputVideoSettingsQuery.addQueryItem("cap_video_codecs", "h264"); + inputVideoSettingsQuery.addQueryItem("cap_audio_codecs", "aac"); + return inputVideoSettingsQuery; +} + +QString FacebookEncoderSettingsProvider::getOBSVersionString() +{ + QString versionString; + +#ifdef HAVE_OBSCONFIG_H + versionString += OBS_VERSION; +#else + versionString += LIBOBS_API_MAJOR_VER + "." + LIBOBS_API_MINOR_VER + + "." + LIBOBS_API_PATCH_VER; +#endif + + return versionString; +} + +// Helper methods for FacebookEncoderSettingsProvider::handleResponse +void addInt(const QJsonObject &json, const char *jsonKey, SettingsMap *map, + const char *mapKey) +{ + if (json[jsonKey].isDouble()) { + map->insert(mapKey, + QPair(QVariant(json[jsonKey].toInt()), true)); + } +} + +void addDouble(const QJsonObject &json, const char *jsonKey, SettingsMap *map, + const char *mapKey) +{ + if (json[jsonKey].isDouble()) { + map->insert(mapKey, + QPair(QVariant(json[jsonKey].toDouble()), true)); + } +} + +void addQString(const QJsonObject &json, const char *jsonKey, SettingsMap *map, + const char *mapKey) +{ + if (json[jsonKey].isString()) { + map->insert(mapKey, + QPair(QVariant(json[jsonKey].toString()), true)); + } +} + +void addBool(const QJsonObject &json, const char *jsonKey, SettingsMap *map, + const char *mapKey) +{ + if (json[jsonKey].isBool()) { + map->insert(mapKey, + QPair(QVariant(json[jsonKey].isBool()), true)); + } +} + +void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) +{ + // Converts byte array into JSON doc as a copy + QByteArray replyAll = reply->readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(replyAll); + reply->deleteLater(); + + // Parse bytes to json object + if (!jsonDoc.isObject()) { + blog(LOG_WARNING, + "FacebookEncoderSettingsProvider json doc not an object as expected"); + jsonParseError(); + return; + } + QJsonObject responseObject = jsonDoc.object(); + + // Get the RTMPS settings object + if (!responseObject["rtmps_settings"].isObject()) { + QString error = + responseObject["error"].toObject()["message"].toString(); + blog(LOG_INFO, + "FacebookEncoderSettingsProvider rtmps_settings not an object"); + jsonParseError(); + return; + } + QJsonObject rmtpSettings = responseObject["rtmps_settings"].toObject(); + + // Get the video codec object + if (!rmtpSettings["video_codec_settings"].isObject()) { + blog(LOG_INFO, + "FacebookEncoderSettingsProvider video_codec_settings not an object"); + jsonParseError(); + return; + } + QJsonObject videoSettingsJsob = + rmtpSettings["video_codec_settings"].toObject(); + + // Create map to send to wizard + SettingsMap *settingsMap = new SettingsMap(); + + addInt(videoSettingsJsob, "video_bitrate", settingsMap, + SettingsResponseKeys.videoBitrate); + addInt(videoSettingsJsob, "video_width", settingsMap, + SettingsResponseKeys.videoWidth); + addInt(videoSettingsJsob, "video_height", settingsMap, + SettingsResponseKeys.videoHeight); + addDouble(videoSettingsJsob, "video_framerate", settingsMap, + SettingsResponseKeys.framerate); + addQString(videoSettingsJsob, "video_h264_profile", settingsMap, + SettingsResponseKeys.h264Profile); + addQString(videoSettingsJsob, "video_h264_level", settingsMap, + SettingsResponseKeys.h264Level); + addInt(videoSettingsJsob, "video_gop_size", settingsMap, + SettingsResponseKeys.gopSizeInFrames); + addQString(videoSettingsJsob, "video_gop_type", settingsMap, + SettingsResponseKeys.gopType); + addBool(videoSettingsJsob, "video_gop_closed", settingsMap, + SettingsResponseKeys.gopClosed); + addInt(videoSettingsJsob, "video_gop_num_b_frames", settingsMap, + SettingsResponseKeys.gopBFrames); + addInt(videoSettingsJsob, "video_gop_num_ref_frames", settingsMap, + SettingsResponseKeys.gopRefFrames); + addQString(videoSettingsJsob, "rate_control_mode", settingsMap, + SettingsResponseKeys.streamRateControlMode); + addInt(videoSettingsJsob, "buffer_size", settingsMap, + SettingsResponseKeys.streamBufferSize); + + // Wrap into shared pointer and emit + QSharedPointer settingsMapShrdPtr = + QSharedPointer(settingsMap); + emit newSettings(settingsMapShrdPtr); +} + +void FacebookEncoderSettingsProvider::jsonParseError() +{ + emit returnErrorDescription( + QTStr("PreLiveWizard.Configure.Error.JsonParse"), + QTStr("PreLiveWizard.Configure.Error.JsonParse.Description")); +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp new file mode 100644 index 00000000000000..6de922d2025086 --- /dev/null +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pre-stream-current-settings.hpp" + +namespace StreamWizard { + +class FacebookEncoderSettingsProvider : public QObject { + Q_OBJECT + +public: + FacebookEncoderSettingsProvider(QObject *parent = nullptr); + + // Pass in encoder request to use, returns FALSE is there is an error. + void setEncoderRequest(QSharedPointer request); + + // Uses the EncoderSettingsRequest to generate SettingsMap + // Does not return in sync becuase can be an async API call + // Success: emits returnedEncoderSettings(SettingsMap) + // Failure: emits returnedError(QString title, QString description) + void run(); + +signals: + // On success. + void newSettings(QSharedPointer response); + // On failure. Will be shown to user. + void returnErrorDescription(QString title, QString description); + +private: + QSharedPointer currentSettings_; + QNetworkAccessManager *restclient_; + QNetworkReply *settingsReply_; + + void makeRequest(QUrl &url); + QUrlQuery inputVideoQueryFromCurrentSettings(); + QString getOBSVersionString(); + void jsonParseError(); + +private slots: + void handleResponse(QNetworkReply *reply); +}; + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.hpp b/UI/pre-stream-wizard/pre-stream-current-settings.hpp index 0ee9b801acb4c7..a4a53b213ecc59 100644 --- a/UI/pre-stream-wizard/pre-stream-current-settings.hpp +++ b/UI/pre-stream-wizard/pre-stream-current-settings.hpp @@ -3,15 +3,19 @@ #include #include #include +#include +#include +#include namespace StreamWizard { + enum class VideoType { live, // Streaming vod, // Video On Demand, recording uploads }; enum class StreamProtocol { - rmpts, + rtmps, }; enum class VideoCodec { @@ -22,13 +26,6 @@ enum class AudioCodec { aac, }; -enum class StreamRateControlMode { - cbr, - abr, - vbr, - crf, -}; - /* There are two launch contexts for starting the wizard - PreStream: the wizard is triggered between pressing Start Streaming and a stream. So the user wizard should indicate when the encoder is ready to stream @@ -71,42 +68,51 @@ struct EncoderSettingsRequest { int audioSamplerate; // in Hz, e.g., 48000Hz int audioBitrate; // in kbps e.g., 128kbps AudioCodec audioCodec; -}; -// To be recieved from encoder config API to suggest new ideal -struct EncoderSettingsResponse { - // For response - int videoWidth; - int videoHeight; - double framerate; // in FPS - int videoBitrate; // in kbps - int audioChannels; - int audioSamplerate; // in Hz, e.g., 48000Hz - int audioBitrate; // in kbps e.g., 128kbps - StreamProtocol protocol; - VideoCodec videoCodec; - AudioCodec audioCodec; - - /* Video Codec Settings */ - QString h264Profile; // "high" - float h264Level; // 1 ... 6 - - /* Group of Pictures (gop) settings */ - int gopSizeInFrames; - int gopSizeInSeconds(void) - { - // Uses ceil to account for drop-frame: we round up 23.96 -> 24 - return (int)(gopSizeInFrames / ceil(framerate)); - }; - // Open-GOP is an encoding technique which increases efficiency but often is - // not compatible with streaming - bool gopClosed; - int gopBFrames; // per group-of-pictures - int gopRefFrames; // Controls the size of the DPB (Decoded Picture Buffer). - - bool videoProgressiveScan; - StreamRateControlMode streamRateControlMode; - int streamBufferSize; // in Kb + // If the user picked a resolution in the wizard + // Holds QSize or null + QVariant userSelectedResolution; }; +// Map for the repsonse passed to UI and settings is: +using SettingsMap = QMap>; +// where String = map key from kSettingsResponseKeys, and +// where QVariant is the Settings value, and +// where bool is if the user wants to apply the settings +// Keys for new settings QMap +static const struct { + const char *videoWidth; //int + const char *videoHeight; //int + const char *framerate; // double (FPS) + const char *videoBitrate; //int + const char *audioBitrate; // int + const char *protocol; // string + const char *videoCodec; // string + const char *h264Profile; // string ("High") + const char *h264Level; // string ("4.1") + const char *gopSizeInFrames; // int + const char *gopType; // string ("fixed") + const char *gopClosed; // bool + const char *gopBFrames; // int + const char *gopRefFrames; // int + const char *streamRateControlMode; // string "CBR" + const char *streamBufferSize; // int (5000 kb) +} SettingsResponseKeys = { + .videoWidth = "videoWidth", + .videoHeight = "videoHeight", + .framerate = "framerate", + .videoBitrate = "videoBitrate", + .audioBitrate = "audioBitrate", + .protocol = "protocol", + .videoCodec = "videoCodec", + .h264Profile = "h264Profile", + .h264Level = "h264Level", + .gopSizeInFrames = "gopSizeInFrames", + .gopType = "gopType", + .gopClosed = "gopClosed", + .gopBFrames = "gopBFrames", + .gopRefFrames = "gopRefFrames", + .streamRateControlMode = "streamRateControlMode", + .streamBufferSize = "streamBufferSize", +}; } diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index b84803e51aa4e0..b9d2150ff4c813 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -5,14 +5,15 @@ #include #include +#include "obs-app.hpp" +#include "encoder-settings-provider-facebook.hpp" + #include "page-input-display.hpp" #include "page-start-prompt.hpp" -#include "obs-app.hpp" - namespace StreamWizard { -enum PSW_Page { Page_InfoDebug, Page_StartPrompt }; +enum PSW_Page { Page_StartPrompt, Page_Loading, Page_Selections }; PreStreamWizard::PreStreamWizard( Destination dest, LaunchContext launchContext, @@ -28,20 +29,60 @@ PreStreamWizard::PreStreamWizard( setWizardStyle(QWizard::ModernStyle); setWindowTitle(QTStr("PreLiveWizard.Title")); - // Always First Page, prompt + // First Page: Explain to the user and confirm sending to API QSize currentRes(currentSettings_->videoWidth, currentSettings_->videoHeight); - userSelectedNewRes_ = currentRes; StartPage *startPage = new StartPage(destination_, launchContext_, currentRes, this); setPage(Page_StartPrompt, startPage); connect(startPage, &StartPage::userSelectedResolution, this, &PreStreamWizard::onUserSelectResolution); + + // Loading page: Shown when loading new settings + setPage(Page_Loading, new QWizardPage()); + + // Suggestion Selection Pahe + setPage(Page_Selections, new QWizardPage()); +} + +void PreStreamWizard::requestSettings() +{ + if (destination_ == Destination::Facebook) { + + FacebookEncoderSettingsProvider *fbProvider = + new FacebookEncoderSettingsProvider(this); + connect(fbProvider, + &FacebookEncoderSettingsProvider::newSettings, this, + &PreStreamWizard::providerEncoderSettings); + connect(fbProvider, + &FacebookEncoderSettingsProvider::returnErrorDescription, + this, &PreStreamWizard::providerError); + fbProvider->setEncoderRequest(currentSettings_); + fbProvider->run(); + + } else { + QString errorTitle = QTStr( + "PreLiveWizard.Configure.ServiceNotAvailable.Title"); + QString errorDescription = QTStr( + "PreLiveWizard.Configure.ServiceNotAvailable.Description"); + providerError(errorTitle, errorDescription); + } } -PreStreamWizard::~PreStreamWizard() +// SLOTS ------------- +void PreStreamWizard::providerEncoderSettings( + QSharedPointer response) { - currentSettings_ = nullptr; + blog(LOG_INFO, "PreStreamWizard got new settings response"); + newSettingsMap_ = response; + if (this->currentId() == Page_Loading) { + this->next(); + } +} + +void PreStreamWizard::providerError(QString title, QString description) +{ + // TODO: Add Wizard Page with finish that shows this } void PreStreamWizard::onPageChanged(int id) @@ -49,13 +90,18 @@ void PreStreamWizard::onPageChanged(int id) if (id == Page_StartPrompt) { setOption(QWizard::NoCancelButton, false); } + + if (id == Page_Loading) { + requestSettings(); + } } void PreStreamWizard::onUserSelectResolution(QSize newSize) { blog(LOG_INFO, "Selected res %d x %d", newSize.width(), newSize.height()); - userSelectedNewRes_ = newSize; + EncoderSettingsRequest *req = currentSettings_.get(); + req->userSelectedResolution = QVariant(newSize); } } // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index 982fcbd7fd3cd5..973fb5f2bbd596 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -8,6 +8,13 @@ namespace StreamWizard { +/* + ** The pre-stream wizard is a workflow focused on delivering encoder settings + ** specfic for the streaming destination before going Live. + ** There is a launch context to know if launched from settings, otherwise + ** we'll later add a wizard page and button for going live after new settings. + */ + class PreStreamWizard : public QWizard { Q_OBJECT @@ -15,14 +22,15 @@ class PreStreamWizard : public QWizard { PreStreamWizard(Destination dest, LaunchContext launchContext, QSharedPointer currentSettings, QWidget *parent = nullptr); - ~PreStreamWizard(); private: // External State Destination destination_; LaunchContext launchContext_; QSharedPointer currentSettings_; - QSize userSelectedNewRes_; + QSharedPointer newSettingsMap_; + + void requestSettings(); signals: // User left the wizard with intention to continue streaming @@ -34,11 +42,20 @@ class PreStreamWizard : public QWizard { // User ready to start stream // If newSettings is not null, they should be applied before stream // If newSettings is null, user or wizard did not opt to apply changes. - void startStreamWithSettings( - QSharedPointer newSettingsOrNull); + void + startStreamWithSettings(QSharedPointer newSettingsOrNull); // Apply settings, don't start stream. e.g., is configuring from settings - void applySettings(QSharedPointer newSettings); + void applySettings(QSharedPointer newSettings); + + // All configuration providers must call one of these exclusively when done to + // continue the wizard. This is the contract any servicer's + // configuration provider must fulfill +public slots: + // On success, returns a EncoderSettingsResponse object + void providerEncoderSettings(QSharedPointer response); + // Title and description to show the user. + void providerError(QString title, QString description); private slots: void onPageChanged(int id); diff --git a/UI/streaming-settings-util.cpp b/UI/streaming-settings-util.cpp index 4100a81872573c..2e6864d15157ef 100644 --- a/UI/streaming-settings-util.cpp +++ b/UI/streaming-settings-util.cpp @@ -41,7 +41,7 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( /* Stream info */ currentSettings->videoType = StreamWizard::VideoType::live; // only live and rmpts is supported for now - currentSettings->protocol = StreamWizard::StreamProtocol::rmpts; + currentSettings->protocol = StreamWizard::StreamProtocol::rtmps; currentSettings->serverUrl = bstrdup(obs_data_get_string(settings, "server")); diff --git a/cmake/Modules/CopyMSVCBins.cmake b/cmake/Modules/CopyMSVCBins.cmake index 504d7b1ca511d0..938fc853730f47 100644 --- a/cmake/Modules/CopyMSVCBins.cmake +++ b/cmake/Modules/CopyMSVCBins.cmake @@ -159,6 +159,7 @@ file(GLOB QT_DEBUG_BIN_FILES "${Qt5Core_DIR}/../../../bin/Qt5Widgetsd.dll" "${Qt5Core_DIR}/../../../bin/Qt5Svgd.dll" "${Qt5Core_DIR}/../../../bin/Qt5Xmld.dll" + "${Qt5Core_DIR}/../../../bin/Qt5Networkd.dll" "${Qt5Core_DIR}/../../../bin/libGLESv2d.dll" "${Qt5Core_DIR}/../../../bin/libEGLd.dll") file(GLOB QT_DEBUG_PLAT_BIN_FILES @@ -177,6 +178,7 @@ file(GLOB QT_BIN_FILES "${Qt5Core_DIR}/../../../bin/Qt5Widgets.dll" "${Qt5Core_DIR}/../../../bin/Qt5Svg.dll" "${Qt5Core_DIR}/../../../bin/Qt5Xml.dll" + "${Qt5Core_DIR}/../../../bin/Qt5Network.dll" "${Qt5Core_DIR}/../../../bin/libGLESv2.dll" "${Qt5Core_DIR}/../../../bin/libEGL.dll") file(GLOB QT_PLAT_BIN_FILES From 3132ac411739e473c6b4f3f77f340fb015069ccf Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Mon, 28 Sep 2020 16:44:24 -0700 Subject: [PATCH 05/13] UI: PreStream Wizard parse & display options This commit changes how we handle the response and pass fields between the encoder configuration request and the displaying to the user. Some spelling issues were changed. Biggest change from previous commit is that it's shown to be cleaner to use a sparse map to store settings which enables different service providers to provide different amounts of configuration. Also the usage of a Pair struct allows us to track us a user wants to apply a specific setting in the same data structure that the UI uses: one less conversion. Todo / known omissions as of this commit: Loading screen needs to be polished or removed, as of here it's empty. Need to build error display page. Next button on check-box page should say "Apply Settings" Ending page. --- UI/CMakeLists.txt | 4 + UI/data/locale/en-US.ini | 18 +- .../encoder-settings-provider-facebook.cpp | 21 +- UI/pre-stream-wizard/page-select-settings.cpp | 203 ++++++++++++++++++ UI/pre-stream-wizard/page-select-settings.hpp | 52 +++++ UI/pre-stream-wizard/page-start-prompt.cpp | 1 - .../pre-stream-current-settings.hpp | 2 - UI/pre-stream-wizard/pre-stream-wizard.cpp | 16 +- UI/pre-stream-wizard/pre-stream-wizard.hpp | 7 + .../setting-selection-row.cpp | 77 +++++++ .../setting-selection-row.hpp | 59 +++++ 11 files changed, 440 insertions(+), 20 deletions(-) create mode 100644 UI/pre-stream-wizard/page-select-settings.cpp create mode 100644 UI/pre-stream-wizard/page-select-settings.hpp create mode 100644 UI/pre-stream-wizard/setting-selection-row.cpp create mode 100644 UI/pre-stream-wizard/setting-selection-row.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index fadb8963a5a043..d9fdebdb555b83 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -194,6 +194,8 @@ set(obs_SOURCES pre-stream-wizard/pre-stream-wizard.cpp pre-stream-wizard/page-input-display.cpp pre-stream-wizard/page-start-prompt.cpp + pre-stream-wizard/page-select-settings.cpp + pre-stream-wizard/setting-selection-row.cpp pre-stream-wizard/encoder-settings-provider-facebook.cpp obs-app.cpp window-dock.cpp @@ -271,6 +273,8 @@ set(obs_HEADERS pre-stream-wizard/pre-stream-wizard.hpp pre-stream-wizard/page-input-display.hpp pre-stream-wizard/page-start-prompt.hpp + pre-stream-wizard/page-select-settings.hpp + pre-stream-wizard/setting-selection-row.hpp pre-stream-wizard/encoder-settings-provider-facebook.hpp obs-app.hpp platform.hpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index e2d52da184b6f5..c63d5b0db9d068 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -715,9 +715,9 @@ PreLiveWizard.Prompt.Subtitle.Default="The streaming wizard suggests the most up PreLiveWizard.Prompt.Subtitle.FB="Your destination is set to Facebook Live." PreLiveWizard.Prompt.Explainer="The Streaming wizard suggests the most up-to-date settings to improve your stream’s reliability, quality, and latency." PreLiveWizard.Prompt.ResSelectTitle="Select a resolution to stream:" -PreLiveWizard.Prompt.FBResolutionHelp.FB="If you’re unsure, click Open Live Producer, then click on the \"Stream Health\" tab and scroll down to find your maximum resolution." -PreLiveWizard.Prompt.FBResolutionHelpButton.FB="Open Live Producer" -PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip="Opens Facebook Live Producer in your default internet browser" +PreLiveWizard.Prompt.FBResolutionHelp.FB="If you’re unsure, click Open Facebook Live, then click on the \"Stream Health\" tab and scroll down to find your maximum resolution." +PreLiveWizard.Prompt.FBResolutionHelpButton.FB="Open Facebook Live" +PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip="Opens Facebook Facebook Live in your default internet browser" PreLiveWizard.Prompt.Resolution.720="720p (Recommended for general purpose)" PreLiveWizard.Prompt.Resolution.1080="1080p (Recommended for gaming)" PreLiveWizard.Prompt.Resolution.Current="%1x%2 (Current setting)" @@ -725,7 +725,9 @@ PreLiveWizard.Configure.ServiceNotAvailable.Title="Service not supported" PreLiveWizard.Configure.ServiceNotAvailable.Description="The service selected in stream settings does not yet have an encoder configuration service." PreLiveWizard.Configure.Error.Url="Settings setup failed. Query: " PreLiveWizard.Configure.Error.JsonParse="Problem with server response" -PreLiveWizard.Configure.Error.JsonParseLonger="Wizard is unavailble at the moment." +PreLiveWizard.Configure.Error.JsonParse.Description="Wizard is unavailble at the moment." +PreLiveWizard.Selection.Title="Settings Suggested" +PreLiveWizard.Selection.Description="Suggested video settings for your best stream." # basic mode 'output' settings @@ -742,6 +744,10 @@ Basic.Settings.Output.Mode="Output Mode" Basic.Settings.Output.Mode.Simple="Simple" Basic.Settings.Output.Mode.Adv="Advanced" Basic.Settings.Output.Mode.FFmpeg="FFmpeg Output" +Basic.Settings.Output.Mode.CodecProfile="Profile" +Basic.Settings.Output.Mode.CodecLevel="Level" +Basic.Settings.Output.Mode.RateControl="Rate Control" +Basic.Settings.Output.Mode.StreamBuffer="Stream Buffer" Basic.Settings.Output.UseReplayBuffer="Enable Replay Buffer" Basic.Settings.Output.ReplayBuffer.SecondsMax="Maximum Replay Time" Basic.Settings.Output.ReplayBuffer.MegabytesMax="Maximum Memory (Megabytes)" @@ -818,6 +824,10 @@ Basic.Settings.Output.Adv.FFmpeg.AEncoder="Audio Encoder" Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Audio Encoder Settings (if any)" Basic.Settings.Output.Adv.FFmpeg.MuxerSettings="Muxer Settings (if any)" Basic.Settings.Output.Adv.FFmpeg.GOPSize="Keyframe interval (frames)" +Basic.Settings.Output.Adv.FFmpeg.GOPType="GOP Type" +Basic.Settings.Output.Adv.FFmpeg.GOPClosed="Closed GOP" +Basic.Settings.Output.Adv.FFmpeg.BFrames="B-Frames" +Basic.Settings.Output.Adv.FFmpeg.ReferenceFrames="Reference frames" Basic.Settings.Output.Adv.FFmpeg.IgnoreCodecCompat="Show all codecs (even if potentially incompatible)" # Screenshot diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp index 99ade9edec0a64..7d4d27a370e50c 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp @@ -129,12 +129,17 @@ void addInt(const QJsonObject &json, const char *jsonKey, SettingsMap *map, } } -void addDouble(const QJsonObject &json, const char *jsonKey, SettingsMap *map, - const char *mapKey) +void addStringDouble(const QJsonObject &json, const char *jsonKey, + SettingsMap *map, const char *mapKey) { - if (json[jsonKey].isDouble()) { - map->insert(mapKey, - QPair(QVariant(json[jsonKey].toDouble()), true)); + if (!json[jsonKey].isString()) { + return; + } + bool converted = false; + QString valueString = json[jsonKey].toString(); + double numberValue = valueString.toDouble(&converted); + if (converted) { + map->insert(mapKey, QPair(QVariant(numberValue), true)); } } @@ -152,7 +157,7 @@ void addBool(const QJsonObject &json, const char *jsonKey, SettingsMap *map, { if (json[jsonKey].isBool()) { map->insert(mapKey, - QPair(QVariant(json[jsonKey].isBool()), true)); + QPair(QVariant(json[jsonKey].toBool()), true)); } } @@ -202,8 +207,8 @@ void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) SettingsResponseKeys.videoWidth); addInt(videoSettingsJsob, "video_height", settingsMap, SettingsResponseKeys.videoHeight); - addDouble(videoSettingsJsob, "video_framerate", settingsMap, - SettingsResponseKeys.framerate); + addStringDouble(videoSettingsJsob, "video_framerate", settingsMap, + SettingsResponseKeys.framerate); addQString(videoSettingsJsob, "video_h264_profile", settingsMap, SettingsResponseKeys.h264Profile); addQString(videoSettingsJsob, "video_h264_level", settingsMap, diff --git a/UI/pre-stream-wizard/page-select-settings.cpp b/UI/pre-stream-wizard/page-select-settings.cpp new file mode 100644 index 00000000000000..ae807d46edcbd1 --- /dev/null +++ b/UI/pre-stream-wizard/page-select-settings.cpp @@ -0,0 +1,203 @@ +#include "page-select-settings.hpp" + +#include +#include +#include +#include +#include +#include + +#include "obs-app.hpp" +#include "setting-selection-row.hpp" + +namespace StreamWizard { + +SelectionPage::SelectionPage(QWidget *parent) : QWizardPage(parent) +{ + this->setTitle(QTStr("PreLiveWizard.Selection.Title")); + setupLayout(); +} + +SelectionPage::~SelectionPage() +{ + // nop +} + +void SelectionPage::setupLayout() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + setLayout(mainLayout); + + // Description to user what scroll area contains + descriptionLabel_ = + new QLabel(QTStr("PreLiveWizard.Selection.Description")); + mainLayout->addWidget(descriptionLabel_); + + // Add Scroll area widget + scrollArea_ = new QScrollArea(); + scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + scrollArea_->setWidgetResizable(true); + + // ScrollArea_ holds a widget: + scrollBoxWidget_ = new QWidget(); + scrollArea_->setWidget(scrollBoxWidget_); + + // Scroll's Widget requires a layout + scrollVBoxLayout_ = new QVBoxLayout(); + + scrollBoxWidget_->setLayout(scrollVBoxLayout_); + scrollBoxWidget_->show(); + + // Add Scroll Aread to mainlayout + mainLayout->addWidget(scrollArea_); +} + +void SelectionPage::setSettingsMap(QSharedPointer settingsMap) +{ + if (settingsMap_ != nullptr) { + return; + } + settingsMap_ = settingsMap; + SettingsMap *mapInfo = settingsMap_.get(); + + if (mapInfo->contains(SettingsResponseKeys.videoBitrate)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.videoBitrate).first; + QString bitrateString = QString::number(data.toInt()) + "kbps"; + addRow(QTStr("Basic.Settings.Output.VideoBitrate"), + bitrateString, SettingsResponseKeys.videoBitrate); + } + + // Uses SettingsResponseKeys.videoHeight to signal change + if (mapInfo->contains(SettingsResponseKeys.videoWidth) && + mapInfo->contains(SettingsResponseKeys.videoHeight)) { + int vidW = mapInfo->value(SettingsResponseKeys.videoWidth) + .first.toInt(); + QString videoWidthString = QString::number(vidW); + int vidH = mapInfo->value(SettingsResponseKeys.videoHeight) + .first.toInt(); + QString videoHeightString = QString::number(vidH); + QString valueString = + videoWidthString + "x" + videoHeightString; + + addRow(QTStr("Basic.Settings.Video.ScaledResolution"), + valueString, SettingsResponseKeys.videoHeight); + } + + if (mapInfo->contains(SettingsResponseKeys.framerate)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.framerate).first; + double framerate = data.toDouble(); + QString fpsString = QString().sprintf("%.3f", framerate); + addRow(QTStr("Basic.Settings.Video.FPS"), fpsString, + SettingsResponseKeys.framerate); + } + + if (mapInfo->contains(SettingsResponseKeys.h264Profile)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.h264Profile).first; + QString profileString = data.toString(); + addRow(QTStr("Basic.Settings.Output.Mode.CodecProfile"), + profileString, SettingsResponseKeys.h264Profile); + } + + if (mapInfo->contains(SettingsResponseKeys.h264Level)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.h264Level).first; + QString level = data.toString(); + addRow(QTStr("Basic.Settings.Output.Mode.CodecLevel"), level, + SettingsResponseKeys.h264Level); + } + + if (mapInfo->contains(SettingsResponseKeys.gopSizeInFrames)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.gopSizeInFrames) + .first; + QString gopFramesString = QString::number(data.toInt()); + addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.GOPSize"), + gopFramesString, SettingsResponseKeys.gopSizeInFrames); + } + + if (mapInfo->contains(SettingsResponseKeys.gopType)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.gopType).first; + QString gopTypeString = data.toString(); + addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.GOPType"), + gopTypeString, SettingsResponseKeys.gopType); + } + + if (mapInfo->contains(SettingsResponseKeys.gopClosed)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.gopClosed).first; + QString gopClosedString = data.toBool() ? QTStr("Yes") + : QTStr("No"); + addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.GOPClosed"), + gopClosedString, SettingsResponseKeys.gopClosed); + } + + if (mapInfo->contains(SettingsResponseKeys.gopBFrames)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.gopBFrames).first; + QString bFramesString = QString::number(data.toInt()); + addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.BFrames"), + bFramesString, SettingsResponseKeys.gopBFrames); + } + + if (mapInfo->contains(SettingsResponseKeys.gopRefFrames)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.gopRefFrames).first; + QString gopRefFramesCountString = QString::number(data.toInt()); + addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.ReferenceFrames"), + gopRefFramesCountString, + SettingsResponseKeys.gopRefFrames); + } + + if (mapInfo->contains(SettingsResponseKeys.streamRateControlMode)) { + QVariant data = mapInfo->value(SettingsResponseKeys + .streamRateControlMode) + .first; + QString rateControlString = data.toString().toUpper(); + addRow(QTStr("Basic.Settings.Output.Mode.RateControl"), + rateControlString, + SettingsResponseKeys.streamRateControlMode); + } + + if (mapInfo->contains(SettingsResponseKeys.streamBufferSize)) { + QVariant data = + mapInfo->value(SettingsResponseKeys.streamBufferSize) + .first; + QString bufferSizeString = + QString::number(data.toInt()) + " Kb"; + addRow(QTStr("Basic.Settings.Output.Mode.StreamBuffer"), + bufferSizeString, SettingsResponseKeys.streamBufferSize); + } +} + +void SelectionPage::addRow(QString title, QString value, const char *mapKey) +{ + SelectionRow *row = new SelectionRow(); + row->setName(title); + row->setValueLabel(value); + row->setPropertyKey(mapKey); + scrollVBoxLayout_->addWidget(row); + connect(row, &SelectionRow::didChangeSelectedStatus, this, + &SelectionPage::checkboxRowChanged); +} + +void SelectionPage::checkboxRowChanged(const char *propertyKey, bool selected) +{ + if (settingsMap_ == nullptr) { + return; + } + + SettingsMap *mapInfo = settingsMap_.get(); + + if (mapInfo == nullptr || !mapInfo->contains(propertyKey)) { + return; + } + + QPair dataPair = mapInfo->value(propertyKey); + dataPair.second = selected; +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-select-settings.hpp b/UI/pre-stream-wizard/page-select-settings.hpp new file mode 100644 index 00000000000000..80ade3cf2474e8 --- /dev/null +++ b/UI/pre-stream-wizard/page-select-settings.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "pre-stream-current-settings.hpp" + +/* +Shows user encoder configuration options and allows them to select and apply +each. For exmaple can apply a resolution limit but opt-out of using b-frames +even if recommended. +*/ + +namespace StreamWizard { + +class SelectionPage : public QWizardPage { + Q_OBJECT + +public: + SelectionPage(QWidget *parent = nullptr); + ~SelectionPage(); + + // Sets data to layout scrollbox + void setSettingsMap(QSharedPointer settingsMap); + +private: + // Main UI setup, setSettingsMap finished layout with data + void setupLayout(); + + void mapContainsVariant(); + // Adds a row only if map contains values for it + void addRow(QString title, QString value, const char *mapKey); + + // Data + QSharedPointer settingsMap_; + + // Layouts and subwidgets + QLabel *descriptionLabel_; + QScrollArea *scrollArea_; + QWidget *scrollBoxWidget_; + QVBoxLayout *scrollVBoxLayout_; + +private slots: + void checkboxRowChanged(const char *propertyKey, bool selected); + +}; // class SelectionPage + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-start-prompt.cpp b/UI/pre-stream-wizard/page-start-prompt.cpp index 35c9e007c9d846..8c847eeac9b120 100644 --- a/UI/pre-stream-wizard/page-start-prompt.cpp +++ b/UI/pre-stream-wizard/page-start-prompt.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "obs-app.hpp" diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.hpp b/UI/pre-stream-wizard/pre-stream-current-settings.hpp index a4a53b213ecc59..cc52ad09296fd1 100644 --- a/UI/pre-stream-wizard/pre-stream-current-settings.hpp +++ b/UI/pre-stream-wizard/pre-stream-current-settings.hpp @@ -85,7 +85,6 @@ static const struct { const char *videoHeight; //int const char *framerate; // double (FPS) const char *videoBitrate; //int - const char *audioBitrate; // int const char *protocol; // string const char *videoCodec; // string const char *h264Profile; // string ("High") @@ -102,7 +101,6 @@ static const struct { .videoHeight = "videoHeight", .framerate = "framerate", .videoBitrate = "videoBitrate", - .audioBitrate = "audioBitrate", .protocol = "protocol", .videoCodec = "videoCodec", .h264Profile = "h264Profile", diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index b9d2150ff4c813..fb9ec0a511a198 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -10,6 +10,7 @@ #include "page-input-display.hpp" #include "page-start-prompt.hpp" +#include "page-select-settings.hpp" namespace StreamWizard { @@ -32,17 +33,18 @@ PreStreamWizard::PreStreamWizard( // First Page: Explain to the user and confirm sending to API QSize currentRes(currentSettings_->videoWidth, currentSettings_->videoHeight); - StartPage *startPage = + startPage_ = new StartPage(destination_, launchContext_, currentRes, this); - setPage(Page_StartPrompt, startPage); - connect(startPage, &StartPage::userSelectedResolution, this, + setPage(Page_StartPrompt, startPage_); + connect(startPage_, &StartPage::userSelectedResolution, this, &PreStreamWizard::onUserSelectResolution); // Loading page: Shown when loading new settings setPage(Page_Loading, new QWizardPage()); - // Suggestion Selection Pahe - setPage(Page_Selections, new QWizardPage()); + // Suggestion Selection Page + selectionPage_ = new SelectionPage(); + setPage(Page_Selections, selectionPage_); } void PreStreamWizard::requestSettings() @@ -94,6 +96,10 @@ void PreStreamWizard::onPageChanged(int id) if (id == Page_Loading) { requestSettings(); } + + if (id == Page_Selections) { + selectionPage_->setSettingsMap(newSettingsMap_); + } } void PreStreamWizard::onUserSelectResolution(QSize newSize) diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index 973fb5f2bbd596..22b89cc19e46dc 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -8,6 +8,9 @@ namespace StreamWizard { +class StartPage; +class SelectionPage; + /* ** The pre-stream wizard is a workflow focused on delivering encoder settings ** specfic for the streaming destination before going Live. @@ -24,6 +27,10 @@ class PreStreamWizard : public QWizard { QWidget *parent = nullptr); private: + // Pages + StartPage *startPage_; + SelectionPage *selectionPage_; + // External State Destination destination_; LaunchContext launchContext_; diff --git a/UI/pre-stream-wizard/setting-selection-row.cpp b/UI/pre-stream-wizard/setting-selection-row.cpp new file mode 100644 index 00000000000000..a6bd17b1a693a8 --- /dev/null +++ b/UI/pre-stream-wizard/setting-selection-row.cpp @@ -0,0 +1,77 @@ +#include "setting-selection-row.hpp" + +#include + +namespace StreamWizard { + +SelectionRow::SelectionRow(QWidget *parent) : QWidget(parent) +{ + createLayout(); +} +SelectionRow::~SelectionRow() +{ + // nop +} + +void SelectionRow::createLayout() +{ + QHBoxLayout *mainlayout = new QHBoxLayout(this); + QMargins margins = QMargins(3, 3, 3, 3); + mainlayout->setContentsMargins(margins); + checkboxValueString_ = QString(); + checkbox_ = new QCheckBox(checkboxValueString_); + checkbox_->setChecked(true); + + setLayout(mainlayout); + + mainlayout->addWidget(checkbox_); + QSpacerItem *checkboxSpace = new QSpacerItem( + 6, 6, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + mainlayout->addSpacerItem(checkboxSpace); + connect(checkbox_, &QCheckBox::stateChanged, this, + &SelectionRow::checkboxUpdated); +} +void SelectionRow::checkboxUpdated() +{ + emit didChangeSelectedStatus(propertyKey_, checkbox_->isChecked()); +} + +void SelectionRow::updateLabel() +{ + checkboxValueString_ = name_ + separator_ + valueLabel_; + checkbox_->setText(checkboxValueString_); +} + +void SelectionRow::setName(QString newName) +{ + name_ = newName; + updateLabel(); +} + +QString SelectionRow::getName() +{ + return name_; +} + +void SelectionRow::setValueLabel(QString newLabel) +{ + valueLabel_ = newLabel; + updateLabel(); +} + +QString SelectionRow::getValueLabel() +{ + return valueLabel_; +} + +void SelectionRow::setPropertyKey(const char *newKey) +{ + propertyKey_ = newKey; +} + +const char *SelectionRow::getPropertyKey() +{ + return propertyKey_; +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/setting-selection-row.hpp b/UI/pre-stream-wizard/setting-selection-row.hpp new file mode 100644 index 00000000000000..0107c1ea1f6484 --- /dev/null +++ b/UI/pre-stream-wizard/setting-selection-row.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "pre-stream-current-settings.hpp" + +/* +Shows user encoder configuration options and allows them to select and apply +each. For exmaple can apply a resolution limit but opt-out of using b-frames +even if recommended. +*/ + +namespace StreamWizard { + +class SelectionRow : public QWidget { + Q_OBJECT + +public: + SelectionRow(QWidget *parent = nullptr); + ~SelectionRow(); + + // User facing name to be shown to the user. ("Resolution") + void setName(QString newName); + QString getName(); + + // Value in a user-presentable manner. ("1920x180p") + void setValueLabel(QString newValue); + QString getValueLabel(); + + // Key to mapping property to Settings Map + void setPropertyKey(const char *newKey); + const char *getPropertyKey(); + +signals: + // User changed if they want to apply an option + void didChangeSelectedStatus(const char *propertyKey, bool selected); + +private: + void createLayout(); + void checkboxUpdated(); + void updateLabel(); + + // Visual from upper class + QString name_; + QString valueLabel_; + const char *propertyKey_; + + // Layout and subwidgets + QString separator_ = ": "; + QString checkboxValueString_; + QCheckBox *checkbox_; + +}; // class SelectionRow + +} // namespace StreamWizard From dc2c1a71c56fdebc190f482ab25ffad83210e65e Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Mon, 28 Sep 2020 21:29:48 -0700 Subject: [PATCH 06/13] UI: PreStream Wizard add completion page Adds a completion page with content that depends on destinations. Back button is disabled on the last page where application will take place since updating settings is undoable. Various code fixes. --- UI/CMakeLists.txt | 2 + UI/common-settings.cpp | 10 +-- UI/data/locale/en-US.ini | 4 +- .../encoder-settings-provider-facebook.cpp | 21 +++++- .../encoder-settings-provider-facebook.hpp | 1 - UI/pre-stream-wizard/page-completed.cpp | 69 +++++++++++++++++++ UI/pre-stream-wizard/page-completed.hpp | 26 +++++++ UI/pre-stream-wizard/page-select-settings.cpp | 13 ++-- UI/pre-stream-wizard/page-select-settings.hpp | 1 - UI/pre-stream-wizard/page-start-prompt.cpp | 6 -- UI/pre-stream-wizard/page-start-prompt.hpp | 1 - .../pre-stream-current-settings.hpp | 23 +++---- UI/pre-stream-wizard/pre-stream-wizard.cpp | 15 +++- .../setting-selection-row.cpp | 4 -- .../setting-selection-row.hpp | 1 - UI/streaming-settings-util.cpp | 2 +- 16 files changed, 153 insertions(+), 46 deletions(-) create mode 100644 UI/pre-stream-wizard/page-completed.cpp create mode 100644 UI/pre-stream-wizard/page-completed.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index d9fdebdb555b83..86e7dc0a78abaf 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -195,6 +195,7 @@ set(obs_SOURCES pre-stream-wizard/page-input-display.cpp pre-stream-wizard/page-start-prompt.cpp pre-stream-wizard/page-select-settings.cpp + pre-stream-wizard/page-completed.cpp pre-stream-wizard/setting-selection-row.cpp pre-stream-wizard/encoder-settings-provider-facebook.cpp obs-app.cpp @@ -274,6 +275,7 @@ set(obs_HEADERS pre-stream-wizard/page-input-display.hpp pre-stream-wizard/page-start-prompt.hpp pre-stream-wizard/page-select-settings.hpp + pre-stream-wizard/page-completed.hpp pre-stream-wizard/setting-selection-row.hpp pre-stream-wizard/encoder-settings-provider-facebook.hpp obs-app.hpp diff --git a/UI/common-settings.cpp b/UI/common-settings.cpp index 2c5d641443d95a..ecfd1dc07845cd 100644 --- a/UI/common-settings.cpp +++ b/UI/common-settings.cpp @@ -2,7 +2,7 @@ #include "audio-encoders.hpp" -bool IsAdvancedMode(config_t *config) +static bool IsAdvancedMode(config_t *config) { const char *outputMode = config_get_string(config, "Output", "Mode"); return (strcmp(outputMode, "Advanced") == 0); @@ -16,7 +16,7 @@ OBSData CommonSettings::GetDataFromJsonFile(const char *jsonFile) int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile); if (ret > 0) { BPtr jsonData = os_quick_read_utf8_file(fullPath); - if (!!jsonData) { + if (jsonData) { data = obs_data_create_from_json(jsonData); } } @@ -145,7 +145,7 @@ int CommonSettings::GetStreamingAudioBitrate(config_t *config) int CommonSettings::GetSimpleAudioBitrate(config_t *config) { - int bitrate = (int)config_get_uint(config, "SimpleOutput", "ABitrate"); + int bitrate = config_get_uint(config, "SimpleOutput", "ABitrate"); return FindClosestAvailableAACBitrate(bitrate); } @@ -163,9 +163,11 @@ int CommonSettings::GetAdvancedAudioBitrateForTrack(config_t *config, "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", }; - // Sanity check for out of bounds + // Sanity check for out of bounds, clamp to bounds if (trackIndex > 5) trackIndex = 5; + if (trackIndex < 0) + trackIndex = 0; int bitrate = (int)config_get_uint(config, "AdvOut", names[trackIndex]); return FindClosestAvailableAACBitrate(bitrate); diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index c63d5b0db9d068..d4bcece8443c99 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -728,7 +728,9 @@ PreLiveWizard.Configure.Error.JsonParse="Problem with server response" PreLiveWizard.Configure.Error.JsonParse.Description="Wizard is unavailble at the moment." PreLiveWizard.Selection.Title="Settings Suggested" PreLiveWizard.Selection.Description="Suggested video settings for your best stream." - +PreLiveWizard.Completed.Title="Encoder configured" +PreLiveWizard.Completed.BodyText="Applied suggested settings and encoder is setup to stream. You can now close the wizard." +PreLiveWizard.Completed.FacebookOnly="To finish going live go to Facebook Live:" # basic mode 'output' settings Basic.Settings.Output="Output" diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp index 7d4d27a370e50c..22768d7a81b0de 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include "obs-app.hpp" #include "obs-config.h" @@ -57,7 +56,7 @@ void FacebookEncoderSettingsProvider::makeRequest(QUrl &url) "application/json"); // GET is made async - settingsReply_ = restclient_->get(request); + restclient_->get(request); // This is the callback when data is ready connect(restclient_, &QNetworkAccessManager::finished, this, &FacebookEncoderSettingsProvider::handleResponse); @@ -66,7 +65,7 @@ void FacebookEncoderSettingsProvider::makeRequest(QUrl &url) QUrlQuery FacebookEncoderSettingsProvider::inputVideoQueryFromCurrentSettings() { // Get input settings, shorten name - EncoderSettingsRequest *input = currentSettings_.get(); + EncoderSettingsRequest *input = currentSettings_.data(); QUrlQuery inputVideoSettingsQuery; inputVideoSettingsQuery.addQueryItem("video_type", "live"); @@ -126,6 +125,10 @@ void addInt(const QJsonObject &json, const char *jsonKey, SettingsMap *map, if (json[jsonKey].isDouble()) { map->insert(mapKey, QPair(QVariant(json[jsonKey].toInt()), true)); + } else { + blog(LOG_WARNING, + "FacebookEncoderSettingsProvider could not parse %s to Int", + jsonKey); } } @@ -140,6 +143,10 @@ void addStringDouble(const QJsonObject &json, const char *jsonKey, double numberValue = valueString.toDouble(&converted); if (converted) { map->insert(mapKey, QPair(QVariant(numberValue), true)); + } else { + blog(LOG_WARNING, + "FacebookEncoderSettingsProvider couldn't parse %s to Double from String", + jsonKey); } } @@ -149,6 +156,10 @@ void addQString(const QJsonObject &json, const char *jsonKey, SettingsMap *map, if (json[jsonKey].isString()) { map->insert(mapKey, QPair(QVariant(json[jsonKey].toString()), true)); + } else { + blog(LOG_WARNING, + "FacebookEncoderSettingsProvider could not parse %s to Strng", + jsonKey); } } @@ -158,6 +169,10 @@ void addBool(const QJsonObject &json, const char *jsonKey, SettingsMap *map, if (json[jsonKey].isBool()) { map->insert(mapKey, QPair(QVariant(json[jsonKey].toBool()), true)); + } else { + blog(LOG_WARNING, + "FacebookEncoderSettingsProvider could not parse %s to Bool", + jsonKey); } } diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp index 6de922d2025086..112b4d6d25714b 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp @@ -37,7 +37,6 @@ class FacebookEncoderSettingsProvider : public QObject { private: QSharedPointer currentSettings_; QNetworkAccessManager *restclient_; - QNetworkReply *settingsReply_; void makeRequest(QUrl &url); QUrlQuery inputVideoQueryFromCurrentSettings(); diff --git a/UI/pre-stream-wizard/page-completed.cpp b/UI/pre-stream-wizard/page-completed.cpp new file mode 100644 index 00000000000000..3def54f158d5b0 --- /dev/null +++ b/UI/pre-stream-wizard/page-completed.cpp @@ -0,0 +1,69 @@ +#include "page-completed.hpp" + +#include +#include +#include +#include +#include +#include + +#include "obs-app.hpp" + +namespace StreamWizard { + +CompletedPage::CompletedPage(Destination destination, + LaunchContext launchContext, QWidget *parent) + : QWizardPage(parent) +{ + destination_ = destination; + launchContext_ = launchContext; + + setTitle(QTStr("PreLiveWizard.Completed.Title")); + + QVBoxLayout *mainlayout = new QVBoxLayout(this); + setLayout(mainlayout); + + // Later will suggest starting stream if launchContext is PreStream + QLabel *closeLabel = + new QLabel(QTStr("PreLiveWizard.Completed.BodyText"), this); + closeLabel->setWordWrap(true); + mainlayout->addWidget(closeLabel); + + if (destination_ == Destination::Facebook) { + mainlayout->addSpacerItem(new QSpacerItem(12, 12)); + + QLabel *facebookGoLiveLabel = new QLabel( + QTStr("PreLiveWizard.Completed.FacebookOnly"), this); + facebookGoLiveLabel->setWordWrap(true); + QPushButton *launchButton = new QPushButton( + QTStr("PreLiveWizard.Prompt.FBResolutionHelpButton.FB"), + this); + launchButton->setToolTip(QTStr( + "PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip")); + connect(launchButton, &QPushButton::clicked, this, + &CompletedPage::didPushOpenWebsite); + + mainlayout->addWidget(facebookGoLiveLabel); + mainlayout->addWidget(launchButton); + } +} + +void CompletedPage::didPushOpenWebsite() +{ + QUrl helpUrl; + + // Prepare per-destination + if (destination_ == Destination::Facebook) { + helpUrl = QUrl( + "https://www.facebook.com/live/producer/?ref=OBS_Wizard", + QUrl::TolerantMode); + } else { + return; + } + // Launch + if (helpUrl.isValid()) { + QDesktopServices::openUrl(helpUrl); + } +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-completed.hpp b/UI/pre-stream-wizard/page-completed.hpp new file mode 100644 index 00000000000000..b634ad19f6a966 --- /dev/null +++ b/UI/pre-stream-wizard/page-completed.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "pre-stream-current-settings.hpp" + +namespace StreamWizard { + +// Last Page +class CompletedPage : public QWizardPage { + Q_OBJECT + +public: + CompletedPage(Destination destination, LaunchContext launchContext, + QWidget *parent = nullptr); + +private: + Destination destination_; + LaunchContext launchContext_; + +private slots: + void didPushOpenWebsite(); + +}; // class CompletedPage + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-select-settings.cpp b/UI/pre-stream-wizard/page-select-settings.cpp index ae807d46edcbd1..383b1c5a72b8ca 100644 --- a/UI/pre-stream-wizard/page-select-settings.cpp +++ b/UI/pre-stream-wizard/page-select-settings.cpp @@ -16,11 +16,12 @@ SelectionPage::SelectionPage(QWidget *parent) : QWizardPage(parent) { this->setTitle(QTStr("PreLiveWizard.Selection.Title")); setupLayout(); -} -SelectionPage::~SelectionPage() -{ - // nop + setButtonText(QWizard::WizardButton::NextButton, + QTStr("Basic.AutoConfig.ApplySettings")); + setButtonText(QWizard::WizardButton::CommitButton, + QTStr("Basic.AutoConfig.ApplySettings")); + setCommitPage(true); } void SelectionPage::setupLayout() @@ -58,7 +59,7 @@ void SelectionPage::setSettingsMap(QSharedPointer settingsMap) return; } settingsMap_ = settingsMap; - SettingsMap *mapInfo = settingsMap_.get(); + SettingsMap *mapInfo = settingsMap_.data(); if (mapInfo->contains(SettingsResponseKeys.videoBitrate)) { QVariant data = @@ -190,7 +191,7 @@ void SelectionPage::checkboxRowChanged(const char *propertyKey, bool selected) return; } - SettingsMap *mapInfo = settingsMap_.get(); + SettingsMap *mapInfo = settingsMap_.data(); if (mapInfo == nullptr || !mapInfo->contains(propertyKey)) { return; diff --git a/UI/pre-stream-wizard/page-select-settings.hpp b/UI/pre-stream-wizard/page-select-settings.hpp index 80ade3cf2474e8..6ef4e6c495c184 100644 --- a/UI/pre-stream-wizard/page-select-settings.hpp +++ b/UI/pre-stream-wizard/page-select-settings.hpp @@ -22,7 +22,6 @@ class SelectionPage : public QWizardPage { public: SelectionPage(QWidget *parent = nullptr); - ~SelectionPage(); // Sets data to layout scrollbox void setSettingsMap(QSharedPointer settingsMap); diff --git a/UI/pre-stream-wizard/page-start-prompt.cpp b/UI/pre-stream-wizard/page-start-prompt.cpp index 8c847eeac9b120..ef3fa8e9a2dc0b 100644 --- a/UI/pre-stream-wizard/page-start-prompt.cpp +++ b/UI/pre-stream-wizard/page-start-prompt.cpp @@ -39,12 +39,6 @@ StartPage::StartPage(Destination dest, LaunchContext launchContext, resCurrentButton_->setChecked(true); } } - -StartPage::~StartPage() -{ - // nop -} - void StartPage::createLayout() { QVBoxLayout *mainlayout = new QVBoxLayout(this); diff --git a/UI/pre-stream-wizard/page-start-prompt.hpp b/UI/pre-stream-wizard/page-start-prompt.hpp index 85475f41bf5b40..418c30b6084fe9 100644 --- a/UI/pre-stream-wizard/page-start-prompt.hpp +++ b/UI/pre-stream-wizard/page-start-prompt.hpp @@ -16,7 +16,6 @@ class StartPage : public QWizardPage { public: StartPage(Destination dest, LaunchContext launchContext, QSize videoSize, QWidget *parent = nullptr); - ~StartPage(); signals: // emitted selected resolution from start page radio buttons diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.hpp b/UI/pre-stream-wizard/pre-stream-current-settings.hpp index cc52ad09296fd1..bcd98ed7cedc2b 100644 --- a/UI/pre-stream-wizard/pre-stream-current-settings.hpp +++ b/UI/pre-stream-wizard/pre-stream-current-settings.hpp @@ -97,20 +97,13 @@ static const struct { const char *streamRateControlMode; // string "CBR" const char *streamBufferSize; // int (5000 kb) } SettingsResponseKeys = { - .videoWidth = "videoWidth", - .videoHeight = "videoHeight", - .framerate = "framerate", - .videoBitrate = "videoBitrate", - .protocol = "protocol", - .videoCodec = "videoCodec", - .h264Profile = "h264Profile", - .h264Level = "h264Level", - .gopSizeInFrames = "gopSizeInFrames", - .gopType = "gopType", - .gopClosed = "gopClosed", - .gopBFrames = "gopBFrames", - .gopRefFrames = "gopRefFrames", - .streamRateControlMode = "streamRateControlMode", - .streamBufferSize = "streamBufferSize", + "videoWidth", "videoHeight", + "framerate", "videoBitrate", + "protocol", "videoCodec", + "h264Profile", "h264Level", + "gopSizeInFrames", "gopType", + "gopClosed", "gopBFrames", + "gopRefFrames", "streamRateControlMode", + "streamBufferSize", }; } diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index fb9ec0a511a198..d1edc1398af56c 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -11,10 +11,16 @@ #include "page-input-display.hpp" #include "page-start-prompt.hpp" #include "page-select-settings.hpp" +#include "page-completed.hpp" namespace StreamWizard { -enum PSW_Page { Page_StartPrompt, Page_Loading, Page_Selections }; +enum PSW_Page { + Page_StartPrompt, + Page_Loading, + Page_Selections, + Page_Complete, +}; PreStreamWizard::PreStreamWizard( Destination dest, LaunchContext launchContext, @@ -45,6 +51,11 @@ PreStreamWizard::PreStreamWizard( // Suggestion Selection Page selectionPage_ = new SelectionPage(); setPage(Page_Selections, selectionPage_); + + // Ending + Confirmation Page + CompletedPage *completedPage = + new CompletedPage(destination_, launchContext_, this); + setPage(Page_Complete, completedPage); } void PreStreamWizard::requestSettings() @@ -106,7 +117,7 @@ void PreStreamWizard::onUserSelectResolution(QSize newSize) { blog(LOG_INFO, "Selected res %d x %d", newSize.width(), newSize.height()); - EncoderSettingsRequest *req = currentSettings_.get(); + EncoderSettingsRequest *req = currentSettings_.data(); req->userSelectedResolution = QVariant(newSize); } diff --git a/UI/pre-stream-wizard/setting-selection-row.cpp b/UI/pre-stream-wizard/setting-selection-row.cpp index a6bd17b1a693a8..86fe8012b07880 100644 --- a/UI/pre-stream-wizard/setting-selection-row.cpp +++ b/UI/pre-stream-wizard/setting-selection-row.cpp @@ -8,10 +8,6 @@ SelectionRow::SelectionRow(QWidget *parent) : QWidget(parent) { createLayout(); } -SelectionRow::~SelectionRow() -{ - // nop -} void SelectionRow::createLayout() { diff --git a/UI/pre-stream-wizard/setting-selection-row.hpp b/UI/pre-stream-wizard/setting-selection-row.hpp index 0107c1ea1f6484..18a8d0dfa531fc 100644 --- a/UI/pre-stream-wizard/setting-selection-row.hpp +++ b/UI/pre-stream-wizard/setting-selection-row.hpp @@ -21,7 +21,6 @@ class SelectionRow : public QWidget { public: SelectionRow(QWidget *parent = nullptr); - ~SelectionRow(); // User facing name to be shown to the user. ("Resolution") void setName(QString newName); diff --git a/UI/streaming-settings-util.cpp b/UI/streaming-settings-util.cpp index 2e6864d15157ef..6d1efb3a40ea77 100644 --- a/UI/streaming-settings-util.cpp +++ b/UI/streaming-settings-util.cpp @@ -39,6 +39,7 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( new StreamWizard::EncoderSettingsRequest()); /* Stream info */ + currentSettings->videoType = StreamWizard::VideoType::live; // only live and rmpts is supported for now currentSettings->protocol = StreamWizard::StreamProtocol::rtmps; @@ -56,7 +57,6 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( currentSettings->videoHeight = resolutionXY[1]; currentSettings->framerate = CommonSettings::GetConfigFPSDouble(config); - // This ONLY works for simple output right now. If they're in advanced: no currentSettings->videoBitrate = CommonSettings::GetVideoBitrateInUse(config); currentSettings->videoCodec = StreamWizard::VideoCodec::h264; From 1ee85d5968abcf6dfaf39b9aa1db2fdb680440e5 Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Wed, 30 Sep 2020 18:16:26 -0700 Subject: [PATCH 07/13] UI: Add error page to PreStream Wizard Add page that shows user text explanation of the issue and prompts to close the wizard. Also handles the case where network response times out which is now attached to the pull request screen shot. --- UI/CMakeLists.txt | 2 + UI/data/locale/en-US.ini | 5 ++ .../encoder-settings-provider-facebook.cpp | 31 +++++++++- .../encoder-settings-provider-facebook.hpp | 3 + UI/pre-stream-wizard/page-completed.cpp | 1 + UI/pre-stream-wizard/page-error.cpp | 33 ++++++++++ UI/pre-stream-wizard/page-error.hpp | 28 +++++++++ UI/pre-stream-wizard/pre-stream-wizard.cpp | 60 +++++++++++++++---- UI/pre-stream-wizard/pre-stream-wizard.hpp | 5 ++ 9 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 UI/pre-stream-wizard/page-error.cpp create mode 100644 UI/pre-stream-wizard/page-error.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 86e7dc0a78abaf..af3da907b9fb40 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -196,6 +196,7 @@ set(obs_SOURCES pre-stream-wizard/page-start-prompt.cpp pre-stream-wizard/page-select-settings.cpp pre-stream-wizard/page-completed.cpp + pre-stream-wizard/page-error.cpp pre-stream-wizard/setting-selection-row.cpp pre-stream-wizard/encoder-settings-provider-facebook.cpp obs-app.cpp @@ -276,6 +277,7 @@ set(obs_HEADERS pre-stream-wizard/page-start-prompt.hpp pre-stream-wizard/page-select-settings.hpp pre-stream-wizard/page-completed.hpp + pre-stream-wizard/page-error.hpp pre-stream-wizard/setting-selection-row.hpp pre-stream-wizard/encoder-settings-provider-facebook.hpp obs-app.hpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index d4bcece8443c99..0b0561623f4548 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -731,6 +731,11 @@ PreLiveWizard.Selection.Description="Suggested video settings for your best stre PreLiveWizard.Completed.Title="Encoder configured" PreLiveWizard.Completed.BodyText="Applied suggested settings and encoder is setup to stream. You can now close the wizard." PreLiveWizard.Completed.FacebookOnly="To finish going live go to Facebook Live:" +PreLiveWizard.Error.Title="There was an issue verifying settings" +PreLiveWizard.Error.Subtitle="This doesn’t block you from starting your stream." +PreLiveWizard.Error.Generic.Headline="Encoder service is not working right now." +PreLiveWizard.Error.Generic.BodyText="You can exit the wizard and try again." +PreLiveWizard.Error.NetworkTimeout="Check your network connection before starting a stream." # basic mode 'output' settings Basic.Settings.Output="Output" diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp index 22768d7a81b0de..7ba4a23c46f6eb 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "obs-app.hpp" #include "obs-config.h" @@ -56,10 +57,15 @@ void FacebookEncoderSettingsProvider::makeRequest(QUrl &url) "application/json"); // GET is made async - restclient_->get(request); + networkReply_ = restclient_->get(request); + pendingResponse_ = true; // This is the callback when data is ready connect(restclient_, &QNetworkAccessManager::finished, this, &FacebookEncoderSettingsProvider::handleResponse); + + // This is a fast API, timeout at 3 seconds and show error + QTimer::singleShot(3000, this, + &FacebookEncoderSettingsProvider::handleTimeout); } QUrlQuery FacebookEncoderSettingsProvider::inputVideoQueryFromCurrentSettings() @@ -178,6 +184,17 @@ void addBool(const QJsonObject &json, const char *jsonKey, SettingsMap *map, void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) { + // In timeout and errors this method may still be called + if (!pendingResponse_) { + return; + } + pendingResponse_ = false; + + if (reply->error()) { + emit returnErrorDescription( + QTStr("PreLiveWizard.Configure.Error.JsonParse"), + reply->errorString()); + } // Converts byte array into JSON doc as a copy QByteArray replyAll = reply->readAll(); QJsonDocument jsonDoc = QJsonDocument::fromJson(replyAll); @@ -249,6 +266,18 @@ void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) emit newSettings(settingsMapShrdPtr); } +void FacebookEncoderSettingsProvider::handleTimeout() +{ + if (!pendingResponse_) + return; + + pendingResponse_ = false; + + QString errorTitle = QTStr("PreLiveWizard.Configure.Error.JsonParse"); + QString errorDescription = QTStr("PreLiveWizard.Error.NetworkTimeout"); + emit returnErrorDescription(errorTitle, errorDescription); +} + void FacebookEncoderSettingsProvider::jsonParseError() { emit returnErrorDescription( diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp index 112b4d6d25714b..a06ddd51a1cbed 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp @@ -37,6 +37,8 @@ class FacebookEncoderSettingsProvider : public QObject { private: QSharedPointer currentSettings_; QNetworkAccessManager *restclient_; + QNetworkReply *networkReply_; + bool pendingResponse_ = false; void makeRequest(QUrl &url); QUrlQuery inputVideoQueryFromCurrentSettings(); @@ -45,6 +47,7 @@ class FacebookEncoderSettingsProvider : public QObject { private slots: void handleResponse(QNetworkReply *reply); + void handleTimeout(); }; } // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-completed.cpp b/UI/pre-stream-wizard/page-completed.cpp index 3def54f158d5b0..6db3416387757c 100644 --- a/UI/pre-stream-wizard/page-completed.cpp +++ b/UI/pre-stream-wizard/page-completed.cpp @@ -19,6 +19,7 @@ CompletedPage::CompletedPage(Destination destination, launchContext_ = launchContext; setTitle(QTStr("PreLiveWizard.Completed.Title")); + setFinalPage(true); QVBoxLayout *mainlayout = new QVBoxLayout(this); setLayout(mainlayout); diff --git a/UI/pre-stream-wizard/page-error.cpp b/UI/pre-stream-wizard/page-error.cpp new file mode 100644 index 00000000000000..c851d7d0478e8b --- /dev/null +++ b/UI/pre-stream-wizard/page-error.cpp @@ -0,0 +1,33 @@ +#include "page-error.hpp" + +#include +#include + +#include "obs-app.hpp" + +namespace StreamWizard { + +ErrorPage::ErrorPage(QWidget *parent) : QWizardPage(parent) +{ + setTitle(QTStr("PreLiveWizard.Error.Title")); + setSubTitle(QTStr("PreLiveWizard.Error.Subtitle")); + setFinalPage(true); + + QVBoxLayout *mainlayout = new QVBoxLayout(this); + titleLabel_ = new QLabel(this); + titleLabel_->setStyleSheet("font-weight: bold;"); + descriptionLabel_ = new QLabel(this); + mainlayout->addWidget(titleLabel_); + mainlayout->addSpacerItem(new QSpacerItem(12, 12)); + mainlayout->addWidget(descriptionLabel_); + setText(QTStr("PreLiveWizard.Error.Generic.Headline"), + QTStr("PreLiveWizard.Error.Generic.BodyText")); +} + +void ErrorPage::setText(QString title, QString description) +{ + titleLabel_->setText(title); + descriptionLabel_->setText(description); +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-error.hpp b/UI/pre-stream-wizard/page-error.hpp new file mode 100644 index 00000000000000..a40120b812995e --- /dev/null +++ b/UI/pre-stream-wizard/page-error.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include "pre-stream-current-settings.hpp" + +namespace StreamWizard { + +// Page shown if there's an error emitted from provider. + +class ErrorPage : public QWizardPage { + Q_OBJECT + +public: + ErrorPage(QWidget *parent = nullptr); + + void setText(QString title, QString description); + +private: + QString title_; + QString description_; + QLabel *titleLabel_; + QLabel *descriptionLabel_; +}; // class ErrorPage + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index d1edc1398af56c..7caecd25e2ffee 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -12,6 +12,7 @@ #include "page-start-prompt.hpp" #include "page-select-settings.hpp" #include "page-completed.hpp" +#include "page-error.hpp" namespace StreamWizard { @@ -20,6 +21,7 @@ enum PSW_Page { Page_Loading, Page_Selections, Page_Complete, + Page_Error, }; PreStreamWizard::PreStreamWizard( @@ -46,16 +48,21 @@ PreStreamWizard::PreStreamWizard( &PreStreamWizard::onUserSelectResolution); // Loading page: Shown when loading new settings - setPage(Page_Loading, new QWizardPage()); + QWizardPage *loadingPage = new QWizardPage(this); + loadingPage->setCommitPage(true); + setPage(Page_Loading, loadingPage); // Suggestion Selection Page - selectionPage_ = new SelectionPage(); + selectionPage_ = new SelectionPage(this); setPage(Page_Selections, selectionPage_); // Ending + Confirmation Page CompletedPage *completedPage = new CompletedPage(destination_, launchContext_, this); setPage(Page_Complete, completedPage); + + errorPage_ = new ErrorPage(this); + setPage(Page_Error, errorPage_); } void PreStreamWizard::requestSettings() @@ -88,29 +95,62 @@ void PreStreamWizard::providerEncoderSettings( { blog(LOG_INFO, "PreStreamWizard got new settings response"); newSettingsMap_ = response; - if (this->currentId() == Page_Loading) { - this->next(); + if (currentId() == Page_Loading) { + next(); } } void PreStreamWizard::providerError(QString title, QString description) { - // TODO: Add Wizard Page with finish that shows this + sendToErrorPage_ = true; + errorPage_->setText(title, description); + if (currentId() == Page_Loading) + next(); } void PreStreamWizard::onPageChanged(int id) { - if (id == Page_StartPrompt) { + switch (id) { + case Page_StartPrompt: setOption(QWizard::NoCancelButton, false); - } + break; - if (id == Page_Loading) { + case Page_Loading: requestSettings(); - } + break; - if (id == Page_Selections) { + case Page_Selections: selectionPage_->setSettingsMap(newSettingsMap_); + break; + + case Page_Complete: + break; + + case Page_Error: + sendToErrorPage_ = false; + break; + } +} + +int PreStreamWizard::nextId() const +{ + switch (currentId()) { + case Page_StartPrompt: + return Page_Loading; + case Page_Loading: + return sendToErrorPage_ ? Page_Error : Page_Selections; + case Page_Selections: + return Page_Complete; + break; + case Page_Complete: + return -1; + case Page_Error: + return -1; + } + + if (currentId() == Page_Loading) { } + return QWizard::nextId(); } void PreStreamWizard::onUserSelectResolution(QSize newSize) diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index 22b89cc19e46dc..84445ecf661d40 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -10,6 +10,7 @@ namespace StreamWizard { class StartPage; class SelectionPage; +class ErrorPage; /* ** The pre-stream wizard is a workflow focused on delivering encoder settings @@ -26,16 +27,20 @@ class PreStreamWizard : public QWizard { QSharedPointer currentSettings, QWidget *parent = nullptr); + int nextId() const override; + private: // Pages StartPage *startPage_; SelectionPage *selectionPage_; + ErrorPage *errorPage_; // External State Destination destination_; LaunchContext launchContext_; QSharedPointer currentSettings_; QSharedPointer newSettingsMap_; + bool sendToErrorPage_ = false; void requestSettings(); From 3039731161f295958804887ef0c2c37d21779db5 Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Wed, 30 Sep 2020 19:23:01 -0700 Subject: [PATCH 08/13] UI: Add loading page to PreStream Wizard Adds minimal loading page to wizard where it animates the ellipses on "Loading..." label --- UI/CMakeLists.txt | 2 + UI/data/locale/en-US.ini | 2 + UI/pre-stream-wizard/page-loading.cpp | 51 ++++++++++++++++++++++ UI/pre-stream-wizard/page-loading.hpp | 34 +++++++++++++++ UI/pre-stream-wizard/pre-stream-wizard.cpp | 6 +-- UI/pre-stream-wizard/pre-stream-wizard.hpp | 2 + 6 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 UI/pre-stream-wizard/page-loading.cpp create mode 100644 UI/pre-stream-wizard/page-loading.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index af3da907b9fb40..449c166da58db4 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -195,6 +195,7 @@ set(obs_SOURCES pre-stream-wizard/page-input-display.cpp pre-stream-wizard/page-start-prompt.cpp pre-stream-wizard/page-select-settings.cpp + pre-stream-wizard/page-loading.cpp pre-stream-wizard/page-completed.cpp pre-stream-wizard/page-error.cpp pre-stream-wizard/setting-selection-row.cpp @@ -276,6 +277,7 @@ set(obs_HEADERS pre-stream-wizard/page-input-display.hpp pre-stream-wizard/page-start-prompt.hpp pre-stream-wizard/page-select-settings.hpp + pre-stream-wizard/page-loading.hpp pre-stream-wizard/page-completed.hpp pre-stream-wizard/page-error.hpp pre-stream-wizard/setting-selection-row.hpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 0b0561623f4548..95ad413340e3f7 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -99,6 +99,7 @@ LogViewer="Log Viewer" ShowOnStartup="Show on startup" OpenFile="Open file" AddValue="Add %1" +Loading="Loading" # warning if program already open AlreadyRunning.Title="OBS is already running" @@ -721,6 +722,7 @@ PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip="Opens Facebook Facebook PreLiveWizard.Prompt.Resolution.720="720p (Recommended for general purpose)" PreLiveWizard.Prompt.Resolution.1080="1080p (Recommended for gaming)" PreLiveWizard.Prompt.Resolution.Current="%1x%2 (Current setting)" +PreLiveWizard.Loading.Title="Fetching updated settings" PreLiveWizard.Configure.ServiceNotAvailable.Title="Service not supported" PreLiveWizard.Configure.ServiceNotAvailable.Description="The service selected in stream settings does not yet have an encoder configuration service." PreLiveWizard.Configure.Error.Url="Settings setup failed. Query: " diff --git a/UI/pre-stream-wizard/page-loading.cpp b/UI/pre-stream-wizard/page-loading.cpp new file mode 100644 index 00000000000000..aca8ac5c1ee07a --- /dev/null +++ b/UI/pre-stream-wizard/page-loading.cpp @@ -0,0 +1,51 @@ +#include "page-loading.hpp" + +#include +#include +#include + +#include "obs-app.hpp" + +namespace StreamWizard { + +LoadingPage::LoadingPage(QWidget *parent) : QWizardPage(parent) +{ + setTitle(QTStr("PreLiveWizard.Loading.Title")); + setCommitPage(true); + + labels_.append(QTStr("Loading")); + labels_.append(QTStr("Loading") + "."); + labels_.append(QTStr("Loading") + "." + "."); + labels_.append(QTStr("Loading") + "." + "." + "."); + + QVBoxLayout *mainlayout = new QVBoxLayout(this); + loadingLabel_ = new QLabel(this); + loadingLabel_->setText(labels_.at(0)); + mainlayout->addWidget(loadingLabel_); + setLayout(mainlayout); +} + +void LoadingPage::initializePage() +{ + if (timer_ == nullptr) { + timer_ = new QTimer(this); + connect(timer_, &QTimer::timeout, this, &LoadingPage::tick); + } + timer_->setSingleShot(false); + timer_->start(500); +} +void LoadingPage::cleanupPage() +{ + timer_->stop(); +} + +void LoadingPage::tick() +{ + count_++; + if (count_ > 3) + count_ = 0; + + loadingLabel_->setText(labels_.at(count_)); +} + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/page-loading.hpp b/UI/pre-stream-wizard/page-loading.hpp new file mode 100644 index 00000000000000..6f0a050df3baf8 --- /dev/null +++ b/UI/pre-stream-wizard/page-loading.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "pre-stream-current-settings.hpp" + +namespace StreamWizard { + +// Page shown while a provider computes best settings. + +class LoadingPage : public QWizardPage { + Q_OBJECT + +public: + LoadingPage(QWidget *parent = nullptr); + void initializePage() override; + void cleanupPage() override; + +private: + QLabel *loadingLabel_; + QTimer *timer_; + QStringList labels_; + int count_ = 0; + +private slots: + void tick(); + +}; // class LoadingPage + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index 7caecd25e2ffee..2b55cd78f79d86 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -10,6 +10,7 @@ #include "page-input-display.hpp" #include "page-start-prompt.hpp" +#include "page-loading.hpp" #include "page-select-settings.hpp" #include "page-completed.hpp" #include "page-error.hpp" @@ -48,9 +49,8 @@ PreStreamWizard::PreStreamWizard( &PreStreamWizard::onUserSelectResolution); // Loading page: Shown when loading new settings - QWizardPage *loadingPage = new QWizardPage(this); - loadingPage->setCommitPage(true); - setPage(Page_Loading, loadingPage); + loadingPage_ = new LoadingPage(this); + setPage(Page_Loading, loadingPage_); // Suggestion Selection Page selectionPage_ = new SelectionPage(this); diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index 84445ecf661d40..bb75f9666efbe4 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -10,6 +10,7 @@ namespace StreamWizard { class StartPage; class SelectionPage; +class LoadingPage; class ErrorPage; /* @@ -32,6 +33,7 @@ class PreStreamWizard : public QWizard { private: // Pages StartPage *startPage_; + LoadingPage *loadingPage_; SelectionPage *selectionPage_; ErrorPage *errorPage_; From e78202caf79236821fb4aa11b371063309b73175 Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Thu, 1 Oct 2020 18:27:50 -0700 Subject: [PATCH 09/13] UI: Pre-stream wizard buttons are dynamic Hide buttons that are disabled for better user clarity. --- UI/data/locale/en-US.ini | 7 ++-- UI/pre-stream-wizard/page-loading.cpp | 12 +++--- UI/pre-stream-wizard/page-start-prompt.cpp | 6 --- UI/pre-stream-wizard/pre-stream-wizard.cpp | 46 +++++++++++++++++++--- UI/pre-stream-wizard/pre-stream-wizard.hpp | 9 +++++ 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 95ad413340e3f7..b3dbdad7a42e39 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -711,7 +711,7 @@ Basic.Settings.Stream.MissingStreamKey="Stream key is missing.\n\nOpen settings ## Pre-Live Wizard and Settings Basic.Settings.Stream.PreLiveWizard.RunNow="Check stream settings" PreLiveWizard.Title="Verify Stream Settings" -PreLiveWizard.Prompt.Title="Verify stream settings before stream?" +PreLiveWizard.Prompt.Title="Verify stream settings before stream" PreLiveWizard.Prompt.Subtitle.Default="The streaming wizard suggests the most up-to-date settings to improve your stream’s reliability, quality, and latency. \n\nSelect the resolution to stream to your account:" PreLiveWizard.Prompt.Subtitle.FB="Your destination is set to Facebook Live." PreLiveWizard.Prompt.Explainer="The Streaming wizard suggests the most up-to-date settings to improve your stream’s reliability, quality, and latency." @@ -728,8 +728,9 @@ PreLiveWizard.Configure.ServiceNotAvailable.Description="The service selected in PreLiveWizard.Configure.Error.Url="Settings setup failed. Query: " PreLiveWizard.Configure.Error.JsonParse="Problem with server response" PreLiveWizard.Configure.Error.JsonParse.Description="Wizard is unavailble at the moment." -PreLiveWizard.Selection.Title="Settings Suggested" -PreLiveWizard.Selection.Description="Suggested video settings for your best stream." +PreLiveWizard.Configure.Error.NoData="No new settings recieved." +PreLiveWizard.Selection.Title="Suggested Settings" +PreLiveWizard.Selection.Description="Suggested video settings for your best stream:" PreLiveWizard.Completed.Title="Encoder configured" PreLiveWizard.Completed.BodyText="Applied suggested settings and encoder is setup to stream. You can now close the wizard." PreLiveWizard.Completed.FacebookOnly="To finish going live go to Facebook Live:" diff --git a/UI/pre-stream-wizard/page-loading.cpp b/UI/pre-stream-wizard/page-loading.cpp index aca8ac5c1ee07a..c3fad9fa663182 100644 --- a/UI/pre-stream-wizard/page-loading.cpp +++ b/UI/pre-stream-wizard/page-loading.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "obs-app.hpp" @@ -13,6 +14,10 @@ LoadingPage::LoadingPage(QWidget *parent) : QWizardPage(parent) setTitle(QTStr("PreLiveWizard.Loading.Title")); setCommitPage(true); + timer_ = new QTimer(this); + connect(timer_, &QTimer::timeout, this, &LoadingPage::tick); + timer_->setSingleShot(false); + labels_.append(QTStr("Loading")); labels_.append(QTStr("Loading") + "."); labels_.append(QTStr("Loading") + "." + "."); @@ -27,12 +32,7 @@ LoadingPage::LoadingPage(QWidget *parent) : QWizardPage(parent) void LoadingPage::initializePage() { - if (timer_ == nullptr) { - timer_ = new QTimer(this); - connect(timer_, &QTimer::timeout, this, &LoadingPage::tick); - } - timer_->setSingleShot(false); - timer_->start(500); + timer_->start(300); } void LoadingPage::cleanupPage() { diff --git a/UI/pre-stream-wizard/page-start-prompt.cpp b/UI/pre-stream-wizard/page-start-prompt.cpp index ef3fa8e9a2dc0b..785cf4549ae143 100644 --- a/UI/pre-stream-wizard/page-start-prompt.cpp +++ b/UI/pre-stream-wizard/page-start-prompt.cpp @@ -23,12 +23,6 @@ StartPage::StartPage(Destination dest, LaunchContext launchContext, setTitle(QTStr("PreLiveWizard.Prompt.Title")); - if (destination_ == Destination::Facebook) { - setSubTitle(QTStr("PreLiveWizard.Prompt.Subtitle.FB")); - } else { - setSubTitle(QTStr("PreLiveWizard.Prompt.Subtitle.Default")); - } - createLayout(); connectRadioButtons(); diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index 2b55cd78f79d86..efbc6d740cb4ab 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "obs-app.hpp" #include "encoder-settings-provider-facebook.hpp" @@ -38,6 +39,8 @@ PreStreamWizard::PreStreamWizard( setWizardStyle(QWizard::ModernStyle); setWindowTitle(QTStr("PreLiveWizard.Title")); + setOption(QWizard::NoBackButtonOnStartPage, true); + setOption(QWizard::NoBackButtonOnLastPage, true); // First Page: Explain to the user and confirm sending to API QSize currentRes(currentSettings_->videoWidth, @@ -61,6 +64,7 @@ PreStreamWizard::PreStreamWizard( new CompletedPage(destination_, launchContext_, this); setPage(Page_Complete, completedPage); + // Add error page shown instead of completion in failure cases errorPage_ = new ErrorPage(this); setPage(Page_Error, errorPage_); } @@ -113,21 +117,26 @@ void PreStreamWizard::onPageChanged(int id) switch (id) { case Page_StartPrompt: setOption(QWizard::NoCancelButton, false); + setButtons(NextStep); break; case Page_Loading: requestSettings(); + setButtons(CancelOnly); break; case Page_Selections: selectionPage_->setSettingsMap(newSettingsMap_); + setButtons(CommitStep); break; case Page_Complete: + setButtons(FinishOnly); break; case Page_Error: sendToErrorPage_ = false; + setButtons(FinishOnly); break; } } @@ -137,22 +146,49 @@ int PreStreamWizard::nextId() const switch (currentId()) { case Page_StartPrompt: return Page_Loading; - case Page_Loading: - return sendToErrorPage_ ? Page_Error : Page_Selections; + case Page_Loading: { + if (sendToErrorPage_) + return Page_Error; + if (newSettingsMap_ == nullptr || newSettingsMap_.isNull()) { + errorPage_->setText( + QTStr("PreLiveWizard.Configure.Error.NoData"), + QTStr("PreLiveWizard.Configure.Error.JsonParse.Description")); + return Page_Error; + } + return Page_Selections; + } case Page_Selections: return Page_Complete; - break; case Page_Complete: return -1; case Page_Error: return -1; } - if (currentId() == Page_Loading) { - } return QWizard::nextId(); } +void PreStreamWizard::setButtons(ButtonLayout layout) +{ + QList layoutList; + layoutList << QWizard::Stretch; + switch (layout) { + case NextStep: + layoutList << QWizard::NextButton << QWizard::CancelButton; + break; + case CommitStep: + layoutList << QWizard::CommitButton << QWizard::CancelButton; + break; + case CancelOnly: + layoutList << QWizard::CancelButton; + break; + case FinishOnly: + layoutList << QWizard::FinishButton; + break; + } + setButtonLayout(layoutList); +} + void PreStreamWizard::onUserSelectResolution(QSize newSize) { blog(LOG_INFO, "Selected res %d x %d", newSize.width(), diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index bb75f9666efbe4..c88beaec7338b8 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -46,6 +46,15 @@ class PreStreamWizard : public QWizard { void requestSettings(); + // Wizard uses custom button layouts to manage user flow & aborts. + enum ButtonLayout { + NextStep, + CancelOnly, + FinishOnly, + CommitStep, + }; + void setButtons(ButtonLayout layout); + signals: // User left the wizard with intention to continue streaming void userSkippedWizard(void); From 7df6801486f0fea150f99585dcad499da6776d28 Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Wed, 7 Oct 2020 12:25:47 -0700 Subject: [PATCH 10/13] UI: Apply settings gathered by Pre-stream wizard Applies selected settings to the simple encoder setup. Use the Custom Encoder Settings to set specific settings. Additionally, changes the output resolution so the canvas layout is not changed possibly right before a stream. Next to add gating so this wizard only shows in output Simple Mode. --- UI/common-settings.hpp | 1 + UI/pre-stream-wizard/page-input-display.cpp | 4 - UI/pre-stream-wizard/page-start-prompt.cpp | 24 ++- UI/pre-stream-wizard/page-start-prompt.hpp | 3 +- .../pre-stream-current-settings.hpp | 2 - UI/pre-stream-wizard/pre-stream-wizard.cpp | 6 + UI/pre-stream-wizard/pre-stream-wizard.hpp | 12 -- UI/streaming-settings-util.cpp | 185 +++++++++++++++++- UI/streaming-settings-util.hpp | 7 +- UI/window-basic-settings-stream.cpp | 42 +++- UI/window-basic-settings.hpp | 4 + 11 files changed, 245 insertions(+), 45 deletions(-) diff --git a/UI/common-settings.hpp b/UI/common-settings.hpp index 5cdf5d47e66c12..979b6f29ce6ddf 100644 --- a/UI/common-settings.hpp +++ b/UI/common-settings.hpp @@ -31,6 +31,7 @@ class CommonSettings { /* Stream Encoder ——————————————————————————————————*/ static int GetVideoBitrateInUse(config_t *config); + static void SetAllVideoBitrates(config_t *config, int newBitrate); private: // Reused Strings diff --git a/UI/pre-stream-wizard/page-input-display.cpp b/UI/pre-stream-wizard/page-input-display.cpp index 9f46f5a9c76ed2..3f62dd9c9b1fa6 100644 --- a/UI/pre-stream-wizard/page-input-display.cpp +++ b/UI/pre-stream-wizard/page-input-display.cpp @@ -23,10 +23,6 @@ QWizardPage *SettingsInputPage(StreamWizard::EncoderSettingsRequest *settings) QWidget *formContainer = new QWidget(); QFormLayout *form = new QFormLayout(formContainer); - form->addRow("Server Url", - new QLabel(QString::fromUtf8(settings->serverUrl))); - form->addRow("Service", - new QLabel(QString::fromUtf8(settings->serviceName))); form->addRow("Video Width", new QLabel(QString::number(settings->videoWidth))); form->addRow("Video Height", diff --git a/UI/pre-stream-wizard/page-start-prompt.cpp b/UI/pre-stream-wizard/page-start-prompt.cpp index 785cf4549ae143..f18fbcdd159c31 100644 --- a/UI/pre-stream-wizard/page-start-prompt.cpp +++ b/UI/pre-stream-wizard/page-start-prompt.cpp @@ -33,6 +33,16 @@ StartPage::StartPage(Destination dest, LaunchContext launchContext, resCurrentButton_->setChecked(true); } } + +void StartPage::initializePage() +{ + // emit defaul resolution check + if (destination_ == Destination::Facebook && + res720Button_->isChecked()) { + emit userSelectedResolution(QSize(1280, 720)); + } +} + void StartPage::createLayout() { QVBoxLayout *mainlayout = new QVBoxLayout(this); @@ -104,17 +114,17 @@ void StartPage::createLayout() void StartPage::connectRadioButtons() { connect(res720Button_, &QRadioButton::clicked, [=]() { - selectedVideoSize_ = QSize(1280, 720); - emit userSelectedResolution(selectedVideoSize_); + QSize selectedVideoSize = QSize(1280, 720); + emit userSelectedResolution(selectedVideoSize); }); connect(res1080Button_, &QRadioButton::clicked, [=]() { - selectedVideoSize_ = QSize(1920, 1080); - emit userSelectedResolution(selectedVideoSize_); + QSize selectedVideoSize = QSize(1920, 1080); + emit userSelectedResolution(selectedVideoSize); }); connect(resCurrentButton_, &QRadioButton::clicked, [=]() { - selectedVideoSize_ = QSize(startVideoSize_.width(), - startVideoSize_.height()); - emit userSelectedResolution(selectedVideoSize_); + QSize selectedVideoSize = QSize(startVideoSize_.width(), + startVideoSize_.height()); + emit userSelectedResolution(selectedVideoSize); }); } diff --git a/UI/pre-stream-wizard/page-start-prompt.hpp b/UI/pre-stream-wizard/page-start-prompt.hpp index 418c30b6084fe9..6dd04225bf8944 100644 --- a/UI/pre-stream-wizard/page-start-prompt.hpp +++ b/UI/pre-stream-wizard/page-start-prompt.hpp @@ -17,6 +17,8 @@ class StartPage : public QWizardPage { StartPage(Destination dest, LaunchContext launchContext, QSize videoSize, QWidget *parent = nullptr); + void initializePage() override; + signals: // emitted selected resolution from start page radio buttons void userSelectedResolution(QSize newVideoSize); @@ -28,7 +30,6 @@ class StartPage : public QWizardPage { QSize startVideoSize_; // Selected settings - QSize selectedVideoSize_; QRadioButton *res720Button_; QRadioButton *res1080Button_; QRadioButton *resCurrentButton_; diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.hpp b/UI/pre-stream-wizard/pre-stream-current-settings.hpp index bcd98ed7cedc2b..86ddaf01781b65 100644 --- a/UI/pre-stream-wizard/pre-stream-current-settings.hpp +++ b/UI/pre-stream-wizard/pre-stream-current-settings.hpp @@ -53,8 +53,6 @@ struct EncoderSettingsRequest { //// Stream StreamProtocol protocol; // Expandable but only supports RTMPS for now VideoType videoType; // LIVE or VOD (but always live for OBS) - char *serverUrl; - char *serviceName; ///// Video Settings int videoWidth; diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index efbc6d740cb4ab..1ba41777582716 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -81,6 +81,7 @@ void PreStreamWizard::requestSettings() connect(fbProvider, &FacebookEncoderSettingsProvider::returnErrorDescription, this, &PreStreamWizard::providerError); + fbProvider->setEncoderRequest(currentSettings_); fbProvider->run(); @@ -132,6 +133,11 @@ void PreStreamWizard::onPageChanged(int id) case Page_Complete: setButtons(FinishOnly); + if (newSettingsMap_ != nullptr && !newSettingsMap_.isNull()) { + // ToDo: messaging in edge case this could be empty + // and still make it here? + emit applySettings(newSettingsMap_); + }; break; case Page_Error: diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index c88beaec7338b8..8a9d17509bb6dd 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -56,18 +56,6 @@ class PreStreamWizard : public QWizard { void setButtons(ButtonLayout layout); signals: - // User left the wizard with intention to continue streaming - void userSkippedWizard(void); - - // User canceled, also canceling intent to stream. - void userAbortedStream(void); - - // User ready to start stream - // If newSettings is not null, they should be applied before stream - // If newSettings is null, user or wizard did not opt to apply changes. - void - startStreamWithSettings(QSharedPointer newSettingsOrNull); - // Apply settings, don't start stream. e.g., is configuring from settings void applySettings(QSharedPointer newSettings); diff --git a/UI/streaming-settings-util.cpp b/UI/streaming-settings-util.cpp index 6d1efb3a40ea77..5a598bf1f04636 100644 --- a/UI/streaming-settings-util.cpp +++ b/UI/streaming-settings-util.cpp @@ -3,6 +3,8 @@ #include "common-settings.hpp" #include +#include + /* We are detecting the stream settings so if they're using a large canvas but already have the rescaler on, they will be streaming correctly. @@ -32,8 +34,7 @@ void UpdateStreamingResolution(int *resolutionXY, bool isRescaled, }; QSharedPointer -StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( - config_t *config, obs_data_t *settings) +StreamingSettingsUtility::makeEncoderSettingsFromCurrentState(config_t *config) { QSharedPointer currentSettings( new StreamWizard::EncoderSettingsRequest()); @@ -43,12 +44,6 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( currentSettings->videoType = StreamWizard::VideoType::live; // only live and rmpts is supported for now currentSettings->protocol = StreamWizard::StreamProtocol::rtmps; - - currentSettings->serverUrl = - bstrdup(obs_data_get_string(settings, "server")); - currentSettings->serviceName = - bstrdup(obs_data_get_string(settings, "service")); - /* Video */ bool isRescaled = config_get_bool(config, "AdvOut", "Rescale"); int resolutionXY[2] = {0, 0}; @@ -73,3 +68,177 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( return currentSettings; }; + +/* +* StreamWizard::SettingsMap is sparse in that it wont have all keys +* as well, the user can opt out of settings being applied. +* The map looks like [ SettingsResponseKeys : QPair ] +* AKA [KEY : SetttingPair ] +* if the key is available, it means the wizard provider added a value for it +* but the bool in the QPair is false if the user selected in the wizard not to +* apply the setting. A case would be we suggest a 720p stream but the user +* knows their account supports 1080 so disabled the setting from applying. + +* Returns TRUE is map contrains something for the key and it is marked true +*/ +bool CheckInMapAndSelected(StreamWizard::SettingsMap *map, const char *key) +{ + if (!map->contains(key)) { + return false; + } + const QPair settingPair = map->value(key); + return settingPair.second; +} + +// Helper Functions for ::applyWizardSettings +int intFromMap(StreamWizard::SettingsMap *map, const char *key) +{ + QPair settingPair = map->value(key); + QVariant data = settingPair.first; + return data.toInt(); +} + +QString stringFromMap(StreamWizard::SettingsMap *map, const char *key) +{ + QPair settingPair = map->value(key); + QVariant data = settingPair.first; + return data.toString(); +} + +double doubleFromMap(StreamWizard::SettingsMap *map, const char *key) +{ + QPair settingPair = map->value(key); + QVariant data = settingPair.first; + return data.toDouble(); +} + +bool boolFromMap(StreamWizard::SettingsMap *map, const char *key) +{ + QPair settingPair = map->value(key); + QVariant data = settingPair.first; + return data.toBool(); +} + +/* +* Given a settings map [ SettingsResponseKeys : QPair ] apply +* settings that are in the sparse map as well selected by the user (which is +* marked by the bool in the pair). +* Apply to Basic encoder settings. +* Possible later goal: autoconfig advanced settings too +*/ +void StreamingSettingsUtility::applyWizardSettings( + QSharedPointer newSettings, config_t *config) +{ + + if (newSettings == nullptr || newSettings.isNull()) + return; + + // scope to function usage + using namespace StreamWizard; + + SettingsMap *map = newSettings.data(); + + QStringList x264SettingList; + config_set_bool(config, "SimpleOutput", "UseAdvanced", true); + + // Resolution must have both + if (CheckInMapAndSelected(map, SettingsResponseKeys.videoHeight) && + CheckInMapAndSelected(map, SettingsResponseKeys.videoWidth)) { + + int canvasX = intFromMap(map, SettingsResponseKeys.videoWidth); + int canvasY = intFromMap(map, SettingsResponseKeys.videoHeight); + config_set_uint(config, "Video", "OutputCX", canvasX); + config_set_uint(config, "Video", "OutputCY", canvasY); + } + + //TODO: FPS is hacky but covers all integer and standard drop frame cases + if (CheckInMapAndSelected(map, SettingsResponseKeys.framerate)) { + double currentFPS = CommonSettings::GetConfigFPSDouble(config); + double newFPS = + doubleFromMap(map, SettingsResponseKeys.framerate); + if (abs(currentFPS - newFPS) > + 0.001) { // Only change if different + if (abs(floor(newFPS) - newFPS) > + 0.01) { // Is a drop-frame FPS + int num = ceil(newFPS) * 1000; + int den = 1001; + config_set_uint(config, "Video", "FPSType", + 2); // Fraction + config_set_uint(config, "Video", "FPSNum", num); + config_set_uint(config, "Video", "FPSDen", den); + } else { // Is integer FPS + config_set_uint(config, "Video", "FPSType", + 1); // Integer + config_set_uint(config, "Video", "FPSInt", + (int)floor(newFPS)); + } + } + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.videoBitrate)) { + int newBitrate = + intFromMap(map, SettingsResponseKeys.videoBitrate); + config_set_int(config, "SimpleOutput", "VBitrate", newBitrate); + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.h264Profile)) { + QString profile = + stringFromMap(map, SettingsResponseKeys.h264Profile); + profile = profile.toLower(); + x264SettingList.append("profile=" + profile); + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.h264Level)) { + QString levelString = + stringFromMap(map, SettingsResponseKeys.h264Level); + x264SettingList.append("level=" + levelString); + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.gopSizeInFrames)) { + int gopSize = + intFromMap(map, SettingsResponseKeys.gopSizeInFrames); + x264SettingList.append("keyint=" + QString::number(gopSize)); + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.gopType)) { + QString gopType = + stringFromMap(map, SettingsResponseKeys.gopType); + x264SettingList.append("slice_mode=" + gopType); + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.gopClosed)) { + bool gopClose = + boolFromMap(map, SettingsResponseKeys.gopClosed); + if (gopClose) { + x264SettingList.append("open_gop=0"); + } else { + x264SettingList.append("open_gop=1"); + } + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.gopBFrames)) { + int bFrames = intFromMap(map, SettingsResponseKeys.gopBFrames); + x264SettingList.append("bframes=" + QString::number(bFrames)); + } + + if (CheckInMapAndSelected(map, SettingsResponseKeys.gopRefFrames)) { + int refFrames = + intFromMap(map, SettingsResponseKeys.gopRefFrames); + x264SettingList.append("ref=" + QString::number(refFrames)); + } + + // SettingsResponseKeys.streamRateControlMode defaults to CBR in Simple + // encoder mode. Can add later for advanced panel. + + if (CheckInMapAndSelected(map, SettingsResponseKeys.streamBufferSize)) { + int bufferSize = + intFromMap(map, SettingsResponseKeys.streamBufferSize); + x264SettingList.append("bufsize=" + + QString::number(bufferSize)); + } + + QString x264String = x264SettingList.join(" "); + const char *x264_c_String = x264String.toStdString().c_str(); + config_set_string(config, "SimpleOutput", "x264Settings", + x264_c_String); +}; diff --git a/UI/streaming-settings-util.hpp b/UI/streaming-settings-util.hpp index 91edc3ee5541de..3bf4c39835e233 100644 --- a/UI/streaming-settings-util.hpp +++ b/UI/streaming-settings-util.hpp @@ -14,6 +14,9 @@ class StreamingSettingsUtility : public QObject { public: // Uses current settings in OBS static QSharedPointer - makeEncoderSettingsFromCurrentState(config_t *config, - obs_data_t *settings); + makeEncoderSettingsFromCurrentState(config_t *config); + + static void applyWizardSettings( + QSharedPointer newSettings, + config_t *config); }; diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index e5cb6eb756ff34..a7c4b84da498ec 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -8,7 +8,6 @@ #include "qt-wrappers.hpp" #include "url-push-button.hpp" #include "pre-stream-wizard.hpp" -#include "streaming-settings-util.hpp" #ifdef BROWSER_AVAILABLE #include @@ -285,19 +284,44 @@ void OBSBasicSettings::UpdateKeyLink() void OBSBasicSettings::preStreamWizardLaunch() { - obs_service_t *service_obj = main->GetService(); - obs_data_t *settings = obs_service_get_settings(service_obj); + StreamWizard::Destination dest; + // Use UI to detect service. + QString serviceName = ui->service->currentText(); + QString customServer = ui->customServer->text(); + if (serviceName == "Facebook Live" || + (IsCustomService() && customServer.contains("fbcdn.net"))) { + dest = StreamWizard::Destination::Facebook; + } else { + blog(LOG_WARNING, + "Showed wizard button for service not supported"); + return; + } QSharedPointer currentSettings = StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( - main->Config(), settings); + main->Config()); - StreamWizard::PreStreamWizard wiz = StreamWizard::PreStreamWizard( - StreamWizard::Destination::Facebook, - StreamWizard::LaunchContext::Settings, currentSettings, this); - wiz.exec(); + StreamWizard::PreStreamWizard *wiz = new StreamWizard::PreStreamWizard( + dest, StreamWizard::LaunchContext::Settings, currentSettings, + this); - obs_data_release(settings); + connect(wiz, &StreamWizard::PreStreamWizard::applySettings, this, + &OBSBasicSettings::preStreamWizardApplySettings); + + // Show wizard over settings + wiz->exec(); +} + +void OBSBasicSettings::preStreamWizardApplySettings( + QSharedPointer newSettings) +{ + blog(LOG_INFO, "OBSBasicSettings::preStreamWizardApplySettings"); + + // Apply and reload + StreamingSettingsUtility::applyWizardSettings(newSettings, + main->Config()); + + LoadSettings(false); } void OBSBasicSettings::LoadServices(bool showAll) diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index a06e949bf128f9..1d83a87efa6012 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -29,6 +29,7 @@ #include #include "auth-base.hpp" +#include "common-settings.hpp" class OBSBasic; class QAbstractButton; @@ -39,6 +40,7 @@ class OBSPropertiesView; class OBSHotkeyWidget; #include "ui_OBSBasicSettings.h" +#include "streaming-settings-util.hpp" #define VOLUME_METER_DECAY_FAST 23.53 #define VOLUME_METER_DECAY_MEDIUM 11.76 @@ -241,6 +243,8 @@ private slots: void UpdateKeyLink(); void UpdateMoreInfoLink(); void preStreamWizardLaunch(); + void preStreamWizardApplySettings( + QSharedPointer newSettings); void on_show_clicked(); void on_authPwShow_clicked(); void on_connectAccount_clicked(); From 8a1636309881397c0a384b1318b3c3bfef65291a Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Wed, 7 Oct 2020 18:58:11 -0700 Subject: [PATCH 11/13] UI: Pre-stream wizard show & hide button correctly Show pre-stream wizard only when in simple mode since it only operates on simple mode for now. Later, will look into expanding capability. Additionally, fix a bug where was making a copy of a QPair instead of creating a reference so settings were not being written. --- UI/common-settings.cpp | 2 +- UI/common-settings.hpp | 1 + UI/pre-stream-wizard/page-loading.hpp | 1 - UI/pre-stream-wizard/page-select-settings.cpp | 7 +--- UI/pre-stream-wizard/page-select-settings.hpp | 11 +++--- UI/pre-stream-wizard/page-start-prompt.hpp | 1 + UI/pre-stream-wizard/pre-stream-wizard.hpp | 11 +++--- .../setting-selection-row.hpp | 10 ++--- UI/streaming-settings-util.cpp | 37 ++----------------- UI/window-basic-settings-stream.cpp | 8 +++- UI/window-basic-settings.cpp | 2 + 11 files changed, 33 insertions(+), 58 deletions(-) diff --git a/UI/common-settings.cpp b/UI/common-settings.cpp index ecfd1dc07845cd..4bd4ab6a49c22a 100644 --- a/UI/common-settings.cpp +++ b/UI/common-settings.cpp @@ -2,7 +2,7 @@ #include "audio-encoders.hpp" -static bool IsAdvancedMode(config_t *config) +bool CommonSettings::IsAdvancedMode(config_t *config) { const char *outputMode = config_get_string(config, "Output", "Mode"); return (strcmp(outputMode, "Advanced") == 0); diff --git a/UI/common-settings.hpp b/UI/common-settings.hpp index 979b6f29ce6ddf..beeecf5514ed37 100644 --- a/UI/common-settings.hpp +++ b/UI/common-settings.hpp @@ -10,6 +10,7 @@ class CommonSettings { public: + static bool IsAdvancedMode(config_t *config); /* Shared Utility Functions --------------------------*/ static OBSData GetDataFromJsonFile(const char *jsonFile); diff --git a/UI/pre-stream-wizard/page-loading.hpp b/UI/pre-stream-wizard/page-loading.hpp index 6f0a050df3baf8..5f18398cc0f3f8 100644 --- a/UI/pre-stream-wizard/page-loading.hpp +++ b/UI/pre-stream-wizard/page-loading.hpp @@ -11,7 +11,6 @@ namespace StreamWizard { // Page shown while a provider computes best settings. - class LoadingPage : public QWizardPage { Q_OBJECT diff --git a/UI/pre-stream-wizard/page-select-settings.cpp b/UI/pre-stream-wizard/page-select-settings.cpp index 383b1c5a72b8ca..fd8be3bf909d59 100644 --- a/UI/pre-stream-wizard/page-select-settings.cpp +++ b/UI/pre-stream-wizard/page-select-settings.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -167,8 +166,7 @@ void SelectionPage::setSettingsMap(QSharedPointer settingsMap) QVariant data = mapInfo->value(SettingsResponseKeys.streamBufferSize) .first; - QString bufferSizeString = - QString::number(data.toInt()) + " Kb"; + QString bufferSizeString = QString::number(data.toInt()) + "Kb"; addRow(QTStr("Basic.Settings.Output.Mode.StreamBuffer"), bufferSizeString, SettingsResponseKeys.streamBufferSize); } @@ -192,12 +190,11 @@ void SelectionPage::checkboxRowChanged(const char *propertyKey, bool selected) } SettingsMap *mapInfo = settingsMap_.data(); - if (mapInfo == nullptr || !mapInfo->contains(propertyKey)) { return; } - QPair dataPair = mapInfo->value(propertyKey); + QPair &dataPair = (*mapInfo)[propertyKey]; dataPair.second = selected; } diff --git a/UI/pre-stream-wizard/page-select-settings.hpp b/UI/pre-stream-wizard/page-select-settings.hpp index 6ef4e6c495c184..1d31ee1a89d1fb 100644 --- a/UI/pre-stream-wizard/page-select-settings.hpp +++ b/UI/pre-stream-wizard/page-select-settings.hpp @@ -9,14 +9,13 @@ #include "pre-stream-current-settings.hpp" -/* -Shows user encoder configuration options and allows them to select and apply -each. For exmaple can apply a resolution limit but opt-out of using b-frames -even if recommended. -*/ - namespace StreamWizard { +/* +* Shows user encoder configuration options and allows them to select and apply +* each. For exmaple can apply a resolution limit but opt-out of using b-frames +* even if recommended. +*/ class SelectionPage : public QWizardPage { Q_OBJECT diff --git a/UI/pre-stream-wizard/page-start-prompt.hpp b/UI/pre-stream-wizard/page-start-prompt.hpp index 6dd04225bf8944..190c698d46b41a 100644 --- a/UI/pre-stream-wizard/page-start-prompt.hpp +++ b/UI/pre-stream-wizard/page-start-prompt.hpp @@ -9,6 +9,7 @@ class QSignalMapper; class QSize; namespace StreamWizard { + // Prompt if the user wants to verify their settings or close. class StartPage : public QWizardPage { Q_OBJECT diff --git a/UI/pre-stream-wizard/pre-stream-wizard.hpp b/UI/pre-stream-wizard/pre-stream-wizard.hpp index 8a9d17509bb6dd..68a7f28779284a 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.hpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.hpp @@ -14,12 +14,11 @@ class LoadingPage; class ErrorPage; /* - ** The pre-stream wizard is a workflow focused on delivering encoder settings - ** specfic for the streaming destination before going Live. - ** There is a launch context to know if launched from settings, otherwise - ** we'll later add a wizard page and button for going live after new settings. - */ - +* The pre-stream wizard is a workflow focused on delivering encoder settings +* specfic for the streaming destination before going Live. +* There is a launch context to know if launched from settings, otherwise +* we'll later add a wizard page and button for going live after new settings. +*/ class PreStreamWizard : public QWizard { Q_OBJECT diff --git a/UI/pre-stream-wizard/setting-selection-row.hpp b/UI/pre-stream-wizard/setting-selection-row.hpp index 18a8d0dfa531fc..3e941ec5469946 100644 --- a/UI/pre-stream-wizard/setting-selection-row.hpp +++ b/UI/pre-stream-wizard/setting-selection-row.hpp @@ -8,14 +8,14 @@ #include "pre-stream-current-settings.hpp" +namespace StreamWizard { + /* -Shows user encoder configuration options and allows them to select and apply -each. For exmaple can apply a resolution limit but opt-out of using b-frames -even if recommended. +* Shows user encoder configuration options and allows them to select and apply +* each. For exmaple can apply a resolution limit but opt-out of using b-frames +* even if recommended. */ -namespace StreamWizard { - class SelectionRow : public QWidget { Q_OBJECT diff --git a/UI/streaming-settings-util.cpp b/UI/streaming-settings-util.cpp index 5a598bf1f04636..4010c7bee09f3a 100644 --- a/UI/streaming-settings-util.cpp +++ b/UI/streaming-settings-util.cpp @@ -5,34 +5,6 @@ #include -/* - We are detecting the stream settings so if they're using a large canvas but - already have the rescaler on, they will be streaming correctly. -*/ -void UpdateStreamingResolution(int *resolutionXY, bool isRescaled, - config_t *config) -{ - - // If scaled, that's the stream resolution sent to servers - if (isRescaled) { - // Only use if there is a resolution in text - const char *rescaleRes = - config_get_string(config, "AdvOut", "RescaleRes"); - if (rescaleRes && *rescaleRes) { - int count = sscanf(rescaleRes, "%ux%u", - &resolutionXY[0], &resolutionXY[1]); - // If text was valid, exit - if (count == 2) { - return; - } - } - } - - // Resolution is not rescaled, use "output resolution" from video tab - resolutionXY[0] = (int)config_get_uint(config, "Video", "OutputCX"); - resolutionXY[1] = (int)config_get_uint(config, "Video", "OutputCY"); -}; - QSharedPointer StreamingSettingsUtility::makeEncoderSettingsFromCurrentState(config_t *config) { @@ -45,11 +17,10 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState(config_t *config) // only live and rmpts is supported for now currentSettings->protocol = StreamWizard::StreamProtocol::rtmps; /* Video */ - bool isRescaled = config_get_bool(config, "AdvOut", "Rescale"); - int resolutionXY[2] = {0, 0}; - UpdateStreamingResolution(resolutionXY, isRescaled, config); - currentSettings->videoWidth = resolutionXY[0]; - currentSettings->videoHeight = resolutionXY[1]; + currentSettings->videoWidth = + (int)config_get_uint(config, "Video", "OutputCX"); + currentSettings->videoHeight = + (int)config_get_uint(config, "Video", "OutputCY"); currentSettings->framerate = CommonSettings::GetConfigFPSDouble(config); currentSettings->videoBitrate = diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index a7c4b84da498ec..b760ffae1aa5cb 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -279,6 +279,8 @@ void OBSBasicSettings::UpdateKeyLink() ui->getStreamKeyButton->setTargetUrl(QUrl(streamKeyLink)); ui->getStreamKeyButton->show(); } + + hasStreamWizard &= ui->outputMode->currentText().contains("Simple"); ui->settingWizardBtn->setHidden(!hasStreamWizard); } @@ -297,6 +299,9 @@ void OBSBasicSettings::preStreamWizardLaunch() return; } + // Save any changes so far since we'll refrence them from config files + SaveSettings(); + QSharedPointer currentSettings = StreamingSettingsUtility::makeEncoderSettingsFromCurrentState( main->Config()); @@ -317,10 +322,11 @@ void OBSBasicSettings::preStreamWizardApplySettings( { blog(LOG_INFO, "OBSBasicSettings::preStreamWizardApplySettings"); - // Apply and reload + // Apply StreamingSettingsUtility::applyWizardSettings(newSettings, main->Config()); + // Reload LoadSettings(false); } diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 80ed5144b85070..57b9065dbb7e86 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -660,6 +660,8 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) SLOT(UpdateStreamDelayEstimate())); connect(ui->outputMode, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateStreamDelayEstimate())); + connect(ui->outputMode, SIGNAL(currentIndexChanged(int)), this, + SLOT(UpdateKeyLink())); connect(ui->simpleOutputVBitrate, SIGNAL(valueChanged(int)), this, SLOT(UpdateStreamDelayEstimate())); connect(ui->simpleOutputABitrate, SIGNAL(currentIndexChanged(int)), From 6e53c897038cf4240c1eada4dcaad0972852bfc4 Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Mon, 19 Oct 2020 13:15:09 -0700 Subject: [PATCH 12/13] UI: Remove QtNetwork Dependency from Settings UI OBS does not ship OpenSSL and has a curl helper already to use. --- CMakeLists.txt | 1 - UI/CMakeLists.txt | 2 - .../encoder-settings-provider-facebook.cpp | 99 +++++++++++-------- .../encoder-settings-provider-facebook.hpp | 11 +-- UI/pre-stream-wizard/pre-stream-wizard.cpp | 4 +- cmake/Modules/CopyMSVCBins.cmake | 2 - 6 files changed, 62 insertions(+), 57 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99d05ac7610cce..6e4a67175808ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,6 @@ if(NOT INSTALLER_RUN) endif() find_package(Qt5Widgets ${FIND_MODE}) - find_package(Qt5Network ${FIND_MODE}) endif() add_subdirectory(deps) diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 449c166da58db4..7a852690fd915b 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -50,7 +50,6 @@ set(CMAKE_AUTOMOC TRUE) find_package(Qt5Widgets ${FIND_MODE}) find_package(Qt5Svg ${FIND_MODE}) find_package(Qt5Xml ${FIND_MODE}) -find_package(Qt5Network ${FIND_MODE}) find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat) @@ -422,7 +421,6 @@ target_link_libraries(obs Qt5::Widgets Qt5::Svg Qt5::Xml - Qt5::Network obs-frontend-api ${FFMPEG_LIBRARIES} ${LIBCURL_LIBRARIES} diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp index 7ba4a23c46f6eb..733f33c56909b2 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp @@ -5,25 +5,24 @@ #include #include #include -#include -#include #include #include #include #include #include -#include #include "obs-app.hpp" #include "obs-config.h" +#include "remote-text.hpp" +#include + namespace StreamWizard { FacebookEncoderSettingsProvider::FacebookEncoderSettingsProvider(QObject *parent) : QObject(parent) { currentSettings_ = nullptr; - restclient_ = new QNetworkAccessManager(this); } void FacebookEncoderSettingsProvider::setEncoderRequest( @@ -36,10 +35,11 @@ void FacebookEncoderSettingsProvider::run() { // Base URL for request QUrl requestUrl( - "https://graph.facebook.com/v6.0/video_encoder_settings"); + "https://graph.facebook.com/v8.0/video_encoder_settings"); QUrlQuery inputVideoSettingsQuery = inputVideoQueryFromCurrentSettings(); requestUrl.setQuery(inputVideoSettingsQuery); + if (requestUrl.isValid()) { makeRequest(requestUrl); } else { @@ -51,21 +51,41 @@ void FacebookEncoderSettingsProvider::run() void FacebookEncoderSettingsProvider::makeRequest(QUrl &url) { - blog(LOG_INFO, "FacebookEncoderSettingsProvider creating request"); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, - "application/json"); - - // GET is made async - networkReply_ = restclient_->get(request); - pendingResponse_ = true; - // This is the callback when data is ready - connect(restclient_, &QNetworkAccessManager::finished, this, - &FacebookEncoderSettingsProvider::handleResponse); - - // This is a fast API, timeout at 3 seconds and show error - QTimer::singleShot(3000, this, - &FacebookEncoderSettingsProvider::handleTimeout); + blog(LOG_INFO, "FacebookEncoderSettingsProvider sending request"); + + bool requestSuccess = false; + + std::string urlString = url.toString().toStdString(); + std::string reply; + std::string error; + long responseCode = 0; + const char *contentType = "application/json"; + const char *postData = nullptr; + std::vector extraHeaders = std::vector(); + int timeout = 3; // seconds + + auto apiRequestBlock = [&]() { + requestSuccess = GetRemoteFile(urlString.c_str(), reply, error, + &responseCode, contentType, + postData, extraHeaders, nullptr, + timeout); + }; + + ExecuteFuncSafeBlock(apiRequestBlock); + + if (!requestSuccess || responseCode >= 400) { + handleTimeout(); + blog(LOG_WARNING, "Server response with error: %s", + error.c_str()); + } + + if (reply.empty()) { + handleEmpty(); + blog(LOG_WARNING, "Server response was empty"); + } + + QByteArray jsonBytes = QByteArray::fromStdString(reply); + handleResponse(jsonBytes); } QUrlQuery FacebookEncoderSettingsProvider::inputVideoQueryFromCurrentSettings() @@ -182,23 +202,9 @@ void addBool(const QJsonObject &json, const char *jsonKey, SettingsMap *map, } } -void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) +void FacebookEncoderSettingsProvider::handleResponse(QByteArray reply) { - // In timeout and errors this method may still be called - if (!pendingResponse_) { - return; - } - pendingResponse_ = false; - - if (reply->error()) { - emit returnErrorDescription( - QTStr("PreLiveWizard.Configure.Error.JsonParse"), - reply->errorString()); - } - // Converts byte array into JSON doc as a copy - QByteArray replyAll = reply->readAll(); - QJsonDocument jsonDoc = QJsonDocument::fromJson(replyAll); - reply->deleteLater(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply); // Parse bytes to json object if (!jsonDoc.isObject()) { @@ -230,6 +236,10 @@ void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) QJsonObject videoSettingsJsob = rmtpSettings["video_codec_settings"].toObject(); + if (videoSettingsJsob.isEmpty()) { + handleEmpty(); + } + // Create map to send to wizard SettingsMap *settingsMap = new SettingsMap(); @@ -260,6 +270,11 @@ void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) addInt(videoSettingsJsob, "buffer_size", settingsMap, SettingsResponseKeys.streamBufferSize); + // If Empty emit to empty / error state + if (settingsMap->isEmpty()) { + handleEmpty(); + } + // Wrap into shared pointer and emit QSharedPointer settingsMapShrdPtr = QSharedPointer(settingsMap); @@ -268,16 +283,18 @@ void FacebookEncoderSettingsProvider::handleResponse(QNetworkReply *reply) void FacebookEncoderSettingsProvider::handleTimeout() { - if (!pendingResponse_) - return; - - pendingResponse_ = false; - QString errorTitle = QTStr("PreLiveWizard.Configure.Error.JsonParse"); QString errorDescription = QTStr("PreLiveWizard.Error.NetworkTimeout"); emit returnErrorDescription(errorTitle, errorDescription); } +void FacebookEncoderSettingsProvider::handleEmpty() +{ + emit returnErrorDescription( + QTStr("PreLiveWizard.Configure.Error.NoData"), + QTStr("PreLiveWizard.Configure.Error.NoData.Description")); +} + void FacebookEncoderSettingsProvider::jsonParseError() { emit returnErrorDescription( diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp index a06ddd51a1cbed..b665f73bdcf74d 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp @@ -6,8 +6,6 @@ #include #include #include -#include -#include #include "pre-stream-current-settings.hpp" @@ -36,18 +34,15 @@ class FacebookEncoderSettingsProvider : public QObject { private: QSharedPointer currentSettings_; - QNetworkAccessManager *restclient_; - QNetworkReply *networkReply_; bool pendingResponse_ = false; void makeRequest(QUrl &url); QUrlQuery inputVideoQueryFromCurrentSettings(); QString getOBSVersionString(); - void jsonParseError(); - -private slots: - void handleResponse(QNetworkReply *reply); + void handleResponse(QByteArray reply); void handleTimeout(); + void handleEmpty(); + void jsonParseError(); }; } // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-wizard.cpp b/UI/pre-stream-wizard/pre-stream-wizard.cpp index 1ba41777582716..8bb9c9fe9e2c81 100644 --- a/UI/pre-stream-wizard/pre-stream-wizard.cpp +++ b/UI/pre-stream-wizard/pre-stream-wizard.cpp @@ -123,7 +123,6 @@ void PreStreamWizard::onPageChanged(int id) case Page_Loading: requestSettings(); - setButtons(CancelOnly); break; case Page_Selections: @@ -134,8 +133,7 @@ void PreStreamWizard::onPageChanged(int id) case Page_Complete: setButtons(FinishOnly); if (newSettingsMap_ != nullptr && !newSettingsMap_.isNull()) { - // ToDo: messaging in edge case this could be empty - // and still make it here? + // ToDo: messaging in edge case this could be empty? emit applySettings(newSettingsMap_); }; break; diff --git a/cmake/Modules/CopyMSVCBins.cmake b/cmake/Modules/CopyMSVCBins.cmake index 938fc853730f47..504d7b1ca511d0 100644 --- a/cmake/Modules/CopyMSVCBins.cmake +++ b/cmake/Modules/CopyMSVCBins.cmake @@ -159,7 +159,6 @@ file(GLOB QT_DEBUG_BIN_FILES "${Qt5Core_DIR}/../../../bin/Qt5Widgetsd.dll" "${Qt5Core_DIR}/../../../bin/Qt5Svgd.dll" "${Qt5Core_DIR}/../../../bin/Qt5Xmld.dll" - "${Qt5Core_DIR}/../../../bin/Qt5Networkd.dll" "${Qt5Core_DIR}/../../../bin/libGLESv2d.dll" "${Qt5Core_DIR}/../../../bin/libEGLd.dll") file(GLOB QT_DEBUG_PLAT_BIN_FILES @@ -178,7 +177,6 @@ file(GLOB QT_BIN_FILES "${Qt5Core_DIR}/../../../bin/Qt5Widgets.dll" "${Qt5Core_DIR}/../../../bin/Qt5Svg.dll" "${Qt5Core_DIR}/../../../bin/Qt5Xml.dll" - "${Qt5Core_DIR}/../../../bin/Qt5Network.dll" "${Qt5Core_DIR}/../../../bin/libGLESv2.dll" "${Qt5Core_DIR}/../../../bin/libEGL.dll") file(GLOB QT_PLAT_BIN_FILES From bdc3a3ebe3889b23f0495957cc14c7a78c44df4a Mon Sep 17 00:00:00 2001 From: Johann Garces Date: Wed, 21 Oct 2020 11:04:57 -0700 Subject: [PATCH 13/13] UI: Solves issue with QMap Keys & Feedback Various fixes. --- UI/CMakeLists.txt | 1 + UI/common-settings.cpp | 13 ++- UI/data/locale/en-US.ini | 23 ++--- .../encoder-settings-provider-facebook.cpp | 82 +++++++--------- .../encoder-settings-provider-facebook.hpp | 1 - UI/pre-stream-wizard/page-completed.cpp | 4 +- UI/pre-stream-wizard/page-select-settings.cpp | 95 +++++++++---------- UI/pre-stream-wizard/page-select-settings.hpp | 4 +- UI/pre-stream-wizard/page-start-prompt.cpp | 7 +- .../pre-stream-current-settings.cpp | 22 +++++ .../pre-stream-current-settings.hpp | 48 ++++------ .../setting-selection-row.cpp | 4 +- .../setting-selection-row.hpp | 8 +- UI/streaming-settings-util.cpp | 75 +++++++-------- 14 files changed, 188 insertions(+), 199 deletions(-) create mode 100644 UI/pre-stream-wizard/pre-stream-current-settings.cpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 7a852690fd915b..ba08f87f565fe3 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -190,6 +190,7 @@ set(obs_SOURCES ${obs_PLATFORM_SOURCES} ${obs_libffutil_SOURCES} ../deps/json11/json11.cpp + pre-stream-wizard/pre-stream-current-settings.cpp pre-stream-wizard/pre-stream-wizard.cpp pre-stream-wizard/page-input-display.cpp pre-stream-wizard/page-start-prompt.cpp diff --git a/UI/common-settings.cpp b/UI/common-settings.cpp index 4bd4ab6a49c22a..444107c1fc5cc4 100644 --- a/UI/common-settings.cpp +++ b/UI/common-settings.cpp @@ -23,6 +23,7 @@ OBSData CommonSettings::GetDataFromJsonFile(const char *jsonFile) if (!data) data = obs_data_create(); + OBSData dataRet(data); obs_data_release(data); return dataRet; @@ -135,9 +136,7 @@ int CommonSettings::GetAudioChannelCount(config_t *config) int CommonSettings::GetStreamingAudioBitrate(config_t *config) { - bool isAdvancedMode = IsAdvancedMode(config); - - if (isAdvancedMode) { + if (IsAdvancedMode(config)) { return GetAdvancedAudioBitrate(config); } return GetSimpleAudioBitrate(config); @@ -164,10 +163,11 @@ int CommonSettings::GetAdvancedAudioBitrateForTrack(config_t *config, }; // Sanity check for out of bounds, clamp to bounds - if (trackIndex > 5) + if (trackIndex > 5) { trackIndex = 5; - if (trackIndex < 0) + } else if (trackIndex < 0) { trackIndex = 0; + } int bitrate = (int)config_get_uint(config, "AdvOut", names[trackIndex]); return FindClosestAvailableAACBitrate(bitrate); @@ -180,6 +180,5 @@ int CommonSettings::GetVideoBitrateInUse(config_t *config) } OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); - int bitrate = obs_data_get_int(streamEncSettings, "bitrate"); - return bitrate; + return obs_data_get_int(streamEncSettings, "bitrate"); } diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index b3dbdad7a42e39..b86a8a3ddd4d38 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -712,13 +712,13 @@ Basic.Settings.Stream.MissingStreamKey="Stream key is missing.\n\nOpen settings Basic.Settings.Stream.PreLiveWizard.RunNow="Check stream settings" PreLiveWizard.Title="Verify Stream Settings" PreLiveWizard.Prompt.Title="Verify stream settings before stream" -PreLiveWizard.Prompt.Subtitle.Default="The streaming wizard suggests the most up-to-date settings to improve your stream’s reliability, quality, and latency. \n\nSelect the resolution to stream to your account:" +PreLiveWizard.Prompt.Subtitle.Default="The streaming wizard suggests the most up-to-date settings to improve your stream's reliability, quality, and latency. \n\nSelect the resolution to stream to your account:" PreLiveWizard.Prompt.Subtitle.FB="Your destination is set to Facebook Live." -PreLiveWizard.Prompt.Explainer="The Streaming wizard suggests the most up-to-date settings to improve your stream’s reliability, quality, and latency." +PreLiveWizard.Prompt.Explainer="The Streaming wizard suggests the most up-to-date settings to improve your stream's reliability, quality, and latency." PreLiveWizard.Prompt.ResSelectTitle="Select a resolution to stream:" -PreLiveWizard.Prompt.FBResolutionHelp.FB="If you’re unsure, click Open Facebook Live, then click on the \"Stream Health\" tab and scroll down to find your maximum resolution." -PreLiveWizard.Prompt.FBResolutionHelpButton.FB="Open Facebook Live" -PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip="Opens Facebook Facebook Live in your default internet browser" +PreLiveWizard.Prompt.ResolutionHelp.FB="If you're unsure, click Open Facebook Live, then click on the \"Stream Health\" tab and scroll down to find your maximum resolution." +PreLiveWizard.Prompt.ResolutionHelpButton.FB="Open Facebook Live" +PreLiveWizard.Prompt.ResolutionHelpButton.FB.ToolTip="Open Facebook Live in your default web browser" PreLiveWizard.Prompt.Resolution.720="720p (Recommended for general purpose)" PreLiveWizard.Prompt.Resolution.1080="1080p (Recommended for gaming)" PreLiveWizard.Prompt.Resolution.Current="%1x%2 (Current setting)" @@ -727,18 +727,23 @@ PreLiveWizard.Configure.ServiceNotAvailable.Title="Service not supported" PreLiveWizard.Configure.ServiceNotAvailable.Description="The service selected in stream settings does not yet have an encoder configuration service." PreLiveWizard.Configure.Error.Url="Settings setup failed. Query: " PreLiveWizard.Configure.Error.JsonParse="Problem with server response" -PreLiveWizard.Configure.Error.JsonParse.Description="Wizard is unavailble at the moment." +PreLiveWizard.Configure.Error.JsonParse.Description="Wizard is unavailable at the moment." PreLiveWizard.Configure.Error.NoData="No new settings recieved." +PreLiveWizard.Configure.Error.NoData.Description=" " PreLiveWizard.Selection.Title="Suggested Settings" PreLiveWizard.Selection.Description="Suggested video settings for your best stream:" PreLiveWizard.Completed.Title="Encoder configured" PreLiveWizard.Completed.BodyText="Applied suggested settings and encoder is setup to stream. You can now close the wizard." PreLiveWizard.Completed.FacebookOnly="To finish going live go to Facebook Live:" PreLiveWizard.Error.Title="There was an issue verifying settings" -PreLiveWizard.Error.Subtitle="This doesn’t block you from starting your stream." +PreLiveWizard.Error.Subtitle="This doesn't block you from starting your stream." PreLiveWizard.Error.Generic.Headline="Encoder service is not working right now." PreLiveWizard.Error.Generic.BodyText="You can exit the wizard and try again." PreLiveWizard.Error.NetworkTimeout="Check your network connection before starting a stream." +PreLiveWizard.Output.Mode.CodecProfile="Profile" +PreLiveWizard.Output.Mode.CodecLevel="Level" +PreLiveWizard.Output.Mode.RateControl="Rate Control" +PreLiveWizard.Output.Mode.StreamBuffer="Stream Buffer" # basic mode 'output' settings Basic.Settings.Output="Output" @@ -754,10 +759,6 @@ Basic.Settings.Output.Mode="Output Mode" Basic.Settings.Output.Mode.Simple="Simple" Basic.Settings.Output.Mode.Adv="Advanced" Basic.Settings.Output.Mode.FFmpeg="FFmpeg Output" -Basic.Settings.Output.Mode.CodecProfile="Profile" -Basic.Settings.Output.Mode.CodecLevel="Level" -Basic.Settings.Output.Mode.RateControl="Rate Control" -Basic.Settings.Output.Mode.StreamBuffer="Stream Buffer" Basic.Settings.Output.UseReplayBuffer="Enable Replay Buffer" Basic.Settings.Output.ReplayBuffer.SecondsMax="Maximum Replay Time" Basic.Settings.Output.ReplayBuffer.MegabytesMax="Maximum Memory (Megabytes)" diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp index 733f33c56909b2..34b29e6ff9293c 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.cpp @@ -130,23 +130,9 @@ QUrlQuery FacebookEncoderSettingsProvider::inputVideoQueryFromCurrentSettings() return inputVideoSettingsQuery; } -QString FacebookEncoderSettingsProvider::getOBSVersionString() -{ - QString versionString; - -#ifdef HAVE_OBSCONFIG_H - versionString += OBS_VERSION; -#else - versionString += LIBOBS_API_MAJOR_VER + "." + LIBOBS_API_MINOR_VER + - "." + LIBOBS_API_PATCH_VER; -#endif - - return versionString; -} - // Helper methods for FacebookEncoderSettingsProvider::handleResponse void addInt(const QJsonObject &json, const char *jsonKey, SettingsMap *map, - const char *mapKey) + QString mapKey) { if (json[jsonKey].isDouble()) { map->insert(mapKey, @@ -159,7 +145,7 @@ void addInt(const QJsonObject &json, const char *jsonKey, SettingsMap *map, } void addStringDouble(const QJsonObject &json, const char *jsonKey, - SettingsMap *map, const char *mapKey) + SettingsMap *map, QString mapKey) { if (!json[jsonKey].isString()) { return; @@ -177,7 +163,7 @@ void addStringDouble(const QJsonObject &json, const char *jsonKey, } void addQString(const QJsonObject &json, const char *jsonKey, SettingsMap *map, - const char *mapKey) + QString mapKey) { if (json[jsonKey].isString()) { map->insert(mapKey, @@ -190,7 +176,7 @@ void addQString(const QJsonObject &json, const char *jsonKey, SettingsMap *map, } void addBool(const QJsonObject &json, const char *jsonKey, SettingsMap *map, - const char *mapKey) + QString mapKey) { if (json[jsonKey].isBool()) { map->insert(mapKey, @@ -224,51 +210,49 @@ void FacebookEncoderSettingsProvider::handleResponse(QByteArray reply) jsonParseError(); return; } - QJsonObject rmtpSettings = responseObject["rtmps_settings"].toObject(); + QJsonObject rtmpSettings = responseObject["rtmps_settings"].toObject(); // Get the video codec object - if (!rmtpSettings["video_codec_settings"].isObject()) { + if (!rtmpSettings["video_codec_settings"].isObject()) { blog(LOG_INFO, "FacebookEncoderSettingsProvider video_codec_settings not an object"); jsonParseError(); return; } - QJsonObject videoSettingsJsob = - rmtpSettings["video_codec_settings"].toObject(); + QJsonObject videoSettingsJson = + rtmpSettings["video_codec_settings"].toObject(); - if (videoSettingsJsob.isEmpty()) { + if (videoSettingsJson.isEmpty()) { handleEmpty(); } // Create map to send to wizard SettingsMap *settingsMap = new SettingsMap(); - addInt(videoSettingsJsob, "video_bitrate", settingsMap, - SettingsResponseKeys.videoBitrate); - addInt(videoSettingsJsob, "video_width", settingsMap, - SettingsResponseKeys.videoWidth); - addInt(videoSettingsJsob, "video_height", settingsMap, - SettingsResponseKeys.videoHeight); - addStringDouble(videoSettingsJsob, "video_framerate", settingsMap, - SettingsResponseKeys.framerate); - addQString(videoSettingsJsob, "video_h264_profile", settingsMap, - SettingsResponseKeys.h264Profile); - addQString(videoSettingsJsob, "video_h264_level", settingsMap, - SettingsResponseKeys.h264Level); - addInt(videoSettingsJsob, "video_gop_size", settingsMap, - SettingsResponseKeys.gopSizeInFrames); - addQString(videoSettingsJsob, "video_gop_type", settingsMap, - SettingsResponseKeys.gopType); - addBool(videoSettingsJsob, "video_gop_closed", settingsMap, - SettingsResponseKeys.gopClosed); - addInt(videoSettingsJsob, "video_gop_num_b_frames", settingsMap, - SettingsResponseKeys.gopBFrames); - addInt(videoSettingsJsob, "video_gop_num_ref_frames", settingsMap, - SettingsResponseKeys.gopRefFrames); - addQString(videoSettingsJsob, "rate_control_mode", settingsMap, - SettingsResponseKeys.streamRateControlMode); - addInt(videoSettingsJsob, "buffer_size", settingsMap, - SettingsResponseKeys.streamBufferSize); + addInt(videoSettingsJson, "video_bitrate", settingsMap, + kSettingsResponseKeys.videoBitrate); + addInt(videoSettingsJson, "video_width", settingsMap, + kSettingsResponseKeys.videoWidth); + addInt(videoSettingsJson, "video_height", settingsMap, + kSettingsResponseKeys.videoHeight); + addStringDouble(videoSettingsJson, "video_framerate", settingsMap, + kSettingsResponseKeys.framerate); + addQString(videoSettingsJson, "video_h264_profile", settingsMap, + kSettingsResponseKeys.h264Profile); + addQString(videoSettingsJson, "video_h264_level", settingsMap, + kSettingsResponseKeys.h264Level); + addInt(videoSettingsJson, "video_gop_size", settingsMap, + kSettingsResponseKeys.gopSizeInFrames); + addBool(videoSettingsJson, "video_gop_closed", settingsMap, + kSettingsResponseKeys.gopClosed); + addInt(videoSettingsJson, "video_gop_num_b_frames", settingsMap, + kSettingsResponseKeys.gopBFrames); + addInt(videoSettingsJson, "video_gop_num_ref_frames", settingsMap, + kSettingsResponseKeys.gopRefFrames); + addQString(videoSettingsJson, "rate_control_mode", settingsMap, + kSettingsResponseKeys.streamRateControlMode); + addInt(videoSettingsJson, "buffer_size", settingsMap, + kSettingsResponseKeys.streamBufferSize); // If Empty emit to empty / error state if (settingsMap->isEmpty()) { diff --git a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp index b665f73bdcf74d..f32ec8914161c3 100644 --- a/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp +++ b/UI/pre-stream-wizard/encoder-settings-provider-facebook.hpp @@ -38,7 +38,6 @@ class FacebookEncoderSettingsProvider : public QObject { void makeRequest(QUrl &url); QUrlQuery inputVideoQueryFromCurrentSettings(); - QString getOBSVersionString(); void handleResponse(QByteArray reply); void handleTimeout(); void handleEmpty(); diff --git a/UI/pre-stream-wizard/page-completed.cpp b/UI/pre-stream-wizard/page-completed.cpp index 6db3416387757c..909c227ce757f6 100644 --- a/UI/pre-stream-wizard/page-completed.cpp +++ b/UI/pre-stream-wizard/page-completed.cpp @@ -37,10 +37,10 @@ CompletedPage::CompletedPage(Destination destination, QTStr("PreLiveWizard.Completed.FacebookOnly"), this); facebookGoLiveLabel->setWordWrap(true); QPushButton *launchButton = new QPushButton( - QTStr("PreLiveWizard.Prompt.FBResolutionHelpButton.FB"), + QTStr("PreLiveWizard.Prompt.ResolutionHelpButton.FB"), this); launchButton->setToolTip(QTStr( - "PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip")); + "PreLiveWizard.Prompt.ResolutionHelpButton.FB.ToolTip")); connect(launchButton, &QPushButton::clicked, this, &CompletedPage::didPushOpenWebsite); diff --git a/UI/pre-stream-wizard/page-select-settings.cpp b/UI/pre-stream-wizard/page-select-settings.cpp index fd8be3bf909d59..e350ac4e3a0d79 100644 --- a/UI/pre-stream-wizard/page-select-settings.cpp +++ b/UI/pre-stream-wizard/page-select-settings.cpp @@ -60,119 +60,112 @@ void SelectionPage::setSettingsMap(QSharedPointer settingsMap) settingsMap_ = settingsMap; SettingsMap *mapInfo = settingsMap_.data(); - if (mapInfo->contains(SettingsResponseKeys.videoBitrate)) { + if (mapInfo->contains(kSettingsResponseKeys.videoBitrate)) { QVariant data = - mapInfo->value(SettingsResponseKeys.videoBitrate).first; + mapInfo->value(kSettingsResponseKeys.videoBitrate).first; QString bitrateString = QString::number(data.toInt()) + "kbps"; addRow(QTStr("Basic.Settings.Output.VideoBitrate"), - bitrateString, SettingsResponseKeys.videoBitrate); + bitrateString, kSettingsResponseKeys.videoBitrate); } - // Uses SettingsResponseKeys.videoHeight to signal change - if (mapInfo->contains(SettingsResponseKeys.videoWidth) && - mapInfo->contains(SettingsResponseKeys.videoHeight)) { - int vidW = mapInfo->value(SettingsResponseKeys.videoWidth) + // Uses kSettingsResponseKeys.videoHeight to signal change + if (mapInfo->contains(kSettingsResponseKeys.videoWidth) && + mapInfo->contains(kSettingsResponseKeys.videoHeight)) { + int vidW = mapInfo->value(kSettingsResponseKeys.videoWidth) .first.toInt(); QString videoWidthString = QString::number(vidW); - int vidH = mapInfo->value(SettingsResponseKeys.videoHeight) + int vidH = mapInfo->value(kSettingsResponseKeys.videoHeight) .first.toInt(); QString videoHeightString = QString::number(vidH); QString valueString = videoWidthString + "x" + videoHeightString; addRow(QTStr("Basic.Settings.Video.ScaledResolution"), - valueString, SettingsResponseKeys.videoHeight); + valueString, kSettingsResponseKeys.videoHeight); } - if (mapInfo->contains(SettingsResponseKeys.framerate)) { + if (mapInfo->contains(kSettingsResponseKeys.framerate)) { QVariant data = - mapInfo->value(SettingsResponseKeys.framerate).first; + mapInfo->value(kSettingsResponseKeys.framerate).first; double framerate = data.toDouble(); - QString fpsString = QString().sprintf("%.3f", framerate); + QString fpsString = QString().asprintf("%.3f", framerate); addRow(QTStr("Basic.Settings.Video.FPS"), fpsString, - SettingsResponseKeys.framerate); + kSettingsResponseKeys.framerate); } - if (mapInfo->contains(SettingsResponseKeys.h264Profile)) { + if (mapInfo->contains(kSettingsResponseKeys.h264Profile)) { QVariant data = - mapInfo->value(SettingsResponseKeys.h264Profile).first; + mapInfo->value(kSettingsResponseKeys.h264Profile).first; QString profileString = data.toString(); - addRow(QTStr("Basic.Settings.Output.Mode.CodecProfile"), - profileString, SettingsResponseKeys.h264Profile); + addRow(QTStr("PreLiveWizard.Output.Mode.CodecProfile"), + profileString, kSettingsResponseKeys.h264Profile); } - if (mapInfo->contains(SettingsResponseKeys.h264Level)) { + if (mapInfo->contains(kSettingsResponseKeys.h264Level)) { QVariant data = - mapInfo->value(SettingsResponseKeys.h264Level).first; + mapInfo->value(kSettingsResponseKeys.h264Level).first; QString level = data.toString(); - addRow(QTStr("Basic.Settings.Output.Mode.CodecLevel"), level, - SettingsResponseKeys.h264Level); + addRow(QTStr("PreLiveWizard.Output.Mode.CodecLevel"), level, + kSettingsResponseKeys.h264Level); } - if (mapInfo->contains(SettingsResponseKeys.gopSizeInFrames)) { + if (mapInfo->contains(kSettingsResponseKeys.gopSizeInFrames)) { QVariant data = - mapInfo->value(SettingsResponseKeys.gopSizeInFrames) + mapInfo->value(kSettingsResponseKeys.gopSizeInFrames) .first; QString gopFramesString = QString::number(data.toInt()); addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.GOPSize"), - gopFramesString, SettingsResponseKeys.gopSizeInFrames); + gopFramesString, kSettingsResponseKeys.gopSizeInFrames); } - if (mapInfo->contains(SettingsResponseKeys.gopType)) { + if (mapInfo->contains(kSettingsResponseKeys.gopClosed)) { QVariant data = - mapInfo->value(SettingsResponseKeys.gopType).first; - QString gopTypeString = data.toString(); - addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.GOPType"), - gopTypeString, SettingsResponseKeys.gopType); - } - - if (mapInfo->contains(SettingsResponseKeys.gopClosed)) { - QVariant data = - mapInfo->value(SettingsResponseKeys.gopClosed).first; + mapInfo->value(kSettingsResponseKeys.gopClosed).first; QString gopClosedString = data.toBool() ? QTStr("Yes") : QTStr("No"); addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.GOPClosed"), - gopClosedString, SettingsResponseKeys.gopClosed); + gopClosedString, kSettingsResponseKeys.gopClosed); } - if (mapInfo->contains(SettingsResponseKeys.gopBFrames)) { + if (mapInfo->contains(kSettingsResponseKeys.gopBFrames)) { QVariant data = - mapInfo->value(SettingsResponseKeys.gopBFrames).first; + mapInfo->value(kSettingsResponseKeys.gopBFrames).first; QString bFramesString = QString::number(data.toInt()); addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.BFrames"), - bFramesString, SettingsResponseKeys.gopBFrames); + bFramesString, kSettingsResponseKeys.gopBFrames); } - if (mapInfo->contains(SettingsResponseKeys.gopRefFrames)) { + if (mapInfo->contains(kSettingsResponseKeys.gopRefFrames)) { QVariant data = - mapInfo->value(SettingsResponseKeys.gopRefFrames).first; + mapInfo->value(kSettingsResponseKeys.gopRefFrames).first; QString gopRefFramesCountString = QString::number(data.toInt()); addRow(QTStr("Basic.Settings.Output.Adv.FFmpeg.ReferenceFrames"), gopRefFramesCountString, - SettingsResponseKeys.gopRefFrames); + kSettingsResponseKeys.gopRefFrames); } - if (mapInfo->contains(SettingsResponseKeys.streamRateControlMode)) { - QVariant data = mapInfo->value(SettingsResponseKeys + if (mapInfo->contains(kSettingsResponseKeys.streamRateControlMode)) { + QVariant data = mapInfo->value(kSettingsResponseKeys .streamRateControlMode) .first; QString rateControlString = data.toString().toUpper(); - addRow(QTStr("Basic.Settings.Output.Mode.RateControl"), + addRow(QTStr("PreLiveWizard.Output.Mode.RateControl"), rateControlString, - SettingsResponseKeys.streamRateControlMode); + kSettingsResponseKeys.streamRateControlMode); } - if (mapInfo->contains(SettingsResponseKeys.streamBufferSize)) { + if (mapInfo->contains(kSettingsResponseKeys.streamBufferSize)) { QVariant data = - mapInfo->value(SettingsResponseKeys.streamBufferSize) + mapInfo->value(kSettingsResponseKeys.streamBufferSize) .first; QString bufferSizeString = QString::number(data.toInt()) + "Kb"; - addRow(QTStr("Basic.Settings.Output.Mode.StreamBuffer"), - bufferSizeString, SettingsResponseKeys.streamBufferSize); + addRow(QTStr("PreLiveWizard.Output.Mode.StreamBuffer"), + bufferSizeString, + kSettingsResponseKeys.streamBufferSize); } } -void SelectionPage::addRow(QString title, QString value, const char *mapKey) +void SelectionPage::addRow(QString title, QString value, QString mapKey) { SelectionRow *row = new SelectionRow(); row->setName(title); @@ -183,7 +176,7 @@ void SelectionPage::addRow(QString title, QString value, const char *mapKey) &SelectionPage::checkboxRowChanged); } -void SelectionPage::checkboxRowChanged(const char *propertyKey, bool selected) +void SelectionPage::checkboxRowChanged(QString propertyKey, bool selected) { if (settingsMap_ == nullptr) { return; diff --git a/UI/pre-stream-wizard/page-select-settings.hpp b/UI/pre-stream-wizard/page-select-settings.hpp index 1d31ee1a89d1fb..fb859a6af59262 100644 --- a/UI/pre-stream-wizard/page-select-settings.hpp +++ b/UI/pre-stream-wizard/page-select-settings.hpp @@ -31,7 +31,7 @@ class SelectionPage : public QWizardPage { void mapContainsVariant(); // Adds a row only if map contains values for it - void addRow(QString title, QString value, const char *mapKey); + void addRow(QString title, QString value, QString mapKey); // Data QSharedPointer settingsMap_; @@ -43,7 +43,7 @@ class SelectionPage : public QWizardPage { QVBoxLayout *scrollVBoxLayout_; private slots: - void checkboxRowChanged(const char *propertyKey, bool selected); + void checkboxRowChanged(QString propertyKey, bool selected); }; // class SelectionPage diff --git a/UI/pre-stream-wizard/page-start-prompt.cpp b/UI/pre-stream-wizard/page-start-prompt.cpp index f18fbcdd159c31..080a9cab16e580 100644 --- a/UI/pre-stream-wizard/page-start-prompt.cpp +++ b/UI/pre-stream-wizard/page-start-prompt.cpp @@ -76,14 +76,13 @@ void StartPage::createLayout() if (destination_ == Destination::Facebook) { // If FB, specfic help section helpLabel = new QLabel( - QTStr("PreLiveWizard.Prompt.FBResolutionHelp.FB"), - this); + QTStr("PreLiveWizard.Prompt.ResolutionHelp.FB"), this); helpLabel->setWordWrap(true); helpButton = new QPushButton( - QTStr("PreLiveWizard.Prompt.FBResolutionHelpButton.FB"), + QTStr("PreLiveWizard.Prompt.ResolutionHelpButton.FB"), this); helpButton->setToolTip(QTStr( - "PreLiveWizard.Prompt.FBResolutionHelpButton.FB.ToolTip")); + "PreLiveWizard.Prompt.ResolutionHelpButton.FB.ToolTip")); connect(helpButton, &QPushButton::clicked, this, &StartPage::didPushOpenWebsiteHelp); } diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.cpp b/UI/pre-stream-wizard/pre-stream-current-settings.cpp new file mode 100644 index 00000000000000..751e5aa47d0bc9 --- /dev/null +++ b/UI/pre-stream-wizard/pre-stream-current-settings.cpp @@ -0,0 +1,22 @@ +#include "pre-stream-current-settings.hpp" + +namespace StreamWizard { + +SettingsResponseKeys kSettingsResponseKeys = { + "videoWidth", + "videoHeight", + "framerate", + "videoBitrate", + "protocol", + "videoCodec", + "h264Profile", + "h264Level", + "gopSizeInFrames", + "gopClosed", + "gopBFrames", + "gopRefFrames", + "streamRateControlMode", + "streamBufferSize", +}; + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/pre-stream-current-settings.hpp b/UI/pre-stream-wizard/pre-stream-current-settings.hpp index 86ddaf01781b65..ad268e8584cc4c 100644 --- a/UI/pre-stream-wizard/pre-stream-current-settings.hpp +++ b/UI/pre-stream-wizard/pre-stream-current-settings.hpp @@ -73,35 +73,29 @@ struct EncoderSettingsRequest { }; // Map for the repsonse passed to UI and settings is: -using SettingsMap = QMap>; +using SettingsMap = QMap>; // where String = map key from kSettingsResponseKeys, and // where QVariant is the Settings value, and // where bool is if the user wants to apply the settings // Keys for new settings QMap -static const struct { - const char *videoWidth; //int - const char *videoHeight; //int - const char *framerate; // double (FPS) - const char *videoBitrate; //int - const char *protocol; // string - const char *videoCodec; // string - const char *h264Profile; // string ("High") - const char *h264Level; // string ("4.1") - const char *gopSizeInFrames; // int - const char *gopType; // string ("fixed") - const char *gopClosed; // bool - const char *gopBFrames; // int - const char *gopRefFrames; // int - const char *streamRateControlMode; // string "CBR" - const char *streamBufferSize; // int (5000 kb) -} SettingsResponseKeys = { - "videoWidth", "videoHeight", - "framerate", "videoBitrate", - "protocol", "videoCodec", - "h264Profile", "h264Level", - "gopSizeInFrames", "gopType", - "gopClosed", "gopBFrames", - "gopRefFrames", "streamRateControlMode", - "streamBufferSize", +struct SettingsResponseKeys { + QString videoWidth; //int + QString videoHeight; //int + QString framerate; // double (FPS) + QString videoBitrate; //int + QString protocol; // string + QString videoCodec; // string + QString h264Profile; // string ("High") + QString h264Level; // string ("4.1") + QString gopSizeInFrames; // int + QString gopClosed; // bool + QString gopBFrames; // int + QString gopRefFrames; // int + QString streamRateControlMode; // string "CBR" + QString streamBufferSize; // int (5000 kb) }; -} + +// Defined in pre-stream-current-settings.cpp +extern SettingsResponseKeys kSettingsResponseKeys; + +} // namespace StreamWizard diff --git a/UI/pre-stream-wizard/setting-selection-row.cpp b/UI/pre-stream-wizard/setting-selection-row.cpp index 86fe8012b07880..321d5e4e62a401 100644 --- a/UI/pre-stream-wizard/setting-selection-row.cpp +++ b/UI/pre-stream-wizard/setting-selection-row.cpp @@ -60,12 +60,12 @@ QString SelectionRow::getValueLabel() return valueLabel_; } -void SelectionRow::setPropertyKey(const char *newKey) +void SelectionRow::setPropertyKey(QString newKey) { propertyKey_ = newKey; } -const char *SelectionRow::getPropertyKey() +QString SelectionRow::getPropertyKey() { return propertyKey_; } diff --git a/UI/pre-stream-wizard/setting-selection-row.hpp b/UI/pre-stream-wizard/setting-selection-row.hpp index 3e941ec5469946..1492d3b7981b7d 100644 --- a/UI/pre-stream-wizard/setting-selection-row.hpp +++ b/UI/pre-stream-wizard/setting-selection-row.hpp @@ -31,12 +31,12 @@ class SelectionRow : public QWidget { QString getValueLabel(); // Key to mapping property to Settings Map - void setPropertyKey(const char *newKey); - const char *getPropertyKey(); + void setPropertyKey(QString newKey); + QString getPropertyKey(); signals: // User changed if they want to apply an option - void didChangeSelectedStatus(const char *propertyKey, bool selected); + void didChangeSelectedStatus(QString propertyKey, bool selected); private: void createLayout(); @@ -46,7 +46,7 @@ class SelectionRow : public QWidget { // Visual from upper class QString name_; QString valueLabel_; - const char *propertyKey_; + QString propertyKey_; // Layout and subwidgets QString separator_ = ": "; diff --git a/UI/streaming-settings-util.cpp b/UI/streaming-settings-util.cpp index 4010c7bee09f3a..ce80fde3c4078f 100644 --- a/UI/streaming-settings-util.cpp +++ b/UI/streaming-settings-util.cpp @@ -43,7 +43,7 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState(config_t *config) /* * StreamWizard::SettingsMap is sparse in that it wont have all keys * as well, the user can opt out of settings being applied. -* The map looks like [ SettingsResponseKeys : QPair ] +* The map looks like [ kSettingsResponseKeys : QPair ] * AKA [KEY : SetttingPair ] * if the key is available, it means the wizard provider added a value for it * but the bool in the QPair is false if the user selected in the wizard not to @@ -52,7 +52,7 @@ StreamingSettingsUtility::makeEncoderSettingsFromCurrentState(config_t *config) * Returns TRUE is map contrains something for the key and it is marked true */ -bool CheckInMapAndSelected(StreamWizard::SettingsMap *map, const char *key) +bool CheckInMapAndSelected(StreamWizard::SettingsMap *map, QString key) { if (!map->contains(key)) { return false; @@ -62,28 +62,28 @@ bool CheckInMapAndSelected(StreamWizard::SettingsMap *map, const char *key) } // Helper Functions for ::applyWizardSettings -int intFromMap(StreamWizard::SettingsMap *map, const char *key) +int intFromMap(StreamWizard::SettingsMap *map, QString key) { QPair settingPair = map->value(key); QVariant data = settingPair.first; return data.toInt(); } -QString stringFromMap(StreamWizard::SettingsMap *map, const char *key) +QString stringFromMap(StreamWizard::SettingsMap *map, QString key) { QPair settingPair = map->value(key); QVariant data = settingPair.first; return data.toString(); } -double doubleFromMap(StreamWizard::SettingsMap *map, const char *key) +double doubleFromMap(StreamWizard::SettingsMap *map, QString key) { QPair settingPair = map->value(key); QVariant data = settingPair.first; return data.toDouble(); } -bool boolFromMap(StreamWizard::SettingsMap *map, const char *key) +bool boolFromMap(StreamWizard::SettingsMap *map, QString key) { QPair settingPair = map->value(key); QVariant data = settingPair.first; @@ -91,7 +91,7 @@ bool boolFromMap(StreamWizard::SettingsMap *map, const char *key) } /* -* Given a settings map [ SettingsResponseKeys : QPair ] apply +* Given a settings map [ kSettingsResponseKeys : QPair ] apply * settings that are in the sparse map as well selected by the user (which is * marked by the bool in the pair). * Apply to Basic encoder settings. @@ -113,20 +113,21 @@ void StreamingSettingsUtility::applyWizardSettings( config_set_bool(config, "SimpleOutput", "UseAdvanced", true); // Resolution must have both - if (CheckInMapAndSelected(map, SettingsResponseKeys.videoHeight) && - CheckInMapAndSelected(map, SettingsResponseKeys.videoWidth)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.videoHeight) && + CheckInMapAndSelected(map, kSettingsResponseKeys.videoWidth)) { - int canvasX = intFromMap(map, SettingsResponseKeys.videoWidth); - int canvasY = intFromMap(map, SettingsResponseKeys.videoHeight); + int canvasX = intFromMap(map, kSettingsResponseKeys.videoWidth); + int canvasY = + intFromMap(map, kSettingsResponseKeys.videoHeight); config_set_uint(config, "Video", "OutputCX", canvasX); config_set_uint(config, "Video", "OutputCY", canvasY); } //TODO: FPS is hacky but covers all integer and standard drop frame cases - if (CheckInMapAndSelected(map, SettingsResponseKeys.framerate)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.framerate)) { double currentFPS = CommonSettings::GetConfigFPSDouble(config); double newFPS = - doubleFromMap(map, SettingsResponseKeys.framerate); + doubleFromMap(map, kSettingsResponseKeys.framerate); if (abs(currentFPS - newFPS) > 0.001) { // Only change if different if (abs(floor(newFPS) - newFPS) > @@ -146,40 +147,34 @@ void StreamingSettingsUtility::applyWizardSettings( } } - if (CheckInMapAndSelected(map, SettingsResponseKeys.videoBitrate)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.videoBitrate)) { int newBitrate = - intFromMap(map, SettingsResponseKeys.videoBitrate); + intFromMap(map, kSettingsResponseKeys.videoBitrate); config_set_int(config, "SimpleOutput", "VBitrate", newBitrate); } - if (CheckInMapAndSelected(map, SettingsResponseKeys.h264Profile)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.h264Profile)) { QString profile = - stringFromMap(map, SettingsResponseKeys.h264Profile); + stringFromMap(map, kSettingsResponseKeys.h264Profile); profile = profile.toLower(); x264SettingList.append("profile=" + profile); } - if (CheckInMapAndSelected(map, SettingsResponseKeys.h264Level)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.h264Level)) { QString levelString = - stringFromMap(map, SettingsResponseKeys.h264Level); + stringFromMap(map, kSettingsResponseKeys.h264Level); x264SettingList.append("level=" + levelString); } - if (CheckInMapAndSelected(map, SettingsResponseKeys.gopSizeInFrames)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.gopSizeInFrames)) { int gopSize = - intFromMap(map, SettingsResponseKeys.gopSizeInFrames); + intFromMap(map, kSettingsResponseKeys.gopSizeInFrames); x264SettingList.append("keyint=" + QString::number(gopSize)); } - if (CheckInMapAndSelected(map, SettingsResponseKeys.gopType)) { - QString gopType = - stringFromMap(map, SettingsResponseKeys.gopType); - x264SettingList.append("slice_mode=" + gopType); - } - - if (CheckInMapAndSelected(map, SettingsResponseKeys.gopClosed)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.gopClosed)) { bool gopClose = - boolFromMap(map, SettingsResponseKeys.gopClosed); + boolFromMap(map, kSettingsResponseKeys.gopClosed); if (gopClose) { x264SettingList.append("open_gop=0"); } else { @@ -187,29 +182,31 @@ void StreamingSettingsUtility::applyWizardSettings( } } - if (CheckInMapAndSelected(map, SettingsResponseKeys.gopBFrames)) { - int bFrames = intFromMap(map, SettingsResponseKeys.gopBFrames); + if (CheckInMapAndSelected(map, kSettingsResponseKeys.gopBFrames)) { + int bFrames = intFromMap(map, kSettingsResponseKeys.gopBFrames); x264SettingList.append("bframes=" + QString::number(bFrames)); } - if (CheckInMapAndSelected(map, SettingsResponseKeys.gopRefFrames)) { + if (CheckInMapAndSelected(map, kSettingsResponseKeys.gopRefFrames)) { int refFrames = - intFromMap(map, SettingsResponseKeys.gopRefFrames); + intFromMap(map, kSettingsResponseKeys.gopRefFrames); x264SettingList.append("ref=" + QString::number(refFrames)); } - // SettingsResponseKeys.streamRateControlMode defaults to CBR in Simple + // kSettingsResponseKeys.streamRateControlMode defaults to CBR in Simple // encoder mode. Can add later for advanced panel. - if (CheckInMapAndSelected(map, SettingsResponseKeys.streamBufferSize)) { + if (CheckInMapAndSelected(map, + kSettingsResponseKeys.streamBufferSize)) { int bufferSize = - intFromMap(map, SettingsResponseKeys.streamBufferSize); - x264SettingList.append("bufsize=" + + intFromMap(map, kSettingsResponseKeys.streamBufferSize); + x264SettingList.append("vbv-bufsize=" + QString::number(bufferSize)); } - QString x264String = x264SettingList.join(" "); - const char *x264_c_String = x264String.toStdString().c_str(); + std::string x264String = + x264SettingList.join(" ").toUtf8().toStdString(); + const char *x264_c_String = x264String.c_str(); config_set_string(config, "SimpleOutput", "x264Settings", x264_c_String); };