Allow enable/disable video in a call.
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 595 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/call_camera_active.png
Normal file
After Width: | Height: | Size: 377 B |
BIN
Telegram/Resources/icons/call_camera_active@2x.png
Normal file
After Width: | Height: | Size: 733 B |
BIN
Telegram/Resources/icons/call_camera_active@3x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/call_camera_muted.png
Normal file
After Width: | Height: | Size: 727 B |
BIN
Telegram/Resources/icons/call_camera_muted@2x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/call_camera_muted@3x.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 460 B |
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 970 B |
Before Width: | Height: | Size: 886 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 489 B |
Before Width: | Height: | Size: 800 B After Width: | Height: | Size: 1,002 B |
Before Width: | Height: | Size: 945 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 744 B |
Before Width: | Height: | Size: 843 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.4 KiB |
|
@ -84,6 +84,13 @@ callMuteToggle: IconButton(callButton) {
|
|||
}
|
||||
}
|
||||
callUnmuteIcon: icon {{ "call_record_muted", callIconFg }};
|
||||
callCameraToggle: IconButton(callButton) {
|
||||
icon: icon {{ "call_camera_active", callIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: callMuteRipple;
|
||||
}
|
||||
}
|
||||
callNoCameraIcon: icon {{ "call_camera_muted", callIconFg }};
|
||||
|
||||
callControlsTop: 80px;
|
||||
callControlsSkip: 0px;
|
||||
|
@ -124,7 +131,7 @@ callBarMuteToggle: IconButton {
|
|||
height: 38px;
|
||||
|
||||
icon: icon {{ "call_record_active", callBarFg }};
|
||||
iconPosition: point(9px, 8px);
|
||||
iconPosition: point(3px, 2px);
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: callBarMuteRipple;
|
||||
|
@ -137,7 +144,7 @@ callBarRightSkip: 12px;
|
|||
callBarSkip: 10px;
|
||||
callBarHangup: IconButton(callBarMuteToggle) {
|
||||
icon: icon {{ "call_discard", callBarFg }};
|
||||
iconPosition: point(9px, 11px);
|
||||
iconPosition: point(3px, 1px);
|
||||
}
|
||||
callBarLabel: LabelSimple(defaultLabelSimple) {
|
||||
font: semiboldFont;
|
||||
|
|
|
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "facades.h"
|
||||
|
||||
#include <tgcalls/Instance.h>
|
||||
#include <tgcalls/VideoCaptureInterface.h>
|
||||
|
||||
namespace tgcalls {
|
||||
class InstanceImpl;
|
||||
|
@ -42,6 +43,7 @@ namespace {
|
|||
constexpr auto kMinLayer = 65;
|
||||
constexpr auto kHangupTimeoutMs = 5000;
|
||||
constexpr auto kSha256Size = 32;
|
||||
constexpr auto kDropFramesWhileInactive = 5 * crl::time(1000);
|
||||
const auto kDefaultVersion = "2.4.4"_q;
|
||||
|
||||
const auto RegisterTag = tgcalls::Register<tgcalls::InstanceImpl>();
|
||||
|
@ -319,12 +321,31 @@ void Call::actuallyAnswer() {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void Call::setMute(bool mute) {
|
||||
_mute = mute;
|
||||
void Call::setMuted(bool mute) {
|
||||
_muted = mute;
|
||||
if (_instance) {
|
||||
_instance->setMuteMicrophone(_mute);
|
||||
_instance->setMuteMicrophone(mute);
|
||||
}
|
||||
}
|
||||
|
||||
void Call::setVideoEnabled(bool enabled) {
|
||||
if (_state.current() != State::Established) {
|
||||
return;
|
||||
}
|
||||
_videoEnabled = enabled;
|
||||
if (enabled) {
|
||||
if (!_videoCapture) {
|
||||
_videoCapture = tgcalls::VideoCaptureInterface::Create();
|
||||
}
|
||||
if (_instance) {
|
||||
_instance->requestVideo(_videoCapture);
|
||||
} else {
|
||||
_videoState = VideoState::OutgoingRequested;
|
||||
}
|
||||
_videoCapture->setIsVideoEnabled(true);
|
||||
} else if (_videoCapture) {
|
||||
_videoCapture->setIsVideoEnabled(false);
|
||||
}
|
||||
_muteChanged.notify(_mute);
|
||||
}
|
||||
|
||||
crl::time Call::getDurationMs() const {
|
||||
|
@ -430,6 +451,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
|||
}
|
||||
_id = data.vid().v;
|
||||
_accessHash = data.vaccess_hash().v;
|
||||
setVideoEnabled(data.is_video());
|
||||
auto gaHashBytes = bytes::make_span(data.vg_a_hash().v);
|
||||
if (gaHashBytes.size() != kSha256Size) {
|
||||
LOG(("Call Error: Wrong g_a_hash size %1, expected %2."
|
||||
|
@ -650,7 +672,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
|||
.encryptionKey = tgcalls::EncryptionKey(
|
||||
std::move(encryptionKeyValue),
|
||||
(_type == Type::Outgoing)),
|
||||
.videoCapture = nullptr,
|
||||
.videoCapture = _videoEnabled.current() ? _videoCapture : nullptr,
|
||||
.stateUpdated = [=](tgcalls::State state, tgcalls::VideoState videoState) {
|
||||
crl::on_main(weak, [=] {
|
||||
handleControllerStateChange(state, videoState);
|
||||
|
@ -662,6 +684,14 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
|||
});
|
||||
},
|
||||
.remoteVideoIsActiveUpdated = [=](bool active) {
|
||||
crl::on_main(weak, [=] {
|
||||
if (!active) {
|
||||
_frames.fire(QImage());
|
||||
_remoteVideoInactiveFrom = crl::now();
|
||||
} else {
|
||||
_remoteVideoInactiveFrom = 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
.signalingDataEmitted = [=](const std::vector<uint8_t> &data) {
|
||||
const auto bytes = QByteArray(
|
||||
|
@ -706,8 +736,6 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
|||
}
|
||||
}
|
||||
|
||||
descriptor.videoCapture = tgcalls::CreateVideoCapture();
|
||||
|
||||
const auto version = call.vprotocol().match([&](
|
||||
const MTPDphoneCallProtocol &data) {
|
||||
return data.vlibrary_versions().v;
|
||||
|
@ -727,13 +755,18 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
|||
}
|
||||
|
||||
const auto raw = _instance.get();
|
||||
if (_mute) {
|
||||
raw->setMuteMicrophone(_mute);
|
||||
if (_muted.current()) {
|
||||
raw->setMuteMicrophone(_muted.current());
|
||||
}
|
||||
const auto &settings = Core::App().settings();
|
||||
raw->setIncomingVideoOutput(webrtc::CreateVideoSink([=](QImage frame) {
|
||||
crl::on_main(weak, [=] {
|
||||
_frames.fire_copy(frame);
|
||||
if (_remoteVideoInactiveFrom > 0
|
||||
&& (_remoteVideoInactiveFrom + kDropFramesWhileInactive
|
||||
> crl::now())) {
|
||||
} else {
|
||||
_frames.fire_copy(frame);
|
||||
}
|
||||
});
|
||||
}));
|
||||
raw->setAudioOutputDevice(
|
||||
|
@ -748,6 +781,18 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
|||
void Call::handleControllerStateChange(
|
||||
tgcalls::State state,
|
||||
tgcalls::VideoState videoState) {
|
||||
_videoState = [&] {
|
||||
switch (videoState) {
|
||||
case tgcalls::VideoState::Possible: return VideoState::Disabled;
|
||||
case tgcalls::VideoState::OutgoingRequested:
|
||||
return VideoState::OutgoingRequested;
|
||||
case tgcalls::VideoState::IncomingRequested:
|
||||
return VideoState::IncomingRequested;
|
||||
case tgcalls::VideoState::Active: return VideoState::Enabled;
|
||||
}
|
||||
Unexpected("VideoState value in Call::handleControllerStateChange.");
|
||||
}();
|
||||
|
||||
switch (state) {
|
||||
case tgcalls::State::WaitInit: {
|
||||
DEBUG_LOG(("Call Info: State changed to WaitingInit."));
|
||||
|
@ -780,10 +825,7 @@ void Call::handleControllerBarCountChange(int count) {
|
|||
}
|
||||
|
||||
void Call::setSignalBarCount(int count) {
|
||||
if (_signalBarCount != count) {
|
||||
_signalBarCount = count;
|
||||
_signalBarCountChanged.notify(count);
|
||||
}
|
||||
_signalBarCount = count;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -986,9 +1028,7 @@ void Call::handleControllerError(const QString &error) {
|
|||
|
||||
void Call::destroyController() {
|
||||
if (_instance) {
|
||||
AssertIsDebug();
|
||||
const auto state = _instance->stop();
|
||||
LOG(("CALL_LOG: %1").arg(QString::fromStdString(state.debugLog)));
|
||||
|
||||
DEBUG_LOG(("Call Info: Destroying call controller.."));
|
||||
_instance.reset();
|
||||
|
|
|
@ -21,6 +21,7 @@ class Track;
|
|||
|
||||
namespace tgcalls {
|
||||
class Instance;
|
||||
class VideoCaptureInterface;
|
||||
enum class State;
|
||||
enum class VideoState;
|
||||
} // namespace tgcalls
|
||||
|
@ -91,29 +92,50 @@ public:
|
|||
Ringing,
|
||||
Busy,
|
||||
};
|
||||
State state() const {
|
||||
[[nodiscard]] State state() const {
|
||||
return _state.current();
|
||||
}
|
||||
rpl::producer<State> stateValue() const {
|
||||
[[nodiscard]] rpl::producer<State> stateValue() const {
|
||||
return _state.value();
|
||||
}
|
||||
|
||||
enum class VideoState {
|
||||
Disabled,
|
||||
OutgoingRequested,
|
||||
IncomingRequested,
|
||||
Enabled
|
||||
};
|
||||
[[nodiscard]] VideoState videoState() const {
|
||||
return _videoState.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<VideoState> videoStateValue() const {
|
||||
return _videoState.value();
|
||||
}
|
||||
|
||||
static constexpr auto kSignalBarStarting = -1;
|
||||
static constexpr auto kSignalBarFinished = -2;
|
||||
static constexpr auto kSignalBarCount = 4;
|
||||
base::Observable<int> &signalBarCountChanged() {
|
||||
return _signalBarCountChanged;
|
||||
[[nodiscard]] rpl::producer<int> signalBarCountValue() const {
|
||||
return _signalBarCount.value();
|
||||
}
|
||||
|
||||
void setMute(bool mute);
|
||||
bool isMute() const {
|
||||
return _mute;
|
||||
void setMuted(bool mute);
|
||||
[[nodiscard]] bool muted() const {
|
||||
return _muted.current();
|
||||
}
|
||||
base::Observable<bool> &muteChanged() {
|
||||
return _muteChanged;
|
||||
[[nodiscard]] rpl::producer<bool> mutedValue() const {
|
||||
return _muted.value();
|
||||
}
|
||||
|
||||
rpl::producer<QImage> frames() const {
|
||||
void setVideoEnabled(bool enabled);
|
||||
[[nodiscard]] bool videoEnabled() const {
|
||||
return _videoEnabled.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> videoEnabledValue() const {
|
||||
return _videoEnabled.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<QImage> frames() const {
|
||||
return _frames.events();
|
||||
}
|
||||
|
||||
|
@ -178,17 +200,18 @@ private:
|
|||
MTP::Sender _api;
|
||||
Type _type = Type::Outgoing;
|
||||
rpl::variable<State> _state = State::Starting;
|
||||
rpl::variable<VideoState> _videoState = VideoState::Disabled;
|
||||
FinishType _finishAfterRequestingCall = FinishType::None;
|
||||
bool _answerAfterDhConfigReceived = false;
|
||||
int _signalBarCount = kSignalBarStarting;
|
||||
base::Observable<int> _signalBarCountChanged;
|
||||
rpl::variable<int> _signalBarCount = kSignalBarStarting;
|
||||
crl::time _startTime = 0;
|
||||
base::DelayedCallTimer _finishByTimeoutTimer;
|
||||
base::Timer _discardByTimeoutTimer;
|
||||
|
||||
bool _mute = false;
|
||||
base::Observable<bool> _muteChanged;
|
||||
rpl::variable<bool> _muted = false;
|
||||
rpl::variable<bool> _videoEnabled = false;
|
||||
rpl::event_stream<QImage> _frames;
|
||||
crl::time _remoteVideoInactiveFrom = 0;
|
||||
|
||||
DhConfig _dhConfig;
|
||||
bytes::vector _ga;
|
||||
|
@ -203,6 +226,7 @@ private:
|
|||
uint64 _keyFingerprint = 0;
|
||||
|
||||
std::unique_ptr<tgcalls::Instance> _instance;
|
||||
std::shared_ptr<tgcalls::VideoCaptureInterface> _videoCapture;
|
||||
|
||||
std::unique_ptr<Media::Audio::Track> _waitingTrack;
|
||||
|
||||
|
|
|
@ -89,9 +89,10 @@ SignalBars::SignalBars(
|
|||
resize(
|
||||
_st.width + (_st.width + _st.skip) * (Call::kSignalBarCount - 1),
|
||||
_st.width * Call::kSignalBarCount);
|
||||
subscribe(call->signalBarCountChanged(), [=](int count) {
|
||||
call->signalBarCountValue(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
changed(count);
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
bool SignalBars::isDisplayed() const {
|
||||
|
@ -307,6 +308,7 @@ Panel::Panel(not_null<Call*> call)
|
|||
, _answerHangupRedial(this, st::callAnswer, &st::callHangup)
|
||||
, _decline(this, object_ptr<Button>(this, st::callHangup))
|
||||
, _cancel(this, object_ptr<Button>(this, st::callCancel))
|
||||
, _camera(this, st::callCameraToggle)
|
||||
, _mute(this, st::callMuteToggle)
|
||||
, _name(this, st::callName)
|
||||
, _status(this, st::callStatus)
|
||||
|
@ -353,14 +355,24 @@ void Panel::hideDeactivated() {
|
|||
|
||||
void Panel::initControls() {
|
||||
_hangupShown = (_call->type() == Type::Outgoing);
|
||||
_mute->setClickedCallback([this] {
|
||||
_mute->setClickedCallback([=] {
|
||||
if (_call) {
|
||||
_call->setMute(!_call->isMute());
|
||||
_call->setMuted(!_call->muted());
|
||||
}
|
||||
});
|
||||
subscribe(_call->muteChanged(), [this](bool mute) {
|
||||
_call->mutedValue(
|
||||
) | rpl::start_with_next([=](bool mute) {
|
||||
_mute->setIconOverride(mute ? &st::callUnmuteIcon : nullptr);
|
||||
}, lifetime());
|
||||
_camera->setClickedCallback([=] {
|
||||
if (_call) {
|
||||
_call->setVideoEnabled(!_call->videoEnabled());
|
||||
}
|
||||
});
|
||||
_call->videoEnabledValue(
|
||||
) | rpl::start_with_next([=](bool enabled) {
|
||||
_camera->setIconOverride(enabled ? nullptr : &st::callNoCameraIcon);
|
||||
}, lifetime());
|
||||
|
||||
_updateDurationTimer.setCallback([this] {
|
||||
if (_call) {
|
||||
|
@ -691,6 +703,7 @@ void Panel::updateControlsGeometry() {
|
|||
updateHangupGeometry();
|
||||
|
||||
_mute->moveToRight(_padding.right() + st::callMuteRight, controlsTop);
|
||||
_camera->moveToLeft(_padding.right() + st::callMuteRight, controlsTop);
|
||||
|
||||
const auto skip = st::callSignalMargin + st::callSignalPadding;
|
||||
const auto delta = (_signalBars->width() - _signalBars->height());
|
||||
|
|
|
@ -34,7 +34,7 @@ struct CallSignalBars;
|
|||
|
||||
namespace Calls {
|
||||
|
||||
class SignalBars : public Ui::RpWidget, private base::Subscriber {
|
||||
class SignalBars final : public Ui::RpWidget {
|
||||
public:
|
||||
SignalBars(
|
||||
QWidget *parent,
|
||||
|
@ -56,10 +56,7 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class Panel
|
||||
: public Ui::RpWidget
|
||||
, private base::Subscriber
|
||||
, private Ui::AbstractTooltipShower {
|
||||
class Panel final : public Ui::RpWidget, private Ui::AbstractTooltipShower {
|
||||
|
||||
public:
|
||||
Panel(not_null<Call*> call);
|
||||
|
@ -138,6 +135,7 @@ private:
|
|||
object_ptr<Ui::FadeWrap<Button>> _cancel;
|
||||
bool _hangupShown = false;
|
||||
Ui::Animations::Simple _hangupShownProgress;
|
||||
object_ptr<Ui::IconButton> _camera;
|
||||
object_ptr<Ui::IconButton> _mute;
|
||||
object_ptr<Ui::FlatLabel> _name;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
|
|
|
@ -94,14 +94,14 @@ TopBar::TopBar(
|
|||
void TopBar::initControls() {
|
||||
_mute->setClickedCallback([=] {
|
||||
if (const auto call = _call.get()) {
|
||||
call->setMute(!call->isMute());
|
||||
call->setMuted(!call->muted());
|
||||
}
|
||||
});
|
||||
setMuted(_call->isMute());
|
||||
subscribe(_call->muteChanged(), [=](bool mute) {
|
||||
setMuted(mute);
|
||||
_call->mutedValue(
|
||||
) | rpl::start_with_next([=](bool muted) {
|
||||
setMuted(muted);
|
||||
update();
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
_call->user()->session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::Name
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace Calls {
|
|||
class Call;
|
||||
class SignalBars;
|
||||
|
||||
class TopBar : public Ui::RpWidget, private base::Subscriber {
|
||||
class TopBar : public Ui::RpWidget {
|
||||
public:
|
||||
TopBar(QWidget *parent, const base::weak_ptr<Call> &call);
|
||||
|
||||
|
|
2
Telegram/ThirdParty/tgcalls
vendored
|
@ -1 +1 @@
|
|||
Subproject commit beb6eac853c6f2caf037ab1c374c25cf81af7a95
|
||||
Subproject commit 8364baddd1e731aec3ae71d6594c10750afce312
|
|
@ -19,6 +19,8 @@ PRIVATE
|
|||
Instance.h
|
||||
InstanceImpl.cpp
|
||||
InstanceImpl.h
|
||||
LogSinkImpl.cpp
|
||||
LogSinkImpl.h
|
||||
Manager.cpp
|
||||
Manager.h
|
||||
MediaManager.cpp
|
||||
|
|