2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-31 06:26:18 +00:00

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

@@ -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(),