obs/Source/SettingsPublish.cpp

1107 lines
48 KiB
C++

/********************************************************************************
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
Copyright (C) 2013 Lucas Murray <lmurray@undefinedfire.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
********************************************************************************/
#include "Settings.h"
#pragma warning(disable: 4530)
#include <vector>
#include <unordered_map>
#include <string>
#include <functional>
#include <algorithm>
//============================================================================
// Helpers
void AdjustWindowPos(HWND hwnd, LONG xOffset, LONG yOffset)
{
RECT rc;
HWND hwndParent = GetParent(hwnd);
GetWindowRect(hwnd, &rc);
if (LocaleIsRTL())
rc.left = rc.right;
ScreenToClient(hwndParent, (LPPOINT)&rc);
SetWindowPos(hwnd, NULL, rc.left+xOffset, rc.top+yOffset, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER);
}
//============================================================================
// SettingsPublish class
SettingsPublish::SettingsPublish()
: SettingsPane()
{
}
SettingsPublish::~SettingsPublish()
{
}
CTSTR SettingsPublish::GetCategory() const
{
static CTSTR name = Str("Settings.Publish");
return name;
}
HWND SettingsPublish::CreatePane(HWND parentHwnd)
{
hwnd = OBSCreateDialog(hinstMain, MAKEINTRESOURCE(IDD_SETTINGS_PUBLISH), parentHwnd, (DLGPROC)DialogProc, (LPARAM)this);
return hwnd;
}
void SettingsPublish::DestroyPane()
{
DestroyWindow(hwnd);
hwnd = NULL;
}
void SettingsPublish::ApplySettings()
{
auto check_expanded_dir = [&](String path, String err, String errCaption)
{
String expanded = GetExpandedRecordingDirectoryBase(path);
if (OSFileExists(expanded))
return true;
return OBSMessageBox(hwnd, err.FindReplace(L"$1", expanded), errCaption, MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2 ) != IDNO;
};
//------------------------------------------
String strSavePath = GetEditText(GetDlgItem(hwnd, IDC_SAVEPATH));
String defaultPath = OSGetDefaultVideoSavePath(L"\\$T.flv");
String actualPath = strSavePath;
if (!strSavePath.IsValid() && defaultPath.IsValid())
{
String text = Str("Settings.Publish.InvalidSavePath");
text.FindReplace(L"$1", defaultPath);
if (OBSMessageBox(hwnd, text, Str("Settings.Publish.InvalidSavePathCaption"), MB_ICONEXCLAMATION | MB_OKCANCEL) != IDOK)
{
SetAbortApplySettings(true);
return;
}
SetWindowText(GetDlgItem(hwnd, IDC_SAVEPATH), defaultPath.Array());
actualPath = defaultPath;
}
if (!check_expanded_dir(actualPath, Str("Settings.Publish.SavePathDoesNotExist"), Str("Settings.Publish.SavePathDoesNotExistCaption")))
return SetAbortApplySettings(true);
//------------------------------------------
String replaySavePath = GetEditText(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH));
defaultPath = OSGetDefaultVideoSavePath(L"\\Replay-$T.flv");
actualPath = replaySavePath;
if (!replaySavePath.IsValid() && defaultPath.IsValid())
{
String text = Str("Settings.Publish.InvalidReplayBufferSavePath");
text.FindReplace(L"$1", defaultPath);
if (OBSMessageBox(hwnd, text, Str("Settings.Publish.InvalidReplayBufferSavePathCaption"), MB_ICONEXCLAMATION | MB_OKCANCEL) != IDOK)
{
SetAbortApplySettings(true);
return;
}
SetWindowText(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH), defaultPath.Array());
actualPath = defaultPath;
}
if (!check_expanded_dir(actualPath, Str("Settings.Publish.ReplayBufferSavePathDoesNotExist"), Str("Settings.Publish.ReplayBufferSavePathDoesNotExistCaption")))
return SetAbortApplySettings(true);
//------------------------------------------
int curSel = (int)SendMessage(GetDlgItem(hwnd, IDC_MODE), CB_GETCURSEL, 0, 0);
if(curSel != CB_ERR)
AppConfig->SetInt(TEXT("Publish"), TEXT("Mode"), curSel);
int serviceID = (int)SendMessage(GetDlgItem(hwnd, IDC_SERVICE), CB_GETCURSEL, 0, 0);
if(serviceID != CB_ERR && serviceID >= 0 && serviceID < (int)services.size())
{
auto sid = services[serviceID];
AppConfig->SetInt(TEXT("Publish"), TEXT("Service"), sid.id);
AppConfig->SetString(L"Publish", L"ServiceFile", sid.file);
}
String strTemp = GetEditText(GetDlgItem(hwnd, IDC_PLAYPATH));
strTemp.KillSpaces();
AppConfig->SetString(TEXT("Publish"), TEXT("PlayPath"), strTemp);
if(serviceID == 0)
{
strTemp = GetEditText(GetDlgItem(hwnd, IDC_URL));
AppConfig->SetString(TEXT("Publish"), TEXT("URL"), strTemp);
}
else
{
strTemp = GetCBText(GetDlgItem(hwnd, IDC_SERVERLIST));
AppConfig->SetString(TEXT("Publish"), TEXT("URL"), strTemp);
}
//------------------------------------------
bool bLowLatencyMode = SendMessage(GetDlgItem(hwnd, IDC_LOWLATENCYMODE), BM_GETCHECK, 0, 0) == BST_CHECKED;
AppConfig->SetInt(TEXT("Publish"), TEXT("LowLatencyMode"), bLowLatencyMode);
//------------------------------------------
App->bAutoReconnect = SendMessage(GetDlgItem(hwnd, IDC_AUTORECONNECT), BM_GETCHECK, 0, 0) == BST_CHECKED;
App->bKeepRecording = SendMessage(GetDlgItem(hwnd, IDC_KEEPRECORDING), BM_GETCHECK, 0, 0) == BST_CHECKED;
BOOL bError = FALSE;
App->reconnectTimeout = (UINT)SendMessage(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT), UDM_GETPOS32, 0, (LPARAM)&bError);
if(bError)
App->reconnectTimeout = 10;
AppConfig->SetInt(TEXT("Publish"), TEXT("AutoReconnect"), App->bAutoReconnect);
AppConfig->SetInt(TEXT("Publish"), TEXT("AutoReconnectTimeout"), App->reconnectTimeout);
AppConfig->SetInt(TEXT("Publish"), TEXT("KeepRecording"), App->bKeepRecording);
//------------------------------------------
bError = FALSE;
int delayTime = (int)SendMessage(GetDlgItem(hwnd, IDC_DELAY), UDM_GETPOS32, 0, (LPARAM)&bError);
if(bError)
delayTime = 0;
AppConfig->SetInt(TEXT("Publish"), TEXT("Delay"), delayTime);
//------------------------------------------
BOOL bSaveToFile = SendMessage(GetDlgItem(hwnd, IDC_SAVETOFILE), BM_GETCHECK, 0, 0) != BST_UNCHECKED;
AppConfig->SetInt (TEXT("Publish"), TEXT("SaveToFile"), bSaveToFile);
AppConfig->SetString(TEXT("Publish"), TEXT("SavePath"), strSavePath);
//------------------------------------------
bError = FALSE;
int replayBufferLength = (int)SendMessage(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH), UDM_GETPOS32, 0, (LPARAM)&bError);
if (bError)
SendMessage(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH), UDM_SETPOS32, 0, replayBufferLength);
AppConfig->SetInt(L"Publish", L"ReplayBufferLength", replayBufferLength);
AppConfig->SetString(L"Publish", L"ReplayBufferSavePath", replaySavePath);
//------------------------------------------
App->ConfigureStreamButtons();
/*
App->strDashboard = GetEditText(GetDlgItem(hwnd, IDC_DASHBOARDLINK)).KillSpaces();
AppConfig->SetString(TEXT("Publish"), TEXT("Dashboard"), App->strDashboard);
ShowWindow(GetDlgItem(hwndMain, ID_DASHBOARD), App->strDashboard.IsValid() && !curSel ? SW_SHOW : SW_HIDE);
*/
}
void SettingsPublish::CancelSettings()
{
}
bool SettingsPublish::HasDefaults() const
{
return false;
}
void SettingsPublish::SetWarningInfo()
{
int serviceID = (int)SendMessage(GetDlgItem(hwnd, IDC_SERVICE), CB_GETCURSEL, 0, 0);
bool bUseCBR = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("UseCBR"), 1) != 0;
int maxBitRate = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("MaxBitrate"), 1000);
int keyframeInt = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("KeyframeInterval"), 0);
int audioBitRate = AppConfig->GetInt(TEXT("Audio Encoding"), TEXT("Bitrate"), 96);
String currentx264Profile = AppConfig->GetString(TEXT("Video Encoding"), TEXT("X264Profile"), L"high");
String currentAudioCodec = AppConfig->GetString(TEXT("Audio Encoding"), TEXT("Codec"), TEXT("AAC"));
float currentAspect = AppConfig->GetInt(L"Video", L"BaseWidth") / (float)max(1, AppConfig->GetInt(L"Video", L"BaseHeight"));
//ignore for non-livestreams
if (data.mode != 0)
{
SetDlgItemText(hwnd, IDC_WARNINGS, TEXT(""));
return;
}
bool hasErrors = false;
bool canOptimize = false;
String strWarnings;
if (serviceID >= 0 && serviceID < (int)services.size())
{
auto serviceData = LoadService(services[serviceID]);
auto service = serviceData.second;
if (service)
{
strWarnings = FormattedString(Str("Settings.Publish.Warning.BadSettings"), service->GetName());
//check to see if the service we're using has recommendations
if (!service->HasItem(TEXT("recommended")))
{
SetDlgItemText(hwnd, IDC_WARNINGS, TEXT(""));
return;
}
XElement *r = service->GetElement(TEXT("recommended"));
if (r->HasItem(TEXT("ratecontrol")))
{
CTSTR rc = r->GetString(TEXT("ratecontrol"));
if (!scmp(rc, TEXT("cbr")) && !bUseCBR)
{
hasErrors = true;
canOptimize = true;
strWarnings << Str("Settings.Publish.Warning.UseCBR");
}
}
if (r->HasItem(TEXT("max bitrate")))
{
int max_bitrate = r->GetInt(TEXT("max bitrate"));
if (maxBitRate > max_bitrate)
{
hasErrors = true;
canOptimize = true;
strWarnings << FormattedString(Str("Settings.Publish.Warning.Maxbitrate"), max_bitrate);
}
}
if (r->HasItem(L"supported audio codec"))
{
StringList codecs;
r->GetStringList(L"supported audio codec", codecs);
if (codecs.FindValueIndex(currentAudioCodec) == INVALID)
{
String msg = Str("Settings.Publish.Warning.UnsupportedAudioCodec"); //good thing OBS only supports MP3 (and AAC), otherwise I'd have to come up with a better translation solution
msg.FindReplace(L"$1", codecs[0].Array());
msg.FindReplace(L"$2", currentAudioCodec.Array());
hasErrors = true;
canOptimize = true;
strWarnings << msg;
}
}
if (r->HasItem(TEXT("max audio bitrate aac")) && (!scmp(currentAudioCodec, TEXT("AAC"))))
{
int maxaudioaac = r->GetInt(TEXT("max audio bitrate aac"));
if (audioBitRate > maxaudioaac)
{
hasErrors = true;
canOptimize = true;
strWarnings << FormattedString(Str("Settings.Publish.Warning.MaxAudiobitrate"), maxaudioaac);
}
}
if (r->HasItem(TEXT("max audio bitrate mp3")) && (!scmp(currentAudioCodec, TEXT("MP3"))))
{
int maxaudiomp3 = r->GetInt(TEXT("max audio bitrate mp3"));
if (audioBitRate > maxaudiomp3)
{
hasErrors = true;
canOptimize = true;
strWarnings << FormattedString(Str("Settings.Publish.Warning.MaxAudiobitrate"), maxaudiomp3);
}
}
if (r->HasItem(L"video aspect ratio"))
{
String aspectRatio = r->GetString(L"video aspect ratio");
StringList numbers;
aspectRatio.GetTokenList(numbers, ':');
if (numbers.Num() == 2)
{
float aspect = numbers[0].ToInt() / max(1.f, numbers[1].ToFloat());
if (!CloseFloat(aspect, currentAspect))
{
String aspectLocalized = Str("Settings.Video.AspectRatioFormat");
aspectLocalized.FindReplace(L"$1", UIntString(numbers[0].ToInt()));
aspectLocalized.FindReplace(L"$2", UIntString(numbers[1].ToInt()));
String msg = Str("Settings.Publish.Warning.VideoAspectRatio");
msg.FindReplace(L"$1", aspectLocalized);
strWarnings << msg;
hasErrors = true;
}
}
}
if (r->HasItem(TEXT("profile")))
{
String expectedProfile = r->GetString(TEXT("profile"));
if (!expectedProfile.CompareI(currentx264Profile))
{
hasErrors = true;
canOptimize = true;
strWarnings << Str("Settings.Publish.Warning.RecommendMainProfile");
}
}
if (r->HasItem(TEXT("keyint")))
{
int keyint = r->GetInt(TEXT("keyint"));
if (!keyframeInt || keyframeInt * 1000 > keyint)
{
hasErrors = true;
canOptimize = true;
strWarnings << FormattedString(Str("Settings.Publish.Warning.Keyint"), keyint / 1000);
}
}
}
}
if (hasErrors)
{
if (canOptimize)
strWarnings << Str("Settings.Publish.Warning.CanOptimize");
SetDlgItemText(hwnd, IDC_WARNINGS, strWarnings.Array());
}
else
SetDlgItemText(hwnd, IDC_WARNINGS, TEXT(""));
SetCanOptimizeSettings(canOptimize);
}
static void UpdateMemoryUsage(HWND hwnd)
{
int maxBitRate = AppConfig->GetInt(L"Video Encoding", L"MaxBitrate", 1000);
int keyframeInt = AppConfig->GetInt(L"Video Encoding", L"KeyframeInterval", 0);
int audioBitRate = AppConfig->GetInt(L"Audio Encoding", L"Bitrate", 96);
if (keyframeInt <= 0)
keyframeInt = 5; // x264 and QSV seem to use a bit over 4 seconds of keyframe interval by default
BOOL error;
int replayBufferLength = (int)SendMessage(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH), UDM_GETPOS32, 0, (LPARAM)&error);
if (error)
SendMessage(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH), UDM_SETPOS32, 0, replayBufferLength);
long long max_kbits = (maxBitRate + audioBitRate) * (keyframeInt * 2 + replayBufferLength);
MEMORYSTATUS ms;
GlobalMemoryStatus(&ms);
String memory = FormattedString(L"%d / %d", (int)ceil(max_kbits * 1000. / 1024. / 1024. / 8.), ms.dwTotalPhys / 1024 / 1024);
SetWindowText(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY), memory.Array());
}
void SettingsPublish::OptimizeSettings()
{
auto refresh_on_exit = GuardScope([&] { SetWarningInfo(); UpdateMemoryUsage(hwnd); });
int serviceID = (int)SendMessage(GetDlgItem(hwnd, IDC_SERVICE), CB_GETCURSEL, 0, 0);
if (serviceID < 0 || serviceID >= (int)services.size())
return;
auto serviceData = LoadService(services[serviceID]);
auto service = serviceData.second;
if (!service)
return;
//check to see if the service we're using has recommendations
if (!service->HasItem(L"recommended"))
return;
XElement *r = service->GetElement(L"recommended");
if (!r)
return;
using optimizers_t = std::vector<std::function<void()>>;
optimizers_t optimizers;
String changes = Str("Settings.Publish.Optimize.Optimizations");
String currentAudioCodec = AppConfig->GetString(L"Audio Encoding", L"Codec", L"AAC");
int audioBitrate = AppConfig->GetInt(L"Audio Encoding", L"Bitrate", 96);
if (r->HasItem(L"ratecontrol"))
{
bool useCBR = AppConfig->GetInt(L"Video Encoding", L"UseCBR", 1) != 0;
CTSTR rc = r->GetString(L"ratecontrol");
if (!scmp(rc, L"cbr") && !useCBR)
{
optimizers.push_back([] { AppConfig->SetInt(L"Video Encoding", L"UseCBR", 1); });
changes << Str("Settings.Publish.Optimize.UseCBR");
}
}
if (r->HasItem(L"max bitrate"))
{
int maxBitrate = AppConfig->GetInt(L"Video Encoding", L"MaxBitrate", 1000);
int max_bitrate = r->GetInt(L"max bitrate");
if (maxBitrate > max_bitrate)
{
optimizers.push_back([max_bitrate] { AppConfig->SetInt(L"Video Encoding", L"MaxBitrate", max_bitrate); });
changes << FormattedString(Str("Settings.Publish.Optimize.Maxbitrate"), max_bitrate);
}
}
if (r->HasItem(L"supported audio codec"))
{
StringList codecs;
r->GetStringList(L"supported audio codec", codecs);
if (codecs.FindValueIndex(currentAudioCodec) == INVALID)
{
String codec = codecs[0];
optimizers.push_back([codec]
{
AppConfig->SetString(L"Audio Encoding", L"Codec", codec.Array());
AppConfig->SetInt(L"Audio Encoding", L"Format", codec.CompareI(L"AAC") ? 1 : 0); //set to 44.1 kHz in case of MP3, see SettingsEncoding.cpp
});
changes << FormattedString(Str("Settings.Publish.Optimize.UnsupportedAudioCodec"), codec.Array());
}
}
if (r->HasItem(L"max audio bitrate aac") && (!scmp(currentAudioCodec, L"AAC")))
{
int maxaudioaac = r->GetInt(L"max audio bitrate aac");
if (audioBitrate > maxaudioaac)
{
optimizers.push_back([maxaudioaac] { AppConfig->SetInt(L"Audio Encoding", L"Bitrate", maxaudioaac); });
changes << FormattedString(Str("Settings.Publish.Optimize.MaxAudiobitrate"), maxaudioaac);
}
}
if (r->HasItem(L"max audio bitrate mp3") && (!scmp(currentAudioCodec, L"MP3")))
{
int maxaudiomp3 = r->GetInt(L"max audio bitrate mp3");
if (audioBitrate > maxaudiomp3)
{
optimizers.push_back([maxaudiomp3] { AppConfig->SetInt(L"Audio Encoding", L"Bitrate", maxaudiomp3); });
changes << FormattedString(Str("Settings.Publish.Optimize.MaxAudiobitrate"), maxaudiomp3);
}
}
if (r->HasItem(L"profile"))
{
String currentx264Profile = AppConfig->GetString(L"Video Encoding", L"X264Profile", L"high");
String expectedProfile = r->GetString(L"profile");
if (!expectedProfile.CompareI(currentx264Profile))
{
optimizers.push_back([expectedProfile] { AppConfig->SetString(L"Video Encoding", L"X264Profile", expectedProfile); });
changes << FormattedString(Str("Settings.Publish.Optimize.RecommendMainProfile"), expectedProfile.Array());
}
}
if (r->HasItem(L"keyint"))
{
int keyframeInt = AppConfig->GetInt(L"Video Encoding", L"KeyframeInterval", 0);
int keyint = r->GetInt(L"keyint");
if (!keyframeInt || keyframeInt * 1000 > keyint)
{
optimizers.push_back([keyint] { AppConfig->SetInt(L"Video Encoding", L"KeyframeInterval", keyint / 1000); });
changes << FormattedString(Str("Settings.Publish.Optimize.Keyint"), keyint / 1000);
}
}
if (OBSMessageBox(hwnd, changes.Array(), Str("Optimize"), MB_OKCANCEL | MB_ICONINFORMATION) != IDOK)
return;
for (optimizers_t::const_reference i : optimizers)
i();
}
INT_PTR SettingsPublish::ProcMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
HWND hwndTemp;
switch(message)
{
case WM_INITDIALOG:
{
LocalizeWindow(hwnd);
RECT serviceRect, saveToFileRect;
GetWindowRect(GetDlgItem(hwnd, IDC_SERVICE), &serviceRect);
GetWindowRect(GetDlgItem(hwnd, IDC_SAVEPATH), &saveToFileRect);
data.fileControlOffset = saveToFileRect.top-serviceRect.top;
//--------------------------------------------
hwndTemp = GetDlgItem(hwnd, IDC_MODE);
SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Publish.Mode.LiveStream"));
SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Publish.Mode.FileOnly"));
int mode = LoadSettingComboInt(hwndTemp, TEXT("Publish"), TEXT("Mode"), 0, 2);
data.mode = mode;
//--------------------------------------------
services.clear();
hwndTemp = GetDlgItem(hwnd, IDC_SERVICE);
int itemId = (int)SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)TEXT("Custom"));
SendMessage(hwndTemp, CB_SETITEMDATA, itemId, 0);
services.emplace_back(0, String());
ServiceIdentifier current = GetCurrentService();
std::unordered_map<std::wstring, int> duplicates;
EnumerateServices([&](ServiceIdentifier sid, XElement *service)
{
if (!service)
return true;
services.emplace_back(sid);
auto pos = duplicates.find(service->GetName());
int id;
if (pos != end(duplicates))
{
const ServiceIdentifier &first = services[pos->second];
if (first.file.IsValid())
{
SendMessage(hwndTemp, CB_DELETESTRING, pos->second, 0);
SendMessage(hwndTemp, CB_INSERTSTRING, pos->second, (LPARAM)FormattedString(L"%s [%s]", service->GetName(), services[pos->second].file.Array()).Array());
}
id = (int)SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)(sid.file.IsValid() ? FormattedString(L"%s [%s]", service->GetName(), sid.file.Array()).Array() : service->GetName()));
}
else
{
id = (int)SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)service->GetName());
duplicates[service->GetName()] = id;
}
[&]()
{
if (sid != current)
return;
SendDlgItemMessage(hwnd, IDC_SERVICE, CB_SETCURSEL, id, 0);
XElement *servers = service->GetElement(L"servers");
if (!servers)
return;
UINT numServers = servers->NumDataItems();
for (UINT i = 0; i < numServers; i++)
{
XDataItem *server = servers->GetDataItemByID(i);
SendMessage(GetDlgItem(hwnd, IDC_SERVERLIST), CB_ADDSTRING, 0, (LPARAM)server->GetName());
}
}();
return true;
});
if(mode != 0) ShowWindow(hwndTemp, SW_HIDE);
//--------------------------------------------
hwndTemp = GetDlgItem(hwnd, IDC_PLAYPATH);
LoadSettingEditString(hwndTemp, TEXT("Publish"), TEXT("PlayPath"), NULL);
if(mode != 0) ShowWindow(hwndTemp, SW_HIDE);
if(current.file.IsEmpty() && current.id == 0) //custom
{
ShowWindow(GetDlgItem(hwnd, IDC_SERVERLIST), SW_HIDE);
hwndTemp = GetDlgItem(hwnd, IDC_URL);
LoadSettingEditString(hwndTemp, TEXT("Publish"), TEXT("URL"), NULL);
SendDlgItemMessage(hwnd, IDC_SERVICE, CB_SETCURSEL, 0, 0);
}
else
{
ShowWindow(GetDlgItem(hwnd, IDC_URL), SW_HIDE);
hwndTemp = GetDlgItem(hwnd, IDC_SERVERLIST);
LoadSettingComboString(hwndTemp, TEXT("Publish"), TEXT("URL"), NULL);
}
if(mode != 0) ShowWindow(hwndTemp, SW_HIDE);
//--------------------------------------------
hwndTemp = GetDlgItem(hwnd, IDC_LOWLATENCYMODE);
BOOL bLowLatencyMode = AppConfig->GetInt(TEXT("Publish"), TEXT("LowLatencyMode"), 0);
SendMessage(hwndTemp, BM_SETCHECK, bLowLatencyMode ? BST_CHECKED : BST_UNCHECKED, 0);
//--------------------------------------------
hwndTemp = GetDlgItem(hwnd, IDC_AUTORECONNECT);
BOOL bAutoReconnect = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnect"), 1);
SendMessage(hwndTemp, BM_SETCHECK, bAutoReconnect ? BST_CHECKED : BST_UNCHECKED, 0);
if(mode != 0) ShowWindow(hwndTemp, SW_HIDE);
hwndTemp = GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT);
EnableWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), bAutoReconnect);
EnableWindow(hwndTemp, bAutoReconnect);
int retryTime = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnectTimeout"), 10);
if(retryTime > 60) retryTime = 60;
else if(retryTime < 0) retryTime = 0;
SendMessage(hwndTemp, UDM_SETRANGE32, 0, 60);
SendMessage(hwndTemp, UDM_SETPOS32, 0, retryTime);
if(mode != 0) ShowWindow(hwndTemp, SW_HIDE);
//--------------------------------------------
hwndTemp = GetDlgItem(hwnd, IDC_DELAY);
int delayTime = AppConfig->GetInt(TEXT("Publish"), TEXT("Delay"), 0);
SendMessage(hwndTemp, UDM_SETRANGE32, 0, 900);
SendMessage(hwndTemp, UDM_SETPOS32, 0, delayTime);
//--------------------------------------------
if(mode != 0)
{
ShowWindow(GetDlgItem(hwnd, IDC_SERVICE_STATIC), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_PLAYPATH_STATIC), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_URL_STATIC), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_SERVER_STATIC), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_LOWLATENCYMODE), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_STATIC), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_DELAY_STATIC), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_DELAY_EDIT), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_DELAY), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_KEEPRECORDING), SW_HIDE);
//ShowWindow(GetDlgItem(hwnd, IDC_DASHBOARDLINK), SW_HIDE);
//ShowWindow(GetDlgItem(hwnd, IDC_DASHBOARDLINK_STATIC), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_SAVETOFILE), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_BROWSEUSERSERVICES), SW_HIDE);
AdjustWindowPos(GetDlgItem(hwnd, IDC_SAVEPATH_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_SAVEPATH), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_BROWSE), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH_EDIT), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERBROWSE), 0, -data.fileControlOffset);
}
//--------------------------------------------
HWND hwndToolTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hwnd, NULL, hinstMain, NULL);
TOOLINFO ti;
zero(&ti, sizeof(ti));
ti.cbSize = sizeof(ti);
ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND;
ti.hwnd = hwnd;
if (LocaleIsRTL())
ti.uFlags |= TTF_RTLREADING;
SendMessage(hwndToolTip, TTM_SETMAXTIPWIDTH, 0, 500);
SendMessage(hwndToolTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, 20000);
//--------------------------------------------
BOOL bKeepRecording = AppConfig->GetInt(TEXT("Publish"), TEXT("KeepRecording"));
SendMessage(GetDlgItem(hwnd, IDC_KEEPRECORDING), BM_SETCHECK, bKeepRecording ? BST_CHECKED : BST_UNCHECKED, 0);
BOOL bSaveToFile = AppConfig->GetInt(TEXT("Publish"), TEXT("SaveToFile"));
SendMessage(GetDlgItem(hwnd, IDC_SAVETOFILE), BM_SETCHECK, bSaveToFile ? BST_CHECKED : BST_UNCHECKED, 0);
String path = OSGetDefaultVideoSavePath(L"\\$T.flv");
CTSTR lpSavePath = AppConfig->GetStringPtr(TEXT("Publish"), TEXT("SavePath"), path.IsValid() ? path.Array() : nullptr);
SetWindowText(GetDlgItem(hwnd, IDC_SAVEPATH), lpSavePath);
ti.lpszText = (LPWSTR)Str("Settings.Publish.SavePathTooltip");
ti.uId = (UINT_PTR)GetDlgItem(hwnd, IDC_SAVEPATH);
SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
EnableWindow(GetDlgItem(hwnd, IDC_KEEPRECORDING), true);
EnableWindow(GetDlgItem(hwnd, IDC_SAVEPATH), true);
EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);
//--------------------------------------------
//SetWindowText(GetDlgItem(hwnd, IDC_DASHBOARDLINK), App->strDashboard);
//--------------------------------------------
bool settingChanged = false;
hwndTemp = GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH);
int replayBufferLength = AppConfig->GetInt(L"Publish", L"ReplayBufferLength", 1);
if (replayBufferLength < 1)
{
replayBufferLength = 1;
settingChanged = true;
}
else if (replayBufferLength > (30 * 60))
{
replayBufferLength = 30 * 60;
settingChanged = true;
}
SendMessage(hwndTemp, UDM_SETRANGE32, 1, 30 * 60); //upper limit of 30 minutes
SendMessage(hwndTemp, UDM_SETPOS32, 0, replayBufferLength);
ti.lpszText = (LPWSTR)Str("Settings.Publish.ReplayBufferTooltip");
ti.uId = (UINT_PTR)GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH_EDIT);
SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
path = OSGetDefaultVideoSavePath(L"\\Replay-$T.flv");
lpSavePath = AppConfig->GetStringPtr(L"Publish", L"ReplayBufferSavePath", path.IsValid() ? path.Array() : nullptr);
SetWindowText(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH), lpSavePath);
ti.lpszText = (LPWSTR)Str("Settings.Publish.SavePathTooltip");
ti.uId = (UINT_PTR)GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH);
SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
//--------------------------------------------
UpdateMemoryUsage(hwnd);
//--------------------------------------------
SetWarningInfo();
if (settingChanged)
return TRUE;
ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_HIDE);
SetChangedSettings(false);
return TRUE;
}
case WM_CTLCOLORSTATIC:
{
switch (GetDlgCtrlID((HWND)lParam))
{
case IDC_WARNINGS:
SetTextColor((HDC)wParam, RGB(255, 0, 0));
SetBkColor((HDC)wParam, COLORREF(GetSysColor(COLOR_3DFACE)));
return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE);
case IDC_REPLAYBUFFERMEMORY:
{
bool highUsage = false;
String mem = GetEditText(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY));
TSTR pos = std::find(mem.Array(), mem.Array() + mem.Length(), '/');
if (pos != (mem.Array() + mem.Length()))
{
int used = mem.Left((UINT)(pos - mem.Array() - 1)).ToInt();
int avail = mem.Right((UINT)(mem.Array() + mem.Length() - pos - 2)).ToInt();
highUsage = used > (avail / 2);
}
SetTextColor((HDC)wParam, highUsage ? RGB(255, 0, 0) : COLORREF(GetSysColor(COLOR_WINDOWTEXT)));
SetBkColor((HDC)wParam, COLORREF(GetSysColor(COLOR_3DFACE)));
return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE);
}
}
}
break;
case WM_DESTROY:
{
}
break;
case WM_NOTIFY:
{
NMHDR *nmHdr = (NMHDR*)lParam;
if(nmHdr->idFrom == IDC_AUTORECONNECT_TIMEOUT)
{
if(nmHdr->code == UDN_DELTAPOS)
SetChangedSettings(true);
}
break;
}
case WM_COMMAND:
{
bool bDataChanged = false;
switch(LOWORD(wParam))
{
case IDC_MODE:
{
if(HIWORD(wParam) != CBN_SELCHANGE)
break;
int mode = (int)SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
int swShowControls = (mode == 0) ? SW_SHOW : SW_HIDE;
ShowWindow(GetDlgItem(hwnd, IDC_SERVICE), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_PLAYPATH), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_BROWSEUSERSERVICES), swShowControls);
int serviceID = (int)SendMessage(GetDlgItem(hwnd, IDC_SERVICE), CB_GETCURSEL, 0, 0);
if(serviceID == 0)
{
ShowWindow(GetDlgItem(hwnd, IDC_SERVERLIST), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_URL), swShowControls);
}
else
{
ShowWindow(GetDlgItem(hwnd, IDC_SERVERLIST), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_URL), SW_HIDE);
}
if(mode == 0 && data.mode == 1)
{
AdjustWindowPos(GetDlgItem(hwnd, IDC_SAVEPATH_STATIC), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_SAVEPATH), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_BROWSE), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH_EDIT), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH_STATIC), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY_STATIC), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH_STATIC), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH), 0, data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERBROWSE), 0, data.fileControlOffset);
}
else if(mode == 1 && data.mode == 0)
{
AdjustWindowPos(GetDlgItem(hwnd, IDC_SAVEPATH_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_SAVEPATH), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_BROWSE), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH_EDIT), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERLENGTH_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERMEMORY), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH_STATIC), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERSAVEPATH), 0, -data.fileControlOffset);
AdjustWindowPos(GetDlgItem(hwnd, IDC_REPLAYBUFFERBROWSE), 0, -data.fileControlOffset);
}
data.mode = mode;
ShowWindow(GetDlgItem(hwnd, IDC_SERVICE_STATIC), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_PLAYPATH_STATIC), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_URL_STATIC), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_SERVER_STATIC), swShowControls);
//ShowWindow(GetDlgItem(hwnd, IDC_DASHBOARDLINK), swShowControls);
//ShowWindow(GetDlgItem(hwnd, IDC_DASHBOARDLINK_STATIC), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_LOWLATENCYMODE), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_STATIC), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_DELAY_STATIC), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_DELAY_EDIT), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_DELAY), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_SAVETOFILE), swShowControls);
ShowWindow(GetDlgItem(hwnd, IDC_KEEPRECORDING), swShowControls);
SetWarningInfo();
bDataChanged = true;
break;
}
case IDC_SERVICE:
if(HIWORD(wParam) == CBN_SELCHANGE)
{
int serviceID = (int)SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
if(serviceID == 0)
{
ShowWindow(GetDlgItem(hwnd, IDC_SERVERLIST), SW_HIDE);
ShowWindow(GetDlgItem(hwnd, IDC_URL), SW_SHOW);
SetWindowText(GetDlgItem(hwnd, IDC_URL), NULL);
//SetWindowText(GetDlgItem(hwnd, IDC_DASHBOARDLINK), NULL);
}
else
{
ShowWindow(GetDlgItem(hwnd, IDC_URL), SW_HIDE);
hwndTemp = GetDlgItem(hwnd, IDC_SERVERLIST);
ShowWindow(hwndTemp, SW_SHOW);
SendMessage(hwndTemp, CB_RESETCONTENT, 0, 0);
if (serviceID >= 0 && serviceID < (int)services.size())
{
auto serviceData = LoadService(services[serviceID]);
auto service = serviceData.second;
if (service)
{
XElement *servers = service->GetElement(TEXT("servers"));
if (servers)
{
UINT numServers = servers->NumDataItems();
for (UINT i = 0; i < numServers; i++)
{
XDataItem *server = servers->GetDataItemByID(i);
SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)server->GetName());
}
}
}
}
SendMessage(hwndTemp, CB_SETCURSEL, 0, 0);
}
SetWindowText(GetDlgItem(hwnd, IDC_PLAYPATH), NULL);
bDataChanged = true;
SetWarningInfo();
}
break;
case IDC_AUTORECONNECT:
if(HIWORD(wParam) == BN_CLICKED)
{
BOOL bAutoReconnect = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED;
EnableWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT), bAutoReconnect);
EnableWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), bAutoReconnect);
SetChangedSettings(true);
}
break;
case IDC_AUTORECONNECT_TIMEOUT_EDIT:
if(HIWORD(wParam) == EN_CHANGE)
SetChangedSettings(true);
break;
case IDC_DELAY_EDIT:
if(HIWORD(wParam) == EN_CHANGE)
bDataChanged = true;
break;
case IDC_REPLAYBUFFERLENGTH_EDIT:
if (HIWORD(wParam) == EN_CHANGE)
{
bDataChanged = true;
UpdateMemoryUsage(hwnd);
}
break;
case IDC_KEEPRECORDING:
if(HIWORD(wParam) == BN_CLICKED)
{
BOOL bKeepRecording = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED;
App->bKeepRecording = bKeepRecording != 0;
bDataChanged = true;
}
break;
case IDC_BROWSE:
case IDC_REPLAYBUFFERBROWSE:
{
bool replayBuffer = LOWORD(wParam) == IDC_REPLAYBUFFERBROWSE;
TCHAR lpFile[512];
OPENFILENAME ofn;
zero(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = lpFile;
ofn.nMaxFile = 511;
ofn.lpstrFile[0] = 0;
ofn.lpstrFilter = TEXT("Flash Video File (*.flv)\0*.flv\0MP4 File (*.mp4)\0*.mp4\0");
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.nFilterIndex = 1;
String path = OSGetDefaultVideoSavePath();
ofn.lpstrInitialDir = AppConfig->GetStringPtr(TEXT("Publish"), replayBuffer ? L"LastReplayBufferSaveDir" : TEXT("LastSaveDir"), path.IsValid() ? path.Array() : nullptr);
ofn.Flags = OFN_PATHMUSTEXIST;
TCHAR curDirectory[512];
GetCurrentDirectory(511, curDirectory);
BOOL bChoseFile = GetSaveFileName(&ofn);
SetCurrentDirectory(curDirectory);
if(*lpFile && bChoseFile)
{
String strFile = lpFile;
strFile.FindReplace(TEXT("\\"), TEXT("/"));
String strExtension = GetPathExtension(strFile);
if(strExtension.IsEmpty() || (!strExtension.CompareI(TEXT("flv")) && /*!strExtension.CompareI(TEXT("avi")) &&*/ !strExtension.CompareI(TEXT("mp4"))))
{
switch(ofn.nFilterIndex)
{
case 1:
strFile << TEXT(".flv"); break;
case 2:
strFile << TEXT(".mp4"); break;
/*case 3:
strFile << TEXT(".avi"); break;*/
}
}
String strFilePath = GetPathDirectory(strFile).FindReplace(TEXT("/"), TEXT("\\")) << TEXT("\\");
AppConfig->SetString(TEXT("Publish"), replayBuffer ? L"LastReplayBufferSaveDir" : TEXT("LastSaveDir"), strFilePath);
strFile.FindReplace(TEXT("/"), TEXT("\\"));
SetWindowText(GetDlgItem(hwnd, replayBuffer ? IDC_REPLAYBUFFERSAVEPATH : IDC_SAVEPATH), strFile);
bDataChanged = true;
}
break;
}
case IDC_BROWSEUSERSERVICES:
ShellExecute(NULL, L"open", FormattedString(L"%s/services", API->GetAppDataPath()), 0, 0, SW_SHOWNORMAL);
break;
case IDC_LOWLATENCYMODE:
case IDC_SAVETOFILE:
if(HIWORD(wParam) == BN_CLICKED)
bDataChanged = true;
break;
case IDC_PLAYPATH:
case IDC_URL:
case IDC_SAVEPATH:
case IDC_REPLAYBUFFERSAVEPATH:
if(HIWORD(wParam) == EN_CHANGE)
bDataChanged = true;
break;
case IDC_SERVERLIST:
if(HIWORD(wParam) == CBN_EDITCHANGE || HIWORD(wParam) == CBN_SELCHANGE)
bDataChanged = true;
break;
}
if(bDataChanged)
{
if (App->GetVideoEncoder())
ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_SHOW);
SetChangedSettings(true);
}
break;
}
}
return FALSE;
}