From ff44f626ba5a9a0ac9a0a919cc6a8ea1de824d30 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Oct 2024 22:43:24 +0400 Subject: [PATCH] Allow switching between voice/video. --- Telegram/Resources/langs/lang.strings | 3 +- .../chat_helpers/chat_helpers.style | 8 ++ Telegram/SourceFiles/core/core_settings.cpp | 11 +- Telegram/SourceFiles/core/core_settings.h | 9 ++ .../SourceFiles/history/history_widget.cpp | 39 +++++- Telegram/SourceFiles/history/history_widget.h | 4 + .../history_view_compose_controls.cpp | 41 +++++- .../controls/history_view_compose_controls.h | 4 + .../history_view_voice_record_bar.cpp | 19 ++- .../history_view_voice_record_button.cpp | 8 +- .../history_view_voice_record_button.h | 1 + .../SourceFiles/media/view/media_view.style | 4 + .../SourceFiles/settings/settings_calls.cpp | 2 +- .../ui/controls/round_video_recorder.cpp | 120 +++++++++++++----- .../ui/controls/round_video_recorder.h | 4 + .../SourceFiles/ui/controls/send_button.cpp | 15 +++ .../SourceFiles/ui/controls/send_button.h | 2 + 17 files changed, 242 insertions(+), 52 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 66a51005b..3d576fe7c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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."; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 0c680dd1e..2d3a1cd63 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -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; } diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index af6819f8f..c0293b01a 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -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(); diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 821f9ec21..b1fce992b 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -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; }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 7f71f6549..3cb539857 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -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(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 7906fec33..17e836beb 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -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 _contactStatus; std::unique_ptr _businessBotStatus; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 47d0b9b6a..0170f1a88 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -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 ComposeControls::recordingActiveValue() const { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index fc26079e7..91e7e9d27 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -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 _photoEditMedia; bool _canReplaceMedia = false; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 7ed5bf0db..dd5968335 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -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 &&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 { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 986ea9d6e..b32fa2d96 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -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(), diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h index bd3f0150c..e8e2dd7b6 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h @@ -31,6 +31,7 @@ public: enum class Type { Send, Record, + Round, }; void setType(Type state); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index e1fb123d7..e25092dbf 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -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; diff --git a/Telegram/SourceFiles/settings/settings_calls.cpp b/Telegram/SourceFiles/settings/settings_calls.cpp index de7c34e3e..d7e2947bd 100644 --- a/Telegram/SourceFiles/settings/settings_calls.cpp +++ b/Telegram/SourceFiles/settings/settings_calls.cpp @@ -679,7 +679,7 @@ object_ptr 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), diff --git a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp index 09b60bb7b..ffcd584c3 100644 --- a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp +++ b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp @@ -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 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 _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 _updates; }; @@ -94,11 +105,6 @@ RoundVideoRecorder::Private::Private(crl::weak_on_queue 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 { + 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(_audioTail.constData()); + const uint8_t *inData = reinterpret_cast( + _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(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 RoundVideoRecorder::audioChunkProcessor() { }; } +auto RoundVideoRecorder::updated() const +-> rpl::producer { + return _private.producer_on_main([](const Private &that) { + return that.updated(); + }); +} + void RoundVideoRecorder::hide(Fn 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(); } diff --git a/Telegram/SourceFiles/ui/controls/round_video_recorder.h b/Telegram/SourceFiles/ui/controls/round_video_recorder.h index 1cfb2d289..56af2110a 100644 --- a/Telegram/SourceFiles/ui/controls/round_video_recorder.h +++ b/Telegram/SourceFiles/ui/controls/round_video_recorder.h @@ -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 done = nullptr); + using Update = Media::Capture::Update; + [[nodiscard]] rpl::producer updated() const; + private: class Private; diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index 639c2a75f..e0388c032 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -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 diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h index 5b3cc104d..835974f51 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.h +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -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);