mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 10:11:41 -05:00
Play streaming video in mediaview.
This commit is contained in:
parent
44df10d6cb
commit
f1e0cd6c1d
43 changed files with 920 additions and 900 deletions
|
@ -422,14 +422,11 @@ namespace App {
|
|||
HistoryItem *histItemById(ChannelId channelId, MsgId itemId) {
|
||||
if (!itemId) return nullptr;
|
||||
|
||||
auto data = fetchMsgsData(channelId, false);
|
||||
const auto data = fetchMsgsData(channelId, false);
|
||||
if (!data) return nullptr;
|
||||
|
||||
auto i = data->constFind(itemId);
|
||||
if (i != data->cend()) {
|
||||
return i.value();
|
||||
}
|
||||
return nullptr;
|
||||
const auto i = data->constFind(itemId);
|
||||
return (i != data->cend()) ? i.value() : nullptr;
|
||||
}
|
||||
|
||||
HistoryItem *histItemById(const ChannelData *channel, MsgId itemId) {
|
||||
|
|
|
@ -154,7 +154,7 @@ QString FileNameUnsafe(
|
|||
if (QRegularExpression(qsl("^[a-zA-Z_0-9]+$")).match(ext).hasMatch()) {
|
||||
QStringList filters = filter.split(sep);
|
||||
if (filters.size() > 1) {
|
||||
QString first = filters.at(0);
|
||||
const auto &first = filters.at(0);
|
||||
int32 start = first.indexOf(qsl("(*."));
|
||||
if (start >= 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
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(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> data,
|
||||
|
@ -503,8 +307,8 @@ void DocumentOpenClickHandler::Open(
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (data->isAudioFile() || data->isVideoFile()) {
|
||||
StartStreaming(data, origin);
|
||||
if (data->canBePlayed()) {
|
||||
Core::App().showDocument(data, context);
|
||||
return;
|
||||
}
|
||||
if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) {
|
||||
|
@ -536,25 +340,6 @@ void DocumentOpenClickHandler::Open(
|
|||
Media::Player::mixer()->play(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) {
|
||||
if (!data->data().isEmpty() && playAnimation) {
|
||||
if (action == ActionOnLoadPlayInline && context) {
|
||||
|
@ -610,14 +395,10 @@ void GifOpenClickHandler::onClickImpl() const {
|
|||
void DocumentSaveClickHandler::Save(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> data,
|
||||
HistoryItem *context,
|
||||
bool forceSavingAs) {
|
||||
if (!data->date) return;
|
||||
|
||||
if (data->isAudioFile() || data->isVideoFile()) {
|
||||
StartStreaming(data, origin);
|
||||
return;
|
||||
}
|
||||
|
||||
auto filepath = data->filepath(
|
||||
DocumentData::FilePathResolveSaveFromDataSilent,
|
||||
forceSavingAs);
|
||||
|
@ -635,7 +416,7 @@ void DocumentSaveClickHandler::Save(
|
|||
}
|
||||
|
||||
void DocumentSaveClickHandler::onClickImpl() const {
|
||||
Save(context(), document());
|
||||
Save(context(), document(), getActionItem());
|
||||
}
|
||||
|
||||
void DocumentCancelClickHandler::onClickImpl() const {
|
||||
|
@ -683,51 +464,44 @@ AuthSession &DocumentData::session() const {
|
|||
return _owner->session();
|
||||
}
|
||||
|
||||
void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes) {
|
||||
void DocumentData::setattributes(
|
||||
const QVector<MTPDocumentAttribute> &attributes) {
|
||||
_isImage = false;
|
||||
_supportsStreaming = false;
|
||||
for (int32 i = 0, l = attributes.size(); i < l; ++i) {
|
||||
switch (attributes[i].type()) {
|
||||
case mtpc_documentAttributeImageSize: {
|
||||
auto &d = attributes[i].c_documentAttributeImageSize();
|
||||
dimensions = QSize(d.vw.v, d.vh.v);
|
||||
} break;
|
||||
case mtpc_documentAttributeAnimated:
|
||||
for (const auto &attribute : attributes) {
|
||||
attribute.match([&](const MTPDdocumentAttributeImageSize & data) {
|
||||
dimensions = QSize(data.vw.v, data.vh.v);
|
||||
}, [&](const MTPDdocumentAttributeAnimated & data) {
|
||||
if (type == FileDocument
|
||||
|| type == StickerDocument
|
||||
|| type == VideoDocument) {
|
||||
type = AnimatedDocument;
|
||||
_additional = nullptr;
|
||||
} break;
|
||||
case mtpc_documentAttributeSticker: {
|
||||
auto &d = attributes[i].c_documentAttributeSticker();
|
||||
}
|
||||
}, [&](const MTPDdocumentAttributeSticker & data) {
|
||||
if (type == FileDocument) {
|
||||
type = StickerDocument;
|
||||
_additional = std::make_unique<StickerData>();
|
||||
}
|
||||
if (sticker()) {
|
||||
sticker()->alt = qs(d.valt);
|
||||
sticker()->alt = qs(data.valt);
|
||||
if (sticker()->set.type() != mtpc_inputStickerSetID
|
||||
|| d.vstickerset.type() == mtpc_inputStickerSetID) {
|
||||
sticker()->set = d.vstickerset;
|
||||
|| data.vstickerset.type() == mtpc_inputStickerSetID) {
|
||||
sticker()->set = data.vstickerset;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case mtpc_documentAttributeVideo: {
|
||||
auto &d = attributes[i].c_documentAttributeVideo();
|
||||
}, [&](const MTPDdocumentAttributeVideo & data) {
|
||||
if (type == FileDocument) {
|
||||
type = d.is_round_message()
|
||||
type = data.is_round_message()
|
||||
? RoundVideoDocument
|
||||
: VideoDocument;
|
||||
}
|
||||
_duration = d.vduration.v;
|
||||
_supportsStreaming = d.is_supports_streaming();
|
||||
dimensions = QSize(d.vw.v, d.vh.v);
|
||||
} break;
|
||||
case mtpc_documentAttributeAudio: {
|
||||
auto &d = attributes[i].c_documentAttributeAudio();
|
||||
_duration = data.vduration.v;
|
||||
_supportsStreaming = data.is_supports_streaming();
|
||||
dimensions = QSize(data.vw.v, data.vh.v);
|
||||
}, [&](const MTPDdocumentAttributeAudio & data) {
|
||||
if (type == FileDocument) {
|
||||
if (d.is_voice()) {
|
||||
if (data.is_voice()) {
|
||||
type = VoiceDocument;
|
||||
_additional = std::make_unique<VoiceData>();
|
||||
} else {
|
||||
|
@ -736,25 +510,19 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
|
|||
}
|
||||
}
|
||||
if (const auto voiceData = voice()) {
|
||||
voiceData->duration = d.vduration.v;
|
||||
VoiceWaveform waveform = documentWaveformDecode(qba(d.vwaveform));
|
||||
uchar wavemax = 0;
|
||||
for (int32 i = 0, l = waveform.size(); i < l; ++i) {
|
||||
uchar waveat = waveform.at(i);
|
||||
if (wavemax < waveat) wavemax = waveat;
|
||||
}
|
||||
voiceData->waveform = waveform;
|
||||
voiceData->wavemax = wavemax;
|
||||
voiceData->duration = data.vduration.v;
|
||||
voiceData->waveform = documentWaveformDecode(
|
||||
qba(data.vwaveform));
|
||||
voiceData->wavemax = voiceData->waveform.empty()
|
||||
? uchar(0)
|
||||
: *ranges::max_element(voiceData->waveform);
|
||||
} else if (const auto songData = song()) {
|
||||
songData->duration = d.vduration.v;
|
||||
songData->title = qs(d.vtitle);
|
||||
songData->performer = qs(d.vperformer);
|
||||
songData->duration = data.vduration.v;
|
||||
songData->title = qs(data.vtitle);
|
||||
songData->performer = qs(data.vperformer);
|
||||
}
|
||||
} break;
|
||||
case mtpc_documentAttributeFilename: {
|
||||
const auto &attribute = attributes[i];
|
||||
_filename = qs(
|
||||
attribute.c_documentAttributeFilename().vfile_name);
|
||||
}, [&](const MTPDdocumentAttributeFilename & data) {
|
||||
_filename = qs(data.vfile_name);
|
||||
|
||||
// We don't want LTR/RTL mark/embedding/override/isolate chars
|
||||
// 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) {
|
||||
_filename = std::move(_filename).replace(ch, "_");
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}, [&](const MTPDdocumentAttributeHasStickers &data) {
|
||||
});
|
||||
}
|
||||
if (type == StickerDocument) {
|
||||
if (dimensions.width() <= 0
|
||||
|
@ -1495,8 +1263,27 @@ bool DocumentData::hasRemoteLocation() const {
|
|||
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
|
||||
-> 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()
|
||||
? std::make_unique<Media::Streaming::LoaderMtproto>(
|
||||
&session().api(),
|
||||
|
|
|
@ -223,6 +223,8 @@ public:
|
|||
const QString &songPerformer);
|
||||
[[nodiscard]] QString composeNameString() const;
|
||||
|
||||
[[nodiscard]] bool canBePlayed() const;
|
||||
[[nodiscard]] bool canBeStreamed() const;
|
||||
[[nodiscard]] auto createStreamingLoader(Data::FileOrigin origin) const
|
||||
-> std::unique_ptr<Media::Streaming::Loader>;
|
||||
|
||||
|
@ -303,6 +305,7 @@ public:
|
|||
static void Save(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *context,
|
||||
bool forceSavingAs = false);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -1092,7 +1092,11 @@ void InnerWidget::savePhotoToFile(PhotoData *photo) {
|
|||
}
|
||||
|
||||
void InnerWidget::saveDocumentToFile(DocumentData *document) {
|
||||
DocumentSaveClickHandler::Save(Data::FileOrigin(), document, true);
|
||||
DocumentSaveClickHandler::Save(
|
||||
Data::FileOrigin(),
|
||||
document,
|
||||
nullptr,
|
||||
true);
|
||||
}
|
||||
|
||||
void InnerWidget::copyContextImage(PhotoData *photo) {
|
||||
|
|
|
@ -1823,7 +1823,11 @@ void HistoryInner::showContextInFolder(not_null<DocumentData*> document) {
|
|||
void HistoryInner::saveDocumentToFile(
|
||||
FullMsgId contextId,
|
||||
not_null<DocumentData*> document) {
|
||||
DocumentSaveClickHandler::Save(contextId, document, true);
|
||||
DocumentSaveClickHandler::Save(
|
||||
contextId,
|
||||
document,
|
||||
App::histItemById(contextId),
|
||||
true);
|
||||
}
|
||||
|
||||
void HistoryInner::openContextGif(FullMsgId itemId) {
|
||||
|
|
|
@ -621,7 +621,6 @@ bool HistoryDocument::updateStatusText() const {
|
|||
} else if (_data->loading()) {
|
||||
statusSize = _data->loadOffset();
|
||||
} else if (_data->loaded()) {
|
||||
using State = Media::Player::State;
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
if (_data->isVoiceMessage()) {
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
|
|
|
@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/audio/media_audio.h"
|
||||
#include "media/clip/media_clip_reader.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 "history/history_item_components.h"
|
||||
#include "history/history_item.h"
|
||||
|
@ -833,7 +833,7 @@ Media::Clip::Reader *HistoryGif::currentReader() const {
|
|||
return (_gif && _gif->ready()) ? _gif.get() : nullptr;
|
||||
}
|
||||
|
||||
Media::Clip::Playback *HistoryGif::videoPlayback() const {
|
||||
Media::View::PlaybackProgress *HistoryGif::videoPlayback() const {
|
||||
if (const auto video = activeRoundVideo()) {
|
||||
return video->playback();
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ struct HistoryMessageReply;
|
|||
struct HistoryMessageForwarded;
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
class Playback;
|
||||
} // namespace Clip
|
||||
namespace View {
|
||||
class PlaybackProgress;
|
||||
} // namespace View
|
||||
|
||||
namespace Player {
|
||||
class RoundController;
|
||||
|
@ -91,7 +91,7 @@ private:
|
|||
Media::Player::RoundController *activeRoundVideo() const;
|
||||
Media::Clip::Reader *activeRoundPlayer() const;
|
||||
Media::Clip::Reader *currentReader() const;
|
||||
Media::Clip::Playback *videoPlayback() const;
|
||||
Media::View::PlaybackProgress *videoPlayback() const;
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
bool needInfoDisplay() const;
|
||||
|
|
|
@ -226,8 +226,9 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, crl
|
|||
p.setOpacity(1);
|
||||
}
|
||||
|
||||
auto icon = ([this, radial, selected, loaded]() -> const style::icon * {
|
||||
if (loaded && !radial) {
|
||||
const auto canPlay = _data->canBePlayed();
|
||||
auto icon = [&]() -> const style::icon * {
|
||||
if (canPlay && !radial) {
|
||||
return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
|
||||
} else if (radial || _data->loading()) {
|
||||
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 &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
|
||||
})();
|
||||
}();
|
||||
if (icon) {
|
||||
icon->paintInCenter(p, inner);
|
||||
}
|
||||
|
@ -275,7 +276,7 @@ TextState HistoryVideo::textState(QPoint point, StateRequest request) const {
|
|||
}
|
||||
|
||||
auto result = TextState(_parent);
|
||||
bool loaded = _data->loaded();
|
||||
const auto canPlay = _data->canBePlayed();
|
||||
|
||||
auto paintx = 0, painty = 0, paintw = width(), painth = height();
|
||||
bool bubble = _parent->hasBubble();
|
||||
|
@ -300,7 +301,7 @@ TextState HistoryVideo::textState(QPoint point, StateRequest request) const {
|
|||
if (_data->uploading()) {
|
||||
result.link = _cancell;
|
||||
} else {
|
||||
result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel);
|
||||
result.link = canPlay ? _openl : (_data->loading() ? _cancell : _savel);
|
||||
}
|
||||
}
|
||||
if (_caption.isEmpty() && _parent->media() == this) {
|
||||
|
@ -389,10 +390,11 @@ void HistoryVideo::drawGrouped(
|
|||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
const auto canPlay = _data->canBePlayed();
|
||||
auto icon = [&]() -> const style::icon * {
|
||||
if (_data->waitingForAlbum()) {
|
||||
return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
|
||||
} else if (loaded && !radial) {
|
||||
} else if (canPlay && !radial) {
|
||||
return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
|
||||
} else if (radial || _data->loading()) {
|
||||
if (_parent->data()->id > 0 || _data->uploading()) {
|
||||
|
@ -436,7 +438,7 @@ TextState HistoryVideo::getStateGrouped(
|
|||
}
|
||||
return TextState(_parent, _data->uploading()
|
||||
? _cancell
|
||||
: _data->loaded()
|
||||
: _data->canBePlayed()
|
||||
? _openl
|
||||
: _data->loading()
|
||||
? _cancell
|
||||
|
|
|
@ -122,6 +122,9 @@ void AddSaveDocumentAction(
|
|||
not_null<Ui::PopupMenu*> menu,
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> document) {
|
||||
const auto save = [=] {
|
||||
DocumentSaveClickHandler::Save(origin, document, nullptr, true);
|
||||
};
|
||||
menu->addAction(
|
||||
lang(document->isVideoFile()
|
||||
? lng_context_save_video
|
||||
|
@ -135,7 +138,7 @@ void AddSaveDocumentAction(
|
|||
App::LambdaDelayed(
|
||||
st::defaultDropdownMenu.menu.ripple.hideDuration,
|
||||
&Auth(),
|
||||
[=] { DocumentSaveClickHandler::Save(origin, document, true); }));
|
||||
save));
|
||||
}
|
||||
|
||||
void AddDocumentActions(
|
||||
|
|
|
@ -1277,6 +1277,7 @@ void ListWidget::showContextMenu(
|
|||
DocumentSaveClickHandler::Save(
|
||||
itemFullId,
|
||||
document,
|
||||
App::histItemById(itemFullId),
|
||||
true);
|
||||
});
|
||||
_contextMenu->addAction(
|
||||
|
|
|
@ -894,7 +894,6 @@ bool File::updateStatusText() const {
|
|||
} else if (_document->loading()) {
|
||||
statusSize = _document->loadOffset();
|
||||
} else if (_document->loaded()) {
|
||||
using State = Media::Player::State;
|
||||
if (_document->isVoiceMessage()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
|
|
|
@ -729,10 +729,10 @@ void Mixer::resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered
|
|||
return;
|
||||
}
|
||||
|
||||
const auto stoppedAtEnd = (alState == AL_STOPPED)
|
||||
const auto stoppedAtEnd = track->state.waitingForData
|
||||
|| ((alState == AL_STOPPED)
|
||||
&& (!IsStopped(track->state.state)
|
||||
|| IsStoppedAtEnd(track->state.state))
|
||||
|| track->state.waitingForData;
|
||||
|| IsStoppedAtEnd(track->state.state)));
|
||||
positionInBuffered = stoppedAtEnd
|
||||
? track->bufferedLength
|
||||
: alSampleOffset;
|
||||
|
@ -1438,10 +1438,10 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
|
|||
}
|
||||
|
||||
int32 emitSignals = 0;
|
||||
const auto stoppedAtEnd = (alState == AL_STOPPED)
|
||||
const auto stoppedAtEnd = track->state.waitingForData
|
||||
|| ((alState == AL_STOPPED)
|
||||
&& (!IsStopped(track->state.state)
|
||||
|| IsStoppedAtEnd(track->state.state))
|
||||
|| track->state.waitingForData;
|
||||
|| IsStoppedAtEnd(track->state.state)));
|
||||
const auto positionInBuffered = stoppedAtEnd
|
||||
? track->bufferedLength
|
||||
: alSampleOffset;
|
||||
|
|
|
@ -47,5 +47,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Player
|
||||
} // namespace Media
|
||||
|
|
|
@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/widgets/buttons.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_instance.h"
|
||||
#include "media/player/media_player_volume_controller.h"
|
||||
|
@ -62,7 +62,7 @@ CoverWidget::CoverWidget(QWidget *parent) : RpWidget(parent)
|
|||
, _timeLabel(this, st::mediaPlayerTime)
|
||||
, _close(this, st::mediaPlayerPanelClose)
|
||||
, _playbackSlider(this, st::mediaPlayerPanelPlayback)
|
||||
, _playback(std::make_unique<Clip::Playback>())
|
||||
, _playbackProgress(std::make_unique<View::PlaybackProgress>())
|
||||
, _playPause(this)
|
||||
, _volumeToggle(this, st::mediaPlayerVolumeToggle)
|
||||
, _volumeController(this)
|
||||
|
@ -76,18 +76,18 @@ CoverWidget::CoverWidget(QWidget *parent) : RpWidget(parent)
|
|||
_timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
setMouseTracking(true);
|
||||
|
||||
_playback->setInLoadingStateChangedCallback([=](bool loading) {
|
||||
_playbackProgress->setInLoadingStateChangedCallback([=](bool loading) {
|
||||
_playbackSlider->setDisabled(loading);
|
||||
});
|
||||
_playback->setValueChangedCallback([=](float64 value) {
|
||||
_playbackProgress->setValueChangedCallback([=](float64 value) {
|
||||
_playbackSlider->setValue(value);
|
||||
});
|
||||
_playbackSlider->setChangeProgressCallback([=](float64 value) {
|
||||
_playback->setValue(value, false);
|
||||
_playbackProgress->setValue(value, false);
|
||||
handleSeekProgress(value);
|
||||
});
|
||||
_playbackSlider->setChangeFinishedCallback([=](float64 value) {
|
||||
_playback->setValue(value, false);
|
||||
_playbackProgress->setValue(value, false);
|
||||
handleSeekFinished(value);
|
||||
});
|
||||
_playPause->setClickedCallback([=] {
|
||||
|
@ -240,9 +240,9 @@ void CoverWidget::handleSongUpdate(const TrackState &state) {
|
|||
}
|
||||
|
||||
if (state.id.audio()->loading()) {
|
||||
_playback->updateLoadingState(state.id.audio()->progress());
|
||||
_playbackProgress->updateLoadingState(state.id.audio()->progress());
|
||||
} else {
|
||||
_playback->updateState(state);
|
||||
_playbackProgress->updateState(state);
|
||||
}
|
||||
|
||||
auto stopped = IsStoppedOrStopping(state.state);
|
||||
|
|
|
@ -19,9 +19,9 @@ class MediaSlider;
|
|||
} // namespace Ui
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
class Playback;
|
||||
} // namespace Clip
|
||||
namespace View {
|
||||
class PlaybackProgress;
|
||||
} // namespace View
|
||||
|
||||
namespace Player {
|
||||
|
||||
|
@ -71,7 +71,7 @@ private:
|
|||
object_ptr<Ui::LabelSimple> _timeLabel;
|
||||
object_ptr<Ui::IconButton> _close;
|
||||
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<PlayButton> _playPause;
|
||||
object_ptr<Ui::IconButton> _nextTrack = { nullptr };
|
||||
|
@ -82,5 +82,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Player
|
||||
} // namespace Media
|
||||
|
|
|
@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_element.h"
|
||||
#include "media/audio/media_audio.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_round_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
|
@ -210,7 +210,7 @@ Clip::Reader *Float::getReader() const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Clip::Playback *Float::getPlayback() const {
|
||||
View::PlaybackProgress *Float::getPlayback() const {
|
||||
if (detached()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ enum class Column;
|
|||
} // namespace Window
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
class Playback;
|
||||
} // namespace Clip
|
||||
namespace View {
|
||||
class PlaybackProgress;
|
||||
} // namespace View
|
||||
|
||||
namespace Player {
|
||||
|
||||
|
@ -70,7 +70,7 @@ protected:
|
|||
private:
|
||||
float64 outRatio() const;
|
||||
Clip::Reader *getReader() const;
|
||||
Clip::Playback *getPlayback() const;
|
||||
View::PlaybackProgress *getPlayback() const;
|
||||
void repaintItem();
|
||||
void prepareShadow();
|
||||
bool hasFrame() const;
|
||||
|
|
|
@ -212,5 +212,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Player
|
||||
} // namespace Media
|
||||
|
|
|
@ -118,5 +118,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Player
|
||||
} // namespace Media
|
||||
|
|
|
@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/audio/media_audio.h"
|
||||
#include "media/clip/media_clip_reader.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 "window/window_controller.h"
|
||||
#include "data/data_media_types.h"
|
||||
|
@ -56,8 +56,8 @@ RoundController::RoundController(
|
|||
_context->fullId(),
|
||||
[=](Clip::Notification notification) { callback(notification); },
|
||||
Clip::Reader::Mode::Video);
|
||||
_playback = std::make_unique<Clip::Playback>();
|
||||
_playback->setValueChangedCallback([=](float64 value) {
|
||||
_playbackProgress = std::make_unique<View::PlaybackProgress>();
|
||||
_playbackProgress->setValueChangedCallback([=](float64 value) {
|
||||
Auth().data().requestItemRepaint(_context);
|
||||
});
|
||||
Auth().data().markMediaRead(_data);
|
||||
|
@ -95,8 +95,8 @@ Clip::Reader *RoundController::reader() const {
|
|||
return _reader ? _reader.get() : nullptr;
|
||||
}
|
||||
|
||||
Clip::Playback *RoundController::playback() const {
|
||||
return _playback.get();
|
||||
View::PlaybackProgress *RoundController::playback() const {
|
||||
return _playbackProgress.get();
|
||||
}
|
||||
|
||||
void RoundController::handleAudioUpdate(const TrackState &state) {
|
||||
|
@ -112,8 +112,8 @@ void RoundController::handleAudioUpdate(const TrackState &state) {
|
|||
} else if (another) {
|
||||
return;
|
||||
}
|
||||
if (_playback) {
|
||||
_playback->updateState(state);
|
||||
if (_playbackProgress) {
|
||||
_playbackProgress->updateState(state);
|
||||
}
|
||||
if (IsPaused(state.state) || state.state == State::Pausing) {
|
||||
if (!_reader->videoPaused()) {
|
||||
|
|
|
@ -15,9 +15,9 @@ class Controller;
|
|||
} // namespace Window
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
class Playback;
|
||||
} // namespace Clip
|
||||
namespace View {
|
||||
class PlaybackProgress;
|
||||
} // namespace View
|
||||
} // namespace Media
|
||||
|
||||
namespace Media {
|
||||
|
@ -39,7 +39,7 @@ public:
|
|||
FullMsgId contextId() const;
|
||||
void pauseResume();
|
||||
Clip::Reader *reader() const;
|
||||
Clip::Playback *playback() const;
|
||||
View::PlaybackProgress *playback() const;
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
|
||||
|
@ -59,7 +59,7 @@ private:
|
|||
not_null<DocumentData*> _data;
|
||||
not_null<HistoryItem*> _context;
|
||||
Clip::ReaderPointer _reader;
|
||||
std::unique_ptr<Clip::Playback> _playback;
|
||||
std::unique_ptr<View::PlaybackProgress> _playbackProgress;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
|
|
@ -74,5 +74,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Player
|
||||
} // namespace Media
|
||||
|
|
|
@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/effects/ripple_animation.h"
|
||||
#include "lang/lang_keys.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_instance.h"
|
||||
#include "media/player/media_player_volume_controller.h"
|
||||
|
@ -86,7 +86,7 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
|
|||
, _close(this, st::mediaPlayerClose)
|
||||
, _shadow(this)
|
||||
, _playbackSlider(this, st::mediaPlayerPlayback)
|
||||
, _playback(std::make_unique<Clip::Playback>()) {
|
||||
, _playbackProgress(std::make_unique<View::PlaybackProgress>()) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
setMouseTracking(true);
|
||||
resize(width(), st::mediaPlayerHeight + st::lineWidth);
|
||||
|
@ -94,24 +94,24 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
|
|||
_nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
_timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_playback->setInLoadingStateChangedCallback([this](bool loading) {
|
||||
_playbackProgress->setInLoadingStateChangedCallback([this](bool loading) {
|
||||
_playbackSlider->setDisabled(loading);
|
||||
});
|
||||
_playback->setValueChangedCallback([this](float64 value) {
|
||||
_playbackProgress->setValueChangedCallback([this](float64 value) {
|
||||
_playbackSlider->setValue(value);
|
||||
});
|
||||
_playbackSlider->setChangeProgressCallback([this](float64 value) {
|
||||
if (_type != AudioMsgId::Type::Song) {
|
||||
return; // Round video seek is not supported for now :(
|
||||
}
|
||||
_playback->setValue(value, false);
|
||||
_playbackProgress->setValue(value, false);
|
||||
handleSeekProgress(value);
|
||||
});
|
||||
_playbackSlider->setChangeFinishedCallback([this](float64 value) {
|
||||
if (_type != AudioMsgId::Type::Song) {
|
||||
return; // Round video seek is not supported for now :(
|
||||
}
|
||||
_playback->setValue(value, false);
|
||||
_playbackProgress->setValue(value, false);
|
||||
handleSeekFinished(value);
|
||||
});
|
||||
_playPause->setClickedCallback([this] {
|
||||
|
@ -430,9 +430,9 @@ void Widget::handleSongUpdate(const TrackState &state) {
|
|||
}
|
||||
|
||||
if (state.id.audio()->loading()) {
|
||||
_playback->updateLoadingState(state.id.audio()->progress());
|
||||
_playbackProgress->updateLoadingState(state.id.audio()->progress());
|
||||
} else {
|
||||
_playback->updateState(state);
|
||||
_playbackProgress->updateState(state);
|
||||
}
|
||||
|
||||
auto stopped = IsStoppedOrStopping(state.state);
|
||||
|
|
|
@ -20,10 +20,12 @@ class FilledSlider;
|
|||
} // namespace Ui
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
class Playback;
|
||||
namespace View {
|
||||
class PlaybackProgress;
|
||||
} // namespace Clip
|
||||
} // namespace Media
|
||||
|
||||
namespace Media {
|
||||
namespace Player {
|
||||
|
||||
class PlayButton;
|
||||
|
@ -109,11 +111,11 @@ private:
|
|||
object_ptr<Ui::IconButton> _close;
|
||||
object_ptr<Ui::PlainShadow> _shadow = { nullptr };
|
||||
object_ptr<Ui::FilledSlider> _playbackSlider;
|
||||
std::unique_ptr<Clip::Playback> _playback;
|
||||
std::unique_ptr<View::PlaybackProgress> _playbackProgress;
|
||||
|
||||
rpl::lifetime _playlistChangesLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Player
|
||||
} // namespace Media
|
||||
|
|
|
@ -36,6 +36,7 @@ struct PlaybackOptions {
|
|||
crl::time position = 0;
|
||||
float64 speed = 1.; // Valid values between 0.5 and 2.
|
||||
bool syncVideoByAudio = true;
|
||||
bool dropStaleFrames = true;
|
||||
};
|
||||
|
||||
struct TrackState {
|
||||
|
|
|
@ -513,7 +513,7 @@ bool Player::failed() const {
|
|||
}
|
||||
|
||||
bool Player::playing() const {
|
||||
return (_stage == Stage::Started) && !_paused;
|
||||
return (_stage == Stage::Started) && !_paused && !finished();
|
||||
}
|
||||
|
||||
bool Player::buffering() const {
|
||||
|
@ -524,6 +524,12 @@ bool Player::paused() const {
|
|||
return _pausedByUser;
|
||||
}
|
||||
|
||||
bool Player::finished() const {
|
||||
return (_stage == Stage::Started)
|
||||
&& (!_audio || _audioFinished)
|
||||
&& (!_video || _videoFinished);
|
||||
}
|
||||
|
||||
void Player::setSpeed(float64 speed) {
|
||||
Expects(valid());
|
||||
Expects(speed >= 0.5 && speed <= 2.);
|
||||
|
|
|
@ -44,10 +44,11 @@ public:
|
|||
float64 speed() const;
|
||||
void setSpeed(float64 speed); // 0.5 <= speed <= 2.
|
||||
|
||||
[[nodiscard]] bool failed() const;
|
||||
[[nodiscard]] bool playing() const;
|
||||
[[nodiscard]] bool buffering() const;
|
||||
[[nodiscard]] bool paused() const;
|
||||
[[nodiscard]] bool failed() const;
|
||||
[[nodiscard]] bool finished() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<Update, Error> updates() const;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/streaming/media_streaming_utility.h"
|
||||
|
||||
#include "media/streaming/media_streaming_common.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/opt.h>
|
||||
|
@ -20,6 +21,7 @@ namespace {
|
|||
constexpr auto kSkipInvalidDataPackets = 10;
|
||||
constexpr auto kAlignImageBy = 16;
|
||||
constexpr auto kPixelBytesSize = 4;
|
||||
constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied;
|
||||
|
||||
void AlignedImageBufferCleanupHandler(void* data) {
|
||||
const auto buffer = static_cast<uchar*>(data);
|
||||
|
@ -39,8 +41,16 @@ void ClearFrameMemory(AVFrame *frame) {
|
|||
|
||||
} // 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.
|
||||
QImage CreateImageForOriginalFrame(QSize size) {
|
||||
QImage CreateFrameStorage(QSize size) {
|
||||
const auto width = size.width();
|
||||
const auto height = size.height();
|
||||
const auto widthAlign = kAlignImageBy / kPixelBytesSize;
|
||||
|
@ -59,7 +69,7 @@ QImage CreateImageForOriginalFrame(QSize size) {
|
|||
width,
|
||||
height,
|
||||
perLine,
|
||||
QImage::Format_ARGB32_Premultiplied,
|
||||
kImageFormat,
|
||||
AlignedImageBufferCleanupHandler,
|
||||
cleanupData);
|
||||
}
|
||||
|
@ -267,18 +277,26 @@ AvErrorWrap ReadNextFrame(Stream &stream) {
|
|||
return error;
|
||||
}
|
||||
|
||||
QImage ConvertFrame(
|
||||
Stream &stream,
|
||||
QSize resize,
|
||||
QImage storage) {
|
||||
bool GoodForRequest(const QImage &image, const FrameRequest &request) {
|
||||
if (request.resize.isEmpty()) {
|
||||
return true;
|
||||
} 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);
|
||||
|
||||
const auto frame = stream.frame.get();
|
||||
const auto frameSize = QSize(frame->width, frame->height);
|
||||
if (frameSize.isEmpty()) {
|
||||
LOG(("Streaming Error: Bad frame size %1,%2"
|
||||
).arg(resize.width()
|
||||
).arg(resize.height()));
|
||||
).arg(frameSize.width()
|
||||
).arg(frameSize.height()));
|
||||
return QImage();
|
||||
} else if (!frame->data[0]) {
|
||||
LOG(("Streaming Error: Bad frame data."));
|
||||
|
@ -289,11 +307,9 @@ QImage ConvertFrame(
|
|||
} else if (RotationSwapWidthHeight(stream.rotation)) {
|
||||
resize.transpose();
|
||||
}
|
||||
if (storage.isNull()
|
||||
|| storage.size() != resize
|
||||
|| !storage.isDetached()
|
||||
|| !IsAlignedImage(storage)) {
|
||||
storage = CreateImageForOriginalFrame(resize);
|
||||
|
||||
if (!GoodStorageForFrame(storage, resize)) {
|
||||
storage = CreateFrameStorage(resize);
|
||||
}
|
||||
const auto format = AV_PIX_FMT_BGRA;
|
||||
const auto hasDesiredFormat = (frame->format == format)
|
||||
|
@ -343,7 +359,24 @@ QImage ConvertFrame(
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -173,11 +173,19 @@ void LogError(QLatin1String method, AvErrorWrap error);
|
|||
[[nodiscard]] AvErrorWrap ProcessPacket(Stream &stream, Packet &&packet);
|
||||
[[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(
|
||||
Stream& stream,
|
||||
QSize resize,
|
||||
QImage storage);
|
||||
[[nodiscard]] QImage PrepareByRequest(
|
||||
const QImage &original,
|
||||
const FrameRequest &request,
|
||||
QImage storage);
|
||||
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
|
|
|
@ -43,6 +43,7 @@ public:
|
|||
void setSpeed(float64 speed);
|
||||
void interrupt();
|
||||
void frameDisplayed();
|
||||
void updateFrameRequest(const FrameRequest &request);
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool interrupted() const;
|
||||
|
@ -79,6 +80,7 @@ private:
|
|||
crl::time _nextFrameDisplayTime = kTimeUnknown;
|
||||
rpl::event_stream<crl::time> _nextFrameTimeUpdates;
|
||||
rpl::event_stream<> _waitingForData;
|
||||
FrameRequest _request;
|
||||
|
||||
bool _queued = false;
|
||||
base::ConcurrentTimer _readFramesTimer;
|
||||
|
@ -150,10 +152,15 @@ void VideoTrackObject::readFrames() {
|
|||
if (interrupted()) {
|
||||
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) {
|
||||
if (readFrame(frame)) {
|
||||
while (readFrame(frame)) {
|
||||
if (!dropStaleFrames || !VideoTrack::IsStale(frame, time)) {
|
||||
presentFrameIfNeeded();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [&](Shared::PrepareNextCheck delay) {
|
||||
Expects(delay > 0);
|
||||
|
@ -183,17 +190,8 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
|
|||
_error();
|
||||
return false;
|
||||
}
|
||||
frame->original = ConvertFrame(
|
||||
_stream,
|
||||
QSize(),
|
||||
std::move(frame->original));
|
||||
frame->position = position;
|
||||
frame->displayed = kTimeUnknown;
|
||||
|
||||
// #TODO streaming later prepare frame
|
||||
//frame->request
|
||||
//frame->prepared
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -202,7 +200,24 @@ void VideoTrackObject::presentFrameIfNeeded() {
|
|||
return;
|
||||
}
|
||||
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) {
|
||||
const auto trackLeft = presented.displayPosition - time.trackTime;
|
||||
|
||||
|
@ -268,6 +283,10 @@ void VideoTrackObject::frameDisplayed() {
|
|||
queueReadFrames();
|
||||
}
|
||||
|
||||
void VideoTrackObject::updateFrameRequest(const FrameRequest &request) {
|
||||
_request = request;
|
||||
}
|
||||
|
||||
bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) {
|
||||
if (ProcessPacket(_stream, std::move(packet)).failed()) {
|
||||
return false;
|
||||
|
@ -412,31 +431,20 @@ not_null<VideoTrack::Frame*> VideoTrack::Shared::getFrame(int index) {
|
|||
return &_frames[index];
|
||||
}
|
||||
|
||||
bool VideoTrack::Shared::IsPrepared(not_null<Frame*> frame) {
|
||||
return (frame->position != kTimeUnknown)
|
||||
&& (frame->displayed == kTimeUnknown)
|
||||
&& !frame->original.isNull();
|
||||
}
|
||||
|
||||
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 {
|
||||
auto VideoTrack::Shared::prepareState(
|
||||
crl::time trackTime,
|
||||
bool dropStaleFrames)
|
||||
-> PrepareState {
|
||||
const auto prepareNext = [&](int index) -> PrepareState {
|
||||
const auto frame = getFrame(index);
|
||||
const auto next = getFrame((index + 1) % kFramesCount);
|
||||
if (!IsPrepared(frame)) {
|
||||
if (!IsDecoded(frame)) {
|
||||
return frame;
|
||||
} else if (IsStale(frame, trackTime)) {
|
||||
} else if (dropStaleFrames && IsStale(frame, trackTime)) {
|
||||
std::swap(*frame, *next);
|
||||
next->displayed = kDisplaySkipped;
|
||||
return IsPrepared(frame) ? next : frame;
|
||||
} else if (!IsPrepared(next)) {
|
||||
return IsDecoded(frame) ? next : frame;
|
||||
} else if (!IsDecoded(next)) {
|
||||
return next;
|
||||
} else {
|
||||
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 frame = getFrame(index);
|
||||
// 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()) {
|
||||
|
@ -461,11 +469,18 @@ auto VideoTrack::Shared::prepareState(crl::time trackTime) -> 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 frame = getFrame(index);
|
||||
Assert(IsPrepared(frame));
|
||||
const auto position = frame->position;
|
||||
prepare(frame);
|
||||
if (!IsPrepared(frame)) {
|
||||
return { kTimeUnknown, crl::time(0) };
|
||||
}
|
||||
|
||||
// Release this frame to the main thread for rendering.
|
||||
_counter.store(
|
||||
|
@ -476,8 +491,8 @@ auto VideoTrack::Shared::presentFrame(crl::time trackTime) -> PresentFrame {
|
|||
const auto nextCheckDelay = [&](int index) -> PresentFrame {
|
||||
const auto frame = getFrame(index);
|
||||
const auto next = getFrame((index + 1) % kFramesCount);
|
||||
if (!IsPrepared(frame)
|
||||
|| !IsPrepared(next)
|
||||
if (!IsDecoded(frame)
|
||||
|| !IsDecoded(next)
|
||||
|| IsStale(frame, trackTime)) {
|
||||
return { kTimeUnknown, crl::time(0) };
|
||||
}
|
||||
|
@ -594,24 +609,50 @@ crl::time VideoTrack::markFrameDisplayed(crl::time now) {
|
|||
return position;
|
||||
}
|
||||
|
||||
QImage VideoTrack::frame(const FrameRequest &request) const {
|
||||
QImage VideoTrack::frame(const FrameRequest &request) {
|
||||
const auto frame = _shared->frameForPaint();
|
||||
Assert(frame != nullptr);
|
||||
Assert(!frame->original.isNull());
|
||||
const auto changed = (frame->request != request);
|
||||
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;
|
||||
} else if (frame->prepared.isNull() || frame->request != request) {
|
||||
// #TODO streaming later prepare frame
|
||||
//frame->request = request;
|
||||
//frame->prepared = PrepareFrame(
|
||||
// frame->original,
|
||||
// request,
|
||||
// std::move(frame->prepared));
|
||||
} else if (frame->prepared.isNull() || !useExistingPrepared) {
|
||||
frame->prepared = PrepareByRequest(
|
||||
frame->original,
|
||||
frame->request,
|
||||
std::move(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 {
|
||||
return _wrapped.producer_on_main([](const Implementation &unwrapped) {
|
||||
return unwrapped.displayFrameAt();
|
||||
|
|
|
@ -46,7 +46,7 @@ public:
|
|||
// Called from the main thread.
|
||||
// Returns the position of the displayed frame.
|
||||
[[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<> waitingForData() const;
|
||||
|
||||
|
@ -81,8 +81,15 @@ private:
|
|||
void init(QImage &&cover, crl::time position);
|
||||
[[nodiscard]] bool initialized() const;
|
||||
|
||||
[[nodiscard]] PrepareState prepareState(crl::time trackTime);
|
||||
[[nodiscard]] PresentFrame presentFrame(crl::time trackTime);
|
||||
[[nodiscard]] PrepareState prepareState(
|
||||
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.
|
||||
// Returns the position of the displayed frame.
|
||||
|
@ -91,10 +98,6 @@ private:
|
|||
|
||||
private:
|
||||
[[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;
|
||||
|
||||
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 AVRational _streamTimeBase;
|
||||
//const int _streamRotation = 0;
|
||||
|
|
|
@ -650,7 +650,7 @@ bool GroupThumbs::hidden() const {
|
|||
void GroupThumbs::checkForAnimationStart() {
|
||||
if (_waitingForAnimationStart) {
|
||||
_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
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_shared_media.h"
|
||||
#include "data/data_user_photos.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "media/view/media_view_playback_controls.h"
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
|
@ -35,9 +36,13 @@ namespace Media {
|
|||
namespace Player {
|
||||
struct TrackState;
|
||||
} // namespace Player
|
||||
namespace Clip {
|
||||
class Controller;
|
||||
} // namespace Clip
|
||||
namespace Streaming {
|
||||
struct Update;
|
||||
struct Error;
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
|
||||
namespace Media {
|
||||
namespace View {
|
||||
|
||||
class GroupThumbs;
|
||||
|
@ -48,7 +53,11 @@ using OverlayParent = Ui::RpWidgetWrap<QOpenGLWidget>;
|
|||
using OverlayParent = Ui::RpWidget;
|
||||
#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
|
||||
|
||||
public:
|
||||
|
@ -70,7 +79,6 @@ public:
|
|||
void activateControls();
|
||||
void onDocClick();
|
||||
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
PeerData *ui_getPeerForMouseAction();
|
||||
|
||||
void clearData();
|
||||
|
@ -105,14 +113,9 @@ private slots:
|
|||
|
||||
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:
|
||||
struct Streamed;
|
||||
|
||||
enum OverState {
|
||||
OverNone,
|
||||
OverLeftNav,
|
||||
|
@ -149,6 +152,16 @@ private:
|
|||
|
||||
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 moveToScreen();
|
||||
bool moveToNext(int delta);
|
||||
|
@ -212,17 +225,18 @@ private:
|
|||
void updateCursor();
|
||||
void setZoomLevel(int newZoom);
|
||||
|
||||
void updateVideoPlaybackState(const Media::Player::TrackState &state);
|
||||
void updateSilentVideoPlaybackState();
|
||||
void restartVideoAtSeekPosition(crl::time positionMs);
|
||||
void toggleVideoPaused();
|
||||
void updatePlaybackState();
|
||||
void restartAtSeekPosition(crl::time position);
|
||||
void togglePauseResume();
|
||||
|
||||
void createClipController();
|
||||
void refreshClipControllerGeometry();
|
||||
void refreshCaptionGeometry();
|
||||
|
||||
void initAnimation();
|
||||
void createClipReader();
|
||||
void initStreaming();
|
||||
void initStreamingThumbnail();
|
||||
void createStreamingObjects();
|
||||
void handleStreamingUpdate(Streaming::Update &&update);
|
||||
void handleStreamingError(Streaming::Error &&error);
|
||||
|
||||
void initThemePreview();
|
||||
void destroyThemePreview();
|
||||
|
@ -231,6 +245,8 @@ private:
|
|||
void documentUpdated(DocumentData *doc);
|
||||
void changingMsgId(not_null<HistoryItem*> row, MsgId newId);
|
||||
|
||||
QRect contentRect() const;
|
||||
|
||||
// Radial animation interface.
|
||||
float64 radialProgress() const;
|
||||
bool radialLoading() const;
|
||||
|
@ -262,6 +278,16 @@ private:
|
|||
void validatePhotoImage(Image *image, bool blurred);
|
||||
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;
|
||||
|
||||
PhotoData *_photo = nullptr;
|
||||
|
@ -285,12 +311,11 @@ private:
|
|||
QString _dateText;
|
||||
QString _headerText;
|
||||
|
||||
object_ptr<Media::Clip::Controller> _clipController = { nullptr };
|
||||
DocumentData *_autoplayVideoDocument = nullptr;
|
||||
bool _fullScreenVideo = false;
|
||||
int _fullScreenZoomCache = 0;
|
||||
|
||||
std::unique_ptr<Media::View::GroupThumbs> _groupThumbs;
|
||||
std::unique_ptr<GroupThumbs> _groupThumbs;
|
||||
QRect _groupThumbsRect;
|
||||
int _groupThumbsAvailableWidth = 0;
|
||||
int _groupThumbsLeft = 0;
|
||||
|
@ -298,9 +323,8 @@ private:
|
|||
Text _caption;
|
||||
QRect _captionRect;
|
||||
|
||||
crl::time _animStarted;
|
||||
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
int _x = 0, _y = 0, _w = 0, _h = 0;
|
||||
int _xStart = 0, _yStart = 0;
|
||||
int _zoom = 0; // < 0 - out, 0 - none, > 0 - in
|
||||
|
@ -309,21 +333,9 @@ private:
|
|||
bool _pressed = false;
|
||||
int32 _dragging = 0;
|
||||
QPixmap _current;
|
||||
Media::Clip::ReaderPointer _gif;
|
||||
bool _blurred = true;
|
||||
|
||||
// Video without audio stream playback information.
|
||||
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();
|
||||
std::unique_ptr<Streamed> _streamed;
|
||||
|
||||
const style::icon *_docIcon = nullptr;
|
||||
style::color _docIconColor;
|
||||
|
|
|
@ -5,10 +5,10 @@ the official desktop application for the Telegram messaging service.
|
|||
For license and copyright information please follow this link:
|
||||
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/view/media_clip_playback.h"
|
||||
#include "media/view/media_view_playback_progress.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/effects/fade_animation.h"
|
||||
|
@ -17,69 +17,89 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "styles/style_mediaview.h"
|
||||
|
||||
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)
|
||||
, _playbackSlider(this, st::mediaviewPlayback)
|
||||
, _playback(std::make_unique<Playback>())
|
||||
, _playbackProgress(std::make_unique<PlaybackProgress>())
|
||||
, _volumeController(this, st::mediaviewPlayback)
|
||||
, _fullScreenToggle(this, st::mediaviewFullScreenButton)
|
||||
, _playedAlready(this, st::mediaviewPlayProgressLabel)
|
||||
, _toPlayLeft(this, st::mediaviewPlayProgressLabel)
|
||||
, _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) {
|
||||
_fadeAnimation->show();
|
||||
_fadeAnimation->setFinishedCallback([this] { fadeFinished(); });
|
||||
_fadeAnimation->setUpdatedCallback([this](float64 opacity) { fadeUpdated(opacity); });
|
||||
_fadeAnimation->setFinishedCallback([=] {
|
||||
fadeFinished();
|
||||
});
|
||||
_fadeAnimation->setUpdatedCallback([=](float64 opacity) {
|
||||
fadeUpdated(opacity);
|
||||
});
|
||||
|
||||
_volumeController->setValue(Global::VideoVolume());
|
||||
_volumeController->setChangeProgressCallback([=](float64 value) {
|
||||
volumeChanged(value);
|
||||
_delegate->playbackControlsVolumeChanged(value);
|
||||
});
|
||||
//_volumeController->setChangeFinishedCallback();
|
||||
|
||||
connect(_playPauseResume, SIGNAL(clicked()), this, SIGNAL(playPressed()));
|
||||
connect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed()));
|
||||
//connect(_volumeController, SIGNAL(volumeChanged(float64)), this, SIGNAL(volumeChanged(float64)));
|
||||
_playPauseResume->addClickHandler([=] {
|
||||
if (_showPause) {
|
||||
_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);
|
||||
});
|
||||
_playback->setValueChangedCallback([this](float64 value) {
|
||||
_playbackProgress->setValueChangedCallback([=](float64 value) {
|
||||
_playbackSlider->setValue(value);
|
||||
});
|
||||
_playbackSlider->setChangeProgressCallback([this](float64 value) {
|
||||
_playback->setValue(value, false);
|
||||
handleSeekProgress(value); // This may destroy Controller.
|
||||
_playbackSlider->setChangeProgressCallback([=](float64 value) {
|
||||
_playbackProgress->setValue(value, false);
|
||||
|
||||
// This may destroy PlaybackControls.
|
||||
handleSeekProgress(value);
|
||||
});
|
||||
_playbackSlider->setChangeFinishedCallback([this](float64 value) {
|
||||
_playback->setValue(value, false);
|
||||
_playbackSlider->setChangeFinishedCallback([=](float64 value) {
|
||||
_playbackProgress->setValue(value, false);
|
||||
handleSeekFinished(value);
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::handleSeekProgress(float64 progress) {
|
||||
void PlaybackControls::handleSeekProgress(float64 progress) {
|
||||
if (!_lastDurationMs) return;
|
||||
|
||||
auto positionMs = snap(static_cast<crl::time>(progress * _lastDurationMs), 0LL, _lastDurationMs);
|
||||
if (_seekPositionMs != positionMs) {
|
||||
_seekPositionMs = positionMs;
|
||||
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;
|
||||
|
||||
auto positionMs = snap(static_cast<crl::time>(progress * _lastDurationMs), 0LL, _lastDurationMs);
|
||||
_seekPositionMs = -1;
|
||||
emit seekFinished(positionMs);
|
||||
_delegate->playbackControlsSeekFinished(positionMs);
|
||||
refreshTimeTexts();
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void Controller::startFading(Callback start) {
|
||||
void PlaybackControls::startFading(Callback start) {
|
||||
if (!_fadeAnimation->animating()) {
|
||||
showChildren();
|
||||
_playbackSlider->disablePaint(true);
|
||||
|
@ -103,45 +123,42 @@ void Controller::startFading(Callback start) {
|
|||
_volumeController->disablePaint(false);
|
||||
}
|
||||
|
||||
void Controller::showAnimated() {
|
||||
void PlaybackControls::showAnimated() {
|
||||
startFading([this]() {
|
||||
_fadeAnimation->fadeIn(st::mediaviewShowDuration);
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::hideAnimated() {
|
||||
void PlaybackControls::hideAnimated() {
|
||||
startFading([this]() {
|
||||
_fadeAnimation->fadeOut(st::mediaviewHideDuration);
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::fadeFinished() {
|
||||
void PlaybackControls::fadeFinished() {
|
||||
fadeUpdated(_fadeAnimation->visible() ? 1. : 0.);
|
||||
}
|
||||
|
||||
void Controller::fadeUpdated(float64 opacity) {
|
||||
void PlaybackControls::fadeUpdated(float64 opacity) {
|
||||
_playbackSlider->setFadeOpacity(opacity);
|
||||
_volumeController->setFadeOpacity(opacity);
|
||||
}
|
||||
|
||||
void Controller::updatePlayback(const Player::TrackState &state) {
|
||||
void PlaybackControls::updatePlayback(const Player::TrackState &state) {
|
||||
updatePlayPauseResumeState(state);
|
||||
_playback->updateState(state);
|
||||
_playbackProgress->updateState(state);
|
||||
updateTimeTexts(state);
|
||||
}
|
||||
|
||||
void Controller::updatePlayPauseResumeState(const Player::TrackState &state) {
|
||||
void PlaybackControls::updatePlayPauseResumeState(const Player::TrackState &state) {
|
||||
auto showPause = ShowPauseIcon(state.state) || (_seekPositionMs >= 0);
|
||||
if (showPause != _showPause) {
|
||||
disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed()));
|
||||
_showPause = showPause;
|
||||
connect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed()));
|
||||
|
||||
_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;
|
||||
|
||||
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 timeAlready = _timeAlready;
|
||||
auto timeLeft = _timeLeft;
|
||||
|
@ -189,16 +206,16 @@ void Controller::refreshTimeTexts() {
|
|||
}
|
||||
}
|
||||
|
||||
void Controller::setInFullScreen(bool inFullScreen) {
|
||||
_fullScreenToggle->setIconOverride(inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr, inFullScreen ? &st::mediaviewFullScreenOutIconOver : nullptr);
|
||||
disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(toFullScreenPressed()));
|
||||
disconnect(_fullScreenToggle, SIGNAL(clicked()), this, SIGNAL(fromFullScreenPressed()));
|
||||
|
||||
auto handler = inFullScreen ? SIGNAL(fromFullScreenPressed()) : SIGNAL(toFullScreenPressed());
|
||||
connect(_fullScreenToggle, SIGNAL(clicked()), this, handler);
|
||||
void PlaybackControls::setInFullScreen(bool inFullScreen) {
|
||||
if (_inFullScreen != inFullScreen) {
|
||||
_inFullScreen = inFullScreen;
|
||||
_fullScreenToggle->setIconOverride(
|
||||
_inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr,
|
||||
_inFullScreen ? &st::mediaviewFullScreenOutIconOver : nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::resizeEvent(QResizeEvent *e) {
|
||||
void PlaybackControls::resizeEvent(QResizeEvent *e) {
|
||||
int playTop = (height() - _playPauseResume->height()) / 2;
|
||||
_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);
|
||||
}
|
||||
|
||||
void Controller::paintEvent(QPaintEvent *e) {
|
||||
void PlaybackControls::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
if (_fadeAnimation->paint(p)) {
|
||||
|
@ -231,11 +248,11 @@ void Controller::paintEvent(QPaintEvent *e) {
|
|||
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.
|
||||
}
|
||||
|
||||
Controller::~Controller() = default;
|
||||
PlaybackControls::~PlaybackControls() = default;
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace View
|
||||
} // namespace Media
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
namespace Ui {
|
||||
class LabelSimple;
|
||||
class FadeAnimation;
|
||||
|
@ -19,15 +21,24 @@ namespace Player {
|
|||
struct TrackState;
|
||||
} // namespace Player
|
||||
|
||||
namespace Clip {
|
||||
namespace View {
|
||||
|
||||
class Playback;
|
||||
|
||||
class Controller : public TWidget {
|
||||
Q_OBJECT
|
||||
class PlaybackProgress;
|
||||
|
||||
class PlaybackControls : public Ui::RpWidget {
|
||||
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 hideAnimated();
|
||||
|
@ -35,16 +46,7 @@ public:
|
|||
void updatePlayback(const Player::TrackState &state);
|
||||
void setInFullScreen(bool inFullScreen);
|
||||
|
||||
~Controller();
|
||||
|
||||
signals:
|
||||
void playPressed();
|
||||
void pausePressed();
|
||||
void seekProgress(crl::time positionMs);
|
||||
void seekFinished(crl::time positionMs);
|
||||
void volumeChanged(float64 volume);
|
||||
void toFullScreenPressed();
|
||||
void fromFullScreenPressed();
|
||||
~PlaybackControls();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
@ -64,6 +66,9 @@ private:
|
|||
void updateTimeTexts(const Player::TrackState &state);
|
||||
void refreshTimeTexts();
|
||||
|
||||
not_null<Delegate*> _delegate;
|
||||
|
||||
bool _inFullScreen = false;
|
||||
bool _showPause = false;
|
||||
bool _childrenHidden = false;
|
||||
QString _timeAlready, _timeLeft;
|
||||
|
@ -72,7 +77,7 @@ private:
|
|||
|
||||
object_ptr<Ui::IconButton> _playPauseResume;
|
||||
object_ptr<Ui::MediaSlider> _playbackSlider;
|
||||
std::unique_ptr<Playback> _playback;
|
||||
std::unique_ptr<PlaybackProgress> _playbackProgress;
|
||||
object_ptr<Ui::MediaSlider> _volumeController;
|
||||
object_ptr<Ui::IconButton> _fullScreenToggle;
|
||||
object_ptr<Ui::LabelSimple> _playedAlready;
|
||||
|
@ -82,5 +87,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace View
|
||||
} // namespace Media
|
|
@ -5,23 +5,23 @@ the official desktop application for the Telegram messaging service.
|
|||
For license and copyright information please follow this link:
|
||||
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 "styles/style_mediaview.h"
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
namespace View {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPlaybackAnimationDurationMs = crl::time(200);
|
||||
|
||||
} // 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;
|
||||
|
||||
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) {
|
||||
_inLoadingState = true;
|
||||
if (_inLoadingStateChanged) {
|
||||
|
@ -71,16 +71,16 @@ void Playback::updateLoadingState(float64 progress) {
|
|||
setValue(progress, animated);
|
||||
}
|
||||
|
||||
float64 Playback::value() const {
|
||||
float64 PlaybackProgress::value() const {
|
||||
return qMin(a_value.current(), 1.);
|
||||
}
|
||||
|
||||
float64 Playback::value(crl::time ms) {
|
||||
float64 PlaybackProgress::value(crl::time ms) {
|
||||
_a_value.step(ms);
|
||||
return value();
|
||||
}
|
||||
|
||||
void Playback::setValue(float64 value, bool animated) {
|
||||
void PlaybackProgress::setValue(float64 value, bool animated) {
|
||||
if (animated) {
|
||||
a_value.start(value);
|
||||
_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);
|
||||
if (dt >= 1.) {
|
||||
_a_value.stop();
|
||||
|
@ -106,5 +106,5 @@ void Playback::step_value(float64 ms, bool timer) {
|
|||
}
|
||||
}
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace View
|
||||
} // namespace Media
|
|
@ -14,11 +14,11 @@ namespace Player {
|
|||
struct TrackState;
|
||||
} // namespace Player
|
||||
|
||||
namespace Clip {
|
||||
namespace View {
|
||||
|
||||
class Playback {
|
||||
class PlaybackProgress {
|
||||
public:
|
||||
Playback();
|
||||
PlaybackProgress();
|
||||
|
||||
void setValueChangedCallback(Fn<void(float64)> callback) {
|
||||
_valueChanged = std::move(callback);
|
||||
|
@ -52,5 +52,5 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace View
|
||||
} // namespace Media
|
|
@ -849,7 +849,6 @@ bool Voice::updateStatusText() {
|
|||
statusSize = FileStatusSizeFailed;
|
||||
} else if (_data->loaded()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
using State = Media::Player::State;
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(_data, parent()->fullId(), state.id.playId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
|
@ -1220,7 +1219,6 @@ bool Document::updateStatusText() {
|
|||
} else if (_data->loaded()) {
|
||||
if (_data->isSong()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
using State = Media::Player::State;
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(_data, parent()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
|
|
|
@ -90,11 +90,11 @@ public:
|
|||
|
||||
template <
|
||||
typename OtherType,
|
||||
typename Error,
|
||||
typename OtherError,
|
||||
typename Generator,
|
||||
typename = std::enable_if_t<
|
||||
std::is_assignable_v<Type&, OtherType>>>
|
||||
variable(producer<OtherType, Error, Generator> &&stream) {
|
||||
variable(producer<OtherType, OtherError, Generator> &&stream) {
|
||||
std::move(stream)
|
||||
| start_with_next([=](auto &&data) {
|
||||
assign(std::forward<decltype(data)>(data));
|
||||
|
@ -103,12 +103,12 @@ public:
|
|||
|
||||
template <
|
||||
typename OtherType,
|
||||
typename Error,
|
||||
typename OtherError,
|
||||
typename Generator,
|
||||
typename = std::enable_if_t<
|
||||
std::is_assignable_v<Type&, OtherType>>>
|
||||
variable &operator=(
|
||||
producer<OtherType, Error, Generator> &&stream) {
|
||||
producer<OtherType, OtherError, Generator> &&stream) {
|
||||
_lifetime.destroy();
|
||||
std::move(stream)
|
||||
| start_with_next([=](auto &&data) {
|
||||
|
|
|
@ -200,6 +200,10 @@ inline T ConvertScale(T value) {
|
|||
return ConvertScale(value, cScale());
|
||||
}
|
||||
|
||||
inline QSize ConvertScale(QSize size) {
|
||||
return QSize(ConvertScale(size.width()), ConvertScale(size.height()));
|
||||
}
|
||||
|
||||
inline void SetScaleChecked(int scale) {
|
||||
const auto checked = (scale == kInterfaceScaleAuto)
|
||||
? kInterfaceScaleAuto
|
||||
|
|
|
@ -472,10 +472,10 @@
|
|||
<(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.h
|
||||
<(src_loc)/media/view/media_clip_controller.cpp
|
||||
<(src_loc)/media/view/media_clip_controller.h
|
||||
<(src_loc)/media/view/media_clip_playback.cpp
|
||||
<(src_loc)/media/view/media_clip_playback.h
|
||||
<(src_loc)/media/view/media_view_playback_controls.cpp
|
||||
<(src_loc)/media/view/media_view_playback_controls.h
|
||||
<(src_loc)/media/view/media_view_playback_progress.cpp
|
||||
<(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.h
|
||||
<(src_loc)/media/view/media_view_overlay_widget.cpp
|
||||
|
|
Loading…
Add table
Reference in a new issue