UI: Add "YouTube Control Panel" dock panel

New dock panel with integrated youtube studio live control room.
This commit also modifies CI files.
This commit is contained in:
Yuriy Chumak 2023-06-05 16:16:38 +03:00 committed by Lain
parent 807c8a9106
commit 81b588137a
14 changed files with 712 additions and 20 deletions

View file

@ -2,8 +2,16 @@ if(YOUTUBE_CLIENTID
AND YOUTUBE_SECRET
AND YOUTUBE_CLIENTID_HASH MATCHES "(0|[a-fA-F0-9]+)"
AND YOUTUBE_SECRET_HASH MATCHES "(0|[a-fA-F0-9]+)")
target_sources(obs-studio PRIVATE auth-youtube.cpp auth-youtube.hpp window-youtube-actions.cpp
window-youtube-actions.hpp youtube-api-wrappers.cpp youtube-api-wrappers.hpp)
target_sources(
obs-studio
PRIVATE auth-youtube.cpp
auth-youtube.hpp
window-dock-youtube-app.cpp
window-dock-youtube-app.hpp
window-youtube-actions.cpp
window-youtube-actions.hpp
youtube-api-wrappers.cpp
youtube-api-wrappers.hpp)
target_enable_feature(obs-studio "YouTube API connection" YOUTUBE_ENABLED)
else()

View file

@ -330,8 +330,16 @@ endif()
if(YOUTUBE_ENABLED)
target_compile_definitions(obs PRIVATE YOUTUBE_ENABLED)
target_sources(obs PRIVATE auth-youtube.cpp auth-youtube.hpp youtube-api-wrappers.cpp youtube-api-wrappers.hpp
window-youtube-actions.cpp window-youtube-actions.hpp)
target_sources(
obs
PRIVATE auth-youtube.cpp
auth-youtube.hpp
window-dock-youtube-app.cpp
window-dock-youtube-app.hpp
window-youtube-actions.cpp
window-youtube-actions.hpp
youtube-api-wrappers.cpp
youtube-api-wrappers.hpp)
endif()
if(OS_WINDOWS)

View file

@ -1526,3 +1526,6 @@ YouTube.Errors.liveChatDisabled="Live chat is disabled on this stream."
YouTube.Errors.liveChatEnded="Live stream has ended."
YouTube.Errors.messageTextInvalid="The message text is not valid."
YouTube.Errors.rateLimitExceeded="You are sending messages too quickly."
# Browser Dock
YouTube.DocksRemoval.Title="Clear Legacy YouTube Browser Docks"
YouTube.DocksRemoval.Text="These browser docks will be removed as deprecated:\n\n%1\nUse \"Docks/YouTube Live Control Room\" instead."

View file

@ -1050,6 +1050,13 @@ void AutoConfig::done(int result)
if (type == Type::Streaming)
SaveStreamSettings();
SaveSettings();
#ifdef YOUTUBE_ENABLED
if (YouTubeAppDock::IsYTServiceSelected()) {
OBSBasic *main = OBSBasic::Get();
main->NewYouTubeAppDock();
}
#endif
}
}

View file

@ -2170,6 +2170,9 @@ void OBSBasic::OBSInit()
/* ----------------------------- */
/* add custom browser docks */
#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED)
YouTubeAppDock::CleanupYouTubeUrls();
#endif
#ifdef BROWSER_AVAILABLE
if (cef) {
@ -2331,6 +2334,12 @@ void OBSBasic::OBSInit()
UpdatePreviewProgramIndicators();
OnFirstLoad();
#ifdef YOUTUBE_ENABLED
/* setup YouTube app dock */
if (YouTubeAppDock::IsYTServiceSelected())
youtubeAppDock = new YouTubeAppDock();
#endif
if (!hideWindowOnStart)
activateWindow();
@ -6823,9 +6832,10 @@ void OBSBasic::DisplayStreamStartError()
}
#ifdef YOUTUBE_ENABLED
void OBSBasic::YouTubeActionDialogOk(const QString &id, const QString &key,
bool autostart, bool autostop,
bool start_now)
void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id,
const QString &stream_id,
const QString &key, bool autostart,
bool autostop, bool start_now)
{
//blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key));
obs_service_t *service_obj = GetService();
@ -6834,8 +6844,11 @@ void OBSBasic::YouTubeActionDialogOk(const QString &id, const QString &key,
const std::string a_key = QT_TO_UTF8(key);
obs_data_set_string(settings, "key", a_key.c_str());
const std::string an_id = QT_TO_UTF8(id);
obs_data_set_string(settings, "stream_id", an_id.c_str());
const std::string b_id = QT_TO_UTF8(broadcast_id);
obs_data_set_string(settings, "broadcast_id", b_id.c_str());
const std::string s_id = QT_TO_UTF8(stream_id);
obs_data_set_string(settings, "stream_id", s_id.c_str());
obs_service_update(service_obj, settings);
autoStartBroadcast = autostart;
@ -7419,6 +7432,11 @@ void OBSBasic::StreamingStart()
OnActivate();
#ifdef YOUTUBE_ENABLED
if (YouTubeAppDock::IsYTServiceSelected())
youtubeAppDock->IngestionStarted();
#endif
blog(LOG_INFO, STREAMING_START);
}
@ -7499,6 +7517,11 @@ void OBSBasic::StreamingStop(int code, QString last_error)
OnDeactivate();
#ifdef YOUTUBE_ENABLED
if (YouTubeAppDock::IsYTServiceSelected())
youtubeAppDock->IngestionStopped();
#endif
blog(LOG_INFO, STREAMING_STOP);
if (encode_error) {
@ -8462,6 +8485,27 @@ config_t *OBSBasic::Config() const
return basicConfig;
}
#ifdef YOUTUBE_ENABLED
YouTubeAppDock *OBSBasic::GetYouTubeAppDock()
{
return youtubeAppDock;
}
void OBSBasic::NewYouTubeAppDock()
{
if (youtubeAppDock)
delete youtubeAppDock;
youtubeAppDock = new YouTubeAppDock();
}
void OBSBasic::DeleteYouTubeAppDock()
{
if (youtubeAppDock)
delete youtubeAppDock;
youtubeAppDock = nullptr;
}
#endif
void OBSBasic::UpdateEditMenu()
{
QModelIndexList items = GetAllSelectedSourceItems();

View file

@ -36,6 +36,9 @@
#include "window-missing-files.hpp"
#include "window-projector.hpp"
#include "window-basic-about.hpp"
#ifdef YOUTUBE_ENABLED
#include "window-dock-youtube-app.hpp"
#endif
#include "auth-base.hpp"
#include "log-viewer.hpp"
#include "undo-stack-obs.hpp"
@ -262,6 +265,9 @@ private:
QPointer<OBSBasicAdvAudio> advAudioWindow;
QPointer<OBSBasicFilters> filters;
QPointer<QDockWidget> statsDock;
#ifdef YOUTUBE_ENABLED
QPointer<YouTubeAppDock> youtubeAppDock;
#endif
QPointer<OBSAbout> about;
QPointer<OBSMissingFiles> missDialog;
QPointer<OBSLogViewer> logView;
@ -631,7 +637,8 @@ private:
#ifdef YOUTUBE_ENABLED
void YoutubeStreamCheck(const std::string &key);
void ShowYouTubeAutoStartWarning();
void YouTubeActionDialogOk(const QString &id, const QString &key,
void YouTubeActionDialogOk(const QString &broadcast_id,
const QString &stream_id, const QString &key,
bool autostart, bool autostop,
bool start_now);
#endif
@ -1253,6 +1260,11 @@ public:
const char *file) const override;
static void InitBrowserPanelSafeBlock();
#ifdef YOUTUBE_ENABLED
void NewYouTubeAppDock();
void DeleteYouTubeAppDock();
YouTubeAppDock *GetYouTubeAppDock();
#endif
};
class SceneRenameDelegate : public QStyledItemDelegate {

View file

@ -770,6 +770,14 @@ void OBSBasicSettings::on_connectAccount_clicked()
auth = OAuthStreamKey::Login(this, service);
if (!!auth) {
OnAuthConnected();
#ifdef YOUTUBE_ENABLED
if (IsYouTubeService(service)) {
if (!main->GetYouTubeAppDock()) {
main->NewYouTubeAppDock();
}
main->GetYouTubeAppDock()->AccountConnected();
}
#endif
ui->useStreamKeyAdv->setVisible(false);
}
@ -812,6 +820,16 @@ void OBSBasicSettings::on_disconnectAccount_clicked()
ui->connectedAccountLabel->setVisible(false);
ui->connectedAccountText->setVisible(false);
#ifdef YOUTUBE_ENABLED
if (IsYouTubeService(service)) {
if (!main->GetYouTubeAppDock()) {
main->NewYouTubeAppDock();
}
main->GetYouTubeAppDock()->AccountDisconnected();
main->GetYouTubeAppDock()->Update();
}
#endif
}
void OBSBasicSettings::on_useStreamKey_clicked()

View file

@ -48,6 +48,10 @@
#include "window-basic-main-outputs.hpp"
#include "window-projector.hpp"
#ifdef YOUTUBE_ENABLED
#include "youtube-api-wrappers.hpp"
#endif
#include <util/platform.h>
#include <util/dstr.hpp>
#include "ui-config.h"
@ -4251,6 +4255,22 @@ void OBSBasicSettings::on_buttonBox_clicked(QAbstractButton *button)
return;
SaveSettings();
#ifdef YOUTUBE_ENABLED
std::string service = ui->service->currentText().toStdString();
if (IsYouTubeService(service)) {
if (!main->GetYouTubeAppDock()) {
main->NewYouTubeAppDock();
}
main->GetYouTubeAppDock()->SettingsUpdated(
!IsYouTubeService(service) || stream1Changed);
} else {
if (main->GetYouTubeAppDock()) {
main->GetYouTubeAppDock()->AccountDisconnected();
}
main->DeleteYouTubeAppDock();
}
#endif
ClearChanged();
}

View file

@ -0,0 +1,482 @@
#include <QUuid>
#include "window-basic-main.hpp"
#include "youtube-api-wrappers.hpp"
#include "window-dock-youtube-app.hpp"
#include "ui-config.h"
#include "qt-wrappers.hpp"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#ifdef YOUTUBE_WEBAPP_PLACEHOLDER
static constexpr const char *YOUTUBE_WEBAPP_PLACEHOLDER_URL =
YOUTUBE_WEBAPP_PLACEHOLDER;
#else
static constexpr const char *YOUTUBE_WEBAPP_PLACEHOLDER_URL =
"https://studio.youtube.com/live/channel/UC/console?kc=OBS";
#endif
#ifdef YOUTUBE_WEBAPP_ADDRESS
static constexpr const char *YOUTUBE_WEBAPP_ADDRESS_URL =
YOUTUBE_WEBAPP_ADDRESS;
#else
static constexpr const char *YOUTUBE_WEBAPP_ADDRESS_URL =
"https://studio.youtube.com/live/channel/%1/console?kc=OBS";
#endif
static constexpr const char *BROADCAST_CREATED = "BROADCAST_CREATED";
static constexpr const char *BROADCAST_SELECTED = "BROADCAST_SELECTED";
static constexpr const char *INGESTION_STARTED = "INGESTION_STARTED";
static constexpr const char *INGESTION_STOPPED = "INGESTION_STOPPED";
YouTubeAppDock::YouTubeAppDock()
: BrowserDock(),
dockBrowser(nullptr),
cookieManager(nullptr)
{
OBSBasic::InitBrowserPanelSafeBlock();
AddYouTubeAppDock("YouTube Live Control Panel");
}
YouTubeAppDock::~YouTubeAppDock()
{
if (cookieManager) {
cookieManager->FlushStore();
delete cookieManager;
}
}
bool YouTubeAppDock::IsYTServiceSelected()
{
obs_service_t *service_obj = OBSBasic::Get()->GetService();
OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
const char *service = obs_data_get_string(settings, "service");
return IsYouTubeService(service);
}
void YouTubeAppDock::AccountConnected()
{
channelId.clear(); // renew channel id
UpdateChannelId();
}
void YouTubeAppDock::AccountDisconnected()
{
SettingsUpdated(true);
}
void YouTubeAppDock::SettingsUpdated(bool cleanup)
{
bool ytservice = IsYTServiceSelected();
SetVisibleYTAppDockInMenu(ytservice);
// definitely cleanup if YT switched off
if (!ytservice || cleanup) {
if (cookieManager)
cookieManager->DeleteCookies("", "");
}
if (ytservice)
Update();
}
std::string YouTubeAppDock::InitYTUserUrl()
{
std::string user_url(YOUTUBE_WEBAPP_PLACEHOLDER_URL);
if (IsUserSignedIntoYT()) {
YoutubeApiWrappers *apiYouTube = GetYTApi();
if (apiYouTube) {
ChannelDescription channel_description;
if (apiYouTube->GetChannelDescription(
channel_description)) {
QString url =
QString(YOUTUBE_WEBAPP_ADDRESS_URL)
.arg(channel_description.id);
user_url = url.toStdString();
} else {
blog(LOG_ERROR,
"YT: InitYTUserUrl() Failed to get channel id");
}
}
} else {
blog(LOG_ERROR, "YT: InitYTUserUrl() User is not signed");
}
blog(LOG_DEBUG, "YT: InitYTUserUrl() User url: %s", user_url.c_str());
return user_url;
}
void YouTubeAppDock::AddYouTubeAppDock(const QString &title)
{
QString bId(QUuid::createUuid().toString());
bId.replace(QRegularExpression("[{}-]"), "");
this->setProperty("uuid", bId);
this->setObjectName(title + "Object");
this->resize(580, 500);
this->setMinimumSize(400, 300);
this->setWindowTitle(title);
this->setAllowedAreas(Qt::AllDockWidgetAreas);
OBSBasic::Get()->addDockWidget(Qt::RightDockWidgetArea, this);
actionYTAppDock = OBSBasic::Get()->AddDockWidget(this);
if (IsYTServiceSelected()) {
const std::string url = InitYTUserUrl();
CreateBrowserWidget(url);
// reload panel layout
const char *dockStateStr = config_get_string(
App()->GlobalConfig(), "BasicWindow", "DockState");
if (dockStateStr) {
QByteArray dockState = QByteArray::fromBase64(
QByteArray(dockStateStr));
OBSBasic::Get()->restoreState(dockState);
}
} else {
this->setVisible(false);
actionYTAppDock->setVisible(false);
}
}
void YouTubeAppDock::CreateBrowserWidget(const std::string &url)
{
std::string dir_name = std::string("obs_profile_cookies_youtube/") +
config_get_string(OBSBasic::Get()->Config(),
"Panels", "CookieId");
if (cookieManager)
delete cookieManager;
cookieManager = cef->create_cookie_manager(dir_name, true);
if (dockBrowser)
delete dockBrowser;
dockBrowser = cef->create_widget(this, url, cookieManager);
if (!dockBrowser)
return;
if (obs_browser_qcef_version() >= 1)
dockBrowser->allowAllPopups(true);
this->SetWidget(dockBrowser);
Update();
}
void YouTubeAppDock::SetVisibleYTAppDockInMenu(bool visible)
{
if (!actionYTAppDock)
return;
actionYTAppDock->setVisible(visible);
this->setVisible(visible);
}
// only 'ACCOUNT' mode supported
void YouTubeAppDock::BroadcastCreated(const char *stream_id)
{
DispatchYTEvent(BROADCAST_CREATED, stream_id, YTSM_ACCOUNT);
}
// only 'ACCOUNT' mode supported
void YouTubeAppDock::BroadcastSelected(const char *stream_id)
{
DispatchYTEvent(BROADCAST_SELECTED, stream_id, YTSM_ACCOUNT);
}
// both 'ACCOUNT' and 'STREAM_KEY' modes supported
void YouTubeAppDock::IngestionStarted()
{
obs_service_t *service_obj = OBSBasic::Get()->GetService();
OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
const char *service = obs_data_get_string(settings, "service");
if (IsYouTubeService(service)) {
if (IsUserSignedIntoYT()) {
const char *broadcast_id =
obs_data_get_string(settings, "broadcast_id");
this->IngestionStarted(broadcast_id,
YouTubeAppDock::YTSM_ACCOUNT);
} else {
const char *stream_key =
obs_data_get_string(settings, "key");
this->IngestionStarted(stream_key,
YouTubeAppDock::YTSM_STREAM_KEY);
}
}
}
void YouTubeAppDock::IngestionStarted(const char *stream_id,
streaming_mode_t mode)
{
DispatchYTEvent(INGESTION_STARTED, stream_id, mode);
}
// both 'ACCOUNT' and 'STREAM_KEY' modes supported
void YouTubeAppDock::IngestionStopped()
{
obs_service_t *service_obj = OBSBasic::Get()->GetService();
OBSDataAutoRelease settings = obs_service_get_settings(service_obj);
const char *service = obs_data_get_string(settings, "service");
if (IsYouTubeService(service)) {
if (IsUserSignedIntoYT()) {
const char *broadcast_id =
obs_data_get_string(settings, "broadcast_id");
this->IngestionStopped(broadcast_id,
YouTubeAppDock::YTSM_ACCOUNT);
} else {
const char *stream_key =
obs_data_get_string(settings, "key");
this->IngestionStopped(stream_key,
YouTubeAppDock::YTSM_STREAM_KEY);
}
}
}
void YouTubeAppDock::IngestionStopped(const char *stream_id,
streaming_mode_t mode)
{
DispatchYTEvent(INGESTION_STOPPED, stream_id, mode);
}
void YouTubeAppDock::showEvent(QShowEvent *)
{
if (!dockBrowser)
Update();
}
void YouTubeAppDock::closeEvent(QCloseEvent *event)
{
BrowserDock::closeEvent(event);
this->SetWidget(nullptr);
}
void YouTubeAppDock::DispatchYTEvent(const char *event, const char *video_id,
streaming_mode_t mode)
{
if (!dockBrowser)
return;
// update channelId if empty:
UpdateChannelId();
// notify YouTube Live Streaming API:
std::string script;
if (mode == YTSM_ACCOUNT) {
script = QString(R"""(
if (window.location.hostname == 'studio.youtube.com') {
let event = {
type: '%1',
channelId: '%2',
videoId: '%3',
};
console.log(event);
if (window.ytlsapi && window.ytlsapi.dispatchEvent)
window.ytlsapi.dispatchEvent(event);
}
)""")
.arg(event)
.arg(channelId)
.arg(video_id)
.toStdString();
} else {
const char *stream_key = video_id;
script = QString(R"""(
if (window.location.hostname == 'studio.youtube.com') {
let event = {
type: '%1',
streamKey: '%2',
};
console.log(event);
if (window.ytlsapi && window.ytlsapi.dispatchEvent)
window.ytlsapi.dispatchEvent(event);
}
)""")
.arg(event)
.arg(stream_key)
.toStdString();
}
dockBrowser->executeJavaScript(script);
// in case of user still not logged in in dock panel, remember last event
SetInitEvent(mode, event, video_id, channelId.toStdString().c_str());
}
void YouTubeAppDock::Update()
{
std::string url = InitYTUserUrl();
if (!dockBrowser) {
CreateBrowserWidget(url);
} else {
dockBrowser->setURL(url);
}
// if streaming already run, let's notify YT about past event
if (OBSBasic::Get()->StreamingActive()) {
obs_service_t *service_obj = OBSBasic::Get()->GetService();
OBSDataAutoRelease settings =
obs_service_get_settings(service_obj);
if (IsUserSignedIntoYT()) {
channelId.clear(); // renew channelId
UpdateChannelId();
const char *broadcast_id =
obs_data_get_string(settings, "broadcast_id");
SetInitEvent(YTSM_ACCOUNT, INGESTION_STARTED,
broadcast_id,
channelId.toStdString().c_str());
} else {
const char *stream_key =
obs_data_get_string(settings, "key");
SetInitEvent(YTSM_STREAM_KEY, INGESTION_STARTED,
stream_key);
}
} else {
SetInitEvent(IsUserSignedIntoYT() ? YTSM_ACCOUNT
: YTSM_STREAM_KEY);
}
dockBrowser->reloadPage();
}
void YouTubeAppDock::UpdateChannelId()
{
if (channelId.isEmpty()) {
YoutubeApiWrappers *apiYouTube = GetYTApi();
if (apiYouTube) {
ChannelDescription channel_description;
if (apiYouTube->GetChannelDescription(
channel_description)) {
channelId = channel_description.id;
} else {
blog(LOG_ERROR, "YT: AccountConnected() Failed "
"to get channel id");
}
}
}
}
void YouTubeAppDock::SetInitEvent(streaming_mode_t mode, const char *event,
const char *video_id, const char *channelId)
{
const std::string version = App()->GetVersionString();
QString api_event;
if (event) {
if (mode == YTSM_ACCOUNT) {
api_event = QString(R"""(,
initEvent: {
type: '%1',
channelId: '%2',
videoId: '%3',
}
)""")
.arg(event)
.arg(channelId)
.arg(video_id);
} else {
api_event = QString(R"""(,
initEvent: {
type: '%1',
streamKey: '%2',
}
)""")
.arg(event)
.arg(video_id);
}
}
std::string script = QString(R"""(
let obs_name = '%1';
let obs_version = '%2';
let client_mode = %3;
if (window.location.hostname == 'studio.youtube.com') {
console.log("name:", obs_name);
console.log("version:", obs_version);
console.log("initEvent:", {
initClientMode: client_mode
%4 });
if (window.ytlsapi && window.ytlsapi.init)
window.ytlsapi.init(obs_name, obs_version, undefined, {
initClientMode: client_mode
%4 });
}
)""")
.arg("OBS")
.arg(version.c_str())
.arg(mode == YTSM_ACCOUNT ? "'ACCOUNT'"
: "'STREAM_KEY'")
.arg(api_event)
.toStdString();
dockBrowser->setStartupScript(script);
}
YoutubeApiWrappers *YouTubeAppDock::GetYTApi()
{
Auth *auth = OBSBasic::Get()->GetAuth();
if (auth) {
YoutubeApiWrappers *apiYouTube(
dynamic_cast<YoutubeApiWrappers *>(auth));
if (apiYouTube) {
return apiYouTube;
} else {
blog(LOG_ERROR,
"YT: GetYTApi() Failed to get YoutubeApiWrappers");
}
} else {
blog(LOG_ERROR, "YT: GetYTApi() Failed to get Auth");
}
return nullptr;
}
void YouTubeAppDock::CleanupYouTubeUrls()
{
static constexpr const char *YOUTUBE_VIDEO_URL =
"://studio.youtube.com/video/";
// remove legacy YouTube Browser Docks (once)
bool youtube_cleanup_done = config_get_bool(
App()->GlobalConfig(), "General", "YtDockCleanupDone");
if (youtube_cleanup_done)
return;
config_set_bool(App()->GlobalConfig(), "General", "YtDockCleanupDone",
true);
const char *jsonStr = config_get_string(
App()->GlobalConfig(), "BasicWindow", "ExtraBrowserDocks");
if (!jsonStr)
return;
json array = json::parse(jsonStr);
if (!array.is_array())
return;
json save_array;
std::string removedYTUrl;
for (json &item : array) {
auto url = item["url"].get<std::string>();
if (url.find(YOUTUBE_VIDEO_URL) != std::string::npos) {
blog(LOG_DEBUG, "YT: found legacy url: %s",
url.c_str());
removedYTUrl += url;
removedYTUrl += ";\n";
} else {
save_array.push_back(item);
}
}
if (!removedYTUrl.empty()) {
const QString msg_title = QTStr("YouTube.DocksRemoval.Title");
const QString msg_text =
QTStr("YouTube.DocksRemoval.Text")
.arg(QT_UTF8(removedYTUrl.c_str()));
OBSMessageBox::warning(OBSBasic::Get(), msg_title, msg_text);
std::string output = save_array.dump();
config_set_string(App()->GlobalConfig(), "BasicWindow",
"ExtraBrowserDocks", output.c_str());
}
}

