mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Allow switching between voice/video.
This commit is contained in:
parent
552343fa37
commit
ff44f626ba
17 changed files with 242 additions and 52 deletions
|
@ -3246,7 +3246,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_record_lock_cancel_sure" = "Do you want to stop recording and discard your voice message?";
|
||||
"lng_record_listen_cancel_sure" = "Do you want to discard your recorded voice message?";
|
||||
"lng_record_lock_discard" = "Discard";
|
||||
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
|
||||
"lng_record_voice_tip" = "Hold to record audio. Click to switch to video.";
|
||||
"lng_record_video_tip" = "Hold to record video. Click to switch to audio.";
|
||||
"lng_record_once_first_tooltip" = "Click to set this message to **Play Once**.";
|
||||
"lng_record_once_active_tooltip" = "The recipient will be able to listen only once.";
|
||||
"lng_will_be_notified" = "Subscribers will be notified when you post.";
|
||||
|
|
|
@ -150,6 +150,8 @@ SendButton {
|
|||
inner: IconButton;
|
||||
record: icon;
|
||||
recordOver: icon;
|
||||
round: icon;
|
||||
roundOver: icon;
|
||||
sendDisabledFg: color;
|
||||
}
|
||||
|
||||
|
@ -1159,6 +1161,10 @@ historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive
|
|||
historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
|
||||
historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
|
||||
historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }};
|
||||
historyRecordRound: icon {{ "info/info_media_round", historyRecordVoiceFg }};
|
||||
historyRecordRoundOver: icon {{ "info/info_media_round", historyRecordVoiceFgOver }};
|
||||
historyRecordRoundActive: icon {{ "info/info_media_round", historyRecordVoiceFgActiveIcon }};
|
||||
historyRecordRoundIconPosition: point(0px, 0px);
|
||||
historyRecordSendIconPosition: point(2px, 0px);
|
||||
historyRecordVoiceRippleBgActive: lightButtonBgOver;
|
||||
historyRecordSignalRadius: 5px;
|
||||
|
@ -1264,6 +1270,8 @@ historySend: SendButton {
|
|||
}
|
||||
record: historyRecordVoice;
|
||||
recordOver: historyRecordVoiceOver;
|
||||
round: historyRecordRound;
|
||||
roundOver: historyRecordRoundOver;
|
||||
sendDisabledFg: historyComposeIconFg;
|
||||
}
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ QByteArray Settings::serialize() const {
|
|||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken)
|
||||
+ sizeof(qint32) * 3;
|
||||
+ sizeof(qint32) * 4;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
|
@ -379,7 +379,8 @@ QByteArray Settings::serialize() const {
|
|||
<< _tonsiteStorageToken
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0)
|
||||
<< qint32(_ivZoom.current())
|
||||
<< qint32(_skipToastsInFocus ? 1 : 0);
|
||||
<< qint32(_skipToastsInFocus ? 1 : 0)
|
||||
<< qint32(_recordVideoMessages ? 1 : 0);
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
|
@ -505,6 +506,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
QByteArray tonsiteStorageToken = _tonsiteStorageToken;
|
||||
qint32 ivZoom = _ivZoom.current();
|
||||
qint32 skipToastsInFocus = _skipToastsInFocus ? 1 : 0;
|
||||
qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
|
@ -820,6 +822,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
if (!stream.atEnd()) {
|
||||
stream >> skipToastsInFocus;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> recordVideoMessages;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
|
@ -1033,6 +1038,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
_tonsiteStorageToken = tonsiteStorageToken;
|
||||
_ivZoom = ivZoom;
|
||||
_skipToastsInFocus = (skipToastsInFocus == 1);
|
||||
_recordVideoMessages = (recordVideoMessages == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
@ -1422,6 +1428,7 @@ void Settings::resetOnLastLogout() {
|
|||
_storiesClickTooltipHidden = false;
|
||||
_ttlVoiceClickTooltipHidden = false;
|
||||
_ivZoom = 100;
|
||||
_recordVideoMessages = false;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
|
|
|
@ -628,6 +628,13 @@ public:
|
|||
return _floatPlayerCorner;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool recordVideoMessages() const {
|
||||
return _recordVideoMessages;
|
||||
}
|
||||
void setRecordVideoMessages(bool value) {
|
||||
_recordVideoMessages = value;
|
||||
}
|
||||
|
||||
void updateDialogsWidthRatio(float64 ratio, bool nochat);
|
||||
[[nodiscard]] float64 dialogsWidthRatio(bool nochat) const;
|
||||
|
||||
|
@ -1069,6 +1076,8 @@ private:
|
|||
bool _rememberedFlashBounceNotifyFromTray = false;
|
||||
bool _dialogsWidthSetToZeroWithoutChat = false;
|
||||
|
||||
bool _recordVideoMessages = false;
|
||||
|
||||
QByteArray _photoEditorBrush;
|
||||
|
||||
};
|
||||
|
|
|
@ -150,6 +150,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "main/session/send_as_peers.h"
|
||||
#include "webrtc/webrtc_environment.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_controller.h"
|
||||
|
@ -1068,7 +1069,17 @@ void HistoryWidget::initVoiceRecordBar() {
|
|||
|
||||
_voiceRecordBar->recordingTipRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
controller()->showToast(tr::lng_record_hold_tip(tr::now));
|
||||
Core::App().settings().setRecordVideoMessages(
|
||||
!Core::App().settings().recordVideoMessages());
|
||||
updateSendButtonType();
|
||||
switch (_send->type()) {
|
||||
case Ui::SendButton::Type::Record:
|
||||
controller()->showToast(tr::lng_record_voice_tip(tr::now));
|
||||
break;
|
||||
case Ui::SendButton::Type::Round:
|
||||
controller()->showToast(tr::lng_record_video_tip(tr::now));
|
||||
break;
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_voiceRecordBar->recordingStateChanges(
|
||||
|
@ -2105,6 +2116,7 @@ void HistoryWidget::showHistory(
|
|||
MsgId showAtMsgId,
|
||||
const TextWithEntities &highlightPart,
|
||||
int highlightPartOffsetHint) {
|
||||
|
||||
_pinnedClickedId = FullMsgId();
|
||||
_minPinnedId = std::nullopt;
|
||||
_showAtMsgHighlightPart = {};
|
||||
|
@ -2299,6 +2311,8 @@ void HistoryWidget::showHistory(
|
|||
_contactStatus = nullptr;
|
||||
_businessBotStatus = nullptr;
|
||||
|
||||
updateRecordMediaState();
|
||||
|
||||
if (peerId) {
|
||||
using namespace HistoryView;
|
||||
_peer = session().data().peer(peerId);
|
||||
|
@ -4254,7 +4268,10 @@ auto HistoryWidget::computeSendButtonType() const {
|
|||
} else if (_isInlineBot) {
|
||||
return Type::Cancel;
|
||||
} else if (showRecordButton()) {
|
||||
return Type::Record;
|
||||
return (Core::App().settings().recordVideoMessages()
|
||||
&& _canRecordVideoMessage)
|
||||
? Type::Round
|
||||
: Type::Record;
|
||||
}
|
||||
return Type::Send;
|
||||
}
|
||||
|
@ -4588,7 +4605,8 @@ void HistoryWidget::sendButtonClicked() {
|
|||
const auto type = _send->type();
|
||||
if (type == Ui::SendButton::Type::Cancel) {
|
||||
cancelInlineBot();
|
||||
} else if (type != Ui::SendButton::Type::Record) {
|
||||
} else if (type != Ui::SendButton::Type::Record
|
||||
&& type != Ui::SendButton::Type::Round) {
|
||||
send({});
|
||||
}
|
||||
}
|
||||
|
@ -4878,7 +4896,7 @@ bool HistoryWidget::isSearching() const {
|
|||
}
|
||||
|
||||
bool HistoryWidget::showRecordButton() const {
|
||||
return Media::Capture::instance()->available()
|
||||
return _canRecordAudioMessage
|
||||
&& !_voiceRecordBar->isListenState()
|
||||
&& !_voiceRecordBar->isRecordingByAnotherBar()
|
||||
&& !HasSendText(_field)
|
||||
|
@ -4909,7 +4927,9 @@ void HistoryWidget::updateSendButtonType() {
|
|||
}();
|
||||
_send->setSlowmodeDelay(delay);
|
||||
_send->setDisabled(disabledBySlowmode
|
||||
&& (type == Type::Send || type == Type::Record));
|
||||
&& (type == Type::Send
|
||||
|| type == Type::Record
|
||||
|| type == Type::Round));
|
||||
|
||||
if (delay != 0) {
|
||||
base::call_delayed(
|
||||
|
@ -5479,6 +5499,15 @@ void HistoryWidget::inlineBotChanged() {
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::updateRecordMediaState() {
|
||||
Media::Capture::instance()->check();
|
||||
_canRecordAudioMessage = Media::Capture::instance()->available();
|
||||
|
||||
const auto environment = &Core::App().mediaDevices();
|
||||
const auto type = Webrtc::DeviceType::Camera;
|
||||
_canRecordVideoMessage = !environment->devices(type).empty();
|
||||
}
|
||||
|
||||
void HistoryWidget::fieldResized() {
|
||||
moveFieldControls();
|
||||
updateHistoryGeometry();
|
||||
|
|
|
@ -497,6 +497,7 @@ private:
|
|||
bool replyToPreviousMessage();
|
||||
bool replyToNextMessage();
|
||||
[[nodiscard]] bool showSlowmodeError();
|
||||
void updateRecordMediaState();
|
||||
|
||||
void hideChildWidgets();
|
||||
void hideSelectorControlsAnimated();
|
||||
|
@ -746,6 +747,9 @@ private:
|
|||
mtpRequestId _inlineBotResolveRequestId = 0;
|
||||
bool _isInlineBot = false;
|
||||
|
||||
bool _canRecordVideoMessage = false;
|
||||
bool _canRecordAudioMessage = false;
|
||||
|
||||
std::unique_ptr<HistoryView::ContactStatus> _contactStatus;
|
||||
std::unique_ptr<HistoryView::BusinessBotStatus> _businessBotStatus;
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/controls/silent_toggle.h"
|
||||
#include "ui/chat/choose_send_as.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "webrtc/webrtc_environment.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "mainwindow.h"
|
||||
|
@ -1517,7 +1518,7 @@ void ComposeControls::orderControls() {
|
|||
}
|
||||
|
||||
bool ComposeControls::showRecordButton() const {
|
||||
return ::Media::Capture::instance()->available()
|
||||
return _canRecordAudioMessage
|
||||
&& !_voiceRecordBar->isListenState()
|
||||
&& !_voiceRecordBar->isRecordingByAnotherBar()
|
||||
&& !HasSendText(_field)
|
||||
|
@ -2413,12 +2414,36 @@ void ComposeControls::initVoiceRecordBar() {
|
|||
return false;
|
||||
});
|
||||
|
||||
_voiceRecordBar->recordingTipRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
Core::App().settings().setRecordVideoMessages(
|
||||
!Core::App().settings().recordVideoMessages());
|
||||
updateSendButtonType();
|
||||
switch (_send->type()) {
|
||||
case Ui::SendButton::Type::Record:
|
||||
_show->showToast(tr::lng_record_voice_tip(tr::now));
|
||||
break;
|
||||
case Ui::SendButton::Type::Round:
|
||||
_show->showToast(tr::lng_record_video_tip(tr::now));
|
||||
break;
|
||||
}
|
||||
}, _wrap->lifetime());
|
||||
|
||||
_voiceRecordBar->updateSendButtonTypeRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateSendButtonType();
|
||||
}, _wrap->lifetime());
|
||||
}
|
||||
|
||||
void ComposeControls::updateRecordMediaState() {
|
||||
::Media::Capture::instance()->check();
|
||||
_canRecordAudioMessage = ::Media::Capture::instance()->available();
|
||||
|
||||
const auto environment = &Core::App().mediaDevices();
|
||||
const auto type = Webrtc::DeviceType::Camera;
|
||||
_canRecordVideoMessage = !environment->devices(type).empty();
|
||||
}
|
||||
|
||||
void ComposeControls::updateWrappingVisibility() {
|
||||
const auto hidden = _hidden.current();
|
||||
const auto &restriction = _writeRestriction.current();
|
||||
|
@ -2454,7 +2479,10 @@ auto ComposeControls::computeSendButtonType() const {
|
|||
} else if (_isInlineBot) {
|
||||
return Type::Cancel;
|
||||
} else if (showRecordButton()) {
|
||||
return Type::Record;
|
||||
return (Core::App().settings().recordVideoMessages()
|
||||
&& _canRecordVideoMessage)
|
||||
? Type::Round
|
||||
: Type::Record;
|
||||
}
|
||||
return (_mode == Mode::Normal) ? Type::Send : Type::Schedule;
|
||||
}
|
||||
|
@ -2487,7 +2515,9 @@ void ComposeControls::updateSendButtonType() {
|
|||
}();
|
||||
_send->setSlowmodeDelay(delay);
|
||||
_send->setDisabled(_sendDisabledBySlowmode.current()
|
||||
&& (type == Type::Send || type == Type::Record));
|
||||
&& (type == Type::Send
|
||||
|| type == Type::Record
|
||||
|| type == Type::Round));
|
||||
}
|
||||
|
||||
void ComposeControls::finishAnimating() {
|
||||
|
@ -3149,8 +3179,9 @@ bool ComposeControls::isRecording() const {
|
|||
bool ComposeControls::isRecordingPressed() const {
|
||||
return !_voiceRecordBar->isRecordingLocked()
|
||||
&& (!_voiceRecordBar->isHidden()
|
||||
|| (_send->type() == Ui::SendButton::Type::Record
|
||||
&& _send->isDown()));
|
||||
|| (_send->isDown()
|
||||
&& (_send->type() == Ui::SendButton::Type::Record
|
||||
|| _send->type() == Ui::SendButton::Type::Round)));
|
||||
}
|
||||
|
||||
rpl::producer<bool> ComposeControls::recordingActiveValue() const {
|
||||
|
|
|
@ -278,6 +278,7 @@ private:
|
|||
bool updateSendAsButton();
|
||||
void updateAttachBotsMenu();
|
||||
void updateHeight();
|
||||
void updateRecordMediaState();
|
||||
void updateWrappingVisibility();
|
||||
void updateControlsVisibility();
|
||||
void updateControlsGeometry(QSize size);
|
||||
|
@ -437,6 +438,9 @@ private:
|
|||
bool _botCommandShown = false;
|
||||
bool _likeShown = false;
|
||||
|
||||
bool _canRecordVideoMessage = false;
|
||||
bool _canRecordAudioMessage = false;
|
||||
|
||||
FullMsgId _editingId;
|
||||
std::shared_ptr<Data::PhotoMedia> _photoEditMedia;
|
||||
bool _canReplaceMedia = false;
|
||||
|
|
|
@ -1535,6 +1535,7 @@ void VoiceRecordBar::init() {
|
|||
return;
|
||||
}
|
||||
_recordingTipRequired = true;
|
||||
_recordingVideo = (_send->type() == Ui::SendButton::Type::Round);
|
||||
_startTimer.callOnce(st::universalDuration);
|
||||
} else if (e->type() == QEvent::MouseButtonRelease) {
|
||||
if (base::take(_recordingTipRequired)) {
|
||||
|
@ -1589,6 +1590,11 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn<void()> &&callback) {
|
|||
// _videoHiding.back()->hide();
|
||||
//}
|
||||
AssertIsDebug();
|
||||
if (_send->type() == Ui::SendButton::Type::Round) {
|
||||
_level->setType(VoiceRecordButton::Type::Round);
|
||||
} else {
|
||||
_level->setType(VoiceRecordButton::Type::Record);
|
||||
}
|
||||
const auto to = show ? 1. : 0.;
|
||||
const auto from = show ? 0. : 1.;
|
||||
auto animationCallback = [=, callback = std::move(callback)](auto value) {
|
||||
|
@ -1656,7 +1662,6 @@ void VoiceRecordBar::startRecording() {
|
|||
if (isRecording()) {
|
||||
return;
|
||||
}
|
||||
_recordingVideo = true; AssertIsDebug();
|
||||
auto appearanceCallback = [=] {
|
||||
if (_showAnimation.animating()) {
|
||||
return;
|
||||
|
@ -1694,6 +1699,15 @@ void VoiceRecordBar::startRecording() {
|
|||
}, [=] {
|
||||
stop(false);
|
||||
}, _recordingLifetime);
|
||||
if (_videoRecorder) {
|
||||
_videoRecorder->updated(
|
||||
) | rpl::start_with_next_error([=](const Update &update) {
|
||||
_recordingTipRequired = (update.samples < kMinSamples);
|
||||
recordUpdated(update.level, update.samples);
|
||||
}, [=] {
|
||||
stop(false);
|
||||
}, _recordingLifetime);
|
||||
}
|
||||
_recordingLifetime.add([=] {
|
||||
_recording = false;
|
||||
});
|
||||
|
@ -2013,7 +2027,8 @@ bool VoiceRecordBar::isListenState() const {
|
|||
}
|
||||
|
||||
bool VoiceRecordBar::isTypeRecord() const {
|
||||
return (_send->type() == Ui::SendButton::Type::Record);
|
||||
return (_send->type() == Ui::SendButton::Type::Record)
|
||||
|| (_send->type() == Ui::SendButton::Type::Round);
|
||||
}
|
||||
|
||||
bool VoiceRecordBar::isRecordingByAnotherBar() const {
|
||||
|
|
|
@ -137,10 +137,14 @@ void VoiceRecordButton::init() {
|
|||
const auto state = *currentState;
|
||||
const auto icon = (state == Type::Send)
|
||||
? st::historySendIcon
|
||||
: st::historyRecordVoiceActive;
|
||||
: (state == Type::Record)
|
||||
? st::historyRecordVoiceActive
|
||||
: st::historyRecordRoundActive;
|
||||
const auto position = (state == Type::Send)
|
||||
? st::historyRecordSendIconPosition
|
||||
: QPoint(0, 0);
|
||||
: (state == Type::Record)
|
||||
? QPoint(0, 0)
|
||||
: st::historyRecordRoundIconPosition;
|
||||
icon.paint(
|
||||
p,
|
||||
-icon.width() / 2 + position.x(),
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
enum class Type {
|
||||
Send,
|
||||
Record,
|
||||
Round,
|
||||
};
|
||||
|
||||
void setType(Type state);
|
||||
|
|
|
@ -480,6 +480,8 @@ storiesLike: IconButton(storiesAttach) {
|
|||
}
|
||||
storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }};
|
||||
storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }};
|
||||
storiesRecordRound: icon {{ "info/info_media_round", storiesComposeGrayIcon }};
|
||||
storiesRecordRoundOver: icon {{ "info/info_media_round", storiesComposeGrayIcon }};
|
||||
storiesRemoveSet: IconButton(stickerPanRemoveSet) {
|
||||
icon: icon {{ "simple_close", storiesComposeGrayIcon }};
|
||||
iconOver: icon {{ "simple_close", storiesComposeGrayIcon }};
|
||||
|
@ -686,6 +688,8 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
|
|||
}
|
||||
record: storiesRecordVoice;
|
||||
recordOver: storiesRecordVoiceOver;
|
||||
round: storiesRecordRound;
|
||||
roundOver: storiesRecordRoundOver;
|
||||
sendDisabledFg: storiesComposeGrayText;
|
||||
}
|
||||
attach: storiesAttach;
|
||||
|
|
|
@ -679,7 +679,7 @@ object_ptr<Ui::GenericBox> ChooseCameraDeviceBox(
|
|||
const style::Radio *radioSt) {
|
||||
return Box(
|
||||
ChooseMediaDeviceBox,
|
||||
tr::lng_settings_call_device_default(),
|
||||
tr::lng_settings_call_camera(),
|
||||
Core::App().mediaDevices().devicesValue(DeviceType::Camera),
|
||||
std::move(currentId),
|
||||
std::move(chosen),
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Ui {
|
|||
namespace {
|
||||
|
||||
constexpr auto kSide = 400;
|
||||
constexpr auto kOutputFilename = "C:\\Tmp\\TestVideo\\output.mp4";
|
||||
constexpr auto kUpdateEach = crl::time(100);
|
||||
|
||||
using namespace FFmpeg;
|
||||
|
||||
|
@ -33,6 +33,9 @@ public:
|
|||
void push(int64 mcstimestamp, const QImage &frame);
|
||||
void push(const Media::Capture::Chunk &chunk);
|
||||
|
||||
using Update = Media::Capture::Update;
|
||||
[[nodiscard]] rpl::producer<Update, rpl::empty_error> updated() const;
|
||||
|
||||
[[nodiscard]] RoundVideoResult finish();
|
||||
|
||||
private:
|
||||
|
@ -42,6 +45,23 @@ private:
|
|||
int write(uint8_t *buf, int buf_size);
|
||||
int64_t seek(int64_t offset, int whence);
|
||||
|
||||
void initEncoding();
|
||||
bool initVideo();
|
||||
bool initAudio();
|
||||
void deinitEncoding();
|
||||
void finishEncoding();
|
||||
void fail();
|
||||
|
||||
void encodeVideoFrame(int64 mcstimestamp, const QImage &frame);
|
||||
void encodeAudioFrame(const Media::Capture::Chunk &chunk);
|
||||
bool writeFrame(
|
||||
const FramePointer &frame,
|
||||
const CodecPointer &codec,
|
||||
AVStream *stream);
|
||||
|
||||
void updateMaxLevel(const Media::Capture::Chunk &chunk);
|
||||
void updateResultDuration(int64 pts, AVRational timeBase);
|
||||
|
||||
const crl::weak_on_queue<Private> _weak;
|
||||
|
||||
FormatPointer _format;
|
||||
|
@ -72,18 +92,9 @@ private:
|
|||
int64_t _resultOffset = 0;
|
||||
crl::time _resultDuration = 0;
|
||||
|
||||
void initEncoding();
|
||||
bool initVideo();
|
||||
bool initAudio();
|
||||
void deinitEncoding();
|
||||
void finishEncoding();
|
||||
|
||||
void encodeVideoFrame(int64 mcstimestamp, const QImage &frame);
|
||||
void encodeAudioFrame(const Media::Capture::Chunk &chunk);
|
||||
bool writeFrame(
|
||||
const FramePointer &frame,
|
||||
const CodecPointer &codec,
|
||||
AVStream *stream);
|
||||
ushort _maxLevelSinceLastUpdate = 0;
|
||||
crl::time _lastUpdateDuration = 0;
|
||||
rpl::event_stream<Update, rpl::empty_error> _updates;
|
||||
|
||||
};
|
||||
|
||||
|
@ -94,11 +105,6 @@ RoundVideoRecorder::Private::Private(crl::weak_on_queue<Private> weak)
|
|||
|
||||
RoundVideoRecorder::Private::~Private() {
|
||||
finishEncoding();
|
||||
|
||||
QFile file(kOutputFilename);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(_result);
|
||||
}
|
||||
}
|
||||
|
||||
int RoundVideoRecorder::Private::Write(void *opaque, uint8_t *buf, int buf_size) {
|
||||
|
@ -155,7 +161,7 @@ void RoundVideoRecorder::Private::initEncoding() {
|
|||
"mp4"_q);
|
||||
|
||||
if (!initVideo() || !initAudio()) {
|
||||
deinitEncoding();
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -164,7 +170,7 @@ void RoundVideoRecorder::Private::initEncoding() {
|
|||
nullptr));
|
||||
if (error) {
|
||||
LogError("avformat_write_header", error);
|
||||
deinitEncoding();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,6 +349,11 @@ void RoundVideoRecorder::Private::finishEncoding() {
|
|||
deinitEncoding();
|
||||
}
|
||||
|
||||
auto RoundVideoRecorder::Private::updated() const
|
||||
-> rpl::producer<Update, rpl::empty_error> {
|
||||
return _updates.events();
|
||||
}
|
||||
|
||||
RoundVideoResult RoundVideoRecorder::Private::finish() {
|
||||
if (!_format) {
|
||||
return {};
|
||||
|
@ -355,6 +366,11 @@ RoundVideoResult RoundVideoRecorder::Private::finish() {
|
|||
};
|
||||
};
|
||||
|
||||
void RoundVideoRecorder::Private::fail() {
|
||||
deinitEncoding();
|
||||
_updates.fire_error({});
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::Private::deinitEncoding() {
|
||||
_swsContext = nullptr;
|
||||
_videoCodec = nullptr;
|
||||
|
@ -406,7 +422,7 @@ void RoundVideoRecorder::Private::encodeVideoFrame(
|
|||
AV_PIX_FMT_YUV420P,
|
||||
&_swsContext);
|
||||
if (!_swsContext) {
|
||||
deinitEncoding();
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -444,14 +460,15 @@ void RoundVideoRecorder::Private::encodeVideoFrame(
|
|||
_videoFrame->linesize);
|
||||
|
||||
_videoFrame->pts = mcstimestamp - _videoFirstTimestamp;
|
||||
|
||||
LOG(("Audio At: %1").arg(_videoFrame->pts / 1'000'000.));
|
||||
if (!writeFrame(_videoFrame, _videoCodec, _videoStream)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::Private::encodeAudioFrame(const Media::Capture::Chunk &chunk) {
|
||||
void RoundVideoRecorder::Private::encodeAudioFrame(
|
||||
const Media::Capture::Chunk &chunk) {
|
||||
updateMaxLevel(chunk);
|
||||
|
||||
if (_audioTail.isEmpty()) {
|
||||
_audioTail = chunk.samples;
|
||||
} else {
|
||||
|
@ -459,7 +476,8 @@ void RoundVideoRecorder::Private::encodeAudioFrame(const Media::Capture::Chunk &
|
|||
}
|
||||
|
||||
const int inSamples = _audioTail.size() / sizeof(int16_t);
|
||||
const uint8_t *inData = reinterpret_cast<const uint8_t*>(_audioTail.constData());
|
||||
const uint8_t *inData = reinterpret_cast<const uint8_t*>(
|
||||
_audioTail.constData());
|
||||
int samplesProcessed = 0;
|
||||
|
||||
while (samplesProcessed + _audioCodec->frame_size <= inSamples) {
|
||||
|
@ -484,7 +502,7 @@ void RoundVideoRecorder::Private::encodeAudioFrame(const Media::Capture::Chunk &
|
|||
|
||||
if (error) {
|
||||
LogError("swr_convert", error);
|
||||
deinitEncoding();
|
||||
fail();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -493,8 +511,6 @@ void RoundVideoRecorder::Private::encodeAudioFrame(const Media::Capture::Chunk &
|
|||
|
||||
_audioFrame->pts = _audioPts;
|
||||
_audioPts += _audioFrame->nb_samples;
|
||||
|
||||
LOG(("Audio At: %1").arg(_audioFrame->pts / 48'000.));
|
||||
if (!writeFrame(_audioFrame, _audioCodec, _audioStream)) {
|
||||
return;
|
||||
}
|
||||
|
@ -514,10 +530,14 @@ bool RoundVideoRecorder::Private::writeFrame(
|
|||
const FramePointer &frame,
|
||||
const CodecPointer &codec,
|
||||
AVStream *stream) {
|
||||
if (frame) {
|
||||
updateResultDuration(frame->pts, codec->time_base);
|
||||
}
|
||||
|
||||
auto error = AvErrorWrap(avcodec_send_frame(codec.get(), frame.get()));
|
||||
if (error) {
|
||||
LogError("avcodec_send_frame", error);
|
||||
deinitEncoding();
|
||||
fail();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -533,21 +553,19 @@ bool RoundVideoRecorder::Private::writeFrame(
|
|||
return true; // Encoding finished
|
||||
} else if (error) {
|
||||
LogError("avcodec_receive_packet", error);
|
||||
deinitEncoding();
|
||||
fail();
|
||||
return false;
|
||||
}
|
||||
|
||||
pkt->stream_index = stream->index;
|
||||
av_packet_rescale_ts(pkt, codec->time_base, stream->time_base);
|
||||
|
||||
accumulate_max(
|
||||
_resultDuration,
|
||||
PtsToTimeCeil(pkt->pts, stream->time_base));
|
||||
updateResultDuration(pkt->pts, stream->time_base);
|
||||
|
||||
error = AvErrorWrap(av_interleaved_write_frame(_format.get(), pkt));
|
||||
if (error) {
|
||||
LogError("av_interleaved_write_frame", error);
|
||||
deinitEncoding();
|
||||
fail();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -555,6 +573,30 @@ bool RoundVideoRecorder::Private::writeFrame(
|
|||
return true;
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::Private::updateMaxLevel(
|
||||
const Media::Capture::Chunk &chunk) {
|
||||
const auto &list = chunk.samples;
|
||||
const auto samples = int(list.size() / sizeof(ushort));
|
||||
const auto data = reinterpret_cast<const ushort*>(list.constData());
|
||||
for (const auto value : gsl::make_span(data, samples)) {
|
||||
accumulate_max(_maxLevelSinceLastUpdate, value);
|
||||
}
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::Private::updateResultDuration(
|
||||
int64 pts,
|
||||
AVRational timeBase) {
|
||||
accumulate_max(_resultDuration, PtsToTimeCeil(pts, timeBase));
|
||||
|
||||
if (_lastUpdateDuration + kUpdateEach >= _resultDuration) {
|
||||
_lastUpdateDuration = _resultDuration;
|
||||
_updates.fire({
|
||||
.samples = int(_resultDuration * 48),
|
||||
.level = base::take(_maxLevelSinceLastUpdate),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RoundVideoRecorder::RoundVideoRecorder(
|
||||
RoundVideoRecorderDescriptor &&descriptor)
|
||||
: _descriptor(std::move(descriptor))
|
||||
|
@ -573,6 +615,13 @@ Fn<void(Media::Capture::Chunk)> RoundVideoRecorder::audioChunkProcessor() {
|
|||
};
|
||||
}
|
||||
|
||||
auto RoundVideoRecorder::updated() const
|
||||
-> rpl::producer<Update, rpl::empty_error> {
|
||||
return _private.producer_on_main([](const Private &that) {
|
||||
return that.updated();
|
||||
});
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::hide(Fn<void(RoundVideoResult)> done) {
|
||||
if (done) {
|
||||
_private.with([done = std::move(done)](Private &that) {
|
||||
|
@ -642,6 +691,9 @@ void RoundVideoRecorder::setPaused(bool paused) {
|
|||
return;
|
||||
}
|
||||
_paused = paused;
|
||||
_descriptor.track->setState(paused
|
||||
? Webrtc::VideoState::Inactive
|
||||
: Webrtc::VideoState::Active);
|
||||
_preview->update();
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace Media::Capture {
|
||||
struct Chunk;
|
||||
struct Update;
|
||||
} // namespace Media::Capture
|
||||
|
||||
namespace tgcalls {
|
||||
|
@ -49,6 +50,9 @@ public:
|
|||
void setPaused(bool paused);
|
||||
void hide(Fn<void(RoundVideoResult)> done = nullptr);
|
||||
|
||||
using Update = Media::Capture::Update;
|
||||
[[nodiscard]] rpl::producer<Update, rpl::empty_error> updated() const;
|
||||
|
||||
private:
|
||||
class Private;
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ void SendButton::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
switch (_type) {
|
||||
case Type::Record: paintRecord(p, over); break;
|
||||
case Type::Round: paintRound(p, over); break;
|
||||
case Type::Save: paintSave(p, over); break;
|
||||
case Type::Cancel: paintCancel(p, over); break;
|
||||
case Type::Send: paintSend(p, over); break;
|
||||
|
@ -108,6 +109,20 @@ void SendButton::paintRecord(QPainter &p, bool over) {
|
|||
icon.paintInCenter(p, rect());
|
||||
}
|
||||
|
||||
void SendButton::paintRound(QPainter &p, bool over) {
|
||||
if (!isDisabled()) {
|
||||
paintRipple(
|
||||
p,
|
||||
(width() - _st.inner.rippleAreaSize) / 2,
|
||||
_st.inner.rippleAreaPosition.y());
|
||||
}
|
||||
|
||||
const auto &icon = (isDisabled() || !over)
|
||||
? _st.round
|
||||
: _st.roundOver;
|
||||
icon.paintInCenter(p, rect());
|
||||
}
|
||||
|
||||
void SendButton::paintSave(QPainter &p, bool over) {
|
||||
const auto &saveIcon = over
|
||||
? st::historyEditSaveIconOver
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
Schedule,
|
||||
Save,
|
||||
Record,
|
||||
Round,
|
||||
Cancel,
|
||||
Slowmode,
|
||||
};
|
||||
|
@ -47,6 +48,7 @@ private:
|
|||
[[nodiscard]] bool isSlowmode() const;
|
||||
|
||||
void paintRecord(QPainter &p, bool over);
|
||||
void paintRound(QPainter &p, bool over);
|
||||
void paintSave(QPainter &p, bool over);
|
||||
void paintCancel(QPainter &p, bool over);
|
||||
void paintSend(QPainter &p, bool over);
|
||||
|
|
Loading…
Add table
Reference in a new issue