calls_group_rtmp.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "calls/group/calls_group_rtmp.h"
  8. #include "apiwrap.h"
  9. #include "calls/group/calls_group_common.h"
  10. #include "data/data_channel.h"
  11. #include "data/data_chat.h"
  12. #include "data/data_user.h"
  13. #include "lang/lang_keys.h"
  14. #include "main/main_account.h"
  15. #include "main/main_session.h"
  16. #include "ui/boxes/confirm_box.h"
  17. #include "ui/layers/generic_box.h"
  18. #include "ui/text/text_utilities.h"
  19. #include "ui/toast/toast.h"
  20. #include "ui/widgets/buttons.h"
  21. #include "ui/widgets/popup_menu.h"
  22. #include "ui/wrap/vertical_layout.h"
  23. #include "ui/vertical_list.h"
  24. #include "styles/style_boxes.h"
  25. #include "styles/style_calls.h"
  26. #include "styles/style_info.h"
  27. #include "styles/style_layers.h"
  28. #include "styles/style_menu_icons.h"
  29. #include <QGuiApplication>
  30. #include <QStyle>
  31. namespace Calls::Group {
  32. namespace {
  33. constexpr auto kPasswordCharAmount = 24;
  34. void StartWithBox(
  35. not_null<Ui::GenericBox*> box,
  36. Fn<void()> done,
  37. Fn<void()> revoke,
  38. std::shared_ptr<Ui::Show> show,
  39. rpl::producer<RtmpInfo> &&data) {
  40. struct State {
  41. base::unique_qptr<Ui::PopupMenu> menu;
  42. };
  43. const auto state = box->lifetime().make_state<State>();
  44. StartRtmpProcess::FillRtmpRows(
  45. box->verticalLayout(),
  46. true,
  47. std::move(show),
  48. std::move(data),
  49. &st::boxLabel,
  50. &st::groupCallRtmpShowButton,
  51. &st::defaultSubsectionTitle,
  52. &st::attentionBoxButton,
  53. &st::defaultPopupMenu);
  54. box->setTitle(tr::lng_group_call_rtmp_title());
  55. Ui::AddDividerText(
  56. box->verticalLayout(),
  57. tr::lng_group_call_rtmp_info());
  58. box->addButton(tr::lng_group_call_rtmp_start(), done);
  59. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  60. box->setWidth(st::boxWideWidth);
  61. {
  62. const auto top = box->addTopButton(st::infoTopBarMenu);
  63. top->setClickedCallback([=] {
  64. state->menu = base::make_unique_q<Ui::PopupMenu>(
  65. top,
  66. st::popupMenuWithIcons);
  67. state->menu->addAction(
  68. tr::lng_group_invite_context_revoke(tr::now),
  69. revoke,
  70. &st::menuIconRemove);
  71. state->menu->setForcedOrigin(
  72. Ui::PanelAnimation::Origin::TopRight);
  73. top->setForceRippled(true);
  74. const auto raw = state->menu.get();
  75. raw->setDestroyedCallback([=] {
  76. if ((state->menu == raw) && top) {
  77. top->setForceRippled(false);
  78. }
  79. });
  80. state->menu->popup(top->mapToGlobal(top->rect().center()));
  81. return true;
  82. });
  83. }
  84. }
  85. } // namespace
  86. StartRtmpProcess::~StartRtmpProcess() {
  87. close();
  88. }
  89. void StartRtmpProcess::start(
  90. not_null<PeerData*> peer,
  91. std::shared_ptr<Ui::Show> show,
  92. Fn<void(JoinInfo)> done) {
  93. Expects(done != nullptr);
  94. const auto session = &peer->session();
  95. if (_request) {
  96. if (_request->peer == peer) {
  97. _request->show = std::move(show);
  98. _request->done = std::move(done);
  99. return;
  100. }
  101. session->api().request(_request->id).cancel();
  102. _request = nullptr;
  103. }
  104. _request = std::make_unique<RtmpRequest>(
  105. RtmpRequest{
  106. .peer = peer,
  107. .show = std::move(show),
  108. .done = std::move(done),
  109. });
  110. session->account().sessionChanges(
  111. ) | rpl::start_with_next([=] {
  112. _request = nullptr;
  113. }, _request->lifetime);
  114. requestUrl(false);
  115. }
  116. void StartRtmpProcess::close() {
  117. if (_request) {
  118. _request->peer->session().api().request(_request->id).cancel();
  119. if (const auto strong = _request->box.data()) {
  120. strong->closeBox();
  121. }
  122. _request = nullptr;
  123. }
  124. }
  125. void StartRtmpProcess::requestUrl(bool revoke) {
  126. const auto session = &_request->peer->session();
  127. _request->id = session->api().request(MTPphone_GetGroupCallStreamRtmpUrl(
  128. _request->peer->input,
  129. MTP_bool(revoke)
  130. )).done([=](const MTPphone_GroupCallStreamRtmpUrl &result) {
  131. auto data = result.match([&](
  132. const MTPDphone_groupCallStreamRtmpUrl &data) {
  133. return RtmpInfo{ .url = qs(data.vurl()), .key = qs(data.vkey()) };
  134. });
  135. processUrl(std::move(data));
  136. }).fail([=] {
  137. _request->show->showToast(Lang::Hard::ServerError());
  138. }).send();
  139. }
  140. void StartRtmpProcess::processUrl(RtmpInfo data) {
  141. if (!_request->box) {
  142. createBox();
  143. }
  144. _request->data = std::move(data);
  145. }
  146. void StartRtmpProcess::finish(JoinInfo info) {
  147. info.rtmpInfo = _request->data.current();
  148. _request->done(std::move(info));
  149. }
  150. void StartRtmpProcess::createBox() {
  151. auto done = [=] {
  152. const auto peer = _request->peer;
  153. const auto joinAs = (peer->isChat() && peer->asChat()->amCreator())
  154. ? peer
  155. : (peer->isChannel() && peer->asChannel()->amCreator())
  156. ? peer
  157. : peer->session().user();
  158. finish({ .peer = peer, .joinAs = joinAs, .rtmp = true });
  159. };
  160. auto revoke = [=] {
  161. const auto guard = base::make_weak(&_request->guard);
  162. _request->show->showBox(Ui::MakeConfirmBox({
  163. .text = tr::lng_group_call_rtmp_revoke_sure(),
  164. .confirmed = crl::guard(guard, [=](Fn<void()> &&close) {
  165. requestUrl(true);
  166. close();
  167. }),
  168. .confirmText = tr::lng_group_invite_context_revoke(),
  169. }));
  170. };
  171. auto object = Box(
  172. StartWithBox,
  173. std::move(done),
  174. std::move(revoke),
  175. _request->show,
  176. _request->data.value());
  177. object->boxClosing(
  178. ) | rpl::start_with_next([=] {
  179. _request = nullptr;
  180. }, _request->lifetime);
  181. _request->box = Ui::MakeWeak(object.data());
  182. _request->show->showBox(std::move(object));
  183. }
  184. void StartRtmpProcess::FillRtmpRows(
  185. not_null<Ui::VerticalLayout*> container,
  186. bool divider,
  187. std::shared_ptr<Ui::Show> show,
  188. rpl::producer<RtmpInfo> &&data,
  189. const style::FlatLabel *labelStyle,
  190. const style::IconButton *showButtonStyle,
  191. const style::FlatLabel *subsectionTitleStyle,
  192. const style::RoundButton *attentionButtonStyle,
  193. const style::PopupMenu *popupMenuStyle) {
  194. struct State {
  195. rpl::variable<bool> hidden = true;
  196. rpl::variable<QString> key;
  197. rpl::variable<QString> url;
  198. bool warned = false;
  199. };
  200. const auto &rowPadding = st::boxRowPadding;
  201. const auto passChar = QChar(container->style()->styleHint(
  202. QStyle::SH_LineEdit_PasswordCharacter));
  203. const auto state = container->lifetime().make_state<State>();
  204. state->key = rpl::duplicate(
  205. data
  206. ) | rpl::map([=](const auto &d) { return d.key; });
  207. state->url = std::move(
  208. data
  209. ) | rpl::map([=](const auto &d) { return d.url; });
  210. const auto showToast = [=](const QString &text) {
  211. show->showToast(text);
  212. };
  213. const auto addButton = [&](
  214. bool key,
  215. rpl::producer<QString> &&text) {
  216. auto wrap = object_ptr<Ui::RpWidget>(container);
  217. auto button = Ui::CreateChild<Ui::RoundButton>(
  218. wrap.data(),
  219. rpl::duplicate(text),
  220. st::groupCallRtmpCopyButton);
  221. button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  222. button->setClickedCallback(key
  223. ? Fn<void()>([=] {
  224. QGuiApplication::clipboard()->setText(state->key.current());
  225. showToast(tr::lng_group_call_rtmp_key_copied(tr::now));
  226. })
  227. : Fn<void()>([=] {
  228. QGuiApplication::clipboard()->setText(state->url.current());
  229. showToast(tr::lng_group_call_rtmp_url_copied(tr::now));
  230. }));
  231. Ui::AddSkip(container, st::groupCallRtmpCopyButtonTopSkip);
  232. const auto weak = container->add(std::move(wrap), rowPadding);
  233. Ui::AddSkip(container, st::groupCallRtmpCopyButtonBottomSkip);
  234. button->heightValue(
  235. ) | rpl::start_with_next([=](int height) {
  236. weak->resize(weak->width(), height);
  237. }, container->lifetime());
  238. return weak;
  239. };
  240. const auto addLabel = [&](rpl::producer<QString> &&text) {
  241. const auto label = container->add(
  242. object_ptr<Ui::FlatLabel>(
  243. container,
  244. std::move(text),
  245. *labelStyle,
  246. *popupMenuStyle),
  247. st::boxRowPadding + QMargins(0, 0, showButtonStyle->width, 0));
  248. label->setSelectable(true);
  249. label->setBreakEverywhere(true);
  250. return label;
  251. };
  252. // Server URL.
  253. Ui::AddSubsectionTitle(
  254. container,
  255. tr::lng_group_call_rtmp_url_subtitle(),
  256. st::groupCallRtmpSubsectionTitleAddPadding,
  257. subsectionTitleStyle);
  258. auto urlLabelContent = state->url.value();
  259. addLabel(std::move(urlLabelContent));
  260. Ui::AddSkip(container, st::groupCallRtmpUrlSkip);
  261. addButton(false, tr::lng_group_call_rtmp_url_copy());
  262. //
  263. if (divider) {
  264. Ui::AddDivider(container);
  265. }
  266. // Stream Key.
  267. Ui::AddSkip(container, st::groupCallRtmpKeySubsectionTitleSkip);
  268. Ui::AddSubsectionTitle(
  269. container,
  270. tr::lng_group_call_rtmp_key_subtitle(),
  271. st::groupCallRtmpSubsectionTitleAddPadding,
  272. subsectionTitleStyle);
  273. auto keyLabelContent = rpl::combine(
  274. state->hidden.value(),
  275. state->key.value()
  276. ) | rpl::map([passChar](bool hidden, const QString &key) {
  277. return key.isEmpty()
  278. ? QString()
  279. : hidden
  280. ? QString().fill(passChar, kPasswordCharAmount)
  281. : key;
  282. }) | rpl::after_next([=] {
  283. container->resizeToWidth(container->widthNoMargins());
  284. });
  285. const auto streamKeyLabel = addLabel(std::move(keyLabelContent));
  286. streamKeyLabel->setSelectable(false);
  287. const auto streamKeyButton = Ui::CreateChild<Ui::IconButton>(
  288. container.get(),
  289. *showButtonStyle);
  290. streamKeyLabel->topValue(
  291. ) | rpl::start_with_next([=, right = rowPadding.right()](int top) {
  292. streamKeyButton->moveToRight(
  293. st::groupCallRtmpShowButtonPosition.x(),
  294. top + st::groupCallRtmpShowButtonPosition.y());
  295. streamKeyButton->raise();
  296. }, container->lifetime());
  297. streamKeyButton->addClickHandler([=] {
  298. const auto toggle = [=] {
  299. const auto newValue = !state->hidden.current();
  300. state->hidden = newValue;
  301. streamKeyLabel->setSelectable(!newValue);
  302. streamKeyLabel->setAttribute(
  303. Qt::WA_TransparentForMouseEvents,
  304. newValue);
  305. };
  306. if (!state->warned && state->hidden.current()) {
  307. show->showBox(Ui::MakeConfirmBox({
  308. .text = tr::lng_group_call_rtmp_key_warning(
  309. Ui::Text::RichLangValue),
  310. .confirmed = [=](Fn<void()> &&close) {
  311. state->warned = true;
  312. toggle();
  313. close();
  314. },
  315. .confirmText = tr::lng_from_request_understand(),
  316. .cancelText = tr::lng_cancel(),
  317. .confirmStyle = attentionButtonStyle,
  318. .labelStyle = labelStyle,
  319. }));
  320. } else {
  321. toggle();
  322. }
  323. });
  324. addButton(true, tr::lng_group_call_rtmp_key_copy());
  325. //
  326. }
  327. } // namespace Calls::Group