View file

@ -0,0 +1,54 @@
#pragma once
#include "window-dock-browser.hpp"
#include "youtube-api-wrappers.hpp"
class QAction;
class QCefWidget;
class YouTubeAppDock : public BrowserDock {
Q_OBJECT
public:
YouTubeAppDock();
~YouTubeAppDock();
enum streaming_mode_t { YTSM_ACCOUNT, YTSM_STREAM_KEY };
void AccountConnected();
void AccountDisconnected();
void SettingsUpdated(bool cleanup = false);
void Update();
void BroadcastCreated(const char *stream_id);
void BroadcastSelected(const char *stream_id);
void IngestionStarted();
void IngestionStopped();
static bool IsYTServiceSelected();
static YoutubeApiWrappers *GetYTApi();
static void CleanupYouTubeUrls();
protected:
void IngestionStarted(const char *stream_id, streaming_mode_t mode);
void IngestionStopped(const char *stream_id, streaming_mode_t mode);
private:
std::string InitYTUserUrl();
void SetVisibleYTAppDockInMenu(bool visible);
void AddYouTubeAppDock(const QString &title);
void CreateBrowserWidget(const std::string &url);
virtual void showEvent(QShowEvent *event) override;
virtual void closeEvent(QCloseEvent *event) override;
void DispatchYTEvent(const char *event, const char *video_id,
streaming_mode_t mode);
void UpdateChannelId();
void SetInitEvent(streaming_mode_t mode, const char *event = nullptr,
const char *video_id = nullptr,
const char *channelId = nullptr);
QString channelId;
QPointer<QCefWidget> dockBrowser;
QCefCookieManager *cookieManager; // is not a Qt object
QPointer<QAction> actionYTAppDock;
};

View file

@ -444,12 +444,12 @@ void OBSYoutubeActions::UpdateOkButtonStatus()
}
}
bool OBSYoutubeActions::CreateEventAction(YoutubeApiWrappers *api,
BroadcastDescription &broadcast,
StreamDescription &stream,
bool stream_later,
bool ready_broadcast)
{
YoutubeApiWrappers *apiYouTube = api;
BroadcastDescription broadcast = {};
UiToBroadcast(broadcast);
if (stream_later) {
@ -513,6 +513,12 @@ bool OBSYoutubeActions::CreateEventAction(YoutubeApiWrappers *api,
}
}
#ifdef YOUTUBE_ENABLED
if (OBSBasic::Get()->GetYouTubeAppDock())
OBSBasic::Get()->GetYouTubeAppDock()->BroadcastCreated(
broadcast.id.toStdString().c_str());
#endif
return true;
}
@ -568,6 +574,12 @@ bool OBSYoutubeActions::ChooseAnEventAction(YoutubeApiWrappers *api,
else
apiYouTube->ResetChat();
#ifdef YOUTUBE_ENABLED
if (OBSBasic::Get()->GetYouTubeAppDock())
OBSBasic::Get()->GetYouTubeAppDock()->BroadcastSelected(
selectedBroadcast.toStdString().c_str());
#endif
return true;
}
@ -585,6 +597,7 @@ void OBSYoutubeActions::ShowErrorDialog(QWidget *parent, QString text)
void OBSYoutubeActions::InitBroadcast()
{
BroadcastDescription broadcast;
StreamDescription stream;
QMessageBox msgBox(this);
msgBox.setWindowFlags(msgBox.windowFlags() &
@ -597,10 +610,12 @@ void OBSYoutubeActions::InitBroadcast()
auto action = [&]() {
if (ui->tabWidget->currentIndex() == 0) {
success = this->CreateEventAction(
apiYouTube, stream,
apiYouTube, broadcast, stream,
ui->checkScheduledLater->isChecked());
} else {
success = this->ChooseAnEventAction(apiYouTube, stream);
if (success)
broadcast.id = this->selectedBroadcast;
};
QMetaObject::invokeMethod(&msgBox, "accept",
Qt::QueuedConnection);
@ -627,15 +642,17 @@ void OBSYoutubeActions::InitBroadcast()
// Stream now usecase.
blog(LOG_DEBUG, "New valid stream: %s",
QT_TO_UTF8(stream.name));
emit ok(QT_TO_UTF8(stream.id),
emit ok(QT_TO_UTF8(broadcast.id),
QT_TO_UTF8(stream.id),
QT_TO_UTF8(stream.name), true, true,
true);
Accept();
}
} else {
// Stream to precreated broadcast usecase.
emit ok(QT_TO_UTF8(stream.id), QT_TO_UTF8(stream.name),
autostart, autostop, true);
emit ok(QT_TO_UTF8(broadcast.id), QT_TO_UTF8(stream.id),
QT_TO_UTF8(stream.name), autostart, autostop,
true);
Accept();
}
} else {
@ -654,6 +671,7 @@ void OBSYoutubeActions::InitBroadcast()
void OBSYoutubeActions::ReadyBroadcast()
{
BroadcastDescription broadcast;
StreamDescription stream;
QMessageBox msgBox(this);
msgBox.setWindowFlags(msgBox.windowFlags() &
@ -666,10 +684,12 @@ void OBSYoutubeActions::ReadyBroadcast()
auto action = [&]() {
if (ui->tabWidget->currentIndex() == 0) {
success = this->CreateEventAction(
apiYouTube, stream,
apiYouTube, broadcast, stream,
ui->checkScheduledLater->isChecked(), true);
} else {
success = this->ChooseAnEventAction(apiYouTube, stream);
if (success)
broadcast.id = this->selectedBroadcast;
};
QMetaObject::invokeMethod(&msgBox, "accept",
Qt::QueuedConnection);
@ -680,8 +700,8 @@ void OBSYoutubeActions::ReadyBroadcast()
thread->wait();
if (success) {
emit ok(QT_TO_UTF8(stream.id), QT_TO_UTF8(stream.name),
autostart, autostop, false);
emit ok(QT_TO_UTF8(broadcast.id), QT_TO_UTF8(stream.id),
QT_TO_UTF8(stream.name), autostart, autostop, false);
Accept();
} else {
// Fail.

View file

@ -36,14 +36,16 @@ class OBSYoutubeActions : public QDialog {
std::unique_ptr<Ui::OBSYoutubeActions> ui;
signals:
void ok(const QString &id, const QString &key, bool autostart,
bool autostop, bool start_now);
void ok(const QString &broadcast_id, const QString &stream_id,
const QString &key, bool autostart, bool autostop,
bool start_now);
protected:
void showEvent(QShowEvent *event) override;
void UpdateOkButtonStatus();
bool CreateEventAction(YoutubeApiWrappers *api,
BroadcastDescription &broadcast,
StreamDescription &stream, bool stream_later,
bool ready_broadcast = false);
bool ChooseAnEventAction(YoutubeApiWrappers *api,

View file

@ -9,6 +9,7 @@
#include "auth-youtube.hpp"
#include "obs-app.hpp"
#include "window-basic-main.hpp"
#include "qt-wrappers.hpp"
#include "remote-text.hpp"
#include "ui-config.h"
@ -45,6 +46,18 @@ bool IsYouTubeService(const std::string &service)
});
return it != youtubeServices.end();
}
bool IsUserSignedIntoYT()
{
Auth *auth = OBSBasic::Get()->GetAuth();
if (auth) {
YoutubeApiWrappers *apiYouTube(
dynamic_cast<YoutubeApiWrappers *>(auth));
if (apiYouTube) {
return true;
}
}
return false;
}
bool YoutubeApiWrappers::GetTranslatedError(QString &error_message)
{

View file

@ -38,6 +38,7 @@ struct BroadcastDescription {
};
bool IsYouTubeService(const std::string &service);
bool IsUserSignedIntoYT();
class YoutubeApiWrappers : public YoutubeAuth {
Q_OBJECT