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) {
|
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) {
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -1277,6 +1277,7 @@ void ListWidget::showContextMenu(
|
||||||
DocumentSaveClickHandler::Save(
|
DocumentSaveClickHandler::Save(
|
||||||
itemFullId,
|
itemFullId,
|
||||||
document,
|
document,
|
||||||
|
App::histItemById(itemFullId),
|
||||||
true);
|
true);
|
||||||
});
|
});
|
||||||
_contextMenu->addAction(
|
_contextMenu->addAction(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -47,5 +47,5 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Clip
|
} // namespace Player
|
||||||
} // namespace Media
|
} // namespace Media
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -212,5 +212,5 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Clip
|
} // namespace Player
|
||||||
} // namespace Media
|
} // namespace Media
|
||||||
|
|
|
@ -118,5 +118,5 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Clip
|
} // namespace Player
|
||||||
} // namespace Media
|
} // namespace Media
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -74,5 +74,5 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Clip
|
} // namespace Player
|
||||||
} // namespace Media
|
} // namespace Media
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue