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.

master
spangleb 2014-04-07 14:20:03 +01:00
parent 1b93554034
commit f764a646d0
5 changed files with 207 additions and 1 deletions

View File

@ -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);
@ -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)
{

View File

@ -228,6 +228,7 @@ public:
inline ~XConfig() {Close();}
bool Open(CTSTR lpFile);
bool ParseString(const String& config);
void Close(bool bSave=false);
void Save();

View File

@ -130,6 +130,111 @@ failure:
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)
{
URL_COMPONENTS components = {

View File

@ -21,4 +21,6 @@
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);

View File

@ -824,6 +824,7 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
goto end;
}
// A service ID implies the settings have come from the xconfig file.
if(serviceID != 0)
{
XConfig serverData;
@ -847,6 +848,7 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
XElement *curService = services->GetElementByID(i);
if(curService->GetInt(TEXT("id")) == serviceID)
{
// Found the service in the xconfig file.
service = curService;
break;
}
@ -858,6 +860,7 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
goto end;
}
// Each service can have many ingestion servers. Look up a server for a particular service.
XElement *servers = service->GetElement(TEXT("servers"));
if(!servers)
{
@ -865,12 +868,78 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
goto end;
}
// Got the server node now so can look up the ingestion URL.
XDataItem *item = servers->GetDataItem(strURL);
if(!item)
item = servers->GetDataItemByID(0);
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(" Server selection: %s"), strURL.Array());
}
@ -903,6 +972,8 @@ DWORD WINAPI RTMPPublisher::CreateConnectionThread(RTMPPublisher *publisher)
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 *rtmpPass = AppConfig->GetString(TEXT("Publish"), TEXT("Password")).CreateUTF8String();