diff --git a/text/0039-json-schemas/codecDefs.json b/text/0039-json-schemas/codecDefs.json new file mode 100644 index 0000000..894b1de --- /dev/null +++ b/text/0039-json-schemas/codecDefs.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://obsproject.com/schemas/codecDefs.json", + "title": "Codec Enums, Patterns and Properties", + "description": "Codec-related enums, patterns and properties used in OBS JSON Schemas", + "$comment": "Made to allow an easy way to add codec to schemas", + "$defs": { + "videoCodecEnum": { + "$comment": "Enumeration of video codecs", + "enum": ["h264","hevc","av1"] + }, + "audioCodecEnum": { + "$comment": "Enumeration of audio codecs", + "enum": ["aac","opus"] + }, + "codecProperties": { + "$comment": "Per-codec properties meant for obs-services schema", + "type": "object", + "properties": { + "h264": + { + "type": "object", + "title": "H264 Settings", + "description": "Properties related to the H264 codec", + "properties": { + "profile": { + "type": "string", + "title": "H264 Profile", + "enum": [ "baseline", "main", "high" ] + }, + "keyint": { + "type": "integer", + "title": "Keyframe Interval (seconds)", + "minimum": 0 + }, + "bframes": + { + "type": "integer", + "title": "B-Frames", + "minimum": 0 + } + }, + "minProperties": 1 + }, + "obs_x264": { + "type": "string", + "title": "x264 Encoder Options", + "description": "Options meant for the x264 encoder implementation with the id 'obs_x264'", + "minLength": 1 + } + } + } + } +} diff --git a/text/0039-json-schemas/examples/services.json b/text/0039-json-schemas/examples/services.json new file mode 100644 index 0000000..5d395f6 --- /dev/null +++ b/text/0039-json-schemas/examples/services.json @@ -0,0 +1,2650 @@ +{ + "$schema": "../obs-services.json", + "format_version": 1, + "services": [ + { + "id": "loola", + "name": "Loola.tv", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 2500 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "lovecast", + "name": "Lovecast", + "servers": [ + { + "name": "Default", + "url": "rtmp://live-a.lovecastapp.com:5222/app" + } + ], + "supported_resolutions": [ + "1920x1080", + "1280x720" + ], + "maximums": { + "video_bitrate": { + "h264": 8000 + }, + "framerate": 30, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "luzento", + "name": "Luzento.com - RTMP", + "stream_key_link": "https://cms.luzento.com/dashboard/stream-key?from=OBS", + "servers": [ + { + "name": "Primary", + "url": "rtmp://ingest.luzento.com/live" + }, + { + "name": "Primary (Test)", + "url": "rtmp://ingest.luzento.com/test" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 256 + } + }, + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "vimm", + "name": "VIMM", + "servers": [ + { + "name": "Europe: Frankfurt", + "url": "rtmp://eu.vimm.tv/live" + }, + { + "name": "North America: Montreal", + "url": "rtmp://us.vimm.tv/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 8000 + }, + "audio_bitrate": { + "aac": 320 + } + }, + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "web_tv", + "name": "Web.TV", + "servers": [ + { + "name": "Primary", + "url": "rtmp://live3.origins.web.tv/liveext" + } + ], + "maximums": { + "video_bitrate": { + "h264": 3500 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "goodgame", + "name": "GoodGame.ru", + "servers": [ + { + "name": "Моscow", + "url": "rtmp://msk.goodgame.ru:1940/live" + } + ] + }, + { + "id": "youstreamer", + "name": "YouStreamer", + "stream_key_link": "https://www.app.youstreamer.com/stream/", + "servers": [ + { + "name": "Moscow", + "url": "rtmp://push.youstreamer.com/in/" + } + ] + }, + { + "id": "vaughn_live", + "name": "Vaughn Live / iNSTAGIB", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 15000 + }, + "audio_bitrate": { + "aac": 320 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "breakers_tv", + "name": "Breakers.TV", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 15000 + }, + "audio_bitrate": { + "aac": 320 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "facebook", + "name": "Facebook Live", + "common": true, + "stream_key_link": "https://www.facebook.com/live/producer?ref=OBS", + "servers": [ + { + "name": "Default", + "url": "rtmps://rtmp-api.facebook.com:443/rtmp/" + } + ], + "supported_resolutions": [ + "640x360@30", + "640x360@60", + "852x480@30", + "852x480@60", + "1280x720@30", + "1280x720@60", + "1920x1080@30", + "1920x1080@60" + ], + "maximums": { + "video_bitrate_matrix": { + "640x360@30": { + "h264": 1000 + }, + "640x360@60": { + "h264": 1500 + }, + "852x480@30": { + "h264": 2000 + }, + "852x480@60": { + "h264": 3000 + }, + "1280x720@30": { + "h264": 4000 + }, + "1280x720@60": { + "h264": 6000 + }, + "1920x1080@30": { + "h264": 6000 + }, + "1920x1080@60": { + "h264": 9000 + } + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "castr", + "name": "Castr.io", + "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" + } + ], + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "boomstream", + "name": "Boomstream", + "servers": [ + { + "name": "Default", + "url": "rtmp://live.boomstream.com/live" + } + ] + }, + { + "id": "meridix", + "name": "Meridix Live Sports Platform", + "servers": [ + { + "name": "Primary", + "url": "rtmp://publish.meridix.com/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 3500 + } + } + }, + { + "id": "afreecatv", + "name": "AfreecaTV", + "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": "South America : Brazil", + "url": "rtmp://rtmp-brz.afreecatv.com/app" + }, + { + "name": "Europe : UK", + "url": "rtmp://rtmp-uk.afreecatv.com/app" + }, + { + "name": "Asia : Singapore", + "url": "rtmp://rtmp-sgp.afreecatv.com/app" + } + ], + "maximums": { + "video_bitrate": { + "h264": 8000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "cam4", + "name": "CAM4", + "servers": [ + { + "name": "CAM4", + "url": "rtmp://origin.cam4.com/cam4-origin-live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 3000 + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "profile": "baseline", + "keyint": 1 + } + } + }, + { + "id": "eplay", + "name": "ePlay", + "servers": [ + { + "name": "ePlay Primary", + "url": "rtmp://live.eplay.link/origin" + } + ], + "maximums": { + "video_bitrate": { + "h264": 7500 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "picarto", + "name": "Picarto", + "servers": [ + { + "name": "Autoselect closest server", + "url": "rtmp://live.us.picarto.tv/golive" + }, + { + "name": "Los Angeles, USA", + "url": "rtmp://live.us-losangeles.picarto.tv/golive" + }, + { + "name": "Dallas, USA", + "url": "rtmp://live.us-dallas.picarto.tv/golive" + }, + { + "name": "Miami, USA", + "url": "rtmp://live.us-miami.picarto.tv/golive" + }, + { + "name": "New York, USA", + "url": "rtmp://live.us-newyork.picarto.tv/golive" + }, + { + "name": "Europe", + "url": "rtmp://live.eu-west1.picarto.tv/golive" + } + ], + "maximums": { + "video_bitrate": { + "h264": 3500 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "livestream", + "name": "Livestream", + "servers": [ + { + "name": "Primary", + "url": "rtmp://rtmpin.livestreamingest.com/rtmpin" + } + ] + }, + { + "id": "uscreen", + "name": "Uscreen", + "servers": [ + { + "name": "Default", + "url": "rtmp://global-live.uscreen.app:5222/app" + } + ], + "maximums": { + "video_bitrate": { + "h264": 8000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "stripchat", + "name": "Stripchat", + "servers": [ + { + "name": "Auto", + "url": "rtmp://s-sd.doppiocdn.com/ext" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + }, + "obs_x264": "tune=zerolatency" + } + }, + { + "id": "camsoda", + "name": "CamSoda", + "servers": [ + { + "name": "North America", + "url": "rtmp://obs-ingest-na.livemediahost.com/cam_obs" + }, + { + "name": "South America", + "url": "rtmp://obs-ingest-sa.livemediahost.com/cam_obs" + }, + { + "name": "Asia", + "url": "rtmp://obs-ingest-as.livemediahost.com/cam_obs" + }, + { + "name": "Europe", + "url": "rtmp://obs-ingest-eu.livemediahost.com/cam_obs" + }, + { + "name": "Oceania", + "url": "rtmp://obs-ingest-oc.livemediahost.com/cam_obs" + } + ], + "supported_resolutions": [ + "1920x1080", + "1280x720", + "852x480", + "480x360" + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "framerate": 30, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "obs_x264": "tune=zerolatency" + } + }, + { + "id": "chartubate", + "name": "Chaturbate", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 50000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "wpstream", + "name": "WpStream", + "more_info_link": "https://wpstream.net/obs-more-info", + "stream_key_link": "https://wpstream.net/obs-get-stream-key", + "servers": [ + { + "name": "Closest server - Automatic", + "url": "rtmp://ingest.wpstream.net/golive" + }, + { + "name": "North America", + "url": "rtmp://ingest-na.wpstream.net/golive" + }, + { + "name": "Europe", + "url": "rtmp://ingest-eu.wpstream.net/golive" + }, + { + "name": "Asia", + "url": "rtmp://ingest-as.wpstream.net/golive" + }, + { + "name": "South America", + "url": "rtmp://ingest-sa.wpstream.net/golive" + }, + { + "name": "Australia & Oceania", + "url": "rtmp://ingest-au.wpstream.net/golive" + } + ], + "maximums": { + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "twitter", + "name": "Twitter", + "common": true, + "stream_key_link": "https://studio.twitter.com/producer/sources", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 12000 + }, + "framerate": 60, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "keyint": 3 + } + } + }, + { + "id": "switchboard_live", + "name": "Switchboard Live", + "servers": [ + { + "name": "Global Zone (geo based)", + "url": "rtmp://ingest-global.switchboard.zone/live" + }, + { + "name": "US Zone (geo based)", + "url": "rtmp://ingest-us.switchboard.zone/live" + }, + { + "name": "US West", + "url": "rtmp://ingest-us-west.switchboard.zone/live" + }, + { + "name": "US East", + "url": "rtmp://ingest-us-east.switchboard.zone/live" + }, + { + "name": "US Central", + "url": "rtmp://ingest-us-central.switchboard.zone/live" + }, + { + "name": "South America", + "url": "rtmp://ingest-sa.switchboard.zone/live" + }, + { + "name": "EU (auto)", + "url": "rtmp://ingest-eu.switchboard.zone/live" + }, + { + "name": "Australia", + "url": "rtmp://ingest-au.switchboard.zone/live" + }, + { + "name": "APAC (auto)", + "url": "rtmp://ingest-asia.switchboard.zone/live" + }, + { + "name": "UK", + "url": "rtmp://ingest-uk.switchboard.zone/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 2 + } + } + }, + { + "id": "looch", + "name": "Looch", + "servers": [ + { + "name": "Primary Looch ingest server", + "url": "rtmp://ingest.looch.tv/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "eventials", + "name": "Eventials", + "servers": [ + { + "name": "Default", + "url": "rtmp://transmission.eventials.com/eventialsLiveOrigin" + } + ], + "maximums": { + "video_bitrate": { + "h264": 900 + }, + "audio_bitrate": { + "aac": 96 + } + }, + "recommended": { + "h264": { + "profile": "baseline", + "keyint": 1 + } + } + }, + { + "id": "eventlive", + "name": "EventLive.pro", + "servers": [ + { + "name": "Default", + "url": "rtmp://go.eventlive.pro/live" + } + ], + "supported_resolutions": [ + "1920x1080", + "1280x720" + ], + "maximums": { + "video_bitrate": { + "h264": 3000 + }, + "framerate": 30, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "lahzenegar", + "name": "Lahzenegar - StreamG | لحظه‌نگار - استریمجی", + "servers": [ + { + "name": "Primary", + "url": "rtmp://rtmp.lahzecdn.com/pro" + }, + { + "name": "Iran", + "url": "rtmp://rtmp-iran.lahzecdn.com/pro" + } + ], + "maximums": { + "video_bitrate": { + "h264": 4000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "mylive", + "name": "MyLive", + "servers": [ + { + "name": "Default", + "url": "rtmp://stream.mylive.in.th/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 7000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "trovo", + "name": "Trovo", + "stream_key_link": "https://studio.trovo.live/mychannel/stream", + "servers": [ + { + "name": "Default", + "url": "rtmp://livepush.trovo.live/live/" + } + ], + "maximums": { + "video_bitrate": { + "h264": 9000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "mixcloud", + "name": "Mixcloud", + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.mixcloud.com/broadcast" + } + ], + "supported_resolutions": [ + "1280x720", + "852x480", + "480x360" + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "framerate": 30, + "audio_bitrate": { + "aac": 320 + } + }, + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "sermonaudio", + "name": "SermonAudio Cloud", + "servers": [ + { + "name": "Primary", + "url": "rtmp://webcast.sermonaudio.com/sa" + } + ], + "maximums": { + "video_bitrate": { + "h264": 2000 + }, + "audio_bitrate": { + "aac": 128 + } + } + }, + { + "id": "vimeo", + "name": "Vimeo", + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.cloud.vimeo.com/live" + } + ] + }, + { + "id": "aparat", + "name": "Aparat", + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.cdn.asset.aparat.com:443/event" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 320 + } + }, + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "kakaotv", + "name": "KakaoTV", + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.play.kakao.com/kakaotv" + } + ], + "maximums": { + "video_bitrate": { + "h264": 8000 + }, + "audio_bitrate": { + "aac": 192 + } + } + }, + { + "id": "piczel_tv", + "name": "Piczel.tv", + "servers": [ + { + "name": "Default", + "url": "rtmp://piczel.tv:1935/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 2500 + }, + "audio_bitrate": { + "aac": 256 + } + }, + "recommended": { + "h264": { + "keyint": 4 + }, + "obs_x264": "tune=zerolatency" + } + }, + { + "id": "stage_ten", + "name": "STAGE TEN", + "servers": [ + { + "name": "STAGE TEN", + "url": "rtmps://app-rtmp.stageten.tv:443/stageten" + } + ], + "maximums": { + "video_bitrate": { + "h264": 4000 + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "profile": "baseline", + "keyint": 2 + } + } + }, + { + "id": "dlive", + "name": "DLive", + "servers": [ + { + "name": "Default", + "url": "rtmp://stream.dlive.tv/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "lightcast_com", + "name": "Lightcast.com", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "bongacams", + "name": "Bongacams", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "tune=zerolatency" + } + }, + { + "id": "chathostess", + "name": "Chathostess", + "servers": [ + { + "name": "Chathostess - Backup", + "url": "rtmp://wowza05.foobarweb.com/cmschatsys_video" + } + ], + "maximums": { + "video_bitrate": { + "h264": 3600 + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "onlyfans", + "name": "OnlyFans.com", + "servers": [ + { + "name": "USA", + "url": "rtmp://route0.onlyfans.com/live" + }, + { + "name": "Europe", + "url": "rtmp://route0-dc2.onlyfans.com/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 2500 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + }, + "obs_x264": "tune=zerolatency" + } + }, + { + "id": "steam", + "name": "Steam", + "servers": [ + { + "name": "Default", + "url": "rtmp://ingest-rtmp.broadcast.steamcontent.com/app" + } + ], + "maximums": { + "video_bitrate": { + "h264": 7000 + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 2 + } + } + }, + { + "id": "konduit", + "name": "Konduit.live", + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.konduit.live/live" + } + ], + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "loco", + "name": "LOCO", + "servers": [ + { + "name": "Default", + "url": "rtmp://ivory-ingest.getloconow.com:1935/stream" + } + ], + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "niconico-premium", + "name": "niconico, premium member (ニコニコ生放送 プレミアム会員)", + "servers": [ + { + "name": "Default", + "url": "rtmp://aliveorigin.dmc.nico/named_input" + } + ], + "maximums": { + "video_bitrate": { + "h264": 5808 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 2 + }, + "obs_x264": "tune=zerolatency" + } + }, + { + "id": "niconico-free", + "name": "niconico, free member (ニコニコ生放送 一般会員)", + "servers": [ + { + "name": "Default", + "url": "rtmp://aliveorigin.dmc.nico/named_input" + } + ], + "maximums": { + "video_bitrate": { + "h264": 904 + }, + "audio_bitrate": { + "aac": 96 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 2 + }, + "obs_x264": "tune=zerolatency" + } + }, + { + "id": "wasd_tv", + "name": "WASD.TV", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 10000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "xlovecam", + "name": "XLoveCam.com", + "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" + } + ], + "recommended": { + "obs_x264": "scenecut=0" + } + }, + { + "id": "angelthump", + "name": "AngelThump", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 3500 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 2 + } + } + }, + { + "id": "api_video", + "name": "api.video", + "servers": [ + { + "name": "Default", + "url": "rtmp://broadcast.api.video/s" + } + ], + "maximums": { + "video_bitrate": { + "h264": 20000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "mux", + "name": "Mux", + "servers": [ + { + "name": "Global (RTMPS)", + "url": "rtmps://global-live.mux.com:443/app" + }, + { + "name": "Global (RTMP)", + "url": "rtmp://global-live.mux.com:5222/app" + } + ], + "maximums": { + "video_bitrate": { + "h264": 5000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "viloud", + "name": "Viloud", + "servers": [ + { + "name": "Default", + "url": "rtmp://live.viloud.tv:5222/app" + } + ], + "maximums": { + "video_bitrate": { + "h264": 5000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "myfreecams", + "name": "MyFreeCams", + "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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 10000 + }, + "framerate": 60, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 1 + }, + "obs_x264": "tune=zerolatency scenecut=0" + } + }, + { + "id": "polystreamer", + "name": "PolyStreamer.com", + "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" + } + ], + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "glimesh", + "name": "Glimesh", + "stream_key_link": "https://glimesh.tv/users/settings/stream", + "servers": [ + { + "name": "North America - Chicago, United States", + "protocol": "FTL", + "url": "ingest.kord.live.glimesh.tv" + }, + { + "name": "North America - New York, United States", + "protocol": "FTL", + "url": "ingest.kjfk.live.glimesh.tv" + }, + { + "name": "North America - San Francisco, United States", + "protocol": "FTL", + "url": "ingest.ksfo.live.glimesh.tv" + }, + { + "name": "North America - Toronto, Canada", + "protocol": "FTL", + "url": "ingest.cyyz.live.glimesh.tv" + }, + { + "name": "South America - Sao Paulo, Brazil", + "protocol": "FTL", + "url": "ingest.sbgr.live.glimesh.tv" + }, + { + "name": "Europe - Amsterdam, Netherlands", + "protocol": "FTL", + "url": "ingest.eham.live.glimesh.tv" + }, + { + "name": "Europe - Frankfurt, Germany", + "protocol": "FTL", + "url": "ingest.eddf.live.glimesh.tv" + }, + { + "name": "Europe - London, United Kingdom", + "protocol": "FTL", + "url": "ingest.egll.live.glimesh.tv" + }, + { + "name": "Asia - Bangalore, India", + "protocol": "FTL", + "url": "ingest.vobl.live.glimesh.tv" + }, + { + "name": "Asia - Singapore", + "protocol": "FTL", + "url": "ingest.wsss.live.glimesh.tv" + }, + { + "name": "Australia - Sydney, Australia", + "protocol": "FTL", + "url": "ingest.yssy.live.glimesh.tv" + }, + { + "name": "North America - Chicago, United States", + "url": "rtmp://ingest.kord.live.glimesh.tv" + }, + { + "name": "North America - New York, United States", + "url": "rtmp://ingest.kjfk.live.glimesh.tv" + }, + { + "name": "North America - San Francisco, United States", + "url": "rtmp://ingest.ksfo.live.glimesh.tv" + }, + { + "name": "North America - Toronto, Canada", + "url": "rtmp://ingest.cyyz.live.glimesh.tv" + }, + { + "name": "South America - Sao Paulo, Brazil", + "url": "rtmp://ingest.sbgr.live.glimesh.tv" + }, + { + "name": "Europe - Amsterdam, Netherlands", + "url": "rtmp://ingest.eham.live.glimesh.tv" + }, + { + "name": "Europe - Frankfurt, Germany", + "url": "rtmp://ingest.eddf.live.glimesh.tv" + }, + { + "name": "Europe - London, United Kingdom", + "url": "rtmp://ingest.egll.live.glimesh.tv" + }, + { + "name": "Asia - Bangalore, India", + "url": "rtmp://ingest.vobl.live.glimesh.tv" + }, + { + "name": "Asia - Singapore", + "url": "rtmp://ingest.wsss.live.glimesh.tv" + }, + { + "name": "Australia - Sydney, Australia", + "url": "rtmp://ingest.yssy.live.glimesh.tv" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "opus": 160, + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "openrec_tv", + "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", + "servers": [ + { + "name": "Default", + "url": "rtmp://a.station.openrec.tv:1935/live1" + } + ], + "maximums": { + "video_bitrate": { + "h264": 5000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "nanostream", + "name": "nanoStream Cloud / bintu", + "more_info_link": "https://www.nanocosmos.de/obs", + "stream_key_link": "https://bintu-cloud-frontend.nanocosmos.de/organisation", + "servers": [ + { + "name": "bintu-stream global ingest (rtmp)", + "url": "rtmp://bintu-stream.nanocosmos.de/live" + }, + { + "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-vtrans.nanocosmos.de/live" + }, + { + "name": "bintu-vtrans global ingest with transcoding/ABR (rtmps)", + "url": "rtmps://bintu-vtrans.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" + } + ], + "maximums": { + "video_bitrate": { + "h264": 5000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "baseline", + "keyint": 2 + }, + "obs_x264": "tune=zerolatency b-pyramid=0 scenecut=0" + } + }, + { + "id": "bilibili", + "name": "Bilibili Live - RTMP | 哔哩哔哩直播 - RTMP", + "more_info_link": "https://link.bilibili.com/p/help/index#/tools-tutorial?id=9", + "stream_key_link": "https://link.bilibili.com/p/center/index#/my-room/start-live", + "servers": [ + { + "name": "Default | 默认", + "url": "rtmp://live-push.bilivideo.com/live-bvc/" + } + ] + }, + { + "id": "volume_com", + "name": "Volume.com", + "stream_key_link": "https://volume.com/b?show_key=1&webrtc=0", + "servers": [ + { + "name": "Default - Recommended", + "url": "rtmp://live.volume.com/live-origin" + }, + { + "name": "US - West", + "url": "rtmp://live-pdx.volume.com/live-origin" + }, + { + "name": "US - East", + "url": "rtmp://live-ash.volume.com/live-origin" + } + ], + "maximums": { + "video_bitrate": { + "h264": 20000 + }, + "framerate": 60 + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "boxcast", + "name": "BoxCast", + "stream_key_link": "https://dashboard.boxcast.com/#/sources", + "servers": [ + { + "name": "BoxCast", + "url": "rtmp://rtmp.boxcast.com/live" + } + ] + }, + { + "id": "disciple", + "name": "Disciple Media", + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.disciplemedia.com/b-fme" + } + ] + }, + { + "id": "jio_games", + "name": "Jio Games", + "servers": [ + { + "name": "Primary", + "url": "rtmp://livepub1.api.engageapps.jio/live" + }, + { + "name": "Secondary", + "url": "rtmp://livepub2.api.engageapps.jio/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 32000 + }, + "audio_bitrate": { + "aac": 256 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "kuaishou", + "name": "Kuaishou Live", + "stream_key_link": "https://studio.kuaishou.com/live/list", + "servers": [ + { + "name": "Default", + "url": "rtmp://open-push.voip.yximgs.com/gifshow/" + }, + { + "name": "North America", + "url": "rtmp://tx.push.yximgs.com/live/" + } + ] + }, + { + "id": "ultreon", + "name": "Utreon", + "servers": [ + { + "name": "Default", + "url": "rtmp://live.utreon.com:5222/app" + } + ], + "maximums": { + "video_bitrate": { + "h264": 5000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "autistici_org", + "name": "Autistici.org Live", + "servers": [ + { + "name": "Default", + "url": "rtmp://live.autistici.org/ingest" + } + ], + "maximums": { + "video_bitrate": { + "h264": 2500 + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "phonelivestreaming", + "name": "PhoneLiveStreaming", + "stream_key_link": "https://app.phonelivestreaming.com/media/rtmp", + "servers": [ + { + "name": "PhoneLiveStreaming", + "url": "rtmp://live.phonelivestreaming.com/live/" + } + ], + "maximums": { + "video_bitrate": { + "h264": 128 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "manyvids", + "name": "ManyVids", + "servers": [ + { + "name": "Default", + "url": "rtmp://rtmp.str.manyvids.com:1935/live_stream/" + } + ], + "supported_resolutions": [ + "960x540@30", + "1280x720@30" + ], + "maximums": { + "video_bitrate_matrix": { + "960x540@30": { + "h264": 3000 + }, + "1280x720@30": { + "h264": 4000 + } + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "fantasy_club", + "name": "Fantasy.Club", + "more_info_link": "https://help.fantasy.club/", + "stream_key_link": "https://fantasy.club/app/create-content/stream-now", + "servers": [ + { + "name": "US: East", + "url": "rtmp://live-east.fantasy.club/live" + }, + { + "name": "US: West", + "url": "rtmp://live-west.fantasy.club/live" + }, + { + "name": "Europe", + "url": "rtmp://live-eu.fantasy.club/live" + }, + { + "name": "South America", + "url": "rtmp://live-sa.fantasy.club/live" + } + ], + "supported_resolutions": [ + "852x480@30", + "1280x720@30", + "1280x720@60", + "1920x1080@30", + "1920x1080@60" + ], + "maximums": { + "video_bitrate_matrix": { + "852x480@30": { + "h264": 1200 + }, + "1280x720@30": { + "h264": 3600 + }, + "1280x720@60": { + "h264": 4200 + }, + "1920x1080@30": { + "h264": 5000 + }, + "1920x1080@60": { + "h264": 7200 + } + }, + "audio_bitrate": { + "aac": 196 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 2 + }, + "obs_x264": "scenecut=0" + } + }, + { + "id": "shareplay", + "name": "Shareplay", + "more_info_link": "https://shareplay.tv", + "servers": [ + { + "name": "Default", + "url": "rtmp://shareplay.tv:833/live" + } + ], + "supported_resolutions": [ + "640x360@30", + "640x360@60", + "852x480@30", + "852x480@60", + "1280x720@30", + "1280x720@60", + "1920x1080@30", + "1920x1080@60" + ], + "maximums": { + "video_bitrate_matrix": { + "640x360@30": { + "h264": 1000 + }, + "640x360@60": { + "h264": 1500 + }, + "852x480@30": { + "h264": 2000 + }, + "852x480@60": { + "h264": 3000 + }, + "1280x720@30": { + "h264": 4000 + }, + "1280x720@60": { + "h264": 6000 + }, + "1920x1080@30": { + "h264": 6000 + }, + "1920x1080@60": { + "h264": 9000 + } + }, + "audio_bitrate": { + "aac": 128 + } + }, + "recommended": { + "h264": { + "profile": "main", + "keyint": 2 + } + } + }, + { + "id": "sympla", + "name": "Sympla", + "servers": [ + { + "name": "Sympla RTMP", + "url": "rtmp://rtmp.sympla.com.br:5222/app" + } + ], + "maximums": { + "video_bitrate": { + "h264": 5000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "mildom", + "name": "Mildom", + "more_info_link": "https://www.mildom.com/course/pc", + "stream_key_link": "https://www.mildom.com/creator/live", + "servers": [ + { + "name": "Global", + "url": "rtmp://txlvb-push.mildom.tv/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "nonolive", + "name": "Nonolive", + "more_info_link": "https://wia.nonolive.com/views/obs_assistant_tutorial.html", + "stream_key_link": "https://www.nonolive.com/room_setting", + "servers": [ + { + "name": "Asia: Hong Kong, China", + "url": "rtmp://live-hk-zl.nonolive.tv/live" + }, + { + "name": "Asia: Jakarta, Indonesia", + "url": "rtmp://live-jkt-zl.nonolive.tv/live" + }, + { + "name": "EU: Frankfurt, DE", + "url": "rtmp://live-fra-zl.nonolive.tv/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 6000 + }, + "audio_bitrate": { + "aac": 160 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "streamvi", + "name": "StreamVi", + "stream_key_link": "https://streamvi.ru/settings", + "servers": [ + { + "name": "Default", + "url": "rtmp://live-default.streamvi.ru/live" + }, + { + "name": "Russia (Moscow)", + "url": "rtmp://live-msk.streamvi.ru/live" + }, + { + "name": "EU Central: Germany", + "url": "rtmp://live1.streamvi.ru/live" + } + ] + }, + { + "id": "livepush", + "name": "Livepush", + "more_info_link": "https://docs.livepush.io/en/articles/5065323-how-to-stream-live-from-obs-to-livepush", + "servers": [ + { + "name": "Livepush Global (Default)", + "url": "rtmp://dc-global.livepush.io/live" + }, + { + "name": "Chicago, US", + "url": "rtmp://us-central-ch.livepush.io/live" + }, + { + "name": "New York, US", + "url": "rtmp://us-east-ny.livepush.io/live" + }, + { + "name": "Los Angeles, US", + "url": "rtmp://us-west-la.livepush.io/live" + }, + { + "name": "Miami, US", + "url": "rtmp://us-south-mia.livepush.io/live" + }, + { + "name": "Dallas, US", + "url": "rtmp://us-central-dal.livepush.io/live" + }, + { + "name": "Montreal, CA", + "url": "rtmp://ca-central-mon.livepush.io/live" + }, + { + "name": "Toronto, CA", + "url": "rtmp://ca-south-tor.livepush.io/live" + }, + { + "name": "Sydney, AU", + "url": "rtmp://au-east-syd.livepush.io/live" + }, + { + "name": "London, UK", + "url": "rtmp://uk-central-ldn.livepush.io/live" + }, + { + "name": "Milan, Italy", + "url": "rtmp://it-north-mln.livepush.io/live" + }, + { + "name": "Paris, FR", + "url": "rtmp://fr-central-par.livepush.io/live" + }, + { + "name": "Singapore", + "url": "rtmp://as-southeast-sg.livepush.io/live" + }, + { + "name": "Bangalore, IN", + "url": "rtmp://in-south-blr.livepush.io/live" + }, + { + "name": "Turkiye", + "url": "rtmp://tr-north-ist.livepush.io/live" + } + ], + "maximums": { + "video_bitrate": { + "h264": 16000 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "vindral", + "name": "Vindral", + "more_info_link": "https://docs.vindral.com/docs/vindral-cdn/", + "servers": [ + { + "name": "eu-north", + "url": "rtmps://rtmp.eu-north.cdn.vindral.com/publish" + }, + { + "name": "eu-west", + "url": "rtmps://rtmp.eu-west.cdn.vindral.com/publish" + } + ], + "maximums": { + "video_bitrate": { + "h264": 20000 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "profile": "high", + "keyint": 1 + } + } + }, + { + "id": "whowhatch", + "name": "Whowatch (ふわっち)", + "more_info_link": "https://whowatch.tv/help/encoder", + "stream_key_link": "https://whowatch.tv/publish", + "servers": [ + { + "name": "default", + "url": "rtmp://live.whowatch.tv/live/" + } + ], + "maximums": { + "video_bitrate": { + "h264": 1800 + }, + "audio_bitrate": { + "aac": 192 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "irltoolkit", + "name": "IRLToolkit", + "stream_key_link": "https://irl.run/settings/ingest/", + "servers": [ + { + "name": "Global (Recommended)", + "url": "rtmp://stream.global.irl.run/ingest" + }, + { + "name": "Los Angeles, US", + "url": "rtmp://stream.lax.irl.run/ingest" + }, + { + "name": "New York, US", + "url": "rtmp://stream.ewr.irl.run/ingest" + }, + { + "name": "Rotterdam, NL", + "url": "rtmp://stream.rtm.irl.run/ingest" + }, + { + "name": "Singapore", + "url": "rtmp://stream.sin.irl.run/ingest" + }, + { + "name": "Tokyo, JP", + "url": "rtmp://stream.tyo.irl.run/ingest" + } + ], + "maximums": { + "video_bitrate": { + "h264": 20000 + } + }, + "recommended": { + "h264": { + "keyint": 2 + } + } + }, + { + "id": "bitmovin", + "name": "Bitmovin", + "more_info_link": "https://developer.bitmovin.com/docs/overview", + "stream_key_link": "https://bitmovin.com/dashboard/streams?streamsTab=LIVE", + "servers": [ + { + "name": "Streams Live", + "url": "rtmp://live-input.bitmovin.com/streams" + } + ], + "recommended": { + "h264": { + "keyint": 2 + } + } + } + ] +} \ No newline at end of file diff --git a/text/0039-json-schemas/obs-services.json b/text/0039-json-schemas/obs-services.json new file mode 100644 index 0000000..8701e11 --- /dev/null +++ b/text/0039-json-schemas/obs-services.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://obsproject.com/schemas/obs-service.json", + "title": "OBS Services Plugin JSON Schema", + "type": "object", + "properties": { + "format_version": { + "type": "integer", + "const": 1 + }, + "services": { + "type": "array", + "items": { + "allOf": [ + { + "title": "Service", + "properties": { + "id": { + "type": "string", + "title": "Service ID", + "description": "Human readable identifier used to register the service in OBS\nMaking it human readable is meant to allow users to use it through scripts and plugins", + "pattern": "^[a-z0-9_\\-]+$", + "minLength": 1 + }, + "name": { + "type": "string", + "title": "Service Name", + "description": "Name of the streaming service. Will be displayed in the Service dropdown.", + "minLength": 1 + }, + "common": { + "type": "boolean", + "title": "Property reserved to OBS Project developers", + "description": "Whether or not the service is shown in the list before it is expanded to all services by the user.", + "default": false + }, + "github_logins": { + "type": "array", + "title": "Github logins", + "description": "Login to ping on Github when the services check is failing.\n 'obsproject' is used as a placeholder.", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + } + }, + "$commit": "Forbid 'common' being set if false", + "if": { "properties": { "common": { "const": false } } }, + "then": { "properties": { "common": { "description": "This property is unneeded if set to false" , "const": true } } }, + "required": ["id","name", "github_logins"] + }, + { + "$comment": "Add base service JSON schema", + "$ref": "service.json" + }, + { + "$comment": "Add protocol properties", + "$ref": "protocolDefs.json#/$defs/protocolProperties" + } + ], + "unevaluatedProperties": false + }, + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + } + }, + "$comment": "Support '$schema' without making it a proper property", + "if": { "not": { "maxProperties": 2 } }, + "then": { "properties": { "$schema": { "type": "string" } } }, + "required": ["format_version", "services"], + "unevaluatedProperties": false +} diff --git a/text/0039-json-schemas/protocolDefs.json b/text/0039-json-schemas/protocolDefs.json new file mode 100644 index 0000000..a7a19e6 --- /dev/null +++ b/text/0039-json-schemas/protocolDefs.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://obsproject.com/schemas/protocolDefs.json", + "title": "Protocol Enums, Patterns and Properties", + "description": "Protocol-related enums, patterns and properties used in OBS JSON Schemas", + "$comment": "Made to allow an easy way to add protocol to schemas", + "$defs": { + "protocolEnum": { + "$comment": "Enumeration of protocols", + "enum": ["RTMP","RTMPS","HLS","SRT","RIST","WHIP"] + }, + "protocolMapEnum": { + "$comment": "Enumeration of protocols (with '*' as any) that have various compatible codecs", + "enum": ["*","RTMP","RTMPS","HLS","SRT","RIST","WHIP"] + }, + "serverPattern": { + "$comment": "Pattern to enforce a supported server URL", + "pattern": "^(rtmps?|https?|srt|rist)://" + }, + "protocolProperties": { + "$comment": "Per-protocol properties meant for obs-services schema", + "type": "object", + "properties": { + "SRT": { + "type": "object", + "title": "SRT Properties", + "description": "Properties related to the SRT protocol", + "properties": { + "stream_id": { + "type": "boolean", + "default": true + }, + "encrypt_passphrase": { + "type": "boolean", + "default": false + } + }, + "required": ["stream_id", "encrypt_passphrase"] + }, + "RIST": { + "type": "object", + "title": "RIST Properties", + "description": "Properties related to the RIST protocol", + "properties": { + "encrypt_passphrase": { + "type": "boolean", + "default": true + }, + "srp_username_password": { + "type": "boolean", + "default": false + } + }, + "required": ["encrypt_passphrase", "srp_username_password"] + } + } + } + } +} diff --git a/text/0039-json-schemas/service.json b/text/0039-json-schemas/service.json new file mode 100644 index 0000000..d05d564 --- /dev/null +++ b/text/0039-json-schemas/service.json @@ -0,0 +1,255 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://obsproject.com/schemas/service.json", + "title": "OBS Service Object Schema", + "type": "object", + "properties": { + "more_info_link": { + "type": "string", + "description": "Link that provides additional info about the service, presented in the UI as a button next to the services dropdown.", + "format": "uri", + "$ref": "#/$defs/httpPattern", + "minLength": 1 + }, + "stream_key_link": { + "type": "string", + "description": "Link where a logged-in user can find the 'stream key', presented as a button alongside the stream key field.", + "format": "uri", + "$ref": "#/$defs/httpPattern", + "minLength": 1 + }, + "servers": { + "type": "array", + "description": "Array of server objects", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the server (e.g. location, primary/backup), displayed in the Server dropdown.", + "minLength": 1 + }, + "protocol": { + "type": "string", + "title": "Server Protocol", + "description": "Protocol used by the server. Required if the URI scheme can't help identify the streaming protocol (e.g. HLS).", + "$ref": "protocolDefs.json#/$defs/protocolEnum" + }, + "url": { + "type": "string", + "title": "Server URL", + "description": "URL of the ingest server.", + "format": "uri", + "$ref": "protocolDefs.json#/$defs/serverPattern", + "minLength": 1 + } + }, + "required": ["name","url","protocol"], + "unevaluatedProperties": false + }, + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + }, + "supported_codecs": { + "type": "object", + "title": "Supported Codecs", + "description": "Codecs that are supported by the service.", + "properties": { + "video": { + "type": "object", + "title": "Supported Video Codecs", + "description": "Video codecs that are supported by the service.", + "propertyNames": { "$ref": "protocolDefs.json#/$defs/protocolMapEnum" }, + "properties": { + "*": { + "type": "array", + "title": "Any Protocol Supported Video Codec", + "description": "Video codecs that are supported by the service and any supported protocol.", + "items": { + "type": "string", + "$ref": "codecDefs.json#/$defs/videoCodecEnum" + }, + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + } + }, + "additionalProperties": { + "type": "array", + "title": "Protocol Supported Video Codec", + "description": "Video codecs that are supported by the service on this protocol.", + "items": { + "type": "string", + "$ref": "codecDefs.json#/$defs/videoCodecEnum" + }, + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + }, + "minProperties": 1, + "unevaluatedProperties": false + }, + "audio": { + "type": "object", + "title": "Supported Audio Codecs", + "description": "Audio codecs that are supported by the service.", + "propertyNames": { "$ref": "protocolDefs.json#/$defs/protocolMapEnum" }, + "properties": { + "*": { + "type": "array", + "title": "Any Protocol Supported Audio Codec", + "description": "Audio codecs that are supported by the service and any supported protocol.", + "items": { + "type": "string", + "$ref": "codecDefs.json#/$defs/audioCodecEnum" + }, + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + } + }, + "additionalProperties": { + "type": "array", + "title": "Protocol Supported Audio Codec", + "description": "Audio codecs that are supported by the service on this protocol.", + "items": { + "type": "string", + "$ref": "codecDefs.json#/$defs/audioCodecEnum" + }, + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + }, + "minProperties": 1, + "unevaluatedProperties": false + } + }, + "minProperties": 1, + "unevaluatedProperties": false + }, + "supported_resolutions": { + "type": "array", + "title": "Supported Resolutions", + "description": "Resolution supported by the service. All with or all withtout the framerate.", + "items": { + "type": "string", + "pattern": "^[0-9]+x[0-9]+(|@[0-9]+)$" + }, + "allOf": [ + { + "if": { "items": { "$ref": "#/$defs/resolutionFrameratePattern" } }, + "then": { "items": { "$ref": "#/$defs/resolutionFrameratePattern" } } + }, + { + "if": { "items": { "$ref": "#/$defs/resolutionPattern" } }, + "then": { "items": { "$ref": "#/$defs/resolutionPattern" } } + } + ], + "minItems": 1, + "uniqueItems": true, + "additionalItems": false + }, + "maximums": { + "type": "object", + "title": "Maximums", + "description": "Maximum values allowed by the service", + "properties": { + "framerate": { + "type": "integer", + "title": "Maximum Framerate", + "exclusiveMinimum": 0 + }, + "video_bitrate": { + "type": "object", + "title": "Maximum Video Bitrate", + "description": "Maximum video bitrate per codec", + "propertyNames": { "$ref": "codecDefs.json#/$defs/videoCodecEnum" }, + "additionalProperties": { "type": "integer", "exclusiveMinimum": 0 }, + "minProperties": 1, + "unevaluatedProperties":false + }, + "audio_bitrate": { + "type": "object", + "title": "Maximum Audio Bitrate", + "description": "Maximum audio bitrate per codec", + "propertyNames": { "$ref": "codecDefs.json#/$defs/audioCodecEnum" }, + "additionalProperties": { "type": "integer", "exclusiveMinimum": 0 }, + "minProperties": 1, + "unevaluatedProperties":false + }, + "video_bitrate_matrix": { + "type": "object", + "title": "Maximum Video Bitrate Matrix", + "description": "Maximum video bitrate per supported resolution with framerate and per codec", + "propertyNames": { "$ref": "#/$defs/resolutionFrameratePattern" }, + "additionalProperties": { + "type": "object", + "propertyNames": { "$ref": "codecDefs.json#/$defs/videoCodecEnum" }, + "additionalProperties": { "type": "integer", "exclusiveMinimum": 0 }, + "minProperties": 1, + "unevaluatedProperties":false + }, + "minProperties": 1, + "unevaluatedProperties": false + } + }, + "minProperties": 1, + "unevaluatedProperties": false + }, + "recommended": { + "type": "object", + "title": "Recommended settings", + "description": "Settings that are applied only if the user wants to do so.", + "allOf": [ + { + "$comment": "Add codec properties", + "$ref": "codecDefs.json#/$defs/codecProperties" + } + ], + "minProperties": 1, + "unevaluatedProperties": true + } + }, + "allOf": [ + { + "$comment": "Make 'video_bitrate_matrix' unusable if 'supported_resolutions' is empty", + "if": { "properties": { "supported_resolutions": { "maxItems": 0 } } }, + "then": { "properties": { + "maximums": { "properties": { "video_bitrate_matrix": { "description": "This matrix is only available if supported resolutions with framerates are set", "maxProperties": 0 } } }, + "recommended": { "properties": { "video_bitrate_matrix": { "description": "This matrix is only available if supported resolutions with framerates are set", "maxProperties": 0 } } } + } } + }, + { + "$comment": "Make 'video_bitrate_matrix' unusable if supported resolutions are without framerate", + "if": { "not": { "properties": { "supported_resolutions": { "items": { "$ref": "#/$defs/resolutionFrameratePattern" } } } } }, + "then": { "properties": { + "maximums": { "properties": { "video_bitrate_matrix": { "description": "This matrix is only available if 'supported_resolutions' is with framerates", "maxProperties": 0 } } }, + "recommended": { "properties": { "video_bitrate_matrix": { "description": "This matrix is only available if 'supported_resolutions' is with framerates", "maxProperties": 0 } } } + } } + }, + { + "$comment": "Make 'framerate' unusable if supported resolutions are with framerate", + "if": { "not": {"properties": { "supported_resolutions": { "items": { "$ref": "#/$defs/resolutionPattern" } } } } }, + "then": { "properties": { + "maximums": { "properties": { "framerate": { "description": "This property is only available if 'supported_resolutions' is without framerates or not set", "const": 0 } } }, + "recommended": { "properties": { "framerate": { "description": "This property is only available if 'supported_resolutions' is without framerates or not set", "const": 0 } } } + } } + } + ], + "required": ["servers"], + "$defs": { + "httpPattern": { + "$comment": "Pattern to enforce HTTP(S) links", + "pattern": "^https?://" + }, + "resolutionPattern": { + "$comment": "Pattern for resolution without framerate", + "pattern": "^[0-9]+x[0-9]+$" + }, + "resolutionFrameratePattern": { + "$comment": "Pattern for resolution with framerate", + "pattern": "^[0-9]+x[0-9]+@[0-9]+$" + } + } +} diff --git a/text/0039-service-overhaul.md b/text/0039-service-overhaul.md new file mode 100644 index 0000000..a638444 --- /dev/null +++ b/text/0039-service-overhaul.md @@ -0,0 +1,436 @@ +# Summary + +- Make OBS able to accept third-party service plugins +- Register services with a unique id rather than a common one +- A service can be provided with multiple protocols +- Separate Twitch, Restream and YouTube integration and make them plugins + +# Motivation + +Actually even if OBS has the Service API, developer can't create third-party service plugin because there is no mechanism to use them at all. + +Before in OBS in the Stream settings page, property views were used for services but with only two registered services `rtmp_common` which contain all services and `rtmp_custom` for custom servers. + +Currently in OBS, this page shows the list of services with many new elements shown or hidden depending on the selection (like recommended settings). With also Twitch and Restream OAuth integrations. And no use of the property views provided by `rtmp-services`. + +This need to be refactored to re-introduce property views for the service and for the protocol output. + +This will also provide the ability for some stream services to be able to make their own plugin. + +# Design + +## Settings UI + +Many elements of the settings UI related to services will be moved inside services properties views like: +- Stream key field +- Username and password fields +- OAuth connect disconnect button +- Text with clickable link (e.g., YouTube integration links) +- Maximum and recommended settings information +- "Get Stream Key" button +- "More Info" button + +The streaming output will be selected through the settings windows and its property view will be placed below the service properties view. + +The service output settings (if any) will be saved as a JSON string in the profile config file. + +Advanced network settings will also be replaced by the output properties view below the service properties view, since those are meant for `"rtmp_output"` (RTMP(S) output). + +### Common, uncommon + +Spoiler: Streaming services will be individually registered, so no more `rtmp_common` that represent various services. + +So to hide services behind the "Show All" option, a flag (`OBS_SERVICE_UNCOMMON`) meant for "first-party" services marked as "uncommon" will be added to the Service API to indicate that the service is "uncommon". + +So third-party services are shown in the list by default. + +## Frontend API + +Those functions will be modified: +- `obs_frontend_set_streaming_service()` because while the output selection is done in the settings windows and is no longer done while the stream is starting will now swap the output if the service protocol and the actual output do not match. +- `obs_frontend_save_streaming_service()` will save service output settings. + +## About the FTL protocol + +The FTL protocol is actually planned to be deprecated once a new protocol is added after this RFC is implemented. + +Any services that only support FTL will follow the deprecation cycle and be dropped at the same time as FTL if those services did not added support for another protocol. + +## Service plugins +### Service API + +Adding to `obs_service_info`: + - `uint32_t flags` with the following flags: + - `OBS_SERVICE_DEPRECATED`: The service is marked as deprecated. + - `OBS_SERVICE_INTERNAL`: The service is meant to be used internally in some plugin (e.g., WebSocket, Scripting) and usually not directly exposed in the UI. + - `OBS_SERVICE_UNCOMMON`: The service can be hidden behind a "Show All/More" option UI/UX-wise. + - `const char *supported_protocols`: Protocol supported by the service. + - `enum obs_service_audio_track_cap (*get_audio_track_cap)(void *data)`: Returns the service audio track capability with the following possible values: + - `OBS_SERVICE_AUDIO_SINGLE_TRACK` - Only a single audio track is used by the service + - `OBS_SERVICE_AUDIO_ARCHIVE_TRACK` - A second audio track is accepted and is meant to become the archive/VOD audio + - `OBS_SERVICE_AUDIO_MULTI_TRACK` - Supports multiple audio tracks + - `void (*get_defaults2)(void *type_data, obs_data_t *settings)`: Same as its non-2 variant but give access to the `type_data` pointer. + - `obs_properties_t *(*get_properties2)(void *data, void *type_data)`: Same as its non-2 variant but give access to the `type_data` pointer. + - `bool (*can_bandwidth_test)(void *data)`: Return if the service is able to do bandwith test + - `void (*enable_bandwidth_test)(void *data, bool enabled)`: Enable bandwidth test on the service + - `bool (*bandwidth_test_enabled)(void *data)`: Return if the service has the bandwidth test enabled + - `void (*get_supported_resolutions2)(void *data, struct obs_service_resolution **resolutions,size_t *count, bool *with_fps)`: Replace its non-two variant to enable framerate value + - `int (*get_max_video_bitrate)(void *data, const char *codec struct obs_service_resolution resolution)`: Return a maximum bitrate based on a video codec and a resolution + - `int (*get_max_codec_bitrate)(void *data, const char *codec)`: Return a maximum bitrate for a specific codec + - `void (*apply_encoder_settings2)(void *data, const char *encoder_id, obs_data_t *encoder_settings)`: Replace its non-two variant to enable settings per encoder id and codec + +Adding to the Services API: + - `enum obs_service_audio_track_cap obs_service_get_audio_track_cap(const obs_service_t *service)`: Returns the service audio track capability with the following possible values: + - `OBS_SERVICE_AUDIO_SINGLE_TRACK` - Only a single audio track is used by the service + - `OBS_SERVICE_AUDIO_ARCHIVE_TRACK` - A second audio track is accepted and is meant to become the archive/VOD audio + - `OBS_SERVICE_AUDIO_MULTI_TRACK` - Supports multiple audio tracks + - `uint32_t obs_get_service_flags(const char *id)` and `uint32_t obs_service_get_flags(const obs_service_t *service)`: Return services flags + - `const char *obs_get_service_supported_protocols(const char *id)`: Return all protocols that the service can support + - `bool obs_service_can_bandwidth_test(const obs_service_t *service)`: Return if the service has bandwidth test capability + - `void obs_service_enable_bandwidth_test(const obs_service_t *service, bool enabled)`: Enable/disable the service bandwidth test + - `bool obs_service_bandwidth_test_enabled(const obs_service_t *service)`; Return if the service bandwidth test is enabled + - `int obs_service_get_max_codec_bitrate(const obs_service_t *service, const char *codec)`: Return the maximum bitrate supported by the service depending on the codec + - `void obs_service_get_supported_resolutions2( const obs_service_t *service, struct obs_service_resolution **resolutions, size_t *count, bool *with_fps)`: Return resolutions supported by the service with optional framerate + - `int obs_service_get_max_video_bitrate(const obs_service_t *service, const char *codec, struct obs_service_resolution resolution)`: Return the maximum bitrate supported by the service depending on the codec and a resolution + - `void obs_service_apply_encoder_settings2(obs_service_t *service, const char *encoder_id, obs_data_t *encoder_settings)`: Apply encoder settings for a specific encoder id + + +Deprecating in the Services API: + - `obs_service_apply_encoder_settings`: replaced by `obs_service_apply_encoder_settings2` to allow per encoder id/codec settings + - `obs_service_get_supported_resolutions`: replaced by `obs_service_get_supported_resolutions2` to enable returning framerate + - `obs_service_get_max_bitrate`: replaced by `obs_service_get_max_codec_bitrate` and `obs_service_get_max_video_bitrate` for per codec (and resolution for the second) bitrate + +### `rtmp-services` + +Services provided by this plugin (`"rtmp_custom"`, `"rtmp_common"`) will be deprecated (`OBS_SERVICE_DEPRECATED` and `OBS_SERVICE_INTERNAL` applied) and completely unused in OBS Studio. + +Those are deprecated rather than completely removed to allow scripting and plugins to migrate if they happen to use them. + +Its service JSON will no longer be updated. + + +### `custom-services` + +If OBS Studio needs to be heavily dependent on one service plugin, it must be this one. + +This plugin is meant to provide replacements for the `"rtmp_custom"` type. + +#### `"custom_service"` + +This the only service of this plugin that will be exposed to the user through the UI. + +The protocol will be selected by the user and the properties view will change depending of the protocol. + +This service will support all first-party protocols, the properties view will change visible fields according to the protocol. + +#### Custom type per protocol + +Service types that support only the protocol inside their id (`"custom_`*insert lowercase protocol acronym*`"`) for any first-party protocol except FTL (deprecation). + +Services that will only support the first-party protocol related to their id/type. +The flag `OBS_SERVICE_INTERNAL` will be applied to not show them in the UI list. Those are meant for being used in WebSocket and scripting. + +### `obs-services` + +This plugin is meant to provide a replacement for `"rtmp_common"` type for services who don't rely on custom behavior nor integration. + +This plugin will need a brand new services.json with new format, to register each streaming service with their own id. No more things like `"rtmp_common"` id. + +Those services will be able to provide multiple protocols so no more "Service - HLS" and "Service - FTL". + +If a certain protocol is not available, the UI will not show it if the service doesn't support another protocol. Same goes for codecs. +If it does, the missing protocol will not be shown. + +Those services should have no custom behavior like ingest management like it was done it `rtmp-services` plugin. + +In the end, adding streaming service will only be a JSON addition for this plugin. +Only improvements will be accepted in the code of this plugin. + +#### Service ID naming scheme +- Only lower case letter +- Hyphen `-` are only used to distinguish two service providing the same stream service but with noteworthy differences like NicoNico (free & premium). +- Like this if the service needs a specific behavior, you can 'transfer' it from `obs-services` to a new first-party plugin and keep the same id. The change will be seamless for the user if done properly. + +### Plugin for special case services + +This plugins is meant to provide replacements for `"rtmp_common"` type for services that were working around the JSON usually by adding ingest management code (except Twitch). + +4 services are concerned: +- Dacast +- Nimo TV +- SHOWROOM +- YouNow (FTL only, so falls under the FTL deprecation cycle) + +Beside the `"rtmp_common"` part being replace by a unique service type, most of the ingest code will stay identical. + +This plugin is not meant to be maintained by OBS Project, but as a temporary measure until those services create their own third-party plugins. So all of those services will be put in deprecation. + +### Conversion from old services + +`streamService.json` will replace `service.json`, a new file approach is taken to avoid sudden breakage if the user happen to downgrade. + +`streamService.json` will contain the type/id of the service and its settings but also the type/id of of the used output and its settings. + +If `streamService.json` is not found, it will be generated from `service.json` if available. + +A JSON or a harcoded list with old service name linked to their new id, to make OBS able to convert the service to a new one. + +If the user had an no longer available service, the user will be switch to `custom_service`. + +Integration settings will be migrated to their respective config file. + +### Integrations + +Twitch, YouTube and Restream have their own integration in OBS with OAuth. But those need to be isolated as plugins. + +Those plugins will provide the basic and OAuth version of the services as one services. + +If OBS is built without client ids and hashes, those plugins will be built without their integration. + +#### Dock state + +Actually the dock state is global (same on any profile). But with docks that appear depending on the service integration which is per profile. + +This can lead to dock state losses between profile switching and exiting. Currents integrations actually store a dock state in the profile config and restore it after the integration is loaded. + +But restoring a dock state from a plugin must not be considered at all. +Making it per profile could avoid this. + +Integrations docks will be added after specific frontend event. +So after each of them the dock state needs to be restored. + +- The initial restoration of the profile dock state will be moved just after `OBS_FRONTEND_EVENT_FINISHED_LOADING`. + +- `OBS_FRONTEND_EVENT_PROFILE_CHANGED`, profile dock state will be loaded after a profile is changes. + +Also they will be removed after specific frontend event. +So before each of them the dock state needs to be saved. + +- `OBS_FRONTEND_EVENT_PROFILE_CHANGING`, profile dock state will be saved before a profile is changed. + +- `OBS_FRONTEND_EVENT_EXIT`, profile dock state must be saved before this event while OBS is exiting. + +#### Dock addition + +Integration plugins will need events/signals to be able to add and remove their docks at the right moment. + +The plugin will have to store if `OBS_FRONTEND_EVENT_FINISHED_LOADING` or `OBS_FRONTEND_EVENT_PROFILE_CHANGED` was passed or not. + +If created before these events, the plugin will wait their emission to add docks (if integration connected). + +When created/updated (after these events) the service will add the docks (if integration connected). + +When `OBS_FRONTEND_EVENT_PROFILE_CHANGING` `OBS_FRONTEND_EVENT_EXIT` or the service is destroyed, docks will be removed (if integration connected). + +#### Browser features +The Front-end API needs to enable the possibility to access some `obs-browser` related feature like adding browser docks (e.g. Chat, Stream Settings) and generating widgets (e.g. login through CEF). + +`bool obs_frontend_is_browser_available()` will indicate if OBS Studio have the `obs-browser` included and available with a Wayland check on Linux/FreeBSD. This will allow plugins to know if they can use browser features. + +```C++ +struct obs_frontend_browser_params { + const char *url; + const char *startup_script; + const char **force_popup_url; + const char **popup_whitelist_urls; + bool enable_cookie; +}; +``` + +- `const char *url`, URL of the dock. +- `const char *startup_script` allow to set a startup script for the dock. +- `const char **force_popup_url` allow to set a list of URL to force those to popup. +- `const char **popup_whitelist_urls` allow to set a list of URL to popup whitelist. +- `bool enable_cookie`, if true `panel_cookie` will be set on the dock rather than a `nullptr`. This will allow to keep cookie which is required. + +`void *obs_frontend_get_browser_widget(struct obs_frontend_browser_params *params)` is the function that will return a browser widget (QCefWidget) that we can cast to a QWidget for OAuth or a custom chat dock (e.g. Youtube). + +`void obs_frontend_delete_browser_cookie(const char *url)` will remove cookies related to the given URL. + +#### Broadcast flow +YouTube is not the only service that could have or need a "Manage Broadcast" button but the feature/flow is actually made only for how YouTube works. + +So refactoring it to make service plugin able to use it without enforcing YouTube flow on them is required. + +Inside the Frontend API: + + - `void obs_frontend_add_broadcast_flow(const obs_service_t *service, const struct obs_frontend_broadcast_flow *flow)` adds a broacast flow bound the given service. If the given service is actually the same service as OBS Studio use, the broadcast flow will be enabled. + + - `void obs_frontend_remove_broadcast_flow(const obs_service_t *service)` removes the broadcast flow. If the given service is actually the same service as OBS Studio use, the broadcast flow will be dsiabled. + +```c +struct obs_frontend_broadcast_flow { + void *priv; + + uint32_t flags; + + enum obs_broadcast_state (*get_broadcast_state)(void *priv); + enum obs_broadcast_start (*get_broadcast_start_type)(void *priv); + enum obs_broadcast_stop (*get_broadcast_stop_type)(void *priv); + + void (*manage_broadcast)(void *priv); + void (*manage_broadcast2)(void *priv, bool streaming_active); + + void (*stopped_streaming)(void *priv); + + void (*differed_start_broadcast)(void *priv); + enum obs_broadcast_stream_state (*is_broadcast_stream_active)(void *priv); + + bool (*differed_stop_broadcast)(void *priv); + + const char *(*get_last_error)(void *priv); +}; +``` + +- `uint32_t flags` with the following flags: + - `OBS_BROADCAST_FLOW_ALLOW_MANAGE_WHILE_STREAMING`, the flow allow managing broadcast while streaming + - `OBS_BROADCAST_FLOW_ALLOW_DIFFERED_BROADCAST_START`, the flow can set broadcast that require to be started after the streaming is started. + - `OBS_BROADCAST_FLOW_ALLOW_DIFFERED_BROADCAST_STOP`, the flow can set broadcast that require to be stopped after the streaming is stopped. + +- `enum obs_broadcast_state (*get_broadcast_state)(void *priv)` return the state of the broadcast: + - `OBS_BROADCAST_NONE`, no broadcast is setup + - `OBS_BROADCAST_ACTIVE`, the broacast is active + - `OBS_BROADCAST_INACTIVE`, the broadcast will need a differed start + +- `enum obs_broadcast_start (*get_broadcast_start_type)(void *priv)` return the start type of the broadcast: + - `OBS_BROADCAST_START_WITH_STREAM`, the broadcast start with the stream + - `OBS_BROADCAST_START_WITH_STREAM_NOW`, same as the previous but streaming is also started + - `OBS_BROADCAST_START_DIFFER_FROM_STREAM`, the broadcast will need a differed start + +- `enum obs_broadcast_stop (*get_broadcast_stop_type)(void *priv)` return the stop type of the broadcast: + - `OBS_BROADCAST_STOP_NEVER`, the broadcast is not meant to be ended + - `OBS_BROADCAST_STOP_WITH_STREAM`, the broadcast is ended with the stream + - `OBS_BROADCAST_STOP_DIFFER_FROM_STREAM`,the broadcast will need a differed stop + +- `void (*manage_broadcast)(void *priv)` and `void (*manage_broadcast2)(void *priv, bool streaming_active)`, callcack to create and show the service broadcast manager. The `2` variant allows to open it while streaming. + +- `void (*stopped_streaming)(void *priv)`, signal to the flow that the broadcast has stopped allowing to change state if needed + +- `void (*differed_start_broadcast)(void *priv)`, start the broadcast if differed. If the broadcast is not updated to active, the broadcast failed to start. If success OBS Studio will run a thread with the next callback to wait for the stream to be active. + +- `enum obs_broadcast_stream_state (*is_broadcast_stream_active)(void *priv)` return the state of the broadcast stream: + - `OBS_BROADCAST_STREAM_FAILURE`, stream has failed to be started + - `OBS_BROADCAST_STREAM_INACTIVE`, stream is not started + - `OBS_BROADCAST_STREAM_ACTIVE`, stream is started + +- `bool (*differed_stop_broadcast)(void *priv)`, stop the broadcast if differred. Returns `true` if it succeeded + +- `const char *(*get_last_error)(void *priv)`, return the last error if differed callbacks failed. + +#### Twitch and YouTube bandwidth test +Those services have code in the UI to have such feature, it will now be moved to their respective services with the new Services API related to bandwidth test. + +#### Twitch VOD track +This Twitch-only feature will be modified to rely on `obs_service_audio_track_cap` rather than the service name. + +### If the saved service id is not registered because the plugin is longer there +OBS Studio will fallback to an empty `"custom_service"` with a message for the user explaining that maybe a plugin is missing. + +## Service JSON for `obs-services` + +### Old version from `rtmp-services` + +The old actually looks like this: +```json +{ + "format_version": 3, + "services": [ + { + "name": "Example", + "common": false, + "more_info_link": "https://example.com/more_info", + "stream_key_link": "https://example.com/stream_key", + "alt_names": [ + "Example with a FTL server" + ], + "servers": [ + { + "name": "Server", + "url": "ftl://example.com" + } + ], + "recommended": { + "keyint": 2, + "profile": "main", + "output": "ftl_output", + "max video bitrate": 4321, + "max audio bitrate": 321, + "supported resolutions": [ + "1600x900", + "1280x720" + ], + "max fps": 24, + "bframes": 2, + "x264opts": "scenecut=0 tune=zerolatency", + "bitrate matrix": [ + { + "res": "1280x720", + "fps": 24, + "max bitrate": 3210 + }, + { + "res": "1600x900", + "fps": 24, + "max bitrate": 4321 + } + ], + } + } + ] +} +``` + +- `"format_version"`: the format version, changed when the JSON is no longer backward compatible. +- `"services"`: array of services. + - `"name"`: name showed in OBS. + - `"common"` (optional): is the service only shown when the user has clicked on `Show all`. + - `"more_info_link"` (optional): link with more info about the service. + - `"stream_key_link"` (optional): link where the user can find his stream key. + - `"alt_names"` (optional): alternative service name. + - `"servers"`: array of servers. + - `"name"`: name of the server. + - `"url"`: url of the server. + - `"recommended"` (it depends): "recommended" settings that become the default when using this service. + - `"output"` (not optional if using a protocol different from RTMP(S)): indicates which output **shall** be used because the service use a protocol different from RTMP(S). + - `"keyint"` (optional): default keyframe interval for this service. + - `"bframes"` (optional): default b-frames for this service. + - `"profile"` (optional): default H264 encoder profile for this service. + - `"x264opts"` (optional): default option for x264 for this service. + - `"max video bitrate"` (optional): maximum video bitrate for this service. + - `"max audio bitrate"` (optional): maximum audio bitrate for this service. + - `"max fps"` (optional): maximum frame per second for this service. + - `"supported resolutions"` (optional): array of supported resolutions for this service. + - `"bitrate matrix"` (optional): array of maximum bitrate for a certain resolution/FPS combo for this service. + - `"res"`: resolution. + - `"fps"`: frame per seconds. + - `"max bitrate"`: maximum video bitrate. + +#### Issues with this format +- About `"output"`, OBS consider every service as RTMP if not added and it's not a recommended settings at all. It makes OBS use the right protocol for the service. Also this prevent a service of being multi-protocol. +- About `"recommended"`, most of the options seems to H264 related and are mostly only maximums. +- `"common"`, the name makes it not understandable at the first sight maybe adding some documention would be good thing. + +### New format + +The new format will rely on more strict JSON schemas. + +Those JSON Schemas (Draft 2020-12) will be: +- `protocolDefs.json`: enums, regex patterns and properties related to protocols to ease protocol additions or removals in schemas +- `codecDefs.json`: enums, regex patterns and properties related to codecs to ease codec additions or removals in schemas +- `service.json`: define the service object itself, will be used for every service plugin that needs a JSON +- `obs-services.json`: `obs-services` JSON Schema + +Those are all present in `0039-json-schemas` folder with an example of `obs-services` JSON. + +JSON Schema allows to validate `services.json` (in `obs-services` case) when a PR is made against it. + +#### Advantages of this format + +- Recommended settings are really recommended settings. +- Recommendation and maximums are two separated thing. +- Some settings are now per protocol or per codec, this allow multi-protocol service to be registered under only one id. This will also need a way to know which codec is used when applying settings. +- Name can be changed without consequences. + +# Drawbacks + +# Additional Information \ No newline at end of file