2016-09-15 16:32:49 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-09-15 16:32:49 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-09-15 16:32:49 +00:00
|
|
|
*/
|
|
|
|
#include "media/player/media_player_widget.h"
|
|
|
|
|
2019-04-29 13:41:51 +00:00
|
|
|
#include "platform/platform_specific.h"
|
2017-09-26 11:49:16 +00:00
|
|
|
#include "data/data_document.h"
|
2019-04-25 12:45:15 +00:00
|
|
|
#include "data/data_session.h"
|
2019-09-13 06:06:02 +00:00
|
|
|
#include "data/data_peer.h"
|
2016-11-16 10:44:06 +00:00
|
|
|
#include "ui/widgets/labels.h"
|
2016-11-22 09:48:13 +00:00
|
|
|
#include "ui/widgets/continuous_sliders.h"
|
2016-10-12 19:34:25 +00:00
|
|
|
#include "ui/widgets/shadow.h"
|
2016-11-11 13:46:04 +00:00
|
|
|
#include "ui/widgets/buttons.h"
|
2016-12-05 11:01:08 +00:00
|
|
|
#include "ui/effects/ripple_animation.h"
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2019-02-13 12:36:59 +00:00
|
|
|
#include "media/audio/media_audio.h"
|
2019-02-27 11:36:19 +00:00
|
|
|
#include "media/view/media_view_playback_progress.h"
|
2016-10-12 19:34:25 +00:00
|
|
|
#include "media/player/media_player_button.h"
|
|
|
|
#include "media/player/media_player_instance.h"
|
|
|
|
#include "media/player/media_player_volume_controller.h"
|
2016-09-17 19:28:33 +00:00
|
|
|
#include "styles/style_media_player.h"
|
2016-10-12 19:34:25 +00:00
|
|
|
#include "styles/style_mediaview.h"
|
2018-01-11 19:33:26 +00:00
|
|
|
#include "history/history_item.h"
|
2018-10-26 15:43:24 +00:00
|
|
|
#include "storage/localstorage.h"
|
2018-01-11 19:33:26 +00:00
|
|
|
#include "layout.h"
|
2019-07-24 11:45:24 +00:00
|
|
|
#include "main/main_session.h"
|
2019-09-13 06:06:02 +00:00
|
|
|
#include "facades.h"
|
2016-09-17 19:28:33 +00:00
|
|
|
|
2016-09-15 16:32:49 +00:00
|
|
|
namespace Media {
|
|
|
|
namespace Player {
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
using ButtonState = PlayButtonLayout::State;
|
2016-09-17 19:28:33 +00:00
|
|
|
|
2016-12-05 11:01:08 +00:00
|
|
|
class Widget::PlayButton : public Ui::RippleButton {
|
2016-10-12 19:34:25 +00:00
|
|
|
public:
|
|
|
|
PlayButton(QWidget *parent);
|
2016-09-17 19:28:33 +00:00
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void setState(PlayButtonLayout::State state) {
|
|
|
|
_layout.setState(state);
|
2016-09-23 16:04:26 +00:00
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
void finishTransform() {
|
|
|
|
_layout.finishTransform();
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
protected:
|
|
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
|
2016-12-05 11:01:08 +00:00
|
|
|
QImage prepareRippleMask() const override;
|
|
|
|
QPoint prepareRippleStartPosition() const override;
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
private:
|
|
|
|
PlayButtonLayout _layout;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2016-12-05 11:01:08 +00:00
|
|
|
Widget::PlayButton::PlayButton(QWidget *parent) : Ui::RippleButton(parent, st::mediaPlayerButton.ripple)
|
2016-10-12 19:34:25 +00:00
|
|
|
, _layout(st::mediaPlayerButton, [this] { update(); }) {
|
|
|
|
resize(st::mediaPlayerButtonSize);
|
|
|
|
setCursor(style::cur_pointer);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::PlayButton::paintEvent(QPaintEvent *e) {
|
|
|
|
Painter p(this);
|
2016-09-17 19:28:33 +00:00
|
|
|
|
2019-04-02 09:13:30 +00:00
|
|
|
paintRipple(p, st::mediaPlayerButton.rippleAreaPosition.x(), st::mediaPlayerButton.rippleAreaPosition.y());
|
2016-10-12 19:34:25 +00:00
|
|
|
p.translate(st::mediaPlayerButtonPosition.x(), st::mediaPlayerButtonPosition.y());
|
|
|
|
_layout.paint(p, st::mediaPlayerActiveFg);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-12-05 11:01:08 +00:00
|
|
|
QImage Widget::PlayButton::prepareRippleMask() const {
|
|
|
|
auto size = QSize(st::mediaPlayerButton.rippleAreaSize, st::mediaPlayerButton.rippleAreaSize);
|
|
|
|
return Ui::RippleAnimation::ellipseMask(size);
|
|
|
|
}
|
|
|
|
|
|
|
|
QPoint Widget::PlayButton::prepareRippleStartPosition() const {
|
|
|
|
return QPoint(mapFromGlobal(QCursor::pos()) - st::mediaPlayerButton.rippleAreaPosition);
|
|
|
|
}
|
|
|
|
|
2017-09-13 16:57:44 +00:00
|
|
|
Widget::Widget(QWidget *parent) : RpWidget(parent)
|
2016-10-12 19:34:25 +00:00
|
|
|
, _nameLabel(this, st::mediaPlayerName)
|
|
|
|
, _timeLabel(this, st::mediaPlayerTime)
|
|
|
|
, _playPause(this)
|
|
|
|
, _volumeToggle(this, st::mediaPlayerVolumeToggle)
|
|
|
|
, _repeatTrack(this, st::mediaPlayerRepeatButton)
|
2018-10-26 15:43:24 +00:00
|
|
|
, _playbackSpeed(this, st::mediaPlayerSpeedButton)
|
2016-10-12 19:34:25 +00:00
|
|
|
, _close(this, st::mediaPlayerClose)
|
2017-09-30 18:26:45 +00:00
|
|
|
, _shadow(this)
|
2017-05-18 16:10:39 +00:00
|
|
|
, _playbackSlider(this, st::mediaPlayerPlayback)
|
2019-02-27 11:36:19 +00:00
|
|
|
, _playbackProgress(std::make_unique<View::PlaybackProgress>()) {
|
2016-10-12 19:34:25 +00:00
|
|
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
2016-10-14 17:10:15 +00:00
|
|
|
setMouseTracking(true);
|
2016-11-04 08:23:50 +00:00
|
|
|
resize(width(), st::mediaPlayerHeight + st::lineWidth);
|
2016-10-12 19:34:25 +00:00
|
|
|
|
2016-10-14 17:10:15 +00:00
|
|
|
_nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
_timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
|
2019-03-26 08:54:51 +00:00
|
|
|
_playbackProgress->setInLoadingStateChangedCallback([=](bool loading) {
|
2017-05-18 16:10:39 +00:00
|
|
|
_playbackSlider->setDisabled(loading);
|
|
|
|
});
|
2019-03-26 08:54:51 +00:00
|
|
|
_playbackProgress->setValueChangedCallback([=](float64 value, float64) {
|
2017-05-18 16:10:39 +00:00
|
|
|
_playbackSlider->setValue(value);
|
|
|
|
});
|
2019-03-26 08:54:51 +00:00
|
|
|
_playbackSlider->setChangeProgressCallback([=](float64 value) {
|
2017-05-21 13:16:39 +00:00
|
|
|
if (_type != AudioMsgId::Type::Song) {
|
|
|
|
return; // Round video seek is not supported for now :(
|
|
|
|
}
|
2019-02-27 11:36:19 +00:00
|
|
|
_playbackProgress->setValue(value, false);
|
2017-05-31 08:58:43 +00:00
|
|
|
handleSeekProgress(value);
|
2016-10-12 19:34:25 +00:00
|
|
|
});
|
2019-03-26 08:54:51 +00:00
|
|
|
_playbackSlider->setChangeFinishedCallback([=](float64 value) {
|
2017-05-21 13:16:39 +00:00
|
|
|
if (_type != AudioMsgId::Type::Song) {
|
|
|
|
return; // Round video seek is not supported for now :(
|
|
|
|
}
|
2019-02-27 11:36:19 +00:00
|
|
|
_playbackProgress->setValue(value, false);
|
2017-05-31 08:58:43 +00:00
|
|
|
handleSeekFinished(value);
|
2016-10-12 19:34:25 +00:00
|
|
|
});
|
2019-03-26 08:54:51 +00:00
|
|
|
_playPause->setClickedCallback([=] {
|
2017-05-21 13:16:39 +00:00
|
|
|
instance()->playPauseCancelClicked(_type);
|
2016-10-12 19:34:25 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
updateVolumeToggleIcon();
|
2018-04-07 08:47:08 +00:00
|
|
|
_volumeToggle->setClickedCallback([=] {
|
2016-10-12 19:34:25 +00:00
|
|
|
Global::SetSongVolume((Global::SongVolume() > 0) ? 0. : Global::RememberedSongVolume());
|
2017-05-18 20:18:59 +00:00
|
|
|
mixer()->setSongVolume(Global::SongVolume());
|
2016-10-12 19:34:25 +00:00
|
|
|
Global::RefSongVolumeChanged().notify();
|
|
|
|
});
|
|
|
|
subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); });
|
|
|
|
|
|
|
|
updateRepeatTrackIcon();
|
2018-04-07 08:47:08 +00:00
|
|
|
_repeatTrack->setClickedCallback([=] {
|
2017-05-19 18:11:33 +00:00
|
|
|
instance()->toggleRepeat(AudioMsgId::Type::Song);
|
2016-10-12 19:34:25 +00:00
|
|
|
});
|
|
|
|
|
2018-10-26 15:43:24 +00:00
|
|
|
updatePlaybackSpeedIcon();
|
|
|
|
_playbackSpeed->setClickedCallback([=] {
|
2018-11-30 14:33:12 +00:00
|
|
|
const auto doubled = !Global::VoiceMsgPlaybackDoubled();
|
|
|
|
Global::SetVoiceMsgPlaybackDoubled(doubled);
|
2019-03-26 12:50:00 +00:00
|
|
|
instance()->updateVoicePlaybackSpeed();
|
2018-10-26 15:43:24 +00:00
|
|
|
updatePlaybackSpeedIcon();
|
2018-11-08 13:06:22 +00:00
|
|
|
Local::writeUserSettings();
|
2018-10-26 15:43:24 +00:00
|
|
|
});
|
|
|
|
|
2017-05-21 13:16:39 +00:00
|
|
|
subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
|
|
|
|
if (type == _type) {
|
|
|
|
updateRepeatTrackIcon();
|
|
|
|
}
|
2017-01-19 08:24:43 +00:00
|
|
|
});
|
2017-05-21 13:16:39 +00:00
|
|
|
subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
|
|
|
|
if (type == _type) {
|
|
|
|
handleSongChange();
|
2017-05-12 17:44:18 +00:00
|
|
|
}
|
2017-01-19 08:24:43 +00:00
|
|
|
});
|
2017-05-21 13:16:39 +00:00
|
|
|
subscribe(instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) {
|
|
|
|
if (type == AudioMsgId::Type::Voice) {
|
|
|
|
_voiceIsActive = false;
|
2019-02-28 21:03:25 +00:00
|
|
|
const auto currentSong = instance()->current(AudioMsgId::Type::Song);
|
|
|
|
const auto songState = instance()->getState(AudioMsgId::Type::Song);
|
2017-05-21 13:16:39 +00:00
|
|
|
if (currentSong == songState.id && !IsStoppedOrStopping(songState.state)) {
|
|
|
|
setType(AudioMsgId::Type::Song);
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 08:24:43 +00:00
|
|
|
});
|
2019-05-11 10:46:04 +00:00
|
|
|
|
|
|
|
instance()->updatedNotifier(
|
|
|
|
) | rpl::start_with_next([=](const TrackState &state) {
|
|
|
|
handleSongUpdate(state);
|
|
|
|
}, lifetime());
|
|
|
|
|
2017-05-21 13:16:39 +00:00
|
|
|
setType(AudioMsgId::Type::Song);
|
2017-01-19 08:24:43 +00:00
|
|
|
_playPause->finishTransform();
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::updateVolumeToggleIcon() {
|
|
|
|
auto icon = []() -> const style::icon * {
|
|
|
|
auto volume = Global::SongVolume();
|
|
|
|
if (volume > 0) {
|
|
|
|
if (volume < 1 / 3.) {
|
|
|
|
return &st::mediaPlayerVolumeIcon1;
|
|
|
|
} else if (volume < 2 / 3.) {
|
|
|
|
return &st::mediaPlayerVolumeIcon2;
|
|
|
|
}
|
|
|
|
return &st::mediaPlayerVolumeIcon3;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
};
|
2016-12-05 11:01:08 +00:00
|
|
|
_volumeToggle->setIconOverride(icon());
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2018-06-04 15:35:11 +00:00
|
|
|
void Widget::setCloseCallback(Fn<void()> callback) {
|
2017-05-23 18:00:57 +00:00
|
|
|
_closeCallback = std::move(callback);
|
|
|
|
_close->setClickedCallback([this] { stopAndClose(); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::stopAndClose() {
|
|
|
|
_voiceIsActive = false;
|
|
|
|
if (_type == AudioMsgId::Type::Voice) {
|
2019-02-28 21:03:25 +00:00
|
|
|
const auto songData = instance()->current(AudioMsgId::Type::Song);
|
|
|
|
const auto songState = instance()->getState(AudioMsgId::Type::Song);
|
2017-05-23 18:00:57 +00:00
|
|
|
if (songData == songState.id && !IsStoppedOrStopping(songState.state)) {
|
|
|
|
instance()->stop(AudioMsgId::Type::Voice);
|
|
|
|
return;
|
2017-05-21 13:16:39 +00:00
|
|
|
}
|
2017-05-23 18:00:57 +00:00
|
|
|
}
|
|
|
|
if (_closeCallback) {
|
|
|
|
_closeCallback();
|
|
|
|
}
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) {
|
|
|
|
_shadow->setGeometryToLeft(x, y, w, h);
|
|
|
|
}
|
2016-09-17 19:28:33 +00:00
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::showShadow() {
|
|
|
|
_shadow->show();
|
2017-05-22 15:25:49 +00:00
|
|
|
_playbackSlider->setVisible(_type == AudioMsgId::Type::Song);
|
2016-10-12 19:34:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::hideShadow() {
|
|
|
|
_shadow->hide();
|
2017-05-18 16:10:39 +00:00
|
|
|
_playbackSlider->hide();
|
2016-10-12 19:34:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QPoint Widget::getPositionForVolumeWidget() const {
|
|
|
|
auto x = _volumeToggle->x();
|
|
|
|
x += (_volumeToggle->width() - st::mediaPlayerVolumeSize.width()) / 2;
|
|
|
|
if (rtl()) x = width() - x - st::mediaPlayerVolumeSize.width();
|
|
|
|
return QPoint(x, height());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::volumeWidgetCreated(VolumeWidget *widget) {
|
|
|
|
_volumeToggle->installEventFilter(widget);
|
|
|
|
}
|
|
|
|
|
2016-12-13 17:07:56 +00:00
|
|
|
Widget::~Widget() = default;
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::handleSeekProgress(float64 progress) {
|
|
|
|
if (!_lastDurationMs) return;
|
|
|
|
|
2019-03-01 14:41:10 +00:00
|
|
|
const auto positionMs = snap(
|
|
|
|
static_cast<crl::time>(progress * _lastDurationMs),
|
|
|
|
crl::time(0),
|
|
|
|
_lastDurationMs);
|
2016-10-12 19:34:25 +00:00
|
|
|
if (_seekPositionMs != positionMs) {
|
|
|
|
_seekPositionMs = positionMs;
|
|
|
|
updateTimeLabel();
|
2017-01-19 08:24:43 +00:00
|
|
|
|
2017-05-21 13:16:39 +00:00
|
|
|
instance()->startSeeking(_type);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::handleSeekFinished(float64 progress) {
|
|
|
|
if (!_lastDurationMs) return;
|
|
|
|
|
2019-03-01 14:41:10 +00:00
|
|
|
const auto positionMs = snap(
|
|
|
|
static_cast<crl::time>(progress * _lastDurationMs),
|
|
|
|
crl::time(0),
|
|
|
|
_lastDurationMs);
|
2016-10-12 19:34:25 +00:00
|
|
|
_seekPositionMs = -1;
|
|
|
|
|
2019-02-28 21:03:25 +00:00
|
|
|
instance()->finishSeeking(_type, progress);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::resizeEvent(QResizeEvent *e) {
|
|
|
|
auto right = st::mediaPlayerCloseRight;
|
|
|
|
_close->moveToRight(right, st::mediaPlayerPlayTop); right += _close->width();
|
2018-11-09 12:28:15 +00:00
|
|
|
if (hasPlaybackSpeedControl()) {
|
2018-10-26 15:43:24 +00:00
|
|
|
_playbackSpeed->moveToRight(right, st::mediaPlayerPlayTop); right += _playbackSpeed->width();
|
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
_repeatTrack->moveToRight(right, st::mediaPlayerPlayTop); right += _repeatTrack->width();
|
|
|
|
_volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width();
|
|
|
|
|
2016-10-14 18:44:15 +00:00
|
|
|
updatePlayPrevNextPositions();
|
2016-10-12 19:34:25 +00:00
|
|
|
|
2017-05-18 16:10:39 +00:00
|
|
|
_playbackSlider->setGeometry(0, height() - st::mediaPlayerPlayback.fullWidth, width(), st::mediaPlayerPlayback.fullWidth);
|
2016-10-12 19:34:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::paintEvent(QPaintEvent *e) {
|
|
|
|
Painter p(this);
|
|
|
|
auto fill = e->rect().intersected(QRect(0, 0, width(), st::mediaPlayerHeight));
|
|
|
|
if (!fill.isEmpty()) {
|
2016-12-05 11:01:08 +00:00
|
|
|
p.fillRect(fill, st::mediaPlayerBg);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-11 11:24:37 +00:00
|
|
|
void Widget::leaveEventHook(QEvent *e) {
|
2016-10-14 17:10:15 +00:00
|
|
|
updateOverLabelsState(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::mouseMoveEvent(QMouseEvent *e) {
|
|
|
|
updateOverLabelsState(e->pos());
|
|
|
|
}
|
|
|
|
|
2017-05-21 15:14:42 +00:00
|
|
|
void Widget::mousePressEvent(QMouseEvent *e) {
|
|
|
|
_labelsDown = _labelsOver;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::mouseReleaseEvent(QMouseEvent *e) {
|
|
|
|
if (auto downLabels = base::take(_labelsDown)) {
|
|
|
|
if (_labelsOver == downLabels) {
|
|
|
|
if (_type == AudioMsgId::Type::Voice) {
|
|
|
|
auto current = instance()->current(_type);
|
2019-04-25 12:45:15 +00:00
|
|
|
if (auto item = Auth().data().message(current.contextId())) {
|
2017-05-21 15:14:42 +00:00
|
|
|
Ui::showPeerHistoryAtItem(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-14 17:10:15 +00:00
|
|
|
void Widget::updateOverLabelsState(QPoint pos) {
|
|
|
|
auto left = getLabelsLeft();
|
|
|
|
auto right = getLabelsRight();
|
|
|
|
auto labels = myrtlrect(left, 0, width() - right - left, height() - st::mediaPlayerPlayback.fullWidth);
|
|
|
|
auto over = labels.contains(pos);
|
|
|
|
updateOverLabelsState(over);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::updateOverLabelsState(bool over) {
|
2017-05-21 15:14:42 +00:00
|
|
|
_labelsOver = over;
|
|
|
|
auto pressShowsItem = _labelsOver && (_type == AudioMsgId::Type::Voice);
|
|
|
|
setCursor(pressShowsItem ? style::cur_pointer : style::cur_default);
|
|
|
|
auto showPlaylist = over && (_type == AudioMsgId::Type::Song);
|
|
|
|
instance()->playerWidgetOver().notify(showPlaylist, true);
|
2016-10-14 17:10:15 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::updatePlayPrevNextPositions() {
|
|
|
|
auto left = st::mediaPlayerPlayLeft;
|
|
|
|
auto top = st::mediaPlayerPlayTop;
|
|
|
|
if (_previousTrack) {
|
|
|
|
_previousTrack->moveToLeft(left, top); left += _previousTrack->width() + st::mediaPlayerPlaySkip;
|
|
|
|
_playPause->moveToLeft(left, top); left += _playPause->width() + st::mediaPlayerPlaySkip;
|
|
|
|
_nextTrack->moveToLeft(left, top);
|
2016-09-17 19:28:33 +00:00
|
|
|
} else {
|
2016-10-12 19:34:25 +00:00
|
|
|
_playPause->moveToLeft(left, top);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
2016-10-14 18:44:15 +00:00
|
|
|
updateLabelsGeometry();
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-14 17:10:15 +00:00
|
|
|
int Widget::getLabelsLeft() const {
|
|
|
|
auto result = st::mediaPlayerPlayLeft + _playPause->width();
|
2016-10-12 19:34:25 +00:00
|
|
|
if (_previousTrack) {
|
2016-10-14 17:10:15 +00:00
|
|
|
result += _previousTrack->width() + st::mediaPlayerPlaySkip + _nextTrack->width() + st::mediaPlayerPlaySkip;
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
2016-10-14 17:10:15 +00:00
|
|
|
result += st::mediaPlayerPadding;
|
|
|
|
return result;
|
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
|
2016-10-14 17:10:15 +00:00
|
|
|
int Widget::getLabelsRight() const {
|
2017-05-21 13:16:39 +00:00
|
|
|
auto result = st::mediaPlayerCloseRight + _close->width();
|
|
|
|
if (_type == AudioMsgId::Type::Song) {
|
|
|
|
result += _repeatTrack->width() + _volumeToggle->width();
|
2018-11-09 12:28:15 +00:00
|
|
|
} else if (hasPlaybackSpeedControl()) {
|
2018-10-26 15:43:24 +00:00
|
|
|
result += _playbackSpeed->width();
|
2017-05-21 13:16:39 +00:00
|
|
|
}
|
2016-10-14 17:10:15 +00:00
|
|
|
result += st::mediaPlayerPadding;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::updateLabelsGeometry() {
|
|
|
|
auto left = getLabelsLeft();
|
|
|
|
auto right = getLabelsRight();
|
2016-10-12 19:34:25 +00:00
|
|
|
|
|
|
|
auto widthForName = width() - left - right;
|
|
|
|
widthForName -= _timeLabel->width() + 2 * st::normalFont->spacew;
|
|
|
|
_nameLabel->resizeToWidth(widthForName);
|
|
|
|
|
2016-12-23 13:21:01 +00:00
|
|
|
_nameLabel->moveToLeft(left, st::mediaPlayerNameTop - st::mediaPlayerName.style.font->ascent);
|
2016-10-12 19:34:25 +00:00
|
|
|
_timeLabel->moveToRight(right, st::mediaPlayerNameTop - st::mediaPlayerTime.font->ascent);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::updateRepeatTrackIcon() {
|
2017-05-19 18:11:33 +00:00
|
|
|
auto repeating = instance()->repeatEnabled(AudioMsgId::Type::Song);
|
2016-12-05 11:01:08 +00:00
|
|
|
_repeatTrack->setIconOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledIcon, repeating ? nullptr : &st::mediaPlayerRepeatDisabledIconOver);
|
|
|
|
_repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2018-11-08 13:06:22 +00:00
|
|
|
void Widget::updatePlaybackSpeedIcon() {
|
2018-11-30 14:33:12 +00:00
|
|
|
const auto doubled = Global::VoiceMsgPlaybackDoubled();
|
|
|
|
const auto isDefaultSpeed = !doubled;
|
|
|
|
_playbackSpeed->setIconOverride(
|
|
|
|
isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIcon : nullptr,
|
|
|
|
isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIconOver : nullptr);
|
|
|
|
_playbackSpeed->setRippleColorOverride(
|
|
|
|
isDefaultSpeed ? &st::mediaPlayerSpeedDisabledRippleBg : nullptr);
|
2018-10-26 15:43:24 +00:00
|
|
|
}
|
|
|
|
|
2017-05-21 13:16:39 +00:00
|
|
|
void Widget::checkForTypeChange() {
|
|
|
|
auto hasActiveType = [](AudioMsgId::Type type) {
|
2019-02-28 21:03:25 +00:00
|
|
|
const auto current = instance()->current(type);
|
|
|
|
const auto state = instance()->getState(type);
|
2017-05-21 13:16:39 +00:00
|
|
|
return (current == state.id && !IsStoppedOrStopping(state.state));
|
|
|
|
};
|
|
|
|
if (hasActiveType(AudioMsgId::Type::Voice)) {
|
|
|
|
_voiceIsActive = true;
|
|
|
|
setType(AudioMsgId::Type::Voice);
|
|
|
|
} else if (!_voiceIsActive && hasActiveType(AudioMsgId::Type::Song)) {
|
|
|
|
setType(AudioMsgId::Type::Song);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-09 12:28:15 +00:00
|
|
|
bool Widget::hasPlaybackSpeedControl() const {
|
2019-02-21 16:01:55 +00:00
|
|
|
return (_type == AudioMsgId::Type::Voice)
|
|
|
|
&& Media::Audio::SupportsSpeedControl();
|
2018-11-09 12:28:15 +00:00
|
|
|
}
|
|
|
|
|
2017-05-21 13:16:39 +00:00
|
|
|
void Widget::setType(AudioMsgId::Type type) {
|
|
|
|
if (_type != type) {
|
|
|
|
_type = type;
|
|
|
|
_repeatTrack->setVisible(_type == AudioMsgId::Type::Song);
|
|
|
|
_volumeToggle->setVisible(_type == AudioMsgId::Type::Song);
|
2018-11-09 12:28:15 +00:00
|
|
|
_playbackSpeed->setVisible(hasPlaybackSpeedControl());
|
2017-05-22 15:25:49 +00:00
|
|
|
if (!_shadow->isHidden()) {
|
|
|
|
_playbackSlider->setVisible(_type == AudioMsgId::Type::Song);
|
|
|
|
}
|
2017-05-21 13:16:39 +00:00
|
|
|
updateLabelsGeometry();
|
|
|
|
handleSongChange();
|
2019-02-28 21:03:25 +00:00
|
|
|
handleSongUpdate(instance()->getState(_type));
|
2017-05-21 15:14:42 +00:00
|
|
|
updateOverLabelsState(_labelsOver);
|
2017-12-08 18:27:28 +00:00
|
|
|
_playlistChangesLifetime = instance()->playlistChanges(
|
|
|
|
_type
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
handlePlaylistUpdate();
|
|
|
|
});
|
2018-10-26 15:43:24 +00:00
|
|
|
// maybe the type change causes a change of the button layout
|
|
|
|
QResizeEvent event = { size(), size() };
|
|
|
|
resizeEvent(&event);
|
2017-05-21 13:16:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
void Widget::handleSongUpdate(const TrackState &state) {
|
2017-05-21 13:16:39 +00:00
|
|
|
checkForTypeChange();
|
|
|
|
if (state.id.type() != _type || !state.id.audio()) {
|
2016-09-17 19:28:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
if (state.id.audio()->loading()) {
|
2019-02-27 11:36:19 +00:00
|
|
|
_playbackProgress->updateLoadingState(state.id.audio()->progress());
|
2016-10-13 09:12:12 +00:00
|
|
|
} else {
|
2019-02-27 11:36:19 +00:00
|
|
|
_playbackProgress->updateState(state);
|
2016-10-13 09:12:12 +00:00
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
|
2017-05-21 13:16:39 +00:00
|
|
|
auto stopped = IsStoppedOrStopping(state.state);
|
2019-02-22 14:28:10 +00:00
|
|
|
auto showPause = ShowPauseIcon(state.state);
|
2017-05-21 13:16:39 +00:00
|
|
|
if (instance()->isSeeking(_type)) {
|
2016-10-12 19:34:25 +00:00
|
|
|
showPause = true;
|
|
|
|
}
|
2017-01-24 21:24:39 +00:00
|
|
|
auto buttonState = [audio = state.id.audio(), showPause] {
|
2016-10-12 19:34:25 +00:00
|
|
|
if (audio->loading()) {
|
2017-01-24 21:24:39 +00:00
|
|
|
return ButtonState::Cancel;
|
2016-10-12 19:34:25 +00:00
|
|
|
} else if (showPause) {
|
2017-01-24 21:24:39 +00:00
|
|
|
return ButtonState::Pause;
|
2016-10-12 19:34:25 +00:00
|
|
|
}
|
2017-01-24 21:24:39 +00:00
|
|
|
return ButtonState::Play;
|
2016-10-12 19:34:25 +00:00
|
|
|
};
|
2017-01-24 21:24:39 +00:00
|
|
|
_playPause->setState(buttonState());
|
2016-10-12 19:34:25 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
updateTimeText(state);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
void Widget::updateTimeText(const TrackState &state) {
|
2016-10-12 19:34:25 +00:00
|
|
|
QString time;
|
2017-05-03 13:01:15 +00:00
|
|
|
qint64 position = 0, length = 0, display = 0;
|
2017-12-10 10:26:58 +00:00
|
|
|
const auto frequency = state.frequency;
|
|
|
|
const auto document = state.id.audio();
|
2017-05-21 13:16:39 +00:00
|
|
|
if (!IsStoppedOrStopping(state.state)) {
|
2017-01-24 21:24:39 +00:00
|
|
|
display = position = state.position;
|
2017-05-03 13:01:15 +00:00
|
|
|
length = state.length;
|
|
|
|
} else if (state.length) {
|
|
|
|
display = state.length;
|
2017-12-10 10:26:58 +00:00
|
|
|
} else if (const auto song = document->song()) {
|
|
|
|
display = (song->duration * frequency);
|
2016-10-12 19:34:25 +00:00
|
|
|
}
|
|
|
|
|
2017-05-03 13:01:15 +00:00
|
|
|
_lastDurationMs = (state.length * 1000LL) / frequency;
|
2016-09-17 19:28:33 +00:00
|
|
|
|
2017-12-10 10:26:58 +00:00
|
|
|
if (document->loading()) {
|
|
|
|
_time = QString::number(qRound(document->progress() * 100)) + '%';
|
2017-05-18 16:10:39 +00:00
|
|
|
_playbackSlider->setDisabled(true);
|
2016-10-12 19:34:25 +00:00
|
|
|
} else {
|
|
|
|
display = display / frequency;
|
|
|
|
_time = formatDurationText(display);
|
2017-05-18 16:10:39 +00:00
|
|
|
_playbackSlider->setDisabled(false);
|
2016-10-12 19:34:25 +00:00
|
|
|
}
|
|
|
|
if (_seekPositionMs < 0) {
|
|
|
|
updateTimeLabel();
|
|
|
|
}
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::updateTimeLabel() {
|
|
|
|
auto timeLabelWidth = _timeLabel->width();
|
|
|
|
if (_seekPositionMs >= 0) {
|
|
|
|
auto playAlready = _seekPositionMs / 1000LL;
|
|
|
|
_timeLabel->setText(formatDurationText(playAlready));
|
|
|
|
} else {
|
|
|
|
_timeLabel->setText(_time);
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
if (timeLabelWidth != _timeLabel->width()) {
|
|
|
|
updateLabelsGeometry();
|
|
|
|
}
|
|
|
|
}
|
2016-09-28 21:31:43 +00:00
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::handleSongChange() {
|
2017-12-10 10:26:58 +00:00
|
|
|
const auto current = instance()->current(_type);
|
|
|
|
const auto document = current.audio();
|
2019-03-11 14:35:11 +00:00
|
|
|
if (!current
|
|
|
|
|| !document
|
|
|
|
|| ((_lastSongId.audio() == document)
|
|
|
|
&& (_lastSongId.contextId() == current.contextId()))) {
|
2017-05-21 13:16:39 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-03-11 14:35:11 +00:00
|
|
|
_lastSongId = current;
|
2016-10-12 19:34:25 +00:00
|
|
|
|
|
|
|
TextWithEntities textWithEntities;
|
2017-12-10 10:26:58 +00:00
|
|
|
if (document->isVoiceMessage() || document->isVideoMessage()) {
|
2019-04-25 12:45:15 +00:00
|
|
|
if (const auto item = Auth().data().message(current.contextId())) {
|
2019-09-13 06:06:02 +00:00
|
|
|
const auto name = item->fromOriginal()->name;
|
2018-02-03 19:52:35 +00:00
|
|
|
const auto date = [item] {
|
|
|
|
const auto parsed = ItemDateTime(item);
|
|
|
|
const auto date = parsed.date();
|
|
|
|
const auto time = parsed.time().toString(cTimeFormat());
|
|
|
|
const auto today = QDateTime::currentDateTime().date();
|
2017-05-21 13:16:39 +00:00
|
|
|
if (date == today) {
|
2019-06-19 16:39:25 +00:00
|
|
|
return tr::lng_player_message_today(
|
|
|
|
tr::now,
|
|
|
|
lt_time,
|
|
|
|
time);
|
2017-05-21 13:16:39 +00:00
|
|
|
} else if (date.addDays(1) == today) {
|
2019-06-19 16:39:25 +00:00
|
|
|
return tr::lng_player_message_yesterday(
|
|
|
|
tr::now,
|
|
|
|
lt_time,
|
|
|
|
time);
|
2017-05-21 13:16:39 +00:00
|
|
|
}
|
2019-06-19 16:39:25 +00:00
|
|
|
return tr::lng_player_message_date(
|
|
|
|
tr::now,
|
2018-02-03 19:52:35 +00:00
|
|
|
lt_date,
|
|
|
|
langDayOfMonthFull(date),
|
|
|
|
lt_time,
|
|
|
|
time);
|
2017-05-21 13:16:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
textWithEntities.text = name + ' ' + date();
|
2018-02-03 19:52:35 +00:00
|
|
|
textWithEntities.entities.append(EntityInText(
|
2019-04-08 15:10:06 +00:00
|
|
|
EntityType::Bold,
|
2018-02-03 19:52:35 +00:00
|
|
|
0,
|
|
|
|
name.size(),
|
|
|
|
QString()));
|
2017-05-21 13:16:39 +00:00
|
|
|
} else {
|
2019-06-19 15:09:03 +00:00
|
|
|
textWithEntities.text = tr::lng_media_audio(tr::now);
|
2017-05-21 13:16:39 +00:00
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
} else {
|
2017-12-10 10:26:58 +00:00
|
|
|
const auto song = document->song();
|
2017-05-21 13:16:39 +00:00
|
|
|
if (!song || song->performer.isEmpty()) {
|
2017-11-05 11:00:48 +00:00
|
|
|
textWithEntities.text = (!song || song->title.isEmpty())
|
2017-12-10 10:26:58 +00:00
|
|
|
? (document->filename().isEmpty()
|
2017-11-05 11:00:48 +00:00
|
|
|
? qsl("Unknown Track")
|
2017-12-10 10:26:58 +00:00
|
|
|
: document->filename())
|
2017-11-05 11:00:48 +00:00
|
|
|
: song->title;
|
2017-05-21 13:16:39 +00:00
|
|
|
} else {
|
2017-11-05 11:00:48 +00:00
|
|
|
auto title = song->title.isEmpty()
|
|
|
|
? qsl("Unknown Track")
|
|
|
|
: TextUtilities::Clean(song->title);
|
|
|
|
auto dash = QString::fromUtf8(" \xe2\x80\x93 ");
|
|
|
|
textWithEntities.text = song->performer + dash + title;
|
2019-04-08 15:10:06 +00:00
|
|
|
textWithEntities.entities.append({ EntityType::Bold, 0, song->performer.size(), QString() });
|
2017-05-21 13:16:39 +00:00
|
|
|
}
|
2016-10-12 19:34:25 +00:00
|
|
|
}
|
|
|
|
_nameLabel->setMarkedText(textWithEntities);
|
|
|
|
|
|
|
|
handlePlaylistUpdate();
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::handlePlaylistUpdate() {
|
2017-12-08 18:27:28 +00:00
|
|
|
const auto previousEnabled = instance()->previousAvailable(_type);
|
|
|
|
const auto nextEnabled = instance()->nextAvailable(_type);
|
|
|
|
if (!previousEnabled && !nextEnabled) {
|
2016-10-12 19:34:25 +00:00
|
|
|
destroyPrevNextButtons();
|
|
|
|
} else {
|
|
|
|
createPrevNextButtons();
|
2016-12-05 11:01:08 +00:00
|
|
|
_previousTrack->setIconOverride(previousEnabled ? nullptr : &st::mediaPlayerPreviousDisabledIcon);
|
|
|
|
_previousTrack->setRippleColorOverride(previousEnabled ? nullptr : &st::mediaPlayerBg);
|
2016-10-12 19:34:25 +00:00
|
|
|
_previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default);
|
2016-12-05 11:01:08 +00:00
|
|
|
_nextTrack->setIconOverride(nextEnabled ? nullptr : &st::mediaPlayerNextDisabledIcon);
|
|
|
|
_nextTrack->setRippleColorOverride(nextEnabled ? nullptr : &st::mediaPlayerBg);
|
2016-10-12 19:34:25 +00:00
|
|
|
_nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default);
|
|
|
|
}
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::createPrevNextButtons() {
|
|
|
|
if (!_previousTrack) {
|
|
|
|
_previousTrack.create(this, st::mediaPlayerPreviousButton);
|
2016-10-14 17:10:15 +00:00
|
|
|
_previousTrack->show();
|
2018-04-07 08:47:08 +00:00
|
|
|
_previousTrack->setClickedCallback([=]() {
|
2017-01-19 08:24:43 +00:00
|
|
|
instance()->previous();
|
2016-10-12 19:34:25 +00:00
|
|
|
});
|
2016-10-14 17:10:15 +00:00
|
|
|
_nextTrack.create(this, st::mediaPlayerNextButton);
|
|
|
|
_nextTrack->show();
|
2018-04-07 08:47:08 +00:00
|
|
|
_nextTrack->setClickedCallback([=]() {
|
2017-01-19 08:24:43 +00:00
|
|
|
instance()->next();
|
2016-10-12 19:34:25 +00:00
|
|
|
});
|
|
|
|
updatePlayPrevNextPositions();
|
|
|
|
}
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
2016-10-12 19:34:25 +00:00
|
|
|
void Widget::destroyPrevNextButtons() {
|
|
|
|
if (_previousTrack) {
|
|
|
|
_previousTrack.destroy();
|
|
|
|
_nextTrack.destroy();
|
|
|
|
updatePlayPrevNextPositions();
|
2016-09-17 19:28:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-15 16:32:49 +00:00
|
|
|
} // namespace Player
|
|
|
|
} // namespace Media
|