mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-06-30 08:33:32 +00:00
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:
parent
807c8a9106
commit
81b588137a
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
482
UI/window-dock-youtube-app.cpp
Normal file
482
UI/window-dock-youtube-app.cpp
Normal 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());
|
||||
}
|
||||
}
|
54
UI/window-dock-youtube-app.hpp
Normal file
54
UI/window-dock-youtube-app.hpp
Normal 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;
|
||||
};
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -38,6 +38,7 @@ struct BroadcastDescription {
|
|||
};
|
||||
|
||||
bool IsYouTubeService(const std::string &service);
|
||||
bool IsUserSignedIntoYT();
|
||||
|
||||
class YoutubeApiWrappers : public YoutubeAuth {
|
||||
Q_OBJECT
|
||||
|
|
Loading…
Reference in a new issue