Play streaming video in mediaview.

This commit is contained in:
John Preston 2019-02-27 15:36:19 +04:00
parent 44df10d6cb
commit f1e0cd6c1d
43 changed files with 920 additions and 900 deletions

View file

@ -422,14 +422,11 @@ namespace App {
HistoryItem *histItemById(ChannelId channelId, MsgId itemId) { HistoryItem *histItemById(ChannelId channelId, MsgId itemId) {
if (!itemId) return nullptr; if (!itemId) return nullptr;
auto data = fetchMsgsData(channelId, false); const auto data = fetchMsgsData(channelId, false);
if (!data) return nullptr; if (!data) return nullptr;
auto i = data->constFind(itemId); const auto i = data->constFind(itemId);
if (i != data->cend()) { return (i != data->cend()) ? i.value() : nullptr;
return i.value();
}
return nullptr;
} }
HistoryItem *histItemById(const ChannelData *channel, MsgId itemId) { HistoryItem *histItemById(const ChannelData *channel, MsgId itemId) {

View file

@ -154,7 +154,7 @@ QString FileNameUnsafe(
if (QRegularExpression(qsl("^[a-zA-Z_0-9]+$")).match(ext).hasMatch()) { if (QRegularExpression(qsl("^[a-zA-Z_0-9]+$")).match(ext).hasMatch()) {
QStringList filters = filter.split(sep); QStringList filters = filter.split(sep);
if (filters.size() > 1) { if (filters.size() > 1) {
QString first = filters.at(0); const auto &first = filters.at(0);
int32 start = first.indexOf(qsl("(*.")); int32 start = first.indexOf(qsl("(*."));
if (start >= 0) { if (start >= 0) {
if (!QRegularExpression(qsl("\\(\\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first).hasMatch()) { if (!QRegularExpression(qsl("\\(\\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first).hasMatch()) {
@ -287,202 +287,6 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals
return FileNameForSave(caption, filter, prefix, name, forceSavingAs, dir); return FileNameForSave(caption, filter, prefix, name, forceSavingAs, dir);
} }
void StartStreaming(
not_null<DocumentData*> document,
Data::FileOrigin origin) {
AssertIsDebug();
using namespace Media::Streaming;
if (auto loader = document->createStreamingLoader(origin)) {
static auto player = std::unique_ptr<Player>();
static auto pauseOnSeek = false;
static auto position = crl::time(0);
static auto preloadedAudio = crl::time(0);
static auto preloadedVideo = crl::time(0);
static auto duration = crl::time(0);
static auto options = PlaybackOptions();
static auto speed = 1.;
static auto step = pow(2., 1. / 12);
static auto frame = QImage();
class Panel
#if defined Q_OS_MAC && !defined OS_MAC_OLD
: public Ui::RpWidgetWrap<QOpenGLWidget> {
using Parent = Ui::RpWidgetWrap<QOpenGLWidget>;
#else // Q_OS_MAC && !OS_MAC_OLD
: public Ui::RpWidget {
using Parent = Ui::RpWidget;
#endif // Q_OS_MAC && !OS_MAC_OLD
public:
Panel() : Parent(nullptr) {
}
protected:
void paintEvent(QPaintEvent *e) override {
}
void keyPressEvent(QKeyEvent *e) override {
if (e->key() == Qt::Key_Space) {
if (player->paused()) {
player->resume();
} else {
player->pause();
}
} else if (e->key() == Qt::Key_Plus) {
speed = std::min(speed * step, 2.);
player->setSpeed(speed);
} else if (e->key() == Qt::Key_Minus) {
speed = std::max(speed / step, 0.5);
player->setSpeed(speed);
}
}
void mousePressEvent(QMouseEvent *e) override {
pauseOnSeek = player->paused();
player->pause();
}
void mouseReleaseEvent(QMouseEvent *e) override {
if (player->ready()) {
frame = player->frame({});
}
preloadedAudio
= preloadedVideo
= position
= options.position
= std::clamp(
(duration * e->pos().x()) / width(),
crl::time(0),
crl::time(duration));
player->play(options);
}
};
static auto video = base::unique_qptr<Panel>();
player = std::make_unique<Player>(
&document->owner(),
std::move(loader));
base::take(video) = nullptr;
player->lifetime().add([] {
base::take(video) = nullptr;
});
document->session().lifetime().add([] {
base::take(player) = nullptr;
});
options.speed = speed;
//options.syncVideoByAudio = false;
preloadedAudio = preloadedVideo = position = options.position = 0;
frame = QImage();
player->play(options);
player->updates(
) | rpl::start_with_next_error_done([=](Update &&update) {
update.data.match([&](Information &update) {
duration = std::max(
update.video.state.duration,
update.audio.state.duration);
if (video) {
if (update.video.cover.isNull()) {
base::take(video) = nullptr;
} else {
video->update();
}
} else if (!update.video.cover.isNull()) {
video = base::make_unique_q<Panel>();
video->setAttribute(Qt::WA_OpaquePaintEvent);
video->paintRequest(
) | rpl::start_with_next([=](QRect rect) {
const auto till1 = duration
? (position * video->width() / duration)
: 0;
const auto till2 = duration
? (std::min(preloadedAudio, preloadedVideo)
* video->width()
/ duration)
: 0;
if (player->ready()) {
Painter(video.get()).drawImage(
video->rect(),
player->frame({}));
} else if (!frame.isNull()) {
Painter(video.get()).drawImage(
video->rect(),
frame);
} else {
Painter(video.get()).fillRect(
rect,
Qt::black);
}
Painter(video.get()).fillRect(
0,
0,
till1,
video->height(),
QColor(255, 255, 255, 64));
if (till2 > till1) {
Painter(video.get()).fillRect(
till1,
0,
till2 - till1,
video->height(),
QColor(255, 255, 255, 32));
}
}, video->lifetime());
const auto size = QSize(
ConvertScale(update.video.size.width()),
ConvertScale(update.video.size.height()));
const auto center = App::wnd()->geometry().center();
video->setGeometry(QRect(
center - QPoint(size.width(), size.height()) / 2,
size));
video->show();
video->shownValue(
) | rpl::start_with_next([=](bool shown) {
if (!shown) {
base::take(player) = nullptr;
}
}, video->lifetime());
}
}, [&](PreloadedVideo &update) {
if (preloadedVideo < update.till) {
if (preloadedVideo < preloadedAudio) {
video->update();
}
preloadedVideo = update.till;
}
}, [&](UpdateVideo &update) {
Expects(video != nullptr);
if (position < update.position) {
position = update.position;
}
video->update();
}, [&](PreloadedAudio &update) {
if (preloadedAudio < update.till) {
if (video && preloadedAudio < preloadedVideo) {
video->update();
}
preloadedAudio = update.till;
}
}, [&](UpdateAudio &update) {
if (position < update.position) {
position = update.position;
if (video) {
video->update();
}
}
}, [&](WaitingForData) {
}, [&](MutedByOther) {
}, [&](Finished) {
base::take(player) = nullptr;
});
}, [=](const Error &error) {
base::take(video) = nullptr;
}, [=] {
}, player->lifetime());
}
}
void DocumentOpenClickHandler::Open( void DocumentOpenClickHandler::Open(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<DocumentData*> data, not_null<DocumentData*> data,
@ -503,8 +307,8 @@ void DocumentOpenClickHandler::Open(
return; return;
} }
} }
if (data->isAudioFile() || data->isVideoFile()) { if (data->canBePlayed()) {
StartStreaming(data, origin); Core::App().showDocument(data, context);
return; return;
} }
if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) {
@ -536,25 +340,6 @@ void DocumentOpenClickHandler::Open(
Media::Player::mixer()->play(song); Media::Player::mixer()->play(song);
Media::Player::Updated().notify(song); Media::Player::Updated().notify(song);
} }
} else if (playVideo) {
if (!data->data().isEmpty()) {
Core::App().showDocument(data, context);
} else if (location.accessEnable()) {
Core::App().showDocument(data, context);
location.accessDisable();
} else {
const auto filepath = location.name();
if (Data::IsValidMediaFile(filepath)) {
File::Launch(filepath);
}
}
data->owner().markMediaRead(data);
} else if (data->isVoiceMessage() || data->isAudioFile() || data->isVideoFile()) {
const auto filepath = location.name();
if (Data::IsValidMediaFile(filepath)) {
File::Launch(filepath);
}
data->owner().markMediaRead(data);
} else if (data->size < App::kImageSizeLimit) { } else if (data->size < App::kImageSizeLimit) {
if (!data->data().isEmpty() && playAnimation) { if (!data->data().isEmpty() && playAnimation) {
if (action == ActionOnLoadPlayInline && context) { if (action == ActionOnLoadPlayInline && context) {
@ -610,14 +395,10 @@ void GifOpenClickHandler::onClickImpl() const {
void DocumentSaveClickHandler::Save( void DocumentSaveClickHandler::Save(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<DocumentData*> data, not_null<DocumentData*> data,
HistoryItem *context,
bool forceSavingAs) { bool forceSavingAs) {
if (!data->date) return; if (!data->date) return;
if (data->isAudioFile() || data->isVideoFile()) {
StartStreaming(data, origin);
return;
}
auto filepath = data->filepath( auto filepath = data->filepath(
DocumentData::FilePathResolveSaveFromDataSilent, DocumentData::FilePathResolveSaveFromDataSilent,
forceSavingAs); forceSavingAs);
@ -635,7 +416,7 @@ void DocumentSaveClickHandler::Save(
} }
void DocumentSaveClickHandler::onClickImpl() const { void DocumentSaveClickHandler::onClickImpl() const {
Save(context(), document()); Save(context(), document(), getActionItem());
} }
void DocumentCancelClickHandler::onClickImpl() const { void DocumentCancelClickHandler::onClickImpl() const {
@ -683,51 +464,44 @@ AuthSession &DocumentData::session() const {
return _owner->session(); return _owner->session();
} }
void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes) { void DocumentData::setattributes(
const QVector<MTPDocumentAttribute> &attributes) {
_isImage = false; _isImage = false;
_supportsStreaming = false; _supportsStreaming = false;
for (int32 i = 0, l = attributes.size(); i < l; ++i) { for (const auto &attribute : attributes) {
switch (attributes[i].type()) { attribute.match([&](const MTPDdocumentAttributeImageSize & data) {
case mtpc_documentAttributeImageSize: { dimensions = QSize(data.vw.v, data.vh.v);
auto &d = attributes[i].c_documentAttributeImageSize(); }, [&](const MTPDdocumentAttributeAnimated & data) {
dimensions = QSize(d.vw.v, d.vh.v);
} break;
case mtpc_documentAttributeAnimated:
if (type == FileDocument if (type == FileDocument
|| type == StickerDocument || type == StickerDocument
|| type == VideoDocument) { || type == VideoDocument) {
type = AnimatedDocument; type = AnimatedDocument;
_additional = nullptr; _additional = nullptr;
} break; }
case mtpc_documentAttributeSticker: { }, [&](const MTPDdocumentAttributeSticker & data) {
auto &d = attributes[i].c_documentAttributeSticker();
if (type == FileDocument) { if (type == FileDocument) {
type = StickerDocument; type = StickerDocument;
_additional = std::make_unique<StickerData>(); _additional = std::make_unique<StickerData>();
} }
if (sticker()) { if (sticker()) {
sticker()->alt = qs(d.valt); sticker()->alt = qs(data.valt);
if (sticker()->set.type() != mtpc_inputStickerSetID if (sticker()->set.type() != mtpc_inputStickerSetID
|| d.vstickerset.type() == mtpc_inputStickerSetID) { || data.vstickerset.type() == mtpc_inputStickerSetID) {
sticker()->set = d.vstickerset; sticker()->set = data.vstickerset;
} }
} }
} break; }, [&](const MTPDdocumentAttributeVideo & data) {
case mtpc_documentAttributeVideo: {
auto &d = attributes[i].c_documentAttributeVideo();
if (type == FileDocument) { if (type == FileDocument) {
type = d.is_round_message() type = data.is_round_message()
? RoundVideoDocument ? RoundVideoDocument
: VideoDocument; : VideoDocument;
} }
_duration = d.vduration.v; _duration = data.vduration.v;
_supportsStreaming = d.is_supports_streaming(); _supportsStreaming = data.is_supports_streaming();
dimensions = QSize(d.vw.v, d.vh.v); dimensions = QSize(data.vw.v, data.vh.v);
} break; }, [&](const MTPDdocumentAttributeAudio & data) {
case mtpc_documentAttributeAudio: {
auto &d = attributes[i].c_documentAttributeAudio();
if (type == FileDocument) { if (type == FileDocument) {
if (d.is_voice()) { if (data.is_voice()) {
type = VoiceDocument; type = VoiceDocument;
_additional = std::make_unique<VoiceData>(); _additional = std::make_unique<VoiceData>();
} else { } else {
@ -736,25 +510,19 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
} }
} }
if (const auto voiceData = voice()) { if (const auto voiceData = voice()) {
voiceData->duration = d.vduration.v; voiceData->duration = data.vduration.v;
VoiceWaveform waveform = documentWaveformDecode(qba(d.vwaveform)); voiceData->waveform = documentWaveformDecode(
uchar wavemax = 0; qba(data.vwaveform));
for (int32 i = 0, l = waveform.size(); i < l; ++i) { voiceData->wavemax = voiceData->waveform.empty()
uchar waveat = waveform.at(i); ? uchar(0)
if (wavemax < waveat) wavemax = waveat; : *ranges::max_element(voiceData->waveform);
}
voiceData->waveform = waveform;
voiceData->wavemax = wavemax;
} else if (const auto songData = song()) { } else if (const auto songData = song()) {
songData->duration = d.vduration.v; songData->duration = data.vduration.v;
songData->title = qs(d.vtitle); songData->title = qs(data.vtitle);
songData->performer = qs(d.vperformer); songData->performer = qs(data.vperformer);
} }
} break; }, [&](const MTPDdocumentAttributeFilename & data) {
case mtpc_documentAttributeFilename: { _filename = qs(data.vfile_name);
const auto &attribute = attributes[i];
_filename = qs(
attribute.c_documentAttributeFilename().vfile_name);
// We don't want LTR/RTL mark/embedding/override/isolate chars // We don't want LTR/RTL mark/embedding/override/isolate chars
// in filenames, because they introduce a security issue, when // in filenames, because they introduce a security issue, when
@ -772,8 +540,8 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
for (const auto ch : controls) { for (const auto ch : controls) {
_filename = std::move(_filename).replace(ch, "_"); _filename = std::move(_filename).replace(ch, "_");
} }
} break; }, [&](const MTPDdocumentAttributeHasStickers &data) {
} });
} }
if (type == StickerDocument) { if (type == StickerDocument) {
if (dimensions.width() <= 0 if (dimensions.width() <= 0
@ -1495,8 +1263,27 @@ bool DocumentData::hasRemoteLocation() const {
return (_dc != 0 && _access != 0); return (_dc != 0 && _access != 0);
} }
bool DocumentData::canBeStreamed() const {
return hasRemoteLocation()
&& (isAudioFile()
|| ((isAnimation() || isVideoFile()) && supportsStreaming()));
}
bool DocumentData::canBePlayed() const {
return (isAnimation() || isVideoFile() || isAudioFile())
&& (loaded() || canBeStreamed());
}
auto DocumentData::createStreamingLoader(Data::FileOrigin origin) const auto DocumentData::createStreamingLoader(Data::FileOrigin origin) const
-> std::unique_ptr<Media::Streaming::Loader> { -> std::unique_ptr<Media::Streaming::Loader> {
// #TODO streaming create local file loader
//auto &location = this->location(true);
//if (!_doc->data().isEmpty()) {
// initStreaming();
//} else if (location.accessEnable()) {
// initStreaming();
// location.accessDisable();
//}
return hasRemoteLocation() return hasRemoteLocation()
? std::make_unique<Media::Streaming::LoaderMtproto>( ? std::make_unique<Media::Streaming::LoaderMtproto>(
&session().api(), &session().api(),

View file

@ -223,6 +223,8 @@ public:
const QString &songPerformer); const QString &songPerformer);
[[nodiscard]] QString composeNameString() const; [[nodiscard]] QString composeNameString() const;
[[nodiscard]] bool canBePlayed() const;
[[nodiscard]] bool canBeStreamed() const;
[[nodiscard]] auto createStreamingLoader(Data::FileOrigin origin) const [[nodiscard]] auto createStreamingLoader(Data::FileOrigin origin) const
-> std::unique_ptr<Media::Streaming::Loader>; -> std::unique_ptr<Media::Streaming::Loader>;
@ -303,6 +305,7 @@ public:
static void Save( static void Save(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<DocumentData*> document, not_null<DocumentData*> document,
HistoryItem *context,
bool forceSavingAs = false); bool forceSavingAs = false);
protected: protected:

View file

@ -1092,7 +1092,11 @@ void InnerWidget::savePhotoToFile(PhotoData *photo) {
} }
void InnerWidget::saveDocumentToFile(DocumentData *document) { void InnerWidget::saveDocumentToFile(DocumentData *document) {
DocumentSaveClickHandler::Save(Data::FileOrigin(), document, true); DocumentSaveClickHandler::Save(
Data::FileOrigin(),
document,
nullptr,
true);
} }
void InnerWidget::copyContextImage(PhotoData *photo) { void InnerWidget::copyContextImage(PhotoData *photo) {

View file

@ -1823,7 +1823,11 @@ void HistoryInner::showContextInFolder(not_null<DocumentData*> document) {
void HistoryInner::saveDocumentToFile( void HistoryInner::saveDocumentToFile(
FullMsgId contextId, FullMsgId contextId,
not_null<DocumentData*> document) { not_null<DocumentData*> document) {
DocumentSaveClickHandler::Save(contextId, document, true); DocumentSaveClickHandler::Save(
contextId,
document,
App::histItemById(contextId),
true);
} }
void HistoryInner::openContextGif(FullMsgId itemId) { void HistoryInner::openContextGif(FullMsgId itemId) {

View file

@ -621,7 +621,6 @@ bool HistoryDocument::updateStatusText() const {
} else if (_data->loading()) { } else if (_data->loading()) {
statusSize = _data->loadOffset(); statusSize = _data->loadOffset();
} else if (_data->loaded()) { } else if (_data->loaded()) {
using State = Media::Player::State;
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
if (_data->isVoiceMessage()) { if (_data->isVoiceMessage()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);

View file

@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "media/player/media_player_round_controller.h" #include "media/player/media_player_round_controller.h"
#include "media/view/media_clip_playback.h" #include "media/view/media_view_playback_progress.h"
#include "boxes/confirm_box.h" #include "boxes/confirm_box.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_item.h" #include "history/history_item.h"
@ -833,7 +833,7 @@ Media::Clip::Reader *HistoryGif::currentReader() const {
return (_gif && _gif->ready()) ? _gif.get() : nullptr; return (_gif && _gif->ready()) ? _gif.get() : nullptr;
} }
Media::Clip::Playback *HistoryGif::videoPlayback() const { Media::View::PlaybackProgress *HistoryGif::videoPlayback() const {
if (const auto video = activeRoundVideo()) { if (const auto video = activeRoundVideo()) {
return video->playback(); return video->playback();
} }

View file

@ -14,9 +14,9 @@ struct HistoryMessageReply;
struct HistoryMessageForwarded; struct HistoryMessageForwarded;
namespace Media { namespace Media {
namespace Clip { namespace View {
class Playback; class PlaybackProgress;
} // namespace Clip } // namespace View
namespace Player { namespace Player {
class RoundController; class RoundController;
@ -91,7 +91,7 @@ private:
Media::Player::RoundController *activeRoundVideo() const; Media::Player::RoundController *activeRoundVideo() const;
Media::Clip::Reader *activeRoundPlayer() const; Media::Clip::Reader *activeRoundPlayer() const;
Media::Clip::Reader *currentReader() const; Media::Clip::Reader *currentReader() const;
Media::Clip::Playback *videoPlayback() const; Media::View::PlaybackProgress *videoPlayback() const;
void clipCallback(Media::Clip::Notification notification); void clipCallback(Media::Clip::Notification notification);
bool needInfoDisplay() const; bool needInfoDisplay() const;

View file

@ -226,8 +226,9 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, crl
p.setOpacity(1); p.setOpacity(1);
} }
auto icon = ([this, radial, selected, loaded]() -> const style::icon * { const auto canPlay = _data->canBePlayed();
if (loaded && !radial) { auto icon = [&]() -> const style::icon * {
if (canPlay && !radial) {
return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
} else if (radial || _data->loading()) { } else if (radial || _data->loading()) {
if (_parent->data()->id > 0 || _data->uploading()) { if (_parent->data()->id > 0 || _data->uploading()) {
@ -236,7 +237,7 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, crl
return nullptr; return nullptr;
} }
return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
})(); }();
if (icon) { if (icon) {
icon->paintInCenter(p, inner); icon->paintInCenter(p, inner);
} }
@ -275,7 +276,7 @@ TextState HistoryVideo::textState(QPoint point, StateRequest request) const {
} }
auto result = TextState(_parent); auto result = TextState(_parent);
bool loaded = _data->loaded(); const auto canPlay = _data->canBePlayed();
auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto paintx = 0, painty = 0, paintw = width(), painth = height();
bool bubble = _parent->hasBubble(); bool bubble = _parent->hasBubble();
@ -300,7 +301,7 @@ TextState HistoryVideo::textState(QPoint point, StateRequest request) const {
if (_data->uploading()) { if (_data->uploading()) {
result.link = _cancell; result.link = _cancell;
} else { } else {
result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel); result.link = canPlay ? _openl : (_data->loading() ? _cancell : _savel);
} }
} }
if (_caption.isEmpty() && _parent->media() == this) { if (_caption.isEmpty() && _parent->media() == this) {
@ -389,10 +390,11 @@ void HistoryVideo::drawGrouped(
p.drawEllipse(inner); p.drawEllipse(inner);
} }
const auto canPlay = _data->canBePlayed();
auto icon = [&]() -> const style::icon * { auto icon = [&]() -> const style::icon * {
if (_data->waitingForAlbum()) { if (_data->waitingForAlbum()) {
return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
} else if (loaded && !radial) { } else if (canPlay && !radial) {
return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
} else if (radial || _data->loading()) { } else if (radial || _data->loading()) {
if (_parent->data()->id > 0 || _data->uploading()) { if (_parent->data()->id > 0 || _data->uploading()) {
@ -436,7 +438,7 @@ TextState HistoryVideo::getStateGrouped(
} }
return TextState(_parent, _data->uploading() return TextState(_parent, _data->uploading()
? _cancell ? _cancell
: _data->loaded() : _data->canBePlayed()
? _openl ? _openl
: _data->loading() : _data->loading()
? _cancell ? _cancell

View file

@ -122,6 +122,9 @@ void AddSaveDocumentAction(
not_null<Ui::PopupMenu*> menu, not_null<Ui::PopupMenu*> menu,
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<DocumentData*> document) { not_null<DocumentData*> document) {
const auto save = [=] {
DocumentSaveClickHandler::Save(origin, document, nullptr, true);
};
menu->addAction( menu->addAction(
lang(document->isVideoFile() lang(document->isVideoFile()
? lng_context_save_video ? lng_context_save_video
@ -135,7 +138,7 @@ void AddSaveDocumentAction(
App::LambdaDelayed( App::LambdaDelayed(
st::defaultDropdownMenu.menu.ripple.hideDuration, st::defaultDropdownMenu.menu.ripple.hideDuration,
&Auth(), &Auth(),
[=] { DocumentSaveClickHandler::Save(origin, document, true); })); save));
} }
void AddDocumentActions( void AddDocumentActions(

View file

@ -1277,6 +1277,7 @@ void ListWidget::showContextMenu(
DocumentSaveClickHandler::Save( DocumentSaveClickHandler::Save(
itemFullId, itemFullId,
document, document,
App::histItemById(itemFullId),
true); true);
}); });
_contextMenu->addAction( _contextMenu->addAction(

View file

@ -894,7 +894,6 @@ bool File::updateStatusText() const {
} else if (_document->loading()) { } else if (_document->loading()) {
statusSize = _document->loadOffset(); statusSize = _document->loadOffset();
} else if (_document->loaded()) { } else if (_document->loaded()) {
using State = Media::Player::State;
if (_document->isVoiceMessage()) { if (_document->isVoiceMessage()) {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);

View file

@ -729,10 +729,10 @@ void Mixer::resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered
return; return;
} }
const auto stoppedAtEnd = (alState == AL_STOPPED) const auto stoppedAtEnd = track->state.waitingForData
&& (!IsStopped(track->state.state) || ((alState == AL_STOPPED)
|| IsStoppedAtEnd(track->state.state)) && (!IsStopped(track->state.state)
|| track->state.waitingForData; || IsStoppedAtEnd(track->state.state)));
positionInBuffered = stoppedAtEnd positionInBuffered = stoppedAtEnd
? track->bufferedLength ? track->bufferedLength
: alSampleOffset; : alSampleOffset;
@ -1438,10 +1438,10 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
} }
int32 emitSignals = 0; int32 emitSignals = 0;
const auto stoppedAtEnd = (alState == AL_STOPPED) const auto stoppedAtEnd = track->state.waitingForData
&& (!IsStopped(track->state.state) || ((alState == AL_STOPPED)
|| IsStoppedAtEnd(track->state.state)) && (!IsStopped(track->state.state)
|| track->state.waitingForData; || IsStoppedAtEnd(track->state.state)));
const auto positionInBuffered = stoppedAtEnd const auto positionInBuffered = stoppedAtEnd
? track->bufferedLength ? track->bufferedLength
: alSampleOffset; : alSampleOffset;

View file

@ -47,5 +47,5 @@ private:
}; };
} // namespace Clip } // namespace Player
} // namespace Media } // namespace Media

View file

@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/continuous_sliders.h" #include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/view/media_clip_playback.h" #include "media/view/media_view_playback_progress.h"
#include "media/player/media_player_button.h" #include "media/player/media_player_button.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "media/player/media_player_volume_controller.h" #include "media/player/media_player_volume_controller.h"
@ -62,7 +62,7 @@ CoverWidget::CoverWidget(QWidget *parent) : RpWidget(parent)
, _timeLabel(this, st::mediaPlayerTime) , _timeLabel(this, st::mediaPlayerTime)
, _close(this, st::mediaPlayerPanelClose) , _close(this, st::mediaPlayerPanelClose)
, _playbackSlider(this, st::mediaPlayerPanelPlayback) , _playbackSlider(this, st::mediaPlayerPanelPlayback)
, _playback(std::make_unique<Clip::Playback>()) , _playbackProgress(std::make_unique<View::PlaybackProgress>())
, _playPause(this) , _playPause(this)
, _volumeToggle(this, st::mediaPlayerVolumeToggle) , _volumeToggle(this, st::mediaPlayerVolumeToggle)
, _volumeController(this) , _volumeController(this)
@ -76,18 +76,18 @@ CoverWidget::CoverWidget(QWidget *parent) : RpWidget(parent)
_timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
setMouseTracking(true); setMouseTracking(true);
_playback->setInLoadingStateChangedCallback([=](bool loading) { _playbackProgress->setInLoadingStateChangedCallback([=](bool loading) {
_playbackSlider->setDisabled(loading); _playbackSlider->setDisabled(loading);
}); });
_playback->setValueChangedCallback([=](float64 value) { _playbackProgress->setValueChangedCallback([=](float64 value) {
_playbackSlider->setValue(value); _playbackSlider->setValue(value);
}); });
_playbackSlider->setChangeProgressCallback([=](float64 value) { _playbackSlider->setChangeProgressCallback([=](float64 value) {
_playback->setValue(value, false); _playbackProgress->setValue(value, false);
handleSeekProgress(value); handleSeekProgress(value);
}); });
_playbackSlider->setChangeFinishedCallback([=](float64 value) { _playbackSlider->setChangeFinishedCallback([=](float64 value) {
_playback->setValue(value, false); _playbackProgress->setValue(value, false);
handleSeekFinished(value); handleSeekFinished(value);
}); });
_playPause->setClickedCallback([=] { _playPause->setClickedCallback([=] {
@ -240,9 +240,9 @@ void CoverWidget::handleSongUpdate(const TrackState &state) {
} }
if (state.id.audio()->loading()) { if (state.id.audio()->loading()) {
_playback->updateLoadingState(state.id.audio()->progress()); _playbackProgress->updateLoadingState(state.id.audio()->progress());
} else { } else {
_playback->updateState(state); _playbackProgress->updateState(state);
} }
auto stopped = IsStoppedOrStopping(state.state); auto stopped = IsStoppedOrStopping(state.state);

View file

@ -19,9 +19,9 @@ class MediaSlider;
} // namespace Ui } // namespace Ui
namespace Media { namespace Media {
namespace Clip { namespace View {
class Playback; class PlaybackProgress;
} // namespace Clip } // namespace View
namespace Player { namespace Player {
@ -71,7 +71,7 @@ private:
object_ptr<Ui::LabelSimple> _timeLabel; object_ptr<Ui::LabelSimple> _timeLabel;
object_ptr<Ui::IconButton> _close; object_ptr<Ui::IconButton> _close;
object_ptr<Ui::MediaSlider> _playbackSlider; object_ptr<Ui::MediaSlider> _playbackSlider;
std::unique_ptr<Clip::Playback> _playback; std::unique_ptr<View::PlaybackProgress> _playbackProgress;
object_ptr<Ui::IconButton> _previousTrack = { nullptr }; object_ptr<Ui::IconButton> _previousTrack = { nullptr };
object_ptr<PlayButton> _playPause; object_ptr<PlayButton> _playPause;
object_ptr<Ui::IconButton> _nextTrack = { nullptr }; object_ptr<Ui::IconButton> _nextTrack = { nullptr };
@ -82,5 +82,5 @@ private:
}; };
} // namespace Clip } // namespace Player
} // namespace Media } // namespace Media

View file

@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "media/view/media_clip_playback.h" #include "media/view/media_view_playback_progress.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "media/player/media_player_round_controller.h" #include "media/player/media_player_round_controller.h"
#include "window/window_controller.h" #include "window/window_controller.h"
@ -210,7 +210,7 @@ Clip::Reader *Float::getReader() const {
return nullptr; return nullptr;
} }
Clip::Playback *Float::getPlayback() const { View::PlaybackProgress *Float::getPlayback() const {
if (detached()) { if (detached()) {
return nullptr; return nullptr;
} }

View file

@ -16,9 +16,9 @@ enum class Column;
} // namespace Window } // namespace Window
namespace Media { namespace Media {
namespace Clip { namespace View {
class Playback; class PlaybackProgress;
} // namespace Clip } // namespace View
namespace Player { namespace Player {
@ -70,7 +70,7 @@ protected:
private: private:
float64 outRatio() const; float64 outRatio() const;
Clip::Reader *getReader() const; Clip::Reader *getReader() const;
Clip::Playback *getPlayback() const; View::PlaybackProgress *getPlayback() const;
void repaintItem(); void repaintItem();
void prepareShadow(); void prepareShadow();
bool hasFrame() const; bool hasFrame() const;

View file

@ -212,5 +212,5 @@ private:
}; };
} // namespace Clip } // namespace Player
} // namespace Media } // namespace Media

View file

@ -118,5 +118,5 @@ private:
}; };
} // namespace Clip } // namespace Player
} // namespace Media } // namespace Media

View file

@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "media/view/media_clip_playback.h" #include "media/view/media_view_playback_progress.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
@ -56,8 +56,8 @@ RoundController::RoundController(
_context->fullId(), _context->fullId(),
[=](Clip::Notification notification) { callback(notification); }, [=](Clip::Notification notification) { callback(notification); },
Clip::Reader::Mode::Video); Clip::Reader::Mode::Video);
_playback = std::make_unique<Clip::Playback>(); _playbackProgress = std::make_unique<View::PlaybackProgress>();
_playback->setValueChangedCallback([=](float64 value) { _playbackProgress->setValueChangedCallback([=](float64 value) {
Auth().data().requestItemRepaint(_context); Auth().data().requestItemRepaint(_context);
}); });
Auth().data().markMediaRead(_data); Auth().data().markMediaRead(_data);
@ -95,8 +95,8 @@ Clip::Reader *RoundController::reader() const {
return _reader ? _reader.get() : nullptr; return _reader ? _reader.get() : nullptr;
} }
Clip::Playback *RoundController::playback() const { View::PlaybackProgress *RoundController::playback() const {
return _playback.get(); return _playbackProgress.get();
} }
void RoundController::handleAudioUpdate(const TrackState &state) { void RoundController::handleAudioUpdate(const TrackState &state) {
@ -112,8 +112,8 @@ void RoundController::handleAudioUpdate(const TrackState &state) {
} else if (another) { } else if (another) {
return; return;
} }
if (_playback) { if (_playbackProgress) {
_playback->updateState(state); _playbackProgress->updateState(state);
} }
if (IsPaused(state.state) || state.state == State::Pausing) { if (IsPaused(state.state) || state.state == State::Pausing) {
if (!_reader->videoPaused()) { if (!_reader->videoPaused()) {

View file

@ -15,9 +15,9 @@ class Controller;
} // namespace Window } // namespace Window
namespace Media { namespace Media {
namespace Clip { namespace View {
class Playback; class PlaybackProgress;
} // namespace Clip } // namespace View
} // namespace Media } // namespace Media
namespace Media { namespace Media {
@ -39,7 +39,7 @@ public:
FullMsgId contextId() const; FullMsgId contextId() const;
void pauseResume(); void pauseResume();
Clip::Reader *reader() const; Clip::Reader *reader() const;
Clip::Playback *playback() const; View::PlaybackProgress *playback() const;
rpl::lifetime &lifetime(); rpl::lifetime &lifetime();
@ -59,7 +59,7 @@ private:
not_null<DocumentData*> _data; not_null<DocumentData*> _data;
not_null<HistoryItem*> _context; not_null<HistoryItem*> _context;
Clip::ReaderPointer _reader; Clip::ReaderPointer _reader;
std::unique_ptr<Clip::Playback> _playback; std::unique_ptr<View::PlaybackProgress> _playbackProgress;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;

View file

@ -74,5 +74,5 @@ private:
}; };
} // namespace Clip } // namespace Player
} // namespace Media } // namespace Media

View file

@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/view/media_clip_playback.h" #include "media/view/media_view_playback_progress.h"
#include "media/player/media_player_button.h" #include "media/player/media_player_button.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "media/player/media_player_volume_controller.h" #include "media/player/media_player_volume_controller.h"
@ -86,7 +86,7 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
, _close(this, st::mediaPlayerClose) , _close(this, st::mediaPlayerClose)
, _shadow(this) , _shadow(this)
, _playbackSlider(this, st::mediaPlayerPlayback) , _playbackSlider(this, st::mediaPlayerPlayback)
, _playback(std::make_unique<Clip::Playback>()) { , _playbackProgress(std::make_unique<View::PlaybackProgress>()) {
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
setMouseTracking(true); setMouseTracking(true);
resize(width(), st::mediaPlayerHeight + st::lineWidth); resize(width(), st::mediaPlayerHeight + st::lineWidth);
@ -94,24 +94,24 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
_nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
_timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
_playback->setInLoadingStateChangedCallback([this](bool loading) { _playbackProgress->setInLoadingStateChangedCallback([this](bool loading) {
_playbackSlider->setDisabled(loading); _playbackSlider->setDisabled(loading);
}); });
_playback->setValueChangedCallback([this](float64 value) { _playbackProgress->setValueChangedCallback([this](float64 value) {
_playbackSlider->setValue(value); _playbackSlider->setValue(value);
}); });
_playbackSlider->setChangeProgressCallback([this](float64 value) { _playbackSlider->setChangeProgressCallback([this](float64 value) {
if (_type != AudioMsgId::Type::Song) { if (_type != AudioMsgId::Type::Song) {
return; // Round video seek is not supported for now :( return; // Round video seek is not supported for now :(
} }
_playback->setValue(value, false); _playbackProgress->setValue(value, false);
handleSeekProgress(value); handleSeekProgress(value);
}); });
_playbackSlider->setChangeFinishedCallback([this](float64 value) { _playbackSlider->setChangeFinishedCallback([this](float64 value) {
if (_type != AudioMsgId::Type::Song) { if (_type != AudioMsgId::Type::Song) {
return; // Round video seek is not supported for now :( return; // Round video seek is not supported for now :(
} }
_playback->setValue(value, false); _playbackProgress->setValue(value, false);
handleSeekFinished(value); handleSeekFinished(value);
}); });
_playPause->setClickedCallback([this] { _playPause->setClickedCallback([this] {
@ -430,9 +430,9 @@ void Widget::handleSongUpdate(const TrackState &state) {
} }
if (state.id.audio()->loading()) { if (state.id.audio()->loading()) {
_playback->updateLoadingState(state.id.audio()->progress()); _playbackProgress->updateLoadingState(state.id.audio()->progress());
} else { } else {
_playback->updateState(state); _playbackProgress->updateState(state);
} }
auto stopped = IsStoppedOrStopping(state.state); auto stopped = IsStoppedOrStopping(state.state);

View file

@ -20,10 +20,12 @@ class FilledSlider;
} // namespace Ui } // namespace Ui
namespace Media { namespace Media {
namespace Clip { namespace View {
class Playback; class PlaybackProgress;
} // namespace Clip } // namespace Clip
} // namespace Media
namespace Media {
namespace Player { namespace Player {
class PlayButton; class PlayButton;
@ -109,11 +111,11 @@ private:
object_ptr<Ui::IconButton> _close; object_ptr<Ui::IconButton> _close;
object_ptr<Ui::PlainShadow> _shadow = { nullptr }; object_ptr<Ui::PlainShadow> _shadow = { nullptr };
object_ptr<Ui::FilledSlider> _playbackSlider; object_ptr<Ui::FilledSlider> _playbackSlider;
std::unique_ptr<Clip::Playback> _playback; std::unique_ptr<View::PlaybackProgress> _playbackProgress;
rpl::lifetime _playlistChangesLifetime; rpl::lifetime _playlistChangesLifetime;
}; };
} // namespace Clip } // namespace Player
} // namespace Media } // namespace Media

View file

@ -36,6 +36,7 @@ struct PlaybackOptions {
crl::time position = 0; crl::time position = 0;
float64 speed = 1.; // Valid values between 0.5 and 2. float64 speed = 1.; // Valid values between 0.5 and 2.
bool syncVideoByAudio = true; bool syncVideoByAudio = true;
bool dropStaleFrames = true;
}; };
struct TrackState { struct TrackState {

View file

@ -513,7 +513,7 @@ bool Player::failed() const {
} }
bool Player::playing() const { bool Player::playing() const {
return (_stage == Stage::Started) && !_paused; return (_stage == Stage::Started) && !_paused && !finished();
} }
bool Player::buffering() const { bool Player::buffering() const {
@ -524,6 +524,12 @@ bool Player::paused() const {
return _pausedByUser; return _pausedByUser;
} }
bool Player::finished() const {
return (_stage == Stage::Started)
&& (!_audio || _audioFinished)
&& (!_video || _videoFinished);
}
void Player::setSpeed(float64 speed) { void Player::setSpeed(float64 speed) {
Expects(valid()); Expects(valid());
Expects(speed >= 0.5 && speed <= 2.); Expects(speed >= 0.5 && speed <= 2.);

View file

@ -44,10 +44,11 @@ public:
float64 speed() const; float64 speed() const;
void setSpeed(float64 speed); // 0.5 <= speed <= 2. void setSpeed(float64 speed); // 0.5 <= speed <= 2.
[[nodiscard]] bool failed() const;
[[nodiscard]] bool playing() const; [[nodiscard]] bool playing() const;
[[nodiscard]] bool buffering() const; [[nodiscard]] bool buffering() const;
[[nodiscard]] bool paused() const; [[nodiscard]] bool paused() const;
[[nodiscard]] bool failed() const;
[[nodiscard]] bool finished() const;
[[nodiscard]] rpl::producer<Update, Error> updates() const; [[nodiscard]] rpl::producer<Update, Error> updates() const;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_utility.h" #include "media/streaming/media_streaming_utility.h"
#include "media/streaming/media_streaming_common.h" #include "media/streaming/media_streaming_common.h"
#include "ui/image/image_prepare.h"
extern "C" { extern "C" {
#include <libavutil/opt.h> #include <libavutil/opt.h>
@ -20,6 +21,7 @@ namespace {
constexpr auto kSkipInvalidDataPackets = 10; constexpr auto kSkipInvalidDataPackets = 10;
constexpr auto kAlignImageBy = 16; constexpr auto kAlignImageBy = 16;
constexpr auto kPixelBytesSize = 4; constexpr auto kPixelBytesSize = 4;
constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied;
void AlignedImageBufferCleanupHandler(void* data) { void AlignedImageBufferCleanupHandler(void* data) {
const auto buffer = static_cast<uchar*>(data); const auto buffer = static_cast<uchar*>(data);
@ -39,8 +41,16 @@ void ClearFrameMemory(AVFrame *frame) {
} // namespace } // namespace
bool GoodStorageForFrame(const QImage &storage, QSize size) {
return !storage.isNull()
&& (storage.format() == kImageFormat)
&& (storage.size() == size)
&& storage.isDetached()
&& IsAlignedImage(storage);
}
// Create a QImage of desired size where all the data is properly aligned. // Create a QImage of desired size where all the data is properly aligned.
QImage CreateImageForOriginalFrame(QSize size) { QImage CreateFrameStorage(QSize size) {
const auto width = size.width(); const auto width = size.width();
const auto height = size.height(); const auto height = size.height();
const auto widthAlign = kAlignImageBy / kPixelBytesSize; const auto widthAlign = kAlignImageBy / kPixelBytesSize;
@ -59,7 +69,7 @@ QImage CreateImageForOriginalFrame(QSize size) {
width, width,
height, height,
perLine, perLine,
QImage::Format_ARGB32_Premultiplied, kImageFormat,
AlignedImageBufferCleanupHandler, AlignedImageBufferCleanupHandler,
cleanupData); cleanupData);
} }
@ -267,18 +277,26 @@ AvErrorWrap ReadNextFrame(Stream &stream) {
return error; return error;
} }
QImage ConvertFrame( bool GoodForRequest(const QImage &image, const FrameRequest &request) {
Stream &stream, if (request.resize.isEmpty()) {
QSize resize, return true;
QImage storage) { } else if ((request.radius != ImageRoundRadius::None)
&& ((request.corners & RectPart::AllCorners) != 0)) {
return false;
}
return (request.resize == request.outer)
&& (request.resize == image.size());
}
QImage ConvertFrame(Stream &stream, QSize resize, QImage storage) {
Expects(stream.frame != nullptr); Expects(stream.frame != nullptr);
const auto frame = stream.frame.get(); const auto frame = stream.frame.get();
const auto frameSize = QSize(frame->width, frame->height); const auto frameSize = QSize(frame->width, frame->height);
if (frameSize.isEmpty()) { if (frameSize.isEmpty()) {
LOG(("Streaming Error: Bad frame size %1,%2" LOG(("Streaming Error: Bad frame size %1,%2"
).arg(resize.width() ).arg(frameSize.width()
).arg(resize.height())); ).arg(frameSize.height()));
return QImage(); return QImage();
} else if (!frame->data[0]) { } else if (!frame->data[0]) {
LOG(("Streaming Error: Bad frame data.")); LOG(("Streaming Error: Bad frame data."));
@ -289,11 +307,9 @@ QImage ConvertFrame(
} else if (RotationSwapWidthHeight(stream.rotation)) { } else if (RotationSwapWidthHeight(stream.rotation)) {
resize.transpose(); resize.transpose();
} }
if (storage.isNull()
|| storage.size() != resize if (!GoodStorageForFrame(storage, resize)) {
|| !storage.isDetached() storage = CreateFrameStorage(resize);
|| !IsAlignedImage(storage)) {
storage = CreateImageForOriginalFrame(resize);
} }
const auto format = AV_PIX_FMT_BGRA; const auto format = AV_PIX_FMT_BGRA;
const auto hasDesiredFormat = (frame->format == format) const auto hasDesiredFormat = (frame->format == format)
@ -343,7 +359,24 @@ QImage ConvertFrame(
return QImage(); return QImage();
} }
} }
ClearFrameMemory(stream.frame.get()); return storage;
}
QImage PrepareByRequest(
const QImage &original,
const FrameRequest &request,
QImage storage) {
Expects(!request.outer.isEmpty());
if (!GoodStorageForFrame(storage, request.outer)) {
storage = CreateFrameStorage(request.outer);
}
{
Painter p(&storage);
PainterHighQualityEnabler hq(p);
p.drawImage(QRect(QPoint(), request.outer), original);
}
// #TODO streaming later full prepare support.
return storage; return storage;
} }

View file

@ -173,11 +173,19 @@ void LogError(QLatin1String method, AvErrorWrap error);
[[nodiscard]] AvErrorWrap ProcessPacket(Stream &stream, Packet &&packet); [[nodiscard]] AvErrorWrap ProcessPacket(Stream &stream, Packet &&packet);
[[nodiscard]] AvErrorWrap ReadNextFrame(Stream &stream); [[nodiscard]] AvErrorWrap ReadNextFrame(Stream &stream);
[[nodiscard]] QImage CreateImageForOriginalFrame(QSize size); [[nodiscard]] bool GoodForRequest(
const QImage &image,
const FrameRequest &request);
[[nodiscard]] bool GoodStorageForFrame(const QImage &storage, QSize size);
[[nodiscard]] QImage CreateFrameStorage(QSize size);
[[nodiscard]] QImage ConvertFrame( [[nodiscard]] QImage ConvertFrame(
Stream& stream, Stream& stream,
QSize resize, QSize resize,
QImage storage); QImage storage);
[[nodiscard]] QImage PrepareByRequest(
const QImage &original,
const FrameRequest &request,
QImage storage);
} // namespace Streaming } // namespace Streaming
} // namespace Media } // namespace Media

View file

@ -43,6 +43,7 @@ public:
void setSpeed(float64 speed); void setSpeed(float64 speed);
void interrupt(); void interrupt();
void frameDisplayed(); void frameDisplayed();
void updateFrameRequest(const FrameRequest &request);
private: private:
[[nodiscard]] bool interrupted() const; [[nodiscard]] bool interrupted() const;
@ -79,6 +80,7 @@ private:
crl::time _nextFrameDisplayTime = kTimeUnknown; crl::time _nextFrameDisplayTime = kTimeUnknown;
rpl::event_stream<crl::time> _nextFrameTimeUpdates; rpl::event_stream<crl::time> _nextFrameTimeUpdates;
rpl::event_stream<> _waitingForData; rpl::event_stream<> _waitingForData;
FrameRequest _request;
bool _queued = false; bool _queued = false;
base::ConcurrentTimer _readFramesTimer; base::ConcurrentTimer _readFramesTimer;
@ -150,10 +152,15 @@ void VideoTrackObject::readFrames() {
if (interrupted()) { if (interrupted()) {
return; return;
} }
const auto state = _shared->prepareState(trackTime().trackTime); const auto time = trackTime().trackTime;
const auto dropStaleFrames = _options.dropStaleFrames;
const auto state = _shared->prepareState(time, dropStaleFrames);
state.match([&](Shared::PrepareFrame frame) { state.match([&](Shared::PrepareFrame frame) {
if (readFrame(frame)) { while (readFrame(frame)) {
presentFrameIfNeeded(); if (!dropStaleFrames || !VideoTrack::IsStale(frame, time)) {
presentFrameIfNeeded();
break;
}
} }
}, [&](Shared::PrepareNextCheck delay) { }, [&](Shared::PrepareNextCheck delay) {
Expects(delay > 0); Expects(delay > 0);
@ -183,17 +190,8 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
_error(); _error();
return false; return false;
} }
frame->original = ConvertFrame(
_stream,
QSize(),
std::move(frame->original));
frame->position = position; frame->position = position;
frame->displayed = kTimeUnknown; frame->displayed = kTimeUnknown;
// #TODO streaming later prepare frame
//frame->request
//frame->prepared
return true; return true;
} }
@ -202,7 +200,24 @@ void VideoTrackObject::presentFrameIfNeeded() {
return; return;
} }
const auto time = trackTime(); const auto time = trackTime();
const auto presented = _shared->presentFrame(time.trackTime); const auto prepare = [&](not_null<Frame*> frame) {
frame->request = _request;
frame->original = ConvertFrame(
_stream,
frame->request.resize,
std::move(frame->original));
if (frame->original.isNull()) {
frame->prepared = QImage();
interrupt();
_error();
return;
}
VideoTrack::PrepareFrameByRequest(frame);
Ensures(VideoTrack::IsPrepared(frame));
};
const auto presented = _shared->presentFrame(time.trackTime, prepare);
if (presented.displayPosition != kTimeUnknown) { if (presented.displayPosition != kTimeUnknown) {
const auto trackLeft = presented.displayPosition - time.trackTime; const auto trackLeft = presented.displayPosition - time.trackTime;
@ -268,6 +283,10 @@ void VideoTrackObject::frameDisplayed() {
queueReadFrames(); queueReadFrames();
} }
void VideoTrackObject::updateFrameRequest(const FrameRequest &request) {
_request = request;
}
bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) { bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) {
if (ProcessPacket(_stream, std::move(packet)).failed()) { if (ProcessPacket(_stream, std::move(packet)).failed()) {
return false; return false;
@ -412,31 +431,20 @@ not_null<VideoTrack::Frame*> VideoTrack::Shared::getFrame(int index) {
return &_frames[index]; return &_frames[index];
} }
bool VideoTrack::Shared::IsPrepared(not_null<Frame*> frame) { auto VideoTrack::Shared::prepareState(
return (frame->position != kTimeUnknown) crl::time trackTime,
&& (frame->displayed == kTimeUnknown) bool dropStaleFrames)
&& !frame->original.isNull(); -> PrepareState {
}
bool VideoTrack::Shared::IsStale(
not_null<Frame*> frame,
crl::time trackTime) {
Expects(IsPrepared(frame));
return (frame->position < trackTime);
}
auto VideoTrack::Shared::prepareState(crl::time trackTime) -> PrepareState {
const auto prepareNext = [&](int index) -> PrepareState { const auto prepareNext = [&](int index) -> PrepareState {
const auto frame = getFrame(index); const auto frame = getFrame(index);
const auto next = getFrame((index + 1) % kFramesCount); const auto next = getFrame((index + 1) % kFramesCount);
if (!IsPrepared(frame)) { if (!IsDecoded(frame)) {
return frame; return frame;
} else if (IsStale(frame, trackTime)) { } else if (dropStaleFrames && IsStale(frame, trackTime)) {
std::swap(*frame, *next); std::swap(*frame, *next);
next->displayed = kDisplaySkipped; next->displayed = kDisplaySkipped;
return IsPrepared(frame) ? next : frame; return IsDecoded(frame) ? next : frame;
} else if (!IsPrepared(next)) { } else if (!IsDecoded(next)) {
return next; return next;
} else { } else {
return PrepareNextCheck(frame->position - trackTime + 1); return PrepareNextCheck(frame->position - trackTime + 1);
@ -445,7 +453,7 @@ auto VideoTrack::Shared::prepareState(crl::time trackTime) -> PrepareState {
const auto finishPrepare = [&](int index) { const auto finishPrepare = [&](int index) {
const auto frame = getFrame(index); const auto frame = getFrame(index);
// If player already awaits next frame - we ignore if it's stale. // If player already awaits next frame - we ignore if it's stale.
return IsPrepared(frame) ? std::nullopt : PrepareState(frame); return IsDecoded(frame) ? std::nullopt : PrepareState(frame);
}; };
switch (counter()) { switch (counter()) {
@ -461,11 +469,18 @@ auto VideoTrack::Shared::prepareState(crl::time trackTime) -> PrepareState {
Unexpected("Counter value in VideoTrack::Shared::prepareState."); Unexpected("Counter value in VideoTrack::Shared::prepareState.");
} }
auto VideoTrack::Shared::presentFrame(crl::time trackTime) -> PresentFrame { template <typename PrepareCallback>
auto VideoTrack::Shared::presentFrame(
crl::time trackTime,
PrepareCallback &&prepare)
-> PresentFrame {
const auto present = [&](int counter, int index) -> PresentFrame { const auto present = [&](int counter, int index) -> PresentFrame {
const auto frame = getFrame(index); const auto frame = getFrame(index);
Assert(IsPrepared(frame));
const auto position = frame->position; const auto position = frame->position;
prepare(frame);
if (!IsPrepared(frame)) {
return { kTimeUnknown, crl::time(0) };
}
// Release this frame to the main thread for rendering. // Release this frame to the main thread for rendering.
_counter.store( _counter.store(
@ -476,8 +491,8 @@ auto VideoTrack::Shared::presentFrame(crl::time trackTime) -> PresentFrame {
const auto nextCheckDelay = [&](int index) -> PresentFrame { const auto nextCheckDelay = [&](int index) -> PresentFrame {
const auto frame = getFrame(index); const auto frame = getFrame(index);
const auto next = getFrame((index + 1) % kFramesCount); const auto next = getFrame((index + 1) % kFramesCount);
if (!IsPrepared(frame) if (!IsDecoded(frame)
|| !IsPrepared(next) || !IsDecoded(next)
|| IsStale(frame, trackTime)) { || IsStale(frame, trackTime)) {
return { kTimeUnknown, crl::time(0) }; return { kTimeUnknown, crl::time(0) };
} }
@ -594,24 +609,50 @@ crl::time VideoTrack::markFrameDisplayed(crl::time now) {
return position; return position;
} }
QImage VideoTrack::frame(const FrameRequest &request) const { QImage VideoTrack::frame(const FrameRequest &request) {
const auto frame = _shared->frameForPaint(); const auto frame = _shared->frameForPaint();
Assert(frame != nullptr); const auto changed = (frame->request != request);
Assert(!frame->original.isNull()); if (changed) {
frame->request = request;
_wrapped.with([=](Implementation &unwrapped) {
unwrapped.updateFrameRequest(request);
});
}
return PrepareFrameByRequest(frame, !changed);
}
if (request.resize.isEmpty()) { QImage VideoTrack::PrepareFrameByRequest(
not_null<Frame*> frame,
bool useExistingPrepared) {
Expects(!frame->original.isNull());
if (GoodForRequest(frame->original, frame->request)) {
return frame->original; return frame->original;
} else if (frame->prepared.isNull() || frame->request != request) { } else if (frame->prepared.isNull() || !useExistingPrepared) {
// #TODO streaming later prepare frame frame->prepared = PrepareByRequest(
//frame->request = request; frame->original,
//frame->prepared = PrepareFrame( frame->request,
// frame->original, std::move(frame->prepared));
// request,
// std::move(frame->prepared));
} }
return frame->prepared; return frame->prepared;
} }
bool VideoTrack::IsDecoded(not_null<Frame*> frame) {
return (frame->position != kTimeUnknown)
&& (frame->displayed == kTimeUnknown);
}
bool VideoTrack::IsPrepared(not_null<Frame*> frame) {
return IsDecoded(frame)
&& !frame->original.isNull();
}
bool VideoTrack::IsStale(not_null<Frame*> frame, crl::time trackTime) {
Expects(IsDecoded(frame));
return (frame->position < trackTime);
}
rpl::producer<crl::time> VideoTrack::renderNextFrame() const { rpl::producer<crl::time> VideoTrack::renderNextFrame() const {
return _wrapped.producer_on_main([](const Implementation &unwrapped) { return _wrapped.producer_on_main([](const Implementation &unwrapped) {
return unwrapped.displayFrameAt(); return unwrapped.displayFrameAt();

View file

@ -46,7 +46,7 @@ public:
// Called from the main thread. // Called from the main thread.
// Returns the position of the displayed frame. // Returns the position of the displayed frame.
[[nodiscard]] crl::time markFrameDisplayed(crl::time now); [[nodiscard]] crl::time markFrameDisplayed(crl::time now);
[[nodiscard]] QImage frame(const FrameRequest &request) const; [[nodiscard]] QImage frame(const FrameRequest &request);
[[nodiscard]] rpl::producer<crl::time> renderNextFrame() const; [[nodiscard]] rpl::producer<crl::time> renderNextFrame() const;
[[nodiscard]] rpl::producer<> waitingForData() const; [[nodiscard]] rpl::producer<> waitingForData() const;
@ -81,8 +81,15 @@ private:
void init(QImage &&cover, crl::time position); void init(QImage &&cover, crl::time position);
[[nodiscard]] bool initialized() const; [[nodiscard]] bool initialized() const;
[[nodiscard]] PrepareState prepareState(crl::time trackTime); [[nodiscard]] PrepareState prepareState(
[[nodiscard]] PresentFrame presentFrame(crl::time trackTime); crl::time trackTime,
bool dropStaleFrames);
// PrepareCallback(not_null<Frame*>).
template <typename PrepareCallback>
[[nodiscard]] PresentFrame presentFrame(
crl::time trackTime,
PrepareCallback &&prepare);
// Called from the main thread. // Called from the main thread.
// Returns the position of the displayed frame. // Returns the position of the displayed frame.
@ -91,10 +98,6 @@ private:
private: private:
[[nodiscard]] not_null<Frame*> getFrame(int index); [[nodiscard]] not_null<Frame*> getFrame(int index);
[[nodiscard]] static bool IsPrepared(not_null<Frame*> frame);
[[nodiscard]] static bool IsStale(
not_null<Frame*> frame,
crl::time trackTime);
[[nodiscard]] int counter() const; [[nodiscard]] int counter() const;
static constexpr auto kCounterUninitialized = -1; static constexpr auto kCounterUninitialized = -1;
@ -105,6 +108,15 @@ private:
}; };
static QImage PrepareFrameByRequest(
not_null<Frame*> frame,
bool useExistingPrepared = false);
[[nodiscard]] static bool IsDecoded(not_null<Frame*> frame);
[[nodiscard]] static bool IsPrepared(not_null<Frame*> frame);
[[nodiscard]] static bool IsStale(
not_null<Frame*> frame,
crl::time trackTime);
const int _streamIndex = 0; const int _streamIndex = 0;
const AVRational _streamTimeBase; const AVRational _streamTimeBase;
//const int _streamRotation = 0; //const int _streamRotation = 0;

View file

@ -650,7 +650,7 @@ bool GroupThumbs::hidden() const {
void GroupThumbs::checkForAnimationStart() { void GroupThumbs::checkForAnimationStart() {
if (_waitingForAnimationStart) { if (_waitingForAnimationStart) {
_waitingForAnimationStart = false; _waitingForAnimationStart = false;
_animation.start([this] { update(); }, 0., 1., kThumbDuration); _animation.start([=] { update(); }, 0., 1., kThumbDuration);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_shared_media.h" #include "data/data_shared_media.h"
#include "data/data_user_photos.h" #include "data/data_user_photos.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "media/view/media_view_playback_controls.h"
namespace Ui { namespace Ui {
class PopupMenu; class PopupMenu;
@ -35,9 +36,13 @@ namespace Media {
namespace Player { namespace Player {
struct TrackState; struct TrackState;
} // namespace Player } // namespace Player
namespace Clip { namespace Streaming {
class Controller; struct Update;
} // namespace Clip struct Error;
} // namespace Streaming
} // namespace Media
namespace Media {
namespace View { namespace View {
class GroupThumbs; class GroupThumbs;
@ -48,7 +53,11 @@ using OverlayParent = Ui::RpWidgetWrap<QOpenGLWidget>;
using OverlayParent = Ui::RpWidget; using OverlayParent = Ui::RpWidget;
#endif // Q_OS_MAC && !OS_MAC_OLD #endif // Q_OS_MAC && !OS_MAC_OLD
class OverlayWidget final : public OverlayParent, private base::Subscriber, public ClickHandlerHost { class OverlayWidget final
: public OverlayParent
, private base::Subscriber
, public ClickHandlerHost
, private PlaybackControls::Delegate {
Q_OBJECT Q_OBJECT
public: public:
@ -70,7 +79,6 @@ public:
void activateControls(); void activateControls();
void onDocClick(); void onDocClick();
void clipCallback(Media::Clip::Notification notification);
PeerData *ui_getPeerForMouseAction(); PeerData *ui_getPeerForMouseAction();
void clearData(); void clearData();
@ -105,14 +113,9 @@ private slots:
void updateImage(); void updateImage();
void onVideoPauseResume();
void onVideoSeekProgress(crl::time positionMs);
void onVideoSeekFinished(crl::time positionMs);
void onVideoVolumeChanged(float64 volume);
void onVideoToggleFullScreen();
void onVideoPlayProgress(const AudioMsgId &audioId);
private: private:
struct Streamed;
enum OverState { enum OverState {
OverNone, OverNone,
OverLeftNav, OverLeftNav,
@ -149,6 +152,16 @@ private:
void setVisibleHook(bool visible) override; void setVisibleHook(bool visible) override;
void playbackControlsPlay() override;
void playbackControlsPause() override;
void playbackControlsSeekProgress(crl::time position) override;
void playbackControlsSeekFinished(crl::time position) override;
void playbackControlsVolumeChanged(float64 volume) override;
void playbackControlsToFullScreen() override;
void playbackControlsFromFullScreen() override;
void playbackPauseResume();
void playbackToggleFullScreen();
void updateOver(QPoint mpos); void updateOver(QPoint mpos);
void moveToScreen(); void moveToScreen();
bool moveToNext(int delta); bool moveToNext(int delta);
@ -212,17 +225,18 @@ private:
void updateCursor(); void updateCursor();
void setZoomLevel(int newZoom); void setZoomLevel(int newZoom);
void updateVideoPlaybackState(const Media::Player::TrackState &state); void updatePlaybackState();
void updateSilentVideoPlaybackState(); void restartAtSeekPosition(crl::time position);
void restartVideoAtSeekPosition(crl::time positionMs); void togglePauseResume();
void toggleVideoPaused();
void createClipController();
void refreshClipControllerGeometry(); void refreshClipControllerGeometry();
void refreshCaptionGeometry(); void refreshCaptionGeometry();
void initAnimation(); void initStreaming();
void createClipReader(); void initStreamingThumbnail();
void createStreamingObjects();
void handleStreamingUpdate(Streaming::Update &&update);
void handleStreamingError(Streaming::Error &&error);
void initThemePreview(); void initThemePreview();
void destroyThemePreview(); void destroyThemePreview();
@ -231,6 +245,8 @@ private:
void documentUpdated(DocumentData *doc); void documentUpdated(DocumentData *doc);
void changingMsgId(not_null<HistoryItem*> row, MsgId newId); void changingMsgId(not_null<HistoryItem*> row, MsgId newId);
QRect contentRect() const;
// Radial animation interface. // Radial animation interface.
float64 radialProgress() const; float64 radialProgress() const;
bool radialLoading() const; bool radialLoading() const;
@ -262,6 +278,16 @@ private:
void validatePhotoImage(Image *image, bool blurred); void validatePhotoImage(Image *image, bool blurred);
void validatePhotoCurrentImage(); void validatePhotoCurrentImage();
[[nodiscard]] bool videoShown() const;
[[nodiscard]] QSize videoSize() const;
[[nodiscard]] bool videoIsGifv() const;
[[nodiscard]] QImage videoFrame() const;
[[nodiscard]] crl::time streamedPosition() const;
[[nodiscard]] crl::time streamedDuration() const;
[[nodiscard]] bool documentContentShown() const;
[[nodiscard]] bool documentBubbleShown() const;
void clearStreaming();
QBrush _transparentBrush; QBrush _transparentBrush;
PhotoData *_photo = nullptr; PhotoData *_photo = nullptr;
@ -285,12 +311,11 @@ private:
QString _dateText; QString _dateText;
QString _headerText; QString _headerText;
object_ptr<Media::Clip::Controller> _clipController = { nullptr };
DocumentData *_autoplayVideoDocument = nullptr; DocumentData *_autoplayVideoDocument = nullptr;
bool _fullScreenVideo = false; bool _fullScreenVideo = false;
int _fullScreenZoomCache = 0; int _fullScreenZoomCache = 0;
std::unique_ptr<Media::View::GroupThumbs> _groupThumbs; std::unique_ptr<GroupThumbs> _groupThumbs;
QRect _groupThumbsRect; QRect _groupThumbsRect;
int _groupThumbsAvailableWidth = 0; int _groupThumbsAvailableWidth = 0;
int _groupThumbsLeft = 0; int _groupThumbsLeft = 0;
@ -298,9 +323,8 @@ private:
Text _caption; Text _caption;
QRect _captionRect; QRect _captionRect;
crl::time _animStarted;
int _width = 0; int _width = 0;
int _height = 0;
int _x = 0, _y = 0, _w = 0, _h = 0; int _x = 0, _y = 0, _w = 0, _h = 0;
int _xStart = 0, _yStart = 0; int _xStart = 0, _yStart = 0;
int _zoom = 0; // < 0 - out, 0 - none, > 0 - in int _zoom = 0; // < 0 - out, 0 - none, > 0 - in
@ -309,21 +333,9 @@ private:
bool _pressed = false; bool _pressed = false;
int32 _dragging = 0; int32 _dragging = 0;
QPixmap _current; QPixmap _current;
Media::Clip::ReaderPointer _gif;
bool _blurred = true; bool _blurred = true;
// Video without audio stream playback information. std::unique_ptr<Streamed> _streamed;
bool _videoIsSilent = false;
bool _videoPaused = false;
bool _videoStopped = false;
crl::time _videoPositionMs = 0;
crl::time _videoDurationMs = 0;
int32 _videoFrequencyMs = 1000; // 1000 ms per second.
bool fileShown() const;
bool gifShown() const;
bool fileBubbleShown() const;
void stopGif();
const style::icon *_docIcon = nullptr; const style::icon *_docIcon = nullptr;
style::color _docIconColor; style::color _docIconColor;

View file

@ -5,10 +5,10 @@ the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link: For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "media/view/media_clip_controller.h" #include "media/view/media_view_playback_controls.h"
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/view/media_clip_playback.h" #include "media/view/media_view_playback_progress.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/continuous_sliders.h" #include "ui/widgets/continuous_sliders.h"
#include "ui/effects/fade_animation.h" #include "ui/effects/fade_animation.h"
@ -17,69 +17,89 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_mediaview.h" #include "styles/style_mediaview.h"
namespace Media { namespace Media {
namespace Clip { namespace View {
Controller::Controller(QWidget *parent) : TWidget(parent) PlaybackControls::PlaybackControls(QWidget *parent, not_null<Delegate *> delegate)
: RpWidget(parent)
, _delegate(delegate)
, _playPauseResume(this, st::mediaviewPlayButton) , _playPauseResume(this, st::mediaviewPlayButton)
, _playbackSlider(this, st::mediaviewPlayback) , _playbackSlider(this, st::mediaviewPlayback)
, _playback(std::make_unique<Playback>()) , _playbackProgress(std::make_unique<PlaybackProgress>())
, _volumeController(this, st::mediaviewPlayback) , _volumeController(this, st::mediaviewPlayback)
, _fullScreenToggle(this, st::mediaviewFullScreenButton) , _fullScreenToggle(this, st::mediaviewFullScreenButton)
, _playedAlready(this, st::mediaviewPlayProgressLabel) , _playedAlready(this, st::mediaviewPlayProgressLabel)
, _toPlayLeft(this, st::mediaviewPlayProgressLabel) , _toPlayLeft(this, st::mediaviewPlayProgressLabel)
, _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) { , _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) {
_fadeAnimation->show(); _fadeAnimation->show();
_fadeAnimation->setFinishedCallback([this] { fadeFinished(); }); _fadeAnimation->setFinishedCallback([=] {
_fadeAnimation->setUpdatedCallback([this](float64 opacity) { fadeUpdated(opacity); }); fadeFinished();
});
_fadeAnimation->setUpdatedCallback([=](float64 opacity) {
fadeUpdated(opacity);
});
_volumeController->setValue(Global::VideoVolume()); _volumeController->setValue(Global::VideoVolume());
_volumeController->setChangeProgressCallback([=](float64 value) { _volumeController->setChangeProgressCallback([=](float64 value) {
volumeChanged(value); _delegate->playbackControlsVolumeChanged(value);
}); });
//_volumeController->setChangeFinishedCallback();
connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed())); _playPauseResume->addClickHandler([=] {
connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); if (_showPause) {
//connect(_volumeController, SIGNAL(volumeChanged(float64)), this, SIGNAL(volumeChanged(float64))); _delegate->playbackControlsPause();
} else {
_delegate->playbackControlsPlay();
}
});
_fullScreenToggle->addClickHandler([=] {
if (_inFullScreen) {
_delegate->playbackControlsFromFullScreen();
} else {
_delegate->playbackControlsToFullScreen();
}
});
_playback->setInLoadingStateChangedCallback([this](bool loading) { _playbackProgress->setInLoadingStateChangedCallback([=](bool loading) {
_playbackSlider->setDisabled(loading); _playbackSlider->setDisabled(loading);
}); });
_playback->setValueChangedCallback([this](float64 value) { _playbackProgress->setValueChangedCallback([=](float64 value) {
_playbackSlider->setValue(value); _playbackSlider->setValue(value);
}); });
_playbackSlider->setChangeProgressCallback([this](float64 value) { _playbackSlider->setChangeProgressCallback([=](float64 value) {
_playback->setValue(value, false); _playbackProgress->setValue(value, false);
handleSeekProgress(value); // This may destroy Controller.
// This may destroy PlaybackControls.
handleSeekProgress(value);
}); });
_playbackSlider->setChangeFinishedCallback([this](float64 value) { _playbackSlider->setChangeFinishedCallback([=](float64 value) {
_playback->setValue(value, false); _playbackProgress->setValue(value, false);
handleSeekFinished(value); handleSeekFinished(value);
}); });
} }
void Controller::handleSeekProgress(float64 progress) { void PlaybackControls::handleSeekProgress(float64 progress) {
if (!_lastDurationMs) return; if (!_lastDurationMs) return;
auto positionMs = snap(static_cast<crl::time>(progress * _lastDurationMs), 0LL, _lastDurationMs); auto positionMs = snap(static_cast<crl::time>(progress * _lastDurationMs), 0LL, _lastDurationMs);
if (_seekPositionMs != positionMs) { if (_seekPositionMs != positionMs) {
_seekPositionMs = positionMs; _seekPositionMs = positionMs;
refreshTimeTexts(); refreshTimeTexts();
emit seekProgress(positionMs); // This may destroy Controller.
// This may destroy PlaybackControls.
_delegate->playbackControlsSeekProgress(positionMs);
} }
} }
void Controller::handleSeekFinished(float64 progress) { void PlaybackControls::handleSeekFinished(float64 progress) {
if (!_lastDurationMs) return; if (!_lastDurationMs) return;
auto positionMs = snap(static_cast<crl::time>(progress * _lastDurationMs), 0LL, _lastDurationMs); auto positionMs = snap(static_cast<crl::time>(progress * _lastDurationMs), 0LL, _lastDurationMs);
_seekPositionMs = -1; _seekPositionMs = -1;
emit seekFinished(positionMs); _delegate->playbackControlsSeekFinished(positionMs);
refreshTimeTexts(); refreshTimeTexts();
} }
template <typename Callback> template <typename Callback>
void Controller::startFading(Callback start) { void PlaybackControls::startFading(Callback start) {
if (!_fadeAnimation->animating()) { if (!_fadeAnimation->animating()) {
showChildren(); showChildren();
_playbackSlider->disablePaint(true); _playbackSlider->disablePaint(true);
@ -103,45 +123,42 @@ void Controller::startFading(Callback start) {
_volumeController->disablePaint(false); _volumeController->disablePaint(false);
} }
void Controller::showAnimated() { void PlaybackControls::showAnimated() {
startFading([this]() { startFading([this]() {
_fadeAnimation->fadeIn(st::mediaviewShowDuration); _fadeAnimation->fadeIn(st::mediaviewShowDuration);
}); });
} }
void Controller::hideAnimated() { void PlaybackControls::hideAnimated() {
startFading([this]() { startFading([this]() {
_fadeAnimation->fadeOut(st::mediaviewHideDuration); _fadeAnimation->fadeOut(st::mediaviewHideDuration);
}); });
} }
void Controller::fadeFinished() { void PlaybackControls::fadeFinished() {
fadeUpdated(_fadeAnimation->visible() ? 1. : 0.); fadeUpdated(_fadeAnimation->visible() ? 1. : 0.);
} }
void Controller::fadeUpdated(float64 opacity) { void PlaybackControls::fadeUpdated(float64 opacity) {
_playbackSlider->setFadeOpacity(opacity); _playbackSlider->setFadeOpacity(opacity);
_volumeController->setFadeOpacity(opacity); _volumeController->setFadeOpacity(opacity);
} }
void Controller::updatePlayback(const Player::TrackState &state) { void PlaybackControls::updatePlayback(const Player::TrackState &state) {
updatePlayPauseResumeState(state); updatePlayPauseResumeState(state);
_playback->updateState(state); _playbackProgress->updateState(state);
updateTimeTexts(state); updateTimeTexts(state);
} }
void Controller::updatePlayPauseResumeState(const Player::TrackState &state) { void PlaybackControls::updatePlayPauseResumeState(const Player::TrackState &state) {
auto showPause = ShowPauseIcon(state.state) || (_seekPositionMs >= 0); auto showPause = ShowPauseIcon(state.state) || (_seekPositionMs >= 0);
if (showPause != _showPause) { if (showPause != _showPause) {
disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed()));
_showPause = showPause; _showPause = showPause;
connect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed()));
_playPauseResume->setIconOverride(_showPause ? &st::mediaviewPauseIcon : nullptr, _showPause ? &st::mediaviewPauseIconOver : nullptr); _playPauseResume->setIconOverride(_showPause ? &st::mediaviewPauseIcon : nullptr, _showPause ? &st::mediaviewPauseIconOver : nullptr);
} }
} }
void Controller::updateTimeTexts(const Player::TrackState &state) { void PlaybackControls::updateTimeTexts(const Player::TrackState &state) {
qint64 position = 0, length = state.length; qint64 position = 0, length = state.length;
if (Player::IsStoppedAtEnd(state.state)) { if (Player::IsStoppedAtEnd(state.state)) {
@ -166,7 +183,7 @@ void Controller::updateTimeTexts(const Player::TrackState &state) {
} }
} }
void Controller::refreshTimeTexts() { void PlaybackControls::refreshTimeTexts() {
auto alreadyChanged = false, leftChanged = false; auto alreadyChanged = false, leftChanged = false;
auto timeAlready = _timeAlready; auto timeAlready = _timeAlready;
auto timeLeft = _timeLeft; auto timeLeft = _timeLeft;
@ -189,16 +206,16 @@ void Controller::refreshTimeTexts() {
} }
} }
void Controller::setInFullScreen(bool inFullScreen) { void PlaybackControls::setInFullScreen(bool inFullScreen) {
_fullScreenToggle->setIconOverride(inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr, inFullScreen ? &st::mediaviewFullScreenOutIconOver : nullptr); if (_inFullScreen != inFullScreen) {
disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed())); _inFullScreen = inFullScreen;
disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(fromFullScreenPressed())); _fullScreenToggle->setIconOverride(
_inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr,
auto handler = inFullScreen ? SIGNAL(fromFullScreenPressed()) : SIGNAL(toFullScreenPressed()); _inFullScreen ? &st::mediaviewFullScreenOutIconOver : nullptr);
connect(_fullScreenToggle, SIGNAL(clicked()), this, handler); }
} }
void Controller::resizeEvent(QResizeEvent *e) { void PlaybackControls::resizeEvent(QResizeEvent *e) {
int playTop = (height() - _playPauseResume->height()) / 2; int playTop = (height() - _playPauseResume->height()) / 2;
_playPauseResume->moveToLeft(st::mediaviewPlayPauseLeft, playTop); _playPauseResume->moveToLeft(st::mediaviewPlayPauseLeft, playTop);
@ -216,7 +233,7 @@ void Controller::resizeEvent(QResizeEvent *e) {
_toPlayLeft->moveToRight(width() - (st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop) - playbackWidth, st::mediaviewPlayProgressTop); _toPlayLeft->moveToRight(width() - (st::mediaviewPlayPauseLeft + _playPauseResume->width() + playTop) - playbackWidth, st::mediaviewPlayProgressTop);
} }
void Controller::paintEvent(QPaintEvent *e) { void PlaybackControls::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
if (_fadeAnimation->paint(p)) { if (_fadeAnimation->paint(p)) {
@ -231,11 +248,11 @@ void Controller::paintEvent(QPaintEvent *e) {
App::roundRect(p, rect(), st::mediaviewSaveMsgBg, MediaviewSaveCorners); App::roundRect(p, rect(), st::mediaviewSaveMsgBg, MediaviewSaveCorners);
} }
void Controller::mousePressEvent(QMouseEvent *e) { void PlaybackControls::mousePressEvent(QMouseEvent *e) {
e->accept(); // Don't pass event to the Media::View::OverlayWidget. e->accept(); // Don't pass event to the Media::View::OverlayWidget.
} }
Controller::~Controller() = default; PlaybackControls::~PlaybackControls() = default;
} // namespace Clip } // namespace View
} // namespace Media } // namespace Media

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "ui/rp_widget.h"
namespace Ui { namespace Ui {
class LabelSimple; class LabelSimple;
class FadeAnimation; class FadeAnimation;
@ -19,15 +21,24 @@ namespace Player {
struct TrackState; struct TrackState;
} // namespace Player } // namespace Player
namespace Clip { namespace View {
class Playback; class PlaybackProgress;
class Controller : public TWidget {
Q_OBJECT
class PlaybackControls : public Ui::RpWidget {
public: public:
Controller(QWidget *parent); class Delegate {
public:
virtual void playbackControlsPlay() = 0;
virtual void playbackControlsPause() = 0;
virtual void playbackControlsSeekProgress(crl::time position) = 0;
virtual void playbackControlsSeekFinished(crl::time position) = 0;
virtual void playbackControlsVolumeChanged(float64 volume) = 0;
virtual void playbackControlsToFullScreen() = 0;
virtual void playbackControlsFromFullScreen() = 0;
};
PlaybackControls(QWidget *parent, not_null<Delegate*> delegate);
void showAnimated(); void showAnimated();
void hideAnimated(); void hideAnimated();
@ -35,16 +46,7 @@ public:
void updatePlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state);
void setInFullScreen(bool inFullScreen); void setInFullScreen(bool inFullScreen);
~Controller(); ~PlaybackControls();
signals:
void playPressed();
void pausePressed();
void seekProgress(crl::time positionMs);
void seekFinished(crl::time positionMs);
void volumeChanged(float64 volume);
void toFullScreenPressed();
void fromFullScreenPressed();
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
@ -64,6 +66,9 @@ private:
void updateTimeTexts(const Player::TrackState &state); void updateTimeTexts(const Player::TrackState &state);
void refreshTimeTexts(); void refreshTimeTexts();
not_null<Delegate*> _delegate;
bool _inFullScreen = false;
bool _showPause = false; bool _showPause = false;
bool _childrenHidden = false; bool _childrenHidden = false;
QString _timeAlready, _timeLeft; QString _timeAlready, _timeLeft;
@ -72,7 +77,7 @@ private:
object_ptr<Ui::IconButton> _playPauseResume; object_ptr<Ui::IconButton> _playPauseResume;
object_ptr<Ui::MediaSlider> _playbackSlider; object_ptr<Ui::MediaSlider> _playbackSlider;
std::unique_ptr<Playback> _playback; std::unique_ptr<PlaybackProgress> _playbackProgress;
object_ptr<Ui::MediaSlider> _volumeController; object_ptr<Ui::MediaSlider> _volumeController;
object_ptr<Ui::IconButton> _fullScreenToggle; object_ptr<Ui::IconButton> _fullScreenToggle;
object_ptr<Ui::LabelSimple> _playedAlready; object_ptr<Ui::LabelSimple> _playedAlready;
@ -82,5 +87,5 @@ private:
}; };
} // namespace Clip } // namespace View
} // namespace Media } // namespace Media

View file

@ -5,23 +5,23 @@ the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link: For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "media/view/media_clip_playback.h" #include "media/view/media_view_playback_progress.h"
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "styles/style_mediaview.h" #include "styles/style_mediaview.h"
namespace Media { namespace Media {
namespace Clip { namespace View {
namespace { namespace {
constexpr auto kPlaybackAnimationDurationMs = crl::time(200); constexpr auto kPlaybackAnimationDurationMs = crl::time(200);
} // namespace } // namespace
Playback::Playback() : _a_value(animation(this, &Playback::step_value)) { PlaybackProgress::PlaybackProgress() : _a_value(animation(this, &PlaybackProgress::step_value)) {
} }
void Playback::updateState(const Player::TrackState &state) { void PlaybackProgress::updateState(const Player::TrackState &state) {
qint64 position = 0, length = state.length; qint64 position = 0, length = state.length;
auto wasInLoadingState = _inLoadingState; auto wasInLoadingState = _inLoadingState;
@ -60,7 +60,7 @@ void Playback::updateState(const Player::TrackState &state) {
} }
} }
void Playback::updateLoadingState(float64 progress) { void PlaybackProgress::updateLoadingState(float64 progress) {
if (!_inLoadingState) { if (!_inLoadingState) {
_inLoadingState = true; _inLoadingState = true;
if (_inLoadingStateChanged) { if (_inLoadingStateChanged) {
@ -71,16 +71,16 @@ void Playback::updateLoadingState(float64 progress) {
setValue(progress, animated); setValue(progress, animated);
} }
float64 Playback::value() const { float64 PlaybackProgress::value() const {
return qMin(a_value.current(), 1.); return qMin(a_value.current(), 1.);
} }
float64 Playback::value(crl::time ms) { float64 PlaybackProgress::value(crl::time ms) {
_a_value.step(ms); _a_value.step(ms);
return value(); return value();
} }
void Playback::setValue(float64 value, bool animated) { void PlaybackProgress::setValue(float64 value, bool animated) {
if (animated) { if (animated) {
a_value.start(value); a_value.start(value);
_a_value.start(); _a_value.start();
@ -93,7 +93,7 @@ void Playback::setValue(float64 value, bool animated) {
} }
} }
void Playback::step_value(float64 ms, bool timer) { void PlaybackProgress::step_value(float64 ms, bool timer) {
auto dt = anim::Disabled() ? 1. : (ms / kPlaybackAnimationDurationMs); auto dt = anim::Disabled() ? 1. : (ms / kPlaybackAnimationDurationMs);
if (dt >= 1.) { if (dt >= 1.) {
_a_value.stop(); _a_value.stop();
@ -106,5 +106,5 @@ void Playback::step_value(float64 ms, bool timer) {
} }
} }
} // namespace Clip } // namespace View
} // namespace Media } // namespace Media

View file

@ -14,11 +14,11 @@ namespace Player {
struct TrackState; struct TrackState;
} // namespace Player } // namespace Player
namespace Clip { namespace View {
class Playback { class PlaybackProgress {
public: public:
Playback(); PlaybackProgress();
void setValueChangedCallback(Fn<void(float64)> callback) { void setValueChangedCallback(Fn<void(float64)> callback) {
_valueChanged = std::move(callback); _valueChanged = std::move(callback);
@ -52,5 +52,5 @@ private:
}; };
} // namespace Clip } // namespace View
} // namespace Media } // namespace Media

View file

@ -849,7 +849,6 @@ bool Voice::updateStatusText() {
statusSize = FileStatusSizeFailed; statusSize = FileStatusSizeFailed;
} else if (_data->loaded()) { } else if (_data->loaded()) {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
using State = Media::Player::State;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, parent()->fullId(), state.id.playId()) && !Media::Player::IsStoppedOrStopping(state.state)) { if (state.id == AudioMsgId(_data, parent()->fullId(), state.id.playId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);
@ -1220,7 +1219,6 @@ bool Document::updateStatusText() {
} else if (_data->loaded()) { } else if (_data->loaded()) {
if (_data->isSong()) { if (_data->isSong()) {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
using State = Media::Player::State;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(_data, parent()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) { if (state.id == AudioMsgId(_data, parent()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);

View file

@ -90,11 +90,11 @@ public:
template < template <
typename OtherType, typename OtherType,
typename Error, typename OtherError,
typename Generator, typename Generator,
typename = std::enable_if_t< typename = std::enable_if_t<
std::is_assignable_v<Type&, OtherType>>> std::is_assignable_v<Type&, OtherType>>>
variable(producer<OtherType, Error, Generator> &&stream) { variable(producer<OtherType, OtherError, Generator> &&stream) {
std::move(stream) std::move(stream)
| start_with_next([=](auto &&data) { | start_with_next([=](auto &&data) {
assign(std::forward<decltype(data)>(data)); assign(std::forward<decltype(data)>(data));
@ -103,12 +103,12 @@ public:
template < template <
typename OtherType, typename OtherType,
typename Error, typename OtherError,
typename Generator, typename Generator,
typename = std::enable_if_t< typename = std::enable_if_t<
std::is_assignable_v<Type&, OtherType>>> std::is_assignable_v<Type&, OtherType>>>
variable &operator=( variable &operator=(
producer<OtherType, Error, Generator> &&stream) { producer<OtherType, OtherError, Generator> &&stream) {
_lifetime.destroy(); _lifetime.destroy();
std::move(stream) std::move(stream)
| start_with_next([=](auto &&data) { | start_with_next([=](auto &&data) {

View file

@ -200,6 +200,10 @@ inline T ConvertScale(T value) {
return ConvertScale(value, cScale()); return ConvertScale(value, cScale());
} }
inline QSize ConvertScale(QSize size) {
return QSize(ConvertScale(size.width()), ConvertScale(size.height()));
}
inline void SetScaleChecked(int scale) { inline void SetScaleChecked(int scale) {
const auto checked = (scale == kInterfaceScaleAuto) const auto checked = (scale == kInterfaceScaleAuto)
? kInterfaceScaleAuto ? kInterfaceScaleAuto

View file

@ -472,10 +472,10 @@
<(src_loc)/media/streaming/media_streaming_utility.h <(src_loc)/media/streaming/media_streaming_utility.h
<(src_loc)/media/streaming/media_streaming_video_track.cpp <(src_loc)/media/streaming/media_streaming_video_track.cpp
<(src_loc)/media/streaming/media_streaming_video_track.h <(src_loc)/media/streaming/media_streaming_video_track.h
<(src_loc)/media/view/media_clip_controller.cpp <(src_loc)/media/view/media_view_playback_controls.cpp
<(src_loc)/media/view/media_clip_controller.h <(src_loc)/media/view/media_view_playback_controls.h
<(src_loc)/media/view/media_clip_playback.cpp <(src_loc)/media/view/media_view_playback_progress.cpp
<(src_loc)/media/view/media_clip_playback.h <(src_loc)/media/view/media_view_playback_progress.h
<(src_loc)/media/view/media_view_group_thumbs.cpp <(src_loc)/media/view/media_view_group_thumbs.cpp
<(src_loc)/media/view/media_view_group_thumbs.h <(src_loc)/media/view/media_view_group_thumbs.h
<(src_loc)/media/view/media_view_overlay_widget.cpp <(src_loc)/media/view/media_view_overlay_widget.cpp