mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-01-28 16:12:08 -05:00
When a source file contains an explicit include with a filename following the "moc_<actual-filename>.cpp" pattern, then CMake's AUTOMOC generation tool will recognize the matching pair and generate the replacement header file and add the required include directory entries. For all files which do contain Q_OBJECT or similar declarations but do not have an explicit include directive, the global mocs_compilation.cpp file will still be generated (which groups all "missing" generated headers). The larger this global file is, the more expensive incremental compilation will be as this file (and all its contained generated headers) will be re-generated regardless of whether actual changes occurred.
479 lines
12 KiB
C++
479 lines
12 KiB
C++
#include <QUuid>
|
|
|
|
#include "window-basic-main.hpp"
|
|
#include "youtube-api-wrappers.hpp"
|
|
#include "moc_window-dock-youtube-app.cpp"
|
|
|
|
#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(const QString &title)
|
|
: BrowserDock(title),
|
|
dockBrowser(nullptr),
|
|
cookieManager(nullptr)
|
|
{
|
|
cef->init_browser();
|
|
OBSBasic::InitBrowserPanelSafeBlock();
|
|
AddYouTubeAppDock();
|
|
}
|
|
|
|
YouTubeAppDock::~YouTubeAppDock()
|
|
{
|
|
if (cookieManager) {
|
|
cookieManager->FlushStore();
|
|
delete cookieManager;
|
|
}
|
|
}
|
|
|
|
bool YouTubeAppDock::IsYTServiceSelected()
|
|
{
|
|
if (!cef_js_avail)
|
|
return false;
|
|
|
|
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()
|
|
{
|
|
QString bId(QUuid::createUuid().toString());
|
|
bId.replace(QRegularExpression("[{}-]"), "");
|
|
this->setProperty("uuid", bId);
|
|
this->setObjectName("youtubeLiveControlPanel");
|
|
this->resize(580, 500);
|
|
this->setMinimumSize(400, 300);
|
|
this->setAllowedAreas(Qt::AllDockWidgetAreas);
|
|
|
|
OBSBasic::Get()->AddDockWidget(this, Qt::RightDockWidgetArea);
|
|
|
|
if (IsYTServiceSelected()) {
|
|
const std::string url = InitYTUserUrl();
|
|
CreateBrowserWidget(url);
|
|
} else {
|
|
this->setVisible(false);
|
|
this->toggleViewAction()->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 (visible && toggleViewAction()->isVisible())
|
|
return;
|
|
|
|
toggleViewAction()->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()
|
|
{
|
|
if (!cef_js_avail)
|
|
return;
|
|
|
|
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());
|
|
}
|
|
}
|