diff --git a/Telegram/Resources/all_files.style b/Telegram/Resources/all_files.style deleted file mode 100644 index 52e8c327ea..0000000000 --- a/Telegram/Resources/all_files.style +++ /dev/null @@ -1,33 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ - -// Legacy styles -using "basic_types.style"; -using "basic.style"; - -using "boxes/boxes.style"; -using "dialogs/dialogs.style"; -using "history/history.style"; -using "overview/overview.style"; -using "profile/profile.style"; -using "settings/settings.style"; -using "media/view/mediaview.style"; -using "ui/widgets/widgets.style"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index d1c5caeb92..ff9b6df084 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2136,8 +2136,6 @@ namespace { } void initMedia() { - audioInit(); - if (!::monofont) { QString family; tryFontFamily(family, qsl("Consolas")); @@ -2210,21 +2208,19 @@ namespace { } void deinitMedia() { - audioFinish(); - delete ::emoji; ::emoji = 0; delete ::emojiLarge; ::emojiLarge = 0; - for (int32 j = 0; j < 4; ++j) { - for (int32 i = 0; i < RoundCornersCount; ++i) { + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < RoundCornersCount; ++i) { delete ::corners[i].p[j]; ::corners[i].p[j] = nullptr; } delete ::cornersMaskSmall[j]; ::cornersMaskSmall[j] = nullptr; delete ::cornersMaskLarge[j]; ::cornersMaskLarge[j] = nullptr; } - for (CornersMap::const_iterator i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { - for (int32 j = 0; j < 4; ++j) { + for (auto i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { + for (int j = 0; j < 4; ++j) { delete i->p[j]; } } diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 90c06fb183..968ff4a65a 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -35,6 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "observer_peer.h" #include "core/observer.h" #include "window/chat_background.h" +#include "media/player/media_player_instance.h" namespace { void mtpStateChanged(int32 dc, int32 state) { @@ -722,6 +723,7 @@ AppClass::AppClass() : QObject() style::startManager(); anim::startManager(); historyInit(); + Media::Player::start(); DEBUG_LOG(("Application Info: inited...")); @@ -1108,6 +1110,7 @@ AppClass::~AppClass() { Window::chatBackground()->reset(); + Media::Player::finish(); style::stopManager(); Local::finish(); diff --git a/Telegram/SourceFiles/codegen/common/logging.cpp b/Telegram/SourceFiles/codegen/common/logging.cpp index 917020a5e4..d94e12016f 100644 --- a/Telegram/SourceFiles/codegen/common/logging.cpp +++ b/Telegram/SourceFiles/codegen/common/logging.cpp @@ -27,8 +27,10 @@ namespace codegen { namespace common { namespace { +QString WorkingPath = "."; + std::string relativeLocalPath(const QString &filepath) { - auto name = QFile::encodeName(QDir().relativeFilePath(filepath)); + auto name = QFile::encodeName(QDir(WorkingPath).relativeFilePath(filepath)); return name.constData(); } @@ -43,5 +45,9 @@ LogStream logError(int code, const QString &filepath, int line) { return LogStream(std::cerr); } +void logSetWorkingPath(const QString &workingpath) { + WorkingPath = workingpath; +} + } // namespace common } // namespace codegen \ No newline at end of file diff --git a/Telegram/SourceFiles/codegen/common/logging.h b/Telegram/SourceFiles/codegen/common/logging.h index 9c90f1fa43..5ad1e467f8 100644 --- a/Telegram/SourceFiles/codegen/common/logging.h +++ b/Telegram/SourceFiles/codegen/common/logging.h @@ -72,6 +72,8 @@ LogStream operator<<(LogStream &&stream, T &&value) { // logError(kErrorFileTooLarge, filepath) << "file too large, size=" << size; LogStream logError(int code, const QString &filepath, int line = 0); +void logSetWorkingPath(const QString &workingpath); + static constexpr int kErrorInternal = 666; } // namespace common diff --git a/Telegram/SourceFiles/codegen/style/options.cpp b/Telegram/SourceFiles/codegen/style/options.cpp index 433c109137..2793e3cf71 100644 --- a/Telegram/SourceFiles/codegen/style/options.cpp +++ b/Telegram/SourceFiles/codegen/style/options.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include #include +#include #include "codegen/common/logging.h" namespace codegen { @@ -32,6 +33,7 @@ constexpr int kErrorIncludePathExpected = 901; constexpr int kErrorOutputPathExpected = 902; constexpr int kErrorInputPathExpected = 903; constexpr int kErrorSingleInputPathExpected = 904; +constexpr int kErrorWorkingPathExpected = 905; } // namespace @@ -77,6 +79,17 @@ Options parseOptions() { } else if (arg.startsWith("-o")) { result.outputPath = arg.mid(2); + // Working path + } else if (arg == "-w") { + if (++i == count) { + logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w"; + return Options(); + } else { + common::logSetWorkingPath(args.at(i)); + } + } else if (arg.startsWith("-w")) { + common::logSetWorkingPath(arg.mid(2)); + // Input path } else { if (result.inputPath.isEmpty()) { diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 68dd42b2ef..4e92ce5594 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -340,7 +340,7 @@ inline constexpr typename remove_reference::type &&move(T &&value) noexcept { } template -void swap(T &a, T &b) { +void swap_moveable(T &a, T &b) { T tmp = move(a); a = move(b); b = move(tmp); @@ -517,7 +517,7 @@ struct is_base_of { template T createAndSwap(T &value) { T result = T(); - std_::swap(result, value); + std_::swap_moveable(result, value); return std_::move(result); } diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h index f37494625d..ef5ae1dc4f 100644 --- a/Telegram/SourceFiles/core/observer.h +++ b/Telegram/SourceFiles/core/observer.h @@ -335,7 +335,7 @@ class CommonObservable { public: using Handler = typename CommonObservableData::Handler; - Subscription subscribe(Handler &&handler) { + Subscription add_subscription(Handler &&handler) { if (!_data) { _data = MakeShared>(this); } @@ -360,6 +360,9 @@ public: this->_data->notify(std_::move(event), sync); } } + void notify(const EventType &event, bool sync = false) { + notify(EventType(event)); + } }; @@ -562,7 +565,7 @@ class Subscriber { protected: template int subscribe(base::Observable &observable, Lambda &&handler) { - _subscriptions.push_back(observable.subscribe(std_::forward(handler))); + _subscriptions.push_back(observable.add_subscription(std_::forward(handler))); return _subscriptions.size() - 1; } diff --git a/Telegram/SourceFiles/core/vector_of_moveable.h b/Telegram/SourceFiles/core/vector_of_moveable.h index 861b184ec3..fbc9bd4b9f 100644 --- a/Telegram/SourceFiles/core/vector_of_moveable.h +++ b/Telegram/SourceFiles/core/vector_of_moveable.h @@ -39,9 +39,9 @@ public: , _plaindata(createAndSwap(other._plaindata)) { } vector_of_moveable &operator=(vector_of_moveable &&other) { - std_::swap(_size, other._size); - std_::swap(_capacity, other._capacity); - std_::swap(_plaindata, other._plaindata); + std_::swap_moveable(_size, other._size); + std_::swap_moveable(_capacity, other._capacity); + std_::swap_moveable(_plaindata, other._plaindata); return *this; } @@ -165,7 +165,7 @@ private: new (newLocation) T(std_::move(*oldLocation)); oldLocation->~T(); } - std_::swap(_plaindata, newPlainData); + std_::swap_moveable(_plaindata, newPlainData); _capacity = newCapacity; operator delete[](newPlainData); } diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 69f133c6ab..afd0d4cc2e 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -584,8 +584,8 @@ struct Data { int32 DebugLoggingFlags = 0; - float64 SongVolume = 0.9; - float64 VideoVolume = 0.9; + float64 SongVolume = kDefaultVolume; + float64 VideoVolume = kDefaultVolume; // config int32 ChatSizeMax = 200; diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index cbe01c49a2..05a513636c 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -31,7 +31,6 @@ class ItemBase; } // namespace Layout } // namespace InlineBots - namespace App { void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0); @@ -236,6 +235,8 @@ bool started(); void start(); void finish(); +constexpr float64 kDefaultVolume = 0.9; + DeclareReadOnlyVar(uint64, LaunchId); DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index bb4422dac0..905289c306 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -1177,7 +1177,7 @@ HistoryBlock *History::prepareBlockForAddingItem() { return _buildingFrontBlock->block; } - HistoryBlock *result = _buildingFrontBlock->block = new HistoryBlock(this); + auto result = _buildingFrontBlock->block = new HistoryBlock(this); if (_buildingFrontBlock->expectedItemsCount > 0) { result->items.reserve(_buildingFrontBlock->expectedItemsCount + 1); } @@ -1194,7 +1194,7 @@ HistoryBlock *History::prepareBlockForAddingItem() { return blocks.back(); } - HistoryBlock *result = new HistoryBlock(this); + auto result = new HistoryBlock(this); result->setIndexInHistory(blocks.size()); blocks.push_back(result); @@ -1206,7 +1206,7 @@ void History::addItemToBlock(HistoryItem *item) { t_assert(item != nullptr); t_assert(item->detached()); - HistoryBlock *block = prepareBlockForAddingItem(); + auto block = prepareBlockForAddingItem(); item->attachToBlock(block, block->items.size()); block->items.push_back(item); @@ -1231,21 +1231,21 @@ void History::addOlderSlice(const QVector &slice) { for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; - HistoryItem *adding = createItem(*i, false, true); + auto adding = createItem(*i, false, true); if (!adding) continue; addItemToBlock(adding); } - HistoryBlock *block = finishBuildingFrontBlock(); + auto block = finishBuildingFrontBlock(); if (!block) { // If no items were added it means we've loaded everything old. oldLoaded = true; } else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants bool channel = isChannel(); int32 mask = 0; - QList *lastAuthors = 0; - OrderedSet *markupSenders = 0; + QList *lastAuthors = nullptr; + OrderedSet *markupSenders = nullptr; if (peer->isChat()) { lastAuthors = &peer->asChat()->lastAuthors; markupSenders = &peer->asChat()->markupSenders; @@ -1254,7 +1254,7 @@ void History::addOlderSlice(const QVector &slice) { markupSenders = &peer->asChannel()->mgInfo->markupSenders; } for (int32 i = block->items.size(); i > 0; --i) { - HistoryItem *item = block->items[i - 1]; + auto item = block->items[i - 1]; mask |= item->addToOverview(AddToOverviewFront); if (item->from()->id) { if (lastAuthors) { // chats @@ -1272,7 +1272,7 @@ void History::addOlderSlice(const QVector &slice) { if (item->author()->id) { if (markupSenders) { // chats with bots if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { - MTPDreplyKeyboardMarkup::Flags markupFlags = item->replyKeyboardFlags(); + auto markupFlags = item->replyKeyboardFlags(); if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { bool wasKeyboardHide = markupSenders->contains(item->author()); if (!wasKeyboardHide) { @@ -1340,7 +1340,7 @@ void History::addNewerSlice(const QVector &slice) { bool atLeastOneAdded = false; for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; - HistoryItem *adding = createItem(*i, false, true); + auto adding = createItem(*i, false, true); if (!adding) continue; addItemToBlock(adding); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 6fcb663362..1acf31a2d4 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -8723,7 +8723,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { if (r != rect()) { p.setClipRect(r); } - bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden(); + bool hasTopBar = !App::main()->topBar()->isHidden(); if (_a_show.animating()) { int retina = cIntRetinaFactor(); @@ -8741,7 +8741,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { } QRect fill(0, 0, width(), App::main()->height()); - int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0; + int fromy = (hasTopBar ? (-st::topBarHeight) : 0), x = 0, y = 0; QPixmap cached = App::main()->cachedBackground(fill, x, y); if (cached.isNull()) { auto &pix = Window::chatBackground()->image(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 06b1e07be3..28eb740445 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_overview.h" #include "inline_bots/inline_bot_result.h" #include "media/media_clip_reader.h" +#include "media/player/media_player_instance.h" #include "localstorage.h" #include "mainwidget.h" #include "lang.h" @@ -874,7 +875,7 @@ bool File::updateStatusText() const { realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); } - if (!showPause && (playing == AudioMsgId(document, FullMsgId())) && App::main() && App::main()->player()->seekingSong(playing)) { + if (!showPause && (playing == AudioMsgId(document, FullMsgId())) && Media::Player::exists() && Media::Player::instance()->isSeeking()) { showPause = true; } } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 6e2fe177a1..c44ff0ab71 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -50,8 +50,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "shortcuts.h" #include "media/media_audio.h" -#include "media/player/media_player_button.h" #include "media/player/media_player_widget.h" +#include "media/player/media_player_instance.h" #include "core/qthelp_regex.h" #include "core/qthelp_url.h" #include "window/chat_background.h" @@ -98,9 +98,14 @@ MainWidget::MainWidget(MainWindow *window) : TWidget(window) connect(_topBar, SIGNAL(clicked()), this, SLOT(onTopBarClick())); connect(_history, SIGNAL(historyShown(History*,MsgId)), this, SLOT(onHistoryShown(History*,MsgId))); connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings())); - if (audioPlayer()) { - connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); + if (auto player = audioPlayer()) { + subscribe(player, [this](const AudioMsgId &audioId) { + if (audioId.type() != AudioMsgId::Type::Video) { + handleAudioUpdate(audioId); + } + }); } + connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted())); connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement())); @@ -1542,11 +1547,7 @@ void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) { } } -void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { - if (audioId.type() == AudioMsgId::Type::Video) { - return; - } - +void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { AudioMsgId playing; auto playbackState = audioPlayer()->currentState(&playing, audioId.type()); if (playing == audioId && playbackState.state == AudioPlayerStoppedAtStart) { @@ -1563,6 +1564,13 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { } if (playing == audioId && audioId.type() == AudioMsgId::Type::Song) { + if (!_mediaPlayer && Media::Player::exists()) { + _mediaPlayer.create(this); + updateMediaPlayerPosition(); + orderWidgets(); + Media::Player::instance()->createdNotifier().notify(Media::Player::CreatedEvent(_mediaPlayer), true); + } + _player->updateState(playing, playbackState); if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { @@ -1600,28 +1608,35 @@ void MainWidget::closePlayer() { } void MainWidget::documentLoadProgress(FileLoader *loader) { - mtpFileLoader *l = loader ? loader->mtpLoader() : 0; - if (!l) return; + if (auto mtpLoader = loader ? loader->mtpLoader() : nullptr) { + documentLoadProgress(App::document(mtpLoader->objId())); + } +} - DocumentData *document = App::document(l->objId()); +void MainWidget::documentLoadProgress(DocumentData *document) { if (document->loaded()) { document->performActionOnLoad(); } - const DocumentItems &items(App::documentItems()); - DocumentItems::const_iterator i = items.constFind(document); + auto &items = App::documentItems(); + auto i = items.constFind(document); if (i != items.cend()) { - for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + for (auto j = i->cbegin(), e = i->cend(); j != e; ++j) { Ui::repaintHistoryItem(j.key()); } } App::wnd()->documentUpdated(document); - if (!document->loaded() && document->loading() && document->song() && audioPlayer()) { - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - if (playing.audio() == document && !_player->isHidden()) { - _player->updateState(playing, playbackState); + if (!document->loaded() && document->song()) { + if (audioPlayer() && document->loading()) { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing.audio() == document && !_player->isHidden()) { + _player->updateState(playing, playbackState); + } + } + if (Media::Player::exists()) { + Media::Player::instance()->documentLoadProgress(document); } } } @@ -2664,9 +2679,7 @@ inline int chatsListWidth(int windowWidth) { void MainWidget::resizeEvent(QResizeEvent *e) { int32 tbh = _topBar->isHidden() ? 0 : st::topBarHeight; - if (_mediaPlayer) { - _mediaPlayer->moveToRight(0, 0); - } + updateMediaPlayerPosition(); if (Adaptive::OneColumn()) { _dialogsWidth = width(); _player->setGeometry(0, 0, _dialogsWidth, _player->height()); @@ -2700,6 +2713,12 @@ void MainWidget::resizeEvent(QResizeEvent *e) { _contentScrollAddToY = 0; } +void MainWidget::updateMediaPlayerPosition() { + if (_mediaPlayer) { + _mediaPlayer->moveToRight(0, 0); + } +} + int MainWidget::contentScrollAddToY() const { return _contentScrollAddToY; } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index c91029e515..35a006bb80 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -139,10 +139,6 @@ class MainWidget : public TWidget, public RPCSender, private base::Subscriber { public: MainWidget(MainWindow *window); - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - bool needBackButton(); // Temporary methods, while top bar was not done inside HistoryWidget / OverviewWidget. @@ -382,6 +378,8 @@ public: void closePlayer(); + void documentLoadProgress(DocumentData *document); + void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col); void ui_repaintHistoryItem(const HistoryItem *item); @@ -423,7 +421,6 @@ signals: public slots: void webPagesUpdate(); - void audioPlayProgress(const AudioMsgId &audioId); void documentLoadProgress(FileLoader *loader); void documentLoadFailed(FileLoader *loader, bool started); void documentLoadRetry(); @@ -479,8 +476,15 @@ public slots: void onDeletePhotoSure(); +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + private: void updateAdaptiveLayout(); + void handleAudioUpdate(const AudioMsgId &audioId); + void updateMediaPlayerPosition(); void sendReadRequest(PeerData *peer, MsgId upTo); void channelReadDone(PeerData *peer, const MTPBool &result); diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 16a7362be6..06978eb64e 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -349,7 +349,7 @@ void AudioPlayer::onUpdated(const AudioMsgId &audio) { if (audio.type() == AudioMsgId::Type::Video) { videoSoundProgress(audio); } - notify(AudioMsgId(audio)); + notify(audio); } void AudioPlayer::onError(const AudioMsgId &audio) { diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index a71a648134..7d2a724584 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -20,6 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ using "basic.style"; +using "ui/widgets/widgets.style"; mediaPlayerTitleButtonSize: size(titleHeight, titleHeight); mediaPlayerTitleButtonInner: size(25px, 25px); @@ -37,6 +38,9 @@ mediaPlayerMarginBottom: 10px; mediaPlayerWidth: 344px; mediaPlayerCoverHeight: 102px; +mediaPlayerActiveFg: #54b5ed; +mediaPlayerInactiveFg: #dfebf2; + mediaPlayerPlayButton: IconButton { width: 32px; height: 32px; @@ -45,7 +49,7 @@ mediaPlayerPlayButton: IconButton { overOpacity: 1.; icon: icon { - { "player_play", #54b5ed, point(6px, 7px) }, + { "player_play", mediaPlayerActiveFg, point(6px, 7px) }, }; iconPosition: point(0px, 0px); downIconPosition: point(0px, 0px); @@ -53,41 +57,88 @@ mediaPlayerPlayButton: IconButton { duration: 0; } mediaPlayerPauseIcon: icon { - { "player_pause", #54b5ed, point(9px, 8px) } + { "player_pause", mediaPlayerActiveFg, point(9px, 8px) } }; mediaPlayerRepeatButton: IconButton(mediaPlayerPlayButton) { + width: 31px; icon: icon { - { "player_repeat", #54b5ed, point(9px, 9px)} + { "player_repeat", mediaPlayerActiveFg, point(9px, 9px)} }; } +mediaPlayerRepeatDisabledIcon: icon { + { "player_repeat", mediaPlayerInactiveFg, point(9px, 9px)} +}; +mediaPlayerPreviousButton: IconButton(mediaPlayerPlayButton) { + width: 37px; + icon: icon { + { "player_previous", mediaPlayerActiveFg, point(10px, 10px) }, + }; +} +mediaPlayerPreviousDisabledIcon: icon { + { "player_previous", mediaPlayerInactiveFg, point(10px, 10px) }, +}; +mediaPlayerNextButton: IconButton(mediaPlayerPreviousButton) { + icon: icon { + { "player_next", mediaPlayerActiveFg, point(10px, 10px) }, + }; +} +mediaPlayerNextDisabledIcon: icon { + { "player_next", mediaPlayerInactiveFg, point(10px, 10px) }, +}; mediaPlayerPadding: 18px; mediaPlayerNameTop: 24px; mediaPlayerPlayLeft: 9px; +mediaPlayerPlaySkip: 7px; mediaPlayerPlayTop: 58px; -mediaPlayerNameFont: normalFont; -mediaPlayerNameFg: windowTextFg; -mediaPlayerTimeFont: normalFont; -mediaPlayerTimeFg: windowSubTextFg; mediaPlayerPlaybackTop: 32px; mediaPlayerPlaybackPadding: 8px; -mediaPlayerPlaybackBg: #54b5ed; -mediaPlayerPlaybackLine: 3px; +mediaPlayerPlayback: MediaSlider { + width: 3px; + activeFg: mediaPlayerActiveFg; + inactiveFg: mediaPlayerInactiveFg; + activeOpacity: 1.; + inactiveOpacity: 1.; + seekSize: size(9px, 9px); + duration: 150; +} -mediaPlayerVolumeRight: 50px; +mediaPlayerName: flatLabel(labelDefFlat) { + maxHeight: 20px; + textFg: windowTextFg; +} +mediaPlayerTime: LabelSimple(defaultLabelSimple) { + textFg: windowSubTextFg; +} + +mediaPlayerVolumeTop: 65px; +mediaPlayerVolumeRight: 51px; mediaPlayerVolumeWidth: 86px; mediaPlayerVolumeLength: 64px; mediaPlayerVolumeIcon0: icon { - { "player_volume0", #54b5ed }, + { "player_volume0", mediaPlayerActiveFg }, }; mediaPlayerVolumeIcon1: icon { - { "player_volume1", #54b5ed }, + { "player_volume1", mediaPlayerActiveFg }, }; mediaPlayerVolumeIcon2: icon { - { "player_volume2", #54b5ed }, + { "player_volume2", mediaPlayerActiveFg }, }; mediaPlayerVolumeIcon3: icon { - { "player_volume3", #54b5ed }, + { "player_volume3", mediaPlayerActiveFg }, }; +mediaPlayerVolumeToggle: IconButton { + width: 18px; + height: 17px; + + opacity: 1.; + overOpacity: 1.; + + icon: mediaPlayerVolumeIcon0; + iconPosition: point(0px, 2px); + downIconPosition: point(0px, 2px); + + duration: 0; +} diff --git a/Telegram/SourceFiles/media/player/media_player_button.cpp b/Telegram/SourceFiles/media/player/media_player_button.cpp index 7955662452..92788e5a01 100644 --- a/Telegram/SourceFiles/media/player/media_player_button.cpp +++ b/Telegram/SourceFiles/media/player/media_player_button.cpp @@ -23,6 +23,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_media_player.h" #include "media/media_audio.h" +#include "media/player/media_player_instance.h" +#include "shortcuts.h" namespace Media { namespace Player { @@ -32,8 +34,33 @@ TitleButton::TitleButton(QWidget *parent) : Button(parent) { resize(st::mediaPlayerTitleButtonSize); setClickedCallback([this]() { - setShowPause(!_showPause); + if (exists()) { + if (_showPause) { + instance()->pause(); + } else { + instance()->play(); + } + } }); + + if (exists()) { + subscribe(instance()->updatedNotifier(), [this](const UpdatedEvent &e) { + updatePauseState(); + }); + updatePauseState(); + finishIconTransform(); + } +} + +void TitleButton::updatePauseState() { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + auto stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing); + auto showPause = !stopped && (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + if (exists() && instance()->isSeeking()) { + showPause = true; + } + setShowPause(showPause); } void TitleButton::setShowPause(bool showPause) { diff --git a/Telegram/SourceFiles/media/player/media_player_button.h b/Telegram/SourceFiles/media/player/media_player_button.h index e07539aa0b..4223a2d511 100644 --- a/Telegram/SourceFiles/media/player/media_player_button.h +++ b/Telegram/SourceFiles/media/player/media_player_button.h @@ -25,12 +25,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Media { namespace Player { -class TitleButton : public Button { +class TitleButton : public Button, private base::Subscriber { public: TitleButton(QWidget *parent); - void setShowPause(bool showPause); - void finishIconTransform(); + void updatePauseState(); protected: void paintEvent(QPaintEvent *e) override; @@ -40,6 +39,9 @@ protected: private: void paintIcon(Painter &p); + void setShowPause(bool showPause); + void finishIconTransform(); + bool _showPause = true; FloatAnimation _iconTransformToPause; ColorAnimation _iconFg; diff --git a/Telegram/SourceFiles/media/player/media_player_cover.cpp b/Telegram/SourceFiles/media/player/media_player_cover.cpp index c1c4165fb4..cd1800ebd4 100644 --- a/Telegram/SourceFiles/media/player/media_player_cover.cpp +++ b/Telegram/SourceFiles/media/player/media_player_cover.cpp @@ -24,30 +24,112 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/flatlabel.h" #include "ui/widgets/label_simple.h" #include "ui/buttons/icon_button.h" -#include "media/player/media_player_playback.h" +#include "media/media_audio.h" +#include "media/view/media_clip_playback.h" +#include "media/player/media_player_instance.h" #include "media/player/media_player_volume_controller.h" #include "styles/style_media_player.h" +#include "styles/style_mediaview.h" +#include "shortcuts.h" namespace Media { namespace Player { CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent) -, _nameLabel(this) -, _timeLabel(this) -, _playback(this) +, _nameLabel(this, st::mediaPlayerName) +, _timeLabel(this, st::mediaPlayerTime) +, _playback(this, st::mediaPlayerPlayback) , _playPause(this, st::mediaPlayerPlayButton) , _volumeController(this) , _repeatTrack(this, st::mediaPlayerRepeatButton) { setAttribute(Qt::WA_OpaquePaintEvent); - _playPause->setIcon(&st::mediaPlayerPauseIcon); + + _playback->setChangeProgressCallback([this](float64 value) { + handleSeekProgress(value); + }); + _playback->setChangeFinishedCallback([this](float64 value) { + handleSeekFinished(value); + }); + + if (_showPause) { + _playPause->setIcon(&st::mediaPlayerPauseIcon); + } + _playPause->setClickedCallback([this]() { + if (exists()) { + if (_showPause) { + instance()->pause(); + } else { + instance()->play(); + } + } + }); + + updateRepeatTrackIcon(); + _repeatTrack->setClickedCallback([this]() { + instance()->toggleRepeat(); + updateRepeatTrackIcon(); + }); + + if (exists()) { + subscribe(instance()->playlistChangedNotifier(), [this]() { + handlePlaylistUpdate(); + }); + subscribe(instance()->updatedNotifier(), [this](const UpdatedEvent &e) { + handleSongUpdate(e); + }); + subscribe(instance()->songChangedNotifier(), [this]() { + handleSongChange(); + }); + handleSongChange(); + if (auto player = audioPlayer()) { + AudioMsgId playing; + auto playbackState = player->currentState(&playing, AudioMsgId::Type::Song); + handleSongUpdate(UpdatedEvent(&playing, &playbackState)); + } + } +} + +void CoverWidget::handleSeekProgress(float64 progress) { + if (!_lastDurationMs) return; + + auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); + if (_seekPositionMs != positionMs) { + _seekPositionMs = positionMs; + updateTimeLabel(); + if (exists()) { + instance()->startSeeking(); + } + } +} + +void CoverWidget::handleSeekFinished(float64 progress) { + if (!_lastDurationMs) return; + + auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); + _seekPositionMs = -1; + + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing && playbackState.duration) { + audioPlayer()->seek(qRound(progress * playbackState.duration)); + } + + updateTimeLabel(); + if (exists()) { + instance()->stopSeeking(); + } } void CoverWidget::resizeEvent(QResizeEvent *e) { - _nameLabel->moveToLeft(st::mediaPlayerPadding, st::mediaPlayerNameTop - st::mediaPlayerNameFont->ascent); - _timeLabel->moveToRight(st::mediaPlayerPadding, st::mediaPlayerNameTop - st::mediaPlayerTimeFont->ascent); - _playback->setGeometry(st::mediaPlayerPadding, st::mediaPlayerPlaybackTop, width() - 2 * st::mediaPlayerPadding, 2 * st::mediaPlayerPlaybackPadding + st::mediaPlayerPlaybackLine); + _nameLabel->resizeToWidth(width() - 2 * (st::mediaPlayerPadding) - _timeLabel->width() - st::normalFont->spacew); + updateLabelPositions(); + + int skip = (st::mediaPlayerPlayback.seekSize.width() / 2); + int length = (width() - 2 * st::mediaPlayerPadding + st::mediaPlayerPlayback.seekSize.width()); + _playback->setGeometry(st::mediaPlayerPadding - skip, st::mediaPlayerPlaybackTop, length, 2 * st::mediaPlayerPlaybackPadding + st::mediaPlayerPlayback.width); + _repeatTrack->moveToRight(st::mediaPlayerPlayLeft, st::mediaPlayerPlayTop); - _volumeController->moveToRight(st::mediaPlayerVolumeRight, st::mediaPlayerPlayTop + (_playPause->height() - _volumeController->height()) / 2); + _volumeController->moveToRight(st::mediaPlayerVolumeRight, st::mediaPlayerVolumeTop); updatePlayPrevNextPositions(); } @@ -57,7 +139,145 @@ void CoverWidget::paintEvent(QPaintEvent *e) { } void CoverWidget::updatePlayPrevNextPositions() { - _playPause->moveToLeft(st::mediaPlayerPlayLeft, st::mediaPlayerPlayTop); + if (_previousTrack) { + auto left = st::mediaPlayerPlayLeft; + _previousTrack->moveToLeft(left, st::mediaPlayerPlayTop); left += _previousTrack->width() + st::mediaPlayerPlaySkip; + _playPause->moveToLeft(left, st::mediaPlayerPlayTop); left += _playPause->width() + st::mediaPlayerPlaySkip; + _nextTrack->moveToLeft(left, st::mediaPlayerPlayTop); + } else { + _playPause->moveToLeft(st::mediaPlayerPlayLeft, st::mediaPlayerPlayTop); + } +} + +void CoverWidget::updateLabelPositions() { + _nameLabel->moveToLeft(st::mediaPlayerPadding, st::mediaPlayerNameTop - st::mediaPlayerName.font->ascent); + _timeLabel->moveToRight(st::mediaPlayerPadding, st::mediaPlayerNameTop - st::mediaPlayerTime.font->ascent); +} + +void CoverWidget::updateRepeatTrackIcon() { + _repeatTrack->setIcon(instance()->repeatEnabled() ? nullptr : &st::mediaPlayerRepeatDisabledIcon); +} + +void CoverWidget::handleSongUpdate(const UpdatedEvent &e) { + auto &audioId = *e.audioId; + auto &playbackState = *e.playbackState; + if (!audioId || !audioId.audio()->song()) { + return; + } + + _playback->updateState(*e.playbackState); + + auto stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing); + auto showPause = !stopped && (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + if (exists() && instance()->isSeeking()) { + showPause = true; + } + if (_showPause != showPause) { + _showPause = showPause; + _playPause->setIcon(_showPause ? &st::mediaPlayerPauseIcon : nullptr); + } + + updateTimeText(audioId, playbackState); +} + +void CoverWidget::updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState) { + QString time; + qint64 position = 0, duration = 0, display = 0; + auto frequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + display = position = playbackState.position; + duration = playbackState.duration; + } else { + display = playbackState.duration ? playbackState.duration : (audioId.audio()->song()->duration * frequency); + } + + _lastDurationMs = (playbackState.duration * 1000LL) / frequency; + + if (duration || !audioId.audio()->loading()) { + display = display / frequency; + _time = formatDurationText(display); + } else { + auto loaded = audioId.audio()->loadOffset(); + auto loadProgress = snap(float64(loaded) / qMax(audioId.audio()->size, 1), 0., 1.); + _time = QString::number(qRound(loadProgress * 100)) + '%'; + } + if (_seekPositionMs < 0) { + updateTimeLabel(); + } +} + +void CoverWidget::updateTimeLabel() { + auto timeLabelWidth = _timeLabel->width(); + if (_seekPositionMs >= 0) { + auto playAlready = _seekPositionMs / 1000LL; + _timeLabel->setText(formatDurationText(playAlready)); + } else { + _timeLabel->setText(_time); + } + if (timeLabelWidth != _timeLabel->width()) { + _nameLabel->resizeToWidth(width() - 2 * (st::mediaPlayerPadding) - _timeLabel->width() - st::normalFont->spacew); + updateLabelPositions(); + } +} + +void CoverWidget::handleSongChange() { + auto ¤t = instance()->current(); + auto song = current.audio()->song(); + + TextWithEntities textWithEntities; + if (song->performer.isEmpty()) { + textWithEntities.text = song->title.isEmpty() ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title; + } else { + auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title); + textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title; + textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() }); + } + _nameLabel->setMarkedText(textWithEntities); + + handlePlaylistUpdate(); +} + +void CoverWidget::handlePlaylistUpdate() { + auto ¤t = instance()->current(); + auto &playlist = instance()->playlist(); + auto index = playlist.indexOf(current.contextId()); + if (!current || index < 0) { + destroyPrevNextButtons(); + } else { + createPrevNextButtons(); + auto previousEnabled = (index > 0); + auto nextEnabled = (index + 1 < playlist.size()); + _previousTrack->setIcon(previousEnabled ? nullptr : &st::mediaPlayerPreviousDisabledIcon); + _previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); + _nextTrack->setIcon(nextEnabled ? nullptr : &st::mediaPlayerNextDisabledIcon); + _nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); + } +} + +void CoverWidget::createPrevNextButtons() { + if (!_previousTrack) { + _previousTrack.create(this, st::mediaPlayerPreviousButton); + _nextTrack.create(this, st::mediaPlayerNextButton); + _previousTrack->setClickedCallback([this]() { + if (exists()) { + instance()->previous(); + } + }); + _nextTrack->setClickedCallback([this]() { + if (exists()) { + instance()->next(); + } + }); + updatePlayPrevNextPositions(); + } +} + +void CoverWidget::destroyPrevNextButtons() { + if (_previousTrack) { + _previousTrack.destroy(); + _nextTrack.destroy(); + updatePlayPrevNextPositions(); + } } } // namespace Player diff --git a/Telegram/SourceFiles/media/player/media_player_cover.h b/Telegram/SourceFiles/media/player/media_player_cover.h index f5ed19de36..1290b81485 100644 --- a/Telegram/SourceFiles/media/player/media_player_cover.h +++ b/Telegram/SourceFiles/media/player/media_player_cover.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +class AudioMsgId; +struct AudioPlaybackState; class FlatLabel; namespace Ui { class LabelSimple; @@ -27,12 +29,17 @@ class IconButton; } // namespace Ui namespace Media { +namespace Clip { +class Playback; +} // namespace Clip + namespace Player { class PlaybackWidget; class VolumeController; +struct UpdatedEvent; -class CoverWidget : public TWidget { +class CoverWidget : public TWidget, private base::Subscriber { public: CoverWidget(QWidget *parent); @@ -41,11 +48,30 @@ protected: void paintEvent(QPaintEvent *e) override; private: + void handleSeekProgress(float64 progress); + void handleSeekFinished(float64 progress); + void updatePlayPrevNextPositions(); + void updateLabelPositions(); + void updateRepeatTrackIcon(); + void createPrevNextButtons(); + void destroyPrevNextButtons(); + + void handleSongUpdate(const UpdatedEvent &e); + void handleSongChange(); + void handlePlaylistUpdate(); + + void updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState); + void updateTimeLabel(); + + bool _showPause = true; + int64 _seekPositionMs = -1; + int64 _lastDurationMs = 0; + QString _time; ChildWidget _nameLabel; ChildWidget _timeLabel; - ChildWidget _playback; + ChildWidget _playback; ChildWidget _previousTrack = { nullptr }; ChildWidget _playPause; ChildWidget _nextTrack = { nullptr }; diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp new file mode 100644 index 0000000000..f7c99fb23f --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -0,0 +1,249 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "media/player/media_player_instance.h" + +#include "media/media_audio.h" +#include "observer_peer.h" + +namespace Media { +namespace Player { +namespace { + +Instance *SingleInstance = nullptr; + +} // namespace + +void start() { + audioInit(); + if (audioPlayer()) { + SingleInstance = new Instance(); + } +} + +bool exists() { + return (audioPlayer() != nullptr); +} + +void finish() { + auto temp = createAndSwap(SingleInstance); + delete temp; + + audioFinish(); +} + +Instance::Instance() { + subscribe(audioPlayer(), [this](const AudioMsgId &audioId) { + if (audioId.type() == AudioMsgId::Type::Song) { + handleSongUpdate(audioId); + } + }); + Notify::registerPeerObserver(Notify::PeerUpdate::Flag::SharedMediaChanged, this, &Instance::notifyPeerUpdated); +} + +void Instance::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (!_history) { + return; + } + if (!(update.mediaTypesMask & (1 << OverviewMusicFiles))) { + return; + } + if (update.peer != _history->peer && (!_migrated || update.peer != _migrated->peer)) { + return; + } + + rebuildPlaylist(); +} + +void Instance::handleSongUpdate(const AudioMsgId &audioId) { + emitUpdate([&audioId](const AudioMsgId &playing) { + return (audioId == playing); + }); +} + +void Instance::setCurrent(const AudioMsgId &audioId) { + if (_current != audioId) { + _current = audioId; + auto history = _history, migrated = _migrated; + auto item = _current ? App::histItemById(_current.contextId()) : nullptr; + if (item) { + _history = item->history()->peer->migrateTo() ? App::history(item->history()->peer->migrateTo()) : item->history(); + _migrated = _history->peer->migrateFrom() ? App::history(_history->peer->migrateFrom()) : nullptr; + } else { + _history = _migrated = nullptr; + } + _songChangedNotifier.notify(true); + if (_history != history || _migrated != migrated) { + rebuildPlaylist(); + } + } +} + +void Instance::rebuildPlaylist() { + _playlist.clear(); + if (_history && _history->loadedAtBottom()) { + auto &historyOverview = _history->overview[OverviewMusicFiles]; + if (_migrated && _migrated->loadedAtBottom() && _history->loadedAtTop()) { + auto &migratedOverview = _migrated->overview[OverviewMusicFiles]; + _playlist.reserve(migratedOverview.size() + historyOverview.size()); + for_const (auto msgId, migratedOverview) { + _playlist.push_back(FullMsgId(_migrated->channelId(), msgId)); + } + } else { + _playlist.reserve(historyOverview.size()); + } + for_const (auto msgId, historyOverview) { + _playlist.push_back(FullMsgId(_history->channelId(), msgId)); + } + } + _playlistChangedNotifier.notify(true); +} + +void Instance::moveInPlaylist(int delta) { + auto index = _playlist.indexOf(_current.contextId()); + auto newIndex = index + delta; + if (!_current || index < 0 || newIndex < 0 || newIndex >= _playlist.size()) { + rebuildPlaylist(); + return; + } + + auto msgId = _playlist[newIndex]; + if (auto item = App::histItemById(msgId)) { + if (auto media = item->getMedia()) { + if (auto document = media->getDocument()) { + if (auto song = document->song()) { + play(AudioMsgId(document, msgId)); + } + } + } + } +} + +Instance *instance() { + t_assert(SingleInstance != nullptr); + return SingleInstance; +} + +void Instance::play() { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing) { + if (playbackState.state & AudioPlayerStoppedMask) { + audioPlayer()->play(playing); + } else { + if (playbackState.state == AudioPlayerPausing || playbackState.state == AudioPlayerPaused || playbackState.state == AudioPlayerPausedAtEnd) { + audioPlayer()->pauseresume(AudioMsgId::Type::Song); + } + } + } else if (_current) { + audioPlayer()->play(_current); + } +} + +void Instance::play(const AudioMsgId &audioId) { + if (!audioId || !audioId.audio()->song()) { + return; + } + audioPlayer()->play(audioId); + setCurrent(audioId); + if (audioId.audio()->loading()) { + documentLoadProgress(audioId.audio()); + } +} + +void Instance::pause() { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing) { + if (!(playbackState.state & AudioPlayerStoppedMask)) { + if (playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerFinishing) { + audioPlayer()->pauseresume(AudioMsgId::Type::Song); + } + } + } +} + +void Instance::stop() { + audioPlayer()->stop(AudioMsgId::Type::Song); +} + +void Instance::playPause() { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing) { + if (playbackState.state & AudioPlayerStoppedMask) { + audioPlayer()->play(playing); + } else { + audioPlayer()->pauseresume(AudioMsgId::Type::Song); + } + } else if (_current) { + audioPlayer()->play(_current); + } +} + +void Instance::next() { + moveInPlaylist(1); +} + +void Instance::previous() { + moveInPlaylist(-1); +} + +void Instance::startSeeking() { + _seeking = _current; + pause(); + emitUpdate([](const AudioMsgId &playing) { return true; }); +} + +void Instance::stopSeeking() { + _seeking = AudioMsgId(); + emitUpdate([](const AudioMsgId &playing) { return true; }); +} + +void Instance::documentLoadProgress(DocumentData *document) { + emitUpdate([document](const AudioMsgId &audioId) { + return (audioId.audio() == document); + }); +} + +template +void Instance::emitUpdate(CheckCallback check) { + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (!playing || !check(playing)) { + return; + } + + setCurrent(playing); + _updatedNotifier.notify(UpdatedEvent(&playing, &playbackState), true); + + if (_isPlaying && playbackState.state == AudioPlayerStoppedAtEnd) { + if (_repeatEnabled) { + audioPlayer()->play(_current); + } else { + next(); + } + } + _isPlaying = !(playbackState.state & AudioPlayerStoppedMask); +} + +} // namespace Player +} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h new file mode 100644 index 0000000000..beecddbe89 --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -0,0 +1,134 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Notify { +struct PeerUpdate; +} // namespace Notify +struct AudioPlaybackState; +class AudioMsgId; + +namespace Media { +namespace Player { + +void start(); +void finish(); + +// We use this method instead of checking for instance() != nullptr +// because audioPlayer() can be destroyed at any time by an +// error in audio playback, so we check it each time. +bool exists(); + +class Instance; +Instance *instance(); + +class Widget; +struct CreatedEvent { + explicit CreatedEvent(Widget *widget) : widget(widget) { + } + Widget *widget; +}; +struct UpdatedEvent { + UpdatedEvent(const AudioMsgId *audioId, const AudioPlaybackState *playbackState) : audioId(audioId), playbackState(playbackState) { + } + const AudioMsgId *audioId; + const AudioPlaybackState *playbackState; +}; + +class Instance : private base::Subscriber, public Notify::Observer { +public: + void play(); + void pause(); + void stop(); + void playPause(); + void next(); + void previous(); + + void play(const AudioMsgId &audioId); + const AudioMsgId ¤t() const { + return _current; + } + + bool repeatEnabled() const { + return _repeatEnabled; + } + void toggleRepeat() { + _repeatEnabled = !_repeatEnabled; + } + + bool isSeeking() const { + return (_seeking == _current); + } + void startSeeking(); + void stopSeeking(); + + const QList &playlist() const { + return _playlist; + } + + base::Observable &createdNotifier() { + return _createdNotifier; + } + base::Observable &updatedNotifier() { + return _updatedNotifier; + } + base::Observable &playlistChangedNotifier() { + return _playlistChangedNotifier; + } + base::Observable &songChangedNotifier() { + return _songChangedNotifier; + } + + void documentLoadProgress(DocumentData *document); + +private: + Instance(); + friend void start(); + + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + void handleSongUpdate(const AudioMsgId &audioId); + + void setCurrent(const AudioMsgId &audioId); + void rebuildPlaylist(); + void moveInPlaylist(int delta); + + template + void emitUpdate(CheckCallback check); + + AudioMsgId _current, _seeking; + History *_history = nullptr; + History *_migrated = nullptr; + + bool _repeatEnabled = false; + + QList _playlist; + bool _isPlaying = false; + + base::Observable _createdNotifier; + base::Observable _updatedNotifier; + base::Observable _playlistChangedNotifier; + base::Observable _songChangedNotifier; + +}; + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_playback.cpp b/Telegram/SourceFiles/media/player/media_player_playback.cpp deleted file mode 100644 index 21839405f0..0000000000 --- a/Telegram/SourceFiles/media/player/media_player_playback.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "media/player/media_player_playback.h" - -#include "styles/style_media_player.h" - -namespace Media { -namespace Player { - -PlaybackWidget::PlaybackWidget(QWidget *parent) : TWidget(parent) { -} - -void PlaybackWidget::paintEvent(QPaintEvent *e) { - Painter p(this); - - p.fillRect(0, st::mediaPlayerPlaybackPadding, width(), st::mediaPlayerPlaybackLine, st::mediaPlayerPlaybackBg); -} - -} // namespace Player -} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_playback.h b/Telegram/SourceFiles/media/player/media_player_playback.h deleted file mode 100644 index 83596c663b..0000000000 --- a/Telegram/SourceFiles/media/player/media_player_playback.h +++ /dev/null @@ -1,38 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -namespace Media { -namespace Player { - -class PlaybackWidget : public TWidget { -public: - PlaybackWidget(QWidget *parent); - -protected: - void paintEvent(QPaintEvent *e) override; - -private: - -}; - -} // namespace Clip -} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp index 41b8087a04..4217bb06d3 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp @@ -21,31 +21,69 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "media/player/media_player_volume_controller.h" +#include "media/media_audio.h" +#include "ui/buttons/icon_button.h" +#include "ui/widgets/media_slider.h" #include "styles/style_media_player.h" +#include "styles/style_widgets.h" namespace Media { namespace Player { -VolumeController::VolumeController(QWidget *parent) : TWidget(parent) { - resize(st::mediaPlayerVolumeWidth, 2 * st::mediaPlayerPlaybackPadding + st::mediaPlayerPlaybackLine); +VolumeController::VolumeController(QWidget *parent) : TWidget(parent) +, _toggle(this, st::mediaPlayerVolumeToggle) +, _slider(this, st::mediaPlayerPlayback) { + _toggle->setClickedCallback([this]() { + setVolume(_slider->value() ? 0. : _rememberedVolume); + }); + _slider->setChangeProgressCallback([this](float64 volume) { + applyVolumeChange(volume); + }); + _slider->setChangeFinishedCallback([this](float64 volume) { + if (volume > 0) { + _rememberedVolume = volume; + } + applyVolumeChange(volume); + }); + + auto animated = false; + setVolume(Global::SongVolume(), animated); + + resize(st::mediaPlayerVolumeWidth, 2 * st::mediaPlayerPlaybackPadding + st::mediaPlayerPlayback.width); } -void VolumeController::paintEvent(QPaintEvent *e) { - Painter p(this); - - st::mediaPlayerVolumeIcon0.paint(p, QPoint(0, (height() - st::mediaPlayerVolumeIcon0.height()) / 2), width()); - - auto left = rtl() ? 0 : width() - st::mediaPlayerVolumeLength; - p.fillRect(left, st::mediaPlayerPlaybackPadding, st::mediaPlayerVolumeLength, st::mediaPlayerPlaybackLine, st::mediaPlayerPlaybackBg); +void VolumeController::resizeEvent(QResizeEvent *e) { + _slider->resize(st::mediaPlayerVolumeLength, height()); + _slider->moveToRight(0, 0); + _toggle->moveToLeft(0, (height() - _toggle->height()) / 2); } -void VolumeController::mousePressEvent(QMouseEvent *e) { +void VolumeController::setVolume(float64 volume, bool animated) { + _slider->setValue(volume, animated); + if (volume > 0) { + _rememberedVolume = volume; + } + applyVolumeChange(volume); } -void VolumeController::mouseMoveEvent(QMouseEvent *e) { -} - -void VolumeController::mouseReleaseEvent(QMouseEvent *e) { +void VolumeController::applyVolumeChange(float64 volume) { + if (volume > 0) { + if (volume < 1 / 3.) { + _toggle->setIcon(&st::mediaPlayerVolumeIcon1); + } else if (volume < 2 / 3.) { + _toggle->setIcon(&st::mediaPlayerVolumeIcon2); + } else { + _toggle->setIcon(&st::mediaPlayerVolumeIcon3); + } + } else { + _toggle->setIcon(nullptr); + } + if (volume != Global::SongVolume()) { + Global::SetSongVolume(volume); + if (auto player = audioPlayer()) { + emit player->songVolumeChanged(); + } + } } } // namespace Player diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.h b/Telegram/SourceFiles/media/player/media_player_volume_controller.h index 8c4c904120..e26de2caed 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.h +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.h @@ -20,6 +20,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +namespace Ui { +class IconButton; +class MediaSlider; +} // namespace Ui + namespace Media { namespace Player { @@ -28,12 +33,15 @@ public: VolumeController(QWidget *parent); protected: - void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; private: + void setVolume(float64 volume, bool animated = true); + void applyVolumeChange(float64 volume); + + ChildWidget _toggle; + ChildWidget _slider; + float64 _rememberedVolume = Global::kDefaultVolume; }; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 7e58821f1b..bc94160f32 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -38,7 +38,9 @@ Widget::Widget(QWidget *parent) : TWidget(parent) _showTimer.setSingleShot(true); connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShowStart())); - connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + if (_scroll) { + connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + } if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged())); diff --git a/Telegram/SourceFiles/media/player/media_player_widget.h b/Telegram/SourceFiles/media/player/media_player_widget.h index 44c01ca914..fa63bd41b8 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.h +++ b/Telegram/SourceFiles/media/player/media_player_widget.h @@ -30,7 +30,7 @@ namespace Player { class CoverWidget; class ListWidget; -class Widget : public TWidget { +class Widget : public TWidget, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index 796aee8cc8..91c53cd9a1 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -34,7 +34,7 @@ namespace Clip { Controller::Controller(QWidget *parent) : TWidget(parent) , _playPauseResume(this, st::mediaviewPlayButton) -, _playback(this) +, _playback(this, st::mediaviewPlayback) , _volumeController(this) , _fullScreenToggle(this, st::mediaviewFullScreenButton) , _playedAlready(this, st::mediaviewPlayProgressLabel) @@ -48,12 +48,17 @@ Controller::Controller(QWidget *parent) : TWidget(parent) connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed())); connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); - connect(_playback, SIGNAL(seekProgress(float64)), this, SLOT(onSeekProgress(float64))); - connect(_playback, SIGNAL(seekFinished(float64)), this, SLOT(onSeekFinished(float64))); connect(_volumeController, SIGNAL(volumeChanged(float64)), this, SIGNAL(volumeChanged(float64))); + + _playback->setChangeProgressCallback([this](float64 value) { + handleSeekProgress(value); + }); + _playback->setChangeFinishedCallback([this](float64 value) { + handleSeekFinished(value); + }); } -void Controller::onSeekProgress(float64 progress) { +void Controller::handleSeekProgress(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); @@ -64,7 +69,7 @@ void Controller::onSeekProgress(float64 progress) { } } -void Controller::onSeekFinished(float64 progress) { +void Controller::handleSeekFinished(float64 progress) { if (!_lastDurationMs) return; auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); @@ -99,9 +104,9 @@ void Controller::fadeUpdated(float64 opacity) { _playback->setFadeOpacity(opacity); } -void Controller::updatePlayback(const AudioPlaybackState &playbackState, bool reset) { +void Controller::updatePlayback(const AudioPlaybackState &playbackState) { updatePlayPauseResumeState(playbackState); - _playback->updateState(playbackState, reset); + _playback->updateState(playbackState); updateTimeTexts(playbackState); } @@ -189,11 +194,13 @@ void Controller::resizeEvent(QResizeEvent *e) { _fullScreenToggle->moveToRight(st::mediaviewFullScreenLeft, fullScreenTop); _volumeController->moveToRight(st::mediaviewFullScreenLeft + _fullScreenToggle->width() + st::mediaviewVolumeLeft, (height() - _volumeController->height()) / 2); - _playback->resize(width() - st::mediaviewPlayPauseLeft - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - st::mediaviewVolumeLeft - _fullScreenToggle->width() - st::mediaviewFullScreenLeft, st::mediaviewSeekSize.height()); + + int playbackWidth = width() - st::mediaviewPlayPauseLeft - _playPauseResume->width() - playTop - fullScreenTop - _volumeController->width() - st::mediaviewVolumeLeft - _fullScreenToggle->width() - st::mediaviewFullScreenLeft; + _playback->resize(playbackWidth, st::mediaviewPlayback.seekSize.height()); _playback->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlaybackTop); _playedAlready->moveToLeft(st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop, st::mediaviewPlayProgressTop); - _toPlayLeft->moveToRight(width() - (st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop) - _playback->width(), st::mediaviewPlayProgressTop); + _toPlayLeft->moveToRight(width() - (st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop) - playbackWidth, st::mediaviewPlayProgressTop); } void Controller::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.h b/Telegram/SourceFiles/media/view/media_clip_controller.h index 4d27737faf..1fb589800d 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.h +++ b/Telegram/SourceFiles/media/view/media_clip_controller.h @@ -43,7 +43,7 @@ public: void showAnimated(); void hideAnimated(); - void updatePlayback(const AudioPlaybackState &playbackState, bool reset); + void updatePlayback(const AudioPlaybackState &playbackState); void setInFullScreen(bool inFullScreen); void grabStart() override; @@ -60,16 +60,15 @@ signals: void toFullScreenPressed(); void fromFullScreenPressed(); -private slots: - void onSeekProgress(float64 progress); - void onSeekFinished(float64 progress); - protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; private: + void handleSeekProgress(float64 progress); + void handleSeekFinished(float64 progress); + template void startFading(Callback start); void fadeFinished(); diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index 6dfbaed158..b3732dc875 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -22,17 +22,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/view/media_clip_playback.h" #include "styles/style_mediaview.h" +#include "ui/widgets/media_slider.h" #include "media/media_audio.h" namespace Media { namespace Clip { -Playback::Playback(QWidget *parent) : TWidget(parent) -, _a_progress(animation(this, &Playback::step_progress)) { - setCursor(style::cur_pointer); +Playback::Playback(QWidget *parent, const style::MediaSlider &st) : _slider(new Ui::MediaSlider(parent, st)) { } -void Playback::updateState(const AudioPlaybackState &playbackState, bool reset) { +void Playback::updateState(const AudioPlaybackState &playbackState) { qint64 position = 0, duration = playbackState.duration; _playing = !(playbackState.state & AudioPlayerStoppedMask); @@ -51,112 +50,11 @@ void Playback::updateState(const AudioPlaybackState &playbackState, bool reset) progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; } if (duration != _duration || position != _position) { - if (duration && _duration && !reset) { - a_progress.start(progress); - _a_progress.start(); - } else { - a_progress = anim::fvalue(progress, progress); - _a_progress.stop(); - } + auto animated = (duration && _duration && progress > _slider->value()); + _slider->setValue(progress, animated); _position = position; _duration = duration; } - update(); -} - -void Playback::setFadeOpacity(float64 opacity) { - _fadeOpacity = opacity; - update(); -} - -void Playback::step_progress(float64 ms, bool timer) { - float64 dt = ms / (2 * AudioVoiceMsgUpdateView); - if (_duration && dt >= 1) { - _a_progress.stop(); - a_progress.finish(); - } else { - a_progress.update(qMin(dt, 1.), anim::linear); - } - if (timer) update(); -} - -void Playback::paintEvent(QPaintEvent *e) { - Painter p(this); - - int radius = st::mediaviewPlaybackWidth / 2; - p.setOpacity(_fadeOpacity); - p.setPen(Qt::NoPen); - p.setRenderHint(QPainter::HighQualityAntialiasing); - - auto ms = getms(); - _a_progress.step(ms); - auto over = _a_over.current(ms, _over ? 1. : 0.); - int skip = (st::mediaviewSeekSize.width() / 2); - int length = (width() - st::mediaviewSeekSize.width()); - float64 prg = _mouseDown ? _downProgress : a_progress.current(); - int32 from = skip, mid = qRound(from + prg * length), end = from + length; - if (mid > from) { - p.setClipRect(0, 0, mid, height()); - p.setOpacity(_fadeOpacity * (over * st::mediaviewActiveOpacity + (1. - over) * st::mediaviewInactiveOpacity)); - p.setBrush(st::mediaviewPlaybackActive); - p.drawRoundedRect(0, (height() - st::mediaviewPlaybackWidth) / 2, mid + radius, st::mediaviewPlaybackWidth, radius, radius); - } - if (end > mid) { - p.setClipRect(mid, 0, width() - mid, height()); - p.setOpacity(_fadeOpacity); - p.setBrush(st::mediaviewPlaybackInactive); - p.drawRoundedRect(mid - radius, (height() - st::mediaviewPlaybackWidth) / 2, width() - (mid - radius), st::mediaviewPlaybackWidth, radius, radius); - } - if (over > 0) { - int x = mid - skip; - p.setClipRect(rect()); - p.setOpacity(_fadeOpacity * st::mediaviewActiveOpacity); - auto seekButton = QRect(x, (height() - st::mediaviewSeekSize.height()) / 2, st::mediaviewSeekSize.width(), st::mediaviewSeekSize.height()); - int remove = ((1. - over) * st::mediaviewSeekSize.width()) / 2.; - if (remove * 2 < st::mediaviewSeekSize.width()) { - p.setBrush(st::mediaviewPlaybackActive); - p.drawEllipse(seekButton.marginsRemoved(QMargins(remove, remove, remove, remove))); - } - } -} - -void Playback::mouseMoveEvent(QMouseEvent *e) { - if (_mouseDown) { - _downProgress = snap(e->pos().x() / float64(width()), 0., 1.); - emit seekProgress(_downProgress); - update(); - } -} - -void Playback::mousePressEvent(QMouseEvent *e) { - _mouseDown = true; - _downProgress = snap(e->pos().x() / float64(width()), 0., 1.); - update(); - emit seekProgress(_downProgress); // This may destroy Playback. -} - -void Playback::mouseReleaseEvent(QMouseEvent *e) { - if (_mouseDown) { - _mouseDown = false; - emit seekFinished(_downProgress); - update(); - } -} - -void Playback::enterEvent(QEvent *e) { - setOver(true); -} - -void Playback::leaveEvent(QEvent *e) { - setOver(false); -} - -void Playback::setOver(bool over) { - if (_over == over) return; - - _over = over; - auto from = _over ? 0. : 1., to = _over ? 1. : 0.; - START_ANIMATION(_a_over, func(this, &Playback::updateCallback), from, to, st::mediaviewOverDuration, anim::linear); } } // namespace Clip diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.h b/Telegram/SourceFiles/media/view/media_clip_playback.h index 190358d782..2538a80bea 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.h +++ b/Telegram/SourceFiles/media/view/media_clip_playback.h @@ -20,51 +20,56 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "ui/widgets/media_slider.h" + struct AudioPlaybackState; +namespace style { +struct MediaSlider; +} // namespace style +namespace Ui { +class MediaSlider; +} // namespace Ui namespace Media { namespace Clip { -class Playback : public TWidget { - Q_OBJECT - +class Playback { public: - Playback(QWidget *parent); + Playback(QWidget *parent, const style::MediaSlider &st); - void updateState(const AudioPlaybackState &playbackState, bool reset); - void setFadeOpacity(float64 opacity); + void updateState(const AudioPlaybackState &playbackState); -signals: - void seekProgress(float64 progress); - void seekFinished(float64 progress); - -protected: - void paintEvent(QPaintEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void enterEvent(QEvent *e) override; - void leaveEvent(QEvent *e) override; + void setFadeOpacity(float64 opacity) { + _slider->setFadeOpacity(opacity); + } + void setChangeProgressCallback(Ui::MediaSlider::Callback &&callback) { + _slider->setChangeProgressCallback(std_::move(callback)); + } + void setChangeFinishedCallback(Ui::MediaSlider::Callback &&callback) { + _slider->setChangeFinishedCallback(std_::move(callback)); + } + void setGeometry(int x, int y, int w, int h) { + _slider->setGeometry(x, y, w, h); + } + void hide() { + _slider->hide(); + } + void show() { + _slider->show(); + } + void moveToLeft(int x, int y) { + _slider->moveToLeft(x, y); + } + void resize(int w, int h) { + _slider->resize(w, h); + } private: - void step_progress(float64 ms, bool timer); - void updateCallback() { - update(); - } - void setOver(bool over); - - bool _over = false; - FloatAnimation _a_over; + Ui::MediaSlider *_slider; int64 _position = 0; int64 _duration = 0; - anim::fvalue a_progress = { 0., 0. }; - Animation _a_progress; - bool _mouseDown = false; - float64 _downProgress = 0.; - - float64 _fadeOpacity = 1.; bool _playing = false; }; diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index ef83075fce..0c1abb341d 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -26,6 +26,18 @@ mediaviewActiveOpacity: 1.; mediaviewInactiveOpacity: 0.78; mediaviewOverDuration: 150; +mediaviewPlaybackActive: #ffffff; +mediaviewPlaybackInactive: #474747; +mediaviewPlayback: MediaSlider { + width: 3px; + activeFg: mediaviewPlaybackActive; + inactiveFg: mediaviewPlaybackInactive; + activeOpacity: mediaviewActiveOpacity; + inactiveOpacity: mediaviewInactiveOpacity; + seekSize: size(11px, 11px); + duration: mediaviewOverDuration; +} + mediaviewControllerSize: size(600px, 50px); mediaviewPlayProgressLabel: LabelSimple(defaultLabelSimple) { font: semiboldFont; @@ -65,11 +77,7 @@ mediaviewFullScreenOutIcon: icon { { "media_fullscreen_from", #ffffff, point(0px, 0px) }, }; -mediaviewPlaybackActive: #ffffff; -mediaviewPlaybackInactive: #474747; -mediaviewPlaybackWidth: 3px; mediaviewPlaybackTop: 28px; -mediaviewSeekSize: size(11px, 11px); mediaviewVolumeSize: size(44px, 20px); mediaviewVolumeIcon: icon { diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 77eb491d78..833019e502 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1450,7 +1450,7 @@ void MediaView::restartVideoAtSeekPosition(int64 positionMs) { state.position = _videoPositionMs; state.duration = _videoDurationMs; state.frequency = _videoFrequencyMs; - updateVideoPlaybackState(state, true); + updateVideoPlaybackState(state); } void MediaView::onVideoSeekProgress(int64 positionMs) { @@ -1497,12 +1497,12 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) { } } -void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state, bool reset) { +void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state) { if (state.frequency) { if (state.state & AudioPlayerStoppedMask) { _videoStopped = true; } - _clipController->updatePlayback(state, reset); + _clipController->updatePlayback(state); } else { // Audio has stopped already. _videoIsSilent = true; updateSilentVideoPlaybackState(); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index ff1e85b6ad..4f8440539a 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -135,7 +135,7 @@ private: void updateCursor(); void setZoomLevel(int newZoom); - void updateVideoPlaybackState(const AudioPlaybackState &state, bool reset = false); + void updateVideoPlaybackState(const AudioPlaybackState &state); void updateSilentVideoPlaybackState(); void restartVideoAtSeekPosition(int64 positionMs); diff --git a/Telegram/SourceFiles/mtproto/generate.py b/Telegram/SourceFiles/mtproto/generate.py index aaa0ecc94c..e55643944c 100644 --- a/Telegram/SourceFiles/mtproto/generate.py +++ b/Telegram/SourceFiles/mtproto/generate.py @@ -22,7 +22,7 @@ import glob import re import binascii -# define some checked flag convertions +# define some checked flag conversions # the key flag type should be a subset of the value flag type # with exact the same names, then the key flag can be implicitly # casted to the value flag type diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 752546ac0e..87214662b8 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -32,6 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "playerwidget.h" #include "media/media_audio.h" +#include "media/player/media_player_instance.h" #include "localstorage.h" namespace Overview { @@ -869,7 +870,7 @@ bool Document::updateStatusText() const { realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); } - if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { + if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && Media::Player::exists() && Media::Player::instance()->isSeeking()) { showPause = true; } } diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index efbf98167c..786ffd2fd2 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1922,7 +1922,7 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); _scrollTimer.setSingleShot(false); - connect(App::main()->player(), SIGNAL(playerSongChanged(const FullMsgId&)), this, SLOT(onPlayerSongChanged(const FullMsgId&))); +// connect(App::main()->player(), SIGNAL(playerSongChanged(const FullMsgId&)), this, SLOT(onPlayerSongChanged(const FullMsgId&))); switchType(type); } @@ -2262,14 +2262,14 @@ void OverviewWidget::onScrollTimer() { _scroll.scrollToY(_scroll.scrollTop() + d); } -void OverviewWidget::onPlayerSongChanged(const FullMsgId &msgId) { - if (type() == OverviewMusicFiles) { +//void OverviewWidget::onPlayerSongChanged(const FullMsgId &msgId) { +// if (type() == OverviewMusicFiles) { // int32 top = _inner.itemTop(msgId); // if (top > 0) { // _scroll.scrollToY(snap(top - int(_scroll.height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll.scrollTopMax())); // } - } -} +// } +//} void OverviewWidget::checkSelectingScroll(QPoint point) { if (point.y() < _scroll.scrollTop()) { diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 016d14ad22..adfb28ee26 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -334,7 +334,7 @@ public slots: void onScroll(); void onScrollTimer(); - void onPlayerSongChanged(const FullMsgId &msgId); +// void onPlayerSongChanged(const FullMsgId &msgId); void onForwardSelected(); void onDeleteSelected(); diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index af71285ac2..fe55e98d40 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -469,7 +469,7 @@ void PlayerWidget::playPressed() { } } else { audioPlayer()->play(_song); - if (App::main()) App::main()->audioPlayProgress(_song); + audioPlayer()->notify(_song); } } @@ -494,15 +494,15 @@ void PlayerWidget::playPausePressed() { audioPlayer()->pauseresume(AudioMsgId::Type::Song); } else { audioPlayer()->play(_song); - if (App::main()) App::main()->audioPlayProgress(_song); + audioPlayer()->notify(_song); } } void PlayerWidget::prevPressed() { if (isHidden()) return; - History *history = _msgmigrated ? _migrated : _history; - const History::MediaOverview *o = history ? &history->overview[OverviewMusicFiles] : 0; + auto history = _msgmigrated ? _migrated : _history; + auto o = history ? &history->overview[OverviewMusicFiles] : nullptr; if (audioPlayer() && o && _index > 0 && _index <= o->size() && !o->isEmpty()) { startPlay(FullMsgId(history->channelId(), o->at(_index - 1))); } else if (!_index && _history && _migrated && !_msgmigrated) { @@ -516,8 +516,8 @@ void PlayerWidget::prevPressed() { void PlayerWidget::nextPressed() { if (isHidden()) return; - History *history = _msgmigrated ? _migrated : _history; - const History::MediaOverview *o = history ? &history->overview[OverviewMusicFiles] : 0; + auto history = _msgmigrated ? _migrated : _history; + auto o = history ? &history->overview[OverviewMusicFiles] : nullptr; if (audioPlayer() && o && _index >= 0 && _index < o->size() - 1) { startPlay(FullMsgId(history->channelId(), o->at(_index + 1))); } else if (o && (_index == o->size() - 1) && _msgmigrated && _history->overviewLoaded(OverviewMusicFiles)) { @@ -680,11 +680,11 @@ void PlayerWidget::updateState(AudioMsgId playing, AudioPlaybackState playbackSt if (wasPlaying && playbackState.state == AudioPlayerStoppedAtEnd) { if (_repeat) { if (_song.audio()) { - audioPlayer()->play(_song, OverviewMusicFiles); - updateState(); +// audioPlayer()->play(_song); +// updateState(); } } else { - nextPressed(); +// nextPressed(); } } diff --git a/Telegram/SourceFiles/pspecific_mac_p.mm b/Telegram/SourceFiles/pspecific_mac_p.mm index c6bb35f30d..2ba2380477 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.mm +++ b/Telegram/SourceFiles/pspecific_mac_p.mm @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "playerwidget.h" #include "localstorage.h" +#include "media/player/media_player_instance.h" #include "lang.h" @@ -336,21 +337,27 @@ bool PsMacWindowPrivate::filterNativeEvent(void *event) { switch (keyCode) { case NX_KEYTYPE_PLAY: if (keyState == 0) { // Play pressed and released - if (App::main()) App::main()->player()->playPausePressed(); + if (Media::Player::exists()) { + Media::Player::instance()->playPause(); + } return true; } break; case NX_KEYTYPE_FAST: if (keyState == 0) { // Next pressed and released - if (App::main()) App::main()->player()->nextPressed(); + if (Media::Player::exists()) { + Media::Player::instance()->next(); + } return true; } break; case NX_KEYTYPE_REWIND: if (keyState == 0) { // Previous pressed and released - if (App::main()) App::main()->player()->prevPressed(); + if (Media::Player::exists()) { + Media::Player::instance()->previous(); + } return true; } break; diff --git a/Telegram/SourceFiles/settings/settings_chat_settings_widget.h b/Telegram/SourceFiles/settings/settings_chat_settings_widget.h index 7b4a1fc9d5..f04ac7f530 100644 --- a/Telegram/SourceFiles/settings/settings_chat_settings_widget.h +++ b/Telegram/SourceFiles/settings/settings_chat_settings_widget.h @@ -51,7 +51,7 @@ private: }; -class DownloadPathState : public TWidget, public base::Subscriber { +class DownloadPathState : public TWidget, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.h b/Telegram/SourceFiles/settings/settings_privacy_widget.h index eec2ff87d7..54d7ca8bb1 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_widget.h +++ b/Telegram/SourceFiles/settings/settings_privacy_widget.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Settings { -class LocalPasscodeState : public TWidget, public base::Subscriber { +class LocalPasscodeState : public TWidget, private base::Subscriber { Q_OBJECT public: diff --git a/Telegram/SourceFiles/shortcuts.cpp b/Telegram/SourceFiles/shortcuts.cpp index 23de564016..322873a872 100644 --- a/Telegram/SourceFiles/shortcuts.cpp +++ b/Telegram/SourceFiles/shortcuts.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "passcodewidget.h" #include "mainwidget.h" -#include "playerwidget.h" +#include "media/player/media_player_instance.h" namespace ShortcutCommands { @@ -77,61 +77,49 @@ bool quit_telegram() { //} bool media_play() { - if (auto m = App::main()) { - if (!m->player()->isHidden()) { - m->player()->playPressed(); - return true; - } + if (Media::Player::exists()) { + Media::Player::instance()->play(); + return true; } return false; } bool media_pause() { - if (auto m = App::main()) { - if (!m->player()->isHidden()) { - m->player()->pausePressed(); - return true; - } + if (Media::Player::exists()) { + Media::Player::instance()->pause(); + return true; } return false; } bool media_playpause() { - if (auto m = App::main()) { - if (!m->player()->isHidden()) { - m->player()->playPausePressed(); - return true; - } + if (Media::Player::exists()) { + Media::Player::instance()->playPause(); + return true; } return false; } bool media_stop() { - if (auto m = App::main()) { - if (!m->player()->isHidden()) { - m->player()->stopPressed(); - return true; - } + if (Media::Player::exists()) { + Media::Player::instance()->stop(); + return true; } return false; } bool media_previous() { - if (auto m = App::main()) { - if (!m->player()->isHidden()) { - m->player()->prevPressed(); - return true; - } + if (Media::Player::exists()) { + Media::Player::instance()->previous(); + return true; } return false; } bool media_next() { - if (auto m = App::main()) { - if (!m->player()->isHidden()) { - m->player()->nextPressed(); - return true; - } + if (Media::Player::exists()) { + Media::Player::instance()->next(); + return true; } return false; } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 82a9867529..4b24dee8dc 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -774,20 +774,20 @@ void PhotoOpenClickHandler::onClickImpl() const { } void PhotoSaveClickHandler::onClickImpl() const { - PhotoData *data = photo(); + auto data = photo(); if (!data->date) return; data->download(); } void PhotoCancelClickHandler::onClickImpl() const { - PhotoData *data = photo(); + auto data = photo(); if (!data->date) return; if (data->uploading()) { - if (HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0)) { - if (HistoryMessage *msg = item->toHistoryMessage()) { - if (msg->getMedia() && msg->getMedia()->type() == MediaTypePhoto && static_cast(msg->getMedia())->photo() == data) { + if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) { + if (auto media = item->getMedia()) { + if (media->type() == MediaTypePhoto && static_cast(media)->photo() == data) { App::contextItem(item); App::main()->deleteLayer(-2); } @@ -975,8 +975,8 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } else { AudioMsgId audio(data, msgId); audioPlayer()->play(audio); + audioPlayer()->notify(audio); if (App::main()) { - App::main()->audioPlayProgress(audio); App::main()->mediaMarkRead(data); } } @@ -988,7 +988,7 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } else { AudioMsgId song(data, msgId); audioPlayer()->play(song); - if (App::main()) App::main()->audioPlayProgress(song); + audioPlayer()->notify(song); } } else if (playVideo) { if (!data->data().isEmpty()) { @@ -1084,13 +1084,13 @@ void DocumentSaveClickHandler::onClickImpl() const { } void DocumentCancelClickHandler::onClickImpl() const { - DocumentData *data = document(); + auto data = document(); if (!data->date) return; if (data->uploading()) { - if (HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0)) { - if (HistoryMessage *msg = item->toHistoryMessage()) { - if (msg->getMedia() && msg->getMedia()->getDocument() == data) { + if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) { + if (auto media = item->getMedia()) { + if (media->getDocument() == data) { App::contextItem(item); App::main()->deleteLayer(-2); } @@ -1270,7 +1270,7 @@ void DocumentData::performActionOnLoad() { auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice); if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { audioPlayer()->pauseresume(AudioMsgId::Type::Voice); - } else { + } else if (playbackState.state & AudioPlayerStoppedMask) { audioPlayer()->play(AudioMsgId(this, _actionOnLoadMsgId)); if (App::main()) App::main()->mediaMarkRead(this); } @@ -1281,10 +1281,10 @@ void DocumentData::performActionOnLoad() { auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { audioPlayer()->pauseresume(AudioMsgId::Type::Song); - } else { + } else if (playbackState.state & AudioPlayerStoppedMask) { AudioMsgId song(this, _actionOnLoadMsgId); audioPlayer()->play(song); - if (App::main()) App::main()->audioPlayProgress(song); + audioPlayer()->notify(song); } } } else if (playAnimation) { @@ -1428,15 +1428,17 @@ void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMs void DocumentData::cancel() { if (!loading()) return; - FileLoader *l = _loader; + auto loader = createAndSwap(_loader); _loader = CancelledMtpFileLoader; - if (l) { - l->cancel(); - l->deleteLater(); - l->stop(); + loader->cancel(); + loader->deleteLater(); + loader->stop(); - notifyLayoutChanged(); + notifyLayoutChanged(); + if (auto main = App::main()) { + main->documentLoadProgress(this); } + _actionOnLoad = ActionOnLoadNone; } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index f9627501ce..3608e44fb5 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1126,13 +1126,13 @@ public: return (type == SongDocument) ? static_cast(_additional.get()) : nullptr; } const SongData *song() const { - return (type == SongDocument) ? static_cast(_additional.get()) : nullptr; + return const_cast(this)->song(); } VoiceData *voice() { return (type == VoiceDocument) ? static_cast(_additional.get()) : nullptr; } const VoiceData *voice() const { - return (type == VoiceDocument) ? static_cast(_additional.get()) : nullptr; + return const_cast(this)->voice(); } bool isAnimation() const { return (type == AnimatedDocument) || !mime.compare(qstr("image/gif"), Qt::CaseInsensitive); @@ -1141,7 +1141,10 @@ public: return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive); } bool isMusic() const { - return (type == SongDocument) ? !static_cast(_additional.get())->title.isEmpty() : false; + if (auto s = song()) { + return (s->duration > 0); + } + return false; } bool isVideo() const { return (type == VideoDocument); diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index 3c370c0111..07b50cd6b6 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -29,6 +29,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/aboutbox.h" #include "media/media_audio.h" #include "media/player/media_player_button.h" +#include "media/player/media_player_widget.h" +#include "media/player/media_player_instance.h" class TitleWidget::Hider : public TWidget { public: @@ -100,10 +102,12 @@ TitleWidget::TitleWidget(QWidget *parent) : TWidget(parent) #endif subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); }); - if (auto player = audioPlayer()) { - subscribe(player, [this](const AudioMsgId &audio) { - if (audio.type() == AudioMsgId::Type::Song) { - handleSongUpdate(audio); + if (Media::Player::exists()) { + subscribe(Media::Player::instance()->createdNotifier(), [this](const Media::Player::CreatedEvent &e) { + if (!_player) { + _player.create(this); + _player->installEventFilter(e.widget); + updateControlsVisibility(); } }); } @@ -157,20 +161,6 @@ void TitleWidget::setHideLevel(float64 level) { } } -void TitleWidget::handleSongUpdate(const AudioMsgId &audioId) { - t_assert(audioId.type() == AudioMsgId::Type::Song); - - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, audioId.type()); - if (playing == audioId) { - auto songIsPlaying = !(playbackState.state & AudioPlayerStoppedMask) && (playbackState.state != AudioPlayerFinishing); - if (songIsPlaying && !_player) { - _player.create(this); - updateControlsVisibility(); - } - } -} - void TitleWidget::onContacts() { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); diff --git a/Telegram/SourceFiles/title.h b/Telegram/SourceFiles/title.h index 137daad830..0643bac68b 100644 --- a/Telegram/SourceFiles/title.h +++ b/Telegram/SourceFiles/title.h @@ -27,6 +27,7 @@ class MainWindow; namespace Media { namespace Player { class TitleButton; +class CreatedEvent; } // namespace Player } // namespace Media class AudioMsgId; @@ -44,10 +45,6 @@ public: void maximizedChanged(bool maximized, bool force = false); - Media::Player::TitleButton *playerButton() { - return _player; - } - HitTestType hitTest(const QPoint &p); void setHideLevel(float64 level); @@ -74,7 +71,7 @@ private: void updateSystemButtonsVisibility(); void updateControlsPosition(); - void handleSongUpdate(const AudioMsgId &audioId); + void handleMediaPlayerCreated(const Media::Player::CreatedEvent &e); style::color statusColor; diff --git a/Telegram/SourceFiles/ui/flatlabel.cpp b/Telegram/SourceFiles/ui/flatlabel.cpp index e879861d58..9430098dc3 100644 --- a/Telegram/SourceFiles/ui/flatlabel.cpp +++ b/Telegram/SourceFiles/ui/flatlabel.cpp @@ -32,7 +32,7 @@ namespace { Qt::LayoutDirectionAuto, // dir }; TextParseOptions _labelMarkedOptions = { - TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands, // flags + TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMono, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir @@ -67,28 +67,32 @@ void FlatLabel::init() { connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect())); } -void FlatLabel::setText(const QString &text) { +template +void FlatLabel::setTextByCallback(SetCallback callback) { textstyleSet(&_tst); - _text.setText(_st.font, text, _labelOptions); + callback(); refreshSize(); textstyleRestore(); setMouseTracking(_selectable || _text.hasLinks()); + update(); +} + +void FlatLabel::setText(const QString &text) { + setTextByCallback([this, &text]() { + _text.setText(_st.font, text, _labelOptions); + }); } void FlatLabel::setRichText(const QString &text) { - textstyleSet(&_tst); - _text.setRichText(_st.font, text, _labelOptions); - refreshSize(); - textstyleRestore(); - setMouseTracking(_selectable || _text.hasLinks()); + setTextByCallback([this, &text]() { + _text.setRichText(_st.font, text, _labelOptions); + }); } void FlatLabel::setMarkedText(const TextWithEntities &textWithEntities) { - textstyleSet(&_tst); - _text.setMarkedText(_st.font, textWithEntities, _labelMarkedOptions); - refreshSize(); - textstyleRestore(); - setMouseTracking(_selectable || _text.hasLinks()); + setTextByCallback([this, &textWithEntities]() { + _text.setMarkedText(_st.font, textWithEntities, _labelMarkedOptions); + }); } void FlatLabel::setSelectable(bool selectable) { diff --git a/Telegram/SourceFiles/ui/flatlabel.h b/Telegram/SourceFiles/ui/flatlabel.h index 978622b79a..fb00def184 100644 --- a/Telegram/SourceFiles/ui/flatlabel.h +++ b/Telegram/SourceFiles/ui/flatlabel.h @@ -84,6 +84,9 @@ private slots: private: void init(); + template + void setTextByCallback(SetCallback callback); + Text::StateResult dragActionUpdate(); Text::StateResult dragActionStart(const QPoint &p, Qt::MouseButton button); Text::StateResult dragActionFinish(const QPoint &p, Qt::MouseButton button); diff --git a/Telegram/SourceFiles/ui/widgets/media_slider.cpp b/Telegram/SourceFiles/ui/widgets/media_slider.cpp new file mode 100644 index 0000000000..4b21ed2e21 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/media_slider.cpp @@ -0,0 +1,164 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/widgets/media_slider.h" + +#include "styles/style_widgets.h" + +namespace Ui { + +MediaSlider::MediaSlider(QWidget *parent, const style::MediaSlider &st) : TWidget(parent) +, _st(st) +, _a_value(animation(this, &MediaSlider::step_value)) { + setCursor(style::cur_pointer); +} + +float64 MediaSlider::value() const { + return a_value.current(); +} + +void MediaSlider::setValue(float64 value, bool animated) { + if (animated) { + a_value.start(value); + _a_value.start(); + } else { + a_value = anim::fvalue(value, value); + _a_value.stop(); + } + update(); +} + +void MediaSlider::setFadeOpacity(float64 opacity) { + _fadeOpacity = opacity; + update(); +} + +void MediaSlider::step_value(float64 ms, bool timer) { + float64 dt = ms / (2 * AudioVoiceMsgUpdateView); + if (dt >= 1) { + _a_value.stop(); + a_value.finish(); + } else { + a_value.update(qMin(dt, 1.), anim::linear); + } + if (timer) update(); +} + +int MediaSlider::lineLeft() const { + return (_st.seekSize.width() / 2); +} + +int MediaSlider::lineWidth() const { + return (width() - _st.seekSize.width()); +} + +void MediaSlider::paintEvent(QPaintEvent *e) { + Painter p(this); + + int radius = _st.width / 2; + p.setOpacity(_fadeOpacity); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::HighQualityAntialiasing); + + auto ms = getms(); + _a_value.step(ms); + auto over = _a_over.current(ms, _over ? 1. : 0.); + int skip = lineLeft(); + int length = lineWidth(); + float64 prg = _mouseDown ? _downValue : a_value.current(); + int32 from = skip, mid = qRound(from + prg * length), end = from + length; + if (mid > from) { + p.setClipRect(0, 0, mid, height()); + p.setOpacity(_fadeOpacity * (over * _st.activeOpacity + (1. - over) * _st.inactiveOpacity)); + p.setBrush(_st.activeFg); + p.drawRoundedRect(from, (height() - _st.width) / 2, mid + radius - from, _st.width, radius, radius); + } + if (end > mid) { + p.setClipRect(mid, 0, width() - mid, height()); + p.setOpacity(_fadeOpacity); + p.setBrush(_st.inactiveFg); + p.drawRoundedRect(mid - radius, (height() - _st.width) / 2, end - (mid - radius), _st.width, radius, radius); + } + if (over > 0) { + int x = mid - skip; + p.setClipRect(rect()); + p.setOpacity(_fadeOpacity * _st.activeOpacity); + auto seekButton = QRect(x, (height() - _st.seekSize.height()) / 2, _st.seekSize.width(), _st.seekSize.height()); + int remove = ((1. - over) * _st.seekSize.width()) / 2.; + if (remove * 2 < _st.seekSize.width()) { + p.setBrush(_st.activeFg); + p.drawEllipse(seekButton.marginsRemoved(QMargins(remove, remove, remove, remove))); + } + } +} + +void MediaSlider::mouseMoveEvent(QMouseEvent *e) { + if (_mouseDown) { + updateDownValueFromPos(e->pos().x()); + } +} + +void MediaSlider::mousePressEvent(QMouseEvent *e) { + _mouseDown = true; + _downValue = snap((e->pos().x() - lineLeft()) / float64(lineWidth()), 0., 1.); + update(); + if (_changeProgressCallback) { + _changeProgressCallback(_downValue); + } +} + +void MediaSlider::mouseReleaseEvent(QMouseEvent *e) { + if (_mouseDown) { + _mouseDown = false; + if (_changeFinishedCallback) { + _changeFinishedCallback(_downValue); + } + a_value = anim::fvalue(_downValue, _downValue); + _a_value.stop(); + update(); + } +} + +void MediaSlider::updateDownValueFromPos(int pos) { + _downValue = snap((pos - lineLeft()) / float64(lineWidth()), 0., 1.); + update(); + if (_changeProgressCallback) { + _changeProgressCallback(_downValue); + } +} + +void MediaSlider::enterEvent(QEvent *e) { + setOver(true); +} + +void MediaSlider::leaveEvent(QEvent *e) { + setOver(false); +} + +void MediaSlider::setOver(bool over) { + if (_over == over) return; + + _over = over; + auto from = _over ? 0. : 1., to = _over ? 1. : 0.; + START_ANIMATION(_a_over, func([this]() { update(); }), from, to, _st.duration, anim::linear); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/media_slider.h b/Telegram/SourceFiles/ui/widgets/media_slider.h new file mode 100644 index 0000000000..27cf742787 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/media_slider.h @@ -0,0 +1,79 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace style { +struct MediaSlider; +} // namespace style + +namespace Ui { + +class MediaSlider : public TWidget { +public: + MediaSlider(QWidget *parent, const style::MediaSlider &st); + + float64 value() const; + void setValue(float64 value, bool animated); + void setFadeOpacity(float64 opacity); + + using Callback = base::lambda_unique; + void setChangeProgressCallback(Callback &&callback) { + _changeProgressCallback = std_::move(callback); + } + void setChangeFinishedCallback(Callback &&callback) { + _changeFinishedCallback = std_::move(callback); + } + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + +private: + void step_value(float64 ms, bool timer); + void setOver(bool over); + void updateDownValueFromPos(int pos); + + int lineLeft() const; + int lineWidth() const; + + const style::MediaSlider &_st; + + Callback _changeProgressCallback; + Callback _changeFinishedCallback; + + bool _over = false; + FloatAnimation _a_over; + + anim::fvalue a_value = { 0., 0. }; + Animation _a_value; + + bool _mouseDown = false; + float64 _downValue = 0.; + + float64 _fadeOpacity = 1.; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 53f5e17d64..a37ff09a35 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -33,4 +33,14 @@ defaultLabelSimple: LabelSimple { textFg: windowTextFg; } +MediaSlider { + width: pixels; + activeFg: color; + inactiveFg: color; + activeOpacity: double; + inactiveOpacity: double; + seekSize: size; + duration: int; +} + widgetSlideDuration: 200; diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 4af728fe9b..7d0a0a0a40 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -245,10 +245,10 @@ '<(src_loc)/media/player/media_player_button.h', '<(src_loc)/media/player/media_player_cover.cpp', '<(src_loc)/media/player/media_player_cover.h', + '<(src_loc)/media/player/media_player_instance.cpp', + '<(src_loc)/media/player/media_player_instance.h', '<(src_loc)/media/player/media_player_list.cpp', '<(src_loc)/media/player/media_player_list.h', - '<(src_loc)/media/player/media_player_playback.cpp', - '<(src_loc)/media/player/media_player_playback.h', '<(src_loc)/media/player/media_player_volume_controller.cpp', '<(src_loc)/media/player/media_player_volume_controller.h', '<(src_loc)/media/player/media_player_widget.cpp', @@ -433,6 +433,8 @@ '<(src_loc)/ui/toast/toast_widget.h', '<(src_loc)/ui/widgets/label_simple.cpp', '<(src_loc)/ui/widgets/label_simple.h', + '<(src_loc)/ui/widgets/media_slider.cpp', + '<(src_loc)/ui/widgets/media_slider.h', '<(src_loc)/ui/widgets/widget_slide_wrap.h', '<(src_loc)/ui/animation.cpp', '<(src_loc)/ui/animation.h', diff --git a/Telegram/gyp/codegen_rules.gypi b/Telegram/gyp/codegen_rules.gypi index 24a8dded8a..361723685a 100644 --- a/Telegram/gyp/codegen_rules.gypi +++ b/Telegram/gyp/codegen_rules.gypi @@ -117,6 +117,7 @@ '<(PRODUCT_DIR)/codegen_style<(exe_ext)', '-I<(res_loc)', '-I<(src_loc)', '--skip-sprites', '-o<(SHARED_INTERMEDIATE_DIR)/styles', + '-w<(PRODUCT_DIR)/../..', # GYP/Ninja bug workaround: if we specify just <(RULE_INPUT_PATH) # the <(RULE_INPUT_ROOT) variables won't be available in Ninja,