Play streaming video in mediaview.

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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