RTMPPublisher able to get stream ingestion URL and path/key from a web service. Any services.config file (broadcaster settings page) that has a http(s) URL for the stream is assumed to be pointing to a Web API that will return an actual RTMP ingestion URL and path/key.
This commit is contained in:
parent
1b93554034
commit
f764a646d0
@ -894,7 +894,13 @@ bool XConfig::ReadFileData2(XElement *curElement, int level, TSTR &lpTemp, bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
++lpTemp;
|
// A ++lpTemp above can step off the end of the string causing
|
||||||
|
// the condition on while to go a bit crazy.
|
||||||
|
// Making sure we preserve the end of string.
|
||||||
|
if (*lpTemp != 0)
|
||||||
|
{
|
||||||
|
++lpTemp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (curElement == RootElement);
|
return (curElement == RootElement);
|
||||||
@ -1000,6 +1006,27 @@ void XConfig::WriteFileData(XFile &file, int indent, XElement *curElement)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basically the same as Open (and in fact Open could/should call ParseString to do its thing)
|
||||||
|
// But ParseString allows chunks of JSON type strings to be parse into the XConfig structure.
|
||||||
|
bool XConfig::ParseString(const String& config)
|
||||||
|
{
|
||||||
|
String safe_copy = config;
|
||||||
|
TSTR lpTemp = safe_copy;
|
||||||
|
|
||||||
|
RootElement = new XElement(this, NULL, TEXT("Root"));
|
||||||
|
|
||||||
|
if(!ReadFileData2(RootElement, 0, lpTemp, true))
|
||||||
|
{
|
||||||
|
for(DWORD i=0; i<RootElement->SubItems.Num(); i++)
|
||||||
|
delete RootElement->SubItems[i];
|
||||||
|
|
||||||
|
CrashError(TEXT("Error parsing X string '%s'"), config.Array());
|
||||||
|
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool XConfig::Open(CTSTR lpFile)
|
bool XConfig::Open(CTSTR lpFile)
|
||||||
{
|
{
|
||||||
|
@ -228,6 +228,7 @@ public:
|
|||||||
inline ~XConfig() {Close();}
|
inline ~XConfig() {Close();}
|
||||||
|
|
||||||
bool Open(CTSTR lpFile);
|
bool Open(CTSTR lpFile);
|
||||||
|
bool ParseString(const String& config);
|
||||||
void Close(bool bSave=false);
|
void Close(bool bSave=false);
|
||||||
void Save();
|
void Save();
|
||||||
|
|
||||||
|
@ -130,6 +130,111 @@ failure:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String HTTPGetString (CTSTR url, CTSTR extraHeaders, int *responseCode)
|
||||||
|
{
|
||||||
|
HINTERNET hSession = NULL;
|
||||||
|
HINTERNET hConnect = NULL;
|
||||||
|
HINTERNET hRequest = NULL;
|
||||||
|
URL_COMPONENTS urlComponents;
|
||||||
|
BOOL secure = FALSE;
|
||||||
|
String result = "";
|
||||||
|
|
||||||
|
String hostName, path;
|
||||||
|
|
||||||
|
const TCHAR *acceptTypes[] = {
|
||||||
|
TEXT("*/*"),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
hostName.SetLength(256);
|
||||||
|
path.SetLength(1024);
|
||||||
|
|
||||||
|
zero(&urlComponents, sizeof(urlComponents));
|
||||||
|
|
||||||
|
urlComponents.dwStructSize = sizeof(urlComponents);
|
||||||
|
|
||||||
|
urlComponents.lpszHostName = hostName;
|
||||||
|
urlComponents.dwHostNameLength = hostName.Length();
|
||||||
|
|
||||||
|
urlComponents.lpszUrlPath = path;
|
||||||
|
urlComponents.dwUrlPathLength = path.Length();
|
||||||
|
|
||||||
|
WinHttpCrackUrl(url, 0, 0, &urlComponents);
|
||||||
|
|
||||||
|
if (urlComponents.nPort == 443)
|
||||||
|
secure = TRUE;
|
||||||
|
|
||||||
|
hSession = WinHttpOpen(OBS_VERSION_STRING, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||||
|
if (!hSession)
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
hConnect = WinHttpConnect(hSession, hostName, secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0);
|
||||||
|
if (!hConnect)
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
hRequest = WinHttpOpenRequest(hConnect, TEXT("GET"), path, NULL, WINHTTP_NO_REFERER, acceptTypes, secure ? WINHTTP_FLAG_SECURE|WINHTTP_FLAG_REFRESH : WINHTTP_FLAG_REFRESH);
|
||||||
|
if (!hRequest)
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
BOOL bResults = WinHttpSendRequest(hRequest, extraHeaders, extraHeaders ? -1 : 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
|
||||||
|
|
||||||
|
// End the request.
|
||||||
|
if (bResults)
|
||||||
|
bResults = WinHttpReceiveResponse(hRequest, NULL);
|
||||||
|
else
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
TCHAR statusCode[8];
|
||||||
|
DWORD statusCodeLen;
|
||||||
|
|
||||||
|
statusCodeLen = sizeof(statusCode);
|
||||||
|
if (!WinHttpQueryHeaders (hRequest, WINHTTP_QUERY_STATUS_CODE, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeLen, WINHTTP_NO_HEADER_INDEX))
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
*responseCode = wcstoul(statusCode, NULL, 10);
|
||||||
|
|
||||||
|
if (bResults && *responseCode == 200)
|
||||||
|
{
|
||||||
|
CHAR buffer[16384];
|
||||||
|
DWORD dwSize, dwOutSize;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Check for available data.
|
||||||
|
dwSize = 0;
|
||||||
|
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
if (!WinHttpReadData(hRequest, (LPVOID)buffer, dwSize, &dwOutSize))
|
||||||
|
{
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!dwOutSize)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Ensure the string is terminated.
|
||||||
|
buffer[dwOutSize] = 0;
|
||||||
|
|
||||||
|
String b = String((LPCSTR)buffer);
|
||||||
|
result.AppendString(b);
|
||||||
|
}
|
||||||
|
} while (dwSize > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
failure:
|
||||||
|
if (hSession)
|
||||||
|
WinHttpCloseHandle(hSession);
|
||||||
|
if (hConnect)
|
||||||
|
WinHttpCloseHandle(hConnect);
|
||||||
|
if (hRequest)
|
||||||
|
WinHttpCloseHandle(hRequest);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
String CreateHTTPURL(String host, String path, String extra, bool secure)
|
String CreateHTTPURL(String host, String path, String extra, bool secure)
|
||||||
{
|
{
|
||||||
URL_COMPONENTS components = {
|
URL_COMPONENTS components = {
|
||||||
|
@ -21,4 +21,6 @@
|
|||||||
|
|
||||||
BOOL HTTPGetFile (CTSTR url, CTSTR outputPath, CTSTR extraHeaders, int *responseCode);
|
BOOL HTTPGetFile (CTSTR url, CTSTR outputPath, CTSTR extraHeaders, int *responseCode);
|
||||||
|
|
||||||
|
String HTTPGetString (CTSTR url, CTSTR extraHeaders, int *responseCode);
|
||||||
|
|
||||||
String CreateHTTPURL(String host, String path, String extra=String(), bool secure=false);
|
String CreateHTTPURL(String host, String path, String extra=String(), bool secure=false);
|
@ -824,6 +824,7 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A service ID implies the settings have come from the xconfig file.
|
||||||
if(serviceID != 0)
|
if(serviceID != 0)
|
||||||
{
|
{
|
||||||
XConfig serverData;
|
XConfig serverData;
|
||||||
@ -847,6 +848,7 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
|
|||||||
XElement *curService = services->GetElementByID(i);
|
XElement *curService = services->GetElementByID(i);
|
||||||
if(curService->GetInt(TEXT("id")) == serviceID)
|
if(curService->GetInt(TEXT("id")) == serviceID)
|
||||||
{
|
{
|
||||||
|
// Found the service in the xconfig file.
|
||||||
service = curService;
|
service = curService;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -858,6 +860,7 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each service can have many ingestion servers. Look up a server for a particular service.
|
||||||
XElement *servers = service->GetElement(TEXT("servers"));
|
XElement *servers = service->GetElement(TEXT("servers"));
|
||||||
if(!servers)
|
if(!servers)
|
||||||
{
|
{
|
||||||
@ -865,12 +868,78 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Got the server node now so can look up the ingestion URL.
|
||||||
XDataItem *item = servers->GetDataItem(strURL);
|
XDataItem *item = servers->GetDataItem(strURL);
|
||||||
if(!item)
|
if(!item)
|
||||||
item = servers->GetDataItemByID(0);
|
item = servers->GetDataItemByID(0);
|
||||||
|
|
||||||
strURL = item->GetData();
|
strURL = item->GetData();
|
||||||
|
|
||||||
|
// Stream urls start with RTMP. If there's an HTTP(S) then assume this is a web API call
|
||||||
|
// to get the proper data.
|
||||||
|
if ((strURL.Left(5).MakeLower() == "https") || (strURL.Left(4).MakeLower() == "http"))
|
||||||
|
{
|
||||||
|
// Query the web API for stream details
|
||||||
|
String web_url = strURL + strPlayPath;
|
||||||
|
|
||||||
|
int responseCode;
|
||||||
|
TCHAR extraHeaders[256];
|
||||||
|
|
||||||
|
extraHeaders[0] = 0;
|
||||||
|
|
||||||
|
String response = HTTPGetString(web_url, extraHeaders, &responseCode);
|
||||||
|
|
||||||
|
if (responseCode != 200 && responseCode != 304)
|
||||||
|
{
|
||||||
|
failReason = TEXT("Webserver failed to respond with valid stream details.");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
XConfig apiData;
|
||||||
|
|
||||||
|
// Expecting a response from the web API to look like this:
|
||||||
|
// {"data":{"stream_url":"rtmp://some_url", "stream_name": "some-name"}}
|
||||||
|
// A nice bit of JSON which is basically the same as the structure for XConfig.
|
||||||
|
if(!apiData.ParseString(response))
|
||||||
|
{
|
||||||
|
failReason = TEXT("Could not understand response from webserver.");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could have read an error string back from the server.
|
||||||
|
// So we need to trap any missing bits of data.
|
||||||
|
|
||||||
|
XElement *p_data = apiData.GetElement(TEXT("data"));
|
||||||
|
|
||||||
|
if (p_data == NULL)
|
||||||
|
{
|
||||||
|
failReason = TEXT("No valid data returned from web server.");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
XDataItem *p_stream_url_data = p_data->GetDataItem(TEXT("stream_url"));
|
||||||
|
|
||||||
|
if (p_stream_url_data == NULL)
|
||||||
|
{
|
||||||
|
failReason = TEXT("No valid broadcast stream URL returned from web server.");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
strURL = p_stream_url_data->GetData();
|
||||||
|
|
||||||
|
XDataItem *p_stream_name_data = p_data->GetDataItem(TEXT("stream_name"));
|
||||||
|
|
||||||
|
if (p_stream_name_data == NULL)
|
||||||
|
{
|
||||||
|
failReason = TEXT("No valid stream name/path returned from web server.");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
strPlayPath = p_stream_name_data->GetData();
|
||||||
|
|
||||||
|
Log(TEXT("Web API returned URL: %s"), strURL.Array());
|
||||||
|
}
|
||||||
|
|
||||||
Log(TEXT("Using RTMP service: %s"), service->GetName());
|
Log(TEXT("Using RTMP service: %s"), service->GetName());
|
||||||
Log(TEXT(" Server selection: %s"), strURL.Array());
|
Log(TEXT(" Server selection: %s"), strURL.Array());
|
||||||
}
|
}
|
||||||
@ -903,6 +972,8 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A user name and password can be kept in the .ini file
|
||||||
|
// If there's some credentials there then they'll be used in the RTMP channel
|
||||||
char *rtmpUser = AppConfig->GetString(TEXT("Publish"), TEXT("Username")).CreateUTF8String();
|
char *rtmpUser = AppConfig->GetString(TEXT("Publish"), TEXT("Username")).CreateUTF8String();
|
||||||
char *rtmpPass = AppConfig->GetString(TEXT("Publish"), TEXT("Password")).CreateUTF8String();
|
char *rtmpPass = AppConfig->GetString(TEXT("Publish"), TEXT("Password")).CreateUTF8String();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user