mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-09-20 04:42:18 +02:00
UI: Fix YouTube event selection, API usage, stream resumption
All these fixes are interlinked but to explain them further: Event selection would only partially work, the code to re-use an existing liveStream was never hit and so didn't work. It would also break going live because broadcast_id would never be set. Additionally it called StartBroadcast for no reason if autostart was enabled. API usage was unoptimal. Instead of only fetching the events we need (active, ready) it would fetch *every single livestream* on the youtube channel, 7 at a time, and then throw away every single result in the majority of use cases. This commit changes it to only fetch "active" and "ready" broadcasts and then only filters out active ones that cannot be resumed (because they're stil live). Resuming existing streams also didn't work because they were just thrown out by the selection. Now they get included if the attached liveStream is not receiving data. The're distinguished in the UI and are listed first. Simply selecting them and starting the stream will work. These's still some stuff left, like redundant API calls. But thankfully those fail silently and we can simply ignore it for now.
This commit is contained in:
parent
e212acf025
commit
a4d37dba73
@ -150,16 +150,35 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth)
|
||||
|
||||
connect(workerThread, &WorkerThread::new_item, this,
|
||||
[&](const QString &title, const QString &dateTimeString,
|
||||
const QString &broadcast, bool astart, bool astop) {
|
||||
const QString &broadcast, const QString &status,
|
||||
bool astart, bool astop) {
|
||||
ClickableLabel *label = new ClickableLabel();
|
||||
label->setStyleSheet(NormalStylesheet);
|
||||
label->setTextFormat(Qt::RichText);
|
||||
label->setText(
|
||||
QString("<big>%1 %2</big><br/>%3 %4")
|
||||
.arg(title,
|
||||
QTStr("YouTube.Actions.Stream"),
|
||||
QTStr("YouTube.Actions.Stream.ScheduledFor")
|
||||
.arg(dateTimeString)));
|
||||
|
||||
if (status == "live" || status == "testing") {
|
||||
// Resumable stream
|
||||
label->setText(
|
||||
QString("<big>%1</big><br/>%2")
|
||||
.arg(title,
|
||||
QTStr("YouTube.Actions.Stream.Resume")));
|
||||
|
||||
} else if (dateTimeString.isEmpty()) {
|
||||
// The broadcast created by YouTube Studio has no start time.
|
||||
// Yes this does violate the restrictions set in YouTube's API
|
||||
// But why would YouTube care about consistency?
|
||||
label->setText(
|
||||
QString("<big>%1</big><br/>%2")
|
||||
.arg(title,
|
||||
QTStr("YouTube.Actions.Stream.YTStudio")));
|
||||
} else {
|
||||
label->setText(
|
||||
QString("<big>%1</big><br/>%2")
|
||||
.arg(title,
|
||||
QTStr("YouTube.Actions.Stream.ScheduledFor")
|
||||
.arg(dateTimeString)));
|
||||
}
|
||||
|
||||
label->setAlignment(Qt::AlignHCenter);
|
||||
label->setMargin(4);
|
||||
|
||||
@ -205,39 +224,65 @@ void WorkerThread::run()
|
||||
if (!pending)
|
||||
return;
|
||||
json11::Json broadcasts;
|
||||
if (!apiYouTube->GetBroadcastsList(broadcasts, "")) {
|
||||
emit failed();
|
||||
return;
|
||||
}
|
||||
|
||||
while (pending) {
|
||||
auto items = broadcasts["items"].array_items();
|
||||
for (auto item = items.begin(); item != items.end(); item++) {
|
||||
auto status = (*item)["status"]["lifeCycleStatus"]
|
||||
.string_value();
|
||||
if (status == "created" || status == "ready") {
|
||||
auto title = QString::fromStdString(
|
||||
(*item)["snippet"]["title"]
|
||||
.string_value());
|
||||
auto scheduledStartTime = QString::fromStdString(
|
||||
(*item)["snippet"]["scheduledStartTime"]
|
||||
.string_value());
|
||||
auto broadcast = QString::fromStdString(
|
||||
(*item)["id"].string_value());
|
||||
auto astart = (*item)["contentDetails"]
|
||||
["enableAutoStart"]
|
||||
.bool_value();
|
||||
auto astop = (*item)["contentDetails"]
|
||||
["enableAutoStop"]
|
||||
.bool_value();
|
||||
for (QString broacastStatus : {"active", "upcoming"}) {
|
||||
if (!apiYouTube->GetBroadcastsList(broadcasts, "",
|
||||
broacastStatus)) {
|
||||
emit failed();
|
||||
return;
|
||||
}
|
||||
|
||||
auto utcDTime = QDateTime::fromString(
|
||||
while (pending) {
|
||||
auto items = broadcasts["items"].array_items();
|
||||
for (auto item : items) {
|
||||
QString status = QString::fromStdString(
|
||||
item["status"]["lifeCycleStatus"]
|
||||
.string_value());
|
||||
|
||||
if (status == "live" || status == "testing") {
|
||||
// Check that the attached liveStream is offline (reconnectable)
|
||||
QString stream_id = QString::fromStdString(
|
||||
item["contentDetails"]
|
||||
["boundStreamId"]
|
||||
.string_value());
|
||||
json11::Json stream;
|
||||
if (!apiYouTube->FindStream(stream_id,
|
||||
stream))
|
||||
continue;
|
||||
if (stream["status"]["streamStatus"] ==
|
||||
"active")
|
||||
continue;
|
||||
}
|
||||
|
||||
QString title = QString::fromStdString(
|
||||
item["snippet"]["title"].string_value());
|
||||
QString scheduledStartTime =
|
||||
QString::fromStdString(
|
||||
item["snippet"]
|
||||
["scheduledStartTime"]
|
||||
.string_value());
|
||||
QString broadcast = QString::fromStdString(
|
||||
item["id"].string_value());
|
||||
|
||||
// Treat already started streams as autostart for UI purposes
|
||||
bool astart =
|
||||
status == "live"
|
||||
? true
|
||||
: item["contentDetails"]
|
||||
["enableAutoStart"]
|
||||
.bool_value();
|
||||
bool astop =
|
||||
item["contentDetails"]["enableAutoStop"]
|
||||
.bool_value();
|
||||
|
||||
QDateTime utcDTime = QDateTime::fromString(
|
||||
scheduledStartTime,
|
||||
SchedulDateAndTimeFormat);
|
||||
// DateTime parser means that input datetime is a local, so we need to move it
|
||||
auto dateTime = utcDTime.addSecs(
|
||||
QDateTime dateTime = utcDTime.addSecs(
|
||||
utcDTime.offsetFromUtc());
|
||||
auto dateTimeString = QLocale().toString(
|
||||
|
||||
QString dateTimeString = QLocale().toString(
|
||||
dateTime,
|
||||
QString("%1 %2").arg(
|
||||
QLocale().dateFormat(
|
||||
@ -246,21 +291,24 @@ void WorkerThread::run()
|
||||
QLocale::ShortFormat)));
|
||||
|
||||
emit new_item(title, dateTimeString, broadcast,
|
||||
astart, astop);
|
||||
status, astart, astop);
|
||||
}
|
||||
}
|
||||
|
||||
auto nextPageToken = broadcasts["nextPageToken"].string_value();
|
||||
if (nextPageToken.empty() || items.empty())
|
||||
break;
|
||||
else {
|
||||
if (!pending)
|
||||
return;
|
||||
if (!apiYouTube->GetBroadcastsList(
|
||||
broadcasts,
|
||||
QString::fromStdString(nextPageToken))) {
|
||||
emit failed();
|
||||
return;
|
||||
auto nextPageToken =
|
||||
broadcasts["nextPageToken"].string_value();
|
||||
if (nextPageToken.empty() || items.empty())
|
||||
break;
|
||||
else {
|
||||
if (!pending)
|
||||
return;
|
||||
if (!apiYouTube->GetBroadcastsList(
|
||||
broadcasts,
|
||||
QString::fromStdString(
|
||||
nextPageToken),
|
||||
broacastStatus)) {
|
||||
emit failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -345,33 +393,36 @@ bool OBSYoutubeActions::StreamLaterAction(YoutubeApiWrappers *api)
|
||||
blog(LOG_DEBUG, "No broadcast created.");
|
||||
return false;
|
||||
}
|
||||
if (!apiYouTube->SetVideoCategory(broadcast.id, broadcast.title,
|
||||
broadcast.description,
|
||||
broadcast.category.id)) {
|
||||
blog(LOG_DEBUG, "No category set.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OBSYoutubeActions::ChooseAnEventAction(YoutubeApiWrappers *api,
|
||||
StreamDescription &stream,
|
||||
bool start)
|
||||
StreamDescription &stream)
|
||||
{
|
||||
YoutubeApiWrappers *apiYouTube = api;
|
||||
|
||||
std::string boundStreamId;
|
||||
{
|
||||
json11::Json json;
|
||||
if (!apiYouTube->FindBroadcast(selectedBroadcast, json)) {
|
||||
blog(LOG_DEBUG, "No broadcast found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto item = json["items"].array_items()[0];
|
||||
auto boundStreamId =
|
||||
item["contentDetails"]["boundStreamId"].string_value();
|
||||
json11::Json json;
|
||||
if (!apiYouTube->FindBroadcast(selectedBroadcast, json)) {
|
||||
blog(LOG_DEBUG, "No broadcast found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string boundStreamId =
|
||||
json["items"]
|
||||
.array_items()[0]["contentDetails"]["boundStreamId"]
|
||||
.string_value();
|
||||
|
||||
stream.id = boundStreamId.c_str();
|
||||
json11::Json json;
|
||||
if (!stream.id.isEmpty() && apiYouTube->FindStream(stream.id, json)) {
|
||||
auto item = json["items"].array_items()[0];
|
||||
auto streamName = item["cdn"]["streamName"].string_value();
|
||||
auto streamName = item["cdn"]["ingestionInfo"]["streamName"]
|
||||
.string_value();
|
||||
auto title = item["snippet"]["title"].string_value();
|
||||
auto description =
|
||||
item["snippet"]["description"].string_value();
|
||||
@ -379,6 +430,7 @@ bool OBSYoutubeActions::ChooseAnEventAction(YoutubeApiWrappers *api,
|
||||
stream.name = streamName.c_str();
|
||||
stream.title = title.c_str();
|
||||
stream.description = description.c_str();
|
||||
api->SetBroadcastId(selectedBroadcast);
|
||||
} else {
|
||||
stream = {"", "", "OBS Studio Video Stream", ""};
|
||||
if (!apiYouTube->InsertStream(stream)) {
|
||||
@ -426,8 +478,7 @@ void OBSYoutubeActions::InitBroadcast()
|
||||
stream);
|
||||
}
|
||||
} else {
|
||||
success = this->ChooseAnEventAction(apiYouTube, stream,
|
||||
this->autostart);
|
||||
success = this->ChooseAnEventAction(apiYouTube, stream);
|
||||
};
|
||||
QMetaObject::invokeMethod(&msgBox, "accept",
|
||||
Qt::QueuedConnection);
|
||||
|
@ -23,7 +23,8 @@ public slots:
|
||||
signals:
|
||||
void ready();
|
||||
void new_item(const QString &title, const QString &dateTimeString,
|
||||
const QString &broadcast, bool astart, bool astop);
|
||||
const QString &broadcast, const QString &status,
|
||||
bool astart, bool astop);
|
||||
void failed();
|
||||
};
|
||||
|
||||
@ -43,7 +44,7 @@ protected:
|
||||
StreamDescription &stream);
|
||||
bool StreamLaterAction(YoutubeApiWrappers *api);
|
||||
bool ChooseAnEventAction(YoutubeApiWrappers *api,
|
||||
StreamDescription &stream, bool start);
|
||||
StreamDescription &stream);
|
||||
|
||||
void ShowErrorDialog(QWidget *parent, QString text);
|
||||
|
||||
|
@ -29,7 +29,7 @@ using namespace json11;
|
||||
#define YOUTUBE_LIVE_VIDEOS_URL YOUTUBE_LIVE_API_URL "/videos"
|
||||
|
||||
#define DEFAULT_BROADCASTS_PER_QUERY \
|
||||
"7" // acceptable values are 0 to 50, inclusive
|
||||
"50" // acceptable values are 0 to 50, inclusive
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
bool IsYouTubeService(const std::string &service)
|
||||
@ -284,14 +284,20 @@ bool YoutubeApiWrappers::BindStream(const QString broadcast_id,
|
||||
data.dump().c_str(), json_out);
|
||||
}
|
||||
|
||||
bool YoutubeApiWrappers::GetBroadcastsList(Json &json_out, QString page)
|
||||
bool YoutubeApiWrappers::GetBroadcastsList(Json &json_out, const QString &page,
|
||||
const QString &status)
|
||||
{
|
||||
lastErrorMessage.clear();
|
||||
lastErrorReason.clear();
|
||||
QByteArray url = YOUTUBE_LIVE_BROADCAST_URL
|
||||
"?part=snippet,contentDetails,status"
|
||||
"&broadcastType=all&maxResults=" DEFAULT_BROADCASTS_PER_QUERY
|
||||
"&mine=true";
|
||||
"&broadcastType=all&maxResults=" DEFAULT_BROADCASTS_PER_QUERY;
|
||||
|
||||
if (status.isEmpty())
|
||||
url += "&mine=true";
|
||||
else
|
||||
url += "&broadcastStatus=" + status.toUtf8();
|
||||
|
||||
if (!page.isEmpty())
|
||||
url += "&pageToken=" + page.toUtf8();
|
||||
return InsertCommand(url, "application/json", "", nullptr, json_out);
|
||||
@ -392,6 +398,11 @@ bool YoutubeApiWrappers::StopLatestBroadcast()
|
||||
return StopBroadcast(this->broadcast_id);
|
||||
}
|
||||
|
||||
void YoutubeApiWrappers::SetBroadcastId(QString &broadcast_id)
|
||||
{
|
||||
this->broadcast_id = broadcast_id;
|
||||
}
|
||||
|
||||
bool YoutubeApiWrappers::ResetBroadcast(const QString &broadcast_id)
|
||||
{
|
||||
lastErrorMessage.clear();
|
||||
|
@ -65,7 +65,8 @@ public:
|
||||
bool InsertBroadcast(BroadcastDescription &broadcast);
|
||||
bool InsertStream(StreamDescription &stream);
|
||||
bool BindStream(const QString broadcast_id, const QString stream_id);
|
||||
bool GetBroadcastsList(json11::Json &json_out, QString page);
|
||||
bool GetBroadcastsList(json11::Json &json_out, const QString &page,
|
||||
const QString &status);
|
||||
bool
|
||||
GetVideoCategoriesList(const QString &country, const QString &language,
|
||||
QVector<CategoryDescription> &category_list_out);
|
||||
@ -79,6 +80,8 @@ public:
|
||||
bool StartLatestBroadcast();
|
||||
bool StopLatestBroadcast();
|
||||
|
||||
void SetBroadcastId(QString &broadcast_id);
|
||||
|
||||
bool FindBroadcast(const QString &id, json11::Json &json_out);
|
||||
bool FindStream(const QString &id, json11::Json &json_out);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user