From 372f7f50814301ff2a985cca5f4e9b058a1b203c Mon Sep 17 00:00:00 2001 From: tytan652 Date: Sat, 22 May 2021 20:36:20 +0200 Subject: [PATCH 01/23] libobs, UI: Add "open url" property --- UI/properties-view.cpp | 29 ++++++++++++++++++++++++++++- UI/properties-view.hpp | 3 +++ libobs/obs-properties.c | 13 +++++++++++++ libobs/obs-properties.h | 5 +++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index a92ae79369ecf7..a7fd83c96d25a1 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "double-slider.hpp" #include "slider-ignorewheel.hpp" #include "spinbox-ignorewheel.hpp" @@ -1397,6 +1398,21 @@ void OBSPropertiesView::AddGroup(obs_property_t *prop, QFormLayout *layout) connect(groupBox, SIGNAL(toggled(bool)), info, SLOT(ControlChanged())); } +QWidget *OBSPropertiesView::AddOpenUrl(obs_property_t *prop) +{ + const char *name = obs_property_name(prop); + const char *desc = obs_property_description(prop); + const char *val = obs_data_get_string(settings, name); + + QPushButton *button = new QPushButton(QT_UTF8(desc)); + button->setProperty("themeID", "settingsButtons"); + button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + QUrl qurl = QString::fromUtf8(val); + connect(button, &QPushButton::clicked, this, + [=]() { this->OpenUrl(qurl); }); + return NewWidget(prop, button, SIGNAL(clicked())); +} + void OBSPropertiesView::AddProperty(obs_property_t *property, QFormLayout *layout) { @@ -1451,13 +1467,17 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, break; case OBS_PROPERTY_COLOR_ALPHA: AddColorAlpha(property, layout, label); + break; + case OBS_PROPERTY_OPEN_URL: + widget = AddOpenUrl(property); } if (widget && !obs_property_enabled(property)) widget->setEnabled(false); if (!label && type != OBS_PROPERTY_BOOL && - type != OBS_PROPERTY_BUTTON && type != OBS_PROPERTY_GROUP) + type != OBS_PROPERTY_BUTTON && type != OBS_PROPERTY_GROUP && + type != OBS_PROPERTY_OPEN_URL) label = new QLabel(QT_UTF8(obs_property_description(property))); if (warning && label) //TODO: select color based on background color @@ -1529,6 +1549,11 @@ void OBSPropertiesView::SignalChanged() emit Changed(); } +void OBSPropertiesView::OpenUrl(QUrl url) +{ + QDesktopServices::openUrl(url); +} + static bool FrameRateChangedVariant(const QVariant &variant, media_frames_per_second &fps, obs_data_item_t *&obj, @@ -1950,6 +1975,8 @@ void WidgetInfo::ControlChanged() if (!ColorAlphaChanged(setting)) return; break; + case OBS_PROPERTY_OPEN_URL: + return; } if (!recently_updated) { diff --git a/UI/properties-view.hpp b/UI/properties-view.hpp index fe1461d94462a0..95a5e0cbac2165 100644 --- a/UI/properties-view.hpp +++ b/UI/properties-view.hpp @@ -134,6 +134,8 @@ class OBSPropertiesView : public VScrollArea { void AddGroup(obs_property_t *prop, QFormLayout *layout); + QWidget *AddOpenUrl(obs_property_t *prop); + void AddProperty(obs_property_t *property, QFormLayout *layout); void resizeEvent(QResizeEvent *event) override; @@ -145,6 +147,7 @@ public slots: void ReloadProperties(); void RefreshProperties(); void SignalChanged(); + void OpenUrl(QUrl url); signals: void PropertiesResized(); diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index de1705daf9270c..b737498abf6356 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -440,6 +440,8 @@ static inline size_t get_property_size(enum obs_property_type type) return sizeof(struct group_data); case OBS_PROPERTY_COLOR_ALPHA: return 0; + case OBS_PROPERTY_OPEN_URL: + return 0; } return 0; @@ -806,6 +808,17 @@ obs_property_t *obs_properties_add_group(obs_properties_t *props, return p; } +obs_property_t *obs_properties_add_open_url(obs_properties_t *props, + const char *name, const char *desc) +{ + if (!props || has_prop(props, name)) + return NULL; + + struct obs_property *p = + new_prop(props, name, desc, OBS_PROPERTY_OPEN_URL); + return p; +} + /* ------------------------------------------------------------------------- */ static inline bool is_combo(struct obs_property *p) diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index 92790120760f3f..aadb8c03aca7c0 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -57,6 +57,7 @@ enum obs_property_type { OBS_PROPERTY_FRAME_RATE, OBS_PROPERTY_GROUP, OBS_PROPERTY_COLOR_ALPHA, + OBS_PROPERTY_OPEN_URL, }; enum obs_combo_format { @@ -267,6 +268,10 @@ EXPORT obs_property_t *obs_properties_add_group(obs_properties_t *props, enum obs_group_type type, obs_properties_t *group); +EXPORT obs_property_t *obs_properties_add_open_url(obs_properties_t *props, + const char *name, + const char *desc); + /* ------------------------------------------------------------------------- */ /** From 64f73179bfa278e9651e5fc132bd413d837112b3 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Sat, 22 May 2021 20:50:31 +0200 Subject: [PATCH 02/23] libobs: Add comments to obs-service.h --- libobs/obs-service.h | 52 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/libobs/obs-service.h b/libobs/obs-service.h index 697ccb39e7f8d9..2f8f62c45cbf37 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -34,21 +34,63 @@ struct obs_service_resolution { }; struct obs_service_info { - /* required */ + /* ----------------------------------------------------------------- */ + /* Required implementation*/ + + /** Specifies the named identifier of this service */ const char *id; + /** + * Gets the full translated name of this service + * + * @param type_data The type_data variable of this structure + * @return Translated name of the service + */ const char *(*get_name)(void *type_data); + + /** + * Creates the service with the specified settings + * + * @param settings Settings for the service + * @param service OBS service context + * @return Data associated with this service context, or + * NULL if initialization failed. + */ void *(*create)(obs_data_t *settings, obs_service_t *service); + + /** + * Destroys the service data + * + * @param data Data associated with this service context + */ void (*destroy)(void *data); - /* optional */ + /* ----------------------------------------------------------------- */ + /* Optional implementation */ + void (*activate)(void *data, obs_data_t *settings); void (*deactivate)(void *data); + /** + * Updates the settings for this service + * + * @param data Data associated with this service context + * @param settings New settings for this service + */ void (*update)(void *data, obs_data_t *settings); + /** + * Gets the default settings for this service + * + * @param[out] settings Data to assign default settings to + */ void (*get_defaults)(obs_data_t *settings); + /** + * Gets the property information of this service + * + * @return The properties data + */ obs_properties_t *(*get_properties)(void *data); /** @@ -91,6 +133,12 @@ struct obs_service_info { EXPORT void obs_register_service_s(const struct obs_service_info *info, size_t size); +/** + * Register an service definition to the current obs context. This should be + * used in obs_module_load. + * + * @param info Pointer to the source definition structure. + */ #define obs_register_service(info) \ obs_register_service_s(info, sizeof(struct obs_service_info)) From d60d4c6bb319b966f325b907a073ae483d522667 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Sat, 22 May 2021 20:53:18 +0200 Subject: [PATCH 03/23] libobs: Add some missing functions to Service API --- libobs/obs-service.c | 23 ++++++++++++++++++++--- libobs/obs-service.h | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/libobs/obs-service.c b/libobs/obs-service.c index e2fb0e8ec6e20b..e417bbfa2d740a 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -126,6 +126,9 @@ static inline obs_data_t *get_defaults(const struct obs_service_info *info) obs_data_t *settings = obs_data_create(); if (info->get_defaults) info->get_defaults(settings); + if (info->get_defaults2) { + info->get_defaults2(settings, info->type_data); + } return settings; } @@ -138,11 +141,17 @@ obs_data_t *obs_service_defaults(const char *id) obs_properties_t *obs_get_service_properties(const char *id) { const struct obs_service_info *info = find_service(id); - if (info && info->get_properties) { + if (info && (info->get_properties || info->get_properties2)) { obs_data_t *defaults = get_defaults(info); - obs_properties_t *properties; + obs_properties_t *properties = NULL; + + if (info->get_properties2) { + properties = + info->get_properties2(NULL, info->type_data); + } else if (info->get_properties) { + properties = info->get_properties(NULL); + } - properties = info->get_properties(NULL); obs_properties_apply_settings(properties, defaults); obs_data_release(defaults); return properties; @@ -155,6 +164,14 @@ obs_properties_t *obs_service_properties(const obs_service_t *service) if (!obs_service_valid(service, "obs_service_properties")) return NULL; + if (service->info.get_properties2) { + obs_properties_t *props; + props = service->info.get_properties2(service->context.data, + service->info.type_data); + obs_properties_apply_settings(props, service->context.settings); + return props; + } + if (service->info.get_properties) { obs_properties_t *props; props = service->info.get_properties(service->context.data); diff --git a/libobs/obs-service.h b/libobs/obs-service.h index 2f8f62c45cbf37..329cf90658aa1a 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -128,6 +128,26 @@ struct obs_service_info { void (*get_max_bitrate)(void *data, int *video_bitrate, int *audio_bitrate); + + /** + * Gets the default settings for this service + * + * If get_defaults is also defined both will be called, and the first + * call will be to get_defaults, then to get_defaults2. + * + * @param[out] settings Data to assign default settings to + * @param[in] typedata Type Data + */ + void (*get_defaults2)(obs_data_t *settings, void *type_data); + + /** + * Gets the property information of this service + * + * @param[in] data Pointer from create (or null) + * @param[in] typedata Type Data + * @return The properties data + */ + obs_properties_t *(*get_properties2)(void *data, void *type_data); }; EXPORT void obs_register_service_s(const struct obs_service_info *info, From 3b33954315af208c2e63e49403e3ad59fade3d26 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 17:48:04 +0200 Subject: [PATCH 04/23] libobs: Add protocol to service API --- libobs/obs-service.c | 10 ++++++++++ libobs/obs-service.h | 1 + 2 files changed, 11 insertions(+) diff --git a/libobs/obs-service.c b/libobs/obs-service.c index e417bbfa2d740a..91f72b6cd7e921 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -224,6 +224,16 @@ proc_handler_t *obs_service_get_proc_handler(const obs_service_t *service) : NULL; } +const char *obs_service_get_protocol(const obs_service_t *service) +{ + if (!obs_service_valid(service, "obs_service_get_protocol")) + return NULL; + + if (!service->info.get_protocol) + return NULL; + return service->info.get_protocol(service->context.data); +} + const char *obs_service_get_url(const obs_service_t *service) { if (!obs_service_valid(service, "obs_service_get_url")) diff --git a/libobs/obs-service.h b/libobs/obs-service.h index 329cf90658aa1a..e199471e9fa7b3 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -104,6 +104,7 @@ struct obs_service_info { */ bool (*initialize)(void *data, obs_output_t *output); + const char *(*get_protocol)(void *data); const char *(*get_url)(void *data); const char *(*get_key)(void *data); From 667ce7913eae5ed7b92266ce932410f814a12099 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Sat, 22 May 2021 21:33:11 +0200 Subject: [PATCH 05/23] docs: Add "open url" property --- docs/sphinx/reference-properties.rst | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/reference-properties.rst b/docs/sphinx/reference-properties.rst index b7d8c8d9071de8..3c341679dcad03 100644 --- a/docs/sphinx/reference-properties.rst +++ b/docs/sphinx/reference-properties.rst @@ -47,7 +47,7 @@ General Functions :param flags: 0 or a bitwise OR combination of one of the following values: - + - OBS_PROPERTIES_DEFER_UPDATE - A hint that tells the front-end to defers updating the settings until the user has finished editing all properties rather than @@ -266,7 +266,7 @@ Property Object Functions :param name: Setting identifier string :param description: Localized name shown to user :param type: Can be one of the following values: - + - **OBS_EDITABLE_LIST_TYPE_STRINGS** - An editable list of strings. - **OBS_EDITABLE_LIST_TYPE_FILES** - An @@ -320,6 +320,19 @@ Property Object Functions --------------------- +.. function:: obs_property_t *obs_properties_add_open_url(obs_properties_t *props, const char *name, const char *description) + + Adds a button which open a URL. This property does not actually + store any settings; it's used to implement a button in user + interface if the properties are used to generate user interface. + + :param name: Setting identifier string + :param description: Localized name shown to user + + :return: The property + +--------------------- + Property Enumeration Functions ------------------------------ From 057f328a0bb8c8f745ae4a547ad5f8a21ce0c71b Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 11:55:54 +0200 Subject: [PATCH 06/23] obs-services: Add service manager and factory --- plugins/CMakeLists.txt | 1 + plugins/obs-services/CMakeLists.txt | 34 + plugins/obs-services/data/locale/en-US.ini | 3 + plugins/obs-services/data/services.json | 1686 ++++++++++++++++++++ plugins/obs-services/json-format-ver.hpp | 3 + plugins/obs-services/plugin-main.cpp | 21 + plugins/obs-services/plugin.hpp | 7 + plugins/obs-services/service-factory.cpp | 170 ++ plugins/obs-services/service-factory.hpp | 41 + plugins/obs-services/service-instance.cpp | 15 + plugins/obs-services/service-instance.hpp | 18 + plugins/obs-services/service-manager.cpp | 140 ++ plugins/obs-services/service-manager.hpp | 21 + 13 files changed, 2160 insertions(+) create mode 100644 plugins/obs-services/CMakeLists.txt create mode 100644 plugins/obs-services/data/locale/en-US.ini create mode 100644 plugins/obs-services/data/services.json create mode 100644 plugins/obs-services/json-format-ver.hpp create mode 100644 plugins/obs-services/plugin-main.cpp create mode 100644 plugins/obs-services/plugin.hpp create mode 100644 plugins/obs-services/service-factory.cpp create mode 100644 plugins/obs-services/service-factory.hpp create mode 100644 plugins/obs-services/service-instance.cpp create mode 100644 plugins/obs-services/service-instance.hpp create mode 100644 plugins/obs-services/service-manager.cpp create mode 100644 plugins/obs-services/service-manager.hpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a39f357719ef42..7043f7a88f3f32 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -87,6 +87,7 @@ add_subdirectory(obs-libfdk) add_subdirectory(obs-ffmpeg) add_subdirectory(obs-outputs) add_subdirectory(obs-filters) +add_subdirectory(obs-services) add_subdirectory(obs-transitions) add_subdirectory(obs-text) add_subdirectory(rtmp-services) diff --git a/plugins/obs-services/CMakeLists.txt b/plugins/obs-services/CMakeLists.txt new file mode 100644 index 00000000000000..bcb96317606ed6 --- /dev/null +++ b/plugins/obs-services/CMakeLists.txt @@ -0,0 +1,34 @@ +project(obs-services) + +include_directories(${OBS_JANSSON_INCLUDE_DIRS}) + +set(obs-services_SOURCES + service-instance.cpp + service-factory.cpp + service-manager.cpp + plugin-main.cpp) +set(obs-services_HEADERS + service-instance.cpp + service-factory.cpp + service-manager.cpp + json-format-ver.hpp + plugin.hpp) + +if(WIN32) + set(MODULE_DESCRIPTION "OBS Core Services") + configure_file(${CMAKE_SOURCE_DIR}/cmake/winrc/obs-module.rc.in obs-services.rc) + list(APPEND obs-services_SOURCES + obs-services.rc) +endif() + +add_library(obs-services MODULE + ${obs-services_SOURCES} + ${obs-services_HEADERS}) + +target_link_libraries(obs-services + libobs + ${OBS_JANSSON_IMPORT}) + +set_target_properties(obs-services PROPERTIES FOLDER "plugins") + +install_obs_plugin_with_data(obs-services data) diff --git a/plugins/obs-services/data/locale/en-US.ini b/plugins/obs-services/data/locale/en-US.ini new file mode 100644 index 00000000000000..b9c85d77cd422d --- /dev/null +++ b/plugins/obs-services/data/locale/en-US.ini @@ -0,0 +1,3 @@ +Protocol="Protocol" +Server="Server" +StreamKey="Stream key" diff --git a/plugins/obs-services/data/services.json b/plugins/obs-services/data/services.json new file mode 100644 index 00000000000000..6ac6793a49256d --- /dev/null +++ b/plugins/obs-services/data/services.json @@ -0,0 +1,1686 @@ +{ + "format_version": 4, + "services": [ + { + "id": "youtube", + "name": "YouTube", + "available_protocols": ["HLS", "RTMPS", "RTMP"], + "servers": [ + { + "protocol": "HLS", + "name": "Primary YouTube ingest server (HLS)", + "url": "https://a.upload.youtube.com/http_upload_hls?cid={stream_key}©=0&file=out.m3u8" + }, + { + "protocol": "HLS", + "name": "Backup YouTube ingest server (HLS)", + "url": "https://b.upload.youtube.com/http_upload_hls?cid={stream_key}©=1&file=out.m3u8" + }, + { + "protocol": "RTMPS", + "name": "Primary YouTube ingest server (RTMPS)", + "url": "rtmps://a.rtmps.youtube.com:443/live2" + }, + { + "protocol": "RTMPS", + "name": "Backup YouTube ingest server (RTMPS)", + "url": "rtmps://b.rtmps.youtube.com:443/live2?backup=1" + }, + { + "protocol": "RTMP", + "name": "Primary YouTube ingest server (legacy RTMP)", + "url": "rtmp://a.rtmp.youtube.com/live2" + }, + { + "protocol": "RTMP", + "name": "Backup YouTube ingest server (legacy RTMP)", + "url": "rtmp://b.rtmp.youtube.com/live2?backup=1" + } + ] + }, + { + "id": "loola_tv", + "name": "Loola.tv", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "US East: Virginia", + "url": "rtmp://rtmp.loola.tv/push" + }, + { + "name": "EU Central: Germany", + "url": "rtmp://rtmp-eu.loola.tv/push" + }, + { + "name": "South America: Brazil", + "url": "rtmp://rtmp-sa.loola.tv/push" + }, + { + "name": "Asia/Pacific: Singapore", + "url": "rtmp://rtmp-sg.loola.tv/push" + }, + { + "name": "Middle East: Bahrain", + "url": "rtmp://rtmp-me.loola.tv/push" + } + ] + }, + { + "id": "luzento", + "name": "Luzento.com", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary", + "url": "rtmp://ingest.luzento.com/live" + }, + { + "name": "Primary (Test)", + "url": "rtmp://ingest.luzento.com/test" + } + ] + }, + { + "id": "vimm", + "name": "VIMM", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Europe: Frankfurt", + "url": "rtmp://eu.vimm.tv/live" + }, + { + "name": "North America: Montreal", + "url": "rtmp://us.vimm.tv/live" + } + ] + }, + { + "id": "mobcrush", + "name": "Mobcrush", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary", + "url": "rtmp://live.mobcrush.net/mob" + } + ] + }, + { + "id": "web_dot_tv", + "name": "Web.TV", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary", + "url": "rtmp://live3.origins.web.tv/liveext" + } + ] + }, + { + "id": "googgame_ru", + "name": "GoodGame.ru", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Моscow", + "url": "rtmp://msk.goodgame.ru:1940/live" + } + ] + }, + { + "id": "youstreamer", + "name": "YouStreamer", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Moscow", + "url": "rtmp://push.youstreamer.com/in/" + } + ] + }, + { + "id": "vaughn", + "name": "Vaughn Live / iNSTAGIB", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "US: Chicago, IL", + "url": "rtmp://live-ord.vaughnsoft.net/live" + }, + { + "name": "US: Vint Hill, VA", + "url": "rtmp://live-iad.vaughnsoft.net/live" + }, + { + "name": "US: Denver, CO", + "url": "rtmp://live-den.vaughnsoft.net/live" + }, + { + "name": "US: New York, NY", + "url": "rtmp://live-nyc.vaughnsoft.net/live" + }, + { + "name": "US: Miami, FL", + "url": "rtmp://live-mia.vaughnsoft.net/live" + }, + { + "name": "US: Seattle, WA", + "url": "rtmp://live-sea.vaughnsoft.net/live" + }, + { + "name": "EU: Amsterdam, NL", + "url": "rtmp://live-ams.vaughnsoft.net/live" + }, + { + "name": "EU: London, UK", + "url": "rtmp://live-lhr.vaughnsoft.net/live" + } + ] + }, + { + "id": "breakers", + "name": "Breakers.TV", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "US: Chicago, IL", + "url": "rtmp://live-ord.vaughnsoft.net/live" + }, + { + "name": "US: Vint Hill, VA", + "url": "rtmp://live-iad.vaughnsoft.net/live" + }, + { + "name": "US: Denver, CO", + "url": "rtmp://live-den.vaughnsoft.net/live" + }, + { + "name": "US: New York, NY", + "url": "rtmp://live-nyc.vaughnsoft.net/live" + }, + { + "name": "US: Miami, FL", + "url": "rtmp://live-mia.vaughnsoft.net/live" + }, + { + "name": "US: Seattle, WA", + "url": "rtmp://live-sea.vaughnsoft.net/live" + }, + { + "name": "EU: Amsterdam, NL", + "url": "rtmp://live-ams.vaughnsoft.net/live" + }, + { + "name": "EU: London, UK", + "url": "rtmp://live-lhr.vaughnsoft.net/live" + } + ] + }, + { + "id": "facebook", + "name": "Facebook Live", + "available_protocols": ["RTMPS"], + "servers": [ + { + "protocol": "RTMPS", + "name": "Default", + "url": "rtmps://rtmp-api.facebook.com:443/rtmp/" + } + ] + }, + { + "id": "restream_io", + "name": "Restream.io", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Autodetect", + "url": "rtmp://live.restream.io/live" + }, + { + "name": "EU-West (London, GB)", + "url": "rtmp://london.restream.io/live" + }, + { + "name": "EU-West (Amsterdam, NL)", + "url": "rtmp://amsterdam.restream.io/live" + }, + { + "name": "EU-West (Luxembourg)", + "url": "rtmp://luxembourg.restream.io/live" + }, + { + "name": "EU-West (Paris, FR)", + "url": "rtmp://paris.restream.io/live" + }, + { + "name": "EU-West (Milan, IT)", + "url": "rtmp://milan.restream.io/live" + }, + { + "name": "EU-Central (Frankfurt, DE)", + "url": "rtmp://frankfurt.restream.io/live" + }, + { + "name": "EU-East (Falkenstein, DE)", + "url": "rtmp://falkenstein.restream.io/live" + }, + { + "name": "EU-East (Prague, Czech)", + "url": "rtmp://prague.restream.io/live" + }, + { + "name": "EU-South (Madrid, Spain)", + "url": "rtmp://madrid.restream.io/live" + }, + { + "name": "Russia (Moscow)", + "url": "rtmp://moscow.restream.io/live" + }, + { + "name": "Turkey (Istanbul)", + "url": "rtmp://istanbul.restream.io/live" + }, + { + "name": "Israel (Tel Aviv)", + "url": "rtmp://telaviv.restream.io/live" + }, + { + "name": "US-West (Seattle, WA)", + "url": "rtmp://seattle.restream.io/live" + }, + { + "name": "US-West (San Jose, CA)", + "url": "rtmp://sanjose.restream.io/live" + }, + { + "name": "US-Central (Dallas, TX)", + "url": "rtmp://dallas.restream.io/live" + }, + { + "name": "US-East (Washington, DC)", + "url": "rtmp://washington.restream.io/live" + }, + { + "name": "US-East (Miami, FL)", + "url": "rtmp://miami.restream.io/live" + }, + { + "name": "US-East (Chicago, IL)", + "url": "rtmp://chicago.restream.io/live" + }, + { + "name": "NA-East (Toronto, Canada)", + "url": "rtmp://toronto.restream.io/live" + }, + { + "name": "SA (Saint Paul, Brazil)", + "url": "rtmp://saopaulo.restream.io/live" + }, + { + "name": "India (Bangalore)", + "url": "rtmp://bangalore.restream.io/live" + }, + { + "name": "Asia (Singapore)", + "url": "rtmp://singapore.restream.io/live" + }, + { + "name": "Asia (Seoul, South Korea)", + "url": "rtmp://seoul.restream.io/live" + }, + { + "name": "Asia (Tokyo, Japan)", + "url": "rtmp://tokyo.restream.io/live" + }, + { + "name": "Australia (Sydney)", + "url": "rtmp://sydney.restream.io/live" + } + ] + }, + { + "id": "nood", + "name": "Nood", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Global: Fastest (Recommended)", + "url": "rtmp://stream.nood.tv/live_source" + }, + { + "name": "NA East: Ashburn, VA, USA", + "url": "rtmp://us-east-1.stream.nood.tv/live_source" + }, + { + "name": "NA East: Columbus, OH, USA", + "url": "rtmp://us-east-2.stream.nood.tv/live_source" + }, + { + "name": "NA East: Montreal, QC, CAN", + "url": "rtmp://ca-central-1.stream.nood.tv/live_source" + }, + { + "name": "NA West: San Francisco, CA, USA", + "url": "rtmp://us-west-1.stream.nood.tv/live_source" + }, + { + "name": "NA West: Portland, OR, USA", + "url": "rtmp://us-west-2.stream.nood.tv/live_source" + }, + { + "name": "SA East: Sao Paulo, BRA", + "url": "rtmp://sa-east-1.stream.nood.tv/live_source" + }, + { + "name": "EU West: Dublin, IRL", + "url": "rtmp://eu-west-1.stream.nood.tv/live_source" + }, + { + "name": "EU West: London, GBR", + "url": "rtmp://eu-west-2.stream.nood.tv/live_source" + }, + { + "name": "EU West: Paris, FRA", + "url": "rtmp://eu-west-3.stream.nood.tv/live_source" + }, + { + "name": "EU West: Frankfurt, DEU", + "url": "rtmp://eu-central-1.stream.nood.tv/live_source" + }, + { + "name": "Asia North-East: Tokyo, JPN", + "url": "rtmp://ap-northeast-1.stream.nood.tv/live_source" + }, + { + "name": "Asia North-East: Seoul, KOR", + "url": "rtmp://ap-northeast-2.stream.nood.tv/live_source" + }, + { + "name": "Asia South-East: Singapore, SGP", + "url": "rtmp://ap-southeast-1.stream.nood.tv/live_source" + }, + { + "name": "Asia South-East: Sydney, AUS", + "url": "rtmp://ap-southeast-2.stream.nood.tv/live_source" + }, + { + "name": "Asia South: Mumbai, IND", + "url": "rtmp://ap-south-1.stream.nood.tv/live_source" + } + ] + }, + { + "id": "castr_io", + "name": "Castr.io", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "US-East (Chicago, IL)", + "url": "rtmp://cg.castr.io/static" + }, + { + "name": "US-East (New York, NY)", + "url": "rtmp://ny.castr.io/static" + }, + { + "name": "US-East (Miami, FL)", + "url": "rtmp://mi.castr.io/static" + }, + { + "name": "US-West (Seattle, WA)", + "url": "rtmp://se.castr.io/static" + }, + { + "name": "US-West (Los Angeles, CA)", + "url": "rtmp://la.castr.io/static" + }, + { + "name": "US-Central (Dallas, TX)", + "url": "rtmp://da.castr.io/static" + }, + { + "name": "NA-East (Toronto, CA)", + "url": "rtmp://qc.castr.io/static" + }, + { + "name": "SA (Sao Paulo, BR)", + "url": "rtmp://br.castr.io/static" + }, + { + "name": "EU-West (London, UK)", + "url": "rtmp://uk.castr.io/static" + }, + { + "name": "EU-Central (Frankfurt, DE)", + "url": "rtmp://fr.castr.io/static" + }, + { + "name": "Russia (Moscow)", + "url": "rtmp://ru.castr.io/static" + }, + { + "name": "Asia (Singapore)", + "url": "rtmp://sg.castr.io/static" + }, + { + "name": "Asia (India)", + "url": "rtmp://in.castr.io/static" + }, + { + "name": "Australia (Sydney)", + "url": "rtmp://au.castr.io/static" + }, + { + "name": "US Central", + "url": "rtmp://us-central.castr.io/static" + }, + { + "name": "US West", + "url": "rtmp://us-west.castr.io/static" + }, + { + "name": "US East", + "url": "rtmp://us-east.castr.io/static" + }, + { + "name": "US South", + "url": "rtmp://us-south.castr.io/static" + }, + { + "name": "South America", + "url": "rtmp://south-am.castr.io/static" + }, + { + "name": "EU Central", + "url": "rtmp://eu-central.castr.io/static" + }, + { + "name": "Singapore", + "url": "rtmp://sg-central.castr.io/static" + } + ] + }, + { + "id": "boomstream", + "name": "Boomstream", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://live.boomstream.com/live" + } + ] + }, + { + "id": "meridix", + "name": "Meridix Live Sports Platform", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary", + "url": "rtmp://publish.meridix.com/live" + } + ] + }, + { + "id": "afreeca", + "name": "AfreecaTV", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Asia : Korea", + "url": "rtmp://rtmpmanager-freecat.afreeca.tv/app" + }, + { + "name": "North America : US East", + "url": "rtmp://rtmp-esu.afreecatv.com/app" + }, + { + "name": "North America : US West", + "url": "rtmp://rtmp-wsu.afreecatv.com/app" + }, + { + "name": "Europe : UK", + "url": "rtmp://rtmp-uk.afreecatv.com/app" + }, + { + "name": "Asia : Singapore", + "url": "rtmp://rtmp-sgp.afreecatv.com/app" + } + ] + }, + { + "id": "cam4", + "name": "CAM4", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "CAM4", + "url": "rtmp://origin.cam4.com/cam4-origin-live" + } + ] + }, + { + "id": "eplay", + "name": "ePlay", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "ePlay Primary", + "url": "rtmp://live.eplay.link/origin" + } + ] + }, + { + "id": "picarto", + "name": "Picarto", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "US East (Chicago, USA)", + "url": "rtmp://live.us-east1.picarto.tv/golive" + }, + { + "name": "US West (Los Angeles, USA)", + "url": "rtmp://live.us-west1.picarto.tv/golive" + }, + { + "name": "EU West (Düsseldorf, Germany)", + "url": "rtmp://live.eu-west1.picarto.tv/golive" + } + ] + }, + { + "id": "pandora_tv", + "name": "Pandora TV Korea", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://plive.pandora.tv:80/mediaHub" + } + ] + }, + { + "id": "livestream", + "name": "Livestream", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary", + "url": "rtmp://rtmpin.livestreamingest.com/rtmpin" + } + ] + }, + { + "id": "uscreen", + "name": "Uscreen", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://global-live.uscreen.app:5222/app" + } + ] + }, + { + "id": "stripchat", + "name": "Stripchat", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Auto", + "url": "rtmp://s-sd.stripst.com/ext" + } + ] + }, + { + "id": "camsoda", + "name": "CamSoda", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "North America", + "url": "rtmp://obs-ingest-na.camsoda.com/cam_obs" + }, + { + "name": "South America", + "url": "rtmp://obs-ingest-sa.camsoda.com/cam_obs" + }, + { + "name": "Asia", + "url": "rtmp://obs-ingest-as.camsoda.com/cam_obs" + }, + { + "name": "Europe", + "url": "rtmp://obs-ingest-eu.camsoda.com/cam_obs" + }, + { + "name": "Oceania", + "url": "rtmp://obs-ingest-oc.camsoda.com/cam_obs" + } + ] + }, + { + "id": "chaturbate", + "name": "Chaturbate", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Global Main Fastest - Recommended", + "url": "rtmp://live.stream.highwebmedia.com/live-origin" + }, + { + "name": "Global Backup", + "url": "rtmp://live-backup.stream.highwebmedia.com/live-origin" + }, + { + "name": "US West: Seattle, WA", + "url": "rtmp://live-sea.stream.highwebmedia.com/live-origin" + }, + { + "name": "US West: Phoenix, AZ", + "url": "rtmp://live-phx.stream.highwebmedia.com/live-origin" + }, + { + "name": "US Central: Salt Lake City, UT", + "url": "rtmp://live-slc.stream.highwebmedia.com/live-origin" + }, + { + "name": "US Central: Chicago, IL", + "url": "rtmp://live-chi.stream.highwebmedia.com/live-origin" + }, + { + "name": "US East: Atlanta, GA", + "url": "rtmp://live-atl.stream.highwebmedia.com/live-origin" + }, + { + "name": "US East: Ashburn, VA", + "url": "rtmp://live-ash.stream.highwebmedia.com/live-origin" + }, + { + "name": "South America: Sao Paulo, Brazil", + "url": "rtmp://live-gru.stream.highwebmedia.com/live-origin" + }, + { + "name": "EU: Amsterdam, NL", + "url": "rtmp://live-nld.stream.highwebmedia.com/live-origin" + }, + { + "name": "EU: Alblasserdam, NL", + "url": "rtmp://live-alb.stream.highwebmedia.com/live-origin" + }, + { + "name": "EU: Frankfurt, DE", + "url": "rtmp://live-fra.stream.highwebmedia.com/live-origin" + }, + { + "name": "EU: Belgrade, Serbia", + "url": "rtmp://live-srb.stream.highwebmedia.com/live-origin" + }, + { + "name": "Asia: Singapore", + "url": "rtmp://live-sin.stream.highwebmedia.com/live-origin" + }, + { + "name": "Asia: Tokyo, Japan", + "url": "rtmp://live-nrt.stream.highwebmedia.com/live-origin" + }, + { + "name": "Australia: Sydney", + "url": "rtmp://live-syd.stream.highwebmedia.com/live-origin" + } + ] + }, + { + "id": "twitter", + "name": "Twitter", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "US West: California", + "url": "rtmp://ca.pscp.tv:80/x" + }, + { + "name": "US West: Oregon", + "url": "rtmp://or.pscp.tv:80/x" + }, + { + "name": "US East: Virginia", + "url": "rtmp://va.pscp.tv:80/x" + }, + { + "name": "South America: Brazil", + "url": "rtmp://br.pscp.tv:80/x" + }, + { + "name": "EU West: France", + "url": "rtmp://fr.pscp.tv:80/x" + }, + { + "name": "EU West: Ireland", + "url": "rtmp://ie.pscp.tv:80/x" + }, + { + "name": "EU Central: Germany", + "url": "rtmp://de.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: Australia", + "url": "rtmp://au.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: India", + "url": "rtmp://in.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: Japan", + "url": "rtmp://jp.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: Korea", + "url": "rtmp://kr.pscp.tv:80/x" + }, + { + "name": "Asia/Pacific: Singapore", + "url": "rtmp://sg.pscp.tv:80/x" + } + ] + }, + { + "id": "switcherboard", + "name": "Switchboard Live", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Global Zone (geo based)", + "url": "rtmp://ingest-global-a.switchboard.zone/live" + }, + { + "name": "US Zone (geo based)", + "url": "rtmp://ingest-us.switchboard.zone/live" + }, + { + "name": "US West 1 (South)", + "url": "rtmp://ingest-us-west.a.switchboard.zone/live" + }, + { + "name": "US West 2 (North)", + "url": "rtmp://ingest-us-west.b.switchboard.zone/live" + }, + { + "name": "US East 1 (North)", + "url": "rtmp://ingest-us-east.a.switchboard.zone/live" + }, + { + "name": "US East 2 (South)", + "url": "rtmp://ingest-us-east.b.switchboard.zone/live" + }, + { + "name": "US Central (North)", + "url": "rtmp://ingest-us-central.a.switchboard.zone/live" + }, + { + "name": "South America East (São Paulo, BR)", + "url": "rtmp://ingest-sa-east.a.switchboard.zone/live" + }, + { + "name": "Europe West (London, UK)", + "url": "rtmp://ingest-eu-west.a.switchboard.zone/live" + }, + { + "name": "Europe North (Hamina, FI)", + "url": "rtmp://ingest-eu-north.a.switchboard.zone/live" + }, + { + "name": "Australia Southeast (Sydney, AU)", + "url": "rtmp://ingest-au-southeast.a.switchboard.zone/live" + }, + { + "name": "Asia East (Changhua County, TW)", + "url": "rtmp://ingest-as-east.a.switchboard.zone/live" + }, + { + "name": "Asia Northeast (Tokyo, JP)", + "url": "rtmp://ingest-as-northeast.a.switchboard.zone/live" + }, + { + "name": "Asia South (Mumbai, IN)", + "url": "rtmp://ingest-as-south.a.switchboard.zone/live" + } + ] + }, + { + "id": "looch", + "name": "Looch", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary Looch ingest server", + "url": "rtmp://ingest.looch.tv/live" + } + ] + }, + { + "id": "eventials", + "name": "Eventials", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://live.eventials.com/eventialsLiveOrigin" + } + ] + }, + { + "id": "eventlive", + "name": "EventLive.pro", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://go.eventlive.pro/live" + } + ] + }, + { + "id": "lahzenegar", + "name": "Lahzenegar - StreamG | لحظه‌نگار - استریمجی", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary", + "url": "rtmp://rtmp.lahzecdn.com/pro" + }, + { + "name": "Iran", + "url": "rtmp://rtmp-iran.lahzecdn.com/pro" + } + ] + }, + { + "id": "mylive", + "name": "MyLive", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://stream.mylive.in.th/live" + } + ] + }, + { + "id": "trovo", + "name": "Trovo", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://livepush.trovo.live/live/" + } + ] + }, + { + "id": "mixcloud", + "name": "Mixcloud", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.mixcloud.com/broadcast" + } + ] + }, + { + "id": "sermonaudio", + "name": "SermonAudio Cloud", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Primary", + "url": "rtmp://webcast.sermonaudio.com/sa" + } + ] + }, + { + "id": "vimeo", + "name": "Vimeo", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.cloud.vimeo.com/live" + } + ] + }, + { + "id": "aparat", + "name": "Aparat", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.cdn.asset.aparat.com:443/event" + } + ] + }, + { + "id": "gametips", + "name": "GameTips.TV", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Iran - Tehran | AsiaTech", + "url": "rtmp://rtmp.s2.gametips.tv:1935/live" + }, + { + "name": "Netherlands - Amsterdam | Serverius", + "url": "rtmp://rtmp.s3.gametips.tv:1935/live" + }, + { + "name": "Iran - Tehran | ParsOnline", + "url": "rtmp://rtmp.s4.gametips.tv:1935/live" + }, + { + "name": "Iran - Tehran | AfraNet", + "url": "rtmp://rtmp.s5.gametips.tv:1935/live" + } + ] + }, + { + "id": "kakao", + "name": "KakaoTV", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.play.kakao.com/kakaotv" + } + ] + }, + { + "id": "piczel_tv", + "name": "Piczel.tv", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://piczel.tv:1935/live" + } + ] + }, + { + "id": "stage_ten", + "name": "STAGE TEN", + "available_protocols": ["RTMPS"], + "servers": [ + { + "protocol": "RTMPS", + "name": "STAGE TEN", + "url": "rtmps://app-rtmp.stageten.tv:443/stageten" + } + ] + }, + { + "id": "dlive", + "name": "DLive", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://stream.dlive.tv/live" + } + ] + }, + { + "id": "lightcast", + "name": "Lightcast.com", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "North America / East", + "url": "rtmp://us-east.live.lightcast.com/202E1F/default" + }, + { + "name": "North America / West", + "url": "rtmp://us-west.live.lightcast.com/202E1F/default" + }, + { + "name": "Europe / Amsterdam", + "url": "rtmp://europe.live.lightcast.com/202E1F/default" + }, + { + "name": "Europe / Frankfurt", + "url": "rtmp://europe-fra.live.lightcast.com/202E1F/default" + }, + { + "name": "Europe / Stockholm", + "url": "rtmp://europe-sto.live.lightcast.com/202E1F/default" + }, + { + "name": "Asia / Hong Kong", + "url": "rtmp://asia.live.lightcast.com/202E1F/default" + }, + { + "name": "Australia / Sydney", + "url": "rtmp://australia.live.lightcast.com/202E1F/default" + } + ] + }, + { + "id": "bongacams", + "name": "Bongacams", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Automatic / Default", + "url": "rtmp://auto.origin.gnsbc.com:1934/live" + }, + { + "name": "Automatic / Backup", + "url": "rtmp://origin.bcvidorigin.com:1934/live" + }, + { + "name": "Europe", + "url": "rtmp://z-eu.origin.gnsbc.com:1934/live" + }, + { + "name": "North America", + "url": "rtmp://z-us.origin.gnsbc.com:1934/live" + } + ] + }, + { + "id": "showit_tv", + "name": "show-it.tv", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://stream-1.show-it.tv:1935/live" + } + ] + }, + { + "id": "chathostness", + "name": "Chathostess", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Chathostess - Default", + "url": "rtmp://wowza01.foobarweb.com/cmschatsys_video" + }, + { + "name": "Chathostess - Backup", + "url": "rtmp://wowza05.foobarweb.com/cmschatsys_video" + } + ] + }, + { + "id": "camplace", + "name": "Camplace", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Camplace - Default", + "url": "rtmp://rtmp.camplace.com" + } + ] + }, + { + "id": "onlyfans", + "name": "OnlyFans.com", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "USA", + "url": "rtmp://route0.onlyfans.com/live" + }, + { + "name": "Europe", + "url": "rtmp://route0-dc2.onlyfans.com/live" + } + ] + }, + { + "id": "steam", + "name": "Steam", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://ingest-rtmp.broadcast.steamcontent.com/app" + } + ] + }, + { + "id": "stars_avn", + "name": "Stars.AVN.com", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://alpha.gateway.stars.avn.com/live" + } + ] + }, + { + "id": "konduit", + "name": "Konduit.live", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.konduit.live/live" + } + ] + }, + { + "id": "uncanny", + "name": "Uncanny.gg", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://stream.uncanny.gg/fortnite" + } + ] + }, + { + "id": "whalebone", + "name": "Whalebone.tv", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Automatic", + "url": "rtmp://live.whalebone.tv/live" + }, + { + "name": "Tokyo, Japan", + "url": "rtmp://ap-northeast.live.whalebone.tv/live" + }, + { + "name": "Frankfurt, Germany", + "url": "rtmp://eu-central.live.whalebone.tv/live" + }, + { + "name": "London, United Kingdom", + "url": "rtmp://eu-west.live.whalebone.tv/live" + }, + { + "name": "São Paulo, Brazil", + "url": "rtmp://sa-east.live.whalebone.tv/live" + }, + { + "name": "North Virgina, United States", + "url": "rtmp://us-east.live.whalebone.tv/live" + }, + { + "name": "Oregon, United States", + "url": "rtmp://us-west.live.whalebone.tv/live" + } + ] + }, + { + "id": "loco", + "name": "LOCO", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://ivory-ingest.getloconow.com:1935/stream" + } + ] + }, + { + "id": "niconico-premium", + "name": "niconico, premium member (ニコニコ生放送 プレミアム会員)", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://aliveorigin.dmc.nico/named_input" + } + ] + }, + { + "id": "niconico-free", + "name": "niconico, free member (ニコニコ生放送 一般会員)", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://aliveorigin.dmc.nico/named_input" + } + ] + }, + { + "id": "wasd_tv", + "name": "WASD.TV", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Automatic", + "url": "rtmp://push.rtmp.wasd.tv/live" + }, + { + "name": "Russia, Moscow", + "url": "rtmp://ru-moscow.rtmp.wasd.tv/live" + }, + { + "name": "Germany, Frankfurt", + "url": "rtmp://de-frankfurt.rtmp.wasd.tv/live" + }, + { + "name": "Finland, Helsinki", + "url": "rtmp://fi-helsinki.rtmp.wasd.tv/live" + } + ] + }, + { + "id": "virtwish", + "name": "VirtWish", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.virtwish.com/live" + } + ] + }, + { + "id": "xlovecam", + "name": "XLoveCam.com", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Europe(main)", + "url": "rtmp://nl.eu.stream.xlove.com/performer-origin" + }, + { + "name": "Europe(Romania)", + "url": "rtmp://ro.eu.stream.xlove.com/performer-origin" + }, + { + "name": "Europe(Russia)", + "url": "rtmp://ru.eu.stream.xlove.com/performer-origin" + }, + { + "name": "North America(US East)", + "url": "rtmp://usec.na.stream.xlove.com/performer-origin" + }, + { + "name": "North America(US West)", + "url": "rtmp://uswc.na.stream.xlove.com/performer-origin" + }, + { + "name": "North America(Canada)", + "url": "rtmp://ca.na.stream.xlove.com/performer-origin" + }, + { + "name": "South America", + "url": "rtmp://co.sa.stream.xlove.com/performer-origin" + }, + { + "name": "Asia", + "url": "rtmp://sg.as.stream.xlove.com/performer-origin" + } + ] + }, + { + "id": "angelthump", + "name": "AngelThump", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Auto", + "url": "rtmp://ingest.angelthump.com/live" + }, + { + "name": "New York 3", + "url": "rtmp://nyc-ingest.angelthump.com:1935/live" + }, + { + "name": "San Francisco 2", + "url": "rtmp://sfo-ingest.angelthump.com:1935/live" + }, + { + "name": "Singapore 1", + "url": "rtmp://sgp-ingest.angelthump.com:1935/live" + }, + { + "name": "London 1", + "url": "rtmp://lon-ingest.angelthump.com:1935/live" + }, + { + "name": "Frankfurt 1", + "url": "rtmp://fra-ingest.angelthump.com:1935/live" + }, + { + "name": "Toronto 1", + "url": "rtmp://tor-ingest.angelthump.com:1935/live" + }, + { + "name": "Bangalore 1", + "url": "rtmp://blr-ingest.angelthump.com:1935/live" + }, + { + "name": "Amsterdam 3", + "url": "rtmp://ams-ingest.angelthump.com:1935/live" + } + ] + }, + { + "id": "apachat", + "name": "Taryana - Apachat | تاریانا - آپاچت", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Global: Fastest (Recommended)", + "url": "rtmp://cdn.apachat.com:443/multistream" + } + ] + }, + { + "id": "api_video", + "name": "api.video", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://broadcast.api.video/s" + } + ] + }, + { + "id": "mux", + "name": "Mux", + "available_protocols": ["RTMPS", "RTMP"], + "servers": [ + { + "protocol": "RTMPS", + "name": "Global (RTMPS)", + "url": "rtmps://global-live.mux.com:443/app" + }, + { + "name": "Global (RTMP)", + "url": "rtmp://global-live.mux.com:5222/app" + } + ] + }, + { + "id": "viloud", + "name": "Viloud", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://live.viloud.tv:5222/app" + } + ] + }, + { + "id": "myfreecams", + "name": "MyFreeCams", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Automatic", + "url": "rtmp://publish.myfreecams.com/NxServer" + }, + { + "name": "Australia", + "url": "rtmp://publish-syd.myfreecams.com/NxServer" + }, + { + "name": "East Asia", + "url": "rtmp://publish-tyo.myfreecams.com/NxServer" + }, + { + "name": "Europe (East)", + "url": "rtmp://publish-buh.myfreecams.com/NxServer" + }, + { + "name": "Europe (West)", + "url": "rtmp://publish-ams.myfreecams.com/NxServer" + }, + { + "name": "North America (East Coast)", + "url": "rtmp://publish-ord.myfreecams.com/NxServer" + }, + { + "name": "North America (West Coast)", + "url": "rtmp://publish-tuk.myfreecams.com/NxServer" + }, + { + "name": "South America", + "url": "rtmp://publish-sao.myfreecams.com/NxServer" + } + ] + }, + { + "id": "polystreamer", + "name": "PolyStreamer.com", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Auto-select closest server", + "url": "rtmp://live.polystreamer.com/live" + }, + { + "name": "United States - West", + "url": "rtmp://us-west.live.polystreamer.com/live" + }, + { + "name": "United States - East", + "url": "rtmp://us-east.live.polystreamer.com/live" + }, + { + "name": "Australia", + "url": "rtmp://aus.live.polystreamer.com/live" + }, + { + "name": "India", + "url": "rtmp://ind.live.polystreamer.com/live" + }, + { + "name": "Germany", + "url": "rtmp://deu.live.polystreamer.com/live" + }, + { + "name": "Japan", + "url": "rtmp://jpn.live.polystreamer.com/live" + }, + { + "name": "Singapore", + "url": "rtmp://sgp.live.polystreamer.com/live" + } + ] + }, + { + "id": "glimesh", + "name": "Glimesh", + "available_protocols": ["FTL"], + "servers": [ + { + "protocol": "FTL", + "name": "North America - Chicago, United States", + "url": "ingest.kord.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "North America - New York, United States", + "url": "ingest.kjfk.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "North America - San Francisco, United States", + "url": "ingest.ksfo.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "North America - Toronto, Canada", + "url": "ingest.cyyz.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "Europe - Amsterdam, Netherlands", + "url": "ingest.eham.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "Europe - Frankfurt, Germany", + "url": "ingest.eddf.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "Europe - London, United Kingdom", + "url": "ingest.egll.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "Asia - Bangalore, India", + "url": "ingest.vobl.live.glimesh.tv" + }, + { + "protocol": "FTL", + "name": "Asia - Singapore", + "url": "ingest.wsss.live.glimesh.tv" + } + ] + }, + { + "id": "openrec", + "name": "OPENREC.tv - Premium member (プレミアム会員)", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "Default", + "url": "rtmp://a.station.openrec.tv:1935/live1" + } + ] + }, + { + "id": "nanistream", + "name": "nanoStream Cloud / bintu", + "available_protocols": ["RTMPS", "RTMP"], + "servers": [ + { + "name": "bintu-stream global ingest (rtmp)", + "url": "rtmp://bintu-stream.nanocosmos.de/live" + }, + { + "protocol": "RTMPS", + "name": "bintu-stream global ingest (rtmps)", + "url": "rtmps://bintu-stream.nanocosmos.de:1937/live" + }, + { + "name": "bintu-vtrans global ingest with transcoding/ABR (rtmp)", + "url": "rtmp://bintu-stream.nanocosmos.de/live" + }, + { + "protocol": "RTMPS", + "name": "bintu-vtrans global ingest with transcoding/ABR (rtmps)", + "url": "rtmps://bintu-stream.nanocosmos.de:1937/live" + }, + { + "name": "bintu-stream Europe (EU)", + "url": "rtmp://bintu-stream-eu.nanocosmos.de/live" + }, + { + "name": "bintu-stream USA West (USW)", + "url": "rtmp://bintu-stream-usw.nanocosmos.de/live" + }, + { + "name": "bintu-stream US East (USE)", + "url": "rtmp://bintu-stream-use.nanocosmos.de/live" + }, + { + "name": "bintu-stream Asia South (ASS)", + "url": "rtmp://bintu-stream-ass.nanocosmos.de/live" + }, + { + "name": "bintu-stream Australia (AU)", + "url": "rtmp://bintu-stream-au.nanocosmos.de/live" + }, + { + "name": "bintu-vtrans Europe (EU)", + "url": "rtmp://bintu-vtrans-eu.nanocosmos.de/live" + }, + { + "name": "bintu-vtrans USA West (USW)", + "url": "rtmp://bintu-vtrans-usw.nanocosmos.de/live" + }, + { + "name": "bintu-vtrans US East (USE)", + "url": "rtmp://bintu-vtrans-use.nanocosmos.de/live" + }, + { + "name": "bintu-vtrans Asia South (ASS)", + "url": "rtmp://bintu-vtrans-ass.nanocosmos.de/live" + }, + { + "name": "bintu-vtrans Australia (AU)", + "url": "rtmp://bintu-vtrans-au.nanocosmos.de/live" + } + ] + }, + { + "id": "brime_live", + "name": "Brime Live", + "available_protocols": ["RTMP"], + "servers": [ + { + "name": "North America - Ashburn, VA", + "url": "rtmp://ingest-us-ashburn.brimelive.com/live" + }, + { + "name": "North America - San Jose, CA", + "url": "rtmp://ingest-us-sanjose.brimelive.com/live" + }, + { + "name": "North America - Atlanta, GA", + "url": "rtmp://ingest-us-atlanta.brimelive.com/live" + }, + { + "name": "North America - Dallas, TX", + "url": "rtmp://ingest-us-dallas.brimelive.com/live" + }, + { + "name": "North America - Chicago, IL", + "url": "rtmp://ingest-us-chicago.brimelive.com/live" + }, + { + "name": "Canada Southeast - Montreal", + "url": "rtmp://ingest-ca-montreal.brimelive.com/live" + }, + { + "name": "Latin America - Brazil East (Sao Paulo)", + "url": "rtmp://ingest-la-saopaulo.brimelive.com/live" + }, + { + "name": "Europe / EMEA - Germany (Frankfurt)", + "url": "rtmp://ingest-eu-frankfurt.brimelive.com/live" + }, + { + "name": "Europe / EMEA - UK South (London)", + "url": "rtmp://ingest-eu-london.brimelive.com/live" + }, + { + "name": "Europe / EMEA - Russia (Moscow)", + "url": "rtmp://ingest-eu-moscow.brimelive.com/live" + }, + { + "name": "APAC - Japan East (Tokyo)", + "url": "rtmp://ingest-apac-tokyo.brimelive.com/live" + }, + { + "name": "APAC - Australia East (Sydney)", + "url": "rtmp://ingest-apac-sydney.brimelive.com/live" + } + ] + } + ] +} diff --git a/plugins/obs-services/json-format-ver.hpp b/plugins/obs-services/json-format-ver.hpp new file mode 100644 index 00000000000000..ba6f1f9610dd2e --- /dev/null +++ b/plugins/obs-services/json-format-ver.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define SERVICES_FORMAT_VERSION 4 diff --git a/plugins/obs-services/plugin-main.cpp b/plugins/obs-services/plugin-main.cpp new file mode 100644 index 00000000000000..6910e0ef073f10 --- /dev/null +++ b/plugins/obs-services/plugin-main.cpp @@ -0,0 +1,21 @@ +#include + +#include "service-manager.hpp" + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("obs-services", "en-US") +MODULE_EXPORT const char *obs_module_description(void) +{ + return "OBS Core Stream Services"; +} + +bool obs_module_load() +{ + service_manager::initialize(); + return true; +} + +void obs_module_unload() +{ + service_manager::finalize(); +} diff --git a/plugins/obs-services/plugin.hpp b/plugins/obs-services/plugin.hpp new file mode 100644 index 00000000000000..54946ca03dab0b --- /dev/null +++ b/plugins/obs-services/plugin.hpp @@ -0,0 +1,7 @@ +#pragma once + +extern "C" { +#include +} + +#define blog(level, msg, ...) blog(level, "[obs-services] " msg, ##__VA_ARGS__) diff --git a/plugins/obs-services/service-factory.cpp b/plugins/obs-services/service-factory.cpp new file mode 100644 index 00000000000000..69ad360429f6c6 --- /dev/null +++ b/plugins/obs-services/service-factory.cpp @@ -0,0 +1,170 @@ +#include "service-factory.hpp" + +#include "plugin.hpp" +#include "service-instance.hpp" + +extern "C" { +#include +} + +static inline int get_int_val(json_t *service, const char *key) +{ + json_t *integer_val = json_object_get(service, key); + if (!integer_val || !json_is_integer(integer_val)) + return -1; + + return (int)json_integer_value(integer_val); +} + +static inline const char *get_string_val(json_t *service, const char *key) +{ + json_t *str_val = json_object_get(service, key); + if (!str_val || !json_is_string(str_val)) + return NULL; + + return json_string_value(str_val); +} + +const char *service_factory::_get_name(void *type_data) noexcept +try { + if (type_data) + return reinterpret_cast(type_data) + ->get_name(); + return nullptr; +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); + return nullptr; +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); + return nullptr; +} + +void *service_factory::_create(obs_data_t *settings, + obs_service_t *service) noexcept +try { + service_factory *fac = reinterpret_cast( + obs_service_get_type_data(service)); + return fac->create(settings, service); +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); + return nullptr; +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); + return nullptr; +} + +void service_factory::_destroy(void *data) noexcept +try { + if (data) + delete reinterpret_cast(data); +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); +} + +void service_factory::_update(void *data, obs_data_t *settings) noexcept +try { + service_instance *priv = reinterpret_cast(data); + if (priv) { + priv->update(settings); + } + +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); +} + +/* ----------------------------------------------------------------- */ + +service_factory::service_factory(json_t *service) +{ + /** JSON data extraction **/ + + _id = get_string_val(service, "id"); + _name = get_string_val(service, "name"); + + json_t *object; + size_t idx; + json_t *element; + + /* Available protocols extraction */ + + object = json_object_get(service, "available_protocols"); + //If not provided set only RTMP by default + if (!object) + protocols.push_back("RTMP"); + else { + json_incref(object); + + json_array_foreach (object, idx, element) { + const char *prtcl = json_string_value(element); + if (prtcl != NULL) + protocols.push_back(prtcl); + } + + json_decref(object); + } + + /* Servers extraction */ + + object = json_object_get(service, "servers"); + if (object) { + json_incref(object); + + json_array_foreach (object, idx, element) { + const char *prtcl = get_string_val(element, "protocol"); + const char *url = get_string_val(element, "url"); + const char *name = get_string_val(element, "name"); + + if ((url != NULL) && (name != NULL)) { + //If not provided set RTMP by default + if (prtcl != NULL) + servers.push_back({strdup(prtcl), + strdup(url), + strdup(name)}); + else + servers.push_back({"RTMP", strdup(url), + strdup(name)}); + } + } + + json_decref(object); + } + + /** Service implementation **/ + + _info.type_data = this; + _info.id = _id.c_str(); + + _info.get_name = _get_name; + + _info.create = _create; + _info.destroy = _destroy; + + _info.update = _update; + + obs_register_service(&_info); +} + +service_factory::~service_factory() +{ + protocols.clear(); + servers.clear(); +} + +const char *service_factory::get_name() +{ + return _name.c_str(); +} + +void *service_factory::create(obs_data_t *settings, obs_service_t *service) +{ + return reinterpret_cast( + new service_instance(settings, service)); +} diff --git a/plugins/obs-services/service-factory.hpp b/plugins/obs-services/service-factory.hpp new file mode 100644 index 00000000000000..d2111eb79519f7 --- /dev/null +++ b/plugins/obs-services/service-factory.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +extern "C" { +#include +#include +} + +struct server_t { + const char *protocol; + const char *url; + const char *name; +}; + +class service_factory { + obs_service_info _info = {}; + + std::string _id; + std::string _name; + std::vector protocols; + std::vector servers; + + static const char *_get_name(void *type_data) noexcept; + + static void *_create(obs_data_t *settings, + obs_service_t *service) noexcept; + static void _destroy(void *data) noexcept; + + static void _update(void *data, obs_data_t *settings) noexcept; + +public: + service_factory(json_t *service); + ~service_factory(); + + const char *get_name(); + + virtual void *create(obs_data_t *settings, obs_service_t *service); +}; diff --git a/plugins/obs-services/service-instance.cpp b/plugins/obs-services/service-instance.cpp new file mode 100644 index 00000000000000..945287369ff5e5 --- /dev/null +++ b/plugins/obs-services/service-instance.cpp @@ -0,0 +1,15 @@ +#include "service-instance.hpp" + +#include + +service_instance::service_instance(obs_data_t *settings, obs_service_t *self) + : _factory(reinterpret_cast( + obs_service_get_type_data(self))) +{ + update(settings); +} + +void service_instance::update(obs_data_t *settings) +{ + UNUSED_PARAMETER(settings); +} diff --git a/plugins/obs-services/service-instance.hpp b/plugins/obs-services/service-instance.hpp new file mode 100644 index 00000000000000..9e70b0dc0bf0e9 --- /dev/null +++ b/plugins/obs-services/service-instance.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "service-factory.hpp" + +extern "C" { +#include +#include +} + +class service_instance { + service_factory *_factory; + +public: + service_instance(obs_data_t *settings, obs_service_t *self); + virtual ~service_instance(){}; + + void update(obs_data_t *settings); +}; diff --git a/plugins/obs-services/service-manager.cpp b/plugins/obs-services/service-manager.cpp new file mode 100644 index 00000000000000..923dfdbaf3d8d4 --- /dev/null +++ b/plugins/obs-services/service-manager.cpp @@ -0,0 +1,140 @@ +#include "service-manager.hpp" + +#include "plugin.hpp" + +extern "C" { +#include +#include +#include +#include +} + +#include "json-format-ver.hpp" + +static inline int get_int_val(json_t *service, const char *key) +{ + json_t *integer_val = json_object_get(service, key); + if (!integer_val || !json_is_integer(integer_val)) + return 0; + + return (int)json_integer_value(integer_val); +} + +static inline const char *get_string_val(json_t *service, const char *key) +{ + json_t *str_val = json_object_get(service, key); + if (!str_val || !json_is_string(str_val)) + return NULL; + + return json_string_value(str_val); +} + +static json_t *open_services_list(const char *file) +{ + char *file_data = os_quick_read_utf8_file(file); + json_error_t error; + json_t *root; + json_t *list; + int format_ver; + + if (!file_data) + return NULL; + + root = json_loads(file_data, JSON_REJECT_DUPLICATES, &error); + bfree(file_data); + + if (!root) { + blog(LOG_WARNING, "%s: Error reading JSON file (%d): %s", + __func__, error.line, error.text); + return NULL; + } + + format_ver = get_int_val(root, "format_version"); + + if (format_ver != SERVICES_FORMAT_VERSION) { + blog(LOG_DEBUG, "%s: Wrong format version (%d), expected %d", + __func__, format_ver, SERVICES_FORMAT_VERSION); + json_decref(root); + return NULL; + } + + list = json_object_get(root, "services"); + if (list) + json_incref(list); + json_decref(root); + + if (!list) { + blog(LOG_WARNING, "%s: No services list", __func__); + return NULL; + } + + return list; +} + +static json_t *get_services_list(void) +{ + char *file; + json_t *list = NULL; + + file = obs_module_config_path("services.json"); + if (file) { + list = open_services_list(file); + bfree(file); + } + + if (!list) { + file = obs_module_file("services.json"); + if (file) { + list = open_services_list(file); + bfree(file); + } + } + + return list; +} + +service_manager::~service_manager() +{ + _factories.clear(); +} + +void service_manager::register_services() +{ + json_t *services_list = get_services_list(); + json_t *service; + size_t idx; + + json_array_foreach (services_list, idx, service) { + const char *id = get_string_val(service, "id"); + + if (id != NULL) { + blog(LOG_DEBUG, "%s: Loading service with id \"%s\"", + __func__, id); + _factories.emplace( + id, std::make_shared(service)); + blog(LOG_DEBUG, "%s: Service with id \"%s\" was loaded", + __func__, id); + } else + blog(LOG_ERROR, + "%s: Unable to load as service JSON object n°%zu", + __func__, idx); + } + + json_decref(service); + json_decref(services_list); +} + +std::shared_ptr _service_manager_instance = nullptr; + +void service_manager::initialize() +{ + if (!_service_manager_instance) { + _service_manager_instance = std::make_shared(); + _service_manager_instance->register_services(); + } +} + +void service_manager::finalize() +{ + _service_manager_instance.reset(); +} diff --git a/plugins/obs-services/service-manager.hpp b/plugins/obs-services/service-manager.hpp new file mode 100644 index 00000000000000..c262e927f8784a --- /dev/null +++ b/plugins/obs-services/service-manager.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +#include "service-factory.hpp" + +class service_manager { + std::map> _factories; + +public: + service_manager(){}; + ~service_manager(); + + void register_services(); + + static void initialize(); + + static void finalize(); +}; From 1758e17e8f44a8c292e16990669ca8ca74bd6574 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 12:50:02 +0200 Subject: [PATCH 07/23] obs-services: Add protocols list, server lists --- plugins/obs-services/service-factory.cpp | 168 ++++++++++++++++++++++ plugins/obs-services/service-factory.hpp | 15 ++ plugins/obs-services/service-instance.cpp | 22 ++- plugins/obs-services/service-instance.hpp | 6 + 4 files changed, 210 insertions(+), 1 deletion(-) diff --git a/plugins/obs-services/service-factory.cpp b/plugins/obs-services/service-factory.cpp index 69ad360429f6c6..56f0e990112db4 100644 --- a/plugins/obs-services/service-factory.cpp +++ b/plugins/obs-services/service-factory.cpp @@ -7,6 +7,52 @@ extern "C" { #include } +// XXX: Add the server list for each protocols +void service_factory::create_server_lists(obs_properties_t *props) +{ + obs_property_t *rtmp, *rtmps, *hls, *ftl; + rtmp = obs_properties_add_list(props, "server_rtmp", + obs_module_text("Server"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + rtmps = obs_properties_add_list(props, "server_rtmps", + obs_module_text("Server"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + hls = obs_properties_add_list(props, "server_hls", + obs_module_text("Server"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + ftl = obs_properties_add_list(props, "server_ftl", + obs_module_text("Server"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + + for (size_t idx = 0; idx < servers.size(); idx++) { + if (strcmp(servers[idx].protocol, "RTMP") == 0) + obs_property_list_add_string( + rtmp, obs_module_text(servers[idx].name), + servers[idx].url); + + if (strcmp(servers[idx].protocol, "RTMPS") == 0) + obs_property_list_add_string( + rtmps, obs_module_text(servers[idx].name), + servers[idx].url); + + if (strcmp(servers[idx].protocol, "HLS") == 0) + obs_property_list_add_string( + hls, obs_module_text(servers[idx].name), + servers[idx].url); + + if (strcmp(servers[idx].protocol, "FTL") == 0) + obs_property_list_add_string( + ftl, obs_module_text(servers[idx].name), + servers[idx].url); + } +} + +/* ----------------------------------------------------------------- */ + static inline int get_int_val(json_t *service, const char *key) { json_t *integer_val = json_object_get(service, key); @@ -80,6 +126,65 @@ try { blog(LOG_ERROR, "Unexpected exception in function %s", __func__); } +void service_factory::_get_defaults2(obs_data_t *settings, + void *type_data) noexcept +try { + if (type_data) + reinterpret_cast(type_data)->get_defaults2( + settings); +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); +} + +obs_properties_t *service_factory::_get_properties2(void *data, + void *type_data) noexcept +try { + if (type_data) + return reinterpret_cast(type_data) + ->get_properties2(data); + return nullptr; +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); + return nullptr; +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); + return nullptr; +} + +const char *service_factory::_get_protocol(void *data) noexcept +try { + service_instance *priv = reinterpret_cast(data); + if (priv) + return priv->get_protocol(); + return nullptr; +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); + return nullptr; +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); + return nullptr; +} + +const char *service_factory::_get_url(void *data) noexcept +try { + service_instance *priv = reinterpret_cast(data); + if (priv) + return priv->get_url(); + return nullptr; +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); + return nullptr; +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); + return nullptr; +} + /* ----------------------------------------------------------------- */ service_factory::service_factory(json_t *service) @@ -149,6 +254,13 @@ service_factory::service_factory(json_t *service) _info.update = _update; + _info.get_defaults2 = _get_defaults2; + + _info.get_properties2 = _get_properties2; + + _info.get_protocol = _get_protocol; + _info.get_url = _get_url; + obs_register_service(&_info); } @@ -168,3 +280,59 @@ void *service_factory::create(obs_data_t *settings, obs_service_t *service) return reinterpret_cast( new service_instance(settings, service)); } + +void service_factory::get_defaults2(obs_data_t *settings) +{ + obs_data_set_default_string(settings, "protocol", protocols[0].c_str()); +} + +// XXX: Set the visibility of server lists for each protocols +static bool modified_protocol(obs_properties_t *props, obs_property_t *, + obs_data_t *settings) noexcept +try { + const char *protocol = obs_data_get_string(settings, "protocol"); + bool rtmp = strcmp(protocol, "RTMP") == 0; + bool rtmps = strcmp(protocol, "RTMPS") == 0; + bool hls = strcmp(protocol, "HLS") == 0; + bool ftl = strcmp(protocol, "FTL") == 0; + + //Server lists + obs_property_set_visible(obs_properties_get(props, "server_rtmp"), + rtmp); + obs_property_set_visible(obs_properties_get(props, "server_rtmps"), + rtmps); + obs_property_set_visible(obs_properties_get(props, "server_hls"), hls); + obs_property_set_visible(obs_properties_get(props, "server_ftl"), ftl); + + return true; +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); + return false; +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); + return false; +} + +obs_properties_t *service_factory::get_properties2(void *data) +{ + UNUSED_PARAMETER(data); + + obs_properties_t *props = obs_properties_create(); + obs_property_t *p; + + p = obs_properties_add_list(props, "protocol", + obs_module_text("Protocol"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + + obs_property_set_modified_callback(p, modified_protocol); + + for (size_t idx = 0; idx < protocols.size(); idx++) + obs_property_list_add_string(p, protocols[idx].c_str(), + protocols[idx].c_str()); + + create_server_lists(props); + + return props; +} diff --git a/plugins/obs-services/service-factory.hpp b/plugins/obs-services/service-factory.hpp index d2111eb79519f7..26394c3f183b80 100644 --- a/plugins/obs-services/service-factory.hpp +++ b/plugins/obs-services/service-factory.hpp @@ -23,6 +23,8 @@ class service_factory { std::vector protocols; std::vector servers; + void create_server_lists(obs_properties_t *props); + static const char *_get_name(void *type_data) noexcept; static void *_create(obs_data_t *settings, @@ -31,6 +33,15 @@ class service_factory { static void _update(void *data, obs_data_t *settings) noexcept; + static void _get_defaults2(obs_data_t *settings, + void *type_data) noexcept; + + static obs_properties_t *_get_properties2(void *data, + void *type_data) noexcept; + + static const char *_get_protocol(void *data) noexcept; + static const char *_get_url(void *data) noexcept; + public: service_factory(json_t *service); ~service_factory(); @@ -38,4 +49,8 @@ class service_factory { const char *get_name(); virtual void *create(obs_data_t *settings, obs_service_t *service); + + virtual void get_defaults2(obs_data_t *settings); + + virtual obs_properties_t *get_properties2(void *data); }; diff --git a/plugins/obs-services/service-instance.cpp b/plugins/obs-services/service-instance.cpp index 945287369ff5e5..9b538836d15476 100644 --- a/plugins/obs-services/service-instance.cpp +++ b/plugins/obs-services/service-instance.cpp @@ -9,7 +9,27 @@ service_instance::service_instance(obs_data_t *settings, obs_service_t *self) update(settings); } +// XXX: Add server updater for each protocols void service_instance::update(obs_data_t *settings) { - UNUSED_PARAMETER(settings); + protocol = obs_data_get_string(settings, "protocol"); + + if (protocol.compare("RTMP") == 0) + server = obs_data_get_string(settings, "server_rtmp"); + if (protocol.compare("RTMPS") == 0) + server = obs_data_get_string(settings, "server_rtmps"); + if (protocol.compare("HLS") == 0) + server = obs_data_get_string(settings, "server_hls"); + if (protocol.compare("FTL") == 0) + server = obs_data_get_string(settings, "server_ftl"); +} + +const char *service_instance::get_protocol() +{ + return protocol.c_str(); +} + +const char *service_instance::get_url() +{ + return server.c_str(); } diff --git a/plugins/obs-services/service-instance.hpp b/plugins/obs-services/service-instance.hpp index 9e70b0dc0bf0e9..414739f03c6c83 100644 --- a/plugins/obs-services/service-instance.hpp +++ b/plugins/obs-services/service-instance.hpp @@ -10,9 +10,15 @@ extern "C" { class service_instance { service_factory *_factory; + std::string protocol; + std::string server; + public: service_instance(obs_data_t *settings, obs_service_t *self); virtual ~service_instance(){}; void update(obs_data_t *settings); + + const char *get_protocol(); + const char *get_url(); }; From aa1a4f592f215e94db469086faaa4e581fecf823 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 12:52:13 +0200 Subject: [PATCH 08/23] obs-services: Add stream key --- plugins/obs-services/service-factory.cpp | 19 +++++++++++++++++++ plugins/obs-services/service-factory.hpp | 1 + plugins/obs-services/service-instance.cpp | 7 +++++++ plugins/obs-services/service-instance.hpp | 2 ++ 4 files changed, 29 insertions(+) diff --git a/plugins/obs-services/service-factory.cpp b/plugins/obs-services/service-factory.cpp index 56f0e990112db4..4f30267b7b51a0 100644 --- a/plugins/obs-services/service-factory.cpp +++ b/plugins/obs-services/service-factory.cpp @@ -185,6 +185,21 @@ try { return nullptr; } +const char *service_factory::_get_key(void *data) noexcept +try { + service_instance *priv = reinterpret_cast(data); + if (priv) + return priv->get_key(); + return nullptr; +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); + return nullptr; +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); + return nullptr; +} + /* ----------------------------------------------------------------- */ service_factory::service_factory(json_t *service) @@ -260,6 +275,7 @@ service_factory::service_factory(json_t *service) _info.get_protocol = _get_protocol; _info.get_url = _get_url; + _info.get_key = _get_key; obs_register_service(&_info); } @@ -334,5 +350,8 @@ obs_properties_t *service_factory::get_properties2(void *data) create_server_lists(props); + obs_properties_add_text(props, "key", obs_module_text("StreamKey"), + OBS_TEXT_PASSWORD); + return props; } diff --git a/plugins/obs-services/service-factory.hpp b/plugins/obs-services/service-factory.hpp index 26394c3f183b80..5072bc75def4d9 100644 --- a/plugins/obs-services/service-factory.hpp +++ b/plugins/obs-services/service-factory.hpp @@ -41,6 +41,7 @@ class service_factory { static const char *_get_protocol(void *data) noexcept; static const char *_get_url(void *data) noexcept; + static const char *_get_key(void *data) noexcept; public: service_factory(json_t *service); diff --git a/plugins/obs-services/service-instance.cpp b/plugins/obs-services/service-instance.cpp index 9b538836d15476..7145482f4080e1 100644 --- a/plugins/obs-services/service-instance.cpp +++ b/plugins/obs-services/service-instance.cpp @@ -22,6 +22,8 @@ void service_instance::update(obs_data_t *settings) server = obs_data_get_string(settings, "server_hls"); if (protocol.compare("FTL") == 0) server = obs_data_get_string(settings, "server_ftl"); + + key = obs_data_get_string(settings, "key"); } const char *service_instance::get_protocol() @@ -33,3 +35,8 @@ const char *service_instance::get_url() { return server.c_str(); } + +const char *service_instance::get_key() +{ + return key.c_str(); +} diff --git a/plugins/obs-services/service-instance.hpp b/plugins/obs-services/service-instance.hpp index 414739f03c6c83..9d90d56ad32f1e 100644 --- a/plugins/obs-services/service-instance.hpp +++ b/plugins/obs-services/service-instance.hpp @@ -12,6 +12,7 @@ class service_instance { std::string protocol; std::string server; + std::string key; public: service_instance(obs_data_t *settings, obs_service_t *self); @@ -21,4 +22,5 @@ class service_instance { const char *get_protocol(); const char *get_url(); + const char *get_key(); }; From f24940575b120e2414e9aec6fb0b54ae6259158a Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 12:54:14 +0200 Subject: [PATCH 09/23] obs-services: Add more info and stream key buttons --- plugins/obs-services/data/locale/en-US.ini | 2 ++ plugins/obs-services/data/services.json | 11 ++++++++++ plugins/obs-services/service-factory.cpp | 24 ++++++++++++++++++++++ plugins/obs-services/service-factory.hpp | 2 ++ 4 files changed, 39 insertions(+) diff --git a/plugins/obs-services/data/locale/en-US.ini b/plugins/obs-services/data/locale/en-US.ini index b9c85d77cd422d..5f5fda7b5e1a3a 100644 --- a/plugins/obs-services/data/locale/en-US.ini +++ b/plugins/obs-services/data/locale/en-US.ini @@ -1,3 +1,5 @@ +MoreInfo="More Info" Protocol="Protocol" Server="Server" StreamKey="Stream key" +StreamKeyLink="Get Stream Key" diff --git a/plugins/obs-services/data/services.json b/plugins/obs-services/data/services.json index 6ac6793a49256d..f9bae7dcaf19f5 100644 --- a/plugins/obs-services/data/services.json +++ b/plugins/obs-services/data/services.json @@ -4,6 +4,8 @@ { "id": "youtube", "name": "YouTube", + "more_info_link": "https://developers.google.com/youtube/v3/live/guides/ingestion-protocol-comparison", + "stream_key_link": "https://www.youtube.com/live_dashboard", "available_protocols": ["HLS", "RTMPS", "RTMP"], "servers": [ { @@ -68,6 +70,7 @@ { "id": "luzento", "name": "Luzento.com", + "stream_key_link": "https://cms.luzento.com/dashboard/stream-key?from=OBS", "available_protocols": ["RTMP"], "servers": [ { @@ -131,6 +134,7 @@ { "id": "youstreamer", "name": "YouStreamer", + "stream_key_link": "https://app.youstreamer.com/stream/", "available_protocols": ["RTMP"], "servers": [ { @@ -220,6 +224,7 @@ { "id": "facebook", "name": "Facebook Live", + "stream_key_link": "https://www.facebook.com/live/producer?ref=OBS", "available_protocols": ["RTMPS"], "servers": [ { @@ -232,6 +237,7 @@ { "id": "restream_io", "name": "Restream.io", + "stream_key_link": "https://restream.io/settings/streaming-setup?from=OBS", "available_protocols": ["RTMP"], "servers": [ { @@ -737,6 +743,7 @@ { "id": "twitter", "name": "Twitter", + "stream_key_link": "https://studio.twitter.com/producer/sources", "available_protocols": ["RTMP"], "servers": [ { @@ -914,6 +921,7 @@ { "id": "trovo", "name": "Trovo", + "stream_key_link": "https://studio.trovo.live/mychannel/stream", "available_protocols": ["RTMP"], "servers": [ { @@ -1502,6 +1510,7 @@ { "id": "glimesh", "name": "Glimesh", + "stream_key_link": "https://glimesh.tv/users/settings/stream", "available_protocols": ["FTL"], "servers": [ { @@ -1554,6 +1563,7 @@ { "id": "openrec", "name": "OPENREC.tv - Premium member (プレミアム会員)", + "stream_key_link": "https://www.openrec.tv/login?keep_login=true&url=https://www.openrec.tv/dashboard/live?from=obs", "available_protocols": ["RTMP"], "servers": [ { @@ -1630,6 +1640,7 @@ { "id": "brime_live", "name": "Brime Live", + "stream_key_link": "https://brimelive.com/obs-stream-key-link", "available_protocols": ["RTMP"], "servers": [ { diff --git a/plugins/obs-services/service-factory.cpp b/plugins/obs-services/service-factory.cpp index 4f30267b7b51a0..b206829f1f87dd 100644 --- a/plugins/obs-services/service-factory.cpp +++ b/plugins/obs-services/service-factory.cpp @@ -209,6 +209,14 @@ service_factory::service_factory(json_t *service) _id = get_string_val(service, "id"); _name = get_string_val(service, "name"); + const char *info_link = get_string_val(service, "more_info_link"); + if (info_link != NULL) + more_info_link = info_link; + + const char *key_link = get_string_val(service, "stream_key_link"); + if (key_link != NULL) + stream_key_link = key_link; + json_t *object; size_t idx; json_t *element; @@ -299,6 +307,14 @@ void *service_factory::create(obs_data_t *settings, obs_service_t *service) void service_factory::get_defaults2(obs_data_t *settings) { + if (!more_info_link.empty()) + obs_data_set_default_string(settings, "info_link", + more_info_link.c_str()); + + if (!stream_key_link.empty()) + obs_data_set_default_string(settings, "key_link", + stream_key_link.c_str()); + obs_data_set_default_string(settings, "protocol", protocols[0].c_str()); } @@ -337,6 +353,10 @@ obs_properties_t *service_factory::get_properties2(void *data) obs_properties_t *props = obs_properties_create(); obs_property_t *p; + if (!more_info_link.empty()) + obs_properties_add_open_url(props, "info_link", + obs_module_text("MoreInfo")); + p = obs_properties_add_list(props, "protocol", obs_module_text("Protocol"), OBS_COMBO_TYPE_LIST, @@ -353,5 +373,9 @@ obs_properties_t *service_factory::get_properties2(void *data) obs_properties_add_text(props, "key", obs_module_text("StreamKey"), OBS_TEXT_PASSWORD); + if (!stream_key_link.empty()) + obs_properties_add_open_url(props, "key_link", + obs_module_text("StreamKeyLink")); + return props; } diff --git a/plugins/obs-services/service-factory.hpp b/plugins/obs-services/service-factory.hpp index 5072bc75def4d9..06ca691b250338 100644 --- a/plugins/obs-services/service-factory.hpp +++ b/plugins/obs-services/service-factory.hpp @@ -20,6 +20,8 @@ class service_factory { std::string _id; std::string _name; + std::string more_info_link; + std::string stream_key_link; std::vector protocols; std::vector servers; From 4b503d45ec12deb96e16cfb93cd77552649c1f1d Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 13:01:25 +0200 Subject: [PATCH 10/23] libobs, UI: Add "info bitrate" property --- UI/properties-view.cpp | 19 ++++++++++++++++++- UI/properties-view.hpp | 1 + libobs/obs-properties.c | 11 +++++++++++ libobs/obs-properties.h | 5 +++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index a7fd83c96d25a1..8fb297a969069a 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -1413,6 +1413,17 @@ QWidget *OBSPropertiesView::AddOpenUrl(obs_property_t *prop) return NewWidget(prop, button, SIGNAL(clicked())); } +QWidget *OBSPropertiesView::AddInfoBitrate(obs_property_t *prop) +{ + const char *name = obs_property_name(prop); + const char *desc = obs_property_description(prop); + int val = obs_data_get_int(settings, name); + + QLabel *label = new QLabel(QT_UTF8(desc) + " " + QString::number(val) + + " kbps"); + return NewWidget(prop, label, SIGNAL(linkActivated(QString))); +} + void OBSPropertiesView::AddProperty(obs_property_t *property, QFormLayout *layout) { @@ -1470,6 +1481,9 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, break; case OBS_PROPERTY_OPEN_URL: widget = AddOpenUrl(property); + break; + case OBS_PROPERTY_INFO_BITRATE: + widget = AddInfoBitrate(property); } if (widget && !obs_property_enabled(property)) @@ -1477,7 +1491,8 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, if (!label && type != OBS_PROPERTY_BOOL && type != OBS_PROPERTY_BUTTON && type != OBS_PROPERTY_GROUP && - type != OBS_PROPERTY_OPEN_URL) + type != OBS_PROPERTY_OPEN_URL && + type != OBS_PROPERTY_INFO_BITRATE) label = new QLabel(QT_UTF8(obs_property_description(property))); if (warning && label) //TODO: select color based on background color @@ -1977,6 +1992,8 @@ void WidgetInfo::ControlChanged() break; case OBS_PROPERTY_OPEN_URL: return; + case OBS_PROPERTY_INFO_BITRATE: + return; } if (!recently_updated) { diff --git a/UI/properties-view.hpp b/UI/properties-view.hpp index 95a5e0cbac2165..2ede67a7d1f8f3 100644 --- a/UI/properties-view.hpp +++ b/UI/properties-view.hpp @@ -135,6 +135,7 @@ class OBSPropertiesView : public VScrollArea { void AddGroup(obs_property_t *prop, QFormLayout *layout); QWidget *AddOpenUrl(obs_property_t *prop); + QWidget *AddInfoBitrate(obs_property_t *prop); void AddProperty(obs_property_t *property, QFormLayout *layout); diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index b737498abf6356..35d9cf4357fe56 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -442,6 +442,8 @@ static inline size_t get_property_size(enum obs_property_type type) return 0; case OBS_PROPERTY_OPEN_URL: return 0; + case OBS_PROPERTY_INFO_BITRATE: + return 0; } return 0; @@ -819,6 +821,15 @@ obs_property_t *obs_properties_add_open_url(obs_properties_t *props, return p; } +obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, + const char *name, + const char *desc) +{ + if (!props || has_prop(props, name)) + return NULL; + return new_prop(props, name, desc, OBS_PROPERTY_INFO_BITRATE); +} + /* ------------------------------------------------------------------------- */ static inline bool is_combo(struct obs_property *p) diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index aadb8c03aca7c0..4fbd6335c750cd 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -58,6 +58,7 @@ enum obs_property_type { OBS_PROPERTY_GROUP, OBS_PROPERTY_COLOR_ALPHA, OBS_PROPERTY_OPEN_URL, + OBS_PROPERTY_INFO_BITRATE, }; enum obs_combo_format { @@ -272,6 +273,10 @@ EXPORT obs_property_t *obs_properties_add_open_url(obs_properties_t *props, const char *name, const char *desc); +EXPORT obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, + const char *name, + const char *desc); + /* ------------------------------------------------------------------------- */ /** From fd5906e4dca24957fa47382254da3aabc21977a1 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 13:02:15 +0200 Subject: [PATCH 11/23] libobs, UI: Add "info FPS" property --- UI/properties-view.cpp | 18 +++++++++++++++++- UI/properties-view.hpp | 1 + libobs/obs-properties.c | 10 ++++++++++ libobs/obs-properties.h | 5 +++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index 8fb297a969069a..0012de4249eb05 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -1424,6 +1424,17 @@ QWidget *OBSPropertiesView::AddInfoBitrate(obs_property_t *prop) return NewWidget(prop, label, SIGNAL(linkActivated(QString))); } +QWidget *OBSPropertiesView::AddInfoFPS(obs_property_t *prop) +{ + const char *name = obs_property_name(prop); + const char *desc = obs_property_description(prop); + int val = obs_data_get_int(settings, name); + + QLabel *label = + new QLabel(QT_UTF8(desc) + " " + QString::number(val) + " FPS"); + return NewWidget(prop, label, SIGNAL(linkActivated(QString))); +} + void OBSPropertiesView::AddProperty(obs_property_t *property, QFormLayout *layout) { @@ -1484,6 +1495,9 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, break; case OBS_PROPERTY_INFO_BITRATE: widget = AddInfoBitrate(property); + break; + case OBS_PROPERTY_INFO_FPS: + widget = AddInfoFPS(property); } if (widget && !obs_property_enabled(property)) @@ -1492,7 +1506,7 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, if (!label && type != OBS_PROPERTY_BOOL && type != OBS_PROPERTY_BUTTON && type != OBS_PROPERTY_GROUP && type != OBS_PROPERTY_OPEN_URL && - type != OBS_PROPERTY_INFO_BITRATE) + type != OBS_PROPERTY_INFO_BITRATE && type != OBS_PROPERTY_INFO_FPS) label = new QLabel(QT_UTF8(obs_property_description(property))); if (warning && label) //TODO: select color based on background color @@ -1994,6 +2008,8 @@ void WidgetInfo::ControlChanged() return; case OBS_PROPERTY_INFO_BITRATE: return; + case OBS_PROPERTY_INFO_FPS: + return; } if (!recently_updated) { diff --git a/UI/properties-view.hpp b/UI/properties-view.hpp index 2ede67a7d1f8f3..827e23e4170b42 100644 --- a/UI/properties-view.hpp +++ b/UI/properties-view.hpp @@ -136,6 +136,7 @@ class OBSPropertiesView : public VScrollArea { QWidget *AddOpenUrl(obs_property_t *prop); QWidget *AddInfoBitrate(obs_property_t *prop); + QWidget *AddInfoFPS(obs_property_t *prop); void AddProperty(obs_property_t *property, QFormLayout *layout); diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index 35d9cf4357fe56..2dd2751bbd6164 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -444,6 +444,8 @@ static inline size_t get_property_size(enum obs_property_type type) return 0; case OBS_PROPERTY_INFO_BITRATE: return 0; + case OBS_PROPERTY_INFO_FPS: + return 0; } return 0; @@ -830,6 +832,14 @@ obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, return new_prop(props, name, desc, OBS_PROPERTY_INFO_BITRATE); } +obs_property_t *obs_properties_add_info_fps(obs_properties_t *props, + const char *name, const char *desc) +{ + if (!props || has_prop(props, name)) + return NULL; + return new_prop(props, name, desc, OBS_PROPERTY_INFO_FPS); +} + /* ------------------------------------------------------------------------- */ static inline bool is_combo(struct obs_property *p) diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index 4fbd6335c750cd..6e8e49ab432546 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -59,6 +59,7 @@ enum obs_property_type { OBS_PROPERTY_COLOR_ALPHA, OBS_PROPERTY_OPEN_URL, OBS_PROPERTY_INFO_BITRATE, + OBS_PROPERTY_INFO_FPS, }; enum obs_combo_format { @@ -277,6 +278,10 @@ EXPORT obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, const char *name, const char *desc); +EXPORT obs_property_t *obs_properties_add_info_fps(obs_properties_t *props, + const char *name, + const char *desc); + /* ------------------------------------------------------------------------- */ /** From 9785fd8fe0229225ace5447fe3f3e701e777d6cc Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 13:16:41 +0200 Subject: [PATCH 12/23] obs-services: Add maximum info and ignore checkbox --- plugins/obs-services/data/locale/en-US.ini | 4 + plugins/obs-services/data/services.json | 416 +++++++++++++++++++++ plugins/obs-services/service-factory.cpp | 203 +++++++++- plugins/obs-services/service-factory.hpp | 14 + plugins/obs-services/service-instance.cpp | 46 ++- plugins/obs-services/service-instance.hpp | 6 + 6 files changed, 684 insertions(+), 5 deletions(-) diff --git a/plugins/obs-services/data/locale/en-US.ini b/plugins/obs-services/data/locale/en-US.ini index 5f5fda7b5e1a3a..40a1b219422709 100644 --- a/plugins/obs-services/data/locale/en-US.ini +++ b/plugins/obs-services/data/locale/en-US.ini @@ -3,3 +3,7 @@ Protocol="Protocol" Server="Server" StreamKey="Stream key" StreamKeyLink="Get Stream Key" +MaxVideoBitrate="Maximum Video Bitrate:" +MaxAudioBitrate="Maximum Audio Bitrate:" +MaxFPS="Maximum FPS:" +IgnoreMaximum="Ignore streaming service maximum limits" diff --git a/plugins/obs-services/data/services.json b/plugins/obs-services/data/services.json index f9bae7dcaf19f5..d4c44edd29f87e 100644 --- a/plugins/obs-services/data/services.json +++ b/plugins/obs-services/data/services.json @@ -38,6 +38,23 @@ "name": "Backup YouTube ingest server (legacy RTMP)", "url": "rtmp://b.rtmp.youtube.com/live2?backup=1" } + ], + "maximum": [ + { + "protocol": "HLS", + "video_bitrate": 51000, + "audio_bitrate": 160 + }, + { + "protocol": "RTMPS", + "video_bitrate": 51000, + "audio_bitrate": 160 + }, + { + "protocol": "RTMP", + "video_bitrate": 51000, + "audio_bitrate": 160 + } ] }, { @@ -65,6 +82,13 @@ "name": "Middle East: Bahrain", "url": "rtmp://rtmp-me.loola.tv/push" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 2500, + "audio_bitrate": 160 + } ] }, { @@ -81,6 +105,13 @@ "name": "Primary (Test)", "url": "rtmp://ingest.luzento.com/test" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 256 + } ] }, { @@ -96,6 +127,13 @@ "name": "North America: Montreal", "url": "rtmp://us.vimm.tv/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 8000, + "audio_bitrate": 320 + } ] }, { @@ -107,6 +145,13 @@ "name": "Primary", "url": "rtmp://live.mobcrush.net/mob" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 160 + } ] }, { @@ -118,6 +163,13 @@ "name": "Primary", "url": "rtmp://live3.origins.web.tv/liveext" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3500, + "audio_bitrate": 160 + } ] }, { @@ -180,6 +232,13 @@ "name": "EU: London, UK", "url": "rtmp://live-lhr.vaughnsoft.net/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 15000, + "audio_bitrate": 320 + } ] }, { @@ -219,6 +278,13 @@ "name": "EU: London, UK", "url": "rtmp://live-lhr.vaughnsoft.net/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 15000, + "audio_bitrate": 320 + } ] }, { @@ -232,6 +298,14 @@ "name": "Default", "url": "rtmps://rtmp-api.facebook.com:443/rtmp/" } + ], + "maximum": [ + { + "protocol": "RTMPS", + "video_bitrate": 6000, + "audio_bitrate": 128, + "fps": 30 + } ] }, { @@ -415,6 +489,13 @@ "name": "Asia South: Mumbai, IND", "url": "rtmp://ap-south-1.stream.nood.tv/live_source" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 25000, + "audio_bitrate": 192 + } ] }, { @@ -528,6 +609,12 @@ "name": "Primary", "url": "rtmp://publish.meridix.com/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3500 + } ] }, { @@ -555,6 +642,13 @@ "name": "Asia : Singapore", "url": "rtmp://rtmp-sgp.afreecatv.com/app" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 8000, + "audio_bitrate": 192 + } ] }, { @@ -566,6 +660,13 @@ "name": "CAM4", "url": "rtmp://origin.cam4.com/cam4-origin-live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3000, + "audio_bitrate": 128 + } ] }, { @@ -577,6 +678,13 @@ "name": "ePlay Primary", "url": "rtmp://live.eplay.link/origin" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 7500, + "audio_bitrate": 192 + } ] }, { @@ -596,6 +704,12 @@ "name": "EU West (Düsseldorf, Germany)", "url": "rtmp://live.eu-west1.picarto.tv/golive" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3500 + } ] }, { @@ -629,6 +743,13 @@ "name": "Default", "url": "rtmp://global-live.uscreen.app:5222/app" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 8000, + "audio_bitrate": 192 + } ] }, { @@ -640,6 +761,13 @@ "name": "Auto", "url": "rtmp://s-sd.stripst.com/ext" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 128 + } ] }, { @@ -667,6 +795,14 @@ "name": "Oceania", "url": "rtmp://obs-ingest-oc.camsoda.com/cam_obs" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 160, + "fps": 30 + } ] }, { @@ -738,6 +874,13 @@ "name": "Australia: Sydney", "url": "rtmp://live-syd.stream.highwebmedia.com/live-origin" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 50000, + "audio_bitrate": 192 + } ] }, { @@ -794,6 +937,14 @@ "name": "Asia/Pacific: Singapore", "url": "rtmp://sg.pscp.tv:80/x" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 12000, + "audio_bitrate": 128, + "fps": 60 + } ] }, { @@ -857,6 +1008,13 @@ "name": "Asia South (Mumbai, IN)", "url": "rtmp://ingest-as-south.a.switchboard.zone/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 10000, + "audio_bitrate": 128 + } ] }, { @@ -868,6 +1026,13 @@ "name": "Primary Looch ingest server", "url": "rtmp://ingest.looch.tv/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 160 + } ] }, { @@ -879,6 +1044,13 @@ "name": "Default", "url": "rtmp://live.eventials.com/eventialsLiveOrigin" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 900, + "audio_bitrate": 192 + } ] }, { @@ -890,6 +1062,14 @@ "name": "Default", "url": "rtmp://go.eventlive.pro/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3000, + "audio_bitrate": 192, + "fps": 30 + } ] }, { @@ -905,6 +1085,13 @@ "name": "Iran", "url": "rtmp://rtmp-iran.lahzecdn.com/pro" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 4000, + "audio_bitrate": 192 + } ] }, { @@ -916,6 +1103,13 @@ "name": "Default", "url": "rtmp://stream.mylive.in.th/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 7000, + "audio_bitrate": 192 + } ] }, { @@ -928,6 +1122,13 @@ "name": "Default", "url": "rtmp://livepush.trovo.live/live/" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 9000, + "audio_bitrate": 160 + } ] }, { @@ -939,6 +1140,14 @@ "name": "Default", "url": "rtmp://rtmp.mixcloud.com/broadcast" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 320, + "fps": 30 + } ] }, { @@ -950,6 +1159,13 @@ "name": "Primary", "url": "rtmp://webcast.sermonaudio.com/sa" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 2000, + "audio_bitrate": 128 + } ] }, { @@ -972,6 +1188,13 @@ "name": "Default", "url": "rtmp://rtmp.cdn.asset.aparat.com:443/event" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 320 + } ] }, { @@ -1006,6 +1229,13 @@ "name": "Default", "url": "rtmp://rtmp.play.kakao.com/kakaotv" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 8000, + "audio_bitrate": 192 + } ] }, { @@ -1017,6 +1247,13 @@ "name": "Default", "url": "rtmp://piczel.tv:1935/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 2500, + "audio_bitrate": 256 + } ] }, { @@ -1029,6 +1266,13 @@ "name": "STAGE TEN", "url": "rtmps://app-rtmp.stageten.tv:443/stageten" } + ], + "maximum": [ + { + "protocol": "RTMPS", + "video_bitrate": 4000, + "audio_bitrate": 128 + } ] }, { @@ -1040,6 +1284,13 @@ "name": "Default", "url": "rtmp://stream.dlive.tv/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 160 + } ] }, { @@ -1075,6 +1326,13 @@ "name": "Australia / Sydney", "url": "rtmp://australia.live.lightcast.com/202E1F/default" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 160 + } ] }, { @@ -1098,6 +1356,13 @@ "name": "North America", "url": "rtmp://z-us.origin.gnsbc.com:1934/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 192 + } ] }, { @@ -1109,6 +1374,13 @@ "name": "Default", "url": "rtmp://stream-1.show-it.tv:1935/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 6000, + "audio_bitrate": 192 + } ] }, { @@ -1124,6 +1396,13 @@ "name": "Chathostess - Backup", "url": "rtmp://wowza05.foobarweb.com/cmschatsys_video" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3600, + "audio_bitrate": 128 + } ] }, { @@ -1135,6 +1414,13 @@ "name": "Camplace - Default", "url": "rtmp://rtmp.camplace.com" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3000, + "audio_bitrate": 128 + } ] }, { @@ -1150,6 +1436,13 @@ "name": "Europe", "url": "rtmp://route0-dc2.onlyfans.com/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 2500, + "audio_bitrate": 192 + } ] }, { @@ -1161,6 +1454,13 @@ "name": "Default", "url": "rtmp://ingest-rtmp.broadcast.steamcontent.com/app" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 7000, + "audio_bitrate": 128 + } ] }, { @@ -1172,6 +1472,13 @@ "name": "Default", "url": "rtmp://alpha.gateway.stars.avn.com/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 2500, + "audio_bitrate": 192 + } ] }, { @@ -1194,6 +1501,13 @@ "name": "Default", "url": "rtmp://stream.uncanny.gg/fortnite" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 10000, + "audio_bitrate": 192 + } ] }, { @@ -1251,6 +1565,13 @@ "name": "Default", "url": "rtmp://aliveorigin.dmc.nico/named_input" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 5808, + "audio_bitrate": 192 + } ] }, { @@ -1262,6 +1583,13 @@ "name": "Default", "url": "rtmp://aliveorigin.dmc.nico/named_input" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 904, + "audio_bitrate": 96 + } ] }, { @@ -1285,6 +1613,13 @@ "name": "Finland, Helsinki", "url": "rtmp://fi-helsinki.rtmp.wasd.tv/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 10000, + "audio_bitrate": 192 + } ] }, { @@ -1378,6 +1713,13 @@ "name": "Amsterdam 3", "url": "rtmp://ams-ingest.angelthump.com:1935/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 3500, + "audio_bitrate": 160 + } ] }, { @@ -1389,6 +1731,13 @@ "name": "Global: Fastest (Recommended)", "url": "rtmp://cdn.apachat.com:443/multistream" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 4000, + "audio_bitrate": 192 + } ] }, { @@ -1400,6 +1749,13 @@ "name": "Default", "url": "rtmp://broadcast.api.video/s" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 20000, + "audio_bitrate": 192 + } ] }, { @@ -1416,6 +1772,18 @@ "name": "Global (RTMP)", "url": "rtmp://global-live.mux.com:5222/app" } + ], + "maximum": [ + { + "protocol": "RTMPS", + "video_bitrate": 5000, + "audio_bitrate": 160 + }, + { + "protocol": "RTMP", + "video_bitrate": 5000, + "audio_bitrate": 160 + } ] }, { @@ -1427,6 +1795,13 @@ "name": "Default", "url": "rtmp://live.viloud.tv:5222/app" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 5000, + "audio_bitrate": 160 + } ] }, { @@ -1466,6 +1841,14 @@ "name": "South America", "url": "rtmp://publish-sao.myfreecams.com/NxServer" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 10000, + "audio_bitrate": 192, + "fps": 60 + } ] }, { @@ -1558,6 +1941,13 @@ "name": "Asia - Singapore", "url": "ingest.wsss.live.glimesh.tv" } + ], + "maximum": [ + { + "protocol": "FTL", + "video_bitrate": 6000, + "audio_bitrate": 160 + } ] }, { @@ -1570,6 +1960,13 @@ "name": "Default", "url": "rtmp://a.station.openrec.tv:1935/live1" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 5000, + "audio_bitrate": 160 + } ] }, { @@ -1635,6 +2032,18 @@ "name": "bintu-vtrans Australia (AU)", "url": "rtmp://bintu-vtrans-au.nanocosmos.de/live" } + ], + "maximum": [ + { + "protocol": "RTMPS", + "video_bitrate": 5000, + "audio_bitrate": 192 + }, + { + "protocol": "RTMP", + "video_bitrate": 5000, + "audio_bitrate": 192 + } ] }, { @@ -1691,6 +2100,13 @@ "name": "APAC - Australia East (Sydney)", "url": "rtmp://ingest-apac-sydney.brimelive.com/live" } + ], + "maximum": [ + { + "protocol": "RTMP", + "video_bitrate": 20000, + "audio_bitrate": 320 + } ] } ] diff --git a/plugins/obs-services/service-factory.cpp b/plugins/obs-services/service-factory.cpp index b206829f1f87dd..9294b1ea179c91 100644 --- a/plugins/obs-services/service-factory.cpp +++ b/plugins/obs-services/service-factory.cpp @@ -53,6 +53,74 @@ void service_factory::create_server_lists(obs_properties_t *props) /* ----------------------------------------------------------------- */ +// XXX: Add maximum default settings for each protocols +void service_factory::add_maximum_defaults(obs_data_t *settings) +{ + obs_data_set_default_int(settings, "max_video_bitrate_rtmp", + maximum["RTMP"].video_bitrate); + obs_data_set_default_int(settings, "max_audio_bitrate_rtmp", + maximum["RTMP"].audio_bitrate); + obs_data_set_default_int(settings, "max_fps_rtmp", maximum["RTMP"].fps); + + obs_data_set_default_int(settings, "max_video_bitrate_rtmps", + maximum["RTMPS"].video_bitrate); + obs_data_set_default_int(settings, "max_audio_bitrate_rtmps", + maximum["RTMPS"].audio_bitrate); + obs_data_set_default_int(settings, "max_fps_rtmps", + maximum["RTMPS"].fps); + + obs_data_set_default_int(settings, "max_video_bitrate_hls", + maximum["HLS"].video_bitrate); + obs_data_set_default_int(settings, "max_audio_bitrate_hls", + maximum["HLS"].audio_bitrate); + obs_data_set_default_int(settings, "max_fps_hls", maximum["HLS"].fps); + + obs_data_set_default_int(settings, "max_video_bitrate_ftl", + maximum["FTL"].video_bitrate); + obs_data_set_default_int(settings, "max_audio_bitrate_ftl", + maximum["FTL"].audio_bitrate); + obs_data_set_default_int(settings, "max_fps_ftl", maximum["FTL"].fps); + + obs_data_set_default_bool(settings, "ignore_maximum", false); +} + +// XXX: Add maximum settings for each protocols +void service_factory::add_maximum_infos(obs_properties_t *props) +{ + obs_properties_add_info_bitrate(props, "max_video_bitrate_rtmp", + obs_module_text("MaxVideoBitrate")); + obs_properties_add_info_bitrate(props, "max_audio_bitrate_rtmp", + obs_module_text("MaxAudioBitrate")); + obs_properties_add_info_fps(props, "max_fps_rtmp", + obs_module_text("MaxFPS")); + + obs_properties_add_info_bitrate(props, "max_video_bitrate_rtmps", + obs_module_text("MaxVideoBitrate")); + obs_properties_add_info_bitrate(props, "max_audio_bitrate_rtmps", + obs_module_text("MaxAudioBitrate")); + obs_properties_add_info_fps(props, "max_fps_rtmps", + obs_module_text("MaxFPS")); + + obs_properties_add_info_bitrate(props, "max_video_bitrate_hls", + obs_module_text("MaxVideoBitrate")); + obs_properties_add_info_bitrate(props, "max_audio_bitrate_hls", + obs_module_text("MaxAudioBitrate")); + obs_properties_add_info_fps(props, "max_fps_hls", + obs_module_text("MaxFPS")); + + obs_properties_add_info_bitrate(props, "max_video_bitrate_ftl", + obs_module_text("MaxVideoBitrate")); + obs_properties_add_info_bitrate(props, "max_audio_bitrate_ftl", + obs_module_text("MaxAudioBitrate")); + obs_properties_add_info_fps(props, "max_fps_ftl", + obs_module_text("MaxFPS")); + + obs_properties_add_bool(props, "ignore_maximum", + obs_module_text("IgnoreMaximum")); +} + +/* ----------------------------------------------------------------- */ + static inline int get_int_val(json_t *service, const char *key) { json_t *integer_val = json_object_get(service, key); @@ -200,10 +268,46 @@ try { return nullptr; } +void service_factory::_get_max_fps(void *data, int *fps) noexcept +try { + service_instance *priv = reinterpret_cast(data); + if (priv) + priv->get_max_fps(fps); +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); +} + +void service_factory::_get_max_bitrate(void *data, int *video, + int *audio) noexcept +try { + service_instance *priv = reinterpret_cast(data); + if (priv) + priv->get_max_bitrate(video, audio); +} catch (const std::exception &ex) { + blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, + ex.what()); +} catch (...) { + blog(LOG_ERROR, "Unexpected exception in function %s", __func__); +} + /* ----------------------------------------------------------------- */ service_factory::service_factory(json_t *service) { + // XXX: Initialize maximum settings for each protocols + // Clang disabled to keep it readable a a list + /* clang-format off */ + maximum.insert({ + {"RTMP", {}}, + {"RTMPS", {}}, + {"HLS", {}}, + {"FTL", {}} + }); + /* clang-format on */ + /** JSON data extraction **/ _id = get_string_val(service, "id"); @@ -265,6 +369,35 @@ service_factory::service_factory(json_t *service) json_decref(object); } + /* Maximum output settings extraction */ + + object = json_object_get(service, "maximum"); + if (object) { + json_incref(object); + + json_array_foreach (object, idx, element) { + const char *prtcl = get_string_val(element, "protocol"); + int video_bitrate = + get_int_val(element, "video_bitrate"); + int audio_bitrate = + get_int_val(element, "audio_bitrate"); + int fps = get_int_val(element, "fps"); + + if (prtcl != NULL) { + if (video_bitrate != -1) + maximum[prtcl].video_bitrate = + video_bitrate; + if (audio_bitrate != -1) + maximum[prtcl].audio_bitrate = + audio_bitrate; + if (fps != -1) + maximum[prtcl].fps = fps; + } + } + + json_decref(object); + } + /** Service implementation **/ _info.type_data = this; @@ -285,6 +418,9 @@ service_factory::service_factory(json_t *service) _info.get_url = _get_url; _info.get_key = _get_key; + _info.get_max_fps = _get_max_fps; + _info.get_max_bitrate = _get_max_bitrate; + obs_register_service(&_info); } @@ -292,6 +428,7 @@ service_factory::~service_factory() { protocols.clear(); servers.clear(); + maximum.clear(); } const char *service_factory::get_name() @@ -316,9 +453,31 @@ void service_factory::get_defaults2(obs_data_t *settings) stream_key_link.c_str()); obs_data_set_default_string(settings, "protocol", protocols[0].c_str()); + + add_maximum_defaults(settings); +} + +static inline void set_visible_maximum(obs_properties_t *props, + obs_data_t *settings, const char *name, + bool prtcl) +{ + obs_property_set_visible(obs_properties_get(props, name), + prtcl && (obs_data_get_int(settings, name) != + -1)); +} + +static inline void +set_visible_ignore_maximum(obs_properties_t *props, obs_data_t *settings, + const char *max1, const char *max2, const char *max3) +{ + obs_property_set_visible( + obs_properties_get(props, "ignore_maximum"), + (obs_data_get_int(settings, max1) != -1) || + (obs_data_get_int(settings, max2) != -1) || + (obs_data_get_int(settings, max3) != -1)); } -// XXX: Set the visibility of server lists for each protocols +// XXX: Set the visibility of server lists and maximum settings for each protocols static bool modified_protocol(obs_properties_t *props, obs_property_t *, obs_data_t *settings) noexcept try { @@ -336,6 +495,46 @@ try { obs_property_set_visible(obs_properties_get(props, "server_hls"), hls); obs_property_set_visible(obs_properties_get(props, "server_ftl"), ftl); + //Max Video Bitrate + set_visible_maximum(props, settings, "max_video_bitrate_rtmp", rtmp); + set_visible_maximum(props, settings, "max_video_bitrate_rtmps", rtmps); + set_visible_maximum(props, settings, "max_video_bitrate_hls", hls); + set_visible_maximum(props, settings, "max_video_bitrate_ftl", ftl); + + //Max Audio Bitrate + set_visible_maximum(props, settings, "max_audio_bitrate_rtmp", rtmp); + set_visible_maximum(props, settings, "max_audio_bitrate_rtmps", rtmps); + set_visible_maximum(props, settings, "max_audio_bitrate_hls", hls); + set_visible_maximum(props, settings, "max_audio_bitrate_ftl", ftl); + + //Max FPS + set_visible_maximum(props, settings, "max_fps_rtmp", rtmp); + set_visible_maximum(props, settings, "max_fps_rtmps", rtmps); + set_visible_maximum(props, settings, "max_fps_hls", hls); + set_visible_maximum(props, settings, "max_fps_ftl", ftl); + + //Ignore Max Toggle + if (rtmp) + set_visible_ignore_maximum(props, settings, + "max_video_bitrate_rtmp", + "max_audio_bitrate_rtmp", + "max_fps_rtmp"); + if (rtmps) + set_visible_ignore_maximum(props, settings, + "max_video_bitrate_rtmps", + "max_audio_bitrate_rtmps", + "max_fps_rtmps"); + if (hls) + set_visible_ignore_maximum(props, settings, + "max_video_bitrate_hls", + "max_audio_bitrate_hls", + "max_fps_hls"); + if (ftl) + set_visible_ignore_maximum(props, settings, + "max_video_bitrate_ftl", + "max_audio_bitrate_ftl", + "max_fps_ftl"); + return true; } catch (const std::exception &ex) { blog(LOG_ERROR, "Unexpected exception in function %s: %s", __func__, @@ -377,5 +576,7 @@ obs_properties_t *service_factory::get_properties2(void *data) obs_properties_add_open_url(props, "key_link", obs_module_text("StreamKeyLink")); + add_maximum_infos(props); + return props; } diff --git a/plugins/obs-services/service-factory.hpp b/plugins/obs-services/service-factory.hpp index 06ca691b250338..d0b93994e61f35 100644 --- a/plugins/obs-services/service-factory.hpp +++ b/plugins/obs-services/service-factory.hpp @@ -15,6 +15,12 @@ struct server_t { const char *name; }; +struct maximum_t { + int video_bitrate = -1; + int audio_bitrate = -1; + int fps = -1; +}; + class service_factory { obs_service_info _info = {}; @@ -24,9 +30,13 @@ class service_factory { std::string stream_key_link; std::vector protocols; std::vector servers; + std::map maximum; void create_server_lists(obs_properties_t *props); + void add_maximum_defaults(obs_data_t *settings); + void add_maximum_infos(obs_properties_t *props); + static const char *_get_name(void *type_data) noexcept; static void *_create(obs_data_t *settings, @@ -45,6 +55,10 @@ class service_factory { static const char *_get_url(void *data) noexcept; static const char *_get_key(void *data) noexcept; + static void _get_max_fps(void *data, int *fps) noexcept; + static void _get_max_bitrate(void *data, int *video, + int *audio) noexcept; + public: service_factory(json_t *service); ~service_factory(); diff --git a/plugins/obs-services/service-instance.cpp b/plugins/obs-services/service-instance.cpp index 7145482f4080e1..deaa667d963016 100644 --- a/plugins/obs-services/service-instance.cpp +++ b/plugins/obs-services/service-instance.cpp @@ -14,14 +14,38 @@ void service_instance::update(obs_data_t *settings) { protocol = obs_data_get_string(settings, "protocol"); - if (protocol.compare("RTMP") == 0) + if (protocol.compare("RTMP") == 0) { server = obs_data_get_string(settings, "server_rtmp"); - if (protocol.compare("RTMPS") == 0) + max_fps = obs_data_get_int(settings, "max_fps_rtmp"); + max_video_bitrate = + obs_data_get_int(settings, "max_video_bitrate_rtmp"); + max_audio_bitrate = + obs_data_get_int(settings, "max_audio_bitrate_rtmp"); + } + if (protocol.compare("RTMPS") == 0) { server = obs_data_get_string(settings, "server_rtmps"); - if (protocol.compare("HLS") == 0) + max_fps = obs_data_get_int(settings, "max_fps_rtmps"); + max_video_bitrate = + obs_data_get_int(settings, "max_video_bitrate_rtmps"); + max_audio_bitrate = + obs_data_get_int(settings, "max_audio_bitrate_rtmps"); + } + if (protocol.compare("HLS") == 0) { server = obs_data_get_string(settings, "server_hls"); - if (protocol.compare("FTL") == 0) + max_fps = obs_data_get_int(settings, "max_fps_hls"); + max_video_bitrate = + obs_data_get_int(settings, "max_video_bitrate_hls"); + max_audio_bitrate = + obs_data_get_int(settings, "max_audio_bitrate_hls"); + } + if (protocol.compare("FTL") == 0) { server = obs_data_get_string(settings, "server_ftl"); + max_fps = obs_data_get_int(settings, "max_fps_ftl"); + max_video_bitrate = + obs_data_get_int(settings, "max_video_bitrate_ftl"); + max_audio_bitrate = + obs_data_get_int(settings, "max_audio_bitrate_ftl"); + } key = obs_data_get_string(settings, "key"); } @@ -40,3 +64,17 @@ const char *service_instance::get_key() { return key.c_str(); } + +void service_instance::get_max_fps(int *fps) +{ + if (max_fps != -1) + *fps = max_fps; +} + +void service_instance::get_max_bitrate(int *video, int *audio) +{ + if (max_video_bitrate != -1) + *video = max_video_bitrate; + if (max_audio_bitrate != -1) + *audio = max_audio_bitrate; +} diff --git a/plugins/obs-services/service-instance.hpp b/plugins/obs-services/service-instance.hpp index 9d90d56ad32f1e..70d8093ea0ad59 100644 --- a/plugins/obs-services/service-instance.hpp +++ b/plugins/obs-services/service-instance.hpp @@ -13,6 +13,9 @@ class service_instance { std::string protocol; std::string server; std::string key; + int max_fps; + int max_video_bitrate; + int max_audio_bitrate; public: service_instance(obs_data_t *settings, obs_service_t *self); @@ -23,4 +26,7 @@ class service_instance { const char *get_protocol(); const char *get_url(); const char *get_key(); + + void get_max_fps(int *fps); + void get_max_bitrate(int *video, int *audio); }; From e58f530d82d74aa6de5499c6e6d8facf0b191b25 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 13:39:41 +0200 Subject: [PATCH 13/23] docs: Add "info bitrate" property --- docs/sphinx/reference-properties.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/sphinx/reference-properties.rst b/docs/sphinx/reference-properties.rst index 3c341679dcad03..ddc4c60f2a61da 100644 --- a/docs/sphinx/reference-properties.rst +++ b/docs/sphinx/reference-properties.rst @@ -333,6 +333,19 @@ Property Object Functions --------------------- +.. function:: obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, const char *name, const char *description) + + Adds a label with a bitrate in kbps. This property does not + actually store any settings; it's used to implement a label in + user interface if the properties are used to generate user + interface. + + :param name: Setting identifier string + :param description: Localized name shown to user + + :return: The property + +--------------------- Property Enumeration Functions ------------------------------ From 37ad62c7fc04bea40616c19e52c0b6beab96e344 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 13:39:56 +0200 Subject: [PATCH 14/23] docs: Add "info FPS" property --- docs/sphinx/reference-properties.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/sphinx/reference-properties.rst b/docs/sphinx/reference-properties.rst index ddc4c60f2a61da..d80b227de132e0 100644 --- a/docs/sphinx/reference-properties.rst +++ b/docs/sphinx/reference-properties.rst @@ -347,6 +347,20 @@ Property Object Functions --------------------- +.. function:: obs_property_t *obs_properties_add_info_fps(obs_properties_t *props, const char *name, const char *description) + + Adds a label with a frequency in FPS. This property does not + actually store any settings; it's used to implement a label in + user interface if the properties are used to generate user + interface. + + :param name: Setting identifier string + :param description: Localized name shown to user + + :return: The property + +--------------------- + Property Enumeration Functions ------------------------------ From 693bf77c10d92699bbd0adb1313571cadba6f6e4 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 14:30:44 +0200 Subject: [PATCH 15/23] libobs, UI: Add "info" property --- UI/properties-view.cpp | 15 ++++++++++++++- UI/properties-view.hpp | 1 + libobs/obs-properties.c | 10 ++++++++++ libobs/obs-properties.h | 5 +++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index 0012de4249eb05..6bc1d900164b38 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -1413,6 +1413,14 @@ QWidget *OBSPropertiesView::AddOpenUrl(obs_property_t *prop) return NewWidget(prop, button, SIGNAL(clicked())); } +QWidget *OBSPropertiesView::AddInfo(obs_property_t *prop) +{ + const char *desc = obs_property_description(prop); + + QLabel *label = new QLabel(QT_UTF8(desc)); + return NewWidget(prop, label, SIGNAL(linkActivated(QString))); +} + QWidget *OBSPropertiesView::AddInfoBitrate(obs_property_t *prop) { const char *name = obs_property_name(prop); @@ -1493,6 +1501,9 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, case OBS_PROPERTY_OPEN_URL: widget = AddOpenUrl(property); break; + case OBS_PROPERTY_INFO: + widget = AddInfo(property); + break; case OBS_PROPERTY_INFO_BITRATE: widget = AddInfoBitrate(property); break; @@ -1505,7 +1516,7 @@ void OBSPropertiesView::AddProperty(obs_property_t *property, if (!label && type != OBS_PROPERTY_BOOL && type != OBS_PROPERTY_BUTTON && type != OBS_PROPERTY_GROUP && - type != OBS_PROPERTY_OPEN_URL && + type != OBS_PROPERTY_OPEN_URL && type != OBS_PROPERTY_INFO && type != OBS_PROPERTY_INFO_BITRATE && type != OBS_PROPERTY_INFO_FPS) label = new QLabel(QT_UTF8(obs_property_description(property))); @@ -2006,6 +2017,8 @@ void WidgetInfo::ControlChanged() break; case OBS_PROPERTY_OPEN_URL: return; + case OBS_PROPERTY_INFO: + return; case OBS_PROPERTY_INFO_BITRATE: return; case OBS_PROPERTY_INFO_FPS: diff --git a/UI/properties-view.hpp b/UI/properties-view.hpp index 827e23e4170b42..7cf406693181a7 100644 --- a/UI/properties-view.hpp +++ b/UI/properties-view.hpp @@ -135,6 +135,7 @@ class OBSPropertiesView : public VScrollArea { void AddGroup(obs_property_t *prop, QFormLayout *layout); QWidget *AddOpenUrl(obs_property_t *prop); + QWidget *AddInfo(obs_property_t *prop); QWidget *AddInfoBitrate(obs_property_t *prop); QWidget *AddInfoFPS(obs_property_t *prop); diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index 2dd2751bbd6164..81bde284d81264 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -442,6 +442,8 @@ static inline size_t get_property_size(enum obs_property_type type) return 0; case OBS_PROPERTY_OPEN_URL: return 0; + case OBS_PROPERTY_INFO: + return 0; case OBS_PROPERTY_INFO_BITRATE: return 0; case OBS_PROPERTY_INFO_FPS: @@ -823,6 +825,14 @@ obs_property_t *obs_properties_add_open_url(obs_properties_t *props, return p; } +obs_property_t *obs_properties_add_info(obs_properties_t *props, + const char *name, const char *desc) +{ + if (!props || has_prop(props, name)) + return NULL; + return new_prop(props, name, desc, OBS_PROPERTY_INFO); +} + obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, const char *name, const char *desc) diff --git a/libobs/obs-properties.h b/libobs/obs-properties.h index 6e8e49ab432546..b268a226f513e7 100644 --- a/libobs/obs-properties.h +++ b/libobs/obs-properties.h @@ -58,6 +58,7 @@ enum obs_property_type { OBS_PROPERTY_GROUP, OBS_PROPERTY_COLOR_ALPHA, OBS_PROPERTY_OPEN_URL, + OBS_PROPERTY_INFO, OBS_PROPERTY_INFO_BITRATE, OBS_PROPERTY_INFO_FPS, }; @@ -274,6 +275,10 @@ EXPORT obs_property_t *obs_properties_add_open_url(obs_properties_t *props, const char *name, const char *desc); +EXPORT obs_property_t *obs_properties_add_info(obs_properties_t *props, + const char *name, + const char *desc); + EXPORT obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, const char *name, const char *desc); From 039ff2704f9f3f56869402838c35aa926c6f8f7a Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 14:34:27 +0200 Subject: [PATCH 16/23] docs: Add "info" property --- docs/sphinx/reference-properties.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/sphinx/reference-properties.rst b/docs/sphinx/reference-properties.rst index d80b227de132e0..78fb6d8d7c2ccd 100644 --- a/docs/sphinx/reference-properties.rst +++ b/docs/sphinx/reference-properties.rst @@ -333,6 +333,19 @@ Property Object Functions --------------------- +.. function:: obs_property_t *obs_properties_add_info(obs_properties_t *props, const char *name, const char *description) + + Adds a label. This property does not actually store any settings; + it's used to implement a label in user interface if the properties + are used to generate user interface. + + :param name: Setting identifier string + :param description: Localized name shown to user + + :return: The property + +--------------------- + .. function:: obs_property_t *obs_properties_add_info_bitrate(obs_properties_t *props, const char *name, const char *description) Adds a label with a bitrate in kbps. This property does not From 36f4cfb5ffab712a8eca359f69703eb6bd53a234 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 14:40:51 +0200 Subject: [PATCH 17/23] obs-services: Add supported resolutions --- plugins/obs-services/data/locale/en-US.ini | 2 + plugins/obs-services/data/services.json | 20 ++++++++++ plugins/obs-services/service-factory.cpp | 45 ++++++++++++++++++++++ plugins/obs-services/service-factory.hpp | 2 + 4 files changed, 69 insertions(+) diff --git a/plugins/obs-services/data/locale/en-US.ini b/plugins/obs-services/data/locale/en-US.ini index 40a1b219422709..479b25b1050c82 100644 --- a/plugins/obs-services/data/locale/en-US.ini +++ b/plugins/obs-services/data/locale/en-US.ini @@ -7,3 +7,5 @@ MaxVideoBitrate="Maximum Video Bitrate:" MaxAudioBitrate="Maximum Audio Bitrate:" MaxFPS="Maximum FPS:" IgnoreMaximum="Ignore streaming service maximum limits" +SupportedResolutions="Supported resolution(s):" +IgnoreSupportedResolutions="Ignore supported resolutions" diff --git a/plugins/obs-services/data/services.json b/plugins/obs-services/data/services.json index d4c44edd29f87e..36467ff7e2dc15 100644 --- a/plugins/obs-services/data/services.json +++ b/plugins/obs-services/data/services.json @@ -306,6 +306,11 @@ "audio_bitrate": 128, "fps": 30 } + ], + "supported_resolutions": [ + "1280x720", + "852x480", + "480x360" ] }, { @@ -803,6 +808,12 @@ "audio_bitrate": 160, "fps": 30 } + ], + "supported_resolutions": [ + "1920x1080", + "1280x720", + "852x480", + "480x360" ] }, { @@ -1070,6 +1081,10 @@ "audio_bitrate": 192, "fps": 30 } + ], + "supported_resolutions": [ + "1920x1080", + "1280x720" ] }, { @@ -1148,6 +1163,11 @@ "audio_bitrate": 320, "fps": 30 } + ], + "supported_resolutions": [ + "1280x720", + "852x480", + "480x360" ] }, { diff --git a/plugins/obs-services/service-factory.cpp b/plugins/obs-services/service-factory.cpp index 9294b1ea179c91..10819bad60d368 100644 --- a/plugins/obs-services/service-factory.cpp +++ b/plugins/obs-services/service-factory.cpp @@ -398,6 +398,29 @@ service_factory::service_factory(json_t *service) json_decref(object); } + /* Supported resolutions extraction */ + + object = json_object_get(service, "supported_resolutions"); + if (object) { + json_incref(object); + + json_array_foreach (object, idx, element) { + const char *res_str = json_string_value(element); + obs_service_resolution res; + + if (res_str != NULL) { + if (sscanf(res_str, "%dx%d", &res.cx, + &res.cy) == 2) { + supported_resolutions_str.push_back( + res_str); + supported_resolutions.push_back(res); + } + } + } + + json_decref(object); + } + /** Service implementation **/ _info.type_data = this; @@ -429,6 +452,8 @@ service_factory::~service_factory() protocols.clear(); servers.clear(); maximum.clear(); + supported_resolutions_str.clear(); + supported_resolutions.clear(); } const char *service_factory::get_name() @@ -455,6 +480,9 @@ void service_factory::get_defaults2(obs_data_t *settings) obs_data_set_default_string(settings, "protocol", protocols[0].c_str()); add_maximum_defaults(settings); + + obs_data_set_default_bool(settings, "ignore_supported_resolutions", + false); } static inline void set_visible_maximum(obs_properties_t *props, @@ -578,5 +606,22 @@ obs_properties_t *service_factory::get_properties2(void *data) add_maximum_infos(props); + if (!supported_resolutions_str.empty()) { + std::string label = obs_module_text("SupportedResolutions"); + label += " "; + + for (size_t idx = 0; idx < supported_resolutions_str.size(); + idx++) { + label += supported_resolutions_str[idx]; + if ((supported_resolutions_str.size() - idx) != 1) + label += ", "; + } + + obs_properties_add_info(props, "resolutions", label.c_str()); + obs_properties_add_bool( + props, "ignore_supported_resolutions", + obs_module_text("IgnoreSupportedResolutions")); + } + return props; } diff --git a/plugins/obs-services/service-factory.hpp b/plugins/obs-services/service-factory.hpp index d0b93994e61f35..737200a0276df1 100644 --- a/plugins/obs-services/service-factory.hpp +++ b/plugins/obs-services/service-factory.hpp @@ -31,6 +31,8 @@ class service_factory { std::vector protocols; std::vector servers; std::map maximum; + std::vector supported_resolutions_str; + std::vector supported_resolutions; void create_server_lists(obs_properties_t *props); From f215d2f04ab09f4642db7d38d9e1f0f5a64b7d65 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Tue, 25 May 2021 18:58:05 +0200 Subject: [PATCH 18/23] docs: Add protocol to service API --- docs/sphinx/reference-services.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/reference-services.rst b/docs/sphinx/reference-services.rst index fdfb85e419c4e9..c97343dccc259e 100644 --- a/docs/sphinx/reference-services.rst +++ b/docs/sphinx/reference-services.rst @@ -90,6 +90,10 @@ Service Definition Structure :return: *true* to allow the output to start up, *false* to prevent output from starting up +.. member:: const char *(*obs_service_info.get_protocol)(void *data) + + :return: The stream protocol + .. member:: const char *(*obs_service_info.get_url)(void *data) :return: The stream URL @@ -163,7 +167,7 @@ General Service Functions .. function:: obs_service_t *obs_service_create(const char *id, const char *name, obs_data_t *settings, obs_data_t *hotkey_data) Creates a service with the specified settings. - + The "service" context is used for encoding video/audio data. Use obs_service_release to release it. @@ -267,7 +271,7 @@ General Service Functions .. function:: void obs_service_apply_encoder_settings(obs_service_t *service, obs_data_t *video_encoder_settings, obs_data_t *audio_encoder_settings) Applies service-specific video encoder settings. - + :param video_encoder_settings: Video encoder settings. Can be *NULL* :param audio_encoder_settings: Audio encoder settings. Can be *NULL* From 16295963111f455dc83d02031e23edc36ae8df23 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 27 May 2021 17:15:40 +0200 Subject: [PATCH 19/23] obs-outputs: Add global protocol CMake variables --- plugins/obs-outputs/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/obs-outputs/CMakeLists.txt b/plugins/obs-outputs/CMakeLists.txt index 3ec627396e75ae..e19146bbbe7b68 100644 --- a/plugins/obs-outputs/CMakeLists.txt +++ b/plugins/obs-outputs/CMakeLists.txt @@ -21,8 +21,10 @@ if (WITH_RTMPS) find_package(ZLIB REQUIRED) add_definitions(-DCRYPTO -DUSE_MBEDTLS) include_directories(${MBEDTLS_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS}) + set(RTMPS_NOT_AVAILABLE OFF CACHE BOOL "Internal global cmake variable" FORCE) else() add_definitions(-DNO_CRYPTO) + set(RTMPS_NOT_AVAILABLE ON CACHE BOOL "Internal global cmake variable" FORCE) endif() set(COMPILE_FTL FALSE) @@ -92,6 +94,12 @@ elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ftl-sdk/CMakeLists.txt") set(COMPILE_FTL TRUE) endif() +if (COMPILE_FTL) + set(FTL_NOT_AVAILABLE OFF CACHE BOOL "Internal global cmake variable" FORCE) +else() + set(FTL_NOT_AVAILABLE ON CACHE BOOL "Internal global cmake variable" FORCE) +endif() + configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/obs-outputs-config.h.in" "${CMAKE_BINARY_DIR}/plugins/obs-outputs/config/obs-outputs-config.h") From b7ae5bc38d35efb965e561d335898fc8fe22a709 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 27 May 2021 17:18:15 +0200 Subject: [PATCH 20/23] obs-services: Add CMake protocol management --- plugins/obs-services/CMakeLists.txt | 18 ++++ plugins/obs-services/protocols.hpp.in | 12 +++ plugins/obs-services/service-factory.cpp | 105 ++++++++++++++++------- 3 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 plugins/obs-services/protocols.hpp.in diff --git a/plugins/obs-services/CMakeLists.txt b/plugins/obs-services/CMakeLists.txt index bcb96317606ed6..d430b4b48a303a 100644 --- a/plugins/obs-services/CMakeLists.txt +++ b/plugins/obs-services/CMakeLists.txt @@ -2,6 +2,23 @@ project(obs-services) include_directories(${OBS_JANSSON_INCLUDE_DIRS}) +set(RTMPS_DISABLED FALSE) +set(FTL_DISABLED FALSE) + +if(RTMPS_NOT_AVAILABLE) + set(RTMPS_DISABLED TRUE) +endif() + +if(FTL_NOT_AVAILABLE) + set(FTL_DISABLED TRUE) +endif() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/protocols.hpp.in" + "${CMAKE_BINARY_DIR}/plugins/obs-services/config/protocols.hpp") + +include_directories("${CMAKE_BINARY_DIR}/plugins/obs-services/config") + set(obs-services_SOURCES service-instance.cpp service-factory.cpp @@ -12,6 +29,7 @@ set(obs-services_HEADERS service-factory.cpp service-manager.cpp json-format-ver.hpp + "${CMAKE_BINARY_DIR}/plugins/obs-services/config/protocols.hpp" plugin.hpp) if(WIN32) diff --git a/plugins/obs-services/protocols.hpp.in b/plugins/obs-services/protocols.hpp.in new file mode 100644 index 00000000000000..a74e729f3b35c6 --- /dev/null +++ b/plugins/obs-services/protocols.hpp.in @@ -0,0 +1,12 @@ +#pragma once + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#define RTMPS_DISABLED @RTMPS_DISABLED@ +#define FTL_DISABLED @FTL_DISABLED@ diff --git a/plugins/obs-services/service-factory.cpp b/plugins/obs-services/service-factory.cpp index 10819bad60d368..7cd519b8978894 100644 --- a/plugins/obs-services/service-factory.cpp +++ b/plugins/obs-services/service-factory.cpp @@ -1,5 +1,6 @@ #include "service-factory.hpp" +#include "protocols.hpp" #include "plugin.hpp" #include "service-instance.hpp" @@ -10,23 +11,26 @@ extern "C" { // XXX: Add the server list for each protocols void service_factory::create_server_lists(obs_properties_t *props) { - obs_property_t *rtmp, *rtmps, *hls, *ftl; + obs_property_t *rtmp, *hls; rtmp = obs_properties_add_list(props, "server_rtmp", obs_module_text("Server"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - rtmps = obs_properties_add_list(props, "server_rtmps", - obs_module_text("Server"), - OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_STRING); +#if !RTMPS_DISABLED + obs_property_t *rtmps = obs_properties_add_list( + props, "server_rtmps", obs_module_text("Server"), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); +#endif hls = obs_properties_add_list(props, "server_hls", obs_module_text("Server"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - ftl = obs_properties_add_list(props, "server_ftl", - obs_module_text("Server"), - OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_STRING); +#if !FTL_DISABLED + obs_property_t *ftl = obs_properties_add_list(props, "server_ftl", + obs_module_text("Server"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); +#endif for (size_t idx = 0; idx < servers.size(); idx++) { if (strcmp(servers[idx].protocol, "RTMP") == 0) @@ -34,20 +38,24 @@ void service_factory::create_server_lists(obs_properties_t *props) rtmp, obs_module_text(servers[idx].name), servers[idx].url); +#if !RTMPS_DISABLED if (strcmp(servers[idx].protocol, "RTMPS") == 0) obs_property_list_add_string( rtmps, obs_module_text(servers[idx].name), servers[idx].url); +#endif if (strcmp(servers[idx].protocol, "HLS") == 0) obs_property_list_add_string( hls, obs_module_text(servers[idx].name), servers[idx].url); +#if !FTL_DISABLED if (strcmp(servers[idx].protocol, "FTL") == 0) obs_property_list_add_string( ftl, obs_module_text(servers[idx].name), servers[idx].url); +#endif } } @@ -62,12 +70,14 @@ void service_factory::add_maximum_defaults(obs_data_t *settings) maximum["RTMP"].audio_bitrate); obs_data_set_default_int(settings, "max_fps_rtmp", maximum["RTMP"].fps); +#if !RTMPS_DISABLED obs_data_set_default_int(settings, "max_video_bitrate_rtmps", maximum["RTMPS"].video_bitrate); obs_data_set_default_int(settings, "max_audio_bitrate_rtmps", maximum["RTMPS"].audio_bitrate); obs_data_set_default_int(settings, "max_fps_rtmps", maximum["RTMPS"].fps); +#endif obs_data_set_default_int(settings, "max_video_bitrate_hls", maximum["HLS"].video_bitrate); @@ -75,11 +85,13 @@ void service_factory::add_maximum_defaults(obs_data_t *settings) maximum["HLS"].audio_bitrate); obs_data_set_default_int(settings, "max_fps_hls", maximum["HLS"].fps); +#if !FTL_DISABLED obs_data_set_default_int(settings, "max_video_bitrate_ftl", maximum["FTL"].video_bitrate); obs_data_set_default_int(settings, "max_audio_bitrate_ftl", maximum["FTL"].audio_bitrate); obs_data_set_default_int(settings, "max_fps_ftl", maximum["FTL"].fps); +#endif obs_data_set_default_bool(settings, "ignore_maximum", false); } @@ -94,12 +106,14 @@ void service_factory::add_maximum_infos(obs_properties_t *props) obs_properties_add_info_fps(props, "max_fps_rtmp", obs_module_text("MaxFPS")); +#if !RTMPS_DISABLED obs_properties_add_info_bitrate(props, "max_video_bitrate_rtmps", obs_module_text("MaxVideoBitrate")); obs_properties_add_info_bitrate(props, "max_audio_bitrate_rtmps", obs_module_text("MaxAudioBitrate")); obs_properties_add_info_fps(props, "max_fps_rtmps", obs_module_text("MaxFPS")); +#endif obs_properties_add_info_bitrate(props, "max_video_bitrate_hls", obs_module_text("MaxVideoBitrate")); @@ -108,12 +122,14 @@ void service_factory::add_maximum_infos(obs_properties_t *props) obs_properties_add_info_fps(props, "max_fps_hls", obs_module_text("MaxFPS")); +#if !FTL_DISABLED obs_properties_add_info_bitrate(props, "max_video_bitrate_ftl", obs_module_text("MaxVideoBitrate")); obs_properties_add_info_bitrate(props, "max_audio_bitrate_ftl", obs_module_text("MaxAudioBitrate")); obs_properties_add_info_fps(props, "max_fps_ftl", obs_module_text("MaxFPS")); +#endif obs_properties_add_bool(props, "ignore_maximum", obs_module_text("IgnoreMaximum")); @@ -302,9 +318,13 @@ service_factory::service_factory(json_t *service) /* clang-format off */ maximum.insert({ {"RTMP", {}}, +#if !RTMPS_DISABLED {"RTMPS", {}}, +#endif {"HLS", {}}, - {"FTL", {}} +#if !FTL_DISABLED + {"FTL", {}}, +#endif }); /* clang-format on */ @@ -444,6 +464,16 @@ service_factory::service_factory(json_t *service) _info.get_max_fps = _get_max_fps; _info.get_max_bitrate = _get_max_bitrate; +#if RTMPS_DISABLED + if ((protocols.size() == 1) && (protocols[0].compare("RTMPS") == 0)) + return; +#endif + +#if FTL_DISABLED + if ((protocols.size() == 1) && (protocols[0].compare("FTL") == 0)) + return; +#endif + obs_register_service(&_info); } @@ -510,36 +540,36 @@ static bool modified_protocol(obs_properties_t *props, obs_property_t *, obs_data_t *settings) noexcept try { const char *protocol = obs_data_get_string(settings, "protocol"); - bool rtmp = strcmp(protocol, "RTMP") == 0; - bool rtmps = strcmp(protocol, "RTMPS") == 0; - bool hls = strcmp(protocol, "HLS") == 0; - bool ftl = strcmp(protocol, "FTL") == 0; - //Server lists + bool rtmp = strcmp(protocol, "RTMP") == 0; obs_property_set_visible(obs_properties_get(props, "server_rtmp"), rtmp); + set_visible_maximum(props, settings, "max_video_bitrate_rtmp", rtmp); + set_visible_maximum(props, settings, "max_audio_bitrate_rtmp", rtmp); + set_visible_maximum(props, settings, "max_fps_rtmp", rtmp); + +#if !RTMPS_DISABLED + bool rtmps = strcmp(protocol, "RTMPS") == 0; obs_property_set_visible(obs_properties_get(props, "server_rtmps"), rtmps); - obs_property_set_visible(obs_properties_get(props, "server_hls"), hls); - obs_property_set_visible(obs_properties_get(props, "server_ftl"), ftl); - - //Max Video Bitrate - set_visible_maximum(props, settings, "max_video_bitrate_rtmp", rtmp); set_visible_maximum(props, settings, "max_video_bitrate_rtmps", rtmps); - set_visible_maximum(props, settings, "max_video_bitrate_hls", hls); - set_visible_maximum(props, settings, "max_video_bitrate_ftl", ftl); - - //Max Audio Bitrate - set_visible_maximum(props, settings, "max_audio_bitrate_rtmp", rtmp); set_visible_maximum(props, settings, "max_audio_bitrate_rtmps", rtmps); - set_visible_maximum(props, settings, "max_audio_bitrate_hls", hls); - set_visible_maximum(props, settings, "max_audio_bitrate_ftl", ftl); - - //Max FPS - set_visible_maximum(props, settings, "max_fps_rtmp", rtmp); set_visible_maximum(props, settings, "max_fps_rtmps", rtmps); +#endif + + bool hls = strcmp(protocol, "HLS") == 0; + obs_property_set_visible(obs_properties_get(props, "server_hls"), hls); + set_visible_maximum(props, settings, "max_video_bitrate_hls", hls); + set_visible_maximum(props, settings, "max_audio_bitrate_hls", hls); set_visible_maximum(props, settings, "max_fps_hls", hls); + +#if !FTL_DISABLED + bool ftl = strcmp(protocol, "FTL") == 0; + obs_property_set_visible(obs_properties_get(props, "server_ftl"), ftl); + set_visible_maximum(props, settings, "max_video_bitrate_ftl", ftl); + set_visible_maximum(props, settings, "max_audio_bitrate_ftl", ftl); set_visible_maximum(props, settings, "max_fps_ftl", ftl); +#endif //Ignore Max Toggle if (rtmp) @@ -547,21 +577,25 @@ try { "max_video_bitrate_rtmp", "max_audio_bitrate_rtmp", "max_fps_rtmp"); +#if !RTMPS_DISABLED if (rtmps) set_visible_ignore_maximum(props, settings, "max_video_bitrate_rtmps", "max_audio_bitrate_rtmps", "max_fps_rtmps"); +#endif if (hls) set_visible_ignore_maximum(props, settings, "max_video_bitrate_hls", "max_audio_bitrate_hls", "max_fps_hls"); +#if !FTL_DISABLED if (ftl) set_visible_ignore_maximum(props, settings, "max_video_bitrate_ftl", "max_audio_bitrate_ftl", "max_fps_ftl"); +#endif return true; } catch (const std::exception &ex) { @@ -591,9 +625,18 @@ obs_properties_t *service_factory::get_properties2(void *data) obs_property_set_modified_callback(p, modified_protocol); - for (size_t idx = 0; idx < protocols.size(); idx++) + for (size_t idx = 0; idx < protocols.size(); idx++) { +#if RTMPS_DISABLED + if (protocols[idx].compare("RTMPS") == 0) + continue; +#endif +#if FTL_DISABLED + if (protocols[idx].compare("FTL") == 0) + continue; +#endif obs_property_list_add_string(p, protocols[idx].c_str(), protocols[idx].c_str()); + } create_server_lists(props); From 9b13e63bbaa3f2aeef448c0401c21225795fb5b4 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 20 May 2021 13:42:04 +0200 Subject: [PATCH 21/23] WIP(UI): Add static function --- UI/window-basic-settings-stream.cpp | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index d59445e3e0491e..70ce4ce6b5468d 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -34,6 +34,48 @@ inline bool OBSBasicSettings::IsCustomService() const return ui->service->currentData().toInt() == (int)ListOpt::Custom; } +static inline bool WidgetChanged(QWidget *widget) +{ + return widget->property("changed").toBool(); +} + +static inline bool SetComboByValue(QComboBox *combo, const char *name) +{ + int idx = combo->findData(QT_UTF8(name)); + if (idx != -1) { + combo->setCurrentIndex(idx); + return true; + } + + return false; +} + +static inline QString GetComboData(QComboBox *combo) +{ + int idx = combo->currentIndex(); + if (idx == -1) + return QString(); + + return combo->itemData(idx).toString(); +} + +static void WriteJsonData(OBSPropertiesView *view, const char *path) +{ + char full_path[512]; + + if (!view || !WidgetChanged(view)) + return; + + int ret = GetProfilePath(full_path, sizeof(full_path), path); + if (ret > 0) { + obs_data_t *settings = view->GetSettings(); + if (settings) { + obs_data_save_json_safe(settings, full_path, "tmp", + "bak"); + } + } +} + void OBSBasicSettings::InitStreamPage() { ui->connectAccount2->setVisible(false); From 0af08e2b85a3cdcf65dc9654441e34611008b55b Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 20 May 2021 13:57:45 +0200 Subject: [PATCH 22/23] WIP(UI): Add CreateServicePropertyView --- UI/window-basic-settings-stream.cpp | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 70ce4ce6b5468d..c0d32708f7d8c7 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -131,6 +131,36 @@ void OBSBasicSettings::InitStreamPage() SLOT(UpdateMoreInfoLink())); } +OBSPropertiesView * +OBSBasicSettings::CreateServicePropertyView(const char *service, + const char *path, bool changed) +{ + obs_data_t *settings = obs_service_defaults(service); + OBSPropertiesView *view; + + if (path) { + char serviceJsonPath[512]; + int ret = GetProfilePath(serviceJsonPath, + sizeof(serviceJsonPath), path); + if (ret > 0) { + obs_data_t *data = obs_data_create_from_json_file_safe( + serviceJsonPath, "bak"); + obs_data_apply(settings, data); + obs_data_release(data); + } + } + + view = new OBSPropertiesView( + settings, service, + (PropertiesReloadCallback)obs_get_service_properties, 170); + view->setFrameShape(QFrame::StyledPanel); + view->setProperty("changed", QVariant(changed)); + QObject::connect(view, SIGNAL(Changed()), this, SLOT(Stream1Changed())); + + obs_data_release(settings); + return view; +} + void OBSBasicSettings::LoadStream1Settings() { bool ignoreRecommended = From ac10f992cd9a91c12314bc08c1cc87207e395bd4 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Fri, 28 May 2021 14:01:07 +0200 Subject: [PATCH 23/23] WIP UI --- UI/forms/OBSBasicSettings.ui | 399 ---------------------------- UI/window-basic-settings-stream.cpp | 211 +++++++++++---- UI/window-basic-settings.cpp | 20 +- UI/window-basic-settings.hpp | 37 +-- 4 files changed, 193 insertions(+), 474 deletions(-) diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index 28c07cdb4e8bea..0b817bf2957f88 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -845,19 +845,6 @@ - - - - - 0 - 0 - - - - Basic.AutoConfig.StreamPage.MoreInfo - - - @@ -877,392 +864,6 @@ - - - - - 0 - 0 - - - - 1 - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 170 - 19 - - - - - - - - - - Basic.AutoConfig.StreamPage.ConnectAccount - - - - - - - Qt::Horizontal - - - - 40 - 10 - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 170 - 19 - - - - - - - - - - Basic.AutoConfig.StreamPage.UseStreamKey - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - Basic.AutoConfig.StreamPage.Server - - - - - - - - 0 - 0 - - - - 1 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - Basic.AutoConfig.StreamPage.StreamKey - - - true - - - key - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - QLineEdit::Password - - - - - - - Show - - - - - - - - - - -4 - - - Basic.AutoConfig.StreamPage.GetStreamKey - - - - - - - - - - Qt::Horizontal - - - - 170 - 8 - - - - - - - - - - Basic.AutoConfig.StreamPage.ConnectAccount - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Basic.AutoConfig.StreamPage.DisconnectAccount - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Basic.Settings.Stream.BandwidthTestMode - - - - - - - Basic.Settings.Stream.Custom.UseAuthentication - - - - - - - Basic.Settings.Stream.Custom.Username - - - authUsername - - - - - - - - - - Basic.Settings.Stream.Custom.Password - - - authPw - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QLineEdit::Password - - - - - - - Show - - - - - - - - - - - - - Basic.Settings.Stream.TTVAddon - - - twitchAddonDropdown - - - - - - - Basic.Settings.Stream.IgnoreRecommended - - - - - - - - - - - - - - diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index c0d32708f7d8c7..3e87c570b7a69d 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -1,14 +1,15 @@ #include -#include +//#include +#include #include "window-basic-settings.hpp" #include "obs-frontend-api.h" #include "obs-app.hpp" #include "window-basic-main.hpp" #include "qt-wrappers.hpp" -#include "url-push-button.hpp" +//#include "url-push-button.hpp" -#ifdef BROWSER_AVAILABLE +/*#ifdef BROWSER_AVAILABLE #include #include "auth-oauth.hpp" #endif @@ -17,13 +18,16 @@ struct QCef; struct QCefCookieManager; extern QCef *cef; -extern QCefCookieManager *panel_cookies; +extern QCefCookieManager *panel_cookies;*/ -enum class ListOpt : int { +/*enum class ListOpt : int { ShowAll = 1, Custom, -}; +};*/ +#define SHOW_ALL "obs_show_all" + +/* enum class Section : int { Connect, StreamKey, @@ -32,7 +36,7 @@ enum class Section : int { inline bool OBSBasicSettings::IsCustomService() const { return ui->service->currentData().toInt() == (int)ListOpt::Custom; -} +}*/ static inline bool WidgetChanged(QWidget *widget) { @@ -78,11 +82,13 @@ static void WriteJsonData(OBSPropertiesView *view, const char *path) void OBSBasicSettings::InitStreamPage() { + /* ui->connectAccount2->setVisible(false); ui->disconnectAccount->setVisible(false); ui->bandwidthTestEnable->setVisible(false); ui->twitchAddonDropdown->setVisible(false); ui->twitchAddonLabel->setVisible(false); + */ int vertSpacing = ui->topStreamLayout->verticalSpacing(); @@ -90,17 +96,17 @@ void OBSBasicSettings::InitStreamPage() m.setBottom(vertSpacing / 2); ui->topStreamLayout->setContentsMargins(m); - m = ui->loginPageLayout->contentsMargins(); + /*m = ui->loginPageLayout->contentsMargins(); m.setTop(vertSpacing / 2); ui->loginPageLayout->setContentsMargins(m); m = ui->streamkeyPageLayout->contentsMargins(); m.setTop(vertSpacing / 2); - ui->streamkeyPageLayout->setContentsMargins(m); + ui->streamkeyPageLayout->setContentsMargins(m);*/ LoadServices(false); - ui->twitchAddonDropdown->addItem( + /*ui->twitchAddonDropdown->addItem( QTStr("Basic.Settings.Stream.TTVAddon.None")); ui->twitchAddonDropdown->addItem( QTStr("Basic.Settings.Stream.TTVAddon.BTTV")); @@ -129,6 +135,7 @@ void OBSBasicSettings::InitStreamPage() this, SLOT(UpdateKeyLink())); connect(ui->service, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateMoreInfoLink())); + */ } OBSPropertiesView * @@ -163,6 +170,31 @@ OBSBasicSettings::CreateServicePropertyView(const char *service, void OBSBasicSettings::LoadStream1Settings() { + loading = true; + //TODO: Legacy Service recovering + + //No legacy behaviour + const char *type = + config_has_user_value(main->Config(), "Stream1", "Service") + ? config_get_string(main->Config(), "Stream1", + "Service") + : "custom_service"; + + delete streamServiceProps; + streamServiceProps = CreateServicePropertyView(type, "service.json"); + ui->streamPage->layout()->addWidget(streamServiceProps); + + curStreamService = type; + + if (!SetComboByValue(ui->service, type)) { + const char *name = obs_service_get_display_name(type); + + ui->service->insertItem(0, QT_UTF8(name), QT_UTF8(type)); + SetComboByValue(ui->service, type); + } + + loading = false; + /* bool ignoreRecommended = config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); @@ -238,10 +270,18 @@ void OBSBasicSettings::LoadStream1Settings() QMetaObject::invokeMethod(this, "UpdateResFPSLimits", Qt::QueuedConnection); + */ } void OBSBasicSettings::SaveStream1Settings() { + curStreamService = GetComboData(ui->service); + + SaveComboData(ui->service, "Stream1", "Service"); + + WriteJsonData(streamServiceProps, "service.json"); + + /* bool customServer = IsCustomService(); const char *service_id = customServer ? "rtmp_custom" : "rtmp_common"; @@ -307,9 +347,10 @@ void OBSBasicSettings::SaveStream1Settings() main->auth->LoadUI(); SaveCheckBox(ui->ignoreRecommended, "Stream1", "IgnoreRecommended"); + */ } -void OBSBasicSettings::UpdateMoreInfoLink() +/*void OBSBasicSettings::UpdateMoreInfoLink() { if (IsCustomService()) { ui->moreInfoButton->hide(); @@ -336,9 +377,9 @@ void OBSBasicSettings::UpdateMoreInfoLink() ui->moreInfoButton->show(); } obs_properties_destroy(props); -} +}*/ -void OBSBasicSettings::UpdateKeyLink() +/*void OBSBasicSettings::UpdateKeyLink() { QString serviceName = ui->service->currentText(); QString customServer = ui->customServer->text(); @@ -386,10 +427,58 @@ void OBSBasicSettings::UpdateKeyLink() ui->getStreamKeyButton->setTargetUrl(QUrl(streamKeyLink)); ui->getStreamKeyButton->show(); } -} +}*/ void OBSBasicSettings::LoadServices(bool showAll) { + QStringList shortListIds; + shortListIds << "custom_service"; + shortListIds << "twitch"; + shortListIds << "twitch-oauth"; + shortListIds << "youtube"; + shortListIds << "facebook"; + shortListIds << "restream"; + shortListIds << "restream-oauth"; + shortListIds << "twitter"; + + const char *id; + size_t idx = 0; + + ui->service->blockSignals(true); + ui->service->clear(); + + while (obs_enum_service_types(idx++, &id)) { + const char *name = obs_service_get_display_name(id); + + QString qName = QT_UTF8(name); + QString qId = QT_UTF8(id); + + if (showAll || shortListIds.contains(qId)) + ui->service->addItem(qName, qId); + } + + if (!showAll) { + ui->service->addItem( + QTStr("Basic.AutoConfig.StreamPage.Service.ShowAll"), + QT_UTF8(SHOW_ALL)); + } else { + QSortFilterProxyModel *model = + new QSortFilterProxyModel(ui->service); + model->setSourceModel(ui->service->model()); + // Combo's current model must be reparented, + // Otherwise QComboBox::setModel() will delete it + ui->service->model()->setParent(model); + + model->setSortCaseSensitivity(Qt::CaseInsensitive); + + ui->service->setModel(model); + + ui->service->model()->sort(0); + } + + ui->service->blockSignals(false); + + /* obs_properties_t *props = obs_get_service_properties("rtmp_common"); OBSData settings = obs_data_create(); @@ -437,15 +526,34 @@ void OBSBasicSettings::LoadServices(bool showAll) obs_properties_destroy(props); ui->service->blockSignals(false); + */ } -static inline bool is_auth_service(const std::string &service) +/*static inline bool is_auth_service(const std::string &service) { return Auth::AuthType(service) != Auth::Type::None; -} +}*/ void OBSBasicSettings::on_service_currentIndexChanged(int) { + bool showAll = GetComboData(ui->service) == QT_UTF8(SHOW_ALL); + if (showAll) { + LoadServices(true); + return; + } + + QString service = GetComboData(ui->service); + if (!loading) { + bool loadSettings = service == curStreamService; + + delete streamServiceProps; + streamServiceProps = CreateServicePropertyView( + QT_TO_UTF8(service), + loadSettings ? "service.json" : nullptr, true); + ui->streamPage->layout()->addWidget(streamServiceProps); + } + + /* bool showMore = ui->service->currentData().toInt() == (int)ListOpt::ShowAll; if (showMore) @@ -507,9 +615,10 @@ void OBSBasicSettings::on_service_currentIndexChanged(int) OnAuthConnected(); } #endif + */ } -void OBSBasicSettings::UpdateServerList() +/*void OBSBasicSettings::UpdateServerList() { QString serviceName = ui->service->currentText(); bool showMore = ui->service->currentData().toInt() == @@ -544,9 +653,9 @@ void OBSBasicSettings::UpdateServerList() } obs_properties_destroy(props); -} +}*/ -void OBSBasicSettings::on_show_clicked() +/*void OBSBasicSettings::on_show_clicked() { if (ui->key->echoMode() == QLineEdit::Password) { ui->key->setEchoMode(QLineEdit::Normal); @@ -555,9 +664,9 @@ void OBSBasicSettings::on_show_clicked() ui->key->setEchoMode(QLineEdit::Password); ui->show->setText(QTStr("Show")); } -} +}*/ -void OBSBasicSettings::on_authPwShow_clicked() +/*void OBSBasicSettings::on_authPwShow_clicked() { if (ui->authPw->echoMode() == QLineEdit::Password) { ui->authPw->setEchoMode(QLineEdit::Normal); @@ -566,9 +675,9 @@ void OBSBasicSettings::on_authPwShow_clicked() ui->authPw->setEchoMode(QLineEdit::Password); ui->authPwShow->setText(QTStr("Show")); } -} +}*/ -OBSService OBSBasicSettings::SpawnTempService() +/*OBSService OBSBasicSettings::SpawnTempService() { bool custom = IsCustomService(); const char *service_id = custom ? "rtmp_custom" : "rtmp_common"; @@ -593,9 +702,9 @@ OBSService OBSBasicSettings::SpawnTempService() obs_service_release(newService); return newService; -} +}*/ -void OBSBasicSettings::OnOAuthStreamKeyConnected() +/*void OBSBasicSettings::OnOAuthStreamKeyConnected() { #ifdef BROWSER_AVAILABLE OAuthStreamKey *a = reinterpret_cast(auth.get()); @@ -622,9 +731,9 @@ void OBSBasicSettings::OnOAuthStreamKeyConnected() ui->streamStackWidget->setCurrentIndex((int)Section::StreamKey); #endif -} +}*/ -void OBSBasicSettings::OnAuthConnected() +/*void OBSBasicSettings::OnAuthConnected() { std::string service = QT_TO_UTF8(ui->service->currentText()); Auth::Type type = Auth::AuthType(service); @@ -637,9 +746,9 @@ void OBSBasicSettings::OnAuthConnected() stream1Changed = true; EnableApplyButton(true); } -} +}*/ -void OBSBasicSettings::on_connectAccount_clicked() +/*void OBSBasicSettings::on_connectAccount_clicked() { #ifdef BROWSER_AVAILABLE std::string service = QT_TO_UTF8(ui->service->currentText()); @@ -705,17 +814,17 @@ void OBSBasicSettings::on_useAuth_toggled() ui->authUsername->setVisible(use_auth); ui->authPwLabel->setVisible(use_auth); ui->authPwWidget->setVisible(use_auth); -} +}*/ void OBSBasicSettings::UpdateVodTrackSetting() { - bool enableForCustomServer = config_get_bool( - GetGlobalConfig(), "General", "EnableCustomServerVodTrack"); + /*bool enableForCustomServer = config_get_bool( + GetGlobalConfig(), "General", "EnableCustomServerVodTrack");*/ bool enableVodTrack = ui->service->currentText() == "Twitch"; bool wasEnabled = !!vodTrackCheckbox; - if (enableForCustomServer && IsCustomService()) - enableVodTrack = true; + /*if (enableForCustomServer && IsCustomService()) + enableVodTrack = true;*/ if (enableVodTrack == wasEnabled) return; @@ -793,13 +902,13 @@ void OBSBasicSettings::UpdateVodTrackSetting() } } -OBSService OBSBasicSettings::GetStream1Service() +/*OBSService OBSBasicSettings::GetStream1Service() { return stream1Changed ? SpawnTempService() : OBSService(main->GetService()); -} +}*/ -void OBSBasicSettings::UpdateServiceRecommendations() +/*void OBSBasicSettings::UpdateServiceRecommendations() { bool customServer = IsCustomService(); ui->ignoreRecommended->setVisible(!customServer); @@ -858,12 +967,12 @@ void OBSBasicSettings::UpdateServiceRecommendations() #undef ENFORCE_TEXT ui->enforceSettingsLabel->setText(text); -} +}*/ void OBSBasicSettings::DisplayEnforceWarning(bool checked) { - if (IsCustomService()) - return; + /*if (IsCustomService()) + return;*/ if (!checked) { SimpleRecordingEncoderChanged(); @@ -880,9 +989,9 @@ void OBSBasicSettings::DisplayEnforceWarning(bool checked) #undef ENFORCE_WARNING if (button == QMessageBox::No) { - QMetaObject::invokeMethod(ui->ignoreRecommended, "setChecked", + /*QMetaObject::invokeMethod(ui->ignoreRecommended, "setChecked", Qt::QueuedConnection, - Q_ARG(bool, false)); + Q_ARG(bool, false));*/ return; } @@ -959,17 +1068,17 @@ void OBSBasicSettings::UpdateResFPSLimits() if (idx == -1) return; - bool ignoreRecommended = ui->ignoreRecommended->isChecked(); + //bool ignoreRecommended = ui->ignoreRecommended->isChecked(); BPtr res_list; size_t res_count = 0; int max_fps = 0; - if (!IsCustomService() && !ignoreRecommended) { + /*if (!IsCustomService() && !ignoreRecommended) { OBSService service = GetStream1Service(); obs_service_get_supported_resolutions(service, &res_list, &res_count); obs_service_get_max_fps(service, &max_fps); - } + }*/ /* ------------------------------------ */ /* Check for enforced res/FPS */ @@ -1031,8 +1140,8 @@ void OBSBasicSettings::UpdateResFPSLimits() /* if the user was already on facebook with an incompatible * resolution, assume it's an upgrade */ if (lastServiceIdx == -1 && lastIgnoreRecommended == -1) { - ui->ignoreRecommended->setChecked(true); - ui->ignoreRecommended->setProperty("changed", true); + /*ui->ignoreRecommended->setChecked(true); + ui->ignoreRecommended->setProperty("changed", true);*/ stream1Changed = true; EnableApplyButton(true); UpdateResFPSLimits(); @@ -1064,11 +1173,11 @@ void OBSBasicSettings::UpdateResFPSLimits() Qt::QueuedConnection, Q_ARG(int, lastServiceIdx)); else - QMetaObject::invokeMethod(ui->ignoreRecommended, + /*QMetaObject::invokeMethod(ui->ignoreRecommended, "setChecked", Qt::QueuedConnection, - Q_ARG(bool, true)); - return; + Q_ARG(bool, true));*/ + return; } } @@ -1148,6 +1257,6 @@ void OBSBasicSettings::UpdateResFPSLimits() /* ------------------------------------ */ - lastIgnoreRecommended = (int)ignoreRecommended; + //lastIgnoreRecommended = (int)ignoreRecommended; lastServiceIdx = idx; } diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 6062c5f95c22a3..d68fe478e39118 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -417,7 +417,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->multiviewDrawAreas, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->multiviewLayout, COMBO_CHANGED, GENERAL_CHANGED); HookWidget(ui->service, COMBO_CHANGED, STREAM1_CHANGED); - HookWidget(ui->server, COMBO_CHANGED, STREAM1_CHANGED); + /*HookWidget(ui->server, COMBO_CHANGED, STREAM1_CHANGED); HookWidget(ui->customServer, EDIT_CHANGED, STREAM1_CHANGED); HookWidget(ui->key, EDIT_CHANGED, STREAM1_CHANGED); HookWidget(ui->bandwidthTestEnable, CHECK_CHANGED, STREAM1_CHANGED); @@ -425,7 +425,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->useAuth, CHECK_CHANGED, STREAM1_CHANGED); HookWidget(ui->authUsername, EDIT_CHANGED, STREAM1_CHANGED); HookWidget(ui->authPw, EDIT_CHANGED, STREAM1_CHANGED); - HookWidget(ui->ignoreRecommended, CHECK_CHANGED, STREAM1_CHANGED); + HookWidget(ui->ignoreRecommended, CHECK_CHANGED, STREAM1_CHANGED);*/ HookWidget(ui->outputMode, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutputPath, EDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleNoSpace, CHECK_CHANGED, OUTPUTS_CHANGED); @@ -760,8 +760,8 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) this, SLOT(SimpleRecordingEncoderChanged())); connect(ui->simpleOutAdvanced, SIGNAL(toggled(bool)), this, SLOT(SimpleRecordingEncoderChanged())); - connect(ui->ignoreRecommended, SIGNAL(toggled(bool)), this, - SLOT(SimpleRecordingEncoderChanged())); + /*connect(ui->ignoreRecommended, SIGNAL(toggled(bool)), this, + SLOT(SimpleRecordingEncoderChanged()));*/ connect(ui->simpleReplayBuf, SIGNAL(toggled(bool)), this, SLOT(SimpleReplayBufferChanged())); connect(ui->simpleOutputVBitrate, SIGNAL(valueChanged(int)), this, @@ -3661,6 +3661,10 @@ void OBSBasicSettings::SaveSettings() if (advancedChanged) SaveAdvancedSettings(); + if (stream1Changed || outputsChanged || videoChanged) { + //TODO: Check maximum bitrates and fps, supported resolutions and recommended settings + } + if (videoChanged || advancedChanged) main->ResetVideo(); @@ -4690,12 +4694,12 @@ void OBSBasicSettings::SimpleRecordingEncoderChanged() { QString qual = ui->simpleOutRecQuality->currentData().toString(); QString warning; - bool enforceBitrate = !ui->ignoreRecommended->isChecked(); - OBSService service = GetStream1Service(); + //bool enforceBitrate = !ui->ignoreRecommended->isChecked(); + //OBSService service = GetStream1Service(); delete simpleOutRecWarning; - if (enforceBitrate && service) { + /*if (enforceBitrate && service) { obs_data_t *videoSettings = obs_data_create(); obs_data_t *audioSettings = obs_data_create(); int oldVBitrate = ui->simpleOutputVBitrate->value(); @@ -4722,7 +4726,7 @@ void OBSBasicSettings::SimpleRecordingEncoderChanged() obs_data_release(videoSettings); obs_data_release(audioSettings); - } + }*/ if (qual == "Lossless") { if (!warning.isEmpty()) diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 4b5d352f20c42a..b9051d95a40c67 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -128,7 +128,7 @@ class OBSBasicSettings : public QDialog { OBSFFFormatDesc formats; - OBSPropertiesView *streamProperties = nullptr; + OBSPropertiesView *streamServiceProps = nullptr; OBSPropertiesView *streamEncoderProps = nullptr; OBSPropertiesView *recordEncoderProps = nullptr; @@ -140,6 +140,7 @@ class OBSBasicSettings : public QDialog { QString curNVENCPreset; QString curAMDPreset; + QString curStreamService; QString curAdvStreamEncoder; QString curAdvRecordEncoder; @@ -243,28 +244,32 @@ class OBSBasicSettings : public QDialog { /* stream */ void InitStreamPage(); - inline bool IsCustomService() const; + //inline bool IsCustomService() const; void LoadServices(bool showAll); - void OnOAuthStreamKeyConnected(); - void OnAuthConnected(); + //void OnOAuthStreamKeyConnected(); + //void OnAuthConnected(); QString lastService; int prevLangIndex; bool prevBrowserAccel; + + OBSPropertiesView *CreateServicePropertyView(const char *service, + const char *path, + bool changed = false); private slots: - void UpdateServerList(); - void UpdateKeyLink(); + //void UpdateServerList(); + //void UpdateKeyLink(); void UpdateVodTrackSetting(); - void UpdateServiceRecommendations(); + //void UpdateServiceRecommendations(); void RecreateOutputResolutionWidget(); void UpdateResFPSLimits(); - void UpdateMoreInfoLink(); + //void UpdateMoreInfoLink(); void DisplayEnforceWarning(bool checked); - void on_show_clicked(); - void on_authPwShow_clicked(); - void on_connectAccount_clicked(); - void on_disconnectAccount_clicked(); - void on_useStreamKey_clicked(); - void on_useAuth_toggled(); + //void on_show_clicked(); + //void on_authPwShow_clicked(); + //void on_connectAccount_clicked(); + //void on_disconnectAccount_clicked(); + //void on_useStreamKey_clicked(); + //void on_useAuth_toggled(); private: /* output */ @@ -330,7 +335,7 @@ private slots: int CurrentFLVTrack(); - OBSService GetStream1Service(); + //OBSService GetStream1Service(); private slots: void on_theme_activated(int idx); @@ -389,7 +394,7 @@ private slots: void SimpleStreamingEncoderChanged(); - OBSService SpawnTempService(); + //OBSService SpawnTempService(); void SetGeneralIcon(const QIcon &icon); void SetStreamIcon(const QIcon &icon);