mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Added ability to start livestream with RTMP.
This commit is contained in:
parent
97dbb98862
commit
8909b654d3
9 changed files with 423 additions and 6 deletions
|
@ -280,6 +280,8 @@ PRIVATE
|
|||
calls/group/calls_group_menu.h
|
||||
calls/group/calls_group_panel.cpp
|
||||
calls/group/calls_group_panel.h
|
||||
calls/group/calls_group_rtmp.cpp
|
||||
calls/group/calls_group_rtmp.h
|
||||
calls/group/calls_group_settings.cpp
|
||||
calls/group/calls_group_settings.h
|
||||
calls/group/calls_group_toasts.cpp
|
||||
|
|
|
@ -2381,6 +2381,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_group_call_join_as_changed" = "Members of this voice chat will now see you as {name}";
|
||||
"lng_group_call_join_as_changed_channel" = "Members of this live stream will now see you as {name}";
|
||||
|
||||
"lng_group_call_rtmp_title" = "Stream with other apps";
|
||||
"lng_group_call_rtmp_url_subtitle" = "Server URL";
|
||||
"lng_group_call_rtmp_url_copy" = "Copy Server URL";
|
||||
"lng_group_call_rtmp_url_copied" = "Server URL copied to clipboard.";
|
||||
"lng_group_call_rtmp_key_subtitle" = "Stream Key";
|
||||
"lng_group_call_rtmp_key_copy" = "Copy Stream Key";
|
||||
"lng_group_call_rtmp_key_copied" = "Stream Key copied to clipboard.";
|
||||
"lng_group_call_rtmp_key_warn" = "Never share your Stream Key with anyone or show it on stream!";
|
||||
"lng_group_call_rtmp_info" = "To stream video with another app, enter these Server URL and Stream Key in your streaming app.\n\nOnce you start broadcasting in your streaming app, tap Start Streaming below";
|
||||
"lng_group_call_rtmp_start" = "Start Streaming";
|
||||
"lng_group_call_rtmp_revoke_sure" = "Are you sure you want to revoke your Server URL and Stream Key?";
|
||||
|
||||
"lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages.";
|
||||
|
||||
"lng_player_message_today" = "Today at {time}";
|
||||
|
|
|
@ -1349,3 +1349,33 @@ groupCallRecordingSubLabelMargins: margins(8px, 22px, 8px, 22px);
|
|||
groupCallRecordingAudioSkip: 23px;
|
||||
groupCallRecordingSelectWidth: 2px;
|
||||
groupCallRecordingInfoHeight: 204px;
|
||||
|
||||
groupCallRtmpCopyButton: RoundButton(defaultActiveButton) {
|
||||
height: 32px;
|
||||
width: -26px;
|
||||
|
||||
textTop: 7px;
|
||||
padding: margins(0px, 12px, 0px, 15px);
|
||||
}
|
||||
groupCallRtmpShowButton: IconButton(defaultIconButton) {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
icon: icon {{ "menu/show_in_chat", menuIconFg }};
|
||||
iconOver: icon {{ "menu/show_in_chat", menuIconFgOver }};
|
||||
iconPosition: point(4px, 4px);
|
||||
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 32px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
groupCallRtmpUrlSkip: 1px;
|
||||
groupCallRtmpKeySubsectionTitleSkip: 8px;
|
||||
groupCallRtmpSubsectionTitleAddPadding: margins(0px, -1px, 0px, -4px);
|
||||
groupCallRtmpShowButtonPosition: point(21px, -5px);
|
||||
groupCallRtmpKeyLabel: FlatLabel(boxLabel) {
|
||||
minWidth: 230px;
|
||||
}
|
||||
groupCallRtmpTopBarMenuPosition: point(-2px, -15px);
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_rtmp.h"
|
||||
#include "mtproto/mtproto_dh_utils.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -170,7 +171,8 @@ FnMut<void()> Instance::Delegate::groupCallAddAsyncWaiter() {
|
|||
Instance::Instance()
|
||||
: _delegate(std::make_unique<Delegate>(this))
|
||||
, _cachedDhConfig(std::make_unique<DhConfig>())
|
||||
, _chooseJoinAs(std::make_unique<Group::ChooseJoinAsProcess>()) {
|
||||
, _chooseJoinAs(std::make_unique<Group::ChooseJoinAsProcess>())
|
||||
, _startWithRtmp(std::make_unique<Group::StartRtmpProcess>()) {
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
|
@ -202,6 +204,18 @@ void Instance::startOrJoinGroupCall(
|
|||
not_null<PeerData*> peer,
|
||||
const StartGroupCallArgs &args) {
|
||||
using JoinConfirm = StartGroupCallArgs::JoinConfirm;
|
||||
if (args.rtmpNeeded) {
|
||||
_startWithRtmp->start(peer, [=](object_ptr<Ui::BoxContent> box) {
|
||||
Ui::show(std::move(box), Ui::LayerOption::KeepOther);
|
||||
}, [=](QString text) {
|
||||
Ui::Toast::Show(text);
|
||||
}, [=](Group::JoinInfo info) {
|
||||
createGroupCall(
|
||||
std::move(info),
|
||||
MTP_inputGroupCall(MTPlong(), MTPlong()));
|
||||
});
|
||||
return;
|
||||
}
|
||||
const auto context = (args.confirm == JoinConfirm::Always)
|
||||
? Group::ChooseJoinAsProcess::Context::JoinWithConfirm
|
||||
: peer->groupCall()
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace Calls::Group {
|
|||
struct JoinInfo;
|
||||
class Panel;
|
||||
class ChooseJoinAsProcess;
|
||||
class StartRtmpProcess;
|
||||
} // namespace Calls::Group
|
||||
|
||||
namespace tgcalls {
|
||||
|
@ -52,7 +53,8 @@ struct StartGroupCallArgs {
|
|||
QString joinHash;
|
||||
JoinConfirm confirm = JoinConfirm::IfNowInAnother;
|
||||
bool scheduleNeeded = false;
|
||||
bool rtmp = false;
|
||||
bool rtmpNeeded = false;
|
||||
bool useRtmp = false;
|
||||
};
|
||||
|
||||
class Instance final : public base::has_weak_ptr {
|
||||
|
@ -150,6 +152,7 @@ private:
|
|||
base::flat_map<QString, std::unique_ptr<Media::Audio::Track>> _tracks;
|
||||
|
||||
const std::unique_ptr<Group::ChooseJoinAsProcess> _chooseJoinAs;
|
||||
const std::unique_ptr<Group::StartRtmpProcess> _startWithRtmp;
|
||||
|
||||
base::flat_set<std::unique_ptr<crl::semaphore>> _asyncWaiters;
|
||||
|
||||
|
|
|
@ -639,7 +639,7 @@ GroupCall::GroupCall(
|
|||
if (_id) {
|
||||
join(inputCall);
|
||||
} else {
|
||||
start(info.scheduleDate);
|
||||
start(info.scheduleDate, info.rtmp);
|
||||
}
|
||||
if (_scheduleDate) {
|
||||
saveDefaultJoinAs(joinAs());
|
||||
|
@ -1013,10 +1013,11 @@ rpl::producer<not_null<Data::GroupCall*>> GroupCall::real() const {
|
|||
return _realChanges.events();
|
||||
}
|
||||
|
||||
void GroupCall::start(TimeId scheduleDate) {
|
||||
void GroupCall::start(TimeId scheduleDate, bool rtmp) {
|
||||
using Flag = MTPphone_CreateGroupCall::Flag;
|
||||
_createRequestId = _api.request(MTPphone_CreateGroupCall(
|
||||
MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag(0)),
|
||||
MTP_flags((scheduleDate ? Flag::f_schedule_date : Flag(0))
|
||||
| (rtmp ? Flag::f_rtmp_stream : Flag(0))),
|
||||
_peer->input,
|
||||
MTP_int(base::RandomValue<int32>()),
|
||||
MTPstring(), // title
|
||||
|
|
|
@ -236,7 +236,7 @@ public:
|
|||
[[nodiscard]] Data::GroupCall *lookupReal() const;
|
||||
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
|
||||
|
||||
void start(TimeId scheduleDate);
|
||||
void start(TimeId scheduleDate, bool rtmp);
|
||||
void hangup();
|
||||
void discard();
|
||||
void rejoinAs(Group::JoinInfo info);
|
||||
|
|
295
Telegram/SourceFiles/calls/group/calls_group_rtmp.cpp
Normal file
295
Telegram/SourceFiles/calls/group/calls_group_rtmp.cpp
Normal file
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/group/calls_group_rtmp.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h" // AddDivider.
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QStyle>
|
||||
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
void StartWithBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Fn<void()> done,
|
||||
Fn<void()> revoke,
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
|
||||
Fn<void(QString)> showToast,
|
||||
rpl::producer<StartRtmpProcess::Data> &&data) {
|
||||
struct State {
|
||||
rpl::variable<bool> hidden = true;
|
||||
rpl::variable<QString> key;
|
||||
rpl::variable<QString> url;
|
||||
base::unique_qptr<Ui::PopupMenu> menu;
|
||||
bool warned = false;
|
||||
};
|
||||
|
||||
const auto &rowPadding = st::boxRowPadding;
|
||||
|
||||
const auto passChar = QChar(box->style()->styleHint(
|
||||
QStyle::SH_LineEdit_PasswordCharacter));
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->key = rpl::duplicate(
|
||||
data
|
||||
) | rpl::map([=](const auto &d) { return d.key; });
|
||||
state->url = std::move(
|
||||
data
|
||||
) | rpl::map([=](const auto &d) { return d.url; });
|
||||
|
||||
const auto addButton = [&](
|
||||
bool key,
|
||||
rpl::producer<QString> &&text) {
|
||||
const auto &padding = st::groupCallRtmpCopyButton.padding;
|
||||
|
||||
auto wrap = object_ptr<Ui::RpWidget>(box);
|
||||
auto button = Ui::CreateChild<Ui::RoundButton>(
|
||||
wrap.data(),
|
||||
rpl::duplicate(text),
|
||||
st::groupCallRtmpCopyButton);
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
button->setClickedCallback(key
|
||||
? Fn<void()>([=] {
|
||||
QGuiApplication::clipboard()->setText(state->key.current());
|
||||
showToast(tr::lng_group_call_rtmp_key_copied(tr::now));
|
||||
})
|
||||
: Fn<void()>([=] {
|
||||
QGuiApplication::clipboard()->setText(state->url.current());
|
||||
showToast(tr::lng_group_call_rtmp_url_copied(tr::now));
|
||||
}));
|
||||
const auto weak = box->addRow(std::move(wrap), rowPadding);
|
||||
button->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
weak->resize(weak->width(), height);
|
||||
}, box->lifetime());
|
||||
return weak;
|
||||
};
|
||||
|
||||
const auto addLabel = [&](
|
||||
rpl::producer<QString> &&text,
|
||||
const style::FlatLabel &st) {
|
||||
const auto label = box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(box, std::move(text), st),
|
||||
st::boxRowPadding);
|
||||
label->setSelectable(true);
|
||||
label->setBreakEverywhere(true);
|
||||
return label;
|
||||
};
|
||||
|
||||
box->setTitle(tr::lng_group_call_rtmp_title());
|
||||
|
||||
// Server URL.
|
||||
Settings::AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
tr::lng_group_call_rtmp_url_subtitle(),
|
||||
st::groupCallRtmpSubsectionTitleAddPadding);
|
||||
auto urlLabelContent = state->url.value();
|
||||
addLabel(std::move(urlLabelContent), st::boxLabel);
|
||||
box->addSkip(st::groupCallRtmpUrlSkip);
|
||||
addButton(false, tr::lng_group_call_rtmp_url_copy());
|
||||
//
|
||||
|
||||
Settings::AddDivider(box->verticalLayout());
|
||||
|
||||
// Stream Key.
|
||||
box->addSkip(st::groupCallRtmpKeySubsectionTitleSkip);
|
||||
Settings::AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
tr::lng_group_call_rtmp_key_subtitle(),
|
||||
st::groupCallRtmpSubsectionTitleAddPadding);
|
||||
|
||||
auto keyLabelContent = rpl::combine(
|
||||
state->hidden.value(),
|
||||
state->key.value()
|
||||
) | rpl::map([passChar](bool hidden, const QString &key) {
|
||||
return hidden
|
||||
? QString().fill(passChar, int(key.size()))
|
||||
: key;
|
||||
});
|
||||
const auto streamKeyLabel = addLabel(
|
||||
std::move(keyLabelContent),
|
||||
st::boxLabel);
|
||||
const auto streamKeyButton = Ui::CreateChild<Ui::IconButton>(
|
||||
box.get(),
|
||||
st::groupCallRtmpShowButton);
|
||||
|
||||
streamKeyLabel->topValue(
|
||||
) | rpl::start_with_next([=, right = rowPadding.right()](int top) {
|
||||
streamKeyButton->moveToRight(
|
||||
st::groupCallRtmpShowButtonPosition.x(),
|
||||
top + st::groupCallRtmpShowButtonPosition.y());
|
||||
streamKeyButton->raise();
|
||||
}, box->lifetime());
|
||||
streamKeyButton->addClickHandler([=] {
|
||||
if (!state->warned && state->hidden.current()) {
|
||||
showBox(Box<Ui::ConfirmBox>(
|
||||
tr::lng_group_call_rtmp_key_warn(tr::now),
|
||||
tr::lng_from_request_understand(tr::now),
|
||||
tr::lng_close(tr::now),
|
||||
[=](Fn<void()> &&close) {
|
||||
state->warned = true;
|
||||
state->hidden = !state->hidden.current();
|
||||
close();
|
||||
}));
|
||||
} else {
|
||||
state->hidden = !state->hidden.current();
|
||||
}
|
||||
});
|
||||
|
||||
addButton(true, tr::lng_group_call_rtmp_key_copy());
|
||||
//
|
||||
|
||||
Settings::AddDividerText(
|
||||
box->verticalLayout(),
|
||||
tr::lng_group_call_rtmp_info());
|
||||
|
||||
box->addButton(tr::lng_group_call_rtmp_start(), done);
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
box->setWidth(st::infoDesiredWidth);
|
||||
{
|
||||
const auto top = box->addTopButton(st::infoTopBarMenu);
|
||||
top->setClickedCallback([=] {
|
||||
state->menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
top,
|
||||
st::popupMenuWithIcons);
|
||||
state->menu->addAction(
|
||||
tr::lng_group_invite_context_revoke(tr::now),
|
||||
revoke,
|
||||
&st::menuIconRemove);
|
||||
state->menu->moveToRight(
|
||||
st::groupCallRtmpTopBarMenuPosition.x(),
|
||||
st::groupCallRtmpTopBarMenuPosition.y());
|
||||
state->menu->setForcedOrigin(
|
||||
Ui::PanelAnimation::Origin::TopRight);
|
||||
state->menu->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StartRtmpProcess::~StartRtmpProcess() {
|
||||
if (_request) {
|
||||
_request->peer->session().api().request(_request->id).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void StartRtmpProcess::start(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
|
||||
Fn<void(QString)> showToast,
|
||||
Fn<void(JoinInfo)> done) {
|
||||
Expects(done != nullptr);
|
||||
|
||||
const auto session = &peer->session();
|
||||
if (_request) {
|
||||
if (_request->peer == peer) {
|
||||
_request->showBox = std::move(showBox);
|
||||
_request->showToast = std::move(showToast);
|
||||
_request->done = std::move(done);
|
||||
return;
|
||||
}
|
||||
session->api().request(_request->id).cancel();
|
||||
_request = nullptr;
|
||||
}
|
||||
_request = std::make_unique<RtmpRequest>(
|
||||
RtmpRequest{
|
||||
.peer = peer,
|
||||
.showBox = std::move(showBox),
|
||||
.showToast = std::move(showToast),
|
||||
.done = std::move(done) });
|
||||
session->account().sessionChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
_request = nullptr;
|
||||
}, _request->lifetime);
|
||||
|
||||
requestUrl(false);
|
||||
}
|
||||
|
||||
void StartRtmpProcess::requestUrl(bool revoke) {
|
||||
const auto session = &_request->peer->session();
|
||||
_request->id = session->api().request(MTPphone_GetGroupCallStreamRtmpUrl(
|
||||
_request->peer->input,
|
||||
MTP_bool(revoke)
|
||||
)).done([=](const MTPphone_GroupCallStreamRtmpUrl &result) {
|
||||
auto data = result.match([&](
|
||||
const MTPDphone_groupCallStreamRtmpUrl &data) {
|
||||
return Data{ .url = qs(data.vurl()), .key = qs(data.vkey()) };
|
||||
});
|
||||
processUrl(std::move(data));
|
||||
}).fail([=] {
|
||||
_request->showToast(Lang::Hard::ServerError());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void StartRtmpProcess::processUrl(Data data) {
|
||||
if (!_request->box) {
|
||||
createBox();
|
||||
}
|
||||
_request->data = std::move(data);
|
||||
}
|
||||
|
||||
void StartRtmpProcess::finish(JoinInfo info) {
|
||||
const auto done = std::move(_request->done);
|
||||
const auto box = _request->box;
|
||||
_request = nullptr;
|
||||
done(std::move(info));
|
||||
if (const auto strong = box.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
|
||||
void StartRtmpProcess::createBox() {
|
||||
auto done = [=] {
|
||||
const auto peer = _request->peer;
|
||||
finish({ .peer = peer, .joinAs = peer, .rtmp = true });
|
||||
};
|
||||
auto revoke = [=] {
|
||||
const auto guard = base::make_weak(&_request->guard);
|
||||
_request->showBox(Box<Ui::ConfirmBox>(
|
||||
tr::lng_group_call_rtmp_revoke_sure(tr::now),
|
||||
tr::lng_group_invite_context_revoke(tr::now),
|
||||
crl::guard(guard, [=](Fn<void()> &&close) {
|
||||
requestUrl(true);
|
||||
close();
|
||||
})));
|
||||
};
|
||||
auto object = Box(
|
||||
StartWithBox,
|
||||
std::move(done),
|
||||
std::move(revoke),
|
||||
_request->showBox,
|
||||
_request->showToast,
|
||||
_request->data.value());
|
||||
object->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_request = nullptr;
|
||||
}, _request->lifetime);
|
||||
_request->box = Ui::MakeWeak(object.data());
|
||||
_request->showBox(std::move(object));
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
60
Telegram/SourceFiles/calls/group/calls_group_rtmp.h
Normal file
60
Telegram/SourceFiles/calls/group/calls_group_rtmp.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Calls::Group {
|
||||
|
||||
struct JoinInfo;
|
||||
|
||||
class StartRtmpProcess final {
|
||||
public:
|
||||
StartRtmpProcess() = default;
|
||||
~StartRtmpProcess();
|
||||
|
||||
struct Data {
|
||||
QString url;
|
||||
QString key;
|
||||
};
|
||||
|
||||
void start(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
|
||||
Fn<void(QString)> showToast,
|
||||
Fn<void(JoinInfo)> done);
|
||||
|
||||
private:
|
||||
void requestUrl(bool revoke);
|
||||
void processUrl(Data data);
|
||||
void createBox();
|
||||
void finish(JoinInfo info);
|
||||
|
||||
struct RtmpRequest {
|
||||
not_null<PeerData*> peer;
|
||||
rpl::variable<Data> data;
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox;
|
||||
Fn<void(QString)> showToast;
|
||||
Fn<void(JoinInfo)> done;
|
||||
base::has_weak_ptr guard;
|
||||
QPointer<Ui::BoxContent> box;
|
||||
rpl::lifetime lifetime;
|
||||
mtpRequestId id = 0;
|
||||
};
|
||||
std::unique_ptr<RtmpRequest> _request;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls::Group
|
Loading…
Add table
Reference in a new issue