AvCaster.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*\
  2. |*| Copyright 2015 bill-auger <https://github.com/bill-auger/av-caster/issues>
  3. |*|
  4. |*| This file is part of the AvCaster program.
  5. |*|
  6. |*| AvCaster is free software: you can redistribute it and/or modify
  7. |*| it under the terms of the GNU Lesser General Public License version 3
  8. |*| as published by the Free Software Foundation.
  9. |*|
  10. |*| AvCaster is distributed in the hope that it will be useful,
  11. |*| but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. |*| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. |*| GNU Lesser General Public License for more details.
  14. |*|
  15. |*| You should have received a copy of the GNU Lesser General Public License
  16. |*| along with AvCaster. If not, see <http://www.gnu.org/licenses/>.
  17. \*/
  18. #include "Gstreamer.h"
  19. #include "AvCaster.h"
  20. #include "Trace/TraceAvCaster.h"
  21. /* AvCaster public class variables */
  22. ScopedPointer<AvCasterStore> AvCaster::Store ; // Initialize()
  23. /* AvCaster private class variables */
  24. MainContent* AvCaster::Gui = nullptr ; // Initialize()
  25. #ifndef DISABLE_CHAT
  26. ScopedPointer<IrcClient> AvCaster::Irc = nullptr ; // Initialize()
  27. #endif // DISABLE_CHAT
  28. StringArray AvCaster::CliParams ; // Initialize()
  29. bool AvCaster::IsInitialized = false ; // Initialize()
  30. bool AvCaster::IsMediaEnabled = true ; // HandleCliParamsPreInit()
  31. bool AvCaster::IsScreenEnabled = true ; // HandleCliParamsPreInit()
  32. bool AvCaster::IsCameraEnabled = true ; // HandleCliParamsPreInit()
  33. bool AvCaster::IsTextEnabled = true ; // HandleCliParamsPreInit()
  34. bool AvCaster::IsImageEnabled = true ; // HandleCliParamsPreInit()
  35. bool AvCaster::IsCompositorEnabled = true ; // HandleCliParamsPreInit()
  36. bool AvCaster::IsPreviewEnabled = true ; // HandleCliParamsPreInit()
  37. bool AvCaster::IsAudioEnabled = true ; // HandleCliParamsPreInit()
  38. bool AvCaster::IsChatEnabled = true ; // HandleCliParamsPreInit()
  39. Array<Alert*> AvCaster::Alerts ; // Warning() , Error()
  40. bool AvCaster::IsAlertModal = false ; // GetModalCb() , OnModalDismissed()
  41. /* AvCaster public class methods */
  42. void AvCaster::Warning(String message_text)
  43. {
  44. Alerts.add(new Alert(GUI::ALERT_TYPE_WARNING , message_text)) ;
  45. }
  46. void AvCaster::Error(String message_text)
  47. {
  48. Alerts.add(new Alert(GUI::ALERT_TYPE_ERROR , message_text)) ;
  49. }
  50. void AvCaster::AddChatLine(String prefix , String nick , String message)
  51. {
  52. Gui->chat->addChatLine(prefix , nick , message) ;
  53. }
  54. #ifndef DISABLE_CHAT
  55. void AvCaster::SendChat(String chat_message) { Irc->sendChat(chat_message) ; }
  56. #else // DISABLE_CHAT
  57. void AvCaster::SendChat(String chat_message) {}
  58. #endif // DISABLE_CHAT
  59. ModalComponentManager::Callback* AvCaster::GetModalCb()
  60. {
  61. IsAlertModal = true ;
  62. return ModalCallbackFunction::create(OnModalDismissed , 0) ;
  63. }
  64. void AvCaster::OnModalDismissed(int result , int unused) { IsAlertModal = false ; }
  65. bool AvCaster::GetIsInitialized() { return IsInitialized ; }
  66. bool AvCaster::GetIsMediaEnabled() { return IsMediaEnabled ; }
  67. bool AvCaster::GetIsScreenEnabled() { return IsScreenEnabled ; }
  68. bool AvCaster::GetIsCameraEnabled() { return IsCameraEnabled ; }
  69. bool AvCaster::GetIsTextEnabled() { return IsTextEnabled ; }
  70. bool AvCaster::GetIsImageEnabled() { return IsImageEnabled ; }
  71. bool AvCaster::GetIsCompositorEnabled() { return IsCompositorEnabled ; }
  72. bool AvCaster::GetIsPreviewEnabled() { return IsPreviewEnabled ; }
  73. bool AvCaster::GetIsAudioEnabled() { return IsAudioEnabled ; }
  74. bool AvCaster::GetIsChatEnabled() { return IsChatEnabled ; }
  75. Rectangle<int> AvCaster::GetPreviewBounds() { return Gui->getPreviewBounds() ; }
  76. void AvCaster::SetConfig(const Identifier& a_key , var a_value)
  77. {
  78. DEBUG_TRACE_SET_CONFIG
  79. Store->setConfig(a_key , a_value) ;
  80. }
  81. ValueTree AvCaster::GetConfigStore() { return ValueTree(Store->config) ; }
  82. void AvCaster::DeactivateControl(const Identifier& a_key) { Store->deactivateControl(a_key) ; }
  83. void AvCaster::StorePreset(String preset_name) { Store->storePreset(preset_name) ; }
  84. void AvCaster::RenamePreset(String preset_name) { Store->renamePreset(preset_name) ; }
  85. void AvCaster::DeletePreset() { Store->deletePreset() ; }
  86. void AvCaster::ResetPreset() { Store->resetPreset() ; }
  87. bool AvCaster::SetPreset(String preset_name , int option_n)
  88. {
  89. int stored_option_n = GetPresetIdx() ;
  90. String stored_preset_name = GetPresetName() ;
  91. bool is_valid_option = !!(~option_n) ;
  92. bool is_static_preset = IsStaticPreset() ;
  93. bool is_empty = preset_name.isEmpty() ;
  94. bool has_name_changed = preset_name != stored_preset_name && !is_empty ;
  95. bool should_rename_preset = !is_valid_option && has_name_changed && !is_static_preset ;
  96. bool should_reset_option = RejectPresetChange() || should_rename_preset ;
  97. var value = var((is_valid_option) ? option_n : stored_option_n) ;
  98. DEBUG_TRACE_HANDLE_PRESETCOMBO
  99. // reject empty preset name
  100. if (!is_valid_option && is_empty) return false ;
  101. // rename preset , restore selection , or commit preset change
  102. if (should_rename_preset) RenamePreset(preset_name) ;
  103. if (!should_reset_option) SetConfig(CONFIG::PRESET_ID , value) ;
  104. else RefreshGui() ;
  105. return true ;
  106. }
  107. bool AvCaster::RejectPresetChange()
  108. {
  109. bool is_output_on = bool(Store->config[CONFIG::IS_OUTPUT_ACTIVE_ID]) ;
  110. DEBUG_TRACE_REJECT_CONFIG_CHANGE
  111. if (is_output_on) Warning(GUI::CONFIG_CHANGE_ERROR_MSG) ;
  112. return is_output_on ;
  113. }
  114. bool AvCaster::IsStaticPreset() { return AvCaster::GetPresetIdx() < CONFIG::N_STATIC_PRESETS ; }
  115. int AvCaster::GetPresetIdx() { return int(Store->root[CONFIG::PRESET_ID]) ; }
  116. String AvCaster::GetPresetName()
  117. {
  118. ValueTree current_preset = Store->presets.getChild(GetPresetIdx()) ;
  119. return STRING(current_preset[CONFIG::PRESET_NAME_ID]) ;
  120. }
  121. bool AvCaster::GetIsPreviewActive()
  122. {
  123. return bool(Store->config[CONFIG::IS_PREVIEW_ACTIVE_ID]) && !GetIsConfigPending() ;
  124. }
  125. bool AvCaster::GetIsConfigPending() { return bool(Store->root[CONFIG::IS_PENDING_ID]) ; }
  126. StringArray AvCaster::GetPresetsNames() { return Store->presetsNames() ; }
  127. StringArray AvCaster::GetCameraNames() { return Store->cameraNames() ; }
  128. StringArray AvCaster::GetAudioNames() { return Store->audioNames() ; }
  129. StringArray AvCaster::GetCameraResolutions() { return Store->getCameraResolutions() ; }
  130. String AvCaster::GetCameraResolution()
  131. {
  132. int resolution_n = int(Store->config[CONFIG::CAMERA_RES_ID]) ;
  133. StringArray resolutions = Store->getCameraResolutions() ;
  134. return resolutions[(~resolution_n) ? resolution_n : 0] ;
  135. }
  136. String AvCaster::GetCameraPath()
  137. {
  138. ValueTree camera_store = Store->getCameraConfig() ;
  139. return STRING(camera_store[CONFIG::CAMERA_PATH_ID]) ;
  140. }
  141. int AvCaster::GetCameraRate()
  142. {
  143. ValueTree camera_store = Store->getCameraConfig() ;
  144. int camera_rate = int(camera_store[CONFIG::CAMERA_RATE_ID]) ;
  145. // int output_rate_idx = int(Store->config[CONFIG::FRAMERATE_ID]) ;
  146. // int output_rate = CONFIG::FRAMERATES[output_rate_idx].getIntValue() ;
  147. return (camera_store.isValid()) ? camera_rate : CONFIG::DEFAULT_CAMERA_RATE ;
  148. }
  149. String AvCaster::GetVersionString()
  150. {
  151. return APP::APP_NAME + " v" + ProjectInfo::versionString ;
  152. }
  153. void AvCaster::UpdateIrcHost(StringArray alias_uris , String actual_host)
  154. {
  155. Store->updateIrcHost(alias_uris , actual_host) ;
  156. }
  157. /*
  158. void AvCaster::RenameServer(String requested_host , String actual_host)
  159. {
  160. ValueTree server_store = Store->servers.getChildWithProperty(CONFIG::HOST_ID , requested_host) ;
  161. ValueTree chatters_store = server_store.getChildWithName(CONFIG::CHATTERS_ID) ;
  162. int server_idx = Store->servers.indexOf(server_store) ;
  163. Identifier server_id = CONFIG::FilterId(actual_host , APP::VALID_URI_CHARS) ;
  164. ValueTree updated_store = ValueTree(server_id) ;
  165. Store->servers.removeChild (server_store , nullptr) ;
  166. Store->servers.addChild (updated_store , server_idx , nullptr) ;
  167. server_store .removeChild (chatters_store , nullptr) ;
  168. updated_store .addChild (chatters_store , -1 , nullptr) ;
  169. updated_store .copyPropertiesFrom(server_store , nullptr) ;
  170. server_store .setProperty (CONFIG::HOST_ID , actual_host , nullptr) ;
  171. }
  172. */
  173. #ifdef PREFIX_CHAT_NICKS
  174. void AvCaster::UpdateChatNicks(String host , String channel , StringArray nicks)
  175. {
  176. Store->updateChatNicks(host , channel , nicks) ;
  177. #else // PREFIX_CHAT_NICKS
  178. void AvCaster::UpdateChatNicks(String host , StringArray nicks)
  179. {
  180. Store->updateChatNicks(host , nicks) ;
  181. #endif // PREFIX_CHAT_NICKS
  182. const MessageManagerLock mmLock ; Gui->chat->updateVisiblilty() ;
  183. }
  184. StringArray AvCaster::GetChatNicks(ValueTree chatters_store)
  185. {
  186. return Store->getChatNicks(chatters_store) ;
  187. }
  188. /* AvCaster private class methods */
  189. bool AvCaster::Initialize(MainContent* main_content)
  190. {
  191. Gui = main_content ;
  192. CliParams = JUCEApplicationBase::getCommandLineParameterArray() ;
  193. CliParams.removeEmptyStrings() ; CliParams.removeDuplicates(false) ;
  194. DEBUG_DISABLE_FEATURES
  195. if (HandleCliParamsTerminating()) return false ;
  196. DEBUG_TRACE_INIT_PHASE_1
  197. if (!ValidateEnvironment()) return false ;
  198. DEBUG_TRACE_INIT_PHASE_2
  199. // load persistent configuration
  200. if ((Store = new AvCasterStore()) == nullptr) return false ;
  201. DEBUG_TRACE_INIT_PHASE_3
  202. // initialze GUI
  203. Gui->initialize(Store->servers) ;
  204. if (!ProcessCliParams()) return false ;
  205. DEBUG_TRACE_INIT_PHASE_4
  206. // initialize libgtreamer
  207. if (GetIsMediaEnabled() && !Gstreamer::Initialize(Gui->getWindowHandle())) return false ;
  208. DEBUG_TRACE_INIT_PHASE_5
  209. // initialize libircclient
  210. #ifndef DISABLE_CHAT
  211. if (GetIsChatEnabled() && (Irc = new IrcClient(Store->servers)) == nullptr) return false ;
  212. #endif // DISABLE_CHAT
  213. DEBUG_TRACE_INIT_PHASE_6
  214. // finalize initialization
  215. StorePreset(GetPresetName()) ; RefreshGui() ; IsInitialized = true ;
  216. // subscribe to model change events
  217. Store->listen(true) ;
  218. DEBUG_TRACE_INIT_PHASE_7
  219. return IsInitialized ;
  220. }
  221. void AvCaster::Shutdown()
  222. {
  223. DisplayAlert() ;
  224. DEBUG_TRACE_SHUTDOWN_PHASE_1
  225. #ifndef DISABLE_CHAT
  226. // shutdown network
  227. if (GetIsChatEnabled() && Irc != nullptr && Irc->isThreadRunning()) Irc->stopThread(500) ;
  228. Irc = nullptr ;
  229. #endif // DISABLE_CHAT
  230. DEBUG_TRACE_SHUTDOWN_PHASE_2
  231. // shutdown media
  232. if (GetIsMediaEnabled()) Gstreamer::Shutdown() ;
  233. DEBUG_TRACE_SHUTDOWN_PHASE_3
  234. // shutdown storage
  235. if (Store != nullptr) Store->listen(false) ; IsInitialized = false ;
  236. if (Store != nullptr) Store->storeConfig() ; Store = nullptr ;
  237. }
  238. void AvCaster::HandleTimer(int timer_id)
  239. {
  240. switch (timer_id)
  241. {
  242. case APP::TIMER_HI_ID: break ;
  243. case APP::TIMER_MED_ID: UpdateStatusGUI() ; DisplayAlert() ; break ;
  244. #ifndef DISABLE_CHAT
  245. case APP::TIMER_LO_ID: if (IsChatEnabled) Irc->startThread() ; break ;
  246. #endif // DISABLE_CHAT
  247. default: break ;
  248. }
  249. }
  250. void AvCaster::UpdateStatusGUI()
  251. {
  252. /*
  253. Gui->statusbar->setStatusL("Frame: " + String(MuxStream->currentFrame ) + " " +
  254. "FPS: " + String(MuxStream->currentFps ) ) ;
  255. Gui->statusbar->setStatusC("Bitrate: " + String(MuxStream->currentBitrate) + " " +
  256. "Q: " + String(MuxStream->currentQ ) ) ;
  257. Gui->statusbar->setStatusR("Filesize: " + String(MuxStream->currentSize ) + " " +
  258. "Duration: " + String(MuxStream->currentTime ) ) ;
  259. */
  260. }
  261. void AvCaster::HandleConfigChanged(const Identifier& a_key)
  262. {
  263. if (!Store->isControlKey(a_key)) return ;
  264. // reconfigure gStreamer element
  265. Gstreamer::Reconfigure(a_key) ;
  266. // store changes
  267. StorePreset(GetPresetName()) ;
  268. // refresh GUI
  269. RefreshGui() ;
  270. }
  271. void AvCaster::RefreshGui()
  272. {
  273. bool is_config_pending = bool(Store->root [CONFIG::IS_PENDING_ID ]) ;
  274. bool is_preview_on = bool(Store->config[CONFIG::IS_PREVIEW_ACTIVE_ID]) && IsPreviewEnabled ;
  275. Component* control_component = (is_config_pending) ? static_cast<Component*>(Gui->presets ) :
  276. static_cast<Component*>(Gui->controls) ;
  277. Component* view_component = (is_config_pending) ? static_cast<Component*>(Gui->config ) :
  278. (is_preview_on ) ? static_cast<Component*>(Gui->preview) :
  279. static_cast<Component*>(Gui->chat ) ;
  280. DEBUG_TRACE_REFRESH_GUI
  281. // propogate configuration change to all interested panes
  282. Gui->background ->toFront(true) ;
  283. control_component->toFront(true) ;
  284. view_component ->toFront(true) ;
  285. RefreshStatus() ;
  286. }
  287. void AvCaster::RefreshStatus()
  288. {
  289. bool is_output_active = bool(Store->config[CONFIG::IS_OUTPUT_ACTIVE_ID]) ;
  290. int sink_idx = int (Store->config[CONFIG::OUTPUT_SINK_ID ]) ;
  291. String title_text = APP::APP_NAME + " - " ;
  292. String status_text ;
  293. // titlebar text
  294. if (!is_output_active ) title_text += GUI::IDLE_TITLE_TEXT ;
  295. else if (sink_idx == CONFIG::FILE_OUTPUT_IDX) title_text += GUI::FILE_TITLE_TEXT ;
  296. else if (sink_idx == CONFIG::RTMP_OUTPUT_IDX) title_text += GUI::RTMP_TITLE_TEXT ;
  297. else return ;
  298. // statusbar text
  299. if (GetIsInitialized()) status_text = GUI::READY_STATUS_TEXT ;
  300. Gui->getTopLevelComponent()->setName (title_text ) ;
  301. Gui->statusbar ->setStatusL(status_text) ;
  302. }
  303. bool AvCaster::HandleCliParamsTerminating()
  304. {
  305. DEBUG_TRACE_HANDLE_CLI_PARAMS_TERMINATING
  306. if (CliParams.contains(APP::CLI_HELP_TOKEN ))
  307. { printf("%s\n" , CHARSTAR(APP::CLI_USAGE_MSG )) ; return true ; }
  308. else if (CliParams.contains(APP::CLI_PRESETS_TOKEN))
  309. {
  310. // load persistent configuration as normal
  311. Store = new AvCasterStore() ; int n_presets = Store->presets.getNumChildren() ;
  312. if (Store == nullptr || n_presets == 0) return true ;
  313. // dump preset indices and names then quit
  314. printf("Presets:\n") ;
  315. for (int preset_n = 0 ; preset_n < n_presets ; ++preset_n)
  316. {
  317. ValueTree preset = Store->presets.getChild(preset_n) ;
  318. String preset_name = STRING(preset[CONFIG::PRESET_NAME_ID]) ;
  319. printf("\t%d: \"%s\"\n" , preset_n , CHARSTAR(preset_name)) ;
  320. }
  321. return true ;
  322. }
  323. else if (CliParams.contains(APP::CLI_VERSION_TOKEN))
  324. { printf("%s\n" , CHARSTAR(APP::CLI_VERSION_MSG)) ; return true ; }
  325. return false ;
  326. }
  327. bool AvCaster::ProcessCliParams()
  328. {
  329. DEBUG_TRACE_HANDLE_CLI_PARAMS
  330. // load initial configuration preset
  331. if (CliParams.contains(APP::CLI_PRESET_TOKEN))
  332. {
  333. // set initial preset from cli param
  334. int token_idx = CliParams.indexOf(APP::CLI_PRESET_TOKEN) ;
  335. int preset_idx = CliParams[token_idx + 1].getIntValue() ;
  336. if (~preset_idx) Store->setConfig(CONFIG::PRESET_ID , preset_idx) ;
  337. }
  338. // disable features
  339. for (String* token = CliParams.begin() ; token != CliParams.end() ; ++token)
  340. if (*token == APP::CLI_DISABLE_MEDIA_TOKEN )
  341. IsMediaEnabled = IsScreenEnabled = IsCameraEnabled = IsTextEnabled =
  342. IsImageEnabled = IsCompositorEnabled = IsPreviewEnabled = IsAudioEnabled = false ;
  343. else if (*token == APP::CLI_SCREEN_ONLY_TOKEN )
  344. IsCameraEnabled = IsTextEnabled = IsImageEnabled = false ;
  345. else if (*token == APP::CLI_CAMERA_ONLY_TOKEN )
  346. IsScreenEnabled = IsTextEnabled = IsImageEnabled = false ;
  347. // else if (*token == APP::CLI_TEXT_ONLY_TOKEN )
  348. // IsScreenEnabled = IsCameraEnabled = IsImageEnabled = false ;
  349. // else if (*token == APP::CLI_IMAGE_ONLY_TOKEN )
  350. // IsScreenEnabled = IsCameraEnabled = IsTextEnabled = false ;
  351. else if (*token == APP::CLI_DISABLE_PREVIEW_TOKEN) IsPreviewEnabled = false ;
  352. else if (*token == APP::CLI_DISABLE_AUDIO_TOKEN ) IsAudioEnabled = false ;
  353. else if (*token == APP::CLI_DISABLE_CHAT_TOKEN ) IsChatEnabled = false ;
  354. // assert dependent compositor elements (TODO: remove these restrictions allowing any configuration)
  355. int n_video_inputs = static_cast<int>(IsScreenEnabled) + static_cast<int>(IsCameraEnabled) +
  356. static_cast<int>(IsTextEnabled ) + static_cast<int>(IsImageEnabled ) ;
  357. IsCompositorEnabled = n_video_inputs == APP::N_COMPOSITOR_INPUTS ;
  358. IsPreviewEnabled = IsCompositorEnabled && IsPreviewEnabled ;
  359. bool is_sane = !IsMediaEnabled || IsCompositorEnabled || n_video_inputs == 1 ;
  360. DUMP_DEBUG_MEDIA_SWITCHES
  361. return is_sane ;
  362. }
  363. bool AvCaster::ValidateEnvironment()
  364. {
  365. DEBUG_TRACE_VALIDATE_ENVIRONMENT
  366. return Gstreamer::IsSufficientVersion() &&
  367. #ifndef DISABLE_CHAT
  368. IrcClient::IsSufficientVersion() &&
  369. #endif // DISABLE_CHAT
  370. APP::HOME_DIR .isDirectory() &&
  371. APP::APPDATA_DIR.isDirectory() &&
  372. APP::VIDEOS_DIR .isDirectory() ;
  373. }
  374. void AvCaster::DisplayAlert()
  375. {
  376. if (IsAlertModal || Alerts.size() == 0) return ;
  377. GUI::AlertType message_type = Alerts[0]->messageType ;
  378. String message_text = Alerts[0]->messageText ;
  379. DEBUG_TRACE_DISPLAY_ALERT
  380. #ifdef SUPRESS_ALERTS
  381. Alerts.remove(0) ; return ;
  382. #endif // SUPRESS_ALERTS
  383. switch (message_type)
  384. {
  385. case GUI::ALERT_TYPE_WARNING: Gui->warning(message_text) ; break ;
  386. case GUI::ALERT_TYPE_ERROR: Gui->error (message_text) ; break ;
  387. default: break ;
  388. }
  389. Alerts.remove(0) ;
  390. }