New audio player done (without playlist).
This commit is contained in:
parent
a8f3582cb1
commit
f970ac3163
|
@ -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";
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include <ostream>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDir>
|
||||
#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()) {
|
||||
|
|
|
@ -340,7 +340,7 @@ inline constexpr typename remove_reference<T>::type &&move(T &&value) noexcept {
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
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 <typename T>
|
||||
T createAndSwap(T &value) {
|
||||
T result = T();
|
||||
std_::swap(result, value);
|
||||
std_::swap_moveable(result, value);
|
||||
return std_::move(result);
|
||||
}
|
||||
|
||||
|
|
|
@ -335,7 +335,7 @@ class CommonObservable {
|
|||
public:
|
||||
using Handler = typename CommonObservableData<EventType>::Handler;
|
||||
|
||||
Subscription subscribe(Handler &&handler) {
|
||||
Subscription add_subscription(Handler &&handler) {
|
||||
if (!_data) {
|
||||
_data = MakeShared<ObservableData<EventType>>(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 <typename EventType, typename Lambda>
|
||||
int subscribe(base::Observable<EventType> &observable, Lambda &&handler) {
|
||||
_subscriptions.push_back(observable.subscribe(std_::forward<Lambda>(handler)));
|
||||
_subscriptions.push_back(observable.add_subscription(std_::forward<Lambda>(handler)));
|
||||
return _subscriptions.size() - 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<MTPMessage> &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<UserData*> *lastAuthors = 0;
|
||||
OrderedSet<PeerData*> *markupSenders = 0;
|
||||
QList<UserData*> *lastAuthors = nullptr;
|
||||
OrderedSet<PeerData*> *markupSenders = nullptr;
|
||||
if (peer->isChat()) {
|
||||
lastAuthors = &peer->asChat()->lastAuthors;
|
||||
markupSenders = &peer->asChat()->markupSenders;
|
||||
|
@ -1254,7 +1254,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &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<MTPMessage> &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<MTPMessage> &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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<int64>(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<int64>(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
|
||||
|
|
|
@ -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<FlatLabel> _nameLabel;
|
||||
ChildWidget<Ui::LabelSimple> _timeLabel;
|
||||
ChildWidget<PlaybackWidget> _playback;
|
||||
ChildWidget<Clip::Playback> _playback;
|
||||
ChildWidget<Ui::IconButton> _previousTrack = { nullptr };
|
||||
ChildWidget<Ui::IconButton> _playPause;
|
||||
ChildWidget<Ui::IconButton> _nextTrack = { nullptr };
|
||||
|
|
|
@ -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 <typename CheckCallback>
|
||||
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
|
|
@ -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<FullMsgId> &playlist() const {
|
||||
return _playlist;
|
||||
}
|
||||
|
||||
base::Observable<CreatedEvent> &createdNotifier() {
|
||||
return _createdNotifier;
|
||||
}
|
||||
base::Observable<UpdatedEvent> &updatedNotifier() {
|
||||
return _updatedNotifier;
|
||||
}
|
||||
base::Observable<void> &playlistChangedNotifier() {
|
||||
return _playlistChangedNotifier;
|
||||
}
|
||||
base::Observable<void> &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 <typename CheckCallback>
|
||||
void emitUpdate(CheckCallback check);
|
||||
|
||||
AudioMsgId _current, _seeking;
|
||||
History *_history = nullptr;
|
||||
History *_migrated = nullptr;
|
||||
|
||||
bool _repeatEnabled = false;
|
||||
|
||||
QList<FullMsgId> _playlist;
|
||||
bool _isPlaying = false;
|
||||
|
||||
base::Observable<CreatedEvent> _createdNotifier;
|
||||
base::Observable<UpdatedEvent> _updatedNotifier;
|
||||
base::Observable<void> _playlistChangedNotifier;
|
||||
base::Observable<void> _songChangedNotifier;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Media
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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<Ui::IconButton> _toggle;
|
||||
ChildWidget<Ui::MediaSlider> _slider;
|
||||
float64 _rememberedVolume = Global::kDefaultVolume;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace Player {
|
|||
class CoverWidget;
|
||||
class ListWidget;
|
||||
|
||||
class Widget : public TWidget {
|
||||
class Widget : public TWidget, private base::Subscriber {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
|
|
@ -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<int64>(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<int64>(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) {
|
||||
|
|
|
@ -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 <typename Callback>
|
||||
void startFading(Callback start);
|
||||
void fadeFinished();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -334,7 +334,7 @@ public slots:
|
|||
void onScroll();
|
||||
|
||||
void onScrollTimer();
|
||||
void onPlayerSongChanged(const FullMsgId &msgId);
|
||||
// void onPlayerSongChanged(const FullMsgId &msgId);
|
||||
|
||||
void onForwardSelected();
|
||||
void onDeleteSelected();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -51,7 +51,7 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class DownloadPathState : public TWidget, public base::Subscriber {
|
||||
class DownloadPathState : public TWidget, private base::Subscriber {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<HistoryPhoto*>(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<HistoryPhoto*>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1126,13 +1126,13 @@ public:
|
|||
return (type == SongDocument) ? static_cast<SongData*>(_additional.get()) : nullptr;
|
||||
}
|
||||
const SongData *song() const {
|
||||
return (type == SongDocument) ? static_cast<const SongData*>(_additional.get()) : nullptr;
|
||||
return const_cast<DocumentData*>(this)->song();
|
||||
}
|
||||
VoiceData *voice() {
|
||||
return (type == VoiceDocument) ? static_cast<VoiceData*>(_additional.get()) : nullptr;
|
||||
}
|
||||
const VoiceData *voice() const {
|
||||
return (type == VoiceDocument) ? static_cast<const VoiceData*>(_additional.get()) : nullptr;
|
||||
return const_cast<DocumentData*>(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<SongData*>(_additional.get())->title.isEmpty() : false;
|
||||
if (auto s = song()) {
|
||||
return (s->duration > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool isVideo() const {
|
||||
return (type == VideoDocument);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 <typename SetCallback>
|
||||
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) {
|
||||
|
|
|
@ -84,6 +84,9 @@ private slots:
|
|||
private:
|
||||
void init();
|
||||
|
||||
template <typename SetCallback>
|
||||
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);
|
||||
|
|
|
@ -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
|
|
@ -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(float64)>;
|
||||
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
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue